django-rest-framework 官方文檔記錄

Tutorial 1: Serialization 序列化

安裝基本環境

pip install django
pip install djangorestframework
pip install pygments  # We'll be using this for the code highlighting

開始 創建測試環境

創建一個 django 工程

django-admin.py startproject tutorial

在工程中創建一個 app

python manage.py startapp snippets

tutorial/settings.py 文件中修改:聲明 app (自己創建的 app 和 rest_framework)

INSTALLED_APPS = (
    ...
    'rest_framework',
    'snippets.apps.SnippetsConfig',
)

創建 Model 模塊

修改 snippets/models.py 文件:

from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles

LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())


class Snippet(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    title = models.CharField(max_length=100, blank=True, default='')
    code = models.TextField()
    linenos = models.BooleanField(default=False)
    language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)

    class Meta:
        ordering = ('created',)

將創建的 Model 建立相應的 db 表,更新數據庫:

python manage.py makemigrations snippets
python manage.py migrate

Creating a Serializer class 創建序列化器類

snippets app 目錄下創建 serializers.py:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES


class SnippetSerializer(serializers.Serializer):
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=False, allow_blank=True, max_length=100)
    code = serializers.CharField(style={'base_template': 'textarea.html'})
    linenos = serializers.BooleanField(required=False)
    language = serializers.ChoiceField(choices=LANGUAGE_CHOICES, default='python')
    style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')

    def create(self, validated_data):
        """
        Create and return a new `Snippet` instance, given the validated data.
        """
        return Snippet.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """
        Update and return an existing `Snippet` instance, given the validated data.
        """
        instance.title = validated_data.get('title', instance.title)
        instance.code = validated_data.get('code', instance.code)
        instance.linenos = validated_data.get('linenos', instance.linenos)
        instance.language = validated_data.get('language', instance.language)
        instance.style = validated_data.get('style', instance.style)
        instance.save()
        return instance

Working with Serializers 使用 序列化器類

開啟 shell
python manage.py shell

創建 model 對象,保存到數據庫中:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser

snippet = Snippet(code='foo = "bar"\n')
snippet.save()

snippet = Snippet(code='print "hello, world"\n')
snippet.save()

序列化對象:將 model 對象轉化為 python 基本數據類型

serializer = SnippetSerializer(snippet)
serializer.data
# {'title': '', 'id': 3, 'code': 'create by new\n', 'style': 'friendly', 'language': 'python', 'linenos': False}

將 python 基本數據,渲染成 json

content = JSONRenderer().render(serializer.data)
content
# b'{"id":3,"title":"","code":"create by new\\n","linenos":false,"language":"python","style":"friendly"}'

反序列化 json-》object

from django.utils.six import BytesIO

stream = BytesIO(content)
data = JSONParser().parse(stream)

將一個反序列化出來的 object 存到數據庫中:

serializer = SnippetSerializer(data=data) # data 是 json->object 轉化出來的 object
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>

查詢一組數據:

serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('langua......

Using ModelSerializers 使用 模型序列化

將原來的 snippets/serializers.py 文件內容替換為:

from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES

class SnippetSerializer(serializers.ModelSerializer):
    class Meta:
        model = Snippet
        fields = ('id', 'title', 'code', 'linenos', 'language', 'style')

使用這個的一個好處是,可以直接看到 serializer 實例的 屬性 by printing its representation:

python manage.py shell


from snippets.serializers import SnippetSerializer
serializer = SnippetSerializer()
print(repr(serializer))
# SnippetSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    title = CharField(allow_blank=True, max_length=100, required=False)
#    code = CharField(style={'base_template': 'textarea.html'})
#    linenos = BooleanField(required=False)
#    language = ChoiceField(choices=[('Clipper', 'FoxPro'), ('Cucumber', 'Gherkin'), ('RobotFramework', 'RobotFramework'), ('abap', 'ABAP'), ('ada', 'Ada')...
#    style = ChoiceField(choices=[('autumn', 'autumn'), ('borland', 'borland'), ('bw', 'bw'), ('colorful', 'colorful')...

需要注意的是 ModelSerializer 類沒有做什么特殊的處理,他只是一個 Serializer 類的簡化:

  1. An automatically determined set of fields.
  2. Simple default implementations for the create() and update() methods.

Writing regular Django views using our Serializer 使用我們定義的 可序列化對象編寫正常的 django View

下面的例子不使用 rest-framework 的特性,按照 django 的方法來建立 view

創建一個 HttpResponse 的子類,用來將 data-》 json

snippets/views.py:

from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.renderers import JSONRenderer
from rest_framework.parsers import JSONParser
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer

class JSONResponse(HttpResponse):
    """
    An HttpResponse that renders its content into JSON.
    """
    def __init__(self, data, **kwargs):
        content = JSONRenderer().render(data)
        kwargs['content_type'] = 'application/json'
        super(JSONResponse, self).__init__(content, **kwargs)

api: 獲取已存在的 snippets ,或者創建新的 snippets

@csrf_exempt
def snippet_list(request):
    """
    List all code snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return JSONResponse(serializer.data)

    elif request.method == 'POST':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data, status=201)
        return JSONResponse(serializer.errors, status=400)

csrf_exempt 表示 POST 不需要 CSRF token,通常我們不會這樣做,rest-framework 中對于這個有更好的解決方案,這里只是一個簡單的例子。

api:獲取一個獨立的 snippets,用于 retrieve, update or delete

@csrf_exempt
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a code snippet.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return HttpResponse(status=404)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return JSONResponse(serializer.data)

    elif request.method == 'PUT':
        data = JSONParser().parse(request)
        serializer = SnippetSerializer(snippet, data=data)
        if serializer.is_valid():
            serializer.save()
            return JSONResponse(serializer.data)
        return JSONResponse(serializer.errors, status=400)

    elif request.method == 'DELETE':
        snippet.delete()
        return HttpResponse(status=204)

聲明上面的 view snippets/urls.py: ( wire these views up)

from django.conf.urls import url
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.snippet_list),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.snippet_detail),
]

聲明 url (wire up the root urlconf) tutorial/urls.py:

from django.conf.urls import url, include

urlpatterns = [
    url(r'^', include('snippets.urls')),
]

Tutorial 2: Requests and Responses 請求和回復

Request objects

rest-framework 采用 Request 對象,它繼承自 HttpRequest,提供了更加靈活的請求解析

其核心是屬性: request.data

request.POST  # Only handles form data.  Only works for 'POST' method.
request.data # Handles arbitrary data.  Works for 'POST', 'PUT' and 'PATCH' methods.

Response objects

return Response(data)  # Renders to content type as requested by the client.

Status codes

status module 提供了請求code, 比如:HTTP_400_BAD_REQUEST

這樣比直接使用 數字code 要更加易懂

Wrapping API views 包裝 API 視圖

兩種包裝方式:

  1. @api_view 修飾方法視圖
  2. APIView 包裝以類為視圖的api

包裝提供了一些新的特性,比如

  1. 確認視圖接收了 Request 實例, 修改 Response 。
  2. 返回 405 Method Not Allowed
  3. 捕獲 ParseError

Pulling it all together 使用上面介紹的 rest-framework 的特性編寫程序

修改 snippets/views.py: 1. 去除 JSONResponse 2. 修改 view

from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer


@api_view(['GET', 'POST'])
def snippet_list(request):
    """
    List all snippets, or create a new snippet.
    """
    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    elif request.method == 'POST':
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

@api_view(['GET', 'PUT', 'DELETE'])
def snippet_detail(request, pk):
    """
    Retrieve, update or delete a snippet instance.
    """
    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        return Response(status=status.HTTP_404_NOT_FOUND)

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    elif request.method == 'PUT':
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    elif request.method == 'DELETE':
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

優點:

  1. 代碼更加簡潔
  2. 使用 HTTP_400_BAD_REQUEST 而不是 400

Adding optional format suffixes to our URLs 給 url 添加后綴

修改 view:添加 format 參數

def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):

修改 urls.py

urlpatterns = format_suffix_patterns(urlpatterns)

發起請求回去不同的數據格式:

  1. 設置請求頭

     Accept:application/json  # Request JSON
     Accept:text/html         # Request HTML
    
  2. 使用 url 后綴

     http http://127.0.0.1:8000/snippets.json  # JSON suffix
     http http://127.0.0.1:8000/snippets.api   # Browsable API suffix
    

Tutorial 3: Class-based Views 類視圖

Rewriting our API using class-based views 重寫 view

views.py:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status


class SnippetList(APIView):
    """
    List all snippets, or create a new snippet.
    """
    def get(self, request, format=None):
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        return Response(serializer.data)

    def post(self, request, format=None):
        serializer = SnippetSerializer(data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


class SnippetDetail(APIView):
    """
    Retrieve, update or delete a snippet instance.
    """
    def get_object(self, pk):
        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        snippet = self.get_object(pk)
        serializer = SnippetSerializer(snippet, data=request.data)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

修改 urls.py :

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

urlpatterns = [
    url(r'^snippets/$', views.SnippetList.as_view()),
    url(r'^snippets/(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
]

urlpatterns = format_suffix_patterns(urlpatterns)

Using mixins

使用類視圖好處:

  1. 可復用
  2. create/retrieve/update/delete 操作在 rest-framework 中都有相應的封裝類

修改 views.py:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import mixins
from rest_framework import generics

class SnippetList(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.list(request, *args, **kwargs)

    def post(self, request, *args, **kwargs):
        return self.create(request, *args, **kwargs)

class SnippetDetail(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

    def get(self, request, *args, **kwargs):
        return self.retrieve(request, *args, **kwargs)

    def put(self, request, *args, **kwargs):
        return self.update(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
        return self.destroy(request, *args, **kwargs)

添加了基本類:GenericAPIView

mixins 方法擴展類: ListModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin,RetrieveModelMixin

Using generic class-based views 使用 generic 基本類

使用 mixin 類能夠簡化很多代碼,但是可以做到更加簡潔,使用包含了 mixin 功能的 generic 類

修改 views.py:

from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from rest_framework import generics


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer

Tutorial 4: Authentication & Permissions 認證和權限

目的:

  1. Code snippets are always associated with a creator.
  2. Only authenticated users may create snippets.
  3. Only the creator of a snippet may update or delete it.
  4. Unauthenticated requests should have full read-only access.

Adding information to our model 修改 model

修改 Snippet/models.py: 添加兩個屬性

owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()

添加 保存方法:

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

def save(self, *args, **kwargs):
    """
    Use the `pygments` library to create a highlighted HTML
    representation of the code snippet.
    """
    lexer = get_lexer_by_name(self.language)
    linenos = self.linenos and 'table' or False
    options = self.title and {'title': self.title} or {}
    formatter = HtmlFormatter(style=self.style, linenos=linenos,
                              full=True, **options)
    self.highlighted = highlight(self.code, lexer, formatter)
    super(Snippet, self).save(*args, **kwargs)

修改完數據后更新數據庫:這里更新是直接刪除舊的數據庫,創建新的數據庫

rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate

創建用戶:

python manage.py createsuperuser

Adding endpoints for our User models 添加用戶控制入口

serializers.py: 添加 User 序列化器

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

添加 User 對應的 View,修改 views.py:

from django.contrib.auth.models import User
from snippets.serializers import UserSerializer


class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer


class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

添加 url urls.py

url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),

Associating Snippets with Users 關聯 Snippets 和 users

修改 views.py 中的 SnippetList 類:重寫下面的方法,讓

def perform_create(self, serializer):
    serializer.save(owner=self.request.user)

Updating our serializer 更新 序列化器

上面的代碼將 Snippets 和創建它的 user 關聯在一起,下面修改 序列化器:添加 field

owner = serializers.ReadOnlyField(source='owner.username')

Adding required permissions to views 添加請求權限到 views

rest-framework 中有很多權限類用于限制哪些用戶可以請求views,下面我們只用 IsAuthenticatedOrReadOnly 來設置權限

views.py 中添加 SnippetList ,SnippetDetail 類屬性

from rest_framework import permissions

permission_classes = (permissions.IsAuthenticatedOrReadOnly,)

Adding login to the Browsable API 添加游覽器登入api

修改 urls.py 添加 登入視圖url:

urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

Object level permissions 對象級別的權限

下面設置權限: 創建 Snippets 的用戶才能修改這個 Snippets

創建 snippets/permissions.py: 添加自定義的權限類

from rest_framework import permissions


class IsOwnerOrReadOnly(permissions.BasePermission):
    """
    Custom permission to only allow owners of an object to edit it.
    """

    def has_object_permission(self, request, view, obj):
        # Read permissions are allowed to any request,
        # so we'll always allow GET, HEAD or OPTIONS requests.
        if request.method in permissions.SAFE_METHODS:
            return True

        # Write permissions are only allowed to the owner of the snippet.
        return obj.owner == request.user

將權限添加到 SnippetDetail 中 views.py:

from snippets.permissions import IsOwnerOrReadOnly

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

Now, if you open a browser again, you find that the 'DELETE' and 'PUT' actions only appear on a snippet instance endpoint if you're logged in as the same user that created the code snippet.

Authenticating with the API 認證api

目前位置我們還沒有設置任何 authentication classes ,和權限有關的類,所以目前這個工程的默認認證類是:SessionAuthentication,BasicAuthentication

可以在請求時設置 Basic Auth: 用戶名:密碼 訪問api

Tutorial 5: Relationships & Hyperlinked APIs 關系和超鏈接 APIs

Creating an endpoint for the root of our API 為接口創建一個入口

添加下面代碼:snippets/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse


@api_view(['GET'])
def api_root(request, format=None):
    return Response({
        'users': reverse('user-list', request=request, format=format),
        'snippets': reverse('snippet-list', request=request, format=format)
    })

上面代碼

  1. 使用了 rest-framework 提供的 reverse 方法來轉化url
  2. 其對應的 URL 模版,我們會在后面的代碼中看到 snippets/urls.py

Creating an endpoint for the highlighted snippets

添加 snippets/views.py:

from rest_framework import renderers
from rest_framework.response import Response

class SnippetHighlight(generics.GenericAPIView):
    queryset = Snippet.objects.all()
    renderer_classes = (renderers.StaticHTMLRenderer,)

    def get(self, request, *args, **kwargs):
        snippet = self.get_object()
        return Response(snippet.highlighted)

添加 snippets/urls.py:

url(r'^$', views.api_root),
url(r'^snippets/(?P<pk>[0-9]+)/highlight/$', views.SnippetHighlight.as_view()),

Hyperlinking our API 為 api 創建超級鏈接

處理 實體類 之間的關系有很多種方式:

  1. 使用主鍵
  2. Using hyperlinking between entities. 使用超級鏈接
  3. Using a unique identifying slug field on the related entity.
  4. Using the default string representation of the related entity.
  5. Nesting the related entity inside the parent representation.
  6. Some other custom representation.

下面使用超級鏈接的方式來處理。

修改 序列化器(serializers) 繼承 HyperlinkedModelSerializer

HyperlinkedModelSerializer 和 ModelSerializer 的區別:

  1. It does not include the id field by default. 默認不包含 id
  2. It includes a url field, using HyperlinkedIdentityField。 包含了 url 屬性
  3. Relationships use HyperlinkedRelatedField, instead of PrimaryKeyRelatedField。 關系維護使用 HyperlinkedRelatedField

修改 snippets/serializers.py:

class SnippetSerializer(serializers.HyperlinkedModelSerializer):
    owner = serializers.ReadOnlyField(source='owner.username')
    highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')

    class Meta:
        model = Snippet
        fields = ('url', 'id', 'highlight', 'owner',
                  'title', 'code', 'linenos', 'language', 'style')


class UserSerializer(serializers.HyperlinkedModelSerializer):
    snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail', read_only=True)

    class Meta:
        model = User
        fields = ('url', 'id', 'username', 'snippets')

Making sure our URL patterns are named 確保 url 中定義了參數 name

snippets/urls.py:

from django.conf.urls import url, include
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

# API endpoints
urlpatterns = format_suffix_patterns([
    url(r'^$', views.api_root),
    url(r'^snippets/$',
        views.SnippetList.as_view(),
        name='snippet-list'),
    url(r'^snippets/(?P<pk>[0-9]+)/$',
        views.SnippetDetail.as_view(),
        name='snippet-detail'),
    url(r'^snippets/(?P<pk>[0-9]+)/highlight/$',
        views.SnippetHighlight.as_view(),
        name='snippet-highlight'),
    url(r'^users/$',
        views.UserList.as_view(),
        name='user-list'),
    url(r'^users/(?P<pk>[0-9]+)/$',
        views.UserDetail.as_view(),
        name='user-detail')
])

# Login and logout views for the browsable API
urlpatterns += [
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
]

Adding pagination 添加分頁

tutorial/settings.py :

REST_FRAMEWORK = {
    'PAGE_SIZE': 10
}

Tutorial 6: ViewSets & Routers

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容