Проверка подлинности 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']
Моя проблема в том, что SolutionForm
не проверяет ограничение Solution
unique_together
и, таким образом, возвращает IntegrityError
при попытке сохранить форму. Я знаю, что я мог бы использовать validate_unique
, чтобы вручную проверить это, но мне было интересно, есть ли способ поймать это в форме проверки и автоматически вернуть ошибку формы.
Спасибо.
Ответы
Ответ 1
Мне удалось исправить это, не изменяя вид, добавив в мою форму чистый метод:
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
.
Ответ 2
Я решил эту же проблему, переопределив метод validate_unique()
ModelForm:
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)
в инициализаторе.
Ответ 3
Как говорит Феликс, ModelForms должны проверять ограничение unique_together
в их проверке.
Однако в вашем случае вы фактически исключаете один элемент этого ограничения из своей формы. Я предполагаю, что это ваша проблема - как форма будет проверять ограничение, если половина его даже не на форме?
Ответ 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
С помощью ответа Ярмо следующее выглядит для меня хорошо (в 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
Ответ 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 в покое, у вас есть метод save() в Solution
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()
Вызов full_clean() в save() не работает, так как ошибка ValidationError не обрабатывается