Customizing Django forms with RadioSelect widget

In my code I discovered that changing the widget from

forms.RadioSelect

to

forms.RadioSelect(attrs={'id': 'value'})

magically causes the resulting tag value to include the id attribute with the index of the item appended. If you use

{% for radio in form.foo %}
    <li>
        {{ radio }}
    </li>
{% endfor %}

in the form you get a label wrapped around an input. If you want the more conventional input followed by label, you need to do this:

{% for radio in form.value %}
    <li>
        {{ radio.tag }}
        <label for="value_{{ forloop.counter0 }}">{{ radio.choice_label }}</label>
    </li>
{% endfor %}

Unfortunately this is more complicated than it should be, it seems you need to override at least 2 classes: RadioRenderer and RadioInput. The following should help you get started but you might need to tweak it a little.

First create a custom radio button input widget. The only purpose of us overriding the render method is to get rid of annoying structure Django enforces (<label><input /></label>) where instead we want ours (<label /><input />):

class CustomRadioInput(RadioInput):
    def render(self, name=None, value=None, attrs=None, choices=()):
        name = name or self.name
        value = value or self.value
        attrs = attrs or self.attrs
        if 'id' in self.attrs:
            label_for = ' for="%s_%s"' % (self.attrs['id'], self.index)
        else:
            label_for = ''
            choice_label = conditional_escape(force_unicode(self.choice_label))
            return mark_safe(u'%s<label%s>%s</label>' % (self.tag(), label_for, choice_label))

Now we need to override RadioRenderer in order to:

  1. Force it to use our custom radio input widget
  2. Remove <li> wraping every single input field and <ul> wrapping all input fields:

Something along these lines should do:

class RadioCustomRenderer(RadioFieldRenderer):
    def __iter__(self):
        for i, choice in enumerate(self.choices):
            yield CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, i)

    def __getitem__(self, idx):
        choice = self.choices[idx]
        return CustomRadioInput(self.name, self.value, self.attrs.copy(), choice, idx)

    def render(self):
        return mark_safe(u'%s' % u'\n'.join([u'%s' % force_unicode(w) for w in self]))

Finally instruct Django to use custom renderer

notify_new_friends = forms.ChoiceField(label='Notify when new friends join', widget=forms.RadioSelect(renderer=RadioCustomRenderer), choices=NOTIFICATION_CHOICES)

Please bear in mind: This now outputs radio buttons together with encompassing <td> hence you need to build a table around it in your template, something along these lines:

<table>
  <tr>
    <td><label for="{{field.auto_id}}">{{field.label}}</label></td>
    <td>{{ field.errors }} {{field}}</td>
  </tr>
</table>