使用序列化器进行反序列化时,需要对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。

在获取反序列化的客户端数据前,必须在视图中调用序列化对象的is_valid()方法,序列化器内部是在is_valid方法内部调用验证选项和验证方法进行验证,验证成功返回True,否则返回False。

验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误提示。如果是非字段错误,可以通过修改REST framework配置中的NON_FIELD_ERRORS_KEY来控制错误字典中的键名。

验证成功,可以通过序列化器对象的validated_data属性获取数据。

在定义序列化器时,指明每个字段的序列化类型和选项参数,本身就是一种验证行为。

1、准备工作

注册一个图书app及图书表模型

python manage.py startapp unsers

在配置文件setting.py中注册子应用

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',  # 把drf框架注册到django项目中
    'unsers',  # 演示反序列化
]

注释csrf校验,因为提交数据涉及到post方法提交数据,把settings.py中的中间件的csrf暂时关闭

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

创建表模型

from django.db import models


# Create your models here.
class BookInfo(models.Model):
    """图书信息"""
    title = models.CharField(max_length=20, verbose_name='标题')
    pub_date = models.DateField(verbose_name='发布日期')
    # 设置存储文件的子目录为avatar,总目录不写的话是在settings中配置,不填则没有
    image = models.ImageField(upload_to="avatar", verbose_name='图书封面')
    price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="价格")
    read = models.IntegerField(verbose_name='阅读量')
    comment = models.IntegerField(verbose_name='评论量')

    class Meta:
        # db_table = "表名"
        db_table = "tb_book_info"
        verbose_name = "图书"
        verbose_name_plural = verbose_name

注意:因为当前模型中, 设置到图片上传处理,运行起来后会有提示,所以需要安装PIL

pip3 install Pillow

执行数据迁移

python3 manage.py makemigrations
python3 manage.py migrate

2、字段验证

经过上面的准备工作,接下来就可以给图书信息增加图书的功能,需要对来自客户端的数据进行处理,例如,验证和保存到数据库中。此时,就可以使用序列化器的反序列化器,接下来,定义一个图书的序列化器,此序列化器主要用于反序列化器阶段,在unsers子应用,创建serializers.py,代码如下

from rest_framework import serializers

class BookInfoSerializer(serializers.Serializer):
    # 这里声明的字段用于进行反序列化器
    # 字段名 = serializers.字段类型(验证选项)
    # read_only=True,设置id为只读字段,当字段设置为read_only为True,则当前字段只会在序列化阶段使用
    id = serializers.IntegerField(read_only=True)
    title = serializers.CharField(required=True, min_length=1, max_length=20, label="标题", help_text="标题", error_messages={
        "required": "标题不能为空!",
        "max_length": "标题不能超过6个字符",
    })
    # required=True 当前字段必填
    # write_only=True 表示当前字段只会在反序列化阶段使用,客户端提交数据的时候使用,不会提供给客户端
    pub_date = serializers.DateField(required=True,label="发布日期", help_text="发布日期")
    price = serializers.DecimalField(max_digits=8, decimal_places=2, required=True, label="价格", help_text="价格")
    read  = serializers.IntegerField(min_value=0, default=0, label="阅读量", help_text="阅读量")
    comment = serializers.IntegerField(min_value=0, default=0, label="评论量", help_text="评论量")

通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证,编写视图类如下

# Create your views here.
from django.views import View
from .models import BookInfo
from django.http.response import JsonResponse
from .serializers import BookInfoSerializer


class BookInfoView(View):
    def post(self, request):
        """反序列化,验证和添加数据"""
        # 接收并实例化序列化器对象
        serializer = BookInfoSerializer(data=request.POST)
        # 启动验证
        # is_valid 有个可选参数raise_exception,用于显示序列化器抛出的异常,直接终止视图代码的执行
        # 如果设置了raise_exception=True,则下面的18~21行代码,就不要开发者自己编写,系统会自动根据请求的方式自动返回错误给客户端。
        # 如果是ajax请求,则自动返回json格式的错误信息
        # 如果是表单请求,则自动返回html格式的错误信息
        result = serializer.is_valid()
        # result = serializer.is_valid(raise_exception=True)
        print(result)  # 验证结果,True表示验证通过了,开发时一般不需要接收
        if not result:
            # 当验证失败,则错误信息属性就有内容
            print(serializer.errors)
            return JsonResponse(serializer.errors)
        else:
            # 获取验证完成后的客户端数据 如果验证失败,则vcalidated_data是空字典
            print(serializer.validated_data)
            # 把数据保存到数据库中
            instance = BookInfo.objects.create(**serializer.validated_data)
            # instance = serializer.create(serializer.validated_data)
            # 序列化器实例化时,如果有save参数,则save相当于update,否则就是create
            # instance = serializer.save()
            # 返回结果,也是需要使用序列化进行转换的
            serializer = BookInfoSerializer(instance=instance)
            return JsonResponse(serializer.data)

