newforms: Added MultiValueField, SplitDateTimeField, MultiWidget, SplitDateTimeWidget
git-svn-id: http://code.djangoproject.com/svn/django/trunk@4403 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
5b96692894
commit
8a6c337f2a
|
@ -3,7 +3,7 @@ Field classes
|
|||
"""
|
||||
|
||||
from django.utils.translation import gettext
|
||||
from util import ValidationError, smart_unicode
|
||||
from util import ErrorList, ValidationError, smart_unicode
|
||||
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, SelectMultiple
|
||||
import datetime
|
||||
import re
|
||||
|
@ -16,7 +16,8 @@ __all__ = (
|
|||
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
|
||||
'RegexField', 'EmailField', 'URLField', 'BooleanField',
|
||||
'ChoiceField', 'MultipleChoiceField',
|
||||
'ComboField',
|
||||
'ComboField', 'MultiValueField',
|
||||
'SplitDateTimeField',
|
||||
)
|
||||
|
||||
# These values, if given to to_python(), will trigger the self.required check.
|
||||
|
@ -376,6 +377,9 @@ class MultipleChoiceField(ChoiceField):
|
|||
return new_value
|
||||
|
||||
class ComboField(Field):
|
||||
"""
|
||||
A Field whose clean() method calls multiple Field clean() methods.
|
||||
"""
|
||||
def __init__(self, fields=(), required=True, widget=None, label=None, initial=None):
|
||||
super(ComboField, self).__init__(required, widget, label, initial)
|
||||
# Set 'required' to False on the individual fields, because the
|
||||
|
@ -394,3 +398,84 @@ class ComboField(Field):
|
|||
for field in self.fields:
|
||||
value = field.clean(value)
|
||||
return value
|
||||
|
||||
class MultiValueField(Field):
|
||||
"""
|
||||
A Field that is composed of multiple Fields.
|
||||
|
||||
Its clean() method takes a "decompressed" list of values. Each value in
|
||||
this list is cleaned by the corresponding field -- the first value is
|
||||
cleaned by the first field, the second value is cleaned by the second
|
||||
field, etc. Once all fields are cleaned, the list of clean values is
|
||||
"compressed" into a single value.
|
||||
|
||||
Subclasses should implement compress(), which specifies how a list of
|
||||
valid values should be converted to a single value. Subclasses should not
|
||||
have to implement clean().
|
||||
|
||||
You'll probably want to use this with MultiWidget.
|
||||
"""
|
||||
def __init__(self, fields=(), required=True, widget=None, label=None, initial=None):
|
||||
super(MultiValueField, self).__init__(required, widget, label, initial)
|
||||
# Set 'required' to False on the individual fields, because the
|
||||
# required validation will be handled by MultiValueField, not by those
|
||||
# individual fields.
|
||||
for f in fields:
|
||||
f.required = False
|
||||
self.fields = fields
|
||||
|
||||
def clean(self, value):
|
||||
"""
|
||||
Validates every value in the given list. A value is validated against
|
||||
the corresponding Field in self.fields.
|
||||
|
||||
For example, if this MultiValueField was instantiated with
|
||||
fields=(DateField(), TimeField()), clean() would call
|
||||
DateField.clean(value[0]) and TimeField.clean(value[1]).
|
||||
"""
|
||||
clean_data = []
|
||||
errors = ErrorList()
|
||||
if self.required and not value:
|
||||
raise ValidationError(gettext(u'This field is required.'))
|
||||
elif not self.required and not value:
|
||||
return self.compress([])
|
||||
if not isinstance(value, (list, tuple)):
|
||||
raise ValidationError(gettext(u'Enter a list of values.'))
|
||||
for i, field in enumerate(self.fields):
|
||||
try:
|
||||
field_value = value[i]
|
||||
except KeyError:
|
||||
field_value = None
|
||||
if self.required and field_value in EMPTY_VALUES:
|
||||
raise ValidationError(gettext(u'This field is required.'))
|
||||
try:
|
||||
clean_data.append(field.clean(field_value))
|
||||
except ValidationError, e:
|
||||
# Collect all validation errors in a single list, which we'll
|
||||
# raise at the end of clean(), rather than raising a single
|
||||
# exception for the first error we encounter.
|
||||
errors.extend(e.messages)
|
||||
if errors:
|
||||
raise ValidationError(errors)
|
||||
return self.compress(clean_data)
|
||||
|
||||
def compress(self, data_list):
|
||||
"""
|
||||
Returns a single value for the given list of values. The values can be
|
||||
assumed to be valid.
|
||||
|
||||
For example, if this MultiValueField was instantiated with
|
||||
fields=(DateField(), TimeField()), this might return a datetime
|
||||
object created by combining the date and time in data_list.
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
class SplitDateTimeField(MultiValueField):
|
||||
def __init__(self, required=True, widget=None, label=None, initial=None):
|
||||
fields = (DateField(), TimeField())
|
||||
super(SplitDateTimeField, self).__init__(fields, required, widget, label, initial)
|
||||
|
||||
def compress(self, data_list):
|
||||
if data_list:
|
||||
return datetime.datetime.combine(*data_list)
|
||||
return None
|
||||
|
|
|
@ -6,6 +6,7 @@ __all__ = (
|
|||
'Widget', 'TextInput', 'PasswordInput', 'HiddenInput', 'MultipleHiddenInput',
|
||||
'FileInput', 'Textarea', 'CheckboxInput',
|
||||
'Select', 'SelectMultiple', 'RadioSelect', 'CheckboxSelectMultiple',
|
||||
'MultiWidget', 'SplitDateTimeWidget',
|
||||
)
|
||||
|
||||
from util import flatatt, StrAndUnicode, smart_unicode
|
||||
|
@ -252,3 +253,66 @@ class CheckboxSelectMultiple(SelectMultiple):
|
|||
id_ += '_0'
|
||||
return id_
|
||||
id_for_label = classmethod(id_for_label)
|
||||
|
||||
class MultiWidget(Widget):
|
||||
"""
|
||||
A widget that is composed of multiple widgets.
|
||||
|
||||
Its render() method takes a "decompressed" list of values, not a single
|
||||
value. Each value in this list is rendered in the corresponding widget --
|
||||
the first value is rendered in the first widget, the second value is
|
||||
rendered in the second widget, etc.
|
||||
|
||||
Subclasses should implement decompress(), which specifies how a single
|
||||
value should be converted to a list of values. Subclasses should not
|
||||
have to implement clean().
|
||||
|
||||
Subclasses may implement format_output(), which takes the list of rendered
|
||||
widgets and returns HTML that formats them any way you'd like.
|
||||
|
||||
You'll probably want to use this with MultiValueField.
|
||||
"""
|
||||
def __init__(self, widgets, attrs=None):
|
||||
self.widgets = [isinstance(w, type) and w() or w for w in widgets]
|
||||
super(MultiWidget, self).__init__(attrs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
# value is a list of values, each corresponding to a widget
|
||||
# in self.widgets.
|
||||
if not isinstance(value, list):
|
||||
value = self.decompress(value)
|
||||
output = []
|
||||
for i, widget in enumerate(self.widgets):
|
||||
try:
|
||||
widget_value = value[i]
|
||||
except KeyError:
|
||||
widget_value = None
|
||||
output.append(widget.render(name + '_%s' % i, widget_value, attrs))
|
||||
return self.format_output(output)
|
||||
|
||||
def value_from_datadict(self, data, name):
|
||||
return [data.get(name + '_%s' % i) for i in range(len(self.widgets))]
|
||||
|
||||
def format_output(self, rendered_widgets):
|
||||
return u''.join(rendered_widgets)
|
||||
|
||||
def decompress(self, value):
|
||||
"""
|
||||
Returns a list of decompressed values for the given compressed value.
|
||||
The given value can be assumed to be valid, but not necessarily
|
||||
non-empty.
|
||||
"""
|
||||
raise NotImplementedError('Subclasses must implement this method.')
|
||||
|
||||
class SplitDateTimeWidget(MultiWidget):
|
||||
"""
|
||||
A Widget that splits datetime input into two <input type="text"> boxes.
|
||||
"""
|
||||
def __init__(self, attrs=None):
|
||||
widgets = (TextInput(attrs=attrs), TextInput(attrs=attrs))
|
||||
super(SplitDateTimeWidget, self).__init__(widgets, attrs)
|
||||
|
||||
def decompress(self, value):
|
||||
if value:
|
||||
return [value.date(), value.time()]
|
||||
return [None, None]
|
||||
|
|
|
@ -684,6 +684,39 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
|
|||
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
|
||||
u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
|
||||
|
||||
# MultiWidget #################################################################
|
||||
|
||||
>>> class MyMultiWidget(MultiWidget):
|
||||
... def decompress(self, value):
|
||||
... if value:
|
||||
... return value.split('__')
|
||||
... return ['', '']
|
||||
... def format_output(self, rendered_widgets):
|
||||
... return u'<br />'.join(rendered_widgets)
|
||||
>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})))
|
||||
>>> w.render('name', ['john', 'lennon'])
|
||||
u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />'
|
||||
>>> w.render('name', 'john__lennon')
|
||||
u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />'
|
||||
|
||||
# SplitDateTimeWidget #########################################################
|
||||
|
||||
>>> w = SplitDateTimeWidget()
|
||||
>>> w.render('date', '')
|
||||
u'<input type="text" name="date_0" /><input type="text" name="date_1" />'
|
||||
>>> w.render('date', None)
|
||||
u'<input type="text" name="date_0" /><input type="text" name="date_1" />'
|
||||
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
|
||||
u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />'
|
||||
>>> w.render('date', [datetime.date(2006, 1, 10), datetime.time(7, 30)])
|
||||
u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />'
|
||||
|
||||
You can also pass 'attrs' to the constructor. In this case, the attrs will be
|
||||
included on both widgets.
|
||||
>>> w = SplitDateTimeWidget(attrs={'class': 'pretty'})
|
||||
>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
|
||||
u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />'
|
||||
|
||||
##########
|
||||
# Fields #
|
||||
##########
|
||||
|
@ -1536,6 +1569,58 @@ u''
|
|||
>>> f.clean(None)
|
||||
u''
|
||||
|
||||
# SplitDateTimeField ##########################################################
|
||||
|
||||
>>> f = SplitDateTimeField()
|
||||
>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
|
||||
datetime.datetime(2006, 1, 10, 7, 30)
|
||||
>>> f.clean(None)
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'This field is required.']
|
||||
>>> f.clean('')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'This field is required.']
|
||||
>>> f.clean('hello')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a list of values.']
|
||||
>>> f.clean(['hello', 'there'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
|
||||
>>> f.clean(['2006-01-10', 'there'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid time.']
|
||||
>>> f.clean(['hello', '07:30'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid date.']
|
||||
|
||||
>>> f = SplitDateTimeField(required=False)
|
||||
>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
|
||||
datetime.datetime(2006, 1, 10, 7, 30)
|
||||
>>> f.clean(None)
|
||||
>>> f.clean('')
|
||||
>>> f.clean('hello')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a list of values.']
|
||||
>>> f.clean(['hello', 'there'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
|
||||
>>> f.clean(['2006-01-10', 'there'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid time.']
|
||||
>>> f.clean(['hello', '07:30'])
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValidationError: [u'Enter a valid date.']
|
||||
|
||||
#########
|
||||
# Forms #
|
||||
#########
|
||||
|
|
Loading…
Reference in New Issue