DRF初识与序列化

一、Django的序列化方法

1、为什么要用序列化组件

做前后端分离的项目,我们前后端数据交互一般都选择JSON,JSON是一个轻量级的数据交互格式。
那么我们给前端数据的时候都要转成json格式,那就需要对我们从数据库拿到的数据进行序列化。

2、表的构建

CHOICES = ((1, "python"), (2, "linux"), (3, "go"))

# 书籍表
class Book(models.Model):
title = models.CharField(max_length=64)
category = models.IntegerField(choices=CHOICES) # 书籍分类
pub_time = models.DateField()
publisher = models.ForeignKey(to="Publisher")
authors = models.ManyToManyField(to="Author") class Meta:
verbose_name = '书籍'
verbose_name_plural = verbose_name def __str__(self):
return self.title # 出版社表
class Publisher(models.Model):
title = models.CharField(max_length=64) class Meta:
verbose_name = '出版社'
verbose_name_plural = verbose_name def __str__(self):
return self.title # 作者表
class Author(models.Model):
name = models.CharField(max_length=32)
class Meta:
verbose_name = '作者'
verbose_name_plural = verbose_name def __str__(self):
return self.name

3、希望构建的字典格式

book_list = [
{
"id": 1,
"title": "",
"publisher": { # 外键
"id": 1
"title": ""
},
"authors": [{}, {}] # 多对多
},
{
"id": 2,
"title": "",
"publisher": { # 外键
"id": 1
"title": ""
},
"authors": [{}, {}] # 多对多
}, ]

4、方法一

在Django中使用Json模块序列化
class BooksView(views.View):
def get(self, request):
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset) # 把queryset类型转换成列表
# 如果我们需要取外键关联的字段信息 需要循环获取外键 再去数据库查然后拼接成我们想要的
ret = []
for book in book_list:
publisher_obj = Publisher.objects.filter(id=book["publisher"]).first()
# 修改原字典中的publisher对应的值
book["publisher"] = {
"id": publisher_obj.id,
"title": publisher_obj.title
}
# 把新的字典追加到一个空列表中
ret.append(book)
ret = json.dumps(ret, ensure_ascii=False, cls=MyJson)
return HttpResponse(ret) # json不能序列化时间字段,重写JSONEncoder里面的default方法解决
class MyJson(json.JSONEncoder):
def default(self, field):
if isinstance(field, datetime.datetime):
return field.strftime('%Y-%m-%d %H:%M:%S')
elif isinstance(field, datetime.date):
return field.strftime('%Y-%m-%d')
else:
return json.JSONEncoder.default(self, field)

5、方法二

使用JsonResponse,自动帮我们重写了JSONEncoder里面的default方法,解决时间字段的问题
class BooksView(views.View):
def get(self, request):
book_queryset = Book.objects.values("id", "title", "pub_time", "publisher")
book_list = list(book_queryset) # 把queryset类型转换成列表
# 如果我们需要取外键关联的字段信息 需要循环获取外键 再去数据库查然后拼接成我们想要的
ret = []
for book in book_list:
publisher_obj = Publisher.objects.filter(id=book["publisher"]).first()
# 修改原字典中的publisher对应的值
book["publisher"] = {
"id": publisher_obj.id,
"title": publisher_obj.title
}
# 把新的字典追加到一个空列表中
ret.append(book)
return JsonResponse(ret, safe=False, json_dumps_params={'ensure_ascii': False})

6、方法三

使用Django自带的序列化模块
from django.core import serializers # 能够得到我们要的效果 结构有点复杂
class BooksView(views.View):
def get(self, request):
book_queryset = Book.objects.all()
ret = serializers.serialize("json", book_queryset, ensure_ascii=False)
return HttpResponse(ret)

二、DRF序列化的介绍

1、介绍

