DRF

DRF(Django Rest Framework)是可以快速基于Restful开发的Django应用的插件,功能非常多。

安装

pip install djangorestframework

Django需要2.2

注册

settings.py中增加

INSTALLED_APPS = [
   ...
    'rest_framework',
]

注册他,可以使用它提供的网页。也可以不注册,使用PostMan调试

序列化器

采用前后端分离后,前端与后端交互实际上使用的只是JSON数据

  • 序列化:后端查数据库获得模型类,发给前端的数据就是JSON字符串,核心就是如何把实例序列化成JSON发给浏览器
    • 实例转换为字典,如何做,使用序列化器
    • 字典转换为JSON字符串,使用json模块的dumps
  • 反序列化:前端浏览器发请求报文到 后端,封装为request请求对象,提交的数据是JSON字符串,需要反序列化
    • JSON字符串转换为字典谁来做? json.loads
    • 字典=>?

序列化器类

DRF中序列化器的作用

  1. 将实例序列化为字典
  2. 反序列化后得到字典,交给序列化器进行数据校验

参考:

https://www.django-rest-framework.org/api-guide/serializers/

BaseSerializer 类源码分析

class BaseSerializer(Field):
    def __init__(self, instance=None, data=empty, **kwargs):
        self.instance = instance # 第一个参数表示实例
        if data is not empty:
            self.initial_data = data # 浏览器端提交的字典
        self.partial = kwargs.pop('partial', False)
        self._context = kwargs.pop('context', {})
        kwargs.pop('many', None)   # many表示数据是多个还是一个
        super().__init__(**kwargs)
    def is_valid(self, raise_exception=False):
        """反序列化校验数据时调用,也就是对浏览器端提交的数据进行验证"""
    @property
    def data(self):
        if hasattr(self, 'initial_data') and not hasattr(self, '_validated_data'): 
 # 将浏览器提交数据转换为字典交给序列化器,但没有校验过数据,应先调用is_valid()校验
            msg = (
                'When a serializer is passed a `data` keyword argument you '
                'must call `.is_valid()` before attempting to access the '
                'serialized `.data` representation.\n'
                'You should either call `.is_valid()` first, '
                'or access `.initial_data` instead.'
           )
            raise AssertionError(msg)
        if not hasattr(self, '_data'): # 没有_data立即开始计算
            if self.instance is not None and not getattr(self, '_errors', None):
             # 序列化。有实例,没有错误
                self._data = self.to_representation(self.instance)
            elif hasattr(self, '_validated_data') and not getattr(self, '_errors', None):
             # 反序列化。is_valid()校验后的数据(提交的数据被验证过了)且无错误
                self._data = self.to_representation(self.validated_data)
            else:
                self._data = self.get_initial()
        return self._data
    @property
    def validated_data(self):
        """获取校验后的数据"""
class Serializer(BaseSerializer, metaclass=SerializerMetaclass):
    @property
    def data(self):
        ret = super().data
        return ReturnDict(ret, serializer=self) # OrderedDict

序列化器原理

Model类
from django.db import models

# Create your models here.

class Employees(models.Model):
    class Gender(models.IntegerChoices): #枚举类型,限定取值范围
        MAN = 1,"男"
        WOMEN = 2,"女"
    class Meta:
        db_table = "employees"
    emp_no = models.IntegerField(primary_key=True)
    birth_date = models.DateField()
    first_name = models.CharField(max_length=14)
    last_name = models.CharField(max_length=16)
    gender = models.SmallIntegerField(choices=Gender.choices)
    hire_date = models.DateField()

    @property
    def name(self):
        return "<{}{}>".format(self.first_name,self.last_name)

    def __str__(self):
        return "{},{},{}".format(self.emp_no,self.first_name,self.last_name)
    __repr__ = __str__
序列化器

在应用目录下构建serializers.py,根据Model类Employee编写EmpSerializer

需要什么字段就在EmpSerializer中定义什么属性

from rest_framework import serializers
from .models import Employees
class EmployeeSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField(max_length=14) # max_length反序列化验证用
    last_name = serializers.CharField(max_length=16)
    gender = serializers.ChoiceField(Employees.Gender.choices)
    hire_date = serializers.DateField()
序列化.py

实例转换为字典的过程

import os
import django
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)

#序列化
from employee.serializers import Employees,EmployeeSerializer

