DjangoRestFramework


一.restful規范

在之前的代碼習慣中,通常使用一個url對應一個視圖函數,現在有了restful規范,就要遵循。簡單來說,就是多個url對應一個視圖,視圖中封裝了get,post,put,patch,delete等主要方法。相對於FBV來說更加簡單,使用了CBV模式。

1.是一套規則,用於程序之間進行數據交換的約定。 他規定了一些協議,對我們感受最直接的的是,以前寫增刪改查需要寫4個接口,restful規范的就是1 個接口,根據method的不同做不同的操作,比如:get/post/delete/put/patch/delete. 
2.除此之外,resetful規范還規定了: - 數據傳輸通過json 擴展:前后端分離、app開發、程序之間(與編程語言無關)
3.面向資源編程,視網絡上一切為資源,因此URL中一般使用名詞
4.建議用https代替http
5.URL中體現api
  https://www.cnblogs.com/api/xxx.html
6.URL中體現版本
  https://www.cnblogs.com/api/v1/userinfo/
7.如果有條件的話,在URL后邊進行過濾
  https://www.cnblogs.com/api/v1/userinfo/?page=1&category=2
8.返回給用戶狀態碼
- 200,成功 - 300,301永久 /302臨時 - 400,403拒絕 /404找不到 - 500,服務端代碼錯誤
9.操作異常時,要返回錯誤信息
{ error: "Invalid API key" }

二.drf

簡單認識

什么是drf

drf是一個基於django開發的組件,本質是一個django的app。
drf可以幫我們快速開發出一個遵循restful規范的程序。

drf提供哪些功能

-免除csrf認證 dispatch中的as_view()函數,return csrf_exempt(view)
#路由組件routers 進行路由分發
#視圖組件ModelViewSet  幫助開發者提供了一些類,並在類中提供了多個方法
#版本  版本控制用來在不同的客戶端使用不同的行為
#認證組件 寫一個類並注冊到認證類(authentication_classes),在類的的authticate方法中編寫認證邏
#權限組件 寫一個類並注冊到權限類(permission_classes),在類的的has_permission方法中編寫認證邏輯。 
#頻率限制 寫一個類並注冊到頻率類(throttle_classes),在類的的allow_request/wait 方法中編寫認證邏輯
#序列化組件:serializers  對queryset序列化以及對請求數據格式校驗
#分頁  對獲取到的數據進行分頁處理, pagination_class
#解析器  選擇對數據解析的類,在解析器類中注冊(parser_classes)
#渲染器 定義數據如何渲染到到頁面上,在渲染器類中注冊(renderer_classes)

使用

app注冊

服務端會根據訪問者使用工具的不同來區分,如果不添加rest_framework,使用postman等工具訪問是沒有問題的,但是使用瀏覽器訪問會報錯,因為會默認找到rest_framework下的靜態html文件進行渲染,此時找不到,因此報錯。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework'
]
View Code

路由

from django.conf.urls import url
from django.contrib import admin
from api import views

urlpatterns = [
    url(r'^drf/info/', views.DrfInfoView.as_view()),
]
View Code

視圖

from rest_framework.views import APIView
from rest_framework.response import Response

class DrfInfoView(APIView):

    def get(self,request,*args,**kwargs):
        data = {"":""}
        return Response(data)
View Code

自定義序列化

路由

from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
    url(r'^drf/category/$', views.DrfCategoryView.as_view()),  #get/post通過此url
    url(r'^drf/category/(?P<pk>\d+)/$', views.DrfCategoryView.as_view()), #put/patch/delete通過此url
]
View Code

視圖

from api import models
from django.forms.models import model_to_dict   #序列化,將model轉為dict類型
class DrfCategoryView(APIView):
    def get(self,request,*args,**kwargs):
        """獲取所有文章分類/單個文章分類"""
        pk = kwargs.get('pk')
        if not pk:
            queryset = models.Category.objects.all().values('id','name')
            data_list = list(queryset)
            return Response(data_list)
        else:
            category_object = models.Category.objects.filter(id=pk).first()
            data = model_to_dict(category_object)
            return Response(data)
     def post(self,request,*args,**kwargs):
        """增加一條分類信息"""
        models.Category.objects.create(**request.data)
        return Response('成功')