下载DRF模块:pip install djangorestframework
导入:
from rest_framework.views import APIView
from rest_framework.response import Response 使用DRF默认的页面,需要在settings的APP注册rest_framework
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
]
首先,我们要用DRF的序列化,就要遵循DRF框架的一些标准,
  -- Django我们CBV继承类是View,现在DRF我们要用APIView
  -- Django中返回的时候我们用HTTPResponse,JsonResponse,render ,DRF我们用Response

2、APIView跟View区别

-- APIView继承了View
-- APIView的as_view方法实现了csrf中间件的豁免(用DRF不需要再把settings的csrf中间件注释掉了)
-- 重新封装了request
request._request可以拿到旧的request
request.query_params 旧的request.GET即_request.GET
request.data 除了GET请求外的所有的数据,_request.POST、_request.FILES等
-- 序列化器对象.data
  存放序列化好的数据
  校验通过的数据存放到validated_data里,最后也会序列化封装到序列化对象.data里面返回给前端
  校验不通过,错误信息存到序列化对象.errors里面
-- DRF有自己的序列化模块
  from rest_framework import serializers
-- Response
  继承了HttpResponse
  携带HTTP标准状态码
  做模板的渲染

3、使用方法

-- 序列化(传数据到前端)
-- 声明一个序列化器
class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=32)
pub_time = serializers.DateField()
-- 视图里序列化我们的queryset或者某个对象
# queryset需要声明many=True
ser_obj = BookSerializer(queryset, many=True)
# 具体的某个的对象则不需要声明
ser_obj = BookSerializer(book_obj)
return Response(ser_obj.data)
-- 实现流程
-- 如果指定了many=True
-- 把queryset当成可迭代对象去循环 得到每个模型对象
-- 把每个模型对象放入序列号器进行序列化
-- 进行字段匹配 匹配上的字段进行序列化 匹配不上丢弃
-- 必须满足序列化的所有字段要求 -- 反序列化(从前端获取数据)
-- 获取前端传过来的数据
-- 用序列化器进行校验
# 新增一条数据
ser_obj = BookSerializer(data=request.data)
# 编辑某条数据
# instance编辑哪个对象,data前端传过来要跟新的某些字段数据,partial表示允许部分跟新
ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
if ser_obj.is_valid():
ser_obj.save()# 调用create/update方法
return Response(ser_obj.data)
else:
return Response(ser_obj.errors)
-- 写create方法
在create方法里用ORM操作创建新对象
-- 写update方法
在update方法里用ORM操作创编辑对象

4、注意事项

1. 外键和多对多字段的序列化需要额外再设置序列化器
2. 序列化
序列化器的参数是queryset和many
3. 反序列化
序列化器的参数是data=提交上来的数据
4. 序列化器字段类型不统一的情况
反序列化要用的一些字段通过一些参数跟序列化区分开
-- required=False
-- read_only=True
-- write_only=True 5. 反序列化的验证
is_valid()    --> 校验数据
post请求中的save() --> 调用序列化器的create方法
  put请求中的save() --> 调用序列化器的update方法

三、DRF序列化示例

1、声明序列化器

# serializers.py文件

