rest-framework框架的基本組件

 

快速實例

Quickstarthtml

大體步驟java

複製代碼
(1)建立表,數據遷移
(2)建立表序列化類BookSerializer
   class BookSerializer(serializers.HyperlinkedModelSerializer):
        class Meta:
             model=Book
             fields="__all__"
             
(3)建立視圖類:
    class BookViewSet(viewsets.ModelViewSet):
          queryset = Book.objects.all()
          serializer_class = BookSerializer

(4) 設計url:
     router.register(r'books', views.BookViewSet)
複製代碼

序列化

建立一個序列化類

簡單使用

開發咱們的Web API的第一件事是爲咱們的Web API提供一種將代碼片斷實例序列化和反序列化爲諸如json之類的表示形式的方式。咱們能夠經過聲明與Django forms很是類似的序列化器(serializers)來實現。python

models部分:數據庫

複製代碼
from django.db import models

# Create your models here.


class Book(models.Model):
    title=models.CharField(max_length=32)
    price=models.IntegerField()
    pub_date=models.DateField()
    publish=models.ForeignKey("Publish")
    authors=models.ManyToManyField("Author")
    def __str__(self):
        return self.title

class Publish(models.Model):
    name=models.CharField(max_length=32)
    email=models.EmailField()
    def __str__(self):
        return self.name

class Author(models.Model):
    name=models.CharField(max_length=32)
    age=models.IntegerField()
    def __str__(self):
        return self.name
複製代碼

views部分:django

複製代碼
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from django.shortcuts import HttpResponse
from django.core import serializers


from rest_framework import serializers

class BookSerializers(serializers.Serializer):
    title=serializers.CharField(max_length=32)
    price=serializers.IntegerField()
    pub_date=serializers.DateField()
    publish=serializers.CharField(source="publish.name")
    #authors=serializers.CharField(source="authors.all")
    authors=serializers.SerializerMethodField()
    def get_authors(self,obj):
        temp=[]
        for author in obj.authors.all():
            temp.append(author.name)
        return temp


class BookViewSet(APIView):

    def get(self,request,*args,**kwargs):
        book_list=Book.objects.all()
        # 序列化方式1:
        # from django.forms.models import model_to_dict
        # import json
        # data=[]
        # for obj in book_list:
        #     data.append(model_to_dict(obj))
        # print(data)
        # return HttpResponse("ok")

        # 序列化方式2:
        # data=serializers.serialize("json",book_list)
        # return HttpResponse(data)

        # 序列化方式3:
        bs=BookSerializers(book_list,many=True)  # 當傳的是一個queryset時要加many=True,若是是一個單個對象則不用加
     return Response(bs.data)
複製代碼

咱們能夠看到當咱們拿到book_list後,有不少種方式來進行序列化,這裏咱們使用的是rest_framwork帶的serializers來進行的序列化,在返回時咱們也用到了rest_framwork的Responsejson

還有一點值得注意的時,咱們以前在寫CBV時,繼承的django.views中的View類,而這裏咱們是從rest_framwork.views中導入的APIView,咱們來看看這個類具體作了什麼api

首先,在url中咱們能夠看到瀏覽器

複製代碼
from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^books/$', views.BookView.as_view()),
]
複製代碼

這裏咱們仍是使用的as_view這個方法,那麼在APIView中這個方法是如何定義的呢session

複製代碼
@classmethod
    def as_view(cls, **initkwargs):
        """
        Store the original class on the view function.

        This allows us to discover information about the view when we do URL
        reverse lookups.  Used for breadcrumb generation.
        """
        if isinstance(getattr(cls, 'queryset', None), models.query.QuerySet):
            def force_evaluation():
                raise RuntimeError(
                    'Do not evaluate the `.queryset` attribute directly, '
                    'as the result will be cached and reused between requests. '
                    'Use `.all()` or call `.get_queryset()` instead.'
                )
            cls.queryset._fetch_all = force_evaluation

        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs

        # Note: session based authentication is explicitly CSRF validated,
        # all other authentication is CSRF exempt.
        return csrf_exempt(view)
複製代碼

能夠看到這裏實際上是經過super方法執行的它父類的as_view方法,而它的父類正式django的View類,因此這裏的view其實就是父類執行as_view方法得到的view函數,在url中獲得這個函數後,會自動去執行它,而執行它其實就是在執行dispatch方法app

複製代碼
def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
    self.request = request
    self.args = args
    self.kwargs = kwargs
    return self.dispatch(request, *args, **kwargs)
複製代碼

咱們來看看APIView的dispatch方法都作了什麼