View Code

三.drf序列化

https://www.cnblogs.com/wuzhengzheng/p/10411785.html

序列化器作用

1.進行數據的校驗
2.對數據對象進行轉換
3.減少代碼量,提高編碼效率

序列化

將模型轉為json稱為序列化(對象->字典->json)

-序列化時instance=queryset,反序列化data參數寫為data=request.data
-進行序列化時,會將fields中所有字段進行序列化,反序列化時,也需要提交fields中的所有字段,如果不想提交某些字段,使用read_only參數。
-在添加數據(post)請求時,可以新建一個serializer,可能有一些不需要的字段。
source:可以指定字段(name publish.name),可以指定方法。
SerializerMethodField搭配方法使用(get_字段名字)
read_only:反序列化時,不傳
write_only:序列化時,不顯示

from django.conf.urls import url
from django.contrib import admin
from api import views
urlpatterns = [
    url(r'^new/category/$', views.NewCategoryView.as_view()),
    url(r'^new/category/(?P<pk>\d+)/$', views.NewCategoryView.as_view()),
]
路由
# ModelSerializer跟表模型綁定序列化
from app import models

class BookSerializer(serializers.ModelSerializer):
    class Meta:
        # 指定表模型
        model = models.Book
        # 序列化所有的字段
        fields = '__all__'
        # 只想序列化title和id兩個字段
        # fields = ['title','id']
        # exclude 和 fields不要連用
        # excude = ['title]
        # depth深度,表示鏈表的深度
        #不建議使用:下幾層要取得參數不能控制,官方建議不要超過10,個人建議不超過3
        # depth = 1

    # publish = serializers.CharField(source='publish.name')
    # authors = serializers.SerializerMethodField()
    # def get_authors(self, obj):
    #     author_list = obj.authors.all()
    #     author_ser = AuthorSer(instance=author_list, many=True)
    #     return author_ser.data

    #為書名增加自定義需求
    title = serializers.CharField(max_length=6,min_length=3,error_messages={'max_length':'太長了'})

    #也有局部鈎子函數
    def validate_title(self,value):
        from rest_framework import exceptions
        print(value)
        if value.startswith('tmd'):
            raise exceptions.ValidationError('不能以tmd開頭')
        return value
序列化 

