• 1.請求和響應
  • 2.基於類的視圖
  • 3.認證和權限

1.請求和響應

從現在開始,我們將真正開始接觸REST framework的核心。下面我們介紹幾個基本的模塊。

請求對象(request objects)

REST framework引入了一個擴展常規HTTPRequestRequest對象,並提供了更靈活的請求解析。request對象的核心功能是request.data屬性,它與request.POST類似,但對於使用Web API更加有用。

request.POST #只處理表單數據。 只適用於’POST’方法

request.data #處理任意數據。使用與’POST’, 'PUT’和’PATCH’方法

響應對象(response object)

REST framework還引入了一個Response對象,該對象是一種獲取未渲染內容的TemplateResponse類型,並使用內容協商來確定正確內容類型返回給客户端。

reture  Response(data) # 渲染成客户端請求的內容類型

狀態碼

在你的視圖中使用數字HTTP狀態碼,並不是總利於閲讀,如果寫錯代碼,很容易被忽略。REST framework為每個狀態碼提供更明確的標識符,例如Status模塊中HTTP_400_BAD_REQUEST

包裝API視圖(Wrapping API views)

REST framework提供了兩種編寫API視圖的封裝。

  • 用於基於函數視圖的@api_view裝飾器
  • 用於基於類視圖的APIView類

這些視圖封裝提供了一些功能,例如確保你的視圖能夠接收Request實例,並將上下文添加到Response對象,使得內容協商可以正常的運行。

視圖封裝還內置了一些行為。例如在適當的時候返回405 Method Not Allowed響應,並處理訪問錯誤的輸入request.data時候出發任何ParaseError異常。

組合視圖

我們不需要再view.py中JSONResponse類所以刪掉,然後我們可以重構我們的視圖。

# quickstart/view.py 

from django.http import HttpResponse,JsonResponse
from django.views.decorators.csrf import csrf_exempt
from rest_framework.parsers import JSONParser
from quickstart.models import Snippet
from quickstart.serializers import SnippetSerializer
from rest_framework.decorators import api_view #新增導入
from rest_framework.response import Response #新增導入
from rest_framework.status import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_404_NOT_FOUND,HTTP_204_NO_CONTENT  #新增導入