複製代碼
def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?
    try:
        self.initial(request, *args, **kwargs)
        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                              self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        response = handler(request, *args, **kwargs)
    except Exception as exc:
        response = self.handle_exception(exc)
    self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response
複製代碼

這裏的dispatch首先從新定義了一下request,request = self.initialize_request(request, *args, **kwargs),這個initialize_request方法的內容以下

複製代碼
    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)

        return Request(
            request,
            parsers=self.get_parsers(),
            authenticators=self.get_authenticators(),
            negotiator=self.get_content_negotiator(),
            parser_context=parser_context
        )
複製代碼

能夠看到它其實就是返回了一個Request類的實例對象,那麼這個類又作了什麼呢

複製代碼
    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )

        self._request = request
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)
複製代碼

別的暫時先不看,咱們能夠看到它定義了一個self._request = request,因此咱們之後若是想使用wsgi封裝的request其實須要用request._request才行

在定義完新的request以後,dispatch又進行了一步初始化操做self.initial(request, *args, **kwargs),這個咱們後面再說,而後進行的就和View類同樣,判斷請求的方式是否在self.http_method_names中,若是在就利用反射的方式取到,而後執行,可是這樣要注意的是,這時後執行get,post等方法時傳進去的request已是從新定義事後的了

看完了源碼的大致流程,咱們再來看看序列化的類幹了什麼

複製代碼
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.views import APIView


class BookSerializers(serializers.Serializer):
    title = serializers.CharField(max_length=32)
    price = serializers.IntegerField()
    pub_date = serializers.DateField()
    publish = serializers.CharField(source="publish.id")
    # authors = serializers.CharField(source="authors.all")
    # 針對多對多
    authors = serializers.SerializerMethodField()
    def get_authors(self, obj):
        temp = []
        for author in obj.authors.all():
            temp.append(author.pk)
        return temp
複製代碼

首先定義這個類其實和咱們以前使用的form有點像,不過這裏要注意一對多的字段,有個source屬性,能夠控制咱們具體顯示的內容,默認是顯示這個對象,這裏咱們能夠定義爲對象的id等,而多對多的字段咱們能夠自定義一個方法

serializers.SerializerMethodField()是固定寫法,而下面的函數名必須是get_字段名,函數的參數obj就是每個book對象,這樣咱們經過這個類,在使用postman進行get請求時就能獲得以下的數據

複製代碼
[
    {
        "id": 1,
        "authors": [
            1,
            2
        ],
        "title": "python",
        "price": 123,
        "pub_date": "2018-04-08",
        "publish": 1
    },
    {
        "id": 2,
        "authors": [
            1
        ],
        "title": "go",
        "price": 222,
        "pub_date": "2018-04-08",
        "publish": 2
    },
    {
        "id": 3,
        "authors": [
            2
        ],
        "title": "java",
        "price": 222,
        "pub_date": "2018-04-08",
        "publish": 2
    }
]
複製代碼

上面的寫法跟普通的form相似,固然咱們還學過ModelForm,這裏也有相似的用法

複製代碼
class BookSerializers(serializers.ModelSerializer):
    class Meta:
        model = Book
        fields = "__all__"

    authors = serializers.SerializerMethodField()

    def get_authors(self, obj):
        temp = []
        for author in obj.authors.all():
            temp.append(author.pk)
        return temp
複製代碼

這裏若是不單獨定義多對多的字段和一對多的字段,那麼默認顯示的是字段的pk值

提交post請求

複製代碼
  def post(self,request,*args,**kwargs):
       
        bs=BookSerializers(data=request.data,many=False)
        if bs.is_valid():
            # print(bs.validated_data)
            bs.save()
            return Response(bs.data)
        else:
            return HttpResponse(bs.errors)
複製代碼

post請求是用來添加數據的,這裏的request通過了新的封裝,咱們能夠從request.data中拿到數據,而後就像使用form表單同樣使用serializers,實例化一個對象,經過is_valid方法驗證數據,若是沒問題直接save保存數據,有問題的話則返回錯誤信息,這裏要注意,當數據保存後咱們要將新添加的數據返回(Json格式)

各類狀況下須要返回的內容

複製代碼
所有信息
    users/ 
     
          ----查看全部數據
          get   users/    :返回全部的用戶的json數據        
          ----提交數據    
         post  users/    :返回添加數據的json數據        
具體對象
     users/2
          ----查看
          get  users/2    :返回具體查看的用戶的json數據
          ----刪除
          delete users/2  :返回空文檔
          
          ----編輯      
          put/patch   users/2:返回的編輯後的json數據包
