Валидация моделей в админке django с помощью инлайнов (когда я сохраняю несколько моделей одновременно)
Я создаю викторину на django. На картинке ниже вы можете видеть 3 модели (Quiz, QuizQuestionQuizAnswer) на 1 странице. Для этого я использовал NestedStackedInline и NestedTabularInline.
Мне нужно, чтобы при сохранении редактируемых форм происходила валидация теста. Например:
- Нужно проверить, отмечен ли хотя бы один ответ как правильный.
- Если ответов больше одного. и т.д.
models.py
class Quiz(models.Model):
title = models.CharField('Название теста', max_length=200)
description = models.TextField('Описание', max_length=2000, blank=True)
owner = models.ForeignKey(User, on_delete=models.DO_NOTHING)
pass_score = models.PositiveIntegerField(help_text='Минимальный результат (в процентах) для прохождения теста',
default=60,
validators=[
MaxValueValidator(100),
])
created_at = models.DateTimeField('Дата создания', auto_now_add=True, )
updated_at = models.DateTimeField('Дата обновления', auto_now=True, )
def get_quiz_question(self):
return self.quizquestion_set.all()
def __str__(self):
return self.title
class QuizQuestion(models.Model):
""" Вопросы для квиза"""
quiz = models.ForeignKey(Quiz, on_delete=models.CASCADE)
question = models.CharField('Вопрос', max_length=1000)
created_at = models.DateTimeField('Дата создания', auto_now_add=True, )
updated_at = models.DateTimeField('Дата обновления', auto_now=True, )
def __str__(self):
return self.question
class Meta:
verbose_name = 'Тест'
verbose_name_plural = 'Тесты'
class QuizAnswer(models.Model):
""" Варианты ответов """
question = models.ForeignKey(QuizQuestion, on_delete=models.CASCADE)
text = models.CharField('Варианты ответа', max_length=250)
is_correct = models.BooleanField('Отметьте правильные', default=False)
class Meta:
verbose_name = 'Ответ'
verbose_name_plural = 'Ответы'
def __str__(self):
return f'Вариант ответа'
admins.py
from quiz.models import *
from django.contrib import admin
from nested_inline.admin import NestedStackedInline, NestedModelAdmin, NestedTabularInline
class AnswerInLine(NestedTabularInline):
""" Делаем вложенную модель в админке"""
model = QuizAnswer
extra = 1
class QuestionInLine(NestedStackedInline):
""" Делаем вложенную модель в админке"""
model = QuizQuestion
extra = 2
inlines = [AnswerInLine]
def save_model(self, request, obj, form, change):
pass
class QuizAdmin(NestedModelAdmin):
# добавляет эти поля в админку приложения
# list_display = ('title','description','pass_score')
model = Quiz
inlines = [QuestionInLine]
readonly_fields = ('owner', 'created_at', 'updated_at')
def save_model(self, request, obj, form, change):
if not obj.pk:
# Only set added_by during the first save.
obj.owner = request.user
super().save_model(request, obj, form, change)
class CategoryAdmin(admin.ModelAdmin):
list_display = ("id", 'title')
list_display_links = ('id', 'title')
search_fields = ('title',)
# регистрирует модуль и его параметры
admin.site.register(Quiz, QuizAdmin)
Лучший способ сделать это - определить пользовательский набор форм, как показано ниже:
form.py
from django import forms
from quiz.models import *
class AnswerModelForm(forms.models.BaseInlineFormSet):
class Meta:
model = QuizAnswer
fields = "__all__"
def clean(self):
true_answer_count = 0
answer_count = 0
for form in self.forms:
try:
if form.cleaned_data.get("is_correct") is True:
true_answer_count += 1
answer_count += 1
except AttributeError:
pass
if true_answer_count > 1:
raise forms.ValidationError(
("A question can not have more than one correct answer")
)
if answer_count not in [2, 3, 4]:
raise forms.ValidationError(
("A question can have only 2, 3, 4 answers")
)
if true_answer_count == 0:
raise forms.ValidationError(
("A question must have at least one correct answer")
)
Вы можете легко подсчитать все правильные ответы и общее количество ответов на ваш вопрос и реализовать с их помощью свои собственные проверки
.
И вам обязательно нужно добавить свой собственный набор форм в AnswerInLine, как показано ниже:
admin.py
from quiz.models import *
from django.contrib import admin
from quiz.forms import *
from nested_inline.admin import NestedStackedInline,
NestedModelAdmin, NestedTabularInline
class AnswerInLine(NestedTabularInline):
formset=AnswerModelForm
""" Делаем вложенную модель в админке"""
model = QuizAnswer
extra = 1
class QuestionInLine(NestedStackedInline):
model = QuizQuestion
extra = 2
inlines = [AnswerInLine]
#rest of your code...