Подтвердить что ты не робот

Проверка подлинности 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, чтобы вручную проверить это, но мне было интересно, есть ли способ поймать это в форме проверки и автоматически вернуть ошибку формы.

Спасибо.

4b9b3361

Ответ 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 не обрабатывается