web-dev-qa-db-ja.com

DjangoのModelFormのunique_together検証

私は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']

私の問題は、SolutionFormSolutionunique_together制約を検証しないため、フォームを保存しようとするとIntegrityErrorが返されることです。 validate_uniqueを使用して手動でこれを確認できることはわかっていますが、フォーム検証でこれをキャッチしてフォームエラーを自動的に返す方法があるかどうか疑問に思っていました。

ありがとう。

60
sttwister

フォームにクリーンメソッドを追加することで、ビューを変更せずにこれをなんとか修正しました。

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を実行する前にフォームに問題のプロパティを追加することだけです。

18
sttwister

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)

36
Jarmo Jaakkola

Felixが言うように、ModelFormsはunique_together制約の検証。

ただし、あなたのケースでは、実際にはフォームからその制約の1つの要素を除外しています。これがあなたの問題だと思います-半分がフォーム上にない場合、フォームは制約をどのようにチェックしますか?

23
Daniel Roseman

@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

おまけとして、重複した場合にオブジェクトを取得せず、データベースに存在するかどうかを確認するだけで、パフォーマンスが少し節約されます。

7
boblefrag

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バグのようです...しかし、以前に報告された問題を回避する必要があります。


編集:私はあまりにも早く話しました。たぶん私が上で書いたものはいくつかの状況ではうまくいくかもしれませんが、私の場合はうまくいきません。私はジャーモの答えを直接使ってしまいました。

1
Sam Hartsfield

私のソリューションは、Django 2.1に基づいています

SolutionFormをそのままにし、ソリューションに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()

ValidationErrorが処理されないため、save()でfull_clean()を呼び出しても機能しません

0
mb_atx

あなたはこのようなことをする必要があります:

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
douglaz

エラーメッセージを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
0
Risadinha