【问题标题】:Raise a validation error in a model's save method in Django在 Django 中模型的保存方法中引发验证错误
【发布时间】:2012-02-04 23:04:16
【问题描述】:

我不确定如何在模型的保存方法中正确引发验证错误并将明确的消息发送回用户。

基本上我想知道“如果”的每一部分应该如何结束,我想在哪里引发错误 以及它实际保存的位置:

def save(self, *args, **kwargs):
    if not good_enough_to_be_saved:
        raise ValidationError
    else:
        super(Model, self).save(*args, **kwargs)

然后我想知道如何发送一个验证错误,准确地告诉用户出了什么问题,就像 Django 自动返回的错误一样,例如一个值不是唯一的。 我正在使用 (ModelForm) 并调整模型中的所有内容。

【问题讨论】:

  • 使用 clean() 方法

标签: django validation model validationerror


【解决方案1】:

大多数 Django 视图,例如Django 管理员将无法处理 save 方法中的验证错误,因此您的用户将收到 500 个错误。

您应该在模型表单或模型上进行验证,并在那里提出ValidationError。然后仅当模型表单数据“足以保存”时才调用save()

【讨论】:

  • 你说得对,我会将验证移到表单中,这样更容易。我只是喜欢在模型中包含所有内容的想法。
  • @bastian,我也喜欢在模型中包含所有内容。编写新表单时很容易忘记业务规则,但如果模型中有业务规则则不会。出于这个原因,正如我在帖子中解释的那样,我已将验证从表单转移到模型。如果存在的话,我愿意了解以更优雅的方式执行此操作的新方法。在任何情况下,我都避免在表单上编写验证代码。
  • 可以通过使用验证器或编写clean() 方法将验证放入模型中。我只是说save() 方法不是正确的地方。查看validating objects 上的文档。
  • 我不明白为什么只能在表单端而不是模型保存端进行验证。就像没有其他方法可以创建对象一样。如果您想在不使用表单的情况下实例化和创建对象,但仍想保证某种状态怎么办?
  • @dabadaba 你可以把验证放在模型的 clean 方法中,我只是说不要放在模型的save() 方法中。如果您将验证放在save() 方法中,那么您将从大多数视图中得到500 个错误,因为它们不会处理ValidationError。请注意,将验证放在 save() 方法中并不是绝对保证 - 您仍然可以编写 Model.objects.filter(...).update(...) 或手动 SQL,从而导致保存无效数据。
【解决方案2】:

Bastian,我向您解释了我的代码模板,希望对您有所帮助:

django 1.2 it is able to write validation code on model.当我们使用模型表单时,会在表单验证时调用 instance.full_clean()。

在每个模型中,我使用自定义函数覆盖 clean() 方法(此方法在模型表单验证时从 full_clean() 自动调用):

from django.db import models
 
class Issue(models.Model):
    ....
    def clean(self): 
        rules.Issue_clean(self)  #<-- custom function invocation

from issues import rules
rules.connect()

然后在rules.py 文件中编写业务规则。我还将pre_save() 连接到我的自定义函数以防止保存状态错误的模型:

从issues.models导入问题

def connect():    
    from django.db.models.signals import post_save, pre_save, pre_delete
    #issues 
    pre_save.connect(Issue_pre_save, sender = Incidencia ) 
    post_save.connect(Issue_post_save, sender = Incidencia )
    pre_delete.connect(Issue_pre_delete, sender= Incidencia) 

def Incidencia_clean( instance ):    #<-- custom function 
    import datetime as dt    
    errors = {}

    #dia i hora sempre informats     
    if not instance.dia_incidencia:   #<-- business rules
        errors.setdefault('dia_incidencia',[]).append(u'Data missing: ...')
        
    #dia i hora sempre informats     
    if not  instance.franja_incidencia: 
        errors.setdefault('franja_incidencia',[]).append(u'Falten Dades: ...')
 
    #Només es poden posar incidències més ennlà de 7 dies 
    if instance.dia_incidencia < ( dt.date.today() + dt.timedelta( days = -7) ): 
        errors.setdefault('dia_incidencia 1',[]).append(u'''blah blah error desc)''')
 
    #No incidències al futur. 
    if instance.getDate() > datetime.now(): 
        errors.setdefault('dia_incidencia 2',[]).append(u'''Encara no pots ....''') 
    ... 

    if len( errors ) > 0: 
        raise ValidationError(errors)  #<-- raising errors

