DRF之序列化组件
Django REST Framework(DRF)提供了强大的序列化功能,可以将模型实例转换为JSON等格式,并能进行数据验证和保存。序列化器是DRF的核心组件之一,主要用于将复杂的数据模型转换为Python原生数据类型或其他可读格式,并支持反序列化操作,将request中的数据反序列化并创建或修改模型实例。
序列化介绍
- 在写接口时,需要序列化和反序列化,而且反序列化的过程中要做数据校验,drf直接提供了固定的写法,只需要按照固定写法,只需要按照固定写法使用,就能完成上面的三个需求。
- 提供了两个类
Serializer
、ModelSerializer
,编写自定义的类,只需要继承drf提供的序列化类,就可以使用其中的某些方法,也能完成上面的三个需求
序列化类的作用:做序列化、反序列化、反序列化校验
序列化器的实现
自定义模型的序列化器,需要继承自Serializer
、ModelSerializer
,如果是继承自Serializer
,必须实现create()、update(),按照需要重写校验方法,save()方法
- create(),在save()方法中调用,用于创建模型实例
- update(),在save()方法中调用,用于修改模型实例数据
- save(),在试图类中使用,在调用is_valid(),返回True后调用,保存数据
- is_valid(),调用校验方法对反序列化数据进行校验
序列化器的使用
使用序列化器,需要传入,
- data: 需要反序列化的数据,一般取自视图函数的request.data
- instance: 模型实例,查询、修改的时候需要传入,将模型实例序列化为python原生数据
- many: bool类型,模型实例为查询结果集需要传入
- partial:部分更新模型实例数据的时候需要用到
- context:需要传入额外反序列化数据的时候用到,如传入user_id
class BaseSerializer(Field):
def __init__(self, instance=None, data=empty, **kwargs):
self.instance = instance
if data is not empty:
self.initial_data = data
self.partial = kwargs.pop('partial', False)
self._context = kwargs.pop('context', {})
kwargs.pop('many', None)
super().__init__(**kwargs)
serializers.Serializer
序列化步骤
- 写一个py文件,叫serializer.py(命名随意)
- 写一个序列化类,继承serializers.Serializer,
- 在类中编写需要序列化的字段 例:name=serializers.CharField()
- 在视图类中使用,导入models文件中的类books,然后实例化得到对象, 对单个对象序列化并传入instance=books参数,如果是query_set,必须对序列化器传入参数many=True
- 序列化类对象:ser.data Python原生数据类型---->传递给Response自动转换为对应请求格式的数据返回
序列化组件的基本使用
1.新建一个文件serializer.py
# 导入序列化模块
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=18, min_length=2, required=True)
price = serializers.IntegerField(required=True)
2.view.py文件中
from app01 import models from rest_framework.views import APIView
from rest_framework.response import Response from .serializer import BookSerializer
class BookView(APIView):
def get(self, request):
book_list = models.Book.objects.all()
ser = BookSerializer(instance=book_list, many=True) # 序列化多条需传入many=True
return Response({'code': 100, 'msg': '查询成功', 'results': ser.data}) # python原生数据类型,自动转化为请求对应格式数据
class BookDetailView(APIView):
def get(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book_obj)
# is_valid()方法,对数据进行校验
if ser.is_valid():
return Response({'code': 100, 'msg': '查询一条成功', 'results': ser})
else:
return Response(ser.errors)
3.urls.py文件中
urlpatterns = [
path('books/', views.BookView.as_view()),
path('books/<int:pk>', views.BookDetailView.as_view()),
]
反序列化基本使用
反序列化过程:新增、修改
新增:
1. 前端传入后端的数据,不论编码格式,都在request.data中,request.data格式是字典
前端根据传入的编码格式不一样,从request.data取到的字典形式也是不一样的
编码格式 字典
urlencoded QueryDict
form-data QueryDict
json dict
2. 将前端传入的数据通过request.data获取进行反序列化,并完成序列化类的反序列化
3. 序列化类得到对象并传入参数:data=request.data
校验数据
保存:ser.save()--->序列化类中重写create方法
修改:
1. 拿到前端传入的数据,进行反序列化,查出要修改的对象--->序列化类的反序列化
2. 序列化类得到对象,传入参数:instance=要修改的对象,data=request.data
校验数据
保存:ser.save() --->序列化类中重写update方法
反序列化的新增
序列化类
class BookSerializer(serializers.Serializer): name = serializers.CharField() price = serializers.IntegerField()
# 新增一条数据
def create(self, validated_data):
# 保存的逻辑
# validated_data 校验过后的数据 {name,price,publish}
# 保存到数据库
book = Book.objects.create(**validated_data)
# 一定不要忘记返回新增的对象
return book
视图类
class BookView(APIView): def get(self, request): # 获取多条数据 book\_list = models.Book.objects.all() '''instance表示要序列化的数据,many=True表示序列化多条(instance是QuerySet对象)''' ser = BookSerializer(instance=book\_list, many=True) # 序列化多条需要many=True return Response({'code': 100, 'msg': '查询成功', 'results': ser.data})
def post(self, request):
ser = BookSerializer(data=request.data) # 从前端传递数据从request.data中取出来
if ser.is_valid(): # is_valid表示校验前端传入的数据,但是我们没有写校验规则
# 保存,需要自己写,要在序列化类BookSerializer中重写create方法
ser.save() # 调用ser.save,自动触发自定义编辑create方法保存数据
'''
这个时候发送post请求会发生报错,NotImplementedError: `create()` must be implemented.
这个时候点击我们点击save查看源码是调用了Save会触发BaseSerializer的方法
判断了 如果instance有值执行update,没有值执行create 看到create没有写 所以我们得重写Create
'''
return Response({'code': 100, 'msg': '添加成功', 'results': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
反序列化的新增
序列化类
class BookSerializer(serializers.Serializer): name = serializers.CharField() price = serializers.IntegerField()
# 修改对象
def update(self, instance, validated_data):
# instance 要修改的对象
# validated_date 校验过后的数据
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.save() # orm的单个对象,修改了单个对象的属性,只要调用对象.save就可以修改保存到数据库
return instance # 记得把修改的对象返回
视图类
class BookDetailView(APIView):
def get(self, request, pk): # 获取单条数据
book_obj = models.Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book_obj)
return Response({'code': 100, 'msg': '查询一条成功', 'results': ser.data})
def put(self, request, pk):
book_obj = models.Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book_obj,data=request.data)
if ser.is_valid():
ser.save() # 同新增一样,需要重写update方法
return Response({'code': 100, 'msg': '修改一条成功', 'results': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
删除单条
class BookDetailView(APIView): def delete(self,request,pk):
models.Book.objects.filter(pk=pk).delete()
return Response({'code': 100, 'msg': '删除一条成功'})
反序列化的校验
反序列化的数据校验功能类比forms组件
1. 内置校验 字段参数,validators参数(参考下方 序列化器常用字段和字段参数)
2. 字段参数validators配合django.core.validators,实现正则校验
3. 局部钩子 validate_字段名 方法
4. 全局钩子 validate 方法
代码实现
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import Book
from django.core.validators import RegexValidator, EmailValidator
class BookSerializer(serializers.Serializer):
# 内置校验
name = serializers.CharField(max_length=18, min_length=2, required=True)
price = serializers.IntegerField(required=True)
publish = serializers.CharField(min_length=3)
# 正则校验
email = serializers.CharField(validators=[EmailValidator(message="邮箱格式错误")])
more = serializers.CharField(validators=[RegexValidator(r"\d+", message="格式错误")])
# 新增一条数据
def create(self, validated_data):
# 保存的逻辑
# validated_data 校验过后的数据 {name,price,publish}
# 保存到数据库
book = Book.objects.create(**validated_data)
# 一定不要忘记返回新增的对象
return book
# 修改对象
def update(self, instance, validated_data):
# instance 要修改的对象
# validated_date 校验过后的数据
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.save()
# orm的单个对象,修改了单个对象的属性,只要调用对象.save就可以修改保存到数据库
return instance # 记得把修改的对象返回
# 局部钩子
def validate_price(self,price):
if price < 10 or price > 999:
raise ValidationError('价格不能高于999或者低于10')
return price
# 全局钩子
def validate(self, attrs):
# 校验过后的数据,出版社后三位文字与书名后三位不能一样
if attrs.get('publish')[-3] == attrs.get('name')[-3]:
raise ValidationError('出版社后三位文字不能与书名后三位一样!')
return attrs
序列化器常用字段和字段参数
常用字段类
字段名 | 字段构造方式 |
---|---|
CharField | CharField(max_length=None, min_length=None, allow_blank=False, |
trim_whitespace=True) | |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
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: 小数点位置 | |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, |
input_formats=None) | |
EmailField | EmailField(max_length=None, min_length=None, |
allow_blank=False) | |
RegexField | RegexField(regex, 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) |
UUIDField | UUIDField(format=’hex_verbose’) format: |
1)'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a " 2)'hex' 如 | |
“5ce0e9a55ffa654bcee01238041fb31a ” 3)'int' - 如: | |
“123456789012312313134124512351145145114 ” 4)'urn' 如: | |
"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" | |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, |
**options) | |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
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=) |
在序列化器内使用这些字段则是将接收到的字段转换成某个类型的。 如:
id
字段值为int
类型,我们通过id=serializer.CharField()
来接收,那么返回到视图里面的则是字符串类型了。
常用字段参数
选项参数:字段不同可使用的参数也不同
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
IntegerField
参数名称 | 作用 |
---|---|
max_value | 最小值 |
min_value | 最大值 |
通用参数
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
序列化器返回字段的定义
- 字段参数source的指定,序列化、反序列化的字段,可以通过外键关联字段或反向关联字段
- 自定义方法,适合需要对数据进行处理的字段,可以是模型自定义的方法,通过source绑定
- 也可以是序列化器自定义的方法,SerializerMethodField(),get_字段名
- 嵌套序列化器,外键字段名[_set]=序列化类名(read_only=True,many=True/False),推荐指定只读,另外定义一个id字段用于反序列化,否则反序列化的时候会报错,或者自己在validate方法中对validated_data进行处理,修改多个表的话还涉及到事务。
序列化器-source的使用
提前准备好models.py中的数据
from django.db import models
# 创建关联表
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
# 外键 书与出版社 -对多,关联字段写在多的一方,写在Book
publish=models.ForeignKey(to='Publish',on_delete=models.CASCADE)
# 书与作者 多对多 需要建立中间表,django自动生成第三张表
authors = models.ManyToManyField(to='Author')
# 方法
def sb_name(self):
return self.name+'sb'
class Publish(models.Model):
name= models.CharField(max_length=32)
city= models.CharField(max_length=32)
email = models.EmailField()
class Author(models.Model):
name = models.CharField(max_length=32)
age = models.IntegerField()
source
- 可以定制序列化字段名
- 防止数据被人篡改,将前端展示的字段名和后端数据的字段名设置成不同的字段名
source序列化自有字段和关联字段的区别
'serializer.py'
class BookSerializer(serializers.Serializer):
'自有字段,修改字段,映射字段,直接随意写序列化字段名'
# 修改、映射字段,以下的book_price是不存在表中的字段
book_price = serializers.CharField(source='price')
name = serializers.CharField(source='name')
# 修改字段、映射方法,以下sb_name是表模型中的方法
name = serializer.CharField(source='sb_name')
'关联字段,通过外键获取'
'''跨表查询'''
一对一、一对多的关联,直接使用外键字段点
publish = serializers.CharField(max_length=8,source='publish.name')
多对多的关联,source不能用实现定制序列化关联表的字段
authors = serializers.CharField(source='authors.all')
代码展示
视图类:
class BookAPIView(APIView):
def get(self,request):
books = Book.objects.all()
ser = BookSerializer(instance=books,many=True)
return Response(ser.data)
定制序列化类
from rest_framework import serializers
# 序列化类
class BookSerializer(serializers.Serializer):
# 字段参数 都可以通过sourse定制具体的字段名
# 自有字段,直接写表字段名
real_name = serializers.CharField(max_length=8,source='name')
real_price = serializers.CharField(source='price')
# 关联字段 一对多 ,直接通过外键点
publish=serializers.CharField(source='publish.name')
# 多对多,source不能用定制关联表的字段,不推荐这么使用
authors = serializers.CharField(source='authors.all')
"""
1.我们序列化的是book表字段,自有字段名直接通过sourse指定的字段名-->>是Book表的字段名
2.sourse定制目标的字段名name(max_length=8)...
3.提高了安全性,后端真实的字段名:name、price,
序列化给前端字段名:real_name、real_price,"authors": "<QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>"
"""
SerializerMethodField
定制字段的两种方式
定制关联字段(publish/authors)
的显示形式
- 一对多显示字典:{}
- 多对多显示列表套字典:[{},{},{}]
SerializerMethodField定制 返回给前端的格式
{ "name": "西游记", "price": "66.00", "publish_detail": { "name": "东方出版社", "city": "东方" }, "author_list": [ { "name": "张三", "city": 19 } ] },
高级序列化之SerializerMethodField
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 定制返回格式----serializerMethodField
# 只要写了这个字段类serializerMethodField,必须配合get_字段名
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self, obj):
print(obj) # 当前序列化到的book对象
return {'name': obj.publish.name, 'city': obj.publish.city}
author_list = serializers.SerializerMethodField()
def get_author_list(self,obj):
l = [] for author in obj.authors.all():
l.append({'name': author.name, 'city': author.age})
return l
在表模型中定制 返回给前端格式
{ "name": "西游记", "price": "66.00", "publish_detail": { "name": "东方出版社", "city": "东方" }, "author_list": [ { "name": "张三", "age": 19 } ] },
'models.py'
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.DecimalField(max_digits=5, decimal_places=2)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
# 定制在模型类中的方法
def publish_detail(self):
return {'name': self.publish.name, 'city': self.publish.city}
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'age': author.age})
return l
''
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 定制返回格式----在表模型中写方法,在序列化类中做映射
publish_detail = serializers.DictField()
author_list = serializers.ListField()
总结:
- 在序列化类中继承
SerializerMethodField
,类中编写一个get_字段名
(get_publish_detail)方法,该方法返回值=该字段值,该字段只能做序列化字段,反序列化不行 - 在表模型中使用编写:
get_字段名
(get_publish_detail)方法,该publish_detail
等于方法返回的值,序列化类中需要配合ListField,DictField两个字段类型---->该字段只能做序列化使用,不能实现做反序列化的字段 - 可以将上述的方法可以包装成数据属性—使用伪装@property,然后定义一个setter方法
PrimaryKeyRelatedField
DRF的to_representation和to_internal_value是序列化和反序列化过程中最核心的方法,它们分别用于将数据对象转换成字典,和将字典转换成数据对象。
to_representation的作用是将查询集合、模型实例、JSON等数据类型,转换成DRF内置的Serializer所支持的数据类型(比如嵌套的Serializer、List、Dict等),从而可以对这些数据进行序列化。
to_internal_value的作用是将传入的字典数据(比如通过API POST提交的JSON数据),转换成需要保存到数据库的原生数据类型(比如序列化后需要保存到Model中的IntegerField、CharField等)。
在to_representation
中进行额外字段的增加
class CabinetSerializer(serializers.Serializer):
idc = serializers. PrimaryKeyRelatedField(many=False, queryset=Idc.objects.all())
name = serializers.CharField(required=True)
def to_representation(self, instance):
# 实例对象中获取关联表的实例对象
idc_obj = instance.idc
# 调用父类的to_representation方法,返回数据
ret = super(CabinetSerializer, self).to_representation(instance)
# 修改数据,将idc_obj实例对象的属性赋值给一个字典
ret["idc"] = {
"id": idc_obj.id,
"name": idc_obj.name
}
return ret
显示如下:
[
{
"idc": {
"id": 1,
"name": "亚太机房"
},
"name": "A座6楼601 13排2机柜"
}
]
反序列化之保存数据
这里我就使用多表关联
'反序列化校验执行流程' 1.先执行自有字段的校验(参数控制)-----》最大长度、最小长度、是否为空、是否必填、最小数字 2.validators=[方法,] ---->单独给这个字段加校验规则(这个用的少,因为局部钩子就能实现) name=serializers.CharField(validators=[方法,]) 3.局部钩子校验规则 4.全局钩子校验规则
新增
views.py
class BookView(APIView):
def get(self, request):
books = models.Book.objects.all()
ser = BookSerializer(instance=books, many=True)
return Response(ser.data)
def post(self,request):
ser = BookSerializer(data=request.data)
if ser.is_valid():
ser.save() # 书写序列化类中create方法
return Response(ser.data)
else:
return Response(ser.errors)
序列化类中,使用
read_only
和write_only
分别定制序列化和反序列化的序列外键字段,并重写新增接口的create
方法,read_only
和write_only
的目的是:需要序列化和反序列化的类,就可以使用这两种方法
read_only
:表明该字段仅用于序列化输出,默认是False,序列化过程write_only
:表明该字段经用于反序列化输入,默认是False,反序列化过程
serializer.py
class BookSerializer(serializers.Serializer):
'''公共的'''
name = serializers.CharField() price = serializers.CharField()
'''只用来做反序列化'''
# 如果直接使用模型的外建字段名,实例化的时候需要传递的是一个实例化对象,否而会报错,且序列化器是不会自动处理的,
# 或者直接定义一个_id接受一个id值
# publish = serializers.IntegerField(write_only=True)
publish_id = serializers.IntegerField(write_only=True)
authors = serializers.ListField(write_only=True)
'''只用来做序列化'''
publish_detail = serializers.SerializerMethodField(read_only=True)
author_list = serializers.SerializerMethodField(read_only=True)
def get_publish_detail(self, obj):
print(obj) # 当前序列化到的book对象
return {'name': obj.publish.name, 'city': obj.publish.city}
def get_author_list(self,obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'city': author.age})
return l
'''新增,继承Serializer类必须要重写create方法'''
def create(self, validated_date): # validated_date是校验过后的数据
authors = validated_date.pop('authors')
print(authors)
print('------')
print(validated_date)
'''
这里不能用**validated_date打散的方式,因为我们写的是 publish=1这种的它需要放一个对象,
而其实存进去是publish_id=1,一个数字,所以无法使用,
如果想要使用这个**打散的方式,需要把上面的publish改成publish_id,然后前端发送的也改成publish_id
'''
# name = validated_date.get('name')
# price = validated_date.get('price')
# publish_id = validated_date.get('publish')
# book = models.Book.objects.create(name=name,price=price,publish_id=publish_id)
book = models.Book.objects.create(**validated_date)
book.authors.add(*authors)
return book
修改
修改跟新增不一样,因为得知道修改那一条,所以接口就得不一样,需要获取到想要修改的一条数据
urls.py
urlpatterns = [ path('books/int:pk', views.BookDetailView.as_view()), ]
views.py
class BookDetailView(APIView): def put(self, request, pk): book = models.Book.objects.get(id=pk) ser = BookSerializer(instance=book, data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) else: return Response(ser.errors)
serializer.py
class BookSerializer(serializers.Serializer): '''公共的''' name = serializers.CharField() price = serializers.CharField()
'''只用来做反序列化'''
# publish = serializers.IntegerField(write_only=True)
publish_id = serializers.IntegerField(write_only=True)
authors = serializers.ListField(write_only=True)
'''只用来做序列化'''
publish_detail = serializers.SerializerMethodField(read_only=True)
author_list = serializers.SerializerMethodField(read_only=True)
def get_publish_detail(self, obj):
print(obj) # 当前序列化到的book对象
return {'name': obj.publish.name, 'city': obj.publish.city}
def get_author_list(self,obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'city': author.age})
return l
'''修改,继承Serializer类必须要重写update方法''' def update(self, instance,validated_date): # validated_data 校验过后的数据 authors = validated_date.pop('authors') '''如果是用的publish的,又想要一次性传入进去,就得自己在定义一个publish_id''' # validated_date['publish_id'] = validated_date.pop('publish') for key in validated_date: setattr(instance, key, validated_date[key])
instance.save()
'''也可以直接使用set修改,无需清空后再添加'''
instance.authors.clear()
instance.authors.add(*authors)
# instance.authors.set(authors) # 无需打散传入了
return instance
反序列化之数据校验
serializer.py
这里沿用上面案例的数据添加数据校验,数据校验和上面的反序列校验一样,所以这里就不细说了
'校验自有的字段' name = serializers.CharField(max_length=8,error_messages={'max_length:不能超过8位'})
'''局部钩子校验'''
def validate_name(self,name):
l = ['sb', '傻逼','fw']
for i in l:
if i in name:
raise ValidationError('图书命名中不能有敏感词!')
return name
'''局部钩子校验'''
def validate_price(self, price):
if int(price) < 10 or int(price) > 999:
raise ValidationError('图书价格不能小于2位数或者不能大于4位数!')
return price
'''全局钩子校验''' def validate(self,attrs): print(attrs) publish_id = attrs.get('publish_id') print(publish_id) publish_obj = models.Publish.objects.filter(pk=publish_id).first() print(publish_obj.name) if attrs.get('name')[-2] == publish_obj.name[-2]: raise ValidationError('图书名后两位不能与出版社后两位相同') return attrs
ModelSerializer的使用
ModelSerializer
继承了Serializer
,帮我们完成了很多的操作,比如跟表模型强关联,大部分的请求post
和put
等,几乎不用再序列化的时候重写create
和update
方法了。
使用方式跟上面的Serializer的基本一样,我这里就还是用上面的案例来操作
serializer.py
'''继承ModelSerializer类做序列化、反序列化以及校验操作''' class BookModelSerializer(serializers.ModelSerializer): '''其他跟Serializer一样使用,不过无需再重写create和update方法''' '''只用来做序列化,这些扩写的字段也需要在field中注册''' publish_detail = serializers.SerializerMethodField(read_only=True) author_list = serializers.SerializerMethodField(read_only=True)
def get_publish_detail(self, obj):
print(obj) # 当前序列化到的book对象
return {'name': obj.publish.name, 'city': obj.publish.city}
def get_author_list(self, obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'city': author.age})
return l
class Meta:
model = models.Book # 这两句是会把表模型中的Book,所有字段映射过来
# fields = '__all__'
fields = ['name', 'price', 'publish', 'authors', 'publish_detail', 'author_list']
'''如果使用表模型定制字段,然后在fields中注册了,可以直接在extra_kwargs中设置字段属性read_only'''
extra_kwargs = { # 给某个或某几个字段设置字段属性
'name': {'max_length': 18, 'min_length': 2},
'price': {'max_digits': 8, 'decimal_places': 3},
'publish': {'write_only': True},
'authors': {'write_only': True},
# 'publish_detail': {'read_only': True},
# 'author_list': {'read_only': True},
}
'''局部钩子'''
def validate_name(self,name):
l = ['sb', '傻逼','fw']
for i in l:
if i in name:
raise ValidationError('图书命名中不能有敏感词!')
return name
def validate_price(self, price):
if int(price) < 10 or int(price) > 999:
raise ValidationError('图书价格不能小于2位数或者不能大于4位数!')
return price
# '''全局钩子'''
# def validate(self,attrs):
# print(attrs)
# publish_id = attrs.get('publish')
# print(publish_id)
# publish_obj = models.Publish.objects.filter(pk=publish_id).first()
# print(publish_obj.name)
# if attrs.get('name')[-2] == publish_obj.name[-2]:
# raise ValidationError('图书名后两位不能与出版社后两位相同')
# return attrs
这里为了区分一下,我就更改了接口路由
urlpatterns = [ path('books/', views.BookModelView.as_view()), path('books/int:pk', views.BookDetailModelView.as_view()), ]
views.py,视图类也是为了做一下区分,所以我修改了序列化类名,其他跟serializer那个操作一样
'''继承serializer类的ModelSerializer方式''' class BookModelView(APIView): def get(self,request): book = models.Book.objects.all() ser = BookModelSerializer(instance=book,many=True) return Response(ser.data)
def post(self,request):
ser = BookModelSerializer(data=request.data)
if ser.is_valid():
ser.save()
return Response(ser.data)
else:
return Response(ser.errors)
class BookDetailModelView(APIView): def put(self, request, pk): book = models.Book.objects.filter(pk=pk).first() ser = BookModelSerializer(instance=book, data=request.data) if ser.is_valid(): ser.save() return Response(ser.data) else: return Response(ser.errors)
使用方法
1.定义一个类继承ModelSerializer
2.类内部写内部类 Class Meta:(注意区分大小写)
3.在内部类中指定model(需要序列化的表)
4.在内部类中指定fields(要序列化的字段,写**all**表示所有,不包含方法,也可以写一个个的字段)
5.在内部类中指定extra\_kwargs,给字段天津爱字段参数
6.在序列化类中,可以重写某个字段,优先使用你重写的 name = serializers.SerializerMethodField() def get_name(self,obj): return obj.name + '__fw'
7.以后几乎不需要重写create和update方法了(除非有需求要用到就得重写) -ModelSerializer写好了,兼容性更好,任意表都可以直接存
进阶
序列化器外键的处理
关联序列化
1、PrimaryKeyRelatedField
此字段将被序列化为关联对象的主键。
type = serializers.PrimaryKeyRelatedField(label=‘分类’, read_only=True) 或
type = serializers.PrimaryKeyRelatedField(label=‘分类’,queryset=BookInfo.objects.all())
指明字段时需要包含read_only=True或者queryset参数: •包含read_only=True参数时,该字段将不能用作反序列化使用 •包含queryset参数时,将被用作反序列化时参数校验使用
2、 StringRelatedField
此字段将被序列化为关联对象的字符串表示方式(即__str__方法的返回值)
type = serializers.StringRelatedField(label=‘分类’)
3、SlugRelatedField
此字段将被序列化为关联对象的指定字段数据
type = serializers.SlugRelatedField(label=‘分类’, read_only=True, slug_field=‘bpub_date’)
slug_field指明使用关联对象的哪个字段
4、使用关联对象的序列化器
hbook = BookInfoSerializer() 此字段只可以用GET方法,也就是只能序列化,不能反序列化
5、重写to_representation方法
序列化器的每个字段实际都是由该字段类型的to_representation
方法决定格式的,可以通过重写该方法来决定格式。
注意,to_representations
方法不仅局限在控制关联对象格式上,适用于各个序列化器字段类型。
定义一个新的关联字段:
class TypeRelateField(serializers.RelatedField):
"""自定义用于处理分类的字段"""
def to_representation(self, value):
return ‘Book: %d %s’ % (value.id, value.btitle)
# 指明type为typeRelateField类型
type = TypeRelateField(read_only=True)
6.HyperlinkedRelatedField
可以用于使用超链接表示关系的目标 例如下面
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.HyperlinkedRelatedField(many=True,read_only=True,view_name=‘track-detail’)
class Meta:
model= Album
fields = ('album_name', 'artist', 'tracks')
将序列化为这样的表示:
{
‘album_name’: ‘Graceland’,
‘artist’: ‘Paul Simon’,
‘tracks’: [
‘http://www.example.com/api/tracks/45/’,
‘http://www.example.com/api/tracks/46/’,
‘http://www.example.com/api/tracks/47/’,
…
]
}
默认情况下,此字段是读写的,但您可以使用该read_only标志更改此行为。
many参数
如果关联的对象数据不是只有一个,而是包含多个数据,如想序列化分类TypeInfo数据,每个TypeInfo对象关联的商品GoodInfo对象可能有多个,此时关联字段类型的指明仍可使用上述几种方式,只是在声明关联字段时,多补充一个many=True参数即可
class BlogListSerializer(serializers.Serializer):
id = serializers.IntegerField()
user = BlogUserInfoSerializer()
title = serializers.CharField()
like_user = serializers.ManyRelatedField(serializers.SlugRelatedField(slug_field="username", source="user_id"), queryset=User.objects.all(), source="follow")
topic_name = serializers.SlugRelatedField(slug_field="name", source="topic", queryset=Topic.objects.all())
create_at = serializers.DateTimeField()
class Blog(models.Model):
follow = models.ManyToManyField(User, verbose_name="关注", related_name="follow_blog")
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="blog")
title = models.CharField(max_length=100)
content = models.TextField(max_length=200000)
topic = models.ForeignKey(Topic, null=True, verbose_name="话题", on_delete=models.CASCADE)
create_at = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
class Topic(models.Model):
name = models.CharField(verbose_name="名称", max_length=100)
create_at = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
外键反向关联序列化
反向关联:model_set
如果查询一个老师,想返回所有关联这个老师的学生们,通过序列化器可以使用关联表模型_set字段完成,还要记得由于是多个学生关联,所以这个序列化字段要加 many=True属性
查询所有老师的信息,并且连带返回所有老师所拥有的学生
class TeacherFullDetail(APIView):
def get(self, request):
teachers = Teacher.objects.all()
ser = TeacherSerializer(instance=teachers, many=True)
return Response(ser.data, status=200)
PrimaryKeyRelatedField
使用反向关联表的主键进行返回
class TeacherSerializer(serializers.ModelSerializer):
student_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)
class Meta:
model = Teacher
fields = '__all__'
结果如下,反向关联字段使用关联表的主键进行返回, 并且封装在一个列表中
[ { "id": 1, "student_set": [ 1, 2 ],
"name": "张老师",
"age": 20
},
{
"id": 2,
"student_set": [],
"name": "李老师",
"age": 18
}
]
StringRelatedField
通过关联表str方法进行返回
class TeacherSerializer(serializers.ModelSerializer):
student_set = serializers.StringRelatedField(read_only=True, many=True)
class Meta:
model = Teacher
fields = '__all__'
结果就是反向关联外键的结果列表中包含的都是学生表str方法返回的结果
[ { "id": 1, "student_set": [ "小红", "小明" ],
"name": "张老师",
"age": 20
},
{
"id": 2,
"student_set": [],
"name": "李老师",
"age": 18
}
]
SlugRelatedField
通过指定表中字段作为序列化字段的结果
class TeacherSerializer(serializers.ModelSerializer):
student_set = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
class Meta:
model = Teacher
fields = '__all__'
结果如下所示
[ { "id": 1, "student_set": [ "小红", "小明" ],
"name": "张老师",
"age": 20
},
{
"id": 2,
"student_set": [],
"name": "李老师",
"age": 18
}
]
嵌套序列化器关联
使用关联表的单独序列化器,和关联外键序列化器一样样的
class StudentSer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'
class TeacherSerializer(serializers.ModelSerializer):
student_set = StudentSer(many=True, read_only=True)
class Meta:
model = Teacher
fields = '__all__'
效果就是学生的反向外键成为了具体的数据信息,而不是单独以指定字段或方式进行维护定义的
[ { "id": 1, "student_set": [ { "id": 1, "name": "小红", "teacher": 1 }, { "id": 2, "name": "小明", "teacher": 1 } ],
"name": "张老师",
"age": 20
},
{
"id": 2,
"student_set": [],
"name": "李老师",
"age": 18
}
]
外键字段反序列化
- 主动传值,ModelSerializer默认接受一个 _id的字段
- 外键传值,如果序列化器中外键字段使用了嵌套序列化器,不会字段反序列化的,在视图函数中使用序列化器的时候传入context,
Serializer(instance=objects, data=request.data,context={})
,此时有两个选择了,可以嵌套的序列化器不设置读写属性,直接在视图函数中实例化外键关联模型,然后通过context:{ 外键名: 外键关联模型实例 },或者嵌套的序列化器设置为只读,直接传入context:{ 外键名_id: id } - 定义一个只写的_id字段,外键字段为嵌套的序列化器,设置为只读,另外添加一个
字段_id
的字段用于接受外键关联的id
注意:涉及多个表的注意事务的管理
还是上面的例子,反序列化创建一个学生,使用模型序列化器很简单,默认是接受外键关联表的主键数据作为创建的依据,也就是代表着外键可以不像原始使用 ORM一样得传一个外键表数据 object,而是直接传一个 id就可以了
主键传值
在 postman或其他手段进行测试时,直接传老师 id、学生名、及其他所需字段即可
学生反序列化器不需要写任何字段,默认的关联字段会接受一个 id数据作为校验依据并创建
class StudentSer(serializers.ModelSerializer):
class Meta:
model = Student
fields = '__all__'
创建接口如下
class StudentCreate(APIView):
def post(self, request):
ser = StudentSer(data=request.data)
if ser.is_valid():
ser.save()
error = ser.errors
return Response({'msg': error if error else 'success'})
postman 测试时,只需要传如下数据即可
{
"teacher": "2", # 外键直接传入 id 即可
"name": "小黑"
}
外界传值
在序列化器使用过程中,可以通过 context 传递参数,这个参是一个字典数据对象,设置一些额外的值,使你的序列化器可以更灵活的操作参数
Serializer(instance=objects, data=request.data,context={})
现在序列化器不会自动验证接受并处理 teacher字段,需要手动维护
class StudentSer(serializers.ModelSerializer):
teacher = TeacherSer(read_only=True)
# read_only 将该字段设置为只是序列化使用,不会经过反序列化处理
class Meta:
model = Student
fields = '__all__'
# fields = 'name' # 指明字段也可以实现屏蔽 teacher 字段反序列化的作用
通过之前的 Json数据提交 teacher与 name字段,此时 teacher是无效的,不会被反序列化处理 但是我们可以单独把提交的 teacher提取出来,传入 context参数,重写 create方法,在 create方法中获取 context参数,拿到 teacher,看下面的新接口
新的序列化器是这样的
class StuSer(serializers.ModelSerializer):
teacher = TeacherSer(read_only=True)
def create(self, validated_data):
validated_data['teacher_id'] = self.context.get('teacher')
ModelClass = self.Meta.model
object = ModelClass._default_manager.create(**validated_data)
return object
class Meta:
model = Student
fields = '__all__'
视图稍微处理一下 teacher数据
class StuCreate(APIView):
def post(self, request):
teacher = request.data.get('teacher')
ser = StuSer(data=request.data, context={'teacher': teacher})
if ser.is_valid():
ser.save()
error = ser.errors
return Response({'msg': error if error else 'success'})
当然还有更直接的办法,在序列化器中定义一个只写的字段_id
,如下:
class StuSer(serializers.ModelSerializer):
teacher = TeacherSer(read_only=True)
# 只写,然后前端传值的时候直接串teacher_id:value就好了
teacher_id = serializers.CharField(write_only=True)
处理可读写的序列化
如果嵌套的序列化器设置为可读写,那么需要对单独的对数据进行处理,可以再视图函数中对数据进行处理,也可以在create方法中进行处理 看下面这个例子,在create方法中进行处理:
# 一个Order实例对应多个OrderItem实例,外键定义在OrderItem中
class OrderSerializer(ModelSerializer):
orderitem_set = OrderItemSerializer(many=True)
status = serializers.CharField(read_only=True)
store = serializers.SlugRelatedField(read_only=True, slug_field='name')
store_id = serializers.IntegerField()
def create(self, validated_data):
# 将序列化器处理过的数据中orderItem_set拿出来处理,然后先实例化对象
orderItem_set = validated_data.pop('orderitem_set', None)
# 启动事务,由于先创建了Order实例,又创建了OrderItem实例,不用事务,可能只成功创建了Order实例
with transaction.atomic():
# 此时调用父类的create方法实例化Order对象就不会报错了
Order_instance = super().create(validated_data)
for attrs in orderItem_set:
attrs['Order'] = Order_instance
OrderItem.objects.create(**attrs)
return Order_instance
反序列化校验
在之前的操作中,我们已经接触到了 update 以及 create 方法进行更新创建的反序列化操作,验证主要通过调用is_valid方法触发
其实在反序列化过程中,还可以通过由 DRF 所指定的一系列校验方法对字段进行更加细致的验证,比如对于密码的强弱判断,直接通过字段属性是无法做到的,但是可以放在以下所提供的校验方法中,自定义其校验规则
validate_字段
这种办法是单独字段的校验,可以对序列化器类对象所写的字段进行判断验证
- 在序列化器里定义校验字段的钩子方法:validate_字段
- 获取字段的数据,就是参数接收到的value值
- 验证不通过,抛出异常:raise serializers.ValidationError(“校验不通过的说明”)
- 验证通过,返回字段数据
def validate_name(self, value):
if len(value) == 1:
raise serializers.ValidationError('名字长度无法为1')
return value
validate
多字段联合校验,这种校验方法是可以一次性获取到所有序列化器字段里的全部值,可以进行一个统一整体的校验判断
- 在序列化器定义validate方法,参数为**attrs__
- attrs是所有数据组成的字典
- 不符合抛出异常:raise serializers.ValidationError(“校验不通过的说明”)
- 如果校验全部成功,那么返回attrs参数对象
def validate(self,attrs):
name = attrs.get('name')
if name != '张三':
raise serializers.ValidationError('抱歉,不是张三不让注册')
return attrs
验证器
封装重复校验规则,使用函数机制,定义校验规则,然后将函数作用至序列化器的字段上
def my_name_validate(value):
if value != '张三':
raise serializers.ValidationError('你不是张三,不可以创建')
else:
return value
生效该验证器
name = serializers.CharField(
max_length=30,
validators=[my_name_validate] # 生效校验器,可以有多个
)
权重:验证器方法 > validate_字段 > validate
嵌套序列化器
主要是ORM类中对应ForeignKey 和 ManyToManyField的字段进行序列化。 models.py
from django.db import models
class Role(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)
order = models.IntegerField(verbose_name="顺序")
class Tag(models.Model):
caption = models.CharField(verbose_name="名称", max_length=32)
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
gender = models.SmallIntegerField(verbose_name="性别", choices=((1, "男"), (2, "女")))
role = models.ForeignKey(verbose_name="角色", to="Role", on_delete=models.CASCADE)
ctime = models.DateTimeField(verbose_name="创建时间", auto_now_add=True)
tags = models.ManyToManyField(verbose_name="标签", to="Tag")
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import serializers
from api import models
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = models.Role
# fields = "__all__"
fields = ["id", 'title']
class TagSerializer(serializers.ModelSerializer):
class Meta:
model = models.Tag
fields = "__all__"
class InfoSerializer(serializers.ModelSerializer):
role = RoleSerializer() # 嵌套序列化器,一个
tags = TagSerializer(many=True) # 嵌套序列化器,多个
class Meta:
model = models.UserInfo
fields = ['id', 'name', "role", "tags"]
class InfoView(APIView):
def get(self, request):
queryset = models.UserInfo.objects.all()
ser = InfoSerializer(instance=queryset, many=True)
print(type(ser.data), ser.data)
return Response(ser.data)
如果使用了嵌套序列化器,是不是fields是不是就不能用"__all__"
了
不是的,只要序列化器的字段名写的正确,序列化器还是能正常的解析的 无论是一对多还是多对多,只要是外键字段定义在哪个模型,哪个模型的序列化器就还是用模型的字段名就好了,不过多对多的需要注意many=True; 如果是外键关联的表,也就是是反向关联的话,序列化器的字段名就需要加上_set,参数many=True
序列化类的继承
to_representation和to_internal_value方法的作用
DRF的to_representation和to_internal_value是序列化和反序列化过程中最核心的方法,它们分别用于将数据对象转换成字典,和将字典转换成数据对象。
DRF所有序列化器类都继承了BaseSerializer类,通过重写该类的to_representation()和to_internal_value()方法可以改变序列化和反序列化的行为,比如给序列化后的数据添加额外的数据,或者对客户端API请求携带的数据进行反序列化处理以及用来自定义序列化器字段。
to_representation的作用是将查询集合、模型实例、JSON等数据类型,转换成DRF内置的Serializer所支持的数据类型(比如嵌套的Serializer、List、Dict等),从而可以对这些数据进行序列化。
to_internal_value的作用是将传入的字典数据(比如通过API POST提交的JSON数据),转换成需要保存到数据库的原生数据类型(比如序列化后需要保存到Model中的IntegerField、CharField等)。
重写这些方法的作用是可以自定义序列化和反序列化过程中的数据转换规则,从而可以满足各种不同的业务需求。比如可以根据不同的角色返回不同的数据、动态过滤掉某些字段、根据某些字段的取值计算生成额外的字段等。
to_representation() 允许改变序列化的输出。 to_internal_value() 允许改变反序列化的输出。 假设有如下一个文章模型(Article):
class Article2(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
like_by = models.ManyToManyField(to=User)
def __str__(self):
return self.title
每个文章资源有title, body和liked_by 三个字段。liked_by代表喜欢该文章的用户对象id列表。
序列化器ArticleSerializer类如下所示:
class ArticleSerializer8(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
如果使用上面序列化器去序列化单篇文章资源,将得到如下输出数据:
{
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
]
}
to_representation方法 如果希望给上面输出数据添加一个total_likes点赞总数的字段,只需要在序列化器类里重写to_representation方法。
class ArticleSerializer9(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def to_representation(self, instance):
# 调用父类获取当前序列化数据,instance代表每个对象实例obj
data = super().to_representation(instance)
# 对序列化数据做修改,添加新的数据
data['total_likes'] = instance.liked_by.count()
return data
现在使用新的序列化器类去序列化单篇文章资源,将得到如下输出结果。to_representation() 方法改变了序列化的输出,并传递了额外的数据。
{
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
],
"total_likes": 3
}
to_internal_value方法 to_internal_value主要在反序列化时用到,其作用处理API请求携带的数据,对其进行验证并转化为Python的数据类型。
假如API客户端通过请求提交了额外的数据,比如extra_info字段,如下所示
{
"extra_info": {
"msg": "Hello world!"
},
"article_data": {
"id": 1,
"title": "DRF advanced tutorials",
"body": "This is a good example.",
"liked_by": [
2,
3,
4
],
"total_likes": 3
}
}
由于extra_info字段不属于ArticleSerializer类里的字段,如果直接使用ArticleSerializer类对上述数据进行反序列化会出现错误。
事实上反序列化时只需要提取article_data然后对其反序列化即可,所以可以重写to_internal_value提取所需要的数据,忽略不想要的数据。
class ArticleSerializer10(serializers.ModelSerializer):
class Meta:
model = Article
fields = '__all__'
def to_internal_value(self, data):
# 提取所需要的数据,对其进行反序列化,data代表未验证的数据
article_data = data.get('article_data', '')
return super().to_internal_value(article_data)
自定义序列化器类字段 to_representation() 和to_internal_value()方法的另一个重要用途就是用来自定义序列化类字段。下例为DRF提供的一个官方演示,展示了如何使用这两个方法自定义了一个包含有x, y坐标的字段CoordinateField字段。
# 自定义序列化器类字段
class CoordinateField(serializers.Field):
def to_representation(self, value):
ret = {
'x': value.x_coordinate,
'y': value.y_coordinate
}
return ret
def to_internal_value(self, data):
ret = {
'x_coordinate': data['x'],
'y_coordinate': data['y']
}
return ret
定义好后,可以在序列化类中使用。
from .models import DataPoint
# 使用上述自定义序列化器类的字段
class DataPointSerializer(serializers.ModelSerializer):
coordinates = CoordinateField(source='*')
class Meta:
model = DataPoint
fields = ['label', 'coordiante']
反序列化数据校验源码分析
反序列化数据校验,
校验顺序为:先校验字段自己的规则(最大、最小),然后是局部钩子,然后是全局钩子
-
反序列化校验开始 在视图类中的
s_valid()
被执行时,就会进行反序列化的校验,校验通过返回True
,否则返回False
-
反序列化的过程
- ser.is_valid()是序列化类的对象,假设序列化类是BookSerializer,当执行is_valid时。首先在序列化类产生的对象中查找,当找不到就会去找到它的父类中找,结果在它父类的父类中找到了BaseSerializer
BaseSerializer类
def is_valid(self, *, raise_exception=False):
'断言检查,确保类的实例中具有名为initial_data的属性,如果没有就引发下面这段话包含的特定的错误信息'
assert hasattr(self, 'initial_data'), (
'Cannot call `.is_valid()` as no `data=` keyword argument was '
'passed when instantiating the serializer instance.'
)
if not hasattr(self, '_validated_data'):
'''
self序列化类的对象,属性中没有_validated_data,就一定会走这句
并且只要走过一次后,下一次就无需再次走了,它优化了is_valid被多次调用,只会走一次校验
'''
try:
'真正的走校验,一旦执行了这里,以后self中就有了_validated_data'
'''
然后我们就得去看看这个self.run_validation(self.initial_data)
我们不能直接按住ctrl键点击,因为它会从当前类中找run_validation,
我们得清楚,这个self是谁,它还是我们的视图类,所以我们得返回到视图类从它一步一步往它继承的类中找
清楚如何查找后,我们就从视图类----》serializer.Serializer(找到了,为什么从这个里面找因为
我们就是使用serializer.Serializer类的)
'''
self._validated_data = self.run_validation(self.initial_data)
'''
这下面的是当校验不通过时,执行的,会给_validatad_data设置为一个空字段,
并且给_errors设置为验证错误的详细信息
'''
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
return not bool(self._errors)
通过上面self.run_validation(self.initial_data)我们找到了下面这块源码,
而这里的run_validatiion方法就是DRF序列化器验证过程中的核心
def run_validation(self, data=empty):
'''
局部钩子的执行,这里的self就还是视图类的对象
data就是前端传入的数据,value是前端传入,字段自己校验通过的字典
'''
value = self.to_internal_value(data)
try:
'这个是字段验证器'
self.run_validators(value)
'''
这个是全局钩子的执行,和上面一样self是视图类的对象,如果我们在序列化其中写了全局钩子,
那么就优先使用我们自己定义的全局钩子,如果没写则执行父类的,(serializer.Serializer)
结果可以看到父类根本就没有做校验
def validate(self, attrs):
return attrs
'''
value = self.validate(value) #运行自定义验证方法
'确保自定义验证方法返回了验证后的数据'
assert value is not None, '.validate() should return the validated data'
except (ValidationError, DjangoValidationError) as exc:
# 捕获验证过程中可能引发的异常,并转换为DRF的ValidationError
raise ValidationError(detail=as_serializer_error(exc))
# 返回验证后的数据
return value
看完了全局钩子后我们在看一下局部钩子的self.to_internal_value(data),也是一样的思路,从视图类找,
视图类肯定没有,然后找父类serializer结果就是这个页面的
def to_internal_value(self, data):
ret = OrderedDict()# 用于存储验证后的数据
errors = OrderedDict()# 用于存储字段验证过程中的错误信息
fields = self._writable_fields# 获取序列化器中的所有字段
'序列化类中所有的字段,for循环每次取一个字段对象。例如name=CharField()'
for field in fields:
'''去视图类的对象中反射,validate_字段名的方法,如果有就执行,没有就不执行'''
validate_method = getattr(self, 'validate_' + field.field_name, None)
primitive_value = field.get_value(data)
try:
'这句话就是字段自己的校验规则(最长最短长度等)'
validated_value = field.run_validation(primitive_value)
'局部钩子,如果在序列化类中写了局部钩子验证规则,就运行自己写的'
if validate_method is not None:
'执行局部钩子,传入当前字段的value值'
validated_value = validate_method(validated_value)
'如果抛异常了就会被捕获'
except ValidationError as exc:
'捕获DRF的ValidationError异常'
errors[field.field_name] = exc.detail
except DjangoValidationError as exc:
'捕获Django的ValidationError异常'
errors[field.field_name] = get_error_detail(exc)
except SkipField:
'如果遇到SkipField异常,就跳过该字段的处理'
pass
else:
'如果没有异常,将验证后的值设置到结果中'
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
其他
ImageField字段序列化处理,怎么才能返回完整的url。
如果你是直接使用ModelViewSet视图类,应该可以发现对于ImageField序列化后是一个完整的url,如果是自己实现的视图方法,在使用序列化器的时候,需要传递参数context={'request':request}
,这时候ImageField序列化的结果就是完整的url了。
补充ImageField的使用
# settings.py
# 配置 MEDIA_ROOT 作为你上传文件在服务器中的基本路径
MEDIA_ROOT = os.path.join(BASE_DIR, 'upload') # 注意此处不要写成列表或元组的形式
# 配置 MEDIA_URL 作为公用 URL,指向上传文件的基本路径
MEDIA_URL = '/media/'
# 这里特意写成 upload 和 media,而不是统一写成 media 或 upload,是为了便于理解 MEDIA_ROOT 和 MEDIA_URL 的作用和区别
# models.py
def user_directory_path(instance, filename):
ext = filename.split('.').pop()
filename = '{0}{1}.{2}'.format(instance.name, instance.identity_card, ext)
return os.path.join(instance.major.name, filename) # 系统路径分隔符差异,增强代码重用性
class Student(models.Model):
......
# upload_to 参数接收一个回调函数 user_directory_path,该函数返回具体的路径字符串,图片会自动上传到指定路径下,即 MEDIA_ROOT + upload_to
# user_directory_path 函数必须接收 instace 和 filename 两个参数。参数 instace 代表一个定义了 ImageField 的模型的实例,说白了就是当前数据记录;filename 是原本的文件名
# null 是针对数据库而言,如果 null = True, 表示数据库的该字段可以为空;blank 是针对表单的,如果 blank = True,表示你的表单填写该字段的时候可以不填,但是对数据库来说,没有任何影响
photo = models.ImageField('照片', upload_to = user_directory_path, blank = True, null = True)
......
# 这里定义一个方法,作用是当用户注册时没有上传照片,模板中调用 [ModelName].[ImageFieldName].url 时赋予一个默认路径
def photo_url(self):
if self.photo and hasattr(self.photo, 'url'):
return self.photo.url
else:
return '/media/default/user.jpg'
# 注意是项目根路由 urls.py 文件,而不是应用中的二级路由 urls.py 文件
# 方法一:
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
......
] + static(settings.MEDIA_URL, document_root = settings.MEDIA_ROOT)
# 方法二:
from django.urls import re_path
from django.conf import settings
from django.views.static import serve
urlpatterns = [
......
# 注意是 media 而不是 upload
re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
]
总结
ModelSerializer继承自Serializer,我们可以直接查询多条,单条,新增和修改等操作 针对fields=\['name','email','city']里面的参数要求:
1.只要是序列化的字段和反序列化的字段,都要在这注册 如:publish\_detail,publish,authors
2.序列化的字段,可能不是表模型的字段,是自己写的方法
3.序列化的字段,可能是使用SerializerMethodField,也要注册
参考
[1] blog.csdn.net/zhouruifu20…
[2] blog.csdn.net/weixin_4822…