Проверка Django ModelForm unique_together - PullRequest
56 голосов
/ 26 января 2010

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

Спасибо.

Ответы [ 8 ]

36 голосов
/ 21 сентября 2010

Я решил эту же проблему, переопределив 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) на инициализаторе.

22 голосов
/ 26 января 2010

Как говорит Феликс, ModelForms должны проверять ограничение unique_together в своей проверке.

Однако в вашем случае вы фактически исключаете из формы один элемент этого ограничения. Я полагаю, что это ваша проблема - как форма будет проверять ограничение, если половина его отсутствует даже в форме?

18 голосов
/ 28 января 2010

Мне удалось исправить это без изменения представления, добавив чистый метод в мою форму:

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.

7 голосов
/ 05 мая 2015

решение от @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

В качестве бонуса вы не извлекаете объект в случае дублирования, а только проверяете, существует ли он в базе данных, сохраняя немного производительности.

1 голос
/ 06 августа 2011

С помощью ответа Ярмо мне кажется, что следующее работает хорошо (в 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 ... но мне придется осмотреть ранее сообщенные проблемы.


Редактировать: Iговорил слишком рано.Может быть, то, что я написал выше, будет работать в некоторых ситуациях, но не в моей;Я закончил тем, что использовал ответ Ярмо напрямую.

0 голосов
/ 26 апреля 2019

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

0 голосов
/ 15 января 2016

Если вы хотите, чтобы сообщение об ошибке было связано с полем 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 голосов
/ 27 января 2010

Вам нужно будет сделать что-то вроде этого:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...