使用序列化类实现五个接口功能,但是我们发现并没有做数据校验,也没有做反序列化,是我们自己手动去进反序列化,是我们自己使用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}})
Django REST framework中的Serializer使用类来定义,须继承自rest_framework.serializers.Serializer。
from rest_framework import serializers
from app01.models import Publish
class PublishSerializer(serializers.ModelSerializer):
name=serializers.CharField()
addr=serializers.CharField()
class Meta:
model=Publish
fields=['name','addr'] # 或指定所有__all__
导入我们自己的序列化器
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}))
三层校验:
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from app01.models import Publish, Books
class PublishSerializer(serializers.ModelSerializer):
# 第一层:写需要序列化的字段名
name = serializers.CharField()
addr = serializers.CharField()
# 第二层 局部钩子
def validate_name(self, name):
if name.startswith('sb'):
raise ValidationError('不能以sb开头')
else:
return name
def 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 attrs
class Meta:
model = Publish
fields = ['name', 'addr'] # 或指定所有__all__
全局钩子:def validate(self, attrs):
补充:attrs是已经校验过后的数据,如果前端传入了数据字段,但是我的类中并没有这个字段,那么最终校验过后的数据attrs中是没有前端传入的数据的,因为那条数据已经被删除了
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 name
def validate_price(self, price):
if price < 10:
raise ValidationError('价格不能小于10')
elif price > 999:
raise ValidationError('价格不能大于999')
else:
return price
def validate(self, attrs):
# attrs是校验过后的数据
print(attrs)
if attrs.get('pub_date')[:3] == attrs.get('name')[:3]:
raise ValidationError("名称不能重复")
return attrs
def 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 instance
class Meta:
model = Books
fields = ['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() # 使用序列化类进行保存,但是会报错,因为没有指定到哪一个表,所以需要重新写create
print(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})
"""新增"""
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页面时,显示的字段帮助提示信息 |
字段校验有四层:
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')
定制序列化返回字段格式
建表
from django.db import models
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 __str__(self):
return self.name
class 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.name
class 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.name
class 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 l
book_name = serializers.SerializerMethodField()
def get_book_name(self, obj):
return obj.name + 'sb'
# 反序列化保存
# 使用同一个序列化类会出现
-序列化字段和反序列化字段不一致
-序列化字段
name
price
publish_detail
author_list
-反序列化字段:
name
price
publish
author
-如果是共同的,不需要额外处理
-如果是不同的,需要通过字段参数控制
read_only 表明该字段仅用于序列化输出,默认False,序列化过程
write_only 表明该字段仅用于反序列化输入,默认False,反序列化过程
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 name
def validate_price(self, price):
if price <= 0:
raise ValidationError("不能等于0")
return price
def validate(self, attrs):
# attrs是校验过后的数据
if attrs.get('name')[:3] == attrs.get('addr')[:3]:
raise ValidationError("名称不能重复")
return attrs
def 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_detail
def get_author_list(self,obj):
l=[]
for i in obj.author.all():
l.append({'name':i.name, 'age':i.age})
return l
def 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--->少写代码
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.detail
else:
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_validation
def 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 value
5 全局钩子读完了: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_name
validate_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.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
set_value(ret, field.source_attrs, validated_value)
if errors:
raise ValidationError(errors)
return ret
# #####读了局部和全局钩子的执行位置#####
# 保存,修改也好,都要用validated_data,它是最准确的
执行is_valid()就会执反序列化的校验
2 源码如下
def is_valid(self, *, raise_exception=False):
#判断self中有没有_validated_data
if 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.detail
else:
self._errors = {}
if self._errors and raise_exception:
raise ValidationError(self.errors)
return not bool(self._errors)
逐步解析:
不要直接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_字段名的方法,如果有就执行,
def to_internal_value(self, data):
for field in fields:
validate_method = getattr(self, 'validate_' + field.field_name, None)
#去BookSerializer中反射validate_加上字段名 : validate_field字段类的对象.field_name
primitive_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.detail
except DjangoValidationError as exc:
errors[field.field_name] = get_error_detail(exc)
except SkipField:
pass
else:
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'