Login

A ModelChoiceField with support for title in options based on a field in the model

Author:
celopes
Posted:
August 8, 2010
Language:
Python
Version:
1.2
Tags:
ModelChoiceField
Score:
0 (after 0 ratings)

ModelChoiceTitleField is a ModelChoiceField descendent that creates <OPTIONS> with title elements based on the field specified in title_source_field:

priority=ModelChoiceTitleField(Priority.objects.all(), 
                               initial=Priority.objects.get(default=True).id, 
                               title_source_field='long_description')

That will generate a <SELECT> element looking like:

<select name="priority" id="id_priority">
    <option value="1" title="Some extremely important task.">Emergency</option>
    ...
</select>

In the <option> above, the title was retrieved from the long_description field for the instance of the Priority class. The word Emergency came from a call to the instance of the Priority class' __unicode__() method. The value of the option is the PK for the instance of the Priority class.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#
# Example of usage in a form:
#
#priority=ModelChoiceTitleField(Priority.objects.all(), 
#                               initial=Priority.objects.get(default=True).id, 
#                               title_source_field='long_description')
#

class SelectWithTitle(Select):
    '''
        An overriden select widget to be used with ModelChoiceTitleField
    '''
    def render_options(self, choices, selected_choices):
        def render_option(option_value, option_label, option_title):
            option_value = force_unicode(option_value)
            selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
            return u'<option value="%s"%s title="%s">%s</option>' % (
                escape(option_value), selected_html, option_title,
                conditional_escape(force_unicode(option_label)))
        # Normalize to strings.
        selected_choices = set([force_unicode(v) for v in selected_choices])
        output = []
        for option_value, option_label, option_title 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, option_title))
        return u'\n'.join(output)

class ModelChoiceTitleIterator(ModelChoiceIterator):
    '''
        A ModelChoiceIterator that includes the label for the options
    '''
    def __iter__(self):
        if self.field.empty_label is not None:
            yield (u"", self.field.empty_label, "")
        if self.field.cache_choices:
            if self.field.choice_cache is None:
                self.field.choice_cache = [
                    self.choice(obj) for obj in self.queryset.all()
                ]
            for choice in self.field.choice_cache:
                yield choice
        else:
            for obj in self.queryset.all():
                yield self.choice(obj)
    
    def choice(self, obj):
        '''
            obj is each queryset object instance (i.e. each row from the DB)
        '''
        if self.field.to_field_name:
            key = obj.serializable_value(self.field.to_field_name)
        else:
            key = obj.pk
        return (key, self.field.label_from_instance(obj), obj.__dict__[self.field.title_source_field])


class ModelChoiceTitleField(ModelChoiceField):
    """
        A ModelChoiceField that also provides an iterator with labels for options
        
        title_source_field - the field name that will be used to retrieve the title for the element
    """
    widget=SelectWithTitle
    
    def __init__(self, queryset, empty_label=u"---------", cache_choices=False,
                 required=True, widget=None, label=None, initial=None,
                 help_text=None, to_field_name=None, title_source_field=None, *args, **kwargs):
        super(ModelChoiceTitleField, self).__init__(queryset, empty_label,
            cache_choices, required, widget, label, initial, help_text,
            *args, **kwargs)
        self.title_source_field = title_source_field

    def _get_choices(self):
        # If self._choices is set, then somebody must have manually set
        # the property self.choices. In this case, just return self._choices.
        if hasattr(self, '_choices'):
            return self._choices

        # Otherwise, execute the QuerySet in self.queryset to determine the
        # choices dynamically. Return a fresh QuerySetIterator that has not been
        # consumed. Note that we're instantiating a new QuerySetIterator *each*
        # time _get_choices() is called (and, thus, each time self.choices is
        # accessed) so that we can ensure the QuerySet has not been consumed. This
        # construct might look complicated but it allows for lazy evaluation of
        # the queryset.
        return ModelChoiceTitleIterator(self)

    choices = property(_get_choices, ChoiceField._set_choices)

More like this

  1. load m2m fields objects by dirol 4 years, 8 months ago
  2. SerializedObjectField by dominno 4 years, 12 months ago
  3. Dynamically change a form select widget to a hidden widget by epicserve 5 years, 8 months ago
  4. Allow disabling options in a select widget by scjody 3 years, 9 months ago
  5. FieldAccessForm (per-field user access for forms derived from models) by Killarny 6 years, 4 months ago

Comments

czpython (on January 18, 2012):

Awesome, thanks :P

#

Please login first before commenting.