from rest_framework import serializers
from libsys.models import Book CHOICES = ((1, "python"), (2, "linux"), (3, "go")) # 继承serializers.Serializer
class PublisherSerializer(serializers.Serializer):
id = serializers.IntegerField()
title = serializers.CharField(max_length=64) class AuthorSerializer(serializers.Serializer):
id = serializers.IntegerField()
name = serializers.CharField(max_length=32) # 字段的声明和models相类似
class BookSerializer(serializers.Serializer):
# POST校验的时候required=False声明不需要校验这个字段
id = serializers.IntegerField(required=False) title = serializers.CharField(max_length=64)
pub_time = serializers.DateField() # 选择字段,显示的是数字
# category = serializers.ChoiceField(choices=CHOICES)
# 把选择字段显示成字符类型,source参数后面跟的是ORM操作
# read_only=True表示这个字段只在渲染前端的时候使用
category = serializers.CharField(source='get_category_display', read_only=True)
# write_only=True表示这个字段只在POST提交数据,做校验的时候使用
post_category = serializers.IntegerField(write_only=True) # 外键需要设置额外的序列化器对它进行序列化
publisher = PublisherSerializer(read_only=True)
publisher_id = serializers.IntegerField(write_only=True) # 多对多字段需要设置额外的序列化器对它进行序列化,且声明many=True
authors = AuthorSerializer(many=True, read_only=True)
author_list = serializers.ListField(write_only=True) def create(self, validated_data): # validated_data是通过校验的数据,最后也会封装到data里面
#通过ORM操作给Book表增加数据
book_obj = Book.objects.create(title=validated_data["title"], pub_time=validated_data["pub_time"],
category=validated_data["post_category"], publisher_id=validated_data["publisher_id"])
book_obj.authors.add(*validated_data["author_list"])
return book_obj def update(self, instance, validated_data):
# 通过ORM操作给Book表编辑数据
# instance就是book_obj
instance.title = validated_data.get("title", instance.title)
instance.pub_time = validated_data.get("pub_time", instance.pub_time)
instance.category = validated_data.get("post_category", instance.category)
instance.publisher_id = validated_data.get("publisher_id", instance.publisher_id)
if validated_data.get("author_list", False):
instance.authors.set(validated_data["author_list"])
instance.save()
return instance

2、在视图函数中调用

from libsys.models import Book, Publisher, Author
from rest_framework.views import APIView
from rest_framework.response import Response
from .serializers import BookSerializer # Create your views here. # 书籍列表
class BookView(APIView):
def get(self, request):
book_queryset = Book.objects.all()
# 声明一个序列化器
# 用序列化器去序列化queryset(queryset有多个对象的时候,需要声明many=True)
# 把数据提交到序列化器,跟序列化器的字段进行匹配,匹配成功就进行序列化
ser_obj = BookSerializer(book_queryset, many=True)
return Response(ser_obj.data) # 新增书籍
def post(self, request):
# 前端传过来的数据应该也是这样的
# {"id""title",
# category: 1
# publisher: 1
# authors: [1, 2]
# } # 获取前端传过来的数据
book_obj = request.data
# 用序列化器做校验
ser_obj = BookSerializer(data=book_obj)
if ser_obj.is_valid():
# 校验通过,新增书籍
ser_obj.save() # 这里的save方法会去调用序列化器的create方法
print(ser_obj.validated_data) # validated_data是通过校验的数据,也会封装到data里面
return Response(ser_obj.data)
# 校验不通过返回错误信息
return Response(ser_obj.errors) # 编辑书籍
class BookEditView(APIView):
def get(self, request, id):
book_obj = Book.objects.filter(pk=id).first()
ser_obj = BookSerializer(book_obj)
return Response(ser_obj.data) def put(self, request, id):
book_obj = Book.objects.filter(id=id).first()
ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
if ser_obj.is_valid():
ser_obj.save()
return Response(ser_obj.data)
return Response(ser_obj.errors)

四、验证

1、 单个字段的验证(局部钩子)

class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
title = serializers.CharField(max_length=64)
其他字段... # 局部钩子方法:validate_字段名,value 是提交过来的这个字段的数据
def validate_title(self, value):
# 定义校验规则:标题必须含有python、linux、go
course_list = ['python', 'linux', 'go']
for course in course_list:
if course in value.lower():
return value
else:
raise serializers.ValidationError('输入的书籍名不合法')

2、 多个字段的验证(全局钩子)

class BookSerializer(serializers.Serializer):
post_category = serializers.IntegerField(write_only=True)
publisher_id = serializers.IntegerField(write_only=True)
其他字段... # 全局钩子方法:validate,attrs 是前端传过来的所有的数据组成的字典
def validate(self, attrs):
# 定义校验规则:书籍分类和作者id不能超过3
if attrs['post_category'] > 3 or attrs['publisher_id'] > 3:
raise serializers.ValidationError('输入的图书分类或作者不存在')
return attrs

3、 自定义校验规则

