Extending a base model in django

Question:

I want to create base model for my questions named Question
and extend it by any other question types:

class Question(models.Model):
    questionnaire = models.ForeignKey(to='Questionnaire', on_delete=models.CASCADE, related_name='questions')
    question = models.TextField()
    description = models.TextField(null=True, blank=True)
    media = models.FileField(upload_to='medias', blank=True, null=True)
    is_required = models.BooleanField(default=False)

    def __str__(self):
        return f'{self.questionnaire.name} - {self.question}'

and I want to extend it like this:

class OptionalQuestion(Question):
    multiple_choice = models.BooleanField(default=False)
    additional_options = models.BooleanField(default=False)
    # If multiple_choice is True, then max_selected_options and min_selected_options will be used
    max_selected_options = models.IntegerField(null=True, blank=True)
    min_selected_options = models.IntegerField(null=True, blank=True)

    # If additional_options is True, then all_options and nothing_selected will be used
    all_options = models.BooleanField(default=False, null=True, blank=True)
    nothing_selected = models.BooleanField(default=False, null=True, blank=True)

    def save(self, *args, **kwargs):
        if not self.multiple_choice:
            self.max_selected_options = None
            self.min_selected_options = None
        if not self.additional_options:
            self.all_options = None
            self.nothing_selected = None
        if self.nothing_selected or self.all_options:
            self.all_options = False
            self.multiple_choice = False
        super().save(*args, **kwargs)

It’s all good in the first creation of migrations
But the next time the Django wants me to provide a default value for ‘question_ptr’ field I don’t even know what is this field

I tried making base class abstract and when making migrations Django shows me so many errors like this:

question_app.TextAnswerQuestion.questionnaire: (fields.E305) Reverse query name for 'question_app.TextAnswerQuestion.questionnaire' clashes with reverse query name for 'question_app.DropDownQuestion.questionnaire'.
        HINT: Add or change a related_name argument to the definition for 'question_app.TextAnswerQuestion.questionnaire' or 'question_app.DropDownQuestion.questionnaire'.
question_app.TextAnswerQuestion.questionnaire: (fields.E305) Reverse query name for 'question_app.TextAnswerQuestion.questionnaire' clashes with reverse query name for 'question_app.NumberAnswerQuestion.questionnaire'.
        HINT: Add or change a related_name argument to the definition for 'question_app.TextAnswerQuestion.questionnaire' or 'question_app.NumberAnswerQuestion.questionnaire'.
question_app.TextAnswerQuestion.questionnaire: (fields.E305) Reverse query name for 'question_app.TextAnswerQuestion.questionnaire' clashes with reverse query name for 'question_app.OptionalQuestion.questionnaire'.
        HINT: Add or change a related_name argument to the definition for 'question_app.TextAnswerQuestion.questionnaire' or 'question_app.OptionalQuestion.questionnaire'.

I don’t know what to do

Asked By: Mostafa Kooti

||

Answers:

First of all make Question model abstract = True

When you have ForeignKey relationships in an abstract base class every derived class will have this relationship. So you cannot hardcode its related_name, because all derived classes will try to create the same accessor on the related class.

As Django documentation suggests, you can do something like this in Question model:

questionnaire = models.ForeignKey(to='Questionnaire', on_delete=models.CASCADE, related_name="%(app_label)s_%(class)s_questions")

Check Django documentation here. Click here

Answered By: Masud Dipu