DRF - 序列化组件
序列化组件
引入:
视图的功能:说白了就是接收前端请求,进行数据处理(这里的处理包括:如果前端是GET请求,则构造查询集,将结果返回,这个过程为序列化;如果前端是POST请求,假如要对数据库进行改动,则需要拿到前端发来的数据,进行校验,将数据写入数据库,这个过程称为反序列化)
最原始的视图可以实现这样的逻辑处理,但是针对不同的请求,需要在类视图中定义多个方法实现各自的处理,这样是可以解决问题,但是存在一个缺陷,那就是每个函数中一般的逻辑都差不多:读请求,从数据库拿数据,写东西到数据库,返回结果给前端。这样就会产生大量的重复代码。
在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
删:判断要删除的数据是否存在 -> 执行数据库删除
改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
查:查询数据库 -> 将数据序列化并返回
序列化组件的使用
定义model:
from django.db import models
class Publish(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
city = models.CharField(max_length=32)
email = models.EmailField()
def __str__(self):
return self.name
class Author(models.Model):
nid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.CASCADE)
authors = models.ManyToManyField(to="Author")
def __str__(self):
return self.title
通过序列化组件进行GET接口设计
首先,设计url,本次我们只设计GET和POST两种接口:
from django.urls import re_path
from serializers import views
urlpatterns = [
re_path(r'books/$', views.BookView.as_view())
]
我们新建一个名为app_serializers.py的模块,将所有的序列化的使用集中在这个模块里面,对程序进行解耦:
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=128)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish = serializers.CharField(max_length=32)
authors = serializers.CharField(max_length=32)
接着,使用序列化组件,开始写视图类:
from rest_framework.views import APIView
from rest_framework.response import Response
# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer
# Create your views here.
class BookView(APIView):
def get(self, request):
# 获取查询集 queryset
origin_books = Book.objects.all()
# 进行校验
# 当为queryset时,many=True.当是model类型时,many=False
serialized_books = BookSerializer(origin_books, many=True)
# 通过序列化对象.data将数据读出来
return Response(serialized_books.data)
如此简单,我们就已经,通过序列化组件定义了一个符合标准的接口,定义好model和url后,使用序列化组件的步骤如下:
- 导入序列化组件:from rest_framework import serializers
- 定义序列化类,继承serializers.Serializer(建议单独创建一个专用的模块用来存放所有的序列化类)
- 定义需要返回的字段(字段类型可以与model中的类型不一致,参数也可以调整),字段名称必须与model中的一致
- 在GET接口逻辑中,获取QuerySet
- 开始序列化:将QuerySet作业第一个参数传给序列化类,many默认为False,如果返回的数据是一个列表嵌套字典的多个对象集合,需要改为many=True
- 返回:将序列化对象的data属性返回即可
上面的接口逻辑中,我们使用了Response对象,它是DRF重新封装的响应对象。该对象在返回响应数据时会判断客户端类型(浏览器或POSTMAN),如果是浏览器,它会以web页面的形式返回,如果是POSTMAN这类工具,就直接返回Json类型的数据。
此外,序列化类中的字段名也可以与model中的不一致,但是需要使用source参数来告诉组件原始的字段名,如下:
class BookSerializer(serializers.Serializer):
BookTitle = serializers.CharField(max_length=32)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
# source也可以用于ForeignKey字段
# source:指定显示的字段
publish = serializers.CharField(max_length=32, source="publish.name")
authors = serializers.CharField(max_length=32)
# 在一对多:在序列化书籍信息时,同时序列化出书籍关联的任务信息
# StringRelatedField : 也是适用的,显示汉字字段而不是指定的PrimaryKey,这样更直观
# many=True :指定heroinfo_set是多的那一方
# read_only:该字段只能进行序列化,反序列化时直接忽略该字段
下面是通过POSTMAN请求该接口后的返回数据,大家可以看到,除ManyToManyField字段不是我们想要的外,其他的都没有任何问题:
[
{
"title": "Python入门",
"price": "119.00",
"publish": "浙江大学出版社",
"authors": "serializers.Author.None"
},
{
"title": "Python进阶",
"price": "128.00",
"publish": "清华大学出版社",
"authors": "serializers.Author.None"
}
]
那么,多对多字段如何处理呢?如果将source参数定义为”authors.all”,那么取出来的结果将是一个QuerySet,对于前端来说,这样的数据并不是特别友好,我们可以使用如下方式:
class BookSerializer(serializers.Serializer):
title = serializers.CharField(max_length=32)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish = serializers.CharField()
publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
publish_email = serializers.CharField(max_length=32, read_only=True, source='publish.email')
# authors = serializers.CharField(max_length=32, source='authors.all')
# 多对多字段需要自己手动获取数据通过调用下面的方法
authors_list = serializers.SerializerMethodField()
def get_authors_list(self, authors_obj):
authors = list()
for author in authors_obj.authors.all():
authors.append(author.name)
return authors
请注意,get_必须与字段名称一致,否则会报错。
通过序列化组件进行POST接口设计
接下来,我们设计POST接口,根据接口规范,我们不需要新增url,只需要在视图类中定义一个POST方法即可,序列化类不需要修改,如下:
from rest_framework.views import APIView
from rest_framework.response import Response
# 当前app中的模块
from .models import Book
from .app_serializer import BookSerializer
class BookView(APIView):
def get(self, request):
origin_books = Book.objects.all()
serialized_books = BookSerializer(origin_books, many=True)
return Response(serialized_books.data)
def post(self, request):
# 将查询集以及前端传过来的数据绑定给序列化器
verified_data = BookSerializer(data=request.data)
if verified_data.is_valid():
#这个的地方的save()会调用create()方法,但源码里面没有,因此,我们需要自己创建
book = verified_data.save()
# 可写字段通过序列化添加成功之后需要手动添加只读字段
authors = Author.objects.filter(nid__in=request.data['authors'])
# Book表对应的多对多表,在这里添加,先查询author表的用户,在添加到book_author的对应关系中
book.authors.add(*authors)
return Response(verified_data.data)
else:
return Response(verified_data.errors)
def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()
return Response()
POST接口的实现方式,如下:
- url定义:需要为post新增url,因为根据规范,url定位资源,http请求方式定义用户行为
- 定义post方法:在视图类中定义post方法
- 开始序列化:通过我们上面定义的序列化类,创建一个序列化对象,传入参数data=request.data(application/json)数据
- 校验数据:通过实例对象的is_valid()方法,对请求数据的合法性进行校验
- 保存数据:调用save()方法,将数据插入数据库(复习:数据库增加数据见末尾)
- 插入数据到多对多关系表:如果有多对多字段,手动插入数据到多对多关系表
- 返回:将插入的对象返回
请注意,因为多对多关系字段是我们自定义的,而且必须这样定义,返回的数据才有意义,而用户插入数据的时候,serializers.Serializer没有实现create,我们必须手动插入数据,就像这样:
# 第二步, 创建一个序列化类,字段类型不一定要跟models的字段一致
class BookSerializer(serializers.Serializer):
# nid = serializers.CharField(max_length=32)
title = serializers.CharField(max_length=32)
price = serializers.DecimalField(max_digits=5, decimal_places=2)
publish = serializers.CharField()
# 外键字段, 显示__str__方法的返回值
publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
# authors = serializers.CharField(max_length=32)
# book_obj.authors.all()
# 多对多字段需要自己手动获取数据,SerializerMethodField()
authors_list = serializers.SerializerMethodField()
def get_authors_list(self, book_obj):
author_list = list()
for author in book_obj.authors.all():
author_list.append(author.name)
return author_list
# validated_data是经过校验之后的数据,已经是标准的字典
def create(self, validated_data):
# validated_data: {'title': 'Python666', 'price': Decimal('66.00'), 'publish': '2'}
# 因为Book表里没有publish字段,因此,我们需要删除publish
validated_data['publish_id'] = validated_data.pop('publish')
# 对字典进行拆包 把数据增加到Book表里
book = Book.objects.create(**validated_data)
return book
def update(self, instance, validated_data):
# 更新数据会调用该方法
# 取不到就取默认值
instance.title = validated_data.get('title', instance.title)
instance.price = validated_data.get('price', instance.price)
instance.publish_id = validated_data.get('publish', instance.publish.nid)
instance.save()
return instance
这样就会非常复杂化程序,如果我希望序列化类自动插入数据呢?
问题一:如何让序列化类自动插入数据?
问题二:如果字段很多,那么显然,写序列化类也会变成一种负担,有没有更加简单的方式呢?
ModelSerializer
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title',
'price',
'publish',
'authors',
'author_list',
'publish_name',
'publish_city'
)
# model 指明参照哪个模型类
# fields 指明为模型类的哪些字段生成
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True}
}
publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
author_list = serializers.SerializerMethodField()
def get_author_list(self, book_obj):
# 拿到queryset开始循环 [{}, {}, {}, {}]
authors = list()
for author in book_obj.authors.all():
authors.append(author.name)
return authors
步骤如下:
-
定义:继承ModelSerializer
- model 指明参照哪个模型类
- fields 指明为模型类的哪些字段生成
我们可以在python manage.py shell中查看自动生成的BookInfoSerializer的具体实现
-
指定字段
- 使用fields来明确字段,
__all__
表名包含所有字段,也可以写明具体哪些字段,如上。 - 使用exclude可以明确排除掉哪些字段,不能跟fields同时用
- 默认ModelSerializer使用主键作为关联字段,但是我们可以使用depth来简单的生成嵌套表示,depth应该是整数,表明嵌套的层级数量。深度控制,写几往里拿几层,层数越多,响应越慢,官方建议0--10之间,个人建议最多3层
- 指明只读字段:通过read_only_fields指明只读字段,即仅用于序列化输出的字段。
- 使用fields来明确字段,
-
添加额外参数:我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
使用serializer进行put接口设计
根据规范,PUT接口用来定义用户对数据修改的逻辑,也就是update,它的url是这样的, 127.0.0.1/books/1/,请求方式是PUT,1代表的是具体的数据,使用户动态传递的,所以我们的url应该是这样的:
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),
此时我们应该重新定义一个视图类,因为url不一样了,所以在views.py中,需新增一个视图类:
from rest_framework.views import APIView
from app_serializer import BookSerializer
class BookFilterView(APIView):
def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid():
serialized_data.save()
else:
return Response(serialized_data.errors)
请注意,在序列化时,我们除了传入data参数外,还需告诉序列化组件,我们需要更新哪条数据,也就是instance,另外,我们使用的序列化类还是之前那个:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('title',
'price',
'publish',
'authors',
'author_list',
'publish_name',
'publish_city'
)
extra_kwargs = {
'publish': {'write_only': True},
'authors': {'write_only': True}
}
publish_name = serializers.CharField(max_length=32, read_only=True, source='publish.name')
publish_city = serializers.CharField(max_length=32, read_only=True, source='publish.city')
author_list = serializers.SerializerMethodField()
def get_author_list(self, book_obj):
# 拿到queryset开始循环 [{}, {}, {}, {}]
authors = list()
for author in book_obj.authors.all():
authors.append(author.name)
return authors
使用POSTMAN工具发送一个PUT请求修改数据:
请注意,此时会报错:RuntimeError: You called this URL via PUT, but the URL doesn’t end in a slash and you have APPEND_SLASH set. Django can’t redirect to the slash URL while maintaining PUT data. Change your form to point to 127.0.0.1:9001/serializers/books/1/ (note the trailing slash), or set APPEND_SLASH=False in your Django settings.
因为,如果是GET请求,Django的全局APPEND_SLASH参数为True,所以会在url后面加上/(如果没有),但是如果是PUT或者DELETE请求,APPEND_SLASH不会添加 / 到url末尾。而我们上面定义的url是明确以 / 结尾的,所以,我们应该在url后面加上反斜线 / ,或者把url修改为不以斜线结尾。
加上之后,再次发送请求修改数据:查看数据库,发现,数据已经被修改了。
这就是PUT接口逻辑的设计,分为如下几个步骤:
- url设计:re_path(r’books/(\d+)/$’, views.BookFilterView.as_view())
- 视图类:重新定义一个视图类
- put方法:在视图类中定义一个put方法
- 序列化:在序列化的过程中,需要传入当前修改的数据行,参数名为instance
- 序列化类:不需要修改
- url路径:请求时,发送的url必须与urls.py中定义的url完全匹配
使用serializer进行delete接口设计
接下来,继续设计delete接口,根据规范,delete接口的url为:127.0.0.1/books/1/,请求方式是DELETE,与put是一致的,都是对用户指定的某行数据进行操作,数字1是动态的,所以我们的url不变:
re_path(r'books/(\d+)/$', views.BookFilterView.as_view()),
同样的,视图类和序列化类都不需要重新定义,只需要在视图类中定义一个delete方法即可,如下代码所示:
class BookFilterView(APIView):
def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)
用POSTMAN来试试DELETE请求,我们将刚刚添加的数据删除,操作成功,同样的,请注意,请求url必须完全匹配urls.py中定义的url。
使用serializer进行单条数据的接口设计
最后一个接口的设计,是对单条数据进行获取,根据规范url为:127.0.0.1/books/1/,请求方式为GET,根据url和前面两个接口的经验,这次仍然使用之前的视图类,因为根据REST规范,url唯一定位资源,127.0.0.1/books/1/和127.0.0.1/books/是不同的资源,所以,我们不能使用之前那个获取全部数据的视图类。这里肯定不能重用之前的那个get方法,必须重新定义一个get方法。
urls.py不变,新增三个接口逻辑后的视图类如下:
class BookFilterView(APIView):
def get(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(book_obj, many=False)
return Response(serialized_data.data)
def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)
many=False, 当然,也可以不传这个参数,因为默认是False。通过POSTMAN发送请求,成功。三个接口定义完成了,加上上一节课的get和post,两个视图类的接口逻辑如下:
class BookView(APIView):
def get(self, request):
origin_books = Book.objects.all()
serialized_books = BookSerializer(origin_books, many=True)
return Response(serialized_books.data)
def post(self, request):
verified_data = BookSerializer(data=request.data)
if verified_data.is_valid():
book = verified_data.save()
return Response(verified_data.data)
else:
return Response(verified_data.errors)
class BookFilterView(APIView):
def get(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(book_obj, many=False)
return Response(serialized_data.data)
def delete(self, request, nid):
book_obj = Book.objects.get(pk=nid).delete()
return Response("")
def put(self, request, nid):
book_obj = Book.objects.get(pk=nid)
serialized_data = BookSerializer(data=request.data, instance=book_obj)
if serialized_data.is_valid():
serialized_data.save()
return Response(serialized_data.data)
else:
return Response(serialized_data.errors)
复习:数据库增加数据
# 方式1
book = Book(
btitle='无问',
bput_date=date(1988,1,1),
bread=10,
bcomment=10
)
book.save()
# 方式2:
book.create(
btitle='无问',
bput_date=date(1988,1,1),
bread=10,
bcomment=10
)
参考资料:https://blog.csdn.net/qq_25068917/article/details/81077145