def my_validate(value):
if '周星星' in value:
raise serializers.ValidationError('输入的书籍太帅,不合法')
return value class BookSerializer(serializers.Serializer):
id = serializers.IntegerField(required=False)
# validators声明校验的规则
title = serializers.CharField(max_length=64, validators=[my_validate,])

4、校验的权重

自定义校验 > 局部钩子 > 全局钩子

五、ModelSerializer

1、介绍

跟Django的Form组件类似,我们使用DRF进行前后端数据交互,有很多需要序列化和反序列化的字段都跟models模型相关,
那么,DRF也给我们提供了跟模型紧密相关的序列化器:ModelSerializer
-- 继承serializers.ModelSerializer
-- 它和Form有点类似
-- 它会根据模型自动生成一组字段
-- 它默认实现了.update()以及.create()方法

2、定义ModelSerializer序列化器

class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
# fields = ["id", "title", "pub_time"]
# exclude = ["user"]
# 分别是所有字段 包含某些字段 排除某些字段

3、外键关系的字段

当序列化类META中定义了depth时,这个序列化类中引用字段(外键)则自动变为只读

class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
# fields = ["id", "title", "pub_time"]
# exclude = ["user"]
# 分别是所有字段 包含某些字段 排除某些字段 # depth 代表找嵌套关系的第几层
depth = 1

4、META中的其他参数

class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
# fields = ["id", "title", "pub_time"]
# exclude = ["user"]
# 分别是所有字段 包含某些字段 排除某些字段 # depth 代表找嵌套关系的第几层
depth = 1 # 只读字段
read_only_fields = ["id"] # 给某些字段设置额外参数
extra_kwargs = {"title": {"validators": [my_validate,]}}

5、SerializerMethodField方法字段

外键关联的对象有很多字段我们是用不到的,如果都传给前端会有数据冗余,
就需要我们自己去定制序列化外键对象的哪些字段。 使用了方法字段,它会自动去找对应的钩子函数(get_字段名),这个方法字段展示的值就是钩子函数的返回值
钩子函数的参数obj:就是序列化的每个模型对象 book_obj class BookSerializer(serializers.ModelSerializer):
# 方法字段
# SerializerMethodField 会去找钩子方法 钩子方法的返回值给这个字段
# 钩子函数:get_字段名称
category_text = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True) def get_category_text(self, obj):
# obj就是序列化的每个模型对象 book_obj
return obj.get_category_display() def get_publisher_info(self, obj):
return {"id": obj.publisher_id, "title": obj.publisher.title} def get_author_info(self, obj):
return [{"id": author.id, "name": author.name} for author in obj.authors.all()] class Meta:
model = Book
fields = "__all__"

6、完整的ModelSerializer思路

因为depth会让我们外键变成只读,所以一般来说我们不用它,
因为如果是前端发数据过来(post,put等),我们对外键、多对多等字段进行验证是验证其在数据库的中真实值,
因此,把ModelSerializer序列化器fields代表的字段应该用于反序列化,而对序列化到前端的外键、多对多等字段的数据,
我们可以使用SerializerMethodField对其进行处理后展示到前端。 class BookSerializer(serializers.ModelSerializer):
# 方法字段
category_text = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True)
# 方法字段的值,取决于它对应的方法字段的钩子函数的返回值
def get_category_text(self, obj):
# obj就是序列化的每个模型对象 book_obj
return obj.get_category_display() def get_publisher_info(self, obj):
return {"id": obj.publisher_id, "title": obj.publisher.title} def get_author_info(self, obj):
return [{"id": author.id, "name": author.name} for author in obj.authors.all()] class Meta:
model = Book
fields = "__all__"
# fields = ["id", "title", "pub_time"]
# exclude = ["user"]
# 分别是所有字段 包含某些字段 排除某些字段 # depth 代表找嵌套关系的第几层
# depth = 1 extra_kwargs = {
"category": {'write_only': True},
"publisher": {'write_only': True},
"authors": {'write_only': True},
}
上一篇:第三方系统打开EAFC的实现


下一篇:laravel代码生成器的记录