複製代碼

單條數據的get、put和delete請求

這裏須要對單條數據進行操做,因此要有一個新的url和新的視圖

複製代碼
from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^books/$', views.BookView.as_view(), name="book_list"),
    url(r'^books/(\d+)/$', views.BookDetailView.as_view(), name="book_detail"),
]
複製代碼

新的url對應的新視圖

複製代碼
# 針對Book某一個數據操做 查看,編輯,刪除一本書
class BookDetailView(APIView):
    # 查看一本書
    def get(self, request, pk, *args, **kwargs):
        obj = Book.objects.filter(pk=pk).first()
        if obj:
            bs = BookSerializers(obj)
            return Response(bs.data)
        else:
            return Response()

    # 編輯一本書
    def put(self, request, pk, *args, **kwargs):
        obj = Book.objects.filter(pk=pk).first()
        bs = BookSerializers(data=request.data, instance=obj)
        if bs.is_valid():
            bs.save()
            return Response(bs.data)
        else:
            return Response(bs.errors)

    # 刪除一本書
    def delete(self, request, pk, *args, **kwargs):
        Book.objects.filter(pk=pk).delete()
        return Response()
複製代碼

查看時跟咱們以前的區別在於,以前是序列化的一個queryset,如今只是一個單個對象,而後將這個對象的序列化結果返回,這裏多加了一步判斷,若是這個對象不存在則返回空

編輯時,咱們首先經過主鍵值取到這個書對象,而後像使用form組件同樣,將更新的數據和這個對象傳給咱們的序列化類進行實例化,這裏注意參數instance和form組件同樣,而後經過is_valid驗證數據是否正確,若是正確則save並返回這條記錄,錯誤則返回錯誤信息

刪除時,直接過濾出咱們要刪除的進行刪除便可,返回空

超連接API:Hyperlinked

採用上面的內容,咱們訪問時,外鍵內容咱們能夠看到的是主鍵值,可是若是咱們想看到一個連接url呢,那麼就要採用下面的方法

複製代碼
class BookSerializers(serializers.ModelSerializer):
      publish= serializers.HyperlinkedIdentityField(
                     view_name='publish_detail',
                     lookup_field="publish_id",
                     lookup_url_kwarg="pk")
      class Meta:
          model=Book
          fields="__all__"
          #depth=1
複製代碼

這裏咱們從新定義了publish字段,view_name是url對應的別名,lookup_field則是url上對應的須要顯示的內容,lookup_url_kwarg是url上的分組名稱

urls部分:

urlpatterns = [
    url(r'^books/$', views.BookViewSet.as_view(),name="book_list"),
    url(r'^books/(?P<pk>\d+)$', views.BookDetailViewSet.as_view(),name="book_detail"),
    url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"),
    url(r'^publishers/(?P<pk>\d+)$', views.PublishDetailViewSet.as_view(),name="publish_detail"),
]

這樣,咱們訪問時就能看到publish的結果是一個url連接

視圖

使用混合(mixins)

使用咱們上面的方法,若是咱們要再定義一個publish表的操做,則須要再寫兩個視圖

複製代碼
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from django.shortcuts import HttpResponse
from django.core import serializers


from rest_framework import serializers


class BookSerializers(serializers.ModelSerializer):
      class Meta:
          model=Book
          fields="__all__"
          #depth=1


class PublshSerializers(serializers.ModelSerializer):

      class Meta:
          model=Publish
          fields="__all__"
          depth=1


class BookViewSet(APIView):

    def get(self,request,*args,**kwargs):
        book_list=Book.objects.all()
        bs=BookSerializers(book_list,many=True,context={'request': request})
        return Response(bs.data)


    def post(self,request,*args,**kwargs):
        print(request.data)
        bs=BookSerializers(data=request.data,many=False)
        if bs.is_valid():
            print(bs.validated_data)
            bs.save()
            return Response(bs.data)
        else:
            return HttpResponse(bs.errors)


class BookDetailViewSet(APIView):

    def get(self,request,pk):
        book_obj=Book.objects.filter(pk=pk).first()
        bs=BookSerializers(book_obj,context={'request': request})
        return Response(bs.data)

    def put(self,request,pk):
        book_obj=Book.objects.filter(pk=pk).first()
        bs=BookSerializers(book_obj,data=request.data,context={'request': request})
        if bs.is_valid():
            bs.save()
            return Response(bs.data)
        else:
            return HttpResponse(bs.errors)


