RESTful api設計
接口:url
- 根據method不同,進行不同操作
- 面向資源編程 :所有的數據不過是通過網絡獲取的還是操作的數據,都是資源
- 體現版本https
- 體現是API
- https 傳輸的是密文
- 過濾條件,通過在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 301 302 403 404 500 更多看這里:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.htm
錯誤信息
{ error: "Invalid API key" }
- 處理結果
GET /collection:返回資源對象的列表(數組) /collection/resource:返回單個資源對象 POST /collection:返回新生成的資源對象 PUT /collection/resource:返回完整的資源對象 PATCH /collection/resource:返回完整的資源對象 DELETE /collection/resource:返回一個空文檔
HEAD
OPTION
TRACE - 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" }}
http://www.ruanyifeng.com/blog/2014/05/restful_api.html
http://www.cnblogs.com/yuanchenqi/articles/8742684.html
http://www.cnblogs.com/wupeiqi/articles/7805382.html
https://www.cnblogs.com/liwenzhou/p/8543035.html
三 、 restful 知識點
pip install djangorestframework
request._request -- 原request request.GET=request._request.GET request.data # post不管以什么方式拼接的數據,都可以獲得 urlencode json
1、登錄認證
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): 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 'Basic realm=api' class TestView(APIView): authentication_classes = [TestAuthentication, ] def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容')
import hashlib import time from rest_framework.views import APIView from rest_framework.response import Response from api.models import * def get_random_str(user): """ 生成隨機 字符串 """ ctime = str(time.time()) md5 = hashlib.md5(bytes(user, encoding='utf-8')) md5.update(bytes(ctime, encoding="utf-8")) return md5.hexdigest() class LoginView(APIView): def post(self, request): name = request.data.get("name") pwd = request.data.get("pwd") user = User.objects.filter(name=name, pwd=pwd).first() res = {"state_code": 1000, "msg": None} if user: random_str = get_random_str(user.name) Token.objects.update_or_create(user=user, defaults={"token": random_str}) res["token"] = random_str else: res["state_code"] = 1001 # 錯誤狀態碼 res["msg"] = "用戶名或密碼錯誤" # import json # from django.shortcuts import HttpResponse # return HttpResponse(json.dumps(res,ensure_ascii=False)) # from django.http import JsonResponse # return JsonResponse(res) return Response(res)
前后端分離 用token
不分離 用cookies 、sessions
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.authentication import BaseAuthentication from rest_framework import exceptions token_list = ['sfsfss123kuf3j123', ] class TestAuthentication(BaseAuthentication): def authenticate(self, request): """ None,表示跳過該驗證; 如果跳過了所有認證,默認用戶和Token和使用配置文件進行設置 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): # 驗證失敗時,返回的響應頭WWW-Authenticate對應的值 # return 123 pass class TestView(APIView): authentication_classes = [TestAuthentication, ]def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容')
REST_FRAMEWORK = { # 登錄認證 "DEFAULT_AUTHENTICATION_CLASSES": [], # 權限控制 "DEFAULT_PERMISSION_CLASSES": [], # 頻率控制 "DEFAULT_THROTTLE_CLASSES": [] }
2、權限控制
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import BasePermission class TestPermission(BasePermission): message = "權限驗證失敗" def has_permission(self, request, view): """ from rest_framework.generics import GenericAPIView 判斷是否有權限訪問當前請求 :return: True有權限;False無權限 """ if request.user == "管理員": return True
def wait(self):
# 返回需要在等多久才能訪問
pass
def has_object_permission(self, request, view, obj): """ GenericAPIView使用get_object時獲取對象時,觸發單獨對象權限驗證 True有權限;False無權限 """ if request.user == "管理員": return True class TestView(APIView): authentication_classes = [] permission_classes = [TestPermission, ] def get(self, request, *args, **kwargs): print(request.user) print(request.auth) return Response('GET請求,響應內容')
3、頻率限制
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""" 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,表示可以通過;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): return Response('GET請求,響應內容') def throttled(self, request, wait): """訪問次數被限制時,定制錯誤信息""" class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
基於用戶IP顯示訪問頻率(利於Django緩存)
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): 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): return Response('GET請求,響應內容') def throttled(self, request, wait): """訪問次數被限制時,定制錯誤信息""" class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'test_scope': '10/m', }, }
view中限制請求頻率
REST_FRAMEWORK = { 'DEFAULT_THROTTLE_RATES': { 'xxxxxx': '10/m', }, }
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): 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): return Response('GET請求,響應內容') def throttled(self, request, wait): """ 訪問次數被限制時,定制錯誤信息 """ class Throttled(exceptions.Throttled): default_detail = '請求被限制.' extra_detail_singular = '請 {wait} 秒之后再重試.' extra_detail_plural = '請 {wait} 秒之后再重試.' raise Throttled(wait)
匿名時用IP限制+登錄時用Token限制
REST_FRAMEWORK = { 'UNAUTHENTICATED_USER': None, 'UNAUTHENTICATED_TOKEN': None, # 登錄認證 "DEFAULT_AUTHENTICATION_CLASSES": [], # 權限控制 "DEFAULT_PERMISSION_CLASSES": [], # 頻率控制 "DEFAULT_THROTTLE_CLASSES": [], "DEFAULT_THROTTLE_RATES": { 'test_scope': '10/m', 'anon': '10/m', 'user': '20/m', } }
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.throttling import SimpleRateThrottle class LuffyAnonRateThrottle(SimpleRateThrottle): """匿名用戶,根據IP進行限制""" scope = "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 = "user" def get_ident(self, request): """認證成功時:request.user是用戶對象;request.auth是token對象""" # return request.auth.token return "user_token" def get_cache_key(self, request, view): """獲取緩存key""" # 未登錄用戶,則跳過 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): return Response('GET請求,響應內容')
request.META包含請求信息
{'ALLUSERSPROFILE': 'C:\\ProgramData',
'APPDATA': 'C:\\Users\\Administrator\\AppData\\Roaming',
'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files',
'COMMONPROGRAMFILES(X86)': 'C:\\Program Files (x86)\\Common Files',
'COMMONPROGRAMW6432': 'C:\\Program Files\\Common Files',
'COMPUTERNAME': 'PC201712041709',
'COMSPEC': 'C:\\Windows\\system32\\cmd.exe',
'DJANGO_SETTINGS_MODULE': 'restdemo.settings',
'FP_NO_HOST_CHECK': 'NO', 'HOMEDRIVE': 'C:',
'HOMEPATH': '\\Users\\Administrator',
'LOCALAPPDATA': 'C:\\Users\\Administrator\\AppData\\Local',
'LOGONSERVER': '\\\\PC201712041709',
'NUMBER_OF_PROCESSORS': '4', 'OS': 'Windows_NT',
'PATH': 'C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36;C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\Scripts;C:\\Python27;E:\\MySQL Server 5.6\\bin;C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\Scripts\\;C:\\Users\\Administrator\\AppData\\Local\\Programs\\Python\\Python36\\;C:\\Users\\Administrator\\AppData\\Local\\atom\\bin',
'PATHEXT': '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC',
'PROCESSOR_ARCHITECTURE': 'AMD64',
'PROCESSOR_IDENTIFIER': 'Intel64 Family 6 Model 60 Stepping 3, GenuineIntel',
'PROCESSOR_LEVEL': '6', 'PROCESSOR_REVISION': '3c03',
'PROGRAMDATA': 'C:\\ProgramData',
'PROGRAMFILES': 'C:\\Program Files',
'PROGRAMFILES(X86)': 'C:\\Program Files (x86)',
'PROGRAMW6432': 'C:\\Program Files',
'PSMODULEPATH': 'C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\Modules\\',
'PUBLIC': 'C:\\Users\\Public', 'PYCHARM_HOSTED': '1', 'PYTHONIOENCODING': 'UTF-8',
'PYTHONPATH': 'C:\\Users\\Administrator\\PycharmProjects\\s9\\restdemo', 'PYTHONUNBUFFERED': '1',
'SESSIONNAME': 'Console', 'SYSTEMDRIVE': 'C:', 'SYSTEMROOT': 'C:\\Windows',
'TEMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp', 'TMP': 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp',
'USERDOMAIN': 'PC201712041709',
'USERNAME': 'Administrator',
'USERPROFILE': 'C:\\Users\\Administrator',
'WINDIR': 'C:\\Windows', 'WINDOWS_TRACING_FLAGS': '3',
'WINDOWS_TRACING_LOGFILE': 'C:\\BVTBin\\Tests\\installpackage\\csilogfile.log',
'RUN_MAIN': 'true', 'SERVER_NAME': 'PC201712041709',
'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8000',
'REMOTE_HOST': '',
'CONTENT_LENGTH': '',
'SCRIPT_NAME': '',
'SERVER_PROTOCOL': 'HTTP/1.1',
'SERVER_SOFTWARE': 'WSGIServer/0.2',
'REQUEST_METHOD': 'GET',
'PATH_INFO': '/authors/',
'QUERY_STRING': 'token=8204b8e3ac40bf59ae480d17c146b51a',
'REMOTE_ADDR': '127.0.0.1',
'CONTENT_TYPE': 'text/plain',
'HTTP_HOST': '127.0.0.1:8000',
'HTTP_CONNECTION': 'keep-alive',
'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36',
'HTTP_UPGRADE_INSECURE_REQUESTS': '1',
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'csrftoken=jtus3l4GJEc9TFXWYCWxkBIZprcOv7C1vFMIyOHs7Zkxt015FwVZ2KEEeDV6LOyN', 'wsgi.input': <_io.BufferedReader name=832>, 'wsgi.errors': <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'>, 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>}
根據URL傳入的后綴,決定數據如何渲染到頁面上
4、版本控制
1、添加配置
2、設置路由
3、獲取版本

REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key } -------------------------------------------------------------------------- urlpatterns = [ path('admin/', admin.site.urls), re_path("test/", views.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請求,響應內容')
REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key } -------------------------------------------------------------------------- urlpatterns = [ path('admin/', admin.site.urls), re_path("test/(?P<version>[v1|v2]+)/", views.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請求,響應內容')
如:Accept: application/json; version=1.0 -------------------------------------------------------------------------- REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key } -------------------------------------------------------------------------- urlpatterns = [ path('admin/', admin.site.urls), re_path("test/", views.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請求,響應內容')
如:v1.example.com ------------------------------------------------------------------ ALLOWED_HOSTS = ['*'] REST_FRAMEWORK = { 'DEFAULT_VERSION': 'v1', # 默認版本 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允許的版本 'VERSION_PARAM': 'version' # URL中獲取值的key } ------------------------------------------------------------------- urlpatterns = [ path('admin/', admin.site.urls), re_path("test/", views.TestView.as_view(), name="test") ] ------------------------------------------------------------------- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import HostNameVersioning class TestView(APIView): versioning_class = HostNameVersioning 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請求,響應內容')
如:example.com/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'^v1/', ([ url(r'test/', TestView.as_view(), name='test'), ], None, 'v1')), url(r'^v2/', ([ url(r'test/', TestView.as_view(), name='test'), ], None, 'v2')), ] ---------------------------------------------------------------------- #!/usr/bin/env python # -*- coding:utf-8 -*- from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.versioning import NamespaceVersioning class TestView(APIView): versioning_class = NamespaceVersioning 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請求,響應內容')
REST_FRAMEWORK = { 'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning", # url正則 'DEFAULT_VERSION': 'v1', 'ALLOWED_VERSIONS': ['v1', 'v2'], 'VERSION_PARAM': 'version' }
5、解析器
根據請求頭 content-type 選擇對應的解析器就請求體內容進行處理。
application/x-www-form-urlencoded
multipart/form-data
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
)
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'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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請求,響應內容')
upload.html
e. 同時多個Parser
當同時使用多個parser時,rest framework會根據請求頭content-type自動進行比對,並使用對應parser
from django.conf.urls import url, include from web.views import TestView urlpatterns = [ url(r'test/', TestView.as_view(), name='test'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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請求,響應內容')
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'), ]
#!/usr/bin/env python # -*- coding:utf-8 -*- 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、路由
path("books/", views.BookView.as_view(), name="books"), re_path('books/(?P<pk>\d+)/', views.BookDetailView.as_view(), name="detail_book")
簡單點---->
from blog import views from rest_framework import routers routers = routers.DefaultRouter() routers.register("authors",views.AuthorModelView) # 不能有$符 urlpatterns=[ path("",include("routers.urls")),# 直接分發到routers下 ]
urlpatterns +=routers.urls
效果:

7、 視圖
restful是基於CBV操作的,而每一種請求都對應一個函數,
而restful 提供了五個類
在from rest_framework import mixins下
CreateModelMixin
ListModelMixin
RetrieveModelMixin
UpdateModelMixin
DestroyModelMixin
get----------------> list/retrieve # 查看全部/查看單條(pk=?)
post---------------> create # 添加一條數據
delete-------------> destroy # 刪除一條數據(pk=?)
put----------------> update # 更新局部數據(pk=?)
from rest_framework import generics 對上面方法進行了組合,generics下有很多類都有多個方法。都繼承了GenericAPIView
指定方式對應的函數
AutherModelView.as_view({"get":lest})
但是相對應的類應該繼承 viewsets.ModelViewSet
8、序列化
對queryset序列化以及對請求數據格式校驗
-------------------------------------------- from django.shortcuts import HttpResponse HttpResponse 只能返回字符串和列表數字,復雜的數據結構不能序列化 如果是一個queryset 先list() -------------------------------------------- 自己構造結構 ------------------------------------------- from django.core import serializers publish_list = Publish.objects.all() ret = serializers.serialize("json",publish_list) return HttpResponse(ret) --------------------------------------------
from rest_framework import serializers # 推薦 class BookModelSerializers(serializers.ModelSerializer): title = serializers.CharField(source="course.title") level = serializers.CharField(source="course.get_level_display") authors = serializers.SerializerMethodField() # 顯示超鏈接 view_name: 鏈接的別名 publish = serializers.HyperlinkedIdentityField(view_name="publish_detail", lookup_field="publish_id", lookup_url_kwarg="pk") def get_authors(self, book_obj): temp = [] for obj in book_obj.authors.all(): temp.append(obj.name) return temp class Meta: model = Book # fields = "__all__" fields = ["course", "title", "level", "publish", "authors"] depth = 1 # 當涉及到外鍵關聯時,會在查找一層,不再是只有一個id,默認為0
extra_kwargs = {'category':{'write_only':True}}
required=False // 提交的數據不需要校驗 read_only=True // 只在序列化的時候有用;校驗的時候無用 write_only=True // 只在校驗的時候有用;序列化的時候無用 - category = serializers.ChoiceField(choices=CHOICES,source="get_category_display",read_only=False) # 只在序列化的時候有用 - w_category = serializers.ChoiceField(choices=CHOICES,write_only=True) # 只在校驗的時候有用 - author_list = serializers.ListField(write_only=True) #
url(^"publishers/(?P<pk>\d+)$",views.PublishDetailViewSet.as_view(),name="publish_detail") def get(*args): book_obj = Book.objects.all() ret = BookModelSerializers(book_list, many=True) # 是一個OrderdDict類型 # many默認是False,如果序列化單個對象,many=false return HttpResponse(ret.data) # 序列化后的數據都在。data里
[ { "id": 1, "title": "一", "price": 55, "pub_date": null, "publish": 1, "authors": [ 1, 2, 3 ] }, { "id": 2, "title": "二", "price": 3, "pub_date": null, "publish": 1, "authors": [ 1, 2 ] }, { "id": 3, "title": "三", "price": 21, "pub_date": null, "publish": 2, "authors": [] } ] class Book(models.Model): title = models.CharField(max_length=32) price = models.IntegerField() pub_date = models.DateField() publish = models.ForeignKey("Publish", on_delete=models.CASCADE) authors = models.ManyToManyField("Author") def __str__(self): return self.title
添加數據 時------->也可以進行校驗(modelform一樣) bs = BookModelSerializers(data = request.data) if bs.is_valid(): # # 驗證后的數據在.validated_data # # 錯誤信息在.errors bs.save()
validated_字段名(self,value)
validated(self,attrs) 這兩個自定義驗證
serializers.ValidationError('xxxxxxx')
在字段聲明時,定義驗證
serializers.ChoiceField(validators=[func_name,]) # 權重比上面的高
def func_name(value):
和上面的校驗一樣
更新操作 時------>進行校驗 model_obj=.... bs = BookModelSerializers(instance=model_obj,data=request.data) if bs.is_valid(): bs.save() # update()
# 當繼承的是普通 的Serializers類時
# 必須重寫update 序列化類里 def update(self,instance,valitdated_data): instance.xx = validated_data.get('',instance.xx) if validtated_data.get('xxxx'): instance.xx.set([]) instace.save() return instance
# create()必須重寫這個方法
def create(self,validated_data):
book = .....create
book.author.add(*args)
return book
authors = validated_data.pop("authors") obj = Book.objects.create(**validated_data) obj.authors.add(*authors)
9、分頁
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination,CursorPagination
from rest_framework.paginatio
class MyPagination(CursorPagination): # - 根據加密:http: // www.luffycity.com / api / v1 / student /?cursor = erd8 cursor_query_param = 'cursor' # 根據什么參數取數據?cursor=1 ordering = '-created' page_size = 2
n import PageNumberPagination,LimitOffsetPagination class MyPagination(PageNumberPagination):
# 根據頁碼分頁 page_size = 2 # 限制每頁有幾條數據 page_query_param = "page" # 根據什么參數取數據?page=1 page_size_query_param = "size" # 當存在size參數時,可以臨時修改每頁獲得數據個數 max_page_size = 10 # 當有參數size時,臨時獲得數據也不能超過的數據限制 class MyPagination(LimitOffsetPagination): default_limit=2 # 限制取多少數據
# /?offset=60&limit=10 #從60開始取10個 游標
book_list = Book.objects.all() pnp = MyPagination() book_page = pnp.paginate_queryset(book_list,request,self) ret = BookModelSerializers(book_page,many=True) return HttpResponse(ret.data) # return pnp.get_paginated_response(ret.data)# 調用了Response
from rest_framework import viewsets class AuthorModelView(viewsets.ModelViewSet): queryset = Author.objects.all() serializer_class = AuthorModelSerializers pagination_class = MyPagination
- 根據頁碼:http://www.luffycity.com/api/v1/student/?page=1&size=10 - 根據索引:http://www.luffycity.com/api/v1/student/?offset=60&limit=10 - 根據加密:http://www.luffycity.com/api/v1/student/?page=erd8
頁碼越大速度越慢,為什么如何解決:
原因:頁碼越大向后需要掃描的行數越多,因為每次都是從0開始掃描
解決:
- 限制顯示的頁數 前端展示的頁數 就到page=200
- where id>10000000000 limit 10 在前端cookies保留當前頁碼最大id,最小id
- 將page頁碼 加密 /?page=erd8
10、渲染器
對於瀏覽器會給一個頁面用來顯示信息。
應用中間件要有 ‘rest_framework’
json
http://127.0.0.1:8000/test/?format=json http://127.0.0.1:8000/test.json http://127.0.0.1:8000/test/
表格
http://127.0.0.1:8000/test/?format=admin http://127.0.0.1:8000/test.admin http://127.0.0.1:8000/test/
Form表單
http://127.0.0.1:8000/test/?format=form http://127.0.0.1:8000/test.form http://127.0.0.1:8000/test/
自定義顯示模板
http://127.0.0.1:8000/test/?format=html http://127.0.0.1:8000/test.html http://127.0.0.1:8000/test/
瀏覽器格式API+JSON
http://127.0.0.1:8000/test/?format=api http://127.0.0.1:8000/test.api http://127.0.0.1:8000/test/
#!/usr/bin/env python # -*- coding:utf-8 -*- 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') views.py
