I hadn’t needed to write a custom widget before today, but I needed to have disabled options included in a <select> list. After a little research and a LOT of trial and error, this is what I came up with.
from django.forms.widgets import Select, force_unicode, flatatt, escape, mark_safe, chain, conditional_escape class SelectDisabled(Select): def __init__(self, attrs=None, choices=(), disabled=[]): super(SelectDisabled, self).__init__(attrs, choices) self.disabled = list(disabled) def render(self, name, value, attrs=None, choices=()): if value is None: value = '' final_attrs = self.build_attrs(attrs, name=name) output = [u'<select%s>' % flatatt(final_attrs)] options = self.render_options(choices, [value], self.disabled) if options: output.append(options) output.append('</select>') return mark_safe(u'\n'.join(output)) def render_options(self, choices, selected_choices, disabled_choices): def render_option(option_value, option_label): option_value = force_unicode(option_value) option_label = (option_value in disabled_choices) and (force_unicode(option_label) + ' - SOLD OUT') or force_unicode(option_label) selected_html = (option_value in selected_choices) and u' selected="selected"' or '' disabled_html = (option_value in disabled_choices) and u' disabled="disabled"' or '' return u'<option value="%s"%s%s>%s</option>' % ( escape(option_value), selected_html, disabled_html, conditional_escape(option_label)) # Normalize to strings. selected_choices = set([force_unicode(v) for v in selected_choices]) disabled_choices = set([force_unicode(v) for v in disabled_choices]) output = [] for option_value, option_label in chain(self.choices, choices): if isinstance(option_label, (list, tuple)): output.append(u'<optgroup label="%s">' % escape(force_unicode(option_value))) for option in option_label: output.append(render_option(*option)) output.append(u'</optgroup>') else: output.append(render_option(option_value, option_label)) return u'\n'.join(output)
And here’s how I used it in the form:
class B2CRegistrationStartForm(Form): def __init__(self, disabled, *args, **kwargs): super(B2CRegistrationStartForm, self).__init__(*args, **kwargs) if disabled: self.fields['event'].widget.disabled = disabled event = ModelChoiceField(widget=SelectDisabled(),queryset=Event.b2c_web_reg.all(),empty_label=None)
And here is the form creation in views.py:
disabled = [] for e in Event.b2c_sold_out.values_list('id'): disabled.append(e[0]) form = B2CRegistrationStartForm( data=request.POST or None, disabled=disabled)
(The list of disabled IDs needs to be in a list, not a list of tuples)
It works fine, and I learned about custom widgets, but I don’t feel like I was very Object Oriented in my approach. The __init__() method uses super() well, but the other two basically replace the methods in the parent class. When I update to a newer version of Django (from the current 1.0.4), I’ll have to check this carefully and probably redo it.
Anyone have suggestions on how I could better implement this feature?
Comments are closed.