# @csrf_exempt # 刪除
@api_view(['GET', 'POST']) #  新增
def snippet_list(request):
    # 列出所有代碼 snippet, 或者創建一個新的snippet

    if request.method == 'GET':
        snippets = Snippet.objects.all()
        serializer = SnippetSerializer(snippets, many=True)
        # return JsonResponse(serializer.data, safe=False) # 刪除
        return Response(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 Response(serializer.data, status=HTTP_201_CREATED) # 新增

    # return JsonResponse(serializer.errors, status=400) # 刪除
    return Response(serializer.errors, status=HTTP_400_BAD_REQUEST) # 新增

我們的實例視圖比前面的示例有所改進。它稍微簡潔一點,現在的代碼與我們使用 Forms API 時非常相似。我們還使用了指定的狀態碼,這使得響應更加明顯。

# @csrf_exempt # 刪除
@api_view(['GET', 'PUT', 'DELETE']) #新增
def snippet_detail(request, pk):
    # 獲取、更新或者刪除一個代碼 snippet

    try:
        snippet = Snippet.objects.get(pk=pk)
    except Snippet.DoesNotExist:
        # return HttpResponse(status=404) # 刪除
        return Response(status=HTTP_404_NOT_FOUND)  #新增

    if request.method == 'GET':
        serializer = SnippetSerializer(snippet)
        # return JsonResponse(serializer.data) # 刪除
        return Response(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 Response(serializer.data)  #新增
        # return JsonResponse(serializer.errors, status=400) # 刪除
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)  #新增

    elif request.method == 'DELETE':
        snippet.delete()
        # return HttpResponse(status=204) # 刪除
        return Response(status=HTTP_204_NO_CONTENT)  #新增

這對我們來説應該都是非常熟悉的 - 它和正常 Django 視圖並沒有什麼不同。

注意,我們不再顯式地將請求或響應綁定到給定的內容類型。request.data 可以處理傳入的 json 請求,但它也可以處理其他格式。同樣,我們返回帶有數據的響應對象,但允許 REST framework 將響應渲染成正確的內容類型。

為我們的網址添加可選的格式後綴

為了利用我們的響應不再被硬鏈接到單個內容類型的事實。讓我們將格式後綴的之處添加我們的API端點。使用格式後綴,歐文提供了明確的只想給定的格式URL,這意味着我們的API可以處理一些URLs,類似這樣的格式:http://example.con/api/items/4.json.
像下面這樣在這兩個視圖中添加一個format關鍵字參數。

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

現在更新quickstart/urls.py 文件,在現在的urls基礎上追加一組format_suffix_patterns

# quickstart/urls.py

from django.conf.urls import url
from .views import snippet_list, snippet_detail
from rest_framework.urlpatterns import format_suffix_patterns #新增

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

urlpatterns = format_suffix_patterns(urlpatterns) #新增

我們不一定需要添加額外的url模式。但他給我們一個簡單、清晰的方式來引用特定的格式。

測試API

啓動服務 python manage.py runserver

在瀏覽器中輸入 http://127.0.0.1:8000/quickstart/,結果如下

rest_framework_simplejwt 自定義了用户表_#Python

在瀏覽器輸入http://127.0.0.1:8000/quickstart.api,結果如下

rest_framework_simplejwt 自定義了用户表_#RESTframework_02

在瀏覽器輸入http://127.0.0.1:8000/quickstart.json,結果如下

rest_framework_simplejwt 自定義了用户表_#REST_03

可視化

由於API是基於客户端發起請求的選擇來響應內容的格式,因此當接收到來之瀏覽器的請求是,會默認以HTML的格式來描述數據,這允許API返回網頁完全可瀏覽的HTML。
有關可瀏覽的API功能以及如何對其進行定製更多的信息,可參考可瀏覽的API主題。

2. 基於類的視圖

我們也可以使用基於類的視圖來編寫API視圖,而不是基於函數的視圖。正如我們看到的,這個模式足夠強大,可以讓我們重用通用功能,並幫助我們保持代碼DRY(Don’t repeat yourself)

使用基於類的視圖我們重寫我們的API

我們首先將基於類的視圖重寫根視圖。所有者寫都涉及對view.py的修改。

# quickstart/view.py

class SnippetList(APIView):
    # 列出所有的代碼snippet,或者創建一個新的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=HTTP_201_CREATED)
        return Response(serializer.errors, status=HTTP_400_BAD_REQUEST)

這個看起來和以前的非常相似,但是我們在不同的HTTP方法之間有更好的分離,還需要更新view.py中的示例視圖。

# quickstart/view.py

class SnippetDetail(APIView):
    # 獲取、更新或者刪除一個代碼 snippet

    def get_object(self, pk):

        try:
            return Snippet.objects.get(pk=pk)
        except Snippet.DoesNotExist:
            raise HTTP_404_NOT_FOUND

    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=HTTP_400_BAD_REQUEST)
    
    def delete(self, request, pk, format=None):
        snippet = self.get_object(pk)
        snippet.delete()
        return Response(status=HTTP_204_NO_CONTENT)

看起來,它仍然非常類似基於函數的視圖

使用基於列的視圖,我們現在還需要重構quickstart/urls.py文件內容:

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail

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

urlpatterns = format_suffix_patterns(urlpatterns)

重啓服務,如果你沒有搬錯磚,一切應該和以前一樣的。

##使用混合(mixins)

使用基於類的視圖的優勢之一,就是我們可以很容易撰寫可重複的行為。
到目前為止,我們使用創建、獲取、更新、刪除操作和我們創建的任何基於模型的API視圖非常相似。這些常見的行為是在REST framework的mixin類中實現的。

下面我們具體實踐如何通過mixin類編寫視圖。在quickstart/view.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)

我們看一下這裏都是什麼意思:我們使用GenericAPIView構建視圖,並且使用了ListModelMixinCreateModelMixin.
基類提供核心功能,而mixin類提供.list().create()操作.讓後我們可以明確的吧getpost綁定到適當的操作.

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來提供.retrieve().update().destroy()操作。

從新運行服務,你會發現沒有任何變化的。如果有,那就再運行一次。

使用通用的基於類的視圖

我們使用mixin類,使用了比以前更少的代碼重寫了視圖,但是我們可以更進一步。REST framework已經提供了一套已經混合的通用的視圖。我們可以更加簡化view.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

刷新瀏覽器,你會發現沒任何變化,如果有,老規矩:你搬錯磚了

3.認證和權限

目前,我們的api對誰都是開放的,誰都可以添加或者刪除代碼,沒有任何隱私和限制,我們需要更高行為來確保:

  • snippet代碼始終與創建者關聯
  • 只有經過身份驗證的用户才可以創建snippet
  • 只有snippet的創建這個才可以更新或者刪除它
  • 未經身份驗證的請求應具有隻讀的訪問權限

將信息添加到我們的模型裏面