emp = Employees.objects
empr = emp.get(pk='10010')
print(type(empr),empr)
# 1 单个实例传给构造器的第一参数instance
ser = EmployeeSerializer(instance=empr)
data = ser.data  ## 序列化
print(ser.data) # ReturnDict


# 2 查询集多个实例,使用many参数
empr2 = emp.filter(pk__gt='10019')
ser2 = EmployeeSerializer(instance=empr2,many=True)
print(ser2.data,type(ser2.data))  #ReturnList列表套字典


#输出
<class 'employee.models.Employees'> 10010,Duangkaew,Piveteau
{'emp_no': 10010, 'birth_date': '1963-06-01', 'first_name': 'Duangkaew', 'last_name': 'Piveteau', 'gender': 2, 'hire_date': '1989-08-24'}
[OrderedDict([('emp_no', 10020), ('birth_date', '1952-12-24'), ('first_name', 'Mayuko'), ('last_name', 'Warwick'), ('gender', 1), ('hire_date', '1991-01-26')])] <class 'rest_framework.utils.serializer_helpers.ReturnList'>


  • 单实例,Employee Instance=>dict
  • 多实例,many=True ,QuerySet=>list ,列表中每一个Employee Instance =>dict
反序列化

字典中数据检验,为入库做准备

# 浏览器POST数据为JSON字符串,被DRF request进一步封装成dict
dict = {'emp_no': 10010,
        'birth_date': '1963-06-01',
        'first_name': 'Duangkaew',
        'last_name': 'Piveteau',
        'gender': '2',  # 这里故意写成字符串
        'hire_date': '1989-08-24'} #注意,这是字典数据是data,而不是Employee的实例
#反序列化
serializer = EmployeeSerializer(data=dict)  #注意参数不是instance,而是data
#print(serializer.data) 没有调用is_valid()直接报错,客户端提交数据不可信,第一步需要先验证
#is_valid方法调用内部成功会把数据放在_validated_data属性
#raise_exception验证失败是否抛异常
serializer.is_valid(raise_exception=False)
data = serializer.data # 这个data是返回给客户端的数据
print(data,type(data))
validate_data = serializer.validated_data #这个是校验后准备入库的数据
print(validate_data)
#输出
{'emp_no': 10010, 'birth_date': '1963-06-01', 'first_name': 'Duangkaew', 'last_name': 'Piveteau', 'gender': 2, 'hire_date': '1989-08-24'} <class 'rest_framework.utils.serializer_helpers.ReturnDict'>
OrderedDict([('emp_no', 10010), ('birth_date', datetime.date(1963, 6, 1)), ('first_name', 'Duangkaew'), ('last_name', 'Piveteau'), ('gender', 2), ('hire_date', datetime.date(1989, 8, 24))])

gender给个3试一试?报错了,因为Employee.Gender.choices是定义的约束,校验失败,如下图

image

校验

对浏览器端提交的数据一定要校验,所以,校验是入库前必须要做的

字段选项参数校验
  • 字符串长度
    • 长度测试min_length和max_length

image

    first_name = serializers.CharField(label='长度限制和必须',max_length=14,min_length=3)
注意:异常返回的[]列表,说明一个字段可以有多个校验器,这和前端开发校验一样
  • 字段值是否必须
    • require=True,默认值,序列化、反序列化都必须提供,否则报错,缺失时可以使用default提供缺省值
    • require=False
      • 反序列化时,字段值提供就要校验,不提供就不校验,校验成功就放到validated_data,校验该字段失败抛异常
      • 序列化时,有则输出,如果有default,缺失时使用缺省值补充
    • default=’abcd’,序列化、反序列化,缺失该字段就补充缺省值
Text
last_name = serializers.CharField(max_length=16,default='abb')

image

image

  • 只读
    • read_only不校验该字段,只会出现在序列化中,有也行,没有也行,有就输出给人看
    • read_only和require不可以同时为True,这是矛盾的
    • read_only=True表示序列化时可以序列化t2这个属性的数据,反序列化不使用它。反序列化时提供了该值也不校验也不输出,serializer.data输出结果中没有t2
t2 = serializers.CharField(read_only=True)
  • 只写
    • write_only=True,表示反序列化用,要校验该字段。不会被序列化,不返回给浏览器
    • .data属性中没有,要在.validated_data属性查看,因为入库用的时validated_data
t3 = serializers.CharField(write_only=True)

对序列化器略作修改做测试,这个测试主要测试的时反序列化的校验

