summaryrefslogtreecommitdiff
path: root/tests/regressiontests
diff options
context:
space:
mode:
Diffstat (limited to 'tests/regressiontests')
-rw-r--r--tests/regressiontests/bug639/__init__.py (renamed from tests/regressiontests/markup/__init__.py)0
-rw-r--r--tests/regressiontests/bug639/models.py16
-rw-r--r--tests/regressiontests/bug639/test.jpgbin0 -> 1780 bytes
-rw-r--r--tests/regressiontests/bug639/tests.py42
-rw-r--r--tests/regressiontests/cache/tests.py5
-rw-r--r--tests/regressiontests/datastructures/__init__.py (renamed from tests/regressiontests/markup/models.py)0
-rw-r--r--tests/regressiontests/datastructures/models.py0
-rw-r--r--tests/regressiontests/datastructures/tests.py67
-rw-r--r--tests/regressiontests/dateformat/tests.py2
-rw-r--r--tests/regressiontests/defaultfilters/tests.py95
-rw-r--r--tests/regressiontests/dispatch/__init__.py2
-rw-r--r--tests/regressiontests/dispatch/models.py0
-rw-r--r--tests/regressiontests/dispatch/tests/__init__.py7
-rw-r--r--tests/regressiontests/dispatch/tests/test_dispatcher.py144
-rw-r--r--tests/regressiontests/dispatch/tests/test_robustapply.py34
-rw-r--r--tests/regressiontests/dispatch/tests/test_saferef.py79
-rw-r--r--tests/regressiontests/fixtures_regress/__init__.py0
-rw-r--r--tests/regressiontests/fixtures_regress/fixtures/sequence.json10
-rw-r--r--tests/regressiontests/fixtures_regress/models.py29
-rw-r--r--tests/regressiontests/forms/localflavor.py1359
-rw-r--r--tests/regressiontests/forms/regressions.py51
-rw-r--r--tests/regressiontests/forms/tests.py1230
-rw-r--r--tests/regressiontests/httpwrappers/tests.py32
-rw-r--r--tests/regressiontests/humanize/__init__.py0
-rw-r--r--tests/regressiontests/humanize/models.py0
-rw-r--r--tests/regressiontests/humanize/tests.py54
-rw-r--r--tests/regressiontests/invalid_admin_options/__init__.py0
-rw-r--r--tests/regressiontests/invalid_admin_options/models.py337
-rw-r--r--tests/regressiontests/many_to_one_regress/models.py29
-rw-r--r--tests/regressiontests/markup/tests.py69
-rw-r--r--tests/regressiontests/null_queries/models.py2
-rw-r--r--tests/regressiontests/serializers_regress/__init__.py0
-rw-r--r--tests/regressiontests/serializers_regress/models.py211
-rw-r--r--tests/regressiontests/serializers_regress/tests.py320
-rw-r--r--tests/regressiontests/templates/tests.py162
-rw-r--r--tests/regressiontests/templates/urls.py11
-rw-r--r--tests/regressiontests/templates/views.py10
-rw-r--r--tests/regressiontests/test_client_regress/__init__.py0
-rw-r--r--tests/regressiontests/test_client_regress/models.py164
-rw-r--r--tests/regressiontests/test_client_regress/urls.py7
-rw-r--r--tests/regressiontests/test_client_regress/views.py8
-rw-r--r--tests/regressiontests/text/__init__.py0
-rw-r--r--tests/regressiontests/text/models.py0
-rw-r--r--tests/regressiontests/text/tests.py17
44 files changed, 4448 insertions, 157 deletions
diff --git a/tests/regressiontests/markup/__init__.py b/tests/regressiontests/bug639/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/regressiontests/markup/__init__.py
+++ b/tests/regressiontests/bug639/__init__.py
diff --git a/tests/regressiontests/bug639/models.py b/tests/regressiontests/bug639/models.py
new file mode 100644
index 0000000000..7cfdfc82ef
--- /dev/null
+++ b/tests/regressiontests/bug639/models.py
@@ -0,0 +1,16 @@
+import tempfile
+from django.db import models
+
+class Photo(models.Model):
+ title = models.CharField(maxlength=30)
+ image = models.FileField(upload_to=tempfile.gettempdir())
+
+ # Support code for the tests; this keeps track of how many times save() gets
+ # called on each instance.
+ def __init__(self, *args, **kwargs):
+ super(Photo, self).__init__(*args, **kwargs)
+ self._savecount = 0
+
+ def save(self):
+ super(Photo, self).save()
+ self._savecount +=1 \ No newline at end of file
diff --git a/tests/regressiontests/bug639/test.jpg b/tests/regressiontests/bug639/test.jpg
new file mode 100644
index 0000000000..391b57a0f3
--- /dev/null
+++ b/tests/regressiontests/bug639/test.jpg
Binary files differ
diff --git a/tests/regressiontests/bug639/tests.py b/tests/regressiontests/bug639/tests.py
new file mode 100644
index 0000000000..f9596d06cb
--- /dev/null
+++ b/tests/regressiontests/bug639/tests.py
@@ -0,0 +1,42 @@
+"""
+Tests for file field behavior, and specifically #639, in which Model.save() gets
+called *again* for each FileField. This test will fail if calling an
+auto-manipulator's save() method causes Model.save() to be called more than once.
+"""
+
+import os
+import unittest
+from regressiontests.bug639.models import Photo
+from django.http import QueryDict
+from django.utils.datastructures import MultiValueDict
+
+class Bug639Test(unittest.TestCase):
+
+ def testBug639(self):
+ """
+ Simulate a file upload and check how many times Model.save() gets called.
+ """
+ # Grab an image for testing
+ img = open(os.path.join(os.path.dirname(__file__), "test.jpg"), "rb").read()
+
+ # Fake a request query dict with the file
+ qd = QueryDict("title=Testing&image=", mutable=True)
+ qd["image_file"] = {
+ "filename" : "test.jpg",
+ "content-type" : "image/jpeg",
+ "content" : img
+ }
+
+ manip = Photo.AddManipulator()
+ manip.do_html2python(qd)
+ p = manip.save(qd)
+
+ # Check the savecount stored on the object (see the model)
+ self.assertEqual(p._savecount, 1)
+
+ def tearDown(self):
+ """
+ Make sure to delete the "uploaded" file to avoid clogging /tmp.
+ """
+ p = Photo.objects.get()
+ os.unlink(p.get_image_filename()) \ No newline at end of file
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index cf58ab321a..9dc7161c03 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -46,6 +46,11 @@ class Cache(unittest.TestCase):
self.assertEqual(cache.has_key("hello"), True)
self.assertEqual(cache.has_key("goodbye"), False)
+ def test_in(self):
+ cache.set("hello", "goodbye")
+ self.assertEqual("hello" in cache, True)
+ self.assertEqual("goodbye" in cache, False)
+
def test_data_types(self):
# test data types
stuff = {
diff --git a/tests/regressiontests/markup/models.py b/tests/regressiontests/datastructures/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/regressiontests/markup/models.py
+++ b/tests/regressiontests/datastructures/__init__.py
diff --git a/tests/regressiontests/datastructures/models.py b/tests/regressiontests/datastructures/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/datastructures/models.py
diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py
new file mode 100644
index 0000000000..18eb4fcccd
--- /dev/null
+++ b/tests/regressiontests/datastructures/tests.py
@@ -0,0 +1,67 @@
+"""
+# Tests for stuff in django.utils.datastructures.
+
+>>> from django.utils.datastructures import *
+
+### MergeDict #################################################################
+
+>>> d1 = {'chris':'cool','camri':'cute','cotton':'adorable','tulip':'snuggable', 'twoofme':'firstone'}
+>>> d2 = {'chris2':'cool2','camri2':'cute2','cotton2':'adorable2','tulip2':'snuggable2'}
+>>> d3 = {'chris3':'cool3','camri3':'cute3','cotton3':'adorable3','tulip3':'snuggable3'}
+>>> d4 = {'twoofme':'secondone'}
+>>> md = MergeDict( d1,d2,d3 )
+>>> md['chris']
+'cool'
+>>> md['camri']
+'cute'
+>>> md['twoofme']
+'firstone'
+>>> md2 = md.copy()
+>>> md2['chris']
+'cool'
+
+### MultiValueDict ##########################################################
+
+>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
+>>> d['name']
+'Simon'
+>>> d.getlist('name')
+['Adrian', 'Simon']
+>>> d.get('lastname', 'nonexistent')
+'nonexistent'
+>>> d.setlist('lastname', ['Holovaty', 'Willison'])
+
+### SortedDict #################################################################
+
+>>> d = SortedDict()
+>>> d['one'] = 'one'
+>>> d['two'] = 'two'
+>>> d['three'] = 'three'
+>>> d['one']
+'one'
+>>> d['two']
+'two'
+>>> d['three']
+'three'
+>>> d.keys()
+['one', 'two', 'three']
+>>> d.values()
+['one', 'two', 'three']
+>>> d['one'] = 'not one'
+>>> d['one']
+'not one'
+>>> d.keys() == d.copy().keys()
+True
+>>> print repr(d)
+{'one': 'not one', 'two': 'two', 'three': 'three'}
+
+### DotExpandedDict ############################################################
+
+>>> d = DotExpandedDict({'person.1.firstname': ['Simon'], 'person.1.lastname': ['Willison'], 'person.2.firstname': ['Adrian'], 'person.2.lastname': ['Holovaty']})
+>>> d['person']['1']['lastname']
+['Willison']
+>>> d['person']['2']['lastname']
+['Holovaty']
+>>> d['person']['2']['firstname']
+['Adrian']
+"""
diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py
index 6e28759a92..f9f84145c5 100644
--- a/tests/regressiontests/dateformat/tests.py
+++ b/tests/regressiontests/dateformat/tests.py
@@ -17,6 +17,8 @@ r"""
'07'
>>> format(my_birthday, 'M')
'Jul'
+>>> format(my_birthday, 'b')
+'jul'
>>> format(my_birthday, 'n')
'7'
>>> format(my_birthday, 'N')
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index 32d6ef5202..4a2e9432b0 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -1,3 +1,5 @@
+# -*- coding: utf-8 -*-
+
r"""
>>> floatformat(7.7)
'7.7'
@@ -11,6 +13,26 @@ r"""
'0.0'
>>> floatformat(0.0)
'0'
+>>> floatformat(7.7,3)
+'7.700'
+>>> floatformat(6.000000,3)
+'6.000'
+>>> floatformat(13.1031,-3)
+'13.103'
+>>> floatformat(11.1197, -2)
+'11.12'
+>>> floatformat(11.0000, -2)
+'11'
+>>> floatformat(11.000001, -2)
+'11.00'
+>>> floatformat(8.2798, 3)
+'8.280'
+>>> floatformat('foo')
+''
+>>> floatformat(13.1031, 'bar')
+'13.1031'
+>>> floatformat('foo', 'bar')
+''
>>> addslashes('"double quotes" and \'single quotes\'')
'\\"double quotes\\" and \\\'single quotes\\\''
@@ -67,6 +89,20 @@ u'\xeb'
>>> truncatewords('A sentence with a few words in it', 'not a number')
'A sentence with a few words in it'
+>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 0)
+''
+
+>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 2)
+'<p>one <a href="#">two ...</a></p>'
+
+>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 4)
+'<p>one <a href="#">two - three <br>four ...</a></p>'
+
+>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 5)
+'<p>one <a href="#">two - three <br>four</a> five</p>'
+
+>>> truncatewords_html('<p>one <a href="#">two - three <br>four</a> five</p>', 100)
+'<p>one <a href="#">two - three <br>four</a> five</p>'
>>> upper('Mixed case input')
'MIXED CASE INPUT'
@@ -77,6 +113,8 @@ u'\xcb'
>>> urlencode('jack & jill')
'jack%20%26%20jill'
+>>> urlencode(1)
+'1'
>>> urlizetrunc('http://short.com/', 20)
@@ -97,6 +135,12 @@ u'\xcb'
>>> wordwrap('this is a long paragraph of text that really needs to be wrapped I\'m afraid', 14)
"this is a long\nparagraph of\ntext that\nreally needs\nto be wrapped\nI'm afraid"
+>>> wordwrap('this is a short paragraph of text.\n But this line should be indented',14)
+'this is a\nshort\nparagraph of\ntext.\n But this\nline should be\nindented'
+
+>>> wordwrap('this is a short paragraph of text.\n But this line should be indented',15)
+'this is a short\nparagraph of\ntext.\n But this line\nshould be\nindented'
+
>>> ljust('test', 10)
'test '
@@ -124,6 +168,9 @@ u'\xcb'
>>> escape('<some html & special characters > here')
'&lt;some html &amp; special characters &gt; here'
+>>> escape(u'<some html & special characters > here ĐÅ€£')
+u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
+
>>> linebreaks('line 1')
'<p>line 1</p>'
@@ -352,7 +399,53 @@ False
>>> phone2numeric('0800 flowers')
'0800 3569377'
-
+# Filters shouldn't break if passed non-strings
+>>> addslashes(123)
+'123'
+>>> linenumbers(123)
+'1. 123'
+>>> lower(123)
+'123'
+>>> make_list(123)
+['1', '2', '3']
+>>> slugify(123)
+'123'
+>>> title(123)
+'123'
+>>> truncatewords(123, 2)
+'123'
+>>> upper(123)
+'123'
+>>> urlencode(123)
+'123'
+>>> urlize(123)
+'123'
+>>> urlizetrunc(123, 1)
+'123'
+>>> wordcount(123)
+1
+>>> wordwrap(123, 2)
+'123'
+>>> ljust('123', 4)
+'123 '
+>>> rjust('123', 4)
+' 123'
+>>> center('123', 5)
+' 123 '
+>>> center('123', 6)
+' 123 '
+>>> cut(123, '2')
+'13'
+>>> escape(123)
+'123'
+>>> linebreaks(123)
+'<p>123</p>'
+>>> linebreaksbr(123)
+'123'
+>>> removetags(123, 'a')
+'123'
+>>> striptags(123)
+'123'
"""
diff --git a/tests/regressiontests/dispatch/__init__.py b/tests/regressiontests/dispatch/__init__.py
new file mode 100644
index 0000000000..679895bb5c
--- /dev/null
+++ b/tests/regressiontests/dispatch/__init__.py
@@ -0,0 +1,2 @@
+"""Unit-tests for the dispatch project
+"""
diff --git a/tests/regressiontests/dispatch/models.py b/tests/regressiontests/dispatch/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/dispatch/models.py
diff --git a/tests/regressiontests/dispatch/tests/__init__.py b/tests/regressiontests/dispatch/tests/__init__.py
new file mode 100644
index 0000000000..0fdefe48a7
--- /dev/null
+++ b/tests/regressiontests/dispatch/tests/__init__.py
@@ -0,0 +1,7 @@
+"""
+Unit-tests for the dispatch project
+"""
+
+from test_dispatcher import *
+from test_robustapply import *
+from test_saferef import *
diff --git a/tests/regressiontests/dispatch/tests/test_dispatcher.py b/tests/regressiontests/dispatch/tests/test_dispatcher.py
new file mode 100644
index 0000000000..0bc99a1505
--- /dev/null
+++ b/tests/regressiontests/dispatch/tests/test_dispatcher.py
@@ -0,0 +1,144 @@
+from django.dispatch.dispatcher import *
+from django.dispatch import dispatcher, robust
+import unittest
+import copy
+
+def x(a):
+ return a
+
+class Dummy(object):
+ pass
+
+class Callable(object):
+ def __call__(self, a):
+ return a
+
+ def a(self, a):
+ return a
+
+class DispatcherTests(unittest.TestCase):
+ """Test suite for dispatcher (barely started)"""
+
+ def setUp(self):
+ # track the initial state, since it's possible that others have bleed receivers in
+ self.sendersBack = copy.copy(dispatcher.sendersBack)
+ self.connections = copy.copy(dispatcher.connections)
+ self.senders = copy.copy(dispatcher.senders)
+
+ def _testIsClean(self):
+ """Assert that everything has been cleaned up automatically"""
+ self.assertEqual(dispatcher.sendersBack, self.sendersBack)
+ self.assertEqual(dispatcher.connections, self.connections)
+ self.assertEqual(dispatcher.senders, self.senders)
+
+ def testExact(self):
+ a = Dummy()
+ signal = 'this'
+ connect(x, signal, a)
+ expected = [(x,a)]
+ result = send('this',a, a=a)
+ self.assertEqual(result, expected)
+ disconnect(x, signal, a)
+ self.assertEqual(list(getAllReceivers(a,signal)), [])
+ self._testIsClean()
+
+ def testAnonymousSend(self):
+ a = Dummy()
+ signal = 'this'
+ connect(x, signal)
+ expected = [(x,a)]
+ result = send(signal,None, a=a)
+ self.assertEqual(result, expected)
+ disconnect(x, signal)
+ self.assertEqual(list(getAllReceivers(None,signal)), [])
+ self._testIsClean()
+
+ def testAnyRegistration(self):
+ a = Dummy()
+ signal = 'this'
+ connect(x, signal, Any)
+ expected = [(x,a)]
+ result = send('this',object(), a=a)
+ self.assertEqual(result, expected)
+ disconnect(x, signal, Any)
+ expected = []
+ result = send('this',object(), a=a)
+ self.assertEqual(result, expected)
+ self.assertEqual(list(getAllReceivers(Any,signal)), [])
+
+ self._testIsClean()
+
+ def testAnyRegistration2(self):
+ a = Dummy()
+ signal = 'this'
+ connect(x, Any, a)
+ expected = [(x,a)]
+ result = send('this',a, a=a)
+ self.assertEqual(result, expected)
+ disconnect(x, Any, a)
+ self.assertEqual(list(getAllReceivers(a,Any)), [])
+ self._testIsClean()
+
+ def testGarbageCollected(self):
+ a = Callable()
+ b = Dummy()
+ signal = 'this'
+ connect(a.a, signal, b)
+ expected = []
+ del a
+ result = send('this',b, a=b)
+ self.assertEqual(result, expected)
+ self.assertEqual(list(getAllReceivers(b,signal)), [])
+ self._testIsClean()
+
+ def testGarbageCollectedObj(self):
+ class x:
+ def __call__(self, a):
+ return a
+ a = Callable()
+ b = Dummy()
+ signal = 'this'
+ connect(a, signal, b)
+ expected = []
+ del a
+ result = send('this',b, a=b)
+ self.assertEqual(result, expected)
+ self.assertEqual(list(getAllReceivers(b,signal)), [])
+ self._testIsClean()
+
+
+ def testMultipleRegistration(self):
+ a = Callable()
+ b = Dummy()
+ signal = 'this'
+ connect(a, signal, b)
+ connect(a, signal, b)
+ connect(a, signal, b)
+ connect(a, signal, b)
+ connect(a, signal, b)
+ connect(a, signal, b)
+ result = send('this',b, a=b)
+ self.assertEqual(len(result), 1)
+ self.assertEqual(len(list(getAllReceivers(b,signal))), 1)
+ del a
+ del b
+ del result
+ self._testIsClean()
+
+ def testRobust(self):
+ """Test the sendRobust function"""
+ def fails():
+ raise ValueError('this')
+ a = object()
+ signal = 'this'
+ connect(fails, Any, a)
+ result = robust.sendRobust('this',a, a=a)
+ err = result[0][1]
+ self.assert_(isinstance(err, ValueError))
+ self.assertEqual(err.args, ('this',))
+
+def getSuite():
+ return unittest.makeSuite(DispatcherTests,'test')
+
+if __name__ == "__main__":
+ unittest.main ()
diff --git a/tests/regressiontests/dispatch/tests/test_robustapply.py b/tests/regressiontests/dispatch/tests/test_robustapply.py
new file mode 100644
index 0000000000..499450eec4
--- /dev/null
+++ b/tests/regressiontests/dispatch/tests/test_robustapply.py
@@ -0,0 +1,34 @@
+from django.dispatch.robustapply import *
+
+import unittest
+
+def noArgument():
+ pass
+
+def oneArgument(blah):
+ pass
+
+def twoArgument(blah, other):
+ pass
+
+class TestCases(unittest.TestCase):
+ def test01(self):
+ robustApply(noArgument)
+
+ def test02(self):
+ self.assertRaises(TypeError, robustApply, noArgument, "this")
+
+ def test03(self):
+ self.assertRaises(TypeError, robustApply, oneArgument)
+
+ def test04(self):
+ """Raise error on duplication of a particular argument"""
+ self.assertRaises(TypeError, robustApply, oneArgument, "this", blah = "that")
+
+def getSuite():
+ return unittest.makeSuite(TestCases,'test')
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/regressiontests/dispatch/tests/test_saferef.py
new file mode 100644
index 0000000000..c0ec879a4b
--- /dev/null
+++ b/tests/regressiontests/dispatch/tests/test_saferef.py
@@ -0,0 +1,79 @@
+from django.dispatch.saferef import *
+
+import unittest
+
+class Test1(object):
+ def x(self):
+ pass
+
+def test2(obj):
+ pass
+
+class Test2(object):
+ def __call__(self, obj):
+ pass
+
+class Tester(unittest.TestCase):
+ def setUp(self):
+ ts = []
+ ss = []
+ for x in xrange(5000):
+ t = Test1()
+ ts.append(t)
+ s = safeRef(t.x, self._closure)
+ ss.append(s)
+ ts.append(test2)
+ ss.append(safeRef(test2, self._closure))
+ for x in xrange(30):
+ t = Test2()
+ ts.append(t)
+ s = safeRef(t, self._closure)
+ ss.append(s)
+ self.ts = ts
+ self.ss = ss
+ self.closureCount = 0
+
+ def tearDown(self):
+ del self.ts
+ del self.ss
+
+ def testIn(self):
+ """Test the "in" operator for safe references (cmp)"""
+ for t in self.ts[:50]:
+ self.assert_(safeRef(t.x) in self.ss)
+
+ def testValid(self):
+ """Test that the references are valid (return instance methods)"""
+ for s in self.ss:
+ self.assert_(s())
+
+ def testShortCircuit (self):
+ """Test that creation short-circuits to reuse existing references"""
+ sd = {}
+ for s in self.ss:
+ sd[s] = 1
+ for t in self.ts:
+ if hasattr(t, 'x'):
+ self.assert_(sd.has_key(safeRef(t.x)))
+ self.assert_(safeRef(t.x) in sd)
+ else:
+ self.assert_(sd.has_key(safeRef(t)))
+ self.assert_(safeRef(t) in sd)
+
+ def testRepresentation (self):
+ """Test that the reference object's representation works
+
+ XXX Doesn't currently check the results, just that no error
+ is raised
+ """
+ repr(self.ss[-1])
+
+ def _closure(self, ref):
+ """Dumb utility mechanism to increment deletion counter"""
+ self.closureCount +=1
+
+def getSuite():
+ return unittest.makeSuite(Tester,'test')
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/regressiontests/fixtures_regress/__init__.py b/tests/regressiontests/fixtures_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/__init__.py
diff --git a/tests/regressiontests/fixtures_regress/fixtures/sequence.json b/tests/regressiontests/fixtures_regress/fixtures/sequence.json
new file mode 100644
index 0000000000..ecaf637b9f
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/fixtures/sequence.json
@@ -0,0 +1,10 @@
+[
+ {
+ "pk": "1",
+ "model": "fixtures_regress.animal",
+ "fields": {
+ "name": "Lion",
+ "latin_name": "Panthera leo"
+ }
+ }
+] \ No newline at end of file
diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py
new file mode 100644
index 0000000000..dd407df353
--- /dev/null
+++ b/tests/regressiontests/fixtures_regress/models.py
@@ -0,0 +1,29 @@
+from django.db import models
+
+class Animal(models.Model):
+ name = models.CharField(maxlength=150)
+ latin_name = models.CharField(maxlength=150)
+
+ def __str__(self):
+ return self.common_name
+
+class Plant(models.Model):
+ name = models.CharField(maxlength=150)
+
+ class Meta:
+ # For testing when upper case letter in app name; regression for #4057
+ db_table = "Fixtures_regress_plant"
+
+__test__ = {'API_TESTS':"""
+>>> from django.core import management
+
+# Load a fixture that uses PK=1
+>>> management.load_data(['sequence'], verbosity=0)
+
+# Create a new animal. Without a sequence reset, this new object
+# will take a PK of 1 (on Postgres), and the save will fail.
+# This is a regression test for ticket #3790.
+>>> animal = Animal(name='Platypus', latin_name='Ornithorhynchus anatinus')
+>>> animal.save()
+
+"""} \ No newline at end of file
diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py
new file mode 100644
index 0000000000..ede89de2a0
--- /dev/null
+++ b/tests/regressiontests/forms/localflavor.py
@@ -0,0 +1,1359 @@
+# -*- coding: utf-8 -*-
+# Tests for the different contrib/localflavor/ form fields.
+
+localflavor_tests = r"""
+# USZipCodeField ##############################################################
+
+USZipCodeField validates that the data is either a five-digit U.S. zip code or
+a zip+4.
+>>> from django.contrib.localflavor.us.forms import USZipCodeField
+>>> f = USZipCodeField()
+>>> f.clean('60606')
+u'60606'
+>>> f.clean(60606)
+u'60606'
+>>> f.clean('04000')
+u'04000'
+>>> f.clean('4000')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> f.clean('60606-1234')
+u'60606-1234'
+>>> f.clean('6060-1234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> f.clean('60606-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> 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 = USZipCodeField(required=False)
+>>> f.clean('60606')
+u'60606'
+>>> f.clean(60606)
+u'60606'
+>>> f.clean('04000')
+u'04000'
+>>> f.clean('4000')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> f.clean('60606-1234')
+u'60606-1234'
+>>> f.clean('6060-1234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> f.clean('60606-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# USPhoneNumberField ##########################################################
+
+USPhoneNumberField validates that the data is a valid U.S. phone number,
+including the area code. It's normalized to XXX-XXX-XXXX format.
+>>> from django.contrib.localflavor.us.forms import USPhoneNumberField
+>>> f = USPhoneNumberField()
+>>> f.clean('312-555-1212')
+u'312-555-1212'
+>>> f.clean('3125551212')
+u'312-555-1212'
+>>> f.clean('312 555-1212')
+u'312-555-1212'
+>>> f.clean('(312) 555-1212')
+u'312-555-1212'
+>>> f.clean('312 555 1212')
+u'312-555-1212'
+>>> f.clean('312.555.1212')
+u'312-555-1212'
+>>> f.clean('312.555-1212')
+u'312-555-1212'
+>>> f.clean(' (312) 555.1212 ')
+u'312-555-1212'
+>>> f.clean('555-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean('312-55-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> 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 = USPhoneNumberField(required=False)
+>>> f.clean('312-555-1212')
+u'312-555-1212'
+>>> f.clean('3125551212')
+u'312-555-1212'
+>>> f.clean('312 555-1212')
+u'312-555-1212'
+>>> f.clean('(312) 555-1212')
+u'312-555-1212'
+>>> f.clean('312 555 1212')
+u'312-555-1212'
+>>> f.clean('312.555.1212')
+u'312-555-1212'
+>>> f.clean('312.555-1212')
+u'312-555-1212'
+>>> f.clean(' (312) 555.1212 ')
+u'312-555-1212'
+>>> f.clean('555-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean('312-55-1212')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in XXX-XXX-XXXX format.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# USStateField ################################################################
+
+USStateField validates that the data is either an abbreviation or name of a
+U.S. state.
+>>> from django.contrib.localflavor.us.forms import USStateField
+>>> f = USStateField()
+>>> f.clean('il')
+u'IL'
+>>> f.clean('IL')
+u'IL'
+>>> f.clean('illinois')
+u'IL'
+>>> f.clean(' illinois ')
+u'IL'
+>>> f.clean(60606)
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a U.S. state or territory.']
+>>> 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 = USStateField(required=False)
+>>> f.clean('il')
+u'IL'
+>>> f.clean('IL')
+u'IL'
+>>> f.clean('illinois')
+u'IL'
+>>> f.clean(' illinois ')
+u'IL'
+>>> f.clean(60606)
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a U.S. state or territory.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# USStateSelect ###############################################################
+
+USStateSelect is a Select widget that uses a list of U.S. states/territories
+as its choices.
+>>> from django.contrib.localflavor.us.forms import USStateSelect
+>>> w = USStateSelect()
+>>> print w.render('state', 'IL')
+<select name="state">
+<option value="AL">Alabama</option>
+<option value="AK">Alaska</option>
+<option value="AS">American Samoa</option>
+<option value="AZ">Arizona</option>
+<option value="AR">Arkansas</option>
+<option value="CA">California</option>
+<option value="CO">Colorado</option>
+<option value="CT">Connecticut</option>
+<option value="DE">Delaware</option>
+<option value="DC">District of Columbia</option>
+<option value="FM">Federated States of Micronesia</option>
+<option value="FL">Florida</option>
+<option value="GA">Georgia</option>
+<option value="GU">Guam</option>
+<option value="HI">Hawaii</option>
+<option value="ID">Idaho</option>
+<option value="IL" selected="selected">Illinois</option>
+<option value="IN">Indiana</option>
+<option value="IA">Iowa</option>
+<option value="KS">Kansas</option>
+<option value="KY">Kentucky</option>
+<option value="LA">Louisiana</option>
+<option value="ME">Maine</option>
+<option value="MH">Marshall Islands</option>
+<option value="MD">Maryland</option>
+<option value="MA">Massachusetts</option>
+<option value="MI">Michigan</option>
+<option value="MN">Minnesota</option>
+<option value="MS">Mississippi</option>
+<option value="MO">Missouri</option>
+<option value="MT">Montana</option>
+<option value="NE">Nebraska</option>
+<option value="NV">Nevada</option>
+<option value="NH">New Hampshire</option>
+<option value="NJ">New Jersey</option>
+<option value="NM">New Mexico</option>
+<option value="NY">New York</option>
+<option value="NC">North Carolina</option>
+<option value="ND">North Dakota</option>
+<option value="MP">Northern Mariana Islands</option>
+<option value="OH">Ohio</option>
+<option value="OK">Oklahoma</option>
+<option value="OR">Oregon</option>
+<option value="PW">Palau</option>
+<option value="PA">Pennsylvania</option>
+<option value="PR">Puerto Rico</option>
+<option value="RI">Rhode Island</option>
+<option value="SC">South Carolina</option>
+<option value="SD">South Dakota</option>
+<option value="TN">Tennessee</option>
+<option value="TX">Texas</option>
+<option value="UT">Utah</option>
+<option value="VT">Vermont</option>
+<option value="VI">Virgin Islands</option>
+<option value="VA">Virginia</option>
+<option value="WA">Washington</option>
+<option value="WV">West Virginia</option>
+<option value="WI">Wisconsin</option>
+<option value="WY">Wyoming</option>
+</select>
+
+# USSocialSecurityNumberField #################################################
+>>> from django.contrib.localflavor.us.forms import USSocialSecurityNumberField
+>>> f = USSocialSecurityNumberField()
+>>> f.clean('987-65-4330')
+u'987-65-4330'
+>>> f.clean('987654330')
+u'987-65-4330'
+>>> f.clean('078-05-1120')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid U.S. Social Security number in XXX-XX-XXXX format.']
+
+# UKPostcodeField #############################################################
+
+UKPostcodeField validates that the data is a valid UK postcode.
+>>> from django.contrib.localflavor.uk.forms import UKPostcodeField
+>>> f = UKPostcodeField()
+>>> f.clean('BT32 4PX')
+u'BT32 4PX'
+>>> f.clean('GIR 0AA')
+u'GIR 0AA'
+>>> f.clean('BT324PX')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
+>>> f.clean('1NV 4L1D')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
+>>> 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 = UKPostcodeField(required=False)
+>>> f.clean('BT32 4PX')
+u'BT32 4PX'
+>>> f.clean('GIR 0AA')
+u'GIR 0AA'
+>>> f.clean('1NV 4L1D')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
+>>> f.clean('BT324PX')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# FRZipCodeField #############################################################
+
+FRZipCodeField validates that the data is a valid FR zipcode.
+>>> from django.contrib.localflavor.fr.forms import FRZipCodeField
+>>> f = FRZipCodeField()
+>>> f.clean('75001')
+u'75001'
+>>> f.clean('93200')
+u'93200'
+>>> f.clean('2A200')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean('980001')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> 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 = FRZipCodeField(required=False)
+>>> f.clean('75001')
+u'75001'
+>>> f.clean('93200')
+u'93200'
+>>> f.clean('2A200')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean('980001')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+
+# FRPhoneNumberField ##########################################################
+
+FRPhoneNumberField validates that the data is a valid french phone number.
+It's normalized to 0X XX XX XX XX format. Dots are valid too.
+>>> from django.contrib.localflavor.fr.forms import FRPhoneNumberField
+>>> f = FRPhoneNumberField()
+>>> f.clean('01 55 44 58 64')
+u'01 55 44 58 64'
+>>> f.clean('0155445864')
+u'01 55 44 58 64'
+>>> f.clean('01 5544 5864')
+u'01 55 44 58 64'
+>>> f.clean('01 55.44.58.64')
+u'01 55 44 58 64'
+>>> f.clean('01.55.44.58.64')
+u'01 55 44 58 64'
+>>> f.clean('01,55,44,58,64')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.']
+>>> f.clean('555 015 544')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.']
+>>> 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 = FRPhoneNumberField(required=False)
+>>> f.clean('01 55 44 58 64')
+u'01 55 44 58 64'
+>>> f.clean('0155445864')
+u'01 55 44 58 64'
+>>> f.clean('01 5544 5864')
+u'01 55 44 58 64'
+>>> f.clean('01 55.44.58.64')
+u'01 55 44 58 64'
+>>> f.clean('01.55.44.58.64')
+u'01 55 44 58 64'
+>>> f.clean('01,55,44,58,64')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.']
+>>> f.clean('555 015 544')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0X XX XX XX XX format.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# FRDepartmentSelect ###############################################################
+
+FRDepartmentSelect is a Select widget that uses a list of french departments
+including DOM TOM
+>>> from django.contrib.localflavor.fr.forms import FRDepartmentSelect
+>>> w = FRDepartmentSelect()
+>>> print w.render('dep', 'Paris')
+<select name="dep">
+<option value="01">01 - Ain</option>
+<option value="02">02 - Aisne</option>
+<option value="03">03 - Allier</option>
+<option value="04">04 - Alpes-de-Haute-Provence</option>
+<option value="05">05 - Hautes-Alpes</option>
+<option value="06">06 - Alpes-Maritimes</option>
+<option value="07">07 - Ardeche</option>
+<option value="08">08 - Ardennes</option>
+<option value="09">09 - Ariege</option>
+<option value="10">10 - Aube</option>
+<option value="11">11 - Aude</option>
+<option value="12">12 - Aveyron</option>
+<option value="13">13 - Bouches-du-Rhone</option>
+<option value="14">14 - Calvados</option>
+<option value="15">15 - Cantal</option>
+<option value="16">16 - Charente</option>
+<option value="17">17 - Charente-Maritime</option>
+<option value="18">18 - Cher</option>
+<option value="19">19 - Correze</option>
+<option value="21">21 - Cote-d&#39;Or</option>
+<option value="22">22 - Cotes-d&#39;Armor</option>
+<option value="23">23 - Creuse</option>
+<option value="24">24 - Dordogne</option>
+<option value="25">25 - Doubs</option>
+<option value="26">26 - Drome</option>
+<option value="27">27 - Eure</option>
+<option value="28">28 - Eure-et-Loire</option>
+<option value="29">29 - Finistere</option>
+<option value="2A">2A - Corse-du-Sud</option>
+<option value="2B">2B - Haute-Corse</option>
+<option value="30">30 - Gard</option>
+<option value="31">31 - Haute-Garonne</option>
+<option value="32">32 - Gers</option>
+<option value="33">33 - Gironde</option>
+<option value="34">34 - Herault</option>
+<option value="35">35 - Ille-et-Vilaine</option>
+<option value="36">36 - Indre</option>
+<option value="37">37 - Indre-et-Loire</option>
+<option value="38">38 - Isere</option>
+<option value="39">39 - Jura</option>
+<option value="40">40 - Landes</option>
+<option value="41">41 - Loir-et-Cher</option>
+<option value="42">42 - Loire</option>
+<option value="43">43 - Haute-Loire</option>
+<option value="44">44 - Loire-Atlantique</option>
+<option value="45">45 - Loiret</option>
+<option value="46">46 - Lot</option>
+<option value="47">47 - Lot-et-Garonne</option>
+<option value="48">48 - Lozere</option>
+<option value="49">49 - Maine-et-Loire</option>
+<option value="50">50 - Manche</option>
+<option value="51">51 - Marne</option>
+<option value="52">52 - Haute-Marne</option>
+<option value="53">53 - Mayenne</option>
+<option value="54">54 - Meurthe-et-Moselle</option>
+<option value="55">55 - Meuse</option>
+<option value="56">56 - Morbihan</option>
+<option value="57">57 - Moselle</option>
+<option value="58">58 - Nievre</option>
+<option value="59">59 - Nord</option>
+<option value="60">60 - Oise</option>
+<option value="61">61 - Orne</option>
+<option value="62">62 - Pas-de-Calais</option>
+<option value="63">63 - Puy-de-Dome</option>
+<option value="64">64 - Pyrenees-Atlantiques</option>
+<option value="65">65 - Hautes-Pyrenees</option>
+<option value="66">66 - Pyrenees-Orientales</option>
+<option value="67">67 - Bas-Rhin</option>
+<option value="68">68 - Haut-Rhin</option>
+<option value="69">69 - Rhone</option>
+<option value="70">70 - Haute-Saone</option>
+<option value="71">71 - Saone-et-Loire</option>
+<option value="72">72 - Sarthe</option>
+<option value="73">73 - Savoie</option>
+<option value="74">74 - Haute-Savoie</option>
+<option value="75">75 - Paris</option>
+<option value="76">76 - Seine-Maritime</option>
+<option value="77">77 - Seine-et-Marne</option>
+<option value="78">78 - Yvelines</option>
+<option value="79">79 - Deux-Sevres</option>
+<option value="80">80 - Somme</option>
+<option value="81">81 - Tarn</option>
+<option value="82">82 - Tarn-et-Garonne</option>
+<option value="83">83 - Var</option>
+<option value="84">84 - Vaucluse</option>
+<option value="85">85 - Vendee</option>
+<option value="86">86 - Vienne</option>
+<option value="87">87 - Haute-Vienne</option>
+<option value="88">88 - Vosges</option>
+<option value="89">89 - Yonne</option>
+<option value="90">90 - Territoire de Belfort</option>
+<option value="91">91 - Essonne</option>
+<option value="92">92 - Hauts-de-Seine</option>
+<option value="93">93 - Seine-Saint-Denis</option>
+<option value="94">94 - Val-de-Marne</option>
+<option value="95">95 - Val-d&#39;Oise</option>
+<option value="2A">2A - Corse du sud</option>
+<option value="2B">2B - Haute Corse</option>
+<option value="971">971 - Guadeloupe</option>
+<option value="972">972 - Martinique</option>
+<option value="973">973 - Guyane</option>
+<option value="974">974 - La Reunion</option>
+<option value="975">975 - Saint-Pierre-et-Miquelon</option>
+<option value="976">976 - Mayotte</option>
+<option value="984">984 - Terres Australes et Antarctiques</option>
+<option value="986">986 - Wallis et Futuna</option>
+<option value="987">987 - Polynesie Francaise</option>
+<option value="988">988 - Nouvelle-Caledonie</option>
+</select>
+
+# JPPostalCodeField ###############################################################
+
+A form field that validates its input is a Japanese postcode.
+
+Accepts 7 digits(with/out hyphen).
+>>> from django.contrib.localflavor.jp.forms import JPPostalCodeField
+>>> f = JPPostalCodeField()
+>>> f.clean('251-0032')
+u'2510032'
+>>> f.clean('2510032')
+u'2510032'
+>>> f.clean('2510-032')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+>>> f.clean('251a0032')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+>>> f.clean('a51-0032')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+>>> f.clean('25100321')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = JPPostalCodeField(required=False)
+>>> f.clean('251-0032')
+u'2510032'
+>>> f.clean('2510032')
+u'2510032'
+>>> f.clean('2510-032')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a postal code in the format XXXXXXX or XXX-XXXX.']
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+
+# JPPrefectureSelect ###############################################################
+
+A Select widget that uses a list of Japanese prefectures as its choices.
+>>> from django.contrib.localflavor.jp.forms import JPPrefectureSelect
+>>> w = JPPrefectureSelect()
+>>> print w.render('prefecture', 'kanagawa')
+<select name="prefecture">
+<option value="hokkaido">Hokkaido</option>
+<option value="aomori">Aomori</option>
+<option value="iwate">Iwate</option>
+<option value="miyagi">Miyagi</option>
+<option value="akita">Akita</option>
+<option value="yamagata">Yamagata</option>
+<option value="fukushima">Fukushima</option>
+<option value="ibaraki">Ibaraki</option>
+<option value="tochigi">Tochigi</option>
+<option value="gunma">Gunma</option>
+<option value="saitama">Saitama</option>
+<option value="chiba">Chiba</option>
+<option value="tokyo">Tokyo</option>
+<option value="kanagawa" selected="selected">Kanagawa</option>
+<option value="yamanashi">Yamanashi</option>
+<option value="nagano">Nagano</option>
+<option value="niigata">Niigata</option>
+<option value="toyama">Toyama</option>
+<option value="ishikawa">Ishikawa</option>
+<option value="fukui">Fukui</option>
+<option value="gifu">Gifu</option>
+<option value="shizuoka">Shizuoka</option>
+<option value="aichi">Aichi</option>
+<option value="mie">Mie</option>
+<option value="shiga">Shiga</option>
+<option value="kyoto">Kyoto</option>
+<option value="osaka">Osaka</option>
+<option value="hyogo">Hyogo</option>
+<option value="nara">Nara</option>
+<option value="wakayama">Wakayama</option>
+<option value="tottori">Tottori</option>
+<option value="shimane">Shimane</option>
+<option value="okayama">Okayama</option>
+<option value="hiroshima">Hiroshima</option>
+<option value="yamaguchi">Yamaguchi</option>
+<option value="tokushima">Tokushima</option>
+<option value="kagawa">Kagawa</option>
+<option value="ehime">Ehime</option>
+<option value="kochi">Kochi</option>
+<option value="fukuoka">Fukuoka</option>
+<option value="saga">Saga</option>
+<option value="nagasaki">Nagasaki</option>
+<option value="kumamoto">Kumamoto</option>
+<option value="oita">Oita</option>
+<option value="miyazaki">Miyazaki</option>
+<option value="kagoshima">Kagoshima</option>
+<option value="okinawa">Okinawa</option>
+</select>
+
+# ITZipCodeField #############################################################
+
+>>> from django.contrib.localflavor.it.forms import ITZipCodeField
+>>> f = ITZipCodeField()
+>>> f.clean('00100')
+u'00100'
+>>> f.clean(' 00100')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid zip code.']
+
+# ITRegionSelect #############################################################
+
+>>> from django.contrib.localflavor.it.forms import ITRegionSelect
+>>> w = ITRegionSelect()
+>>> w.render('regions', 'PMN')
+u'<select name="regions">\n<option value="ABR">Abruzzo</option>\n<option value="BAS">Basilicata</option>\n<option value="CAL">Calabria</option>\n<option value="CAM">Campania</option>\n<option value="EMR">Emilia-Romagna</option>\n<option value="FVG">Friuli-Venezia Giulia</option>\n<option value="LAZ">Lazio</option>\n<option value="LIG">Liguria</option>\n<option value="LOM">Lombardia</option>\n<option value="MAR">Marche</option>\n<option value="MOL">Molise</option>\n<option value="PMN" selected="selected">Piemonte</option>\n<option value="PUG">Puglia</option>\n<option value="SAR">Sardegna</option>\n<option value="SIC">Sicilia</option>\n<option value="TOS">Toscana</option>\n<option value="TAA">Trentino-Alto Adige</option>\n<option value="UMB">Umbria</option>\n<option value="VAO">Valle d\u2019Aosta</option>\n<option value="VEN">Veneto</option>\n</select>'
+
+# ITSocialSecurityNumberField #################################################
+
+>>> from django.contrib.localflavor.it.forms import ITSocialSecurityNumberField
+>>> f = ITSocialSecurityNumberField()
+>>> f.clean('LVSGDU99T71H501L')
+u'LVSGDU99T71H501L'
+>>> f.clean('LBRRME11A01L736W')
+u'LBRRME11A01L736W'
+>>> f.clean('lbrrme11a01l736w')
+u'LBRRME11A01L736W'
+>>> f.clean('LBR RME 11A01 L736W')
+u'LBRRME11A01L736W'
+>>> f.clean('LBRRME11A01L736A')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Social Security number.']
+>>> f.clean('%BRRME11A01L736W')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Social Security number.']
+
+# ITVatNumberField ###########################################################
+
+>>> from django.contrib.localflavor.it.forms import ITVatNumberField
+>>> f = ITVatNumberField()
+>>> f.clean('07973780013')
+u'07973780013'
+>>> f.clean('7973780013')
+u'07973780013'
+>>> f.clean(7973780013)
+u'07973780013'
+>>> f.clean('07973780014')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid VAT number.']
+>>> f.clean('A7973780013')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid VAT number.']
+
+# FIZipCodeField #############################################################
+
+FIZipCodeField validates that the data is a valid FI zipcode.
+>>> from django.contrib.localflavor.fi.forms import FIZipCodeField
+>>> f = FIZipCodeField()
+>>> f.clean('20540')
+u'20540'
+>>> f.clean('20101')
+u'20101'
+>>> f.clean('20s40')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean('205401')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> 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 = FIZipCodeField(required=False)
+>>> f.clean('20540')
+u'20540'
+>>> f.clean('20101')
+u'20101'
+>>> f.clean('20s40')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean('205401')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# FIMunicipalitySelect ###############################################################
+
+A Select widget that uses a list of Finnish municipalities as its choices.
+>>> from django.contrib.localflavor.fi.forms import FIMunicipalitySelect
+>>> w = FIMunicipalitySelect()
+>>> unicode(w.render('municipalities', 'turku'))
+u'<select name="municipalities">\n<option value="akaa">Akaa</option>\n<option value="alaharma">Alah\xe4rm\xe4</option>\n<option value="alajarvi">Alaj\xe4rvi</option>\n<option value="alastaro">Alastaro</option>\n<option value="alavieska">Alavieska</option>\n<option value="alavus">Alavus</option>\n<option value="anjalankoski">Anjalankoski</option>\n<option value="artjarvi">Artj\xe4rvi</option>\n<option value="asikkala">Asikkala</option>\n<option value="askainen">Askainen</option>\n<option value="askola">Askola</option>\n<option value="aura">Aura</option>\n<option value="brando">Br\xe4nd\xf6</option>\n<option value="dragsfjard">Dragsfj\xe4rd</option>\n<option value="eckero">Ecker\xf6</option>\n<option value="elimaki">Elim\xe4ki</option>\n<option value="eno">Eno</option>\n<option value="enonkoski">Enonkoski</option>\n<option value="enontekio">Enonteki\xf6</option>\n<option value="espoo">Espoo</option>\n<option value="eura">Eura</option>\n<option value="eurajoki">Eurajoki</option>\n<option value="evijarvi">Evij\xe4rvi</option>\n<option value="finstrom">Finstr\xf6m</option>\n<option value="forssa">Forssa</option>\n<option value="foglo">F\xf6gl\xf6</option>\n<option value="geta">Geta</option>\n<option value="haapajarvi">Haapaj\xe4rvi</option>\n<option value="haapavesi">Haapavesi</option>\n<option value="hailuoto">Hailuoto</option>\n<option value="halikko">Halikko</option>\n<option value="halsua">Halsua</option>\n<option value="hamina">Hamina</option>\n<option value="hammarland">Hammarland</option>\n<option value="hankasalmi">Hankasalmi</option>\n<option value="hanko">Hanko</option>\n<option value="harjavalta">Harjavalta</option>\n<option value="hartola">Hartola</option>\n<option value="hattula">Hattula</option>\n<option value="hauho">Hauho</option>\n<option value="haukipudas">Haukipudas</option>\n<option value="hausjarvi">Hausj\xe4rvi</option>\n<option value="heinola">Heinola</option>\n<option value="heinavesi">Hein\xe4vesi</option>\n<option value="helsinki">Helsinki</option>\n<option value="himanka">Himanka</option>\n<option value="hirvensalmi">Hirvensalmi</option>\n<option value="hollola">Hollola</option>\n<option value="honkajoki">Honkajoki</option>\n<option value="houtskari">Houtskari</option>\n<option value="huittinen">Huittinen</option>\n<option value="humppila">Humppila</option>\n<option value="hyrynsalmi">Hyrynsalmi</option>\n<option value="hyvinkaa">Hyvink\xe4\xe4</option>\n<option value="hameenkoski">H\xe4meenkoski</option>\n<option value="hameenkyro">H\xe4meenkyr\xf6</option>\n<option value="hameenlinna">H\xe4meenlinna</option>\n<option value="ii">Ii</option>\n<option value="iisalmi">Iisalmi</option>\n<option value="iitti">Iitti</option>\n<option value="ikaalinen">Ikaalinen</option>\n<option value="ilmajoki">Ilmajoki</option>\n<option value="ilomantsi">Ilomantsi</option>\n<option value="imatra">Imatra</option>\n<option value="inari">Inari</option>\n<option value="inio">Ini\xf6</option>\n<option value="inkoo">Inkoo</option>\n<option value="isojoki">Isojoki</option>\n<option value="isokyro">Isokyr\xf6</option>\n<option value="jaala">Jaala</option>\n<option value="jalasjarvi">Jalasj\xe4rvi</option>\n<option value="janakkala">Janakkala</option>\n<option value="joensuu">Joensuu</option>\n<option value="jokioinen">Jokioinen</option>\n<option value="jomala">Jomala</option>\n<option value="joroinen">Joroinen</option>\n<option value="joutsa">Joutsa</option>\n<option value="joutseno">Joutseno</option>\n<option value="juankoski">Juankoski</option>\n<option value="jurva">Jurva</option>\n<option value="juuka">Juuka</option>\n<option value="juupajoki">Juupajoki</option>\n<option value="juva">Juva</option>\n<option value="jyvaskyla">Jyv\xe4skyl\xe4</option>\n<option value="jyvaskylan_mlk">Jyv\xe4skyl\xe4n maalaiskunta</option>\n<option value="jamijarvi">J\xe4mij\xe4rvi</option>\n<option value="jamsa">J\xe4ms\xe4</option>\n<option value="jamsankoski">J\xe4ms\xe4nkoski</option>\n<option value="jarvenpaa">J\xe4rvenp\xe4\xe4</option>\n<option value="kaarina">Kaarina</option>\n<option value="kaavi">Kaavi</option>\n<option value="kajaani">Kajaani</option>\n<option value="kalajoki">Kalajoki</option>\n<option value="kalvola">Kalvola</option>\n<option value="kangasala">Kangasala</option>\n<option value="kangasniemi">Kangasniemi</option>\n<option value="kankaanpaa">Kankaanp\xe4\xe4</option>\n<option value="kannonkoski">Kannonkoski</option>\n<option value="kannus">Kannus</option>\n<option value="karijoki">Karijoki</option>\n<option value="karjaa">Karjaa</option>\n<option value="karjalohja">Karjalohja</option>\n<option value="karkkila">Karkkila</option>\n<option value="karstula">Karstula</option>\n<option value="karttula">Karttula</option>\n<option value="karvia">Karvia</option>\n<option value="kaskinen">Kaskinen</option>\n<option value="kauhajoki">Kauhajoki</option>\n<option value="kauhava">Kauhava</option>\n<option value="kauniainen">Kauniainen</option>\n<option value="kaustinen">Kaustinen</option>\n<option value="keitele">Keitele</option>\n<option value="kemi">Kemi</option>\n<option value="kemijarvi">Kemij\xe4rvi</option>\n<option value="keminmaa">Keminmaa</option>\n<option value="kemio">Kemi\xf6</option>\n<option value="kempele">Kempele</option>\n<option value="kerava">Kerava</option>\n<option value="kerimaki">Kerim\xe4ki</option>\n<option value="kestila">Kestil\xe4</option>\n<option value="kesalahti">Kes\xe4lahti</option>\n<option value="keuruu">Keuruu</option>\n<option value="kihnio">Kihni\xf6</option>\n<option value="kiikala">Kiikala</option>\n<option value="kiikoinen">Kiikoinen</option>\n<option value="kiiminki">Kiiminki</option>\n<option value="kinnula">Kinnula</option>\n<option value="kirkkonummi">Kirkkonummi</option>\n<option value="kisko">Kisko</option>\n<option value="kitee">Kitee</option>\n<option value="kittila">Kittil\xe4</option>\n<option value="kiukainen">Kiukainen</option>\n<option value="kiuruvesi">Kiuruvesi</option>\n<option value="kivijarvi">Kivij\xe4rvi</option>\n<option value="kokemaki">Kokem\xe4ki</option>\n<option value="kokkola">Kokkola</option>\n<option value="kolari">Kolari</option>\n<option value="konnevesi">Konnevesi</option>\n<option value="kontiolahti">Kontiolahti</option>\n<option value="korpilahti">Korpilahti</option>\n<option value="korppoo">Korppoo</option>\n<option value="korsnas">Korsn\xe4s</option>\n<option value="kortesjarvi">Kortesj\xe4rvi</option>\n<option value="koskitl">KoskiTl</option>\n<option value="kotka">Kotka</option>\n<option value="kouvola">Kouvola</option>\n<option value="kristiinankaupunki">Kristiinankaupunki</option>\n<option value="kruunupyy">Kruunupyy</option>\n<option value="kuhmalahti">Kuhmalahti</option>\n<option value="kuhmo">Kuhmo</option>\n<option value="kuhmoinen">Kuhmoinen</option>\n<option value="kumlinge">Kumlinge</option>\n<option value="kuopio">Kuopio</option>\n<option value="kuortane">Kuortane</option>\n<option value="kurikka">Kurikka</option>\n<option value="kuru">Kuru</option>\n<option value="kustavi">Kustavi</option>\n<option value="kuusamo">Kuusamo</option>\n<option value="kuusankoski">Kuusankoski</option>\n<option value="kuusjoki">Kuusjoki</option>\n<option value="kylmakoski">Kylm\xe4koski</option>\n<option value="kyyjarvi">Kyyj\xe4rvi</option>\n<option value="kalvia">K\xe4lvi\xe4</option>\n<option value="karkola">K\xe4rk\xf6l\xe4</option>\n<option value="karsamaki">K\xe4rs\xe4m\xe4ki</option>\n<option value="kokar">K\xf6kar</option>\n<option value="koylio">K\xf6yli\xf6</option>\n<option value="lahti">Lahti</option>\n<option value="laihia">Laihia</option>\n<option value="laitila">Laitila</option>\n<option value="lammi">Lammi</option>\n<option value="lapinjarvi">Lapinj\xe4rvi</option>\n<option value="lapinlahti">Lapinlahti</option>\n<option value="lappajarvi">Lappaj\xe4rvi</option>\n<option value="lappeenranta">Lappeenranta</option>\n<option value="lappi">Lappi</option>\n<option value="lapua">Lapua</option>\n<option value="laukaa">Laukaa</option>\n<option value="lavia">Lavia</option>\n<option value="lehtimaki">Lehtim\xe4ki</option>\n<option value="leivonmaki">Leivonm\xe4ki</option>\n<option value="lemi">Lemi</option>\n<option value="lemland">Lemland</option>\n<option value="lempaala">Lemp\xe4\xe4l\xe4</option>\n<option value="lemu">Lemu</option>\n<option value="leppavirta">Lepp\xe4virta</option>\n<option value="lestijarvi">Lestij\xe4rvi</option>\n<option value="lieksa">Lieksa</option>\n<option value="lieto">Lieto</option>\n<option value="liljendal">Liljendal</option>\n<option value="liminka">Liminka</option>\n<option value="liperi">Liperi</option>\n<option value="lohja">Lohja</option>\n<option value="lohtaja">Lohtaja</option>\n<option value="loimaa">Loimaa</option>\n<option value="loppi">Loppi</option>\n<option value="loviisa">Loviisa</option>\n<option value="luhanka">Luhanka</option>\n<option value="lumijoki">Lumijoki</option>\n<option value="lumparland">Lumparland</option>\n<option value="luoto">Luoto</option>\n<option value="luumaki">Luum\xe4ki</option>\n<option value="luvia">Luvia</option>\n<option value="maalahti">Maalahti</option>\n<option value="maaninka">Maaninka</option>\n<option value="maarianhamina">Maarianhamina</option>\n<option value="marttila">Marttila</option>\n<option value="masku">Masku</option>\n<option value="mellila">Mellil\xe4</option>\n<option value="merijarvi">Merij\xe4rvi</option>\n<option value="merikarvia">Merikarvia</option>\n<option value="merimasku">Merimasku</option>\n<option value="miehikkala">Miehikk\xe4l\xe4</option>\n<option value="mikkeli">Mikkeli</option>\n<option value="mouhijarvi">Mouhij\xe4rvi</option>\n<option value="muhos">Muhos</option>\n<option value="multia">Multia</option>\n<option value="muonio">Muonio</option>\n<option value="mustasaari">Mustasaari</option>\n<option value="muurame">Muurame</option>\n<option value="muurla">Muurla</option>\n<option value="mynamaki">Myn\xe4m\xe4ki</option>\n<option value="myrskyla">Myrskyl\xe4</option>\n<option value="mantsala">M\xe4nts\xe4l\xe4</option>\n<option value="mantta">M\xe4ntt\xe4</option>\n<option value="mantyharju">M\xe4ntyharju</option>\n<option value="naantali">Naantali</option>\n<option value="nakkila">Nakkila</option>\n<option value="nastola">Nastola</option>\n<option value="nauvo">Nauvo</option>\n<option value="nilsia">Nilsi\xe4</option>\n<option value="nivala">Nivala</option>\n<option value="nokia">Nokia</option>\n<option value="noormarkku">Noormarkku</option>\n<option value="nousiainen">Nousiainen</option>\n<option value="nummi-pusula">Nummi-Pusula</option>\n<option value="nurmes">Nurmes</option>\n<option value="nurmijarvi">Nurmij\xe4rvi</option>\n<option value="nurmo">Nurmo</option>\n<option value="narpio">N\xe4rpi\xf6</option>\n<option value="oravainen">Oravainen</option>\n<option value="orimattila">Orimattila</option>\n<option value="oripaa">Orip\xe4\xe4</option>\n<option value="orivesi">Orivesi</option>\n<option value="oulainen">Oulainen</option>\n<option value="oulu">Oulu</option>\n<option value="oulunsalo">Oulunsalo</option>\n<option value="outokumpu">Outokumpu</option>\n<option value="padasjoki">Padasjoki</option>\n<option value="paimio">Paimio</option>\n<option value="paltamo">Paltamo</option>\n<option value="parainen">Parainen</option>\n<option value="parikkala">Parikkala</option>\n<option value="parkano">Parkano</option>\n<option value="pedersore">Peders\xf6re</option>\n<option value="pelkosenniemi">Pelkosenniemi</option>\n<option value="pello">Pello</option>\n<option value="perho">Perho</option>\n<option value="pernaja">Pernaja</option>\n<option value="pernio">Perni\xf6</option>\n<option value="pertteli">Pertteli</option>\n<option value="pertunmaa">Pertunmaa</option>\n<option value="petajavesi">Pet\xe4j\xe4vesi</option>\n<option value="pieksamaki">Pieks\xe4m\xe4ki</option>\n<option value="pielavesi">Pielavesi</option>\n<option value="pietarsaari">Pietarsaari</option>\n<option value="pihtipudas">Pihtipudas</option>\n<option value="piikkio">Piikki\xf6</option>\n<option value="piippola">Piippola</option>\n<option value="pirkkala">Pirkkala</option>\n<option value="pohja">Pohja</option>\n<option value="polvijarvi">Polvij\xe4rvi</option>\n<option value="pomarkku">Pomarkku</option>\n<option value="pori">Pori</option>\n<option value="pornainen">Pornainen</option>\n<option value="porvoo">Porvoo</option>\n<option value="posio">Posio</option>\n<option value="pudasjarvi">Pudasj\xe4rvi</option>\n<option value="pukkila">Pukkila</option>\n<option value="pulkkila">Pulkkila</option>\n<option value="punkaharju">Punkaharju</option>\n<option value="punkalaidun">Punkalaidun</option>\n<option value="puolanka">Puolanka</option>\n<option value="puumala">Puumala</option>\n<option value="pyhtaa">Pyht\xe4\xe4</option>\n<option value="pyhajoki">Pyh\xe4joki</option>\n<option value="pyhajarvi">Pyh\xe4j\xe4rvi</option>\n<option value="pyhanta">Pyh\xe4nt\xe4</option>\n<option value="pyharanta">Pyh\xe4ranta</option>\n<option value="pyhaselka">Pyh\xe4selk\xe4</option>\n<option value="pylkonmaki">Pylk\xf6nm\xe4ki</option>\n<option value="palkane">P\xe4lk\xe4ne</option>\n<option value="poytya">P\xf6yty\xe4</option>\n<option value="raahe">Raahe</option>\n<option value="raisio">Raisio</option>\n<option value="rantasalmi">Rantasalmi</option>\n<option value="rantsila">Rantsila</option>\n<option value="ranua">Ranua</option>\n<option value="rauma">Rauma</option>\n<option value="rautalampi">Rautalampi</option>\n<option value="rautavaara">Rautavaara</option>\n<option value="rautjarvi">Rautj\xe4rvi</option>\n<option value="reisjarvi">Reisj\xe4rvi</option>\n<option value="renko">Renko</option>\n<option value="riihimaki">Riihim\xe4ki</option>\n<option value="ristiina">Ristiina</option>\n<option value="ristijarvi">Ristij\xe4rvi</option>\n<option value="rovaniemi">Rovaniemi</option>\n<option value="ruokolahti">Ruokolahti</option>\n<option value="ruotsinpyhtaa">Ruotsinpyht\xe4\xe4</option>\n<option value="ruovesi">Ruovesi</option>\n<option value="rusko">Rusko</option>\n<option value="rymattyla">Rym\xe4ttyl\xe4</option>\n<option value="raakkyla">R\xe4\xe4kkyl\xe4</option>\n<option value="saarijarvi">Saarij\xe4rvi</option>\n<option value="salla">Salla</option>\n<option value="salo">Salo</option>\n<option value="saltvik">Saltvik</option>\n<option value="sammatti">Sammatti</option>\n<option value="sauvo">Sauvo</option>\n<option value="savitaipale">Savitaipale</option>\n<option value="savonlinna">Savonlinna</option>\n<option value="savonranta">Savonranta</option>\n<option value="savukoski">Savukoski</option>\n<option value="seinajoki">Sein\xe4joki</option>\n<option value="sievi">Sievi</option>\n<option value="siikainen">Siikainen</option>\n<option value="siikajoki">Siikajoki</option>\n<option value="siilinjarvi">Siilinj\xe4rvi</option>\n<option value="simo">Simo</option>\n<option value="sipoo">Sipoo</option>\n<option value="siuntio">Siuntio</option>\n<option value="sodankyla">Sodankyl\xe4</option>\n<option value="soini">Soini</option>\n<option value="somero">Somero</option>\n<option value="sonkajarvi">Sonkaj\xe4rvi</option>\n<option value="sotkamo">Sotkamo</option>\n<option value="sottunga">Sottunga</option>\n<option value="sulkava">Sulkava</option>\n<option value="sund">Sund</option>\n<option value="suomenniemi">Suomenniemi</option>\n<option value="suomusjarvi">Suomusj\xe4rvi</option>\n<option value="suomussalmi">Suomussalmi</option>\n<option value="suonenjoki">Suonenjoki</option>\n<option value="sysma">Sysm\xe4</option>\n<option value="sakyla">S\xe4kyl\xe4</option>\n<option value="sarkisalo">S\xe4rkisalo</option>\n<option value="taipalsaari">Taipalsaari</option>\n<option value="taivalkoski">Taivalkoski</option>\n<option value="taivassalo">Taivassalo</option>\n<option value="tammela">Tammela</option>\n<option value="tammisaari">Tammisaari</option>\n<option value="tampere">Tampere</option>\n<option value="tarvasjoki">Tarvasjoki</option>\n<option value="tervo">Tervo</option>\n<option value="tervola">Tervola</option>\n<option value="teuva">Teuva</option>\n<option value="tohmajarvi">Tohmaj\xe4rvi</option>\n<option value="toholampi">Toholampi</option>\n<option value="toivakka">Toivakka</option>\n<option value="tornio">Tornio</option>\n<option value="turku" selected="selected">Turku</option>\n<option value="tuulos">Tuulos</option>\n<option value="tuusniemi">Tuusniemi</option>\n<option value="tuusula">Tuusula</option>\n<option value="tyrnava">Tyrn\xe4v\xe4</option>\n<option value="toysa">T\xf6ys\xe4</option>\n<option value="ullava">Ullava</option>\n<option value="ulvila">Ulvila</option>\n<option value="urjala">Urjala</option>\n<option value="utajarvi">Utaj\xe4rvi</option>\n<option value="utsjoki">Utsjoki</option>\n<option value="uurainen">Uurainen</option>\n<option value="uusikaarlepyy">Uusikaarlepyy</option>\n<option value="uusikaupunki">Uusikaupunki</option>\n<option value="vaala">Vaala</option>\n<option value="vaasa">Vaasa</option>\n<option value="vahto">Vahto</option>\n<option value="valkeakoski">Valkeakoski</option>\n<option value="valkeala">Valkeala</option>\n<option value="valtimo">Valtimo</option>\n<option value="vammala">Vammala</option>\n<option value="vampula">Vampula</option>\n<option value="vantaa">Vantaa</option>\n<option value="varkaus">Varkaus</option>\n<option value="varpaisjarvi">Varpaisj\xe4rvi</option>\n<option value="vehmaa">Vehmaa</option>\n<option value="velkua">Velkua</option>\n<option value="vesanto">Vesanto</option>\n<option value="vesilahti">Vesilahti</option>\n<option value="veteli">Veteli</option>\n<option value="vierema">Vierem\xe4</option>\n<option value="vihanti">Vihanti</option>\n<option value="vihti">Vihti</option>\n<option value="viitasaari">Viitasaari</option>\n<option value="vilppula">Vilppula</option>\n<option value="vimpeli">Vimpeli</option>\n<option value="virolahti">Virolahti</option>\n<option value="virrat">Virrat</option>\n<option value="vardo">V\xe5rd\xf6</option>\n<option value="vahakyro">V\xe4h\xe4kyr\xf6</option>\n<option value="vastanfjard">V\xe4stanfj\xe4rd</option>\n<option value="voyri-maksamaa">V\xf6yri-Maksamaa</option>\n<option value="yliharma">Ylih\xe4rm\xe4</option>\n<option value="yli-ii">Yli-Ii</option>\n<option value="ylikiiminki">Ylikiiminki</option>\n<option value="ylistaro">Ylistaro</option>\n<option value="ylitornio">Ylitornio</option>\n<option value="ylivieska">Ylivieska</option>\n<option value="ylamaa">Yl\xe4maa</option>\n<option value="ylane">Yl\xe4ne</option>\n<option value="ylojarvi">Yl\xf6j\xe4rvi</option>\n<option value="ypaja">Yp\xe4j\xe4</option>\n<option value="aetsa">\xc4ets\xe4</option>\n<option value="ahtari">\xc4ht\xe4ri</option>\n<option value="aanekoski">\xc4\xe4nekoski</option>\n</select>'
+
+# FISocialSecurityNumber
+##############################################################
+
+>>> from django.contrib.localflavor.fi.forms import FISocialSecurityNumber
+>>> f = FISocialSecurityNumber()
+>>> f.clean('010101-0101')
+u'010101-0101'
+>>> f.clean('010101+0101')
+u'010101+0101'
+>>> f.clean('010101A0101')
+u'010101A0101'
+>>> f.clean('101010-0102')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Finnish social security number.']
+>>> f.clean('10a010-0101')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Finnish social security number.']
+>>> f.clean('101010-0\xe401')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Finnish social security number.']
+>>> f.clean('101010b0101')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Finnish social security number.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f = FISocialSecurityNumber(required=False)
+>>> f.clean('010101-0101')
+u'010101-0101'
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+# BRZipCodeField ############################################################
+>>> from django.contrib.localflavor.br.forms import BRZipCodeField
+>>> f = BRZipCodeField()
+>>> f.clean('12345-123')
+u'12345-123'
+>>> f.clean('12345_123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('1234-123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('abcde-abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('12345-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('-123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> f = BRZipCodeField(required=False)
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+>>> f.clean('-123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('12345-')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('abcde-abc')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('1234-123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('12345_123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX-XXX.']
+>>> f.clean('12345-123')
+u'12345-123'
+
+# BRCNPJField ############################################################
+
+>>> from django.contrib.localflavor.br.forms import BRCNPJField
+>>> f = BRCNPJField(required=True)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('12-345-678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('12.345.678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('12345678/9012-10')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CNPJ number.']
+>>> f.clean('64.132.916/0001-88')
+'64.132.916/0001-88'
+>>> f.clean('64-132-916/0001-88')
+'64-132-916/0001-88'
+>>> f.clean('64132916/0001-88')
+'64132916/0001-88'
+>>> f.clean('64.132.916/0001-XX')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f = BRCNPJField(required=False)
+>>> f.clean('')
+u''
+
+# BRCPFField #################################################################
+
+>>> from django.contrib.localflavor.br.forms import BRCPFField
+>>> f = BRCPFField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('489.294.654-54')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CPF number.']
+>>> f.clean('295.669.575-98')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CPF number.']
+>>> f.clean('539.315.127-22')
+Traceback (most recent call last):
+...
+ValidationError: [u'Invalid CPF number.']
+>>> f.clean('663.256.017-26')
+u'663.256.017-26'
+>>> f.clean('66325601726')
+u'66325601726'
+>>> f.clean('375.788.573-20')
+u'375.788.573-20'
+>>> f.clean('84828509895')
+u'84828509895'
+>>> f.clean('375.788.573-XX')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires only numbers.']
+>>> f.clean('375.788.573-000')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 14 characters.']
+>>> f.clean('123.456.78')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 11 characters.']
+>>> f.clean('123456789555')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field requires at most 11 digits or 14 characters.']
+>>> f = BRCPFField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+
+# BRPhoneNumberField #########################################################
+
+>>> from django.contrib.localflavor.br.forms import BRPhoneNumberField
+>>> f = BRPhoneNumberField()
+>>> f.clean('41-3562-3464')
+u'41-3562-3464'
+>>> f.clean('4135623464')
+u'41-3562-3464'
+>>> f.clean('41 3562-3464')
+u'41-3562-3464'
+>>> f.clean('41 3562 3464')
+u'41-3562-3464'
+>>> f.clean('(41) 3562 3464')
+u'41-3562-3464'
+>>> f.clean('41.3562.3464')
+u'41-3562-3464'
+>>> f.clean('41.3562-3464')
+u'41-3562-3464'
+>>> f.clean(' (41) 3562.3464')
+u'41-3562-3464'
+>>> 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 = BRPhoneNumberField(required=False)
+>>> f.clean('')
+u''
+>>> f.clean(None)
+u''
+>>> f.clean(' (41) 3562.3464')
+u'41-3562-3464'
+>>> f.clean('41.3562-3464')
+u'41-3562-3464'
+>>> f.clean('(41) 3562 3464')
+u'41-3562-3464'
+>>> f.clean('4135623464')
+u'41-3562-3464'
+>>> f.clean('41 3562-3464')
+u'41-3562-3464'
+
+# BRStateSelect ##############################################################
+
+>>> from django.contrib.localflavor.br.forms import BRStateSelect
+>>> w = BRStateSelect()
+>>> w.render('states', 'PR')
+u'<select name="states">\n<option value="AC">Acre</option>\n<option value="AL">Alagoas</option>\n<option value="AP">Amap\xe1</option>\n<option value="AM">Amazonas</option>\n<option value="BA">Bahia</option>\n<option value="CE">Cear\xe1</option>\n<option value="DF">Distrito Federal</option>\n<option value="ES">Esp\xedrito Santo</option>\n<option value="GO">Goi\xe1s</option>\n<option value="MA">Maranh\xe3o</option>\n<option value="MT">Mato Grosso</option>\n<option value="MS">Mato Grosso do Sul</option>\n<option value="MG">Minas Gerais</option>\n<option value="PA">Par\xe1</option>\n<option value="PB">Para\xedba</option>\n<option value="PR" selected="selected">Paran\xe1</option>\n<option value="PE">Pernambuco</option>\n<option value="PI">Piau\xed</option>\n<option value="RJ">Rio de Janeiro</option>\n<option value="RN">Rio Grande do Norte</option>\n<option value="RS">Rio Grande do Sul</option>\n<option value="RO">Rond\xf4nia</option>\n<option value="RR">Roraima</option>\n<option value="SC">Santa Catarina</option>\n<option value="SP">S\xe3o Paulo</option>\n<option value="SE">Sergipe</option>\n<option value="TO">Tocantins</option>\n</select>'
+
+# DEZipCodeField ##############################################################
+
+>>> from django.contrib.localflavor.de.forms import DEZipCodeField
+>>> f = DEZipCodeField()
+>>> f.clean('99423')
+u'99423'
+>>> f.clean(' 99423')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXXX.']
+
+# DEStateSelect #############################################################
+
+>>> from django.contrib.localflavor.de.forms import DEStateSelect
+>>> w = DEStateSelect()
+>>> w.render('states', 'TH')
+u'<select name="states">\n<option value="BW">Baden-Wuerttemberg</option>\n<option value="BY">Bavaria</option>\n<option value="BE">Berlin</option>\n<option value="BB">Brandenburg</option>\n<option value="HB">Bremen</option>\n<option value="HH">Hamburg</option>\n<option value="HE">Hessen</option>\n<option value="MV">Mecklenburg-Western Pomerania</option>\n<option value="NI">Lower Saxony</option>\n<option value="NW">North Rhine-Westphalia</option>\n<option value="RP">Rhineland-Palatinate</option>\n<option value="SL">Saarland</option>\n<option value="SN">Saxony</option>\n<option value="ST">Saxony-Anhalt</option>\n<option value="SH">Schleswig-Holstein</option>\n<option value="TH" selected="selected">Thuringia</option>\n</select>'
+
+# DEIdentityCardNumberField #################################################
+
+>>> from django.contrib.localflavor.de.forms import DEIdentityCardNumberField
+>>> f = DEIdentityCardNumberField()
+>>> f.clean('7549313035D-6004103-0903042-0')
+u'7549313035D-6004103-0903042-0'
+>>> f.clean('9786324830D 6104243 0910271 2')
+u'9786324830D-6104243-0910271-2'
+>>> f.clean('0434657485D-6407276-0508137-9')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.']
+
+# CHZipCodeField ############################################################
+
+>>> from django.contrib.localflavor.ch.forms import CHZipCodeField
+>>> f = CHZipCodeField()
+>>> f.clean('800x')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean('80 00')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a zip code in the format XXXX.']
+>>> f.clean('8000')
+u'8000'
+
+# CHPhoneNumberField ########################################################
+
+>>> from django.contrib.localflavor.ch.forms import CHPhoneNumberField
+>>> f = CHPhoneNumberField()
+>>> f.clean('01234567890')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.']
+>>> f.clean('1234567890')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.']
+>>> f.clean('0123456789')
+u'012 345 67 89'
+
+# CHIdentityCardNumberField #################################################
+
+>>> from django.contrib.localflavor.ch.forms import CHIdentityCardNumberField
+>>> f = CHIdentityCardNumberField()
+>>> f.clean('C1234567<0')
+u'C1234567<0'
+>>> f.clean('C1234567<1')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
+>>> f.clean('2123456700')
+u'2123456700'
+>>> f.clean('2123456701')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.']
+
+# CHStateSelect #############################################################
+
+>>> from django.contrib.localflavor.ch.forms import CHStateSelect
+>>> w = CHStateSelect()
+>>> w.render('state', 'AG')
+u'<select name="state">\n<option value="AG" selected="selected">Aargau</option>\n<option value="AI">Appenzell Innerrhoden</option>\n<option value="AR">Appenzell Ausserrhoden</option>\n<option value="BS">Basel-Stadt</option>\n<option value="BL">Basel-Land</option>\n<option value="BE">Berne</option>\n<option value="FR">Fribourg</option>\n<option value="GE">Geneva</option>\n<option value="GL">Glarus</option>\n<option value="GR">Graubuenden</option>\n<option value="JU">Jura</option>\n<option value="LU">Lucerne</option>\n<option value="NE">Neuchatel</option>\n<option value="NW">Nidwalden</option>\n<option value="OW">Obwalden</option>\n<option value="SH">Schaffhausen</option>\n<option value="SZ">Schwyz</option>\n<option value="SO">Solothurn</option>\n<option value="SG">St. Gallen</option>\n<option value="TG">Thurgau</option>\n<option value="TI">Ticino</option>\n<option value="UR">Uri</option>\n<option value="VS">Valais</option>\n<option value="VD">Vaud</option>\n<option value="ZG">Zug</option>\n<option value="ZH">Zurich</option>\n</select>'
+
+## AUPostCodeField ##########################################################
+
+A field that accepts a four digit Australian post code.
+
+>>> from django.contrib.localflavor.au.forms import AUPostCodeField
+>>> f = AUPostCodeField()
+>>> f.clean('1234')
+u'1234'
+>>> f.clean('2000')
+u'2000'
+>>> f.clean('abcd')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a 4 digit post code.']
+>>> f.clean('20001')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a 4 digit post code.']
+>>> 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 = AUPostCodeField(required=False)
+>>> f.clean('1234')
+u'1234'
+>>> f.clean('2000')
+u'2000'
+>>> f.clean('abcd')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a 4 digit post code.']
+>>> f.clean('20001')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a 4 digit post code.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## AUPhoneNumberField ########################################################
+
+A field that accepts a 10 digit Australian phone number.
+llows spaces and parentheses around area code.
+
+>>> from django.contrib.localflavor.au.forms import AUPhoneNumberField
+>>> f = AUPhoneNumberField()
+>>> f.clean('1234567890')
+u'1234567890'
+>>> f.clean('0213456789')
+u'0213456789'
+>>> f.clean('02 13 45 67 89')
+u'0213456789'
+>>> f.clean('(02) 1345 6789')
+u'0213456789'
+>>> f.clean('(02) 1345-6789')
+u'0213456789'
+>>> f.clean('(02)1345-6789')
+u'0213456789'
+>>> f.clean('0408 123 456')
+u'0408123456'
+>>> f.clean('123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must contain 10 digits.']
+>>> f.clean('1800DJANGO')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must contain 10 digits.']
+>>> 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 = AUPhoneNumberField(required=False)
+>>> f.clean('1234567890')
+u'1234567890'
+>>> f.clean('0213456789')
+u'0213456789'
+>>> f.clean('02 13 45 67 89')
+u'0213456789'
+>>> f.clean('(02) 1345 6789')
+u'0213456789'
+>>> f.clean('(02) 1345-6789')
+u'0213456789'
+>>> f.clean('(02)1345-6789')
+u'0213456789'
+>>> f.clean('0408 123 456')
+u'0408123456'
+>>> f.clean('123')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must contain 10 digits.']
+>>> f.clean('1800DJANGO')
+Traceback (most recent call last):
+...
+ValidationError: [u'Phone numbers must contain 10 digits.']
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## AUStateSelect #############################################################
+
+AUStateSelect is a Select widget that uses a list of Australian
+states/territories as its choices.
+
+>>> from django.contrib.localflavor.au.forms import AUStateSelect
+>>> f = AUStateSelect()
+>>> print f.render('state', 'NSW')
+<select name="state">
+<option value="ACT">Australian Capital Territory</option>
+<option value="NSW" selected="selected">New South Wales</option>
+<option value="NT">Northern Territory</option>
+<option value="QLD">Queensland</option>
+<option value="SA">South Australia</option>
+<option value="TAS">Tasmania</option>
+<option value="VIC">Victoria</option>
+<option value="WA">Western Australia</option>
+</select>
+
+## ISIdNumberField #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISIdNumberField()
+>>> f.clean('2308803449')
+u'230880-3449'
+>>> f.clean('230880-3449')
+u'230880-3449'
+>>> f.clean('230880 3449')
+u'230880-3449'
+>>> f.clean('230880343')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 10 characters.']
+>>> f.clean('230880343234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 11 characters.']
+>>> f.clean('abcdefghijk')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('2308803439')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Icelandic identification number is not valid.']
+>>> f.clean('2308803440')
+u'230880-3440'
+>>> f = ISIdNumberField(required=False)
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## ISPhoneNumberField #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISPhoneNumberField()
+>>> f.clean('1234567')
+u'1234567'
+>>> f.clean('123 4567')
+u'1234567'
+>>> f.clean('123-4567')
+u'1234567'
+>>> f.clean('123-456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+>>> f.clean('123456')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at least 7 characters.']
+>>> f.clean('123456555')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters.']
+>>> f.clean('abcdefg')
+Traceback (most recent call last):
+ValidationError: [u'Enter a valid value.']
+>>> f.clean(' 1234567 ')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value has at most 8 characters.']
+>>> f.clean(' 12367 ')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid value.']
+
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f = ISPhoneNumberField(required=False)
+>>> f.clean(None)
+u''
+>>> f.clean('')
+u''
+
+## ISPostalCodeSelect #############################################################
+
+>>> from django.contrib.localflavor.is_.forms import *
+>>> f = ISPostalCodeSelect()
+
+>>> f.render('foo', 'bar')
+u'<select name="foo">\n<option value="101">101 Reykjav\xedk</option>\n<option value="103">103 Reykjav\xedk</option>\n<option value="104">104 Reykjav\xedk</option>\n<option value="105">105 Reykjav\xedk</option>\n<option value="107">107 Reykjav\xedk</option>\n<option value="108">108 Reykjav\xedk</option>\n<option value="109">109 Reykjav\xedk</option>\n<option value="110">110 Reykjav\xedk</option>\n<option value="111">111 Reykjav\xedk</option>\n<option value="112">112 Reykjav\xedk</option>\n<option value="113">113 Reykjav\xedk</option>\n<option value="116">116 Kjalarnes</option>\n<option value="121">121 Reykjav\xedk</option>\n<option value="123">123 Reykjav\xedk</option>\n<option value="124">124 Reykjav\xedk</option>\n<option value="125">125 Reykjav\xedk</option>\n<option value="127">127 Reykjav\xedk</option>\n<option value="128">128 Reykjav\xedk</option>\n<option value="129">129 Reykjav\xedk</option>\n<option value="130">130 Reykjav\xedk</option>\n<option value="132">132 Reykjav\xedk</option>\n<option value="150">150 Reykjav\xedk</option>\n<option value="155">155 Reykjav\xedk</option>\n<option value="170">170 Seltjarnarnes</option>\n<option value="172">172 Seltjarnarnes</option>\n<option value="190">190 Vogar</option>\n<option value="200">200 K\xf3pavogur</option>\n<option value="201">201 K\xf3pavogur</option>\n<option value="202">202 K\xf3pavogur</option>\n<option value="203">203 K\xf3pavogur</option>\n<option value="210">210 Gar\xf0ab\xe6r</option>\n<option value="212">212 Gar\xf0ab\xe6r</option>\n<option value="220">220 Hafnarfj\xf6r\xf0ur</option>\n<option value="221">221 Hafnarfj\xf6r\xf0ur</option>\n<option value="222">222 Hafnarfj\xf6r\xf0ur</option>\n<option value="225">225 \xc1lftanes</option>\n<option value="230">230 Reykjanesb\xe6r</option>\n<option value="232">232 Reykjanesb\xe6r</option>\n<option value="233">233 Reykjanesb\xe6r</option>\n<option value="235">235 Keflav\xedkurflugv\xf6llur</option>\n<option value="240">240 Grindav\xedk</option>\n<option value="245">245 Sandger\xf0i</option>\n<option value="250">250 Gar\xf0ur</option>\n<option value="260">260 Reykjanesb\xe6r</option>\n<option value="270">270 Mosfellsb\xe6r</option>\n<option value="300">300 Akranes</option>\n<option value="301">301 Akranes</option>\n<option value="302">302 Akranes</option>\n<option value="310">310 Borgarnes</option>\n<option value="311">311 Borgarnes</option>\n<option value="320">320 Reykholt \xed Borgarfir\xf0i</option>\n<option value="340">340 Stykkish\xf3lmur</option>\n<option value="345">345 Flatey \xe1 Brei\xf0afir\xf0i</option>\n<option value="350">350 Grundarfj\xf6r\xf0ur</option>\n<option value="355">355 \xd3lafsv\xedk</option>\n<option value="356">356 Sn\xe6fellsb\xe6r</option>\n<option value="360">360 Hellissandur</option>\n<option value="370">370 B\xfa\xf0ardalur</option>\n<option value="371">371 B\xfa\xf0ardalur</option>\n<option value="380">380 Reykh\xf3lahreppur</option>\n<option value="400">400 \xcdsafj\xf6r\xf0ur</option>\n<option value="401">401 \xcdsafj\xf6r\xf0ur</option>\n<option value="410">410 Hn\xedfsdalur</option>\n<option value="415">415 Bolungarv\xedk</option>\n<option value="420">420 S\xfa\xf0av\xedk</option>\n<option value="425">425 Flateyri</option>\n<option value="430">430 Su\xf0ureyri</option>\n<option value="450">450 Patreksfj\xf6r\xf0ur</option>\n<option value="451">451 Patreksfj\xf6r\xf0ur</option>\n<option value="460">460 T\xe1lknafj\xf6r\xf0ur</option>\n<option value="465">465 B\xedldudalur</option>\n<option value="470">470 \xdeingeyri</option>\n<option value="471">471 \xdeingeyri</option>\n<option value="500">500 Sta\xf0ur</option>\n<option value="510">510 H\xf3lmav\xedk</option>\n<option value="512">512 H\xf3lmav\xedk</option>\n<option value="520">520 Drangsnes</option>\n<option value="522">522 Kj\xf6rvogur</option>\n<option value="523">523 B\xe6r</option>\n<option value="524">524 Nor\xf0urfj\xf6r\xf0ur</option>\n<option value="530">530 Hvammstangi</option>\n<option value="531">531 Hvammstangi</option>\n<option value="540">540 Bl\xf6ndu\xf3s</option>\n<option value="541">541 Bl\xf6ndu\xf3s</option>\n<option value="545">545 Skagastr\xf6nd</option>\n<option value="550">550 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="551">551 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="560">560 Varmahl\xed\xf0</option>\n<option value="565">565 Hofs\xf3s</option>\n<option value="566">566 Hofs\xf3s</option>\n<option value="570">570 Flj\xf3t</option>\n<option value="580">580 Siglufj\xf6r\xf0ur</option>\n<option value="600">600 Akureyri</option>\n<option value="601">601 Akureyri</option>\n<option value="602">602 Akureyri</option>\n<option value="603">603 Akureyri</option>\n<option value="610">610 Greniv\xedk</option>\n<option value="611">611 Gr\xedmsey</option>\n<option value="620">620 Dalv\xedk</option>\n<option value="621">621 Dalv\xedk</option>\n<option value="625">625 \xd3lafsfj\xf6r\xf0ur</option>\n<option value="630">630 Hr\xedsey</option>\n<option value="640">640 H\xfasav\xedk</option>\n<option value="641">641 H\xfasav\xedk</option>\n<option value="645">645 Fossh\xf3ll</option>\n<option value="650">650 Laugar</option>\n<option value="660">660 M\xfdvatn</option>\n<option value="670">670 K\xf3pasker</option>\n<option value="671">671 K\xf3pasker</option>\n<option value="675">675 Raufarh\xf6fn</option>\n<option value="680">680 \xde\xf3rsh\xf6fn</option>\n<option value="681">681 \xde\xf3rsh\xf6fn</option>\n<option value="685">685 Bakkafj\xf6r\xf0ur</option>\n<option value="690">690 Vopnafj\xf6r\xf0ur</option>\n<option value="700">700 Egilssta\xf0ir</option>\n<option value="701">701 Egilssta\xf0ir</option>\n<option value="710">710 Sey\xf0isfj\xf6r\xf0ur</option>\n<option value="715">715 Mj\xf3ifj\xf6r\xf0ur</option>\n<option value="720">720 Borgarfj\xf6r\xf0ur eystri</option>\n<option value="730">730 Rey\xf0arfj\xf6r\xf0ur</option>\n<option value="735">735 Eskifj\xf6r\xf0ur</option>\n<option value="740">740 Neskaupsta\xf0ur</option>\n<option value="750">750 F\xe1skr\xfa\xf0sfj\xf6r\xf0ur</option>\n<option value="755">755 St\xf6\xf0varfj\xf6r\xf0ur</option>\n<option value="760">760 Brei\xf0dalsv\xedk</option>\n<option value="765">765 Dj\xfapivogur</option>\n<option value="780">780 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="781">781 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="785">785 \xd6r\xe6fi</option>\n<option value="800">800 Selfoss</option>\n<option value="801">801 Selfoss</option>\n<option value="802">802 Selfoss</option>\n<option value="810">810 Hverager\xf0i</option>\n<option value="815">815 \xdeorl\xe1ksh\xf6fn</option>\n<option value="820">820 Eyrarbakki</option>\n<option value="825">825 Stokkseyri</option>\n<option value="840">840 Laugarvatn</option>\n<option value="845">845 Fl\xfa\xf0ir</option>\n<option value="850">850 Hella</option>\n<option value="851">851 Hella</option>\n<option value="860">860 Hvolsv\xf6llur</option>\n<option value="861">861 Hvolsv\xf6llur</option>\n<option value="870">870 V\xedk</option>\n<option value="871">871 V\xedk</option>\n<option value="880">880 Kirkjub\xe6jarklaustur</option>\n<option value="900">900 Vestmannaeyjar</option>\n<option value="902">902 Vestmannaeyjar</option>\n</select>'
+
+## CLRutField #############################################################
+
+CLRutField is a Field that checks the validity of the Chilean
+personal identification number (RUT). It has two modes relaxed (default) and
+strict.
+
+>>> from django.contrib.localflavor.cl.forms import CLRutField
+>>> rut = CLRutField()
+
+>>> rut.clean('11-6')
+'11-6'
+>>> rut.clean('116')
+'11-6'
+
+# valid format, bad verifier.
+>>> rut.clean('11.111.111-0')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+>>> rut.clean('111')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+
+>>> rut.clean('767484100')
+'76.748.410-0'
+>>> rut.clean('78.412.790-7')
+'78.412.790-7'
+>>> rut.clean('8.334.6043')
+'8.334.604-3'
+>>> rut.clean('76793310-K')
+'76.793.310-K'
+
+Strict RUT usage (does not allow imposible values)
+>>> rut = CLRutField(strict=True)
+
+>>> rut.clean('11-6')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+
+# valid format, bad verifier.
+>>> rut.clean('11.111.111-0')
+Traceback (most recent call last):
+...
+ValidationError: [u'The Chilean RUT is not valid.']
+
+# Correct input, invalid format.
+>>> rut.clean('767484100')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+>>> rut.clean('78.412.790-7')
+'78.412.790-7'
+>>> rut.clean('8.334.6043')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+>>> rut.clean('76793310-K')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.']
+
+"""
diff --git a/tests/regressiontests/forms/regressions.py b/tests/regressiontests/forms/regressions.py
new file mode 100644
index 0000000000..5fe057b5d8
--- /dev/null
+++ b/tests/regressiontests/forms/regressions.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Tests to prevent against recurrences of earlier bugs.
+
+regression_tests = r"""
+It should be possible to re-use attribute dictionaries (#3810)
+>>> from django.newforms import *
+>>> extra_attrs = {'class': 'special'}
+>>> class TestForm(Form):
+... f1 = CharField(max_length=10, widget=TextInput(attrs=extra_attrs))
+... f2 = CharField(widget=TextInput(attrs=extra_attrs))
+>>> TestForm(auto_id=False).as_p()
+u'<p>F1: <input type="text" class="special" name="f1" maxlength="10" /></p>\n<p>F2: <input type="text" class="special" name="f2" /></p>'
+
+#######################
+# Tests for form i18n #
+#######################
+There were some problems with form translations in #3600
+
+>>> from django.utils.translation import gettext_lazy, activate, deactivate
+>>> class SomeForm(Form):
+... username = CharField(max_length=10, label=gettext_lazy('Username'))
+>>> f = SomeForm()
+>>> print f.as_p()
+<p><label for="id_username">Username:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
+>>> activate('de')
+>>> print f.as_p()
+<p><label for="id_username">Benutzername:</label> <input id="id_username" type="text" name="username" maxlength="10" /></p>
+>>> deactivate()
+
+Unicode decoding problems...
+>>> GENDERS = (('0', u'En tied\xe4'), ('1', u'Mies'), ('2', u'Nainen'))
+>>> class SomeForm(Form):
+... somechoice = ChoiceField(choices=GENDERS, widget=RadioSelect())
+>>> f = SomeForm()
+>>> f.as_p()
+u'<p><label for="id_somechoice_0">Somechoice:</label> <ul>\n<li><label><input type="radio" id="id_somechoice_0" value="0" name="somechoice" /> En tied\xe4</label></li>\n<li><label><input type="radio" id="id_somechoice_1" value="1" name="somechoice" /> Mies</label></li>\n<li><label><input type="radio" id="id_somechoice_2" value="2" name="somechoice" /> Nainen</label></li>\n</ul></p>'
+
+#######################
+# Miscellaneous Tests #
+#######################
+
+There once was a problem with Form fields called "data". Let's make sure that
+doesn't come back.
+>>> class DataForm(Form):
+... data = CharField(max_length=10)
+>>> f = DataForm({'data': 'xyzzy'})
+>>> f.is_valid()
+True
+>>> f.cleaned_data
+{'data': u'xyzzy'}
+"""
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 50bc7a7595..4844267b43 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -1,8 +1,16 @@
# -*- coding: utf-8 -*-
-r"""
+from localflavor import localflavor_tests
+from regressions import regression_tests
+
+form_tests = r"""
>>> from django.newforms import *
>>> import datetime
+>>> import time
>>> import re
+>>> try:
+... from decimal import Decimal
+... except ImportError:
+... from django.utils._decimal import Decimal
###########
# Widgets #
@@ -72,6 +80,22 @@ u'<input type="password" class="special" name="email" />'
>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
+The render_value argument lets you specify whether the widget should render
+its value. You may want to do this for security reasons.
+>>> w = PasswordInput(render_value=True)
+>>> w.render('email', 'secret')
+u'<input type="password" name="email" value="secret" />'
+>>> w = PasswordInput(render_value=False)
+>>> w.render('email', '')
+u'<input type="password" name="email" />'
+>>> w.render('email', None)
+u'<input type="password" name="email" />'
+>>> w.render('email', 'secret')
+u'<input type="password" name="email" />'
+>>> w = PasswordInput(attrs={'class': 'fun'}, render_value=False)
+>>> w.render('email', 'secret')
+u'<input type="password" class="fun" name="email" />'
+
# HiddenInput Widget ############################################################
>>> w = HiddenInput()
@@ -106,6 +130,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()
@@ -134,30 +198,30 @@ u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u01
>>> w = Textarea()
>>> w.render('msg', '')
-u'<textarea name="msg"></textarea>'
+u'<textarea rows="10" cols="40" name="msg"></textarea>'
>>> w.render('msg', None)
-u'<textarea name="msg"></textarea>'
+u'<textarea rows="10" cols="40" name="msg"></textarea>'
>>> w.render('msg', 'value')
-u'<textarea name="msg">value</textarea>'
+u'<textarea rows="10" cols="40" name="msg">value</textarea>'
>>> w.render('msg', 'some "quoted" & ampersanded value')
-u'<textarea name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>'
->>> w.render('msg', 'value', attrs={'class': 'pretty'})
-u'<textarea name="msg" class="pretty">value</textarea>'
+u'<textarea rows="10" cols="40" name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>'
+>>> w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20})
+u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>'
You can also pass 'attrs' to the constructor:
>>> w = Textarea(attrs={'class': 'pretty'})
>>> w.render('msg', '')
-u'<textarea class="pretty" name="msg"></textarea>'
+u'<textarea rows="10" cols="40" name="msg" class="pretty"></textarea>'
>>> w.render('msg', 'example')
-u'<textarea class="pretty" name="msg">example</textarea>'
+u'<textarea rows="10" cols="40" name="msg" class="pretty">example</textarea>'
'attrs' passed to render() get precedence over those passed to the constructor:
>>> w = Textarea(attrs={'class': 'pretty'})
>>> w.render('msg', '', attrs={'class': 'special'})
-u'<textarea class="special" name="msg"></textarea>'
+u'<textarea rows="10" cols="40" name="msg" class="special"></textarea>'
>>> w.render('msg', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
-u'<textarea class="fun" name="msg">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>'
+u'<textarea rows="10" cols="40" name="msg" class="fun">\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</textarea>'
# CheckboxInput Widget ########################################################
@@ -262,6 +326,7 @@ The value is compared to its str():
</select>
The 'choices' argument can be any iterable:
+>>> from itertools import chain
>>> def get_choices():
... for i in range(5):
... yield (i, i)
@@ -273,6 +338,17 @@ The 'choices' argument can be any iterable:
<option value="3">3</option>
<option value="4">4</option>
</select>
+>>> things = ({'id': 1, 'name': 'And Boom'}, {'id': 2, 'name': 'One More Thing!'})
+>>> class SomeForm(Form):
+... somechoice = ChoiceField(choices=chain((('', '-'*9),), [(thing['id'], thing['name']) for thing in things]))
+>>> f = SomeForm()
+>>> f.as_table()
+u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>'
+>>> f.as_table()
+u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="" selected="selected">---------</option>\n<option value="1">And Boom</option>\n<option value="2">One More Thing!</option>\n</select></td></tr>'
+>>> f = SomeForm({'somechoice': 2})
+>>> f.as_table()
+u'<tr><th><label for="id_somechoice">Somechoice:</label></th><td><select name="somechoice" id="id_somechoice">\n<option value="">---------</option>\n<option value="1">And Boom</option>\n<option value="2" selected="selected">One More Thing!</option>\n</select></td></tr>'
You can also pass 'choices' to the constructor:
>>> w = Select(choices=[(1, 1), (2, 2), (3, 3)])
@@ -296,6 +372,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()
@@ -527,12 +657,37 @@ True
>>> r[1].is_checked()
False
>>> r[1].name, r[1].value, r[1].choice_value, r[1].choice_label
-('beatle', u'J', 'P', 'Paul')
+('beatle', u'J', u'P', u'Paul')
>>> r[10]
Traceback (most recent call last):
...
IndexError: list index out of range
+# Unicode choices are correctly rendered as HTML
+>>> 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>'
+
+# Attributes provided at instantiation are passed to the constituent inputs
+>>> w = RadioSelect(attrs={'id':'foo'})
+>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<ul>
+<li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li>
+<li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li>
+<li><label><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li>
+<li><label><input type="radio" id="foo_3" value="R" name="beatle" /> Ringo</label></li>
+</ul>
+
+# Attributes provided at render-time are passed to the constituent inputs
+>>> w = RadioSelect()
+>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'})
+<ul>
+<li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li>
+<li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li>
+<li><label><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li>
+<li><label><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></li>
+</ul>
+
# CheckboxSelectMultiple Widget ###############################################
>>> w = CheckboxSelectMultiple()
@@ -640,6 +795,44 @@ 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" />'
+>>> w.render('name', 'john__lennon', attrs={'id':'foo'})
+u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><input id="foo_1" type="text" class="small" value="lennon" name="name_1" />'
+>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'})
+>>> w.render('name', ['john', 'lennon'])
+u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" 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 #
##########
@@ -658,6 +851,8 @@ Each Field's __init__() takes at least these parameters:
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.
@@ -764,9 +959,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)
@@ -853,6 +1050,133 @@ Traceback (most recent call last):
...
ValidationError: [u'Ensure this value is less than or equal to 20.']
+# FloatField ##################################################################
+
+>>> f = FloatField()
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('1')
+1.0
+>>> isinstance(f.clean('1'), float)
+True
+>>> f.clean('23')
+23.0
+>>> f.clean('3.14')
+3.1400000000000001
+>>> f.clean('a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a number.']
+>>> f.clean('1.0 ')
+1.0
+>>> f.clean(' 1.0')
+1.0
+>>> f.clean(' 1.0 ')
+1.0
+>>> f.clean('1.0a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a number.']
+
+>>> f = FloatField(required=False)
+>>> f.clean('')
+
+>>> f.clean(None)
+
+>>> f.clean('1')
+1.0
+
+FloatField accepts min_value and max_value just like IntegerField:
+>>> f = FloatField(max_value=1.5, min_value=0.5)
+
+>>> f.clean('1.6')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is less than or equal to 1.5.']
+>>> f.clean('0.4')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
+>>> f.clean('1.5')
+1.5
+>>> f.clean('0.5')
+0.5
+
+# DecimalField ################################################################
+
+>>> f = DecimalField(max_digits=4, decimal_places=2)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('1')
+Decimal("1")
+>>> isinstance(f.clean('1'), Decimal)
+True
+>>> f.clean('23')
+Decimal("23")
+>>> f.clean('3.14')
+Decimal("3.14")
+>>> f.clean('a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a number.']
+>>> f.clean('1.0 ')
+Decimal("1.0")
+>>> f.clean(' 1.0')
+Decimal("1.0")
+>>> f.clean(' 1.0 ')
+Decimal("1.0")
+>>> f.clean('1.0a')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a number.']
+>>> f.clean('123.45')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure that there are no more than 4 digits in total.']
+>>> f.clean('1.234')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure that there are no more than 2 decimal places.']
+>>> f.clean('123.4')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure that there are no more than 2 digits before the decimal point.']
+>>> f = DecimalField(max_digits=4, decimal_places=2, required=False)
+>>> f.clean('')
+
+>>> f.clean(None)
+
+>>> f.clean('1')
+Decimal("1")
+
+DecimalField accepts min_value and max_value just like IntegerField:
+>>> f = DecimalField(max_digits=4, decimal_places=2, max_value=Decimal('1.5'), min_value=Decimal('0.5'))
+
+>>> f.clean('1.6')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is less than or equal to 1.5.']
+>>> f.clean('0.4')
+Traceback (most recent call last):
+...
+ValidationError: [u'Ensure this value is greater than or equal to 0.5.']
+>>> f.clean('1.5')
+Decimal("1.5")
+>>> f.clean('0.5')
+Decimal("0.5")
+
# DateField ###################################################################
>>> import datetime
@@ -1283,8 +1607,13 @@ 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.
+URLField 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):
@@ -1353,7 +1682,7 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')], required=False)
>>> f.clean('')
@@ -1367,7 +1696,7 @@ u'1'
>>> f.clean('3')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. 3 is not one of the available choices.']
+ValidationError: [u'Select a valid choice. That choice is not one of the available choices.']
>>> f = ChoiceField(choices=[('J', 'John'), ('P', 'Paul')])
>>> f.clean('J')
@@ -1375,7 +1704,21 @@ u'J'
>>> f.clean('John')
Traceback (most recent call last):
...
-ValidationError: [u'Select a valid choice. John is not one of the available choices.']
+ValidationError: [u'Select a valid choice. That choice 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 #########################################################
@@ -1483,6 +1826,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 #
#########
@@ -1500,6 +1895,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()
@@ -1508,7 +1905,7 @@ True
u''
>>> p.errors.as_text()
u''
->>> p.clean_data
+>>> p.cleaned_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" id="id_first_name" />
@@ -1538,10 +1935,16 @@ Birthday 1940-10-9
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.cleaned_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> print p
<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>
@@ -1563,13 +1966,19 @@ False
<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.cleaned_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> print p
<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>
@@ -1608,9 +2017,10 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re
* This field is required.
* birthday
* This field is required.
->>> p.clean_data
->>> repr(p.clean_data)
-'None'
+>>> p.cleaned_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'cleaned_data'
>>> p['first_name'].errors
[u'This field is required.']
>>> p['first_name'].errors.as_ul()
@@ -1626,6 +2036,45 @@ u'* This field is required.'
>>> print p['birthday']
<input type="text" name="birthday" id="id_birthday" />
+cleaned_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 cleaned_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.cleaned_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
+cleaned_data will include a key and value for *all* fields defined in the Form,
+even if the Form's data didn't include a value for fields that are not
+required. In this example, the data dictionary doesn't include a value for the
+"nick_name" field, but cleaned_data includes it. For CharFields, it's set to the
+empty string.
+>>> class OptionalPersonForm(Form):
+... first_name = CharField()
+... last_name = CharField()
+... nick_name = CharField(required=False)
+>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
+>>> f = OptionalPersonForm(data)
+>>> f.is_valid()
+True
+>>> f.cleaned_data
+{'nick_name': u'', 'first_name': u'John', 'last_name': u'Lennon'}
+
+For DateFields, it's set to None.
+>>> class OptionalPersonForm(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birth_date = DateField(required=False)
+>>> data = {'first_name': u'John', 'last_name': u'Lennon'}
+>>> f = OptionalPersonForm(data)
+>>> f.is_valid()
+True
+>>> f.cleaned_data
+{'birth_date': None, 'first_name': u'John', 'last_name': u'Lennon'}
+
"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
@@ -1703,12 +2152,12 @@ Any Field can have a Widget class passed to its constructor:
>>> print f['subject']
<input type="text" name="subject" />
>>> print f['message']
-<textarea name="message"></textarea>
+<textarea rows="10" cols="40" name="message"></textarea>
as_textarea(), as_text() and as_hidden() are shortcuts for changing the output
widget type:
>>> f['subject'].as_textarea()
-u'<textarea name="subject"></textarea>'
+u'<textarea rows="10" cols="40" name="subject"></textarea>'
>>> f['message'].as_text()
u'<input type="text" name="message" />'
>>> f['message'].as_hidden()
@@ -1728,7 +2177,7 @@ as_hidden():
u'<input type="text" name="message" />'
>>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False)
>>> f['subject'].as_textarea()
-u'<textarea name="subject">Hello</textarea>'
+u'<textarea rows="10" cols="40" name="subject">Hello</textarea>'
>>> f['message'].as_text()
u'<input type="text" name="message" value="I love you." />'
>>> f['message'].as_hidden()
@@ -1751,6 +2200,70 @@ For a form with a <select>, use ChoiceField:
<option value="J">Java</option>
</select>
+A subtlety: If one of the choices' value is the empty string and the form is
+unbound, then the <option> for the empty-string choice will get selected="selected".
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField(choices=[('', '------'), ('P', 'Python'), ('J', 'Java')])
+>>> f = FrameworkForm(auto_id=False)
+>>> print f['language']
+<select name="language">
+<option value="" selected="selected">------</option>
+<option value="P">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()
@@ -1832,6 +2345,17 @@ 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()
@@ -1855,6 +2379,16 @@ MultipleChoiceField can also be used with the CheckboxSelectMultiple widget.
<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']}
@@ -1867,11 +2401,20 @@ conveniently work with this.
>>> f.errors
{}
>>> from django.utils.datastructures import MultiValueDict
->>> data = MultiValueDict(dict(name='Yesterday', composers=['J', 'P']))
+>>> 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'}, auto_id=False)
@@ -1880,19 +2423,34 @@ returns a list of input.
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
>>> f.errors
{}
->>> f.clean_data
+>>> f.cleaned_data
{'composers': [u'J'], 'name': u'Yesterday'}
>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
>>> f.errors
{}
->>> f.clean_data
+>>> f.cleaned_data
{'composers': [u'J', u'P'], 'name': u'Yesterday'}
+Validation errors are HTML-escaped when output as HTML.
+>>> class EscapingForm(Form):
+... special_name = CharField()
+... def clean_special_name(self):
+... raise ValidationError("Something's wrong with '%s'" % self.cleaned_data['special_name'])
+
+>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False)
+>>> print f
+<tr><th>Special name:</th><td><ul class="errorlist"><li>Something&#39;s wrong with &#39;Nothing to escape&#39;</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr>
+>>> f = EscapingForm({'special_name': "Should escape < & > and <script>alert('xss')</script>"}, auto_id=False)
+>>> print f
+<tr><th>Special name:</th><td><ul class="errorlist"><li>Something&#39;s wrong with &#39;Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;&#39;</li></ul><input type="text" name="special_name" value="Should escape &lt; &amp; &gt; and &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;" /></td></tr>
+
+# 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
Field.clean(), the clean_XXX() method should return the cleaned value. In the
-clean_XXX() method, you have access to self.clean_data, which is a dictionary
+clean_XXX() method, you have access to self.cleaned_data, which is a dictionary
of all the data that has been cleaned *so far*, in order by the fields,
including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> class UserRegistration(Form):
@@ -1900,9 +2458,9 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean_password2(self):
-... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
-... return self.clean_data['password2']
+... return self.cleaned_data['password2']
>>> f = UserRegistration(auto_id=False)
>>> f.errors
{}
@@ -1915,14 +2473,14 @@ including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors
{}
->>> f.clean_data
+>>> f.cleaned_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
Another way of doing multiple-field validation is by implementing the
Form's clean() method. If you do this, any ValidationError raised by that
method will not be associated with a particular field; it will have a
special-case association with the field named '__all__'.
-Note that in Form.clean(), you have access to self.clean_data, a dictionary of
+Note that in Form.clean(), you have access to self.cleaned_data, a dictionary of
all the fields/values that have *not* raised a ValidationError. Also note
Form.clean() is required to return a dictionary of all clean data.
>>> class UserRegistration(Form):
@@ -1930,9 +2488,9 @@ Form.clean() is required to return a dictionary of all clean data.
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean(self):
-... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
-... return self.clean_data
+... return self.cleaned_data
>>> f = UserRegistration(auto_id=False)
>>> f.errors
{}
@@ -1959,9 +2517,11 @@ Form.clean() is required to return a dictionary of all clean data.
>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
>>> f.errors
{}
->>> f.clean_data
+>>> f.cleaned_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__().
@@ -1977,6 +2537,83 @@ subclass' __init__().
<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>
+
+Similarly, changes to field attributes do not persist from one Form instance
+to the next.
+>>> class Person(Form):
+... first_name = CharField(required=False)
+... last_name = CharField(required=False)
+... def __init__(self, names_required=False, *args, **kwargs):
+... super(Person, self).__init__(*args, **kwargs)
+... if names_required:
+... self.fields['first_name'].required = True
+... self.fields['last_name'].required = True
+>>> f = Person(names_required=False)
+>>> f['first_name'].field.required, f['last_name'].field.required
+(False, False)
+>>> f = Person(names_required=True)
+>>> f['first_name'].field.required, f['last_name'].field.required
+(True, True)
+>>> f = Person(names_required=False)
+>>> f['first_name'].field.required, f['last_name'].field.required
+(False, False)
+>>> class Person(Form):
+... first_name = CharField(max_length=30)
+... last_name = CharField(max_length=30)
+... def __init__(self, name_max_length=None, *args, **kwargs):
+... super(Person, self).__init__(*args, **kwargs)
+... if name_max_length:
+... self.fields['first_name'].max_length = name_max_length
+... self.fields['last_name'].max_length = name_max_length
+>>> f = Person(name_max_length=None)
+>>> f['first_name'].field.max_length, f['last_name'].field.max_length
+(30, 30)
+>>> f = Person(name_max_length=20)
+>>> f['first_name'].field.max_length, f['last_name'].field.max_length
+(20, 20)
+>>> f = Person(name_max_length=None)
+>>> f['first_name'].field.max_length, f['last_name'].field.max_length
+(30, 30)
+
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
separate row is not displayed. They're displayed in the last row of the
@@ -2108,6 +2745,8 @@ in "attrs".
<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.
@@ -2121,6 +2760,27 @@ underscores converted to spaces, and the initial letter capitalized.
<li>Password1: <input type="password" name="password1" /></li>
<li>Password (again): <input type="password" name="password2" /></li>
+Labels for as_* methods will only end in a colon if they don't end in other
+punctuation already.
+>>> class Questions(Form):
+... q1 = CharField(label='The first question')
+... q2 = CharField(label='What is your name?')
+... q3 = CharField(label='The answer to life is:')
+... q4 = CharField(label='Answer this question!')
+... q5 = CharField(label='The last question. Period.')
+>>> print Questions(auto_id=False).as_p()
+<p>The first question: <input type="text" name="q1" /></p>
+<p>What is your name? <input type="text" name="q2" /></p>
+<p>The answer to life is: <input type="text" name="q3" /></p>
+<p>Answer this question! <input type="text" name="q4" /></p>
+<p>The last question. Period. <input type="text" name="q5" /></p>
+>>> print Questions().as_p()
+<p><label for="id_q1">The first question:</label> <input type="text" name="q1" id="id_q1" /></p>
+<p><label for="id_q2">What is your name?</label> <input type="text" name="q2" id="id_q2" /></p>
+<p><label for="id_q3">The answer to life is:</label> <input type="text" name="q3" id="id_q3" /></p>
+<p><label for="id_q4">Answer this question!</label> <input type="text" name="q4" id="id_q4" /></p>
+<p><label for="id_q5">The last question. Period.</label> <input type="text" name="q5" id="id_q5" /></p>
+
A label can be a Unicode object or a bytestring with special characters.
>>> class UserRegistration(Form):
... username = CharField(max_length=10, label='ŠĐĆŽćžšđ')
@@ -2156,6 +2816,242 @@ is default behavior.
<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>
+
+# Callable initial data ########################################################
+
+The previous technique dealt with raw values as initial data, but it's also
+possible to specify callable data.
+
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password = CharField(widget=PasswordInput)
+
+We need to define functions that get called later.
+>>> def initial_django():
+... return 'django'
+>>> def initial_stephane():
+... return 'stephane'
+
+Here, we're not submitting any data, so the initial value will be displayed.
+>>> p = UserRegistration(initial={'username': initial_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>
+
+The 'initial' parameter is meaningless if you pass data.
+>>> p = UserRegistration({}, initial={'username': initial_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': initial_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': initial_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 callable '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': initial_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=initial_django)
+... password = CharField(widget=PasswordInput)
+>>> 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>
+>>> p = UserRegistration(initial={'username': initial_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>
+
+# 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>
+
+Help text can include arbitrary Unicode characters.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ')
+>>> p = UserRegistration(auto_id=False)
+>>> p.as_ul()
+u'<li>Username: <input type="text" name="username" maxlength="10" /> \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</li>'
+
+# Subclassing forms ###########################################################
+
+You can subclass a Form to add fields. The resulting form subclass will have
+all of the fields of the parent Form, plus whichever fields you define in the
+subclass.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+>>> class Musician(Person):
+... instrument = CharField()
+>>> p = Person(auto_id=False)
+>>> 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>
+>>> m = Musician(auto_id=False)
+>>> print m.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>Instrument: <input type="text" name="instrument" /></li>
+
+Yes, you can subclass multiple forms. The fields are added in the order in
+which the parent classes are listed.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+>>> class Instrument(Form):
+... instrument = CharField()
+>>> class Beatle(Person, Instrument):
+... haircut_type = CharField()
+>>> b = Beatle(auto_id=False)
+>>> print b.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>Instrument: <input type="text" name="instrument" /></li>
+<li>Haircut type: <input type="text" name="haircut_type" /></li>
+
# Forms with prefixes #########################################################
Sometimes it's necessary to have multiple forms display on the same HTML page,
@@ -2189,7 +3085,7 @@ actual field name.
{}
>>> p.is_valid()
True
->>> p.clean_data
+>>> p.cleaned_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
@@ -2233,12 +3129,12 @@ of the same form.
>>> p1 = Person(data, prefix='person1')
>>> p1.is_valid()
True
->>> p1.clean_data
+>>> p1.cleaned_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
+>>> p2.cleaned_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
@@ -2264,9 +3160,60 @@ self.prefix.
>>> p = Person(data, prefix='foo')
>>> p.is_valid()
True
->>> p.clean_data
+>>> p.cleaned_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 #############################################
>>> from django.template import Template, Context
@@ -2275,16 +3222,16 @@ True
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean(self):
-... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
-... return self.clean_data
+... return self.cleaned_data
>>> def my_function(method, post_data):
... if method == 'POST':
... form = UserRegistration(post_data, auto_id=False)
... else:
... form = UserRegistration(auto_id=False)
... if form.is_valid():
-... return 'VALID: %r' % form.clean_data
+... return 'VALID: %r' % form.cleaned_data
... t = Template('<form action="" method="post">\n<table>\n{{ form }}\n</table>\n<input type="submit" />\n</form>')
... return t.render(Context({'form': form}))
@@ -2318,13 +3265,13 @@ VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}
# Some ideas for using templates with forms ###################################
>>> class UserRegistration(Form):
-... username = CharField(max_length=10)
+... username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.")
... password1 = CharField(widget=PasswordInput)
... password2 = CharField(widget=PasswordInput)
... def clean(self):
-... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... if self.cleaned_data.get('password1') and self.cleaned_data.get('password2') and self.cleaned_data['password1'] != self.cleaned_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
-... return self.clean_data
+... return self.cleaned_data
You have full flexibility in displaying form fields in a template. Just pass a
Form instance to the template, and use "dot" access to refer to individual
@@ -2395,6 +3342,33 @@ field an "id" attribute.
<input type="submit" />
</form>
+User form.[field].help_text to output a field's help text. If the given field
+does not have help text, nothing will be output.
+>>> t = Template('''<form action="">
+... <p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p>
+... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p>
+... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p>
+... <input type="submit" />
+... </form>''')
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
+<form action="">
+<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p>
+<p>Password1: <input type="password" name="password1" /></p>
+<p>Password2: <input type="password" name="password2" /></p>
+<input type="submit" />
+</form>
+>>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)}))
+''
+
+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
@@ -2428,17 +3402,17 @@ the list of errors is empty). You can also use it in {% if %} statements.
<input type="submit" />
</form>
-#################
-# Extra widgets #
-#################
+###############
+# Extra stuff #
+###############
-The newforms library comes with some extra, higher-level Widget classes that
-demonstrate some of the library's abilities.
+The newforms library comes with some extra, higher-level Field and Widget
+classes that demonstrate some of the library's abilities.
# SelectDateWidget ############################################################
>>> from django.newforms.extras import SelectDateWidget
->>> w = 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>
@@ -2488,7 +3462,6 @@ demonstrate some of the library's abilities.
<option value="31">31</option>
</select>
<select name="mydate_year">
-<option value="2006">2006</option>
<option value="2007">2007</option>
<option value="2008">2008</option>
<option value="2009">2009</option>
@@ -2498,6 +3471,7 @@ demonstrate some of the library's abilities.
<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
@@ -2550,7 +3524,6 @@ True
<option value="31">31</option>
</select>
<select name="mydate_year">
-<option value="2006">2006</option>
<option value="2007">2007</option>
<option value="2008">2008</option>
<option value="2009">2009</option>
@@ -2560,10 +3533,155 @@ True
<option value="2013">2013</option>
<option value="2014">2014</option>
<option value="2015">2015</option>
+<option value="2016">2016</option>
+</select>
+
+# MultiWidget and MultiValueField #############################################
+# MultiWidgets are widgets composed of other widgets. They are usually
+# combined with MultiValueFields - a field that is composed of other fields.
+# MulitWidgets can themselved be composed of other MultiWidgets.
+# SplitDateTimeWidget is one example of a MultiWidget.
+
+>>> class ComplexMultiWidget(MultiWidget):
+... def __init__(self, attrs=None):
+... widgets = (
+... TextInput(),
+... SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+... SplitDateTimeWidget(),
+... )
+... super(ComplexMultiWidget, self).__init__(widgets, attrs)
+...
+... def decompress(self, value):
+... if value:
+... data = value.split(',')
+... return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])]
+... return [None, None, None]
+... def format_output(self, rendered_widgets):
+... return u'\n'.join(rendered_widgets)
+>>> w = ComplexMultiWidget()
+>>> print w.render('name', 'some text,JP,2007-04-25 06:24:00')
+<input type="text" name="name_0" value="some text" />
+<select multiple="multiple" name="name_1">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
</select>
+<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="06:24:00" />
+
+>>> class ComplexField(MultiValueField):
+... def __init__(self, required=True, widget=None, label=None, initial=None):
+... fields = (
+... CharField(),
+... MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))),
+... SplitDateTimeField()
+... )
+... super(ComplexField, self).__init__(fields, required, widget, label, initial)
+...
+... def compress(self, data_list):
+... if data_list:
+... return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2])
+... return None
+
+>>> f = ComplexField(widget=w)
+>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']])
+u'some text,JP,2007-04-25 06:24:00'
+>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']])
+Traceback (most recent call last):
+...
+ValidationError: [u'Select a valid choice. X is not one of the available choices.']
+# If insufficient data is provided, None is substituted
+>>> f.clean(['some text',['JP']])
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+
+>>> class ComplexFieldForm(Form):
+... field1 = ComplexField(widget=w)
+>>> f = ComplexFieldForm()
+>>> print f
+<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" />
+<select multiple="multiple" name="field1_1" id="id_field1_1">
+<option value="J">John</option>
+<option value="P">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>
+<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr>
+
+>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'})
+>>> print f
+<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" />
+<select multiple="multiple" name="field1_1" id="id_field1_1">
+<option value="J" selected="selected">John</option>
+<option value="P" selected="selected">Paul</option>
+<option value="G">George</option>
+<option value="R">Ringo</option>
+</select>
+<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr>
+
+>>> f.cleaned_data
+{'field1': u'some text,JP,2007-04-25 06:24:00'}
+
+#################################
+# Tests of underlying functions #
+#################################
+
+# smart_unicode tests
+>>> from django.utils.encoding import smart_unicode
+>>> class Test:
+... def __str__(self):
+... return 'ŠĐĆŽćžšđ'
+>>> class TestU:
+... def __str__(self):
+... return 'Foo'
+... def __unicode__(self):
+... return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
+>>> smart_unicode(Test())
+u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
+>>> smart_unicode(TestU())
+u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111'
+>>> smart_unicode(1)
+u'1'
+>>> smart_unicode('foo')
+u'foo'
+
+# flatatt tests
+>>> from django.newforms.util import flatatt
+>>> flatatt({'id': "header"})
+u' id="header"'
+>>> flatatt({'class': "news", 'title': "Read this"})
+u' class="news" title="Read this"'
+>>> flatatt({})
+u''
+
+####################################
+# Test accessing errors in clean() #
+####################################
+
+>>> class UserForm(Form):
+... username = CharField(max_length=10)
+... password = CharField(widget=PasswordInput)
+... def clean(self):
+... data = self.cleaned_data
+... if not self.errors:
+... data['username'] = data['username'].lower()
+... return data
+
+>>> f = UserForm({'username': 'SirRobin', 'password': 'blue'})
+>>> f.is_valid()
+True
+>>> f.cleaned_data['username']
+u'sirrobin'
"""
+__test__ = {
+ 'form_tests': form_tests,
+ 'localflavor': localflavor_tests,
+ 'regressions': regression_tests,
+}
+
if __name__ == "__main__":
import doctest
doctest.testmod()
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 385c3048d9..e7245104e9 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -34,6 +34,9 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('foo')
False
+>>> 'foo' in q
+False
+
>>> q.items()
[]
@@ -93,6 +96,12 @@ MultiValueDictKeyError: "Key 'foo' not found in <MultiValueDict: {}>"
>>> q['name']
'john'
+>>> del q['name']
+>>> 'name' in q
+False
+
+>>> q['name'] = 'john'
+
>>> q.get('foo', 'default')
'default'
@@ -124,6 +133,9 @@ MultiValueDictKeyError: "Key 'foo' not found in <MultiValueDict: {}>"
>>> q.has_key('foo')
True
+>>> 'foo' in q
+True
+
>>> q.items()
[('foo', 'another'), ('name', 'john')]
@@ -154,6 +166,9 @@ True
>>> q.pop('foo')
['bar', 'baz', 'another', 'hello']
+>>> q.pop('foo', 'not there')
+'not there'
+
>>> q.get('foo', 'not there')
'not there'
@@ -218,9 +233,15 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('foo')
True
+>>> 'foo' in q
+True
+
>>> q.has_key('bar')
False
+>>> 'bar' in q
+False
+
>>> q.items()
[('foo', 'bar')]
@@ -303,9 +324,15 @@ AttributeError: This QueryDict instance is immutable
>>> q.has_key('vote')
True
+>>> 'vote' in q
+True
+
>>> q.has_key('foo')
False
+>>> 'foo' in q
+False
+
>>> q.items()
[('vote', 'no')]
@@ -349,6 +376,11 @@ AttributeError: This QueryDict instance is immutable
>>> q.urlencode()
'vote=yes&vote=no'
+>>> del q['vote']
+Traceback (most recent call last):
+...
+AttributeError: This QueryDict instance is immutable
+
"""
from django.http import QueryDict
diff --git a/tests/regressiontests/humanize/__init__.py b/tests/regressiontests/humanize/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/humanize/__init__.py
diff --git a/tests/regressiontests/humanize/models.py b/tests/regressiontests/humanize/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/humanize/models.py
diff --git a/tests/regressiontests/humanize/tests.py b/tests/regressiontests/humanize/tests.py
new file mode 100644
index 0000000000..eca65f7575
--- /dev/null
+++ b/tests/regressiontests/humanize/tests.py
@@ -0,0 +1,54 @@
+import unittest
+from django.template import Template, Context, add_to_builtins
+
+add_to_builtins('django.contrib.humanize.templatetags.humanize')
+
+class HumanizeTests(unittest.TestCase):
+
+ def humanize_tester(self, test_list, result_list, method):
+ # Using max below ensures we go through both lists
+ # However, if the lists are not equal length, this raises an exception
+ for index in xrange(len(max(test_list,result_list))):
+ test_content = test_list[index]
+ t = Template('{{ test_content|%s }}' % method)
+ rendered = t.render(Context(locals())).strip()
+ self.assertEqual(rendered, result_list[index],
+ msg="""%s test failed, produced %s,
+should've produced %s""" % (method, rendered, result_list[index]))
+
+ def test_ordinal(self):
+ test_list = ('1','2','3','4','11','12',
+ '13','101','102','103','111',
+ 'something else')
+ result_list = ('1st', '2nd', '3rd', '4th', '11th',
+ '12th', '13th', '101st', '102nd', '103rd',
+ '111th', 'something else')
+
+ self.humanize_tester(test_list, result_list, 'ordinal')
+
+ def test_intcomma(self):
+ test_list = (100, 1000, 10123, 10311, 1000000, 1234567.25,
+ '100', '1000', '10123', '10311', '1000000', '1234567.1234567')
+ result_list = ('100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.25',
+ '100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567')
+
+ self.humanize_tester(test_list, result_list, 'intcomma')
+
+ def test_intword(self):
+ test_list = ('100', '1000000', '1200000', '1290000',
+ '1000000000','2000000000','6000000000000')
+ result_list = ('100', '1.0 million', '1.2 million', '1.3 million',
+ '1.0 billion', '2.0 billion', '6.0 trillion')
+
+ self.humanize_tester(test_list, result_list, 'intword')
+
+ def test_apnumber(self):
+ test_list = [str(x) for x in xrange(1,11)]
+ result_list = ('one', 'two', 'three', 'four', 'five', 'six',
+ 'seven', 'eight', 'nine', '10')
+
+ self.humanize_tester(test_list, result_list, 'apnumber')
+
+if __name__ == '__main__':
+ unittest.main()
+
diff --git a/tests/regressiontests/invalid_admin_options/__init__.py b/tests/regressiontests/invalid_admin_options/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/invalid_admin_options/__init__.py
diff --git a/tests/regressiontests/invalid_admin_options/models.py b/tests/regressiontests/invalid_admin_options/models.py
new file mode 100644
index 0000000000..43bcc533ba
--- /dev/null
+++ b/tests/regressiontests/invalid_admin_options/models.py
@@ -0,0 +1,337 @@
+"""
+Admin options
+
+Test invalid and valid admin options to make sure that
+model validation is working properly.
+"""
+
+from django.db import models
+model_errors = ""
+
+# TODO: Invalid admin options should not cause a metaclass error
+##This should fail gracefully but is causing a metaclass error
+#class BadAdminOption(models.Model):
+# "Test nonexistent admin option"
+# name = models.CharField(maxlength=30)
+#
+# class Admin:
+# nonexistent = 'option'
+#
+#model_errors += """invalid_admin_options.badadminoption: "admin" attribute, if given, must be set to a models.AdminOptions() instance.
+#"""
+
+class ListDisplayBadOne(models.Model):
+ "Test list_display, list_display must be a list or tuple"
+ first_name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_display = 'first_name'
+
+model_errors += """invalid_admin_options.listdisplaybadone: "admin.list_display", if given, must be set to a list or tuple.
+"""
+
+class ListDisplayBadTwo(models.Model):
+ "Test list_display, list_display items must be attributes, methods or properties."
+ first_name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_display = ['first_name','nonexistent']
+
+model_errors += """invalid_admin_options.listdisplaybadtwo: "admin.list_display" refers to 'nonexistent', which isn't an attribute, method or property.
+"""
+class ListDisplayBadThree(models.Model):
+ "Test list_display, list_display items can not be a ManyToManyField."
+ first_name = models.CharField(maxlength=30)
+ nick_names = models.ManyToManyField('ListDisplayGood')
+
+ class Admin:
+ list_display = ['first_name','nick_names']
+
+model_errors += """invalid_admin_options.listdisplaybadthree: "admin.list_display" doesn't support ManyToManyFields ('nick_names').
+"""
+
+class ListDisplayGood(models.Model):
+ "Test list_display, Admin list_display can be a attribute, method or property."
+ first_name = models.CharField(maxlength=30)
+
+ def _last_name(self):
+ return self.first_name
+ last_name = property(_last_name)
+
+ def full_name(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+ class Admin:
+ list_display = ['first_name','last_name','full_name']
+
+class ListDisplayLinksBadOne(models.Model):
+ "Test list_display_links, item must be included in list_display."
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_display = ['last_name']
+ list_display_links = ['first_name']
+
+model_errors += """invalid_admin_options.listdisplaylinksbadone: "admin.list_display_links" refers to 'first_name', which is not defined in "admin.list_display".
+"""
+
+class ListDisplayLinksBadTwo(models.Model):
+ "Test list_display_links, must be a list or tuple."
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_display = ['first_name','last_name']
+ list_display_links = 'last_name'
+
+model_errors += """invalid_admin_options.listdisplaylinksbadtwo: "admin.list_display_links", if given, must be set to a list or tuple.
+"""
+
+# TODO: Fix list_display_links validation or remove the check for list_display
+## This is failing but the validation which should fail is not.
+#class ListDisplayLinksBadThree(models.Model):
+# "Test list_display_links, must define list_display to use list_display_links."
+# first_name = models.CharField(maxlength=30)
+# last_name = models.CharField(maxlength=30)
+#
+# class Admin:
+# list_display_links = ('first_name',)
+#
+#model_errors += """invalid_admin_options.listdisplaylinksbadthree: "admin.list_display" must be defined for "admin.list_display_links" to be used.
+#"""
+
+class ListDisplayLinksGood(models.Model):
+ "Test list_display_links, Admin list_display_list can be a attribute, method or property."
+ first_name = models.CharField(maxlength=30)
+
+ def _last_name(self):
+ return self.first_name
+ last_name = property(_last_name)
+
+ def full_name(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+ class Admin:
+ list_display = ['first_name','last_name','full_name']
+ list_display_links = ['first_name','last_name','full_name']
+
+class ListFilterBadOne(models.Model):
+ "Test list_filter, must be a list or tuple."
+ first_name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_filter = 'first_name'
+
+model_errors += """invalid_admin_options.listfilterbadone: "admin.list_filter", if given, must be set to a list or tuple.
+"""
+
+class ListFilterBadTwo(models.Model):
+ "Test list_filter, must be a field not a property or method."
+ first_name = models.CharField(maxlength=30)
+
+ def _last_name(self):
+ return self.first_name
+ last_name = property(_last_name)
+
+ def full_name(self):
+ return "%s %s" % (self.first_name, self.last_name)
+
+ class Admin:
+ list_filter = ['first_name','last_name','full_name']
+
+model_errors += """invalid_admin_options.listfilterbadtwo: "admin.list_filter" refers to 'last_name', which isn't a field.
+invalid_admin_options.listfilterbadtwo: "admin.list_filter" refers to 'full_name', which isn't a field.
+"""
+
+class DateHierarchyBadOne(models.Model):
+ "Test date_hierarchy, must be a date or datetime field."
+ first_name = models.CharField(maxlength=30)
+ birth_day = models.DateField()
+
+ class Admin:
+ date_hierarchy = 'first_name'
+
+# TODO: Date Hierarchy needs to check if field is a date/datetime field.
+#model_errors += """invalid_admin_options.datehierarchybadone: "admin.date_hierarchy" refers to 'first_name', which isn't a date field or datetime field.
+#"""
+
+class DateHierarchyBadTwo(models.Model):
+ "Test date_hieracrhy, must be a field."
+ first_name = models.CharField(maxlength=30)
+ birth_day = models.DateField()
+
+ class Admin:
+ date_hierarchy = 'nonexistent'
+
+model_errors += """invalid_admin_options.datehierarchybadtwo: "admin.date_hierarchy" refers to 'nonexistent', which isn't a field.
+"""
+
+class DateHierarchyGood(models.Model):
+ "Test date_hieracrhy, must be a field."
+ first_name = models.CharField(maxlength=30)
+ birth_day = models.DateField()
+
+ class Admin:
+ date_hierarchy = 'birth_day'
+
+class SearchFieldsBadOne(models.Model):
+ "Test search_fields, must be a list or tuple."
+ first_name = models.CharField(maxlength=30)
+
+ class Admin:
+ search_fields = ('nonexistent')
+
+# TODO: Add search_fields validation
+#model_errors += """invalid_admin_options.seacrhfieldsbadone: "admin.search_fields", if given, must be set to a list or tuple.
+#"""
+
+class SearchFieldsBadTwo(models.Model):
+ "Test search_fields, must be a field."
+ first_name = models.CharField(maxlength=30)
+
+ def _last_name(self):
+ return self.first_name
+ last_name = property(_last_name)
+
+ class Admin:
+ search_fields = ['first_name','last_name']
+
+# TODO: Add search_fields validation
+#model_errors += """invalid_admin_options.seacrhfieldsbadone: "admin.search_fields" refers to 'last_name', which isn't a field.
+#"""
+
+class SearchFieldsGood(models.Model):
+ "Test search_fields, must be a list or tuple."
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ search_fields = ['first_name','last_name']
+
+
+class JsBadOne(models.Model):
+ "Test js, must be a list or tuple"
+ name = models.CharField(maxlength=30)
+
+ class Admin:
+ js = 'test.js'
+
+# TODO: Add a js validator
+#model_errors += """invalid_admin_options.jsbadone: "admin.js", if given, must be set to a list or tuple.
+#"""
+
+class SaveAsBad(models.Model):
+ "Test save_as, should be True or False"
+ name = models.CharField(maxlength=30)
+
+ class Admin:
+ save_as = 'not True or False'
+
+# TODO: Add a save_as validator.
+#model_errors += """invalid_admin_options.saveasbad: "admin.save_as", if given, must be set to True or False.
+#"""
+
+class SaveOnTopBad(models.Model):
+ "Test save_on_top, should be True or False"
+ name = models.CharField(maxlength=30)
+
+ class Admin:
+ save_on_top = 'not True or False'
+
+# TODO: Add a save_on_top validator.
+#model_errors += """invalid_admin_options.saveontopbad: "admin.save_on_top", if given, must be set to True or False.
+#"""
+
+class ListSelectRelatedBad(models.Model):
+ "Test list_select_related, should be True or False"
+ name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_select_related = 'not True or False'
+
+# TODO: Add a list_select_related validator.
+#model_errors += """invalid_admin_options.listselectrelatebad: "admin.list_select_related", if given, must be set to True or False.
+#"""
+
+class ListPerPageBad(models.Model):
+ "Test list_per_page, should be a positive integer value."
+ name = models.CharField(maxlength=30)
+
+ class Admin:
+ list_per_page = 89.3
+
+# TODO: Add a list_per_page validator.
+#model_errors += """invalid_admin_options.listperpagebad: "admin.list_per_page", if given, must be a positive integer.
+#"""
+
+class FieldsBadOne(models.Model):
+ "Test fields, should be a tuple"
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ fields = 'not a tuple'
+
+# TODO: Add a fields validator.
+#model_errors += """invalid_admin_options.fieldsbadone: "admin.fields", if given, must be a tuple.
+#"""
+
+class FieldsBadTwo(models.Model):
+ """Test fields, 'fields' dict option is required."""
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ fields = ('Name', {'description': 'this fieldset needs fields'})
+
+# TODO: Add a fields validator.
+#model_errors += """invalid_admin_options.fieldsbadtwo: "admin.fields" each fieldset must include a 'fields' dict.
+#"""
+
+class FieldsBadThree(models.Model):
+ """Test fields, 'classes' and 'description' are the only allowable extra dict options."""
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ fields = ('Name', {'fields': ('first_name','last_name'),'badoption': 'verybadoption'})
+
+# TODO: Add a fields validator.
+#model_errors += """invalid_admin_options.fieldsbadthree: "admin.fields" fieldset options must be either 'classes' or 'description'.
+#"""
+
+class FieldsGood(models.Model):
+ "Test fields, working example"
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+ birth_day = models.DateField()
+
+ class Admin:
+ fields = (
+ ('Name', {'fields': ('first_name','last_name'),'classes': 'collapse'}),
+ (None, {'fields': ('birth_day',),'description': 'enter your b-day'})
+ )
+
+class OrderingBad(models.Model):
+ "Test ordering, must be a field."
+ first_name = models.CharField(maxlength=30)
+ last_name = models.CharField(maxlength=30)
+
+ class Admin:
+ ordering = 'nonexistent'
+
+# TODO: Add a ordering validator.
+#model_errors += """invalid_admin_options.orderingbad: "admin.ordering" refers to 'nonexistent', which isn't a field.
+#"""
+
+## TODO: Add a manager validator, this should fail gracefully.
+#class ManagerBad(models.Model):
+# "Test manager, must be a manager object."
+# first_name = models.CharField(maxlength=30)
+#
+# class Admin:
+# manager = 'nonexistent'
+#
+#model_errors += """invalid_admin_options.managerbad: "admin.manager" refers to 'nonexistent', which isn't a Manager().
+#""" \ No newline at end of file
diff --git a/tests/regressiontests/many_to_one_regress/models.py b/tests/regressiontests/many_to_one_regress/models.py
index 6c067446b1..8ddec98da4 100644
--- a/tests/regressiontests/many_to_one_regress/models.py
+++ b/tests/regressiontests/many_to_one_regress/models.py
@@ -1,13 +1,34 @@
from django.db import models
+# If ticket #1578 ever slips back in, these models will not be able to be
+# created (the field names being lower-cased versions of their opposite
+# classes is important here).
+
class First(models.Model):
second = models.IntegerField()
class Second(models.Model):
first = models.ForeignKey(First, related_name = 'the_first')
-# If ticket #1578 ever slips back in, these models will not be able to be
-# created (the field names being lower-cased versions of their opposite
-# classes is important here).
+# Protect against repetition of #1839, #2415 and #2536.
+class Third(models.Model):
+ name = models.CharField(maxlength=20)
+ third = models.ForeignKey('self', null=True, related_name='child_set')
+
+class Parent(models.Model):
+ name = models.CharField(maxlength=20)
+ bestchild = models.ForeignKey('Child', null=True, related_name='favored_by')
+
+class Child(models.Model):
+ name = models.CharField(maxlength=20)
+ parent = models.ForeignKey(Parent)
+
-__test__ = {'API_TESTS':""}
+__test__ = {'API_TESTS':"""
+>>> Third.AddManipulator().save(dict(id='3', name='An example', another=None))
+<Third: Third object>
+>>> parent = Parent(name = 'fred')
+>>> parent.save()
+>>> Child.AddManipulator().save(dict(name='bam-bam', parent=parent.id))
+<Child: Child object>
+"""}
diff --git a/tests/regressiontests/markup/tests.py b/tests/regressiontests/markup/tests.py
deleted file mode 100644
index bd3f52b9dd..0000000000
--- a/tests/regressiontests/markup/tests.py
+++ /dev/null
@@ -1,69 +0,0 @@
-# Quick tests for the markup templatetags (django.contrib.markup)
-
-from django.template import Template, Context, add_to_builtins
-import re
-import unittest
-
-add_to_builtins('django.contrib.markup.templatetags.markup')
-
-class Templates(unittest.TestCase):
- def test_textile(self):
- try:
- import textile
- except ImportError:
- textile = None
-
- textile_content = """Paragraph 1
-
-Paragraph 2 with "quotes" and @code@"""
-
- t = Template("{{ textile_content|textile }}")
- rendered = t.render(Context(locals())).strip()
- if textile:
- self.assertEqual(rendered, """<p>Paragraph 1</p>
-
-<p>Paragraph 2 with &#8220;quotes&#8221; and <code>code</code></p>""")
- else:
- self.assertEqual(rendered, textile_content)
-
- def test_markdown(self):
- try:
- import markdown
- except ImportError:
- markdown = None
-
- markdown_content = """Paragraph 1
-
-## An h2"""
-
- t = Template("{{ markdown_content|markdown }}")
- rendered = t.render(Context(locals())).strip()
- if markdown:
- pattern = re.compile("""<p>Paragraph 1\s*</p>\s*<h2>\s*An h2</h2>""")
- self.assert_(pattern.match(rendered))
- else:
- self.assertEqual(rendered, markdown_content)
-
- def test_docutils(self):
- try:
- import docutils
- except ImportError:
- docutils = None
-
- rest_content = """Paragraph 1
-
-Paragraph 2 with a link_
-
-.. _link: http://www.example.com/"""
-
- t = Template("{{ rest_content|restructuredtext }}")
- rendered = t.render(Context(locals())).strip()
- if docutils:
- self.assertEqual(rendered, """<p>Paragraph 1</p>
-<p>Paragraph 2 with a <a class="reference" href="http://www.example.com/">link</a></p>""")
- else:
- self.assertEqual(rendered, rest_content)
-
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py
index 09024f18c2..21944d9e7a 100644
--- a/tests/regressiontests/null_queries/models.py
+++ b/tests/regressiontests/null_queries/models.py
@@ -32,7 +32,7 @@ __test__ = {'API_TESTS':"""
>>> Choice.objects.filter(foo__exact=None)
Traceback (most recent call last):
...
-TypeError: Cannot resolve keyword 'foo' into field
+TypeError: Cannot resolve keyword 'foo' into field. Choices are: id, poll, choice
# Can't use None on anything other than __exact
>>> Choice.objects.filter(id__gt=None)
diff --git a/tests/regressiontests/serializers_regress/__init__.py b/tests/regressiontests/serializers_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/serializers_regress/__init__.py
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
new file mode 100644
index 0000000000..b441885f10
--- /dev/null
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -0,0 +1,211 @@
+"""
+A test spanning all the capabilities of all the serializers.
+
+This class sets up a model for each model field type
+(except for image types, because of the PIL dependency).
+"""
+
+from django.db import models
+from django.contrib.contenttypes import generic
+from django.contrib.contenttypes.models import ContentType
+
+# The following classes are for testing basic data
+# marshalling, including NULL values.
+
+class BooleanData(models.Model):
+ data = models.BooleanField(null=True)
+
+class CharData(models.Model):
+ data = models.CharField(maxlength=30, null=True)
+
+class DateData(models.Model):
+ data = models.DateField(null=True)
+
+class DateTimeData(models.Model):
+ data = models.DateTimeField(null=True)
+
+class DecimalData(models.Model):
+ data = models.DecimalField(null=True, decimal_places=3, max_digits=5)
+
+class EmailData(models.Model):
+ data = models.EmailField(null=True)
+
+class FileData(models.Model):
+ data = models.FileField(null=True, upload_to='/foo/bar')
+
+class FilePathData(models.Model):
+ data = models.FilePathField(null=True)
+
+class FloatData(models.Model):
+ data = models.FloatField(null=True)
+
+class IntegerData(models.Model):
+ data = models.IntegerField(null=True)
+
+# class ImageData(models.Model):
+# data = models.ImageField(null=True)
+
+class IPAddressData(models.Model):
+ data = models.IPAddressField(null=True)
+
+class NullBooleanData(models.Model):
+ data = models.NullBooleanField(null=True)
+
+class PhoneData(models.Model):
+ data = models.PhoneNumberField(null=True)
+
+class PositiveIntegerData(models.Model):
+ data = models.PositiveIntegerField(null=True)
+
+class PositiveSmallIntegerData(models.Model):
+ data = models.PositiveSmallIntegerField(null=True)
+
+class SlugData(models.Model):
+ data = models.SlugField(null=True)
+
+class SmallData(models.Model):
+ data = models.SmallIntegerField(null=True)
+
+class TextData(models.Model):
+ data = models.TextField(null=True)
+
+class TimeData(models.Model):
+ data = models.TimeField(null=True)
+
+class USStateData(models.Model):
+ data = models.USStateField(null=True)
+
+class XMLData(models.Model):
+ data = models.XMLField(null=True)
+
+class Tag(models.Model):
+ """A tag on an item."""
+ data = models.SlugField()
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+
+ content_object = generic.GenericForeignKey()
+
+ class Meta:
+ ordering = ["data"]
+
+class GenericData(models.Model):
+ data = models.CharField(maxlength=30)
+
+ tags = generic.GenericRelation(Tag)
+
+# The following test classes are all for validation
+# of related objects; in particular, forward, backward,
+# and self references.
+
+class Anchor(models.Model):
+ """This is a model that can be used as
+ something for other models to point at"""
+
+ data = models.CharField(maxlength=30)
+
+class UniqueAnchor(models.Model):
+ """This is a model that can be used as
+ something for other models to point at"""
+
+ data = models.CharField(unique=True, maxlength=30)
+
+class FKData(models.Model):
+ data = models.ForeignKey(Anchor, null=True)
+
+class M2MData(models.Model):
+ data = models.ManyToManyField(Anchor, null=True)
+
+class O2OData(models.Model):
+ data = models.OneToOneField(Anchor, null=True)
+
+class FKSelfData(models.Model):
+ data = models.ForeignKey('self', null=True)
+
+class M2MSelfData(models.Model):
+ data = models.ManyToManyField('self', null=True, symmetrical=False)
+
+
+class FKDataToField(models.Model):
+ data = models.ForeignKey(UniqueAnchor, null=True, to_field='data')
+
+class FKDataToO2O(models.Model):
+ data = models.ForeignKey(O2OData, null=True)
+
+# The following test classes are for validating the
+# deserialization of objects that use a user-defined
+# field as the primary key.
+# Some of these data types have been commented out
+# because they can't be used as a primary key on one
+# or all database backends.
+
+class BooleanPKData(models.Model):
+ data = models.BooleanField(primary_key=True)
+
+class CharPKData(models.Model):
+ data = models.CharField(maxlength=30, primary_key=True)
+
+# class DatePKData(models.Model):
+# data = models.DateField(primary_key=True)
+
+# class DateTimePKData(models.Model):
+# data = models.DateTimeField(primary_key=True)
+
+class DecimalPKData(models.Model):
+ data = models.DecimalField(primary_key=True, decimal_places=3, max_digits=5)
+
+class EmailPKData(models.Model):
+ data = models.EmailField(primary_key=True)
+
+class FilePKData(models.Model):
+ data = models.FileField(primary_key=True, upload_to='/foo/bar')
+
+class FilePathPKData(models.Model):
+ data = models.FilePathField(primary_key=True)
+
+class FloatPKData(models.Model):
+ data = models.FloatField(primary_key=True)
+
+class IntegerPKData(models.Model):
+ data = models.IntegerField(primary_key=True)
+
+# class ImagePKData(models.Model):
+# data = models.ImageField(primary_key=True)
+
+class IPAddressPKData(models.Model):
+ data = models.IPAddressField(primary_key=True)
+
+class NullBooleanPKData(models.Model):
+ data = models.NullBooleanField(primary_key=True)
+
+class PhonePKData(models.Model):
+ data = models.PhoneNumberField(primary_key=True)
+
+class PositiveIntegerPKData(models.Model):
+ data = models.PositiveIntegerField(primary_key=True)
+
+class PositiveSmallIntegerPKData(models.Model):
+ data = models.PositiveSmallIntegerField(primary_key=True)
+
+class SlugPKData(models.Model):
+ data = models.SlugField(primary_key=True)
+
+class SmallPKData(models.Model):
+ data = models.SmallIntegerField(primary_key=True)
+
+# class TextPKData(models.Model):
+# data = models.TextField(primary_key=True)
+
+# class TimePKData(models.Model):
+# data = models.TimeField(primary_key=True)
+
+class USStatePKData(models.Model):
+ data = models.USStateField(primary_key=True)
+
+# class XMLPKData(models.Model):
+# data = models.XMLField(primary_key=True)
+
+class ComplexModel(models.Model):
+ field1 = models.CharField(maxlength=10)
+ field2 = models.CharField(maxlength=10)
+ field3 = models.CharField(maxlength=10)
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
new file mode 100644
index 0000000000..1a144c8356
--- /dev/null
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -0,0 +1,320 @@
+"""
+A test spanning all the capabilities of all the serializers.
+
+This class defines sample data and a dynamically generated
+test case that is capable of testing the capabilities of
+the serializers. This includes all valid data values, plus
+forward, backwards and self references.
+"""
+
+
+import unittest, datetime
+from cStringIO import StringIO
+
+from django.utils.functional import curry
+from django.core import serializers
+from django.db import transaction
+from django.core import management
+
+from models import *
+try:
+ import decimal
+except ImportError:
+ from django.utils import _decimal as decimal
+
+# A set of functions that can be used to recreate
+# test data objects of various kinds
+def data_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.data = data
+ instance.save()
+ return instance
+
+def generic_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.data = data[0]
+ instance.save()
+ for tag in data[1:]:
+ instance.tags.create(data=tag)
+ return instance
+
+def fk_create(pk, klass, data):
+ instance = klass(id=pk)
+ setattr(instance, 'data_id', data)
+ instance.save()
+ return instance
+
+def m2m_create(pk, klass, data):
+ instance = klass(id=pk)
+ instance.save()
+ instance.data = data
+ return instance
+
+def o2o_create(pk, klass, data):
+ instance = klass()
+ instance.data_id = data
+ instance.save()
+ return instance
+
+def pk_create(pk, klass, data):
+ instance = klass()
+ instance.data = data
+ instance.save()
+ return instance
+
+# A set of functions that can be used to compare
+# test data objects of various kinds
+def data_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, instance.data,
+ "Objects with PK=%d not equal; expected '%s' (%s), got '%s' (%s)" % (pk,data, type(data), instance.data, type(instance.data)))
+
+def generic_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data[0], instance.data)
+ testcase.assertEqual(data[1:], [t.data for t in instance.tags.all()])
+
+def fk_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, instance.data_id)
+
+def m2m_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(id=pk)
+ testcase.assertEqual(data, [obj.id for obj in instance.data.all()])
+
+def o2o_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(data=data)
+ testcase.assertEqual(data, instance.data_id)
+
+def pk_compare(testcase, pk, klass, data):
+ instance = klass.objects.get(data=data)
+ testcase.assertEqual(data, instance.data)
+
+# Define some data types. Each data type is
+# actually a pair of functions; one to create
+# and one to compare objects of that type
+data_obj = (data_create, data_compare)
+generic_obj = (generic_create, generic_compare)
+fk_obj = (fk_create, fk_compare)
+m2m_obj = (m2m_create, m2m_compare)
+o2o_obj = (o2o_create, o2o_compare)
+pk_obj = (pk_create, pk_compare)
+
+test_data = [
+ # Format: (data type, PK value, Model Class, data)
+ (data_obj, 1, BooleanData, True),
+ (data_obj, 2, BooleanData, False),
+ (data_obj, 10, CharData, "Test Char Data"),
+ (data_obj, 11, CharData, ""),
+ (data_obj, 12, CharData, "None"),
+ (data_obj, 13, CharData, "null"),
+ (data_obj, 14, CharData, "NULL"),
+ (data_obj, 15, CharData, None),
+ (data_obj, 20, DateData, datetime.date(2006,6,16)),
+ (data_obj, 21, DateData, None),
+ (data_obj, 30, DateTimeData, datetime.datetime(2006,6,16,10,42,37)),
+ (data_obj, 31, DateTimeData, None),
+ (data_obj, 40, EmailData, "hovercraft@example.com"),
+ (data_obj, 41, EmailData, None),
+ (data_obj, 50, FileData, 'file:///foo/bar/whiz.txt'),
+ (data_obj, 51, FileData, None),
+ (data_obj, 60, FilePathData, "/foo/bar/whiz.txt"),
+ (data_obj, 61, FilePathData, None),
+ (data_obj, 70, DecimalData, decimal.Decimal('12.345')),
+ (data_obj, 71, DecimalData, decimal.Decimal('-12.345')),
+ (data_obj, 72, DecimalData, decimal.Decimal('0.0')),
+ (data_obj, 73, DecimalData, None),
+ (data_obj, 74, FloatData, 12.345),
+ (data_obj, 75, FloatData, -12.345),
+ (data_obj, 76, FloatData, 0.0),
+ (data_obj, 77, FloatData, None),
+ (data_obj, 80, IntegerData, 123456789),
+ (data_obj, 81, IntegerData, -123456789),
+ (data_obj, 82, IntegerData, 0),
+ (data_obj, 83, IntegerData, None),
+ #(XX, ImageData
+ (data_obj, 90, IPAddressData, "127.0.0.1"),
+ (data_obj, 91, IPAddressData, None),
+ (data_obj, 100, NullBooleanData, True),
+ (data_obj, 101, NullBooleanData, False),
+ (data_obj, 102, NullBooleanData, None),
+ (data_obj, 110, PhoneData, "212-634-5789"),
+ (data_obj, 111, PhoneData, None),
+ (data_obj, 120, PositiveIntegerData, 123456789),
+ (data_obj, 121, PositiveIntegerData, None),
+ (data_obj, 130, PositiveSmallIntegerData, 12),
+ (data_obj, 131, PositiveSmallIntegerData, None),
+ (data_obj, 140, SlugData, "this-is-a-slug"),
+ (data_obj, 141, SlugData, None),
+ (data_obj, 150, SmallData, 12),
+ (data_obj, 151, SmallData, -12),
+ (data_obj, 152, SmallData, 0),
+ (data_obj, 153, SmallData, None),
+ (data_obj, 160, TextData, """This is a long piece of text.
+It contains line breaks.
+Several of them.
+The end."""),
+ (data_obj, 161, TextData, ""),
+ (data_obj, 162, TextData, None),
+ (data_obj, 170, TimeData, datetime.time(10,42,37)),
+ (data_obj, 171, TimeData, None),
+ (data_obj, 180, USStateData, "MA"),
+ (data_obj, 181, USStateData, None),
+ (data_obj, 190, XMLData, "<foo></foo>"),
+ (data_obj, 191, XMLData, None),
+
+ (generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']),
+ (generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']),
+
+ (data_obj, 300, Anchor, "Anchor 1"),
+ (data_obj, 301, Anchor, "Anchor 2"),
+ (data_obj, 302, UniqueAnchor, "UAnchor 1"),
+
+ (fk_obj, 400, FKData, 300), # Post reference
+ (fk_obj, 401, FKData, 500), # Pre reference
+ (fk_obj, 402, FKData, None), # Empty reference
+
+ (m2m_obj, 410, M2MData, []), # Empty set
+ (m2m_obj, 411, M2MData, [300,301]), # Post reference
+ (m2m_obj, 412, M2MData, [500,501]), # Pre reference
+ (m2m_obj, 413, M2MData, [300,301,500,501]), # Pre and Post reference
+
+ (o2o_obj, None, O2OData, 300), # Post reference
+ (o2o_obj, None, O2OData, 500), # Pre reference
+
+ (fk_obj, 430, FKSelfData, 431), # Pre reference
+ (fk_obj, 431, FKSelfData, 430), # Post reference
+ (fk_obj, 432, FKSelfData, None), # Empty reference
+
+ (m2m_obj, 440, M2MSelfData, []),
+ (m2m_obj, 441, M2MSelfData, []),
+ (m2m_obj, 442, M2MSelfData, [440, 441]),
+ (m2m_obj, 443, M2MSelfData, [445, 446]),
+ (m2m_obj, 444, M2MSelfData, [440, 441, 445, 446]),
+ (m2m_obj, 445, M2MSelfData, []),
+ (m2m_obj, 446, M2MSelfData, []),
+
+ (fk_obj, 450, FKDataToField, "UAnchor 1"),
+ (fk_obj, 451, FKDataToField, "UAnchor 2"),
+ (fk_obj, 452, FKDataToField, None),
+
+ (fk_obj, 460, FKDataToO2O, 300),
+
+ (data_obj, 500, Anchor, "Anchor 3"),
+ (data_obj, 501, Anchor, "Anchor 4"),
+ (data_obj, 502, UniqueAnchor, "UAnchor 2"),
+
+ (pk_obj, 601, BooleanPKData, True),
+ (pk_obj, 602, BooleanPKData, False),
+ (pk_obj, 610, CharPKData, "Test Char PKData"),
+# (pk_obj, 620, DatePKData, datetime.date(2006,6,16)),
+# (pk_obj, 630, DateTimePKData, datetime.datetime(2006,6,16,10,42,37)),
+ (pk_obj, 640, EmailPKData, "hovercraft@example.com"),
+ (pk_obj, 650, FilePKData, 'file:///foo/bar/whiz.txt'),
+ (pk_obj, 660, FilePathPKData, "/foo/bar/whiz.txt"),
+ (pk_obj, 670, DecimalPKData, decimal.Decimal('12.345')),
+ (pk_obj, 671, DecimalPKData, decimal.Decimal('-12.345')),
+ (pk_obj, 672, DecimalPKData, decimal.Decimal('0.0')),
+ (pk_obj, 673, FloatPKData, 12.345),
+ (pk_obj, 674, FloatPKData, -12.345),
+ (pk_obj, 675, FloatPKData, 0.0),
+ (pk_obj, 680, IntegerPKData, 123456789),
+ (pk_obj, 681, IntegerPKData, -123456789),
+ (pk_obj, 682, IntegerPKData, 0),
+# (XX, ImagePKData
+ (pk_obj, 690, IPAddressPKData, "127.0.0.1"),
+ (pk_obj, 700, NullBooleanPKData, True),
+ (pk_obj, 701, NullBooleanPKData, False),
+ (pk_obj, 710, PhonePKData, "212-634-5789"),
+ (pk_obj, 720, PositiveIntegerPKData, 123456789),
+ (pk_obj, 730, PositiveSmallIntegerPKData, 12),
+ (pk_obj, 740, SlugPKData, "this-is-a-slug"),
+ (pk_obj, 750, SmallPKData, 12),
+ (pk_obj, 751, SmallPKData, -12),
+ (pk_obj, 752, SmallPKData, 0),
+# (pk_obj, 760, TextPKData, """This is a long piece of text.
+# It contains line breaks.
+# Several of them.
+# The end."""),
+# (pk_obj, 770, TimePKData, datetime.time(10,42,37)),
+ (pk_obj, 780, USStatePKData, "MA"),
+# (pk_obj, 790, XMLPKData, "<foo></foo>"),
+]
+
+# Dynamically create serializer tests to ensure that all
+# registered serializers are automatically tested.
+class SerializerTests(unittest.TestCase):
+ pass
+
+def serializerTest(format, self):
+ # Clear the database first
+ management.flush(verbosity=0, interactive=False)
+
+ # Create all the objects defined in the test data
+ objects = []
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+ for (func, pk, klass, datum) in test_data:
+ objects.append(func[0](pk, klass, datum))
+ transaction.commit()
+ transaction.leave_transaction_management()
+
+ # Add the generic tagged objects to the object list
+ objects.extend(Tag.objects.all())
+
+ # Serialize the test database
+ serialized_data = serializers.serialize(format, objects, indent=2)
+
+ # Flush the database and recreate from the serialized data
+ management.flush(verbosity=0, interactive=False)
+ transaction.enter_transaction_management()
+ transaction.managed(True)
+ for obj in serializers.deserialize(format, serialized_data):
+ obj.save()
+ transaction.commit()
+ transaction.leave_transaction_management()
+
+ # Assert that the deserialized data is the same
+ # as the original source
+ for (func, pk, klass, datum) in test_data:
+ func[1](self, pk, klass, datum)
+
+def fieldsTest(format, self):
+ # Clear the database first
+ management.flush(verbosity=0, interactive=False)
+
+ obj = ComplexModel(field1='first',field2='second',field3='third')
+ obj.save()
+
+ # Serialize then deserialize the test database
+ serialized_data = serializers.serialize(format, [obj], indent=2, fields=('field1','field3'))
+ result = serializers.deserialize(format, serialized_data).next()
+
+ # Check that the deserialized object contains data in only the serialized fields.
+ self.assertEqual(result.object.field1, 'first')
+ self.assertEqual(result.object.field2, '')
+ self.assertEqual(result.object.field3, 'third')
+
+def streamTest(format, self):
+ # Clear the database first
+ management.flush(verbosity=0, interactive=False)
+
+ obj = ComplexModel(field1='first',field2='second',field3='third')
+ obj.save()
+
+ # Serialize the test database to a stream
+ stream = StringIO()
+ serializers.serialize(format, [obj], indent=2, stream=stream)
+
+ # Serialize normally for a comparison
+ string_data = serializers.serialize(format, [obj], indent=2)
+
+ # Check that the two are the same
+ self.assertEqual(string_data, stream.getvalue())
+ stream.close()
+
+for format in serializers.get_serializer_formats():
+ setattr(SerializerTests, 'test_'+format+'_serializer', curry(serializerTest, format))
+ setattr(SerializerTests, 'test_'+format+'_serializer_fields', curry(fieldsTest, format))
+ if format != 'python':
+ setattr(SerializerTests, 'test_'+format+'_serializer_stream', curry(streamTest, format))
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 0a41f5b5b7..e983a539ae 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -127,61 +127,97 @@ class Templates(unittest.TestCase):
# Fail silently when accessing a non-simple method
'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")),
+ # Don't get confused when parsing something that is almost, but not
+ # quite, a template tag.
+ 'basic-syntax21': ("a {{ moo %} b", {}, "a {{ moo %} b"),
+ 'basic-syntax22': ("{{ moo #}", {}, "{{ moo #}"),
+
+ # Will try to treat "moo #} {{ cow" as the variable. Not ideal, but
+ # costly to work around, so this triggers an error.
+ 'basic-syntax23': ("{{ moo #} {{ cow }}", {"cow": "cow"}, template.TemplateSyntaxError),
+
+ # Embedded newlines make it not-a-tag.
+ 'basic-syntax24': ("{{ moo\n }}", {}, "{{ moo\n }}"),
+
+ # List-index syntax allows a template to access a certain item of a subscriptable object.
+ 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
+
+ # Fail silently when the list index is out of range.
+ 'list-index02': ("{{ var.5 }}", {"var": ["first item", "second item"]}, ("", "INVALID")),
+
+ # Fail silently when the variable is not a subscriptable object.
+ 'list-index03': ("{{ var.1 }}", {"var": None}, ("", "INVALID")),
+
+ # Fail silently when variable is a dict without the specified key.
+ 'list-index04': ("{{ var.1 }}", {"var": {}}, ("", "INVALID")),
+
+ # Dictionary lookup wins out when dict's key is a string.
+ 'list-index05': ("{{ var.1 }}", {"var": {'1': "hello"}}, "hello"),
+
+ # But list-index lookup wins out when dict's key is an int, which
+ # behind the scenes is really a dictionary lookup (for a dict)
+ # after converting the key to an int.
+ 'list-index06': ("{{ var.1 }}", {"var": {1: "hello"}}, "hello"),
+
+ # Dictionary lookup wins out when there is a string and int version of the key.
+ 'list-index07': ("{{ var.1 }}", {"var": {'1': "hello", 1: "world"}}, "hello"),
+
# Basic filter usage
- 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
+ 'filter-syntax01': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"),
# Chained filters
- 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
+ 'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"),
# Raise TemplateSyntaxError for space between a variable and filter pipe
- 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
+ 'filter-syntax03': ("{{ var |upper }}", {}, template.TemplateSyntaxError),
# Raise TemplateSyntaxError for space after a filter pipe
- 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
+ 'filter-syntax04': ("{{ var| upper }}", {}, template.TemplateSyntaxError),
# Raise TemplateSyntaxError for a nonexistent filter
- 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
+ 'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError),
# Raise TemplateSyntaxError when trying to access a filter containing an illegal character
- 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
+ 'filter-syntax06': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError),
# Raise TemplateSyntaxError for invalid block tags
- 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
+ 'filter-syntax07': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError),
# Raise TemplateSyntaxError for empty block tags
- 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError),
+ 'filter-syntax08': ("{% %}", {}, template.TemplateSyntaxError),
# Chained filters, with an argument to the first one
- 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
+ 'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
# Escaped string as argument
- 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
+ 'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
# Variable as argument
- 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
+ 'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
# Default argument testing
- 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
+ 'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'),
- # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute
- 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
+ # Fail silently for methods that raise an exception with a
+ # "silent_variable_failure" attribute
+ 'filter-syntax13': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
- # In methods that raise an exception without a "silent_variable_attribute" set to True,
- # the exception propogates
- 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
+ # In methods that raise an exception without a
+ # "silent_variable_attribute" set to True, the exception propagates
+ 'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException),
# Escaped backslash in argument
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
+ 'filter-syntax15': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'),
# Escaped backslash using known escape char
- 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
+ 'filter-syntax16': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'),
# Empty strings can be passed as arguments to filters
- 'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
+ 'filter-syntax17': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
- # If a variable has a __str__() that returns a Unicode object, the value
- # will be converted to a bytestring.
- 'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
+ # If a variable has a __str__() that returns a Unicode object, the
+ # value will be converted to a bytestring.
+ 'filter-syntax18': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
### COMMENT SYNTAX ########################################################
'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
@@ -236,6 +272,7 @@ class Templates(unittest.TestCase):
'filter01': ('{% filter upper %}{% endfilter %}', {}, ''),
'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'),
'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'),
+ 'filter04': ('{% filter cut:remove %}djangospam{% endfilter %}', {'remove': 'spam'}, 'django'),
### FIRSTOF TAG ###########################################################
'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''),
@@ -252,6 +289,20 @@ class Templates(unittest.TestCase):
'for-tag-vars02': ("{% for val in values %}{{ forloop.counter0 }}{% endfor %}", {"values": [6, 6, 6]}, "012"),
'for-tag-vars03': ("{% for val in values %}{{ forloop.revcounter }}{% endfor %}", {"values": [6, 6, 6]}, "321"),
'for-tag-vars04': ("{% for val in values %}{{ forloop.revcounter0 }}{% endfor %}", {"values": [6, 6, 6]}, "210"),
+ 'for-tag-unpack01': ("{% for key,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack03': ("{% for key, value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack04': ("{% for key , value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack05': ("{% for key ,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ 'for-tag-unpack06': ("{% for key value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ 'for-tag-unpack07': ("{% for key,,value in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ 'for-tag-unpack08': ("{% for key,value, in items %}{{ key }}:{{ value }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, template.TemplateSyntaxError),
+ # Ensure that a single loopvar doesn't truncate the list in val.
+ 'for-tag-unpack09': ("{% for val in items %}{{ val.0 }}:{{ val.1 }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, "one:1/two:2/"),
+ # Otherwise, silently truncate if the length of loopvars differs to the length of each set of items.
+ 'for-tag-unpack10': ("{% for x,y in items %}{{ x }}:{{ y }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'orange'))}, "one:1/two:2/"),
+ 'for-tag-unpack11': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1), ('two', 2))}, ("one:1,/two:2,/", "one:1,INVALID/two:2,INVALID/")),
+ 'for-tag-unpack12': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2))}, ("one:1,carrot/two:2,/", "one:1,carrot/two:2,INVALID/")),
+ 'for-tag-unpack13': ("{% for x,y,z in items %}{{ x }}:{{ y }},{{ z }}/{% endfor %}", {"items": (('one', 1, 'carrot'), ('two', 2, 'cheese'))}, ("one:1,carrot/two:2,cheese/", "one:1,carrot/two:2,cheese/")),
### IF TAG ################################################################
'if-tag01': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": True}, "yes"),
@@ -378,6 +429,20 @@ class Templates(unittest.TestCase):
'ifequal-split09': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slash\man"}, "yes"),
'ifequal-split10': (r"{% ifequal a 'slash\man' %}yes{% else %}no{% endifequal %}", {'a': r"slashman"}, "no"),
+ # NUMERIC RESOLUTION
+ 'ifequal-numeric01': ('{% ifequal x 5 %}yes{% endifequal %}', {'x': '5'}, ''),
+ 'ifequal-numeric02': ('{% ifequal x 5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
+ 'ifequal-numeric03': ('{% ifequal x 5.2 %}yes{% endifequal %}', {'x': 5}, ''),
+ 'ifequal-numeric04': ('{% ifequal x 5.2 %}yes{% endifequal %}', {'x': 5.2}, 'yes'),
+ 'ifequal-numeric05': ('{% ifequal x 0.2 %}yes{% endifequal %}', {'x': .2}, 'yes'),
+ 'ifequal-numeric06': ('{% ifequal x .2 %}yes{% endifequal %}', {'x': .2}, 'yes'),
+ 'ifequal-numeric07': ('{% ifequal x 2. %}yes{% endifequal %}', {'x': 2}, ''),
+ 'ifequal-numeric08': ('{% ifequal x "5" %}yes{% endifequal %}', {'x': 5}, ''),
+ 'ifequal-numeric09': ('{% ifequal x "5" %}yes{% endifequal %}', {'x': '5'}, 'yes'),
+ 'ifequal-numeric10': ('{% ifequal x -5 %}yes{% endifequal %}', {'x': -5}, 'yes'),
+ 'ifequal-numeric11': ('{% ifequal x -5.2 %}yes{% endifequal %}', {'x': -5.2}, 'yes'),
+ 'ifequal-numeric12': ('{% ifequal x +5 %}yes{% endifequal %}', {'x': 5}, 'yes'),
+
### IFNOTEQUAL TAG ########################################################
'ifnotequal01': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 2}, "yes"),
'ifnotequal02': ("{% ifnotequal a b %}yes{% endifnotequal %}", {"a": 1, "b": 1}, ""),
@@ -390,6 +455,21 @@ class Templates(unittest.TestCase):
'include03': ('{% include template_name %}', {'template_name': 'basic-syntax02', 'headline': 'Included'}, "Included"),
'include04': ('a{% include "nonexistent" %}b', {}, "ab"),
+ ### NAMED ENDBLOCKS #######################################################
+
+ # Basic test
+ 'namedendblocks01': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock first %}3", {}, '1_2_3'),
+
+ # Unbalanced blocks
+ 'namedendblocks02': ("1{% block first %}_{% block second %}2{% endblock first %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks03': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock second %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks04': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock third %}3", {}, template.TemplateSyntaxError),
+ 'namedendblocks05': ("1{% block first %}_{% block second %}2{% endblock first %}", {}, template.TemplateSyntaxError),
+
+ # Mixed named and unnamed endblocks
+ 'namedendblocks06': ("1{% block first %}_{% block second %}2{% endblock %}_{% endblock first %}3", {}, '1_2_3'),
+ 'namedendblocks07': ("1{% block first %}_{% block second %}2{% endblock second %}_{% endblock %}3", {}, '1_2_3'),
+
### INHERITANCE ###########################################################
# Standard template with no inheritance
@@ -470,8 +550,8 @@ class Templates(unittest.TestCase):
### I18N ##################################################################
# {% spaceless %} tag
- 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
- 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b> <i> text </i> </b>"),
+ 'spaceless01': ("{% spaceless %} <b> <i> text </i> </b> {% endspaceless %}", {}, "<b><i> text </i></b>"),
+ 'spaceless02': ("{% spaceless %} <b> \n <i> text </i> \n </b> {% endspaceless %}", {}, "<b><i> text </i></b>"),
'spaceless03': ("{% spaceless %}<b><i>text</i></b>{% endspaceless %}", {}, "<b><i>text</i></b>"),
# simple translation of a string delimited by '
@@ -520,6 +600,8 @@ class Templates(unittest.TestCase):
'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''),
'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'),
'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'),
+ 'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')),
+ 'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')),
### MULTILINE #############################################################
@@ -598,6 +680,13 @@ class Templates(unittest.TestCase):
'widthratio09': ('{% widthratio a b %}', {'a':50,'b':100}, template.TemplateSyntaxError),
'widthratio10': ('{% widthratio a b 100.0 %}', {'a':50,'b':100}, template.TemplateSyntaxError),
+ ### WITH TAG ########################################################
+ 'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'),
+ 'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')),
+
+ 'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
+ 'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
+
### NOW TAG ########################################################
# Simple case
'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)),
@@ -630,6 +719,18 @@ class Templates(unittest.TestCase):
# Compare to a given parameter
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
+
+ ### URL TAG ########################################################
+ # Successes
+ 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
+ 'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'),
+ 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'),
+ 'url04' : ('{% url named-client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'),
+
+ # Failures
+ 'url-fail01' : ('{% url %}', {}, template.TemplateSyntaxError),
+ 'url-fail02' : ('{% url no_such_view %}', {}, ''),
+ 'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
}
# Register our custom template loader.
@@ -652,6 +753,7 @@ class Templates(unittest.TestCase):
# Set TEMPLATE_STRING_IF_INVALID to a known string
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
+ expected_invalid_str = 'INVALID'
for name, vals in tests:
install()
@@ -659,6 +761,10 @@ class Templates(unittest.TestCase):
if isinstance(vals[2], tuple):
normal_string_result = vals[2][0]
invalid_string_result = vals[2][1]
+ if '%s' in invalid_string_result:
+ expected_invalid_str = 'INVALID %s'
+ invalid_string_result = invalid_string_result % vals[2][2]
+ template.invalid_var_format_string = True
else:
normal_string_result = vals[2]
invalid_string_result = vals[2]
@@ -669,7 +775,7 @@ class Templates(unittest.TestCase):
activate('en-us')
for invalid_str, result in [('', normal_string_result),
- ('INVALID', invalid_string_result)]:
+ (expected_invalid_str, invalid_string_result)]:
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
try:
output = loader.get_template(name).render(template.Context(vals[1]))
@@ -683,6 +789,10 @@ class Templates(unittest.TestCase):
if 'LANGUAGE_CODE' in vals[1]:
deactivate()
+ if template.invalid_var_format_string:
+ expected_invalid_str = 'INVALID'
+ template.invalid_var_format_string = False
+
loader.template_source_loaders = old_template_loaders
deactivate()
settings.TEMPLATE_DEBUG = old_td
diff --git a/tests/regressiontests/templates/urls.py b/tests/regressiontests/templates/urls.py
new file mode 100644
index 0000000000..eaa9fd5d9f
--- /dev/null
+++ b/tests/regressiontests/templates/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls.defaults import *
+from regressiontests.templates import views
+
+urlpatterns = patterns('',
+
+ # Test urls for testing reverse lookups
+ (r'^$', views.index),
+ (r'^client/(\d+)/$', views.client),
+ (r'^client/(\d+)/(?P<action>[^/]+)/$', views.client_action),
+ url(r'^named-client/(\d+)/$', views.client, name="named-client"),
+)
diff --git a/tests/regressiontests/templates/views.py b/tests/regressiontests/templates/views.py
new file mode 100644
index 0000000000..b68809944a
--- /dev/null
+++ b/tests/regressiontests/templates/views.py
@@ -0,0 +1,10 @@
+# Fake views for testing url reverse lookup
+
+def index(request):
+ pass
+
+def client(request, id):
+ pass
+
+def client_action(request, id, action):
+ pass
diff --git a/tests/regressiontests/test_client_regress/__init__.py b/tests/regressiontests/test_client_regress/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/test_client_regress/__init__.py
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
new file mode 100644
index 0000000000..40d022a47a
--- /dev/null
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -0,0 +1,164 @@
+"""
+Regression tests for the Test Client, especially the customized assertions.
+
+"""
+from django.test import Client, TestCase
+from django.core import mail
+
+class AssertTemplateUsedTests(TestCase):
+ fixtures = ['testdata.json']
+
+ def test_no_context(self):
+ "Template usage assertions work then templates aren't in use"
+ response = self.client.get('/test_client_regress/no_template_view/')
+
+ # Check that the no template case doesn't mess with the template assertions
+ self.assertTemplateNotUsed(response, 'GET Template')
+
+ try:
+ self.assertTemplateUsed(response, 'GET Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "No templates used to render the response")
+
+ def test_single_context(self):
+ "Template assertions work when there is a single context"
+ response = self.client.get('/test_client/post_view/', {})
+
+ #
+ try:
+ self.assertTemplateNotUsed(response, 'Empty GET Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateUsed(response, 'Empty POST Template')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Empty POST Template' was not used to render the response. Actual template was 'Empty GET Template'")
+
+ def test_multiple_context(self):
+ "Template assertions work when there are multiple contexts"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'foo@example.com',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view_with_template/', post_data)
+ self.assertContains(response, 'POST data OK')
+ try:
+ self.assertTemplateNotUsed(response, "form_view.html")
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateNotUsed(response, 'base.html')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
+
+ try:
+ self.assertTemplateUsed(response, "Valid POST Template")
+ except AssertionError, e:
+ self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: ['form_view.html', 'base.html']")
+
+class AssertRedirectsTests(TestCase):
+ def test_redirect_page(self):
+ "An assertion is raised if the original page couldn't be retrieved as expected"
+ # This page will redirect with code 301, not 302
+ response = self.client.get('/test_client/permanent_redirect_view/')
+ try:
+ self.assertRedirects(response, '/test_client/get_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Reponse code was 301 (expected 302)")
+
+ def test_incorrect_target(self):
+ "An assertion is raised if the response redirects to another target"
+ response = self.client.get('/test_client/permanent_redirect_view/')
+ try:
+ # Should redirect to get_view
+ self.assertRedirects(response, '/test_client/some_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Response didn't redirect as expected: Reponse code was 301 (expected 302)")
+
+ def test_target_page(self):
+ "An assertion is raised if the reponse redirect target cannot be retrieved as expected"
+ response = self.client.get('/test_client/double_redirect_view/')
+ try:
+ # The redirect target responds with a 301 code, not 200
+ self.assertRedirects(response, '/test_client/permanent_redirect_view/')
+ except AssertionError, e:
+ self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
+
+class AssertFormErrorTests(TestCase):
+ def test_unknown_form(self):
+ "An assertion is raised if the form name is unknown"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
+
+ def test_unknown_field(self):
+ "An assertion is raised if the field name is unknown"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'some_field', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
+
+ def test_noerror_field(self):
+ "An assertion is raised if the field doesn't have any errors"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'value', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
+
+ def test_unknown_error(self):
+ "An assertion is raised if the field doesn't contain the provided error"
+ post_data = {
+ 'text': 'Hello World',
+ 'email': 'not an email address',
+ 'value': 37,
+ 'single': 'b',
+ 'multi': ('b','c','e')
+ }
+ response = self.client.post('/test_client/form_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+
+ try:
+ self.assertFormError(response, 'form', 'email', 'Some error.')
+ except AssertionError, e:
+ self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
+
diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py
new file mode 100644
index 0000000000..e9cd0aea15
--- /dev/null
+++ b/tests/regressiontests/test_client_regress/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls.defaults import *
+from django.views.generic.simple import redirect_to
+import views
+
+urlpatterns = patterns('',
+ (r'^no_template_view/$', views.no_template_view),
+)
diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py
new file mode 100644
index 0000000000..d8dd2b349c
--- /dev/null
+++ b/tests/regressiontests/test_client_regress/views.py
@@ -0,0 +1,8 @@
+from django.core.mail import EmailMessage, SMTPConnection
+from django.http import HttpResponse
+from django.shortcuts import render_to_response
+
+def no_template_view(request):
+ "A simple view that expects a GET request, and returns a rendered template"
+ return HttpResponse("No template used")
+
diff --git a/tests/regressiontests/text/__init__.py b/tests/regressiontests/text/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/text/__init__.py
diff --git a/tests/regressiontests/text/models.py b/tests/regressiontests/text/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/text/models.py
diff --git a/tests/regressiontests/text/tests.py b/tests/regressiontests/text/tests.py
new file mode 100644
index 0000000000..f758ecaf90
--- /dev/null
+++ b/tests/regressiontests/text/tests.py
@@ -0,0 +1,17 @@
+"""
+# Tests for stuff in django.utils.text.
+
+>>> from django.utils.text import *
+
+### smart_split ###########################################################
+>>> list(smart_split(r'''This is "a person" test.'''))
+['This', 'is', '"a person"', 'test.']
+>>> print list(smart_split(r'''This is "a person's" test.'''))[2]
+"a person's"
+>>> print list(smart_split(r'''This is "a person\\"s" test.'''))[2]
+"a person"s"
+>>> list(smart_split('''"a 'one'''))
+['"a', "'one"]
+>>> print list(smart_split(r'''all friends' tests'''))[1]
+friends'
+"""