ModelSerializer 及 Validation自定义验证逻辑

serializers.fieild

我们知道在django中,form也有许多field,那serializers其实也是drf中发挥着这样的功能。我们先简单了解常用的几个field。

1. 常用的field

CharField、BooleanField、IntegerField、DateTimeField这几个用得比较多,我们把外键的field放到后面去说!

# 举例子
mobile = serializers.CharField(max_length=11, min_length=11)
age = serializers.IntegerField(min_value=1, max_value=100)
# format可以设置时间的格式,下面例子会输出如:2018-1-24 12:10
pay_time = serializers.DateTimeField(read_only=True,format='%Y-%m-%d %H:%M')
is_hot = serializers.BooleanField()

不同的是,我们在django中,form更强调对提交的表单进行一种验证,而serializer的field不仅在进行数据验证时起着至关重要的作用,在将数据进行序列化后返回也发挥着重要作用!!
  我们可以看出,不同的field可以用不同的关键参数,除此之外,还有一些十分重要有用的参数。

2. Core arguments参数

read_only:True

表示不允许用户自己上传,只能用于api的输出。如果某个字段设置了read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回
  举个简单的例子:在用户进行购物的时候,用户post订单时,肯定会产生一个订单号,而这个订单号应该由后台逻辑完成,而不应该由用户post过来,如果不设置read_only=True,那么验证的时候就会报错。

order_sn = serializers.CharField(readonly=True)

write_only: 与read_only对应

required: 顾名思义,就是这个字段是否必填。

allow_null/allow_blank:是否允许为NULL/空 。

error_messages:出错时,信息提示。

name = serializers.CharField(required=True, min_length=6,
                error_messages={
                    'min_length': '名字不能小于6个字符',
                    'required': '请填写名字'})

label: 字段显示设置,如 label='验证码'

help_text: 在指定字段增加一些提示文字,这两个字段作用于api页面比较有用

style: 说明字段的类型,这样看可能比较抽象,看下面例子:

# 在api页面,输入密码就会以*显示
password = serializers.CharField(
    style={'input_type': 'password'})
# 会显示选项框
color_channel = serializers.ChoiceField(
    choices=['red', 'green', 'blue'],
    style={'base_template': 'radio.html'})
  这里面,还有一个十分有用的validators参数,这个我们会在后面提及!

3. HiddenField

HiddenField的值不依靠输入,而需要设置默认的值,不需要用户自己post数据过来,也不会显式返回给用户,最常用的就是user!!
  我们在登录情况下,进行一些操作,假设一个用户去收藏了某一门课,那么后台应该自动识别这个用户,然后用户只需要将课程的id post过来,那么这样的功能,我们配合CurrentUserDefault()实现。

# 这样就可以直接获取到当前用户
user = serializers.HiddenField(
    default=serializers.CurrentUserDefault())

ModelSerializer

在大多数情况下,我们都是基于model字段去开发。

好处:

ModelSerializer已经重载了create与update方法,它能够满足将post或patch上来的数据进行进行直接地创建与更新,除非有额外需求,那么就可以重载create与update方法。
  ModelSerializer在Meta中设置fields字段,系统会自动进行映射,省去每个字段再写一个field。

class UserDetailSerializer(serializers.ModelSerializer):
    """
    用户详情序列化
    """
    class Meta:
        model = User
        fields = ("name", "gender", "birthday", "email", "mobile")
        # fields = '__all__': 表示所有字段
        # exclude = ('add_time',):  除去指定的某些字段
        # 这三种方式,存在一个即可1234567891011

ModelSerializer需要解决的2个问题:

1,某个字段不属于指定model,它是write_only,需要用户传进来,但我们不能对它进行save( ),因为ModelSerializer是基于Model,这个字段在Model中没有对应,这个时候,我们需要重载validate!
如在用户注册时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个Model中:

    def validate(self, attrs):
        del attrs["code"]
        return attrs123

2,某个字段不属于指定model,它是read_only,只需要将它序列化传递给用户,但是在这个model中,没有这个字段!我们需要用到SerializerMethodField。
  假设需要返回用户加入这个网站多久了,不可能维持这样加入的天数这样一个数据,一般会记录用户加入的时间点,然后当用户获取这个数据,我们再计算返回给它。

class UserSerializer(serializers.ModelSerializer):  
    days_since_joined = serializers.SerializerMethodField()
    # 方法写法:get_ + 字段
    def get_days_since_joined(self, obj):
    # obj指这个model的对象
        return (now() - obj.date_joined).days

    class Meta:
        model = User123456789

Validation自定义验证逻辑

单独的validate

我们在上面提到field,它能起到一定的验证作用,但很明显,它存在很大的局限性,举个简单的例子,我们要判断我们手机号码,如果使用CharField(max_length=11, min_length=11),它只能确保我们输入的是11个字符,那么我们需要自定义!

mobile_phone = serializers.CharField(max_length=11, min_length=11)

def validate_mobile_phone(self, mobile_phone):
    # 注意参数,self以及字段名
    # 注意函数名写法,validate_ + 字段名字
    if not re.match(REGEX_MOBILE, mobile):
    # REGEX_MOBILE表示手机的正则表达式
        raise serializers.ValidationError("手机号码非法")
    return mobile_phone123456789

