Django Model Form Different Choices

Hi,

Is it possible to create a select field on a form, but with different answers for different questions?

I have a Model with Choices, but I want different choices for each question?

class ProjectQuestionnaireAnswer(models.Model):
    YN_Choices = [
        ('Yes', 'Yes'),
        ('No', 'No'),
        ('Unknown', 'Unknown')
    ]
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    answer = models.CharField(max_length=50, blank=True, choices=YN_Choices)
    notes = models.TextField(blank=True)
    response = models.ForeignKey(ProjectQuestionnaireResponse, on_delete=models.CASCADE)
    
    class Meta:
        constraints = [
                models.UniqueConstraint(fields=['question','response'], name='project_unique_response2'),
            ]

The value stored in the database will be common, but the choices might be different.

So for example a question is:
Do any of the projects customer-facing procedures highlight areas of concern?

Selection would be:
very positive experience
not very positive

But the value in the database would be:
Green
Red

Hope that makes sense?

Thanks

Tom

Yes.
It’s actually rather easy to do if the answers are defined in a table that contains a ForeignKey to the questions.

In fact, it’s effectively covered in the Django Tutorial. If you review that tutorial, you’ll see that each polls question has a separate set of answers that apply to each question.

Hi Ken, hope all is good with you, and thanks for the reply.

I’ll take a look at the polls tutorial, and come back with any questions :slight_smile:

Thanks

Tom

Hi Ken,

Been a while since I raise this topic and just come back to it. For my questions choices would i FK my question to the choices model like below?

class Choice(models.Model):
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
class ProjectQuestionnaireQuestion(models.Model):
    questionnaire = models.ForeignKey(ProjectQuestionnaire, on_delete=models.CASCADE)
    sequence = models.IntegerField()
    question = models.TextField()

Would i need to modify this:

answer = models.CharField(max_length=50, blank=True, choices=YN_Choices)

which is from the

class ProjectQuestionnaireAnswer(models.Model):
    YN_Choices = [
        ('Yes', 'Yes'),
        ('No', 'No'),
        ('Unknown', 'Unknown')
    ]
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    answer = models.CharField(max_length=50, blank=True, choices=YN_Choices)
    notes = models.TextField(blank=True)
    response = models.ForeignKey(ProjectQuestionnaireResponse, on_delete=models.CASCADE)
    
    class Meta:
        constraints = [
                models.UniqueConstraint(fields=['question','response'], name='project_unique_response2'),
            ]

Thanks

Basically I’m not really sure how the answer model works with choices having a FK to question - should there be a link between Choices and Answer?

Ive added this
answer = models.ForeignKey(Choice, on_delete=models.CASCADE) within my ProjectQuestionnaireAnswer model.

Which I think is correct?

But I’ve created another issue now. :frowning:

ValueError: Cannot query "ProjectQuestionnaireResponse object (1)": Must be "ProjectQuestionnaire" instance.

Which i am trying to debug, but unrelated to the change with choices

Yes, this field can become an FK to Choice. In this case, Django automatically renders this as a select field. (You will need to apply the appropriate selection constraint to the queryset. See Form fields | Django documentation | Django)

Thanks, Ken. Ill come back to the queryset once I can render the page. :frowning:

I’ve renamed the models to include Project prefix and now i have broken it. I’m struggling to identify where i’ve broken it

