源碼入口
self.dispatch()
這個方法中主要有兩個操作,一個是重新封裝request,另一個就是執(zhí)行initialize方法
initialize方法
這里面主要做了這幾件事
1. 獲取版本信息
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
進入determine_version
方法,
if self.versioning_class is None:
return (None, None)
scheme = self.versioning_class()
return (scheme.determine_version(request, *args, **kwargs), scheme)
如果沒有配置版本信息,則return None
,如果配置了,執(zhí)行配置類的determine_version
方法
這里提供的版本相關的配置有:
BaseVersioning
AcceptHeaderVersioning
URLPathVersioning
NamespaceVersioning
HostNameVersioning
QueryParameterVersioning
這里主要介紹URLPathVersioning
和QueryParameterVersioning
QueryParameterVersioning
這個配置主要是從get請求的參數(shù)里獲取版本信息,看源碼:
version = request.query_params.get(self.version_param, self.default_version)
URLPathVersioning
這個配置是從url中獲取版本信息
version = kwargs.get(self.version_param, self.default_version)
這種方式,需要在路由匹配里面,用一個參數(shù)接收版本號
url(r'^(?P<version>\w+)/users/',views.UserView.as_view()),
反向生成url
def reverse(self, viewname, args=None, kwargs=None, request=None, format=None, **extra):
if request.version is not None:
kwargs = {} if (kwargs is None) else kwargs
kwargs[self.version_param] = request.version
return super(URLPathVersioning, self).reverse(
viewname, args, kwargs, request, format, **extra
)
2. 獲取用戶認證
源碼邏輯
這個是從initial方法中的self.perform_authentication(request)
中看起
首先來到perform_authentication
方法,只返回了一個request.user
再選擇rest_framework的request中的user
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise
if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return
self._not_authenticated()
發(fā)現(xiàn)這個里面,如果有user_auth_tuple
這個元組,就直接返回了這個元組的內(nèi)容,用戶驗證的默認配置是ForcedAuthentication
if force_user is not None or force_token is not None:
forced_auth = ForcedAuthentication(force_user, force_token)
self.authenticators = (forced_auth,)
這是個強制認證,一般用在測試中,此時不必進行身份驗證
如果我們需要使用用戶認證,就得修改這個配置類,所用的配置類必須要有authenticate
方法,但是這里并沒有找到更進一步的信息,所以要退回去重新找。先來看一下這里最后認證不成功的處理:
def _not_authenticated(self):
"""
Set authenticator, user & authtoken representing an unauthenticated request.
Defaults are None, AnonymousUser & None.
"""
self._authenticator = None
if api_settings.UNAUTHENTICATED_USER:
self.user = api_settings.UNAUTHENTICATED_USER()
else:
self.user = None
if api_settings.UNAUTHENTICATED_TOKEN:
self.auth = api_settings.UNAUTHENTICATED_TOKEN()
else:
self.auth = None
這里有兩條配置,UNAUTHENTICATED_USER
和UNAUTHENTICATED_TOKEN
這兩個就是配置驗證不通過時,也就是用戶沒有登錄時的配置,可以配置成匿名用戶,如果不配置,就會返回None
回到開始,從封裝request里面,看能不能找到想要的
看到這里有一個操作,authenticators=self.get_authenticators(),
讓人充滿希望
def get_authenticators(self):
return [auth() for auth in self.authentication_classes]
果然在這里,看到一個東西加括號,可以聯(lián)想到這幾種情況,函數(shù)加括號執(zhí)行,類加括號實例化。
ps,其實這里還應該看到這個文件的整齊性,能看到的函數(shù),都是這種形式的返回值,可以留意一下
那我們先看看authentication_classes
里面是什么
是配置信息authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
此時我們可以打印一下這個authentication_classes
,看看有沒有默認值,打印之后發(fā)現(xiàn)真的有默認值:
[<class 'rest_framework.authentication.SessionAuthentication'>, <class 'rest_framework.authentication.BasicAuthentication'>]
這就好說了,我們可以到這個默認的配置里看看,
from rest_framework.authentication import BasicAuthentication
哈,這里有個authenticate
方法,這就和前面對上了,同時也說明了前面那個auth()
是實例化對象
看一下這個文件,提供了這么幾個配置
BaseAuthentication
BasicAuthentication
SessionAuthentication
TokenAuthentication
RemoteUserAuthentication
這些配置最后都是返回了一個兩個元素的元組,第一個元素是user,第二個是token
如果驗證不成功,返回None表示支持匿名用戶,拋出異常表示不支持匿名用戶
if token_obj:
return (token_obj.user, token_obj)
else:
raise exceptions.AuthenticationFailed('請登錄') #拋出異常表示不允許匿名用戶訪問
好了,到此為止,關于用戶認證的源碼也基本上明白了
擴展
用戶登錄,會獲得一個token,再次登錄,就會替換掉這個token,如何實現(xiàn)多設備登陸呢?
在數(shù)據(jù)庫中,token表是和用戶表做OnetoOne關聯(lián)的,此時只能有一個設備登陸,如果我們改成ManytoMany,就可以實現(xiàn)多設備登陸了
3. 獲取用戶權限信息
回到最開始的initialize方法,用戶驗證的下面就是獲取權限的代碼
self.perform_authentication(request)
self.check_permissions(request) # 權限
self.check_throttles(request)
然后我們就來到了這:
def get_permissions(self):
return [permission() for permission in self.permission_classes]
和用戶驗證同樣的套路
提供的配置還挺多的,以BasePermission為例吧
class BasePermission(object):
"""
A base class from which all permission classes should inherit.
"""
def has_permission(self, request, view):
"""
Return `True` if permission is granted, `False` otherwise.
授予權限返回True,否則返回False
"""
return True
def has_object_permission(self, request, view, obj):
"""
Return `True` if permission is granted, `False` otherwise.
"""
return True
所以,這兩個方法都是,有權限返回True,沒有權限返回False,具體邏輯需要我們自己寫
4. 限制用戶訪問頻率
源碼邏輯
self.perform_authentication(request)
self.check_permissions(request) # 權限
self.check_throttles(request) # 限制訪問頻率
也是一樣的套路,看一下配置,發(fā)現(xiàn)后面幾個都繼承了SimpleRateThrottle
代碼太多就不貼了
基本原理就是,首先要獲取用戶的唯一標識,通過這個get_cache_key
函數(shù),默認這個函數(shù)是拋出了一個異常的,所以如果用到這個函數(shù),就必須重寫這個方法。
如果沒有獲取到用戶的唯一標識,直接返回True,表示不限制訪問頻率
將用戶的訪問時間,記錄到一個列表中(以用戶的唯一標識為key,訪問記錄為這個列表),訪問一次記錄一次(插到列表第一個位置),如果列表中的最后一條記錄(最早的記錄)的時間超過當前時間減去限制的時間,就從列表中剔除,否則插入到列表,當列表長度超過限制的訪問次數(shù),拋出異常。
主要的邏輯代碼在這:
def allow_request(self, request, view):
"""
Implement the check to see if the request should be throttled.
On success calls `throttle_success`.
On failure calls `throttle_failure`.
"""
if self.rate is None:
return True
self.key = self.get_cache_key(request, view) # 獲取用戶唯一標識
if self.key is None:
return True # 返回True就不執(zhí)行下面的了
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:
return self.throttle_failure()
return self.throttle_success()
對于登錄用戶,可以用他的用戶名標識身份,如果是匿名用戶,如何標識身份呢?源碼中AnonRateThrottle
里為我們提供了一種方案就是使用用戶的ip,
return self.cache_format % {
'scope': self.scope,
'ident': self.get_ident(request)
}
這里說明了配置應該怎么寫:
def parse_rate(self, rate):
"""
Given the request rate string, return a two tuple of:
<allowed number of requests>, <period of time in seconds>
"""
if rate is None:
return (None, None)
num, period = rate.split('/')
num_requests = int(num)
duration = {'s': 1, 'm': 60, 'h': 3600, 'd': 86400}[period[0]]
return (num_requests, duration)
需要注意,這個scope = None
默認是None,使用的時候需要指定,比如登錄用戶設置成'user',匿名用戶設置成'anon'
那配置就可以這樣寫:
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_RATES': {
'user': '5/min',
'anon': '2/min'
}
}
擴展
默認的情況是,限制訪問頻率是對站點中所有頁面的限制,是訪問站點的頻率。如果想限制某個用戶對某一個頁面的訪問頻率,可以怎么做?
本質(zhì)上就是設置用戶的唯一標識,可以把唯一標識設置成用戶+指定頁面的名字
指定頁面的名字,其實就是CBV中,對應的視圖類的名字,所以
self.key = request.user.user + view.__class__.__name__
關于csrf的處理
其實在路由匹配中,就已經(jīng)做了處理
看一下這個as_view()方法
@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)
最后默認就禁止了csrf
這里還為我們提供了一種思路,就是CBV的裝飾器還可以這么用csrf_exempt(view)
,其實這也是裝飾器的本質(zhì)
注意是因為在CBV中,CBV中的裝飾器才能這么用
CBV裝飾器的三種寫法,可以看看這篇文章
解析器
關于request.POST 里面有沒有東西的討論,在這里已經(jīng)說過,
在rest framework里面,怎么處理呢? restful里面有個解析器
先看看封裝request那里面,還有個negotiator=self.get_content_negotiator()
,
其實這里面就是處理的解析器
配置在這里
content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
一般情況下不用自定制,如果需要的話這樣寫:
from rest_framework.renderers import JSONRenderer, TemplateHTMLRenderer, BrowsableAPIRenderer, MultiPartRenderer
parser_classes = [JSONRenderer, TemplateHTMLRenderer, BrowsableAPIRenderer, MultiPartRenderer]
只有在調(diào)用request.data 的時候,才會觸發(fā)這些配置
序列化/驗證
從數(shù)據(jù)庫直接查詢到的結果,是queryset類型,如何把這個queryset發(fā)送給前端,在Django中可以用queryset.values(),然后list轉(zhuǎn)成列表發(fā)送給前端,在rest framework里面,有個serializers
這個序列化有兩大功能,一是將用戶請求的數(shù)據(jù)序列化,二是對用戶提交的數(shù)據(jù)進行驗證
手動版本:
from rest_framework import serializers
class PasswordValidator(object):
#密碼字段的復雜驗證
def __call__(self,value):
"""
:param vlaue: 用戶傳來的數(shù)據(jù)
:return:
"""
try:
int(value) #這個只是舉個例子
except Exception as e:
message = '密碼必須是數(shù)字'
raise serializers.ValidationError(message)
class UserSerialize(serializers.Serializer):
"""
要先寫這個類,定義字段,字段是要取的數(shù)據(jù)表的字段
"""
user = serializers.CharField()
pwd = serializers.CharField(validators=[PasswordValidator()])#復雜驗證
email = serializers.CharField()
user_type_id = serializers.IntegerField()
# ug = serializers.CharField(source='ug.title')#跨表查詢用source
ug = serializers.CharField(source='ug.title',required=False)#表示這個字段可以為空
class CustomSerialize(APIView):
def get(self, request, *args,**kwargs):
user_list = models.UserInfo.objects.filter()
# print(user_list.first().ug)
ser = UserSerialize(instance=user_list,many=True) # 如果是一個子段,many就等于False,多個字段就用True
# user_obj = models.UserInfo.objects.filter().first()
# ser = UserSerialize(instance=user_obj,many=False)
return Response(ser.data) #.data方法就拿到了所有的數(shù)據(jù)
#這個Response 是restframework的返回值 from rest_framework.response import Response
def post(self, request, *args,**kwargs):
#序列化的第二大功能,對用戶輸入的數(shù)據(jù)進行合法性驗證
ser = UserSerialize(data=request.data)#提交數(shù)據(jù)就不能用對象了,而應該是用data參數(shù)
if ser.is_valid():
#返回提交的post請求的數(shù)據(jù)
print(ser.validated_data)#OrderedDict([('user', 'zhang'), ('pwd', '123'), ('email', 'ags@1414.com'), ('user_type_id', 2), ('ug', {'title': '2'})]) 返回結果是一個有序字典
print(request.data)#<QueryDict: {'user': ['zhang'], 'pwd': ['123'], 'email': ['ags@1414.com'], 'user_type_id': ['2'], 'ug': ['2']}>
return Response(ser.validated_data)#頁面就顯示剛才提交的數(shù)據(jù)
else:
return Response(ser.errors)#用戶數(shù)據(jù)不通過驗證,則返回錯誤信息
注意最上面的那個復雜驗證,用到了__call__
方法,因為源碼里有用到示例對象加括號的操作,所以想到了__call__
,事實證明有效
還有個ModelSerializer,類似Django中的modelForm,可以幫我們封裝自定義字段的步驟,上面例子中的UserSerialize可以這么寫
class UserSerialize(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__' # 表示獲取所有字段
更多操作:
# class UserSerialize(serializers.ModelSerializer):
# #還可以添加字段,已有就更新,沒有的就新增
# # user = serializers.CharField() #可以加簡單的限制
# ug = serializers.HyperlinkedIdentityField(view_name='xxx')#這個view_name 就是反向生成的name
# v = serializers.CharField() #發(fā)送post請求的時候,可以多添加這個參數(shù),數(shù)據(jù)表中已有這個字段則覆蓋,
# # 但是注意如果要創(chuàng)建數(shù)據(jù),要把這條記錄刪掉,否則會報錯,可以用pop拿出來
#
# class Meta:
# model = models.UserInfo
# fields = '__all__'
#
# extra_kwargs = {
# 'user':{'min_length':2} #這個限制和上面那個效果是一樣的,這個的意義是添加復雜認證
# }
# depth = 2 #跨表的深度也可以設置
分頁
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
class UserPageSerialize(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__'
說一說這三種分頁的配置:
class StandardResultsSetPagination(PageNumberPagination):
#根據(jù)頁碼分頁
page_size = 1 #默認每頁顯示的數(shù)據(jù)條數(shù)
page_size_query_param = 'page_size' #獲取url參數(shù)中設置的每頁 顯示數(shù)據(jù)條數(shù)
page_query_param = 'page' #獲取url參數(shù)中傳入的頁碼key
# max_page_size = 1 #最大支持的每頁顯示的數(shù)據(jù)條數(shù)
class StandardResultsSetPagination(LimitOffsetPagination):
# 根據(jù)位置和個數(shù)分頁
default_limit = 10 # 默認每頁顯示的數(shù)據(jù)條數(shù)
limit_query_param = 'limit' # URL中傳入的顯示數(shù)據(jù)條數(shù)的參數(shù),是從0開始的
offset_query_param = 'offset' # URL中傳入的數(shù)據(jù)位置的參數(shù)
max_limit = None # 最大每頁顯得條數(shù)
class StandardResultsSetPagination(CursorPagination):
# 游標分頁,只能點上一頁,下一頁
cursor_query_param = 'cursor' # URL傳入的游標參數(shù)
page_size = 1 # 默認每頁顯示的數(shù)據(jù)條數(shù)
page_size_query_param = 'page_size' # URL傳入的每頁顯示條數(shù)的參數(shù)
max_page_size = 1000 # 每頁顯示數(shù)據(jù)最大條數(shù)
ordering = "id" # 根據(jù)ID從大到小排列
最終返回數(shù)據(jù)的代碼
class UserViewSet(APIView):
def get(self, request, *args, **kwargs):
paginator = StandardResultsSetPagination() # 獲取上面設置的頁碼配置
user_list = models.UserInfo.objects.filter().order_by('-id')
page_user_list = paginator.paginate_queryset(user_list, request, self) # 這個要拿到每頁的數(shù)據(jù)
ser = UserPageSerialize(instance=page_user_list, many=True)
print(user_list)
# return Response(ser.data)#這種只會返回數(shù)據(jù),下面這種還能返回上一頁和下一頁
response = paginator.get_paginated_response(ser.data) # 這樣就會有上一頁下一頁的連接了
return response
這三種頁碼中,重點說一下游標這個,這里面涉及一個加密解密的過程
如果使用這種配置,頁面中將不能直接跳到多少頁,只能乖乖的一頁一頁點
為什么要有這種設定呢?
分頁有個性能問題1億條數(shù)據(jù),如果用offset和limit如果拿第9000萬條數(shù)據(jù),會把之前所有的都讀一遍所以,頁碼越靠后,越慢
所以想辦法不讓他掃描,就是這個游標(cursor)
記錄上一頁的最后一個下標,找到時候就直接從那個下標開始找所以有的網(wǎng)站只有上一頁和下一頁
比如
select * from offset 220 limit 5
從第220條開始找5條
但是這種是一條一條找到第220條數(shù)據(jù)的,然后開始繼續(xù)找5條,這樣就會有性能問題
然而cursor ,是這樣的: select * from offset where id>220 offfset 0 limit 5
這樣就不會一條一條找前220條數(shù)據(jù)了
路由
這個其實涉及挺多的,一點一點說吧
1. 手動寫路由和視圖
我們知道,如果是單純訪問數(shù)據(jù),應該支持url最后是以.json
這種格式結尾的,這個怎么實現(xiàn)呢?
url(r'^ser\.(?P<format>\w+)$', views.CustomSerialize.as_view())
這種就表示帶不帶后綴都能訪問到數(shù)據(jù),注意這里只能用format
作為正則分組的名字,否則要改配置
但是現(xiàn)在這個路由有個問題,能處理查詢和新增,但是不能做修改和刪除,因為沒有id,所以還需要再增加一個url
url(r'^test\.(?P<format>\w+)/(?P<pk>\d+)', views.RouteView.as_view())
然后查詢數(shù)據(jù)的請求再來的時候,視圖中就可以判斷參數(shù)中有沒有id,有的話就是就返回指定的數(shù)據(jù),沒有就返回全部數(shù)據(jù)
最終需要的路由有這四條:
url(r'^test\.(?P<format>\w+)', views.RouteView.as_view()),
url(r'^test/', views.RouteView.as_view()),
url(r'^test/(?P<pk>\d+)/', views.RouteView.as_view()),
url(r'^test\.(?P<format>\w+)/(?P<pk>\d+)', views.RouteView.as_view()),
視圖中:
class RouteView(APIView):
# authentication_classes = [CustomAuthenticate,] #應用認證配置類
def get(self,request,*args,**kwargs):
pk = kwargs.get('pk')
if not pk:
user_list = models.UserInfo.objects.filter()
ser = TestServerlizer(instance=user_list,many=True) #many=True表示多條
else:
obj = models.UserInfo.objects.filter(pk=pk)
ser = TestServerlizer(instance=obj, many=False)
return Response(ser.data) #ser.data 才能拿到數(shù)據(jù)
2. GenericAPIView
路由匹配不改,視圖中可以簡化一點
需要先導入這個類
from rest_framework.generics import GenericAPIView
class RouteView(GenericAPIView):#要繼承這個類
queryset = models.UserInfo.objects.all()
serializer_class = RouteSerializer
pagination_class = StandardResultsSetPagination
def get(self,request,*args,**kwargs):
user_list = self.get_queryset()
page_user_list = self.paginate_queryset(user_list)
ser = self.get_serializer(instance=page_user_list,many=True)
response = self.get_paginated_response(ser.data)#還內(nèi)置了分頁的方法
return response
這種方法其實沒啥用,一點都沒有簡化,但是也算是個過程,開始不用繼承APIView了
3. GenericViewSet
這個就可以簡化路由了,并加入增刪改查方法
視圖中
url(r'^test', views.RouteView.as_view({'get':'list','post':'create'})),
url(r'^test/(?P<pk>\d+)', views.RouteView.as_view({'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}))
增刪改查都需要導入類
from rest_framework.viewsets.mixins import ListModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin
class RouteView(GenericViewSet,ListModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin):#要繼承這個類
queryset = models.UserInfo.objects.all()
serializer_class = RouteSerializer
pagination_class = StandardResultsSetPagination
此時不用我們手動寫增刪改查方法了,只需要導入類就行了
4. ModelViewSet
這種就真的是完全自動了
路由:
首先在urlpartterns之前,注冊好url訪問的路徑
from rest_framework.routers import DefaultRouter
route = DefaultRouter()
route.register('abc',views.RouteView)
比如訪問的路徑是abc,不管后面有沒有id,有沒有format,都可以識別
路由里只需要寫一句話就行
url(r'^',include(route.urls)),
然后視圖里面也很簡單,繼承一個ModelViewSet就行了
from rest_framework.viewsets import ModelViewSet
class RouteSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = '__all__'
class RouteView(ModelViewSet):
queryset = models.UserInfo.objects.all()
serializer_class = RouteSerializer
實際上這ModelViewSet內(nèi)部幫我們封裝了增刪改查那幾個類
源碼中:
class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass
但這種封裝度這么高,也注定了他并不會被經(jīng)常使用
渲染
這個是根據(jù)url,使用合適的渲染組件
配置在這里:
from rest_framework.renderers import JSONRenderer,AdminRenderer,HTMLFormRenderer,TemplateHTMLRenderer
一般就用JSON這個就行了,其他的可以了解一下
在視圖類里面:
renderer_classes = [JSONRenderer, ]
注意路由中url的格式,要寫成這樣:
url(r'^test\.(?P<format>[a-z0-9]+)', views.RouteView.as_view()),
瀏覽器訪問的時候,url是這樣的:
http://127.0.0.1:8000/test/?format=json
http://127.0.0.1:8000/test.json
http://127.0.0.1:8000/test/
這三種都行
其他幾種寫法都類似:
AdminRenderer
是表格形式,HTMLFormRenderer
是form表單,
TemplateHTMLRenderer
是自定義的模板