drf序列化类
目录
drf序列化器
我们在编写接口的时候需要对数据进行序列化与反序列化。而且反序列化过程中还要做数据校验。
drf给我们提供了固定的写法.
提供了两序列化个类
1.Serializer
2.ModelSerializer
继承drf提供的序列化类使用其中的方法就可以完成序列化与反序列化并在反序列化过程中校验数据。
练习
1.使用APIView 序列化类 Response完成接口的编写
models
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
urls
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('books/',views.BookView.as_view()),
path('books/<int:pk>/',views.BookDetailView.as_view())
]
serializer
# 导入序列化类,反序列化时进行校验
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.Serializer):
# 序列化字段
name = serializers.CharField()
price = serializers.CharField()
def create(self, validated_data):
# validated_data 就是校验过的数据
# 保存
book = Book.objects.create(**validated_data)
return book
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.save() # instance是个对象,对象可以一直点对象的方法
return instance
views
from django.shortcuts import render
# Create your views here.
from rest_framework.views import APIView
from .models import Book
from rest_framework.views import Response
from .serializer import BookSerializer
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
# 使用序列化类完成,instance参数就是需要序列化的queryset对象,many多个就传True默认None
ser = BookSerializer(instance=books, many=True)
return Response(ser.data)
# 新增一个
def post(self, request):
# 把数据传入序列化类
ser = BookSerializer(data=request.data)
# 校验数据
if ser.is_valid():
# 保存,需要在序列化类中自己写create方法
ser.save() # 调用save会自动触发create
"查看save内部源码发现内部的if判断通过我们传的参数判断是保存还是更新"
return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
class BookDetailView(APIView):
# 查询一个
def get(self,request,*args,**kwargs):
book = Book.objects.filter(pk=kwargs.get('pk')).first()
ser = BookSerializer(instance=book)
return Response(ser.data)
# 修改一个
def put(self,request,*args,**kwargs):
book = Book.objects.filter(pk=kwargs.get('pk')).first()
#反序列化保存
ser = BookSerializer(data=request.data,instance=book)
if ser.is_valid():
ser.save()
return Response({'code':100,'msg':'修改成功','result':ser.data})
else:
return Response({'code':101,'msg':ser.errors})
# 删除一个
def delete(self,request,pk):
Book.objects.filter(pk=pk).delete()
return Response({'code':100,'msg':'删除成功'})
反序列化的校验
序列化类中也可以填写局部钩子与全局钩子
# 导入序列化类,反序列化时进行校验
from rest_framework import serializers
from .models import Book
from rest_framework.exceptions import ValidationError
class BookSerializer(serializers.Serializer):
# 序列化字段
name = serializers.CharField()
price = serializers.CharField()
def create(self, validated_data):
# validated_data 就是校验过的数据
# 保存
book = Book.objects.create(**validated_data)
return book
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.save() # instance是个对象,对象可以一直点对象的方法
return instance
#局部钩子
def validate_name(self,name):
# 校验name
if name.startswith('2b'):
# 校验不通过主动抛异常
raise ValidationError('用户名不能2b开头')
pass
else:
return name
# 全局钩子
def validate(self, attrs):
"""
attrs:校验过的数据
"""
if attrs.get('price') == '0':
raise ValidationError('价钱不能为0')
else:
return attrs
反序列化校验的执行流程
1.执行字段的校验参数
2.validators 校验 单独给这个字段+校验方法
3.局部钩子
4.全局钩子
总结
序列化类几个参数的用处
1. instance 传单个对象或queryset对象
"当我们传单个对象的时候不需要添加额外的参数只需要instance参数"
2. many 传 True 默认None
"当我们传一个queryset对象过去时,需要给many传参True"
3.data 传 需要检验的数据 如request.data携带的
4.validated_data 序列化类中校验过的数据
5.全局钩子和局部狗子在序列化类里是 用 validate 与validate_字段名代表
"其中需要什么参数传什么参数,全局钩子的attrs 就是校验过的参数可以拿出数据"
序列化类字段
常用的几种需要记住,之前也学习过这里就在复习一下
字段 | 功能 |
---|---|
CharField | 字符串字段 |
IntegerField | 整型字段 |
DecimalField | 小数字段 |
DateTimeField | 时间字段 |
BooleanField | 布尔值字段 |
还有一些需要了解的
NullbooleanField # 布尔值
EmailField # 邮箱字段
RegexField # 正则字段
SlugField # 正则字段
URLField # 用于存取URL
UUIDField # 存储 xx_xx格式
IPAddressField #ip地址字段
FloatField # 小数字段
dateField # 年与日
TimeField # 时分秒
DurationField #时间戳
ChoiceField #
MultipleChoiceField # 多选
FileField # 文件
ImageField # 图片
重点
字段 | 功能 |
---|---|
ListField | 接收表模型中定制的字段,列表形式 |
DictField | 接收表模型中定制的字段,字典形式 |
常用字段参数
CharField及其子类的参数
参数 | 功能 |
---|---|
max_length | 最大长度 |
min_length | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
IntegerField
参数 | 功能 |
---|---|
max_value | 最大值 |
min_value | 最小值 |
所有字段类都有的
required # 反序列化时(存)是否必须输入 默认True
default # 反序列化时 (存) 默认值
allow_null # 字段是否允许传入None,默认False
validators # 字段使用的验证器
error_messages # 定制字段校验错误信息
label # HTML展示 该字段的名字
help_text # HTML展示显示该字段帮助提示信息
重点
参数 | 功能 |
---|---|
read_only | 字段只能序列化默认False |
write_only | 字段只能反序列化默认False |
序列化高级用法
source(了解)
source其实也是一个字段参数,他是用于给前端传输数据的时候修改字段名称
使用source定制名字
class BookSerializer(serializers.Serializer):
"在source里填写了name如果外面还用name就会报错,没有为什么"
name_html = serializers.CharField(source='name') # 使用source指定字段
price_html = serializers.CharField(source= 'price')
# 一对多字段 直接点
publish = serializers.CharField(source='publish.name')
# 多对多字段 无法点 sources方法没办法使用
authors = serializers.CharField(source='authors.all')
"无法处理多对多关系表,对于一对多表 只能处理一个字段"
高级定制字段名的两种方式
一:SerializerMethodField定制
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
name_html = serializers.CharField(source='name') # 使用source指定字段
price_html = serializers.CharField(source= 'price')
publish_detail = serializers.SerializerMethodField()
# 注意这里上面字段定制了什么名字就要get_什么名字并且需要传obj (此时是book对象)
def get_publish_detail(self,obj):
return {'name':obj.publish.name,'addr':obj.publish.addr}
authors_list = serializers.SerializerMethodField()
def get_authors_list(self,obj):
# 通过book对象.外键字段获得外键表中所有对象
"""
obj.authors.all():<QuerySet [<Author: Author object (1)>, <Author: Author object (2)>]>
"""
# 因通过.的方法拿到的是一个queryset列表对象需要处理
l = []
for author in obj.authors.all():
l.append({'name':author.name,'phone':author.phone})
return l
"这种方法这能做序列化,不能用来反序列化"
二:直接在模型表内定制
models
from django.db import models
# Create your models here.
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
@property # 不伪装也可以
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
@property
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
class Author(models.Model):
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
serializer
class BookSerializer(serializers.Serializer):
name_html = serializers.CharField(source='name') # 使用source指定字段
price_html = serializers.CharField(source= 'price')
publish_detail = serializers.DictField()
author_list = serializers.ListField()
"底层机制就是通过对象.名字来返回给前端,所以直接.伪装方法就能拿到数据"
多表关联反序列化
models
from django.db import models
class Book(models.Model):
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
authors = models.ManyToManyField(to='Author')
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
@property # 不伪装也可以
def publish_detail(self):
return {'name': self.publish.name, 'addr': self.publish.addr}
@property
def author_list(self):
l = []
for author in self.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
class Author(models.Model):
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
class Publish(models.Model):
name = models.CharField(max_length=32)
addr = models.CharField(max_length=64)
serializer
from rest_framework import serializers
from .models import Book
# 反序列化的多表关联保存
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 只用来序列化出来给前端展示 read_only = True
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 只用来反序列化 存 write_only 如果不写的话就会展示给前端
publish = serializers.CharField(write_only=True) # 前端展示的是选择出版社传对应的pk值过来
authors = serializers.ListField(write_only=True)
# 重写create
def create(self, validated_data):
book = Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),
publish_id=validated_data.get('publish'))
# 传过来是个列表 打散成位置参数
book.authors.add(*validated_data.get('authors'))
return book
# 重写update方法
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish_id = validated_data.get('publish')
# 获取作者列表
author_list = validated_data.get('authors')
# 先清除第三张关系表
instance.authors.clear()
# 在建立关系
"注意 get获得列表是个[1,2] 应打散传"
instance.authors.add(*author_list)
instance.save()
return instance
views
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.views import Response
# Create your views here.
from .serializer import BookSerializer
from .models import Book
class BookView(APIView):
def get(self, request):
books = 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()
return Response({'code':100,'msg':'新增成功','result':ser.data})
else:
return Response({'code':101,'msg':ser.errors})
class BookDetailView(APIView):
def get(self,request,pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(instance=book)
return Response(ser.data)
def put(self,request,pk):
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(data=request.data,instance=book)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
反序列化校验
与之前一样直接在序列化类中编写局部钩子与全局钩子
from rest_framework import serializers
from .models import Book
from rest_framework.exceptions import ValidationError
# 反序列化的多表关联保存
class BookSerializer(serializers.Serializer):
name = serializers.CharField()
price = serializers.CharField()
# 只用来序列化出来给前端展示 read_only = True
publish_detail = serializers.DictField(read_only=True)
author_list = serializers.ListField(read_only=True)
# 只用来反序列化 存 write_only 如果不写的话就会展示给前端
publish = serializers.CharField(write_only=True) # 前端展示的是选择出版社传对应的pk值过来
authors = serializers.ListField(write_only=True)
# 重写create
def create(self, validated_data):
book = Book.objects.create(name=validated_data.get('name'), price=validated_data.get('price'),
publish_id=validated_data.get('publish'))
# 传过来是个列表 打散成位置参数
book.authors.add(*validated_data.get('authors'))
return book
# 重写update方法
def update(self, instance, validated_data):
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish_id = validated_data.get('publish')
# 获取作者列表
author_list = validated_data.get('authors')
# 先清除第三张关系表
instance.authors.clear()
# 在建立关系
instance.authors.add(*author_list)
instance.save()
return instance
def validate_name(self,name):
if name.startswith('赌'):
raise ValidationError('拒绝赌')
else:
return name
def validate(self, attrs):
if attrs.get('name') == attrs.get('publish'):
raise ValidationError('出版社名字不能与书名一致')
else:
return attrs
ModelSerializer序列化类使用
ModelSerializer继承了Serialzer 又帮我们完成了一些操作,不用再去重写create与update方法。
但是他必须与模型表进行强关联。无法在通过instance参数进行关联。
继承serializers.ModelSerializer类编写class Meta:
Meta类中的参数
参数 | 功能 |
---|---|
model= | 关联表 |
fields= | 双下 all 序列化所有字段,也可以[‘字段1’,‘字段2’]指定序列化字段 |
extra_kwargs= | 给字段类加属性{‘字段’:{属性}} |
代码:
# ModelsSerializer序列化类编写
class BookSerializer(serializers.ModelSerializer):
# 关联表
class Meta:
model = Book # 关联
fields = '__all__' # 序列化Book所有字段
# fields = ['name','price'] #指定序列化Book表的字段
extra_kwargs = {'name':{'max_length':8}}
我们也可以在Meta类的外面重写字段进行校验
class BookSerializer(serializers.ModelSerializer):
# 关联表
class Meta:
model = Book # 关联
# fields = '__all__' # 序列化Book所有字段
fields = ['name','price','publish_detail','author_list','publish','authors'] #指定序列化Book表的字段
extra_kwargs = {'name':{'max_length':8}}
name = serializers.CharField(max_length=8) # 优先使用这个校验
price = serializers.CharField()
def get_publish_detail(self, obj):
return {'name': obj.publish.name, 'addr': obj.publish.addr}
def get_author_list(self, obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
"这样我们就可以在BookSerializer类里重写序列化字段,但是这因为我们在模型类里已经写好了字段,这样代码太繁琐"
终极方法
class BookSerializer(serializers.ModelSerializer):
# 关联表
class Meta:
model = Book # 关联
# fields = '__all__' # 序列化Book所有字段
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors'] # 指定序列化Book表的字段
"通过给模型表内的字段传只序列化或只反序列化来控制给前端返回字段"
extra_kwargs = {'publish': {'write_only': True},
'authors': {'write_only': True},
'publish_detail': {'read_only': True},
'author_list': {'read_only': True}}
# 钩子一样写
def validate_name(self,name):
if name.startswith('赌'):
raise ValidationError('拒绝赌')
else:
return name
def validate(self, attrs):
if attrs.get('name') == attrs.get('publish'):
raise ValidationError('出版社名字不能与书名一致')
else:
return attrs
反序列化注意事项
例子: 基于book表
我们在接收前端数据时,接收到了不属于book表内的字段,而是和book表关联的字段时,需要在 fields = []中注册。并在序列化类里重写 被关联的字段后接收到数据才能存入被关联表。
特殊情况
第三张关联表的字段被放在了一个字典里,这个字典有一个k ,那么我们就需要按照这个k的名字重写一个字段接收数据后 在重写create方法
反序列化类校验部分功能源码
"我们是从BookSerializer中找过去的父类中的self都是BookSerializer对象"
反序列化校验的时候是通过 视图类中的 ser.is_valid()校验,通过返回True,否则False,我们找到is_valid()
在序列化类继承的ModelSerializer的父类BaseSerializer中找到该方法:
# raise_exception参数通过if is_valid()给他传True就会校验没通过自动抛异常,不需要在加if判断了
def is_valid(self, *, raise_exception=False):
# 判断self序列化类的对象,属性中有没有_validated_data
if not hasattr(self, '_validated_data'):
try:
# 走完了就有了_validated_data方法,这句话是核心
self._validated_data = self.run_validation(self.initial_data)
except ValidationError as exc:
self._validated_data = {}
self._errors = exc.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
2.通过is_valid 源码研究发现self._validated_data = self.run_validation(self.initial_data)核心,我们去查看run_validation源码
注意: 不能按Ctrl点(会到Filed里的) 要从序列化类 开始从下往上找
查找发现 在父类Serializer 里含有run_validation方法
def run_validation(self, data=empty):
# 字段自己的校验方法
(is_empty_value, data) = self.validate_empty_values(data)
if is_empty_value:
return data
# 局部钩子 研究self.to_internal_value(data)源码
value = self.to_internal_value(data)
try:
self.run_validators(value)
# 全局钩子,研究发现 self.validate(value)什么都没做 返回的是attr,那么我们在按照模型类里填写的validate(全局钩子)执行校验
value = self.validate(value)
except (ValidationError, DjangoValidationError) as exc:
raise ValidationError(detail=as_serializer_error(exc))
return value
3.经过研究run_validation源码发现了内部含有校验的方法,全局钩子很简单什么都没做直接反回了attr,就是按照我们编写全局钩子的来校验,局部钩子to_internal_value(data)方法需要研究一下
def to_internal_value(self, data):
ret = OrderedDict()
errors = OrderedDict()
fields = self._writable_fields
# fields是写在序列化类中注册的一个个字段对象,其实我们在表中写的字段后面eg:name=CharField()其实产生一个字段对象。
for field in fields:
# self 序列化类的对象,拿出来的对象内部有field_name属性,对应的是字段的名称. 拿到validate_name
validate_method = getattr(self, 'validate_' + field.field_name, None)
try:
# 执行BookSerializer类中的validate_name方法(局部钩子),传入了校验的数据。
"""
此时通过上面的步骤现在:
validate_method = validate_name
eg: 局部钩子
def validate_name(self,name):
pass
在这里被执行了validate_method(validated_value),"""
validated_value = validate_method(validated_value)
except ValidationError as exc:
errors[field.field_name] = exc.detail
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
"注意,查看源码的时候无论从哪里查看都应该从 最初的类从下往上找方法"
断言
关键字assert
name = aa
assert name=xx # 断定 name = xx 如果不等于则抛异常如果是True 继续往下走代码