Commit 649463dd authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #4412 -- Added support for optgroups, both in the model when defining...

Fixed #4412 -- Added support for optgroups, both in the model when defining choices, and in the form field and widgets when the optgroups are displayed. Thanks to Matt McClanahan <cardinal@dodds.net>, Tai Lee <real.human@mrmachine.net> and SmileyChris for their contributions at various stages in the life of this ticket.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@7977 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent b5b0febc
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -426,7 +426,7 @@ class Model(object):

    def _get_FIELD_display(self, field):
        value = getattr(self, field.attname)
        return force_unicode(dict(field.choices).get(value, value), strings_only=True)
        return force_unicode(dict(field.flatchoices).get(value, value), strings_only=True)

    def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
        op = is_next and 'gt' or 'lt'
+11 −1
Original line number Diff line number Diff line
@@ -288,7 +288,7 @@ class Field(object):
        if self.choices:
            field_objs = [oldforms.SelectField]

            params['choices'] = self.get_choices_default()
            params['choices'] = self.flatchoices
        else:
            field_objs = self.get_manipulator_field_objs()
        return (field_objs, params)
@@ -407,6 +407,16 @@ class Field(object):
            return self._choices
    choices = property(_get_choices)

    def _get_flatchoices(self):
        flat = []
        for choice, value in self.get_choices_default():
            if type(value) in (list, tuple):
                flat.extend(value)
            else:
                flat.append((choice,value))
        return flat
    flatchoices = property(_get_flatchoices)
    
    def save_form_data(self, instance, data):
        setattr(instance, self.name, data)

+16 −5
Original line number Diff line number Diff line
@@ -585,7 +585,7 @@ class NullBooleanField(BooleanField):
class ChoiceField(Field):
    widget = Select
    default_error_messages = {
        'invalid_choice': _(u'Select a valid choice. That choice is not one of the available choices.'),
        'invalid_choice': _(u'Select a valid choice. %(value)s is not one of the available choices.'),
    }

    def __init__(self, choices=(), required=True, widget=None, label=None,
@@ -615,11 +615,23 @@ class ChoiceField(Field):
        value = smart_unicode(value)
        if value == u'':
            return value
        valid_values = set([smart_unicode(k) for k, v in self.choices])
        if value not in valid_values:
        if not self.valid_value(value):
            raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
        return value

    def valid_value(self, value):
        "Check to see if the provided value is a valid choice"
        for k, v in self.choices:
            if type(v) in (tuple, list):
                # This is an optgroup, so look inside the group for options
                for k2, v2 in v:
                    if value == smart_unicode(k2):
                        return True
            else:
                if value == smart_unicode(k):
                    return True
        return False
        
class MultipleChoiceField(ChoiceField):
    hidden_widget = MultipleHiddenInput
    widget = SelectMultiple
@@ -640,9 +652,8 @@ class MultipleChoiceField(ChoiceField):
            raise ValidationError(self.error_messages['invalid_list'])
        new_value = [smart_unicode(val) for val in value]
        # Validate that each value in the value list is in self.choices.
        valid_values = set([smart_unicode(k) for k, v in self.choices])
        for val in new_value:
            if val not in valid_values:
            if not self.valid_value(val):
                raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
        return new_value

+29 −23
Original line number Diff line number Diff line
@@ -345,16 +345,31 @@ class Select(Widget):
        if value is None: value = ''
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select%s>' % flatatt(final_attrs)]
        # Normalize to string.
        str_value = force_unicode(value)
        for option_value, option_label in chain(self.choices, choices):
        options = self.render_options(choices, [value])
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

    def render_options(self, choices, selected_choices):
        def render_option(option_value, option_label):
            option_value = force_unicode(option_value)
            selected_html = (option_value == str_value) and u' selected="selected"' or ''
            output.append(u'<option value="%s"%s>%s</option>' % (
            selected_html = (option_value in selected_choices) and u' selected="selected"' or ''
            return u'<option value="%s"%s>%s</option>' % (
                escape(option_value), selected_html,
                    conditional_escape(force_unicode(option_label))))
        output.append(u'</select>')
        return mark_safe(u'\n'.join(output))
                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 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)

class NullBooleanSelect(Select):
    """
@@ -380,24 +395,15 @@ class NullBooleanSelect(Select):
        # same thing as False.
        return bool(initial) != bool(data)

class SelectMultiple(Widget):
    def __init__(self, attrs=None, choices=()):
        super(SelectMultiple, self).__init__(attrs)
        # choices can be any iterable
        self.choices = choices

class SelectMultiple(Select):
    def render(self, name, value, attrs=None, choices=()):
        if value is None: value = []
        final_attrs = self.build_attrs(attrs, name=name)
        output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
        str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
        for option_value, option_label in chain(self.choices, choices):
            option_value = force_unicode(option_value)
            selected_html = (option_value in str_values) and ' selected="selected"' or ''
            output.append(u'<option value="%s"%s>%s</option>' % (
                    escape(option_value), selected_html,
                    conditional_escape(force_unicode(option_label))))
        output.append(u'</select>')
        options = self.render_options(choices, value)
        if options:
            output.append(options)
        output.append('</select>')
        return mark_safe(u'\n'.join(output))

    def value_from_datadict(self, data, files, name):
+23 −0
Original line number Diff line number Diff line
@@ -554,6 +554,29 @@ or outside your model class altogether::
    class Foo(models.Model):
        gender = models.CharField(max_length=1, choices=GENDER_CHOICES)

You can also collect your available choices into named groups that can
be used for organizational purposes::

    MEDIA_CHOICES = (
        ('Audio', (
                ('vinyl', 'Vinyl'),
                ('cd', 'CD'),
            )
        ),
        ('Video', (
                ('vhs', 'VHS Tape'),
                ('dvd', 'DVD'),
            )
        ),
        ('unknown', 'Unknown'),
    )

The first element in each tuple is the name to apply to the group. The 
second element is an iterable of 2-tuples, with each 2-tuple containing
a value and a human-readable name for an option. Grouped options may be 
combined with ungrouped options within a single list (such as the 
`unknown` option in this example).

For each model field that has ``choices`` set, Django will add a method to
retrieve the human-readable name for the field's current value. See
`get_FOO_display`_ in the database API documentation.
Loading