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
This commit is contained in:
parent
b5b0febc4c
commit
649463dd34
|
@ -426,7 +426,7 @@ class Model(object):
|
||||||
|
|
||||||
def _get_FIELD_display(self, field):
|
def _get_FIELD_display(self, field):
|
||||||
value = getattr(self, field.attname)
|
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):
|
def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
|
||||||
op = is_next and 'gt' or 'lt'
|
op = is_next and 'gt' or 'lt'
|
||||||
|
|
|
@ -288,7 +288,7 @@ class Field(object):
|
||||||
if self.choices:
|
if self.choices:
|
||||||
field_objs = [oldforms.SelectField]
|
field_objs = [oldforms.SelectField]
|
||||||
|
|
||||||
params['choices'] = self.get_choices_default()
|
params['choices'] = self.flatchoices
|
||||||
else:
|
else:
|
||||||
field_objs = self.get_manipulator_field_objs()
|
field_objs = self.get_manipulator_field_objs()
|
||||||
return (field_objs, params)
|
return (field_objs, params)
|
||||||
|
@ -407,6 +407,16 @@ class Field(object):
|
||||||
return self._choices
|
return self._choices
|
||||||
choices = property(_get_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):
|
def save_form_data(self, instance, data):
|
||||||
setattr(instance, self.name, data)
|
setattr(instance, self.name, data)
|
||||||
|
|
||||||
|
|
|
@ -585,7 +585,7 @@ class NullBooleanField(BooleanField):
|
||||||
class ChoiceField(Field):
|
class ChoiceField(Field):
|
||||||
widget = Select
|
widget = Select
|
||||||
default_error_messages = {
|
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,
|
def __init__(self, choices=(), required=True, widget=None, label=None,
|
||||||
|
@ -615,11 +615,23 @@ class ChoiceField(Field):
|
||||||
value = smart_unicode(value)
|
value = smart_unicode(value)
|
||||||
if value == u'':
|
if value == u'':
|
||||||
return value
|
return value
|
||||||
valid_values = set([smart_unicode(k) for k, v in self.choices])
|
if not self.valid_value(value):
|
||||||
if value not in valid_values:
|
|
||||||
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
raise ValidationError(self.error_messages['invalid_choice'] % {'value': value})
|
||||||
return 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):
|
class MultipleChoiceField(ChoiceField):
|
||||||
hidden_widget = MultipleHiddenInput
|
hidden_widget = MultipleHiddenInput
|
||||||
widget = SelectMultiple
|
widget = SelectMultiple
|
||||||
|
@ -640,9 +652,8 @@ class MultipleChoiceField(ChoiceField):
|
||||||
raise ValidationError(self.error_messages['invalid_list'])
|
raise ValidationError(self.error_messages['invalid_list'])
|
||||||
new_value = [smart_unicode(val) for val in value]
|
new_value = [smart_unicode(val) for val in value]
|
||||||
# Validate that each value in the value list is in self.choices.
|
# 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:
|
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})
|
raise ValidationError(self.error_messages['invalid_choice'] % {'value': val})
|
||||||
return new_value
|
return new_value
|
||||||
|
|
||||||
|
|
|
@ -345,17 +345,32 @@ class Select(Widget):
|
||||||
if value is None: value = ''
|
if value is None: value = ''
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
output = [u'<select%s>' % flatatt(final_attrs)]
|
output = [u'<select%s>' % flatatt(final_attrs)]
|
||||||
# Normalize to string.
|
options = self.render_options(choices, [value])
|
||||||
str_value = force_unicode(value)
|
if options:
|
||||||
for option_value, option_label in chain(self.choices, choices):
|
output.append(options)
|
||||||
option_value = force_unicode(option_value)
|
output.append('</select>')
|
||||||
selected_html = (option_value == str_value) and u' 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>')
|
|
||||||
return mark_safe(u'\n'.join(output))
|
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 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)))
|
||||||
|
# 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):
|
class NullBooleanSelect(Select):
|
||||||
"""
|
"""
|
||||||
A Select Widget intended to be used with NullBooleanField.
|
A Select Widget intended to be used with NullBooleanField.
|
||||||
|
@ -380,24 +395,15 @@ class NullBooleanSelect(Select):
|
||||||
# same thing as False.
|
# same thing as False.
|
||||||
return bool(initial) != bool(data)
|
return bool(initial) != bool(data)
|
||||||
|
|
||||||
class SelectMultiple(Widget):
|
class SelectMultiple(Select):
|
||||||
def __init__(self, attrs=None, choices=()):
|
|
||||||
super(SelectMultiple, self).__init__(attrs)
|
|
||||||
# choices can be any iterable
|
|
||||||
self.choices = choices
|
|
||||||
|
|
||||||
def render(self, name, value, attrs=None, choices=()):
|
def render(self, name, value, attrs=None, choices=()):
|
||||||
if value is None: value = []
|
if value is None: value = []
|
||||||
final_attrs = self.build_attrs(attrs, name=name)
|
final_attrs = self.build_attrs(attrs, name=name)
|
||||||
output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
|
output = [u'<select multiple="multiple"%s>' % flatatt(final_attrs)]
|
||||||
str_values = set([force_unicode(v) for v in value]) # Normalize to strings.
|
options = self.render_options(choices, value)
|
||||||
for option_value, option_label in chain(self.choices, choices):
|
if options:
|
||||||
option_value = force_unicode(option_value)
|
output.append(options)
|
||||||
selected_html = (option_value in str_values) and ' selected="selected"' or ''
|
output.append('</select>')
|
||||||
output.append(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))
|
return mark_safe(u'\n'.join(output))
|
||||||
|
|
||||||
def value_from_datadict(self, data, files, name):
|
def value_from_datadict(self, data, files, name):
|
||||||
|
|
|
@ -554,6 +554,29 @@ or outside your model class altogether::
|
||||||
class Foo(models.Model):
|
class Foo(models.Model):
|
||||||
gender = models.CharField(max_length=1, choices=GENDER_CHOICES)
|
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
|
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
|
retrieve the human-readable name for the field's current value. See
|
||||||
`get_FOO_display`_ in the database API documentation.
|
`get_FOO_display`_ in the database API documentation.
|
||||||
|
|
|
@ -1236,7 +1236,11 @@ given length.
|
||||||
* Error message keys: ``required``, ``invalid_choice``
|
* Error message keys: ``required``, ``invalid_choice``
|
||||||
|
|
||||||
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
|
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
|
||||||
tuple) of 2-tuples to use as choices for this field.
|
tuple) of 2-tuples to use as choices for this field. This argument accepts
|
||||||
|
the same formats as the ``choices`` argument to a model field. See the
|
||||||
|
`model API documentation on choices`_ for more details.
|
||||||
|
|
||||||
|
.. _model API documentation on choices: ../model-api#choices
|
||||||
|
|
||||||
``DateField``
|
``DateField``
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
@ -1444,7 +1448,9 @@ These control the range of values permitted in the field.
|
||||||
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
|
* Error message keys: ``required``, ``invalid_choice``, ``invalid_list``
|
||||||
|
|
||||||
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
|
Takes one extra argument, ``choices``, which is an iterable (e.g., a list or
|
||||||
tuple) of 2-tuples to use as choices for this field.
|
tuple) of 2-tuples to use as choices for this field. This argument accepts
|
||||||
|
the same formats as the ``choices`` argument to a model field. See the
|
||||||
|
`model API documentation on choices`_ for more details.
|
||||||
|
|
||||||
``NullBooleanField``
|
``NullBooleanField``
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -980,7 +980,7 @@ False
|
||||||
|
|
||||||
# ChoiceField #################################################################
|
# ChoiceField #################################################################
|
||||||
|
|
||||||
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')])
|
>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')])
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
|
@ -996,9 +996,9 @@ u'1'
|
||||||
>>> f.clean('3')
|
>>> f.clean('3')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
||||||
|
|
||||||
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
|
>>> f = ChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
u''
|
u''
|
||||||
>>> f.clean(None)
|
>>> f.clean(None)
|
||||||
|
@ -1010,7 +1010,7 @@ u'1'
|
||||||
>>> f.clean('3')
|
>>> f.clean('3')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
||||||
|
|
||||||
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
|
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
|
||||||
>>> f.clean('J')
|
>>> f.clean('J')
|
||||||
|
@ -1018,7 +1018,25 @@ u'J'
|
||||||
>>> f.clean('John')
|
>>> f.clean('John')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. John is not one of the available choices.']
|
||||||
|
|
||||||
|
>>> f = ChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
|
||||||
|
>>> f.clean(1)
|
||||||
|
u'1'
|
||||||
|
>>> f.clean('1')
|
||||||
|
u'1'
|
||||||
|
>>> f.clean(3)
|
||||||
|
u'3'
|
||||||
|
>>> f.clean('3')
|
||||||
|
u'3'
|
||||||
|
>>> f.clean(5)
|
||||||
|
u'5'
|
||||||
|
>>> f.clean('5')
|
||||||
|
u'5'
|
||||||
|
>>> f.clean('6')
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
|
||||||
|
|
||||||
# NullBooleanField ############################################################
|
# NullBooleanField ############################################################
|
||||||
|
|
||||||
|
@ -1036,7 +1054,7 @@ False
|
||||||
|
|
||||||
# MultipleChoiceField #########################################################
|
# MultipleChoiceField #########################################################
|
||||||
|
|
||||||
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
|
>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')])
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
|
@ -1072,7 +1090,7 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
||||||
|
|
||||||
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
|
>>> f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], required=False)
|
||||||
>>> f.clean('')
|
>>> f.clean('')
|
||||||
[]
|
[]
|
||||||
>>> f.clean(None)
|
>>> f.clean(None)
|
||||||
|
@ -1100,6 +1118,29 @@ Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
|
||||||
|
|
||||||
|
>>> f = MultipleChoiceField(choices=[('Numbers', (('1', 'One'), ('2', 'Two'))), ('Letters', (('3','A'),('4','B'))), ('5','Other')])
|
||||||
|
>>> f.clean([1])
|
||||||
|
[u'1']
|
||||||
|
>>> f.clean(['1'])
|
||||||
|
[u'1']
|
||||||
|
>>> f.clean([1, 5])
|
||||||
|
[u'1', u'5']
|
||||||
|
>>> f.clean([1, '5'])
|
||||||
|
[u'1', u'5']
|
||||||
|
>>> f.clean(['1', 5])
|
||||||
|
[u'1', u'5']
|
||||||
|
>>> f.clean(['1', '5'])
|
||||||
|
[u'1', u'5']
|
||||||
|
>>> f.clean(['6'])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
|
||||||
|
>>> f.clean(['1','6'])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'Select a valid choice. 6 is not one of the available choices.']
|
||||||
|
|
||||||
|
|
||||||
# ComboField ##################################################################
|
# ComboField ##################################################################
|
||||||
|
|
||||||
ComboField takes a list of fields that should be used to validate a value,
|
ComboField takes a list of fields that should be used to validate a value,
|
||||||
|
@ -1165,7 +1206,7 @@ u''
|
||||||
>>> f.clean('fields.py')
|
>>> f.clean('fields.py')
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
|
ValidationError: [u'Select a valid choice. fields.py is not one of the available choices.']
|
||||||
>>> fix_os_paths(f.clean(path + 'fields.py'))
|
>>> fix_os_paths(f.clean(path + 'fields.py'))
|
||||||
u'.../django/forms/fields.py'
|
u'.../django/forms/fields.py'
|
||||||
>>> f = forms.FilePathField(path=path, match='^.*?\.py$')
|
>>> f = forms.FilePathField(path=path, match='^.*?\.py$')
|
||||||
|
|
|
@ -458,6 +458,35 @@ over multiple times without getting consumed:
|
||||||
<option value="4">4</option>
|
<option value="4">4</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
Choices can be nested one level in order to create HTML optgroups:
|
||||||
|
>>> w.choices=(('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
|
||||||
|
>>> print w.render('nestchoice', None)
|
||||||
|
<select name="nestchoice">
|
||||||
|
<option value="outer1">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> print w.render('nestchoice', 'outer1')
|
||||||
|
<select name="nestchoice">
|
||||||
|
<option value="outer1" selected="selected">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> print w.render('nestchoice', 'inner1')
|
||||||
|
<select name="nestchoice">
|
||||||
|
<option value="outer1">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1" selected="selected">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
# NullBooleanSelect Widget ####################################################
|
# NullBooleanSelect Widget ####################################################
|
||||||
|
|
||||||
>>> w = NullBooleanSelect()
|
>>> w = NullBooleanSelect()
|
||||||
|
@ -626,6 +655,44 @@ True
|
||||||
>>> w._has_changed([1, 2], [u'1', u'3'])
|
>>> w._has_changed([1, 2], [u'1', u'3'])
|
||||||
True
|
True
|
||||||
|
|
||||||
|
# Choices can be nested one level in order to create HTML optgroups:
|
||||||
|
>>> w.choices = (('outer1', 'Outer 1'), ('Group "1"', (('inner1', 'Inner 1'), ('inner2', 'Inner 2'))))
|
||||||
|
>>> print w.render('nestchoice', None)
|
||||||
|
<select multiple="multiple" name="nestchoice">
|
||||||
|
<option value="outer1">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> print w.render('nestchoice', ['outer1'])
|
||||||
|
<select multiple="multiple" name="nestchoice">
|
||||||
|
<option value="outer1" selected="selected">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> print w.render('nestchoice', ['inner1'])
|
||||||
|
<select multiple="multiple" name="nestchoice">
|
||||||
|
<option value="outer1">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1" selected="selected">Inner 1</option>
|
||||||
|
<option value="inner2">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
>>> print w.render('nestchoice', ['outer1', 'inner2'])
|
||||||
|
<select multiple="multiple" name="nestchoice">
|
||||||
|
<option value="outer1" selected="selected">Outer 1</option>
|
||||||
|
<optgroup label="Group "1"">
|
||||||
|
<option value="inner1">Inner 1</option>
|
||||||
|
<option value="inner2" selected="selected">Inner 2</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>
|
||||||
|
|
||||||
# RadioSelect Widget ##########################################################
|
# RadioSelect Widget ##########################################################
|
||||||
|
|
||||||
>>> w = RadioSelect()
|
>>> w = RadioSelect()
|
||||||
|
|
Loading…
Reference in New Issue