summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAdrian Holovaty <adrian@holovaty.com>2007-05-12 02:36:05 +0000
committerAdrian Holovaty <adrian@holovaty.com>2007-05-12 02:36:05 +0000
commit415e84ad53e0d0d8f7df87784c1893489bdbe0b8 (patch)
tree09541f8319c45ec51fb44154534abc41cb86aee9 /tests
parent4938c8ea6db6f23ebb0883b8a092985344508b25 (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')
-rw-r--r--tests/modeltests/custom_columns/models.py2
-rw-r--r--tests/modeltests/generic_relations/models.py7
-rw-r--r--tests/modeltests/lookup/models.py4
-rw-r--r--tests/modeltests/many_to_one/models.py4
-rw-r--r--tests/modeltests/model_forms/models.py41
-rw-r--r--tests/modeltests/reverse_lookup/models.py2
-rw-r--r--tests/modeltests/test_client/models.py144
-rw-r--r--tests/modeltests/test_client/urls.py9
-rw-r--r--tests/modeltests/test_client/views.py56
-rw-r--r--tests/regressiontests/cache/tests.py5
-rw-r--r--tests/regressiontests/datastructures/tests.py2
-rw-r--r--tests/regressiontests/dispatch/tests/test_saferef.py2
-rw-r--r--tests/regressiontests/fixtures_regress/models.py7
-rw-r--r--tests/regressiontests/forms/localflavor.py348
-rw-r--r--tests/regressiontests/forms/tests.py136
-rw-r--r--tests/regressiontests/httpwrappers/tests.py29
-rw-r--r--tests/regressiontests/null_queries/models.py2
-rw-r--r--tests/regressiontests/serializers_regress/models.py5
-rw-r--r--tests/regressiontests/templates/tests.py78
-rw-r--r--tests/regressiontests/test_client_regress/__init__.py0
-rw-r--r--tests/regressiontests/test_client_regress/models.py136
-rw-r--r--tests/regressiontests/test_client_regress/urls.py7
-rw-r--r--tests/regressiontests/test_client_regress/views.py8
-rw-r--r--tests/templates/base.html8
-rw-r--r--tests/templates/form_view.html15
-rw-r--r--tests/templates/login.html10
-rw-r--r--tests/urls.py1
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&#39;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&#39;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&#39;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&#39;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&#39;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'}),