要跨表查看其他表中的字段,或者顯示models.CharField(choice=((1,"發布"),(2,"刪除")),三個方式

class ArticleSerializer(serializers.ModelSerializer):
  #方式一:設置跨表深度
    depth=1
    #方式二:指定source
    category = serializers.CharField(source="category.name")
    status = serializers.CharField(source="get_status_display")
    #方式三,使用鈎子,和下面get_tags_txt結合使用
    tags_txt = serializers.SerializerMethodField(read_only=True) 
    class Meta:
        model = models.Article
        fields = ["id","title","summary","content","category","status","tags_txt"]
    def get_tags_txt(self,obj): #obj為表對象
        tag_list = []
        for i in obj.tags.all():
            tag_list.append({"id":i.id,"name":i.name})
        return tag_list
三種方式

#查詢

class ArticleView(APIView):
    def get(self,request,*args,**kwargs):
        pk = kwargs.get("pk")
        if not pk: 
            queryset = models.Article.objects.all()
            ser = ArticleSerializer(instance=queryset,many=True) #查詢多個,many=True
            return Response(ser.data)
        else:
            queryset = models.Article.objects.filter(id=pk).first() 
            ser = ArticleSerializer(instance=queryset,many=False) #查詢一個,many=False
            return Response(ser.data)
查詢

#新增

    def post(self,request,*args,**kwargs):
        ser = ArticleSerializer(data=request.data)
        if ser.is_valid():
            ser.save()
            return Response("添加成功!")
        return Response(ser.errors)
添加
def post(self,request,*args,**kwargs):
        ser = ArticleSerializer(data=request.data)
        ser_detail = ArticleDetailSerializer(data=request.data)
        if ser.is_valid() and ser_detail.is_valid():
            #前端提交時沒有author_id字段,在save時才添加,此處針對連表添加
            article_object = ser.save(author_id=1)  
            ser_detail.save(article=article_object)
            return Response('添加成功')
        return Response('錯誤')
針對連表添加

#修改

  def put(self,request,*args,**kwargs):
        pk = kwargs.get("pk")
        article_object = models.Article.objects.filter(id=pk).first()
        ser = ArticleSerializer(instance=article_object,data=request.data)  #局部更新添加partial=True,所有要反序列化的字段都不是必填的
        if ser.is_valid():
            ser.save()
            return Response(ser.data)
        return Response(ser.errors)
修改

反序列化

將json轉為模型稱為反序列化(json->字典->對象)

在自己定義的類中重寫create方法

class BookSerializer(serializers.Serializer):
    ... ...
    def create(self, validated_data):
        res = models.Book.objects.create(**validated_data)
        return res

對應的view.py

class Books(APIView):    
    # 使用繼承了Serializers序列化類的對象,反序列化
    def post(self,request):
        response_dic = {'code': 100, 'msg': '添加成功!'}
        # 實例化產生一個序列化類的對象,data是要反序列化的字典
        booker = BookSerializer(data=request.data)
        if booker.is_valid():
            # 清洗通過的數據,通過create方法進行保存
            res = booker.create(booker.validated_data)
        return Response(response_dic)

四.分頁

drf默認集成了幾個分頁類

from rest_framework.pagination import BasePagination,PageNumberPagination,LimitOffsetPagination,CursorPagination

自己寫類繼承這些,只需要配置某些參數

class GoodsPagination(PageNumberPagination):
    """
    分頁
    """
    page_size = 10
    page_size_query_param = 'page_size'
    page_query_param = 'page'
    max_page_size = 100

1.PageNumberPagination

url訪問

http:127.0.0.1:8000/?page=1

配置settings.py,在配置drf組件都要寫在REST_FRAMEWORK字典中

REST_FRAMEWORK = { "PAGE_SIZE":2 }

views中視圖函數

def get(self,request,*args,**kwargs):
        queryset = models.Article.objects.all()

        # 方式一:僅數據
        """
        page_object = PageNumberPagination()
        result = page_object.paginate_queryset(queryset,request,self)
        ser = PageArticleSerializer(instance=result,many=True)
        return Response(ser.data)
        """
        # 方式二:數據 + 分頁信息
        """
        page_object = PageNumberPagination()
        result = page_object.paginate_queryset(queryset, request, self)
        ser = PageArticleSerializer(instance=result, many=True)
        return page_object.get_paginated_response(ser.data)
        """
        # 方式三:數據 + 部分分頁信息
        """
        page_object = PageNumberPagination()
        result = page_object.paginate_queryset(queryset, request, self)
        ser = PageArticleSerializer(instance=result, many=True)
        return Response({'count':page_object.page.paginator.count,'result':ser.data})
        """
return Response(ser.data)
View Code
1.settings中配置(全局配置)
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",  #默認使用PagenumberPagination
"PAGE_SIZE":2,    #每頁顯示數量
}
2.views.py中(局部配置)如果全局配置了此處不用
from rest_framework.pagination import PageNumberPagination
class ArticleView(ListAPIView,CreateAPIView):
    queryset = models.Article.objects.all()
    serializer_class = ArticleSerials
    pagination_class = PageNumberPagination 

繼承自ListAPIView
ListAPIView

2.LimitOffffsetPagination

url訪問

http://127.0.0.1:8000/?limit=4&offset=0

views視圖

from rest_framework.pagination import PageNumberPagination
from rest_framework.pagination import LimitOffsetPagination
from rest_framework import serializers
class HulaLimitOffsetPagination(LimitOffsetPagination):
    max_limit = 2

class PageArticleView(APIView):
    def get(self,request,*args,**kwargs):
        queryset = models.Article.objects.all()
        page_object = HulaLimitOffsetPagination()
        result = page_object.paginate_queryset(queryset, request, self)
        ser = PageArticleSerializer(instance=result, many=True)
        return Response(ser.data)
