summaryrefslogtreecommitdiff
path: root/tests/regressiontests/forms/tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/regressiontests/forms/tests.py')
-rw-r--r--tests/regressiontests/forms/tests.py1399
1 files changed, 1235 insertions, 164 deletions
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 779db97528..20a1937f56 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -106,6 +106,46 @@ u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u
>>> w.render('email', '', attrs={'class': 'special'})
u'<input type="hidden" class="special" name="email" />'
+# MultipleHiddenInput Widget ##################################################
+
+>>> w = MultipleHiddenInput()
+>>> w.render('email', [])
+u''
+>>> w.render('email', None)
+u''
+>>> w.render('email', ['test@example.com'])
+u'<input type="hidden" name="email" value="test@example.com" />'
+>>> w.render('email', ['some "quoted" & ampersanded value'])
+u'<input type="hidden" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />'
+>>> w.render('email', ['test@example.com', 'foo@example.com'])
+u'<input type="hidden" name="email" value="test@example.com" />\n<input type="hidden" name="email" value="foo@example.com" />'
+>>> w.render('email', ['test@example.com'], attrs={'class': 'fun'})
+u'<input type="hidden" name="email" value="test@example.com" class="fun" />'
+>>> w.render('email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'})
+u'<input type="hidden" name="email" value="test@example.com" class="fun" />\n<input type="hidden" name="email" value="foo@example.com" class="fun" />'
+
+You can also pass 'attrs' to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'fun'})
+>>> w.render('email', [])
+u''
+>>> w.render('email', ['foo@example.com'])
+u'<input type="hidden" class="fun" value="foo@example.com" name="email" />'
+>>> w.render('email', ['foo@example.com', 'test@example.com'])
+u'<input type="hidden" class="fun" value="foo@example.com" name="email" />\n<input type="hidden" class="fun" value="test@example.com" name="email" />'
+
+'attrs' passed to render() get precedence over those passed to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
+u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
+
+>>> w.render('email', ['ŠĐĆŽćžšđ'], attrs={'class': 'fun'})
+u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
+
+'attrs' passed to render() get precedence over those passed to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
+u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
+
# FileInput Widget ############################################################
>>> w = FileInput()
@@ -296,6 +336,60 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
>>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
+If choices is passed to the constructor and is a generator, it can be iterated
+over multiple times without getting consumed:
+>>> w = Select(choices=get_choices())
+>>> print w.render('num', 2)
+<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>
+>>> print w.render('num', 3)
+<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3" selected="selected">3</option>
+<option value="4">4</option>
+</select>
+
+# NullBooleanSelect Widget ####################################################
+
+>>> w = NullBooleanSelect()
+>>> print w.render('is_cool', True)
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', False)
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+>>> print w.render('is_cool', None)
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', '2')
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', '3')
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+
# SelectMultiple Widget #######################################################
>>> w = SelectMultiple()
@@ -514,6 +608,29 @@ beatle J P Paul False
beatle J G George False
beatle J R Ringo False
+A RadioFieldRenderer object also allows index access to individual RadioInput
+objects.
+>>> w = RadioSelect()
+>>> r = w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+>>> print r[1]
+<label><input type="radio" name="beatle" value="P" /> Paul</label>
+>>> print r[0]
+<label><input checked="checked" type="radio" name="beatle" value="J" /> John</label>
+>>> r[0].is_checked()
+True
+>>> r[1].is_checked()
+False
+>>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label
+('beatle', u'J', u'P', u'Paul')
+>>> r[10]
+Traceback (most recent call last):
+...
+IndexError: list index out of range
+
+>>> w = RadioSelect()
+>>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]))
+u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
+
# CheckboxSelectMultiple Widget ###############################################
>>> w = CheckboxSelectMultiple()
@@ -621,6 +738,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 #
##########
@@ -636,6 +786,11 @@ Each Field's __init__() takes at least these parameters:
used for this Field when displaying it. Each Field has a default
Widget that it'll use if you don't specify this. In most cases,
the default widget is TextInput.
+ label -- A verbose name for this field, for use in displaying this field in
+ a form. By default, Django will use a "pretty" version of the form
+ field name, if the Field is part of a Form.
+ initial -- A value to use in this Field's initial display. This value is
+ *not* used as a fallback if data isn't given.
Other than that, the Field subclasses have class-specific options for
__init__(). For example, CharField has a max_length option.
@@ -684,9 +839,21 @@ ValidationError: [u'Ensure this value has at most 10 characters.']
CharField accepts an optional min_length parameter:
>>> f = CharField(min_length=10, required=False)
>>> f.clean('')
+u''
+>>> f.clean('12345')
Traceback (most recent call last):
...
ValidationError: [u'Ensure this value has at least 10 characters.']
+>>> f.clean('1234567890')
+u'1234567890'
+>>> f.clean('1234567890a')
+u'1234567890a'
+
+>>> f = CharField(min_length=10, required=True)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
>>> f.clean('12345')
Traceback (most recent call last):
...
@@ -730,9 +897,11 @@ ValidationError: [u'Enter a whole number.']
>>> f = IntegerField(required=False)
>>> f.clean('')
-u''
+>>> repr(f.clean(''))
+'None'
>>> f.clean(None)
-u''
+>>> repr(f.clean(None))
+'None'
>>> f.clean('1')
1
>>> isinstance(f.clean('1'), int)
@@ -754,6 +923,71 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter a whole number.']
+IntegerField accepts an optional max_value parameter:
+>>> f = IntegerField(max_value=10)
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(1)
+1
+>>> f.clean(10)
+10
+>>> f.clean(11)
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is less than or equal to 10.']
+>>> f.clean('10')
+10
+>>> f.clean('11')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is less than or equal to 10.']
+
+IntegerField accepts an optional min_value parameter:
+>>> f = IntegerField(min_value=10)
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(1)
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is greater than or equal to 10.']
+>>> f.clean(10)
+10
+>>> f.clean(11)
+11
+>>> f.clean('10')
+10
+>>> f.clean('11')
+11
+
+min_value and max_value can be used together:
+>>> f = IntegerField(min_value=10, max_value=20)
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(1)
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is greater than or equal to 10.']
+>>> f.clean(10)
+10
+>>> f.clean(11)
+11
+>>> f.clean('10')
+10
+>>> f.clean('11')
+11
+>>> f.clean(20)
+20
+>>> f.clean(21)
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is less than or equal to 20.']
+
# DateField ###################################################################
>>> import datetime
@@ -831,6 +1065,45 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter a valid date.']
+# TimeField ###################################################################
+
+>>> import datetime
+>>> f = TimeField()
+>>> f.clean(datetime.time(14, 25))
+datetime.time(14, 25)
+>>> f.clean(datetime.time(14, 25, 59))
+datetime.time(14, 25, 59)
+>>> f.clean('14:25')
+datetime.time(14, 25)
+>>> f.clean('14:25:59')
+datetime.time(14, 25, 59)
+>>> f.clean('hello')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid time.']
+>>> f.clean('1:24 p.m.')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid time.']
+
+TimeField accepts an optional input_formats parameter:
+>>> f = TimeField(input_formats=['%I:%M %p'])
+>>> f.clean(datetime.time(14, 25))
+datetime.time(14, 25)
+>>> f.clean(datetime.time(14, 25, 59))
+datetime.time(14, 25, 59)
+>>> f.clean('4:25 AM')
+datetime.time(4, 25)
+>>> f.clean('4:25 PM')
+datetime.time(16, 25)
+
+The input_formats parameter overrides all default input formats,
+so the default formats won't work unless you specify them:
+>>> f.clean('14:30:45')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid time.']
+
# DateTimeField ###############################################################
>>> import datetime
@@ -960,7 +1233,7 @@ Traceback (most recent call last):
ValidationError: [u'Enter a valid value.']
RegexField takes an optional error_message argument:
->>> f = RegexField('^\d\d\d\d$', 'Enter a four-digit number.')
+>>> f = RegexField('^\d\d\d\d$', error_message='Enter a four-digit number.')
>>> f.clean('1234')
u'1234'
>>> f.clean('123')
@@ -972,6 +1245,29 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter a four-digit number.']
+RegexField also access min_length and max_length parameters, for convenience.
+>>> f = RegexField('^\d+$', min_length=5, max_length=10)
+>>> f.clean('123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 5 characters.']
+>>> f.clean('abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 5 characters.']
+>>> f.clean('12345')
+u'12345'
+>>> f.clean('1234567890')
+u'1234567890'
+>>> f.clean('12345678901')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 10 characters.']
+>>> f.clean('12345a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+
# EmailField ##################################################################
>>> f = EmailField()
@@ -1018,6 +1314,19 @@ Traceback (most recent call last):
...
ValidationError: [u'Enter a valid e-mail address.']
+EmailField also access min_length and max_length parameters, for convenience.
+>>> f = EmailField(min_length=10, max_length=15)
+>>> f.clean('a@foo.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 10 characters.']
+>>> f.clean('alf@foo.com')
+u'alf@foo.com'
+>>> f.clean('alf123456788@foo.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 15 characters.']
+
# URLField ##################################################################
>>> f = URLField()
@@ -1109,6 +1418,24 @@ ValidationError: [u'This URL appears to be a broken link.']
Traceback (most recent call last):
...
ValidationError: [u'This URL appears to be a broken link.']
+>>> f = URLField(verify_exists=True, required=False)
+>>> f.clean('')
+u''
+>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
+u'http://www.google.com'
+
+EmailField also access min_length and max_length parameters, for convenience.
+>>> f = URLField(min_length=15, max_length=20)
+>>> f.clean('http://f.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 15 characters.']
+>>> f.clean('http://example.com')
+u'http://example.com'
+>>> f.clean('http://abcdefghijklmnopqrstuvwxyz.com')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 20 characters.']
# BooleanField ################################################################
@@ -1190,6 +1517,20 @@ Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. John is not one of the available choices.']
+# NullBooleanField ############################################################
+
+>>> f = NullBooleanField()
+>>> f.clean('')
+>>> f.clean(True)
+True
+>>> f.clean(False)
+False
+>>> f.clean(None)
+>>> f.clean('1')
+>>> f.clean('2')
+>>> f.clean('3')
+>>> f.clean('hello')
+
# MultipleChoiceField #########################################################
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
@@ -1296,6 +1637,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 #
#########
@@ -1313,6 +1706,8 @@ You can pass it data in __init__(), as a dictionary.
Pass a dictionary to a Form's __init__().
>>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'})
+>>> p.is_bound
+True
>>> p.errors
{}
>>> p.is_valid()
@@ -1324,91 +1719,102 @@ u''
>>> p.clean_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
>>> print p['first_name']
-<input type="text" name="first_name" value="John" />
+<input type="text" name="first_name" value="John" id="id_first_name" />
>>> print p['last_name']
-<input type="text" name="last_name" value="Lennon" />
+<input type="text" name="last_name" value="Lennon" id="id_last_name" />
>>> print p['birthday']
-<input type="text" name="birthday" value="1940-10-9" />
+<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />
+>>> print p['nonexistentfield']
+Traceback (most recent call last):
+...
+KeyError: "Key 'nonexistentfield' not found in Form"
+
>>> for boundfield in p:
... print boundfield
-<input type="text" name="first_name" value="John" />
-<input type="text" name="last_name" value="Lennon" />
-<input type="text" name="birthday" value="1940-10-9" />
+<input type="text" name="first_name" value="John" id="id_first_name" />
+<input type="text" name="last_name" value="Lennon" id="id_last_name" />
+<input type="text" name="birthday" value="1940-10-9" id="id_birthday" />
>>> for boundfield in p:
-... print boundfield.verbose_name, boundfield.data
+... print boundfield.label, boundfield.data
First name John
Last name Lennon
Birthday 1940-10-9
>>> print p
-<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" value="Lennon" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="Lennon" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>
Empty dictionaries are valid, too.
>>> p = Person({})
+>>> p.is_bound
+True
>>> p.errors
{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
>>> p.is_valid()
False
+>>> p.clean_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_data'
>>> print p
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
>>> print p.as_table()
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="birthday" id="id_birthday" /></td></tr>
>>> print p.as_ul()
-<li><ul class="errorlist"><li>This field is required.</li></ul>First name: <input type="text" name="first_name" /></li>
-<li><ul class="errorlist"><li>This field is required.</li></ul>Last name: <input type="text" name="last_name" /></li>
-<li><ul class="errorlist"><li>This field is required.</li></ul>Birthday: <input type="text" name="birthday" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
>>> print p.as_p()
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
-<p>First name: <input type="text" name="first_name" /></p>
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
-<p>Last name: <input type="text" name="last_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
<p><ul class="errorlist"><li>This field is required.</li></ul></p>
-<p>Birthday: <input type="text" name="birthday" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
If you don't pass any values to the Form's __init__(), or if you pass None,
-the Form won't do any validation. Form.errors will be an empty dictionary *but*
-Form.is_valid() will return False.
+the Form will be considered unbound and won't do any validation. Form.errors
+will be an empty dictionary *but* Form.is_valid() will return False.
>>> p = Person()
+>>> p.is_bound
+False
>>> p.errors
{}
>>> p.is_valid()
False
+>>> p.clean_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_data'
>>> print p
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
>>> print p.as_table()
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
>>> print p.as_ul()
-<li>First name: <input type="text" name="first_name" /></li>
-<li>Last name: <input type="text" name="last_name" /></li>
-<li>Birthday: <input type="text" name="birthday" /></li>
+<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
+<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
+<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
>>> print p.as_p()
-<p>First name: <input type="text" name="first_name" /></p>
-<p>Last name: <input type="text" name="last_name" /></p>
-<p>Birthday: <input type="text" name="birthday" /></p>
+<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
+<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
+<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
Unicode values are handled properly.
>>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'})
>>> p.as_table()
-u'<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>\n<tr><td>Last name:</td><td><input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></td></tr>\n<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /></td></tr>'
+u'<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" value="John" id="id_first_name" /></td></tr>\n<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></td></tr>\n<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></td></tr>'
>>> p.as_ul()
-u'<li>First name: <input type="text" name="first_name" value="John" /></li>\n<li>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></li>\n<li>Birthday: <input type="text" name="birthday" value="1940-10-9" /></li>'
+u'<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></li>\n<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></li>\n<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></li>'
>>> p.as_p()
-u'<p>First name: <input type="text" name="first_name" value="John" /></p>\n<p>Last name: <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /></p>\n<p>Birthday: <input type="text" name="birthday" value="1940-10-9" /></p>'
+u'<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" value="John" id="id_first_name" /></p>\n<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" id="id_last_name" /></p>\n<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" value="1940-10-9" id="id_birthday" /></p>'
>>> p = Person({'last_name': u'Lennon'})
>>> p.errors
@@ -1423,8 +1829,9 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re
* birthday
* This field is required.
>>> p.clean_data
->>> repr(p.clean_data)
-'None'
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_data'
>>> p['first_name'].errors
[u'This field is required.']
>>> p['first_name'].errors.as_ul()
@@ -1434,29 +1841,40 @@ u'* This field is required.'
>>> p = Person()
>>> print p['first_name']
-<input type="text" name="first_name" />
+<input type="text" name="first_name" id="id_first_name" />
>>> print p['last_name']
-<input type="text" name="last_name" />
+<input type="text" name="last_name" id="id_last_name" />
>>> print p['birthday']
-<input type="text" name="birthday" />
+<input type="text" name="birthday" id="id_birthday" />
+
+clean_data will always *only* contain a key for fields defined in the
+Form, even if you pass extra data when you define the Form. In this
+example, we pass a bunch of extra fields to the form constructor,
+but clean_data contains only the form's fields.
+>>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'}
+>>> p = Person(data)
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
"auto_id" tells the Form to add an "id" attribute to each form element.
If it's a string that contains '%s', Django will use that as a format string
into which the field's name will be inserted. It will also put a <label> around
the human-readable labels for a field.
->>> p = Person(auto_id='id_%s')
+>>> p = Person(auto_id='%s_id')
>>> print p.as_table()
-<tr><td><label for="id_first_name">First name:</label></td><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
-<tr><td><label for="id_last_name">Last name:</label></td><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
-<tr><td><label for="id_birthday">Birthday:</label></td><td><input type="text" name="birthday" id="id_birthday" /></td></tr>
+<tr><th><label for="first_name_id">First name:</label></th><td><input type="text" name="first_name" id="first_name_id" /></td></tr>
+<tr><th><label for="last_name_id">Last name:</label></th><td><input type="text" name="last_name" id="last_name_id" /></td></tr>
+<tr><th><label for="birthday_id">Birthday:</label></th><td><input type="text" name="birthday" id="birthday_id" /></td></tr>
>>> print p.as_ul()
-<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
-<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
-<li><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></li>
+<li><label for="first_name_id">First name:</label> <input type="text" name="first_name" id="first_name_id" /></li>
+<li><label for="last_name_id">Last name:</label> <input type="text" name="last_name" id="last_name_id" /></li>
+<li><label for="birthday_id">Birthday:</label> <input type="text" name="birthday" id="birthday_id" /></li>
>>> print p.as_p()
-<p><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></p>
-<p><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></p>
-<p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" /></p>
+<p><label for="first_name_id">First name:</label> <input type="text" name="first_name" id="first_name_id" /></p>
+<p><label for="last_name_id">Last name:</label> <input type="text" name="last_name" id="last_name_id" /></p>
+<p><label for="birthday_id">Birthday:</label> <input type="text" name="birthday" id="birthday_id" /></p>
If auto_id is any True value whose str() does not contain '%s', the "id"
attribute will be the name of the field.
@@ -1497,13 +1915,13 @@ attribute in the Form gets precedence.
>>> class SignupForm(Form):
... email = EmailField()
... get_spam = BooleanField()
->>> f = SignupForm()
+>>> f = SignupForm(auto_id=False)
>>> print f['email']
<input type="text" name="email" />
>>> print f['get_spam']
<input type="checkbox" name="get_spam" />
->>> f = SignupForm({'email': 'test@example.com', 'get_spam': True})
+>>> f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False)
>>> print f['email']
<input type="text" name="email" value="test@example.com" />
>>> print f['get_spam']
@@ -1513,7 +1931,7 @@ Any Field can have a Widget class passed to its constructor:
>>> class ContactForm(Form):
... subject = CharField()
... message = CharField(widget=Textarea)
->>> f = ContactForm()
+>>> f = ContactForm(auto_id=False)
>>> print f['subject']
<input type="text" name="subject" />
>>> print f['message']
@@ -1532,7 +1950,7 @@ The 'widget' parameter to a Field can also be an instance:
>>> class ContactForm(Form):
... subject = CharField()
... message = CharField(widget=Textarea(attrs={'rows': 80, 'cols': 20}))
->>> f = ContactForm()
+>>> f = ContactForm(auto_id=False)
>>> print f['message']
<textarea rows="80" cols="20" name="message"></textarea>
@@ -1540,7 +1958,7 @@ Instance-level attrs are *not* carried over to as_textarea(), as_text() and
as_hidden():
>>> f['message'].as_text()
u'<input type="text" name="message" />'
->>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'})
+>>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False)
>>> f['subject'].as_textarea()
u'<textarea name="subject">Hello</textarea>'
>>> f['message'].as_text()
@@ -1552,32 +1970,83 @@ For a form with a <select>, use ChoiceField:
>>> class FrameworkForm(Form):
... name = CharField()
... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')])
->>> f = FrameworkForm()
+>>> f = FrameworkForm(auto_id=False)
>>> print f['language']
<select name="language">
<option value="P">Python</option>
<option value="J">Java</option>
</select>
->>> f = FrameworkForm({'name': 'Django', 'language': 'P'})
+>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
>>> print f['language']
<select name="language">
<option value="P" selected="selected">Python</option>
<option value="J">Java</option>
</select>
+You can specify widget attributes in the Widget constructor.
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(attrs={'class': 'foo'}))
+>>> f = FrameworkForm(auto_id=False)
+>>> print f['language']
+<select class="foo" name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>
+>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
+>>> print f['language']
+<select class="foo" name="language">
+<option value="P" selected="selected">Python</option>
+<option value="J">Java</option>
+</select>
+
+When passing a custom widget instance to ChoiceField, note that setting
+'choices' on the widget is meaningless. The widget will use the choices
+defined on the Field, not the ones defined on the Widget.
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=Select(choices=[('R', 'Ruby'), ('P', 'Perl')], attrs={'class': 'foo'}))
+>>> f = FrameworkForm(auto_id=False)
+>>> print f['language']
+<select class="foo" name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>
+>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
+>>> print f['language']
+<select class="foo" name="language">
+<option value="P" selected="selected">Python</option>
+<option value="J">Java</option>
+</select>
+
+You can set a ChoiceField's choices after the fact.
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField()
+>>> f = FrameworkForm(auto_id=False)
+>>> print f['language']
+<select name="language">
+</select>
+>>> f.fields['language'].choices = [('P', 'Python'), ('J', 'Java')]
+>>> print f['language']
+<select name="language">
+<option value="P">Python</option>
+<option value="J">Java</option>
+</select>
+
Add widget=RadioSelect to use that widget with a ChoiceField.
>>> class FrameworkForm(Form):
... name = CharField()
... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=RadioSelect)
->>> f = FrameworkForm()
+>>> f = FrameworkForm(auto_id=False)
>>> print f['language']
<ul>
<li><label><input type="radio" name="language" value="P" /> Python</label></li>
<li><label><input type="radio" name="language" value="J" /> Java</label></li>
</ul>
>>> print f
-<tr><td>Name:</td><td><input type="text" name="name" /></td></tr>
-<tr><td>Language:</td><td><ul>
+<tr><th>Name:</th><td><input type="text" name="name" /></td></tr>
+<tr><th>Language:</th><td><ul>
<li><label><input type="radio" name="language" value="P" /> Python</label></li>
<li><label><input type="radio" name="language" value="J" /> Java</label></li>
</ul></td></tr>
@@ -1602,8 +2071,8 @@ When RadioSelect is used with auto_id, and the whole form is printed using
either as_table() or as_ul(), the label for the RadioSelect will point to the
ID of the *first* radio button.
>>> print f
-<tr><td><label for="id_name">Name:</label></td><td><input type="text" name="name" id="id_name" /></td></tr>
-<tr><td><label for="id_language_0">Language:</label></td><td><ul>
+<tr><th><label for="id_name">Name:</label></th><td><input type="text" name="name" id="id_name" /></td></tr>
+<tr><th><label for="id_language_0">Language:</label></th><td><ul>
<li><label><input type="radio" id="id_language_0" value="P" name="language" /> Python</label></li>
<li><label><input type="radio" id="id_language_1" value="J" name="language" /> Java</label></li>
</ul></td></tr>
@@ -1624,20 +2093,20 @@ MultipleChoiceField is a special case, as its data is required to be a list:
>>> class SongForm(Form):
... name = CharField()
... composers = MultipleChoiceField()
->>> f = SongForm()
+>>> f = SongForm(auto_id=False)
>>> print f['composers']
<select multiple="multiple" name="composers">
</select>
>>> class SongForm(Form):
... name = CharField()
... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')])
->>> f = SongForm()
+>>> f = SongForm(auto_id=False)
>>> print f['composers']
<select multiple="multiple" name="composers">
<option value="J">John Lennon</option>
<option value="P">Paul McCartney</option>
</select>
->>> f = SongForm({'name': 'Yesterday', 'composers': ['P']})
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
>>> print f['name']
<input type="text" name="name" value="Yesterday" />
>>> print f['composers']
@@ -1646,45 +2115,94 @@ MultipleChoiceField is a special case, as its data is required to be a list:
<option value="P" selected="selected">Paul McCartney</option>
</select>
+MultipleChoiceField rendered as_hidden() is a special case. Because it can
+have multiple values, its as_hidden() renders multiple <input type="hidden">
+tags.
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
+>>> print f['composers'].as_hidden()
+<input type="hidden" name="composers" value="P" />
+>>> f = SongForm({'name': 'From Me To You', 'composers': ['P', 'J']}, auto_id=False)
+>>> print f['composers'].as_hidden()
+<input type="hidden" name="composers" value="P" />
+<input type="hidden" name="composers" value="J" />
+
MultipleChoiceField can also be used with the CheckboxSelectMultiple widget.
>>> class SongForm(Form):
... name = CharField()
... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
->>> f = SongForm()
+>>> f = SongForm(auto_id=False)
>>> print f['composers']
<ul>
<li><label><input type="checkbox" name="composers" value="J" /> John Lennon</label></li>
<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
</ul>
->>> f = SongForm({'composers': ['J']})
+>>> f = SongForm({'composers': ['J']}, auto_id=False)
>>> print f['composers']
<ul>
<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
<li><label><input type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
</ul>
->>> f = SongForm({'composers': ['J', 'P']})
+>>> f = SongForm({'composers': ['J', 'P']}, auto_id=False)
>>> print f['composers']
<ul>
<li><label><input checked="checked" type="checkbox" name="composers" value="J" /> John Lennon</label></li>
<li><label><input checked="checked" type="checkbox" name="composers" value="P" /> Paul McCartney</label></li>
</ul>
+Regarding auto_id, CheckboxSelectMultiple is a special case. Each checkbox
+gets a distinct ID, formed by appending an underscore plus the checkbox's
+zero-based index.
+>>> f = SongForm(auto_id='%s_id')
+>>> print f['composers']
+<ul>
+<li><label><input type="checkbox" name="composers" value="J" id="composers_id_0" /> John Lennon</label></li>
+<li><label><input type="checkbox" name="composers" value="P" id="composers_id_1" /> Paul McCartney</label></li>
+</ul>
+
+Data for a MultipleChoiceField should be a list. QueryDict and MultiValueDict
+conveniently work with this.
+>>> data = {'name': 'Yesterday', 'composers': ['J', 'P']}
+>>> f = SongForm(data)
+>>> f.errors
+{}
+>>> from django.http import QueryDict
+>>> data = QueryDict('name=Yesterday&composers=J&composers=P')
+>>> f = SongForm(data)
+>>> f.errors
+{}
+>>> from django.utils.datastructures import MultiValueDict
+>>> data = MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P']))
+>>> f = SongForm(data)
+>>> f.errors
+{}
+
+The MultipleHiddenInput widget renders multiple values as hidden fields.
+>>> class SongFormHidden(Form):
+... name = CharField()
+... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=MultipleHiddenInput)
+>>> f = SongFormHidden(MultiValueDict(dict(name=['Yesterday'], composers=['J', 'P'])), auto_id=False)
+>>> print f.as_ul()
+<li>Name: <input type="text" name="name" value="Yesterday" /><input type="hidden" name="composers" value="J" />
+<input type="hidden" name="composers" value="P" /></li>
+
When using CheckboxSelectMultiple, the framework expects a list of input and
returns a list of input.
->>> f = SongForm({'name': 'Yesterday'})
+>>> f = SongForm({'name': 'Yesterday'}, auto_id=False)
>>> f.errors
{'composers': [u'This field is required.']}
->>> f = SongForm({'name': 'Yesterday', 'composers': ['J']})
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
>>> f.errors
{}
>>> f.clean_data
{'composers': [u'J'], 'name': u'Yesterday'}
->>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']})
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
>>> f.errors
{}
>>> f.clean_data
{'composers': [u'J', u'P'], 'name': u'Yesterday'}
+# Validating multiple fields in relation to another ###########################
+
There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in
@@ -1700,16 +2218,16 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data['password2']
->>> f = UserRegistration()
+>>> f = UserRegistration(auto_id=False)
>>> f.errors
{}
->>> f = UserRegistration({})
+>>> f = UserRegistration({}, auto_id=False)
>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
>>> f.errors
{'password2': [u'Please make sure your passwords match.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors
{}
>>> f.clean_data
@@ -1730,52 +2248,91 @@ Form.clean() is required to return a dictionary of all clean data.
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data
->>> f = UserRegistration()
+>>> f = UserRegistration(auto_id=False)
>>> f.errors
{}
->>> f = UserRegistration({})
+>>> f = UserRegistration({}, auto_id=False)
>>> print f.as_table()
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Password1:</td><td><input type="password" name="password1" /></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>This field is required.</li></ul></td></tr>
-<tr><td>Password2:</td><td><input type="password" name="password2" /></td></tr>
+<tr><th>Username:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="text" name="username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password1" /></td></tr>
+<tr><th>Password2:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="password" name="password2" /></td></tr>
>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
>>> f.errors
{'__all__': [u'Please make sure your passwords match.']}
>>> print f.as_table()
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
-<tr><td>Username:</td><td><input type="text" name="username" value="adrian" /></td></tr>
-<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
-<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
+<tr><th>Username:</th><td><input type="text" name="username" value="adrian" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>
>>> print f.as_ul()
<li><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></li>
-<li>Username: <input type="text" name="username" value="adrian" /></li>
+<li>Username: <input type="text" name="username" value="adrian" maxlength="10" /></li>
<li>Password1: <input type="password" name="password1" value="foo" /></li>
<li>Password2: <input type="password" name="password2" value="bar" /></li>
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors
{}
>>> f.clean_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
+# Dynamic construction ########################################################
+
It's possible to construct a Form dynamically by adding to the self.fields
dictionary in __init__(). Don't forget to call Form.__init__() within the
subclass' __init__().
>>> class Person(Form):
... first_name = CharField()
... last_name = CharField()
-... def __init__(self):
-... super(Person, self).__init__()
+... def __init__(self, *args, **kwargs):
+... super(Person, self).__init__(*args, **kwargs)
... self.fields['birthday'] = DateField()
->>> p = Person()
+>>> p = Person(auto_id=False)
>>> print p
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /></td></tr>
+<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" /></td></tr>
+
+Instances of a dynamic Form do not persist fields from one Form instance to
+the next.
+>>> class MyForm(Form):
+... def __init__(self, data=None, auto_id=False, field_list=[]):
+... Form.__init__(self, data, auto_id)
+... for field in field_list:
+... self.fields[field[0]] = field[1]
+>>> field_list = [('field1', CharField()), ('field2', CharField())]
+>>> my_form = MyForm(field_list=field_list)
+>>> print my_form
+<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
+>>> field_list = [('field3', CharField()), ('field4', CharField())]
+>>> my_form = MyForm(field_list=field_list)
+>>> print my_form
+<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
+
+>>> class MyForm(Form):
+... default_field_1 = CharField()
+... default_field_2 = CharField()
+... def __init__(self, data=None, auto_id=False, field_list=[]):
+... Form.__init__(self, data, auto_id)
+... for field in field_list:
+... self.fields[field[0]] = field[1]
+>>> field_list = [('field1', CharField()), ('field2', CharField())]
+>>> my_form = MyForm(field_list=field_list)
+>>> print my_form
+<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr>
+<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr>
+<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
+>>> field_list = [('field3', CharField()), ('field4', CharField())]
+>>> my_form = MyForm(field_list=field_list)
+>>> print my_form
+<tr><th>Default field 1:</th><td><input type="text" name="default_field_1" /></td></tr>
+<tr><th>Default field 2:</th><td><input type="text" name="default_field_2" /></td></tr>
+<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
HiddenInput widgets are displayed differently in the as_table(), as_ul()
and as_p() output of a Form -- their verbose names are not displayed, and a
@@ -1786,11 +2343,11 @@ form, directly after that row's form element.
... last_name = CharField()
... hidden_text = CharField(widget=HiddenInput)
... birthday = DateField()
->>> p = Person()
+>>> p = Person(auto_id=False)
>>> print p
-<tr><td>First name:</td><td><input type="text" name="first_name" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
+<tr><th>First name:</th><td><input type="text" name="first_name" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" /><input type="hidden" name="hidden_text" /></td></tr>
>>> print p.as_ul()
<li>First name: <input type="text" name="first_name" /></li>
<li>Last name: <input type="text" name="last_name" /></li>
@@ -1803,9 +2360,9 @@ form, directly after that row's form element.
With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
>>> p = Person(auto_id='id_%s')
>>> print p
-<tr><td><label for="id_first_name">First name:</label></td><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
-<tr><td><label for="id_last_name">Last name:</label></td><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
-<tr><td><label for="id_birthday">Birthday:</label></td><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
+<tr><th><label for="id_first_name">First name:</label></th><td><input type="text" name="first_name" id="id_first_name" /></td></tr>
+<tr><th><label for="id_last_name">Last name:</label></th><td><input type="text" name="last_name" id="id_last_name" /></td></tr>
+<tr><th><label for="id_birthday">Birthday:</label></th><td><input type="text" name="birthday" id="id_birthday" /><input type="hidden" name="hidden_text" id="id_hidden_text" /></td></tr>
>>> print p.as_ul()
<li><label for="id_first_name">First name:</label> <input type="text" name="first_name" id="id_first_name" /></li>
<li><label for="id_last_name">Last name:</label> <input type="text" name="last_name" id="id_last_name" /></li>
@@ -1819,12 +2376,12 @@ If a field with a HiddenInput has errors, the as_table() and as_ul() output
will include the error message(s) with the text "(Hidden field [fieldname]) "
prepended. This message is displayed at the top of the output, regardless of
its field's order in the form.
->>> p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'})
+>>> p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False)
>>> print p
<tr><td colspan="2"><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></td></tr>
-<tr><td>First name:</td><td><input type="text" name="first_name" value="John" /></td></tr>
-<tr><td>Last name:</td><td><input type="text" name="last_name" value="Lennon" /></td></tr>
-<tr><td>Birthday:</td><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
+<tr><th>First name:</th><td><input type="text" name="first_name" value="John" /></td></tr>
+<tr><th>Last name:</th><td><input type="text" name="last_name" value="Lennon" /></td></tr>
+<tr><th>Birthday:</th><td><input type="text" name="birthday" value="1940-10-9" /><input type="hidden" name="hidden_text" /></td></tr>
>>> print p.as_ul()
<li><ul class="errorlist"><li>(Hidden field hidden_text) This field is required.</li></ul></li>
<li>First name: <input type="text" name="first_name" value="John" /></li>
@@ -1840,7 +2397,7 @@ A corner case: It's possible for a form to have only HiddenInputs.
>>> class TestForm(Form):
... foo = CharField(widget=HiddenInput)
... bar = CharField(widget=HiddenInput)
->>> p = TestForm()
+>>> p = TestForm(auto_id=False)
>>> print p.as_table()
<input type="hidden" name="foo" /><input type="hidden" name="bar" />
>>> print p.as_ul()
@@ -1864,22 +2421,391 @@ A Form's fields are displayed in the same order in which they were defined.
... field12 = CharField()
... field13 = CharField()
... field14 = CharField()
->>> p = TestForm()
+>>> p = TestForm(auto_id=False)
>>> print p
-<tr><td>Field1:</td><td><input type="text" name="field1" /></td></tr>
-<tr><td>Field2:</td><td><input type="text" name="field2" /></td></tr>
-<tr><td>Field3:</td><td><input type="text" name="field3" /></td></tr>
-<tr><td>Field4:</td><td><input type="text" name="field4" /></td></tr>
-<tr><td>Field5:</td><td><input type="text" name="field5" /></td></tr>
-<tr><td>Field6:</td><td><input type="text" name="field6" /></td></tr>
-<tr><td>Field7:</td><td><input type="text" name="field7" /></td></tr>
-<tr><td>Field8:</td><td><input type="text" name="field8" /></td></tr>
-<tr><td>Field9:</td><td><input type="text" name="field9" /></td></tr>
-<tr><td>Field10:</td><td><input type="text" name="field10" /></td></tr>
-<tr><td>Field11:</td><td><input type="text" name="field11" /></td></tr>
-<tr><td>Field12:</td><td><input type="text" name="field12" /></td></tr>
-<tr><td>Field13:</td><td><input type="text" name="field13" /></td></tr>
-<tr><td>Field14:</td><td><input type="text" name="field14" /></td></tr>
+<tr><th>Field1:</th><td><input type="text" name="field1" /></td></tr>
+<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
+<tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr>
+<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
+<tr><th>Field5:</th><td><input type="text" name="field5" /></td></tr>
+<tr><th>Field6:</th><td><input type="text" name="field6" /></td></tr>
+<tr><th>Field7:</th><td><input type="text" name="field7" /></td></tr>
+<tr><th>Field8:</th><td><input type="text" name="field8" /></td></tr>
+<tr><th>Field9:</th><td><input type="text" name="field9" /></td></tr>
+<tr><th>Field10:</th><td><input type="text" name="field10" /></td></tr>
+<tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr>
+<tr><th>Field12:</th><td><input type="text" name="field12" /></td></tr>
+<tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr>
+<tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr>
+
+Some Field classes have an effect on the HTML attributes of their associated
+Widget. If you set max_length in a CharField and its associated widget is
+either a TextInput or PasswordInput, then the widget's rendered HTML will
+include the "maxlength" attribute.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10) # uses TextInput by default
+... password = CharField(max_length=10, widget=PasswordInput)
+... realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test
+... address = CharField() # no max_length defined here
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" maxlength="10" /></li>
+<li>Realname: <input type="text" name="realname" maxlength="10" /></li>
+<li>Address: <input type="text" name="address" /></li>
+
+If you specify a custom "attrs" that includes the "maxlength" attribute,
+the Field's max_length attribute will override whatever "maxlength" you specify
+in "attrs".
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20}))
+... password = CharField(max_length=10, widget=PasswordInput)
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" maxlength="10" /></li>
+
+# Specifying labels ###########################################################
+
+You can specify the label for a field by using the 'label' argument to a Field
+class. If you don't specify 'label', Django will use the field name with
+underscores converted to spaces, and the initial letter capitalized.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, label='Your username')
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput, label='Password (again)')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Your username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password1: <input type="password" name="password1" /></li>
+<li>Password (again): <input type="password" name="password2" /></li>
+
+A label can be a Unicode object or a bytestring with special characters.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, label='ŠĐĆŽćžšđ')
+... password = CharField(widget=PasswordInput, label=u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111')
+>>> p = UserRegistration(auto_id=False)
+>>> p.as_ul()
+u'<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="text" name="username" maxlength="10" /></li>\n<li>\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111: <input type="password" name="password" /></li>'
+
+If a label is set to the empty string for a field, that field won't get a label.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, label='')
+... password = CharField(widget=PasswordInput)
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li> <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration(auto_id='id_%s')
+>>> print p.as_ul()
+<li> <input id="id_username" type="text" name="username" maxlength="10" /></li>
+<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
+
+If label is None, Django will auto-create the label from the field name. This
+is default behavior.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, label=None)
+... password = CharField(widget=PasswordInput)
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration(auto_id='id_%s')
+>>> print p.as_ul()
+<li><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></li>
+<li><label for="id_password">Password:</label> <input type="password" name="password" id="id_password" /></li>
+
+# Initial data ################################################################
+
+You can specify initial data for a field by using the 'initial' argument to a
+Field class. This initial data is displayed when a Form is rendered with *no*
+data. It is not displayed when a Form is rendered with any data (including an
+empty dictionary). Also, the initial value is *not* used if data for a
+particular required field isn't provided.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, initial='django')
+... password = CharField(widget=PasswordInput)
+
+Here, we're not submitting any data, so the initial value will be displayed.
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+
+Here, we're submitting data, so the initial value will *not* be displayed.
+>>> p = UserRegistration({}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u''}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u'foo'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+
+An 'initial' value is *not* used as a fallback if data is not provided. In this
+example, we don't provide a value for 'username', and the form raises a
+validation error rather than using the initial value for 'username'.
+>>> p = UserRegistration({'password': 'secret'})
+>>> p.errors
+{'username': [u'This field is required.']}
+>>> p.is_valid()
+False
+
+# Dynamic initial data ########################################################
+
+The previous technique dealt with "hard-coded" initial data, but it's also
+possible to specify initial data after you've already created the Form class
+(i.e., at runtime). Use the 'initial' parameter to the Form constructor. This
+should be a dictionary containing initial values for one or more fields in the
+form, keyed by field name.
+
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password = CharField(widget=PasswordInput)
+
+Here, we're not submitting any data, so the initial value will be displayed.
+>>> p = UserRegistration(initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration(initial={'username': 'stephane'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+
+The 'initial' parameter is meaningless if you pass data.
+>>> p = UserRegistration({}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+
+A dynamic 'initial' value is *not* used as a fallback if data is not provided.
+In this example, we don't provide a value for 'username', and the form raises a
+validation error rather than using the initial value for 'username'.
+>>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
+>>> p.errors
+{'username': [u'This field is required.']}
+>>> p.is_valid()
+False
+
+If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
+then the latter will get precedence.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, initial='django')
+... password = CharField(widget=PasswordInput)
+>>> p = UserRegistration(initial={'username': 'babik'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+
+# Help text ###################################################################
+
+You can specify descriptive text for a field by using the 'help_text' argument
+to a Field class. This help text is displayed when a Form is rendered.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, help_text='e.g., user@example.com')
+... password = CharField(widget=PasswordInput, help_text='Choose wisely.')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /> Choose wisely.</li>
+>>> print p.as_p()
+<p>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</p>
+<p>Password: <input type="password" name="password" /> Choose wisely.</p>
+>>> print p.as_table()
+<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /><br />e.g., user@example.com</td></tr>
+<tr><th>Password:</th><td><input type="password" name="password" /><br />Choose wisely.</td></tr>
+
+The help text is displayed whether or not data is provided for the form.
+>>> p = UserRegistration({'username': u'foo'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="foo" maxlength="10" /> e.g., user@example.com</li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> Choose wisely.</li>
+
+help_text is not displayed for hidden fields. It can be used for documentation
+purposes, though.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, help_text='e.g., user@example.com')
+... password = CharField(widget=PasswordInput)
+... next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
+
+# Forms with prefixes #########################################################
+
+Sometimes it's necessary to have multiple forms display on the same HTML page,
+or multiple copies of the same form. We can accomplish this with form prefixes.
+Pass the keyword argument 'prefix' to the Form constructor to use this feature.
+This value will be prepended to each HTML form field name. One way to think
+about this is "namespaces for HTML forms". Notice that in the data argument,
+each field's key has the prefix, in this case 'person1', prepended to the
+actual field name.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+>>> data = {
+... 'person1-first_name': u'John',
+... 'person1-last_name': u'Lennon',
+... 'person1-birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='person1')
+>>> print p.as_ul()
+<li><label for="id_person1-first_name">First name:</label> <input type="text" name="person1-first_name" value="John" id="id_person1-first_name" /></li>
+<li><label for="id_person1-last_name">Last name:</label> <input type="text" name="person1-last_name" value="Lennon" id="id_person1-last_name" /></li>
+<li><label for="id_person1-birthday">Birthday:</label> <input type="text" name="person1-birthday" value="1940-10-9" id="id_person1-birthday" /></li>
+>>> print p['first_name']
+<input type="text" name="person1-first_name" value="John" id="id_person1-first_name" />
+>>> print p['last_name']
+<input type="text" name="person1-last_name" value="Lennon" id="id_person1-last_name" />
+>>> print p['birthday']
+<input type="text" name="person1-birthday" value="1940-10-9" id="id_person1-birthday" />
+>>> p.errors
+{}
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
+Let's try submitting some bad data to make sure form.errors and field.errors
+work as expected.
+>>> data = {
+... 'person1-first_name': u'',
+... 'person1-last_name': u'',
+... 'person1-birthday': u''
+... }
+>>> p = Person(data, prefix='person1')
+>>> p.errors
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
+>>> p['first_name'].errors
+[u'This field is required.']
+>>> p['person1-first_name'].errors
+Traceback (most recent call last):
+...
+KeyError: "Key 'person1-first_name' not found in Form"
+
+In this example, the data doesn't have a prefix, but the form requires it, so
+the form doesn't "see" the fields.
+>>> data = {
+... 'first_name': u'John',
+... 'last_name': u'Lennon',
+... 'birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='person1')
+>>> p.errors
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
+
+With prefixes, a single data dictionary can hold data for multiple instances
+of the same form.
+>>> data = {
+... 'person1-first_name': u'John',
+... 'person1-last_name': u'Lennon',
+... 'person1-birthday': u'1940-10-9',
+... 'person2-first_name': u'Jim',
+... 'person2-last_name': u'Morrison',
+... 'person2-birthday': u'1943-12-8'
+... }
+>>> p1 = Person(data, prefix='person1')
+>>> p1.is_valid()
+True
+>>> p1.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+>>> p2 = Person(data, prefix='person2')
+>>> p2.is_valid()
+True
+>>> p2.clean_data
+{'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)}
+
+By default, forms append a hyphen between the prefix and the field name, but a
+form can alter that behavior by implementing the add_prefix() method. This
+method takes a field name and returns the prefixed field, according to
+self.prefix.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+... def add_prefix(self, field_name):
+... return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name
+>>> p = Person(prefix='foo')
+>>> print p.as_ul()
+<li><label for="id_foo-prefix-first_name">First name:</label> <input type="text" name="foo-prefix-first_name" id="id_foo-prefix-first_name" /></li>
+<li><label for="id_foo-prefix-last_name">Last name:</label> <input type="text" name="foo-prefix-last_name" id="id_foo-prefix-last_name" /></li>
+<li><label for="id_foo-prefix-birthday">Birthday:</label> <input type="text" name="foo-prefix-birthday" id="id_foo-prefix-birthday" /></li>
+>>> data = {
+... 'foo-prefix-first_name': u'John',
+... 'foo-prefix-last_name': u'Lennon',
+... 'foo-prefix-birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='foo')
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
+# Forms with NullBooleanFields ################################################
+
+NullBooleanField is a bit of a special case because its presentation (widget)
+is different than its data. This is handled transparently, though.
+
+>>> class Person(Form):
+... name = CharField()
+... is_cool = NullBooleanField()
+>>> p = Person({'name': u'Joe'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
# Basic form processing in a view #############################################
@@ -1894,9 +2820,9 @@ A Form's fields are displayed in the same order in which they were defined.
... return self.clean_data
>>> def my_function(method, post_data):
... if method == 'POST':
-... form = UserRegistration(post_data)
+... form = UserRegistration(post_data, auto_id=False)
... else:
-... form = UserRegistration()
+... form = UserRegistration(auto_id=False)
... if form.is_valid():
... return 'VALID: %r' % form.clean_data
... t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>')
@@ -1906,9 +2832,9 @@ Case 1: GET (an empty form, with no errors).
>>> print my_function('GET', {})
<form action="" method="post">
<table>
-<tr><td>Username:</td><td><input type="text" name="username" /></td></tr>
-<tr><td>Password1:</td><td><input type="password" name="password1" /></td></tr>
-<tr><td>Password2:</td><td><input type="password" name="password2" /></td></tr>
+<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" /></td></tr>
</table>
<input type="submit" />
</form>
@@ -1918,10 +2844,9 @@ Case 2: POST with erroneous data (a redisplayed form, with errors).
<form action="" method="post">
<table>
<tr><td colspan="2"><ul class="errorlist"><li>Please make sure your passwords match.</li></ul></td></tr>
-<tr><td colspan="2"><ul class="errorlist"><li>Ensure this value has at most 10 characters.</li></ul></td></tr>
-<tr><td>Username:</td><td><input type="text" name="username" value="this-is-a-long-username" /></td></tr>
-<tr><td>Password1:</td><td><input type="password" name="password1" value="foo" /></td></tr>
-<tr><td>Password2:</td><td><input type="password" name="password2" value="bar" /></td></tr>
+<tr><th>Username:</th><td><ul class="errorlist"><li>Ensure this value has at most 10 characters.</li></ul><input type="text" name="username" value="this-is-a-long-username" maxlength="10" /></td></tr>
+<tr><th>Password1:</th><td><input type="password" name="password1" value="foo" /></td></tr>
+<tr><th>Password2:</th><td><input type="password" name="password2" value="bar" /></td></tr>
</table>
<input type="submit" />
</form>
@@ -1952,39 +2877,41 @@ particular field.
... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
... <input type="submit" />
... </form>''')
->>> print t.render(Context({'form': UserRegistration()}))
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
<form action="">
-<p><label>Your username: <input type="text" name="username" /></label></p>
+<p><label>Your username: <input type="text" name="username" maxlength="10" /></label></p>
<p><label>Password: <input type="password" name="password1" /></label></p>
<p><label>Password (again): <input type="password" name="password2" /></label></p>
<input type="submit" />
</form>
->>> print t.render(Context({'form': UserRegistration({'username': 'django'})}))
+>>> print t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)}))
<form action="">
-<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password: <input type="password" name="password1" /></label></p>
<ul class="errorlist"><li>This field is required.</li></ul><p><label>Password (again): <input type="password" name="password2" /></label></p>
<input type="submit" />
</form>
-Use form.[field].verbose_name to output a field's "verbose name" -- its field
-name with underscores converted to spaces, and the initial letter capitalized.
+Use form.[field].label to output a field's label. You can specify the label for
+a field by using the 'label' argument to a Field class. If you don't specify
+'label', Django will use the field name with underscores converted to spaces,
+and the initial letter capitalized.
>>> t = Template('''<form action="">
-... <p><label>{{ form.username.verbose_name }}: {{ form.username }}</label></p>
-... <p><label>{{ form.password1.verbose_name }}: {{ form.password1 }}</label></p>
-... <p><label>{{ form.password2.verbose_name }}: {{ form.password2 }}</label></p>
+... <p><label>{{ form.username.label }}: {{ form.username }}</label></p>
+... <p><label>{{ form.password1.label }}: {{ form.password1 }}</label></p>
+... <p><label>{{ form.password2.label }}: {{ form.password2 }}</label></p>
... <input type="submit" />
... </form>''')
->>> print t.render(Context({'form': UserRegistration()}))
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
<form action="">
-<p><label>Username: <input type="text" name="username" /></label></p>
+<p><label>Username: <input type="text" name="username" maxlength="10" /></label></p>
<p><label>Password1: <input type="password" name="password1" /></label></p>
<p><label>Password2: <input type="password" name="password2" /></label></p>
<input type="submit" />
</form>
-User form.[field].label_tag to output a field's verbose_name with a <label>
-tag wrapped around it, but *only* if the given field has an "id" attribute.
+User form.[field].label_tag to output a field's label with a <label> tag
+wrapped around it, but *only* if the given field has an "id" attribute.
Recall from above that passing the "auto_id" argument to a Form gives each
field an "id" attribute.
>>> t = Template('''<form action="">
@@ -1993,21 +2920,30 @@ field an "id" attribute.
... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
... <input type="submit" />
... </form>''')
->>> print t.render(Context({'form': UserRegistration()}))
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
<form action="">
-<p>Username: <input type="text" name="username" /></p>
+<p>Username: <input type="text" name="username" maxlength="10" /></p>
<p>Password1: <input type="password" name="password1" /></p>
<p>Password2: <input type="password" name="password2" /></p>
<input type="submit" />
</form>
>>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')}))
<form action="">
-<p><label for="id_username">Username</label>: <input type="text" name="username" id="id_username" /></p>
+<p><label for="id_username">Username</label>: <input id="id_username" type="text" name="username" maxlength="10" /></p>
<p><label for="id_password1">Password1</label>: <input type="password" name="password1" id="id_password1" /></p>
<p><label for="id_password2">Password2</label>: <input type="password" name="password2" id="id_password2" /></p>
<input type="submit" />
</form>
+The label_tag() method takes an optional attrs argument: a dictionary of HTML
+attributes to add to the <label> tag.
+>>> f = UserRegistration(auto_id='id_%s')
+>>> for bf in f:
+... print bf.label_tag(attrs={'class': 'pretty'})
+<label for="id_username" class="pretty">Username</label>
+<label for="id_password1" class="pretty">Password1</label>
+<label for="id_password2" class="pretty">Password2</label>
+
To display the errors that aren't associated with a particular field -- e.g.,
the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
template. If used on its own, it is displayed as a <ul> (or an empty string, if
@@ -2018,9 +2954,9 @@ the list of errors is empty). You can also use it in {% if %} statements.
... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
... <input type="submit" />
... </form>''')
->>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'})}))
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)}))
<form action="">
-<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
<input type="submit" />
@@ -2032,14 +2968,149 @@ the list of errors is empty). You can also use it in {% if %} statements.
... {{ form.password2.errors.as_ul }}<p><label>Password (again): {{ form.password2 }}</label></p>
... <input type="submit" />
... </form>''')
->>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'})}))
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)}))
<form action="">
<ul class="errorlist"><li>Please make sure your passwords match.</li></ul>
-<p><label>Your username: <input type="text" name="username" value="django" /></label></p>
+<p><label>Your username: <input type="text" name="username" value="django" maxlength="10" /></label></p>
<p><label>Password: <input type="password" name="password1" value="foo" /></label></p>
<p><label>Password (again): <input type="password" name="password2" value="bar" /></label></p>
<input type="submit" />
</form>
+
+#################
+# Extra widgets #
+#################
+
+The newforms library comes with some extra, higher-level Widget classes that
+demonstrate some of the library's abilities.
+
+# SelectDateWidget ############################################################
+
+>>> from django.newforms.extras import SelectDateWidget
+>>> w = SelectDateWidget(years=('2007','2008','2009','2010','2011','2012','2013','2014','2015','2016'))
+>>> print w.render('mydate', '')
+<select name="mydate_month">
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year">
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>
+>>> w.render('mydate', None) == w.render('mydate', '')
+True
+>>> print w.render('mydate', '2010-04-15')
+<select name="mydate_month">
+<option value="1">January</option>
+<option value="2">February</option>
+<option value="3">March</option>
+<option value="4" selected="selected">April</option>
+<option value="5">May</option>
+<option value="6">June</option>
+<option value="7">July</option>
+<option value="8">August</option>
+<option value="9">September</option>
+<option value="10">October</option>
+<option value="11">November</option>
+<option value="12">December</option>
+</select>
+<select name="mydate_day">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+<option value="5">5</option>
+<option value="6">6</option>
+<option value="7">7</option>
+<option value="8">8</option>
+<option value="9">9</option>
+<option value="10">10</option>
+<option value="11">11</option>
+<option value="12">12</option>
+<option value="13">13</option>
+<option value="14">14</option>
+<option value="15" selected="selected">15</option>
+<option value="16">16</option>
+<option value="17">17</option>
+<option value="18">18</option>
+<option value="19">19</option>
+<option value="20">20</option>
+<option value="21">21</option>
+<option value="22">22</option>
+<option value="23">23</option>
+<option value="24">24</option>
+<option value="25">25</option>
+<option value="26">26</option>
+<option value="27">27</option>
+<option value="28">28</option>
+<option value="29">29</option>
+<option value="30">30</option>
+<option value="31">31</option>
+</select>
+<select name="mydate_year">
+<option value="2007">2007</option>
+<option value="2008">2008</option>
+<option value="2009">2009</option>
+<option value="2010" selected="selected">2010</option>
+<option value="2011">2011</option>
+<option value="2012">2012</option>
+<option value="2013">2013</option>
+<option value="2014">2014</option>
+<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>
+
"""
if __name__ == "__main__":