1-DRF - 序列化器
before
有两件无关的事需要在这里重复一下,防止有些铁子不知道这个知识点。
1. 错误提示如何改为中文
Django默认的语言是英语,所以后面你会发现一些提示也是英语,那么怎么搞成中文的呢?修改settings.py:
# LANGUAGE_CODE = 'en-us'
LANGUAGE_CODE = 'zh-hans'
2. request.body/request.data/request.POST的区别
首先,类视图只有继承了restful的APIView,才可以用request.data。
由于后续会用到postman提交数据,那么不同的提交方式,后台接收的套路也不一样,所以这里说一下。
get请求,那么后端的取值方式也不同,注意观察:
# 如果客户端是get请求 http://127.0.0.1:8000/api/test/?k1=v1&k2=v2
# Content-Type:text/plain
# 那么后端应该用request.GET来处理请求参数
from rest_framework.response import Response
class TestView(APIView):
def get(self, request, *args, **kwargs):
print('post request.headers', request.headers['Content-Type'])
print('post request.body', request.body)
print('post request.data', request.data)
print('post request.data', request.data.get("k1"))
print('post request.POST', request.POST)
print('post request.POST', request.POST.get("k1"))
print('post request.GET', request.GET)
print('post request.GET', request.GET.get("k1"))
"""
post request.headers text/plain
post request.body b''
post request.data {}
post request.data None
post request.POST <QueryDict: {}>
post request.POST None
post request.GET <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
post request.GET v1
"""
return Response({"code": 0})
post请求,请求体内携带参数,那么后端的取值方式也不同,注意观察:
# http://127.0.0.1:8000/api/test/
# Content-Type:multipart/form-data
from rest_framework.response import Response
class TestView(APIView):
def post(self, request, *args, **kwargs):
print('post request.headers', request.headers['Content-Type'])
print('post request.body', request.body)
print('post request.data', request.data)
print('post request.data', request.data.get("k1"))
print('post request.POST', request.POST)
print('post request.POST', request.POST.get("k1"))
print('post request.GET', request.GET)
print('post request.GET', request.GET.get("k1"))
"""
post request.headers multipart/form-data; boundary=--------------------------542167205111294546050153
post request.body b'----------------------------542167205111294546050153\r\nContent-Disposition: form-data; name="k1"\r\n\r\nv1\r\n----------------------------542167205111294546050153\r\nContent-Disposition: form-data; name="k2"\r\n\r\nv2\r\n----------------------------542167205111294546050153--\r\n'
post request.data <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
post request.data v1
post request.POST <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
post request.POST v1
post request.GET <QueryDict: {}>
post request.GET None
"""
return Response({"code": 0})
post请求,请求体内携带参数,那么后端的取值方式也不同,注意观察:
# http://127.0.0.1:8000/api/test/
# Content-Type:application/x-www-form-urlencoded
from rest_framework.response import Response
class TestView(APIView):
def post(self, request, *args, **kwargs):
print('post request.headers', request.headers['Content-Type'])
print('post request.body', request.body)
print('post request.data', request.data)
print('post request.data', request.data.get("k1"))
print('post request.POST', request.POST)
print('post request.POST', request.POST.get("k1"))
print('post request.GET', request.GET)
print('post request.GET', request.GET.get("k1"))
"""
post request.headers application/x-www-form-urlencoded
post request.body b'k1=v1&k2=v2'
post request.data <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
post request.data v1
post request.POST <QueryDict: {'k1': ['v1'], 'k2': ['v2']}>
post request.POST v1
post request.GET <QueryDict: {}>
post request.GET None
"""
return Response({"code": 0})
post请求,请求体内携带参数,那么后端的取值方式也不同,注意观察:
# http://127.0.0.1:8000/api/test/
# Content-Type:application/json
from rest_framework.response import Response
class TestView(APIView):
def post(self, request, *args, **kwargs):
print('post request.headers', request.headers['Content-Type'])
print('post request.body', request.body)
print('post request.data', request.data)
print('post request.data', request.data.get("k1"))
print('post request.POST', request.POST)
print('post request.POST', request.POST.get("k1"))
print('post request.GET', request.GET)
print('post request.GET', request.GET.get("k1"))
"""
post request.headers application/json
post request.body b'{"k1": "v1", "k2": "v2"}'
post request.data {'k1': 'v1', 'k2': 'v2'}
post request.data v1
post request.POST <QueryDict: {}>
post request.POST None
post request.GET <QueryDict: {}>
post request.GET None
"""
return Response({"code": 0})
所以,根据客户端不同的请求方式和不同的Content-Type
类型,我们取值的方式也要跟着改变。
about
drf中为我们提供了Serializer组件,它主要有两大功能:
- 对请求数据进行校验,学习起来你会感觉跟之前学Django的form和modelform差不多,没错,它底层的确调用了Django的form和modelform。
- 对数据库查询到的对象进行序列化。也即是将我们查询到queryset对象给序列化为json格式的数据,返回给客户端。
先来看基础的序列化器Serializer。
serializers.Serializer
这一小节学起来,跟学习Django的forms组件差不多。
Serializer是基础的序列化器。
序列化器
本小节主要演示,基本的序列化器如何定义,以及如何在视图类中使用。
models.py
:
from django.db import models
class Student(models.Model):
# 模型字段
name = models.CharField(max_length=100, verbose_name="姓名", help_text='提示文本:不能为空')
sex = models.BooleanField(default=1, verbose_name="性别")
age = models.IntegerField(verbose_name="年龄")
class_null = models.CharField(max_length=5, verbose_name="班级编号")
description = models.TextField(max_length=1000, verbose_name="个性签名")
class Meta:
db_table = "tb_student"
verbose_name = "学生"
verbose_name_plural = verbose_name
views.py
:
from django.http import JsonResponse
from rest_framework import serializers
from django.views import View
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
# 需要进行数据转换的字段
# 这里写几个字段,序列化器在返回每个记录时,就返回几个字段
id = serializers.IntegerField()
name = serializers.CharField()
age = serializers.IntegerField()
sex = serializers.BooleanField()
description = serializers.CharField()
class StudentView(View):
""" 使用序列化器转换单个模型类数据 """
def get(self, request):
# 序列化单条数据
# student_obj = models.Student.objects.filter(pk=1).first()
# serializer_obj = StudentSerializer(instance=student_obj)
# 序列化多条数据, 要指定many=True
student_list = models.Student.objects.all()
serializer_obj = StudentSerializer(instance=student_list, many=True)
# 序列化后的数据
print(serializer_obj.data)
"""
# 序列化后的单条数据
{'id': 1, 'name': 'abc', 'age': 18, 'sex': True, 'description': '666'}
# 序列化后的多条数据
[
OrderedDict([('id', 1), ('name', 'abc'), ('age', 18), ('sex', True), ('description', '666')]),
OrderedDict([('id', 2), ('name', '张开'), ('age', 18), ('sex', True), ('description', '666')])
]
"""
# 因为序列化后的数据是列表,需要加上safe参数
# ensure_ascii参数让中文正常在页面中显示
return JsonResponse(serializer_obj.data, safe=False, json_dumps_params={"ensure_ascii": False})
urls.py
:
# 总路由
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('stu/', include("student.urls")),
path('ser/', include("ser.urls")),
]
# student/urls.py
from django.urls import path, re_path
from student import views
urlpatterns = [
path("student/", views.StudentView.as_view())
]
现在浏览器中可以访问http://127.0.0.1:8000/stu/student/
进行测试了。
序列化器类的常用字段和参数
上小节中,序列化器类中我们根据模型类定义了若干字段。
from rest_framework import serializers
from django.views import View
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
# 需要进行数据转换的字段
# 这里写几个字段,序列化器在返回每个记录时,就返回几个字段
id = serializers.IntegerField()
name = serializers.CharField()
age = serializers.IntegerField()
sex = serializers.BooleanField()
description = serializers.CharField()
下表则介绍了drf中,序列化器中常用的字段:
字段 | 字段构造方式 |
---|---|
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(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) |
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_length | 最小长度 |
allow_blank | 允许客户端提交过来的数据为空字符串 |
trim_whitespace | 是否截断空白字符,也就是去除数据两边的空格 |
max_value | 用于数值类型的字段中设置最大值 |
min_value | 用于数值类型的字段中设置最小值 |
通用参数
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 客户端提交数据时,该字段可以不传,或者传个null,后端解析成None,默认为False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
这些字段和参数的搭配用法我们接下来会在示例中介绍。
关于required、allow_blank、allow_null的进一步说明
如果设置了required=False
,客户端可以不传该字段,但是如果客户端为这个字段传个空字符串,不行!如果传个null
也不行!!想要支持传个空字符串请设置allow_blank=True
;如果想要支持传个null
也支持的话,就设置allow_null=True
,所以这三个属性通常搭配使用,要结合数据库约束、然后跟客户端开发人员也约定好,怎么传。
反序列化数据校验
序列化器校验和数据库约束校验应该相辅相成
首先,反序列化主要就是对客户端提交过来的数据进行反序列化,同时根据序列化器中指定的校验规则,对提交过来的数据进行校验。校验通过,数据将保存到数据库,当然此时不一定能写入成功,因为还要经过模型类的约束校验,只有模型类的约束校验也通过了,数据才能正常写入到数据库。
客户端提交的数据
↓√
序列化器的规则校验
↓√
模型类的约束校验
↓√
数据写入数据库成功
所以,序列化器的校验应该结合模型类的约束来搭配使用。像序列化器你设置最大值8,数据库约束最大长度为6,结果客户端传过来的数据长度是7,通过了序列化器校验,然后写不进数据库,这种行为还是不要出现为好。
反序列化器校验的基本使用
模型类还是前面定义的学生类,不变。
所以,我们只需要关注序列化器和视图类部分关于校验的应用。
这部分主要学习:
- 主要的字段校验手段:
- 基础校验,定义字段时或者
__init__
中指定的诸如min_length
、max_length
、required
这些基础的校验规则,以及字段自己的校验规则,例如EmailField
这种字段内部有自己的邮箱格式校验。 - 局部钩子,
validate_字段名
定义的这对某个字段做的校验,只能声明一次。 validators
校验列表,当基础校验、局部钩子和全局钩子都都用完了,但某些字段还需要额外的校验怎么办?这个时候,就需要在字段定义时指定validators
校验列表,指定你要补充的额外校验规则,由于是列表,所以可以写很多个。
- 基础校验,定义字段时或者
- 每个字段的校验规则的先后执行顺序:
- 首先是基础校验,通过了再执行后续的校验规则,通不过后面的校验规则就不执行了。
- 其次是局部钩子,每个字段都只能有一个局部钩子。
- 然后是
validators
校验列表。 - 以上的的顺序是每个字段都要经历的这些过程。
- 全局钩子是在上面各种钩子都执行完,才最后执行的钩子。
__init__
方法对序列化器的补充,比如为所有的字段添加相同的属性,当然还有其他姿势,但我比较传统,我不会!!!!- 当校验成功后,拿到的干净的数据,即字典,在保存到数据库之前,如何根据需要做一些增删改。
from django.http import JsonResponse
from rest_framework import serializers
# from django.views import View
from rest_framework.views import APIView
from student import models
def validate_email1(value):
""" 针对email字段做的补充校验函数"""
if '浜边美波' in value:
raise serializers.ValidationError("邮箱不能含有敏感词[{}]".format("浜边美波"))
return value # 校验通过必须返回value
def validate_email2(value):
""" 针对email字段做的补充校验函数"""
if '明日花绮罗' in value:
raise serializers.ValidationError("邮箱不能含有敏感词[{}]".format("明日花绮罗"))
return value # 校验通过必须返回value
class RegexValidator(object):
def __init__(self, rex):
self.rex = str(rex)
def __call__(self, value):
match_obj = re.match(self.rex, value)
if not match_obj: # 校验失败
raise serializers.ValidationError("自定义校验类:email不合法[{}]".format(value))
return value
def set_context(self, serializer_field):
""" 执行验证之前调用该方法,serializer_field是当前字段对象 """
pass
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
# 需要进行数据转换的字段
# 这里写几个字段,序列化器在返回每个记录时,就返回几个字段,并对这些字段进行校验
# id = serializers.IntegerField(max_value=32, min_value=8, required=True)
name = serializers.CharField()
age = serializers.IntegerField()
sex = serializers.BooleanField(default=1)
description = serializers.CharField()
# 除了模型类中有的字段之外,这里还可以额外的写其他字段,比如对于确认密码这类的校验
pwd = serializers.CharField(max_length=18, min_length=4)
re_pwd = serializers.CharField(max_length=18, min_length=4)
email = serializers.CharField(validators=[validate_email1, validate_email2])
# RegexValidator(r"^\w+@\w+\.\w+$") # 自定义校验类,要传递校验规则
email2 = serializers.CharField(validators=[RegexValidator(r"^\w+@\w+\.\w+$")])
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# 为所有的字段添加相同的属性
for field in self.fields.values():
field.error_messages.update({"required": "该字段不能为空"})
# 局部钩子
def validate_name(self, value):
# 敏感词列表
li = ['浜边美波', '新垣结衣', '由板深雪', '明日花绮罗']
for item in li:
if item == value:
raise serializers.ValidationError("敏感词[{}],换个用户名吧".format(item))
else:
return value # 必须有返回value
# 全局钩子,方法名是固定的,data中包含多个字段的值
def validate(self, data):
# print(data)
"""
OrderedDict([('name', 'root'), ('age', 18), ('sex', 1), ('description', '真帅'), ('pwd', '1234'), ('re_pwd', '1234')])
"""
if data.get("pwd") != data.get("re_pwd"):
raise serializers.ValidationError("两次密码不一致!!")
return data # 必须有返回data
# 默认的,django无法自动解析客户端提交过来的json类型的数据
# 如果是post请求,默认是Content-Type:application/x-www-form-urlencoded,可以通过request.POST获取
# 如果是post请求,默认数据类型是Content-Type:application/json,那么django默认就无能为力了
# 所以,为了处理客户端传来的json数据,这里使用drf的APIView视图,这个视图类的request.data能直接获取到json数据
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def post(self, request):
# 客户端提交的json类型的数据
# print(request.data)
serializer_obj = StudentSerializer(data=request.data)
# 默认的raise_exception=False,即当校验失败,程序不报错
# 如果指定raise_exception=True时,校验失败,程序直接在这里报错了,可以作为调试时使用
if serializer_obj.is_valid(raise_exception=False): # 通过序列化器的各个规则校验
# 干净的数据
# print(serializer_obj.validated_data)
"""
OrderedDict([('name', 'root'), ('age', 18), ('sex', 1), ('description', '真帅'), ('pwd', '1234'), ('re_pwd', '1234')])
"""
# 在保存到数据库之前,排除一些额外的字段
serializer_obj.validated_data.pop("pwd")
serializer_obj.validated_data.pop("re_pwd")
serializer_obj.validated_data.pop("email")
# 或者添加一些字段
serializer_obj.validated_data['class_null'] = 'python 1班'
# print(serializer_obj.validated_data)
# 直写入数据库
# models.Student.objects.create(**serializer_obj.validated_data)
# 这里不能直接使用,会报错
# 想要使用的话,需要单独处理,在后续示例中再讲
# serializer_obj.save()
return JsonResponse({"code": 0})
# 校验失败的错误信息
# print(serializer_obj.errors)
return JsonResponse({"code": 1, "error_msg": serializer_obj.errors})
注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。serializer是独立于数据库之外的存在。
反序列化之create/update
当客户端提交过来的数据通过校验之后,我们就可以存储到数据库,按需可以进行create和update操作,来看看都有哪些姿势吧。
下面的示例,我把那些钩子都删除了,毕竟不是本小节的重点嘛,重点关注保存部分。
from django.http import JsonResponse
from rest_framework import serializers
from rest_framework.views import APIView
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
# id = serializers.IntegerField(max_value=32, min_value=8, required=True)
name = serializers.CharField(max_length=9, min_length=2)
age = serializers.IntegerField()
sex = serializers.BooleanField(default=1)
description = serializers.CharField()
def create(self, validated_data):
"""
validated_data:校验后的干净的数据
"""
# validated_data和self.validated_data本质上都是一样的,你用哪个都行
print(validated_data)
print(self.validated_data)
"""
{'name': 'zhangkai1', 'age': 18, 'sex': True, 'description': '666'}
OrderedDict([('name', 'zhangkai1'), ('age', 18), ('sex', True), ('description', '666')])
"""
# 对于干净的数据,在写入数据库之前,按需对这个字典进行调整,比如剔除或者添加一些字段
# validated_data.pop("xxx")
# validated_data['k1'] = "v1"
# 然后写入到数据库中去,并且一定要将创建好的记录对象返回
instance = models.Student.objects.create(**validated_data)
return instance
def update(self, instance, validated_data):
"""
instance: 要更新的模型类对象
validated_data: 通过校验的干净的数据
"""
# 按需对这个validated_data字典进行调整后,比如剔除或者添加一些字段
# 我们就可以更新了
print(instance)
print(validated_data)
"""
Student object (1)
{'name': 'zhangkai6', 'age': 18, 'sex': True, 'description': '666'}
"""
# 可以使用 __dict__.update
instance.__dict__.update(**validated_data)
instance.save() # 别忘了save
# 或者使用下面的方式一个个字段的更新,然后save
# instance.name = validated_data.get("name")
# instance.age = validated_data.get("age")
# ...
# instance.save()
# 最后都要把更新后的对象返回
return instance
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def post(self, request):
""" 添加学生信息 """
# 客户端提交的json类型的数据
# print(request.data)
serializer_obj = StudentSerializer(data=request.data)
if serializer_obj.is_valid(): # 通过序列化器的各个规则校验
# 方法1,直写入数据库
# models.Student.objects.create(**serializer_obj.validated_data)
# 方法2,当serializer_obj执行save方法时,且StudentSerializer(data=request.data)
# 中直传了data参数,序列化类会自动执行其create方法
# instance就是create方法的返回值,然后我们可以将它序列化后再返回给客户端,表示创建成功了
instance = serializer_obj.save()
ser_obj = StudentSerializer(instance=instance)
# print(ser_obj.data) # {'name': 'zhangkai3', 'age': 18, 'sex': True, 'description': '666'}
return JsonResponse({"code": 0, "ser_obj": ser_obj.data})
# 上面那种返回又经过一次序列化步骤,比较繁琐,其实在序列化内部,当通过所有钩子检验后,干净的数据也会给serializer_obj.data一份
# 所以我们也可以将serializer_obj.data给前端返回,这样就省了一步序列化步骤了
# return JsonResponse({"code": 0, "ser_obj": serializer_obj.data})
# 如果想额外的返回一些其他键值对,你可以这样做
# data = dict(serializer_obj.data)
# data['k1'] = 'v1'
# return JsonResponse({"code": 0, "ser_obj": data})
# 校验失败的错误信息
# print(serializer_obj.errors)
return JsonResponse({"code": 1, "error_msg": serializer_obj.errors})
def put(self, request):
""" 更新学生信息 """
# 模拟从url上传过来的要更新的记录id
# http://127.0.0.1:8000/stu/student/?pk=1
current_pk = request.GET.get("pk")
# data是客户端传过来的json类型的要更新的数据
data = request.data
instance = models.Student.objects.filter(pk=current_pk).first()
if instance: # 表示要更新的记录存在
ser_obj = StudentSerializer(instance=instance, data=data)
if ser_obj.is_valid(): # 如果通过钩子校验
"""
当给序列化类传递了instance和data这两个参数后
StudentSerializer(instance=instance, data=data)
调用save方法会自动触发序列化类的update方法
update将更新后的模型类对象返回
然后通过序列化类序列化后返回给客户端,表示更新成功
"""
instance = ser_obj.save()
dump_ser_obj = StudentSerializer(instance=instance)
return JsonResponse({"code": 0, "ser_obj": dump_ser_obj.data}) # 更新成功
return JsonResponse({"code": 1, "error_msg": ser_obj.errors}) # 校验失败
return JsonResponse({"code": 1, "error_msg": "要更新的记录id不存在"}) # 要更新的记录id不存在
说了这么多,这里我们只是多做了解,即让我们手动干,该怎么做,后续我们有别的序列化类来自动做create或者update操作。
补充
在添加和更新后,我们后端要给前端再次返回instance对象,按照上面的逻辑是再次调用序列化类,序列化后返回给客户端。
那么这里其实还可以用原来的serializer对象来处理。
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def post(self, request):
""" 添加学生信息 """
# 客户端提交的json类型的数据
# print(request.data)
serializer_obj = StudentSerializer(data=request.data)
if serializer_obj.is_valid(): # 通过序列化器的各个规则校验
# 原来是重新将instance给序列化,再返回给客户端
# instance = serializer_obj.save()
# ser_obj = StudentSerializer(instance=instance)
# print(ser_obj.data) # {'name': 'zhangkai3', 'age': 18, 'sex': True, 'description': '666'}
# return JsonResponse({"code": 0, "ser_obj": ser_obj.data})
# 现在你可以这么做,还用原来的serializer_obj.data即可
serializer_obj.save()
return JsonResponse({"code": 0, "ser_obj": serializer_obj.data})
# 校验失败的错误信息
# print(serializer_obj.errors)
return JsonResponse({"code": 1, "error_msg": serializer_obj.errors})
这两种都可以。
read_only/write_only
read_only/write_only
这样的一个场景:
read_only
,序列化阶段,需要给客户端返回的字段,反序列化阶段不需要客户端提交,序列化类也不会校验,对于这样的字段,指定read_only=True
。比如客户端请求所有的学生信息,学生表的id字段,需要给前端返回,但如果创建一个学生信息,这个时候,客户端就不需要给服务端提交id字段。write_only
,跟read_only
正相反,序列化阶段,不能给客户端这个字段信息,如客户端请求所有的学生信息,但有些敏感字段,比如身份证号这些,不能作为常规页面展示;但客户端如果要提交一个学生信息的时候,就要提交身份证号字段。这就要求必须要客户端提供该身份证的字段值,用于后端校验保存,下面示例以sex
为例。
from django.http import JsonResponse
from rest_framework import serializers
from rest_framework.views import APIView
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
# read_only=True 给客户端返回时有该字段,反序列化保存时不需要该字段,也不为该字段进行校验。
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=9, min_length=2)
age = serializers.IntegerField()
# write_only=True 给客户端返回时,没有该字段,但反序列化(即要保存一条记录时)时,需要客户端提供该字段的值
# 进行校验,然后保存到数据库
sex = serializers.BooleanField(write_only=True) # 例如sex字段,在客户端提交一个学生信息时,必须提交该字段的数据
description = serializers.CharField()
"""
# 如序列化阶段,给客户端返回一条学会记录,id字段需要给客户端返回,但sex字段由于是write_only=True,就不给客户端返回了, 所以客户端拿到的数据是这样的:
[
{
"id": 1, # 可以看到id值,前端是能获取到的
"name": "张开",
"age": 18,
"description": "真帅"
}
]
# 反序列化阶段,也就是客户端要提交一个学生信息,我们经过序列化器校验后,保存到数据库,客户端发来的数据是这样的
{
"name": "张开",
"age": 18,
"sex": true,
"description": "真帅"
}
# 可以看到,id不需要客户端提供,因为创建一条数据,也没必要提供数据,我们数据库自动生成
# 而sex字段由于设置了write_only=True,客户端也要提交该字段的数据
"""
owner
serializer对象在save时,可以通过owner
参数额外传递一些参数,这个参数被保存到了validated_data
字典中,可以在后续的逻辑中使用。
PS:用的不多。
from django.http import JsonResponse
from rest_framework import serializers
from rest_framework.views import APIView
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=9, min_length=2)
age = serializers.IntegerField()
sex = serializers.BooleanField(write_only=True)
description = serializers.CharField()
def update(self, instance, validated_data):
"""
instance: 要更新的模型类对象
validated_data: 通过校验的干净的数据
"""
print(instance)
print(validated_data)
"""
Student object (2)
{'name': 'zhangkai6', 'age': 18, 'sex': True, 'description': '666', 'owner': <django.contrib.auth.models.AnonymousUser object at 0x0000028927D5B4C0>}
"""
# 就是打印的字典中的owner,至于用途就看你需求了。
validated_data.pop('owner')
instance.__dict__.update(**validated_data)
instance.save()
return instance
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def put(self, request):
""" 更新学生信息 """
# 模拟从url上传过来的要更新的记录id
# http://127.0.0.1:8000/stu/student/?pk=1
current_pk = request.GET.get("pk")
# data是客户端传过来的json类型的要更新的数据
data = request.data
instance = models.Student.objects.filter(pk=current_pk).first()
if instance: # 表示要更新的记录存在
ser_obj = StudentSerializer(instance=instance, data=data)
if ser_obj.is_valid(): # 如果通过钩子校验
# 就是save中的owner参数
# instance = ser_obj.save(owner="abc") # 可以传个字符串或者其它数据
instance = ser_obj.save(owner=request.user) # 也可以传个对象
dump_ser_obj = StudentSerializer(instance=instance)
return JsonResponse({"code": 0, "ser_obj": dump_ser_obj.data})
return JsonResponse({"code": 1, "error_msg": ser_obj.errors})
return JsonResponse({"code": 1, "error_msg": "要更新的记录id不存在"})
partial
默认的序列化类中的required
默认都为True
,这就意味着客户端就必须提交该字段信息。
但是有些特殊的场景,比如只更新部分字段,那么客户端就只提交要更新的字段即可,问题来了,那其它字段不提交,但序列化类又要做required=True
校验,这种情况怎么处理?这就用到了partial
参数。
from django.http import JsonResponse
from rest_framework import serializers
from rest_framework.views import APIView
from student import models
class StudentSerializer(serializers.Serializer):
""" 学生表序列化器 """
id = serializers.IntegerField(read_only=True)
name = serializers.CharField(max_length=9, min_length=2)
age = serializers.IntegerField()
sex = serializers.BooleanField(write_only=True)
description = serializers.CharField()
def update(self, instance, validated_data):
validated_data.pop('owner')
instance.__dict__.update(**validated_data)
instance.save()
return instance
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def put(self, request):
""" 更新学生信息 """
# 模拟从url上传过来的要更新的记录id
# http://127.0.0.1:8000/stu/student/?pk=1
current_pk = request.GET.get("pk")
# data是客户端传过来的json类型的要更新的数据
data = request.data
instance = models.Student.objects.filter(pk=current_pk).first()
if instance: # 表示要更新的记录存在
# 就在这指定partial参数,后续序列化进行required=True校验时,就会放行了
ser_obj = StudentSerializer(instance=instance, data=data, partial=True)
if ser_obj.is_valid(): # 如果通过钩子校验
instance = ser_obj.save()
dump_ser_obj = StudentSerializer(instance=instance)
return JsonResponse({"code": 0, "ser_obj": dump_ser_obj.data})
return JsonResponse({"code": 1, "error_msg": ser_obj.errors})
return JsonResponse({"code": 1, "error_msg": "要更新的记录id不存在"})
当然,这么搞,你还要考虑数据库的字段约束是否允许为空!如果数据库不允许为空,你无脑的设置了partial=True
,结果序列化类校验通过,然后到了数据库保存时,又通不过.....不要给自己挖坑!
外键关系中的序列化器的使用
上面的基础部分,都用的是单表的操作。
当我们学完了序列化其中的各个知识点后,来看下有外键关系的表结构中,如何进行使用序列化器。
首先,models.py
长这样:
from django.db import models
class Book(models.Model):
""" 书籍表 """
title = models.CharField(max_length=32, verbose_name='书籍名称')
choice = ((1, "Python"), (2, "Linux"), (3, "Go"))
category = models.IntegerField(choices=choice, verbose_name="书籍类别")
pub_time = models.DateField(verbose_name="出版日期")
publisher = models.ForeignKey(to='Publisher', on_delete=models.CASCADE, verbose_name='出版社')
author = models.ManyToManyField(to='author', verbose_name='作者')
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "书籍表"
db_table = verbose_name_plural
class Publisher(models.Model):
""" 出版社表 """
title = models.CharField(max_length=32, verbose_name="出版社名称")
address = models.CharField(max_length=255, verbose_name="地址")
email = models.CharField(max_length=32, default="publisher@qq.com")
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "出版社表"
db_table = verbose_name_plural
class Author(models.Model):
""" 作者表 """
name = models.CharField(max_length=32, verbose_name="作者名称")
age = models.IntegerField(verbose_name="作者名称")
phone = models.CharField(max_length=20, default='182111101111')
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "作者表"
db_table = verbose_name_plural
数据你就随便填充点数据就行了。
主路由urls.py
:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('stu/', include("student.urls")),
path('ser/', include("ser.urls")),
path('book/', include("book.urls")),
]
book app的urls.py
:
from django.urls import path, re_path
from .views import BookView
urlpatterns = [
path('list/', BookView.as_view()),
path('list/<int:id>', BookView.as_view()),
]
这里主要看序列化器和视图类的搭配使用:
# 这里用到了新的知识点就是drf提供的response,来替代之前的JsonResponse
# 后续会单独的讲这个知识点,我们先用着
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from .models import Book, Publisher, Author
class PublisherSerializer(serializers.Serializer):
""" 出版社表序列化器 """
id = serializers.IntegerField()
title = serializers.CharField(max_length=32)
address = serializers.CharField(max_length=255)
email = serializers.EmailField(max_length=32)
class AuthorSerializer(serializers.Serializer):
""" 作者表序列化器 """
id = serializers.IntegerField()
name = serializers.CharField(max_length=32)
phone = serializers.CharField(max_length=20)
class BookSerializer(serializers.Serializer):
""" 书籍表序列化器 """
id = serializers.IntegerField(required=False, read_only=True) # 当客户端新增时,无需提供id值
title = serializers.CharField(max_length=32)
choice = ((1, "Python"), (2, "Linux"), (3, "Go"))
category = serializers.ChoiceField(
choices=choice,
source="get_category_display",
read_only=True, # 只在序列化阶段给客户端展示
)
# 当客户端新增一本书籍时,category字段需要的1,2,3这些,都有w_category来完成
# 至于提交的id合不合法,你可以用钩子进行校验
# 下面的出版社字段和作者字段一样
w_category = serializers.ChoiceField(
choices=choice,
write_only=True,
)
pub_time = serializers.DateField()
publisher = PublisherSerializer(read_only=True)
# 新增时,出版社字段只需要提交所属出版社id即可
w_publisher_id = serializers.IntegerField(write_only=True)
author = AuthorSerializer(many=True, read_only=True)
# 新增作者时,可能是多个作者,所以这里以列表的形式接受作者id
w_author_list = serializers.ListField(write_only=True)
def create(self, validated_data):
book_obj = Book.objects.create(
title=validated_data['title'],
category=validated_data['w_category'],
pub_time=validated_data['pub_time'],
publisher_id=validated_data['w_publisher_id']
)
book_obj.author.add(*validated_data['w_author_list'])
return book_obj
def update(self, instance, validated_data):
# 更新部分字段,所以能get到表示要更新该字段,否则就用原来的值即可
# 这里客户端传过来的json数据的key的写法你要和客户端协商好叫啥名字
instance.title = validated_data.get("title", instance.title)
instance.category = validated_data.get("w_category", instance.category)
instance.pub_time = validated_data.get("pub_time", instance.pub_time)
instance.publisher_id = validated_data.get("w_publisher_id", instance.publisher_id)
if validated_data.get("w_author_list"): # 判断多对多字段是否需要更改
instance.author.set(validated_data.get("w_author_list"))
instance.save()
return instance
class BookView(APIView):
def get(self, request, id=None):
# http://127.0.0.1:8000/book/list/
# http://127.0.0.1:8000/book/list/11
if id:
book_obj = Book.objects.filter(id=id).first()
ser_obj = BookSerializer(instance=book_obj)
else:
book_obj = Book.objects.all()
ser_obj = BookSerializer(instance=book_obj, many=True)
return Response(ser_obj.data)
def post(self, request, id=None):
# http://127.0.0.1:8000/book/list/
# print(request.data)
"""
{'title': 'Python快速入门2', 'w_category': 1, 'pub_time': '2022-06-14', 'w_publisher_id': 1, 'w_author_list': [1, 2]}
"""
ser_obj = BookSerializer(data=request.data)
if ser_obj.is_valid():
instance = ser_obj.save()
ret = BookSerializer(instance=instance)
return Response(ret.data)
else:
return Response(ser_obj.errors)
def put(self, request, id):
# http://127.0.0.1:8000/book/list/11
# print(request.data, id)
"""
{'title': 'Python快速入门22'} 11
"""
book_obj = Book.objects.filter(id=id).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
if ser_obj.is_valid():
instance = ser_obj.save()
ret = BookSerializer(instance=instance)
return Response(ret.data)
else:
return Response(ser_obj.errors)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
序列化阶段到还好,结合read_only
参数给客户端返回数据。
在反序列化阶段,添加和编辑功能中,就要做一些约定了,比如外键字段、choice字段,给客户端展示的数据长啥样,添加和编辑提交数据时,客户端该如何传值,这都要和客户端进行协商,让我们的后端程序能够正常的保存到数据库。
serializers.ModelSerializer
如果我们想要使用序列化器对应的是Django的模型类,drf为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段。
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现,针对单表操作,直接save可以了。
同时支持Serializer中的owner、partial这些功能,包括基础校验的钩子、局部钩子、全局钩子、is_valid
中指定raise_exception=True
、在__init__
中做其它的操作。
from rest_framework import serializers
class UserModelSerializer(serializers.ModelSerializer):
r_pwd = serializers.CharField()
class Meta:
model = models.UserInfo
# 所有字段
# fields = "__all__"
# 指定字段
fields = ['fields1', 'fields2']
# 排除字段
exclude = ['fields1', 'fields2']
# 指定多个 read_only、write_only 字段
read_only_fields = ['fields1', 'fields2']
write_only_fields = ['fields1', 'fields2']
# 上面这么写,虽然很好用,但是通常我们更喜欢在extra_kwargs中添加校验规则,且优先级更高
extra_kwargs = {
"fields1": {
"write_only": True,
"max_length": 10
},
"fields2": {
"read_only": True,
"min_length": 10
}
}
def create(self, validated_data):
"""
在有外键的情况下,进行自定义添加记录
:param validated_data:
:return:
"""
pass
def update(self, instance,validated_data):
"""
在有外键的情况下,进行自定义更新记录
:param validated_data:
:return:
"""
pass
添加额外的字段
比如我们要在原密码的基础上, 额外搞个确认密码的字段,并进行校验,就可以这么搞:
from rest_framework import serializers
class UserModelSerializer(serializers.ModelSerializer):
# 添加额外的校验字段
r_pwd = serializers.CharField()
class Meta:
model = models.User
# 将额外的字段添加到fields中
fields = ['fields1', 'fields2', 'r_pwd']
extra_kwargs
如果想在序列化器中指定其它的校验规则,可以通过extra_kwargs进行扩展:
from rest_framework import serializers
from django.core.validators import EmailValidator
class UserModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
# 可以为指定的字段一并添加更多的校验规则
extra_kwargs = {
"fields1": {
"write_only": True,
"max_length": 10
},
"fields2": {
"read_only": True,
"min_length": 10
},
"email": {"validators": [EmailValidator]}
}
read_only_fields/write_only_fields
如果有多个字段都需要添加read_only或者write_only,你也可以这样写:
from rest_framework import serializers
class UserModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
# read_only_fields = ['fields1', 'fields2']
# write_only_fields = ['fields1', 'fields2']
# 上面这么写,虽然很好用,但是通常我们更喜欢在extra_kwargs中进行约束,因为这里可以一并添加更多的校验规则
extra_kwargs = {
"fields1": {
"write_only": True,
"max_length": 10
},
"fields2": {
"read_only": True,
"min_length": 10
}
}
create/update
如果按照上面的写法,直接save的话,根据instance参数,内部会自动决定是create还是update:
class StudentView(APIView):
""" 使用序列化器转换单个模型类数据 """
def post(self, request):
""" 添加学生信息 """
# 只传data,save时触发内部的create
serializer_obj = StudentSerializer(data=request.data)
if serializer_obj.is_valid(): # 通过序列化器的各个规则校验
instance = serializer_obj.save()
ser_obj = StudentSerializer(instance=instance)
# print(ser_obj.data) # {'name': 'zhangkai3', 'age': 18, 'sex': True, 'description': '666'}
return JsonResponse({"code": 0, "ser_obj": ser_obj.data})
# 校验失败的错误信息
# print(serializer_obj.errors)
return JsonResponse({"code": 1, "error_msg": serializer_obj.errors})
def put(self, request):
""" 更新学生信息 """
# 模拟从url上传过来的要更新的记录id
# http://127.0.0.1:8000/stu/student/?pk=1
current_pk = request.GET.get("pk")
# data是客户端传过来的json类型的要更新的数据
data = request.data
instance = models.Student.objects.filter(pk=current_pk).first()
if instance: # 表示要更新的记录存在
# 如果有instance,save时触发内部的update操作
ser_obj = StudentSerializer(instance=instance, data=data, partial=True)
if ser_obj.is_valid(): # 如果通过钩子校验
instance = ser_obj.save()
dump_ser_obj = StudentSerializer(instance=instance)
return JsonResponse({"code": 0, "ser_obj": dump_ser_obj.data})
return JsonResponse({"code": 1, "error_msg": ser_obj.errors})
return JsonResponse({"code": 1, "error_msg": "要更新的记录id不存在"})
这么简单粗暴适用于单表环境,如果涉及到外键字段的话,在添加和保存的时候,就要注意了,来看下个小节。
数据校验
这块就没啥,跟serializers.Serializer中差不多。
我们来单独看看各种校验钩子都是怎么写:
import re
from api.models import UserInfo
from rest_framework import serializers
from rest_framework.views import APIView
from rest_framework.response import Response
from django.core.validators import EmailValidator
class RegexValidator(object):
def __init__(self, rex):
self.rex = str(rex)
def __call__(self, value):
match_obj = re.match(self.rex, value)
if not match_obj: # 校验失败
raise serializers.ValidationError("自定义校验类:email不合法[{}]".format(value))
return value
def set_context(self, serializer_field):
""" 执行验证之前调用该方法,serializer_field是当前字段对象 """
pass
def validate_email3(value):
""" 针对email3字段做的补充校验函数"""
if 'zhangkai' in value:
raise serializers.ValidationError("邮箱不能含有敏感词[{}]".format("zhangkai"))
return value # 校验通过必须返回value
class BookSerializer(serializers.ModelSerializer):
# RegexValidator(r"^\w+@\w+\.\w+$") # 自定义校验类,要传递校验规则
email2 = serializers.CharField(validators=[RegexValidator(r"^\w+@\w+\.\w+$")])
# validate_email3,如果是函数的话,就是照抄上面的示例就行
email3 = serializers.CharField(validators=[validate_email3])
class Meta:
model = UserInfo
fields = ['user', 'email', 'email2', 'email3']
extra_kwargs = {
"email": {"validators": [EmailValidator("邮箱格式错误")]} # EmailValidator("邮箱格式错误") 这个类一定要加括号,然后传递错误信息
}
# 局部钩子
def validate_user(self, value):
# 敏感词列表
li = ['浜边美波', '新垣结衣', '由板深雪', '明日花绮罗']
for item in li:
if item == value:
raise serializers.ValidationError("敏感词[{}],换个用户名吧".format(item))
else:
return value # 必须有返回value
# 全局钩子,方法名是固定的,data中包含多个字段的值
def validate(self, data):
"""
OrderedDict([('name', 'root'), ('age', 18), ('sex', 1), ('description', '真帅'), ('pwd', '1234'), ('re_pwd', '1234')])
"""
if data.get("pwd") != data.get("re_pwd"):
raise serializers.ValidationError("两次密码不一致!!")
return data # 必须有返回data
各种钩子的执行顺序也是跟serializers.Serializer一样,这里不在多表。
外键关系中的序列化器的使用
数据和表还是用上面的那个book app中的那些表。
基础版
序列化器非常简单,而视图类一行代码还是原来的那些代码,一行代码也不变:
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from .models import Book, Publisher, Author
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
class BookView(APIView):
def get(self, request, id=None):
# http://127.0.0.1:8000/book/list/
# http://127.0.0.1:8000/book/list/11
if id:
book_obj = Book.objects.filter(id=id).first()
ser_obj = BookSerializer(instance=book_obj)
else:
book_obj = Book.objects.all()
ser_obj = BookSerializer(instance=book_obj, many=True)
return Response(ser_obj.data)
def post(self, request, id=None):
# http://127.0.0.1:8000/book/list/
# print(request.data)
"""
{'title': 'Python快速入门2', 'w_category': 1, 'pub_time': '2022-06-14', 'w_publisher_id': 1, 'w_author_list': [1, 2]}
"""
ser_obj = BookSerializer(data=request.data)
if ser_obj.is_valid():
instance = ser_obj.save()
ret = BookSerializer(instance=instance)
return Response(ret.data)
else:
return Response(ser_obj.errors)
def put(self, request, id):
# http://127.0.0.1:8000/book/list/11
# print(request.data, id)
"""
{'title': 'Python快速入门22'} 11
"""
book_obj = Book.objects.filter(id=id).first()
if book_obj:
ser_obj = BookSerializer(instance=book_obj, data=request.data, partial=True)
if ser_obj.is_valid():
instance = ser_obj.save()
ret = BookSerializer(instance=instance)
return Response(ret.data)
else:
return Response(ser_obj.errors)
else:
return Response({"code": 1, "msg": "请求的数据id错误"})
我们通过postman,以get方法请求一条数据,来观察序列化的结果:
# GET
# http://127.0.0.1:8000/book/list/11
{
"id": 11,
"title": "Python快速入门22",
"category": 1,
"pub_time": "2022-06-14",
"publisher": 1,
"author": [
1,
2
]
}
可以看到,普通字段都没问题,但:
- choice字段,应该给客户端返回分类信息,而不是数字,但注意,如果是添加或者更新时,又必须是数字,这些我们都要单独处理。
- 外键字段,也仅返回了id,但应该给客户端返回其它需要的信息,比如title、name这些,而添加或者新增时,也必须是id,所以还要单独处理。
另外,如果外键字段对应的表,还有外键字段,比如书籍的外键字段出版社表,如果出版社这个表关联了其它外键字段,我们序列化书籍表时,需不需要特殊处理?这些都是我们应该考虑的。
depth
这个属性就是用来声明,序列化时,嵌套序列化的层级为1层,即只序列化当前表的外键字段,对于关联表的外键关系不序列化。
用法:
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework import serializers
from .models import Book, Publisher, Author
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
depth = 1
还是请求一条数据,看看效果:
# GET
# http://127.0.0.1:8000/book/list/11
{
"id": 11,
"title": "Python快速入门22",
"category": 1,
"pub_time": "2022-06-14",
"publisher": {
"id": 1,
"title": "沙河出版社",
"address": "沙河",
"email": "shahe@qq.com"
},
"author": [
{
"id": 1,
"name": "张开",
"age": 18,
"phone": "18211101111"
},
{
"id": 2,
"name": "李开",
"age": 20,
"phone": "18211101112"
}
]
}
这样,就有对应的外键字段的信息了。
其实这样还不够,比如只想获取外键字段的部分字段而不是全部的字段怎么办?比如只要作者的name,你却给了所有的字段。
包括choice字段,仅返回数字还不够。
所以,我们继续往下走。
重写指定字段
这里主要解决上面提到的两个问题:
- 对于choice字段,如何返回中文。
- 对于外键字段,如何只返回需要的字段,例如,出版社表,有20个字段,但我们书籍表在序列化时只需要出版社表的指定的几个字段,该怎么办?
这就用到了重写字段这部分内容了:
class BookSerializer(serializers.ModelSerializer):
# 通过orm语句,获取category的中文信息
category = serializers.CharField(source="get_category_display")
# 对于外键字段就要特殊处理了
# 这就用到了serializers.SerializerMethodField 这个方法字段了,写法如下
publisher = serializers.SerializerMethodField() # 首先声明方法字段
author = serializers.SerializerMethodField()
# 然后通过钩子方法来进一步处理 语法是: get_字段名
def get_publisher(self, obj):
""" obj:是每个book对象 """
# 通过orm获取对应的出版社表对象
publisher_obj = obj.publisher
# 然后需要啥字段,就区啥字段就行了
return {"id": publisher_obj.id, "title": publisher_obj.title}
def get_author(self, obj):
""" obj:是每个book对象 """
author_queryset = obj.author.all()
return [
{"id": i.id, "name": i.name}
for i in author_queryset
]
class Meta:
model = Book
fields = "__all__"
# depth = 1 # 字段都重写了,这个属性也没必要使用了
效果如下:
# GET
# http://127.0.0.1:8000/book/list/11
{
"id": 11,
"category": "Python",
"publisher": {
"id": 1,
"title": "沙河出版社"
},
"author": [
{
"id": 1,
"name": "张开"
},
{
"id": 2,
"name": "李开"
}
],
"title": "Python快速入门22",
"pub_time": "2022-06-14"
}
要啥取啥,非常方便。
反序列化
简单版:不推荐
简单版,代码简单,但是给客户端的数据不太友好。
我这里提前用到了drf的路由了视图部分的知识,主要是为了不重要的代码少写几行。重点还在序列化器部分。
来看代码:
urls.py
:
from django.urls import path, include, re_path
from rest_framework.routers import SimpleRouter
from .views import BookView
router = SimpleRouter()
router.register(prefix="list", viewset=BookView, basename='book-list')
urlpatterns = [
re_path(r"^", include(router.urls)),
]
views.py
:
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from .models import Book, Publisher
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = "__all__"
# 因为ModelSerializer会自动的帮我们进行create和update操作,这一块我们也不用手动写了
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
代码非常简洁,来用postman测下吧。
# GET请求一条数据
# http://127.0.0.1:8000/book/list/1/
{
"id": 1,
"title": "Python快速入门",
"category": 1,
"pub_time": "2022-06-14",
"publisher": 1,
"author": [
1,
2
]
}
# GET请求所有数据,结果太多不展示了
# http://127.0.0.1:8000/book/list/
# POST创建一条数据
# http://127.0.0.1:8000/book/list/
# 携带参数:
{
"title": "Python快速入门7",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 1,
"author": [
1,
2
]
}
# 创建成功,给客户端返回
{
"id": 16,
"title": "Python快速入门7",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 1,
"author": [
1,
2
]
}
# PUT更新一条数据
# http://127.0.0.1:8000/book/list/16/
# 携带参数,你也可以只传部分字段,但外键字段和choice字段一定要按照下面的格式写
# 即,choice字段要传存在的数字,foreignkey字段要传被关联表存在的id值,manytomany字段要以列表的形式传id,可以是多个
{
"title": "Python快速入门77",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 3,
"author": [
2,
3
]
}
# 更新成功,给客户端返回结果
{
"id": 16,
"title": "Python快速入门77",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 3,
"author": [
2,
3
]
}
# DELETE删除一条数据
# http://127.0.0.1:8000/book/list/16/
# 删除成功只有204状态码返回
看似一切都没问题,外键字段也都能正常处理。
代码也很简洁,但不足也是有的,我们单独来看get获取数据,看看drf序列化后的结果:
# GET请求一条数据
# http://127.0.0.1:8000/book/list/1/
# drf序列化给前端返回的
{
"id": 1,
"title": "Python快速入门",
"category": 1,
"pub_time": "2022-06-14",
"publisher": 1,
"author": [
1,
2
]
}
# 我们期望的每条记录应该包含的
{
"id": 1,
"title": "Python快速入门",
"category": "Python",
"pub_time": "2022-06-14",
"publisher": {
"id": 1,
"title": "沙河出版社"
},
"author": [
{
"id": 1,
"name": "张开1",
"phone": "18211101111"
},
{
"id": 2,
"name": "张开2",
"phone": "18211101112"
}
]
}
上面的记录,在drf序列化返回时,普通字段都正常,但是choice字段和外键字段就不太完美了:
- 对于choice字段,希望展示的是具体内容,而不是序号。
- 对于外键字段,我们希望能按需返回信息,而不是仅返回对应的id,比如出版社就希望展示出版社名称、地址,作者的话,就希望展示名字、年龄、手机号等等。
对于上述需求,我们目前的序列化器类就不能这么简单的写了,而是要做些处理。
普通字段正常序列化,特殊字段特殊处理版:推荐
来看看这些特殊的字段要如何处理,代码更改部分仅限于序列化器,其它不变:
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from .models import Book, Publisher
class BookSerializer(serializers.ModelSerializer):
# 这三个特殊的字段,我们重写,并且仅在序列化时展示给客户端就行,所以加了个read_only=True
# 但如果反序列化时,这三个特殊字段又要有变动,read_only=True显然和我们的需求冲突了,怎么解决呢
# 就是我们给这三个字段,起个别名,序列化时,这三个字段都名都由 字段名_info 组成,我这里都用_info来处理,当然,你也可以自己决定怎么命名
# 并且都设置为read_only=True,也就是仅仅是给客户端展示用,且反序列化时,无需提交这三个字段
category_info = serializers.SerializerMethodField(read_only=True)
publisher_info = serializers.SerializerMethodField(read_only=True)
author_info = serializers.SerializerMethodField(read_only=True)
# 再为它们分别写个 get_ 开头的方法,手动处理该字段要展示的内容,这么着,这个字段的数据就可以按需决定返回什么内容了
def get_category_info(self, obj):
return obj.get_category_display()
def get_publisher_info(self, obj):
pub_obj = obj.publisher
return {"id": pub_obj.id, "title": pub_obj.title, 'email': pub_obj.email}
def get_author_info(self, obj):
au_obj = obj.author.all()
return [{"id": i.id, "name": i.name} for i in au_obj]
class Meta:
model = Book
fields = "__all__"
# 另一方面,在create和update时
# 这三个特殊的字段,由于通过别名字段给客户端返回了自定义的数据,那么这里就要排除一下,不用序列化了
# "write_only": True 表示仅在反序列化create和update时客户端需要传递的三个字段
# 并且客户端传递这三个字段时,名字必须跟模型类中字段名保持一致,千万不要写成了别名,不然传递过来也没用
extra_kwargs = {
"category": {"write_only": True},
"publisher": {"write_only": True},
"author": {"write_only": True},
}
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
这么一写,get请求的内容更友好了,create和update时,也按照之前的处理就行:
# GET
# http://127.0.0.1:8000/book/list/15/
{
"id": 15,
"category_info": "Python",
"publisher_info": {
"id": 1,
"title": "沙河出版社",
"email": "shahe@qq.com"
},
"author_info": [
{
"id": 1,
"name": "张开"
},
{
"id": 2,
"name": "李开"
}
],
"title": "Python快速入门6",
"pub_time": "2022-06-15"
}
新增、更新、删除也不变:
# POST创建一条数据
# http://127.0.0.1:8000/book/list/
# 携带参数:
{
"title": "Python快速入门8",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 3,
"author": [
2,
3
]
}
# 创建成功,给客户端返回
{
"id": 17,
"category_info": "Python",
"publisher_info": {
"id": 3,
"title": "云谷园出版社",
"email": "yunguyuan@qq.com"
},
"author_info": [
{
"id": 2,
"name": "李开"
},
{
"id": 3,
"name": "王开"
}
],
"title": "Python快速入门8",
"pub_time": "2022-06-15"
}
# PUT更新一条数据
# http://127.0.0.1:8000/book/list/17/
# 携带参数,你也可以只传部分字段,但外键字段和choice字段一定要按照下面的格式写
# 即,choice字段要传存在的数字,foreignkey字段要传被关联表存在的id值,manytomany字段要以列表的形式传id,可以是多个
{
"title": "Python快速入门8",
"category": 1,
"pub_time": "2022-06-15",
"publisher": 1,
"author": [
1,
2
]
}
# 更新成功,给客户端返回结果
{
"id": 17,
"category_info": "Python",
"publisher_info": {
"id": 1,
"title": "沙河出版社",
"email": "shahe@qq.com"
},
"author_info": [
{
"id": 1,
"name": "张开"
},
{
"id": 2,
"name": "李开"
}
],
"title": "Python快速入门8",
"pub_time": "2022-06-15"
}
# DELETE删除一条数据
# http://127.0.0.1:8000/book/list/17/
# 删除成功只有204状态码返回
ok,反序列化也都没问题了。