I have a ModelForm that contains a ModelChoiceField using the RadioSelect widget.
class MyAForm(forms.ModelForm):
one_property = models.ModelChoiceField(
widget=forms.RadioSelect,
queryset=MyBModel.objects.filter(visible=True),
empty_label=None)
class Meta:
model = MyAModel
There are attributes on MyBModel that I want to display next to the radio button. I would override label_from_instance
on a sub-class of ModelChoiceField but this does not allow me to do what I want as I want the radio button to appear inside a table which has a row for each selection item.
So somewhere in my template I want something like...
{% for field in form.visible_fields %}
{% if field.name == "one_property" %}
<table>
{% for choice in field.choices %}
<tr>
<td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
<td><img src="{{choice.img_url}}" /></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
Unfortunately field.choices returns a tuple of the object's id and the label and not an instance from the queryset.
Is there a simple way to get instances of the choices for a ModelChoiceField to use within a template?
After delving into the django source for ModelChoiceField I discovered it has a property "queryset".
I was able to use something like...
{% for field in form.visible_fields %}
{% if field.name == "one_property" %}
<table>
{% for choice in field.queryset %}
<tr>
<td><input value="{{choice.id}}" type="radio" name="one_property" />{{choice.description}}</td>
<td><img src="{{choice.img_url}}" /></td>
</tr>
{% endfor %}
</table>
{% endif %}
{% endfor %}
I wanted to do something nearly identical to OP's question (table and all), was similarly frustrated by Django's lack of cooperation, and similarly ended up delving into the source to come up with my own implementation. What I came up with is a bit different than the accepted answer, and I liked it better because I was using a simple {{ form.as_table }}
in my template and didn't want to have to loop through visible_fields
needlessly or hard-code a radio button in my template that merely looks similar to Django's current implementation (which could change). Here's what I did instead:
Django's RadioSelect
widget uses RadioFieldRenderer
to yield a generator of RadioInputs
, which do the actual work of rendering the radio buttons. RadioSelect
seems to have an undocumented feature where you can pass it a different renderer than this default, so you can subclass both of these to get what OP wants.
from django import forms
from django.utils.safestring import mark_safe
class CustomTableRadioInput(forms.widgets.RadioInput):
# We can override the render method to display our table rows
def render(self, *args, **kwargs):
# default_html will hold the normally rendered radio button
# which we can then use somewhere in our table
default_html = super(CustomTableRadioInput, self).render(*args, **kwargs)
# Do whatever you want to the input, then return it, remembering to use
# either django.utils.safestring.mark_safe or django.utils.html.format_html
# ...
return mark_safe(new_html)
class CustomTableFieldRenderer(forms.widget.RadioFieldRenderer):
# Here we just need to override the two methods that yield RadioInputs
# and make them yield our custom subclass instead
def __iter__(self):
for i, choice in enumerate(self.choices):
yield CustomTableRadioInput(self.name, self.value,
self.attrs.copy(), choice, i)
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propogate
return CustomTableRadioInput(self.name, self.value,
self.attrs.copy(), choice, idx)
With that done, we just need to tell the RadioSelect
widget to use our custom renderer whenever we call it somewhere in our form code:
...
radio = forms.ChoiceField(widget=forms.RadioSelect(renderer=CustomTableFieldRenderer),
choices=...)
...
And that's it!
Do note that to use this in the template, you'll probably want to loop over the field rather than calling it directly, i.e. this:
<table>
<tbody>
{% for tr in form.radio %}
<tr>{{ tr }}</tr>
{% endfor %}
</tbody>
</table>
rather than this:
<table>
<tbody>{{ form.radio }}</tbody>
</table>
If you do the latter, it will try to wrap your table elements in <ul><li>...</li></ul>
.
Usually you don't need the actual object, but its rendition.
Consider this code:
class LabelledHiddenWidget(forms.HiddenInput):
def __init__(self, get_object, *args, **kwargs):
super(LabelledHiddenWidget, self).__init__(*args, **kwargs)
self.get_object = get_object
def render(self, name, value, attrs=None):
s = super(LabelledHiddenWidget, self).render(name, value, attrs)
if value:
s += SafeUnicode("<span>%s</span>" % self.get_object(value))
return s
Then you can use it like this:
class SomeForm(forms.Form):
object = forms.ModelChoiceField(
SomeModel.objects.all(),
widget=LabelledHiddenWidget(get_object=lambda id: get_object_or_404(SomeModel, id=id)))
Then in the template code, {{ form.object }}
will output a hidden field with the object id, concatenated with some label. Of course your SomeModel should implement __unicode__
or some other method that returns a nice, human readable label.
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