diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 0d6c5bcbbc..2e01634571 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -10,7 +10,7 @@ except NameError: import copy from itertools import chain from django.conf import settings -from django.utils.datastructures import MultiValueDict +from django.utils.datastructures import MultiValueDict, MergeDict from django.utils.html import escape, conditional_escape from django.utils.translation import ugettext from django.utils.encoding import StrAndUnicode, force_unicode @@ -35,36 +35,36 @@ class Media(StrAndUnicode): 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 mark_safe(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 @@ class Media(StrAndUnicode): 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 @@ def media_property(cls): 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 @@ def media_property(cls): 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 . @@ -250,7 +250,7 @@ class MultipleHiddenInput(HiddenInput): for v in value])) def value_from_datadict(self, data, files, name): - if isinstance(data, MultiValueDict): + if isinstance(data, (MultiValueDict, MergeDict)): return data.getlist(name) return data.get(name, None) @@ -264,7 +264,7 @@ class FileInput(Input): 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 @@ -417,10 +417,10 @@ class SelectMultiple(Select): return mark_safe(u'\n'.join(output)) def value_from_datadict(self, data, files, name): - if isinstance(data, MultiValueDict): + if isinstance(data, (MultiValueDict, MergeDict)): return data.getlist(name) return data.get(name, None) - + def _has_changed(self, initial, data): if initial is None: initial = [] @@ -537,7 +537,7 @@ class CheckboxSelectMultiple(SelectMultiple): 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) @@ -611,7 +611,7 @@ class MultiWidget(Widget): 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))] @@ -647,7 +647,7 @@ class MultiWidget(Widget): media = media + w.media return media media = property(_get_media) - + class SplitDateTimeWidget(MultiWidget): """ A Widget that splits datetime input into two boxes. diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index d834bdaccc..76132b273f 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -540,8 +540,9 @@ zero-based index.
  • -Data for a MultipleChoiceField should be a list. QueryDict and MultiValueDict -conveniently work with this. +Data for a MultipleChoiceField should be a list. QueryDict, MultiValueDict and +MergeDict (when created as a merge of MultiValueDicts) conveniently work with +this. >>> data = {'name': 'Yesterday', 'composers': ['J', 'P']} >>> f = SongForm(data) >>> f.errors @@ -556,6 +557,11 @@ conveniently work with this. >>> f = SongForm(data) >>> f.errors {} +>>> from django.utils.datastructures import MergeDict +>>> data = MergeDict(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P']))) +>>> f = SongForm(data) +>>> f.errors +{} The MultipleHiddenInput widget renders multiple values as hidden fields. >>> class SongFormHidden(Form):