diff options
| author | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-02-02 17:35:55 +0000 |
|---|---|---|
| committer | Boulder Sprinters <boulder-sprinters@djangoproject.com> | 2007-02-02 17:35:55 +0000 |
| commit | e17f75551491f5b864c1fc8a97c21d0b2bbf0bcd (patch) | |
| tree | 49a5a779e1278eca17fffe81a83fce55fb35ce46 /tests | |
| parent | 92b7851424069336f76112932682c77a6a1e3cb9 (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.py | 92 | ||||
| -rw-r--r-- | tests/modeltests/generic_relations/models.py | 34 | ||||
| -rw-r--r-- | tests/modeltests/lookup/models.py | 15 | ||||
| -rw-r--r-- | tests/modeltests/many_to_many/models.py | 14 | ||||
| -rw-r--r-- | tests/modeltests/model_forms/models.py | 135 | ||||
| -rw-r--r-- | tests/modeltests/or_lookups/models.py | 15 | ||||
| -rw-r--r-- | tests/modeltests/serializers/models.py | 21 | ||||
| -rw-r--r-- | tests/modeltests/test_client/views.py | 2 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 521 |
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'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'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'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 "quoted" & 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 |