from rest_framework import serializers
from .models import Employee
class EmpSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField(max_length=14)
    last_name = serializers.CharField(max_length=16)
    gender = serializers.ChoiceField(choices=Employee.Gender.choices)
    hire_date = serializers.DateField()
    t1 = serializers.CharField(label="长度限制和必须", min_length=4, 
max_length=8)
    t2 = serializers.CharField(read_only=True)
    t3 = serializers.CharField(write_only=True)
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
# 所有测试代码,都要在上面4行之下
from employee.models import Employee
from employee.serializers import EmpSerializer
emgr = Employee.objects
# 单个对象
emp = emgr.get(pk=10010)
print(emp.__dict__)
# 为序列化凑几个字段
emp.t1 = 't1' # 长度为2
emp.t2 = 't2'
emp.t3 = 't3'
# 给实例准备序列化为Json字符串
serializer = EmpSerializer(instance=emp)
data = serializer.data # 序列化
print(data) # 字典

序列化结果如下:

  • 序列化时,不校验t1
  • t2是read_only,所以有它
  • t3是write_only,不参与序列化,所以没有它
{'emp_no': 10010, 'birth_date': '1963-06-01', 'first_name': 'Duangkaew', 
'last_name': 'Piveteau', 'gender': 2, 'hire_date': '1989-08-24', 't1': 't1', 
't2': 't2'}
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
# 所有测试代码,都要在上面4行之下
from employee.models import Employee
from employee.serializers import EmpSerializer
emgr = Employee.objects
# POST方法提交上来的数据被request封装为字典
data = {
    'emp_no': 10010, 'birth_date': '1963-06-01',
    'first_name': 'Duangkaew', 'last_name': 'Piveteau',
    'gender': "2", 'hire_date': '1989-08-24',
    't1':'abcd',
    't2':'t2++', # t2给不给无所谓
    't3':'t3=='
} # 注意,这是字典数据是data,不是Employee的实例
serializer = EmpSerializer(data=data)
# print(serializer.data) # 没有调用.is_valid()直接报错,第一步需要验证
validated = serializer.is_valid(raise_exception=True)
# is_valid方法调用内部成功会把数据放在_validated_data属性
# raise_exception验证失败是否抛异常
print(validated)
print(serializer.data)
print(serializer.validated_data)

反序列化结果如下:

  • 校验t1
  • t2是read_only,所以没有它
  • t3是write_only,反序列化必须有它,还要校验它
{'emp_no': 10010, 'birth_date': datetime.date(1963, 6, 1), 'first_name': 
'Duangkaew', 'last_name': 'Piveteau', 'gender': 2, 'hire_date': 
datetime.date(1989, 8, 24), 't1': 'abcd', 't3': 't3=='}
字段级校验器

参考: https://www.django-rest-framework.org/api-guide/serializers/#field-level-validation

看官网,非常清楚

image

image

from rest_framework import serializers
from .models import Employees
class EmployeeSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField() ##使用单独字段的校验器
    last_name = serializers.CharField(max_length=16,default='abb')
    gender = serializers.ChoiceField(Employees.Gender.choices)
    hire_date = serializers.DateField()
    def validate_first_name(self, value):
        """
        Check that the blog post is about Django.
        """
        print(value,type(value),len(value))
        if len(value) < 3 or len(value) >14:
            raise serializers.ValidationError("长度必须大于3,小于14")
        return value

注意:value可以在这里被替换成其他值

还用一种写法validators=[validate_first_name]

def validate_first_name( value):
    print(value, type(value), len(value))
    if len(value) < 3 or len(value) > 14:
        return 'abcd'
        # raise serializers.ValidationError("长度必须大于3,小于14")
    return value
class EmployeeSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField(validators=[validate_first_name])
    last_name = serializers.CharField(max_length=16,default='abb')
    gender = serializers.ChoiceField(Employees.Gender.choices)
    hire_date = serializers.DateField()

除非有复用的必要,不要把校验器定义在外部

import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
# 所有测试代码,都要在上面4行之下
from employee.models import Employee
from employee.serializers import EmpSerializer
emgr = Employee.objects
# POST方法提交上来的数据被request封装为字典
data = {
    'emp_no': 10010, 'birth_date': '1963-06-01',
    'first_name': 'Duangkaew', 'last_name': 'Piveteau',
    'gender': "2", 'hire_date': '1989-08-24',
} # 注意,这是字典数据是data,不是Employee的实例
serializer = EmpSerializer(data=data)
# print(serializer.data) # 没有调用.is_valid()直接报错,第一步需要验证
validated = serializer.is_valid(raise_exception=True)
# is_valid方法调用内部成功会把数据放在_validated_data属性
# raise_exception验证失败是否抛异常
print(validated)
print(serializer.data)
print(serializer.validated_data)
对象级校验器