class PublishViewSet(APIView):

    def get(self,request,*args,**kwargs):
        publish_list=Publish.objects.all()
        bs=PublshSerializers(publish_list,many=True,context={'request': request})
        return Response(bs.data)


    def post(self,request,*args,**kwargs):

        bs=PublshSerializers(data=request.data,many=False)
        if bs.is_valid():
            # print(bs.validated_data)
            bs.save()
            return Response(bs.data)
        else:
            return HttpResponse(bs.errors)


class PublishDetailViewSet(APIView):

    def get(self,request,pk):

        publish_obj=Publish.objects.filter(pk=pk).first()
        bs=PublshSerializers(publish_obj,context={'request': request})
        return Response(bs.data)

    def put(self,request,pk):
        publish_obj=Publish.objects.filter(pk=pk).first()
        bs=PublshSerializers(publish_obj,data=request.data,context={'request': request})
        if bs.is_valid():
            bs.save()
            return Response(bs.data)
        else:
            return HttpResponse(bs.errors)
複製代碼

能夠方法,代碼基本都是重複的,context={'request': request}是使用超連接API時須要加的內容

經過使用mixin類編寫視圖:

複製代碼
from rest_framework import mixins
from rest_framework import generics

class BookViewSet(mixins.ListModelMixin,
                  mixins.CreateModelMixin,
                  generics.GenericAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializers

    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 BookDetailViewSet(mixins.RetrieveModelMixin,
                    mixins.UpdateModelMixin,
                    mixins.DestroyModelMixin,
                    generics.GenericAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializers

    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)
複製代碼

這裏咱們用到了多繼承,當執行對應的方法時,其實會一步一步的從每個繼承的類中去找,這裏咱們以BookViewSet的get方法爲例,當執行它時其實執行的是self.list(request, *args, **kwargs),這個list方法咱們能夠在它的第一個繼承類mixins.ListModelMixin中找到

這個方法的內容其實和咱們本身寫的get方法是相似的,先拿到queryset,再利用序列化類進行實例化,最後返回序列化的結果

其它的方法也都跟這裏相似,執行一個方法時只要按照繼承順序一步一步的找就好了

經過這種方式,咱們就能夠不用本身寫那麼多代碼,而是直接使用繼承類的內容,可是若是要再寫一個publish表仍是要寫不少重複的內容

使用通用的基於類的視圖

經過使用mixin類,咱們使用更少的代碼重寫了這些視圖,但咱們還能夠再進一步。REST框架提供了一組已經混合好(mixed-in)的通用視圖,咱們可使用它來簡化咱們的views.py模塊

複製代碼
from rest_framework import mixins
from rest_framework import generics

class BookViewSet(generics.ListCreateAPIView):

    queryset = Book.objects.all()
    serializer_class = BookSerializers

class BookDetailViewSet(generics.RetrieveUpdateDestroyAPIView):
    queryset = Book.objects.all()
    serializer_class = BookSerializers

class PublishViewSet(generics.ListCreateAPIView):

    queryset = Publish.objects.all()
    serializer_class = PublshSerializers

class PublishDetailViewSet(generics.RetrieveUpdateDestroyAPIView):
    queryset = Publish.objects.all()
    serializer_class = PublshSerializers
複製代碼

generics.ListCreateAPIView,咱們能夠點擊進去看看這個類的內容

 

能夠看到它已經幫咱們把get和post方法都封裝好了,其它的類也作了同樣的事情,因此咱們在寫時,只須要繼承這些類,而後在咱們本身類中定義好queryset和serializer_class這兩個參數便可,這樣重複的代碼就不用再寫了

 再次優化

經過上面的方法咱們已經將代碼優化了不少,那麼還能不能再進行優化呢,其實還能夠將咱們定義的兩個類合併

from rest_framework.viewsets import  ModelViewSet

class BookViewSet(viewsets.ModelViewSet):
    queryset = Book.objects.all()
    serializer_class = BookSerializers

這裏咱們繼承了一個新的類,能夠看看這個類都有一些什麼內容

能夠看到其實這個類就是繼承了咱們上面用到的各類類,這麼用之後,爲了加以區分,在url中咱們須要多傳一點參數

複製代碼
views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
    url(r'^books/(?P<pk>\d+)$', views.BookViewSet.as_view({
                'get': 'retrieve',
                'put': 'update',
                'patch': 'partial_update',
                'delete': 'destroy'
            }),name="book_detail"),
複製代碼

認證與權限組件

認證組件

局部視圖認證

爲了作認證,咱們先要有用戶相關的表,因此咱們先添加表結構

複製代碼
from django.db import models


# Create your models here.


class Book(models.Model):
    title = models.CharField(max_length=32)
    price = models.IntegerField()
    pub_date = models.DateField()
    publish = models.ForeignKey("Publish")
    authors = models.ManyToManyField("Author")

    def __str__(self):
        return self.title


class Publish(models.Model):
    name = models.CharField(max_length=32)
    email = models.EmailField()

    def __str__(self):
        return self.name


class Author(models.Model):
    name = models.CharField(max_length=32)
    age = models.IntegerField()

    def __str__(self):
        return self.name


class User(models.Model):
    user = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    user_type = models.IntegerField(choices=((1, "普通用戶"), (2, "VIP"), (3, "SVIP")), default=1)


class UserToken(models.Model):
    user = models.OneToOneField("User")
    token = models.CharField(max_length=128)
複製代碼

這裏咱們添加了兩個表,一個用戶表還有一個用戶token表,token表中的token信息就是咱們用來認證的字符串

表建好後,咱們就要來建立login的邏輯了,這裏的login咱們只須要建立post請求的邏輯

複製代碼
from django.http import JsonResponse


def get_random_str(user):
    import hashlib, time
    ctime = str(time.time())

    md5 = hashlib.md5(bytes(user, encoding="utf8"))
    md5.update(bytes(ctime, encoding="utf8"))

    return md5.hexdigest()


class LoginView(APIView):

    def post(self, request, *args, **kwargs):
        user = request.data.get("user")
        pwd = request.data.get("pwd")
        token = request.data.get("token")
        user = User.objects.filter(user=user, pwd=pwd).first()
        res = {"state_code": 200, "msg": None}
        if user:
            random_str = get_random_str(user.user)
            UserToken.objects.update_or_create(user=user, defaults={"token": random_str})
            res["msg"] = "success"
            res["token"] = random_str
        else:
            res["msg"] = "用戶名或密碼錯誤"
            res["state_code"] = 110
        return JsonResponse(res)
複製代碼

當用戶登陸後咱們先要判斷用戶名和密碼是否正確,若是正確的話,咱們經過md5生成一串隨機字符串,而後將這個隨機字符串添加到usertoken表中,這裏要注意的是咱們用到了update_or_create,當user=user的數據存在時就更新,不然就添加

若是登陸不成功,則返回錯誤信息

用戶登陸完成後,咱們就開始作認證了,首先咱們須要建立一個認證類

複製代碼
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    def authenticate(self, request):
        token = request._request.GET.get("token")
        user_token_obj = UserToken.objects.filter(token=token).first()
        if user_token_obj:
            return user_token_obj.user, token
        else:
            raise exceptions.AuthenticationFailed("token驗證失敗")
複製代碼

類名隨便取,可是類中必須有一個authenticate方法,在這個方法中作咱們的邏輯判斷,這裏咱們就從url上拿token值到數據庫中搜索,若是有表示登陸成功,這是須要返回一個元組,元組的兩個值能夠根據須要返回,若是不成功則要拋出一個異常

類定義完後咱們就須要在咱們的視圖類中添加一個屬性

class BookViewSet(ModelViewSet):
    authentication_classes = [Authentication]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

這樣,當用戶訪問Book表相關功能時就會進行認證了

全局視圖認證組件

上面咱們介紹了局部認證,那麼若是想讓全局都進行認證呢,只須要在settings配置中增長

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]
}

