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、delete等操作,需要传递id,所以在实现的时候,需要两个路由;
views.py
from rest_framework.viewsets import ModelViewSet class View1View(ModelViewSet): queryset = models.Role.objects.all() serializer_class = PagerSerializer pagination_class = PageNumberPagination
当然了,也可以只继承mixins中的任意一个或者多个类;
总结使用:
a. 增删改查 使用ModelViewSet b. 增删 使用CreateModelMixin,DestroyModelMixin GenericViewSet c. 复杂逻辑 使用GenericViewSet 或 APIView
二十四、路由
一般一个视图,我们最多写四个url就够了
# http://127.0.0.1:8000/api/v1/v1/?format=json url(r'^(?P<version>[v1|v2]+)/v1/$', views.View1View.as_view({'get': 'list','post':'create'})), # http://127.0.0.1:8000/api/v1/v1.json url(r'^(?P<version>[v1|v2]+)/v1\.(?P<format>\w+)$', views.View1View.as_view({'get': 'list','post':'create'})), url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)/$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})), url(r'^(?P<version>[v1|v2]+)/v1/(?P<pk>\d+)\.(?P<format>\w+)$', views.View1View.as_view({'get': 'retrieve','delete':'destroy','put':'update','patch':'partial_update'})),
如果嫌麻烦,可以使用全自动路由:
from api import views from rest_framework import routers router = routers.DefaultRouter() router.register(r'xxxxx', views.View1View) router.register(r'rt', views.View1View) urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/', include(router.urls)), ]
如果写单个url就自己写,如果写全部的增删改查就自动生成;
二十五、content-type
在做前后端分离的时候,涉及跨域的问题,解决办法:
1. jsonp
2. cors: - 将这个响应头放在中间件中进行实现;
content-type: 是django内置的组件,帮助开发者做连表操作。
from django.db import models # Create your models here. from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation class Course(models.Model): """ 普通课程 """ title = models.CharField(max_length=12) # 仅用于反向查找 price_policy_list = GenericRelation("PricePlicy") class DegreeCourse(models.Model): """ 学位课程 """ title = models.CharField(max_length=32) price_policy_list = GenericRelation("PricePlicy") class PricePlicy(models.Model): """ 价格策略 """ price = models.IntegerField() period = models.IntegerField() content_type = models.ForeignKey(ContentType, verbose_name='关联的表的名称') object_id = models.IntegerField(verbose_name='关联的表中的数据行的ID') # 快速实现content_type 操作 content_object = GenericForeignKey('content_type', 'object_id') # 会自动找到obj 对象的id和关联的表中的数据行的ID,并进行赋值 obj = DegreeCourse.objects.filter(title='python').first() PricePlicy.objects.create(price=9.9, period=30, content_object=obj)
视图函数
from app01 import models def test(request): obj1 = models.DegreeCourse.objects.filter(title='python').first() models.PricePlicy.objects.create(price=9.9, period=30, content_object=obj1) obj1 = models.Course.objects.filter(title='rest framework').first() models.PricePlicy.objects.create(price=9.9, period=30, content_object=obj1) course = models.Course.objects.filter(id=1).first() price_policys = course.price_policy_list.all() print(price_policys) return HttpResponse('....')