Traceback (most recent call last):
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\core\handlers\exception.py", line 47, in inner
    response = get_response(request)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\core\handlers\base.py", line 181, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "D:\_AzDo\Python.WebApp\app\views.py", line 425, in get_questionnaire
    new_answers = ProjectQuestionnaireQuestion.objects.filter(
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\query.py", line 974, in filter
    return self._filter_or_exclude(False, args, kwargs)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\query.py", line 992, in _filter_or_exclude
    clone._filter_or_exclude_inplace(negate, args, kwargs)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\query.py", line 999, in _filter_or_exclude_inplace
    self._query.add_q(Q(*args, **kwargs))
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1375, in add_q
    clause, _ = self._add_q(q_object, self.used_aliases)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1396, in _add_q
    child_clause, needed_inner = self.build_filter(
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1302, in build_filter
    self.check_related_objects(join_info.final_field, value, join_info.opts)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1134, in check_related_objects
    self.check_query_object_type(value, opts, field)
  File "d:\_AzDo\Python.WebApp\venv\lib\site-packages\django\db\models\sql\query.py", line 1115, in check_query_object_type
    raise ValueError(
ValueError: Cannot query "ProjectQuestionnaireResponse object (1)": Must be "ProjectQuestionnaire" instance.
def get_questionnaire(request, project_id, questionnaire_id):

    # Get or Create the Response object for the parameters
    next = request.POST.get('next', '/')
    response, created = ProjectQuestionnaireResponse.objects.get_or_create(
        project_name_id=project_id, questionnaire_id=questionnaire_id
    )

    AnswerFormSet = modelformset_factory(ProjectQuestionnaireAnswer, form=AnswerForm, fields=('answer','notes',), extra=0)

    answer_queryset = ProjectQuestionnaireAnswer.objects.filter(response=response
    ).order_by('question__sequence'
    ).select_related('question')

    if request.method == 'POST':
        formset = AnswerFormSet(request.POST)
        instances = formset.save()
        return HttpResponseRedirect(next)
    else:
        # Get the list of questions for which no Answer exists
        new_answers = ProjectQuestionnaireQuestion.objects.filter(
            questionnaire__response=response
        ).exclude(
            answer__response=response
        )

        # This is safe to execute every time. If all answers exist, nothing happens
        for new_answer in new_answers:
            ProjectQuestionnaireAnswer(question=new_answer, response=response).save()

        answer_formset = AnswerFormSet(queryset=answer_queryset)
                

    return render(request, 'project_questionnaire.html', {'formset': answer_formset,'project_name':response.project_name,'title':response.questionnaire.title})

You’ve made a change to your Model (the answer field). Did you have data in that model? If so, that data is no longer valid.

From the error message, I’m guessing this is the code at line 425.

What is the answer field of your ProjectQuestionnaireQuestion?

I cleared all data down. So starting fresh.

I’ve messed up. I added the prefix to all the models that make up the questionnaire.

class ProjectQuestionnaire(models.Model):
    title = models.CharField(max_length=50, blank=False, unique=True)

    def __str__(self):
        return str(self.title)
    

class ProjectQuestionnaireQuestion(models.Model):
    questionnaire = models.ForeignKey(ProjectQuestionnaire, on_delete=models.CASCADE)
    sequence = models.IntegerField()
    question = models.TextField()

    def __str__(self):
        return str(self.question)

class ProjectQuestionnaireResponse(models.Model):
    project_name = models.ForeignKey(Project, on_delete=models.CASCADE)
    questionnaire = models.ForeignKey(ProjectQuestionnaire, on_delete=models.CASCADE)
    user = models.CharField(max_length=50, blank=True)

    class Meta:
        constraints = [
                models.UniqueConstraint(fields= ['project_name','questionnaire'], name='project_unique_response1'),
            ]

class Choice(models.Model):
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)

 
class ProjectQuestionnaireAnswer(models.Model):
    YN_Choices = [
        ('Yes', 'Yes'),
        ('No', 'No'),
        ('Unknown', 'Unknown')
    ]
    question = models.ForeignKey(ProjectQuestionnaireQuestion, on_delete=models.CASCADE)
    answer = models.ForeignKey(Choice, on_delete=models.CASCADE)
    notes = models.TextField(blank=True)
    response = models.ForeignKey(ProjectQuestionnaireResponse, on_delete=models.CASCADE)
    
    class Meta:
        constraints = [
                models.UniqueConstraint(fields=['question','response'], name='project_unique_response2'),
            ]

This comes back to my misunderstanding of the left side of something = something

ProgrammingError at /admin/app/projectquestionnaireanswer/
column app_projectquestionnaireanswer.answer_id does not exist
LINE 1: ...tquestionnaire"."id") INNER JOIN "app_choice" ON ("app_proje...
                                                             ^
HINT:  Perhaps you meant to reference the column "app_projectquestionnaireanswer.answer".

proper broken

Is it because i’ve added a fk to Choices answer = models.ForeignKey(Choice, on_delete=models.CASCADE) thats causing this.

I can see anywhere else that would be causing this.

No, this appears to me to be a result of all the renaming you’ve done.

I’m guessing you’ve either missed doing a makemigrations / migrate, or somehow you’ve gotten your migrations messed up.

If you don’t think you’ve missed any of that, then my first recommendation is to try this with a completely new database.

It’s a completely new database, i’ve only just added these models.

Ill swap out the models for new old models and see if this resolves the issue.

Might be easier to just delete all your migration files, do a fresh new makemigrations, and build your database from there.

Will do, this is only dev, so no worries.

The old models are even worse. I think because i corrected the name of the FK fields from being project_name to project which i think has broken other things :frowning:

What a nightmare

I’ve put the new models back. Still doesn’t work, but something must be up with the view, cause I can create questions, answers, responses, choices all within the admin.

Does being able to do this in admin, suggest its the view? so going from the template to the questionnaire?

No way to tell without the complete context of the problem / errors you’re receiving.

Fixed it :slight_smile:

        new_answers = ProjectQuestionnaireQuestion.objects.filter(
            questionnaire__projectquestionnaireresponse=response
        ).exclude(
            projectquestionnaireanswer__response=response
        )

instead of

        new_answers = ProjectQuestionnaireQuestion.objects.filter(
            questionnaire__response=response
        ).exclude(
            answer__response=response
        )

Now onto the queryset.

Thanks, Ken.

Tom.