将Django模型验证错误传递给表单的惯用方法

7 投票
2 回答
1978 浏览
提问于 2025-04-17 04:08

请帮我理解一下,我做出的以下选择是否合适,如果不合适,应该怎么改进。

1) 模型验证优于表单验证
我更喜欢在可能的情况下使用新的模型验证,而不是表单验证,因为这样似乎是一种更简洁和基本的方式来为数据创建规则。以下是一个简单的日历条目模型的两个例子:

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

把这些规则放在模型层面上是否合适,这样就不需要在表单中重复写?如果使用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) 这种测试新“表单”错误的方式是否合适?

注意:这个例子使用了非字段错误,但我认为同样适用于字段错误。

2 个回答

5

1) 是的,在模型上进行验证是完全合理的。这就是Django团队添加这个功能的原因。因为在保存模型的过程中,并不总是会使用表单,所以如果验证只通过表单来进行,就会出现问题。当然,以前人们通过重写save方法来解决这个限制,加入验证逻辑。不过,现在的新模型验证更符合语义,并且在实际使用表单时,可以更好地接入验证过程。

2) 文档中很清楚地说明,当调用ModelForm.is_valid时,会运行模型验证(full_clean)。但是,如果你不使用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)

你需要稍微动动代码,但这应该能给你一个很好的起点,帮助你结合表单和模型的验证错误。

5

[编辑 - 希望这能回答你的评论]

我会简短直接,但不想显得不礼貌 :)

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

我想说的一个简单原则是,只要这个规则确实和模型有关,那就最好在模型层面进行验证。

关于多个模型的表单,请查看这个其他的SO问题:Django:在一个模板中使用表单处理多个模型,还有这个附加链接,虽然有点旧,但仍然适用,讲述了实现它的最简单方法。这里讨论太多内容了,不方便再详细说!

2)

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

是的,这些答案在处理多个模型的表单时仍然适用。

所以你应该做的是:

  • 仍然使用ModelForm
  • 不用担心验证模型,因为ModelForm会为你处理这个。

一般来说,如果你发现自己在用Django做一些繁琐的事情,那是因为还有其他更简单的方法可以做到!

第二个建议应该能帮助你入门,并且实际上会大大简化你的代码……

撰写回答