当然,这里面还可以加入很多逻辑,例如,还可以判断手机是否原本就存在数据库等等。

联合validate

上面验证方式,只能验证一个字段,如果是两个字段联合在一起进行验证,那么我们就可以重载validate( )方法。

    start = serializers.DateTimeField()
    finish = serializers.DateTimeField()

    def validate(self, attrs):
    # 传进来什么参数,就返回什么参数,一般情况下用attrs
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return attrs12345678

这个方法非常的有用,我们还可以再这里对一些read_only的字段进行操作,我们在read_only提及到一个例子,订单号的生成,我们可以在这步生成一个订单号,然后添加到attrs这个字典中。

order_sn = serializers.CharField(readonly=True)
def validate(self, attrs):
    # 调用一个方法生成order_sn
    attrs['order_sn'] = generate_order_sn()
    return attrs12345

这个方法运用在modelserializer中,可以剔除掉write_only的字段,这个字段只验证,但不存在与指定的model当中,即不能save( ),可以在这delete掉!

Validators

validators可以直接作用于某个字段,这个时候,它与单独的validate作用差不多

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])123456

当然,drf提供的validators还有很好的功能:UniqueValidator,UniqueTogetherValidator等
  UniqueValidator: 指定某一个对象是唯一的,如,用户名只能存在唯一:

username = serializers.CharField(
        max_length=11, 
        min_length=11,
        validators=[UniqueValidator(queryset=UserProfile.objects.all())
    )12345

UniqueTogetherValidator: 联合唯一,如用户收藏某个课程,这个时候就不能单独作用于某个字段,我们在Meta中设置。

    class Meta:
        validators = [
            UniqueTogetherValidator(
                queryset=UserFav.objects.all(),
                fields=('user', 'course'),
                message='已经收藏'
            )]

关于外键的serializers

其实,外键的field也比较简单,如果我们直接使用serializers.Serializer,那么直接用PrimaryKeyRelatedField就解决了。
  假设现在有一门课python入门教学(course),它的类别是python(catogory)。

# 指定queryset
category = serializers.PrimaryKeyRelatedField(queryset=CourseCategory.objects.all(), required=True)

ModelSerializer就更简单了,直接通过映射就好了
  不过这样只是用户获得的只是一个外键类别的id,并不能获取到详细的信息,如果想要获取到具体信息,那需要嵌套serializer

category = CourseCategorySerializer()

注意:上面两种方式,外键都是正向取得,下面介绍怎么反向去取,如,我们需要获取python这个类别下,有什么课程。  首先,在课程course的model中,需要在外键中设置related_name

class Course(model.Model):
    category = models.ForeignKey(CourseCategory, related_name='courses')
# 反向取课程,通过related_name
# 一对多,一个类别下有多个课程,一定要设定many=True
courses = CourseSerializer(many=True)

写到这里,外键就基本讲完了!还有一个小问题:我们在上面提到ModelSerializer需要解决的第二个问题中,其实还有一种情况,就是某个字段属于指定model,但不能获取到相关数据。
  假设现在是一个多级分类的课程,例如,编程语言-->python-->python入门学习课程,编程语言与python属于类别,另外一个属于课程,编程语言类别是python类别的一个外键,而且属于同一个model,实现方法:

parent_category = models.ForeignKey('self', null=True, blank=True, 
                    verbose_name='父类目别',
                    related_name='sub_cat')

现在获取编程语言下的课程,显然无法直接获取到python入门学习这个课程,因为它们两没有外键关系。SerializerMethodField( )也可以解决这个问题,只要在自定义的方法中实现相关的逻辑即可!

courses = SerializerMethodField()
def get_courses(self, obj):
    all_courses = Course.objects.filter(category__parent_category_id=obj.id)
    courses_serializer = CourseSerializer(all_course, many=True, 
                    context={'request': self.context['request']})
    return courses_serializer.data

上面的例子看起来有点奇怪,因为我们在SerializerMethodField()嵌套了serializer,就需要自己进行序列化,然后再从data就可以取出json数据。
  可以看到传递的参数是分别是:queryset,many=True多个对象,context上下文。这个context十分关键,如果不将request传递给它,在序列化的时候,图片与文件这些Field不会再前面加上域名,也就是说,只会有/media/img...这样的路径!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • HTML表单 在HTML中,表单是 ... 之间元素的集合,它们允许访问者输入文本、选择选项、操作对象等等,然后将...
    兰山小亭阅读 3,417评论 2 14
  • 1. 什么是Struts2 验证器 一个健壮的 web 应用程序必须确保用户输入是合法、有效的. Struts2 ...
    MPPC阅读 1,085评论 0 5
  • Serializers 序列化器允许将诸如查询集和模型实例之类的复杂数据转换为原生 Python 数据类型,然后可...
    lkning阅读 1,023评论 0 1
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架,建立于...
    Hsinwong阅读 22,392评论 1 92
  • 沉默文/荔园默默 围着茶壶傍着清香我们忘却了沉默 我们分别时水的声音逃走我们各自回归寂静 2017.1.5 22:55
    荔园默默阅读 339评论 8 19