接下來學(xué)習(xí)RestFramework框架中的認(rèn)證芽狗、權(quán)限隘膘、頻率組件的使用
一旗芬、首先實(shí)現(xiàn)用戶login登錄認(rèn)證功能
做用戶登錄認(rèn)證功能可以通過session纳击、cookie和token三種形式,下面的login認(rèn)證基于token實(shí)現(xiàn)
- 關(guān)鍵點(diǎn)
-- 首先需要設(shè)計(jì)用戶表昧识、用戶token表钠四,用戶表需要包含用戶類型,用戶token表包含用戶的token值
-- 用戶的token值應(yīng)該是一個(gè)隨機(jī)的跪楞、動(dòng)態(tài)的字符串
-- 用戶認(rèn)證成功需要將用戶信息和對(duì)應(yīng)的token值返回缀去,后續(xù)訪問url就可以通過token值來判斷用戶的狀態(tài)
附:models.py
from django.db import models
class Userinfo(models.Model):
name = models.CharField(max_length=32)
pwd = models.CharField(max_length=64)
user_type = models.IntegerField(choices=((1,"common_user"),(2,"VIP_user"),(3,"SVIP_user")))
class UserToken(models.Model):
user = models.OneToOneField(to="Userinfo")
token = models.CharField(max_length=128)
- view.py認(rèn)證邏輯
from rest_framework.views import APIView
from rest_framework.response import Response
import uuid
class LoginView(APIView):
"""
1000:成功
1001:用戶名或者密碼錯(cuò)誤
1002:異常錯(cuò)誤
"""
def post(self, request):
#自定義的返回體數(shù)據(jù)
ret = {"code": 1000, "msg": None, "user": None}
try:
print(request.data) # 重裝后的request侣灶,request.data從中取所需的數(shù)據(jù)
name = request.data.get("name")#獲取前端用戶名
pwd = request.data.get("pwd")#獲取前端密碼
#數(shù)據(jù)庫校驗(yàn)用戶名密碼是否正確
user_obj = models.Userinfo.objects.filter(name=name, pwd=pwd).first()
if user_obj:
#通過uuid模塊獲得隨機(jī)字符串作為token值
random_str = uuid.uuid4()
#UserToken表中有該用戶的信息就用新token值覆蓋,沒有就創(chuàng)建數(shù)據(jù)
models.UserToken.objects.update_or_create(user=user_obj, defaults={"token": random_str})
ret["user"] = user_obj.name
ret["token"] = random_str
else:
ret["code"] = 1001
ret["msg"] = "用戶名或者密碼錯(cuò)誤"
except Exception as e:
ret["code"] = 1002
ret["msg"] = str(e)
return Response(ret)
這樣就實(shí)現(xiàn)了用戶的login認(rèn)證缕碎。褥影。。咏雌。凡怎。。赊抖。栅贴。。
二熏迹、基于RestFramework的認(rèn)證組件做視圖處理類的user認(rèn)證
首先看一下UserAuth的具體用法:
- 關(guān)鍵點(diǎn):
--視圖處理類下定義名為authentication_classes屬性,屬性值是一個(gè)列表或者元組
--寫一個(gè)UserAuth(類名隨便)認(rèn)證的類凝赛,將類名添加到authentication_classes列表中
--UserAuth認(rèn)證類必須有一個(gè)authenticate(self,request)方法,這個(gè)方法下寫用戶認(rèn)證邏輯注暗,認(rèn)證成功:可以返回?cái)?shù)據(jù),也可以什么都不做墓猎。認(rèn)證失敗:捆昏,必須拋異常
-- UserAuth類繼承BaseAuthentication類,其實(shí)就是給類加了一個(gè)authenticate_header方法毙沾,沒理由骗卜,源碼要求!
這四點(diǎn)你一定看不懂是什么意思左胞,很正常寇仓,這都是RestFramework源碼給設(shè)定的特定規(guī)則,稍后認(rèn)證源碼流程分析會(huì)一一分析解答
view.py代碼示例:代碼實(shí)現(xiàn)了book視圖類的用戶認(rèn)證功能
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
#用于認(rèn)證的類
class UserAuth(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
usertoken_obj = models.UserToken.objects.filter(token=token).first()
if usertoken_obj:
return usertoken_obj.user, usertoken_obj.token
else:
raise AuthenticationFailed("用戶認(rèn)證失敗烤宙,您無權(quán)訪問1榉场!躺枕!")
# book視圖處理類
class Booklist(ModelViewSet):
authentication_classes = [UserAuth, ]
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
接下來一張圖帶你透析認(rèn)證組件:
- 這樣的寫法只是給book視圖類加上了認(rèn)證服猪,可以將認(rèn)證類單獨(dú)卸載一個(gè)py文件中,然后在全局settings中添加對(duì)應(yīng)的配置項(xiàng)拐云,這樣就可以做到對(duì)所有的視圖類添加認(rèn)證功能了0罩怼!叉瘩!
根據(jù)實(shí)際需求選擇合適的方法I排痢!房揭!
app001-utils-Userauth.py
from app001 import models
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
class UserAuth(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
usertoken_obj = models.UserToken.objects.filter(token=token).first()
if usertoken_obj:
return usertoken_obj.user, usertoken_obj.token
else:
raise AuthenticationFailed("用戶認(rèn)證失敗备闲,您無權(quán)訪問I味恕!恬砂!")
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'app001.utils.Userauth.UserAuth',
)
}
view.py
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
# book視圖處理類
class Booklist(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
三咧纠、基于RestFramework的認(rèn)證組件做視圖處理類的權(quán)限認(rèn)證
首先看一下UserAuth的具體用法:
- 關(guān)鍵點(diǎn):
--視圖處理類下定義名為permission_classes屬性,屬性值是一個(gè)列表或者元組
--寫一個(gè)UserAuth(類名隨便)認(rèn)證的類泻骤,將類名添加到permission_classes列表中
--UserAuth認(rèn)證類必須有一個(gè)has_permission(self,request,view)方法,這個(gè)方法下寫用戶認(rèn)證邏輯漆羔,認(rèn)證成功:返回true。認(rèn)證失敗:狱掂,返回false演痒。
permission源碼執(zhí)行流程:
- app001-utils-permission_class.py
from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message="您沒有訪問權(quán)限!"
def has_permission(self,request,view):
if request.user.user_type >= 2:
return True
return False
- view.py
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
from app001.utils.permission_class import SVIPPermission
# book視圖處理類
class Booklist(ModelViewSet):
permission_classes = [SVIPPermission,]
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
四趋惨、基于RestFramework的認(rèn)證組件做視圖處理類的訪問頻率限制
1鸟顺、訪問頻率限制和認(rèn)證、權(quán)限的執(zhí)行流程一樣器虾,都是restframework源碼提供的一種書寫格式讯嫂,再此就不一一贅述。
- app001-utils-throttle_classes.py
import time
from rest_framework.throttling import BaseThrottle
VISIT_RECORD = {}
class VisitThrottle(BaseThrottle):
def __init__(self):
self.history = None
def allow_request(self, request, view):
# 1. 拿到用戶請(qǐng)求的IP
# print(request.META)
ip = request.META.get("REMOTE_ADDR")
# 2. 當(dāng)前請(qǐng)求的時(shí)間
now = time.time()
# 3. 記錄訪問的歷史
if ip not in VISIT_RECORD:
VISIT_RECORD[ip] = []
history = VISIT_RECORD[ip]
self.history = history
# [11:07:20, 10:07:11, 10:07:06, 10:07:01]
while history and now - history[-1] > 60:
history.pop()
# 判斷用戶在一分鐘的時(shí)間間隔內(nèi)是否訪問超過3次
if len(history) >= 3:
return False
history.insert(0, now)
return True
def wait(self):
# 當(dāng)前訪問時(shí)間
ctime = time.time()
# 訪問時(shí)間歷史記錄 self.history
return 60 - (ctime - self.history[-1])
- view.py
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
from app001.utils.throttle_classes import VisitThrottle
# book視圖處理類
class Booklist(ModelViewSet):
throttle_classes = [VisitThrottle, ]
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
2兆沙、基于RestFramework給我們提供了幾種頻率控制組件欧芽,省去了我們?cè)谧约簩懥?/h4>
-
SimpleRateThrottle類,A simple cache implementation, that only requires
.get_cache_key()
to be overridden.
-
AnonRateThrottle類葛圃,Limits the rate of API calls that may be made by a anonymous users.
-
UserRateThrottle類千扔,Limits the rate of API calls that may be made by a given user.
-
ScopedRateThrottle類,Limits the rate of API calls by different amounts for various parts of
the API.
.get_cache_key()
to be overridden.
the API.
app001-utils-throttle_classes.py
from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
scope="visit_rate"
def get_cache_key(self,request, view):
return self.get_ident(request)
settings.py
REST_FRAMEWORK = {
"DEFAULT_THROTTLE_CLASSES": ("app001.utils.throttle_classes.VisitThrottle",),
"DEFAULT_THROTTLE_RATES": {
"visit_rate": "10/m",//頻率設(shè)置
},
}
view.py
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
from app001.utils.throttle_classes import VisitThrottle
# book視圖處理類
class Booklist(ModelViewSet):
throttle_classes = [VisitThrottle, ]
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
五库正、基于RestFramework的視圖處理類的分頁組件
Restframework提供了幾種很好的分頁方式:
- 基于頁碼的分頁的 PageNumberPagination類
- 基于limit offset 做分頁的 LimitOffsetPagination類
- 基于游標(biāo)的分頁的 CursorPagination類曲楚。但這種方式存在性能問題,如果用戶吧page給改的很大诀诊,查詢速度就會(huì)很慢洞渤。還有一種頁碼加密的方式
1、PageNumberPagination類的使用
- 方法一属瓣、視圖類繼承APIView時(shí)
-- 參考的API:
GET請(qǐng)求:http://127.0.0.1:8000/booklist/?page=2
from rest_framework.views import APIView
from app001 import models
# rest_framework重裝的response
from rest_framework.response import Response
from app001.serializers.bookserializer import BooklistSerializer
from rest_framework.pagination import PageNumberPagination
class Booklist(APIView):
def get(self, request):
class MyPageNumberPagination(PageNumberPagination):
page_ size = 2 //指定的每頁顯示數(shù)
page_query_param = "page" //url欄的頁碼參數(shù)
page_size_query_param = "size" //獲取url參數(shù)中設(shè)置的每頁顯示數(shù)據(jù)條數(shù)
max_page_size = 5 //最大支持的每頁顯示的數(shù)據(jù)條數(shù)
book_obj = models.Book.objects.all()
# 實(shí)例化
pnp = MyPageNumberPagination()
# 分頁
paged_book_list = pnp.paginate_queryset(book_obj, request)
bs = BooklistSerializer(paged_book_list, many=True)
data = bs.data # 序列化接口
return Response(data)
- 方式二载迄、視圖類繼承ModelViewSet時(shí)(少用)
from rest_framework.viewsets import ModelViewSet
from rest_framework.pagination import PageNumberPagination
class MyPageNumberPagination(PageNumberPagination):
page_size = 3
# book視圖處理
class Booklist(ModelViewSet):
pagination_class = MyPageNumberPagination
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer
2、LimitOffsetPagination類的使用
- 這種分頁樣式反映了查找多個(gè)數(shù)據(jù)庫記錄時(shí)使用的語法抡蛙』っ粒客戶端包含 “l(fā)imit” 和 “offset” 查詢參數(shù)。limit 表示要返回的 數(shù)據(jù) 的最大數(shù)量粗截,并且等同于其他樣式中的 page_size惋耙。offset 指定查詢的起始位置與完整的未分類 數(shù)據(jù) 集的關(guān)系。多用于返回的內(nèi)容是靜態(tài)的,或者不用實(shí)時(shí)返回?cái)?shù)據(jù)最新的變化绽榛。Google Search 和一些論壇用了這種方式:
- 參考API:
GET請(qǐng)求:http://127.0.0.1:8000/booklist/?limit=3&offset=3
Response響應(yīng):{ "count": 7,"next": "http://127.0.0.1:8000/booklist/?limit=3&offset=6", "previous": "http://127.0.0.1:8000/booklist/?limit=3","results": [...]
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import LimitOffsetPagination
class MyPageNumberPagination(LimitOffsetPagination):
max_limit = 3 # 最大限制默認(rèn)是None
default_limit = 2 # 設(shè)置每一頁顯示多少條
limit_query_param = 'limit' # 往后取幾條
offset_query_param = 'offset' # 當(dāng)前所在的位置
class Booklist(APIView):
def get(self, request):
book_obj = models.Book.objects.all()
# 實(shí)例化
pnp = MyPageNumberPagination()
# 分頁
paged_book_list = pnp.paginate_queryset(book_obj, request)
bs = BooklistSerializer(paged_book_list, many=True)
data = bs.data # 序列化接口
# return Response(data) #不含上一頁下一頁
return pnp.get_paginated_response(data)
- LimitOffsetPagination 類包含一些可以被覆蓋以修改分頁樣式的屬性湿酸。要設(shè)置這些屬性,應(yīng)該繼承 LimitOffsetPagination 類灭美,然后像上面那樣啟用你的自定義分頁類推溃。
default_limit- 一個(gè)數(shù)字值,指定客戶端在查詢參數(shù)中未提供的 limit 届腐。默認(rèn)值與 PAGE_SIZE setting key 相同铁坎。
limit_query_param - 一個(gè)字符串值,指示 “l(fā)imit” 查詢參數(shù)的名稱犁苏。默認(rèn)為 'limit'硬萍。
offset_query_param - 一個(gè)字符串值,指示 “offset” 查詢參數(shù)的名稱围详。默認(rèn)為 'offset'朴乖。
max_limit - 一個(gè)數(shù)字值,表示客戶端可以要求的最大允許 limit助赞。默認(rèn)為 None寒砖。
- 了解LIMIT / OFFSET的實(shí)際工作原理。為此嫉拐,我們舉例說明,假如有大約1000個(gè)符合要求的結(jié)果魁兼。你若要前100數(shù)據(jù)婉徘,這很容易,這意味著它只返回與結(jié)果集匹配的前100行咐汞。但是現(xiàn)在想要900-1000之間的數(shù)據(jù)盖呼。數(shù)據(jù)庫現(xiàn)在必須遍歷前900個(gè)結(jié)果才能開始返回(因?yàn)樗鼪]有指針告訴它如何獲得結(jié)果900)』海總之几晤,LIMIT / OFFSET在大型結(jié)果集上非常慢。
3植阴、CursorPagination類的使用
- 現(xiàn)在很多場(chǎng)景蟹瘾,查詢結(jié)果在用戶瀏覽過程中是變化的,例如微博時(shí)間線掠手,用戶看的時(shí)候憾朴,可能后一頁的某些微博會(huì)被刪除,而前一頁又增添了新的微博喷鸽。這種情況就不適合用 limitoffset分頁方式了众雷。Facebook 和 Twitter 都采用了基于游標(biāo)的分頁方法。
- 這種方式有以下兩個(gè)特點(diǎn):
-- 1. 查詢的結(jié)果流可能是動(dòng)態(tài)變化的,例如: 時(shí)間線里出現(xiàn)了新的數(shù)據(jù)砾省,或者刪除了數(shù)據(jù)鸡岗,這些變化都可以在 “前一頁” 或者 “后一頁” 上體現(xiàn)出來。
-- 2. Cursor 體現(xiàn)了排序编兄,是持久化的轩性。一般情況下 Cursor 的順序是和時(shí)間有關(guān)。如果你的實(shí)體(例如:微博)可能展現(xiàn)給用戶多種可能的排序(例如:創(chuàng)建時(shí)間或者修改時(shí)間)翻诉,那么則需要?jiǎng)?chuàng)建不同的 Cursor炮姨。
具體實(shí)現(xiàn)時(shí),Cursor 可能分別創(chuàng)建自 創(chuàng)建用戶 或者 修改用戶 字段碰煌。Facebook Relay 用了查詢名稱 + 時(shí)間戳 的 Base64 形式來做 Cursor舒岸。 - CursorPagination 類包含一些可以被覆蓋以修改分頁樣式的屬性。
要設(shè)置這些屬性芦圾,你應(yīng)該繼承 CursorPagination 類蛾派,然后像上面那樣啟用你的自定義分頁類。
-- page_size = 指定頁面大小的數(shù)字值个少。如果設(shè)置洪乍,則會(huì)覆蓋 PAGE_SIZE 設(shè)置。默認(rèn)值與 PAGE_SIZE setting key 相同夜焦。
-- cursor_query_param = 一個(gè)字符串值壳澳,指定 “游標(biāo)” 查詢參數(shù)的名稱。默認(rèn)為 'cursor'.
-- ordering = 這應(yīng)該是一個(gè)字符串或字符串列表茫经,指定將應(yīng)用基于游標(biāo)的分頁的字段巷波。例如: ordering = 'slug'。默認(rèn)為 -created卸伞。該值也可以通過在視圖上使用 OrderingFilter 來覆蓋抹镊。
-關(guān)于限制偏移量和游標(biāo)分頁的分析 請(qǐng)參考:http://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
六、基于RestFramework的視圖處理類響應(yīng)器
- restframework可以根據(jù)url的不同來源做出不同的響應(yīng)荤傲,比如說垮耳,當(dāng)來自瀏覽器端的請(qǐng)求,會(huì)返回一個(gè)頁面遂黍,當(dāng)請(qǐng)求來自于postman等請(qǐng)求軟件時(shí)终佛,返回的是json數(shù)據(jù),這樣的效果是restframework的響應(yīng)器在控制雾家,settings中可以按需配置:
REST_FRAMEWORK={
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer', //響應(yīng)json
'rest_framework.renderers.BrowsableAPIRenderer', //響應(yīng)頁面
),
}
七查蓉、基于RestFramework的視圖處理類的url注冊(cè)器
url注冊(cè)器只是適用于視圖類繼承ModelViewSet類的寫法:
url注冊(cè)器會(huì)生成四條url:
^ ^booklist/$ [name='book-list']
^ ^booklist\.(?P<format>[a-z0-9]+)/?$ [name='book-list'] //可以指定數(shù)據(jù)類型
^ ^booklist/(?P<pk>[^/.]+)/$ [name='book-detail']
^ ^booklist/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='book-detail'] //可以指定單個(gè)數(shù)據(jù)數(shù)據(jù)類型
urls.py
from rest_framework import routers
router = routers.DefaultRouter() //實(shí)例化
router.register("booklist", views.Booklist) //注冊(cè)
urlpatterns = [
url(r'^', include(router.urls)),
]
view.py
from app001 import models
from app001.serializers.bookserializer import BooklistSerializer
# 重裝了APIView
from rest_framework.viewsets import ModelViewSet
# book視圖處理類
class Booklist(ModelViewSet):
queryset = models.Book.objects.all()
serializer_class = BooklistSerializer