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_lengthmax_lengthrequired这些基础的校验规则,以及字段自己的校验规则,例如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字段,仅返回数字还不够。

所以,我们继续往下走。

重写指定字段

这里主要解决上面提到的两个问题:

  1. 对于choice字段,如何返回中文。
  2. 对于外键字段,如何只返回需要的字段,例如,出版社表,有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,反序列化也都没问题了。

Serializer和ModelSerializer的区别

posted @ 2023-02-26 14:56  听雨危楼  阅读(654)  评论(0编辑  收藏  举报