参考https://www.django-rest-framework.org/api-guide/serializers/#object-level-validation

image

对象级校验器就是对实例所有字段数据的校验

class EmployeeSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField()
    last_name = serializers.CharField(max_length=16,default='abb')
    gender = serializers.ChoiceField(Employees.Gender.choices)
    hire_date = serializers.DateField()
    def validate(self, data):

        print('_______',data)
        if data['birth_date'] > data['hire_date']:
            raise serializers.ValidationError("finish must occur after start")
        return data

入库

参考: https://www.django-rest-framework.org/api-guide/serializers/#saving-instances

校验后的数据是安全的,可以写入数据库。

原理

BaseSerializer中定义了save()方法

  • save()之前一定要调用.is_valid()
  • BaseSerializer(instance=None,data=empty),无实例就是新增,调用create;有实例就是更新,调用update。最终返回实例
  • 使用的是validated_data
  • 在BaseSerializer中,create、update是未实现的抽象方法,Serializer也没有实现这2个方法。所以就需要用户自己实现。
from rest_framework import serializers
from .models import Employees

class EmployeeSerializer(serializers.Serializer):
    emp_no = serializers.IntegerField()
    birth_date = serializers.DateField()
    first_name = serializers.CharField()
    last_name = serializers.CharField(max_length=16,default='abb')
    gender = serializers.ChoiceField(Employees.Gender.choices)
    hire_date = serializers.DateField()
# 序列化器的基类只负责调用create或update,但未实现
    def create(self, validated_data):
        # validated_data校验过的数据
        # 管理器实现了create方法完成数据新增
        return Employees.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.birth_date = validated_data.get('birth_date', instance.birth_date)
        instance.first_name = validated_data.get('first_name', instance.first_name)
        instance.last_name = validated_data.get('last_name', instance.last_name)
        instance.gender = validated_data.get('gender', instance.gender)
        instance.hire_date = validated_data.get('hire_date', instance.hire_date)

        return instance

注意:序列化器只负责实现、调用save、create、update等方法,但是数据库的CRUD操作依然是Model类的objects完成的

import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
# 所有测试代码,都要在上面4行之下
from employee.models import Employee
from employee.serializers import EmpSerializer
emgr = Employee.objects
# POST方法提交上来的数据被request封装为字典
data = {
    'emp_no': 10021, 'birth_date': '1963-06-01',
    'first_name': 'san', 'last_name': 'zhang',
    'gender': "1", 'hire_date': '1989-08-24',
} # 注意,这是字典数据是data,不是Employee的实例
# 只有data没有实例,是新增
serializer = EmpSerializer(data=data)
validated = serializer.is_valid(raise_exception=True)
print(serializer.save())
print('=' * 30)
print(serializer.data)

先查再改:先查返回结果填充实例,然后根据这个实例修改数据

import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'salary.settings')
django.setup(set_prefix=False)
# 所有测试代码,都要在上面4行之下
from employee.models import Employee
from employee.serializers import EmpSerializer
emgr = Employee.objects
emp = emgr.get(pk=10021)
print(emp)
# POST方法提交上来的数据被request封装为字典
data = {
    'emp_no': 10021, 'birth_date': '1963-06-01',
    'first_name': 'si', 'last_name': 'li',
    'gender': 1, 'hire_date': '1989-08-24',
} # 注意,这是字典数据是data,不是Employee的实例
# 有实例,有data,是更新
serializer = EmpSerializer(emp, data=data)
validated = serializer.is_valid(raise_exception=True)
print(serializer.save())
print('=' * 30)
print(serializer.data)

修改或增加完后,数据库中对应的记录就会被增加或更改了

总结

  • 序列化
    • 查库:查询数据库得到单个实例或多个实例
    • 构造序列化器实例:构造序列化器实例
    • 输出:使用data属性获取字典
  • 反序列化
    • 反序列化:将JSON数据反序列化为字典
    • 构造序列化器实例:使用字典构造序列化器实例
    • 校验:调用序列化器的is_valid方法校验各个字段的值
    • 入库:调用序列化器的save方法,背后调用了序列化器的create、update方法,本质上用的是Model类写库