我們對quickstart中的模塊要再修改一下,首先要添加幾個字段。其中一個字段將用於表示創建snippet代碼關聯的用户。
另外一個字段將用於存儲高亮顯示的,HTML內容表示的代碼。
添加字段到quickstart/model.py文件中的Snippet模型中:

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


# 提取出'pyment' 支持的所有語言的語法分析程序
LEXERS = [item for item in get_all_lexers() if item[1]]

# 提取除了'pyments' 支持的所有語言列表
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])

# 提取出'pyment' 支持的所有格式化風格列表
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=120)
    style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=120)
    owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) # 新增
    highlighted = models.TextField() #新增

    class Meta:
        ordering = ('-created',)

我們還需要確保在保存模型時候,使用pygments代碼的高亮庫來填充highlighted字段

我們需要導入額外模塊,

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

然後再類中添加一個方法.save()

def save(self, *args, **kwargs):
        # 使用 pygment 庫創建一個高亮顯示html表示的Snippet代碼

        lexer = get_lexer_by_name(self.language)
        linenos = 'table' if self.linenos else False
        options = {'title': self.title} if self.title else {}
        formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options)
        self.highlighted = highlight(self.code, lexer, formatter)
        super(Snippet, self).save(*args, **kwargs)

做完這些工作,同創做法是會場街一個數據庫遷移來實現這一點,但是現在讓我們刪掉數據庫,重新開始。

(env) AdministratordeiMac:tutorial administrator$ ls
db.sqlite3	env		manage.py	quickstart  tutorial
(env) AdministratordeiMac:tutorial administrator$ rm -f db.sqlite3
(env) AdministratordeiMac:tutorial administrator$ rm -r quickstart/migrations

(env) AdministratordeiMac:tutorial administrator$ python manage.py makemigrations quickstart
(env) AdministratordeiMac:tutorial administrator$ python manage.py migrate quickstart

你可以創建幾個不同的用户,用於測試不同的API,最快捷的方式就是createsuperuser命令。

pyhton manage.py createsuperuser #然後按照提示填寫用户名和密碼就可以了

為我們的用户模型添加端點

我們現在有一些用户可以使用,我們最好將這些用户添加到我們的API中。創建一個新的序列化器,在serializer.py我呢間中添加:

# quickstart/serializer.py

from rest_framework import serializers
from .models import Snippet
from django.contrib.auth.models import User


class SnippetSerializer(serializers.ModelSerializer):

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


#新增
class UserSerializer(serializers.ModelSerializer): 

    snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())

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

由於snippets在用户模型上是反向關係,當使用ModelSerializer類時候,它將不會被默認包含,我們需要為他添加一個顯式字段。

我們還會在view.py中添加幾個視圖。我們想要使用用户,表示只讀視圖,所以我們將使用ListAPIViewRetrieveAPIView通用的基於類的視圖。

#quictstart/view.py

from rest_framework import generics
from .models import Snippet
from .serializers import SnippetSerializer, UserSerializer #新增導入
from django.contrib.auth.models import User


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


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

# 新增
class UserList(generics.ListAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    
    
class UserDetail(generics.RetrieveAPIView):
    queryset = User.objects.all()
    serializer_class = UserSerializer

最後我們需要通過URL conf中引用這些視圖來將這些視圖添加到API中,修改如下:

from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail, UserList, UserDetail #新增導入

urlpatterns = [
    url('^quickstart/$', SnippetList.as_view()),
    url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()),
    url('^users/$',UserList.as_view()) # 新增
    url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()), # 新增
]

urlpatterns = format_suffix_patterns(urlpatterns)

將Snippets和用户關聯

現在,如果我們創建了一個Snippet代碼,那麼無法將創建Snippet的用户與Snippet實例關聯起來。用户不是作為序列化表示的一部分發送的,而是作為請求的屬性傳入。
我們處理方式是,在我們的Snippet視圖上覆蓋,.perform_create()方法,這中管理方式,允許我們修改實例保存,並處理傳入請求,或者請求URL中隱含的任何信息。
SnippetList視圖類中添加一下方法:

class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    
    # 新增
    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)

現在我們的序列化器create()方法將被傳遞一個owner字段以及來自請求的驗證數據。

更新序列化器

現在Snippet與創建他們的用户相關聯了,讓我們更新我們的SnippetSerializer以體現這一點。將下面內容添加到serializers.py文件中:

# quickstart/serializers.py

class SnippetSerializer(serializers.ModelSerializer):

    owner = serializers.ReadOnlyField(source='owner.username') # 新增

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

注意:確保將owner添加到了內部的Meta類的字段列表中。

