我有一个看起来像这样的Django模型。
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
不会验证Solution
的{{1}}约束,因此,在尝试保存表单时会返回unique_together
。我知道我可以使用IntegrityError
手动检查这个,但我想知道是否有任何方法可以在表单验证中捕获它并自动返回表单错误。
感谢。
答案 0 :(得分:35)
我通过覆盖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)
。
答案 1 :(得分:22)
正如Felix所说,ModelForms应该在验证中检查unique_together
约束。
但是,在您的情况下,您实际上是从表单中排除该约束的一个元素。我想这是你的问题 - 如果表格的一半甚至不在表格上,表格将如何检查约束?
答案 2 :(得分:17)
我设法通过在表单中添加一个干净的方法来修改它而不修改视图:
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
之前向表单添加一个问题属性。
答案 3 :(得分:7)
来自@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
作为奖励,您不会在重复的情况下检索该对象,但只检查它是否存在于数据库中,从而节省了一些性能。
答案 4 :(得分:1)
在Jarmo的回答的帮助下,以下似乎对我很有效(在Django 1.3中),但我可能已经打破了一些角落案例(围绕_get_validation_exclusions
有很多票证):< / p>
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
我不确定,但这对我来说似乎是一个Django错误...但我不得不环顾以前报道过的问题。
答案 5 :(得分:0)
你需要做这样的事情:
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
答案 6 :(得分:0)
如果您希望错误消息与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
答案 7 :(得分:0)
我的解决方案基于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将无法处理
答案 8 :(得分:0)
我需要排除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)
这不是我想要的最干净的解决方案,但认为它可能会使某人受益。