View Code

五.篩選

url訪問

https://www.cnblogs.com/article/?category=1

自定義篩選功能MyFilterBackend

from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from . import models
from rest_framework.filters import BaseFilterBackend   

class MyFilterBackend(BaseFilterBackend):  #繼承BaseFilterBackend,是約束作用
    def filter_queryset(self, request, queryset, view): 
        val = request.query_params.get('cagetory')
        return queryset.filter(category_id=val)

應用

class IndexView(APIView):
    def get(self, request, *args,**kwargs):
        # http://www.xx.com/cx/index/?category=1
        # models.News.objects.filter(category=1)
        # http://www.xx.com/cx/index/?category=1
        queryset = models.News.objects.all()
        obj = MyFilterBackend()
        result = obj.filter_queryset(request,queryset,self)
        print(result)
        return Response('...')
#views.py中

from rest_framework.filters import BaseFilterBackend
#自定義篩選器
class MyFilterBackend(BaseFilterBackend):  
    def filter_queryset(self, request, queryset, view):
        val = request.query_params.get("category")
        return queryset.filter(category=val)

#視圖
class ArticleView(ListAPIView,CreateAPIView):
    queryset = models.Article.objects.all()
    serializer_class = ArticleSerials
    filter_backends = [MyFilterBackend,]
繼承自ListAPIView

六.視圖

2個視圖基類

1.APIView
APIView是drf中所有視圖的基類,繼承自django的view類
APIView和view的不同之處:
傳入到視圖方法中的是REST framework的Request對象,而不是Django的HttpRequeset對象;
視圖方法可以返回REST framework的Response對象,視圖會為響應數據設置(render)符合前端要求的格式;
任何APIException異常都會被捕獲到,並且處理成合適的響應信息;
在進行dispatch()分發前,會對請求進行身份認證、權限檢查、流量控制。

支持定義的類屬性:

authentication_classes 列表或元祖,身份認證類
permissoin_classes 列表或元祖,權限檢查類
throttle_classes 列表或元祖,流量控制類

APIView中仍以常規的類視圖定義方法來實現get() 、post() 或者其他請求方式的方法。

from rest_framework.views import APIView
from rest_framework.response import Response

# url(r'^students/$', views.StudentsAPIView.as_view()),
class StudentsAPIView(APIView):
    def get(self, request):
        data_list = Student.objects.all()
        serializer = StudentModelSerializer(instance=data_list, many=True)
        return Response(serializer.data)

2.GenericAPIView[通用視圖類]

繼承自APIView 主要增加了操作序列化器和數據庫查詢的方法,作用是為下面Mixin擴展類的執行提供方法支持。通常在使用時,可搭配一個或多個Mixin擴展類
支持定義的類屬性:
serializer_class 指明視圖使用的序列化器
queryset 指明使用的數據查詢集
pagination_class 指明分頁控制類
filter_backends 指明過濾控制后端

支持的方法:

get_serializer_class(self)
當出現一個視圖類中調用多個序列化器時,那么可以通過條件判斷在get_serializer_class方法中通過返回不同的序列化器類名就可以讓視圖方法執行不同的序列化器對象了

get_serializer(self, args, *kwargs)
返回序列化器對象,主要用來提供給Mixin擴展類使用,如果我們在視圖中想要獲取序列化器對象,也可以直接調用此方法。

get_queryset(self)
返回視圖使用的查詢集,主要用來提供給Mixin擴展類使用,是列表視圖與詳情視圖獲取數據的基礎,默認返回queryset屬性

get_object(self)
返回詳情視圖所需的模型類數據對象,主要用來提供給Mixin擴展類使用

5個視圖擴展類

ListModelMixin
列表視圖擴展類,提供list(request, *args, **kwargs)方法快速實現列表視圖返回200狀態碼。
該Mixin的list方法會對數據進行過濾和分頁

CreateModelMixin
創建視圖擴展類,提供create(request, *args, **kwargs)方法快速實現創建資源的視圖,成功返回201狀態碼。
如果序列化器對前端發送的數據驗證失敗,返回400錯誤。

