目录
序言:Django自带序列化组件
一、restframework介绍
什么是restframework
restframework介绍
HTTP动词
状态码
二 rest-framework序列化之Serializer
序列化的意义:
常用字段类型:
序列化实例
三 rest-framework序列化之ModelSerializer
四 生成hypermedialink(极少数)
五 序列化组件之请求数据校验和保存功能
序列化组件源码分析
也就是使用原生的django来处理请求
详见
在正式开始介绍Rest-Framework之前我们需要对它有一定的了解:
django restframework是基于django和restful协议开发的框架,在restful协议里,一切皆是资源,操作是通过请求方式控制,可以将python中的对象转换成json格式的字符串
在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
删:判断要删除的数据是否存在 -> 执行数据库删除
改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
查:查询数据库 -> 将数据序列化并返回
Django REST framework可以帮助我们简化上述两部分的代码编写,大大提高REST API的开发速度。
对于资源的具体操作类型,由HTTP动词表示。
常用的HTTP动词有下面四个(括号里是对应的SQL命令)。
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。
DELETE(DELETE):从服务器删除资源。
还有三个不常用的HTTP动词。
PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性)。
HEAD:获取资源的元数据。
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的。
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
更多状态码看这里:更多的状态码
下面我们主要介绍使用CBV在视图中处理请求的方式来,
在这里我们还是简单说一下,为什么使用CBV而不采用FBV:
之前介绍Django的时候我们使用的基本上都是FBV, 虽然函数简单明了,但如果只用函数来开发,有很多面向对象的优点就错失了(继承、封装、多态)。所以Django在后来加入了CBV。可以让我们用类写View,然后通过反射执行as_view()方法,这样做的优点主要下面两种:
提高了代码的复用性,可以使用面向对象的技术,比如Mixin(多继承)
可以用不同的函数针对不同的HTTP方法处理,而不是通过很多if判断,提高代码可读性
下面我就正式开始介绍使用rest-framework在Django中处理请求,
注意:
1.记得使用之前没有安装Djangorestframework模块的要安装好该模块
2.安装成功之后一定要在setting中将rest_framework注册之后才能正常使用
web有两种应用模式,一种是前后端不分离,一种是前后端分离,当前后端分离的时候,后端只需要向前端传输数据即可,不需要进行其他的操作,而restframework在前后端传输数据时,主要是json数据,过程中就要需要把其他数据转换成json数据,比如数据库查询所有数据时,是queryset对象,那就要把这对象处理成json数据返回前端,一般如果是中大型公司,都是前后端分离,这也是目前的市场规则需要.
然后我们来看序列化时常用的字段和通用参数:
字段 | 字段构造方式和参数默认值 |
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
相对而言,常用字段类型是比较常见的,在我们的ORM模式里,只要是连接数据库那么就一定需要定义我们的模型参数,下面介绍静态常用的模型参数
通用参数
参数名称 | 说明 |
max_length | 最大长度 |
min_lenght | 最小长度 |
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
这里比较常用的字段是前面六个,其中max_length和min_length一般配合着charfield使用,可以给该字段设置大小上下限。而read_only和write_only是两个相反的概念,前者是不接收客户端的数据,只向客户端输出数据,后者是只接收客户端的数据,不向客户端输出数据,这就可以类比于我们登录注册时的密码框,我们只需要向它写入而并不需要它像我们输出,并且该字段是经过hash加密的,寻常情况难以解密
Models部分:
首先在应用的models中建立原始的数据库模型
from django.db import models
# Create your models here.
class Book(models.Model):
title=models.CharField(max_length=32)
price=models.IntegerField()
pub_date=models.DateField()
publish=models.ForeignKey("Publish")
authors=models.ManyToManyField("Author")
def __str__(self):
return self.title
class Publish(models.Model):
name=models.CharField(max_length=32)
email=models.EmailField()
def __str__(self):
return self.name
class Author(models.Model):
name=models.CharField(max_length=32)
age=models.IntegerField()
def __str__(self):
return self.name
view部分:
在对数据库中取出来的数据进行序列化的时候的步骤:
需要写一个类来继承serializers.Serializer, 这个类可以单独写一个文件(建议), 也可以直接在view中书写,
书写的这个类中序列化的字段要和数据库中字段要保持高度一致
不一致的时候我们就要介绍一下在序列化的时候使用到的一个参数source:
source可以在后面指定要序列化的字段, 然后再序列化的类中字段就可以自己命名了
1 变量名和source指定的值不能一样
2 source='publish.name'还支持继续使用点语法
3 source 还支持方法(没啥用)
4 支持写方法对字段进行序列化, 如下
方法的返回值,会赋给前面定义的序列化的字段名字
方法一定传一个参数,是当前序列化的表对象
写方法的时候时固定写法,get_字段名(self, obj):pass
publish_dic=serializers.SerializerMethodField()
def get_publish_dic(self,obj):
这个obj是当前book对象.
return {'id':obj.publish.pk,'name':obj.publish.name}
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import *
from django.shortcuts import HttpResponse
from django.core import serializers
from rest_framework import serializers
# 为序列化做准备
class BookSerializers(serializers.Serializer):
title=serializers.CharField(max_length=32)
price=serializers.IntegerField()
pub_date=serializers.DateField()
# 想要publish字段显示出版社名字的时候
publish=serializers.CharField(source="publish.name")
#authors=serializers.CharField(source="authors.all")
authors=serializers.SerializerMethodField()
def get_authors(self,obj):
temp=[]
for author in obj.authors.all():
temp.append(author.name)
return temp
#此处可以继续用author的Serializers,
# def get_authors(self,obj):
# ret=obj.authors.all()
# ss=AuthorSerializer(ret,many=True)
# return ss.data
# 序列化
class BookViewSet(APIView):
def get(self,request,*args,**kwargs):
book_list=Book.objects.all()
# 序列化方式1:
# 单个数据对象 model_to_dict(obj),是Django中的一个方法:返回一个字典,key是obj 这个
# 对象的字段名,value是字段对应的值。这种是最快的一种序列化的方式。
# from django.forms.models import model_to_dict
# import json
# data=[]
# for obj in book_list:
# data.append(model_to_dict(obj))
# print(data)
# return HttpResponse("ok")
# 序列化方式2:
# data=serializers.serialize("json",book_list)
# return HttpResponse(data)
# 序列化方式3:
bs=BookSerializers(book_list,many=True) #many=True代表有多条数据,如果只有一条 数据,many=False
return Response(bs.data)
# 序列化方式4:
# ret=models.Book.objects.all().values('nid','title')
# dd=list(ret)
# return HttpResponse(json.dumps(dd))
注意:
1. source 如果是字段,会显示字段,如果是方法,会执行方法,不用加括号(authors=serializers.CharField(source='authors.all'))
2. rest-framework中对request和response都做了处理, 我们来看一下request在rest-framework中的请求流程及相关处理:
rest-framework中的request对象不再是Django中原生的HttpRequest对象, 而是rest-framework提供的扩展了HttpRequest类的Request类的对象
REST framework 提供了Parser解析器,在接收到请求后会自动根据Content-Type指明的请求数据类型(如JSON、表单等)将请求数据进行parse解析,解析为类字典对象保存到Request对象中。
它和django大致相同,因为它的APIView继承是django的View,但在APiView中重写了dispatch方法
看到这段代码:
url(r'^publishers/$', views.PublishViewSet.as_view(),name="publish_list"),
执行PublishViewSet就是APIView的as_view方法
class APIView(View):
APIView继承了View,APIView中有as_view方法,所以会执行这个方法,方法中有这么一句代码
view = super(APIView, cls).as_view(**initkwargs)
最终还是执行了父类里的as_view方法,所以最终执行结果,得到这么这个view函数。
下面我们就去源码中看看这个函数的执行顺序:
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
当请求来时,会执行view函数,然后结果调用了dispatch方法,而这里dispatch方法则不是View里的,因为APIView中重写了父类中的dispatch方法,并且是整个rest_framework中最重要的部分,实现了大部分逻辑。
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
所以我们可以总结,dispatch是通过view的函数的执行而被调用,那么它返回的结果就是view函数返回的结果,而view函数返回的结果就是as_view()方法返回的结果。也就是通过这样的方式获取到了请求方式并执行。
3. 使用rest-framework的Response的时候, 服务器端会根据请求的不同请求客户端而发送给客户端不同的数据, 比如使用浏览器访问的时候,会返回一个完整的带数据和格式的页面, 而使用postman进行访问的时候,只返回一个json格式的数据, 当然这两种方式返回的主要数据-json格式的数据都是一样的,只是一个带页面一个不带页面而已
如在模型中定义一个方法,直接可以在source指定执行
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)
#视图
ret=models.UserInfo.objects.filter(pk=1).first()
aa=ret.get_user_type_display()
#serializer
xx=serializers.CharField(source='get_user_type_display')
使用modelSerializer的时候一定要指定序列化数据库中的哪张表
使用rest-framework来操作数据库的时候, 对数据进行增、删、改的时候一定要使用modelSerializer序列化过的
fields = "__all__": 该表的所有字段都进行序列化
fields=['nid', 'name', 'authors'] : 只序列化列表中的字段
exclude=('nid') : 除了元组里边的字段之外该表剩下所有字段进行序列化
在modelSerializer同样可以使用SerializerMethodField
在modelSerializer中可以定义局部钩子和全局钩子对某个字段进行二次校验, 或者加逻辑处理
使用钩子的时候也是固定写法, 局部钩子(def validate_字段名(self, value)) | 全局钩子(def validate(self, value))
在这里要注意加局部钩子的时候的代码缩进, 不要将钩子写到Meta中,写进去之后将无法执行钩子
使用局部钩子的时候,注意要传入value参数, 该参数的值是局部钩子要校验的字段的值
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
# fields = "__all__"
fields=['nid','title','authors','publish']
# exclude=('nid',) #不能跟fields同时用
# depth = 1 #深度控制,写 几 往里拿几层,层数越多,响应越慢,官方建议0--10之间,个人建议最多3层
publish=serializers.SerializerMethodField()
def get_publish(self,obj):
return obj.publish.name
authors=serializers.SerializerMethodField()
def get_authors(self,obj):
ret=obj.authors.all()
ss=AuthorSerializer(ret,many=True)
return ss.data
class BookSerializers(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = "__all__"
# 生成连接,直接查看出版社详情
publish = serializers.HyperlinkedIdentityField(view_name='ttt', lookup_field='publish_id', lookup_url_kwarg='pkk')
authors=serializers.SerializerMethodField()
def get_authors(self,obj):
ret=obj.authors.all()
ss=AuthorSerializer(ret,many=True)
return ss.data
#--------------
res=BookSerializers(ret,many=True,context={'request': request})
#--------------
class Publish(APIView):
def get(self,request,pkk):
print(pkk)
return HttpResponse('ok')
#----路由---
url(r'^publish/(?P\d+)$', views.Publish.as_view(),name='ttt'),
class BookSerializers(serializers.ModelSerializer):
class Meta:
model=Book
fields="__all__"
#————————
class BookView(APIView):
def post(self, request):
# 添加一条数据
print(request.data)
bs=BookSerializers(data=request.data)
if bs.is_valid():
bs.save() # 生成记录
return Response(bs.data)
else:
return Response(bs.errors)
class BookSerializer1(serializers.Serializer):
title=serializers.CharField(error_messages={'required': '标题不能为空'})
#这种方式要保存,必须重写create方法
通过源码查看留的校验字段的钩子函数:
#is_valid---->self.run_validation-(执行Serializer的run_validation)-->self.to_internal_value(data)---(执行Serializer的run_validation:485行)
# 局部钩子
def validate_title(self, value):
from rest_framework import exceptions
raise exceptions.ValidationError('看你不顺眼')
return value
# 全局钩子
def validate(self, attrs):
from rest_framework import exceptions
if attrs.get('title')== attrs.get('title2'):
return attrs
else:
raise exceptions.ValidationError('不想等啊')
'''
序列化组件,先调用__new__方法,如果many=True,生成ListSerializer对象,如果为False,生成Serializer对象
序列化对象.data方法--调用父类data方法---调用对象自己的to_representation(自定义的序列化类无此方法,去父类找)
Aerializer类里有to_representation方法,for循环执行attribute = field.get_attribute(instance)
再去Field类里去找get_attribute方法,self.source_attrs就是被切分的source,然后执行get_attribute方法,source_attrs
当参数传过去,判断是方法就加括号执行,是属性就把值取出来
'''
图书的增删查改resful接口案例:
视图层:
class BookSerializers(serializers.ModelSerializer):
class Meta:
model=models.Book
fields='__all__'
class BookView(APIView):
def get(self, request):
book_list = models.Book.objects.all()
bs = BookSerializers(book_list, many=True)
# 序列化数据
return Response(bs.data)
def post(self, request):
# 添加一条数据
print(request.data)
bs=BookSerializers(data=request.data)
if bs.is_valid():
bs.save() # 生成记录
return Response(bs.data)
else:
return Response(bs.errors)
class BookDetailView(APIView):
def get(self,request,pk):
book_obj=models.Book.objects.filter(pk=pk).first()
bs=BookSerializers(book_obj,many=False)
return Response(bs.data)
def put(self,request,pk):
book_obj = models.Book.objects.filter(pk=pk).first()
bs=BookSerializers(data=request.data,instance=book_obj)
if bs.is_valid():
bs.save() # update
return Response(bs.data)
else:
return Response(bs.errors)
def delete(self,request,pk):
models.Book.objects.filter(pk=pk).delete()
return Response("")
路由:
url(r'^books/$', views.BookView.as_view()),
url(r'^books/(?P\d+)$', views.BookDetailView.as_view()),