drf內(nèi)置了很多便捷的功能,在接下來(lái)的課程中會(huì)給大家依次講解下面的內(nèi)容:
-
快速上手
-
請(qǐng)求的封裝
-
版本管理
-
認(rèn)證
-
權(quán)限
-
限流
-
序列化
-
視圖
-
條件搜索
-
分頁(yè)
-
路由
-
解析器
1. 快速上手
-
安裝
pip install djangorestframework==3.12.4版本要求:djangorestframework==3.12.4
Python (3.5, 3.6, 3.7, 3.8, 3.9)
Django (2.2, 3.0, 3.1)
版本要求:djangorestframework==3.11.2
Python (3.5, 3.6, 3.7, 3.8)
Django (1.11, 2.0, 2.1, 2.2, 3.0) -
配置,在settings.py中添加配置
INSTALLED_APPS = [
...
# 注冊(cè)rest_framework(drf)
'rest_framework',
]
?
# drf相關(guān)配置以后編寫(xiě)在這里
REST_FRAMEWORK = {
} -
URL和視圖
# urls.py
?
from django.urls import path
from app01 import views
?
urlpatterns = [
path('users/', views.UserView.as_view()),
]# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
?
?
class UserView(APIView):
def get(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
?
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
其實(shí)drf框架是在django基礎(chǔ)進(jìn)行的擴(kuò)展,所以上述執(zhí)行過(guò)得底層實(shí)現(xiàn)流程(同django的CBV):
drf中重寫(xiě)了 as_view 和dispatch方法,其實(shí)就是在原來(lái)django的功能基礎(chǔ)上添加了一些功能,例如:
-
as_view,免除了csrf 驗(yàn)證,一般前后端分離不會(huì)使用csrf token認(rèn)證(后期會(huì)使用jwt認(rèn)證)。 -
dispatch,內(nèi)部添加了 版本處理、認(rèn)證、權(quán)限、訪問(wèn)頻率限制等諸多功能(后期逐一講解)。
2. 請(qǐng)求數(shù)據(jù)的封裝
以前我們通過(guò)django開(kāi)發(fā)項(xiàng)目時(shí),視圖中的request是 django.core.handlers.wsgi.WSGIRequest 類(lèi)的對(duì)象,其中包含了請(qǐng)求相關(guān)的所有數(shù)據(jù)。
# Django FBV
def index(request):
request.method
request.POST
request.GET
request.body
?
# Django CBV
from django.views import View
class UserView(View):
def get(self,request):
request.method
request.POST
request.GET
request.body
而在使用drf框架時(shí),視圖中的request是rest_framework.request.Request類(lèi)的對(duì)象,其是又對(duì)django的request進(jìn)行了一次封裝,包含了除django原request對(duì)象以外,還包含其他后期會(huì)使用的其他對(duì)象。
from rest_framework.views import APIView
from rest_framework.response import Response
?
?
class UserView(APIView):
def get(self, request, *args, **kwargs):
# request,不再是django中的request,而是又被封裝了一層,內(nèi)部包含:django的request、認(rèn)證、解析器等。
return Response({"code": 1000, "data": "xxx"})
?
def post(self, request, *args, **kwargs):
return Response({"code": 1000, "data": "xxx"})
?
對(duì)象 = (request, 其他數(shù)據(jù))
# rest_framework.request.Request 類(lèi)
class Request:
"""
Wrapper allowing to enhance a standard `HttpRequest` instance.
Kwargs:
- request(HttpRequest). The original request instance. (django中的request)
- parsers(list/tuple). The parsers to use for parsing the
request content.
- authenticators(list/tuple). The authenticators used to try
authenticating the request's user.
"""
def __init__(self, request, parsers=None, authenticators=None,negotiator=None, parser_context=None):
self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()
...
@property
def query_params(self):
"""
More semantically correct name for request.GET.
"""
return self._request.GET
@property
def data(self):
if not _hasattr(self, '_full_data'):
self._load_data_and_files()
return self._full_data
def __getattr__(self, attr):
try:
return getattr(self._request, attr) # self._request.method
except AttributeError:
return self.__getattribute__(attr)
所以,在使用drf框架開(kāi)發(fā)時(shí),視圖中的request對(duì)象與原來(lái)的有些不同,例如:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.views import View
from rest_framework.request import Request
class UserView(APIView):
def get(self, request, *args, **kwargs):
# 通過(guò)對(duì)象的嵌套直接找到原request,讀取相關(guān)值
request._request.method
request._request.GET
request._request.POST
request._request.body
# 舉例:
content-type: url-form-encoded
v1=123&v2=456&v3=999
django一旦讀取到這個(gè)請(qǐng)求頭之后,就會(huì)按照 {"v1":123,"v2":456,"v3":999}
content-type: application/json
{"v1":123,"v2":456}
request._request.POST
request._request.body
# 直接讀取新request對(duì)象中的值,一般此處會(huì)對(duì)原始的數(shù)據(jù)進(jìn)行一些處理,方便開(kāi)發(fā)者在視圖中使用。
request.query_params # 內(nèi)部本質(zhì)上就是 request._request.GET
request.data # 內(nèi)部讀取請(qǐng)求體中的數(shù)據(jù),并進(jìn)行處理,例如:請(qǐng)求者發(fā)來(lái)JSON格式,他的內(nèi)部會(huì)對(duì)json字符串進(jìn)行反序列化。
# 通過(guò) __getattr__ 去訪問(wèn) request._request 中的值
request.method
底層源碼實(shí)現(xiàn):
3. 版本管理
在restful規(guī)范中要去,后端的API中需要體現(xiàn)版本。
drf框架中支持5種版本的設(shè)置。
3.1 URL的GET參數(shù)傳遞(*)
# settings.py
REST_FRAMEWORK = {
"VERSION_PARAM": "v",
"DEFAULT_VERSION": "v1",
"ALLOWED_VERSIONS": ["v1", "v2", "v3"],
"DEFAULT_VERSIONING_CLASS":"rest_framework.versioning.QueryParameterVersioning"
}
源碼執(zhí)行流程:
3.2 URL路徑傳遞(*)
3.3 請(qǐng)求頭傳遞
3.4 二級(jí)域名傳遞
在使用二級(jí)域名這種模式時(shí)需要先做兩個(gè)配置:
-
域名需解析至IP,本地可以在hosts文件中添加
127.0.0.1 v1.wupeiqi.com 127.0.0.1 v2.wupeiqi.com
-
在django的settings.py配置文件中添加允許域名訪問(wèn)
ALLOWED_HOSTS = ["*"]
3.5 路由的namespace傳遞
以上就是drf中支持的5種版本管理的類(lèi)的使用和配置。
全局配置
上述示例中,如果想要應(yīng)用某種 版本 的形式,需要在每個(gè)視圖類(lèi)中定義類(lèi)變量:
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioning
class UserView(APIView):
versioning_class = QueryParameterVersioning
...
如果你項(xiàng)目比較大,需要些很多的視圖類(lèi),在每一個(gè)類(lèi)中都寫(xiě)一遍會(huì)比較麻煩,所有drf中也支持了全局配置。
# settings.py
REST_FRAMEWORK = {
"DEFAULT_VERSIONING_CLASS": "rest_framework.versioning.QueryParameterVersioning", # 處理版本的類(lèi)的路徑
"VERSION_PARAM": "version", # URL參數(shù)傳參時(shí)的key,例如:xxxx?version=v1
"ALLOWED_VERSIONS": ["v1", "v2", "v3"], # 限制支持的版本,None表示無(wú)限制
"DEFAULT_VERSION": "v1", # 默認(rèn)版本
}
訪問(wèn)URL:
http://127.0.0.1:8000/api/users/?version=v1 http://127.0.0.1:8000/api/users/?version=v2 http://127.0.0.1:8000/api/users/?version=v3 http://127.0.0.1:8000/api/admin/?version=v1 http://127.0.0.1:8000/api/admin/?version=v2 http://127.0.0.1:8000/api/admin/?version=v3 http://127.0.0.1:8000/api/v1/order/ http://127.0.0.1:8000/api/v2/order/ http://127.0.0.1:8000/api/v3/order/
底層源碼實(shí)現(xiàn)
反向生成URL
在每個(gè)版本處理的類(lèi)中還定義了reverse方法,他是用來(lái)反向生成URL并攜帶相關(guān)的的版本信息用的,例如:
小結(jié)
以后使用drf開(kāi)發(fā)后端API接口時(shí):
-
創(chuàng)建django程序
-
安裝drf框架
-
創(chuàng)建一個(gè)app專(zhuān)門(mén)來(lái)處理用戶的請(qǐng)求
-
注冊(cè)APP
-
設(shè)置版本
-
編寫(xiě)視圖類(lèi)
4. 認(rèn)證
在開(kāi)發(fā)后端的API時(shí),不同的功能會(huì)有不同的限制,例如:
-
無(wú)需認(rèn)證,就可以訪問(wèn)并獲取數(shù)據(jù)。
-
需認(rèn)證,用戶需先登錄,后續(xù)發(fā)送請(qǐng)求需攜帶登錄時(shí)發(fā)放的憑證(后期會(huì)講jwt)
在drf中也給我們提供了 認(rèn)證組件 ,幫助我們快速實(shí)現(xiàn)認(rèn)證相關(guān)的功能,例如:
# models.py
from django.db import models
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)
在視圖類(lèi)中設(shè)置類(lèi)變量 authentication_classes的值為 認(rèn)證類(lèi) MyAuthentication,表示此視圖在執(zhí)行內(nèi)部功能之前需要先經(jīng)過(guò) 認(rèn)證。
認(rèn)證類(lèi)的內(nèi)部就是去執(zhí)行:authenticate方法,根據(jù)返回值來(lái)表示認(rèn)證結(jié)果。
-
拋出異常AuthenticationFailed,表示認(rèn)證失敗。內(nèi)部還會(huì)執(zhí)行
authenticate_header將返回值設(shè)置給響應(yīng)頭WWW-Authenticate -
返回含有兩個(gè)元素的元組,表示認(rèn)證成功,并且會(huì)將元素的第1個(gè)元素賦值給
request.user、第2個(gè)值賦值給request.auth。第1個(gè)值,一般是用戶對(duì)象。 第2個(gè)值,一般是token
-
返回None,表示繼續(xù)調(diào)用 后續(xù)的認(rèn)證類(lèi) 進(jìn)行認(rèn)證(上述案例未涉及)
關(guān)于 ”返回None“
接下來(lái)說(shuō)說(shuō) “返回None” 是咋回事。
在視圖類(lèi)的
authentication_classes中定義認(rèn)證類(lèi)時(shí),傳入的是一個(gè)列表,支持定義多個(gè)認(rèn)證類(lèi)。當(dāng)出現(xiàn)多個(gè)認(rèn)證類(lèi)時(shí),drf內(nèi)部會(huì)按照列表的順序,逐一執(zhí)行認(rèn)證類(lèi)的
authenticate方法,如果 返回元組 或 拋出異常 則會(huì)終止后續(xù)認(rèn)證類(lèi)的執(zhí)行;如果返回None,則意味著繼續(xù)執(zhí)行后續(xù)的認(rèn)證類(lèi)。如果所有的認(rèn)證類(lèi)
authenticate都返回了None,則默認(rèn) request.user="AnonymousUser" 和 request.auth=None,也可以通過(guò)修改配置文件來(lái)修改默認(rèn)值。REST_FRAMEWORK = { "UNAUTHENTICATED_USER": lambda: None, "UNAUTHENTICATED_TOKEN": lambda: None, }
”返回None“的應(yīng)用場(chǎng)景:
當(dāng)某個(gè)API,已認(rèn)證 和 未認(rèn)證 的用戶都可以方法時(shí),比如:
已認(rèn)證用戶,訪問(wèn)API返回該用戶的視頻播放記錄列表。
未認(rèn)證用戶,訪問(wèn)API返回最新的的視頻列表。
注意:不同于之前的案例,之前案例是:必須認(rèn)證成功后才能訪問(wèn),而此案例則是已認(rèn)證和未認(rèn)證均可訪問(wèn)。
關(guān)于多個(gè)認(rèn)證類(lèi)
一般情況下,編寫(xiě)一個(gè)認(rèn)證類(lèi)足矣。
當(dāng)項(xiàng)目中可能存在多種認(rèn)證方式時(shí),就可以寫(xiě)多個(gè)認(rèn)證類(lèi)。例如,項(xiàng)目認(rèn)證支持:
-
在請(qǐng)求中傳遞token進(jìn)行驗(yàn)證。
-
請(qǐng)求攜帶cookie進(jìn)行驗(yàn)證。
-
請(qǐng)求攜帶jwt進(jìn)行驗(yàn)證(后期講)。
-
請(qǐng)求攜帶的加密的數(shù)據(jù),需用特定算法解密(一般為app開(kāi)發(fā)的接口都是有加密算法)
-
...
此時(shí),就可以編寫(xiě)多個(gè)認(rèn)證類(lèi),并按照需要應(yīng)用在相應(yīng)的視圖中,例如:
注意:此示例后續(xù)在視圖中讀取的 request.user 的值為None時(shí),表示未認(rèn)證成功;不為None時(shí),則表示認(rèn)證成功。
全局配置
在每個(gè)視圖類(lèi)的類(lèi)變量 authentication_classes 中可以定義,其實(shí)在配置文件中也可以進(jìn)行全局配置,例如:
REST_FRAMEWORK = {
"UNAUTHENTICATED_USER": lambda: None,
"UNAUTHENTICATED_TOKEN": lambda: None,
"DEFAULT_AUTHENTICATION_CLASSES":["xxxx.xxxx.xx.類(lèi)名","xxxx.xxxx.xx.類(lèi)名",]
}
底層源碼實(shí)現(xiàn):
5. 權(quán)限
認(rèn)證,根據(jù)用戶攜帶的 token/其他 獲取當(dāng)前用戶信息。
權(quán)限,讀取認(rèn)證中獲取的用戶信息,判斷當(dāng)前用戶是否有權(quán)限訪問(wèn),例如:普通用戶、管理員、超級(jí)用戶,不同用戶具有不同的權(quán)限。
class UserInfo(models.Model):
role_choices = ((1, "普通用戶"), (2, "管理員"), (3, "超級(jí)管理員"),)
role = models.IntegerField(verbose_name="角色", choices=role_choices, default=1)
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)
import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class AuthView(APIView):
""" 用戶登錄認(rèn)證 """
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
print(request.data) # {"username": "wupeiqi", "password": "123"}
username = request.data.get('username')
password = request.data.get('password')
user_object = models.UserInfo.objects.filter(username=username, password=password).first()
if not user_object:
return Response({"code": 1000, "data": "用戶名或密碼錯(cuò)誤"})
token = str(uuid.uuid4())
user_object.token = token
user_object.save()
return Response({"code": 0, "data": {"token": token, "name": username}})
class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed({"code": 1002, "data": "認(rèn)證失敗"})
user_object = models.UserInfo.objects.filter(token=token).first()
if not user_object:
raise AuthenticationFailed({"code": 1002, "data": "認(rèn)證失敗"})
return user_object, token
def authenticate_header(self, request):
return 'Bearer realm="API"'
class PermissionA(BasePermission):
message = {"code": 1003, 'data': "無(wú)權(quán)訪問(wèn)"}
def has_permission(self, request, view):
if request.user.role == 2:
return True
return False
# 暫時(shí)先這么寫(xiě)
def has_object_permission(self, request, view, obj):
return True
class OrderView(APIView):
authentication_classes = [TokenAuthentication, ]
permission_classes = [PermissionA,]
def get(self, request, *args, **kwargs):
print(request.user)
return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})
class PayView(APIView):
authentication_classes = [TokenAuthentication, ]
permission_classes = [PermissionA, ]
def get(self, request, *args, **kwargs):
print(request.user)
return Response({"code": 0, "data": "數(shù)據(jù)..."})
關(guān)于多個(gè)權(quán)限類(lèi)
當(dāng)開(kāi)發(fā)過(guò)程中需要用戶同時(shí)具備多個(gè)權(quán)限(缺一不可)時(shí),可以用多個(gè)權(quán)限類(lèi)來(lái)實(shí)現(xiàn)。
權(quán)限組件內(nèi)部處理機(jī)制:按照列表的順序逐一執(zhí)行 has_permission 方法,如果返回True,則繼續(xù)執(zhí)行后續(xù)的權(quán)限類(lèi);如果返回None或False,則拋出權(quán)限異常并停止后續(xù)權(quán)限類(lèi)的執(zhí)行。
# models.py
from django.db import models
class Role(models.Model):
""" 角色表 """
title = models.CharField(verbose_name="名稱(chēng)", max_length=32)
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)
roles = models.ManyToManyField(verbose_name="角色", to="Role")
# urls.py
from django.urls import path, re_path, include
from app01 import views
urlpatterns = [
path('api/auth/', views.AuthView.as_view()),
path('api/order/', views.OrderView.as_view()),
]
# views.py
import uuid
from rest_framework.views import APIView
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.exceptions import AuthenticationFailed
from app01 import models
class AuthView(APIView):
""" 用戶登錄認(rèn)證 """
def post(self, request, *args, **kwargs):
print(request.data) # {"username": "wupeiqi", "password": "123"}
username = request.data.get('username')
password = request.data.get('password')
user_object = models.UserInfo.objects.filter(username=username, password=password).first()
if not user_object:
return Response({"code": 1000, "data": "用戶名或密碼錯(cuò)誤"})
token = str(uuid.uuid4())
user_object.token = token
user_object.save()
return Response({"code": 0, "data": {"token": token, "name": username}})
class TokenAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
if not token:
raise AuthenticationFailed({"code": 1002, "data": "認(rèn)證失敗"})
user_object = models.UserInfo.objects.filter(token=token).first()
if not user_object:
raise AuthenticationFailed({"code": 1002, "data": "認(rèn)證失敗"})
return user_object, token
def authenticate_header(self, request):
return 'Bearer realm="API"'
class PermissionA(BasePermission):
message = {"code": 1003, 'data': "無(wú)權(quán)訪問(wèn)"}
def has_permission(self, request, view):
exists = request.user.roles.filter(title="員工").exists()
if exists:
return True
return False
def has_object_permission(self, request, view, obj):
return True
class PermissionB(BasePermission):
message = {"code": 1003, 'data': "無(wú)權(quán)訪問(wèn)"}
def has_permission(self, request, view):
exists = request.user.roles.filter(title="主管").exists()
if exists:
return True
return False
def has_object_permission(self, request, view, obj):
return True
class OrderView(APIView):
authentication_classes = [TokenAuthentication, ]
permission_classes = [PermissionA, PermissionA]
def get(self, request, *args, **kwargs):
return Response({"code": 0, "data": {"user": None, 'list': [1, 2, 3]}})
class PayView(APIView):
authentication_classes = [TokenAuthentication, ]
permission_classes = [PermissionA, ]
def get(self, request, *args, **kwargs):
return Response({"code": 0, "data": "數(shù)據(jù)..."})
關(guān)于 has_object_permission【欠】
當(dāng)我們使用drf來(lái)編寫(xiě) 視圖類(lèi)時(shí),如果是繼承 APIView,則 has_object_permission不會(huì)被執(zhí)行(沒(méi)用),例如:
from rest_framework.views import APIView
class PayView(APIView):
authentication_classes = [TokenAuthentication, ]
permission_classes = [PermissionA, ]
def get(self, request, *args, **kwargs):
return Response({"code": 0, "data": "數(shù)據(jù)..."})
但是,當(dāng)我們后期學(xué)習(xí)了 視圖類(lèi)的各種騷操作之后,發(fā)現(xiàn)視圖也可以繼承 GenericAPIView,此時(shí) 有可能 會(huì)執(zhí)行 has_object_permission 用于判斷是否有權(quán)限訪問(wèn)某個(gè)特定ID的對(duì)象(學(xué)完視圖后,再細(xì)講)。
調(diào)用 self.get_object 方法時(shí),會(huì)按照 permission_classes中權(quán)限組件的順序,依次執(zhí)行他們的 has_object_permission 方法。
self.get_object其實(shí)就根據(jù)用戶傳入的 pk,搜索并獲取某個(gè)對(duì)象的過(guò)程。
全局配置
REST_FRAMEWORK = {
"DEFAULT_PERMISSION_CLASSES":["xxxx.xxxx.xx.類(lèi)名","xxxx.xxxx.xx.類(lèi)名",]
}
底層源碼實(shí)現(xiàn)
小結(jié)
-
請(qǐng)求的封裝
-
版本的處理
-
過(guò)程:選擇版本處理類(lèi),獲取用戶傳入的版本信息
-
結(jié)果:在
request.version = 版本、request.versioning_scheme=版本處理類(lèi)的對(duì)象
-
-
認(rèn)證組件,在視圖執(zhí)行之前判斷用戶是否認(rèn)證成功。
-
過(guò)程:執(zhí)行所有的認(rèn)證類(lèi)中的
authenticate方法-
返回None,繼續(xù)執(zhí)行后續(xù)的認(rèn)證類(lèi)(都未認(rèn)證成功,request.user 和 auth有默認(rèn)值,也可以全局配置)
-
返回2個(gè)元素的元組,中斷
-
拋出
AuthenticationFailed,中斷
-
-
結(jié)果:在
request.user和request.auth賦值(后續(xù)代碼可以使用)
-
-
權(quán)限
-
過(guò)程:執(zhí)行所有的權(quán)限類(lèi)的
has_permission方法,只有所有都返回True時(shí),才表示具有權(quán)限 -
結(jié)果:有權(quán)限則可以執(zhí)行后續(xù)的視圖,無(wú)權(quán)限則直接返回 自定義的錯(cuò)誤信息
-
-
浙公網(wǎng)安備 33010602011771號(hào)