注册url

from django.urls import path
from . import views
urlpatterns = [
    path("books/", views.BookInfoView.as_view()),
]

利用postman测试向此接口提交数据

此时查看数据库中的记录,已经成功被写入

3、validate_字段名验证

<field_name>字段进行验证,在序列化器中编写如下内容:

def validate_title(self, data):
    # 验证单个字段时,方法名必须固定为validate_字段,这里的data代表的就是字段值,
    if "测试" in data:
        """抛出异常"""
        raise serializers.ValidationError("对不起,当前标题不能出现关键字")

    # 验证方法必须要有返回值,这里的返回值将会被填写到 serailzier对象的validated_data里面
    return data  # 验证通过以后,必须要返回验证的结果数据,否则序列化器的validated_data无法得到当前字段的结果

利用postman测试向此接口提交数据

4、validate验证

在序列化器中需要同时对多个字段进行比较验证时,可以定义validate方法来验证

def validate(self, data):
    """验证多个字段时,方法名必须为validate,
    参数data代表了所有字段的数据值,其实就是视图代码中实例化序列化器对象时的data参数
    开发中,类似 密码和确认密码,此时这2个字段,必须进行比较才能通过验证
    """
    print(data)
    # 例如,我们要求图书的评论必须比阅读量要少
    read = data.get("read")
    comment = data.get("comment")
    if read < comment:
        raise serializers.ValidationError("对不起,阅读量必须比评论量大")

    # 验证密码和确认密码
    # 验证方法必须要有返回值
    return data

利用postman测试向此接口提交数据

5、validators验证器验证

验证器类似于验证方法,但是验证方法只属于当前序列化器,如果有多个序列化器共用同样的验证功能,则可以把验证代码分离到序列化器外部,作为一个普通函数,由validators加载到序列化器中使用。

在字段中添加validators选项参数,也可以补充验证行为,如下

# 在序列化器的外面声明一个验证函数
def check_price(data):  # data代表要验证的数据
    if data < 0:
        raise serializers.ValidationError("对不起,价格不能出现负数")
    # 验证函数也必须把数据返回
    return data
    
...
class BookInfoSerializer(serializers.Serializer):
...
    # 调用验证器validators,这里的参数是一个列表,列表的成员是函数,函数名不能加引号
    # price = serializers.DecimalField(required=True, max_digits=8, decimal_places=2)
    price = serializers.DecimalField(required=True, max_digits=8, decimal_places=2, validators=[check_price])

利用postman测试向此接口提交数据

6、小结

is_valid实际上内部执行了三种不同的验证方式:

  • 先执行了字段内置的验证选项
  • 在执行了validators自定义选项
  • 最后执行了validate自定义验证方法[包含了validate_<字段>, validate]

附:常用字段和参数

常用字段类型

字段 字段构造方式
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_lenght 最小长度[适用于字符串,列表,文件]
allow_blank 是否允许数据的值为空,如果使用这个选项,则前端传递过来的数据必须有这个属性。
trim_whitespace 是否截断空白字符
max_value 【数值】最小值
min_value 【数值】最大值

通用参数:

参数名称 说明
read_only 表明该字段仅用于序列化输出,默认False
write_only 表明该字段仅用于反序列化输入,默认False
required 表明该字段在反序列化时必须输入,默认True
default 反序列化时使用的默认值
allow_null 表明该字段是否允许传入None,默认False
validators 该字段使用的验证器
error_messages 包含错误编号与错误信息的字典
label 用于HTML展示API页面时,显示的字段名称
help_text 用于HTML展示API页面时,显示的字段帮助提示信息