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

发布于 2024-10-07 13:15:41 字数 586 浏览 6 评论 0原文

我只是好奇是否有人知道 Django 的 ORM 不在模型上调用 full_clean 是否有充分的理由,除非它被保存为模型表单的一部分?

请注意,当您调用模型的 save() 方法时,不会自动调用 full_clean()。当您想要为自己手动创建的模型运行一步模型验证时,您需要手动调用它。
- 来自 Django 的 full_clean 文档

(注意:更新了 Django 1.6 的引用...之前的 Django 文档也对 ModelForms 提出了警告。)

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

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

I'm just curious if anyone knows if there's good reason why Django's ORM doesn't call full_clean on a model unless it is being saved as part of a model form?

Note that full_clean() will not be called automatically when you call your model’s save() method. You’ll need to call it manually when you want to run one-step model validation for your own manually created models.
- from Django's full_clean doc

(NOTE: quote updated for Django 1.6... previous Django docs had a caveat about ModelForms as well.)

Are there good reasons why people wouldn't want this behavior? I'd think if you took the time to add validation to a model, you'd want that validation run every time the model is saved.

I know how to get everything to work properly, I'm just looking for an explanation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

晒暮凉 2024-10-14 13:15:41

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

您可能感兴趣的来源:

AFAIK, this is because of backwards compatibility. There are also problems with ModelForms with excluded fields, models with default values, pre_save() signals, etc.

Sources you might be intrested in:

风苍溪 2024-10-14 13:15:41

由于兼容性考虑,django 内核中未启用保存时自动清理功能。

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

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()

Because of the compatibility considering, the auto clean on save is not enabled in django kernel.

If we are starting a new project and want the default save method on Model could clean automatically, we can use the following signal to do clean before every model was saved.

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()
童话 2024-10-14 13:15:41

调用 full_clean 方法的最简单方法就是重写模型中的 save 方法:

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

The simplest way to call the full_clean method is just to override the save method in your model:

class YourModel(models.Model):
    ...  
    
    def save(self, *args, **kwargs):
        self.full_clean()
        return super(YourModel, self).save(*args, **kwargs)
帅的被狗咬 2024-10-14 13:15:41

评论@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()

Commenting on @Alfred Huang's answer and coments on it. One might lock the pre_save hook down to an app by defining a list of classes in the current module (models.py) and checking against it in the pre_save hook:

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()
心清如水 2024-10-14 13:15:41

如果您希望始终确保模型验证,则全局 pre_save 信号可以很好地发挥作用。但是,它会在当前版本 (3.1.x) 中遇到 Django 身份验证问题,并可能导致您正在使用的其他应用程序的模型出现问题。

详细说明@Peter Shannon的答案,这个版本只会验证您执行它的模块内的模型,跳过验证 "raw" 保存 并将 dispatch_uid 添加到 避免重复信号

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')

A global pre_save signal can work well if you want to always ensure model validation. However it will run into issues with Django's auth in current versions (3.1.x) and could cause issues with models from other apps you are using.

Elaborating on @Peter Shannon's answer, this version will only validate models inside the module you execute it in, skips validation with "raw" saves and adds a dispatch_uid to avoid duplicate signals.

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')
酷遇一生 2024-10-14 13:15:41

如果您想要确保一个模型至少具有一个 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

If you have a model that you want to ensure has at least one FK relationship, and you don't want to use null=False because that requires setting a default FK (which would be garbage data), the best way I've come up with is to add custom .clean() and .save() methods. .clean() raises the validation error, and .save() calls the clean. This way the integrity is enforced both from forms and from other calling code, the command line, and tests. Without this, there is (AFAICT) no way to write a test that ensures that a model has a FK relation to a specifically chosen (not default) other model.

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
仅冇旳回忆 2024-10-14 13:15:41

我们可以将应用程序用作 settings.py 中的 INSTALLED_APPS 部分,而不是插入一段声明接收器的代码,

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

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

pip install django-fullclean

Instead of inserting a piece of code that declares a receiver, we can use an app as INSTALLED_APPS section in settings.py

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

Before that, you may need to install django-fullclean using PyPI:

pip install django-fullclean
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文