def Issue_pre_save(sender, instance, **kwargs): 
    instance.clean()     #<-- custom function invocation

然后,modelform 调用模型的 clean 方法,我的 custon 函数检查正确的状态或引发由模型表单处理的错误。

为了在表单上显示错误,您应该在表单模板中包含以下内容:

{% if form.non_field_errors %}
      {% for error in form.non_field_errors %}
        {{error}}
      {% endfor %}
{% endif %}  

原因是模型验证错误 ara 绑定到 non_field_errors 错误字典条目。

当您从表单中保存或删除模型时,您应该记住可能会引发错误:

try:
    issue.delete()
except ValidationError, e:
    import itertools
    errors = list( itertools.chain( *e.message_dict.values() ) )

此外,您可以在没有模型表单的表单字典中添加错误:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )

请记住,此代码不会在 save() 方法上执行:请注意,当您调用模型的 save() 方法时,不会自动调用 full_clean(),也不会作为 ModelForm 验证的结果。然后,您可以在 no modelforms 上将错误添加到表单字典中:

    try:
        #provoco els errors per mostrar-los igualment al formulari.
        issue.clean()
    except ValidationError, e:
        form._errors = {}
        for _, v in e.message_dict.items():
            form._errors.setdefault(NON_FIELD_ERRORS, []).extend(  v  )

【讨论】:

  • Moltes gràcies 为您提供冗长的解释。我一直在寻找自动的东西,Djangoish。你的例子可能会让我对其他情况感兴趣,但我现在写的只是一个 1 行验证,所以我不会在这里实现整个事情。
  • 您始终可以使用 1 行验证覆盖 clean 方法...
  • 嗯..这对我不起作用。我正在使用弹出表单,异常最终显示而不是验证错误。我应该指出,因为我有一个适用于两个模型的表单,所以我正在扩展 forms.Form 而不是 models.Form
【解决方案3】:

我认为这对于 Django 1.2+ 来说是更清晰的方法

在表单中,它会作为 non_field_error 引发,在其他情况下,例如 DRF,您必须查看此案例手册,因为它将是 500 错误。

class BaseModelExt(models.Model):
    is_cleaned = False

    def clean(self):
        # check validation rules here

        self.is_cleaned = True

    def save(self, *args, **kwargs):
        if not self.is_cleaned:
            self.clean()

        super().save(*args, **kwargs)

【讨论】:

  • 这对我来说似乎非常简单有效,每当您需要验证以编程方式创建的对象时,即:过程中不涉及表单提交。谢谢
【解决方案4】:

在 Django 文档中,他们在 .save 方法中提出了 ValueError,它可能对您有用。

https://docs.djangoproject.com/en/3.1/ref/models/instances/

