Reputation: 91
I am building Django quiz application that will contain questions of 3 types of answers:
I am not sure how should I design django models for such pattern.
At the moment my models look like this:
class Question(CoreModel, models.Model):
TYPES = (
(1, 'radio'),
(2, 'checkbox'),
(3, 'text'),
)
type = models.CharField(max_length=8, choices=TYPES, default='radio')
text = models.CharField(max_length=2048, null=False, blank=False)
class Choice(CoreModel, models.Model):
question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
text = models.CharField(max_length=2048, null=False, blank=False)
correct = models.BooleanField(default=False)
class Answer(CoreModel, models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
choice = models.ForeignKey(Choice, on_delete=models.DO_NOTHING)
text = models.CharField(max_length=2048, null=False, blank=False)
created = models.DateTimeField(auto_now_add=True)
But it looks like it will not work as expected. I really don't like 'choice' and 'text' fields at the same time in my Answer model but that’s all I could think of.
Any ideas or suggestions? Maybe I need some more other models for further logic?
Upvotes: 4
Views: 1991
Reputation: 91
Thanks for the answers, so here's mine solution:
TYPES = (
(1, 'One answer'),
(2, 'Multiple answer'),
(3, 'Text answer'),
)
class Quiz(CoreModel, models.Model):
class Meta:
verbose_name = 'Quiz'
verbose_name_plural = 'Quizzes'
ordering = ('id',)
def __str__(self):
return self.title
class Question(models.Model):
class Meta:
verbose_name = 'Question'
verbose_name_plural = 'Questions'
ordering = ('id',)
title = models.CharField('Title (must be unique)', max_length=256, blank=False, null=False, unique=True)
active = models.BooleanField('Is active?', default=True, db_index=True)
type = models.IntegerField(choices=TYPES, default=1, verbose_name='Question type')
text = models.CharField(max_length=2048, null=False, blank=False, verbose_name='Question text')
from_quiz = models.ForeignKey(Quiz, on_delete=models.DO_NOTHING, verbose_name='Quiz', null=True)
def __str__(self):
return self.title
class Choice(CoreModel, models.Model):
class Meta:
verbose_name = 'Answer choice'
verbose_name_plural = 'Answer choices'
ordering = ('id',)
from_question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name='Question')
text = models.CharField(max_length=2048, null=False, blank=False, verbose_name='Question text')
correct = models.BooleanField(default=False, verbose_name='Is it right choice?')
def __str__(self):
return self.title
class Answer(CoreModel, models.Model):
class Meta:
verbose_name = 'Answer'
verbose_name_plural = 'Answers'
ordering = ('id',)
user_ip = models.CharField(max_length=2048, verbose_name='ip')
user = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name='User', null=True)
answered_question = models.ForeignKey(Question, on_delete=models.CASCADE, verbose_name='Question')
answer_choice = models.ForeignKey(Choice, on_delete=models.CASCADE, verbose_name='Chosen option', null=True)
answer_text = models.CharField(max_length=2048, null=True, verbose_name='Text answer')
created = models.DateTimeField(auto_now_add=True, verbose_name='Answer time')
def clean(self):
super().clean()
if self.answer_choice is None and self.answer_text is None:
raise ValidationError('Field 'answer_choice' and 'answer_text' are empty! '
'Must be filled at least one of')
def __str__(self):
return f'{self.answered_question} - {self.answer_choice or ""}{self.answer_text or ""}'
Answer model contain 2 fields of answers: FK to choice and text answer and clean method that check if at least one answer type field is filled.
Upvotes: 1
Reputation: 10237
An answer should be able to have many choices, in your current architecture Answer
has choice
foreign key, i.e. it points to a single choice. Instead, I would create choices
field on Answers
that is of a text type and it should contain comma-separated IDs of choices.
You will need to perform a validation when creating an answer: if a question is a text type then there should be no choices, if single choice then it should be a single ID, if multiple then 1+ IDs.
Upvotes: 0
Reputation: 6828
I would do something around the lines of:
class Quiz(CoreModel, models.Model):
name = models.CharField(max_length=2048, null=False, blank=False)
class Question(CoreModel, models.Model):
TYPES = (
(1, 'radio'),
(2, 'checkbox'),
(3, 'text'),
)
quiz = models.ForeignKey(Quiz)
type = models.CharField(max_length=8, choices=TYPES, default='radio')
prompt = models.CharField(max_length=2048, null=False, blank=False)
correct_free_text = models.CharField(max_length=2048, null=True, blank=True)
rank = models.SmallIntegerField(default=0)
class Choice(CoreModel, models.Model):
question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
text = models.CharField(max_length=2048, null=False, blank=False)
correct = models.BooleanField(default=False)
rank = models.SmallIntegerField(default=0)
class Answer(CoreModel, models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.DO_NOTHING)
question = models.ForeignKey(Question, on_delete=models.DO_NOTHING)
choice = models.ForeignKey(Choice, null=True, on_delete=models.DO_NOTHING)
free_text = models.CharField(max_length=2048, null=True, blank=True)
created = models.DateTimeField(auto_now_add=True)
I would try to keep the "reusability" to an absolute minimum. What I mean is don't try to reuse Choice
or Question
in multiple Quiz
es, it doesn't matter if they contain the same information, let them be different. Hope this helps, let me know if you have any other questions.
Upvotes: 1