Search code examples
django-modelsdjango-formsdjango-model-fieldlimit-choices-to

How to display CHOICES in Django ModelForm


  • I have a policy_category model with 16 categories.
  • I have a Response model to record survey answers. The model includes a field to record the respondent's 1-5 rating for each of the policy categories. (policy_1_rank..policy_16_rank, etc.)

I can't figure out how to display the CHOICES I created for each field:

Form Template

 <div><b>How important are each of the following issues or policy areas to you, when you're selecting a candidate for president?</b></div>
<div><br></div>
<ul><div>{{ policy_category.object(pk=1) }}</div></ul>
# show POLICY_1_CHOICES below:
<div>{{ form.policy_1_rank }}</div>

<div><br></div>
<ul><div>{{ policy_category.object(pk=2) }}</div></ul>
# show POLICY_2_CHOICES below:
<div>{{ form.policy_2_rank }}</div>
...

Responses Model:

# Temp Survey Response
class Temporaryresponse(models.Model):
    # Healthcare
    POLICY_1_CHOICES = [
    (1, '1: Extremely supportive of a healthcare system with ONLY private insurance'),
    (2, '2: Somewhat supportive of a healthcare system with ONLY private insurance'),
    (3, '3: Somewhat supportive of a healthcare system with BOTH private insurance and a public insurance option'),
    (4, '4: Extremely supportive of a healthcare system with BOTH private insurance and a public insurance option'),
    (5, '5: Somewhat supportive of a healthcare system with ONLY public insurance (commonly referred to as "universal healthcare")'),
    (6, '6: Extremely supportive of a healthcare system with ONLY public insurance (commonly referred to as "universal healthcare")'),
]
...
# Healthcare
    policy_1_rank = models.IntegerField(blank=True, default=0, choices=POLICY_1_CHOICES)

Forms.py

class NextresponseForm(ModelForm):
    policy_1_rank = forms.IntegerField()
    policy_2_rank = forms.IntegerField()
...

class Meta:
        model = Temporaryresponse
        fields = ['policy_1_rank', 'policy_2_rank',...]

EDIT: Maybe the answer below isn't working because there is a problem with my view. I'm trying to navigate from a first form page "tr.html", save the data, and send the pk of the response to "nr.html" for a second part of the survey. This is working. But is my view of "nr.html" incorrect?

views.py

# Create temporary response view - this saves and goes to nr perfectly. pk produced from saving the response shows in nr link in web browser (myapp/nr/pk# shows here perfectly.)
def tr(request):
    if request.method == "POST":
        form = TemporaryresponseForm(request.POST)
        if form.is_valid():
            tempresponse = form.save()
            tempresponse.save()
            return redirect('nr', pk=tempresponse.pk)
    else:
        form = TemporaryresponseForm()
    return render(request, 'politicalexperimentpollapp/tr.html', {'form': form})


def nr(request, pk):
    tempresponse = get_object_or_404(Temporaryresponse, pk=pk)
    instance = Temporaryresponse.objects.get(pk=pk)
    if request.method == "POST":
        form = NextresponseForm(request.POST, instance=instance)
        if form.is_valid():
            nextresponse = form.save()
            nextresponse.save()
            return redirect('fr', pk=nextresponse.pk)
    else:
        form = NextresponseForm(instance=instance)
    return render(request, 'politicalexperimentpollapp/nr.html', {'tempresponse': tempresponse}, {'form': form})

Solution

  • You can override your form's fields in __init__ to include a forms.ChoiceField:

    class NextresponseForm(ModelForm):  
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
            self.fields['policy_1_rank'] = forms.ChoiceField(
                choices=self._meta.model.POLICY_1_CHOICES)
    
        class Meta:
                model = Temporaryresponse
                fields = ['policy_1_rank', 'policy_2_rank',...]
    

    Update

    The easiest way to render a form object in django is to pass it as context in your view:

    def some_view(request):
        form = NextresponseForm(request.POST or None)
        context = {'form':form}
        return render(request, 'some_template.html', context)
    

    Then you can access the form by calling form.as_ul in your template:

    <ul>
    {{ form.as_ul }}
    </ul>
    

    Update 2

    The last line in your views is incorrect:

    return render(request, 'politicalexperimentpollapp/nr.html', {'tempresponse': tempresponse}, {'form': form})
    

    Instead this should be:

    return render(request, 'politicalexperimentpollapp/nr.html', {'tempresponse': tempresponse, 'form': form})
    

    context is a dictionary. If you need to pass multiple items to your template, you need to include multiple keys in your context dictionary, not multiple arguments.

    Lastly, I'd recommend always initializing forms with request.POST or None, to avoid invalidating your form:

        form = TemporaryresponseForm(request.POST or None)
    

    This a textbook answer to a commonly asked question "Why is form.is_valid() always invalid?".