如果模型表单排除了某些字段,当模型有一个干净的方法时,如何处理模型表单的验证?

Posted

技术标签:

【中文标题】如果模型表单排除了某些字段,当模型有一个干净的方法时,如何处理模型表单的验证?【英文标题】:How to handle the validation of the model form when the model has a clean method if the model form excluded some fields? 【发布时间】:2020-02-03 20:25:06 【问题描述】:

我有这个模型:

class IeltsExam(Model):

    student = OneToOneField(Student, on_delete=CASCADE)
    has_taken_exam = BooleanField(default=False,)
    listening = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    reading = FloatField(choices=SCORE_CHOICES, null=True, blank=True, )
    exam_date = DateField(null=True, blank=True, )

    non_empty_fields = \
        
            'listening': 'please enter your listening score',
            'reading': 'please enter your reading score',
            'exam_date': 'please specify your exam date',
        

    def clean(self):
        errors = 
        if self.has_taken_exam:
            for field_name, field_error in self.non_empty_fields.items():
                if getattr(self, field_name) is None:
                    errors[field_name] = field_error
        if errors:
            raise ValidationError(errors)

并拥有这个模型

class IeltsExamForm(ModelForm):

    class Meta:
        model = IeltsExam
        fields = ('has_taken_exam', 'listening', 'reading', )

当我在模板中提交此表单时,我收到以下错误:

ValueError at /
'ExamForm' has no field named 'exam_date'.

During handling of the above exception ('listening': ['please enter your listening score'], 'reading': ['please enter your reading score'], 'exam_date': ['please specify your exam date']), another exception occurred:

错误发生在我正在验证表单的视图中。 我的数据库逻辑是这样的,我需要有一个exam_date 字段,如果检查了has_taken_exam,它应该是强制性的。但是,在 ExamForm 中,出于商业原因,我不需要exam_date。 我如何告诉 ExamForm 对考试日期视而不见,因为我没有保存 modelform 实例?

【问题讨论】:

【参考方案1】:

ModelForm 初始化后,它有一个instance 属性,这是将在其上调用clean() 的模型实例。所以如果你从实例的non_empty_fields 字典中删除exam_date,它就不会在clean 中使用它:

class IeltsExamForm(ModelForm): 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.instance.non_empty_fields.pop('exam_date')

您可以对self._meta.exclude 中的每个字段执行此操作。

但是,这样做时,属性non_empty_fields 不应是类属性,而是实例属性。修改实例的non_empty_fields 实际上会修改类属性(它是一个字典,因此它是可变的),这将产生意想不到的副作用(一旦删除,它会在您创建的任何后续实例中删除)。更改模型以在 init 方法中设置属性:

class IeltsExam(Model):
    # ...
    # remove the class attribute non_empty_fields

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.non_empty_fields =  ... 

一般来说,如果你真的要保存模型,我建议你只使用ModelForm,在这种情况下,类属性是更简洁的方法。如果您的表单不打算保存实际模型,而不是执行所有这些操作,则不应使用 ModelForm 而是使用 Form 并在表单本身中定义所有字段和清理。

【讨论】:

我说的对吗? :您正在阻止模型实例进入模型表单并从中弹出“exam_date”。修改后的模型实例将被传递给模型构建机器。对吗? 没有。只需从 clean 方法检查的字段列表中动态删除 exam_date 我正在尝试解释您的代码的三行以从中学习。第一行说明进入 ModelForm 的任何内容都应该首先通过下面的两行。对吗? 我只是重写了 init 方法。首先初始化表单(设置实例)然后更改实例。 类与实例属性dzone.com/articles/…【参考方案2】:

对模型的save()进行验证

考虑以下模型:

class Exam(Model):

    student = OneToOneField(Student, on_delete=CASCADE)
    has_taken_exam = BooleanField(default=False)
    score = FloatField(choices=SCORE_CHOICES, null=True, blank=True)
    exam_date = DateField(null=True, blank=True)

    def save(self, *a, **kw):
        if self.has_taken_exam and not self.exam_date:
            raise ValidationError("Exam date must be set when has_taken_exam is True")
        return super().save()

【讨论】:

我需要特定于字段的验证错误。我编辑了问题以反映这一事实。 IeltsExamForm 将在您的IeltsExam 模型中调用clean,无论exclude 中有什么内容。 我需要告诉IeltsExamForm不要对模型exam_date字段的验证错误采取行动。我该怎么做? 好吧,你可以覆盖ModelForm._post_clean(),如果错误是由exclude 中的字段产生的,则检查self.instance.full_clean(exclude=exclude, validate_unique=False) 而不是self._update_errors(e) 上出现的错误,但与刚才相比似乎有点过头了重构你的代码,让多个表单执行不同的验证,而不是在模型上验证 @NaderAlexan 在模型的save() 方法中提出ValidationError 绝不是一个好主意。 ValidationErrors 用于clean(),因为它们在调用form.is_valid() 期间被收集并返回到表单的errors 字典中。在save() 中,它们只是被视为任何其他异常,并将导致 500 错误作为响应。

以上是关于如果模型表单排除了某些字段,当模型有一个干净的方法时,如何处理模型表单的验证?的主要内容,如果未能解决你的问题,请参考以下文章

我正在尝试使用模型表单小部件更新模型。除了 forms.DateField 之外的所有字段都可以在 HTML 表单中进行编辑

Django:更新 X 数量模型的表单

我可以在不直接更新模型的主干表单中拥有表单控件吗?

如果表单输入没有明确使用它,如何设置Spring Form和Thymeleaf不更改作为模型属性添加的对象的字段?

Django 中的动态模型字段

Prisma 如何仅更新 update() 中的某些模型字段