【问题标题】:Is this the way to validate Django model fields?这是验证 Django 模型字段的方法吗?
【发布时间】:2012-10-08 08:40:37
【问题描述】:

据我了解,当创建 Django 应用程序时,数据在插入模型实例之前由表单验证,然后将其写入数据库。但是,如果我想在数据模型层创建额外的保护层,我所做的是否低于当前的“最佳实践”?我试图确保审稿人的姓名不能省略,也不能留空。我是否应该像我在这里所做的那样在“干净”方法中放置任何自定义验证,然后让“保存”调用“full_clean”来调用“干净”?如果没有,首选方法是什么?谢谢。

class Reviewer(models.Model):
    name = models.CharField(max_length=128, default=None)

    def clean(self, *args, **kwargs):
        if self.name == '':
            raise ValidationError('Reviewer name cannot be blank')
        super(Reviewer, self).clean(*args, **kwargs)

    def full_clean(self, *args, **kwargs):
        return self.clean(*args, **kwargs)

    def save(self, *args, **kwargs):
        self.full_clean()
        super(Reviewer, self).save(*args, **kwargs)

【问题讨论】:

    标签: python django django-models


    【解决方案1】:

    首先,您不应该像您所做的那样覆盖full_clean。来自django docs on full_clean

    Model.full_clean(exclude=None)
    此方法按此顺序调用Model.clean_fields()Model.clean()Model.validate_unique(),并引发一个ValidationError,该message_dict 属性包含来自所有三个阶段的错误。

    所以full_clean 方法已经调用了clean,但是通过覆盖它,您阻止了它调用其他两个方法。

    其次,在save 方法中调用full_clean 是一种权衡。请注意,full_clean 在验证模型表单时已被调用,例如在 Django 管理员中。所以如果你在save方法中调用full_clean,那么该方法会运行两次。

    通常不会期望 save 方法引发验证错误,有人可能会调用 save 并没有捕获结果错误。但是,我喜欢您调用 full_clean 而不是在 save 方法本身中进行检查 - 这种方法允许模型表单首先发现问题。

    最后,您的clean 方法可以工作,但您实际上可以在模型字段本身中处理您的示例案例。将您的 CharField 定义为

    name = models.CharField(max_length=128)
    

    blank 选项将默认为 False。如果该字段为空,则在您运行 full_clean 时将引发 ValidationError。将 default=None 放入您的 CharField 不会造成任何伤害,但是当您实际上不允许将 None 作为值时会有点混乱。

    【讨论】:

    • 感谢 Aladair。你对 full_clean 已经调用 clean 是正确的,所以我把后者去掉了。对于您的第二点,如果我尝试从 Python shell 而不是 Django admin I/F 更改数据库,我将执行此附加验证。我认为这个额外的验证是值得的。至于你的最后一点,如果你删除'default = None',你可以完全省略 name 值并且不会引发异常。我不想要这个。留下它并调用 clean 会阻止我输入一个空白值或完全省略名称字段。
    • 抱歉,我拼错了“Alasdair”。
    • 我不同意default=None。我刚刚测试了r=Reviewer(); r.full_clean(),它引发了ValidationError {'name': [u'This field cannot be blank.']}。无论如何,这与您的主要问题无关,所以不太重要。
    • 我对运行验证两次的主要担心是clean 方法是否昂贵,例如它涉及大量的数据库查询。在这种情况下,我同意这不是太大的开销。请注意,即使您实现了这一点,仍然有一些方法可以将无效数据放入数据库。例如Review.objects.update(name="").
    • 我今天将对此进行更多实验。在了解您提到的方法(stackoverflow.com/questions/12879256/…)之前,我在上周末遇到了这个问题。我认为 Django 没有进行任何仅模型验证。你似乎说我可以用full_clean强制它。我也想做模型验证和约束检查。更多稍后...谢谢!
    【解决方案2】:

    在考虑了 Alasdair 的答案并进行了补充阅读之后,我现在的感觉是 Django 的模型并不是像我试图做的那样在仅模型的基础上进行验证。可以进行此类验证,但要付出一定的代价,并且需要以非预期的方式使用验证方法。

    相反,我现在认为,除了可以直接输入到模型字段声明中的约束(例如“unique=True”)之外的任何约束都应该作为 Form 或 ModelForm 验证的一部分来执行。如果想防止通过任何其他方式(例如在 Python 解释器中工作时通过 ORM)将无效数据输入到项目的数据库中,那么验证应该在数据库本身内进行。因此,验证可以在三个层次上实现: 1)首先,通过数据库中的 DDL 实现所有约束和触发器; 2) 实现模型字段可用的任何约束(例如“unique=True”); 3) 实现所有其他约束和验证,这些约束和验证反映了您的表单和模型表单中的数据库级约束和触发器。使用这种方法,任何表单验证错误都可以重新显示给用户。如果程序员通过 ORM 直接与数据库交互,他/她会直接看到数据库异常。

    有人想吗?

    【讨论】:

    • 例如,如果在我的模型中定义:“name = models.CharField(max_length=128, default=None)”,我将无法执行“Movie.objects.create() " 因为模型将向数据库发送一个空值,这会导致它引发完整性错误。如果我还将检查约束“CHECK (name '')”添加到我的 Postgres 数据库,如果我尝试执行“Movie.objects.create(name='')”,它将引发完整性错误。
    • 我发现数据库中的额外逻辑很糟糕。你在哪里添加这个逻辑?你在哪里测试它?您如何在安装过程中保持同步?测试?版本控制?如果您打算这样做,那么您应该找到一种方法将其集成到您的模型声明中,以便它与其他所有内容在 Python 代码中可见。自定义字段类型?模型混合?
    • Django 模型只在保存时接触数据库。因此,在模型类中而不是在数据库中进行验证是有意义的,因为这样您可以在接触数据库之前检测并纠正无效值。
    【解决方案3】:

    在我的模型上捕获预保存信号确保 clean 将被自动调用。

    from django.db.models.signals import pre_save
    
    def validate_model(sender, **kwargs):
        if 'raw' in kwargs and not kwargs['raw']:
            kwargs['instance'].full_clean()
    
    pre_save.connect(validate_model, dispatch_uid='validate_models')
    

    【讨论】:

      【解决方案4】:

      感谢@Kevin Parker 的回答,很有帮助!

      在您的应用中存在您定义的模型之外的模型是很常见的,因此这里有一个修改版本,您可以根据需要将此行为范围仅限于您自己的模型或特定的应用/模块。

      from django.db.models.signals import pre_save
      import inspect
      import sys
      
      MODELS = [obj for name, obj in
          inspect.getmembers(sys.modules[__name__], inspect.isclass)]
      
      def validate_model(sender, instance, **kwargs):
          if 'raw' in kwargs and not kwargs['raw']:
              if type(instance) in MODELS:
                  instance.full_clean()
      
      pre_save.connect(validate_model, dispatch_uid='validate_models')
      

      此代码将针对执行它的模块内定义的任何模型运行,但如果需要,您可以将其调整为更严格的范围或成为一组模块/应用程序。

      【讨论】:

        猜你喜欢
        • 2010-12-10
        • 2019-12-24
        • 1970-01-01
        • 2016-03-02
        • 1970-01-01
        • 2020-06-22
        • 1970-01-01
        • 2020-02-08
        相关资源
        最近更新 更多