RetrieveModelMixin
詳情視圖擴展類,提供retrieve(request, *args, **kwargs)方法,可以快速實現返回一個存在的數據對象。
如果存在,返回200, 否則返回404

UpdateModelMixin
更新視圖擴展類,提供update(request, *args, **kwargs)方法,可以快速實現更新一個存在的數據對象。
同時也提供partial_update(request, *args, **kwargs)方法,可以實現局部更新。
成功返回200,序列化器校驗數據失敗時,返回400錯誤

DestroyModelMixin
刪除視圖擴展類,提供destroy(request, *args, **kwargs)方法,可以快速實現刪除一個存在的數據對象。
成功返回204,不存在返回404。

GenericAPIView的視圖子類

CreateAPIView
提供 post 方法
繼承自: GenericAPIView、CreateModelMixin

ListAPIView
提供 get 方法
繼承自:GenericAPIView、ListModelMixin

RetrieveAPIView
提供 get 方法
繼承自: GenericAPIView、RetrieveModelMixin

DestoryAPIView
提供 delete 方法
繼承自:GenericAPIView、DestoryModelMixin

UpdateAPIView
提供 put 和 patch 方法
繼承自:GenericAPIView、UpdateModelMixin

RetrieveUpdateAPIView
提供 get、put、patch方法
繼承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin
 
自定義的視圖類繼承自
ListAPIView #查詢(內含get請求)
CreateAPIView #創建(內含post請求)
RetrieveAPIView #單條查詢
UpdateAPIView #更新
DestroyAPIView #刪除

整體流程

1.發送get請求,會調用ListAPIView中的get方法
2.執行list方法,先去ArticleView中找,找不到,直到找到父類ListModelMixin中的list方法
3.queryset = self.filter_queryset(self.get_queryset())先找ArticleView中的filter_queryset和get_queryset()方法,找不到,直到找到GenericAPIView中有這2中方式,get_queryset()中queryset = self.queryset,從ArticleView中找self.queryset,找不到報錯,找到了將其queryset傳給此類中的queryset變量,然后將其值返回給filter_querset方法,最后將filter_queryset的返回值賦值給3步中的queryset,在list方法中執行序列化

 

使用

class TagSer(serializers.ModelSerializer):
    class Meta:
        model = models.Tag
        fields = "__all__"

class TagView(ListAPIView,CreateAPIView):
    queryset = models.Tag.objects.all()
    serializer_class = TagSer

    def get_serializer_class(self): #重寫GenericAPIView中的方法
        # self.request
        # self.args
        # self.kwargs
        if self.request.method == 'GET':
            return TagSer
        elif self.request.method == 'POST':
            return OtherTagSer
    def perform_create(self,serializer):
        serializer.save(author=1)

七.認證

django自帶token認證

先來登錄功能,登錄成功后將隨機字符串加入Userinfo表的token字段中

class UserInfo(models.Model):
    """ 用戶表 """
    username = models.CharField(verbose_name='用戶名',max_length=32)
    password = models.CharField(verbose_name='密碼',max_length=64)
    token = models.CharField(verbose_name='token',max_length=64,null=True,blank=True)

LoginView登錄視圖的實現

from rest_framework.views import APIView
from rest_framework.response import Response
from api import models
import uuid
class LoginView(APIView):
    """
    登錄接口
    """
    def post(self,request,*args,**kwargs):
        user_obj = models.UserInfo.objects.filter(**request.data).first()
        if not user_obj:
            return Response("登錄失敗!")
        random_string = str(uuid.uuid4())
        # 登錄成功后將token添加到數據庫,且返回給前端
        user_obj.token = random_string
        user_obj.save()
        return Response(random_string)

在之后寫的其他視圖函數中,某些視圖函數需要驗證用戶是否登錄,就用到drf提供的BaseAuthentication認證功能。

先來單獨寫認證組件,需要用到的視圖函數直接調用即可

from rest_framework.authentication import BaseAuthentication
from api import models
class LuffyAuthentication(BaseAuthentication):
    def authenticate(self, request):
        token = request.query_params.get("token")
        if not token:
            return (None,None)
        user_obj = models.UserInfo.objects.filter(token=token).first()
        if user_obj:
            return (user_obj,token) #參數一給request.user,參數二給request.auth
        return (None,None)

