我应该在哪里对Django对象和字段进行验证?

7
我正在创建一个Django应用程序,该应用程序同时使用Django Rest Framework和普通的django视图作为用户的入口点。
我想对我的模型的独立字段和整个对象进行验证。例如:
- 字段:输入的车牌号是否符合基于regex函数的正确格式,与其他字段无关。 - 对象:输入的邮政编码是否对给定国家有效,与模型中的邮政编码和国家有关。
对于DRF-API,我使用ModelSerializers自动调用我在模型中放置的所有验证器,例如:
class MyModel(models.Model):
    licence_plate = CharField(max_length=20, validators=[LicencePlateValidator])

由于验证器已经在模型中设置,因此使用 ModelSerializer 的 API POSTS(以及在 Django 后端管理中创建的对象)都会被验证。

但是,当我想要引入对象级别的验证时,我需要在序列化程序的 validate() 方法中进行操作,这意味着对象只在 API 中得到验证。

我还必须重写模型的保存方法,以验证在 Django 管理页面中创建的对象。

问题:对我来说,这似乎有些凌乱,是否有一个单一的点可以放置对象级别的验证器,以便在 API 和管理页面中运行它们,就像我使用字段级别验证器一样(我只需将 它们 放入模型声明中即可处理所有内容)

2个回答

10

针对模型级别验证,可以使用 Model.clean 方法。

如果您使用的是 ModelForm(默认在 admin 中使用),则会调用该方法,这解决了 Django 视图和管理部分的问题。

另一方面,DRF 不会自动调用模型的 clean 方法,因此您需要在 Serializer.validate 中自行处理它(正如文档所建议的那样)。您可以通过一个序列化器 mixin 来实现。

class ValidateModelMixin(object)
    def validate(self, attrs):
        attrs = super().validate(attrs)
        obj = self.Meta.model(**attrs)
        obj.clean()
        return attrs

class SomeModelSerializer(ValidateModelMixin, serializers.ModelSerializer):
    #...
    class Meta:
        model = SomeModel

或者编写一个验证器:

class DelegateToModelValidator(object):

    def set_context(self, serializer):
        self.model = serializer.Meta.model

    def __call__(self, attrs):
        obj = self.model(**attrs)
        obj.clean()

class SomeModelSerializer(serializers.ModelSerializer):
    #...
    class Meta:
        model = SomeModel
        validators = (
            DelegateToModelValidator(),
        )

注意事项:

  • 额外实例化模型只是为了调用clean方法
  • 您仍需要将mixin/validator添加到序列化器中

谢谢,这个答案以及Rahul的回答让我更清楚地了解到,在两个地方调用模型验证器(一次在序列化器中,一次在模型中)确实是通过表单和API进行验证的唯一方法。它给了我一些想法,如何以更清晰的方式编写代码。我认为我会采用MixIn路线...对我来说更清晰。 - Robin van Leeuwen
当与具有关系的模型一起使用时,会导致以下错误:TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use users.set() instead.是否有任何解决方法? - Philipp Mildenberger

3

您可以创建一个名为validate_zipcode_with_country(zipcode, country)的单独函数,该函数将接受两个参数zipcodecountry

然后,在序列化程序的validate()和模型的clean()调用此方法

from django.core.exceptions import ValidationError

def validate_zipcode_with_country(zipcode, country):
    # check zipcode is valid for the given country 
    if not valid_zipcode:
        raise ValidationError("Zipcode is not valid for this country.") 

然后在你的serializers.py中,你需要在validate()函数中调用这个函数。

class MySerializer(serializers.ModelSerializer):   

    def validate(self, attrs):
        zipcode = attrs.get('zipcode')
        country = attrs.get('country')
        validate_zipcode_with_country(zipcode, country) # call the function
        ...

同样地,您需要覆盖模型的 clean() 方法并调用此函数。
class MyModel(models.Model):

    def clean(self):        
        validate_zipcode_with_country(self.zipcode, self.country) # call this function
        ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接