Django REST框架与模型验证
我在使用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 个回答
其实,我觉得你提的第一个解决方案 @astrognocci,虽然看起来有点啰嗦,但对于 Django REST Framework 3.0 及以上版本来说,是个不错的选择。
实际上,.full_clean()
这个方法在 ModelSerializer
的验证过程中不再被调用了,具体可以参考 这篇文章。
所以,写自定义的类验证器——可以在 Model
和 ModelSerializer
中使用——看起来是个很合适的选择,这样可以避免重复代码,也能保持一致性。
这个对我有效:
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.']}