django的model.save()为什么不调用full_clean()?


150

我只是好奇是否有人知道,除非有理由将django的orm不保存在模型中,否则它不会在模型上调用“ full_clean”。

请注意,当您调用模型的save()方法时,不会自动调用full_clean()。若要为自己创建的模型运行一步模型验证,则需要手动调用它。 django的完整档案

(注意:报价已针对Django 1.6更新...之前的django文档也对ModelForms提出了警告。)

人们为什么不希望这种行为有充分的理由?我想如果您花时间向模型添加验证,则希望每次保存模型时都运行验证。

我知道如何使一切正常工作,我只是在寻找一种解释。


11
非常感谢您提出这个问题,它使我无法将自己的头撞在墙上。我创建了一个可以帮助其他人的mixin。查看要点:gist.github.com/glarrain/5448253
glarrain

最后,我使用信号来捕捉问题pre_savefull_clean在所有被捕获的模型上执行操作。
Alfred Huang

Answers:


59

AFAIK,这是因为向后兼容。带有排除字段的ModelForms,具有默认值的模型,pre_save()信号等也存在问题。

您可能感兴趣的资源:


3
第二本参考书中最有用的摘录(IMHO):“开发“自动”验证选项既简单又有效,又足以应付所有极端情况,那么即使有可能,它也远远超过了可以在1.2的时间范围内完成。因此,到目前为止,Django还没有这样的东西,并且在1.2中也没有。如果您认为可以在1.3上使用它,那么最好的选择就是提案,至少包括一些示例代码,并说明如何使它既简单又健壮。”
2012年

30

由于考虑到兼容性,因此在django内核中未启用保存时自动清除。

如果我们正在启动一个新项目,并且希望saveModel上的默认方法可以自动清除,则可以在保存每个模型之前使用以下信号进行清除。

from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save

@receiver(pre_save)
def pre_save_handler(sender, instance, *args, **kwargs):
    instance.full_clean()

2
为什么比在某些BaseModel上覆盖save方法(所有其他模型都将继承自该方法)先调用full_clean,然后再调用super()更好(或更糟)?
J__

7
我看到这种方法有两个问题:1)如果ModelForm的full_clean()会被调用两次:通过表单和信号2)如果表单不包含某些字段,它们仍将通过信号进行验证。
mehmet

1
@mehmet因此,您可以if send == somemodel, then exclude some fieldspre_save_handler
Simin Jie

4
对于那些正在使用或正在考虑使用此方法的人:请记住,Django尚未正式支持此方法,并且在可预见的将来也不会支持此方法(请参阅Django Bug Tracker中的此注释:code.djangoproject.com/ticket/ 29655#comment:3),因此,如果对所有模型启用验证,您很可能会发现某些缺陷,例如身份验证停止工作(code.djangoproject.com/ticket/29655)。您将不得不自己处理此类问题。但是,没有更好的atm方法。
Evgeny A.

2
从Django 2.2.3开始,这会导致基本身份验证系统出现问题。你会得到一个ValidationError: Session with this Session key already exists。为了避免这种情况,您需要添加一个if语句,sender in list_of_model_classes以防止信号覆盖Django的默认身份验证模型。定义list_of_model_classes无论您选择
艾迪Klinke

15

调用该full_clean方法的最简单方法是save在您的方法中覆盖该方法model

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

为什么这比使用信号更好(或更糟)?
J__

6
我看到这种方法有两个问题:1)如果ModelForm的full_clean()会被调用两次:通过表单和保存2)如果表单不包含某些字段,则仍然可以通过保存来验证它们。
mehmet

3

除了插入件的代码,声明了一个接收器的,我们可以使用一个应用程序如INSTALLED_APPS在节settings.py

INSTALLED_APPS = [
    # ...
    'django_fullclean',
    # your apps here,
]

在此之前,您可能需要django-fullclean使用PyPI 安装:

pip install django-fullclean

13
为什么您要在pip install其中包含4行代码(请检查源代码)的某个应用程序而不是自己编写这些行呢?
David D.

我还没有尝试过自己的另一个库:github.com/danielgatis/django-smart-save
Flimm

2

如果您要确保具有至少一个FK关系的模型,并且不想使用该模型,null=False因为这需要设置默认FK(这将是垃圾数据),那么我想出的最好方法是添加自定义.clean().save()方法。.clean()引发验证错误,并.save()调用clean。这样,可以从表单以及其他调用代码,命令行和测试中强制执行完整性。没有这个,就无法(AFAICT)编写测试来确保模型与特定选择的(非默认)其他模型具有FK关系。

class Payer(models.Model):

    name = models.CharField(blank=True, max_length=100)
    # Nullable, but will enforce FK in clean/save:
    payer_group = models.ForeignKey(PayerGroup, null=True, blank=True,)

    def clean(self):
        # Ensure every Payer is in a PayerGroup (but only via forms)
        if not self.payer_group:
            raise ValidationError(
                {'payer_group': 'Each Payer must belong to a PayerGroup.'})

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

    def __str__(self):
        return self.name

1

评论@Alfred Huang的回答和评论。可以通过在当前模块(models.py)中定义一个类列表并在pre_save钩子中对其进行检查来将pre_save钩子锁定到应用程序:

CUSTOM_CLASSES = [obj for name, obj in
        inspect.getmembers(sys.modules[__name__])
        if inspect.isclass(obj)]

@receiver(pre_save)
def pre_save_handler(sender, instance, **kwargs):
    if type(instance) in CUSTOM_CLASSES:
        instance.full_clean()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.