Django REST框架与模型验证

27 投票
2 回答
10086 浏览
提问于 2025-04-18 02:57

我在使用django-rest-framework时刚开始遇到了一些关于验证的问题。

我有一个基本的模型,并且对其中几个字段应用了验证器(一个普通的 MaxLengthValidator 和一个自定义的 RegexValidator,最后的代码大概是这样的:

class ZipCodeValidator(RegexValidator):
    regex = '^([0-9]{5})$'
    message = u'Invalid ZipCode.'


class User(AbstractUser, BaseUser):
    """
    Custom user model
    """

    # ... other fields ...
    zipcode = models.CharField(
        max_length=5, blank=True, validators=[ZipCodeValidator()]
    )
    description = models.TextField(
        null=True, blank=True, max_length=1000, validators=[MaxLengthValidator(1000)]
    )

然后我创建了一个与这个模型对应的 ModelSerializer,还添加了一些额外的字段和方法。所有这些都是通过一个非常简单的 `RetrieveUpdateAPIView` 来提供的。

我注意到这些验证器并没有被调用(我可以在邮政编码字段中输入任何内容,或者在描述中输入超过1000个字符)。

一个快速且简单的解决办法是,在序列化器层面重写这两个字段,并在那儿给它们分配验证器:

class UserSerializer(serializers.ModelSerializer):
    zipcode = serializers.WritableField(
        source='zipcode', required=False, validators=[ZipCodeValidator()]
    )
    description = serializers.WritableField(
        source='description', required=False, validators=[MaxLengthValidator(1000)]
    )

这样做是可以的,但我并不太喜欢。我更希望在模型层面进行验证,这样更安全(我不介意在序列化器上进行自定义或额外的验证,但这些规则需要在所有情况下都能执行)。因为序列化器的工作方式和django表单很像,我本以为它们在保存之前会调用模型的 clean 方法,但快速查看一下 源代码,似乎并没有这样做。

这让我有点烦恼,如果我想确保验证总是发生,就不得不重复很多字段的代码,我希望能尽量保持代码的简洁。

我可能漏掉了什么,但有没有一种简单而干净的方法,确保这些验证器会在更新模型之前被序列化器运行呢?

编辑:我仔细检查了一下源代码,发现实例的 full_clean 方法确实在视图中被调用,然后再保存到数据库,这样就会运行模型的验证器。不过,我还是搞不清楚为什么那些验证器似乎没有被执行。

2 个回答

2

其实,我觉得你提的第一个解决方案 @astrognocci,虽然看起来有点啰嗦,但对于 Django REST Framework 3.0 及以上版本来说,是个不错的选择。

实际上,.full_clean() 这个方法在 ModelSerializer 的验证过程中不再被调用了,具体可以参考 这篇文章

所以,写自定义的类验证器——可以在 ModelModelSerializer 中使用——看起来是个很合适的选择,这样可以避免重复代码,也能保持一致性。

3

这个对我有效:

class ZipCodeValidator(RegexValidator):
    regex = r'^[0-9]{5}$'
    message = 'Invalid ZipCode.'


class MyModel(models.Model):
    zipcode = models.CharField(max_length=5, blank=True, validators=[ZipCodeValidator()])


class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel


>>> s1 = MyModelSerializer(data={'zipcode': '34234'})
>>> s1.is_valid()
True
>>> s2 = MyModelSerializer(data={'zipcode': 'f3434'})
>>> s2.is_valid()
False
>>> s2.errors
{'zipcode': [u'Invalid ZipCode.']}

撰写回答