Django 的 ModelForm only_together 验证

我有一个姜戈模型看起来像这样。

class Solution(models.Model):
'''
Represents a solution to a specific problem.
'''
name = models.CharField(max_length=50)
problem = models.ForeignKey(Problem)
description = models.TextField(blank=True)
date = models.DateTimeField(auto_now_add=True)


class Meta:
unique_together = ("name", "problem")

我使用一个表单来添加如下模型:

class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']

我的问题是,SolutionForm不验证 Solutionunique_together约束,因此,它在尝试保存表单时返回一个 IntegrityError。我知道我可以使用 validate_unique手动检查这一点,但我想知道是否有任何方法可以在表单验证中捕捉到这一点,并自动返回表单错误。

谢谢。

25285 次浏览

正如 Felix 所说,ModelForms 应该在验证中检查 unique_together约束。

但是,在您的示例中,实际上是从窗体中排除了该约束的一个元素。我想这就是你的问题了——如果一半的约束甚至都不在表单上,那么表单如何检查约束呢?

你需要这样做:

def your_view(request):
if request.method == 'GET':
form = SolutionForm()
elif request.method == 'POST':
problem = ... # logic to find the problem instance
solution = Solution(problem=problem) # or solution.problem = problem
form = SolutionForm(request.POST, instance=solution)
# the form will validate because the problem has been provided on solution instance
if form.is_valid():
solution = form.save()
# redirect or return other response
# show the form

通过在表单中添加一个干净的方法,我设法在不修改视图的情况下解决了这个问题:

class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']


def clean(self):
cleaned_data = self.cleaned_data


try:
Solution.objects.get(name=cleaned_data['name'], problem=self.problem)
except Solution.DoesNotExist:
pass
else:
raise ValidationError('Solution with this Name already exists for this problem')


# Always return cleaned_data
return cleaned_data

在视图中,我现在需要做的唯一一件事情是在执行 is_valid之前向窗体添加一个有问题的属性。

我通过重写 ModelForm 的 validate_unique()方法解决了同样的问题:


def validate_unique(self):
exclude = self._get_validation_exclusions()
exclude.remove('problem') # allow checking against the missing attribute


try:
self.instance.validate_unique(exclude=exclude)
except ValidationError, e:
self._update_errors(e.message_dict)

现在我只需要确保表单上没有提供的属性仍然可用,例如初始化器上的 instance=Solution(problem=some_problem)

在 Jarmo 的回答帮助下,下面的内容似乎对我很有用(在 Django 1.3中) ,但是我可能破解了一些关键问题(有很多票围绕 _get_validation_exclusions) :

class SolutionForm(forms.ModelForm):
class Meta:
model = Solution
exclude = ['problem']


def _get_validation_exclusions(self):
exclude = super(SolutionForm, self)._get_validation_exclusions()
exclude.remove('problem')
return exclude

我不确定,但我觉得这像是姜戈的虫子,但我得看看之前报道过的问题。


编辑: 我说得太早了。也许我上面所写的在某些情况下会有用,但在我的情况下不会; 我最终直接使用了 Jarmo 的答案。

来自@sttwister 的解决方案是正确的,但可以简化。

class SolutionForm(forms.ModelForm):


class Meta:
model = Solution
exclude = ['problem']


def clean(self):
cleaned_data = self.cleaned_data
if Solution.objects.filter(name=cleaned_data['name'],
problem=self.problem).exists():
raise ValidationError(
'Solution with this Name already exists for this problem')


# Always return cleaned_data
return cleaned_data

作为奖励,您不会在重复的情况下检索对象,而只是检查它是否存在于数据库中,这样可以节省一点性能。

如果希望错误消息与 name字段相关联(并出现在其旁边) :

def clean(self):
cleaned_data = super().clean()
name_field = 'name'
name = cleaned_data.get(name_field)


if name:
if Solution.objects.filter(name=name, problem=self.problem).exists():
cleaned_data.pop(name_field)  # is also done by add_error
self.add_error(name_field, _('There is already a solution with this name.'))


return cleaned_data

我的解决方案是基于 Django 2.1

不要使用 SolutionForm,在 Solution 中使用 save ()方法

class Solution(models.Model):
...
def save(self, *args, **kwargs):
self.clean()
return super(Solution, self).save(*args, **kwargs)




def clean():
# have your custom model field checks here
# They can raise Validation Error


# Now this is the key to enforcing unique constraint
self.validate_unique()

在 save ()中调用 full _ clean ()不起作用,因为 ValidationError 将被取消处理

我需要在我的案例中排除 company字段,并将其添加到视图的 form_valid函数中。我最终做了以下事情(从不同的答案中获得灵感)。 在我的 CreateView

    def form_valid(self, form):
cleaned_data = form.cleaned_data
user_company = self.request.user.profile.company
if UnitCategory.objects.filter(code=cleaned_data['code'],
company=user_company).exists():
form.add_error('code',                           _(
'A UnitCategory with this Code already exists for this company.'))
return super(UnitCategoryCreateView, self).form_invalid(form)
if UnitCategory.objects.filter(color=cleaned_data['color'],
company=user_company).exists():
form.add_error('color',                           _(
'A UnitCategory with this Color already exists for this company.'))
return super(UnitCategoryCreateView, self).form_invalid(form)
form.instance.company = user_company
return super(UnitCategoryCreateView, self).form_valid(form)

在我的 UpdateView中,我必须排除对象的当前实例,以检查查询是否使用 exclude(pk=self.kwargs['pk'])存在

    def form_valid(self, form):
cleaned_data = form.cleaned_data
user_company = self.request.user.profile.company
if UnitCategory.objects.filter(code=cleaned_data['code'],
company=user_company).exclude(pk=self.kwargs['pk']).exists():
form.add_error(
'code', _('A UnitCategory with this Code already exists for this company.'))
return super(UnitCategoryUpdateView, self).form_invalid(form)
if UnitCategory.objects.filter(color=cleaned_data['color'],
company=user_company).exclude(pk=self.kwargs['pk']).exists():
form.add_error('color', _(
'A UnitCategory with this Color already exists for this company.'))
return super(UnitCategoryUpdateView, self).form_invalid(form)
# Return form_valid if no errors raised
# Add logged-in user's company as form's company field
form.instance.company = user_company
return super(UnitCategoryUpdateView, self).form_valid(form)

这不是我想要的最干净的解决方案,但我觉得对某些人有好处。

如果 一直都是想要检查唯一性约束(例如在您创建的每个 ModelForm 上) ,您可以确保唯一性约束始终在模型上进行验证:

# Always check name-problem uniqueness
def validate_unique(self, exclude=None):
if exclude is None:
exclude = []


try:
exclude.remove("problem")
except ValueError:
pass


return super().validate_unique(exclude)

这是因为在表单验证期间调用了模型的 validate_unique方法。