字典中的內容爲認證類的路徑

源碼分析

上面咱們介紹了認證組件的用法,那麼爲何這麼用呢,這就要經過源碼來進行分析了,首先當用戶來訪問時,url匹配到了咱們先執行的是一個as_view方法,這裏咱們從BookViewSet類繼承的ModelViewSet類開始一步步往上找

首先咱們來看看這些類的繼承順序

而後再一步步找

ModelViewSet中沒有as_view

 

GenericViewSet中也沒有,generics.GenericAPIView中也沒有,最後在generics.GenericAPIView的父類APIView中發現as_view方法,這個方法在上面咱們已經提到了,他返回了一個view函數,當匹配到url後,會執行這個view函數並給它傳一個request

而執行這個view其實就是在self.dispatch方法,咱們仍是一步步找,最後仍是在APIView找到了,這個方法咱們上面說到了他從新封裝了request,而後又進行了初始化操做

如今咱們來看看初始化操做self.initial方法作了什麼

上面的操做咱們先無論,咱們發現這裏有一步關於認證的操做self.perform_authentication(request),這裏到底作了什麼呢

咱們發現這裏只有一個request.user,咱們猜測這是一個靜態方法,這時咱們就要去從新定義request的類中找這個方法了

能夠看到這裏其實就是執行了self._authenticate()方法

在這個方法中咱們看到在for循環self.authenticators,那麼這個self.authenticators是什麼呢,咱們注意到在Request類的初始化方法__init__中咱們對它進行了定義

 

那麼咱們在實例化的過程當中傳了什麼呢

這裏咱們能夠看到這裏調用了self.get_authenticators()的方法

這個方法其實就是放回了一個列表,這個列表解析式中咱們發現self.authentication_classes就是咱們在局部認證時加在咱們的視圖類中的內容,是一個列表中放着認證類

因此這裏返回的列表中放着的其實就是咱們的認證類對象

知道了這個內容後,咱們在上圖所示的方法中其實就是在循環認證類對象的列表,而後調用對象的authenticate方法,因此咱們在建立認證類時必需要有這個方法,而這個方法返回的是一個元組,在下面分別給self.user和self.auth賦值這個元組中的值

到這裏咱們就明白了爲何局部認證時要在視圖類中設置authentication_classes,而且認證類中必定要有authenticate方法了,那麼全局認證時又是爲何呢

咱們發現,若是咱們在視圖類中沒有定義authentication_classes屬性,那麼在找authentication_classes時,咱們會在APIView中找到

這裏的api_settings是一個實例化的對象

在這個類中都有什麼內容呢

首先咱們發現,若是咱們沒有傳值,這個defaults會有一個默認的內容DEFAULTS

這就是咱們不作任何配置時系統自動進行的認證內容,可是這是個字典的內容,咱們爲何能直接點出它的內容呢,咱們發如今這個類中有個__getter__方法

在這個方法中,咱們發現他經過val = self.defaults[attr]直接取值了,那麼上面還有一個val = self.user_settings[attr],當它取值出錯時纔會去默認的defaults中取值

那麼這個user_settings是什麼呢

能夠看到它就是咱們在settings中配置的REST_FRAMEWORK,因此當咱們要作全局認證時只要在settings中配置REST_FRAMEWORK而且裏面的鍵叫作DEFAULT_AUTHENTICATION_CLASSES便可

權限組件

局部視圖權限

複製代碼
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
    message="SVIP才能訪問!"
    def has_permission(self, request, view):
        if request.user.user_type==3:
            return True
        return False
複製代碼

權限組件的原理與上面的認證組件相似,這裏就再也不贅述,能夠參考源碼

能夠看到也是先定義一個權限類,類中必須有has_permission方法,這個方法返回Ture或者False

from app01.service.permissions import *

class BookViewSet(generics.ListCreateAPIView):
    permission_classes = [SVIPPermission,]
    queryset = Book.objects.all()
    serializer_class = BookSerializers

在視圖類中一樣是加一個參數permission_classes

全局視圖權限

在settings中配置

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}



hrottle(訪問頻率)組件

局部視圖throttle

和認證、權限組件同樣,首先咱們要定義一個頻率類

複製代碼
from rest_framework.throttling import BaseThrottle,SimpleRateThrottle

import time
VISITED_RECORD={}

class VisitThrottle(BaseThrottle):
    def __init__(self):
        self.history=None

    def allow_request(self,request,view):
        print("ident",self.get_ident(request))
        #visit_ip=request.META.get('REMOTE_ADDR')
        visit_ip=self.get_ident(request)
        print(visit_ip)
        ctime=time.time()

        #第一次訪問請求
        if visit_ip not in VISITED_RECORD:
            VISITED_RECORD[visit_ip]=[ctime]
            return True
        # self.history:當前請求IP的記錄列表
        self.history = VISITED_RECORD[visit_ip]
        print(self.history)

        # 第2,3次訪問請求
        if len(VISITED_RECORD[visit_ip])<3:
            VISITED_RECORD[visit_ip].insert(0,ctime)
            return True

        if ctime-VISITED_RECORD[visit_ip][-1]>60:
            VISITED_RECORD[visit_ip].pop()
            VISITED_RECORD[visit_ip].insert(0,ctime)
            print("ok")
            return True

        return False


    def wait(self):
        import time
        ctime = time.time()
        return 60 - (ctime - self.history[-1])
複製代碼

這個類中必須有一個allow_request方法和wait方法,期中allow_request中放咱們的邏輯,這裏咱們限制同一個ip每分鐘只能進行3次訪問,若是超過了就返回False,不超過在返回True

在邏輯中咱們先定義了一個空的字典,當一個ip來訪問時,咱們先判斷這個ip在不在字典中,若是不在,說明是第一次訪問,那麼在字典中添加一個鍵值對,鍵爲這個ip,值爲當前訪問時間戳的列表

當這個ip第2次或第3次來訪問時,因爲列表中的時間戳小於3個,因此直接將訪問的時間戳插入進去就好了,第4次訪問時,咱們就拿訪問的時間戳減去列表中的最後一個值,若是大於60,說明當前此次訪問與第一次訪問的間隔超過了1分鐘,沒有超過咱們的訪問頻率要求,因此咱們將新的時間戳加入列表,並將原來列表最後的值刪掉(保證列表中最多隻有3個值),若是結果小於60則表示訪問頻率太高,返回False限制訪問

wait函數的返回值就是咱們還有多少秒能再次訪問,定義好這個類後,在咱們視圖類中像使用

複製代碼
class BookViewSet(ModelViewSet):

    authentication_classes = [MyAuthentication,]
    permission_classes = [SVIPPermission]
    throttle_classes = [VisitThrottle]
    queryset = Book.objects.all()
    serializer_class = BookSerializers
複製代碼

這裏咱們用到的是throttle_classes參數,這樣book表就能享受到訪問頻率限制了

全局視圖throttle

REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
}

和上面兩個組件同樣,在settings中配置DEFAULT_THROTTLE_CLASSES

內置throttle類

BaseThrottle,在上面的局部使用中,咱們能夠看到咱們繼承了BaseThrottle類,這個類中有幾個方法

能夠看到裏面已經有了咱們用到的allow_request和wait方法,因此咱們能夠不寫wait,可是allow_request要求要被覆蓋,這裏還有一個get_ident方法,經過這個方法咱們能夠直接獲取訪問的ip地址

SimpleRateThrottle

class VisitThrottle(SimpleRateThrottle):

    scope="visit_rate"
    def get_cache_key(self, request, view):

        return self.get_ident(request)

繼承這個類後,咱們在其中定義一個scope屬性,經過這個屬性的值咱們能夠在settings中配置訪問頻率,還要定義一個get_cache_key方法,返回訪問的ip地址

複製代碼
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
    "DEFAULT_THROTTLE_RATES":{
        "visit_rate":"5/m",
    }
}
複製代碼

在settings中配置DEFAULT_THROTTLE_RATES

解析器

request類

django的request類和rest-framework的request類的源碼解析

django的request對象是經過from django.core.handlers.wsgi import WSGIRequest類實例化來的,經過它咱們能夠找到許多咱們在request中使用的方法

而rest-framework中對request又進行了封裝

在使用django的request對象時,若是數據是以application/x-www-form-urlencoded格式發來的,那麼咱們能夠在request.POST和request.GET中拿到數據,若是是別的形式的數據,那麼咱們能從request.body中拿到源數據

而在rest-framework的request中,不論數據什麼格式,咱們都是從request.data中拿數據,其實rest-framework的request中有好幾個解析器,分別解析不一樣格式的數據,若是咱們要定義一個解析器,能夠在視圖類中加上一個參數

複製代碼
from rest_framework.parsers import JSONParser,FormParser
class PublishViewSet(generics.ListCreateAPIView):
    parser_classes = [FormParser,JSONParser]
    queryset = Publish.objects.all()
    serializer_class = PublshSerializers
    def post(self, request, *args, **kwargs):
        print("request.data",request.data)
        return self.create(request, *args, **kwargs)
複製代碼

parser_classes參數就是來定義咱們使用的解析器的,咱們來看看rest-framework中都自帶了哪些解析器

能夠看到默認狀況下,有3個解析器,分別解析json、formdata和另外形式的數據,基本已經夠咱們使用了,通常狀況下咱們是不須要再本身定義的

咱們可使用的一共有這麼幾個解析器from rest_framework.parsers import JSONParser,FormParser,FileUploadParser,MultiPartParser

若是要在全局配置中定義使用的解析器,能夠在settings中配置

複製代碼
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
    "DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
    "DEFAULT_THROTTLE_RATES":{
        "visit_rate":"5/m",
    },
    "DEFAULT_PARSER_CLASSES":['rest_framework.parsers.FormParser',]
}
複製代碼

分頁

簡單分頁

複製代碼
from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination

class PNPagination(PageNumberPagination):
        page_size = 1                    # 每一頁顯示的數量
        page_query_param = 'page'        # url上的分頁參數
        page_size_query_param = "size"   # 在url上能夠經過這個參數來調整每一頁顯示的數量
        max_page_size = 5                # 每一頁上最多顯示的數量,不論上面設置的size定多少都不會超過這個

class BookViewSet(viewsets.ModelViewSet):

    queryset = Book.objects.all()
    serializer_class = BookSerializers
    def list(self,request,*args,**kwargs):

        book_list=Book.objects.all()
        pp=LimitOffsetPagination()
        pager_books=pp.paginate_queryset(queryset=book_list,request=request,view=self)
        print(pager_books)
        bs=BookSerializers(pager_books,many=True)

        #return Response(bs.data)
        return pp.get_paginated_response(bs.data)
複製代碼

這裏咱們使用了分頁類PageNumberPagination,咱們能夠直接使用它來實例化,也能夠寫一個類來繼承它,這樣能夠從新定義一些參數

咱們在視圖類中從新寫了list方法,先拿到book_list,而後經過分頁類實例化一個對象,而後經過這個對象的paginate_queryset方法得到新的數據類型,利用序列化類進行序列化

在return時再利用get_paginated_response方法返回更多的信息

能夠看到返回的數據上多出了上一頁和下一頁的連接

上面的方法咱們須要重寫咱們的list等方法才能實現分頁,其實咱們也能夠像用其它組件同樣,在視圖類中加一個參數來實現局部的分頁

複製代碼
class BookViewSet(ModelViewSet):
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
    authentication_classes = [MyAuthentication,]
    permission_classes = [SVIPPermission]
    throttle_classes = [VisitThrottle]
    queryset = Book.objects.all()
    serializer_class = BookSerializers
    pagination_class = MyPageNumberPagination
複製代碼

pagination_class參數就是咱們要定義的

偏移分頁

from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination,CursorPagination

class MyPageNumberPagination(LimitOffsetPagination):pass

繼承了這個類後咱們來看看有什麼參數

offset是開始時的索引,limit是顯示幾條,這樣就能決定在頁面上顯示多少信息

CursorPagination

class MyPageNumberPagination(CursorPagination):
    cursor_query_param="page"
    page_size=2
    ordering="id"

繼承這個類後,咱們在看到頁面上數據的上一頁和下一頁的信息時,會對頁碼進行加密

路由

在前面的代碼中路由都是咱們本身寫的,其實rest-framework已經幫咱們作了封裝,咱們只要引用就能夠自動生成路由了

複製代碼
from django.conf.urls import url,include
from django.contrib import admin



from api import views
from rest_framework import routers


router = routers.DefaultRouter()
router.register(r'books', views.BookViewSet)

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    # url(r"books\.(?P<format>\w+)$",views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
    # url(r"books/$",views.BookViewSet.as_view({"get":"list","post":"create"}),name="book_list"),
    # url(r"books/(?P<pk>\d+)/$",views.BookViewSet.as_view({"get":"retrieve","delete":"destroy","put":"update"}),name="book_detail"),
    # url(r"books/(?P<pk>\d+)\.(?P<format>\w+)$",views.BookViewSet.as_view({"get":"retrieve","delete":"destroy","put":"update"}),name="book_detail"),

    url(r'^', include(router.urls)),

    url(r"publishes/$",views.PublishView.as_view(),name="publish_list"),
    url(r"publishes/(?P<pk>\d+)/$",views.PublishDetailView.as_view(),name="publish_detail"),

    url("login/$",views.LoginView.as_view())
]
複製代碼

這裏導入routers,而後經過DefaultRouter實例化一個對象,再經過register註冊,最後在url中寫上url(r'^', include(router.urls)),就會自動幫咱們生成books相關的路由了,一共4條,就是上面註釋的內容

響應器

咱們能夠發現,當咱們用瀏覽器訪問時是會返回頁面的(必須在settings中註冊rest-framework,不然會報錯),而用postman等工具返回時只會返回json數據,這是爲何呢

瀏覽器訪問

postman訪問

其實這是由響應器決定的

複製代碼
from api.service.page import *
from rest_framework.renderers import BrowsableAPIRenderer,JSONRenderer

class BookViewSet(ModelViewSet):
    renderer_classes = [JSONRenderer,BrowsableAPIRenderer]
    authentication_classes = [MyAuthentication,]
    permission_classes = [SVIPPermission]
    #throttle_classes = [VisitThrottle]
    queryset = Book.objects.all()
    serializer_class = BookSerializers
    pagination_class = MyPageNumberPagination
    # parser_classes = [JSONParser]
複製代碼

這裏renderer_classes參數定義的就是響應器,其中JSONRenderer是用來響應postman等工具的,響應結果就是json數據,而BrowsableAPIRenderer響應的瀏覽器頁面,若是想要改變響應的形式只要修改這個參數便可

相關文章
相關標籤/搜索