一.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' ]
路由

from django.conf.urls import url from django.contrib import admin from api import views urlpatterns = [ url(r'^drf/info/', views.DrfInfoView.as_view()), ]
視圖

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)
自定義序列化
路由

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 ]
視圖

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('成功')
三.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)

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
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)
五.篩選
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,]
六.視圖
2個視圖基類
傳入到視圖方法中的是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.
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,只要一修改就認證失敗。
使用
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' ]
登錄

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"], }
如果不想使用此認證,在視圖函數中添加以下代碼

# def get_authenticators(self): # if self.request.method == "GET": # return [] # elif self.request.method == "POST": # return super().get_authenticators()
八.權限
要用權限需要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()
組件參考:https://www.jianshu.com/p/c16f8786e9f7