Django-REST-framework使用技巧(二)

1.請求和響應

從如今開始,咱們將真正開始接觸REST framework的核心。下面咱們介紹幾個基本的模塊。html

請求對象(request objects)

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

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

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

響應對象(response object)

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

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

狀態碼

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

包裝API視圖(Wrapping API views)

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

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

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

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

組合視圖

咱們不須要再view.py中JSONResponse類因此刪掉,而後咱們能夠重構咱們的視圖。app

# 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/,結果以下

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

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

可視化

因爲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。以下圖:

一旦建立了一些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,咱們會獲得一個錯誤:

咱們能夠經過添加以前的用戶名和密碼來發送成功請求:

咱們已經在咱們的webAPI上得到了一套精細的權限組合,下一節咱們將介紹如何經過爲高亮顯示 snippet 建立 HTML 端點來將全部內容聯結在一塊兒,並經過對系統內的關係使用超連接來提升 API 的凝聚力。

相關文章
相關標籤/搜索