一、什么是Restful
- REST與技術無關,代表的是一種軟件架構風格,REST是Representational State Transfer的簡稱,中文翻譯為“表征狀態轉移”
- REST從資源的角度類審視整個網絡,它將分布在網絡中某個節點的資源通過URL進行標識,客戶端應用通過URL來獲取資源的表征,獲得這些表征致使這些應用轉變狀態
- 所有的數據,不過是通過網絡獲取的還是操作(增刪改查)的數據,都是資源,將一切數據視為資源是REST區別與其他架構風格的最本質屬性
- 對於REST這種面向資源的架構風格,有人提出一種全新的結構理念,即:面向資源架構(ROA:Resource Oriented Architecture)
REST是什么呢?它是一種架構風格,騰訊公司或其他公司建立API時要遵守的一種規則/風格,當然也有其他規則可以用。
那么何為REST架構風格呢?首先我們來說說Web,因為rest是以web為平台的。我們知道,web是分布式信息系統為超文本文件和其他對象(資源)提供訪問入口。
在web上訪問一個資源,需要3點:標識,表示,交互。通過這三個操作,又引出了三個概念:uri(包括url和urn),用來識別資源;representation(例如,圖片,html,媒體)用來表示資源;通過協議與資源進行交互。所以,REST就是通過使用HTTP協議和URI,利用client/server對資源進行CRUD操作。
那么為什么要使用REST設計呢?肯定是有它的優點的。
1.客戶端-服務端分離
優點:提高用戶界面的便攜性,通過簡化服務器提高可伸縮性....
2..無狀態(Stateless):從客戶端的每個請求要包含服務器所需要的所有信息
優點:提高可見性(可以單獨考慮每個請求),提高了可靠性(更容易從局部故障中修復),提高可擴展性(降低了服務器資源使用)

有狀態與無狀態區別:
如查詢員工工資,如果查詢工資是需要登陸系統,進入查詢工資的頁面,執行相關操作,獲取工資的多少,則這種情況下是有狀態的,因為查詢工資的每一步操作都依賴於前一步操作,只需要前面操作不成功,后序操作就無法執行;如果輸入一個url即可得到指定員工的工資,則這種情況下是無狀態的,因為獲取員工工資不依賴於其他資源或者狀態,且這種情況下,員工工資是一個資源,由一個url與其對應,可以通過HTTP的GET方法得到資源。
3.緩存(Cachable):服務器返回信息必須被標記是否可以緩存,如果緩存,客戶端可能會重用之前的信息發送請求
優點:減少交互次數,減少交互的平均延遲
4.統一接口
優點:提高交互的可見性,鼓勵單獨改善組件
5.支持按需代碼(Code-On-Demand 可選)
優點:提高可擴展性
下面來解釋一下何為表征狀態轉移:
舉個例子:例如我訂閱了一個人的博客,想要獲取他發表的所有文章(這里『他發表的所有文章』就是一個資源Resource)。於是我就向他的服務發出請求,說『我要獲取你發表的所有文章,最好是atom格式的』,這時候服務器向你返回了atom格式的文章列表第一頁(這里『atom格式的文章列表』就是表征Representation)。
你看到了第一頁的頁尾,想要看第二頁,這時候有趣的事情就來了。如果服務器記錄了應用的狀態(stateful),那么你只要向服務詢問『我要看下一頁』,那么服務器自然就會返回第二頁。類似的,如果你當前在第二頁,想服務器請求『我要看下一頁』,那就會得到第三頁。但是REST的服務器恰恰是無狀態的(stateless),服務器並沒有保持你當前處於第幾頁,也就無法響應『下一頁』這種具有狀態性質的請求。因此客戶端需要去維護當前應用的狀態(application state),也就是『如何獲取下一頁資源』。當然,『下一頁資源』的業務邏輯必然是由服務端來提供。服務器在文章列表的atom表征中加入一個URI超鏈接(hyper link),指向下一頁文章列表對應的資源。客戶端就可以使用統一接口(Uniform Interface)的方式,從這個URI中獲取到他想要的下一頁文章列表資源。上面的『能夠進入下一頁』就是應用的狀態(State)。服務器把『能夠進入下一頁』這個狀態以atom表征形式傳輸(Transfer)給客戶端就是表征狀態傳輸(REpresentational State Transfer)這個概念。
REST是面向資源進行的,而資源是通過URI進行暴露的。
URI 的設計只要負責把資源通過合理方式暴露出來就可以了。對資源的操作與它無關,操作是通過 HTTP動詞來體現,所以REST 通過 URI 暴露資源時,會強調不要在 URI 中出現動詞。
例如:比如:左邊是錯誤的設計,而右邊是正確的
GET /rest/api/getDogs --> GET /rest/api/dogs 獲取所有小狗狗 GET /rest/api/addDogs --> POST /rest/api/dogs 添加一個小狗狗 GET /rest/api/editDogs/:dog_id --> PUT /rest/api/dogs/:dog_id 修改一個小狗狗 GET /rest/api/deleteDogs/:dog_id --> DELETE /rest/api/dogs/:dog_id 刪除一個小狗狗
REST很好利用了HTTP本身就有的一些特征,如HTTP動詞,HTTP狀態碼,HTTP報頭等等。
更多參考:https://github.com/aisuhua/restful-api-design-references
二、Restful API設計
- API與用戶通信的協議,總是使用HTTPS協議。
- 域名:https://example.org/api/
- 版本:URL:https://api.example.com/v1
- 路徑,定位網絡上的唯一資源,均使用名詞
- https://api.example.com/v1/zoos
- https://api.example.com/v1/animals
- method
- GET 從服務器獲取資源
- POST 在服務器上創建一個資源
- PUT 更新服務器上的資源
- PATCH 更新服務器資源(如果執行更新某幾列,則用這個方法;上面是更新全部列數據)
- DELETE 刪除某個資源
- 過濾,通過URL上傳參的形式傳遞搜索條件
- https://api.example.com/v1/zoos?limit=10:指定返回記錄的數量
- https://api.example.com/v1/zoos?offset=10:指定返回記錄的開始位置
- https://api.example.com/v1/zoos?page=2&per_page=100:指定第幾頁,以及每頁的記錄數
- https://api.example.com/v1/zoos?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序
- https://api.example.com/v1/zoos?animal_type_id=1:指定篩選條件
- 狀態碼