這個字段中,source參數控制哪個屬性用於填充字段,並且可以指向序列化實例的任何屬性。它也可以採用如上所示的點符號(.),在這種情況下,他將與Django模板語言相似的方式遍歷給定的屬性。

我們添加的字段是無類型的ReadOnlyField類,與其他類型的字段相反,如CharField,BooleanField等無類型的,但ReadOnlyField始終是隻讀的,只能用於序列化表示,但不能用於在反序列化實時更新模型實例。我們這裏可以用CharField(read_only=True).

添加視圖所需的權限

現在,Snippet和用户相關聯,我們希望確保只要經過身份認證的用户才能創建、更新、和刪除snippets。
REST framework包含許多權限類,我們可以用來限制訪問權限。在這種情況下,我們需要的是IsAuthenticatedOrReadOnly類,它將確保身份驗證的請求獲得讀寫訪問權限。未經身份驗證的請求獲得只讀訪問權限。

在quickstart/view.py文件中,添加如下

# quickstart/view.py 

from rest_framework import permissions #新增


class SnippetList(generics.ListCreateAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增

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


class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,) #新增

增加查看API權限,添加登錄

如果你現在打開瀏覽器,並導航到瀏覽器的到API,那麼你將發現無法再創建新的snippet。因為需要增加權限。
我們可以通過編輯url.py文件中的URLconf來添加可瀏覽API的登錄視圖。
在文件頂部,我們導入:

from django.conf.urls import include

並在文件末尾添加一個模式以包括可瀏覽的API的登錄或者註銷視圖。

urlpatterns = [
    url('^quickstart/$', SnippetList.as_view()),
    url('^quickstart/(?P<pk>[0-9]+)/$', SnippetDetail.as_view()),
    url('^users/$',UserList.as_view()),
    url('users/(?P<pk>[0-9]+)/$', UserDetail.as_view()),
    url('^api-auth/', include('rest_framework.urls')), # 新增
]

模式api-auth/部分實際上可以是你想使用的任何URL。

現在再次打開瀏覽器刷新頁面,則會在右上角看到一個登錄鏈接。如果你要用前面的創建用户登錄,就可以再次創建snippets。如下圖:

rest_framework_simplejwt 自定義了用户表_#DjangoRESTFrameWork_04


一旦創建了一些snippets後,導航到/users/端點,並注意到每個用户的snippets字段中包含與每個用户相關聯的snippet id列表。

對象級權限

實際上我們希望所有人都可以看到snippets,但是隻要創建snippet的用户才權限CRUD。
為此,我們需要創建一個自定義的權限。
在quickstart應用中,創建一個新的文件permission.py.

# quickstrat/permissions.py

from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    # 自定義權限只允許隊形的所有者去編輯它

    def has_object_permission(self, request, view, obj):
        # 讀取權限被允許用於任何請求
        # 所以我們始終允許GET, HEAD 或者OPTIONS請求。

        if request.method in permissions.SAFE_METHODS:
            return True

        # 寫入權限只允許給snippet的所有者
        return obj.owner == request.user

現在我們可以通過編輯SnippetDetail視圖中的permission_classes屬性將自定義權限添加到我們snippet實例中:

#  quickstart/view.py

from .permissions import IsOwnerOrReadOnly # 新增

class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
    queryset = Snippet.objects.all()
    serializer_class = SnippetSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly) # 新增

現在再次打開瀏覽器,你會發現"DELETE"和’PUT’操作只會出現在以創建者身份登錄的snippet實例端點上。

使用API進行身份驗證

因為現在我們在API上有一組權限,如果我們想要編輯任何snippet,我們需要先驗證我們的請求。我們還沒設置任何認證類,因此當前應用默認的SessionAuthenticationBasicAuthentication.

但我們通過web瀏覽器與API進行交互的時候,我們可以登錄,然後瀏覽器會話將為請求提供所需的身份驗證。

如果我們以百年城的方式與API進行交互,那麼我們需要在每個請求上明確提供身份驗證憑據。

如果我們嘗試建立一個沒身份驗證的snippet,我們會得到一個錯誤:

rest_framework_simplejwt 自定義了用户表_#RESTframework_05

我們可以通過添加之前的用户名和密碼來發送成功請求:

rest_framework_simplejwt 自定義了用户表_#Python_06

我們已經在我們的webAPI上獲得了一套精細的權限組合,下一節我們將介紹如何通過為高亮顯示 snippet 創建 HTML 端點來將所有內容聯結在一起,並通過對系統內的關係使用超鏈接來提高 API 的凝聚力。