需要用到認證功能的函數如下,調用方式如下:

from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.auth import LuffyAuthentication

class CommentView(APIView):
    #authentication_classes = [] 在settings中設置了全局認證,但此處不需,只需設置為空列表即可
    authentication_classes = [LuffyAuthentication,]
    def get(self,request,*args,**kwargs):
        print(request.user)  #request.user得到user object對象,只有request.user才會調用認證
        print(request.auth)  #得到token值
        return Response("獲取所有評論")
    def post(self,request,*args,**kwargs):
        if request.user:
            pass

如果你有100個視圖想要用認證組件,就需要在全局settings.py中設置了

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.LuffyAuthentication",]
}

源碼分析(從認證功能函數開始)

1.當有請求發來時,會先執行APIView中的dispath方法,dispath方法中會執行initialize_request(request, *args, **kwargs),進行老的request的封裝。在封裝過程中authenticators=self.get_authenticators(),此時執行get_authenticators(),進去此方法會看到return [auth() for auth in self.authentication_classes],因為自己寫的類中有authentication_class變量,此時authenticators=[LuffyAuthentication(),]

2.
當執行到get函數時,也是先進APIView的dispatch()方法,然后進入request的封裝函數中,查詢def user(self)方法,找到其中的self._authenticate()方法並執行,可以看到 user_auth_tuple = authenticator.authenticate(self),執行自己定義組件中的authenticate方法,返回(a,b)元組,然后self.user, self.auth = 此元組,從而得到request.user等於值1,request.auth等於值2
源碼大體流程
當用戶發來請求時,找到認證的所有類並實例化成為對象列表,然后將對象列表封裝到新的request對象中。

以后在視同中調用request.user

在內部會循環認證的對象列表,並執行每個對象的authenticate方法,該方法用於認證,他會返回兩個值分別會賦值給
request.user/request.auth 
總結

jwt

jwt的實現原理:
    - 用戶登錄成功之后,會給前端返回一段token。
    - token是由.分割的三段組成。
        - 第一段:類型和算法信心
        - 第二段:用戶信息+超時時間
        - 第三段:hs256(前兩段拼接)加密 + base64url
    - 以后前端再次發來信息時
        - 超時驗證
        - token合法性校驗
優勢:
    - token只在前端保存,后端只負責校驗。
    - 內部集成了超時時間,后端可以根據時間進行校驗是否超時。
    - 由於內部存在hash256加密,所以用戶不可以修改token,只要一修改就認證失敗。
jwt實現原理及其優勢

使用

app中注冊

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'api.apps.ApiConfig',
    'rest_framework',
    'rest_framework_jwt'
]
注冊rest_framework_jwt

登錄

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework_jwt.settings import api_settings
from api import models
from rest_framework_jwt.views import VerifyJSONWebToken
from rest_framework.throttling import AnonRateThrottle,BaseThrottle
class LoginView(APIView):
    authentication_classes = []
    def post(self,request,*args,**kwargs):
        user  = models.UserInfo.objects.filter(username=request.data.get("username"),password=request.data.get("password")).first()
        if not user:
            return Response({"code":10001,"error":"用戶名或密碼錯誤"})
        #
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        payload = jwt_payload_handler(user)
        #
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
        token = jwt_encode_handler(payload)
        return Response({"code":10000,"data":token})
登錄視圖實現生成隨機字符串

認證視圖

import jwt
from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication
from rest_framework_jwt.settings import api_settings
from api import models

