Index: django/forms/widgets.py
===================================================================
--- django/forms/widgets.py (revision 8204)
+++ django/forms/widgets.py (working copy)
@@ -35,36 +35,36 @@
media_attrs = media.__dict__
else:
media_attrs = kwargs
-
+
self._css = {}
self._js = []
-
+
for name in MEDIA_TYPES:
getattr(self, 'add_' + name)(media_attrs.get(name, None))
# Any leftover attributes must be invalid.
# if media_attrs != {}:
# raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
-
+
def __unicode__(self):
return self.render()
-
+
def render(self):
return u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))
-
+
def render_js(self):
return [u'' % self.absolute_path(path) for path in self._js]
-
+
def render_css(self):
# To keep rendering order consistent, we can't just iterate over items().
# We need to sort the keys, and iterate over the sorted list.
media = self._css.keys()
media.sort()
return chain(*[
- [u'' % (self.absolute_path(path), medium)
- for path in self._css[medium]]
+ [u'' % (self.absolute_path(path), medium)
+ for path in self._css[medium]]
for medium in media])
-
+
def absolute_path(self, path):
if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'):
return path
@@ -77,9 +77,9 @@
raise KeyError('Unknown media type "%s"' % name)
def add_js(self, data):
- if data:
+ if data:
self._js.extend([path for path in data if path not in self._js])
-
+
def add_css(self, data):
if data:
for medium, paths in data.items():
@@ -99,8 +99,8 @@
base = super(cls, self).media
else:
base = Media()
-
- # Get the media definition for this class
+
+ # Get the media definition for this class
definition = getattr(cls, 'Media', None)
if definition:
extend = getattr(definition, 'extend', True)
@@ -117,16 +117,16 @@
else:
return base
return property(_media)
-
+
class MediaDefiningClass(type):
"Metaclass for classes that can have media definitions"
- def __new__(cls, name, bases, attrs):
+ def __new__(cls, name, bases, attrs):
new_class = super(MediaDefiningClass, cls).__new__(cls, name, bases,
attrs)
if 'media' not in attrs:
new_class.media = media_property(new_class)
return new_class
-
+
class Widget(object):
__metaclass__ = MediaDefiningClass
is_hidden = False # Determines whether this corresponds to an .
@@ -264,7 +264,7 @@
def value_from_datadict(self, data, files, name):
"File widgets take data from FILES, not POST"
return files.get(name, None)
-
+
def _has_changed(self, initial, data):
if data is None:
return False
@@ -334,6 +334,8 @@
return bool(initial) != bool(data)
class Select(Widget):
+ allow_multiple_selected = False
+
def __init__(self, attrs=None, choices=()):
super(Select, self).__init__(attrs)
# choices can be any iterable, but we may need to render this widget
@@ -354,7 +356,14 @@
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 ''
+ if not getattr(render_option, 'selected', None)\
+ and option_value in selected_choices:
+ if not self.allow_multiple_selected:
+ # Only allow for a single selection.
+ render_option.selected = True
+ selected_html = u' selected="selected"'
+ else:
+ selected_html = ''
return u'' % (
escape(option_value), selected_html,
conditional_escape(force_unicode(option_label)))
@@ -396,6 +405,8 @@
return bool(initial) != bool(data)
class SelectMultiple(Select):
+ allow_multiple_selected = True
+
def render(self, name, value, attrs=None, choices=()):
if value is None: value = []
final_attrs = self.build_attrs(attrs, name=name)
@@ -410,7 +421,7 @@
if isinstance(data, MultiValueDict):
return data.getlist(name)
return data.get(name, None)
-
+
def _has_changed(self, initial, data):
if initial is None:
initial = []
@@ -527,7 +538,7 @@
label_for = u' for="%s"' % final_attrs['id']
else:
label_for = ''
-
+
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value)
@@ -601,7 +612,7 @@
def value_from_datadict(self, data, files, name):
return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
-
+
def _has_changed(self, initial, data):
if initial is None:
initial = [u'' for x in range(0, len(data))]
@@ -637,7 +648,7 @@
media = media + w.media
return media
media = property(_get_media)
-
+
class SplitDateTimeWidget(MultiWidget):
"""
A Widget that splits datetime input into two boxes.
Index: tests/regressiontests/forms/widgets.py
===================================================================
--- tests/regressiontests/forms/widgets.py (revision 8204)
+++ tests/regressiontests/forms/widgets.py (working copy)
@@ -458,6 +458,28 @@
+Only one option can be selected:
+>>> print w.render('choices', 0, choices=(('0', 'extra'),))
+
+
+Ensure that it still selects the first element next time round:
+>>> print w.render('choices', 0, choices=(('0', 'extra'),))
+
+
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)
@@ -655,6 +677,15 @@
>>> w._has_changed([1, 2], [u'1', u'3'])
True
+Multiple options (with the same value) can be selected:
+>>> print w.render('choices', [1], choices=(('1', 'extra'),))
+
+
# 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)