200 OK - [GET]:服務器成功返回用戶請求的數據,該操作是冪等的(Idempotent)。 201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。 202 Accepted - [*]:表示一個請求已經進入后台排隊(異步任務) 204 NO CONTENT - [DELETE]:用戶刪除數據成功。 400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操作,該操作是冪等的。 401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。 403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對),但是訪問是被禁止的。 404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操作,該操作是冪等的。 406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式,但是只有XML格式)。 410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的。 422 Unprocesable entity - [POST/PUT/PATCH] 當創建一個對象時,發生一個驗證錯誤。 500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將無法判斷發出的請求是否成功。
- 錯誤處理,狀態碼是4xx時,應返回錯誤信息,error當做key。
{ error: "Invalid API key" }
- 返回結果,針對不同操作,服務器向用戶返回的結果應該符合以下規范。
GET /collection:返回資源對象的列表(數組) GET /collection/resource:返回單個資源對象 POST /collection:返回新生成的資源對象 PUT /collection/resource:返回完整的資源對象 PATCH /collection/resource:返回完整的資源對象 DELETE /collection/resource:返回一個空文檔
- Hypermedia API,RESTful API最好做到Hypermedia,即返回結果中提供鏈接,連向其他API方法,使得用戶不查文檔,也知道下一步應該做什么。
{"link": { "rel": "collection https://www.example.com/zoos", "href": "https://api.example.com/zoos", "title": "List of zoos", "type": "application/vnd.yourformat+json" }}
三、基於Django實現
路由系統:
urlpatterns = [ url(r'^users', Users.as_view()), ]
CBV視圖:
from django.views import View from django.http import JsonResponse class Users(View): def get(self, request, *args, **kwargs): result = { 'status': True, 'data': 'response data' } return JsonResponse(result, status=200) def post(self, request, *args, **kwargs): result = { 'status': True, 'data': 'response data' } return JsonResponse(result, status=200)
四、基於Django Rest FrameWork框架實現
1.基本流程
url.py
from django.conf.urls import url, include from web.views.s1_api import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]
views.py
from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def dispatch(self, request, *args, **kwargs): """ 請求到來之后,都要執行dispatch方法,dispatch方法根據請求方式不同觸發 get/post/put等方法 注意:APIView中的dispatch方法有好多好多的功能 """ return super().dispatch(request, *args, **kwargs) def get(self, request, *args, **kwargs): return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
上述是rest framework框架基本流程,重要的功能是在APIView的dispatch中觸發。
2.版本
在Django rest-framework中提供了5中version設置方式。
a. 基於url的get傳參方式:如:/users?version=v1

REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(),name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import QueryParameterVersioning class TestView(APIView): versioning_class = QueryParameterVersioning def get(self, request, *args, **kwargs): # 獲取版本 print(request.version) # 獲取版本管理的類 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
b.基於url的正則方式(推薦使用這種方式):如:/v1/users/

REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import URLPathVersioning class TestView(APIView): versioning_class = URLPathVersioning def get(self, request, *args, **kwargs): # 獲取版本 print(request.version) # 獲取版本管理的類 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
c.基於 accept 請求頭方式:如:Accept: application/json; version=1.0

REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import AcceptHeaderVersioning class TestView(APIView): versioning_class = AcceptHeaderVersioning def get(self, request, *args, **kwargs): # 獲取版本 HTTP_ACCEPT頭 print(request.version) # 獲取版本管理的類 print(request.versioning_scheme) # 反向生成URL reverse_url = request.versioning_scheme.reverse('test', request=request) print(reverse_url) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
d.基於主機名方式:如:v1.example.com

#其他與上面一樣,唯一改變的就是 from rest_framework.versioning import HostNameVersioning class TestView(APIView): versioning_class = HostNameVersioning # 下面一模一樣
e.基於django路由系統的namespace:如:example.com/v1/users/

#需要改變的地方 from rest_framework.versioning import NamespaceVersioning class TestView(APIView): versioning_class = NamespaceVersioning #下面是一樣的
f.全局使用方式
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning", 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2'], 'VERSION_PARAM': 'version' }
3.認證和授權
a. 用戶url傳入的token認證

from django.conf.urls import url, include from web.viewsimport TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework.request import Request from rest_framework import exceptions token_list = [ 'sfsfss123kuf3j123', 'asijnfowerkkf9812', ] class TestAuthentication(BaseAuthentication): def authenticate(self, request): """ 用戶認證,如果驗證成功后返回元組: (用戶,用戶Token) :param request: :return: None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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 (user,token)表示驗證通過並設置用戶名和Token; AuthenticationFailed異常 """ val = request.query_params.get('token') if val not in token_list: raise exceptions.AuthenticationFailed("用戶認證失敗") return ('登錄用戶', '用戶token') def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ # 驗證失敗時,返回的響應頭WWW-Authenticate對應的值 pass class TestView(APIView): authentication_classes = [TestAuthentication, ] permission_classes = [] def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
b. 請求頭認證

from django.conf.urls import url, include from web.viewsimport TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework.request import Request from rest_framework import exceptions token_list = [ 'sfsfss123kuf3j123', 'asijnfowerkkf9812', ] class TestAuthentication(BaseAuthentication): def authenticate(self, request): """ 用戶認證,如果驗證成功后返回元組: (用戶,用戶Token) :param request: :return: None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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 (user,token)表示驗證通過並設置用戶名和Token; AuthenticationFailed異常 """ import base64 auth = request.META.get('HTTP_AUTHORIZATION', b'') if auth: auth = auth.encode('utf-8') auth = auth.split() if not auth or auth[0].lower() != b'basic': raise exceptions.AuthenticationFailed('驗證失敗') if len(auth) != 2: raise exceptions.AuthenticationFailed('驗證失敗') username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':') if username == 'alex' and password == '123': return ('登錄用戶', '用戶token') else: raise exceptions.AuthenticationFailed('用戶名或密碼錯誤') def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ return 'Basic realm=api' class TestView(APIView): authentication_classes = [TestAuthentication, ] permission_classes = [] def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
c. 多個認證規則

from django.conf.urls import url, include from web.views.s2_auth import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework.request import Request from rest_framework import exceptions token_list = [ 'sfsfss123kuf3j123', 'asijnfowerkkf9812', ] class Test1Authentication(BaseAuthentication): def authenticate(self, request): """ 用戶認證,如果驗證成功后返回元組: (用戶,用戶Token) :param request: :return: None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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()# 默認值為:None else: self.auth = None (user,token)表示驗證通過並設置用戶名和Token; AuthenticationFailed異常 """ import base64 auth = request.META.get('HTTP_AUTHORIZATION', b'') if auth: auth = auth.encode('utf-8') else: return None print(auth,'xxxx') auth = auth.split() if not auth or auth[0].lower() != b'basic': raise exceptions.AuthenticationFailed('驗證失敗') if len(auth) != 2: raise exceptions.AuthenticationFailed('驗證失敗') username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':') if username == 'alex' and password == '123': return ('登錄用戶', '用戶token') else: raise exceptions.AuthenticationFailed('用戶名或密碼錯誤') def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ # return 'Basic realm=api' pass class Test2Authentication(BaseAuthentication): def authenticate(self, request): """ 用戶認證,如果驗證成功后返回元組: (用戶,用戶Token) :param request: :return: None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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()# 默認值為:None else: self.auth = None (user,token)表示驗證通過並設置用戶名和Token; AuthenticationFailed異常 """ val = request.query_params.get('token') if val not in token_list: raise exceptions.AuthenticationFailed("用戶認證失敗") return ('登錄用戶', '用戶token') def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ pass class TestView(APIView): authentication_classes = [Test1Authentication, Test2Authentication] permission_classes = [] def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
d.認證和權限

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework.permissions import BasePermission from rest_framework.request import Request from rest_framework import exceptions token_list = [ 'sfsfss123kuf3j123', 'asijnfowerkkf9812', ] class TestAuthentication(BaseAuthentication): def authenticate(self, request): """ 用戶認證,如果驗證成功后返回元組: (用戶,用戶Token) :param request: :return: None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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()# 默認值為:None else: self.auth = None (user,token)表示驗證通過並設置用戶名和Token; AuthenticationFailed異常 """ val = request.query_params.get('token') if val not in token_list: raise exceptions.AuthenticationFailed("用戶認證失敗") return ('登錄用戶', '用戶token') def authenticate_header(self, request): """ Return a string to be used as the value of the `WWW-Authenticate` header in a `401 Unauthenticated` response, or `None` if the authentication scheme should return `403 Permission Denied` responses. """ pass class TestPermission(BasePermission): message = "權限驗證失敗" def has_permission(self, request, view): """ 判斷是否有權限訪問當前請求 Return `True` if permission is granted, `False` otherwise. :param request: :param view: :return: True有權限;False無權限 """ if request.user == "管理員": return True # GenericAPIView中get_object時調用 def has_object_permission(self, request, view, obj): """ 視圖繼承GenericAPIView,並在其中使用get_object時獲取對象時,觸發單獨對象權限驗證 Return `True` if permission is granted, `False` otherwise. :param request: :param view: :param obj: :return: True有權限;False無權限 """ if request.user == "管理員": return True class TestView(APIView): # 認證的動作是由request.user觸發 authentication_classes = [TestAuthentication, ] # 權限 # 循環執行所有的權限 permission_classes = [TestPermission, ] def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
e.全局使用
上述操作中均是對單獨視圖進行特殊配置,如果想要對全局進行配置,則需要再配置文件中寫入即可。

REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, "DEFAULT_AUTHENTICATION_CLASSES": [ "web.utils.TestAuthentication", ], "DEFAULT_PERMISSION_CLASSES": [ "web.utils.TestPermission", ], }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
4.用戶訪問次數/頻率限制
a. 基於用戶IP限制訪問頻率

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

import time from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import exceptions from rest_framework.throttling import BaseThrottle from rest_framework.settings import api_settings # 保存訪問記錄 RECORD = { '用戶IP': [12312139, 12312135, 12312133, ] } class TestThrottle(BaseThrottle): ctime = time.time def get_ident(self, request): """ 根據用戶IP和代理IP,當做請求者的唯一IP Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR if present and number of proxies is > 0. If not use all of HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR. """ xff = request.META.get('HTTP_X_FORWARDED_FOR') remote_addr = request.META.get('REMOTE_ADDR') num_proxies = api_settings.NUM_PROXIES if num_proxies is not None: if num_proxies == 0 or xff is None: return remote_addr addrs = xff.split(',') client_addr = addrs[-min(num_proxies, len(addrs))] return client_addr.strip() return ''.join(xff.split()) if xff else remote_addr def allow_request(self, request, view): """ 是否仍然在允許范圍內 Return `True` if the request should be allowed, `False` otherwise. :param request: :param view: :return: True,表示可以通過;False表示已超過限制,不允許訪問 """ # 獲取用戶唯一標識(如:IP) # 允許一分鍾訪問10次 num_request = 10 time_request = 60 now = self.ctime() ident = self.get_ident(request) self.ident = ident if ident not in RECORD: RECORD[ident] = [now, ] return True history = RECORD[ident] while history and history[-1] <= now - time_request: history.pop() if len(history) < num_request: history.insert(0, now) return True def wait(self): """ 多少秒后可以允許繼續訪問 Optionally, return a recommended number of seconds to wait before the next request. """ last_time = RECORD[self.ident][0] now = self.ctime() return int(60 + last_time - now) class TestView(APIView): throttle_classes = [TestThrottle, ] def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容') def throttled(self, request, wait): """ 訪問次數被限制時,定制錯誤信息 """ class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
b. 基於用戶IP顯示訪問頻率(利於Django緩存)

REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'test_scope': '10/m', }, }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import exceptions from rest_framework.throttling import SimpleRateThrottle class TestThrottle(SimpleRateThrottle): # 配置文件定義的顯示頻率的Key scope = "test_scope" def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ if not request.user: ident = self.get_ident(request) else: ident = request.user return self.cache_format % { 'scope': self.scope, 'ident': ident } class TestView(APIView): throttle_classes = [TestThrottle, ] def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容') def throttled(self, request, wait): """ 訪問次數被限制時,定制錯誤信息 """ class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
c. view中限制請求頻率

REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'xxxxxx': '10/m', }, }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import exceptions from rest_framework.throttling import ScopedRateThrottle # 繼承 ScopedRateThrottle class TestThrottle(ScopedRateThrottle): def get_cache_key(self, request, view): """ Should return a unique cache-key which can be used for throttling. Must be overridden. May return `None` if the request should not be throttled. """ if not request.user: ident = self.get_ident(request) else: ident = request.user return self.cache_format % { 'scope': self.scope, 'ident': ident } class TestView(APIView): throttle_classes = [TestThrottle, ] # 在settings中獲取 xxxxxx 對應的頻率限制值 throttle_scope = "xxxxxx" def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容') def throttled(self, request, wait): """ 訪問次數被限制時,定制錯誤信息 """ class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
d. 匿名時用IP限制+登錄時用Token限制

REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, 'DEFAULT_THROTTLE_RATES': { 'luffy_anon': '10/m', 'luffy_user': '20/m', }, }

from django.conf.urls import url, include from web.views.s3_throttling import TestView urlpatterns = [ url(r'^test/', TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import SimpleRateThrottle class LuffyAnonRateThrottle(SimpleRateThrottle): """ 匿名用戶,根據IP進行限制 """ scope = "luffy_anon" def get_cache_key(self, request, view): # 用戶已登錄,則跳過 匿名頻率限制 if request.user: return None return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) } class LuffyUserRateThrottle(SimpleRateThrottle): """ 登錄用戶,根據用戶token限制 """ scope = "luffy_user" def get_ident(self, request): """ 認證成功時:request.user是用戶對象;request.auth是token對象 :param request: :return: """ # return request.auth.token return "user_token" def get_cache_key(self, request, view): """ 獲取緩存key :param request: :param view: :return: """ # 未登錄用戶,則跳過 Token限制 if not request.user: return None return self.cache_format % { 'scope': self.scope, 'ident': self.get_ident(request) } class TestView(APIView): throttle_classes = [LuffyUserRateThrottle, LuffyAnonRateThrottle, ] def get(self, request, *args, **kwargs): # self.dispatch print(request.user) print(request.auth) return Response('GET請求,響應內容') def post(self, request, *args, **kwargs): return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
e. 全局使用

REST_FRAMEWORK = { 'DEFAULT_THROTTLE_CLASSES': [ 'api.utils.throttles.throttles.LuffyAnonRateThrottle', 'api.utils.throttles.throttles.LuffyUserRateThrottle', ], 'DEFAULT_THROTTLE_RATES': { 'anon': '10/day', 'user': '10/day', 'luffy_anon': '10/m', 'luffy_user': '20/m', }, }
5.解析器
根據請求頭 content-type 選擇對應的解析器就請求體內容進行處理。
a. 僅處理請求頭content-type為application/json的請求體

from django.conf.urls import url, include from web.views.s5_parser import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import JSONParser class TestView(APIView): parser_classes = [JSONParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
b. 僅處理請求頭content-type為application/x-www-form-urlencoded 的請求體

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import FormParser class TestView(APIView): parser_classes = [FormParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
c. 僅處理請求頭content-type為multipart/form-data的請求體

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import MultiPartParser class TestView(APIView): parser_classes = [MultiPartParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data"> <input type="text" name="user" /> <input type="file" name="img"> <input type="submit" value="提交"> </form> </body> </html>
d. 僅上傳文件

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/(?P<filename>[^/]+)', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import FileUploadParser class TestView(APIView): parser_classes = [FileUploadParser, ] def post(self, request, filename, *args, **kwargs): print(filename) print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="http://127.0.0.1:8000/test/f1.numbers" method="post" enctype="multipart/form-data"> <input type="text" name="user" /> <input type="file" name="img"> <input type="submit" value="提交"> </form> </body> </html>
e. 同時多個Parser

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.request import Request from rest_framework.parsers import JSONParser, FormParser, MultiPartParser class TestView(APIView): parser_classes = [JSONParser, FormParser, MultiPartParser, ] def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
當同時使用多個parser時,rest framework會根據請求頭content-type自動進行比對,並使用對應parser
f. 全局使用

REST_FRAMEWORK = { 'DEFAULT_PARSER_CLASSES':[ 'rest_framework.parsers.JSONParser' 'rest_framework.parsers.FormParser' 'rest_framework.parsers.MultiPartParser' ] }

from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response class TestView(APIView): def post(self, request, *args, **kwargs): print(request.content_type) # 獲取請求的值,並使用對應的JSONParser進行處理 print(request.data) # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值 print(request.POST) print(request.FILES) return Response('POST請求,響應內容') def put(self, request, *args, **kwargs): return Response('PUT請求,響應內容')
注意:個別特殊的值可以通過Django的request對象 request._request 來進行獲取
6.序列化
序列化用於對用戶請求數據進行驗證和數據進行序列化。
a. 自定義字段

from django.conf.urls import url, include from web.views.s6_serializers import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from .. import models class PasswordValidator(object): def __init__(self, base): self.base = base def __call__(self, value): if value != self.base: message = 'This field must be %s.' % self.base raise serializers.ValidationError(message) def set_context(self, serializer_field): """ This hook is called by the serializer instance, prior to the validation call being made. """ # 執行驗證之前調用,serializer_fields是當前字段對象 pass class UserSerializer(serializers.Serializer): ut_title = serializers.CharField(source='ut.title') user = serializers.CharField(min_length=6) pwd = serializers.CharField(error_messages={'required': '密碼不能為空'}, validators=[PasswordValidator('666')]) class TestView(APIView): def get(self, request, *args, **kwargs): # 序列化,將數據庫查詢字段序列化為字典 data_list = models.UserInfo.objects.all() ser = UserSerializer(instance=data_list, many=True) # 或 # obj = models.UserInfo.objects.all().first() # ser = UserSerializer(instance=obj, many=False) return Response(ser.data) def post(self, request, *args, **kwargs): # 驗證,對請求發來的數據進行驗證 ser = UserSerializer(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) return Response('POST請求,響應內容')
b. 基於Model自動生成字段

from django.conf.urls import url, include from web.views.s6_serializers import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from .. import models class PasswordValidator(object): def __init__(self, base): self.base = str(base) def __call__(self, value): if value != self.base: message = 'This field must be %s.' % self.base raise serializers.ValidationError(message) def set_context(self, serializer_field): """ This hook is called by the serializer instance, prior to the validation call being made. """ # 執行驗證之前調用,serializer_fields是當前字段對象 pass class ModelUserSerializer(serializers.ModelSerializer): user = serializers.CharField(max_length=32) class Meta: model = models.UserInfo fields = "__all__" # fields = ['user', 'pwd', 'ut'] depth = 2 extra_kwargs = {'user': {'min_length': 6}, 'pwd': {'validators': [PasswordValidator(666), ]}} # read_only_fields = ['user'] class TestView(APIView): def get(self, request, *args, **kwargs): # 序列化,將數據庫查詢字段序列化為字典 data_list = models.UserInfo.objects.all() ser = ModelUserSerializer(instance=data_list, many=True) # 或 # obj = models.UserInfo.objects.all().first() # ser = UserSerializer(instance=obj, many=False) return Response(ser.data) def post(self, request, *args, **kwargs): # 驗證,對請求發來的數據進行驗證 print(request.data) ser = ModelUserSerializer(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) return Response('POST請求,響應內容')
c. 生成URL

from django.conf.urls import url, include from web.views.s6_serializers import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), url(r'detail/(?P<pk>\d+)/', TestView.as_view(), name='detail'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from .. import models class PasswordValidator(object): def __init__(self, base): self.base = str(base) def __call__(self, value): if value != self.base: message = 'This field must be %s.' % self.base raise serializers.ValidationError(message) def set_context(self, serializer_field): """ This hook is called by the serializer instance, prior to the validation call being made. """ # 執行驗證之前調用,serializer_fields是當前字段對象 pass class ModelUserSerializer(serializers.ModelSerializer): ut = serializers.HyperlinkedIdentityField(view_name='detail') class Meta: model = models.UserInfo fields = "__all__" extra_kwargs = { 'user': {'min_length': 6}, 'pwd': {'validators': [PasswordValidator(666),]}, } class TestView(APIView): def get(self, request, *args, **kwargs): # 序列化,將數據庫查詢字段序列化為字典 data_list = models.UserInfo.objects.all() ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request}) # 或 # obj = models.UserInfo.objects.all().first() # ser = UserSerializer(instance=obj, many=False) return Response(ser.data) def post(self, request, *args, **kwargs): # 驗證,對請求發來的數據進行驗證 print(request.data) ser = ModelUserSerializer(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) return Response('POST請求,響應內容')
d. 自動生成URL

from django.conf.urls import url, include from web.views.s6_serializers import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), url(r'detail/(?P<pk>\d+)/', TestView.as_view(), name='xxxx'), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from .. import models class PasswordValidator(object): def __init__(self, base): self.base = str(base) def __call__(self, value): if value != self.base: message = 'This field must be %s.' % self.base raise serializers.ValidationError(message) def set_context(self, serializer_field): """ This hook is called by the serializer instance, prior to the validation call being made. """ # 執行驗證之前調用,serializer_fields是當前字段對象 pass class ModelUserSerializer(serializers.HyperlinkedModelSerializer): ll = serializers.HyperlinkedIdentityField(view_name='xxxx') tt = serializers.CharField(required=False) class Meta: model = models.UserInfo fields = "__all__" list_serializer_class = serializers.ListSerializer extra_kwargs = { 'user': {'min_length': 6}, 'pwd': {'validators': [PasswordValidator(666), ]}, 'url': {'view_name': 'xxxx'}, 'ut': {'view_name': 'xxxx'}, } class TestView(APIView): def get(self, request, *args, **kwargs): # # 序列化,將數據庫查詢字段序列化為字典 data_list = models.UserInfo.objects.all() ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request}) # # 如果Many=True # # 或 # # obj = models.UserInfo.objects.all().first() # # ser = UserSerializer(instance=obj, many=False) return Response(ser.data) def post(self, request, *args, **kwargs): # 驗證,對請求發來的數據進行驗證 print(request.data) ser = ModelUserSerializer(data=request.data) if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) return Response('POST請求,響應內容')
7.分頁
a. 根據頁碼進行分頁

from django.conf.urls import url, include from rest_framework import routers from web.views import s9_pagination urlpatterns = [ url(r'^test/', s9_pagination.UserViewSet.as_view()), ]

from rest_framework.views import APIView from rest_framework import serializers from .. import models from rest_framework.pagination import PageNumberPagination class StandardResultsSetPagination(PageNumberPagination): # 默認每頁顯示的數據條數 page_size = 1 # 獲取URL參數中設置的每頁顯示數據條數 page_size_query_param = 'page_size' # 獲取URL參數中傳入的頁碼key page_query_param = 'page' # 最大支持的每頁顯示的數據條數 max_page_size = 1 class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(APIView): def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().order_by('-id') # 實例化分頁對象,獲取數據庫中的分頁數據 paginator = StandardResultsSetPagination() page_user_list = paginator.paginate_queryset(user_list, self.request, view=self) # 序列化對象 serializer = UserSerializer(page_user_list, many=True) # 生成分頁和數據 response = paginator.get_paginated_response(serializer.data) return response
b. 位置和個數進行分頁

from django.conf.urls import url, include from web.views import s9_pagination urlpatterns = [ url(r'^test/', s9_pagination.UserViewSet.as_view()), ]

from rest_framework.views import APIView from rest_framework import serializers from .. import models from rest_framework.pagination import PageNumberPagination,LimitOffsetPagination class StandardResultsSetPagination(LimitOffsetPagination): # 默認每頁顯示的數據條數 default_limit = 10 # URL中傳入的顯示數據條數的參數 limit_query_param = 'limit' # URL中傳入的數據位置的參數 offset_query_param = 'offset' # 最大每頁顯得條數 max_limit = None class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(APIView): def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().order_by('-id') # 實例化分頁對象,獲取數據庫中的分頁數據 paginator = StandardResultsSetPagination() page_user_list = paginator.paginate_queryset(user_list, self.request, view=self) # 序列化對象 serializer = UserSerializer(page_user_list, many=True) # 生成分頁和數據 response = paginator.get_paginated_response(serializer.data) return response
c. 游標分頁

from django.conf.urls import url, include from web.views import s9_pagination urlpatterns = [ url(r'^test/', s9_pagination.UserViewSet.as_view()), ]

from rest_framework.views import APIView from rest_framework import serializers from .. import models from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination class StandardResultsSetPagination(CursorPagination): # URL傳入的游標參數 cursor_query_param = 'cursor' # 默認每頁顯示的數據條數 page_size = 2 # URL傳入的每頁顯示條數的參數 page_size_query_param = 'page_size' # 每頁顯示數據最大條數 max_page_size = 1000 # 根據ID從大到小排列 ordering = "id" class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(APIView): def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().order_by('-id') # 實例化分頁對象,獲取數據庫中的分頁數據 paginator = StandardResultsSetPagination() page_user_list = paginator.paginate_queryset(user_list, self.request, view=self) # 序列化對象 serializer = UserSerializer(page_user_list, many=True) # 生成分頁和數據 response = paginator.get_paginated_response(serializer.data) return response
8.路由系統
a. 自定義路由

from django.conf.urls import url, include from web.views import s11_render urlpatterns = [ url(r'^test/$', s11_render.TestView.as_view()), url(r'^test\.(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view()), url(r'^test/(?P<pk>[^/.]+)/$', s11_render.TestView.as_view()), url(r'^test/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view()) ]

from rest_framework.views import APIView from rest_framework.response import Response from .. import models class TestView(APIView): def get(self, request, *args, **kwargs): print(kwargs) print(self.renderer_classes) return Response('...')
b. 半自動路由

from django.conf.urls import url, include from web.views import s10_generic urlpatterns = [ url(r'^test/$', s10_generic.UserViewSet.as_view({'get': 'list', 'post': 'create'})), url(r'^test/(?P<pk>\d+)/$', s10_generic.UserViewSet.as_view( {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})), ]

from rest_framework.viewsets import ModelViewSet from rest_framework import serializers from .. import models class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserSerializer
c. 全自動路由

from django.conf.urls import url, include from rest_framework import routers from web.views import s10_generic router = routers.DefaultRouter() router.register(r'users', s10_generic.UserViewSet) urlpatterns = [ url(r'^', include(router.urls)), ]

from rest_framework.viewsets import ModelViewSet from rest_framework import serializers from .. import models class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserSerializer
9.視圖
a. GenericViewSet

from django.conf.urls import url, include from web.views.s7_viewset import TestView urlpatterns = [ url(r'test/', TestView.as_view({'get':'list'}), name='test'), url(r'detail/(?P<pk>\d+)/', TestView.as_view({'get':'list'}), name='xxxx'), ]

from rest_framework import viewsets from rest_framework.response import Response class TestView(viewsets.GenericViewSet): def list(self, request, *args, **kwargs): return Response('...') def add(self, request, *args, **kwargs): pass def delete(self, request, *args, **kwargs): pass def edit(self, request, *args, **kwargs): pass
b. ModelViewSet(自定義URL)

from django.conf.urls import url, include from web.views import s10_generic urlpatterns = [ url(r'^test/$', s10_generic.UserViewSet.as_view({'get': 'list', 'post': 'create'})), url(r'^test/(?P<pk>\d+)/$', s10_generic.UserViewSet.as_view( {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})), ]

from rest_framework.viewsets import ModelViewSet from rest_framework import serializers from .. import models class UserSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class UserViewSet(ModelViewSet): queryset = models.UserInfo.objects.all() serializer_class = UserSerializer
c. ModelViewSet(rest framework路由)

from django.conf.urls import url, include from rest_framework import routers from app01 import views router = routers.DefaultRouter() router.register(r'users', views.UserViewSet) router.register(r'groups', views.GroupViewSet) # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ url(r'^', include(router.urls)), ] urls.py

from rest_framework import viewsets from rest_framework import serializers class UserSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = models.User fields = ('url', 'username', 'email', 'groups') class GroupSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = models.Group fields = ('url', 'name') class UserViewSet(viewsets.ModelViewSet): """ API endpoint that allows users to be viewed or edited. """ queryset = User.objects.all().order_by('-date_joined') serializer_class = UserSerializer class GroupViewSet(viewsets.ModelViewSet): """ API endpoint that allows groups to be viewed or edited. """ queryset = Group.objects.all() serializer_class = GroupSerializer
10.渲染器
根據 用戶請求URL 或 用戶可接受的類型,篩選出合適的 渲染組件。
用戶請求URL:
- http://127.0.0.1:8000/test/?format=json
- http://127.0.0.1:8000/test.json
用戶請求頭:
- Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
a. json
訪問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/

from django.conf.urls import url, include from web.views import s11_render urlpatterns = [ url(r'^test/$', s11_render.TestView.as_view()), url(r'^test\.(?P<format>[a-z0-9]+)', s11_render.TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import JSONRenderer from .. import models class TestSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class TestView(APIView): renderer_classes = [JSONRenderer, ] def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all() ser = TestSerializer(instance=user_list, many=True) return Response(ser.data)
b. 表格
訪問URL:
- http://127.0.0.1:8000/test/?format=admin
- http://127.0.0.1:8000/test.admin
- http://127.0.0.1:8000/test/

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import AdminRenderer from .. import models class TestSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class TestView(APIView): renderer_classes = [AdminRenderer, ] def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all() ser = TestSerializer(instance=user_list, many=True) return Response(ser.data)
c. Form表單
訪問URL:
- http://127.0.0.1:8000/test/?format=form
- http://127.0.0.1:8000/test.form
- http://127.0.0.1:8000/test/

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import JSONRenderer from rest_framework.renderers import AdminRenderer from rest_framework.renderers import HTMLFormRenderer from .. import models class TestSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class TestView(APIView): renderer_classes = [HTMLFormRenderer, ] def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().first() ser = TestSerializer(instance=user_list, many=False) return Response(ser.data)
d. 自定義顯示模板
訪問URL:
- http://127.0.0.1:8000/test/?format=html
- http://127.0.0.1:8000/test.html
- http://127.0.0.1:8000/test/

from django.conf.urls import url, include from web.views import s11_render urlpatterns = [ url(r'^test/$', s11_render.TestView.as_view()), url(r'^test\.(?P<format>[a-z0-9]+)', s11_render.TestView.as_view()), ]

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import TemplateHTMLRenderer from .. import models class TestSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class TestView(APIView): renderer_classes = [TemplateHTMLRenderer, ] def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().first() ser = TestSerializer(instance=user_list, many=False) return Response(ser.data, template_name='user_detail.html')

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{ user }} {{ pwd }} {{ ut }} </body> </html>
e. 瀏覽器格式API+JSON
訪問URL:
- http://127.0.0.1:8000/test/?format=api
- http://127.0.0.1:8000/test.api
- http://127.0.0.1:8000/test/

from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import serializers from rest_framework.renderers import JSONRenderer from rest_framework.renderers import BrowsableAPIRenderer from .. import models class TestSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo fields = "__all__" class CustomBrowsableAPIRenderer(BrowsableAPIRenderer): def get_default_renderer(self, view): return JSONRenderer() class TestView(APIView): renderer_classes = [CustomBrowsableAPIRenderer, ] def get(self, request, *args, **kwargs): user_list = models.UserInfo.objects.all().first() ser = TestSerializer(instance=user_list, many=False) return Response(ser.data, template_name='user_detail.html')
注意:如果同時多個存在時,自動根據URL后綴來選擇渲染器。