diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-05-12 02:36:05 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-05-12 02:36:05 +0000 |
| commit | 415e84ad53e0d0d8f7df87784c1893489bdbe0b8 (patch) | |
| tree | 09541f8319c45ec51fb44154534abc41cb86aee9 /tests | |
| parent | 4938c8ea6db6f23ebb0883b8a092985344508b25 (diff) | |
newforms-admin: Merged to [5194]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5195 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
27 files changed, 1003 insertions, 65 deletions
diff --git a/tests/modeltests/custom_columns/models.py b/tests/modeltests/custom_columns/models.py index c09ca05557..1283da07cf 100644 --- a/tests/modeltests/custom_columns/models.py +++ b/tests/modeltests/custom_columns/models.py @@ -71,7 +71,7 @@ __test__ = {'API_TESTS':""" >>> Author.objects.filter(firstname__exact='John') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'firstname' into field +TypeError: Cannot resolve keyword 'firstname' into field. Choices are: article, id, first_name, last_name >>> a = Author.objects.get(last_name__exact='Smith') >>> a.first_name diff --git a/tests/modeltests/generic_relations/models.py b/tests/modeltests/generic_relations/models.py index 2b2f64165f..195f67db8f 100644 --- a/tests/modeltests/generic_relations/models.py +++ b/tests/modeltests/generic_relations/models.py @@ -11,6 +11,7 @@ from complete). from django.db import models from django.contrib.contenttypes.models import ContentType +from django.contrib.contenttypes import generic class TaggedItem(models.Model): """A tag on an item.""" @@ -18,7 +19,7 @@ class TaggedItem(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() - content_object = models.GenericForeignKey() + content_object = generic.GenericForeignKey() class Meta: ordering = ["tag"] @@ -30,7 +31,7 @@ class Animal(models.Model): common_name = models.CharField(maxlength=150) latin_name = models.CharField(maxlength=150) - tags = models.GenericRelation(TaggedItem) + tags = generic.GenericRelation(TaggedItem) def __str__(self): return self.common_name @@ -39,7 +40,7 @@ class Vegetable(models.Model): name = models.CharField(maxlength=150) is_yucky = models.BooleanField(default=True) - tags = models.GenericRelation(TaggedItem) + tags = generic.GenericRelation(TaggedItem) def __str__(self): return self.name diff --git a/tests/modeltests/lookup/models.py b/tests/modeltests/lookup/models.py index 106c97d3b4..c634aef8a1 100644 --- a/tests/modeltests/lookup/models.py +++ b/tests/modeltests/lookup/models.py @@ -223,11 +223,11 @@ DoesNotExist: Article matching query does not exist. >>> Article.objects.filter(pub_date_year='2005').count() Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'pub_date_year' into field +TypeError: Cannot resolve keyword 'pub_date_year' into field. Choices are: id, headline, pub_date >>> Article.objects.filter(headline__starts='Article') Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'headline__starts' into field +TypeError: Cannot resolve keyword 'headline__starts' into field. Choices are: id, headline, pub_date """} diff --git a/tests/modeltests/many_to_one/models.py b/tests/modeltests/many_to_one/models.py index 82eb3257d0..02f7bf1066 100644 --- a/tests/modeltests/many_to_one/models.py +++ b/tests/modeltests/many_to_one/models.py @@ -174,13 +174,13 @@ False >>> Article.objects.filter(reporter_id__exact=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field +TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter # You need to specify a comparison clause >>> Article.objects.filter(reporter_id=1) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'reporter_id' into field +TypeError: Cannot resolve keyword 'reporter_id' into field. Choices are: id, headline, pub_date, reporter # You can also instantiate an Article by passing # the Reporter's ID instead of a Reporter object. diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py index 2757787571..a23529b566 100644 --- a/tests/modeltests/model_forms/models.py +++ b/tests/modeltests/model_forms/models.py @@ -24,6 +24,12 @@ a commit=True parameter. from django.db import models +ARTICLE_STATUS = ( + (1, 'Draft'), + (2, 'Pending'), + (3, 'Live'), +) + class Category(models.Model): name = models.CharField(maxlength=20) url = models.CharField('The URL', maxlength=40) @@ -44,6 +50,7 @@ class Article(models.Model): writer = models.ForeignKey(Writer) article = models.TextField() categories = models.ManyToManyField(Category, blank=True) + status = models.IntegerField(choices=ARTICLE_STATUS, blank=True, null=True) def save(self): import datetime @@ -147,8 +154,8 @@ Create a couple of Writers. >>> w = Writer(name='Bob Woodward') >>> w.save() -ManyToManyFields are represented by a MultipleChoiceField, and ForeignKeys are -represented by a ChoiceField. +ManyToManyFields are represented by a MultipleChoiceField, ForeignKeys and any +fields with the 'choices' attribute are represented by a ChoiceField. >>> ArticleForm = form_for_model(Article) >>> f = ArticleForm(auto_id=False) >>> print f @@ -160,6 +167,12 @@ represented by a ChoiceField. <option value="2">Bob Woodward</option> </select></td></tr> <tr><th>Article:</th><td><textarea rows="10" cols="40" name="article"></textarea></td></tr> +<tr><th>Status:</th><td><select name="status"> +<option value="" selected="selected">---------</option> +<option value="1">Draft</option> +<option value="2">Pending</option> +<option value="3">Live</option> +</select></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> @@ -200,6 +213,12 @@ current values are inserted as 'initial' data in each Field. <option value="2">Bob Woodward</option> </select></li> <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li> +<li>Status: <select name="status"> +<option value="" selected="selected">---------</option> +<option value="1">Draft</option> +<option value="2">Pending</option> +<option value="3">Live</option> +</select></li> <li>Categories: <select multiple="multiple" name="categories"> <option value="1">Entertainment</option> <option value="2">It's a test</option> @@ -232,6 +251,12 @@ Add some categories and test the many-to-many form output. <option value="2">Bob Woodward</option> </select></li> <li>Article: <textarea rows="10" cols="40" name="article">Hello.</textarea></li> +<li>Status: <select name="status"> +<option value="" selected="selected">---------</option> +<option value="1">Draft</option> +<option value="2">Pending</option> +<option value="3">Live</option> +</select></li> <li>Categories: <select multiple="multiple" name="categories"> <option value="1" selected="selected">Entertainment</option> <option value="2">It's a test</option> @@ -310,6 +335,12 @@ the data in the database when the form is instantiated. <option value="2">Bob Woodward</option> </select></li> <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li> +<li>Status: <select name="status"> +<option value="" selected="selected">---------</option> +<option value="1">Draft</option> +<option value="2">Pending</option> +<option value="3">Live</option> +</select></li> <li>Categories: <select multiple="multiple" name="categories"> <option value="1">Entertainment</option> <option value="2">It's a test</option> @@ -329,6 +360,12 @@ the data in the database when the form is instantiated. <option value="3">Carl Bernstein</option> </select></li> <li>Article: <textarea rows="10" cols="40" name="article"></textarea></li> +<li>Status: <select name="status"> +<option value="" selected="selected">---------</option> +<option value="1">Draft</option> +<option value="2">Pending</option> +<option value="3">Live</option> +</select></li> <li>Categories: <select multiple="multiple" name="categories"> <option value="1">Entertainment</option> <option value="2">It's a test</option> diff --git a/tests/modeltests/reverse_lookup/models.py b/tests/modeltests/reverse_lookup/models.py index 7e6712676f..4d6591551a 100644 --- a/tests/modeltests/reverse_lookup/models.py +++ b/tests/modeltests/reverse_lookup/models.py @@ -55,5 +55,5 @@ __test__ = {'API_TESTS':""" >>> Poll.objects.get(choice__name__exact="This is the answer") Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'choice' into field +TypeError: Cannot resolve keyword 'choice' into field. Choices are: poll_choice, related_choice, id, question, creator """} diff --git a/tests/modeltests/test_client/models.py b/tests/modeltests/test_client/models.py index 44ddffb55f..5ebf29678c 100644 --- a/tests/modeltests/test_client/models.py +++ b/tests/modeltests/test_client/models.py @@ -20,23 +20,19 @@ rather than the HTML rendered to the end-user. """ from django.test import Client, TestCase +from django.core import mail class ClientTest(TestCase): fixtures = ['testdata.json'] - def setUp(self): - "Set up test environment" - self.client = Client() - def test_get_view(self): "GET a view" response = self.client.get('/test_client/get_view/') # Check some response details - self.assertEqual(response.status_code, 200) + self.assertContains(response, 'This is a test') self.assertEqual(response.context['var'], 42) self.assertEqual(response.template.name, 'GET Template') - self.failUnless('This is a test.' in response.content) def test_get_post_view(self): "GET a view that normally expects POSTs" @@ -45,6 +41,8 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty GET Template') + self.assertTemplateUsed(response, 'Empty GET Template') + self.assertTemplateNotUsed(response, 'Empty POST Template') def test_empty_post(self): "POST an empty dictionary to a view" @@ -53,6 +51,8 @@ class ClientTest(TestCase): # Check some response details self.assertEqual(response.status_code, 200) self.assertEqual(response.template.name, 'Empty POST Template') + self.assertTemplateNotUsed(response, 'Empty GET Template') + self.assertTemplateUsed(response, 'Empty POST Template') def test_post(self): "POST some data to a view" @@ -68,6 +68,7 @@ class ClientTest(TestCase): self.failUnless('Data received' in response.content) def test_raw_post(self): + "POST raw data (with a content type) to a view" 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") @@ -80,7 +81,29 @@ class ClientTest(TestCase): response = self.client.get('/test_client/redirect_view/') # Check that the response was a 302 (redirect) - self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/test_client/get_view/') + + def test_permanent_redirect(self): + "GET a URL that redirects permanently elsewhere" + response = self.client.get('/test_client/permanent_redirect_view/') + + # Check that the response was a 301 (permanent redirect) + self.assertRedirects(response, '/test_client/get_view/', status_code=301) + + def test_redirect_to_strange_location(self): + "GET a URL that redirects to a non-200 page" + response = self.client.get('/test_client/double_redirect_view/') + + # Check that the response was a 302, and that + # the attempt to get the redirection location returned 301 when retrieved + self.assertRedirects(response, '/test_client/permanent_redirect_view/', target_status_code=301) + + def test_notfound_response(self): + "GET a URL that responds as '404:Not Found'" + response = self.client.get('/test_client/bad_view/') + + # Check that the response was a 404, and that the content contains MAGIC + self.assertContains(response, 'MAGIC', status_code=404) def test_valid_form(self): "POST valid data to a form" @@ -93,7 +116,7 @@ class ClientTest(TestCase): } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Valid POST Template") + self.assertTemplateUsed(response, "Valid POST Template") def test_incomplete_data_form(self): "POST incomplete data to a form" @@ -102,8 +125,13 @@ class ClientTest(TestCase): 'value': 37 } response = self.client.post('/test_client/form_view/', post_data) + self.assertContains(response, 'This field is required.', 3) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Invalid POST Template") + self.assertTemplateUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'This field is required.') + self.assertFormError(response, 'form', 'single', 'This field is required.') + self.assertFormError(response, 'form', 'multi', 'This field is required.') def test_form_error(self): "POST erroneous data to a form" @@ -116,7 +144,57 @@ class ClientTest(TestCase): } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) - self.assertEqual(response.template.name, "Invalid POST Template") + self.assertTemplateUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') + + def test_valid_form_with_template(self): + "POST valid data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'email': 'foo@example.com', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data OK') + self.assertTemplateUsed(response, "form_view.html") + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Valid POST Template") + + def test_incomplete_data_form_with_template(self): + "POST incomplete data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'value': 37 + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data has errors') + self.assertTemplateUsed(response, 'form_view.html') + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'This field is required.') + self.assertFormError(response, 'form', 'single', 'This field is required.') + self.assertFormError(response, 'form', 'multi', 'This field is required.') + + def test_form_error_with_template(self): + "POST erroneous data to a form using multiple templates" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data has errors') + self.assertTemplateUsed(response, "form_view.html") + self.assertTemplateUsed(response, 'base.html') + self.assertTemplateNotUsed(response, "Invalid POST Template") + + self.assertFormError(response, 'form', 'email', 'Enter a valid e-mail address.') def test_unknown_page(self): "GET an invalid URL" @@ -130,20 +208,21 @@ class ClientTest(TestCase): # Get the page without logging in. Should result in 302. response = self.client.get('/test_client/login_protected_view/') - self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/accounts/login/') + # Log in + self.client.login(username='testclient', password='password') + # Request a page that requires a login - response = self.client.login('/test_client/login_protected_view/', 'testclient', 'password') - self.failUnless(response) + response = self.client.get('/test_client/login_protected_view/') self.assertEqual(response.status_code, 200) self.assertEqual(response.context['user'].username, 'testclient') - self.assertEqual(response.template.name, 'Login Template') def test_view_with_bad_login(self): "Request a page that is protected with @login, but use bad credentials" - response = self.client.login('/test_client/login_protected_view/', 'otheruser', 'nopassword') - self.failIf(response) + login = self.client.login(username='otheruser', password='nopassword') + self.failIf(login) def test_session_modifying_view(self): "Request a page that modifies the session" @@ -170,3 +249,36 @@ class ClientTest(TestCase): self.fail('Should raise an error') except KeyError: pass + + def test_mail_sending(self): + "Test that mail is redirected to a dummy outbox during test setup" + + response = self.client.get('/test_client/mail_sending_view/') + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].subject, 'Test message') + self.assertEqual(mail.outbox[0].body, 'This is a test email') + self.assertEqual(mail.outbox[0].from_email, 'from@example.com') + self.assertEqual(mail.outbox[0].to[0], 'first@example.com') + self.assertEqual(mail.outbox[0].to[1], 'second@example.com') + + def test_mass_mail_sending(self): + "Test that mass mail is redirected to a dummy outbox during test setup" + + response = self.client.get('/test_client/mass_mail_sending_view/') + self.assertEqual(response.status_code, 200) + + self.assertEqual(len(mail.outbox), 2) + self.assertEqual(mail.outbox[0].subject, 'First Test message') + self.assertEqual(mail.outbox[0].body, 'This is the first test email') + self.assertEqual(mail.outbox[0].from_email, 'from@example.com') + self.assertEqual(mail.outbox[0].to[0], 'first@example.com') + self.assertEqual(mail.outbox[0].to[1], 'second@example.com') + + self.assertEqual(mail.outbox[1].subject, 'Second Test message') + self.assertEqual(mail.outbox[1].body, 'This is the second test email') + self.assertEqual(mail.outbox[1].from_email, 'from@example.com') + self.assertEqual(mail.outbox[1].to[0], 'second@example.com') + self.assertEqual(mail.outbox[1].to[1], 'third@example.com') +
\ No newline at end of file diff --git a/tests/modeltests/test_client/urls.py b/tests/modeltests/test_client/urls.py index 707ecc186d..538c0e4b43 100644 --- a/tests/modeltests/test_client/urls.py +++ b/tests/modeltests/test_client/urls.py @@ -1,4 +1,5 @@ from django.conf.urls.defaults import * +from django.views.generic.simple import redirect_to import views urlpatterns = patterns('', @@ -6,8 +7,14 @@ urlpatterns = patterns('', (r'^post_view/$', views.post_view), (r'^raw_post_view/$', views.raw_post_view), (r'^redirect_view/$', views.redirect_view), + (r'^permanent_redirect_view/$', redirect_to, { 'url': '/test_client/get_view/' }), + (r'^double_redirect_view/$', views.double_redirect_view), + (r'^bad_view/$', views.bad_view), (r'^form_view/$', views.form_view), + (r'^form_view_with_template/$', views.form_view_with_template), (r'^login_protected_view/$', views.login_protected_view), (r'^session_view/$', views.session_view), - (r'^broken_view/$', views.broken_view) + (r'^broken_view/$', views.broken_view), + (r'^mail_sending_view/$', views.mail_sending_view), + (r'^mass_mail_sending_view/$', views.mass_mail_sending_view) ) diff --git a/tests/modeltests/test_client/views.py b/tests/modeltests/test_client/views.py index 36ec144cf6..9bdf213b35 100644 --- a/tests/modeltests/test_client/views.py +++ b/tests/modeltests/test_client/views.py @@ -1,9 +1,11 @@ from xml.dom.minidom import parseString +from django.core.mail import EmailMessage, SMTPConnection from django.template import Context, Template -from django.http import HttpResponse, HttpResponseRedirect +from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound from django.contrib.auth.decorators import login_required from django.newforms.forms import Form from django.newforms import fields +from django.shortcuts import render_to_response def get_view(request): "A simple view that expects a GET request, and returns a rendered template" @@ -48,6 +50,14 @@ def redirect_view(request): "A view that redirects all requests to the GET view" return HttpResponseRedirect('/test_client/get_view/') +def double_redirect_view(request): + "A view that redirects all requests to a redirection view" + return HttpResponseRedirect('/test_client/permanent_redirect_view/') + +def bad_view(request): + "A view that returns a 404 with some error content" + return HttpResponseNotFound('Not found!. This page contains some MAGIC content') + TestChoices = ( ('a', 'First Choice'), ('b', 'Second Choice'), @@ -79,6 +89,25 @@ def form_view(request): c = Context({'form': form}) return HttpResponse(t.render(c)) + +def form_view_with_template(request): + "A view that tests a simple form" + if request.method == 'POST': + form = TestForm(request.POST) + if form.is_valid(): + message = 'POST data OK' + else: + message = 'POST data has errors' + else: + form = TestForm() + message = 'GET form page' + return render_to_response('form_view.html', + { + 'form': form, + 'message': message + } + ) + def login_protected_view(request): "A simple view that is login protected." @@ -100,3 +129,28 @@ def session_view(request): def broken_view(request): """A view which just raises an exception, simulating a broken view.""" raise KeyError("Oops! Looks like you wrote some bad code.") + +def mail_sending_view(request): + EmailMessage( + "Test message", + "This is a test email", + "from@example.com", + ['first@example.com', 'second@example.com']).send() + return HttpResponse("Mail sent") + +def mass_mail_sending_view(request): + m1 = EmailMessage( + 'First Test message', + 'This is the first test email', + 'from@example.com', + ['first@example.com', 'second@example.com']) + m2 = EmailMessage( + 'Second Test message', + 'This is the second test email', + 'from@example.com', + ['second@example.com', 'third@example.com']) + + c = SMTPConnection() + c.send_messages([m1,m2]) + + return HttpResponse("Mail sent") diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py index cf58ab321a..9dc7161c03 100644 --- a/tests/regressiontests/cache/tests.py +++ b/tests/regressiontests/cache/tests.py @@ -46,6 +46,11 @@ class Cache(unittest.TestCase): self.assertEqual(cache.has_key("hello"), True) self.assertEqual(cache.has_key("goodbye"), False) + def test_in(self): + cache.set("hello", "goodbye") + self.assertEqual("hello" in cache, True) + self.assertEqual("goodbye" in cache, False) + def test_data_types(self): # test data types stuff = { diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py index e008c255f1..18eb4fcccd 100644 --- a/tests/regressiontests/datastructures/tests.py +++ b/tests/regressiontests/datastructures/tests.py @@ -52,6 +52,8 @@ 'not one' >>> d.keys() == d.copy().keys() True +>>> print repr(d) +{'one': 'not one', 'two': 'two', 'three': 'three'} ### DotExpandedDict ############################################################ diff --git a/tests/regressiontests/dispatch/tests/test_saferef.py b/tests/regressiontests/dispatch/tests/test_saferef.py index 233a2023e0..c0ec879a4b 100644 --- a/tests/regressiontests/dispatch/tests/test_saferef.py +++ b/tests/regressiontests/dispatch/tests/test_saferef.py @@ -55,8 +55,10 @@ class Tester(unittest.TestCase): for t in self.ts: if hasattr(t, 'x'): self.assert_(sd.has_key(safeRef(t.x))) + self.assert_(safeRef(t.x) in sd) else: self.assert_(sd.has_key(safeRef(t))) + self.assert_(safeRef(t) in sd) def testRepresentation (self): """Test that the reference object's representation works diff --git a/tests/regressiontests/fixtures_regress/models.py b/tests/regressiontests/fixtures_regress/models.py index f9d4d35045..dd407df353 100644 --- a/tests/regressiontests/fixtures_regress/models.py +++ b/tests/regressiontests/fixtures_regress/models.py @@ -7,6 +7,13 @@ class Animal(models.Model): def __str__(self): return self.common_name +class Plant(models.Model): + name = models.CharField(maxlength=150) + + class Meta: + # For testing when upper case letter in app name; regression for #4057 + db_table = "Fixtures_regress_plant" + __test__ = {'API_TESTS':""" >>> from django.core import management diff --git a/tests/regressiontests/forms/localflavor.py b/tests/regressiontests/forms/localflavor.py index b37aa3c6ea..ede89de2a0 100644 --- a/tests/regressiontests/forms/localflavor.py +++ b/tests/regressiontests/forms/localflavor.py @@ -193,7 +193,7 @@ as its choices. <option value="CA">California</option> <option value="CO">Colorado</option> <option value="CT">Connecticut</option> -<option value="DE">Deleware</option> +<option value="DE">Delaware</option> <option value="DC">District of Columbia</option> <option value="FM">Federated States of Micronesia</option> <option value="FL">Florida</option> @@ -633,7 +633,7 @@ u'00100' >>> f.clean(' 00100') Traceback (most recent call last): ... -ValidationError: [u'Enter a zip code in the format XXXXX.'] +ValidationError: [u'Enter a valid zip code.'] # ITRegionSelect ############################################################# @@ -642,6 +642,46 @@ ValidationError: [u'Enter a zip code in the format XXXXX.'] >>> w.render('regions', 'PMN') u'<select name="regions">\n<option value="ABR">Abruzzo</option>\n<option value="BAS">Basilicata</option>\n<option value="CAL">Calabria</option>\n<option value="CAM">Campania</option>\n<option value="EMR">Emilia-Romagna</option>\n<option value="FVG">Friuli-Venezia Giulia</option>\n<option value="LAZ">Lazio</option>\n<option value="LIG">Liguria</option>\n<option value="LOM">Lombardia</option>\n<option value="MAR">Marche</option>\n<option value="MOL">Molise</option>\n<option value="PMN" selected="selected">Piemonte</option>\n<option value="PUG">Puglia</option>\n<option value="SAR">Sardegna</option>\n<option value="SIC">Sicilia</option>\n<option value="TOS">Toscana</option>\n<option value="TAA">Trentino-Alto Adige</option>\n<option value="UMB">Umbria</option>\n<option value="VAO">Valle d\u2019Aosta</option>\n<option value="VEN">Veneto</option>\n</select>' +# ITSocialSecurityNumberField ################################################# + +>>> from django.contrib.localflavor.it.forms import ITSocialSecurityNumberField +>>> f = ITSocialSecurityNumberField() +>>> f.clean('LVSGDU99T71H501L') +u'LVSGDU99T71H501L' +>>> f.clean('LBRRME11A01L736W') +u'LBRRME11A01L736W' +>>> f.clean('lbrrme11a01l736w') +u'LBRRME11A01L736W' +>>> f.clean('LBR RME 11A01 L736W') +u'LBRRME11A01L736W' +>>> f.clean('LBRRME11A01L736A') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Social Security number.'] +>>> f.clean('%BRRME11A01L736W') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Social Security number.'] + +# ITVatNumberField ########################################################### + +>>> from django.contrib.localflavor.it.forms import ITVatNumberField +>>> f = ITVatNumberField() +>>> f.clean('07973780013') +u'07973780013' +>>> f.clean('7973780013') +u'07973780013' +>>> f.clean(7973780013) +u'07973780013' +>>> f.clean('07973780014') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid VAT number.'] +>>> f.clean('A7973780013') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid VAT number.'] + # FIZipCodeField ############################################################# FIZipCodeField validates that the data is a valid FI zipcode. @@ -800,6 +840,94 @@ ValidationError: [u'Enter a zip code in the format XXXXX-XXX.'] >>> f.clean('12345-123') u'12345-123' +# BRCNPJField ############################################################ + +>>> from django.contrib.localflavor.br.forms import BRCNPJField +>>> f = BRCNPJField(required=True) +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('12-345-678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('12.345.678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('12345678/9012-10') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CNPJ number.'] +>>> f.clean('64.132.916/0001-88') +'64.132.916/0001-88' +>>> f.clean('64-132-916/0001-88') +'64-132-916/0001-88' +>>> f.clean('64132916/0001-88') +'64132916/0001-88' +>>> f.clean('64.132.916/0001-XX') +Traceback (most recent call last): +... +ValidationError: [u'This field requires only numbers.'] +>>> f = BRCNPJField(required=False) +>>> f.clean('') +u'' + +# BRCPFField ################################################################# + +>>> from django.contrib.localflavor.br.forms import BRCPFField +>>> f = BRCPFField() +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('489.294.654-54') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('295.669.575-98') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('539.315.127-22') +Traceback (most recent call last): +... +ValidationError: [u'Invalid CPF number.'] +>>> f.clean('663.256.017-26') +u'663.256.017-26' +>>> f.clean('66325601726') +u'66325601726' +>>> f.clean('375.788.573-20') +u'375.788.573-20' +>>> f.clean('84828509895') +u'84828509895' +>>> f.clean('375.788.573-XX') +Traceback (most recent call last): +... +ValidationError: [u'This field requires only numbers.'] +>>> f.clean('375.788.573-000') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 14 characters.'] +>>> f.clean('123.456.78') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 11 characters.'] +>>> f.clean('123456789555') +Traceback (most recent call last): +... +ValidationError: [u'This field requires at most 11 digits or 14 characters.'] +>>> f = BRCPFField(required=False) +>>> f.clean('') +u'' +>>> f.clean(None) +u'' + # BRPhoneNumberField ######################################################### >>> from django.contrib.localflavor.br.forms import BRPhoneNumberField @@ -883,6 +1011,60 @@ Traceback (most recent call last): ... ValidationError: [u'Enter a valid German identity card number in XXXXXXXXXXX-XXXXXXX-XXXXXXX-X format.'] +# CHZipCodeField ############################################################ + +>>> from django.contrib.localflavor.ch.forms import CHZipCodeField +>>> f = CHZipCodeField() +>>> f.clean('800x') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('80 00') +Traceback (most recent call last): +... +ValidationError: [u'Enter a zip code in the format XXXX.'] +>>> f.clean('8000') +u'8000' + +# CHPhoneNumberField ######################################################## + +>>> from django.contrib.localflavor.ch.forms import CHPhoneNumberField +>>> f = CHPhoneNumberField() +>>> f.clean('01234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('1234567890') +Traceback (most recent call last): +... +ValidationError: [u'Phone numbers must be in 0XX XXX XX XX format.'] +>>> f.clean('0123456789') +u'012 345 67 89' + +# CHIdentityCardNumberField ################################################# + +>>> from django.contrib.localflavor.ch.forms import CHIdentityCardNumberField +>>> f = CHIdentityCardNumberField() +>>> f.clean('C1234567<0') +u'C1234567<0' +>>> f.clean('C1234567<1') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] +>>> f.clean('2123456700') +u'2123456700' +>>> f.clean('2123456701') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Swiss identity or passport card number in X1234567<0 or 1234567890 format.'] + +# CHStateSelect ############################################################# + +>>> from django.contrib.localflavor.ch.forms import CHStateSelect +>>> w = CHStateSelect() +>>> w.render('state', 'AG') +u'<select name="state">\n<option value="AG" selected="selected">Aargau</option>\n<option value="AI">Appenzell Innerrhoden</option>\n<option value="AR">Appenzell Ausserrhoden</option>\n<option value="BS">Basel-Stadt</option>\n<option value="BL">Basel-Land</option>\n<option value="BE">Berne</option>\n<option value="FR">Fribourg</option>\n<option value="GE">Geneva</option>\n<option value="GL">Glarus</option>\n<option value="GR">Graubuenden</option>\n<option value="JU">Jura</option>\n<option value="LU">Lucerne</option>\n<option value="NE">Neuchatel</option>\n<option value="NW">Nidwalden</option>\n<option value="OW">Obwalden</option>\n<option value="SH">Schaffhausen</option>\n<option value="SZ">Schwyz</option>\n<option value="SO">Solothurn</option>\n<option value="SG">St. Gallen</option>\n<option value="TG">Thurgau</option>\n<option value="TI">Ticino</option>\n<option value="UR">Uri</option>\n<option value="VS">Valais</option>\n<option value="VD">Vaud</option>\n<option value="ZG">Zug</option>\n<option value="ZH">Zurich</option>\n</select>' + ## AUPostCodeField ########################################################## A field that accepts a four digit Australian post code. @@ -1012,4 +1194,166 @@ states/territories as its choices. <option value="VIC">Victoria</option> <option value="WA">Western Australia</option> </select> + +## ISIdNumberField ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISIdNumberField() +>>> f.clean('2308803449') +u'230880-3449' +>>> f.clean('230880-3449') +u'230880-3449' +>>> f.clean('230880 3449') +u'230880-3449' +>>> f.clean('230880343') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 10 characters.'] +>>> f.clean('230880343234') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 11 characters.'] +>>> f.clean('abcdefghijk') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid Icelandic identification number. The format is XXXXXX-XXXX.'] +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean('2308803439') +Traceback (most recent call last): +... +ValidationError: [u'The Icelandic identification number is not valid.'] +>>> f.clean('2308803440') +u'230880-3440' +>>> f = ISIdNumberField(required=False) +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## ISPhoneNumberField ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISPhoneNumberField() +>>> f.clean('1234567') +u'1234567' +>>> f.clean('123 4567') +u'1234567' +>>> f.clean('123-4567') +u'1234567' +>>> f.clean('123-456') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] +>>> f.clean('123456') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at least 7 characters.'] +>>> f.clean('123456555') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 8 characters.'] +>>> f.clean('abcdefg') +Traceback (most recent call last): +ValidationError: [u'Enter a valid value.'] +>>> f.clean(' 1234567 ') +Traceback (most recent call last): +... +ValidationError: [u'Ensure this value has at most 8 characters.'] +>>> f.clean(' 12367 ') +Traceback (most recent call last): +... +ValidationError: [u'Enter a valid value.'] + +>>> f.clean('') +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f.clean(None) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] +>>> f = ISPhoneNumberField(required=False) +>>> f.clean(None) +u'' +>>> f.clean('') +u'' + +## ISPostalCodeSelect ############################################################# + +>>> from django.contrib.localflavor.is_.forms import * +>>> f = ISPostalCodeSelect() + +>>> f.render('foo', 'bar') +u'<select name="foo">\n<option value="101">101 Reykjav\xedk</option>\n<option value="103">103 Reykjav\xedk</option>\n<option value="104">104 Reykjav\xedk</option>\n<option value="105">105 Reykjav\xedk</option>\n<option value="107">107 Reykjav\xedk</option>\n<option value="108">108 Reykjav\xedk</option>\n<option value="109">109 Reykjav\xedk</option>\n<option value="110">110 Reykjav\xedk</option>\n<option value="111">111 Reykjav\xedk</option>\n<option value="112">112 Reykjav\xedk</option>\n<option value="113">113 Reykjav\xedk</option>\n<option value="116">116 Kjalarnes</option>\n<option value="121">121 Reykjav\xedk</option>\n<option value="123">123 Reykjav\xedk</option>\n<option value="124">124 Reykjav\xedk</option>\n<option value="125">125 Reykjav\xedk</option>\n<option value="127">127 Reykjav\xedk</option>\n<option value="128">128 Reykjav\xedk</option>\n<option value="129">129 Reykjav\xedk</option>\n<option value="130">130 Reykjav\xedk</option>\n<option value="132">132 Reykjav\xedk</option>\n<option value="150">150 Reykjav\xedk</option>\n<option value="155">155 Reykjav\xedk</option>\n<option value="170">170 Seltjarnarnes</option>\n<option value="172">172 Seltjarnarnes</option>\n<option value="190">190 Vogar</option>\n<option value="200">200 K\xf3pavogur</option>\n<option value="201">201 K\xf3pavogur</option>\n<option value="202">202 K\xf3pavogur</option>\n<option value="203">203 K\xf3pavogur</option>\n<option value="210">210 Gar\xf0ab\xe6r</option>\n<option value="212">212 Gar\xf0ab\xe6r</option>\n<option value="220">220 Hafnarfj\xf6r\xf0ur</option>\n<option value="221">221 Hafnarfj\xf6r\xf0ur</option>\n<option value="222">222 Hafnarfj\xf6r\xf0ur</option>\n<option value="225">225 \xc1lftanes</option>\n<option value="230">230 Reykjanesb\xe6r</option>\n<option value="232">232 Reykjanesb\xe6r</option>\n<option value="233">233 Reykjanesb\xe6r</option>\n<option value="235">235 Keflav\xedkurflugv\xf6llur</option>\n<option value="240">240 Grindav\xedk</option>\n<option value="245">245 Sandger\xf0i</option>\n<option value="250">250 Gar\xf0ur</option>\n<option value="260">260 Reykjanesb\xe6r</option>\n<option value="270">270 Mosfellsb\xe6r</option>\n<option value="300">300 Akranes</option>\n<option value="301">301 Akranes</option>\n<option value="302">302 Akranes</option>\n<option value="310">310 Borgarnes</option>\n<option value="311">311 Borgarnes</option>\n<option value="320">320 Reykholt \xed Borgarfir\xf0i</option>\n<option value="340">340 Stykkish\xf3lmur</option>\n<option value="345">345 Flatey \xe1 Brei\xf0afir\xf0i</option>\n<option value="350">350 Grundarfj\xf6r\xf0ur</option>\n<option value="355">355 \xd3lafsv\xedk</option>\n<option value="356">356 Sn\xe6fellsb\xe6r</option>\n<option value="360">360 Hellissandur</option>\n<option value="370">370 B\xfa\xf0ardalur</option>\n<option value="371">371 B\xfa\xf0ardalur</option>\n<option value="380">380 Reykh\xf3lahreppur</option>\n<option value="400">400 \xcdsafj\xf6r\xf0ur</option>\n<option value="401">401 \xcdsafj\xf6r\xf0ur</option>\n<option value="410">410 Hn\xedfsdalur</option>\n<option value="415">415 Bolungarv\xedk</option>\n<option value="420">420 S\xfa\xf0av\xedk</option>\n<option value="425">425 Flateyri</option>\n<option value="430">430 Su\xf0ureyri</option>\n<option value="450">450 Patreksfj\xf6r\xf0ur</option>\n<option value="451">451 Patreksfj\xf6r\xf0ur</option>\n<option value="460">460 T\xe1lknafj\xf6r\xf0ur</option>\n<option value="465">465 B\xedldudalur</option>\n<option value="470">470 \xdeingeyri</option>\n<option value="471">471 \xdeingeyri</option>\n<option value="500">500 Sta\xf0ur</option>\n<option value="510">510 H\xf3lmav\xedk</option>\n<option value="512">512 H\xf3lmav\xedk</option>\n<option value="520">520 Drangsnes</option>\n<option value="522">522 Kj\xf6rvogur</option>\n<option value="523">523 B\xe6r</option>\n<option value="524">524 Nor\xf0urfj\xf6r\xf0ur</option>\n<option value="530">530 Hvammstangi</option>\n<option value="531">531 Hvammstangi</option>\n<option value="540">540 Bl\xf6ndu\xf3s</option>\n<option value="541">541 Bl\xf6ndu\xf3s</option>\n<option value="545">545 Skagastr\xf6nd</option>\n<option value="550">550 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="551">551 Sau\xf0\xe1rkr\xf3kur</option>\n<option value="560">560 Varmahl\xed\xf0</option>\n<option value="565">565 Hofs\xf3s</option>\n<option value="566">566 Hofs\xf3s</option>\n<option value="570">570 Flj\xf3t</option>\n<option value="580">580 Siglufj\xf6r\xf0ur</option>\n<option value="600">600 Akureyri</option>\n<option value="601">601 Akureyri</option>\n<option value="602">602 Akureyri</option>\n<option value="603">603 Akureyri</option>\n<option value="610">610 Greniv\xedk</option>\n<option value="611">611 Gr\xedmsey</option>\n<option value="620">620 Dalv\xedk</option>\n<option value="621">621 Dalv\xedk</option>\n<option value="625">625 \xd3lafsfj\xf6r\xf0ur</option>\n<option value="630">630 Hr\xedsey</option>\n<option value="640">640 H\xfasav\xedk</option>\n<option value="641">641 H\xfasav\xedk</option>\n<option value="645">645 Fossh\xf3ll</option>\n<option value="650">650 Laugar</option>\n<option value="660">660 M\xfdvatn</option>\n<option value="670">670 K\xf3pasker</option>\n<option value="671">671 K\xf3pasker</option>\n<option value="675">675 Raufarh\xf6fn</option>\n<option value="680">680 \xde\xf3rsh\xf6fn</option>\n<option value="681">681 \xde\xf3rsh\xf6fn</option>\n<option value="685">685 Bakkafj\xf6r\xf0ur</option>\n<option value="690">690 Vopnafj\xf6r\xf0ur</option>\n<option value="700">700 Egilssta\xf0ir</option>\n<option value="701">701 Egilssta\xf0ir</option>\n<option value="710">710 Sey\xf0isfj\xf6r\xf0ur</option>\n<option value="715">715 Mj\xf3ifj\xf6r\xf0ur</option>\n<option value="720">720 Borgarfj\xf6r\xf0ur eystri</option>\n<option value="730">730 Rey\xf0arfj\xf6r\xf0ur</option>\n<option value="735">735 Eskifj\xf6r\xf0ur</option>\n<option value="740">740 Neskaupsta\xf0ur</option>\n<option value="750">750 F\xe1skr\xfa\xf0sfj\xf6r\xf0ur</option>\n<option value="755">755 St\xf6\xf0varfj\xf6r\xf0ur</option>\n<option value="760">760 Brei\xf0dalsv\xedk</option>\n<option value="765">765 Dj\xfapivogur</option>\n<option value="780">780 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="781">781 H\xf6fn \xed Hornafir\xf0i</option>\n<option value="785">785 \xd6r\xe6fi</option>\n<option value="800">800 Selfoss</option>\n<option value="801">801 Selfoss</option>\n<option value="802">802 Selfoss</option>\n<option value="810">810 Hverager\xf0i</option>\n<option value="815">815 \xdeorl\xe1ksh\xf6fn</option>\n<option value="820">820 Eyrarbakki</option>\n<option value="825">825 Stokkseyri</option>\n<option value="840">840 Laugarvatn</option>\n<option value="845">845 Fl\xfa\xf0ir</option>\n<option value="850">850 Hella</option>\n<option value="851">851 Hella</option>\n<option value="860">860 Hvolsv\xf6llur</option>\n<option value="861">861 Hvolsv\xf6llur</option>\n<option value="870">870 V\xedk</option>\n<option value="871">871 V\xedk</option>\n<option value="880">880 Kirkjub\xe6jarklaustur</option>\n<option value="900">900 Vestmannaeyjar</option>\n<option value="902">902 Vestmannaeyjar</option>\n</select>' + +## CLRutField ############################################################# + +CLRutField is a Field that checks the validity of the Chilean +personal identification number (RUT). It has two modes relaxed (default) and +strict. + +>>> from django.contrib.localflavor.cl.forms import CLRutField +>>> rut = CLRutField() + +>>> rut.clean('11-6') +'11-6' +>>> rut.clean('116') +'11-6' + +# valid format, bad verifier. +>>> rut.clean('11.111.111-0') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] +>>> rut.clean('111') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] + +>>> rut.clean('767484100') +'76.748.410-0' +>>> rut.clean('78.412.790-7') +'78.412.790-7' +>>> rut.clean('8.334.6043') +'8.334.604-3' +>>> rut.clean('76793310-K') +'76.793.310-K' + +Strict RUT usage (does not allow imposible values) +>>> rut = CLRutField(strict=True) + +>>> rut.clean('11-6') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] + +# valid format, bad verifier. +>>> rut.clean('11.111.111-0') +Traceback (most recent call last): +... +ValidationError: [u'The Chilean RUT is not valid.'] + +# Correct input, invalid format. +>>> rut.clean('767484100') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +>>> rut.clean('78.412.790-7') +'78.412.790-7' +>>> rut.clean('8.334.6043') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] +>>> rut.clean('76793310-K') +Traceback (most recent call last): +... +ValidationError: [u'Enter valid a Chilean RUT. The format is XX.XXX.XXX-X.'] + """ diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index db420757f0..f15034ec66 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -6,6 +6,7 @@ from formsets import formset_tests form_tests = r""" >>> from django.newforms import * >>> import datetime +>>> import time >>> import re ########### @@ -659,10 +660,31 @@ Traceback (most recent call last): ... IndexError: list index out of range +# Unicode choices are correctly rendered as HTML >>> w = RadioSelect() >>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])) u'<ul>\n<li><label><input checked="checked" type="radio" name="email" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="radio" name="email" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>' +# Attributes provided at instantiation are passed to the constituent inputs +>>> w = RadioSelect(attrs={'id':'foo'}) +>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))) +<ul> +<li><label><input checked="checked" type="radio" id="foo_0" value="J" name="beatle" /> John</label></li> +<li><label><input type="radio" id="foo_1" value="P" name="beatle" /> Paul</label></li> +<li><label><input type="radio" id="foo_2" value="G" name="beatle" /> George</label></li> +<li><label><input type="radio" id="foo_3" value="R" name="beatle" /> Ringo</label></li> +</ul> + +# Attributes provided at render-time are passed to the constituent inputs +>>> w = RadioSelect() +>>> print w.render('beatle', 'J', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')), attrs={'id':'bar'}) +<ul> +<li><label><input checked="checked" type="radio" id="bar_0" value="J" name="beatle" /> John</label></li> +<li><label><input type="radio" id="bar_1" value="P" name="beatle" /> Paul</label></li> +<li><label><input type="radio" id="bar_2" value="G" name="beatle" /> George</label></li> +<li><label><input type="radio" id="bar_3" value="R" name="beatle" /> Ringo</label></li> +</ul> + # CheckboxSelectMultiple Widget ############################################### >>> w = CheckboxSelectMultiple() @@ -784,6 +806,11 @@ u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />' >>> w.render('name', 'john__lennon') u'<input type="text" class="big" value="john" name="name_0" /><br /><input type="text" class="small" value="lennon" name="name_1" />' +>>> w.render('name', 'john__lennon', attrs={'id':'foo'}) +u'<input id="foo_0" type="text" class="big" value="john" name="name_0" /><br /><input id="foo_1" type="text" class="small" value="lennon" name="name_1" />' +>>> w = MyMultiWidget(widgets=(TextInput(attrs={'class': 'big'}), TextInput(attrs={'class': 'small'})), attrs={'id': 'bar'}) +>>> w.render('name', ['john', 'lennon']) +u'<input id="bar_0" type="text" class="big" value="john" name="name_0" /><br /><input id="bar_1" type="text" class="small" value="lennon" name="name_1" />' # SplitDateTimeWidget ######################################################### @@ -2575,6 +2602,27 @@ underscores converted to spaces, and the initial letter capitalized. <li>Password1: <input type="password" name="password1" /></li> <li>Password (again): <input type="password" name="password2" /></li> +Labels for as_* methods will only end in a colon if they don't end in other +punctuation already. +>>> class Questions(Form): +... q1 = CharField(label='The first question') +... q2 = CharField(label='What is your name?') +... q3 = CharField(label='The answer to life is:') +... q4 = CharField(label='Answer this question!') +... q5 = CharField(label='The last question. Period.') +>>> print Questions(auto_id=False).as_p() +<p>The first question: <input type="text" name="q1" /></p> +<p>What is your name? <input type="text" name="q2" /></p> +<p>The answer to life is: <input type="text" name="q3" /></p> +<p>Answer this question! <input type="text" name="q4" /></p> +<p>The last question. Period. <input type="text" name="q5" /></p> +>>> print Questions().as_p() +<p><label for="id_q1">The first question:</label> <input type="text" name="q1" id="id_q1" /></p> +<p><label for="id_q2">What is your name?</label> <input type="text" name="q2" id="id_q2" /></p> +<p><label for="id_q3">The answer to life is:</label> <input type="text" name="q3" id="id_q3" /></p> +<p><label for="id_q4">Answer this question!</label> <input type="text" name="q4" id="id_q4" /></p> +<p><label for="id_q5">The last question. Period.</label> <input type="text" name="q5" id="id_q5" /></p> + A label can be a Unicode object or a bytestring with special characters. >>> class UserRegistration(Form): ... username = CharField(max_length=10, label='ŠĐĆŽćžšđ') @@ -3272,6 +3320,94 @@ True <option value="2016">2016</option> </select> +# MultiWidget and MultiValueField ############################################# +# MultiWidgets are widgets composed of other widgets. They are usually +# combined with MultiValueFields - a field that is composed of other fields. +# MulitWidgets can themselved be composed of other MultiWidgets. +# SplitDateTimeWidget is one example of a MultiWidget. + +>>> class ComplexMultiWidget(MultiWidget): +... def __init__(self, attrs=None): +... widgets = ( +... TextInput(), +... SelectMultiple(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), +... SplitDateTimeWidget(), +... ) +... super(ComplexMultiWidget, self).__init__(widgets, attrs) +... +... def decompress(self, value): +... if value: +... data = value.split(',') +... return [data[0], data[1], datetime.datetime(*time.strptime(data[2], "%Y-%m-%d %H:%M:%S")[0:6])] +... return [None, None, None] +... def format_output(self, rendered_widgets): +... return u'\n'.join(rendered_widgets) +>>> w = ComplexMultiWidget() +>>> print w.render('name', 'some text,JP,2007-04-25 06:24:00') +<input type="text" name="name_0" value="some text" /> +<select multiple="multiple" name="name_1"> +<option value="J" selected="selected">John</option> +<option value="P" selected="selected">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> +</select> +<input type="text" name="name_2_0" value="2007-04-25" /><input type="text" name="name_2_1" value="06:24:00" /> + +>>> class ComplexField(MultiValueField): +... def __init__(self, required=True, widget=None, label=None, initial=None): +... fields = ( +... CharField(), +... MultipleChoiceField(choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo'))), +... SplitDateTimeField() +... ) +... super(ComplexField, self).__init__(fields, required, widget, label, initial) +... +... def compress(self, data_list): +... if data_list: +... return '%s,%s,%s' % (data_list[0],''.join(data_list[1]),data_list[2]) +... return None + +>>> f = ComplexField(widget=w) +>>> f.clean(['some text', ['J','P'], ['2007-04-25','6:24:00']]) +u'some text,JP,2007-04-25 06:24:00' +>>> f.clean(['some text',['X'], ['2007-04-25','6:24:00']]) +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. X is not one of the available choices.'] + +# If insufficient data is provided, None is substituted +>>> f.clean(['some text',['JP']]) +Traceback (most recent call last): +... +ValidationError: [u'This field is required.'] + +>>> class ComplexFieldForm(Form): +... field1 = ComplexField(widget=w) +>>> f = ComplexFieldForm() +>>> print f +<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" id="id_field1_0" /> +<select multiple="multiple" name="field1_1" id="id_field1_1"> +<option value="J">John</option> +<option value="P">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> +</select> +<input type="text" name="field1_2_0" id="id_field1_2_0" /><input type="text" name="field1_2_1" id="id_field1_2_1" /></td></tr> + +>>> f = ComplexFieldForm({'field1_0':'some text','field1_1':['J','P'], 'field1_2_0':'2007-04-25', 'field1_2_1':'06:24:00'}) +>>> print f +<tr><th><label for="id_field1_0">Field1:</label></th><td><input type="text" name="field1_0" value="some text" id="id_field1_0" /> +<select multiple="multiple" name="field1_1" id="id_field1_1"> +<option value="J" selected="selected">John</option> +<option value="P" selected="selected">Paul</option> +<option value="G">George</option> +<option value="R">Ringo</option> +</select> +<input type="text" name="field1_2_0" value="2007-04-25" id="id_field1_2_0" /><input type="text" name="field1_2_1" value="06:24:00" id="id_field1_2_1" /></td></tr> + +>>> f.clean_data +{'field1': u'some text,JP,2007-04-25 06:24:00'} + ################################# # Tests of underlying functions # ################################# diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py index 385c3048d9..c8016bc5bd 100644 --- a/tests/regressiontests/httpwrappers/tests.py +++ b/tests/regressiontests/httpwrappers/tests.py @@ -34,6 +34,9 @@ AttributeError: This QueryDict instance is immutable >>> q.has_key('foo') False +>>> 'foo' in q +False + >>> q.items() [] @@ -93,6 +96,12 @@ MultiValueDictKeyError: "Key 'foo' not found in <MultiValueDict: {}>" >>> q['name'] 'john' +>>> del q['name'] +>>> 'name' in q +False + +>>> q['name'] = 'john' + >>> q.get('foo', 'default') 'default' @@ -124,6 +133,9 @@ MultiValueDictKeyError: "Key 'foo' not found in <MultiValueDict: {}>" >>> q.has_key('foo') True +>>> 'foo' in q +True + >>> q.items() [('foo', 'another'), ('name', 'john')] @@ -218,9 +230,15 @@ AttributeError: This QueryDict instance is immutable >>> q.has_key('foo') True +>>> 'foo' in q +True + >>> q.has_key('bar') False +>>> 'bar' in q +False + >>> q.items() [('foo', 'bar')] @@ -303,9 +321,15 @@ AttributeError: This QueryDict instance is immutable >>> q.has_key('vote') True +>>> 'vote' in q +True + >>> q.has_key('foo') False +>>> 'foo' in q +False + >>> q.items() [('vote', 'no')] @@ -349,6 +373,11 @@ AttributeError: This QueryDict instance is immutable >>> q.urlencode() 'vote=yes&vote=no' +>>> del q['vote'] +Traceback (most recent call last): +... +AttributeError: This QueryDict instance is immutable + """ from django.http import QueryDict diff --git a/tests/regressiontests/null_queries/models.py b/tests/regressiontests/null_queries/models.py index 09024f18c2..21944d9e7a 100644 --- a/tests/regressiontests/null_queries/models.py +++ b/tests/regressiontests/null_queries/models.py @@ -32,7 +32,7 @@ __test__ = {'API_TESTS':""" >>> Choice.objects.filter(foo__exact=None) Traceback (most recent call last): ... -TypeError: Cannot resolve keyword 'foo' into field +TypeError: Cannot resolve keyword 'foo' into field. Choices are: id, poll, choice # Can't use None on anything other than __exact >>> Choice.objects.filter(id__gt=None) diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py index d3415ac1b9..c287b6e0d6 100644 --- a/tests/regressiontests/serializers_regress/models.py +++ b/tests/regressiontests/serializers_regress/models.py @@ -6,6 +6,7 @@ This class sets up a model for each model field type """ from django.db import models +from django.contrib.contenttypes import generic from django.contrib.contenttypes.models import ContentType # The following classes are for testing basic data @@ -80,7 +81,7 @@ class Tag(models.Model): content_type = models.ForeignKey(ContentType) object_id = models.PositiveIntegerField() - content_object = models.GenericForeignKey() + content_object = generic.GenericForeignKey() class Meta: ordering = ["data"] @@ -88,7 +89,7 @@ class Tag(models.Model): class GenericData(models.Model): data = models.CharField(maxlength=30) - tags = models.GenericRelation(Tag) + tags = generic.GenericRelation(Tag) # The following test classes are all for validation # of related objects; in particular, forward, backward, diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index b544207be8..a5ed2dbf56 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -127,6 +127,18 @@ class Templates(unittest.TestCase): # Fail silently when accessing a non-simple method 'basic-syntax20': ("{{ var.method2 }}", {"var": SomeClass()}, ("","INVALID")), + # Don't get confused when parsing something that is almost, but not + # quite, a template tag. + 'basic-syntax21': ("a {{ moo %} b", {}, "a {{ moo %} b"), + 'basic-syntax22': ("{{ moo #}", {}, "{{ moo #}"), + + # Will try to treat "moo #} {{ cow" as the variable. Not ideal, but + # costly to work around, so this triggers an error. + 'basic-syntax23': ("{{ moo #} {{ cow }}", {"cow": "cow"}, template.TemplateSyntaxError), + + # Embedded newlines make it not-a-tag. + 'basic-syntax24': ("{{ moo\n }}", {}, "{{ moo\n }}"), + # List-index syntax allows a template to access a certain item of a subscriptable object. 'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"), @@ -151,60 +163,61 @@ class Templates(unittest.TestCase): 'list-index07': ("{{ var.1 }}", {"var": {'1': "hello", 1: "world"}}, "hello"), # Basic filter usage - 'basic-syntax21': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), + 'filter-syntax01': ("{{ var|upper }}", {"var": "Django is the greatest!"}, "DJANGO IS THE GREATEST!"), # Chained filters - 'basic-syntax22': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"), + 'filter-syntax02': ("{{ var|upper|lower }}", {"var": "Django is the greatest!"}, "django is the greatest!"), # Raise TemplateSyntaxError for space between a variable and filter pipe - 'basic-syntax23': ("{{ var |upper }}", {}, template.TemplateSyntaxError), + 'filter-syntax03': ("{{ var |upper }}", {}, template.TemplateSyntaxError), # Raise TemplateSyntaxError for space after a filter pipe - 'basic-syntax24': ("{{ var| upper }}", {}, template.TemplateSyntaxError), + 'filter-syntax04': ("{{ var| upper }}", {}, template.TemplateSyntaxError), # Raise TemplateSyntaxError for a nonexistent filter - 'basic-syntax25': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError), + 'filter-syntax05': ("{{ var|does_not_exist }}", {}, template.TemplateSyntaxError), # Raise TemplateSyntaxError when trying to access a filter containing an illegal character - 'basic-syntax26': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError), + 'filter-syntax06': ("{{ var|fil(ter) }}", {}, template.TemplateSyntaxError), # Raise TemplateSyntaxError for invalid block tags - 'basic-syntax27': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError), + 'filter-syntax07': ("{% nothing_to_see_here %}", {}, template.TemplateSyntaxError), # Raise TemplateSyntaxError for empty block tags - 'basic-syntax28': ("{% %}", {}, template.TemplateSyntaxError), + 'filter-syntax08': ("{% %}", {}, template.TemplateSyntaxError), # Chained filters, with an argument to the first one - 'basic-syntax29': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), + 'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"), # Escaped string as argument - 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), + 'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), # Variable as argument - 'basic-syntax31': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'), + 'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'), # Default argument testing - 'basic-syntax32': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), + 'filter-syntax12': (r'{{ var|yesno:"yup,nup,mup" }} {{ var|yesno }}', {"var": True}, 'yup yes'), - # Fail silently for methods that raise an exception with a "silent_variable_failure" attribute - 'basic-syntax33': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")), + # Fail silently for methods that raise an exception with a + # "silent_variable_failure" attribute + 'filter-syntax13': (r'1{{ var.method3 }}2', {"var": SomeClass()}, ("12", "1INVALID2")), - # In methods that raise an exception without a "silent_variable_attribute" set to True, - # the exception propagates - 'basic-syntax34': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException), + # In methods that raise an exception without a + # "silent_variable_attribute" set to True, the exception propagates + 'filter-syntax14': (r'1{{ var.method4 }}2', {"var": SomeClass()}, SomeOtherException), # Escaped backslash in argument - 'basic-syntax35': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'), + 'filter-syntax15': (r'{{ var|default_if_none:"foo\bar" }}', {"var": None}, r'foo\bar'), # Escaped backslash using known escape char - 'basic-syntax35': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'), + 'filter-syntax16': (r'{{ var|default_if_none:"foo\now" }}', {"var": None}, r'foo\now'), # Empty strings can be passed as arguments to filters - 'basic-syntax36': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'), + 'filter-syntax17': (r'{{ var|join:"" }}', {'var': ['a', 'b', 'c']}, 'abc'), - # If a variable has a __str__() that returns a Unicode object, the value - # will be converted to a bytestring. - 'basic-syntax37': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'), + # If a variable has a __str__() that returns a Unicode object, the + # value will be converted to a bytestring. + 'filter-syntax18': (r'{{ var }}', {'var': UnicodeInStrClass()}, '\xc5\xa0\xc4\x90\xc4\x86\xc5\xbd\xc4\x87\xc5\xbe\xc5\xa1\xc4\x91'), ### COMMENT SYNTAX ######################################################## 'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"), @@ -259,6 +272,7 @@ class Templates(unittest.TestCase): 'filter01': ('{% filter upper %}{% endfilter %}', {}, ''), 'filter02': ('{% filter upper %}django{% endfilter %}', {}, 'DJANGO'), 'filter03': ('{% filter upper|lower %}django{% endfilter %}', {}, 'django'), + 'filter04': ('{% filter cut:remove %}djangospam{% endfilter %}', {'remove': 'spam'}, 'django'), ### FIRSTOF TAG ########################################################### 'firstof01': ('{% firstof a b c %}', {'a':0,'b':0,'c':0}, ''), @@ -572,6 +586,8 @@ class Templates(unittest.TestCase): 'invalidstr03': ('{% for v in var %}({{ v }}){% endfor %}', {}, ''), 'invalidstr04': ('{% if var %}Yes{% else %}No{% endif %}', {}, 'No'), 'invalidstr04': ('{% if var|default:"Foo" %}Yes{% else %}No{% endif %}', {}, 'Yes'), + 'invalidstr05': ('{{ var }}', {}, ('', 'INVALID %s', 'var')), + 'invalidstr06': ('{{ var.prop }}', {'var': {}}, ('', 'INVALID %s', 'var.prop')), ### MULTILINE ############################################################# @@ -654,6 +670,9 @@ class Templates(unittest.TestCase): 'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'), 'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')), + 'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError), + 'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError), + ### NOW TAG ######################################################## # Simple case 'now01' : ('{% now "j n Y"%}', {}, str(datetime.now().day) + ' ' + str(datetime.now().month) + ' ' + str(datetime.now().year)), @@ -690,7 +709,7 @@ class Templates(unittest.TestCase): ### URL TAG ######################################################## # Successes 'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'), - 'url02' : ('{% url regressiontests.templates.views.client_action client.id,action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), + 'url02' : ('{% url regressiontests.templates.views.client_action client.id, action="update" %}', {'client': {'id': 1}}, '/url_tag/client/1/update/'), 'url03' : ('{% url regressiontests.templates.views.index %}', {}, '/url_tag/'), 'url04' : ('{% url named-client client.id %}', {'client': {'id': 1}}, '/url_tag/named-client/1/'), @@ -720,6 +739,7 @@ class Templates(unittest.TestCase): # Set TEMPLATE_STRING_IF_INVALID to a known string old_invalid = settings.TEMPLATE_STRING_IF_INVALID + expected_invalid_str = 'INVALID' for name, vals in tests: install() @@ -727,6 +747,10 @@ class Templates(unittest.TestCase): if isinstance(vals[2], tuple): normal_string_result = vals[2][0] invalid_string_result = vals[2][1] + if '%s' in invalid_string_result: + expected_invalid_str = 'INVALID %s' + invalid_string_result = invalid_string_result % vals[2][2] + template.invalid_var_format_string = True else: normal_string_result = vals[2] invalid_string_result = vals[2] @@ -737,7 +761,7 @@ class Templates(unittest.TestCase): activate('en-us') for invalid_str, result in [('', normal_string_result), - ('INVALID', invalid_string_result)]: + (expected_invalid_str, invalid_string_result)]: settings.TEMPLATE_STRING_IF_INVALID = invalid_str try: output = loader.get_template(name).render(template.Context(vals[1])) @@ -751,6 +775,10 @@ class Templates(unittest.TestCase): if 'LANGUAGE_CODE' in vals[1]: deactivate() + if template.invalid_var_format_string: + expected_invalid_str = 'INVALID' + template.invalid_var_format_string = False + loader.template_source_loaders = old_template_loaders deactivate() settings.TEMPLATE_DEBUG = old_td diff --git a/tests/regressiontests/test_client_regress/__init__.py b/tests/regressiontests/test_client_regress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/regressiontests/test_client_regress/__init__.py diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py new file mode 100644 index 0000000000..c39fafe314 --- /dev/null +++ b/tests/regressiontests/test_client_regress/models.py @@ -0,0 +1,136 @@ +""" +Regression tests for the Test Client, especially the customized assertions. + +""" +from django.test import Client, TestCase +from django.core import mail + +class AssertTemplateUsedTests(TestCase): + fixtures = ['testdata.json'] + + def test_no_context(self): + "Template usage assertions work then templates aren't in use" + response = self.client.get('/test_client_regress/no_template_view/') + + # Check that the no template case doesn't mess with the template assertions + self.assertTemplateNotUsed(response, 'GET Template') + + try: + self.assertTemplateUsed(response, 'GET Template') + except AssertionError, e: + self.assertEquals(str(e), "No templates used to render the response") + + def test_single_context(self): + "Template assertions work when there is a single context" + response = self.client.get('/test_client/post_view/', {}) + + # + try: + self.assertTemplateNotUsed(response, 'Empty GET Template') + except AssertionError, e: + self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response") + + try: + self.assertTemplateUsed(response, 'Empty POST Template') + except AssertionError, e: + self.assertEquals(str(e), "Template 'Empty POST Template' was not used to render the response. Actual template was 'Empty GET Template'") + + def test_multiple_context(self): + "Template assertions work when there are multiple contexts" + post_data = { + 'text': 'Hello World', + 'email': 'foo@example.com', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view_with_template/', post_data) + self.assertContains(response, 'POST data OK') + try: + self.assertTemplateNotUsed(response, "form_view.html") + except AssertionError, e: + self.assertEquals(str(e), "Template 'form_view.html' was used unexpectedly in rendering the response") + + try: + self.assertTemplateNotUsed(response, 'base.html') + except AssertionError, e: + self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response") + + try: + self.assertTemplateUsed(response, "Valid POST Template") + except AssertionError, e: + self.assertEquals(str(e), "Template 'Valid POST Template' was not one of the templates used to render the response. Templates used: ['form_view.html', 'base.html']") + +class AssertFormErrorTests(TestCase): + def test_unknown_form(self): + "An assertion is raised if the form name is unknown" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.') + except AssertionError, e: + self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response") + + def test_unknown_field(self): + "An assertion is raised if the field name is unknown" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'some_field', 'Some error.') + except AssertionError, e: + self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'") + + def test_noerror_field(self): + "An assertion is raised if the field doesn't have any errors" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'value', 'Some error.') + except AssertionError, e: + self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors") + + def test_unknown_error(self): + "An assertion is raised if the field doesn't contain the provided error" + post_data = { + 'text': 'Hello World', + 'email': 'not an email address', + 'value': 37, + 'single': 'b', + 'multi': ('b','c','e') + } + response = self.client.post('/test_client/form_view/', post_data) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "Invalid POST Template") + + try: + self.assertFormError(response, 'form', 'email', 'Some error.') + except AssertionError, e: + self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])") + diff --git a/tests/regressiontests/test_client_regress/urls.py b/tests/regressiontests/test_client_regress/urls.py new file mode 100644 index 0000000000..e9cd0aea15 --- /dev/null +++ b/tests/regressiontests/test_client_regress/urls.py @@ -0,0 +1,7 @@ +from django.conf.urls.defaults import * +from django.views.generic.simple import redirect_to +import views + +urlpatterns = patterns('', + (r'^no_template_view/$', views.no_template_view), +) diff --git a/tests/regressiontests/test_client_regress/views.py b/tests/regressiontests/test_client_regress/views.py new file mode 100644 index 0000000000..d8dd2b349c --- /dev/null +++ b/tests/regressiontests/test_client_regress/views.py @@ -0,0 +1,8 @@ +from django.core.mail import EmailMessage, SMTPConnection +from django.http import HttpResponse +from django.shortcuts import render_to_response + +def no_template_view(request): + "A simple view that expects a GET request, and returns a rendered template" + return HttpResponse("No template used") + diff --git a/tests/templates/base.html b/tests/templates/base.html new file mode 100644 index 0000000000..611bc094a9 --- /dev/null +++ b/tests/templates/base.html @@ -0,0 +1,8 @@ +<html> +<head></head> +<body> +<h1>Django Internal Tests: {% block title %}{% endblock %}</h1> +{% block content %} +{% endblock %} +</body> +</html>
\ No newline at end of file diff --git a/tests/templates/form_view.html b/tests/templates/form_view.html new file mode 100644 index 0000000000..1487217547 --- /dev/null +++ b/tests/templates/form_view.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% block title %}Submit data{% endblock %} +{% block content %} +<h1>{{ message }}</h1> +<form method='post' action='.'> +{% if form.errors %} +<p class='warning'>Please correct the errors below:</p> +{% endif %} +<ul class='form'> +{{ form }} +<li><input type='submit' value='Submit'></li> +</ul> +</form> + +{% endblock %}
\ No newline at end of file diff --git a/tests/templates/login.html b/tests/templates/login.html index 8a0974c9a1..d55e9ddc75 100644 --- a/tests/templates/login.html +++ b/tests/templates/login.html @@ -1,7 +1,6 @@ -<html> -<head></head> -<body> -<h1>Django Internal Tests: Login</h1> +{% extends "base.html" %} +{% block title %}Login{% endblock %} +{% block content %} {% if form.has_errors %} <p>Your username and password didn't match. Please try again.</p> {% endif %} @@ -15,5 +14,4 @@ <input type="submit" value="login" /> <input type="hidden" name="next" value="{{ next }}" /> </form> -</body> -</html>
\ No newline at end of file +{% endblock %}
\ No newline at end of file diff --git a/tests/urls.py b/tests/urls.py index 9dcdca944e..dd475b0ea7 100644 --- a/tests/urls.py +++ b/tests/urls.py @@ -3,6 +3,7 @@ from django.conf.urls.defaults import * urlpatterns = patterns('', # test_client modeltest urls (r'^test_client/', include('modeltests.test_client.urls')), + (r'^test_client_regress/', include('regressiontests.test_client_regress.urls')), # Always provide the auth system login and logout views (r'^accounts/login/$', 'django.contrib.auth.views.login', {'template_name': 'login.html'}), |
