序列化类的使用
使用序列化类实现五个接口功能,但是我们发现并没有做数据校验,也没有做反序列化,是我们自己手动去进反序列化,是我们自己使用for来进行拼接的,很不方便,我们可以使用一个drf自带的名叫序列化器,来完成
路由
urlpatterns = [path('admin/', admin.site.urls),path('publish/',views.PublishView.as_view()),
]
视图函数
class PublishView(APIView):def get(self, request):publish_list = Publish.objects.all()l = []for i in publish_list:l.append({'name': i.name, 'addr': i.addr, })return Response({'code': 100, 'msg': 'ok', 'results': l})def post(self, request):publish_list = Publish.objects.create(name=request.data.get('name'), addr=request.data.get('addr'))return Response({'code': 100, 'msg': 'ok', 'results': {'name': publish_list.name, 'price': publish_list.addr}})class PublishDetail(APIView):def get(self, request, pk):book_list = Publish.objects.filter(id=pk).first()l = []for i in book_list:l.append({'name': i.name, 'price': i.price, })return Response({'code': 100, 'msg': 'ok', 'results': l})def delete(self, request, pk):book_list = Publish.objects.filter(id=pk).delete()return Response({'code': 100, 'msg': 'ok', 'results': book_list})def put(self, request):publish_list = Publish.objects.update(**request.data)return Response({'code': 100, 'msg': 'ok', 'results': {'name': publish_list.name, 'price': publish_list.addr}})
序列化类-Serializer
Django REST framework中的Serializer使用类来定义,须继承自rest_framework.serializers.Serializer。
from rest_framework import serializersfrom app01.models import Publishclass PublishSerializer(serializers.ModelSerializer):name=serializers.CharField()addr=serializers.CharField()class Meta:model=Publishfields=['name','addr'] # 或指定所有__all__
- 建立一个名为Serializer的py文件·
- 写一个类,继承为Serializer.Serializer
- 在类中写需要序列化的字段
- 在视图类中使用,完成序列化
使用序列化类
导入我们自己的序列化器
from app01.Serializer import PublishSerializer# 使用序列化类做序列化
class PublishView(APIView):def get(self, request):publish_list = Publish.objects.all()ser=PublishSerializer(instance=publish_list,many=True)return Response({'code': 100, 'msg': 'ok', 'results': ser.data})class PublishDetail(APIView):def get(self, request, pk):publish_list = Publish.objects.filter(id=pk).first()ser=PublishSerializer(instance=publish_list)return Response({'code': 100, 'msg': 'ok', 'results': ser.data}))
- 如果序列化多个则需要加上many=Ture,
使用序列化类进行校验
三层校验:
- 字段的校验
- 局部钩子:校验失败,主动使用ValidationError抛出错误
- 全局钩子
from rest_framework import serializers
from rest_framework.exceptions import ValidationErrorfrom app01.models import Publish, Booksclass PublishSerializer(serializers.ModelSerializer):# 第一层:写需要序列化的字段名name = serializers.CharField()addr = serializers.CharField()
# 第二层 局部钩子def validate_name(self, name):if name.startswith('sb'):raise ValidationError('不能以sb开头')else:return namedef validate_addr(self, addr):if 'sb'in addr:raise ValidationError("不能以sb开头")else:return addr# 第三层全局钩子def validate(self, attrs):# attrs是校验过后的数据print(attrs)if attrs.get('name')[:3]==attrs.get('addr')[:3]:raise ValidationError("名称不能重复")return attrsclass Meta:model = Publishfields = ['name', 'addr'] # 或指定所有__all__
全局钩子:def validate(self, attrs):
补充:attrs是已经校验过后的数据,如果前端传入了数据字段,但是我的类中并没有这个字段,那么最终校验过后的数据attrs中是没有前端传入的数据的,因为那条数据已经被删除了
put和post保存到数据库
class BookSerializer(serializers.ModelSerializer):name = serializers.CharField(max_length=18, min_length=3)price = serializers.IntegerField(max_value=999, min_value=10)pub_date = serializers.CharField(max_length=32)def validate_name(self, name):l = ['金瓶梅', 'sb', '傻逼']for i in l:if i in name:raise ValidationError('不能使用敏感字')return namedef validate_price(self, price):if price < 10:raise ValidationError('价格不能小于10')elif price > 999:raise ValidationError('价格不能大于999')else:return pricedef validate(self, attrs):# attrs是校验过后的数据print(attrs)if attrs.get('pub_date')[:3] == attrs.get('name')[:3]:raise ValidationError("名称不能重复")return attrsdef create(self, validated_data):# validated_data:校验过的数据,之前传入没有校验的数据,在这没有publish = Publish.objects.create(**validated_data)return publish # 一定要返回publish对象,后续会拿着这个对象进行序列化,ser.def update(self, instance, validated_data):# 本方法# instance.name = validated_data.get('name')# instance.price = validated_data.get('price')# instance.pub_date = validated_data.get('pub_date')# instance.save() # book的对象save的对象保存到数据库中# 高级写法for item in validated_data:# setattr(books,'name','上海出版社')setattr(instance, item, validated_data[item])instance.save()return instanceclass Meta:model = Booksfields = ['name', 'price', 'pub_date']
路由
class BookView(APIView):def get(self, request):book_list = Books.objects.all()ser = BookSerializer(instance=book_list, many=True)return Response({'code': 100, 'msg': 'ok', 'results': ser.data})def post(self, request):ser = BookSerializer(data=request.data)if ser.is_valid():ser.save() # 使用序列化类进行保存,但是会报错,因为没有指定到哪一个表,所以需要重新写createprint(ser.data)return Response({'code': 100, 'msg': 'ok', 'results': ser.data})else:print(ser.errors)return Response({'code': 100, 'msg': ser.errors})def delete(self, request, pk):book_list = Books.objects.filter(id=pk).delete()ser = BookSerializer(instance=book_list)return Response({'code': 100, 'msg': 'ok', 'results': ser.data})def put(self, request):book_list = Books.objects.update(**request.data)ser = BookSerializer(instance=book_list, data=request.data)if ser.is_valid():ser.save()print(ser.data)return Response({'code': 100, 'msg': '修改成功', 'results': ser.data})else:print(ser.errors)return Response({'code': 100, 'msg': ser.errors})
- instance:需要序列化的数据,如果是queryset对象,即便是一个数,也需要使用many=Ture
- serializers.data 转为字典格式,return Response(serializers.data)这样就实现序列化了
- 视图中使用新增与修改:
"""新增"""
serializers=Bookserializers(data=reuqest.data)
新增后执行视图函数中的is_valid:就是执行三层校验
成功后保存ser.save()
"""修改"""
ser = BookSerializer(instance=book_list, data=request.data)
is_valid()
ser.save()def update(self, instance, validated_data):Book.objects.filer(pk=instanc).update(*validated_data)return instance修改第二种方式def update(self, instance, validated_data):Book.objects.filer(pk=instanc).update(**validated_data)return instance
常用的字段名
小技巧:如果跟表模型中对不上,可以直接使用CharField。
字段类型:
字段 | 字段构造方式 |
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) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(max_length=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) |
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的用法相同 |
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=) |
选项参数:
参数名称 | 作用 |
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
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页面时,显示的字段帮助提示信息 |
字段校验有四层:
- 字段自己
- validdators校验
- 局部钩子
- 全局钩子
序列化高级之soruce
soruce:用来修改返回字段的
1 修改字段,映射字段 publish_name表中不存在
publish_name = serializers.CharField(source='name')
2 修改字段,映射方法
sb_name是表模型中一个方法
name = serializers.CharField(source='sb_name')
3 修改字段,跨表查询
book表中可以链表查询
publish=models.ForeignKey(to='Publish.name')
序列化高级之返回字段
定制序列化返回字段格式
- 在表模型中写,在序列化中做映射,可以使用soruce
建表
from django.db import modelsclass 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 __str__(self):return self.nameclass Author(models.Model):name = models.CharField(max_length=32)age = models.IntegerField()author_detail = models.OneToOneField(to='AuthorDetail', on_delete=models.CASCADE)def __str__(self):return self.nameclass AuthorDetail(models.Model):telephone = models.BigIntegerField()birthday = models.DateField()addr = models.CharField(max_length=64)class Publish(models.Model):name = models.CharField(max_length=32)city = models.CharField(max_length=32)email = models.EmailField()def __str__(self):return self.nameclass Meta:verbose_name = '出版社'verbose_name_plural = verbose_name
models
class Book(models.Model):name = models.CharField(max_length=32)price = models.DecimalField(max_digits=5, decimal_places=2)publish_name = models.ForeignKey(to='Publish', on_delete=models.CASCADE)author = models.ManyToManyField(to='Author')def __str__(self):return self.name# def book_name(self):# return self.name+'sb'# def publish_detail(self):# return {'name': self.name, 'price': self.price, 'publish_name': self.publish_name.addr}## def author_list(self):# l=[]# for i in self.author.all():# l.append({'name':i.name, 'age':i.age})## return l# def publish_detail(self):# return {'name': self.name, 'price': self.price, 'publish_name': self.publish_name.addr}### def author_list(self):# l=[]# for i in self.author.all():# l.append({'name':i.name, 'age':i.age})# return l#class Meta:verbose_name_plural='图书表'
view
class BookView(APIView):def get(self, request):obj = Book.objects.all()ser = BookSerializer(instance=obj, many=True)'''[{"name": "西游记","price": "66.00","publish_detail": {name:名字,city:城市},"authors_list":[{name:名字,age:19}]},]'''return Response(ser.data)
序列化类
### 定制返回字段
class BookSerializer(serializers.Serializer):name = serializers.CharField()price = serializers.CharField()#### 定制返回字段---》方案一:在表模型中写方法,在序列化类中做映射# publish_detail = serializers.CharField() # publish_detail 会映射表模型中 publish_detail方法,方法返回值是 字典,强行用CharField字符串接收# publish_detail = serializers.DictField() # publish_detail 会映射表模型中 publish_detail方法,方法返回值是 字典,用DictField接收# author_list = serializers.ListField()###定制返回字段---》方案二:在序列化类中写 SerializerMethodField# 只要写了这个字段类SerializerMethodField,必须配合一个方法:get_字段名,这个方法返回什么,前端这个字段就显示什么publish_detail = serializers.SerializerMethodField()def get_publish_detail(self, 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, 'age': author.age})return lbook_name = serializers.SerializerMethodField()def get_book_name(self, obj):return obj.name + 'sb'
多表关联反序列化
# 反序列化保存
# 使用同一个序列化类会出现-序列化字段和反序列化字段不一致-序列化字段namepricepublish_detailauthor_list-反序列化字段:namepricepublishauthor-如果是共同的,不需要额外处理-如果是不同的,需要通过字段参数控制read_only 表明该字段仅用于序列化输出,默认False,序列化过程write_only 表明该字段仅用于反序列化输入,默认False,反序列化过程
serializer
class BookSerializer(serializers.Serializer):name = serializers.CharField(max_length=18, min_length=3, required=True) # 公共的price = serializers.IntegerField() # 公共的publish = serializers.IntegerField(write_only=True)# 反序列化author = serializers.ListField(write_only=True)# 反序列化publish_detail=serializers.SerializerMethodField(read_only=True) #序列化author_list = serializers.SerializerMethodField(read_only=True) #序列化def validate_name(self, name):if name.startswith('sb'):raise ValidationError("不能使用敏感字开头")return namedef validate_price(self, price):if price <= 0:raise ValidationError("不能等于0")return pricedef validate(self, attrs):# attrs是校验过后的数据if attrs.get('name')[:3] == attrs.get('addr')[:3]:raise ValidationError("名称不能重复")return attrsdef get_publish_detail(self, obj):# obj就是当前序列化到的return {'name': obj.publish_name.name ,'addr': obj.publish_name.addr}# 返回的' name':obj.publish.name, 'price':obj.publish.addr就相当于给了这个publish_detaildef get_author_list(self,obj):l=[]for i in obj.author.all():l.append({'name':i.name, 'age':i.age})return ldef create(self, validated_data):author=validated_data.pop('author')book = Book.objects.create(**validated_data)# book=Book.objects.create(name=validated_data['name'], price=validated_data['price'],publish_name=validated_data['publish_name'])book.author.add(*author)return book
ModelSerializer
- ModelSerializer跟表是一一对应的,r,需要重写create和update
- 之前写的Serializer.serializer跟表没有必然的联系
- 继承ModleSerializer可以少写很多代码
### 继承ModelSerializer--->少写代码
class BookSerializer(serializers.ModelSerializer):# name = serializers.CharField(max_length=18,min_length=3) # 公共的# price = serializers.CharField() # 公共的# publish = serializers.IntegerField(write_only=True) # 只用来做反序列化# authors = serializers.ListField(write_only=True) # 只用来做反序列化# 上述操作,通过 Meta实现了# 扩写的字段,也要在fields注册# 方式二:定制字段# publish_detail = serializers.SerializerMethodField(read_only=True) # 只用来做序列化# author_list = serializers.SerializerMethodField(read_only=True) # 只用来做序列化# def get_publish_detail(self, obj):# 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, 'age': author.age})# return l# 方式二:定制字段方式一# publish_detail = serializers.DictField(read_only=True)# author_list = serializers.ListField(read_only=True)class Meta:model = Book # 写了这两句,会把表模型中Book,所有字段映射过来# fields='__all__'fields = ['name', 'price', 'publish', 'authors', 'publish_detail', 'author_list']extra_kwargs = { # 给某个或某几个字段设置字段属性'name': {'max_length': 18, 'min_length': 3},'publish': {'write_only': True},'authors': {'write_only': True},'publish_detail': {'read_only': True},'author_list': {'read_only': True},}# 一般不需要写create和update了---》ModelSerializer帮咱们实现了# 局部钩子和全局钩子,该怎么写还怎么写
反序列化的源码分析
#1 执行 ser.is_valid() 就会执行 反序列化的校验---》字段自己--》局部钩子---》全局钩子
#2 入口是:ser.is_valid()---》BaseSerializer 找到了1 自己写的BookSerializer---》serializer.Serializer---->BaseSerializer 2 源码如下def is_valid(self, *, raise_exception=False):# self 是 ser对象---》自己写的BookSerializer的对象--》一开始没有# 一旦有了,就不执行了,优化is_valid被多次调用,只会走一次校验if not hasattr(self, '_validated_data'):try:# 一旦执行过,以后self中就有_validated_data# 接下来看self.run_validation(self.initial_data)self._validated_data = self.run_validation(self.initial_data)except ValidationError as exc:self._validated_data = {}self._errors = exc.detailelse:self._errors = {}if self._errors and raise_exception:raise ValidationError(self.errors)return not bool(self._errors)3 self.run_validation(self.initial_data)---》serializer.Serializer类的,不要按住ctrl点击,否则会进 Field 类,看错了4 serializer.Serializer类的run_validationdef run_validation(self, data=empty):# data前端传入的--{"name":"张三","age":68}# value是---》前端传入的,字段自己校验通过的字典---{"name":"张三","age":68}value = self.to_internal_value(data) # 执行局部钩子try:self.run_validators(value) # 先不看,忽略掉# self 是 BookSerializer的对象,如果我们写了全局钩子,走我们自己的,如果没写,走父类的,父类的根本没做校验# value={"name":"张三","age":68}value = self.validate(value)# 执行全局钩子except (ValidationError, DjangoValidationError) as exc:raise ValidationError(detail=as_serializer_error(exc))return value5 全局钩子读完了:self 是 BookSerializer的对象,如果我们写了全局钩子,走我们自己的,如果没写,走父类的,父类的根本没做校验6 局部钩子:value = self.to_internal_value(data)--》Serializer类的# for循环着去BookSerializer的对象中反射 validate_字段名的方法,如果有就执行,没有就不执行def to_internal_value(self, data):for field in fields: # 序列化类中所有字段类的对象 name=CharField()# self 是BookSerializer类的对象# 去BookSerializer类中,反射 validate_field字段类的对象.field_namevalidate_method = getattr(self, 'validate_' + field.field_name, None)try:# 如果能拿到,说明咱么写了局部钩子if validate_method is not None:# 执行局部钩子--》传入了当前字段的value值validated_value = validate_method(validated_value)except ValidationError as exc:# 如果抛异常,会被捕获errors[field.field_name] = exc.detailexcept DjangoValidationError as exc:errors[field.field_name] = get_error_detail(exc)except SkipField:passelse:set_value(ret, field.source_attrs, validated_value)if errors:raise ValidationError(errors)return ret# #####读了局部和全局钩子的执行位置#####
# 保存,修改也好,都要用validated_data,它是最准确的
执行is_valid()就会执反序列化的校验
- 字段自己的校验
- 局部钩子
- 全局钩子
- 查看源码从is_valid()中去查看,建议不使用ctrl点击查看,去ser,is_valid()的对象就是Booksserializer中查看-----没有找到,去父类的serializer中去寻找,没有继续去Serializer中找is_valiid(),没有继续去BaseSerializer中找,如果直接从ctrl中点击的话,可能直接跳转到不是BaseSerializer中了。
- 源码如下
2 源码如下def is_valid(self, *, raise_exception=False):#判断self中有没有_validated_dataif not hasattr(self, '_validated_data'):try:self._validated_data = self.run_validation(self.initial_data)except ValidationError as exc:self._validated_data = {}self._errors = exc.detailelse:self._errors = {}if self._errors and raise_exception:raise ValidationError(self.errors)return not bool(self._errors)
逐步解析:
- if not hasattr(self, '_validated_data'):----判断self中有没有_validated_data
- 其中的self就是ser对象,ser对象其实就是自己写的BookSerializer类的对象,因为从BookSerializer进入的
- 一开始是没有_validated_data,(如果有就不会执行下面的hasattr,如果执行过is_valid(),那么之后的is_valid就不会执行了,多次调用也只会执行一次)所以执行下面的反射hasattr
- self._validated_data = self.run_validation(self.initial_data)------去Serializer中去找run_validation
不要直接ctrl直接进去找,不然找到了fild里就不对了
def run_validation(self, data=empty):# 局部钩子value = self.to_internal_value(data)try:self.run_validators(value) value = self.validate(value) # 全局钩子# 不符合直接捕获错误except (ValidationError, DjangoValidationError) as exc:raise ValidationError(detail=as_serializer_error(exc))return value
在run_validation中
全局钩子: 不符合直接捕获错误 value = self.validate(value) 全局钩子:self是BookSerializer的对象,如果自己写了钩子,那么就执行我们的,没写走父类,父类中直接返回了,并没有做校验
局部钩子
value = self.to_internal_value(data)-----是Serializer的
for循环去BookSerializer的对象去反射,validata_字段名的方法,如果有就执行,
- value就前端传入的数据
def to_internal_value(self, data):for field in fields:validate_method = getattr(self, 'validate_' + field.field_name, None)#去BookSerializer中反射validate_加上字段名 : validate_field字段类的对象.field_nameprimitive_value = field.get_value(data)try:# 如果能拿到,说明写了局部钩子,否则抛出异常validated_value = field.run_validation(primitive_value)if validate_method is not None:validated_value = validate_method(validated_value)except ValidationError as exc:#如果抛异常,会被捕获到,放入下面errors中errors[field.field_name] = exc.detailexcept DjangoValidationError as exc:errors[field.field_name] = get_error_detail(exc)except SkipField:passelse:set_value(ret, field.source_attrs, validated_value)if errors:raise ValidationError(errors)# 最终返回return ret
for field in fields: 序列化类中所有字段的对象:
validate_method = getattr(self, 'validate_' + field.field_name, None)
去BookSerializer中反射validate_加上字段名 : validate_field字段类的对象.field_name
断言
assert hasattr(self, 'initial_data'), ('Cannot call `.is_valid()` as no `data=` keyword argument was ''passed when instantiating the serializer instance.')
判断self里有没有'initial_data'没有就报错
a = 10## assert 后写条件,只要不符合条件,就会抛AssertionError异常,后面写异常信息
assert a == 11, ("不等于11,报错了")# 等同于---》上面只要一行代码,源码中喜欢用
if not a == 11:raise Exception('不等于11,报错了')# 源码中使用
assert value is not None, '.validate() should return the validated data'