summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-02-02 17:35:55 +0000
committerBoulder Sprinters <boulder-sprinters@djangoproject.com>2007-02-02 17:35:55 +0000
commite17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (patch)
tree49a5a779e1278eca17fffe81a83fce55fb35ce46 /tests
parent92b7851424069336f76112932682c77a6a1e3cb9 (diff)
boulder-oracle-sprint: Merged to trunk [4455].
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4456 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/custom_columns/models.py92
-rw-r--r--tests/modeltests/generic_relations/models.py34
-rw-r--r--tests/modeltests/lookup/models.py15
-rw-r--r--tests/modeltests/many_to_many/models.py14
-rw-r--r--tests/modeltests/model_forms/models.py135
-rw-r--r--tests/modeltests/or_lookups/models.py15
-rw-r--r--tests/modeltests/serializers/models.py21
-rw-r--r--tests/modeltests/test_client/views.py2
-rw-r--r--tests/regressiontests/forms/tests.py521
9 files changed, 781 insertions, 68 deletions
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py
index e88fa80da2..c09ca05557 100644
--- a/tests/modeltests/custom_columns/models.py
+++ b/tests/modeltests/custom_columns/models.py
@@ -1,53 +1,105 @@
"""
-17. Custom column names
+17. Custom column/table names
If your database column name is different than your model attribute, use the
``db_column`` parameter. Note that you'll use the field's name, not its column
name, in API usage.
+
+If your database table name is different than your model name, use the
+``db_table`` Meta attribute. This has no effect on the API used to
+query the database.
+
+If you need to use a table name for a many-to-many relationship that differs
+from the default generated name, use the ``db_table`` parameter on the
+ManyToMany field. This has no effect on the API for querying the database.
+
"""
from django.db import models
-class Person(models.Model):
+class Author(models.Model):
first_name = models.CharField(maxlength=30, db_column='firstname')
last_name = models.CharField(maxlength=30, db_column='last')
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
+ class Meta:
+ db_table = 'my_author_table'
+ ordering = ('last_name','first_name')
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=100)
+ authors = models.ManyToManyField(Author, db_table='my_m2m_table')
+
+ def __str__(self):
+ return self.headline
+
+ class Meta:
+ ordering = ('headline',)
+
__test__ = {'API_TESTS':"""
-# Create a Person.
->>> p = Person(first_name='John', last_name='Smith')
->>> p.save()
+# Create a Author.
+>>> a = Author(first_name='John', last_name='Smith')
+>>> a.save()
->>> p.id
+>>> a.id
1
->>> Person.objects.all()
-[<Person: John Smith>]
+# Create another author
+>>> a2 = Author(first_name='Peter', last_name='Jones')
+>>> a2.save()
+
+# Create an article
+>>> art = Article(headline='Django lets you build web apps easily')
+>>> art.save()
+>>> art.authors = [a, a2]
->>> Person.objects.filter(first_name__exact='John')
-[<Person: John Smith>]
+# Although the table and column names on Author have been set to
+# custom values, nothing about using the Author model has changed...
->>> Person.objects.get(first_name__exact='John')
-<Person: John Smith>
+# Query the available authors
+>>> Author.objects.all()
+[<Author: Peter Jones>, <Author: John Smith>]
->>> Person.objects.filter(firstname__exact='John')
+>>> Author.objects.filter(first_name__exact='John')
+[<Author: John Smith>]
+
+>>> Author.objects.get(first_name__exact='John')
+<Author: John Smith>
+
+>>> Author.objects.filter(firstname__exact='John')
Traceback (most recent call last):
...
TypeError: Cannot resolve keyword 'firstname' into field
->>> p = Person.objects.get(last_name__exact='Smith')
->>> p.first_name
+>>> a = Author.objects.get(last_name__exact='Smith')
+>>> a.first_name
'John'
->>> p.last_name
+>>> a.last_name
'Smith'
->>> p.firstname
+>>> a.firstname
Traceback (most recent call last):
...
-AttributeError: 'Person' object has no attribute 'firstname'
->>> p.last
+AttributeError: 'Author' object has no attribute 'firstname'
+>>> a.last
Traceback (most recent call last):
...
-AttributeError: 'Person' object has no attribute 'last'
+AttributeError: 'Author' object has no attribute 'last'
+
+# Although the Article table uses a custom m2m table,
+# nothing about using the m2m relationship has changed...
+
+# Get all the authors for an article
+>>> art.authors.all()
+[<Author: Peter Jones>, <Author: John Smith>]
+
+# Get the articles for an author
+>>> a.article_set.all()
+[<Article: Django lets you build web apps easily>]
+
+# Query the authors across the m2m relation
+>>> art.authors.filter(last_name='Jones')
+[<Author: Peter Jones>]
+
"""}
diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py
index eb64d7ec3d..2bfb55e618 100644
--- a/tests/modeltests/generic_relations/models.py
+++ b/tests/modeltests/generic_relations/models.py
@@ -65,14 +65,14 @@ __test__ = {'API_TESTS':"""
# Objects with declared GenericRelations can be tagged directly -- the API
# mimics the many-to-many API.
->>> lion.tags.create(tag="yellow")
-<TaggedItem: yellow>
->>> lion.tags.create(tag="hairy")
-<TaggedItem: hairy>
>>> bacon.tags.create(tag="fatty")
<TaggedItem: fatty>
>>> bacon.tags.create(tag="salty")
<TaggedItem: salty>
+>>> lion.tags.create(tag="yellow")
+<TaggedItem: yellow>
+>>> lion.tags.create(tag="hairy")
+<TaggedItem: hairy>
>>> lion.tags.all()
[<TaggedItem: hairy>, <TaggedItem: yellow>]
@@ -105,4 +105,30 @@ __test__ = {'API_TESTS':"""
[<TaggedItem: shiny>]
>>> TaggedItem.objects.filter(content_type__pk=ctype.id, object_id=quartz.id)
[<TaggedItem: clearish>]
+
+# If you delete an object with an explicit Generic relation, the related
+# objects are deleted when the source object is deleted.
+# Original list of tags:
+>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
+[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('hairy', <ContentType: animal>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2), ('yellow', <ContentType: animal>, 1)]
+
+>>> lion.delete()
+>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
+[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+
+# If Generic Relation is not explicitly defined, any related objects
+# remain after deletion of the source object.
+>>> quartz.delete()
+>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
+[('clearish', <ContentType: mineral>, 1), ('fatty', <ContentType: vegetable>, 2), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+
+# If you delete a tag, the objects using the tag are unaffected
+# (other than losing a tag)
+>>> tag = TaggedItem.objects.get(id=1)
+>>> tag.delete()
+>>> bacon.tags.all()
+[<TaggedItem: salty>]
+>>> [(t.tag, t.content_type, t.object_id) for t in TaggedItem.objects.all()]
+[('clearish', <ContentType: mineral>, 1), ('salty', <ContentType: vegetable>, 2), ('shiny', <ContentType: animal>, 2)]
+
"""}
diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py
index 09c3aa7aa8..aa903d1a64 100644
--- a/tests/modeltests/lookup/models.py
+++ b/tests/modeltests/lookup/models.py
@@ -191,4 +191,19 @@ DoesNotExist: Article matching query does not exist.
>>> Article.objects.filter(headline__contains='\\')
[<Article: Article with \ backslash>]
+# none() returns an EmptyQuerySet that behaves like any other QuerySet object
+>>> Article.objects.none()
+[]
+>>> Article.objects.none().filter(headline__startswith='Article')
+[]
+>>> Article.objects.none().count()
+0
+
+# using __in with an empty list should return an empty query set
+>>> Article.objects.filter(id__in=[])
+[]
+
+>>> Article.objects.exclude(id__in=[])
+[<Article: Article with \ backslash>, <Article: Article% with percent sign>, <Article: Article_ with underscore>, <Article: Article 5>, <Article: Article 6>, <Article: Article 4>, <Article: Article 2>, <Article: Article 3>, <Article: Article 7>, <Article: Article 1>]
+
"""}
diff --git a/tests/modeltests/many_to_many/models.py b/tests/modeltests/many_to_many/models.py
index 38f8931ee7..5e46ad428d 100644
--- a/tests/modeltests/many_to_many/models.py
+++ b/tests/modeltests/many_to_many/models.py
@@ -203,7 +203,19 @@ __test__ = {'API_TESTS':"""
>>> p2.article_set.all()
[<Article: Oxygen-free diet works wonders>]
-# Recreate the article and Publication we just deleted.
+# Relation sets can also be set using primary key values
+>>> p2.article_set = [a4.id, a5.id]
+>>> p2.article_set.all()
+[<Article: NASA finds intelligent life on Earth>, <Article: Oxygen-free diet works wonders>]
+>>> a4.publications.all()
+[<Publication: Science News>]
+>>> a4.publications = [p3.id]
+>>> p2.article_set.all()
+[<Article: Oxygen-free diet works wonders>]
+>>> a4.publications.all()
+[<Publication: Science Weekly>]
+
+# Recreate the article and Publication we have deleted.
>>> p1 = Publication(id=None, title='The Python Journal')
>>> p1.save()
>>> a2 = Article(id=None, headline='NASA uses Python')
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 7ce89c61a6..657d506b33 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -6,17 +6,20 @@ model instance.
The function django.newforms.form_for_model() takes a model class and returns
a Form that is tied to the model. This Form works just like any other Form,
-with one additional method: create(). The create() method creates an instance
+with one additional method: save(). The save() method creates an instance
of the model and returns that newly created instance. It saves the instance to
-the database if create(save=True), which is default. If you pass
-create(save=False), then you'll get the object without saving it.
+the database if save(commit=True), which is default. If you pass
+commit=False, then you'll get the object without committing the changes to the
+database.
The function django.newforms.form_for_instance() takes a model instance and
returns a Form that is tied to the instance. This form works just like any
-other Form, with one additional method: apply_changes(). The apply_changes()
-method updates the model instance. It saves the changes to the database if
-apply_changes(save=True), which is default. If you pass save=False, then you'll
-get the object without saving it.
+other Form, with one additional method: save(). The save()
+method updates the model instance. It also takes a commit=True parameter.
+
+The function django.newforms.save_instance() takes a bound form instance and a
+model instance and saves the form's clean_data into the instance. It also takes
+a commit=True parameter.
"""
from django.db import models
@@ -29,7 +32,7 @@ class Category(models.Model):
return self.name
class Writer(models.Model):
- name = models.CharField(maxlength=50)
+ name = models.CharField(maxlength=50, help_text='Use both first and last names.')
def __str__(self):
return self.name
@@ -38,13 +41,14 @@ class Article(models.Model):
headline = models.CharField(maxlength=50)
pub_date = models.DateField()
writer = models.ForeignKey(Writer)
+ article = models.TextField()
categories = models.ManyToManyField(Category, blank=True)
def __str__(self):
return self.headline
__test__ = {'API_TESTS': """
->>> from django.newforms import form_for_model, form_for_instance, BaseForm
+>>> from django.newforms import form_for_model, form_for_instance, save_instance, BaseForm, Form, CharField
>>> import datetime
>>> Category.objects.all()
@@ -67,35 +71,36 @@ __test__ = {'API_TESTS': """
<li>The URL: <input type="text" name="url" maxlength="40" /></li>
>>> f = CategoryForm({'name': 'Entertainment', 'url': 'entertainment'})
->>> f.errors
-{}
+>>> f.is_valid()
+True
>>> f.clean_data
{'url': u'entertainment', 'name': u'Entertainment'}
->>> obj = f.create()
+>>> obj = f.save()
>>> obj
<Category: Entertainment>
>>> Category.objects.all()
[<Category: Entertainment>]
>>> f = CategoryForm({'name': "It's a test", 'url': 'test'})
->>> f.errors
-{}
+>>> f.is_valid()
+True
>>> f.clean_data
{'url': u'test', 'name': u"It's a test"}
->>> obj = f.create()
+>>> obj = f.save()
>>> obj
<Category: It's a test>
>>> Category.objects.all()
[<Category: Entertainment>, <Category: It's a test>]
-If you call create() with save=False, then it will return an object that hasn't
-yet been saved. In this case, it's up to you to save it.
+If you call save() with commit=False, then it will return an object that
+hasn't yet been saved to the database. In this case, it's up to you to call
+save() on the resulting model instance.
>>> f = CategoryForm({'name': 'Third test', 'url': 'third'})
->>> f.errors
-{}
+>>> f.is_valid()
+True
>>> f.clean_data
{'url': u'third', 'name': u'Third test'}
->>> obj = f.create(save=False)
+>>> obj = f.save(commit=False)
>>> obj
<Category: Third test>
>>> Category.objects.all()
@@ -104,17 +109,20 @@ yet been saved. In this case, it's up to you to save it.
>>> Category.objects.all()
[<Category: Entertainment>, <Category: It's a test>, <Category: Third test>]
-If you call create() with invalid data, you'll get a ValueError.
+If you call save() with invalid data, you'll get a ValueError.
>>> f = CategoryForm({'name': '', 'url': 'foo'})
>>> f.errors
{'name': [u'This field is required.']}
>>> f.clean_data
->>> f.create()
+Traceback (most recent call last):
+...
+AttributeError: 'CategoryForm' object has no attribute 'clean_data'
+>>> f.save()
Traceback (most recent call last):
...
ValueError: The Category could not be created because the data didn't validate.
>>> f = CategoryForm({'name': '', 'url': 'foo'})
->>> f.create()
+>>> f.save()
Traceback (most recent call last):
...
ValueError: The Category could not be created because the data didn't validate.
@@ -137,11 +145,12 @@ represented by a ChoiceField.
<option value="1">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></td></tr>
+<tr><th>Article:</th><td><textarea name="article"></textarea></td></tr>
<tr><th>Categories:</th><td><select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
-</select></td></tr>
+</select><br /> Hold down "Control", or "Command" on a Mac, to select more than one.</td></tr>
You can pass a custom Form class to form_for_model. Make sure it's a
subclass of BaseForm, not Form.
@@ -153,17 +162,16 @@ subclass of BaseForm, not Form.
>>> f.say_hello()
hello
-Use form_for_instance to create a Form from a model instance. There are two
-differences between this Form and one created via form_for_model. First, the
-object's current values are inserted as 'initial' data in each Field. Second,
-the Form gets an apply_changes() method instead of a create() method.
+Use form_for_instance to create a Form from a model instance. The difference
+between this Form and one created via form_for_model is that the object's
+current values are inserted as 'initial' data in each Field.
>>> w = Writer.objects.get(name='Mike Royko')
>>> RoykoForm = form_for_instance(w)
>>> f = RoykoForm(auto_id=False)
>>> print f
-<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /></td></tr>
+<tr><th>Name:</th><td><input type="text" name="name" value="Mike Royko" maxlength="50" /><br />Use both first and last names.</td></tr>
->>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w)
+>>> art = Article(headline='Test article', pub_date=datetime.date(1988, 1, 4), writer=w, article='Hello.')
>>> art.save()
>>> art.id
1
@@ -177,15 +185,16 @@ the Form gets an apply_changes() method instead of a create() method.
<option value="1" selected="selected">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></li>
+<li>Article: <textarea name="article">Hello.</textarea></li>
<li>Categories: <select multiple="multiple" name="categories">
<option value="1">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
-</select></li>
->>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1'})
+</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
+>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04', 'writer': u'1', 'article': 'Hello.'})
>>> f.is_valid()
True
->>> new_art = f.apply_changes()
+>>> new_art = f.save()
>>> new_art.id
1
>>> new_art = Article.objects.get(id=1)
@@ -208,10 +217,68 @@ Add some categories and test the many-to-many form output.
<option value="1" selected="selected">Mike Royko</option>
<option value="2">Bob Woodward</option>
</select></li>
+<li>Article: <textarea name="article">Hello.</textarea></li>
<li>Categories: <select multiple="multiple" name="categories">
<option value="1" selected="selected">Entertainment</option>
<option value="2">It&#39;s a test</option>
<option value="3">Third test</option>
-</select></li>
+</select> Hold down "Control", or "Command" on a Mac, to select more than one.</li>
+
+>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04',
+... 'writer': u'1', 'article': u'Hello.', 'categories': [u'1', u'2']})
+>>> new_art = f.save()
+>>> new_art.id
+1
+>>> new_art = Article.objects.get(id=1)
+>>> new_art.categories.all()
+[<Category: Entertainment>, <Category: It's a test>]
+
+Now, submit form data with no categories. This deletes the existing categories.
+>>> f = TestArticleForm({'headline': u'New headline', 'pub_date': u'1988-01-04',
+... 'writer': u'1', 'article': u'Hello.'})
+>>> new_art = f.save()
+>>> new_art.id
+1
+>>> new_art = Article.objects.get(id=1)
+>>> new_art.categories.all()
+[]
+Create a new article, with categories, via the form.
+>>> ArticleForm = form_for_model(Article)
+>>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01',
+... 'writer': u'1', 'article': u'Test.', 'categories': [u'1', u'2']})
+>>> new_art = f.save()
+>>> new_art.id
+2
+>>> new_art = Article.objects.get(id=2)
+>>> new_art.categories.all()
+[<Category: Entertainment>, <Category: It's a test>]
+
+Create a new article, with no categories, via the form.
+>>> ArticleForm = form_for_model(Article)
+>>> f = ArticleForm({'headline': u'The walrus was Paul', 'pub_date': u'1967-11-01',
+... 'writer': u'1', 'article': u'Test.'})
+>>> new_art = f.save()
+>>> new_art.id
+3
+>>> new_art = Article.objects.get(id=3)
+>>> new_art.categories.all()
+[]
+
+Here, we define a custom Form. Because it happens to have the same fields as
+the Category model, we can use save_instance() to apply its changes to an
+existing Category instance.
+>>> class ShortCategory(Form):
+... name = CharField(max_length=5)
+... url = CharField(max_length=3)
+>>> cat = Category.objects.get(name='Third test')
+>>> cat
+<Category: Third test>
+>>> cat.id
+3
+>>> sc = ShortCategory({'name': 'Third', 'url': '3rd'})
+>>> save_instance(sc, cat)
+<Category: Third>
+>>> Category.objects.get(id=3)
+<Category: Third>
"""}
diff --git a/tests/modeltests/or_lookups/models.py b/tests/modeltests/or_lookups/models.py
index 2de18edc1f..9f926a7373 100644
--- a/tests/modeltests/or_lookups/models.py
+++ b/tests/modeltests/or_lookups/models.py
@@ -69,6 +69,21 @@ __test__ = {'API_TESTS':"""
>>> Article.objects.filter(Q(pk=1) | Q(pk=2) | Q(pk=3))
[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]
+# You could also use "in" to accomplish the same as above.
+>>> Article.objects.filter(pk__in=[1,2,3])
+[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]
+
+>>> Article.objects.filter(pk__in=[1,2,3,4])
+[<Article: Hello>, <Article: Goodbye>, <Article: Hello and goodbye>]
+
+# Passing "in" an empty list returns no results ...
+>>> Article.objects.filter(pk__in=[])
+[]
+
+# ... but can return results if we OR it with another query.
+>>> Article.objects.filter(Q(pk__in=[]) | Q(headline__icontains='goodbye'))
+[<Article: Goodbye>, <Article: Hello and goodbye>]
+
# Q arg objects are ANDed
>>> Article.objects.filter(Q(headline__startswith='Hello'), Q(headline__contains='bye'))
[<Article: Hello and goodbye>]
diff --git a/tests/modeltests/serializers/models.py b/tests/modeltests/serializers/models.py
index d1d10b43c0..e24ff537c1 100644
--- a/tests/modeltests/serializers/models.py
+++ b/tests/modeltests/serializers/models.py
@@ -37,6 +37,13 @@ class Article(models.Model):
def __str__(self):
return self.headline
+class AuthorProfile(models.Model):
+ author = models.OneToOneField(Author)
+ date_of_birth = models.DateField()
+
+ def __str__(self):
+ return "Profile of %s" % self.author
+
__test__ = {'API_TESTS':"""
# Create some data:
>>> from datetime import datetime
@@ -118,4 +125,18 @@ __test__ = {'API_TESTS':"""
>>> Article.objects.all()
[<Article: Just kidding; I love TV poker>, <Article: Time to reform copyright>]
+# If you use your own primary key field (such as a OneToOneField),
+# it doesn't appear in the serialized field list - it replaces the
+# pk identifier.
+>>> profile = AuthorProfile(author=joe, date_of_birth=datetime(1970,1,1))
+>>> profile.save()
+
+>>> json = serializers.serialize("json", AuthorProfile.objects.all())
+>>> json
+'[{"pk": "1", "model": "serializers.authorprofile", "fields": {"date_of_birth": "1970-01-01"}}]'
+
+>>> for obj in serializers.deserialize("json", json):
+... print obj
+<DeserializedObject: Profile of Joe>
+
"""}
diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py
index bf131032eb..7acfc2db60 100644
--- a/tests/modeltests/test_client/views.py
+++ b/tests/modeltests/test_client/views.py
@@ -26,10 +26,10 @@ def redirect_view(request):
"A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client/get_view/')
-@login_required
def login_protected_view(request):
"A simple view that is login protected."
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
c = Context({'user': request.user})
return HttpResponse(t.render(c))
+login_protected_view = login_required(login_protected_view) \ No newline at end of file
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index 0852fbcf0e..20a1937f56 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -106,6 +106,46 @@ u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u
>>> w.render('email', '', attrs={'class': 'special'})
u'<input type="hidden" class="special" name="email" />'
+# MultipleHiddenInput Widget ##################################################
+
+>>> w = MultipleHiddenInput()
+>>> w.render('email', [])
+u''
+>>> w.render('email', None)
+u''
+>>> w.render('email', ['test@example.com'])
+u'<input type="hidden" name="email" value="test@example.com" />'
+>>> w.render('email', ['some "quoted" & ampersanded value'])
+u'<input type="hidden" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />'
+>>> w.render('email', ['test@example.com', 'foo@example.com'])
+u'<input type="hidden" name="email" value="test@example.com" />\n<input type="hidden" name="email" value="foo@example.com" />'
+>>> w.render('email', ['test@example.com'], attrs={'class': 'fun'})
+u'<input type="hidden" name="email" value="test@example.com" class="fun" />'
+>>> w.render('email', ['test@example.com', 'foo@example.com'], attrs={'class': 'fun'})
+u'<input type="hidden" name="email" value="test@example.com" class="fun" />\n<input type="hidden" name="email" value="foo@example.com" class="fun" />'
+
+You can also pass 'attrs' to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'fun'})
+>>> w.render('email', [])
+u''
+>>> w.render('email', ['foo@example.com'])
+u'<input type="hidden" class="fun" value="foo@example.com" name="email" />'
+>>> w.render('email', ['foo@example.com', 'test@example.com'])
+u'<input type="hidden" class="fun" value="foo@example.com" name="email" />\n<input type="hidden" class="fun" value="test@example.com" name="email" />'
+
+'attrs' passed to render() get precedence over those passed to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
+u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
+
+>>> w.render('email', ['ŠĐĆŽćžšđ'], attrs={'class': 'fun'})
+u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />'
+
+'attrs' passed to render() get precedence over those passed to the constructor:
+>>> w = MultipleHiddenInput(attrs={'class': 'pretty'})
+>>> w.render('email', ['foo@example.com'], attrs={'class': 'special'})
+u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
+
# FileInput Widget ############################################################
>>> w = FileInput()
@@ -296,6 +336,60 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
>>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
+If choices is passed to the constructor and is a generator, it can be iterated
+over multiple times without getting consumed:
+>>> w = Select(choices=get_choices())
+>>> print w.render('num', 2)
+<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2" selected="selected">2</option>
+<option value="3">3</option>
+<option value="4">4</option>
+</select>
+>>> print w.render('num', 3)
+<select name="num">
+<option value="0">0</option>
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3" selected="selected">3</option>
+<option value="4">4</option>
+</select>
+
+# NullBooleanSelect Widget ####################################################
+
+>>> w = NullBooleanSelect()
+>>> print w.render('is_cool', True)
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', False)
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+>>> print w.render('is_cool', None)
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', '2')
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> print w.render('is_cool', '3')
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+
# SelectMultiple Widget #######################################################
>>> w = SelectMultiple()
@@ -527,12 +621,16 @@ 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
+>>> w = RadioSelect()
+>>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]))
+u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
+
# CheckboxSelectMultiple Widget ###############################################
>>> w = CheckboxSelectMultiple()
@@ -640,6 +738,39 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
+# MultiWidget #################################################################
+
+>>> class MyMultiWidget(MultiWidget):
+... def decompress(self, value):
+... if value:
+... return value.split('__')
+... return ['', '']
+... def format_output(self, rendered_widgets):
+... return u'<br />'.join(rendered_widgets)
+>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})))
+>>> w.render('name', ['john', 'lennon'])
+u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />'
+>>> w.render('name', 'john__lennon')
+u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />'
+
+# SplitDateTimeWidget #########################################################
+
+>>> w = SplitDateTimeWidget()
+>>> w.render('date', '')
+u'<input type="text" name="date_0" /><input type="text" name="date_1" />'
+>>> w.render('date', None)
+u'<input type="text" name="date_0" /><input type="text" name="date_1" />'
+>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
+u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />'
+>>> w.render('date', [datetime.date(2006, 1, 10), datetime.time(7, 30)])
+u'<input type="text" name="date_0" value="2006-01-10" /><input type="text" name="date_1" value="07:30:00" />'
+
+You can also pass 'attrs' to the constructor. In this case, the attrs will be
+included on both widgets.
+>>> w = SplitDateTimeWidget(attrs={'class': 'pretty'})
+>>> w.render('date', datetime.datetime(2006, 1, 10, 7, 30))
+u'<input type="text" class="pretty" value="2006-01-10" name="date_0" /><input type="text" class="pretty" value="07:30:00" name="date_1" />'
+
##########
# Fields #
##########
@@ -766,9 +897,11 @@ ValidationError: [u'Enter a whole number.']
>>> f = IntegerField(required=False)
>>> f.clean('')
-u''
+>>> repr(f.clean(''))
+'None'
>>> f.clean(None)
-u''
+>>> repr(f.clean(None))
+'None'
>>> f.clean('1')
1
>>> isinstance(f.clean('1'), int)
@@ -1285,6 +1418,11 @@ ValidationError: [u'This URL appears to be a broken link.']
Traceback (most recent call last):
...
ValidationError: [u'This URL appears to be a broken link.']
+>>> f = URLField(verify_exists=True, required=False)
+>>> f.clean('')
+u''
+>>> f.clean('http://www.google.com') # This will fail if there's no Internet connection
+u'http://www.google.com'
EmailField also access min_length and max_length parameters, for convenience.
>>> f = URLField(min_length=15, max_length=20)
@@ -1379,6 +1517,20 @@ Traceback (most recent call last):
...
ValidationError: [u'Select a valid choice. John is not one of the available choices.']
+# NullBooleanField ############################################################
+
+>>> f = NullBooleanField()
+>>> f.clean('')
+>>> f.clean(True)
+True
+>>> f.clean(False)
+False
+>>> f.clean(None)
+>>> f.clean('1')
+>>> f.clean('2')
+>>> f.clean('3')
+>>> f.clean('hello')
+
# MultipleChoiceField #########################################################
>>> f = MultipleChoiceField(choices=[('1', '1'), ('2', '2')])
@@ -1485,6 +1637,58 @@ u''
>>> f.clean(None)
u''
+# SplitDateTimeField ##########################################################
+
+>>> f = SplitDateTimeField()
+>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
+datetime.datetime(2006, 1, 10, 7, 30)
+>>> f.clean(None)
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'This field is required.']
+>>> f.clean('hello')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a list of values.']
+>>> f.clean(['hello', 'there'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
+>>> f.clean(['2006-01-10', 'there'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid time.']
+>>> f.clean(['hello', '07:30'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+
+>>> f = SplitDateTimeField(required=False)
+>>> f.clean([datetime.date(2006, 1, 10), datetime.time(7, 30)])
+datetime.datetime(2006, 1, 10, 7, 30)
+>>> f.clean(None)
+>>> f.clean('')
+>>> f.clean('hello')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a list of values.']
+>>> f.clean(['hello', 'there'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.', u'Enter a valid time.']
+>>> f.clean(['2006-01-10', 'there'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid time.']
+>>> f.clean(['hello', '07:30'])
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a valid date.']
+
#########
# Forms #
#########
@@ -1502,6 +1706,8 @@ You can pass it data in __init__(), as a dictionary.
Pass a dictionary to a Form's __init__().
>>> p = Person({'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9'})
+>>> p.is_bound
+True
>>> p.errors
{}
>>> p.is_valid()
@@ -1540,10 +1746,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.clean_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_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>
@@ -1565,13 +1777,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.clean_data
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_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>
@@ -1611,8 +1829,9 @@ u'<ul class="errorlist"><li>first_name<ul class="errorlist"><li>This field is re
* birthday
* This field is required.
>>> p.clean_data
->>> repr(p.clean_data)
-'None'
+Traceback (most recent call last):
+...
+AttributeError: 'Person' object has no attribute 'clean_data'
>>> p['first_name'].errors
[u'This field is required.']
>>> p['first_name'].errors.as_ul()
@@ -1628,6 +1847,17 @@ u'* This field is required.'
>>> print p['birthday']
<input type="text" name="birthday" id="id_birthday" />
+clean_data will always *only* contain a key for fields defined in the
+Form, even if you pass extra data when you define the Form. In this
+example, we pass a bunch of extra fields to the form constructor,
+but clean_data contains only the form's fields.
+>>> data = {'first_name': u'John', 'last_name': u'Lennon', 'birthday': u'1940-10-9', 'extra1': 'hello', 'extra2': 'hello'}
+>>> p = Person(data)
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
"auto_id" tells the Form to add an "id" attribute to each form element.
If it's a string that contains '%s', Django will use that as a format string
into which the field's name will be inserted. It will also put a <label> around
@@ -1753,6 +1983,57 @@ For a form with a <select>, use ChoiceField:
<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()
@@ -1834,6 +2115,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()
@@ -1857,6 +2149,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']}
@@ -1869,11 +2171,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)
@@ -1890,6 +2201,8 @@ returns a list of input.
>>> f.clean_data
{'composers': [u'J', u'P'], 'name': u'Yesterday'}
+# Validating multiple fields in relation to another ###########################
+
There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in
@@ -1964,6 +2277,8 @@ Form.clean() is required to return a dictionary of all clean data.
>>> f.clean_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
+# Dynamic construction ########################################################
+
It's possible to construct a Form dynamically by adding to the self.fields
dictionary in __init__(). Don't forget to call Form.__init__() within the
subclass' __init__().
@@ -1979,6 +2294,46 @@ 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>
+
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
@@ -2200,6 +2555,96 @@ validation error rather than using the initial value for 'username'.
>>> p.is_valid()
False
+# Dynamic initial data ########################################################
+
+The previous technique dealt with "hard-coded" initial data, but it's also
+possible to specify initial data after you've already created the Form class
+(i.e., at runtime). Use the 'initial' parameter to the Form constructor. This
+should be a dictionary containing initial values for one or more fields in the
+form, keyed by field name.
+
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password = CharField(widget=PasswordInput)
+
+Here, we're not submitting any data, so the initial value will be displayed.
+>>> p = UserRegistration(initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="django" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration(initial={'username': 'stephane'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="stephane" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+
+The 'initial' parameter is meaningless if you pass data.
+>>> p = UserRegistration({}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u''}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li><ul class="errorlist"><li>This field is required.</li></ul>Username: <input type="text" name="username" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+>>> p = UserRegistration({'username': u'foo'}, initial={'username': 'django'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="foo" maxlength="10" /></li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /></li>
+
+A dynamic 'initial' value is *not* used as a fallback if data is not provided.
+In this example, we don't provide a value for 'username', and the form raises a
+validation error rather than using the initial value for 'username'.
+>>> p = UserRegistration({'password': 'secret'}, initial={'username': 'django'})
+>>> p.errors
+{'username': [u'This field is required.']}
+>>> p.is_valid()
+False
+
+If a Form defines 'initial' *and* 'initial' is passed as a parameter to Form(),
+then the latter will get precedence.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, initial='django')
+... password = CharField(widget=PasswordInput)
+>>> p = UserRegistration(initial={'username': 'babik'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="babik" maxlength="10" /></li>
+<li>Password: <input type="password" name="password" /></li>
+
+# Help text ###################################################################
+
+You can specify descriptive text for a field by using the 'help_text' argument
+to a Field class. This help text is displayed when a Form is rendered.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, help_text='e.g., user@example.com')
+... password = CharField(widget=PasswordInput, help_text='Choose wisely.')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /> Choose wisely.</li>
+>>> print p.as_p()
+<p>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</p>
+<p>Password: <input type="password" name="password" /> Choose wisely.</p>
+>>> print p.as_table()
+<tr><th>Username:</th><td><input type="text" name="username" maxlength="10" /><br />e.g., user@example.com</td></tr>
+<tr><th>Password:</th><td><input type="password" name="password" /><br />Choose wisely.</td></tr>
+
+The help text is displayed whether or not data is provided for the form.
+>>> p = UserRegistration({'username': u'foo'}, auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" value="foo" maxlength="10" /> e.g., user@example.com</li>
+<li><ul class="errorlist"><li>This field is required.</li></ul>Password: <input type="password" name="password" /> Choose wisely.</li>
+
+help_text is not displayed for hidden fields. It can be used for documentation
+purposes, though.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, help_text='e.g., user@example.com')
+... password = CharField(widget=PasswordInput)
+... next = CharField(widget=HiddenInput, initial='/', help_text='Redirect destination')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+<li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li>
+<li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li>
+
# Forms with prefixes #########################################################
Sometimes it's necessary to have multiple forms display on the same HTML page,
@@ -2311,6 +2756,57 @@ True
>>> p.clean_data
{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+# Forms with NullBooleanFields ################################################
+
+NullBooleanField is a bit of a special case because its presentation (widget)
+is different than its data. This is handled transparently, though.
+
+>>> class Person(Form):
+... name = CharField()
+... is_cool = NullBooleanField()
+>>> p = Person({'name': u'Joe'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'1'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1" selected="selected">Unknown</option>
+<option value="2">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'2'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': u'3'}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': True}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2" selected="selected">Yes</option>
+<option value="3">No</option>
+</select>
+>>> p = Person({'name': u'Joe', 'is_cool': False}, auto_id=False)
+>>> print p['is_cool']
+<select name="is_cool">
+<option value="1">Unknown</option>
+<option value="2">Yes</option>
+<option value="3" selected="selected">No</option>
+</select>
+
# Basic form processing in a view #############################################
>>> from django.template import Template, Context
@@ -2439,6 +2935,15 @@ field an "id" attribute.
<input type="submit" />
</form>
+The label_tag() method takes an optional attrs argument: a dictionary of HTML
+attributes to add to the <label> tag.
+>>> f = UserRegistration(auto_id='id_%s')
+>>> for bf in f:
+... print bf.label_tag(attrs={'class': 'pretty'})
+<label for="id_username" class="pretty">Username</label>
+<label for="id_password1" class="pretty">Password1</label>
+<label for="id_password2" class="pretty">Password2</label>
+
To display the errors that aren't associated with a particular field -- e.g.,
the errors caused by Form.clean() -- use {{ form.non_field_errors }} in the
template. If used on its own, it is displayed as a <ul> (or an empty string, if