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 a label 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 just label
to wrapp it all with.
I also tried doing something like this, but then the label
and label_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)
A widget is Django's representation of an HTML input element. The widget handles the rendering of the HTML, and the extraction of data from a GET/POST dictionary that corresponds to the widget. The HTML generated by the built-in widgets uses HTML5 syntax, targeting <! DOCTYPE html> .
The widget in Django will handle the rendering of the HTML elements and extract data from a POST/GET dictionary that corresponds to the same widget. Whenever we specify a field on a Django form, Django uses some default widget appropriate to the data type to be displayed.
Working with form templates. All you need to do to get your form into a template is to place the form instance into the template context. So if your form is called form in the context, {{ form }} will render its <label> and <input> elements appropriately.
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:
<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>
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 %}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With