75

我有一个看起来像这样的 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']

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

谢谢。

4

9 回答 9

39

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


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)在初始化程序上。

于 2010-09-21T06:40:03.057 回答
29

我设法通过在我的表单中添加一个干净的方法来解决这个问题,而无需修改视图:

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

于 2010-01-28T16:03:00.150 回答
28

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

但是,在您的情况下,您实际上是从表单中排除了该约束的一个元素。我想这是你的问题 - 如果其中一半甚至不在表单上,​​表单将如何检查约束?

于 2010-01-26T20:19:46.667 回答
9

@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

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

于 2015-05-05T10:03:17.667 回答
1

在 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

我不确定,但这对我来说似乎是一个 Django 错误......但我必须查看先前报告的问题。


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

于 2011-08-05T22:35:30.557 回答
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
于 2010-01-27T09:54:09.157 回答
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
于 2016-01-15T09:45:41.523 回答
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 将未处理

于 2019-04-26T20:57:33.070 回答
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)

不是我希望的最干净的解决方案,但认为它可能会使某人受益。

于 2019-09-06T00:00:53.270 回答