class HulaQueryParamAuthentication(BaseAuthentication):
    def authenticate(self, request):
        """
        # raise Exception(), 不在繼續往下執行,直接返回給用戶。
        # return None ,本次認證完成,執行下一個認證
        # return ('x',"x"),認證成功,不需要再繼續執行其他認證了,繼續往后權限、節流、視圖函數
        """
        token = request.query_params.get('token')
        if not token:
            raise exceptions.AuthenticationFailed({'code':10002,'error':"登錄成功之后才能操作"})

        jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
        try:
            payload = jwt_decode_handler(token)
        except jwt.ExpiredSignature:
            raise exceptions.AuthenticationFailed({'code':10003,'error':"token已過期"})
        except jwt.DecodeError:
            raise exceptions.AuthenticationFailed({'code':10004,'error':"token格式錯誤"})
        except jwt.InvalidTokenError:
            raise exceptions.AuthenticationFailed({'code':10005,'error':"認證失敗"})

        jwt_get_username_from_payload = api_settings.JWT_PAYLOAD_GET_USERNAME_HANDLER
        username = jwt_get_username_from_payload(payload)
        user_object = models.UserInfo.objects.filter(username=username).first()
        return (user_object,token)
認證視圖函數

settings配置(全局配置)

REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":["api.extension.auth.HulaQueryParamAuthentication"],

}
View Code

如果不想使用此認證,在視圖函數中添加以下代碼

# def get_authenticators(self):
    #     if self.request.method == "GET":
    #         return []
    #     elif self.request.method == "POST":
    #         return super().get_authenticators()
View Code

八.權限

要用權限需要drf中的BasePermission類,先來自定義權限組件

from rest_framework.permissions import BasePermission
from rest_framework import exceptions

class MyPermission(BasePermission):
    message = {'code': 10001, 'error': '你沒權限'} #has_permission返回false時會拋出此異常
    def has_permission(self, request, view): #針對所有請求
        if request.user:
            return True
        return False

    def has_object_permission(self, request, view, obj): #針對有pk值的請求(例RetrieveAPIView)
        return False

視圖函數中使用上定義的權限組件

from rest_framework.views import APIView
from rest_framework.response import Response
from api.extension.permission import LuffyPermission

class CommentView(APIView):
    permission_classes = [] #如果在settings設置了全局權限,但此視圖不想使用
    permission_classes = [LuffyPermission,]
    def get(self,request,*args,**kwargs):
        return Response("獲取所有評論")
    def post(self,request,*args,**kwargs):
        if request.user:
            pass

settings配置全局權限

REST_FRAMEWORK = {
    DEFAULT_PERMISSION_CLASSES:"api.extension.permission.LuffyPermission"
}

源碼分析

class APIView(View):
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    
    def dispatch(self, request, *args, **kwargs): #1執行dispath方法
       #2 封裝request對象
        self.initial(request, *args, **kwargs)
        通過反射執行視圖中的方法
    
    def initial(self, request, *args, **kwargs):
        
        # 權限判斷
        self.check_permissions(request)
    
    def check_permissions(self, request):
        # [對象,對象,]
        for permission in self.get_permissions():
            if not permission.has_permission(request, self):
                self.permission_denied(request, message=getattr(permission, 'message', None))
    def permission_denied(self, request, message=None):
        if request.authenticators and not request.successful_authenticator:
            raise exceptions.NotAuthenticated()
        raise exceptions.PermissionDenied(detail=message)
        
    def get_permissions(self):
        return [permission() for permission in self.permission_classes]
    
class UserView(APIView):
    permission_classes = [MyPermission, ]
    
    def get(self,request,*args,**kwargs):
        return Response('user')
源碼分析
1.請求過來,先走APIView中的dispatch方法,執行self.initialize_request()方法進行老的request的封裝,然后執行initial方法,進行認證和權限處理:
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
2.進入check_permissions方法:for循環遍歷自己定義的permission_classes,檢查每個對象的has_permission()方法,如果返回false,拋出異常,返回true,繼續通過反射執行dispatch方法中的請求分發
for permission in self.get_permissions():
     if not permission.has_permission(request, self): 
          self.permission_denied(
              request, message=getattr(permission, 'message', None)
                )
總結

九.版本

使用思路

在視圖類中配置 versioning_class =注意這是單數形式,只能配置一個類
實現 determine_version 方法
全局配置 DEFAULT_VERSION ALLOWED_VERSIONS VERSION_PARAM

在 initial 中首先執行 determine_version,它里面會生成獲取版本的對象以及版本。
獲取版本:request.version
獲取處理版本的對象:request.versioning_scheme
反向生成 url :url = request.versioning_scheme.reverse(viewname='<url 的別名>', request=request)

