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请求,响应内容')

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