13、Django_rest framework_序列化器和自定义校验

一、序列化器中的类属性字段
  • 序列化中所定义的类属性字段,一般情况下与模型类字段相对应
  • 默认情况下,这些类属性字段既可以进行序列化输出,也可以进行反序列化输入
  • 不需要输入(反序列化)、输出(序列化)的字段,则不需要定义
  • 只需要反序列化输入,则定义write_only = True
  • 只需要序列化输出,则定义read_only = True
  • 响应的参数如果是多个查询集,需要在JsonResponse()中传参many=True
二、类属性字段的参数
  • 1.通用参数
    read_only = True:当前字段只能进行序列化输出(用于不需要反序列化输入的字段)
    write_only = True:当前字段只能进行反序列化输入,不进行序列化输出
    required = True:在反序列化时是必填字段,默认为True
    allow_null = False:当前字段是否允许传None,默认是False(必填字段False,反之则True)
    allow_blank = False:当前字段是否运行为空,默认是False(必填字段False,反之则True)
    default = xx:指定在反序列化时,当前字段没值时所使用的默认值
    validators=校验器:当前字段所使用的校验器(下面介绍常用的)
    error_messages:包含错误key-value的字段(下面会举例)
    label:当前字段在前端的api页面中所显示的字段名称
  • 2.选项参数
    max_length:字段最大长度
    min_length:字段最小长度
    trim_whitespace:是否截断空字符串
    max_value:字段最大值
    min_value:字段最小值
三、反序列化_校验机制
  • 调用序列化器对象的is_valid()方法,校验前端参数的正确性,不调用则不校验
  • 校验成功返回True、校验失败返回False
  • is_valid(raise_exception = True):校验失败后,则抛出异常
  • 当调用is_valid()之后,才能调用序列化器对象的errors属性,内容为校验的错误提示(dict)
  • views.py中,如果传参进行了输入反序列化的话,那么需要调用的是经过校验后的数据,比如说新增数据,应该是:xxx类.objects.create(**serializer.validated_data)
    try:
        # 不符合规则则抛出错误
        serializer.is_valid(raise_exception=True)  
    except Exception as e:
        # 返回序列化器原有的dict类型的错误信息
        return JsonResponse(serializer.errors)