使用(局部)

url中寫version

 url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),

視圖中應用

from rest_framework.views import APIView
  from rest_framework.response import Response
  from rest_framework.request import Request
  from rest_framework.versioning import URLPathVersioning
  
  
  class OrderView(APIView):
  
      versioning_class = URLPathVersioning
      def get(self,request,*args,**kwargs):
          print(request.version)
          print(request.versioning_scheme)
          return Response('...')
  
      def post(self,request,*args,**kwargs):
          return Response('post')

settings中配置

REST_FRAMEWORK = {
      "PAGE_SIZE":2,
      "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
      "ALLOWED_VERSIONS":['v1','v2'], #允許的版本
      'VERSION_PARAM':'version' #默認參數
  }

使用(全局)

url中寫version

url(r'^(?P<version>[v1|v2]+)/users/$', users_list, name='users-list'),  
url(r'^(?P<version>\w+)/users/$', users_list, name='users-list'),

視圖中應用

from rest_framework.views import APIView
  from rest_framework.response import Response
  from rest_framework.request import Request
  from rest_framework.versioning import URLPathVersioning
  
  class OrderView(APIView):
      def get(self,request,*args,**kwargs):
          print(request.version)
          print(request.versioning_scheme)
          return Response('...')
  
      def post(self,request,*args,**kwargs):
          return Response('post')

settings配置

REST_FRAMEWORK = {
      "PAGE_SIZE":2,
      "DEFAULT_PAGINATION_CLASS":"rest_framework.pagination.PageNumberPagination",
      "DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.URLPathVersioning", #
      "ALLOWED_VERSIONS":['v1','v2'],
      'VERSION_PARAM':'version'
  }

十.頻率限制

如何實現頻率限制

匿名用戶:用ip作為唯一標記(缺點:如果用戶使用代理ip,無法做到真正的限制)
登錄用戶:用用戶名或者id作為標識

具體實現

首先,django會在緩存中生成如下數據結構,用來存儲用戶訪問的時間戳
throttle_匿名標識(anon)_用戶ip:[時間戳1,時間戳2...]
每次用戶登錄時:
1.獲取當前時間戳
2.當前時間戳-60(如3/60s,一分鍾內3次),循環數據結構中時間戳B,將得到值A和B比較,B<A,則刪除B
3.判斷數據結構中列表的個數,如果小於3,則可以訪問

使用

#settings配置:
REST_FRAMEWORK={
"DEFAULT_THROTTLE_RATES":{
        "anon":'3/m' #一分鍾3次, 3/h 3/d 
    }
#"DEFAULT_THROTTLE_CLASSES":"rest_framework.throttling.AnonRateThrottle"  #全局設置 
}

#views函數中使用
from rest_framework.throttling import AnonRateThrottle
class ArticleView(APIView):
    #throttle_classes = [] 
    throttle_classes = [AnonRateThrottle,]

源碼剖析

請求發來,先執行dispatch方法,從dispatch方法中進入inital函數。先后執行到check_throttles()方法,在此看到,會執行對象的allow_request()方法
    def initial(self, request, *args, **kwargs):
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    def check_throttles(self, request):
        throttle_durations = []
        for throttle in self.get_throttles():
            if not throttle.allow_request(request, self):
                throttle_durations.append(throttle.wait())
def allow_request(self, request, view):
        if self.rate is None:
            return True
        # 獲取請求用戶的ip
        self.key = self.get_cache_key(request, view)
        if self.key is None:
            return True
        # 根據ip獲取所有訪問記錄,得到的一個列表
        self.history = self.cache.get(self.key, [])
        self.now = self.timer()

        # Drop any requests from the history which have now passed the
        # throttle duration
        while self.history and self.history[-1] <= self.now - self.duration:
            self.history.pop()
        if len(self.history) >= self.num_requests: #num_requests自己規定的訪問次數
            return self.throttle_failure()
        return self.throttle_success()
View Code

 

 

組件參考:https://www.jianshu.com/p/c16f8786e9f7

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM