diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-02-20 00:41:14 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-02-20 00:41:14 +0000 |
| commit | 67e0248806e8adc03ddd81e9543f77b540ed1673 (patch) | |
| tree | aea723939c32c32670070696a8fedb1ee51f2750 /tests | |
| parent | 649423e81c07c1db654c24f6779068e1e9823002 (diff) | |
newforms-admin: Merged to [4545]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@4546 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/modeltests/basic/models.py | 10 | ||||
| -rw-r--r-- | tests/modeltests/test_client/models.py | 12 | ||||
| -rw-r--r-- | tests/modeltests/test_client/urls.py | 1 | ||||
| -rw-r--r-- | tests/modeltests/test_client/views.py | 30 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 384 |
5 files changed, 422 insertions, 15 deletions
diff --git a/tests/modeltests/basic/models.py b/tests/modeltests/basic/models.py index 1663068892..9af13c0e3e 100644 --- a/tests/modeltests/basic/models.py +++ b/tests/modeltests/basic/models.py @@ -12,7 +12,7 @@ class Article(models.Model): class Meta: ordering = ('pub_date','headline') - + def __str__(self): return self.headline @@ -319,7 +319,6 @@ AttributeError: Manager isn't accessible via Article instances >>> Article.objects.filter(id__lte=4).delete() >>> Article.objects.all() [<Article: Article 6>, <Article: Default headline>, <Article: Article 7>, <Article: Updated article 8>] - """} from django.conf import settings @@ -358,4 +357,11 @@ __test__['API_TESTS'] += """ >>> a10 = Article.objects.create(headline="Article 10", pub_date=datetime(2005, 7, 31, 12, 30, 45)) >>> Article.objects.get(headline="Article 10") <Article: Article 10> + +# Edge-case test: A year lookup should retrieve all objects in the given +year, including Jan. 1 and Dec. 31. +>>> a11 = Article.objects.create(headline='Article 11', pub_date=datetime(2008, 1, 1)) +>>> a12 = Article.objects.create(headline='Article 12', pub_date=datetime(2008, 12, 31, 23, 59, 59, 999999)) +>>> Article.objects.filter(pub_date__year=2008) +[<Article: Article 11>, <Article: Article 12>] """ diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 74f5e9700c..c3febfb99f 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -43,7 +43,7 @@ class ClientTest(unittest.TestCase): # Check some response details self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, 'Empty POST Template') + self.assertEqual(response.template.name, 'Empty GET Template') def test_empty_post(self): "POST an empty dictionary to a view" @@ -53,7 +53,7 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') - def test_post_view(self): + def test_post(self): "POST some data to a view" post_data = { 'value': 37 @@ -66,6 +66,14 @@ class ClientTest(unittest.TestCase): self.assertEqual(response.template.name, 'POST Template') self.failUnless('Data received' in response.content) + def test_raw_post(self): + test_doc = """<?xml version="1.0" encoding="utf-8"?><library><book><title>Blink</title><author>Malcolm Gladwell</author></book></library>""" + response = self.client.post("/test_client/raw_post_view/", test_doc, + content_type="text/xml") + self.assertEqual(response.status_code, 200) + self.assertEqual(response.template.name, "Book template") + self.assertEqual(response.content, "Blink - Malcolm Gladwell") + def test_redirect(self): "GET a URL that redirects elsewhere" response = self.client.get('/test_client/redirect_view/') diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 96da4ec34e..0bef1e9b71 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -4,6 +4,7 @@ import views urlpatterns = patterns('', (r'^get_view/$', views.get_view), (r'^post_view/$', views.post_view), + (r'^raw_post_view/$', views.raw_post_view), (r'^redirect_view/$', views.redirect_view), (r'^login_protected_view/$', views.login_protected_view), (r'^session_view/$', views.session_view), diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index b1f2739845..653e9a10e9 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,3 +1,4 @@ +from xml.dom.minidom import parseString from django.template import Context, Template from django.http import HttpResponse, HttpResponseRedirect from django.contrib.auth.decorators import login_required @@ -13,15 +14,34 @@ def post_view(request): """A view that expects a POST, and returns a different template depending on whether any POST data is available """ - if request.POST: - t = Template('Data received: {{ data }} is the value.', name='POST Template') - c = Context({'data': request.POST['value']}) + if request.method == 'POST': + if request.POST: + t = Template('Data received: {{ data }} is the value.', name='POST Template') + c = Context({'data': request.POST['value']}) + else: + t = Template('Viewing POST page.', name='Empty POST Template') + c = Context() else: - t = Template('Viewing POST page.', name='Empty POST Template') + t = Template('Viewing GET page.', name='Empty GET Template') c = Context() - + return HttpResponse(t.render(c)) +def raw_post_view(request): + """A view which expects raw XML to be posted and returns content extracted + from the XML""" + if request.method == 'POST': + root = parseString(request.raw_post_data) + first_book = root.firstChild.firstChild + title, author = [n.firstChild.nodeValue for n in first_book.childNodes] + t = Template("{{ title }} - {{ author }}", name="Book template") + c = Context({"title": title, "author": author}) + else: + t = Template("GET request.", name="Book GET template") + c = Context() + + return HttpResponse(t.render(c)) + def redirect_view(request): "A view that redirects all requests to the GET view" return HttpResponseRedirect('/test_client/get_view/') diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index be17864b9d..c4502b2202 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -72,6 +72,22 @@ u'<input type="password" class="special" name="email" />' >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) u'<input type="password" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' +The render_value argument lets you specify whether the widget should render +its value. You may want to do this for security reasons. +>>> w = PasswordInput(render_value=True) +>>> w.render('email', 'secret') +u'<input type="password" name="email" value="secret" />' +>>> w = PasswordInput(render_value=False) +>>> w.render('email', '') +u'<input type="password" name="email" />' +>>> w.render('email', None) +u'<input type="password" name="email" />' +>>> w.render('email', 'secret') +u'<input type="password" name="email" />' +>>> w = PasswordInput(attrs={'class': 'fun'}, render_value=False) +>>> w.render('email', 'secret') +u'<input type="password" class="fun" name="email" />' + # HiddenInput Widget ############################################################ >>> w = HiddenInput() @@ -2201,6 +2217,19 @@ returns a list of input. >>> f.clean_data {'composers': [u'J', u'P'], 'name': u'Yesterday'} +Validation errors are HTML-escaped when output as HTML. +>>> class EscapingForm(Form): +... special_name = CharField() +... def clean_special_name(self): +... raise ValidationError("Something's wrong with '%s'" % self.clean_data['special_name']) + +>>> f = EscapingForm({'special_name': "Nothing to escape"}, auto_id=False) +>>> print f +<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Nothing to escape'</li></ul><input type="text" name="special_name" value="Nothing to escape" /></td></tr> +>>> f = EscapingForm({'special_name': "Should escape < & > and <script>alert('xss')</script>"}, auto_id=False) +>>> print f +<tr><th>Special name:</th><td><ul class="errorlist"><li>Something's wrong with 'Should escape < & > and <script>alert('xss')</script>'</li></ul><input type="text" name="special_name" value="Should escape < & > and <script>alert('xss')</script>" /></td></tr> + # Validating multiple fields in relation to another ########################### There are a couple of ways to do multiple-field validation. If you want the @@ -2334,6 +2363,43 @@ the next. <tr><th>Field3:</th><td><input type="text" name="field3" /></td></tr> <tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr> +Similarly, changes to field attributes do not persist from one Form instance +to the next. +>>> class Person(Form): +... first_name = CharField(required=False) +... last_name = CharField(required=False) +... def __init__(self, names_required=False, *args, **kwargs): +... super(Person, self).__init__(*args, **kwargs) +... if names_required: +... self.fields['first_name'].required = True +... self.fields['last_name'].required = True +>>> f = Person(names_required=False) +>>> f['first_name'].field.required, f['last_name'].field.required +(False, False) +>>> f = Person(names_required=True) +>>> f['first_name'].field.required, f['last_name'].field.required +(True, True) +>>> f = Person(names_required=False) +>>> f['first_name'].field.required, f['last_name'].field.required +(False, False) +>>> class Person(Form): +... first_name = CharField(max_length=30) +... last_name = CharField(max_length=30) +... def __init__(self, name_max_length=None, *args, **kwargs): +... super(Person, self).__init__(*args, **kwargs) +... if name_max_length: +... self.fields['first_name'].max_length = name_max_length +... self.fields['last_name'].max_length = name_max_length +>>> f = Person(name_max_length=None) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(30, 30) +>>> f = Person(name_max_length=20) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(20, 20) +>>> f = Person(name_max_length=None) +>>> f['first_name'].field.max_length, f['last_name'].field.max_length +(30, 30) + HiddenInput widgets are displayed differently in the as_table(), as_ul() and as_p() output of a Form -- their verbose names are not displayed, and a separate row is not displayed. They're displayed in the last row of the @@ -2645,6 +2711,54 @@ purposes, though. <li>Username: <input type="text" name="username" maxlength="10" /> e.g., user@example.com</li> <li>Password: <input type="password" name="password" /><input type="hidden" name="next" value="/" /></li> +Help text can include arbitrary Unicode characters. +>>> class UserRegistration(Form): +... username = CharField(max_length=10, help_text='ŠĐĆŽćžšđ') +>>> p = UserRegistration(auto_id=False) +>>> p.as_ul() +u'<li>Username: <input type="text" name="username" maxlength="10" /> \u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111</li>' + +# Subclassing forms ########################################################### + +You can subclass a Form to add fields. The resulting form subclass will have +all of the fields of the parent Form, plus whichever fields you define in the +subclass. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Musician(Person): +... instrument = CharField() +>>> p = Person(auto_id=False) +>>> print p.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +>>> m = Musician(auto_id=False) +>>> print m.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> + +Yes, you can subclass multiple forms. The fields are added in the order in +which the parent classes are listed. +>>> class Person(Form): +... first_name = CharField() +... last_name = CharField() +... birthday = DateField() +>>> class Instrument(Form): +... instrument = CharField() +>>> class Beatle(Person, Instrument): +... haircut_type = CharField() +>>> b = Beatle(auto_id=False) +>>> print b.as_ul() +<li>First name: <input type="text" name="first_name" /></li> +<li>Last name: <input type="text" name="last_name" /></li> +<li>Birthday: <input type="text" name="birthday" /></li> +<li>Instrument: <input type="text" name="instrument" /></li> +<li>Haircut type: <input type="text" name="haircut_type" /></li> + # Forms with prefixes ######################################################### Sometimes it's necessary to have multiple forms display on the same HTML page, @@ -2858,7 +2972,7 @@ VALID: {'username': u'adrian', 'password1': u'secret', 'password2': u'secret'} # Some ideas for using templates with forms ################################### >>> class UserRegistration(Form): -... username = CharField(max_length=10) +... username = CharField(max_length=10, help_text="Good luck picking a username that doesn't already exist.") ... password1 = CharField(widget=PasswordInput) ... password2 = CharField(widget=PasswordInput) ... def clean(self): @@ -2935,6 +3049,24 @@ field an "id" attribute. <input type="submit" /> </form> +User form.[field].help_text to output a field's help text. If the given field +does not have help text, nothing will be output. +>>> t = Template('''<form action=""> +... <p>{{ form.username.label_tag }}: {{ form.username }}<br />{{ form.username.help_text }}</p> +... <p>{{ form.password1.label_tag }}: {{ form.password1 }}</p> +... <p>{{ form.password2.label_tag }}: {{ form.password2 }}</p> +... <input type="submit" /> +... </form>''') +>>> print t.render(Context({'form': UserRegistration(auto_id=False)})) +<form action=""> +<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p> +<p>Password1: <input type="password" name="password1" /></p> +<p>Password2: <input type="password" name="password2" /></p> +<input type="submit" /> +</form> +>>> Template('{{ form.password1.help_text }}').render(Context({'form': UserRegistration(auto_id=False)})) +'' + The label_tag() method takes an optional attrs argument: a dictionary of HTML attributes to add to the <label> tag. >>> f = UserRegistration(auto_id='id_%s') @@ -2977,12 +3109,12 @@ the list of errors is empty). You can also use it in {% if %} statements. <input type="submit" /> </form> -################# -# Extra widgets # -################# +############### +# Extra stuff # +############### -The newforms library comes with some extra, higher-level Widget classes that -demonstrate some of the library's abilities. +The newforms library comes with some extra, higher-level Field and Widget +classes that demonstrate some of the library's abilities. # SelectDateWidget ############################################################ @@ -3111,6 +3243,246 @@ True <option value="2016">2016</option> </select> +# USZipCodeField ############################################################## + +USZipCodeField validates that the data is either a five-digit U.S. zip code or +a zip+4. +>>> from django.contrib.localflavor.usa.forms import USZipCodeField +>>> f = USZipCodeField() +>>> f.clean('60606') +u'60606' +>>> f.clean(60606) +u'60606' +>>> f.clean('04000') +u'04000' +>>> f.clean('4000') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-1234') +u'60606-1234' +>>> f.clean('6060-1234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = USZipCodeField(required=False) +>>> f.clean('60606') +u'60606' +>>> f.clean(60606) +u'60606' +>>> f.clean('04000') +u'04000' +>>> f.clean('4000') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-1234') +u'60606-1234' +>>> f.clean('6060-1234') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean('60606-') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXXX or XXXXX-XXXX.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# USStateField ################################################################ + +USStateField validates that the data is either an abbreviation or name of a +U.S. state. +>>> from django.contrib.localflavor.usa.forms import USStateField +>>> f = USStateField() +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = USStateField(required=False) +>>> f.clean('il') +u'IL' +>>> f.clean('IL') +u'IL' +>>> f.clean('illinois') +u'IL' +>>> f.clean(' illinois ') +u'IL' +>>> f.clean(60606) +Traceback (most recent call last): +... +ValidationError: [u'Enter a U.S. state or territory.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +# USStateSelect ############################################################### + +USStateSelect is a Select widget that uses a list of U.S. states/territories +as its choices. +>>> from django.contrib.localflavor.usa.forms import USStateSelect +>>> w = USStateSelect() +>>> print w.render('state', 'IL') +<select name="state"> +<option value="AL">Alabama</option> +<option value="AK">Alaska</option> +<option value="AS">American Samoa</option> +<option value="AZ">Arizona</option> +<option value="AR">Arkansas</option> +<option value="CA">California</option> +<option value="CO">Colorado</option> +<option value="CT">Connecticut</option> +<option value="DE">Deleware</option> +<option value="DC">District of Columbia</option> +<option value="FM">Federated States of Micronesia</option> +<option value="FL">Florida</option> +<option value="GA">Georgia</option> +<option value="GU">Guam</option> +<option value="HI">Hawaii</option> +<option value="ID">Idaho</option> +<option value="IL" selected="selected">Illinois</option> +<option value="IN">Indiana</option> +<option value="IA">Iowa</option> +<option value="KS">Kansas</option> +<option value="KY">Kentucky</option> +<option value="LA">Louisiana</option> +<option value="ME">Maine</option> +<option value="MH">Marshall Islands</option> +<option value="MD">Maryland</option> +<option value="MI">Michigan</option> +<option value="MN">Minnesota</option> +<option value="MS">Mississippi</option> +<option value="MO">Missouri</option> +<option value="MT">Montana</option> +<option value="NE">Nebraska</option> +<option value="NV">Nevada</option> +<option value="NH">New Hampshire</option> +<option value="NJ">New Jersey</option> +<option value="NM">New Mexico</option> +<option value="NY">New York</option> +<option value="NC">North Carolina</option> +<option value="ND">North Dakota</option> +<option value="MP">Northern Mariana Islands</option> +<option value="OH">Ohio</option> +<option value="OK">Oklahoma</option> +<option value="OR">Oregon</option> +<option value="PW">Palau</option> +<option value="PA">Pennsylvania</option> +<option value="PR">Puerto Rico</option> +<option value="RI">Rhode Island</option> +<option value="SC">South Carolina</option> +<option value="SD">South Dakota</option> +<option value="TN">Tennessee</option> +<option value="TX">Texas</option> +<option value="UT">Utah</option> +<option value="VT">Vermont</option> +<option value="VI">Virgin Islands</option> +<option value="VA">Virginia</option> +<option value="WA">Washington</option> +<option value="WV">West Virginia</option> +<option value="WI">Wisconsin</option> +<option value="WY">Wyoming</option> +</select> + +# UKPostcodeField ############################################################# + +UKPostcodeField validates that the data is a valid UK postcode. +>>> from django.contrib.localflavor.uk.forms import UKPostcodeField +>>> f = UKPostcodeField() +>>> f.clean('BT32 4PX') +u'BT32 4PX' +>>> f.clean('GIR 0AA') +u'GIR 0AA' +>>> f.clean('BT324PX') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean('1NV 4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> f = UKPostcodeField(required=False) +>>> f.clean('BT32 4PX') +u'BT32 4PX' +>>> f.clean('GIR 0AA') +u'GIR 0AA' +>>> f.clean('1NV 4L1D') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean('BT324PX') +Traceback (most recent call last): +... +ValidationError: [u'Enter a postcode. A space is required between the two postcode parts.'] +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +################################# +# Tests of underlying functions # +################################# + +# smart_unicode tests +>>> from django.newforms.util import smart_unicode +>>> class Test: +... def __str__(self): +... return 'ŠĐĆŽćžšđ' +>>> class TestU: +... def __str__(self): +... return 'Foo' +... def __unicode__(self): +... return u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(Test()) +u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(TestU()) +u'\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111' +>>> smart_unicode(1) +u'1' +>>> smart_unicode('foo') +u'foo' """ if __name__ == "__main__": |
