关于 Python 系列其他文章的传送门
《 Python 基础语法 》
《 Python 进阶:网络、多线程、异步、数据库、WEB 》
《 Python 和 RabbitMQ 的那点事 》
《 Python 和 Redis 的那点事 》
《 Python 爬虫基础和包 XPath、BeautifulSoup4、PhantomJS、Selenium 》
《 边爬豆瓣,边学习 Python 爬虫框架 Scrapy 》
《 用了 Scrapy-Redis 分布式爬虫后,一个能打的都没有! 》
《 全栈开发入门 JavaScript、React 和 Django 》
《 Django REST framework 的那点事 》 您当前所在位置
Django 的 FBV、CBV
FBV
1 2 3 def users (request ): user_list = ['alex' , 'oldboy' ] return HttpResponse(json.dumps((user_list)))
CBV
基于反射实现根据请求方式不同,执行不同的方法。
原理:
url -> view 方法 -> dispatch 方法(反射执行其他:GET/POST/DELETE/PUT)
流程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 url(r'^students/' , views.StudentsView.as_view()), from django.views import Viewclass StudentsView (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('PUT' ) def delete (self, request, *args, **kwargs ): return HttpResponse('DELETE'
继承:多个类共用的功能,为了避免重复编写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from django.views import Viewclass MyBaseView (object ): def dispatch (self, request, *args, **kwargs ): print ('before' ) ret = super (MyBaseView, self ).dispatch(request, *args, **kwargs) print ('after' ) return ret class StudentsView (MyBaseView, View): def get (self, request, *args, **kwargs ): return HttpResponse('GET' ) class TeachersView (MyBaseView, View): def get (self, request, *args, **kwargs ): return HttpResponse('GET' )
Django 中的 csrf
实现原理:
通过中间件的 rocess_view 方法
检查视图是否被 @csrf_exempt(免除 csrf 认证)
去请求体或 cookie 中获取 token
FBV 的 csrf
全局开启 csrf 时,免除指定函数的认证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.middleware.csrf.CsrfViewMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , ] from django.views.decorators.csrf import csrf_exempt@csrf_exempt def users (request ): user_list = ['alex' , 'oldboy' ] return HttpResponse(json.dumps((user_list)))
全站未开启 csrf 时,使指定函数启用认证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware' , 'django.contrib.sessions.middleware.SessionMiddleware' , 'django.middleware.common.CommonMiddleware' , 'django.contrib.auth.middleware.AuthenticationMiddleware' , 'django.contrib.messages.middleware.MessageMiddleware' , 'django.middleware.clickjacking.XFrameOptionsMiddleware' , ] from django.views.decorators.csrf import csrf_protect@csrf_protect def users (request ): user_list = ['alex' , 'oldboy' ] return HttpResponse(json.dumps((user_list))) )
CBV 的 csrf
注意 :当使用 CBV 时,要使用 csrf 的 csrf_exempt 装饰器做全局排除时,需要这样使用,否则会无效:
使用 @method_decorator(csrf_exempt),装饰 dispatch 方法。
示例 1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from django.views.decorators.csrf import csrf_exempt, csrf_protectfrom django.utils.decorators import method_decoratorclass StudentsView (View ): @method_decorator(csrf_exempt ) def dispatch (self, request, *args, **kwargs ): return super (StudentsView, 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('PUT' ) def delete (self, request, *args, **kwargs ): return HttpResponse('DELETE' )
或装饰在类上,但要指定 name 为 dispatch,效果和示例 1 一样:
示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from django.views.decorators.csrf import csrf_exempt, csrf_protectfrom django.utils.decorators import method_decorator@method_decorator(csrf_exempt, name='dispatch' ) class StudentsView (View ): def get (self, request, *args, **kwargs ): print ('get 方法' ) return HttpResponse('GET' ) def post (self, request, *args, **kwargs ): return HttpResponse('POST' ) def put (self, request, *args, **kwargs ): return HttpResponse('PUT' ) def delete (self, request, *args, **kwargs ): return HttpResponse('DELETE' )
Django REST framework 框架
安装:pip3 install djangorestframework
认证
源码流程
定义 CBV,继承 from rest_framework.views import APIView
路由进来匹配到 CBV 类,执行 as_view()
执行 self.dispatch
封装 request,initialize_request
获取定义的认证类(全局 / 局部),通过列表生成时创建对象。
initial
perform_authentication,身份验证
request.user(内部循环…)
check_permissions,权限
check_throttles,限流
内置的认证类:
认证类,继承(建议):from rest_framework.authentication import BaseAuthentication
其他认证类:BasicAuthentication,浏览器的基础认证。
使用方式:
自定义一个类: 继承自 BaseAuthentication,并实现 authenticate 方法;
authenticate 的返回值含义:
None, 我不管了,下一认证来执行。
raise exceptions.AuthenticationFailed('用户认证失败') # from rest_framework import exceptions:
(元素 1,元素 2):元素 1 会赋值给 request.user;元素 2 会赋值给 request.auth。
局部使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 from rest_framework.views import APIViewfrom rest_framework.authentication import BasicAuthenticationfrom rest_framework import exceptionsfrom django.http import HttpRequest, HttpResponseimport jsonclass MyAuthentication (BasicAuthentication ): def authenticate (self, request ): token = request._request.GET.get('token' ) if not token: raise exceptions.AuthenticationFailed('用户认证失败' ) return "alex" , None def authenticate_header (self, val ): pass class DogView (APIView ): authentication_classes = [MyAuthentication,] def get (self, request, *args, **kwargs ): print (request) print (request.user) ret = { 'code' : 1000 , 'msg' : 'xxx' } return HttpResponse(json.dumps(ret), status=201 ) def post (self, request, *args, **kwargs ): return HttpResponse('创建 Dog' ) def put (self, request, *args, **kwargs ): return HttpResponse('更新 Dog' ) def delete (self, request, *args, **kwargs ): return HttpResponse('删除 Dog' )
全局使用示例:
在 Django 的配置文件中定义:
1 2 3 4 5 6 7 8 REST_FRAMEWORK = { "DEFAULT_AUTHENTICATION_CLASSES" : ['api.utils.auth.FirstAuthtication' , 'api.utils.auth.Authtication' ,], "UNAUTHENTICATED_USER" : None , "UNAUTHENTICATED_TOKEN" : None , }
权限
代码逻辑和权限的基本一致。
不用框架,自己实现时大概是这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class MyPermission (object ): def has_permission (self, request, view ): if request.user.user_type != 3 : return False return True class OrderView (APIView ): """ 订单相关业务 (只有 SVIP 用户有权限) """ permission_classes = [MyPermission,] def get (self, request, *args, **kwargs ): ret = {'code' : 1000 , 'msg' : None , 'data' : None } return JsonResponse(ret)
使用框架要求:
类,必须继承:BasePermission,必须实现:has_permission 方法:
返回值:
1 2 3 4 5 6 7 8 9 10 from rest_framework.permissions import BasePermissionclass SVIPPermission (BasePermission ): message = "必须是 SVIP 才能访问" def has_permission (self, request, view ): if request.user.user_type != 3 : return False return True
局部使用示例:
1 2 3 4 5 6 7 8 9 10 class UserInfoView (APIView ): """ 订单相关业务(普通用户、VIP) """ permission_classes = [MyPermission1,] def get (self, request, *args, **kwargs ): return HttpResponse('用户信息' )
全局使用示例:
1 2 3 4 5 REST_FRAMEWORK = { "DEFAULT_PERMISSION_CLASSES" : ['api.utils.permission.SVIPPermission' ] }
控制访问频率(节流)
控制指定的秒数内的访问次数。
普通实现 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import timeVISIT_RECORD = {} class VisitThrottle (): """60s 内只能访问 3 次""" def __init__ (self ): self .history = None def allow_request (self, request, view ): remote_addr = request.META.get('REMOTE_ADDR' ) ctime = time.time() if remote_addr not in VISIT_RECORD: VISIT_RECORD[remote_addr] = [ctime, ] return True history = VISIT_RECORD.get(remote_addr) self .history = history while history and history[-1 ] < ctime - 60 : history.pop() if len (history) < 3 : history.insert(0 , ctime) return True def wait (self ): """ 还需要等多少秒才能访问 :return: """ ctime = time.time() return 60 - (ctime - self .history[-1 ])
继承实现 :
SimpleRateThrottle 这个自带类帮你实现了上面 allow_request 和 wait 中的访问频次限制和剩余时间提醒功能。
先在 setting 中定义间隔时间:
1 2 3 4 5 6 7 REST_FRAMEWORK = { "DEFAULT_THROTTLE_RATES" : { "users_ip" : '3/m' , "users_username" : '20/m' } }
然后只需要重写一个方法 get_cache_key 即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 from rest_framework.throttling import SimpleRateThrottlefrom rest_framework.views import APIViewclass VisitThrottle (SimpleRateThrottle ): scope = 'users_ip' def get_cache_key (self, request, view ): return self .get_ident(request) class UserThrottle (SimpleRateThrottle ): scope = 'users_username' def get_cache_key (self, request, view ): return request.user.username class AuthView (APIView ): """ 用于用户登录认证 """ authentication_classes = [] permission_classes = [] throttle_classes = [VisitThrottle,] def post (self, request, *args, **kwargs ): ret = {'code' : 1000 , '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 = md5(user) models.UserToken.objects.update_or_create(user=obj, defaults={'token' : token}) ret['token' ] = token except Exception as e: ret['code' ] = 1002 ret['msg' ] = '请求异常' return JsonResponse(ret)
版本
源码总体流程:
1 2 3 4 5 self .dispatch self .initial(request, *args, **kwargs) self .determine_version(request, *args, **kwargs) self .versioning_class,获取解析类,比如 URLPathVersioning 类,或自定义类 根据不同的类,执行类中的 determine_version 方法,最后 return version
URL 中通过 GET 传参(不推荐)
通过自定义类实现。
url: http://127.0.0.1:8000/api/users/?version=v2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class ParamVersion (object ): def determine_version (self, request, *args, **kwargs ): version = request.query_params.get('version' ) return version class UsersView (APIView ): versioning_class = ParamVersion def get (self, request, *args, **kwargs ): print (request.version) return HttpResponse('用户列表' )
在 URL 中传参(推荐使用)
使用 rest_framework 框架中的 URLPathVersioning 类实现。
1、定义配置文件:
1 2 3 4 5 6 7 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" : "rest_framework.versioning.URLPathVersioning" , "DEFAULT_VERSION" : 'v1' , "ALLOWED_VERSIONS" : ['v1' , 'v2' ], "VERSION_PARAM" : 'version' , }
定义路由系统:
url: http://127.0.0.1:8000/api/v1/users/
1 2 3 4 5 6 7 8 9 10 11 12 urlpatterns = [ url(r'^api/' , include('api.urls' )), ] urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/users/$' , views.UsersView.as_view(), name='uuu' ), ]
在视图中使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class UsersView (APIView ): def get (self,request,*args,**kwargs ): print (request.version) print (request.versioning_scheme) u1 = request.versioning_scheme.reverse(viewname='uuu' ,request=request) print (u1) u2 = reverse(viewname='uuu' ,kwargs={'version' :2 }) print (u2) return HttpResponse('用户列表' )
解析器
由于 Django 中使用 request.POST 方法获取时,要求:
要求请求头中必须有值 Content-Type: application/x-www-form-urlencoded
数据格式要求 name=alex&age=18&gender = 男
同时满足以上两点时,request.POST 中才有值,否则只能去 request.body 中才能解析数据。
如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 <form method...> input... input... input... </form> $.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 } }) $.ajax ({ url :... type :POST , headers :{'Content-Type' :"application/json" } data :JSON .stringfy ({name :alex,age=18 }) })
原来 Django 不支持解析的方式,使用 rest_framework 解析器,就可以对请求体数据进行解析了。
全局配置:
1 2 3 4 5 6 7 8 9 REST_FRAMEWORK = { "DEFAULT_VERSIONING_CLASS" :"rest_framework.versioning.URLPathVersioning" , "DEFAULT_VERSION" :'v1' , "ALLOWED_VERSIONS" :['v1' ,'v2' ], "VERSION_PARAM" :'version' , "DEFAULT_PARSER_CLASSES" :['rest_framework.parsers.JSONParser' ,'rest_framework.parsers.FormParser' ] }
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class ParserView (APIView ): """ JSONParser: 表示只能解析 content-type:application/json 头 FormParser: 表示只能解析 content-type:application/x-www-form-urlencoded 头 """ def post (self, request, *args, **kwargs ): """ 允许用户发送 JSON 格式数据 a. content-type: application/json b. {'name':'alex',age:18} """ """ 1. 获取用户请求 2. 获取用户请求体 3. 根据用户请求头 和 parser_classes = [JSONParser,FormParser,] 中支持的请求头进行比较 4. JSONParser 对象去请求体 5. request.data """ print (request.data) return HttpResponse('ParserView' )
源码流程:
1 2 3 dispatch initialize_request 在 Request 中封装你指定的 parser_classes
序列化
常用的 serializers.fieild
CharField、BooleanField、IntegerField、DateTimeField
等等等等 email 什么的
1 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
参数 :
1 order_sn = serializers.CharField(readonly=True )
write_only: 与 read_only 对应;
required: 顾名思义,就是这个字段是否必填;
allow_null/allow_blank:是否允许为 NULL/空;
error_messages: 出错时,信息提示。
1 2 name = serializers.CharField(required=True , min_length=6 , error_messages={ 'min_length' : '名字不能小于6个字符' , 'required' : '请填写名字' })
label: 字段显示设置,如 label='验证码'
help_text: 在指定字段增加一些提示文字,这两个字段作用于 api 页面比较有用
style: 说明字段的类型,这样看可能比较抽象,看下面例子:
1 2 3 4 5 6 7 password = serializers.CharField( style={'input_type' : 'password' }) color_channel = serializers.ChoiceField( choices=['red' , 'green' , 'blue' ], style={'base_template' : 'radio.html' })
将数据库的值序列化
通过自定义一个类,将从数据库查到的 QuerySet 类型的对象,自动序列化。
入门使用,快速理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 from rest_framework import serializersclass RolesSerializer (serializers.Serializer): id = serializers.IntegerField() title = serializers.CharField() class RolesView (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 ) roles = models.Role.objects.all () ser = RolesSerializer(instance=roles, many=True ) ret = json.dumps(ser.data, ensure_ascii=False ) role = models.Role.objects.all ().first() ser = RolesSerializer(instance=role, many=False ) ret = json.dumps(ser.data, ensure_ascii=False ) return HttpResponse(ret)
对于 外键 和 返回字段的 “人类可读” 值 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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) username = models.CharField(max_length=32 , unique=True ) password = models.CharField(max_length=64 ) group = models.ForeignKey("UserGroup" ) roles = models.ManyToManyField("Role" ) class UserInfoSerializer (serializers.Serializer): user_type_1 = serializers.CharField(source="user_type" ) user_type_2 = serializers.CharField(source="get_user_type_display" ) username = serializers.CharField() password = serializers.CharField() gp = serializers.CharField(source="group.title" ) rls = serializers.SerializerMethodField() def get_rls (self, row ): role_obj_list = row.roles.all () ret = [] for item in role_obj_list: ret.append({'id' : item.id , 'title' : item.title}) return ret
使用 ModelSerializer 自动生成字段 :
之前的方法,需要我们逐个配置每个字段,而继承自 serializers.ModelSerializer 的话,会自动帮我们将整张表序列化。
它为创建一个序列化,提供了一个快捷的方式,它会自动创建一个 Serializer 类,字段与 model 中的字段一一对应。
ModelSerializer 类与常规 Serializer 类相同,不同之处在于:
它会根据模型自动生成一组字段。
它会自动为序列化类生成验证器,例如 unique_together 验证器。
它包含 .create() 和 .update() 的简单默认实现。它能够满足将 post 或 patch 上来的数据进行进行直接地创建与更新。如果有额外需求,那么就可以重载 create 与 update 方法。
声明 ModelSerializer 时,在 Meta 中设置 fields 字段,系统会自动进行映射成序列化字段,省去每个字段再写一个 field。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class UserInfoSerializer (serializers.ModelSerializer): oooo = serializers.CharField(source="get_user_type_display" ) rls = serializers.SerializerMethodField() class Meta : model = models.UserInfo fields = "__all__" fields = ['id' , 'username' , 'password' , 'oooo' , 'rls' , 'group' ] exclude = ('rls' ,) read_only_fields =(’field_name‘,) def get_rls (self, row ): role_obj_list = row.roles.all () ret = [] for item in role_obj_list: ret.append({'id' : item.id , 'title' : item.title}) return ret
终极全自动化方案 :自动序列化连表(注意效率问题)
1 2 3 4 5 6 7 8 class UserInfoSerializer (serializers.ModelSerializer): class Meta : model = models.UserInfo fields = ['id' , 'username' , 'password' , 'group' , 'roles' ] depth = 1
ModelSerializer 主要需要解决的 2 个问题(2 个应用方案):
1、某个字段不属于 Model,它是 write_only,需要用户传进来,但我们不能对它进行 save(),因为 ModelSerializer 是基于 Model 的,这个字段在 Model 中没有对应。这个时候,我们可以通过重载 validate 方法来解决。比如:在用户注册 时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个 Model 中。
1 2 3 def validate (self, attrs ): del attrs["code" ] return attrs
2、某个字段不属于指定 Model,它是 read_only,只需要将它序列化传递给用户,但是在这个 Model 中,没有这个字段!这时就可以使用 SerializerMethodField。假设需要返回用户加入这个网站多久了 ,不可能维持这样加入的天数这样一个数据,一般会记录用户加入的时间点,然后当用户获取这个数据,我们再计算返回给它。
1 2 3 4 5 6 7 8 9 10 class UserSerializer (serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() class Meta : model = User def get_days_since_joined (self, obj ): return (now() - obj.date_joined).days
生成链接
通过反向解析向前台返回一个链接。
示例:当用户请求 Userinfo 数据时,将其中的 group 字段返回 url,而不是真正的值。这个 url 可以自动生成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/userinfo/$' , views.UserInfoView.as_view()), url(r'^(?P<version>[v1|v2]+)/group/(?P<xxx>\d+)$' , views.GroupView.as_view(), name='gp' ), ] class UserInfoSerializer (serializers.ModelSerializer): group = serializers.HyperlinkedIdentityField(view_name='gp' , lookup_field='group_id' , lookup_url_kwarg='xxx' ) class Meta : model = models.UserInfo fields = ['id' , 'username' , 'password' , 'group' , 'roles' ] 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)
源码:
在 __new__ 时,通过 if kwargs.pop('many', False): 判断 many 的值。
当为 False 时是单一对象,交给 Serializer 类处理;
当为 True 时是 QuerySet,交给 ListSerializer 类去处理;
ser.data:已经是转换完成的结果。
请求数据校验
下面介绍和校验相关的一些使用方法。
1. 校验 :
反序列化数据的时候,你始终需要先调用 is_valid() 方法,然后再尝试去访问经过验证的数据或保存对象实例。如果发生任何验证错误,.errors 属性将包含表示生成的错误消息的字典。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework import serializersclass CommentSerializer (serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200 ) created = serializers.DateTimeField() erializer = CommentSerializer(data={'email' : 'foobar' , 'content' : 'baz' }) serializer.is_valid() serializer.errors
2. 抛出无效数据的异常 :
.is_valid() 方法有一个可选的参数 raise_exception,当存在错误时将会抛出一个 serializers.ValidationError 异常。这些异常由 REST framework 提供的默认异常处理程序自动处理,默认情况下将返回 HTTP 400 Bad Request 响应。
3. 字段级别自定义的验证 :
你可以向你的 Serializer 子类中添加一个方法,这个方法可以为指定的字段进行自定义的验证。方法名字的格式为 .validate_<field_name>,其中 <field_name> 就是需要验证的字段值。
这个验证方法可以获得一个参数,就是用户传的值。
你的 validate_<field_name> 方法应该返回一个验证过的数据或者抛出一个 serializers.ValidationError 异常。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 from rest_framework import serializersclass BlogPostSerializer (serializers.Serializer): title = serializers.CharField(max_length=100 ) content = serializers.CharField() def validate_title (self, value ): """ Check that the blog post is about Django. """ if 'django' not in value.lower(): raise serializers.ValidationError("Blog post is not about Django" ) return value
4. 对象级别的验证 :
上一个方法是对指定字段进行验证,如果需要对多个字段(也就是整个对象)进行验证,添加一个名为 validate() 的方法即可。
这个方法可以获得一个参数,为字典类型。
最后依然需要应该抛出一个 ValidationError 异常,或者返回经过验证的值。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from rest_framework import serializersclass EventSerializer (serializers.Serializer): description = serializers.CharField(max_length=100 ) start = serializers.DateTimeField() finish = serializers.DateTimeField() def validate (self, data ): """ Check that the start is before the stop. """ if data['start' ] > data['finish' ]: raise serializers.ValidationError("finish must occur after start" ) return data
5. 验证器 :
序列化器上的各个字段都可以包含验证器,通过在字段实例上声明
validators 可以直接作用于某个字段,这个时候,它与单独的 validate 作用差不多。
例如:
1 2 3 4 5 6 def multiple_of_ten (value ): if value % 10 != 0 : raise serializers.ValidationError('Not a multiple of ten' ) class GameRecord (serializers.Serializer): score = IntegerField(validators=[multiple_of_ten])
6. 自定义错误信息 :
1 2 3 4 5 6 class BooksSerializers (serializers.ModelSerializer): class Meta : model = models.Book fields = '__all__' name = serializers.CharField(max_length=10 , min_length=3 , error_messages={'max_length' : '最长为10' ,'min_length' : '最短为3' ,'required' :'不能为空' })
综合示例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class XXValidator (object ): def __init__ (self, base ): self .base = base def __call__ (self, value ): if not value.startswith(self .base): message = '标题必须以 %s 为开头。' % self .base raise serializers.ValidationError(message) def set_context (self, serializer_field ): """ This hook is called by the serializer instance, prior to the validation call being made. """ pass class UserGroupSerializer (serializers.Serializer): title = serializers.CharField(error_messages={'required' : '标题不能为空' }) class UserGroupSerializer (serializers.Serializer): title = serializers.CharField(error_messages={'required' : '标题不能为空' }, validators=[XXValidator('老男人' ), ]) def validate_title (self, value ): from rest_framework import exceptions raise exceptions.ValidationError('看你不顺眼' ) return value class UserGroupView (APIView ): def post (self, request, *args, **kwargs ): ser = UserGroupSerializer(data=request.data) if ser.is_valid(): print (ser.validated_data['title' ]) else : print (ser.errors) return HttpResponse('提交数据' )
保存实例
如果我们希望能够返回基于验证数据的完整对象实例,我们需要实现其中一个或全部实现 .create() 和 update() 方法。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class CommentSerializer (serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200 ) created = serializers.DateTimeField() def create (self, validated_data ): return Comment.objects.create(**validated_data) def update (self, instance, validated_data ): instance.email = validated_data.get('email' , instance.email) instance.content = validated_data.get('content' , instance.content) instance.created = validated_data.get('created' , instance.created) return instance
ps :.create() 和 .update() 方法都是可选的。你可以根据你序列化器类的用例不实现、实现它们之一或都实现。
现在当我们反序列化数据的时候,基于验证过的数据我们可以调用 .save() 方法返回一个对象实例。
1 comment = serializer.save()
调用 .save() 方法将创建新实例或者更新现有实例,具体取决于实例化序列化器类的时候是否传递了现有实例:
1 2 3 4 5 serializer = CommentSerializer(data=data) serializer = CommentSerializer(comment, data=data)
默认情况下,序列化程序必须为所有必填字段传递值,否则会引发验证错误。你可以使用 partial 参数以允许部分更新
1 2 serializer = CommentSerializer(comment, data=data,partial=True )
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class BooksSerializers (serializers.ModelSerializer): class Meta : model = models.Book fields = '__all__' def post (self, request ): response = {'status' : 200 , 'msg' : '添加成功' , 'data' : None } ret = BooksSerializers(data=request.data) if ret.is_valid(): ret.save() response['data' ] = ret.data else : response['status' ] = 201 response['data' ] = ret.errors response['msg' ] = '添加失败' return JsonResponse(response, safe=False ) def put (self, request, id ): response = {'status' : 200 , 'msg' : '修改成功' , 'data' : None } books = models.Book.objects.filter (pk=id ).first() if books: ret = BooksSerializers(data=request.data, instance=books) if ret.is_valid(): ret.save() response['data' ] = ret.data else : response['status' ] = 201 response['data' ] = ret.errors response['msg' ] = '修改失败' else : response['status' ] = 201 response['msg' ] = '修改对象不存在' return JsonResponse(response, safe=False )
分页
基本分页
看第 n 页,每页显示 n 条数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 from rest_framework.pagination import PageNumberPaginationclass MyPageNumberPagination (PageNumberPagination ): page_size = 2 page_size_query_param = 'size' max_page_size = 5 page_query_param = 'page' class Pager1View (APIView ): def get (self, request, *args, **kwargs ): roles = models.Role.objects.all () pg = PageNumberPagination() pg = MyPageNumberPagination() pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self ) ser = PagerSerialiser(instance=pager_roles, many=True ) return Response(ser.data) return pg.get_paginated_response(ser.data)
偏移、Limit
在 n 个位置,向后查看 n 条数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 from api.utils.serializsers.pager import PagerSerialiserfrom rest_framework.response import Responsefrom rest_framework.pagination import PageNumberPagination, LimitOffsetPaginationclass MyLimitOffsetPagination (LimitOffsetPagination ): default_limit = 2 limit_query_param = 'limit' offset_query_param = 'offset' max_limit = 5 class Pager1View (APIView ): def get (self, request, *args, **kwargs ): roles = models.Role.objects.all () pg = LimitOffsetPagination() pg = MyLimitOffsetPagination() pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self ) ser = PagerSerialiser(instance=pager_roles, many=True ) return Response(ser.data)
加密分页
返回值内会有上一页和下一页的链接,值是加密的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPaginationclass MyCursorPagination (CursorPagination ): cursor_query_param = 'cursor' page_size = 2 ordering = 'id' page_size_query_param = None max_page_size = None class Pager1View (APIView ): def get (self, request, *args, **kwargs ): roles = models.Role.objects.all () pg = MyCursorPagination() pager_roles = pg.paginate_queryset(queryset=roles, request=request, view=self ) ser = PagerSerialiser(instance=pager_roles, many=True ) return pg.get_paginated_response(ser.data)
视图
View 的继承关系:
View:Django 自带 view;
↓
APIView:REST-framework 基本 view;
↓
GenericAPIView: 封装了一些参数,可以少写一些参数,用途不大;
↓
GenericViewSet: 在 url 上可以将请求方法自定义映射;
比如 url(r'/', views.View1View.as_view({'get': 'list','post':'create'}))
get 请求本来应该执行 get 方法,现在会执行 list 方法
↓
ModelViewSet: 实现了增删改、查多、查一
继承了:mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet,
ModelViewSet 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 url(r'^(?P<version>[v1|v2]+)/v1/$' , 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' })), from rest_framework.viewsets import GenericViewSet, ModelViewSetclass MySerialiser (serializers.ModelSerializer): class Meta : model = models.Role fields = "__all__" class View1View (ModelViewSet ): queryset = models.Role.objects.all () serializer_class = MySerialiser pagination_class = PageNumberPagination
路由
REST-framework 的渲染界面原生支持在 url 末尾加 /api/v1/user/1/?format=json,使返回 json 格式的数据。
可以配置 url ,实现访问 /api/v1/user/1.json 达到同样的效果。
1 2 3 4 5 6 7 url(r'^(?P<version>[v1|v2]+)/v1/$' , views.View1View.as_view({'get' : 'list' ,'post' :'create' })), 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' })),
以上 url 可以通过路由注册功能,自动生成
注意 :这样自动生成的方式,增删改查功能都有。
1 2 3 4 5 6 7 8 9 10 11 from api import viewsfrom rest_framework import routersrouter = routers.DefaultRouter() router.register(r'xxxxx' , views.View1View) router.register(r'rt' , views.View1View) urlpatterns = [ url(r'^(?P<version>[v1|v2]+)/' , include(router.urls)), ]
渲染
使用 from rest_framework.response import Response 返回,框架会自动渲染出 web 页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from rest_framework.response import Responsefrom rest_framework.renderers import JSONRenderer, BrowsableAPIRendererclass TestView (APIView ): renderer_classes = [JSONRenderer,BrowsableAPIRenderer] def get (self, request, *args, **kwargs ): return Response(data) REST_FRAMEWORK = { "DEFAULT_RENDERER_CLASSES" :[ 'rest_framework.renderers.JSONRenderer' , 'rest_framework.renderers.BrowsableAPIRenderer' ] }
其实就算没有上边的配置,只要使用 rest_framework.response.Response 返回数据即可。
框架渲染时会从自己的 templates 目录中读取模板文件,如果要自定义 logo 更改这里边的文件即可。