私は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
がSolution
のunique_together
制約を検証しないため、フォームを保存しようとするとIntegrityError
が返されることです。 validate_unique
を使用して手動でこれを確認できることはわかっていますが、フォーム検証でこれをキャッチしてフォームエラーを自動的に返す方法があるかどうか疑問に思っていました。
ありがとう。
フォームにクリーンメソッドを追加することで、ビューを変更せずにこれをなんとか修正しました。
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
を実行する前にフォームに問題のプロパティを追加することだけです。
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)
。
Felixが言うように、ModelFormsはunique_together
制約の検証。
ただし、あなたのケースでは、実際にはフォームからその制約の1つの要素を除外しています。これがあなたの問題だと思います-半分がフォーム上にない場合、フォームは制約をどのようにチェックしますか?
@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
おまけとして、重複した場合にオブジェクトを取得せず、データベースに存在するかどうかを確認するだけで、パフォーマンスが少し節約されます。
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バグのようです...しかし、以前に報告された問題を回避する必要があります。
編集:私はあまりにも早く話しました。たぶん私が上で書いたものはいくつかの状況ではうまくいくかもしれませんが、私の場合はうまくいきません。私はジャーモの答えを直接使ってしまいました。
私のソリューションは、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()を呼び出しても機能しません
あなたはこのようなことをする必要があります:
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
エラーメッセージを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