From 93d83df61195ea598bc0a2a5cdce1df8e88fcb6d Mon Sep 17 00:00:00 2001
From: Boulder Sprinters
Date: Fri, 15 Dec 2006 18:32:47 +0000
Subject: boulder-oracle-sprint: Merged to trunk [4210].
git-svn-id: http://code.djangoproject.com/svn/django/branches/boulder-oracle-sprint@4212 bcc190cf-cafb-0310-a4f2-bffc1f526a37
---
tests/modeltests/model_forms/__init__.py | 0
tests/modeltests/model_forms/models.py | 44 +
tests/regressiontests/forms/tests.py | 1285 +++++++++++++++++++++++++++---
tests/regressiontests/templates/tests.py | 18 +-
tests/runtests.py | 39 +-
5 files changed, 1250 insertions(+), 136 deletions(-)
create mode 100644 tests/modeltests/model_forms/__init__.py
create mode 100644 tests/modeltests/model_forms/models.py
(limited to 'tests')
diff --git a/tests/modeltests/model_forms/__init__.py b/tests/modeltests/model_forms/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
new file mode 100644
index 0000000000..b51b4e1a8b
--- /dev/null
+++ b/tests/modeltests/model_forms/models.py
@@ -0,0 +1,44 @@
+"""
+34. Generating HTML forms from models
+
+Django provides shortcuts for creating Form objects from a model class.
+"""
+
+from django.db import models
+
+class Category(models.Model):
+ name = models.CharField(maxlength=20)
+ url = models.CharField('The URL', maxlength=20)
+
+ def __str__(self):
+ return self.name
+
+class Article(models.Model):
+ headline = models.CharField(maxlength=50)
+ pub_date = models.DateTimeField()
+ categories = models.ManyToManyField(Category)
+
+ def __str__(self):
+ return self.headline
+
+__test__ = {'API_TESTS': """
+>>> from django.newforms import form_for_model
+>>> CategoryForm = form_for_model(Category)
+>>> f = CategoryForm()
+>>> print f
+ID:
+Name:
+The URL:
+>>> print f.as_ul()
+ID:
+Name:
+The URL:
+>>> print f['name']
+
+
+>>> f = CategoryForm(auto_id=False)
+>>> print f.as_ul()
+ID:
+Name:
+The URL:
+"""}
diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py
index c4dd7074a5..c75cedab14 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -4,6 +4,14 @@ r"""
>>> import datetime
>>> import re
+###########
+# Widgets #
+###########
+
+Each Widget class corresponds to an HTML form widget. A Widget knows how to
+render itself, given a field name and some data. Widgets don't perform
+validation.
+
# TextInput Widget ############################################################
>>> w = TextInput()
@@ -156,10 +164,18 @@ u'
+First name:
+
+Last name:
+
+Birthday:
+
+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.
+>>> p = Person()
+>>> p.errors
+{}
+>>> p.is_valid()
+False
+>>> print p
+First name:
+Last name:
+Birthday:
+>>> print p.as_table()
+First name:
+Last name:
+Birthday:
+>>> print p.as_ul()
+First name:
+Last name:
+Birthday:
+>>> print p.as_p()
+First name:
+Last name:
+Birthday:
+
+Unicode values are handled properly.
+>>> p = Person({'first_name': u'John', 'last_name': u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111', 'birthday': '1940-10-9'})
+>>> p.as_table()
+u'First name: \nLast name: \nBirthday: '
+>>> p.as_ul()
+u'First name: \nLast name: \nBirthday: '
+>>> p.as_p()
+u'First name:
\nLast name:
\nBirthday:
'
>>> p = Person({'last_name': u'Lennon'})
->>> p.errors()
+>>> p.errors
{'first_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
>>> p.is_valid()
False
->>> p.errors().as_ul()
+>>> p.errors.as_ul()
u''
->>> print p.errors().as_text()
+>>> print p.errors.as_text()
* first_name
* This field is required.
* birthday
* This field is required.
->>> p.clean()
->>> repr(p.clean())
+>>> p.clean_data
+>>> repr(p.clean_data)
'None'
>>> p['first_name'].errors
[u'This field is required.']
@@ -979,28 +1481,37 @@ u'* This field is required.'
>>> p = Person()
>>> print p['first_name']
-
+
>>> print p['last_name']
-
+
>>> print p['birthday']
-
+
"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.
->>> p = Person(auto_id='id_%s')
+into which the field's name will be inserted. It will also put a around
+the human-readable labels for a field.
+>>> p = Person(auto_id='%s_id')
+>>> print p.as_table()
+First name:
+Last name:
+Birthday:
>>> print p.as_ul()
-First name:
-Last name:
-Birthday:
+First name:
+Last name:
+Birthday:
+>>> print p.as_p()
+First name:
+Last name:
+Birthday:
If auto_id is any True value whose str() does not contain '%s', the "id"
attribute will be the name of the field.
>>> p = Person(auto_id=True)
>>> print p.as_ul()
-First name:
-Last name:
-Birthday:
+First name:
+Last name:
+Birthday:
If auto_id is any False value, an "id" attribute won't be output unless it
was manually entered.
@@ -1011,14 +1522,14 @@ was manually entered.
Birthday:
In this example, auto_id is False, but the "id" attribute for the "first_name"
-field is given.
+field is given. Also note that field gets a , while the others don't.
>>> class PersonNew(Form):
... first_name = CharField(widget=TextInput(attrs={'id': 'first_name_id'}))
... last_name = CharField()
... birthday = DateField()
>>> p = PersonNew(auto_id=False)
>>> print p.as_ul()
-First name:
+First name:
Last name:
Birthday:
@@ -1026,20 +1537,20 @@ If the "id" attribute is specified in the Form and auto_id is True, the "id"
attribute in the Form gets precedence.
>>> p = PersonNew(auto_id=True)
>>> print p.as_ul()
-First name:
-Last name:
-Birthday:
+First name:
+Last name:
+Birthday:
>>> class SignupForm(Form):
... email = EmailField()
... get_spam = BooleanField()
->>> f = SignupForm()
+>>> f = SignupForm(auto_id=False)
>>> print f['email']
>>> print f['get_spam']
->>> f = SignupForm({'email': 'test@example.com', 'get_spam': True})
+>>> f = SignupForm({'email': 'test@example.com', 'get_spam': True}, auto_id=False)
>>> print f['email']
>>> print f['get_spam']
@@ -1049,70 +1560,131 @@ Any Field can have a Widget class passed to its constructor:
>>> class ContactForm(Form):
... subject = CharField()
... message = CharField(widget=Textarea)
->>> f = ContactForm()
+>>> f = ContactForm(auto_id=False)
>>> print f['subject']
>>> print f['message']
-as_textarea() and as_text() are shortcuts for changing the output widget type:
+as_textarea(), as_text() and as_hidden() are shortcuts for changing the output
+widget type:
>>> f['subject'].as_textarea()
u''
>>> f['message'].as_text()
u' '
+>>> f['message'].as_hidden()
+u' '
The 'widget' parameter to a Field can also be an instance:
>>> class ContactForm(Form):
... subject = CharField()
... message = CharField(widget=Textarea(attrs={'rows': 80, 'cols': 20}))
->>> f = ContactForm()
+>>> f = ContactForm(auto_id=False)
>>> print f['message']
-Instance-level attrs are *not* carried over to as_textarea() and as_text():
+Instance-level attrs are *not* carried over to as_textarea(), as_text() and
+as_hidden():
>>> f['message'].as_text()
u' '
->>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'})
+>>> f = ContactForm({'subject': 'Hello', 'message': 'I love you.'}, auto_id=False)
>>> f['subject'].as_textarea()
u''
>>> f['message'].as_text()
u' '
+>>> f['message'].as_hidden()
+u' '
For a form with a , use ChoiceField:
>>> class FrameworkForm(Form):
... name = CharField()
... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')])
->>> f = FrameworkForm()
+>>> f = FrameworkForm(auto_id=False)
>>> print f['language']
Python
Java
->>> f = FrameworkForm({'name': 'Django', 'language': 'P'})
+>>> f = FrameworkForm({'name': 'Django', 'language': 'P'}, auto_id=False)
>>> print f['language']
Python
Java
+Add widget=RadioSelect to use that widget with a ChoiceField.
+>>> class FrameworkForm(Form):
+... name = CharField()
+... language = ChoiceField(choices=[('P', 'Python'), ('J', 'Java')], widget=RadioSelect)
+>>> f = FrameworkForm(auto_id=False)
+>>> print f['language']
+
+>>> print f
+Name:
+Language:
+>>> print f.as_ul()
+Name:
+Language:
+
+Regarding auto_id and , RadioSelect is a special case. Each radio button
+gets a distinct ID, formed by appending an underscore plus the button's
+zero-based index.
+>>> f = FrameworkForm(auto_id='id_%s')
+>>> print f['language']
+
+
+When RadioSelect is used with auto_id, and the whole form is printed using
+either as_table() or as_ul(), the label for the RadioSelect will point to the
+ID of the *first* radio button.
+>>> print f
+Name:
+Language:
+>>> print f.as_ul()
+Name:
+Language:
+>>> print f.as_p()
+Name:
+Language:
+
MultipleChoiceField is a special case, as its data is required to be a list:
>>> class SongForm(Form):
... name = CharField()
... composers = MultipleChoiceField()
->>> f = SongForm()
+>>> f = SongForm(auto_id=False)
>>> print f['composers']
>>> class SongForm(Form):
... name = CharField()
... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')])
->>> f = SongForm()
+>>> f = SongForm(auto_id=False)
>>> print f['composers']
John Lennon
Paul McCartney
->>> f = SongForm({'name': 'Yesterday', 'composers': ['P']})
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['P']}, auto_id=False)
>>> print f['name']
>>> print f['composers']
@@ -1121,10 +1693,69 @@ MultipleChoiceField is a special case, as its data is required to be a list:
Paul McCartney
+MultipleChoiceField can also be used with the CheckboxSelectMultiple widget.
+>>> class SongForm(Form):
+... name = CharField()
+... composers = MultipleChoiceField(choices=[('J', 'John Lennon'), ('P', 'Paul McCartney')], widget=CheckboxSelectMultiple)
+>>> f = SongForm(auto_id=False)
+>>> print f['composers']
+
+>>> f = SongForm({'composers': ['J']}, auto_id=False)
+>>> print f['composers']
+
+>>> f = SongForm({'composers': ['J', 'P']}, auto_id=False)
+>>> print f['composers']
+
+
+Data for a MultipleChoiceField should be a list. QueryDict and MultiValueDict
+conveniently work with this.
+>>> data = {'name': 'Yesterday', 'composers': ['J', 'P']}
+>>> f = SongForm(data)
+>>> f.errors
+{}
+>>> from django.http import QueryDict
+>>> data = QueryDict('name=Yesterday&composers=J&composers=P')
+>>> f = SongForm(data)
+>>> f.errors
+{}
+>>> from django.utils.datastructures import MultiValueDict
+>>> data = MultiValueDict(dict(name='Yesterday', composers=['J', 'P']))
+>>> f = SongForm(data)
+>>> f.errors
+{}
+
+When using CheckboxSelectMultiple, the framework expects a list of input and
+returns a list of input.
+>>> f = SongForm({'name': 'Yesterday'}, auto_id=False)
+>>> f.errors
+{'composers': [u'This field is required.']}
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J']}, auto_id=False)
+>>> f.errors
+{}
+>>> f.clean_data
+{'composers': [u'J'], 'name': u'Yesterday'}
+>>> f = SongForm({'name': 'Yesterday', 'composers': ['J', 'P']}, auto_id=False)
+>>> f.errors
+{}
+>>> f.clean_data
+{'composers': [u'J', u'P'], 'name': u'Yesterday'}
+
There are a couple of ways to do multiple-field validation. If you want the
validation message to be associated with a particular field, implement the
clean_XXX() method on the Form, where XXX is the field name. As in
-Field.clean(), the clean_XXX() method should return the cleaned value:
+Field.clean(), the clean_XXX() method should return the cleaned value. In the
+clean_XXX() method, you have access to self.clean_data, which is a dictionary
+of all the data that has been cleaned *so far*, in order by the fields,
+including the current field (e.g., the field XXX if you're in clean_XXX()).
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
@@ -1133,23 +1764,28 @@ Field.clean(), the clean_XXX() method should return the cleaned value:
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data['password2']
->>> f = UserRegistration()
->>> f.errors()
+>>> f = UserRegistration(auto_id=False)
+>>> f.errors
+{}
+>>> f = UserRegistration({}, auto_id=False)
+>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
->>> f.errors()
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+>>> f.errors
{'password2': [u'Please make sure your passwords match.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
->>> f.errors()
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
+>>> f.errors
{}
->>> f.clean()
+>>> f.clean_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
Another way of doing multiple-field validation is by implementing the
Form's clean() method. If you do this, any ValidationError raised by that
method will not be associated with a particular field; it will have a
-special-case association with the field named '__all__'. Note that
-Form.clean() still needs to return a dictionary of all clean data:
+special-case association with the field named '__all__'.
+Note that in Form.clean(), you have access to self.clean_data, a dictionary of
+all the fields/values that have *not* raised a ValidationError. Also note
+Form.clean() is required to return a dictionary of all clean data.
>>> class UserRegistration(Form):
... username = CharField(max_length=10)
... password1 = CharField(widget=PasswordInput)
@@ -1158,34 +1794,36 @@ Form.clean() still needs to return a dictionary of all clean data:
... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
... raise ValidationError(u'Please make sure your passwords match.')
... return self.clean_data
->>> f = UserRegistration()
+>>> f = UserRegistration(auto_id=False)
+>>> f.errors
+{}
+>>> f = UserRegistration({}, auto_id=False)
>>> print f.as_table()
-Username:
-Password1:
-Password2:
->>> f.errors()
+
+Username:
+
+Password1:
+
+Password2:
+>>> f.errors
{'username': [u'This field is required.'], 'password1': [u'This field is required.'], 'password2': [u'This field is required.']}
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'})
->>> f.errors()
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)
+>>> f.errors
{'__all__': [u'Please make sure your passwords match.']}
>>> print f.as_table()
-Username:
-Password1:
-Password2:
->>> print f.as_table_with_errors()
-Please make sure your passwords match.
-Username:
-Password1:
-Password2:
->>> print f.as_ul_with_errors()
-Please make sure your passwords match.
-Username:
+Please make sure your passwords match.
+Username:
+Password1:
+Password2:
+>>> print f.as_ul()
+Please make sure your passwords match.
+Username:
Password1:
Password2:
->>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'})
->>> f.errors()
+>>> f = UserRegistration({'username': 'adrian', 'password1': 'foo', 'password2': 'foo'}, auto_id=False)
+>>> f.errors
{}
->>> f.clean()
+>>> f.clean_data
{'username': u'adrian', 'password1': u'foo', 'password2': u'foo'}
It's possible to construct a Form dynamically by adding to the self.fields
@@ -1194,14 +1832,431 @@ subclass' __init__().
>>> class Person(Form):
... first_name = CharField()
... last_name = CharField()
-... def __init__(self):
-... super(Person, self).__init__()
+... def __init__(self, *args, **kwargs):
+... super(Person, self).__init__(*args, **kwargs)
... self.fields['birthday'] = DateField()
->>> p = Person()
+>>> p = Person(auto_id=False)
+>>> print p
+First name:
+Last name:
+Birthday:
+
+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
+form, directly after that row's form element.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... hidden_text = CharField(widget=HiddenInput)
+... birthday = DateField()
+>>> p = Person(auto_id=False)
>>> print p
-First name:
-Last name:
-Birthday:
+First name:
+Last name:
+Birthday:
+>>> print p.as_ul()
+First name:
+Last name:
+Birthday:
+>>> print p.as_p()
+First name:
+Last name:
+Birthday:
+
+With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label.
+>>> p = Person(auto_id='id_%s')
+>>> print p
+First name:
+Last name:
+Birthday:
+>>> print p.as_ul()
+First name:
+Last name:
+Birthday:
+>>> print p.as_p()
+First name:
+Last name:
+Birthday:
+
+If a field with a HiddenInput has errors, the as_table() and as_ul() output
+will include the error message(s) with the text "(Hidden field [fieldname]) "
+prepended. This message is displayed at the top of the output, regardless of
+its field's order in the form.
+>>> p = Person({'first_name': 'John', 'last_name': 'Lennon', 'birthday': '1940-10-9'}, auto_id=False)
+>>> print p
+(Hidden field hidden_text) This field is required.
+First name:
+Last name:
+Birthday:
+>>> print p.as_ul()
+(Hidden field hidden_text) This field is required.
+First name:
+Last name:
+Birthday:
+>>> print p.as_p()
+
(Hidden field hidden_text) This field is required.
+First name:
+Last name:
+Birthday:
+
+A corner case: It's possible for a form to have only HiddenInputs.
+>>> class TestForm(Form):
+... foo = CharField(widget=HiddenInput)
+... bar = CharField(widget=HiddenInput)
+>>> p = TestForm(auto_id=False)
+>>> print p.as_table()
+
+>>> print p.as_ul()
+
+>>> print p.as_p()
+
+
+A Form's fields are displayed in the same order in which they were defined.
+>>> class TestForm(Form):
+... field1 = CharField()
+... field2 = CharField()
+... field3 = CharField()
+... field4 = CharField()
+... field5 = CharField()
+... field6 = CharField()
+... field7 = CharField()
+... field8 = CharField()
+... field9 = CharField()
+... field10 = CharField()
+... field11 = CharField()
+... field12 = CharField()
+... field13 = CharField()
+... field14 = CharField()
+>>> p = TestForm(auto_id=False)
+>>> print p
+Field1:
+Field2:
+Field3:
+Field4:
+Field5:
+Field6:
+Field7:
+Field8:
+Field9:
+Field10:
+Field11:
+Field12:
+Field13:
+Field14:
+
+Some Field classes have an effect on the HTML attributes of their associated
+Widget. If you set max_length in a CharField and its associated widget is
+either a TextInput or PasswordInput, then the widget's rendered HTML will
+include the "maxlength" attribute.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10) # uses TextInput by default
+... password = CharField(max_length=10, widget=PasswordInput)
+... realname = CharField(max_length=10, widget=TextInput) # redundantly define widget, just to test
+... address = CharField() # no max_length defined here
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+Username:
+Password:
+Realname:
+Address:
+
+If you specify a custom "attrs" that includes the "maxlength" attribute,
+the Field's max_length attribute will override whatever "maxlength" you specify
+in "attrs".
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, widget=TextInput(attrs={'maxlength': 20}))
+... password = CharField(max_length=10, widget=PasswordInput)
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+Username:
+Password:
+
+You can specify the label for a field by using the 'label' argument to a Field
+class. If you don't specify 'label', Django will use the field name with
+underscores converted to spaces, and the initial letter capitalized.
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10, label='Your username')
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput, label='Password (again)')
+>>> p = UserRegistration(auto_id=False)
+>>> print p.as_ul()
+Your username:
+Password1:
+Password (again):
+
+# Forms with prefixes #########################################################
+
+Sometimes it's necessary to have multiple forms display on the same HTML page,
+or multiple copies of the same form. We can accomplish this with form prefixes.
+Pass the keyword argument 'prefix' to the Form constructor to use this feature.
+This value will be prepended to each HTML form field name. One way to think
+about this is "namespaces for HTML forms". Notice that in the data argument,
+each field's key has the prefix, in this case 'person1', prepended to the
+actual field name.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+>>> data = {
+... 'person1-first_name': u'John',
+... 'person1-last_name': u'Lennon',
+... 'person1-birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='person1')
+>>> print p.as_ul()
+First name:
+Last name:
+Birthday:
+>>> print p['first_name']
+
+>>> print p['last_name']
+
+>>> print p['birthday']
+
+>>> p.errors
+{}
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
+Let's try submitting some bad data to make sure form.errors and field.errors
+work as expected.
+>>> data = {
+... 'person1-first_name': u'',
+... 'person1-last_name': u'',
+... 'person1-birthday': u''
+... }
+>>> p = Person(data, prefix='person1')
+>>> p.errors
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
+>>> p['first_name'].errors
+[u'This field is required.']
+>>> p['person1-first_name'].errors
+Traceback (most recent call last):
+...
+KeyError: "Key 'person1-first_name' not found in Form"
+
+In this example, the data doesn't have a prefix, but the form requires it, so
+the form doesn't "see" the fields.
+>>> data = {
+... 'first_name': u'John',
+... 'last_name': u'Lennon',
+... 'birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='person1')
+>>> p.errors
+{'first_name': [u'This field is required.'], 'last_name': [u'This field is required.'], 'birthday': [u'This field is required.']}
+
+With prefixes, a single data dictionary can hold data for multiple instances
+of the same form.
+>>> data = {
+... 'person1-first_name': u'John',
+... 'person1-last_name': u'Lennon',
+... 'person1-birthday': u'1940-10-9',
+... 'person2-first_name': u'Jim',
+... 'person2-last_name': u'Morrison',
+... 'person2-birthday': u'1943-12-8'
+... }
+>>> p1 = Person(data, prefix='person1')
+>>> p1.is_valid()
+True
+>>> p1.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+>>> p2 = Person(data, prefix='person2')
+>>> p2.is_valid()
+True
+>>> p2.clean_data
+{'first_name': u'Jim', 'last_name': u'Morrison', 'birthday': datetime.date(1943, 12, 8)}
+
+By default, forms append a hyphen between the prefix and the field name, but a
+form can alter that behavior by implementing the add_prefix() method. This
+method takes a field name and returns the prefixed field, according to
+self.prefix.
+>>> class Person(Form):
+... first_name = CharField()
+... last_name = CharField()
+... birthday = DateField()
+... def add_prefix(self, field_name):
+... return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name
+>>> p = Person(prefix='foo')
+>>> print p.as_ul()
+First name:
+Last name:
+Birthday:
+>>> data = {
+... 'foo-prefix-first_name': u'John',
+... 'foo-prefix-last_name': u'Lennon',
+... 'foo-prefix-birthday': u'1940-10-9'
+... }
+>>> p = Person(data, prefix='foo')
+>>> p.is_valid()
+True
+>>> p.clean_data
+{'first_name': u'John', 'last_name': u'Lennon', 'birthday': datetime.date(1940, 10, 9)}
+
+# Basic form processing in a view #############################################
+
+>>> from django.template import Template, Context
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput)
+... def clean(self):
+... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... raise ValidationError(u'Please make sure your passwords match.')
+... return self.clean_data
+>>> def my_function(method, post_data):
+... if method == 'POST':
+... form = UserRegistration(post_data, auto_id=False)
+... else:
+... form = UserRegistration(auto_id=False)
+... if form.is_valid():
+... return 'VALID: %r' % form.clean_data
+... t = Template('')
+... return t.render(Context({'form': form}))
+
+Case 1: GET (an empty form, with no errors).
+>>> print my_function('GET', {})
+
+
+Case 2: POST with erroneous data (a redisplayed form, with errors).
+>>> print my_function('POST', {'username': 'this-is-a-long-username', 'password1': 'foo', 'password2': 'bar'})
+
+
+Case 3: POST with valid data (the success message).
+>>> print my_function('POST', {'username': 'adrian', 'password1': 'secret', 'password2': 'secret'})
+VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'}
+
+# Some ideas for using templates with forms ###################################
+
+>>> class UserRegistration(Form):
+... username = CharField(max_length=10)
+... password1 = CharField(widget=PasswordInput)
+... password2 = CharField(widget=PasswordInput)
+... def clean(self):
+... if self.clean_data.get('password1') and self.clean_data.get('password2') and self.clean_data['password1'] != self.clean_data['password2']:
+... raise ValidationError(u'Please make sure your passwords match.')
+... return self.clean_data
+
+You have full flexibility in displaying form fields in a template. Just pass a
+Form instance to the template, and use "dot" access to refer to individual
+fields. Note, however, that this flexibility comes with the responsibility of
+displaying all the errors, including any that might not be associated with a
+particular field.
+>>> t = Template('''''')
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
+
+>>> print t.render(Context({'form': UserRegistration({'username': 'django'}, auto_id=False)}))
+
+
+Use form.[field].label to output a field's label. You can specify the label for
+a field by using the 'label' argument to a Field class. If you don't specify
+'label', Django will use the field name with underscores converted to spaces,
+and the initial letter capitalized.
+>>> t = Template('''''')
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
+
+
+User form.[field].label_tag to output a field's label with a tag
+wrapped around it, but *only* if the given field has an "id" attribute.
+Recall from above that passing the "auto_id" argument to a Form gives each
+field an "id" attribute.
+>>> t = Template('''''')
+>>> print t.render(Context({'form': UserRegistration(auto_id=False)}))
+
+>>> print t.render(Context({'form': UserRegistration(auto_id='id_%s')}))
+
+
+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 (or an empty string, if
+the list of errors is empty). You can also use it in {% if %} statements.
+>>> t = Template(''' ''')
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)}))
+
+>>> t = Template('''''')
+>>> print t.render(Context({'form': UserRegistration({'username': 'django', 'password1': 'foo', 'password2': 'bar'}, auto_id=False)}))
+
"""
if __name__ == "__main__":
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 3c31bb0604..0a41f5b5b7 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -1,3 +1,4 @@
+# -*- coding: utf-8 -*-
from django.conf import settings
if __name__ == '__main__':
@@ -62,6 +63,11 @@ class OtherClass:
def method(self):
return "OtherClass.method"
+class UnicodeInStrClass:
+ "Class whose __str__ returns a Unicode object."
+ def __str__(self):
+ return u'ŠĐĆŽćžšđ'
+
class Templates(unittest.TestCase):
def test_templates(self):
# NOW and NOW_tz are used by timesince tag tests.
@@ -173,6 +179,10 @@ class Templates(unittest.TestCase):
# Empty strings can be passed as arguments to filters
'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'),
+ # If a variable has a __str__() that returns a Unicode object, the value
+ # will be converted to a bytestring.
+ 'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'),
+
### COMMENT SYNTAX ########################################################
'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
@@ -328,18 +338,18 @@ class Templates(unittest.TestCase):
'ifchanged05': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (1, 2, 3)}, '1123123123'),
'ifchanged06': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2)}, '1222'),
'ifchanged07': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% for x in numx %}{% ifchanged %}{{ x }}{% endifchanged %}{% for y in numy %}{% ifchanged %}{{ y }}{% endifchanged %}{% endfor %}{% endfor %}{% endfor %}', { 'num': (1, 1, 1), 'numx': (2, 2, 2), 'numy': (3, 3, 3)}, '1233323332333'),
-
+
# Test one parameter given to ifchanged.
'ifchanged-param01': ('{% for n in num %}{% ifchanged n %}..{% endifchanged %}{{ n }}{% endfor %}', { 'num': (1,2,3) }, '..1..2..3'),
'ifchanged-param02': ('{% for n in num %}{% for x in numx %}{% ifchanged n %}..{% endifchanged %}{{ x }}{% endfor %}{% endfor %}', { 'num': (1,2,3), 'numx': (5,6,7) }, '..567..567..567'),
-
+
# Test multiple parameters to ifchanged.
'ifchanged-param03': ('{% for n in num %}{{ n }}{% for x in numx %}{% ifchanged x n %}{{ x }}{% endifchanged %}{% endfor %}{% endfor %}', { 'num': (1,1,2), 'numx': (5,6,6) }, '156156256'),
-
+
# Test a date+hour like construct, where the hour of the last day
# is the same but the date had changed, so print the hour anyway.
'ifchanged-param04': ('{% for d in days %}{% ifchanged %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
-
+
# Logically the same as above, just written with explicit
# ifchanged for the day.
'ifchanged-param04': ('{% for d in days %}{% ifchanged d.day %}{{ d.day }}{% endifchanged %}{% for h in d.hours %}{% ifchanged d.day h %}{{ h }}{% endifchanged %}{% endfor %}{% endfor %}', {'days':[{'day':1, 'hours':[1,2,3]},{'day':2, 'hours':[3]},] }, '112323'),
diff --git a/tests/runtests.py b/tests/runtests.py
index 359fca2bf5..20189c2d99 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -40,12 +40,12 @@ def get_invalid_models():
if f.startswith('invalid'):
models.append((loc, f))
return models
-
+
class InvalidModelTestCase(unittest.TestCase):
def __init__(self, model_label):
unittest.TestCase.__init__(self)
self.model_label = model_label
-
+
def runTest(self):
from django.core import management
from django.db.models.loading import load_app
@@ -55,7 +55,7 @@ class InvalidModelTestCase(unittest.TestCase):
module = load_app(self.model_label)
except Exception, e:
self.fail('Unable to load invalid model module')
-
+
s = StringIO()
count = management.get_validation_errors(s, module)
s.seek(0)
@@ -71,39 +71,43 @@ class InvalidModelTestCase(unittest.TestCase):
def django_tests(verbosity, tests_to_run):
from django.conf import settings
- from django.db.models.loading import get_apps, load_app
old_installed_apps = settings.INSTALLED_APPS
old_test_database_name = settings.TEST_DATABASE_NAME
old_root_urlconf = settings.ROOT_URLCONF
old_template_dirs = settings.TEMPLATE_DIRS
-
+ old_use_i18n = settings.USE_I18N
+
# Redirect some settings for the duration of these tests
settings.TEST_DATABASE_NAME = TEST_DATABASE_NAME
settings.INSTALLED_APPS = ALWAYS_INSTALLED_APPS
settings.ROOT_URLCONF = 'urls'
settings.TEMPLATE_DIRS = (os.path.join(os.path.dirname(__file__), TEST_TEMPLATE_DIR),)
-
- # load all the ALWAYS_INSTALLED_APPS
+ settings.USE_I18N = True
+
+ # Load all the ALWAYS_INSTALLED_APPS.
+ # (This import statement is intentionally delayed until after we
+ # access settings because of the USE_I18N dependency.)
+ from django.db.models.loading import get_apps, load_app
get_apps()
-
+
# Load all the test model apps
test_models = []
- for model_dir, model_name in get_test_models():
+ for model_dir, model_name in get_test_models():
model_label = '.'.join([model_dir, model_name])
try:
# if the model was named on the command line, or
- # no models were named (i.e., run all), import
+ # no models were named (i.e., run all), import
# this model and add it to the list to test.
if not tests_to_run or model_name in tests_to_run:
if verbosity >= 1:
print "Importing model %s" % model_name
mod = load_app(model_label)
- settings.INSTALLED_APPS.append(model_label)
+ settings.INSTALLED_APPS.append(model_label)
test_models.append(mod)
except Exception, e:
sys.stderr.write("Error while importing %s:" % model_name + ''.join(traceback.format_exception(*sys.exc_info())[1:]))
- continue
+ continue
# Add tests for invalid models
extra_tests = []
@@ -111,28 +115,29 @@ def django_tests(verbosity, tests_to_run):
model_label = '.'.join([model_dir, model_name])
if not tests_to_run or model_name in tests_to_run:
extra_tests.append(InvalidModelTestCase(model_label))
-
+
# Run the test suite, including the extra validation tests.
from django.test.simple import run_tests
run_tests(test_models, verbosity, extra_tests=extra_tests)
-
+
# Restore the old settings
settings.INSTALLED_APPS = old_installed_apps
settings.TESTS_DATABASE_NAME = old_test_database_name
settings.ROOT_URLCONF = old_root_urlconf
settings.TEMPLATE_DIRS = old_template_dirs
-
+ settings.USE_I18N = old_use_i18n
+
if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [model model model ...]"
parser = OptionParser(usage=usage)
parser.add_option('-v','--verbosity', action='store', dest='verbosity', default='0',
type='choice', choices=['0', '1', '2'],
- help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
+ help='Verbosity level; 0=minimal output, 1=normal output, 2=all output')
parser.add_option('--settings',
help='Python path to settings module, e.g. "myproject.settings". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
options, args = parser.parse_args()
if options.settings:
os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
-
+
django_tests(int(options.verbosity), args)
--
cgit v1.3