保存 ModelForm 时验证多个外键

Validate multiple foreign keys while saving a ModelForm

解释:

我有一个有两个外键的模型:

class Book(models.Model):
    name = models.CharField(max_length=100)
    author = models.ForeignKey(Author, on_delete=CASCADE)
    created_by = models.ForeignKey(User, blank=True, null=True, on_delete=CASCADE)

我有一个模型:

class BookForm(modelForm):
    class Meta:
        model = Book
        exclude = ('author', 'created_by',)

在我的业务逻辑中,我想在同一个视图中保存一位新作者和一本书,并且 created_by 在某些情况下可能为空。因此,在保存 BookForm 的实例之前,我将首先创建一个作者。所以,假设我已经创建了一个作者,那么我将执行以下操作来保存一本书:

f = BookForm(post_data)
if form.is_valid():
    instance = f.save(commit=False)
    instance.author = author
    instance.created_by = user
    instance.save()

到目前为止我所做的工作正常,但外键字段不是验证的一部分。从模型 class 可以看出,author 字段是必填项,但 created_by 字段是可选的。所以,如果我想为它们添加一些验证,我必须通过自定义 is_valid() 方法来显式添加它们,或者在 instance.author = author.

之前添加一些 if 条件。

问题:

更大的图景是我有一个带有更多外键的模型;其中一些是强制性的,而另一些则不是。因此,使用我在上面所做的相同方法,我必须明确地为每个 FK 添加自定义验证,我认为这是重复和不干净的,因为那些 FK 字段验证是在我的模型 class.

中定义的

那么,通过直接检查模型验证而不一一提及每个 FK,在保存时自动验证那些多个外键的最佳做法是什么?

由于外键字段未包含在您的表单中,因此不会对它们进行任何表单验证。您需要的是模型验证。

Django 提供了 model.full_clean() 方法,该方法执行所有模型验证步骤。

您将需要手动调用该方法,因为 save() 方法不会调用任何模型清理方法。

参见文档 here

例如,

f = BookForm(post_data)
if form.is_valid():
    instance = f.save(commit=False)
    instance.author = author
    instance.created_by = user
    try:
        instance.full_clean()
    except ValidationError as e:
        # Do something based on the errors contained in e.message_dict.
        # Display them to a user, or handle them programmatically.
        pass
    instance.save()

如果您想向用户显示错误消息,只需将 e 传递给上下文并循环遍历模板中的消息,在您想要的任何位置显示它们。这似乎比向表单添加错误更简洁,因为它们是模型验证错误而不是表单错误。

在您看来:

    except ValidationError as e:
        context = {'e':e} # add other relevant data to context
        return render(request, 'your_template', context)

在您的模板中:

{% for key, values in e.message_dict %}
{{key}}: {{value}}
{% endfor %}

如果要将错误添加到表单中,请使用 form.add_error:

except ValidationError as e:
    for key, value in e.items():
        # this adds none-field errors to the form for each model error
        form.add_error(None, f'{key}: {value}')