2010-01-26 92 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