将 Django 模型验证错误传输到表单的惯用方式

Posted

技术标签:

【中文标题】将 Django 模型验证错误传输到表单的惯用方式【英文标题】:Idiomatic Way to Transfer Django Model Validation Errors to a Form 【发布时间】:2011-12-05 21:37:27 【问题描述】:

请帮助我了解我所做的以下选择是否符合习惯/良好,如果不是,如何改进。

1) 模型验证优于表单验证 我更喜欢尽可能使用新的model validation 而不是表单验证,因为它似乎是为数据创建规则的更干燥和基本的方法。简单日历条目模型的两个示例:

“开始必须在结束之前” “期间必须小于(结束-开始)”

将它们放在模型级别以便不必将它们放入表单中是惯用/好的吗?如果 ModelForm 是最好的答案,那么在同一个表单中使用多个模型的情况呢? (编辑:我没有意识到实际上可以一起使用多个 ModelForm)

2) 将模型验证转移到表单(不是 ModelForm) (编辑:在我的情况下,重新发明模型验证和表单验证之间的管道是不必要的,下面的解决方案说明了原因) 让我们暂时假设我的任何模型验证错误都可以直接传输并直接显示给用户(即忽略将模型验证错误转换为用户友好的表单验证错误)。

这是我想出的一种工作方式(都在一个地方,没有辅助功能):

view_with_a_form:
...
if form.is_valid():
    instance = ...
    ...
    #save() is overridden to do full_clean with optional exclusions
    try: instance.save()
    except ValidationError e:
        nfe= e.message_dict[NON_FIELD_ERRORS]
        #this is one big question mark:
        instance._errors['__all__'] = ErrorList(nfe)
#this is the other big question mark. seems unnatural
if form.is_valid()
    return response...
... #other code in standard format
#send the user the form as 1)new 2)form errors 3)model errors
return form

如代码中所述: a) 这是将模型错误转移到表单的惯用/好方法吗?

b) 这是测试新“表单”错误的惯用/好方法吗?

注意:此示例使用非字段错误,但我认为它同样适用于字段错误。

【问题讨论】:

我正在更改选择的答案,因为来自 Stefano 的更新实际上说服我采用另一种方法 (one of the combined ModelForms methods here),而无需重新发明模型验证和表单验证之间的管道。 (我不知道它们可以合并)。据我所知,克里斯的回答也很棒。 【参考方案1】:

[已编辑 - 希望这能回答您的 cmets]

我会简明扼要,但不想不礼貌:)

1) 模型验证优于表单验证

我想经验法则可能是,只要该规则确实与模型相关联,是的,最好在模型级别进行验证。

对于多模型表单,请查看其他 SO 问题:Django: multiple models in one template using forms,不要忘记 attached link,它有点旧,但仍然与实现它的最干方法相关。在这里重新讨论太长了!

2)

a) 不!您应该从将为您执行 model validation 的 ModelForm 派生您的表单 -> 您不需要自己调用模型验证 b) 不!如果你想验证一个模型,你不应该尝试保存它;你应该使用full_clean -> 所以如果你的 ModelForm 是有效的,那么你知道模型也是有效的,你可以保存。

是的,即使使用多模型表单,这些答案仍然适用。

所以你应该做的是:

仍然使用 ModelForm 不用担心验证模型,因为 ModelForm 会为您完成。

一般来说,如果您发现自己在使用 django 进行一些处理,那是因为还有另一种更简单的方法!

第二个指针应该可以帮助您开始并实际上大大简化了您的代码...

【讨论】:

至于第2部分),让我回答几点以便更好地理解。 a)在这种情况下,是的,我可以更干净地使用 ModelForm 。但是,我应该说我有包含多个模型的表单,所以 ModelForm 不合适吗?在这种情况下,我做的方式好吗? b) 足够公平。到目前为止,我更喜欢保存它,这是我找到的唯一组合。如果没有问题,则 full_clean 然后保存。所以我只是将它们结合起来,而不需要在每个领域都记住它。我认为更干燥。 @yakiimo 希望我回答了你的其他问题【参考方案2】:

1) 是的,对模型进行验证是完全有效的。这就是 Django 人员添加它的原因。在保存模型的过程中并不总是使用表单,因此如果仅通过表单进行验证,您将遇到问题。当然,过去人们通过覆盖save 方法并以这种方式包含验证来解决此限制。然而,新的模型验证更加语义化,并在实际使用表单时为您提供了一个进入验证过程的钩子。

2) 文档非常清楚地表明模型验证 (full_clean) 在调用 ModelForm.is_valid 时运行。但是,如果您不使用ModelForm 或者想要进行额外处理,则需要手动调用full_clean。您正在这样做,但是将其放在被覆盖的 save 中是错误的方法。请记住:“显式胜于隐式。”此外,save 在许多其他地方和方式中被调用,在 ModelForm 的情况下,您实际上最终会以这种方式运行 full_clean 两次。

也就是说,由于文档说ModelForm 自动执行full_clean,我认为看看它如何处理错误是有意义的。在_post_clean方法中,从django.forms.models的第323行开始:

# Clean the model instance's fields.
try:
    self.instance.clean_fields(exclude=exclude)
except ValidationError, e:
    self._update_errors(e.message_dict)

# Call the model instance's clean method.
try:
    self.instance.clean()
except ValidationError, e:
    self._update_errors(NON_FIELD_ERRORS: e.messages)

反过来,_update_errors 的代码从同一模块的第 248 行开始:

def _update_errors(self, message_dict):
    for k, v in message_dict.items():
        if k != NON_FIELD_ERRORS:
            self._errors.setdefault(k, self.error_class()).extend(v)
            # Remove the data from the cleaned_data dict since it was invalid
            if k in self.cleaned_data:
                del self.cleaned_data[k]
    if NON_FIELD_ERRORS in message_dict:
        messages = message_dict[NON_FIELD_ERRORS]
        self._errors.setdefault(NON_FIELD_ERRORS, self.error_class()).extend(messages)

您将不得不稍微修改一下代码,但这应该可以为您提供一个很好的起点来组合表单和模型验证错误。

【讨论】:

很好地呼吁查看 ModelForm 的源代码。那件事从我的脑海中掠过,随后又消失了。我现在会调查一下。感谢您回复的所有部分。 在检查了开发代码并进一步观察后,以我的技能水平来看,它看起来已经足够吊死我自己了。希望合并后的表格能奏效。再次感谢您的回复和领导。

以上是关于将 Django 模型验证错误传输到表单的惯用方式的主要内容,如果未能解决你的问题,请参考以下文章

验证模型表单日期字段时,Django 期望格式错误

在 django 中将 css 类添加到验证错误的字段

在 Django 中分离表单输入和模型验证?

Django,将排除的属性添加到提交的模型表单

附加到 Django 表单验证错误?

不使用 django 表单如何验证和保存表单数据