四、rest_framework的校验器(用于validators=校验器

rest_framework.framework中有有多个验证器,这里用其中一个最常见的UniqueValidator唯一验证器示例,更多见:https://juejin.im/post/5aa93246518825555c1d5bba

  • UniqueValidator唯一校验器
    该验证器可用于在模型字段上强制实施 unique=True 约束。它需要一个必需的参数和一个可选的 messages 参数:
    1.queryset 必须 - 这是校验唯一性的查询集,一般是模型类.objects.all()
    2.message - 校验失败时使用的错误消息
    3.lookup - 用于查找已校验值的现有实例。默认为 'exact'

  • 例子:修改之前的serializer.py,对name字段使用UniqueValidator唯一校验器

from rest_framework import serializers
# from login.serializer import UserModelSerializer
from rest_framework.viewsets import ModelViewSet
from login.models import User
from rest_framework.validators import UniqueValidator


class UserSerializer(serializers.Serializer):
  """
    该验证器可用于在模型字段上强制实施 unique=True 约束。它需要一个必需的参数和一个可选的 messages 参数:
                    queryset 必须 - 这是验证唯一性的查询集。
                    message - 验证失败时使用的错误消息。
                    lookup - 用于查找已验证值的现有实例。默认为 'exact'
    """
    name = serializers.CharField(label='用户名', max_length=128, min_length = 10, help_text='用户名',
                                 validators=[
                                     UniqueValidator(
                                         queryset=User.objects.all(),
                                         message="该用户名已存在"
                                     )
                                 ],
                                        error_messages = {
                                            ## 键值对,对应上面的限制条件,以及对应的提示
                                            "max_length":"最长128个字符",
                                            "min_length ":"最短10个字符"
                                        }
)
    password = serializers.CharField(label='账号密码', max_length=256, help_text='账号密码', )
    email = serializers.EmailField(label='账号密码', max_length=128, help_text='账号密码',
                                   allow_null=True, allow_blank=True, default='')
    sex = serializers.CharField(label='账号密码', max_length=128, help_text='账号密码', )
    c_time = serializers.DateTimeField(label='创建时间', help_text='创建时间', read_only=True)
  • 结果
    1.这里使用httpie工具的一个传输json文件为参数的技巧
    a.创建一个名为user.json的文件,内容如下:
{
  "name": "张三",
  "password": "qw123ssd的",
  "email": "23423@163.com",
  "sex": "男"
}

b.在项目路径使用httpie的命令:http POST http://127.0.0.1:8000/users/ < test/user.json
c.结果:

name重复的结果

五、自定义校验器
  • 注意
    1.如果自定义校验器中定义的字段,是在所调用的序列化类中所调用的模型类是没有定义的字段。那么就是相当于在模型类的基础上对字段进行了拓展,并且要在class Metafields = ('拓展字段',)中定义上,不然就会报错(关于ModelSerializer下篇讲)
    2.如果一个字段有两个校验器,分别是框架自带的、自定义的,那么会优先校验框架自带的,如果框架自带的没通过,还会再去校验自定义的校验器,但是不会去进行单字段校验和联合字段的校验;如果两个校验器都通过了,才会去进行单字段校验和联合字段的校验
  • views.py的序列化类上面,添加一个自定义校验器函数。
    比如校验用户名需要含有'用户'两个字:
def is_unique_user_name(name):
    if '用户' not in name:
        raise serializers,ValidationError('用户名称必须含有“用户”')
    else:
        return name

然后在下面的序列化类中这样调用(看注释):

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from login.models import User
from rest_framework.validators import UniqueValidator

class UserSerializer(serializers.Serializer):

    name = serializers.CharField(label='用户名', max_length=128, help_text='用户名',
                                 validators=[
                                     UniqueValidator(
                                         queryset=User.objects.all(),
                                         message="该用户名已存在"
                                     ), 
                                         is_unique_user_name  # 在这里调用自定义校验器
                                 ])
### 下面内容省略
六、自定义校验字段级别单个字段的校验

如果想要对某个特定的字段机械能自定义的验证规则,那么可以在所写的Serializer子类中,添加validate_<filed_name>的方法来指定自定义该字段的验证

  • validate_<filed_name>方法需要返回一个验证过的数据,或者抛出一个serializers.ValidationError异常
  • 例子,修改serializer.py文件
from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from login.models import User
from rest_framework.validators import UniqueValidator


class UserSerializer(serializers.Serializer):

    # 自定义字段级别的验证
    def validate_name(self, value):
        """
        用户名需要以“用户”开头
        :return:
        """
        if not str(value).startswith('用户'):
            # 抛出erializers.ValidationError异常
            raise serializers.ValidationError(detail='用户名需要以用户两个字开头')
        # 返回一个验证过的数据
        else:
            return value

    ## 下面内容无需修改
    name = serializers.CharField(label='用户名', max_length=128, help_text='用户名',
                                 validators=[
                                     UniqueValidator(
                                         queryset=User.objects.all(),
                                         message="该用户名已存在"
                                     )
                                 ])
    password = serializers.CharField(label='账号密码', max_length=256, help_text='账号密码', )
    email = serializers.EmailField(label='账号密码', max_length=128, help_text='账号密码',
                                   allow_null=True, allow_blank=True, default='')
    sex = serializers.CharField(label='账号密码', max_length=128, help_text='账号密码', )
    c_time = serializers.DateTimeField(label='创建时间', help_text='创建时间', read_only=True)
  • 传不是“用户”开头的name:


    验证失败
  • 传以“用户”开头的name:(json数据中,email打错了,所以email值为空)


    验证成功,创建user
七、自定义验证字段级别多个字段的验证

单个字段的校验,走的是validate_<filed_name>方法
多个字段的组合校验,走的是validate方法

  • validate_<filed_name>方法一样,validate方法也是在在所写的Serializer子类中,添加validate方法方法,进行自定义多个字段的验证
  • 例子,在serializer.py文件中的Serializer子类添加这个方法,其他不变
    # 自定义多个字段的组合验证规则
    def validate(self, attrs):
        """
        password和email必需含有“lzl”这三个字母
        :return:
        """
        if "lzl" not in attrs['password'] or "lzl" not in attrs['email']:
            raise serializers.ValidationError(detail='password和email必需含有“lzl”这三个字母')
        else:
            return attrs

  • 传不符合规则的参数


    校验不通过
  • 传符合规则的参数


    校验通过,创建user
八、校验器的顺序
  • 顺序如下:
    1.首先是进行字段定义时所设置的限制条件(包含validators的列表条目),从左到右开始校验
    2.然后到单字段validators_filename方法的校验
    3.接着到多字段validators方法的校验
  • 校验失败的逻辑:
    1.不管前面的校验是否成功,“进行字段定义时所设置的限制条件(包含validators的列表条目)”的条件都将全部进行校验;如果都失败,则对应的失败提示都会进行响应
    2.如果步骤1的校验都失败,就不会进行单字段和多字段的校验
九、优化views.py的代码

在之前的代码中,操作数据库的代码是放在views方法中。那么,是否可以放到序列化器中呢?这样在调用序列化器时,就能跟着对数据库进行对应的操作,比如:createupdate操作

1.优化的前提,需了解
  • views.py的方法中,创建序列化器对象时,如果只传参data,那么在调用save()方法时,实际上调用的是序列化器对象的create()方法
  • views.py的方法中,创建序列化器对象时,如果传参datainstance,那么在调用save()方法时,实际上调用的是序列化器对象的update()方法
2.基于上面,可进行的优化

1.可重写序列化器对象的create()方法,从而优化views.py中的新增方法
2.可重写序列化器对象的update()方法,从而优化views.py中的修改方法

  • 1.重写序列化器对象UserSerializercreate()update()方法
    def create(self, validated_data):
        """
        新增user
        :param validated_data:
        :return:
        """
        return User.objects.create(**validated_data)

    # 如果在创建UserSerializer对象时,传参data和instance,则在调用save()方法时,
    # 实际调用的是序列化器对象中的update()方法
    def update(self, instance, validated_data):
        """
        更新user
        :param instance:
        :param validated_data:
        :return:
        """
        instance.name = validated_data['name']
        instance.password = validated_data['password']
        instance.email = validated_data['email']
        instance.sex = validated_data['sex']
        # instance.c_time = validated_data['c_time']  # 这个字段只读,不进行反序列化,所以注释
        instance.save()
        return instance
  • 2.优化views.py的新增和修改方法
    a.新增方法
class Users(View):

    def post(self, request):
        """V1.3"""
        user_dict = json.loads(request.body.decode('utf8'), encoding='utf8')
        # 实例化序列化器,只给data传值
        serializer = UserSerializer(data=user_dict)
        try:
            serializer.is_valid(raise_exception=True)
        except Exception as e:
            return JsonResponse(serializer.errors)
        serializer.save()  # 这里实际调用的是序列化器的create方法
        return JsonResponse(serializer.data, status=201)

b.修改方法

class UserDetail(View):


    def put(self, request, pk):
        """V1.2"""
        old_user = User.objects.get(id=pk)
        user_dict = json.loads(request.body.decode('utf8'), encoding='utf8')
        # 实例化序列化器,给instance和data传值(instance:需要修改的模型类对象、data:前端传来的修改数据,需进行反序列化和序列化)
        serializer = UserSerializer(instance=old_user, data=user_dict)
        try:
            serializer.is_valid(raise_exception=True)
        except Exception as e:
            raise JsonResponse(serializer.errors)
        serializer.save()  # 实际调用的是序列化器的update方法
        return JsonResponse(serializer.data, status=201)
  • 3.结果
    a.新增方法


    新增成功

    b.修改方法


    修改成功
十、最终代码

ps.这段代码是后来在文中复制粘贴添加的,可能有误

from rest_framework import serializers
from rest_framework.viewsets import ModelViewSet
from login.models import User
from rest_framework.validators import UniqueValidator

    # 自定义校验器
    def is_unique_user_name(name):
        if '用户' not in name:
            raise serializers,ValidationError('用户名称必须含有“用户”')
        else:
            return name

class UserSerializer(serializers.Serializer):
   
    # 自定义单字段级别的验证
    def validate_name(self, value):
        """
        用户名需要以“用户”开头
        :return:
        """
        if "lzl" not in attrs['password'] or "lzl" not in attrs['email']:
            raise serializers.ValidationError(detail='password和email必需含有“lzl”这三个字母')
        else:
            return attrs

    def validate(self, attrs):
        """
        多字段校验
        :param attrs:
        :return:
        """
        if 'tester' not in attrs['tester'] or 'leader' not in attrs['leader']:
            raise serializers.ValidationError('tester需要包含“tester”,leader需要包含“leader”')
        return attrs


    ## 下面内容无需修改
    name = serializers.CharField(label='用户名', max_length=128, help_text='用户名',
                                 validators=[
                                     UniqueValidator(
                                         queryset=User.objects.all(),
                                         message="该用户名已存在"
                                     )
                                 ], 
                                     is_unique_user_name  # 在这里调用自定义校验器
                                 )
    password = serializers.CharField(label='账号密码', max_length=256, help_text='账号密码', )
    email = serializers.EmailField(label='账号密码', max_length=128, help_text='账号密码',
                                   allow_null=True, allow_blank=True, default='')
    sex = serializers.CharField(label='账号密码', max_length=128, help_text='账号密码', )
    c_time = serializers.DateTimeField(label='创建时间', help_text='创建时间', read_only=True)

       自定义多个字段的组合验证规则
    def validate(self, attrs):
        """
        password和email必需含有“lzl”这三个字母
        :return:
        """
        if "lzl" not in attrs['password'] or "lzl" not in attrs['email']:
            raise serializers.ValidationError(detail='password和email必需含有“lzl”这三个字母')
        else:
            return attrs
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容