【讨论】:

    【解决方案5】:

    如果要对模型进行验证,可以在模型上使用clean()clean_fields 方法。

    编辑: 这些在执行save() 之前由 django 调用,并且验证错误以用户友好的方式处理不正确,感谢@Brad 采纳。

    这些 cleanclean_fields 方法在保存模型之前由 Django 的表单验证器调用(例如在 django admin 中,在这种情况下你的验证错误得到很好的处理),但不会在 save() 上自动调用由 DRF 序列化程序或如果您使用自定义视图,在这种情况下,您必须确保它们被调用(或以其他方式验证,例如通过将逻辑放入序列化程序的验证中)。

    值得强调:如果您将自定义验证逻辑直接放入 save() 并从那里引发 ValidationError,这与表单效果不佳(例如,以 500 错误破坏管理员),这使得事情变得真实如果你想让 django-admin 和 DRF 一起工作很痛苦......你基本上必须在序列化程序和 clean* 方法中复制验证逻辑,或者找到一些可以与两者共享的尴尬方法进行验证。

    Django docs on ValidationErrors here..

    【讨论】:

    • "这些在执行之前被 django 调用save()"。不,他们不是。
    • 很好看,布拉德。我很快就回答了,忘记了验证是在表单级别完成的,而不是 save() 级别。
    【解决方案6】:

    编辑:此答案假设您有一个不允许您编辑当前实现的User 类的场景,因为您不是从头开始项目,当前实现还没有使用自定义用户类,而您必须弄清楚如何通过修改 Django 内置的用户模型行为来完成此任务。

    大多数时候,您可以将clean 方法粘贴到您的模型中,但是对于内置的auth.User 模型,您不一定拥有该选项。此解决方案将允许您为 auth.User 模型创建一个 clean 方法,这样ValidationErrors 将传播到调用 clean 方法的表单(包括管理表单)。

    如果有人尝试创建或编辑 auth.User 实例以使用与现有 auth.User 实例相同的电子邮件地址,则以下示例会引发错误。 免责声明,如果您将注册表单暴露给新用户,您不希望您的验证错误像下面我的那样调用用户名。

    from django.contrib.auth.models import User
    from django.forms import ValidationError as FormValidationError
    
    def clean_user_email(self):
        instance = self
        super(User, self).clean()
        if instance.email:
            if User.objects.filter(id=instance.id, email=instance.email).exists():
                pass  # email was not modified
            elif User.objects.filter(email=instance.email).exists():
                other_users = [*User.objects.filter(email=instance.email).values_list('username', flat=True)]
                raise FormValidationError(f'At least one other user already has this email address: '
                                          f'{", ".join(other_users)}'
                                          , code='invalid')
    
    # assign the above function to the User.clean method
    User.add_to_class("clean", clean_user_email)
    

    我在my_app.models 的底部有这个,但我相信只要你把它贴在有问题的表单之前加载的某个地方,它就可以工作。

    【讨论】:

    • 如果你不喜欢我的回答,你应该解释原因。
    • 我没有投反对票,但我猜投反对票是因为你在回答 2012 年的一个问题,而 [A](虽然很有趣)不是对所提问题的回答, [B] 不调用任何现有的User.clean(),并且[C] 使用猴子补丁而不是从AbstractUser 继承并在您自己的类上实现clean()...
    • 这不是我自己的课。 User 模型由 Django 定义。我不得不做这个猴子补丁来修改 Django 内置用户模型的方法,因为在你开始一个项目并且它在没有AbstractUser 自定义用户模型实现的情况下投入生产后,基本上不可能成功地改造你自己的用户模型。请注意,我的回答的前两句话明确解决了您提出的问题。
    • 此外,我“回答了 2012 年的一个问题”并给出了适合我情况的答案,因为当我为我的特定问题寻找解决方案时,这是 2018 年出现的问题。所以让我们说像我这样的人出现并遇到了这个问题。好吧,有一个可能的解决方案,它花了我不可忽视的时间来想出,并且可以为某人节省几乎相等的时间。据我了解,Stack Overflow 旨在成为一个有用的解决方案集合。覆盖潜在的边缘情况是其中很大一部分。
    • 正如我所说,我没有投反对票,但您对为什么这是一个很酷的解决方案的所有理由(确实如此)使您离 this 问题的答案更远了。我可以建议您创建自己的新问题,而不是将您自己的问题的解决方案附加到您在搜索 your 问题的解决方案(并被否决)时发现的旧的半相关问题上?回答您自己的问题是完全可以的,因此,如果您有来之不易的经验可以分享,您可以自行回答(并且可能会为问题和答案投票)。
    【解决方案7】:
    def clean(self):
        raise ValidationError("Validation Error")
    
    def save(self, *args, **kwargs):
        if some condition:
            #do something here
        else:
            self.full_clean()
        super(ClassName, self).save(*args, **kwargs)
    

    【讨论】:

    • 发帖码不够,需要解释一下。
    • 你可以在保存函数中调用 full_clean() 方法,这在 Django==1.11 中可以正常工作,我不确定旧版本。
    猜你喜欢
    • 2010-12-01
    • 1970-01-01
    • 2016-01-26
    • 1970-01-01
    • 2021-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-19
    相关资源
    最近更新 更多