Django - проверка m2m отношений

У меня есть следующие модели:

class Question(Model):
    # ...

class Choice(Model):
    question = ForeignKey(Question)
    # ...

class AnswerToQuestion(Model):
    user = ForeignKey(User)
    question = ForeignKey(Question)
    selected_choices = ManyToManyField(Choice)

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

Вот что я пробовал в методе clean AnswerToQuestion:

    def clean(self):
        super().clean()
        if self.pk and self.selected_choices.exclude(question=self.question).exists():
            raise ValidationError("Invalid choice(s)")

Однако это не работает так, как ожидалось: поскольку отношения m2m обновляются после фактического сохранения модели, если я попытаюсь связать недействительный выбор, это будет работать нормально, а затем каждое последующее сохранение будет неудачным, потому что я уже имею недействительный связанный выбор.

Есть ли другой способ достичь того, что я пытаюсь сделать?

def save(self, *args, **kwargs):
    if self.pk and self.selected_choices.exclude(question=self.question).exists():
       raise ValidationError("Invalid choice(s)")
    super(AnswerToQuestion, self).save(*args, **kwargs)

Надеюсь, это решит

Другим способом может быть использование сквозной модели.

Допустим, у вас есть вот такой вариант:

class Choice(Model):
    question = ForeignKey(Question)
    # ...

class AnswerToQuestion(Model):
    user = ForeignKey(User)
    question = ForeignKey(Question)
    selected_choices = ManyToManyField(Choice, through="AnswerToQuestionChoiceM2M", through_fields=("answer", "choice"))


class AnswerToQuestionChoiceM2M(Model):
    answer = ForeignKey(AnswerToQuestion)
    choice = ForeignKey(Choice)
    # ...

Тогда вы можете сохранить отношения напрямую:

AnswerToQuestionChoiceM2M(answer_id=..., choice_id=...).save()

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

try:
    AnswerToQuestionChoiceM2M(answer_id=..., choice_id=...).save()
except IntegrityError:
    raise ValidationError("Invalid choice(s)")

Этот подход благоприятен для параллелизма, поскольку фактическая проверка происходит в базе данных. Базы данных хорошо справляются с одновременными транзакциями, поэтому вам не придется этого делать.

Такой подход также позволит вам добавить дополнительную информацию AnswerToQuestionChoiceM2M на случай, если вам понадобится сделать это в какой-то момент. Например, вы можете захотеть сохранить datetime, когда произошел выбор.

Этот подход требует больше кода. Кроме того, работа с M2M отношениями напрямую может показаться странной, если вы не привыкли к этому.

Вернуться на верх