2010-01-26 91 views
46

我有一个看起来像这样的Django模型。Django的ModelForm unique_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来手动检查这一点,但我想知道是否有任何方法可以在表单验证中捕获这些信息并自动返回表单错误。

谢谢。

+2

你确定你设置都正确,因为有关模型的形式syas清晰的文档: “默认情况下,clean()方法验证在模型上标记为unique,unique_together或unique_for_date | month | year的字段的唯一性。”http://docs.djangoproject.com/en/1.1/topics/forms/modelforms /#重写 - 清除方法 – 2010-01-26 18:47:18

+2

你可以在没有排除部分的情况下尝试吗?手动选择我假设的问题由您的视图决定。 – 2010-01-26 20:27:06

回答

12

我设法解决这个问题,而不加入干净的方法,我的形式修改视图:

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


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) 

现在我只是始终确保不会在表格上提供的属性仍然是可用的,例如:

+9

不要使用裸体except子句。即使异常是由于数据库服务器被流星击中而造成的,也会通过。相反,请使用“Solution.DoesNotExist除外:”。 – GDorn 2013-10-15 21:38:38

18

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

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

+2

的确是这个问题。所以我想我不能在没有包括问题字段的情况下在表单上发生错误,而且我必须手动检查这种情况。 – sttwister 2010-01-27 15:16:53

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 
+0

表单仍然没有验证'unique_together'约束,可能是因为'exclude'属性中提到了问题,即使它有一个有效的实例 – sttwister 2010-01-28 16:06:11

32

我通过重写的ModelForm的validate_unique()方法解决了这个相同的问题初始值设定项上的instance=Solution(problem=some_problem)

+1

请注意,这只验证此模型的任何表单,而unique_together用于底层数据库。 这意味着任何直接使用模型对象的东西都不受此验证的约束。 – Herge 2012-08-09 16:16:23

+1

使用受保护的方法是否好。 – Satyajeet 2017-03-31 07:07:08

1

随着亚尔莫的答案的帮助,下面似乎很好地为我工作(在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的答案。

5

@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 

作为奖励,你不retreive对象重复的情况,但如果它在数据库中保存的表演一点点仅存检查。

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