DRF 版本、認證、權限、限制、解析器和渲染器
目錄
- 一.DRF之版本控制
- 為什麽要有版本控制?
- DRF提供的版本控制方案
- 版本的使用
- 全局配置
- 局部配置(使用較少)
- 二.DRF之認證
- 內置的認證
- 步驟
- 三.DRF之權限
- 1.自定義一個權限類
- 2.權限 局部配置
- 3.權限 全局配置
- 四.DRF之限制
- 1.使用自定義限制類
- 1.1自定義一個限制類
- 1.2限制 局部配置
- 1.3限制 全局配置
- 2.使用內置限制類
- 2.1定義內置限制類
- 2.2全局配置
- 1.使用自定義限制類
- 五.DRF之分頁
- 1.為什麽要使用分頁
- 2.DRF使用分頁器
- 2.1分頁模式
- 2.2全局配置
- 2.3局部配置
- 3.DRF內置分頁器
- 3.1
PageNumberPagination
- 3.2
LimitOffsetPagination
- 3.3
CursorPagination
- 3.1
- 六.解析器和渲染器
- 七.對DRF中的
request
對象的相關總結- 1.查看源碼
- 2.總結
- 八.版本,認證,權限,限制,分頁 -- 源碼查看方法
- 九.補充知識
- 1.
issubset()
- 2.語法糖
setter
,getter
,deleter
- 3.ORM之
update_or_create()
- 4.
assert
斷言 - 5.while循環測試
- 6.ORM的QuerySet操作
- 1.
一.DRF之版本控制
為什麽要有版本控制?
API版本控制允許我們在不同的客戶端之間更改行為(同一個接口的不同版本會返回不同的數據). DRF提供了許多不同的版本控制方案.
可能會有一些客戶端因為某些原因不再維護了, 但是我們後端的接口還要不斷的更新叠代, 這個時候通過版本控制返回不同的內容就是一種不錯的解決方案.
DRF提供的版本控制方案
DRF提供了五種版本控制方案, 如下:
版本的使用
全局配置
settings.py
文件中進行全局配置
除非明確設置, 否則DEFAULT_VERSIONING_CLASS
值為None, 此例中的request.version
將會始終返回None.
REST_FRAMEWORK = { # 配置默認使用的版本控制方案: URLPathVersioning 'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning', 'DEFAULT_VERSION': 'v1', # 默認的版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 有效的版本 'VERSION_PARAM': 'version', # 版本的參數名與URL conf中一致 }
urls.py
文件中:
from django.conf.urls import url
from django.contrib import admin
from bms import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^(?P<version>[v1|v2]+)/book/$', # 版本的參數名與URL conf中一致
views.BookViewSet.as_view(actions={'get': 'list', 'post': 'create'})),
url(r'^(?P<version>[v1|v2]+)/book/(?P<pk>\d+)$',
views.BookViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
bms/views.py
文件中:
我們在中可以通過訪問request.version
來獲取當前請求的具體版本, 然後根據不同的版本來返回不同的內容.
- 只要在
settings.py
中配置了版本信息, 在視圖(bms/views.py
)中就能通過request.version
獲取當前版本 get_serilaizer_class
方法可以根據不同版本返回不同的序列化類get_queryset
方法可以根據不同的版本返回不同的數據控制
思考: 為什麽可以直接用request.version
拿到版本號? --> 這就是我們看源碼的目的
from bms import models
from bms.modelserializers import BookModelSerializer
from rest_framework.viewsets import ModelViewSet
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BookModelSerializer
def get_serializer_class(self):
"""不同版本 使用不同的序列化類"""
if self.request.version == 'v1':
return BookModelSerializer1
return self.serializer_class
def get_queryset(self):
"""不同的版本可以 返回不同的數據控制"""
if self.request.version == 'v1':
return models.Book.objects.all()[:3]
return self.queryset.all()
局部配置(使用較少)
我們可以在一個單獨的視圖上設置版本控制方案. 通常, 我們==不需要這樣做==, 因為在全局範圍內使用一個版本控制方案更有意義. 如果我們確實需要這樣做, 請使用versioning_class
屬性.
- 導入版本控制方案:
from rest_framework.versioning import 版本控制方案
versioning_class=版本控制方案
- 定義
get_queryset
方法 或get_serializer_class
方法
==註意==: 版本控制方案有五種
# AcceptHeaderVersioning
# -- 將版本信息放在請求頭中
URLPathVersioning
# -- 將版本信息放在URL中,如: 127.0.0.1:8000/v1/book
NamespaceVersioning
# -- 通過namespace來區分版本
HostNameVersioning
# -- 通過主機名來區分版本
QueryParameterVersioning
# -- 通過URL查詢參數來區分版本 如: 127.0.0.1:8000/authors/?version=1
my_app/views.py
文件中:
# 第一步
from rest_framework.versioning import URLPathVersioning
class AuthorViewSet(ModelViewSet):
queryset = models.Author.objects.all()
serializer_class = AuthorModelSerializer
# 第二步
versioning_class = URLPathVersioning
# 第三步
def get_queryset(self):
"""不同的版本可以 返回不同的數據控制量"""
pass
# 第三步
def get_serializer_class(self):
"""不同版本 使用不同的序列化類"""
pass
urls.py
文件中:
from django.conf.urls import url
from bms import views
urlpatterns = [
url(r'^(?P<version>[v1|v2]+)/authors/$',
views.AuthorViewSet.as_view(actions={'get': 'list', 'post': 'create'})),
url(r'^(?P<version>[v1|v2]+)/authors/(?P<pk>\d+)$',
views.AuthorViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
二.DRF之認證
身份驗證是將==傳入請求==與==一組標識憑據(如請求來自的用戶或其簽名的令牌)==相關聯的機制. 然後 權限 和 限制 組件決定是否拒絕這個請求.
簡單來說:
- 認證 -- 確定了你是誰
- 權限 -- 確定你能不能訪問某個接口
- 限制 -- 確定你訪問某個接口的頻率
認證的目的: 告訴服務端你是誰
思考: 有個問題, 我們的Django和Vue項目是分離的, 它們很可能是建立在兩個不同的服務器上的, 這種前後端分離的情況我們該怎樣存cookie和session呢? 對於這種情況, 我們一般是通過Vue發ajax請求來把數據保存到cookie/session中的. 還有一種解決辦法, 當前端請求到來時, 前端發送過來一個==token==值給後端, 後端通過查詢這個token值(數據庫中匹配)就可以確定你是誰了.
總結: 我們通過token值來確定前端來訪問的用戶是誰.
內置的認證
步驟
1.新創建一個app: auth_demo
2.在settings.py
中註冊auth_demo
這個app
3.auth_demo/models.py
中的表結構設計:
from django.db import models
# 用戶表
class UserInfo(models.Model):
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=32)
vip = models.BooleanField(default=False)
token = models.CharField(max_length=128, null=True, blank=True)
4.二級路由
根目錄下的urls.py
:
from django.conf.urls import url, include
urlpatterns = [
url(r'^users/', include('auth_demo.urls')),
]
auth_demo/urls.py
:
from django.conf.urls import url
from auth_demo import views
urlpatterns = [
url(r'reg/$', views.RegView.as_view()), # 註冊
url(r'login/$', views.LoginView.as_view()), # 登錄
url(r'test_auth/$', views.TestAuthView.as_view()), # 測試登錄認證
]
5.視圖函數 auth_demo/views.py
:
註冊
:
from rest_framework.views import APIView
from rest_framework.response import Response
from auth_demo import models
class RegView(APIView):
"""只支持註冊用戶"""
def post(self, request):
# 1.獲取用戶註冊的數據
name = request.data.get('name')
pwd = request.data.get('pwd')
re_pwd = request.data.get('re_pwd')
if name and pwd:
# 2.判斷密碼和確認密碼是否一致
if pwd == re_pwd:
# 3.創建用戶
models.UserInfo.objects.create(name=name, pwd=pwd)
# 4.返回響應
return Response('註冊成功')
else:
return Response('兩次密碼不一致')
else:
return Response('無效的參數')
登錄
:
class LoginView(APIView):
"""只支持用戶登錄"""
def post(self, request):
# 1.通過request.data獲取前端提交的數據
name = request.data.get('name')
pwd = request.data.get('pwd')
if name and pwd:
# 2.從數據庫中進行篩選匹配
user_obj = models.UserInfo.objects.filter(name=name, pwd=pwd).first()
if user_obj:
# 3.登錄成功,生成token(時間戳 + Mac地址)
import uuid
token = uuid.uuid1().hex
# 4.把token保存到用戶表中
user_obj.token = token
user_obj.save()
# 5.返回響應(包括狀態碼和token值)
return Response({'error_no': 0, 'token': token})
else:
# 3.用戶名或密碼錯誤,登錄失敗
return Response({'error_no': 1, 'error': '用戶名或密碼錯誤'})
else:
return Response('無效的參數')
6.自定義認證類 auth_demo/auth.py
:
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from auth_demo import models
class MyAuth(BaseAuthentication):
def authenticate(self, request):
# 1.通過request.query_params獲取前端的url參數
token = request.query_params.get('token')
if token:
# 2.如果請求的url中攜帶了token參數
user_obj = models.UserInfo.objects.filter(token=token).filter()
if user_obj:
# 3.token是有效的
return user_obj, token # 必須返回一個元組: (user_obj, token) --> (request.user, request.auth)
else:
raise AuthenticationFailed('無效的token')
else:
raise AuthenticationFailed('請求的URL中必須攜帶token參數')
7.局部認證 配置: auth_demo/views.py
註意: ==局部配置的優先級高於全局配置==
from auth_demo.auth import MyAuth
# 登錄之後才能看到的數據接口
class TestAuthView(APIView):
authentication_classes = [MyAuth, ] # 配置局部認證, 全局認證在settings.py文件中配置
def get(self, request):
print(request.user.name) # request.user 用戶對象
print(request.auth) # reequest.auth 設置的token值
return Response('這個視圖裏面的數據只有登錄以後才能看到!')
8.全局配置: settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ], # 認證 全局配置
}
三.DRF之權限
1.自定義一個權限類
auth_demo/permissions.py
:
"""
自定義一個權限組件
"""
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission): # 繼承BasePermission
message = '只有VIP才能訪問'
def has_permission(self, request, view): # 必須實現has_permission方法
# 只有通過權限驗證的用戶才能訪問has_permission方法
if not request.auth: # request.auth --> token值
return False
# request.user --> 當前通過token認證的用戶(UserInfo表中的用戶對象)
if request.user.vip:
# 是VIP就通過
return True
else:
# 不是VIP就拒絕
return False
2.權限 局部配置
auth_demo/views.py
:
from auth_demo.auth import MyAuth
from auth_demo.permissions import MyPermission
# 登錄之後才能看到的數據接口
class TestAuthView(APIView):
authentication_classes = [MyAuth, ] # 配置局部認證, 全局認證在settings.py文件中配置
permission_classes = [MyPermission, ] # 配置局部權限, 全局權限在settings.py文件中配置
def get(self, request):
print(request.user.name)
print(request.auth)
return Response('這個視圖裏面的數據只有登錄以後才能看到!')
3.權限 全局配置
settings.py
:
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ],
'DEFAULT_PERMISSION_CLASSES': ['auth_demo.permissions.MyPermission', ]
}
四.DRF之限制
1.使用自定義限制類
DRF內置了基本的限制類,首先我們自己動手寫一個限制類,熟悉下限制組件的執行過程。
1.1自定義一個限制類
auth_demo/throttle.py
:
import time
# 訪問記錄
VISIT_RECORD = {}
class MyThrottle(object):
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1.拿到當前請求的ip作為VISIT_RECORD的key
ip = request.META.get('REMOTE_ADDR')
# 2.拿到當前請求的時間戳
now = time.time()
# 3.如果是請求是第一次來訪問
if ip not in VISIT_RECORD:
# {ip:[]}
VISIT_RECORD[ip] = []
return True
# 4.把當前請求的訪問記錄拿出來保存到一個變量(訪問歷史)中
history = VISIT_RECORD[ip]
self.history = history
# 5.循環訪問歷史,把超過10秒鐘的請求時間去掉
while history and now - history[-1] > 10:
history.pop()
# 6.此時,history中只保存了最近10秒鐘的訪問記錄
if len(history) >= 3:
# (1)history中存放了3條及以上的歷史記錄,拒絕訪問
return False
else:
# (2)history中的歷史記錄不到3條,存儲當前歷史記錄
self.history.insert(0, now)
return True
def wait(self):
"""告訴客戶端還需要等待多久"""
now = time.time()
return self.history[-1] + 10 - now
1.2限制 局部配置
auth_demo/views.py
:
from auth_demo.throttle import MyThrottle
# 登錄之後才能看到的數據接口
class TestView(APIView):
throttle_classes = [MyThrottle, ] # 配置局部限制, 全局限制在settings.py文件中配置
def get(self, request):
return Response('你成功了!這個視圖裏面的數據只有登錄以後才能看到!')
1.3限制 全局配置
settings.py
:
# 在settings.py中設置rest framework相關配置項
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ], # 認證 全局配置
'DEFAULT_PERMISSION_CLASSES': ['auth_demo.permissions.MyPermission', ], # 權限 全局配置
'DEFAULT_THROTTLE_CLASSES': ['auth_demo.throttle.MyThrottle'], # 限制 全局配置
}
2.使用內置限制類
2.1定義內置限制類
auth_demo/throttle.py
:
#使用內置限制類
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
scope = "throttle"
def get_cache_key(self, request, view):
return self.get_ident(request)
2.2全局配置
REST_FRAMEWORK = {
# 內置限制類的全局配置
"DEFAULT_THROTTLE_RATES": {
"throttle": "5/m", # 這裏的key要與throttle.py文件中的scope="throttle"相對應
},
}
五.DRF之分頁
1.為什麽要使用分頁
我們的數據表中可能會有成千上萬條數據, 當我們訪問某張表的所有數據時,我們不大可能需要一次把所有數據都展示出來, 因為數據量很大, 對服務端的內存壓力比較大並且網絡傳輸過程中耗時也會比較大.
通常我們會希望一部分一部分去請求數據, 也就是我們常說的一頁一頁獲取數據並展示出來.
2.DRF使用分頁器
2.1分頁模式
REST framework中提供了三種分頁模式:
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination
2.2全局配置
REST_FRAMEWORK = {
# 默認使用的分頁類
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 默認每頁的數據個數
'PAGE_SIZE': 100
}
2.3局部配置
我們可以在視圖類中進行局部配置
class PublisherViewSet(ModelViewSet):
queryset = models.Publisher.objects.all()
serializer_class = PublisherModelSerializer
pagination_class = PageNumberPagination # 註意不是列表(只能有一個分頁模式)
3.DRF內置分頁器
3.1PageNumberPagination
按頁碼數分頁, 第n頁, 每頁顯示m條數據.
例如: http:127.0.0.1:8000/v1/book/?page=2&size=1
分頁器
# bms/pagination.py
from rest_framework.pagination import PageNumberPagination
class MyPageNumber(PageNumberPagination):
page_size = 2 # 每頁顯示多少條
page_size_query_param = 'size' # URL中每頁顯示條數的參數
page_query_param = 'page' # URL中頁碼的參數
max_page_size = None # 最大頁碼數限制
視圖
# bms/views.py
from bms.pagination import MyPageNumber
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all().order_by('id')
serializer_class = BookModelSerializer
"""
普通分頁器
"""
pagination_class = MyPageNumber
3.2LimitOffsetPagination
分頁, 在n位置, 向後查看m條數據.
例如: 127.0.0.1:8000/v1/book/?offset=2&limit=2
分頁器
# bms/pagination.py
from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffset(LimitOffsetPagination):
default_limit = 1
limit_query_param = 'limit'
offset_query_param = 'offset'
max_limit = 999
視圖
# bms/views.py
from bms.pagination import MyLimitOffset
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all().order_by('id')
serializer_class = BookModelSerializer
"""
offset分頁器
"""
pagination_class = MyLimitOffset
3.3CursorPagination
加密分頁, 把上一頁和下一頁的id值記住.
分頁器
# bms/pagination.py
from rest_framework.pagination import CursorPagination
class MyCursorPagination(CursorPagination):
cursor_query_param = 'cursor'
page_size = 1
ordering = '-id' # 重寫要排序的字段
視圖
# bms/views.py
from bms.pagination import MyCursorPagination
class BookViewSet(ModelViewSet):
queryset = models.Book.objects.all().order_by('id')
serializer_class = BookModelSerializer
"""
加密分頁器
"""
pagination_class = MyCursorPagination
六.解析器和渲染器
參考資料
略.
七.對DRF中的request
對象的相關總結
1.查看源碼
1.APIView
類
2.1.1initialize_request
方法
2.1.2Request
類
2.2.1initial
方法
2.總結
request.data
-- 前端post提交的數據request.query_params
-- 前端頁面的url參數request.user
-- 通過認證的用戶對象request.auth
-- 前端發過來的token值request.version
-- 版本號(如: v1, v2)requst.versioning_scheme
-- 版本控制方案(5個)
八.版本,認證,權限,限制,分頁 -- 源碼查看方法
from rest_framework.versioning import * # 查看 版本 源碼
from rest_framework.authentication import * # 查看 認證 源碼
from rest_framework.permissions import * # 查看 權限 源碼
from rest_framework.throttling import * # 查看 限制 源碼
from rest_framework.pagination import * # 查看 分頁 源碼
from django.core.handlers.wsgi import WSGIRequest # 查看Django自己的request 源碼
from rest_framework import settings # 查看settings.py文件中的配置項(版本,認證,權限,等等)
九.補充知識
1.issubset()
描述: issubset()
方法用於判斷集合的所有元素是否都包含在指定集合中, 如果是則返回True, 否則返回False.
語法:
set.issubset(set)
參數:
set
-- 必需, 要比較查找的集合
返回值: 返回布爾值, 如果都包含返回True, 否則返回False.
實例說明: 判斷集合x的所有元素是否都包含在集合u中.
x = {"a", "b", "c"}
y = {"f", "e", "d", "c", "b", "a"}
z = x.issubset(y)
print(z)
# 執行結果:
# True
x = {"a", "b", "c"}
y = {"f", "e", "d", "c", "b"}
z = x.issubset(y)
print(z)
# 執行結果:
# False
2.語法糖setter
,getter
,deleter
實例說明:
- 例1:
class Person:
def __init__(self, name):
self.name = name
p1 = Person('王乃卉')
print(p1.name)
# 執行結果:
# 王乃卉
- 例2:
class Person:
def __init__(self, name):
self.name = name
@property # getter -- 獲取屬性
def age(self):
print('get age called')
return self._age
@age.setter # setter -- 設置屬性
def age(self, value):
print('set age called')
if not isinstance(value, int):
raise TypeError('Excepted an int')
self._age = value
p2 = Person('王力宏') # 實例化
p2.age = 19 # 設置屬性
print(p2.age) # 獲取屬性
##執行結果:
# set age called
# get age called
# 19
- 例3:
class Person:
def __init__(self, age):
self.age = age
@property # getter -- 獲取屬性
def age(self):
print('get age called')
return self._age
@age.setter # setter -- 設置屬性
def age(self, value):
print('set age called')
if not isinstance(value, int):
raise TypeError('Excepted an int')
self._age = value
p3 = Person(19) # 實例化,設置屬性
print(p3.age) # 獲取屬性
p3.age = 22 # 設置屬性
print(p3.age) # 獲取屬性
##執行結果:
# set age called
# get age called
# 19
# set age called
# get age called
# 22
- 例4:
class Person:
def __init__(self):
self.__name = None
@property # 訪問屬性
def name(self):
return self.__name
@name.setter # 設置屬性
def name(self, value):
self.__name = value
@name.deleter # 刪除屬性
def name(self):
del self.__name
p = Person()
print(p.name) # 訪問屬性 --> None
p.name = '王乃卉' # 設置屬性
print(p.name) # 訪問屬性 --> 王乃卉
del p.name # 刪除屬性
#print(p.name) # 訪問屬性 --> 報錯: 對象沒有該屬性
3.ORM之update_or_create()
# 檢查是否有這條記錄,有則更新(需要defaults參數,字典類型),無則新增
models.UserToken.objects.update_or_create(user=user_obj, defaults={
"token": access_token
})
4.assert
斷言
根據Python 官方文檔解釋 : "Assert statements are a convenient way to insert debugging assertions into a program".
語法:
assert condition
用來讓程序測試這個condition
, 如果condition
為False, 則raise一個AssertionError
出來. 邏輯上等同於:
if not condition:
raise AssertionError('error_message')
實例說明:
>>> assert 1==1
>>> assert 1==0
Traceback (most recent call last):
File "<input>", line 1, in <module>
AssertionError
>>> assert True
>>> assert False
Traceback (most recent call last):
File "<input>", line 1, in <module>
AssertionError
>>> assert 1<2
>>> assert 1>2
Traceback (most recent call last):
File "<input>", line 1, in <module>
AssertionError
5.while循環測試
對比以下兩個例子並思考為什麽執行結果會不同.
- 例1:
lst1 = []
while lst1[-1] and lst1:
print('這裏是lst1')
# 執行結果:
# IndexError: list index out of range
- 例2:
lst2 = []
while lst2 and lst2[-1]:
print('這裏是lst2')
# 由於lst2為空,所以不執行while循環
6.ORM的QuerySet操作
記住一點: QuerySet
切片之後不能再order_by
了.
DRF 版本、認證、權限、限制、解析器和渲染器