django restfulwork 源码剖析
Posted 阿里山QQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django restfulwork 源码剖析相关的知识,希望对你有一定的参考价值。
from django.views import View class StudentView(View): def get(self,request): return HttpResponse(\'GET\') def post(self,request): return HttpResponse(\'POST\') def put(self,request): return HttpResponse(\'POST\') def delete(self,request): return HttpResponse(\'DELETE\')
那么url必须这么写:
from app01 import views urlpatterns = [ url(r\'^students/$\', views.StudentView.as_view()), ]
使用postman进行测试:
查看源码预备知识:封装
#!/usr/bin/env python # -*- coding: utf-8 -*- class Request(object): def __init__(self, obj): self.obj = obj @property def user(self): return self.obj.authticate() class Auth(object): def __init__(self, name, age): self.name = name self.age = age def authticate(self): return self.name class APIView(object): def dispatch(self): self.f2() def f2(self): a = Auth(\'charles\', 18) req = Request(a) print(req.user) obj = APIView() obj.dispatch()
CBV实现原理: 在View类中有一个dispatch方法,在每个请求到达之后,会先执行,获取请求的method,然后通过反射,执行子类中对应的方法;
from django.views import View class StudentView(View): def dispatch(self, request, *args, **kwargs): # return HttpResponse(\'dispatch\') func = getattr(self,request.method.lower()) ret = func(request, *args, **kwargs) return ret def get(self,request): return HttpResponse(\'GET\') def post(self,request): return HttpResponse(\'POST\') def put(self,request): return HttpResponse(\'POST\') def delete(self,request): return HttpResponse(\'DELETE\')
由上面的例子可以看到,dispatch方法是所有使用CBV的视图必须使用到的方法,为了避免每一个类都实现这个方法,可以通过类的继承,来避免代码的重复:
在下面的例子中,基类MyBaseView实现了一个dispatch方法,子类StudentView实现了继承了MyBaseView和View两个类,在实例化StudentView,并执行其方法的时候,会先在StudentView中寻找dispatch方法,如果没有,会去MyBaseView中去寻找,MyBaseView没有父类,所以会看是self是谁,然后从self的另外一个父类View中去寻找; 顺序是StudentView-->MyBaseView-->View
from django.views import View class MyBaseView(object): def dispatch(self,request, *args, **kwargs): print(\'before\') ret = super(MyBaseView, self).dispatch(request, *args, **kwargs) print(\'after\') return ret class StudentView(MyBaseView,View): def get(self,request,*args, **kwargs): return HttpResponse(\'GET\') def post(self,request,*args, **kwargs): return HttpResponse(\'POST\') def put(self,request,*args, **kwargs): return HttpResponse(\'POST\') def delete(self,request,*args, **kwargs): return HttpResponse(\'DELETE\')
二、Django中间件
1. 中间件执行顺序(中间件最多可以实现5个方法)
正常顺序: 执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response
如果有报错: 执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response
如果视图函数有render: 执行所有process_request-->路由匹配-->执行所有process_view-->执行视图函数-->process_response/process_render_template;
2. 使用中间件做过什么?
- 权限
- 用户登录验证
- django csrf token
那么用户的csrf token是如何实现的?
FBV:在django中,csrf token检测是在process_view方法中实现的,会检查视图是否使用@csrf_exempt,然后去请求体或者cookie中获取token;
如果不使用中间件做csrf token认证,那么可以加@csrf_protect,对指定的实视图做验证;
CBV: 有两种实现方法csrf_exempt
from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator class StudentView(View): @method_decorator(csrf_exempt) def dispatch(self, request, *args, **kwargs): return super(StudentView,self).dispatch(request, *args, **kwargs) def get(self,request,*args, **kwargs): return HttpResponse(\'GET\') def post(self,request,*args, **kwargs): return HttpResponse(\'POST\') def put(self,request,*args, **kwargs): return HttpResponse(\'POST\') def delete(self,request,*args, **kwargs): return HttpResponse(\'DELETE\')
from django.views.decorators.csrf import csrf_exempt from django.utils.decorators import method_decorator @method_decorator(csrf_exempt, name=\'dispatch\') class StudentView(View): def get(self,request,*args, **kwargs): return HttpResponse(\'GET\') def post(self,request,*args, **kwargs): return HttpResponse(\'POST\') def put(self,request,*args, **kwargs): return HttpResponse(\'POST\')
三、restful 规范
1.根据method 不同,做不同的操作
url(r\'^order/$\', views.order), def order(request): if request.method == \'GET\': return HttpResponse(\'获取订单\') elif request.method == \'POST\': return HttpResponse(\'创建订单\') elif request.method == \'PUT\': return HttpResponse(\'更新订单\') elif request.method == \'DELETE\': return HttpResponse(\'删除订单\')
参考:https://www.cnblogs.com/wupeiqi/articles/7805382.html
四、restframework
使用自定义的类,实现API 认证; 具体看源码,和CBV 执行流程类似;
from rest_framework.views import APIView from rest_framework.request import Request from rest_framework import exceptions class MyAuthenticate(object): def authenticate(self,request): token = request._request.GET.get(\'token\') if not token: raise exceptions.AuthenticationFailed(\'用户认证失败\') def authenticate_header(self, val): pass class DogView(APIView): authentication_classes = [MyAuthenticate, ] def get(self,request,*args, **kwargs): ret = { \'code\': 1000, \'msg\': \'xxx\' } return HttpResponse(json.dumps(ret),status=201) def post(self,request,*args, **kwargs): return HttpResponse(\'POST\') def put(self,request,*args, **kwargs): return HttpResponse(\'POST\') def delete(self,request,*args, **kwargs): return HttpResponse(\'DELETE\')
需要掌握的内容:
1.中间件
2.CBV
3.csrf
4.规范
5.djangorestframework
- 如何验证(基于数据库实现用户认证)
-源码流程(面向对象回顾流程)
五、restframework之登录
问题: 有些API用户登录之后才可以访问,有些不需要用户登录;
先创建两张表
class UserInfo(models.Model): # 用户表,存储用户信息 user_type_choices = ( (1,\'普通用户\'), (2,\'VIP\'), (3,\'SVIP\'), ) user_type = models.IntegerField(choices=user_type_choices) username = models.CharField(max_length=32, unique=True) password = models.CharField(max_length=64) class UserToken(models.Model): # 存储用户登录成功之后的token user = models.OneToOneField(to=\'UserInfo\') token = models.CharField(max_length=64)
编写API
url(r\'^api/v1/auth/$\', views.AuthView.as_view()) from django.http import JsonResponse # Create your views here. from rest_framework.views import APIView from api import models def md5(user): import hashlib import time ctime = str(time.time()) m = hashlib.md5(bytes(user, encoding=\'utf-8\')) m.update(bytes(ctime, encoding=\'utf-8\')) return m.hexdigest() class AuthView(APIView):
"""
用于用户登录认证
""" def post(self, request, *args, **kwargs): ret = {\'code\': 10000, \'msg\': None } try: user = request._request.POST.get(\'username\') pwd = request._request.POST.get(\'password\') obj = models.UserInfo.objects.filter(username=user,password=pwd).first() if not obj: ret[\'code\'] = 1001 ret[\'msg\'] = \'用户名或密码错误\' # 为登录用户创建token token = md5(user) # 用户token存在就更新,不存在就创建 models.UserToken.objects.update_or_create(user=obj, defaults={\'token\': token}) ret[\'token\'] = token # 将token返回给用户 except Exception as e: ret[\'code\'] = 1002 ret[\'msg\'] = \'请求异常\' return JsonResponse(ret)
使用postman发送请求进行验证
六、 rest framework之基于token实现基本用户认证
上面的例子是用户通过访问登录认证的API,获取返回的token,并将token存储到token表中;
用户拿到这个token之后,就可以访问其他的API了;
from rest_framework.views import APIView from rest_framework.views import exceptions from rest_framework.authentication import BaseAuthentication from api import models ORDER_DICT = { 1: { \'name\': \'charles\', \'age\': 18, \'gender\': \'男\', \'content\': \'....\' }, 2: { \'name\': \'男\', \'age\': 19, \'gender\': \'男\', \'content\': \'......\' }, } class Authtication(object): def authenticate(self, request): token = request._request.GET.get(\'token\') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed(\'用户认证失败\') # 在restframework内部会将整个两个字段赋值给request,以供后续继续使用 return (token_obj.user, token_obj) def authenticate_header(self, request): pass class OrderView(APIView): """ 订单相关业务 """ authentication_classes = [Authtication, ] # 在访问API的时候,先走这个用户认证的类 def get(self, request, *args, **kwargs): ret = {\'code\': 1000, \'msg\': None, \'data\': None} # request.user --> token_obj.user # request.auth --> token_obj try: ret[\'data\'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret)
七、rest framework之认证基本流程源码分析
1、请求进来之后,会先执行dispatch()方法;
class OrderView(APIView): """ 订单相关业务 """ authentication_classes = [Authtication, ] def get(self, request, *args, **kwargs): self.dispatch() # 使用pycharm进入dispatch方法,查看源码
2、先对request进行封装
self.args = args self.kwargs = kwargs request = self.initialize_request(request, *args, **kwargs) # 对request进行封装,点击继续查看该方法 def initialize_request(self, request, *args, **kwargs): """ Returns the initial request object. """ parser_context = self.get_parser_context(request) return Request( request, parsers=self.get_parsers(), authenticators=self.get_authenticators(), # 执行这个方法会先在本子类中找,显然,子类中是有这个方法的,继续点击查看这个方法;[] negotiator=self.get_content_negotiator(), parser_context=parser_context ) def get_authenticators(self): """ Instantiates and returns the list of authenticators that this view can use. """ return [auth() for auth in self.authentication_classes] # 通过列表生成式,执行self.authentication_classes方法,因为子类中没有这个方法,
那么会执行父类中的这个方法; 点击继续查看; authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 这个是存在于父类中的; 我们可以在自己的实例化的子类中使用自定义的类,替代这个类; 默认升是
BaseAuthentication
3、认证
self.initial(request, *args, **kwargs) # 继续点击查看; self.perform_authentication(request) # 实现认证; def perform_authentication(self, request): request.user # 执行request.user @property def user(self): """ Returns the user associated with the current request, as authenticated by the authentication classes provided to the request. """ if not hasattr(self, \'_user\'): with wrap_attributeerrors(): # 获取认证对象,进行进一步认证 self._authenticate() return self._user def _authenticate(self): """ Attempt to authenticate the request using each authentication instance in turn. """ # 循环所有authenticator对象 for authenticator in self.authenticators: try: # 执行authenticate方法 # 1.如果authenticate 方法抛出异常,self._not_authenticated执行 # 2. 没有抛出异常,有返回值,必须是元祖:(request.user, request.auth) # 3. 返回None,我不管,下一个认证进行处理; # 4.如果都返回None,执行self._not_authenticated(),返回(AnonymousUser, None) user_auth_tuple = authenticator.authenticate(self) except exceptions.APIException: self._not_authenticated() raise if user_auth_tuple is not None: self._authenticator = authenticator # request.user和request.auth self.user, self.auth = user_auth_tuple return def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: # AnonymousUser 匿名用户 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
简要流程图如下:
八、rest framework之匿名用户配置
1.认证类的全局配置(全局使用)
authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES # 认证类的默认配置 api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS) def reload_api_settings(*args, **kwargs): setting = kwargs[\'setting\'] if setting == \'REST_FRAMEWORK\': # 获取settings的REST_FRAMEWORK 的配置项 api_settings.reload()
在settings中定义 REST_FRAMEWORK 配置
REST_FRAMEWORK = { \'DEFAULT_AUTHENTICATION_CLASSES\': [\'api.utils.auth.FirstAuthtication\', \'api.utils.auth.Authtication\'] } # 将认证的类,放入到上面配置的路径里面 # 视图函数中不包含上述的认证的类,并且要将登陆的API的authentication_classes设为空; class AuthView(APIView): authentication_classes = [] def post(self, request, *args, **kwargs): pass class OrderView(APIView): """ 订单相关业务 """ def get(self, request, *args, **kwargs): ret = {\'code\': 1000, \'msg\': None, \'data\': None} # request.user --> token_obj.user # request.auth --> token_obj try: ret[\'data\'] = ORDER_DICT except Exception as e: pass return JsonResponse(ret)
2、在用户没有登录(匿名用户的时候)
def _not_authenticated(self): """ Set authenticator, user & authtoken representing an unauthenticated request. Defaults are None, AnonymousUser & None. """ self._authenticator = None if api_settings.UNAUTHENTICATED_USER: # 获取settings中的用户默认用户是啥 self.user = api_settings.UNAUTHENTICATED_USER() else: self.user = None if api_settings.UNAUTHENTICATED_TOKEN: # 获取settings中的默认token是啥 self.auth = api_settings.UNAUTHENTICATED_TOKEN() else: self.auth = None
没有通过认证,默认为匿名用户
class UserInfo(APIView): authentication_classes = [] def get(self,request, *args, **kwargs): ret = {\'code\': 1000, \'msg\': None, \'data\': None} print(request.user) return JsonResponse(ret # 打印结果 AnonymousUser # 在settings.py中增加如下配置,未登录时,用户和auth都为None,方面后续判断用户是否登录; REST_FRAMEWORK = { \'DEFAULT_AUTHENTICATION_CLASSES\': [\'api.utils.auth.FirstAuthtication\', \'api.utils.auth.Authtication\'], \'UNAUTHENTICATED_USER\':None, #request.user \'UNAUTHENTICATED_TOKEN\':None # request.auth }
九、rest framework之内置基本认证
1.BaseAuthentication 基类,可以规范自定义的认证的类
from rest_framework.authentication import BaseAuthentication, BasicAuthentication class FirstAuthtication(BaseAuthentication): def authenticate(self, request): pass def authenticate_header(self, request): pass class Authtication(BaseAuthentication): def authenticate(self, request): token = request._request.GET.get(\'token\') token_obj = models.UserToken.objects.filter(token=token).first() if not token_obj: raise exceptions.AuthenticationFailed(\'用户认证失败\') # 在restframework内部会将整个两个字段赋值给request,以供后续继续使用 return (token_obj.user, token_obj) def authenticate_header(self, request): return \'Basic realm="api"\' # api信息加入请求头
2.其他认证 BasicAuthentication
十、rest framework之权限的基本使用
使用方法和自定义认证类非常类似
1.定义权限类
# 定义权限类 class MyPermisson(object): def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermisson1(object): def has_permission(self, request, view): if request.user.user_type == 3: return False return True
2. 使用自定义类
class OrderView(APIView): """ 订单相关业务 """ permission_classes = [MyPermisson, ] def get(self, request, *args, **kwargs): ret = {\'code\': 1000, \'msg\': None, \'data\': None} # request.user --> token_obj.user # request.auth --> token_obj
十一、 rest framework之权限源码流程
# 查看dispatch中检测权限的方法 def check_permissions(self, request): """ Check if the request should be permitted. Raises an appropriate exception if the request is not permitted. """ for permission in self.get_permissions(): if not permission.has_permission(request, self): self.permission_denied( request, message=getattr(permission, \'message\', None) )
定义全局的权限检测类,并使用全局配置定义全局权限检测的类
class SVIPPermisson(object): message = \'必须是SVIP才能访问\' def has_permission(self, request, view): if request.user.user_type != 3: return False return True #定义配置 REST_FRAMEWORK = { \'DEFAULT_AUTHENTICATION_CLASSES\': [\'api.utils.auth.FirstAuthtication\', \'api.utils.auth.Authtication\'], \'UNAUTHENTICATED_USER\':None, #request.user \'UNAUTHENTICATED_TOKEN\':None, # request.auth \'DEFAULT_PERMISSION_CLASSES\':[\'api.utils.permission.SVIPPermisson\'] # request.auth }
十二、rest framework之权限的内置类
django 内置的权限类在实际生成的环境不建议使用,但是建议继承,可以帮助规范自定义权限类的方法名称;
除了BasePermission之外,还有其他的类:
#实现代码:
from rest_framework.permissions import BasePermission class SVIPPermisson(BasePermission): message = \'必须是SVIP才能访问\' def has_permission(self, request, view): if request.user.user_type != 3: return False return True class MyPermisson1(BasePermission): def has_permission(self, request, view): if request.user.user_type == 3: return False return True
十三、rest framework之访问频率控制基本实现
import time VISIT_RECODE = {} # 放在全局变量中,重启之后,就会变空,可以放到缓存中 from rest_framework.throttling import BaseThrottle class VisitThrottle(object): """60秒内只能访问3次""" def __init__(self): self.histoy = None def allow_request(self, request, view): # 1.获取用户IP remote_addr = request.META.get(\'REMOTE_ADDR\') print(remote_addr) ctime = time.time() if remote_addr not in VISIT_RECODE: VISIT_RECODE[remote_addr] = [ctime, ] return True history = VISIT_RECODE.get(remote_addr) self.histoy = history while history and history[-1] < ctime -60: # 如果记录的时间戳超过60s以内,就删除; history.pop() if len(history) < 3: history.insert(0, ctime) return True # return True # 表示可以继续访问 # return False # 表示访问频率太高,被限制 def wait(self): """还有等多少秒才能访问 return 10 等10S才能访问""" ctime = time.time() return 60 - (ctime - self.histoy[-1]) class AuthView(APIView): authentication_classes = [] throttle_classes = [VisitThrottle,]
十四、rest framework之访问频率控制源码流程
源码流程和上述认证与权限源码流程类似,下面使用全局配置类控制访问频率:
将上述代码挪到目录api.utils.thottle.VisitThrottle
REST_FRAMEWORK = { \'DEFAULT_AUTHENTICATION_CLASSES\': [\'api.utils.auth.FirstAuthtication\', \'api.utils.auth.Authtication\'], \'UNAUTHENTICATED_USER\': None, # request.user \'UNAUTHENTICATED_TOKEN\': None, # request.auth \'DEFAULT_PERMISSION_CLASSES\':[\'api.utils.permission.SVIPPermisson\'], # request.auth \'DEFAULT_THROTTLE_CLASSES\': [\'api.utils.thottle.VisitThrottle\'] # 全局生效 }
十五、rest framework之基于内置类实现访问频率控制
实际上,内置的访问频率类已经实现了上述的方法,可以通过配置来自定义访问频率,VISIT_RECODE 是放置在缓存中的:
from rest_framework.throttling import BaseThrottle, SimpleRateThrottle class VisitThrottle(SimpleRateThrottle): scope = \'visit\' def get_cache_key(self, request, view): return self.get_ident(request) # 获取用户IP class UserThrottle(SimpleRateThrottle): scope = \'user\' def get_cache_key(self, request, view): return request.user.username ###配置### REST_FRAMEWORK = { \'DEFAULT_AUTHENTICATION_CLASSES\': [\'api.utils.auth.FirstAuthtication\', \'api.utils.auth.Authtication\'], \'UNAUTHENTICATED_USER\': None, # request.user \'UNAUTHENTICATED_TOKEN\': None, # request.auth \'DEFAULT_PERMISSION_CLASSES\':[\'api.utils.permission.SVIPPermisson\'], # request.auth \'DEFAULT_THROTTLE_CLASSES\': [\'api.utils.thottle.VisitThrottle\'], #默认为对匿名用户做限制 \'DEFAULT_THROTTLE_RATES\': { \'visit\': \'3/m\', # 一分钟访问3次 \'user\': \'10/m\' } } ###同时对登录用户做限制##### from api.utils.thottle import UserThrottle class OrderView(APIView): """ 订单相关业务 """ # permission_classes = [SVIPPermisson, ] throttle_classes = [UserThrottle, ] # 只有用户登录才可以查看订单,使用另外一个访问频率限制类;
十六、版本
1、在url中通过GET传参:
使用自定义的类解析版本
class ParamVersion(object): def determine_version(self, request, *args, **kwargs): version = request.query_params.get(\'version\') return version class UserView(APIView): versioning_class = ParamVersion permission_classes = [] authentication_classes = [] throttle_classes = [] def get(self, request, *args, **kwargs): print(request.version) return HttpResponse(\'用户列表\') #get请求如下: http://127.0.0.1:8080/api/users/?version=v3
使用内置的类解析版本参数,还可以通过配置定义默认的版本以及允许的版本:
from rest_framework.versioning import QueryParameterVersioning, class ParamVersion(object): def determine_version(self, request, *args, **kwargs): version = request.query_params.get(\'version\') return version class UserView(APIView): versioning_class = QueryParameterVersioning permission_classes = [] authentication_classes = [] throttle_classes = [] def get(self, request, *args, **kwargs): print(request.version) return HttpResponse(\'用户列表\') #####settings##### REST_FRAMEWORK = { \'DEFAULT_VERSION\' : \'v1\', # 默认的版本 \'ALLOWED_VERSIONS\' : [\'v1\', \'v2\'], # 允许请求的版本 \'VERSION_PARAM\': \'version\', # 版本的参数的key }
2、在URL中传参(推荐使用): 版本在使用的时候,无需自定义,使用下面的方式就可以实现了;
urlpatterns = [ url(r\'^(?P<version>[v1|v2]+)/users/$\', views.UserView.as_view()), ] from rest_framework.versioning import QueryParameterVersioning, URLPathVersioning class UserView(APIView): versioning_class = URLPathVersioning # 除了在这儿设置之外,还可以在配置中设置 permission_classes = [] authentication_classes = [] throttle_classes = [] def get(self, request, *args, **kwargs): print(request.version) return HttpResponse(\'用户列表\') ###settings.py######在配置中设置 REST_FRAMEWORK = { \'DEFAULT_VERSIONING_CLASS\': \'rest_framework.versioning.URLPathVersioning\', \'DEFAULT_VERSION\' : \'v1\', \'ALLOWED_VERSIONS\' : [\'v1\', \'v2\'], \'VERSION_PARAM\': \'version\' }
十七、rest framework框架之版本源码
# 可以在视图中反向解析URL
from django.urls import reverse class UserView(APIView): # versioning_class = ParamVersion # versioning_class = URLPathVersioning permission_classes = [] authentication_classes = [] throttle_classes = [] def get(self, request, *args, **kwargs): # 1.获取版本 print(request.version) # 2.获取处理版本的对象 print(request.versioning_scheme) # 3.反向生成URL(REST FRAMEWORK) url1 = request.versioning_scheme.reverse(viewname=\'uuu\', request=request) print(url1) # 4.反向生成URL url2 = reverse(viewname=\'uuu\', kwargs={\'version\': 2}) print(url2) return HttpResponse(\'用户列表\') ###打印结果 <rest_framework.versioning.URLPathVersioning object at 0x04335D50> http://127.0.0.1:8080/api/v2/users/ /api/2/users/
十八、解析器
1.解析器预备知识(post提交的数据,会保存在request.body中,转换为QueryDict才能被request.post获取到)
#点击查看源码, from django.core.handlers.wsgi import WSGIRequest elif self.content_type == \'application/x-www-form-urlencoded\': self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict() # #如果想通过request.POST获取到post提交的数据,那么必须满足如下两个条件: django:request.POST/ request.body 1. 请求头要求: Content-Type: application/x-www-form-urlencoded PS: 如果请求头中的 Content-Type: application/x-www-form-urlencoded,request.POST中才有值(去request.body中解析数据)。 2. 数据格式要求: name=charles&age=18&gender=男 # 如果不满足上述条件,那么就必须使用request.body将字节转换为str,然后再做解析: 如: a. form表单提交,请求头和数据都满足上述条件: <form method...> input... </form> b. ajax提交 $.ajax({ url:... type:POST, data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男 }) 情况一: #数据满足,请求头不满足 $.ajax({ url:... type:POST, headers:{\'Content-Type\':"application/json"} data:{name:alex,age=18} # 内部转化 name=alex&age=18&gender=男 }) # body有值;POST无 情况二:# 数据和请求头都不满足 $.ajax({ url:... type:POST, headers:{\'Content-Type\':"application/json"} data:JSON.stringfy({name:alex,age=18}) # {name:alex,age:18...} }) # body有值;POST无 # json.loads(request.body)
# rest framework 解析器
#JSONParser支持解析请求头为application/json的数据 #FormParser 支持解析请求头为content-type:application/x-www-form-urlencoded的数据 from rest_framework.parsers import JSONParser,FormParser class ParserView(APIView): # parser_classes = [JSONParser,FormParser,] #查看请求头,自动匹配解析器 """ JSONParser:表示只能解析content-type:application/json头 JSONParser:表示只能解析content-type:application/x-www-form-urlencoded头 """ def post(self, request, *args, **kwargs): """ 允许用户发送JSON格式数据 a. content-type: application/json b. {\'name\':\'alex\',age:18} :param request: :param args: :param kwargs: :return: """ """ 1. 获取用户请求 2. 获取用户请求体 3. 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较 4. JSONParser对象去请求体 5. request.data """ print(request.data) # 解析后的数据 return HttpResponse(\'ParserView\')
通过request.data可以看到解析器的源码,分析得到,解析器可以通过配置定义全局的解析器:
REST_FRAMEWORK = { \'DEFAULT_PARSER_CLASSES\' : [\'rest_framework.parsers.JSONParser\', \'rest_framework.parsers.FormParser\'] } 使用非默认的解析器使用配置如下: class ParserView(APIView): parser_classes = [JSONParser,FormParser,] # 自己的视图类中使用的解析器 """ #除了之外,还有如下的解析器: class FormParser(BaseParser): """ Parser for form data. """ media_type = \'application/x-www-form-urlencoded\' def parse(self, stream, media_type=None, parser_context=None): """ Parses the incoming bytestream as a URL encoded form, and returns the resulting QueryDict. """ parser_context = parser_context or {} encoding = parser_context.get(\'encoding\', settings.DEFAULT_CHARSET) data = QueryDict(stream.read(), encoding=encoding) return data class MultiPartParser(BaseParser): """ Parser for multipart form data, which may include file data. """ media_type = \'multipart/form-data\' def parse(self, stream, media_type=None, parser_context=None): """ Parses the incoming bytestream as a multipart encoded form, and returns a DataAndFiles object. `.data` will be a `QueryDict` containing all the form parameters. `.files` will be a `QueryDict` containing all the form files. """ parser_context = parser_context or {} request = parser_context[\'request\'] encoding = parser_context.get(\'encoding\', settings.DEFAULT_CHARSET) meta = request.META.copy() meta[\'CONTENT_TYPE\'] = media_type upload_handlers = request.upload_handlers try: parser = DjangoMultiPartParser(meta, stream, upload_handlers, encoding) data, files = parser.parse() return DataAndFiles(data, files) except MultiPartParserError as exc: raise ParseError(\'Multipart form parse error - %s\' % six.text_type(exc)) class FileUploadParser(BaseParser): """ Parser for file upload data. """ media_type = \'*/*\' errors = { \'unhandled\': \'FileUpload parse error - none of upload handlers can handle the stream\', \'no_filename\': \'Missing filename. Request should include a Content-Disposition header with a filename parameter.\', } def parse(self, stream, media_type=None, parser_context=None): """ Treats the incoming bytestream as a raw file upload and returns a `DataAndFiles` object. `.data` will be None (we expect request body to be a file content). `.files` will be a `QueryDict` containing one \'file\' element. """ parser_context = parser_context or {} request = parser_context[\'request\'] encoding = parser_context.get(\'encoding\', settings.DEFAULT_CHARSET) meta = request.META upload_handlers = request.upload_handlers filename = self.get_filename(stream, media_type, parser_context) if not filename: raise ParseError(self.errors[\'no_filename\']) # Note that this code is extracted from Django\'s handling of # file uploads in MultiPartParser. content_type = meta.get(\'HTTP_CONTENT_TYPE\', meta.get(\'CONTENT_TYPE\', \'\')) try: content_length = int(meta.get(\'HTTP_CONTENT_LENGTH\', meta.get(\'CONTENT_LENGTH\', 0))) except (ValueError, TypeError): content_length = None # See if the handler will want to take care of the parsing. for handler in upload_handlers: result = handler.handle_raw_input(stream, meta, content_length, None, encoding) if result is not None: return DataAndFiles({}, {\'file\': result[1]}) # This is the standard case. possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size] chunk_size = min([2 ** 31 - 4] + possible_sizes) chunks = ChunkIter(stream, chunk_size) counters = [0] * len(upload_handlers) for index, handler in enumerate(upload_handlers): try: handler.new_file(None, filename, content_type, content_length, encoding) except StopFutureHandlers: upload_handlers = upload_handlers[:index + 1] break for chunk in chunks: for index, handler in enumerate(upload_handlers): chunk_length = len(chunk) chunk = handler.receive_data_chunk(chunk, counters[index]) counters[index] += chunk_length if chunk is None: break for index, handler in enumerate(upload_handlers): file_obj = handler.file_complete(counters[index]) if file_obj is not None: return DataAndFiles({}, {\'file\': file_obj}) raise ParseError(self.errors[\'unhandled\']) def get_filename(self, stream, media_type, parser_context): """ Detects the uploaded file name. First searches a \'filename\' url kwarg. Then tries to parse Content-Disposition header. """ try: return parser_context[\'kwargs\'][\'filename\'] except KeyError: pass try: meta = parser_context[\'request\'].META disposition = parse_header(meta[\'HTTP_CONTENT_DISPOSITION\'].encode(\'utf-8\')) filename_parm = disposition[1] if \'filename*\' in filename_parm: return self.get_encoded_filename(filename_parm) return force_text(filename_parm[\'filename\']) except (AttributeError, KeyError, ValueError): pass def get_encoded_filename(self, filename_parm): """ Handle encoded filenames per RFC6266. See also: http://tools.ietf.org/html/rfc2231#section-4 """ encoded_filename = force_text(filename_parm[\'filename*\']) try: charset, lang, filename = encoded_filename.split(\'\\\'\', 2) filename = urlparse.unquote(filename) except (ValueError, LookupError): filename = force_text(filename_parm[\'filename\']) return filename
引申内容如下:
1. http 状态码 2. http请求方法 3. http 请求头
十九、 rest framework框架之序列化
数据库表结构如下:
class UserGroup(models.Model): title = models.CharField(max_length=32) class UserInfo(models.Model): user_type_choices = ( (1,\'普通用户\'), (2,\'VIP\'), (3,\'SVIP\'), ) user_type = models.IntegerField(choices=user_type_choices) group = models.ForeignKey(\'UserGroup\') username = models.CharField(max_length=32, unique=True) password = models.CharField(max_length=64) roles = models.ManyToManyField(\'Role\') class UserToken(models.Model): user = models.OneToOneField(to=\'UserInfo\') token = models.CharField(max_length=64) class Role(models.Model): title = models.CharField(max_length=32)
1、序列化基本使用
a. django的序列化
如果是django的QuerySet对象,直接使用json.dumps进行处理,是会报错的,使用django的序列化工具不太好用,一版我们使用values/value_list转换为列表之后,再进行序列化:
import json class RoleView(APIView): def get(self, request, *args, **kwargs): roles = models.Role.objects.all().values(\'id\', \'title\') roles = list(roles) ret = json.dumps(roles, ensure_ascii=False) # ensure_ascii=False 表示如果有中文,不是输出字节码,而是中文字符 return HttpResponse(ret)
b.使用rest framework的序列化工具
b1.
from rest_framework import serializers class RolesSerializer(serializers.Serializer): # 下面的字段必须是数据库的字段 id = serializers.IntegerField() title = serializers.CharField() class RoleView(APIView): def get(self, request, *args, **kwargs): roles = models.Role.objects.all() ser = RolesSerializer(instance=roles, many=True) # 如果QuerySet不是一个对象,使用many=True,如果是一个对象,如.first()/.last(),那么使用many=False ret = json.dumps(ser.data, ensure_ascii=False) return HttpResponse(ret)
b2.
上述序列化的是简单的CharField字典,如果字段是choice/ForeignKey/ManyToMany,那么如何序列化呢?
class UserInfoSerializer(serializers.Serializer): xxxx = serializers.CharField(source=\'user_type\') # 显示choice的id ooo = serializers.CharField(source=\'get_user_type_display\') # 显示choice的value username = serializers.CharField() password = serializers.CharField() gp = serializers.CharField(source=\'group.title\') # source指定序列化的字段 # rls = serializers.CharField(source=\'roles.all\') rls = serializers.SerializerMethodField() # ManyToMany 可以指定方法,由方法返回需要被序列化展示的内容 def get_rls(self, row): # 方法名为get_名称(这里是rls) roles_obj_list = row.roles.all() ret = [] for item in roles_obj_list: ret.append({\'id\': item.id, \'title\': item.title}) return ret class UserInfoView(APIView): def get(self, request, *args, **kwargs): users = models.UserInfo.objects.all() ser = UserInfoSerializer(instance=users, many=True) ret = json.dumps(ser.data, ensure_ascii=False) return HttpResponse(ret)
参考: http://www.cnblogs.com/wupeiqi/articles/7805382.html
b3.
使用rest framework ModelSerializer也可以使用上述的序列化的功能,但是更省事:
class UserInfoSerializer(serializers.ModelSerializer): ooo = serializers.CharField(source=\'get_user_type_display\') rls = serializers.SerializerMethodField() class Meta: model = models.UserInfo # fields = "__all__" # 显示所有字段,但是外键部分只显示ID fields = [\'id\', \'username\', \'password\', \'ooo\', \'rls\', \'group\'] def get_rls(self, row): roles_obj_list = row.roles.all() ret = [] for item in roles_obj_list: ret.append({\'id\': item.id, \'title\': item.title}) return ret
在序列化的时候,上面的CharField可以使用自定义的类(一般不使用):
class MyField(serializers.CharField): pass class UserInfoSerializer(serializers.ModelSerializer): ooo = serializers.CharField(source=\'get_user_type_display\') rls = serializers.SerializerMethodField() x1 = MyField(source=\'username\') class Meta: model = models.UserInfo # fields = "__all__" # 显示所有字段,但是外键部分只显示ID fields = [\'id\', \'username\', \'password\', \'ooo\', \'rls\', \'group\', \'x1\'] def get_rls(self, row): roles_obj_list = row.roles.all() ret = [] for item in roles_obj_list: ret.append({\'id\': item.id, \'title\': item.title}) return ret class MyField(serializers.CharField): def to_representation(self, value): print(value) return \'xxxxx\' # 返回值, 这里将返回显示的值写死了,而不是从数据库中去获取
b4.
使用depth, 可以自动序列化连表
class UserInfoSerializer(serializers.ModelSerializer): class Meta: model = models.UserInfo # fields = "__all__" # 显示所有字段,但是外键部分只显示ID fields = [\'id\', \'username\', \'password\', \'roles\', \'group\'] depth = 1 # 建议值为0~3,默认为0
b5.
自动生成链接
urls.py
urlpatterns = [ url(r\'^(?P<version>[v1|v2]+)/userinfo/$\', views.UserInfoView.as_view()), url(r\'^(?P<version>[v1|v2]+)/usergroup/(?P<xxx>\\d+)$\', views.UserGroupView.as_view(), name=\'gp\'), ]
views.py
class UserInfoSerializer(serializers.ModelSerializer): group = serializers.HyperlinkedIdentityField(view_name=\'gp\', lookup_field=\'group_id\', lookup_url_kwarg=\'xxx\') #lookup_field 从数据库取值 class Meta: model = models.UserInfo fields = [\'id\', \'username\', \'password\', \'roles\', \'group\'] depth = 0 class UserInfoView(APIView): def get(self, request, *args, **kwargs): users = models.UserInfo.objects.all() ser = UserInfoSerializer(instance=users, many=True, context={\'request\': request}) ret = json.dumps(ser.data, ensure_ascii=False) return HttpResponse(ret) class UserGroupSerializer(serializers.ModelSerializer): class Meta: model = models.UserGroup fields = "__all__" # 显示所有字段,但是外键部分只显示ID # fields = [\'id\', \'username\', \'password\', \'roles\', \'group\'] # depth = 0 class UserGroupView(APIView): def get(self, request, *args, **kwargs): pk = kwargs.get(\'xxx\') obj = models.UserGroup.objects.filter(pk=pk).first() print(obj) ser = UserGroupSerializer(instance=obj, many=False) ret = json.dumps(ser.data, ensure_ascii=False) return HttpResponse(ret)
引申知识点: 如何判断一个变量是否是函数
import types def func(arg): # if callable(arg): if isinstance(arg, types.FunctionType): print(arg()) else: print(arg) func(123) func(lambda :"666")
二十、验证用户请求数据
这里我们使用的解析器是: [\'rest_framework.parsers.JSONParser\', \'rest_framework.parsers.FormParser\']
所以提交的验证数据为:
class XXValidator(object): def __init__(self, base): self.base = base def __call__(self, value, *args, **kwargs): # 这里的value是用户提交的数据 if not value.startswith(self.base): message = \'标题必须以 %s 开头\' %self.base raise serializers.ValidationError(message) def set_context(self, seralizer_field): pass class GroupSerializer(serializers.Serializer): title = serializers.CharField(error_messages={\'required\': \'标题不能为空\'}, validators=[XXValidator(\'老男人\')]) # validator表示自定义验证规则,
class GroupView(APIView): def post(self, request, *args, **kwargs): ser = GroupSerializer(data=request.data) # request.data 获取请求体中的数据 if ser.is_valid(): print(ser.validated_data) else: print(ser.errors) # 输出 {\'title\': [\'标题必须以 老男人 开头\']}
return HttpResponse(\'提交数据\')
验证钩子
class GroupSerializer(serializers.Serializer): title = serializers.CharField(error_messages={\'required\': \'标题不能为空\'}, validators=[XXValidator(\'老男人\')]) def validate_title(self, value): # 这里的value是验证的消息,是ser.validated_data,数据通过验证,会走这个钩子函数 from rest_framework import exceptions # raise exceptions.ValidationError(\'哈哈哈\') print(value, "xxxxx") return value
二十一、渲染器
from api.utils.serializers.pager import PagerSerializer from rest_framework.response import Response class Pager1View(APIView): def get(self, request, *args, **kwargs): roles = models.Role.objects.all() ser = PagerSerializer(instance=roles, many=True) return Response(ser.data) # 使用渲染器显示接口数据
为什么会展示上面的内容呢?
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer # 默认使用的渲染器是这里的全部的渲染器 class TestView(APIView): # renderer_classes = [JSONRenderer,BrowsableAPIRenderer] # 可以在这里定义该视图使用的渲染器, def get(self, request, *args, **kwargs): # 获取所有数据 roles = models.Role.objects.all() # 创建分页对象 # pg = CursorPagination() pg = MyCursorPagination() # 在数据库中获取分页的数据 pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self) # 对数据进行序列化 ser = PagerSerialiser(instance=pager_roles, many=True) return Response(ser.data) # 也可以使用全局的配置,配置默认的渲染器 REST_FRAMEWORK = { "DEFAULT_RENDERER_CLASSES":[ \'rest_framework.renderers.JSONRenderer\', \'rest_framework.renderers.BrowsableAPIRenderer\', ] }
当然,我们可以继承上面的渲染器,然后自定制自己的显示页面等内容:
class BrowsableAPIRenderer(BaseRenderer): """ HTML renderer used to self-document the API. """ media_type = \'text/html\' format = \'api\' template = \'rest_framework/api.html\' # 这里页面的内容,我们可以进行在子类中替换,哈哈哈哈 filter_template = \'rest_framework/filters/base.html\' code_style = \'emacs\' charset = \'utf-8\' form_renderer_class = HTMLFormRenderer
二十二、分页器
#自定义序列化的类
#pager.py from rest_framework import serializers from api import models class PagerSerializer(serializers.ModelSerializer): class Meta: model = models.Role fields = "__all__"
22.1
#分页
REST_FRAMEWORK = { \'PAGE_SIZE\': 2, # 定义每页分页的大小 } class Pager1View(APIView): def get(self, request, *args, **kwargs): #获取所有数据 roles = models.Role.objects.all() # 创建分页对象 pg = PageNumberPagination() # 在数据库中获取分页的数据 pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self) # 返回的是分页的对象 # 对分页的数据进行序列化 ser = PagerSerializer(instance=pager_roles, many=True) print(pager_roles) return Response(ser.data)
22.2
除此之外,我们还可以自定义分页的大小,通过自定义的类来实现:
class MyPageNumberPagination(PageNumberPagination): page_size = 2 page_query_param = \'page\' # 查询分页时使用的参数 page_size_query_param = \'size\' # 是否可以自定义查询分页的大小 max_page_size = 5 # 每个分页的最大值 class Pager1View(APIView): def get(self, request, *args, **kwargs): #获取所有数据 roles = models.Role.objects.all() # 创建分页对象 pg = MyPageNumberPagination() # 自定义的分页类 # 在数据库中获取分页的数据 pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self) # 对分页的数据进行序列化 ser = PagerSerializer(instance=pager_roles, many=True) print(pager_roles) return Response(ser.data)
22.3
如果返回为:
ser = PagerSerializer(instance=pager_roles, many=True) print(pager_roles) # return Response(ser.data) return pg.get_paginated_response(ser.data)
则显示如下的内容:
22.4
另外使用LimitOffsetPagination也可以实现上述功能
from rest_framework.pagination import LimitOffsetPagination
class LimitOffsetPagination(BasePagination): """ A limit/offset based style. For example: http://api.example.org/accounts/?limit=100 http://api.example.org/accounts/?offset=400&limit=100 # offset 是从0开始的 """ default_limit = api_settings.PAGE_SIZE limit_query_param = \'limit\' limit_query_description = _(\'Number of results to return per page.\') offset_query_param = \'offset\' offset_query_description = _(\'The initial index from which to return the results.\') max_limit = None template = \'rest_framework/pagination/numbers.html\'
22.5 加密分页
from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination class MyPageNumberPagination(CursorPagination): cursor_query_param = \'cursor\' # 查询页的ID page_size = 2 ordering = \'-id\' # 排序 page_size_query_param = None max_page_size = None
二十三、 rest framework之视图
23.1 GenericAPIView
从源码看,GenericAPIView是继承了APIView, 实现的功能和APIView没有任何区别,做了解即可:
继承的顺序是View-->APIView--> GenericView class APIView(View): # The following policies ma class GenericAPIView(views.APIView): """ Base class for all other generic views.
实现代码如下:
from rest_framework.pagination import PageNumberPagination from api.utils.serializers.pager import PagerSerializer from rest_framework.generics import GenericAPIView class View1View(GenericAPIView): queryset = models.Role.objects.all() serializer_class = PagerSerializer pagination_class = PageNumberPagination def get(self, request, *args, **kwargs): # 获取数据 roles = self.get_queryset() # models.Role.objects.all() pager_roles = self.paginate_queryset(roles) # 序列化 ser = self.get_serializer(instance=pager_roles, many=True) return Response(ser.data)
23.2 GenericViewSet
与上面的GenericAPIView不同的是,重写了as_view()方法;
# 继承了ViewSetMixin和GenericAPIView两个类,ViewSetMixin中重写了as_view方法; class GenericViewSet(ViewSetMixin, generics.GenericAPIView): """ The GenericViewSet class does not provide any actions by default, but does include the base set of generic view behavior, such as the `get_object` and `get_queryset` methods. """ pass class ViewSetMixin(object): """ This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the \'GET\' and \'POST\' methods to the \'list\' and \'create\' actions... view = MyViewSet.as_view({\'get\': \'list\', \'post\': \'create\'}) """ @classonlymethod def as_view(cls, actions=None, **initkwargs): """ Because of the way class based views create a closure around the instantiated view, we need to totally reimplement `.as_view`, and slightly modify the view function that is created and returned. """ # The suffix initkwarg is reserved for displaying the viewset type. # eg. \'List\' or \'Instance\'. cls.suffix = None # Setting a basename allows a view to reverse its action urls. This # value is provided by the router through the initkwargs. cls.basename = None
实现代码:
urls.py
url(r\'^(?P<version>[v1|v2]+)/v1/$\', views.View1View.as_view({\'get\': \'list\'})), # method为GET时,去寻找视图类中的list方法;
views.py
from rest_framework.viewsets import GenericViewSet class View1View(GenericViewSet): queryset = models.Role.objects.all() serializer_class = PagerSerializer pagination_class = PageNumberPagination def list(self, request, *args, **kwargs): # list方法必须要实现 # 获取数据 roles = self.get_queryset() # models.Role.objects.all() pager_roles = self.paginate_queryset(roles) # 序列化 ser = self.get_serializer(instance=pager_roles, many=True) return Response(ser.data)
23.3 ModelViewSet
ModelViewSet 继承了多个类: 每个类实现了一个特定的方法,在实现的时候,无需在视图中实现这些方法,只需要在as_view中指定方法名就可以了
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. """ pass
实现代码:
urls.py
url(r\'^(?P<version>[v1|v2]+)/v1/$\', views.View1View.as_view({\'get\': \'list\', \'post\':\'create\'})), # 获取列表和创建数据无需传递id url(r\'^(?P<version>[v1|v2]+)/v1/(?P<pk>\\d+)$\', views.View1View.as_view({\'get\': \'retrieve\', \'delete\': \'destroy\', \'put\': \'update\', \'patch\': \'partial_update\'})), # 因为update、以上是关于django restfulwork 源码剖析的主要内容,如果未能解决你的问题,请参考以下文章
Django对中间件的调用思想csrf中间件详细介绍Django settings源码剖析Django的Auth模块
Django(wsgi,middleware,url源码剖析)
Django----djagorest-framwork源码剖析