30

If I have a model that contains a ChoiceField with a RadioSelect widget, how can I render the radio buttons separately in a template?

Let's say I'm building a web app that allows new employees at a company to choose what kind of computer they want on their desktop. This is the relevant model:

class ComputerOrder(forms.Form):
    name = forms.CharField(max_length=50)
    office_address = forms.Charfield(max_length=75)
    pc_type = forms.ChoiceField(widget=RadioSelect(), choices=[(1, 'Mac'), (2, 'PC')])

On the template, how do I render just the Mac choice button? If I do this, it renders all the choices:

{{ form.pc_type  }}

Somewhat naively I tried this, but it produced no output:

{{ form.pc_type.0 }}

(I found a few similar questions here on SO:

In a Django form, how do I render a radio button so that the choices are separated on the page?
Django Forms: How to iterate over a Choices of a field in Django form

But I didn't feel like they had good answers. Is there a way to resurrect old questions?)

1
  • Thanks for asking
    – Siraj Alam
    Commented Mar 31, 2018 at 9:48

4 Answers 4

56

Django 1.4+ allows you to iterate over the choices in a RadioSelect, along with the lines of

{% for choice in form.pc_type %}
  {{ choice.choice_label }}
  <span class="radio">{{ choice.tag }}</span>
{% endfor %}

I'm not sure if this change allows you to use the syntax you describe ({{ form.pc_type.0 }}) — if not, you could work around this limitation with the for loop above and a tag like {% if forloop.counter0 == 0 %}.

If you're tied to Django < 1.4, you can either override the render() method as suggested or go with the slightly-more-verbose-but-less-complicated option of building up the form field yourself in the template:

 {% for choice in form.pc_type.field.choices %}
   <input name='{{ form.pc_type.name }}' 
     id='{{ form.pc_type.auto_id }}_{{ forloop.counter0 }}' type='radio' value='{{ choice.0 }}'
     {% if not form.is_bound %}{% ifequal form.pc_type.field.initial choice.0 %} checked='checked' {% endifequal %}
   {% else %}{% ifequal form.pc_type.data choice.0 %} checked='checked' {% endifequal %}{% endif %}/>
   <label for='{{ form.pc_type.auto_id }}_{{ forloop.counter0 }}'>{{ choice.1 }}</label>
 {% endfor %}

(choice.0 and choice.1 are the first and second items in your choices two-tuple)

2
  • {{ choice.1 } needs to be {{ choice.1 }} awesome solution :D
    – frederik-b
    Commented Aug 29, 2014 at 9:44
  • 1
    Thanks for the correction! Remember you can always directly suggest an edit by clicking "edit" under someone's answer :)
    – supervacuo
    Commented Aug 29, 2014 at 11:00
6

The rendering of the individual radio inputs is handled by the RadioSelect widget's render method. If you want a different rendering, subclass RadioSelect, change the render method accordingly, and then use your subclass as the field's widget.

4

I think the simply looking at what's available inside the for loop of a choice field will tell one what they need to know. For example, I needed the value to set a class surrounding the span of the option (for colors and such):

<div>
    {% for radio_input in form.role %}
        {# Skip the empty value #}
        {% if radio_input.choice_value %}
            <span class="user-level {{ radio_input.choice_value }}">{{ radio_input }}</span>
        {% endif %}
    {% endfor %}
</div>

There are several attributes as you can see that keep you from having to use the ordinal.

PyCharm Debugging Inside a Template

0
3

In Django 2.0+ you can subclass forms.RadioSelect and "simply" specify a template for rendering the radio fields:

class SlimRadioSelect(forms.RadioSelect):
    template_name = 'includes/slim_radio.html'

where slim_radio.html contains a revised version of the combined template_name and option_template_name used by the default RadioSelect widget.

Note, the default RadioSelect widget template is low-level rendering and consists of heavily layered templates: include, conditional and loop logic tags abound.

You'll know you've arrived when you're digging around in packages/django/forms/templates/django/forms/widgets/input.html to get what you need.

One other oddity for overriding the default widget's template is that you must invoke the TemplatesSetting renderer or your subclass won't be able to find slim_radio.html in your project's normally accessible template paths.

To override RadioSelect's local-only template path lookup:

  1. Add 'django.forms' to your INSTALLED_APPS;

  2. Add FORM_RENDERER = 'django.forms.renderers.TemplatesSetting' to your settings.py.

This all seems harder than it should be, but that's frameworks. Good luck.

Not the answer you're looking for? Browse other questions tagged or ask your own question.