I have a Location model and an associated form that displays the "location" field as a bunch of radio buttons (using the form queryset to display the values). There are a small number of locations, but they need to be dynamic. I'd like to display the location description next to each radio box option so users have a bit more info on the locations.
Pretend this is a list of radio buttons, this is what I'd like it to look like:
<> East - This location is east. <> West - This is the west location! <> North - This is the north location
I have a model similar to the following:
class Location(models.Models):
location = models.CharField(max_length=50)
description = models.TextField(blank=True, null=True)
And a form as such:
class LocationForm(forms.Form):
location = ExtraModelChoiceField(
widget=RadioSelect(renderer=ExtraHorizRadioRenderer),
queryset = models.Locations.objects.filter(active=True))
I can't seem to find a good way to render the form so I can display the description along with each select option. I've done a lot of overriding, but am not having too much luck.
MY ATTEMPT TO SOLVE (BUT NO LUCK YET):
From what I gather, normally if a queryset is provided on the form field, the Django form logic translates that into a choices tupal of tupals. Each "subtupal" contains an id and label that is displayed when it is rendered. I'm trying to add a third value to those "subtupals" which would be a description.
I've defined a custom renderer to display my radio buttons horizontally and to pass in my custom choices.
class ExtraHorizRadioRenderer(forms.RadioSelect.renderer):
def render(self):
return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))
def __iter__(self):
for i, choice in enumerate(self.choices):
yield ExtraRadioInput(self.name, self.value,
self.attrs.copy(), choice, i)
def __getitem__(self, idx):
choice = self.choices[idx] # Let the IndexError propogate
return ExtraRadioInput(self.name, self.value,
self.attrs.copy(), choice, idx)
I've overridden the Django RadioInput class so I can add the description information that I need to display next to the Radio Buttons.
class ExtraRadioInput(forms.widgets.RadioInput):
def __init__(self, name, value, attrs, choice, index):
self.name, self.value = name, value
self.attrs = attrs
self.choice_value = force_unicode(choice[0])
self.choice_label = force_unicode(choice[1])
self.choice_description = force_unicode(choice[2]) # <--- MY ADDITION; FAILS
self.index = index
def __unicode__(self):
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'<label%s>%s %s</label>' % (
label_for, self.tag(), choice_label))
def tag(self):
if 'id' in self.attrs:
self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type='radio', name=self.name,
value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return mark_safe(
u'<input%s /><span class="description">%s</span>' % \
(flatatt(final_attrs),self.choice_description )) # <--- MY ADDTIONS
I've also overridden the following two Django classes hoping to pass around my modified choices tupals.
class ExtraModelChoiceIterator(forms.models.ModelChoiceIterator ):
def choice(self, obj):
if self.field.to_field_name:
key = obj.serializable_value(self.field.to_field_name)
else:
key = obj.pk
if obj.description: # <-- MY ADDITIONS
description = obj.description
else:
description = ""
return (key, self.field.label_from_instance(obj),description)
class ExtraModelChoiceField(forms.models.ModelChoiceField):
def _get_choices(self):
if hasattr(self, '_choices'):
return self._choices
return ExtraModelChoiceIterator(self) # <-- Uses MY NEW ITERATOR
Using the approach above, I can't seem to be able to pass around my 3-value tupal. I get a "tuple index out of range" failure (up where I mark FAILURE above) indicating that somehow my tupal does not have the extra value.
Does anyone see a flaw in my logic, or more generally have an approach to displaying a description next to a list of choices using a widget?
Thanks for reading. Any comments are much appreciated. Joe
Sorry to answer my own question, but I think I have a method to do this. As always, it appears to be simpler than I was making it before. Overriding the label_from_instance method on an extended ModelChoiceField seems to allow me to access the model object instance to be able to print out extra information.
from django.utils.encoding import smart_unicode, force_unicode
class ExtraModelChoiceField(forms.models.ModelChoiceField):
def label_from_instance(self, obj):
return mark_safe(
"<span>%s</span><span class=\"desc\" id=\"desc_%s\">%s</span>" % (
mart_unicode(obj), obj.id, smart_unicode(obj.description),))
class HorizRadioRenderer(forms.RadioSelect.renderer):
# for displaying select options horizontally.
# https://wikis.utexas.edu/display/~bm6432/Django-Modifying+RadioSelect+Widget+to+have+horizontal+buttons
def render(self):
return mark_safe(u'\n'.join([u'%s\n' % w for w in self]))
class LocationForm(forms.Form):
location = ExtraModelChoiceField(widget=forms.RadioSelect(renderer=HorizRadioRenderer),
queryset=models.Location.objects.filter(active=True))
If you know of a better approach, I'd be excited to see it. Otherwise, this will have to do. Thanks for reading. Hope this saves someone the frustration I had.
Joe
Did you take a look at this snippet: RadioSelectWithHelpText ?
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