【问题标题】:Django's ModelForm unique_together validationDjango 的 ModelForm unique_together 验证
【发布时间】:2011-01-09 14:34:21
【问题描述】:

我有一个看起来像这样的 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 不验证Solutionunique_together 约束,因此,它在尝试保存表单时返回IntegrityError。我知道我可以使用validate_unique 手动检查这个,但我想知道是否有任何方法可以在表单验证中捕获它并自动返回表单错误。

谢谢。

【问题讨论】:

  • 您确定一切设置正确吗,因为有关模型的文档清楚地形成了 syas:“默认情况下,clean() 方法验证标记为 unique、unique_together 或 unique_for_date|month 的字段的唯一性|型号年份。" docs.djangoproject.com/en/1.1/topics/forms/modelforms/…
  • 你可以试试没有排除部分吗?手动选择我认为由您的观点决定的问题。

标签: django validation modelform


【解决方案1】:

我通过覆盖 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) 在初始化器上。

【讨论】:

  • 请注意,这仅验证此模型的任何表单,而 unique_together 用于底层数据库。这意味着任何直接使用模型对象的东西都不受此验证的约束。
【解决方案2】:

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

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 之前向表单添加一个问题属性。

【讨论】:

  • 不要使用裸的 except 子句。即使异常是由于数据库服务器被流星击中,这也会通过。相反,请使用“除了 Solution.DoesNotExist:”。
  • 这对我有用。然而,与 OP 不同的是,我的两个字段是常规字段,我不必排除 META's exclude` 属性中的任何一个字段。
  • 这种技术的一个小问题是竞争条件。在此检查之后可能会插入解决方案对象,如果是,您仍然会看到 IntegrityError。我认为为避免这种情况,您可以在保存时发现错误。
【解决方案3】:

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

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

【讨论】:

  • 确实是问题所在。所以我想如果不包括问题字段,我就不会在表单上出现错误,并且我必须手动检查这种情况。
  • 那我们该如何解决这个问题呢?我们想从模型表单中排除 ForeignKey 字段,但在视图中将 ForeignKey 存储在模型表单中,因为我们不授予用户访问将 ForeignKey 存储在模型表单中的权限。
【解决方案4】:

@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

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

【讨论】:

    【解决方案5】:

    在 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 的答案。

    【讨论】:

      【解决方案6】:

      你需要做这样的事情:

      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
      

      【讨论】:

      • 表单仍然没有验证unique_together约束,可能是因为exclude属性中提到了问题,即使它有一个有效的实例
      【解决方案7】:

      如果您希望错误消息与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
      

      【讨论】:

        【解决方案8】:

        我的解决方案基于 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 将未被处理

        【讨论】:

          【解决方案9】:

          在我的例子中,我需要排除 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)
          

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

          【讨论】:

            猜你喜欢
            • 2016-12-20
            • 2012-08-13
            • 2015-11-22
            • 2012-09-30
            • 1970-01-01
            • 2017-04-26
            • 2013-11-26
            • 2013-08-22
            相关资源
            最近更新 更多