Fixed #7195 -- Fixed the validation of MultipleChoice fields so that they can

be populated from request.REQUEST. Based on a patch from Daniel Roseman.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@8525 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2008-08-25 00:32:32 +00:00
parent d6e5632969
commit 6d6fb392b4
2 changed files with 32 additions and 26 deletions

View File

@ -10,7 +10,7 @@ except NameError:
import copy import copy
from itertools import chain from itertools import chain
from django.conf import settings 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.html import escape, conditional_escape
from django.utils.translation import ugettext from django.utils.translation import ugettext
from django.utils.encoding import StrAndUnicode, force_unicode from django.utils.encoding import StrAndUnicode, force_unicode
@ -35,36 +35,36 @@ class Media(StrAndUnicode):
media_attrs = media.__dict__ media_attrs = media.__dict__
else: else:
media_attrs = kwargs media_attrs = kwargs
self._css = {} self._css = {}
self._js = [] self._js = []
for name in MEDIA_TYPES: for name in MEDIA_TYPES:
getattr(self, 'add_' + name)(media_attrs.get(name, None)) getattr(self, 'add_' + name)(media_attrs.get(name, None))
# Any leftover attributes must be invalid. # Any leftover attributes must be invalid.
# if media_attrs != {}: # if media_attrs != {}:
# raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys()) # raise TypeError, "'class Media' has invalid attribute(s): %s" % ','.join(media_attrs.keys())
def __unicode__(self): def __unicode__(self):
return self.render() return self.render()
def render(self): def render(self):
return mark_safe(u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES]))) return mark_safe(u'\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES])))
def render_js(self): def render_js(self):
return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js] return [u'<script type="text/javascript" src="%s"></script>' % self.absolute_path(path) for path in self._js]
def render_css(self): def render_css(self):
# To keep rendering order consistent, we can't just iterate over items(). # To keep rendering order consistent, we can't just iterate over items().
# We need to sort the keys, and iterate over the sorted list. # We need to sort the keys, and iterate over the sorted list.
media = self._css.keys() media = self._css.keys()
media.sort() media.sort()
return chain(*[ return chain(*[
[u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium) [u'<link href="%s" type="text/css" media="%s" rel="stylesheet" />' % (self.absolute_path(path), medium)
for path in self._css[medium]] for path in self._css[medium]]
for medium in media]) for medium in media])
def absolute_path(self, path): def absolute_path(self, path):
if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'): if path.startswith(u'http://') or path.startswith(u'https://') or path.startswith(u'/'):
return path return path
@ -77,9 +77,9 @@ class Media(StrAndUnicode):
raise KeyError('Unknown media type "%s"' % name) raise KeyError('Unknown media type "%s"' % name)
def add_js(self, data): def add_js(self, data):
if data: if data:
self._js.extend([path for path in data if path not in self._js]) self._js.extend([path for path in data if path not in self._js])
def add_css(self, data): def add_css(self, data):
if data: if data:
for medium, paths in data.items(): for medium, paths in data.items():
@ -99,8 +99,8 @@ def media_property(cls):
base = super(cls, self).media base = super(cls, self).media
else: else:
base = Media() base = Media()
# Get the media definition for this class # Get the media definition for this class
definition = getattr(cls, 'Media', None) definition = getattr(cls, 'Media', None)
if definition: if definition:
extend = getattr(definition, 'extend', True) extend = getattr(definition, 'extend', True)
@ -117,16 +117,16 @@ def media_property(cls):
else: else:
return base return base
return property(_media) return property(_media)
class MediaDefiningClass(type): class MediaDefiningClass(type):
"Metaclass for classes that can have media definitions" "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, new_class = super(MediaDefiningClass, cls).__new__(cls, name, bases,
attrs) attrs)
if 'media' not in attrs: if 'media' not in attrs:
new_class.media = media_property(new_class) new_class.media = media_property(new_class)
return new_class return new_class
class Widget(object): class Widget(object):
__metaclass__ = MediaDefiningClass __metaclass__ = MediaDefiningClass
is_hidden = False # Determines whether this corresponds to an <input type="hidden">. is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
@ -250,7 +250,7 @@ class MultipleHiddenInput(HiddenInput):
for v in value])) for v in value]))
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict): if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name) return data.getlist(name)
return data.get(name, None) return data.get(name, None)
@ -264,7 +264,7 @@ class FileInput(Input):
def value_from_datadict(self, data, files, name): def value_from_datadict(self, data, files, name):
"File widgets take data from FILES, not POST" "File widgets take data from FILES, not POST"
return files.get(name, None) return files.get(name, None)
def _has_changed(self, initial, data): def _has_changed(self, initial, data):
if data is None: if data is None:
return False return False
@ -417,10 +417,10 @@ class SelectMultiple(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):
if isinstance(data, MultiValueDict): if isinstance(data, (MultiValueDict, MergeDict)):
return data.getlist(name) return data.getlist(name)
return data.get(name, None) return data.get(name, None)
def _has_changed(self, initial, data): def _has_changed(self, initial, data):
if initial is None: if initial is None:
initial = [] initial = []
@ -537,7 +537,7 @@ class CheckboxSelectMultiple(SelectMultiple):
label_for = u' for="%s"' % final_attrs['id'] label_for = u' for="%s"' % final_attrs['id']
else: else:
label_for = '' label_for = ''
cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values) cb = CheckboxInput(final_attrs, check_test=lambda value: value in str_values)
option_value = force_unicode(option_value) option_value = force_unicode(option_value)
rendered_cb = cb.render(name, option_value) rendered_cb = cb.render(name, option_value)
@ -611,7 +611,7 @@ class MultiWidget(Widget):
def value_from_datadict(self, data, files, name): 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)] return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
def _has_changed(self, initial, data): def _has_changed(self, initial, data):
if initial is None: if initial is None:
initial = [u'' for x in range(0, len(data))] initial = [u'' for x in range(0, len(data))]
@ -647,7 +647,7 @@ class MultiWidget(Widget):
media = media + w.media media = media + w.media
return media return media
media = property(_get_media) media = property(_get_media)
class SplitDateTimeWidget(MultiWidget): class SplitDateTimeWidget(MultiWidget):
""" """
A Widget that splits datetime input into two <input type="text"> boxes. A Widget that splits datetime input into two <input type="text"> boxes.

View File

@ -540,8 +540,9 @@ zero-based index.
<li><label for="composers_id_1"><input type="checkbox" name="composers" value="P" id="composers_id_1" /> Paul McCartney</label></li> <li><label for="composers_id_1"><input type="checkbox" name="composers" value="P" id="composers_id_1" /> Paul McCartney</label></li>
</ul> </ul>
Data for a MultipleChoiceField should be a list. QueryDict and MultiValueDict Data for a MultipleChoiceField should be a list. QueryDict, MultiValueDict and
conveniently work with this. MergeDict (when created as a merge of MultiValueDicts) conveniently work with
this.
>>> data = {'name': 'Yesterday', 'composers': ['J', 'P']} >>> data = {'name': 'Yesterday', 'composers': ['J', 'P']}
>>> f = SongForm(data) >>> f = SongForm(data)
>>> f.errors >>> f.errors
@ -556,6 +557,11 @@ conveniently work with this.
>>> f = SongForm(data) >>> f = SongForm(data)
>>> f.errors >>> 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. The MultipleHiddenInput widget renders multiple values as hidden fields.
>>> class SongFormHidden(Form): >>> class SongFormHidden(Form):