Paul
Paul

Reputation: 91

Django quiz application with different types of questions

I am building Django quiz application that will contain questions of 3 types of answers:

  1. Single option answer;
  2. Multiple option answer;
  3. Text answer;

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

Answers (3)

Paul
Paul

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 ""}'

enter image description here

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

GProst
GProst

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

Javier Buzzi
Javier Buzzi

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 Quizes, 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

Related Questions