基於Django實現 RESTful API 之RestFramework框架3
接下來學習RestFramework框架中的認證、許可權、頻率元件的使用
一、首先實現使用者login登入認證功能
做使用者登入認證功能可以通過session、cookie和token三種形式,下面的login認證基於token實現
-
關鍵點
-- 首先需要設計使用者表、使用者token表,使用者表需要包含使用者型別,使用者token表包含使用者的token值
-- 使用者的token值應該是一個隨機的、動態的字串
-- 使用者認證成功需要將使用者資訊和對應的token值返回,後續訪問url就可以通過token值來判斷使用者的狀態
附: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認證邏輯
from rest_framework.views import APIView from rest_framework.response import Response import uuid class LoginView(APIView): """ 1000:成功 1001:使用者名稱或者密碼錯誤 1002:異常錯誤 """ def post(self, request): #自定義的返回體資料 ret = {"code": 1000, "msg": None, "user": None} try: print(request.data)# 重灌後的request,request.data從中取所需的資料 name = request.data.get("name")#獲取前端使用者名稱 pwd = request.data.get("pwd")#獲取前端密碼 #資料庫校驗使用者名稱密碼是否正確 user_obj = models.Userinfo.objects.filter(name=name, pwd=pwd).first() if user_obj: #通過uuid模組獲得隨機字串作為token值 random_str = uuid.uuid4() #UserToken表中有該使用者的資訊就用新token值覆蓋,沒有就建立資料 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"] = "使用者名稱或者密碼錯誤" except Exception as e: ret["code"] = 1002 ret["msg"] = str(e) return Response(ret)
這樣就實現了使用者的login認證。。。。。。。。。
二、基於RestFramework的認證元件做檢視處理類的user認證
首先看一下UserAuth的具體用法:
-
關鍵點:
--檢視處理類下定義 名為authentication_classes 屬性,屬性值是一個列表或者元組
--寫一個 UserAuth(類名隨便)認證的類 ,將類名新增到authentication_classes列表中
--UserAuth認證類必須有一個 authenticate(self,request) 方法,這個方法下寫使用者認證邏輯, 認證成功: 可以返回資料,也可以什麼都不做。 認證失敗: ,必須拋異常
-- UserAuth類 繼承BaseAuthentication類 ,其實就是給類加了一個authenticate_header方法,沒理由,原始碼要求!
這四點你一定看不懂是什麼意思,很正常,這都是RestFramework原始碼給設定的特定規則,稍後認證原始碼流程分析會一一分析解答
view.py程式碼示例:程式碼實現了book檢視類的使用者認證功能
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("使用者認證失敗,您無權訪問!!!") # book檢視處理類 class Booklist(ModelViewSet): authentication_classes = [UserAuth, ] queryset = models.Book.objects.all() serializer_class = BooklistSerializer
接下來一張圖帶你透析認證元件:

UserAuth認證原始碼解析.jpg
- 這樣的寫法只是給book檢視類加上了認證,可以將認證類單獨解除安裝一個py檔案中,然後在全域性settings中新增對應的配置項,這樣就可以做到對所有的檢視類新增認證功能了!!!
根據實際需求選擇合適的方法!!!
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("使用者認證失敗,您無權訪問!!!")
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的認證元件做檢視處理類的許可權認證
首先看一下UserAuth的具體用法:
-
關鍵點:
--檢視處理類下定義 名為permission_classes 屬性,屬性值是一個列表或者元組
--寫一個 UserAuth(類名隨便)認證的類 ,將類名新增到permission_classes列表中
--UserAuth認證類必須有一個 has_permission(self,request,view) 方法,這個方法下寫使用者認證邏輯, 認證成功: 返回true。 認證失敗: ,返回false。
permission原始碼執行流程:

permission原始碼解析_副本.jpg
- app001-utils-permission_class.py
from rest_framework.permissions import BasePermission class SVIPPermission(BasePermission): message="您沒有訪問許可權!" 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的認證元件做檢視處理類的訪問頻率限制
1、訪問頻率限制和認證、許可權的執行流程一樣,都是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. 拿到使用者請求的IP # print(request.META) ip = request.META.get("REMOTE_ADDR") # 2. 當前請求的時間 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() # 判斷使用者在一分鐘的時間間隔內是否訪問超過3次 if len(history) >= 3: return False history.insert(0, now) return True def wait(self): # 當前訪問時間 ctime = time.time() #訪問時間歷史記錄 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給我們提供了幾種頻率控制組件,省去了我們在自己寫了
- 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.
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",//頻率設定 }, }
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類
- 基於遊標的分頁的 CursorPagination類 。但這種方式存在效能問題,如果使用者吧page給改的很大,查詢速度就會很慢。還有一種頁碼加密的方式
1、PageNumberPagination類的使用
-
方法一、檢視類繼承APIView時
-- 參考的API:
GET請求: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//指定的每頁顯示數 page_query_param = "page"//url欄的頁碼引數 page_size_query_param = "size"//獲取url引數中設定的每頁顯示資料條數 max_page_size = 5//最大支援的每頁顯示的資料條數 book_obj = models.Book.objects.all() # 例項化 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時(少用)
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類的使用
- 這種分頁樣式反映了查詢多個數據庫記錄時使用的語法。客戶端包含 “limit” 和 “offset” 查詢引數。limit 表示要返回的 資料 的最大數量,並且等同於其他樣式中的 page_size。offset 指定查詢的起始位置與完整的未分類 資料 集的關係。多用於返回的內容是靜態的,或者不用實時返回資料最新的變化。Google Search 和一些論壇用了這種方式:
- 參考API:
GET請求:http://127.0.0.1:8000/booklist/?limit=3&offset=3
Response響應:{ "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# 最大限制預設是None default_limit = 2# 設定每一頁顯示多少條 limit_query_param = 'limit'# 往後取幾條 offset_query_param = 'offset'# 當前所在的位置 class Booklist(APIView): def get(self, request): book_obj = models.Book.objects.all() # 例項化 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 類包含一些可以被覆蓋以修改分頁樣式的屬性。要設定這些屬性,應該繼承 LimitOffsetPagination 類,然後像上面那樣啟用你的自定義分頁類。
default_limit- 一個數字值,指定客戶端在查詢引數中未提供的 limit 。預設值與 PAGE_SIZE setting key 相同。
limit_query_param- 一個字串值,指示 “limit” 查詢引數的名稱。預設為 'limit'。
offset_query_param- 一個字串值,指示 “offset” 查詢引數的名稱。預設為 'offset'。
max_limit- 一個數字值,表示客戶端可以要求的最大允許 limit。預設為 None。
- 瞭解LIMIT / OFFSET的實際工作原理。為此,我們舉例說明,假如有大約1000個符合要求的結果。你若要前100資料,這很容易,這意味著它只返回與結果集匹配的前100行。但是現在想要900-1000之間的資料。資料庫現在必須遍歷前900個結果才能開始返回(因為它沒有指標告訴它如何獲得結果900)。總之,LIMIT / OFFSET在大型結果集上非常慢。
3、CursorPagination類的使用
- 現在很多場景,查詢結果在使用者瀏覽過程中是變化的,例如微博時間線,使用者看的時候,可能後一頁的某些微博會被刪除,而前一頁又增添了新的微博。這種情況就不適合用 limitoffset分頁方式了。Facebook 和 Twitter 都採用了基於遊標的分頁方法。
- 這種方式有以下兩個特點:
-- 1. 查詢的結果流可能是動態變化的,例如: 時間線裡出現了新的資料,或者刪除了資料,這些變化都可以在 “前一頁” 或者 “後一頁” 上體現出來。
-- 2. Cursor 體現了排序,是持久化的。一般情況下 Cursor 的順序是和時間有關。如果你的實體(例如:微博)可能展現給使用者多種可能的排序(例如:建立時間或者修改時間),那麼則需要建立不同的 Cursor。
具體實現時,Cursor 可能分別建立自 建立使用者 或者 修改使用者 欄位。Facebook Relay 用了查詢名稱 + 時間戳 的 Base64 形式來做 Cursor。 - CursorPagination 類包含一些可以被覆蓋以修改分頁樣式的屬性。
要設定這些屬性,你應該繼承 CursorPagination 類,然後像上面那樣啟用你的自定義分頁類。
-- page_size = 指定頁面大小的數字值。如果設定,則會覆蓋 PAGE_SIZE 設定。預設值與 PAGE_SIZE setting key 相同。
-- cursor_query_param = 一個字串值,指定 “遊標” 查詢引數的名稱。預設為 'cursor'.
-- ordering = 這應該是一個字串或字串列表,指定將應用基於遊標的分頁的欄位。例如: ordering = 'slug'。預設為 -created。該值也可以通過在檢視上使用 OrderingFilter 來覆蓋。
-關於限制偏移量和遊標分頁的分析 請參考: ofollow,noindex">http://cra.mr/2011/03/08/building-cursors-for-the-disqus-api
六、基於RestFramework的檢視處理類響應器
- restframework可以根據url的不同來源做出不同的響應,比如說,當來自瀏覽器端的請求,會返回一個頁面,當請求來自於postman等請求軟體時,返回的是json資料,這樣的效果是restframework的響應器在控制,settings中可以按需配置:
REST_FRAMEWORK={ 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer',//響應json 'rest_framework.renderers.BrowsableAPIRenderer',//響應頁面 ), }
七、基於RestFramework的檢視處理類的url註冊器
url註冊器只是 適用於檢視類繼承ModelViewSet類 的寫法:
url註冊器會生成四條url:
^ ^booklist/$ [name='book-list']
^ ^booklist\.(?P<format>[a-z0-9]+)/?$ [name='book-list'] //可以指定資料型別
^ ^booklist/(?P<pk>[^/.]+)/$ [name='book-detail']
^ ^booklist/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)/?$ [name='book-detail'] //可以指定單個數據資料型別
urls.py
from rest_framework import routers router = routers.DefaultRouter() //例項化 router.register("booklist", views.Booklist)//註冊 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