Customizing Django forms with RadioSelect widget
Solution 1
Since it doesn't seem to be a good way to do this I chose to rearrange the generated code using jQuery.
// First remove the ul and li tags
$('.radio ul').contents().unwrap();
$('.radio li').contents().unwrap();
// Then move the input to outside of the label
$('.radio > label > input').each(function() {
$(this).parent().before(this);
});
// Then apply the jQuery UI buttonset
$( ".radio" ).buttonset();
This made it go from:
<ul>
<li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li>
<li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li>
</ul>
to:
<input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /><label for="id_notify_new_friends_0"> Immediately</label></li>
<input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /><label for="id_notify_new_friends_1"> Never</label></li>
and my jQuery UI styling works fine.
Solution 2
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 %}
Solution 3
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:
- Force it to use our custom radio input widget
- 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>
Solution 4
If anyone stumble upon this problem and just want to render the radio button without ul: they should follow this link.
https://docs.djangoproject.com/en/3.1/ref/forms/widgets/#selector-widgets
Example below.
{% for radio in myform.beatles %}
<div class="myradio">
{{ radio }}
</div>
{% endfor %}
olofom
Updated on June 13, 2022Comments
-
olofom almost 2 years
So I'm using jQuery UI to skin the radio buttons but I can't get Django to render my form the way it has to be done.
I need to have this structure:
<table> <tr> <td><label for="notify_new_friends">Notify when new friends join</label></td> <td class="radio"> <input type="radio" name="notify_new_friends" id="notify_new_friends_immediately" value="1" checked="checked"/><label for="notify_new_friends_immediately">Immediately</label> <input type="radio" name="notify_new_friends" id="notify_new_friends_never" value="0"/><label for="notify_new_friends_never">Never</label> </td> </tr> </table>
So to summarize that I need the radio buttons within a class (radio) where they have an
input
and alabel for
.When I render the form in my template with
{{ profile_form.notify_new_friends }}
I get the following:<ul> <li><label for="id_notify_new_friends_0"><input type="radio" id="id_notify_new_friends_0" value="0" name="notify_new_friends" /> Immediately</label></li> <li><label for="id_notify_new_friends_1"><input type="radio" id="id_notify_new_friends_1" value="1" name="notify_new_friends" /> Never</label></li> </ul>
Which is exactly what I want except for the list-part. So I tried looping over it which gives me the labels formatted differently:
{% for item in profile_form.notify_new_friends %} {{ item }} {% endfor %}
which gives me:
<label><input type="radio" name="notify_new_friends" value="0" /> Immediately</label> <label><input type="radio" name="notify_new_friends" value="1" /> Never</label>
So the problem here is that it stops using
label for
and starts using justlabel
to wrapp it all with.I also tried doing something like this, but then the
label
andlabel_tag
don't render anything.{{ profile_form.notify_new_friends.0 }} {{ profile_form.notify_new_friends.0.label_tag }} {{ profile_form.notify_new_friends.0.label }}
So does anyone know how I can render this properly!?
FYI, this is my forms.py:
self.fields['notify_new_friends'] = forms.ChoiceField(label='Notify when new friends join', widget=forms.RadioSelect, choices=NOTIFICATION_CHOICES)