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 отношениями напрямую может показаться странной, если вы не привыкли к этому.