summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2012-12-18 09:02:07 +0000
committerAndrew Godwin <andrew@aeracode.org>2012-12-18 09:02:07 +0000
commitb62e82365ad56ca930f7abb1d1dbdf9ce5a7c7c3 (patch)
tree76bce44f4c7ee6172dcf116be6e4a26e380ef20a /tests
parent6a632e04578776e877adc5e2dc53f008c890a0d4 (diff)
parentc64b57d16688025b2d48668d5c4cb9eda7484612 (diff)
Merge remote-tracking branch 'core/master' into schema-alteration
Conflicts: django/db/models/loading.py django/db/models/options.py
Diffstat (limited to 'tests')
-rw-r--r--tests/modeltests/basic/tests.py36
-rw-r--r--tests/modeltests/expressions/tests.py43
-rw-r--r--tests/modeltests/field_subclassing/tests.py3
-rw-r--r--tests/modeltests/fixtures/tests.py26
-rw-r--r--tests/modeltests/generic_relations/tests.py6
-rw-r--r--tests/modeltests/get_object_or_404/tests.py25
-rw-r--r--tests/modeltests/invalid_models/invalid_models/models.py8
-rw-r--r--tests/modeltests/m2m_recursive/tests.py27
-rw-r--r--tests/modeltests/many_to_one/tests.py8
-rw-r--r--tests/modeltests/model_forms/models.py9
-rw-r--r--tests/modeltests/model_forms/tests.py69
-rw-r--r--tests/modeltests/pagination/tests.py134
-rw-r--r--tests/modeltests/prefetch_related/models.py10
-rw-r--r--tests/modeltests/prefetch_related/tests.py10
-rw-r--r--tests/modeltests/proxy_model_inheritance/tests.py3
-rw-r--r--tests/modeltests/raw_query/models.py2
-rw-r--r--tests/modeltests/str/models.py8
-rw-r--r--tests/modeltests/str/tests.py12
-rw-r--r--tests/regressiontests/admin_changelist/admin.py12
-rw-r--r--tests/regressiontests/admin_changelist/tests.py25
-rw-r--r--tests/regressiontests/admin_filters/tests.py58
-rw-r--r--tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.icobin0 -> 894 bytes
-rw-r--r--tests/regressiontests/admin_scripts/tests.py65
-rw-r--r--tests/regressiontests/admin_scripts/urls.py3
-rw-r--r--tests/regressiontests/admin_util/tests.py4
-rw-r--r--tests/regressiontests/admin_views/admin.py46
-rw-r--r--tests/regressiontests/admin_views/customadmin.py9
-rw-r--r--tests/regressiontests/admin_views/models.py8
-rw-r--r--tests/regressiontests/admin_views/tests.py116
-rw-r--r--tests/regressiontests/admin_views/urls.py1
-rw-r--r--tests/regressiontests/admin_widgets/models.py1
-rw-r--r--tests/regressiontests/admin_widgets/tests.py50
-rw-r--r--tests/regressiontests/aggregation_regress/tests.py95
-rw-r--r--tests/regressiontests/app_loading/tests.py3
-rw-r--r--tests/regressiontests/backends/tests.py40
-rw-r--r--tests/regressiontests/bug639/tests.py3
-rw-r--r--tests/regressiontests/bulk_create/tests.py16
-rw-r--r--tests/regressiontests/cache/tests.py6
-rw-r--r--tests/regressiontests/comment_tests/tests/app_api_tests.py27
-rw-r--r--tests/regressiontests/comment_tests/tests/comment_view_tests.py7
-rw-r--r--tests/regressiontests/comment_tests/tests/feed_tests.py23
-rw-r--r--tests/regressiontests/comment_tests/tests/moderation_view_tests.py90
-rw-r--r--tests/regressiontests/defaultfilters/tests.py24
-rw-r--r--tests/regressiontests/defer_regress/models.py4
-rw-r--r--tests/regressiontests/defer_regress/tests.py31
-rw-r--r--tests/regressiontests/delete_regress/tests.py16
-rw-r--r--tests/regressiontests/expressions_regress/tests.py37
-rw-r--r--tests/regressiontests/extra_regress/tests.py9
-rw-r--r--tests/regressiontests/file_storage/tests.py27
-rw-r--r--tests/regressiontests/file_uploads/models.py9
-rw-r--r--tests/regressiontests/file_uploads/tests.py62
-rw-r--r--tests/regressiontests/file_uploads/views.py4
-rw-r--r--tests/regressiontests/fixtures_regress/tests.py8
-rw-r--r--tests/regressiontests/forms/models.py6
-rw-r--r--tests/regressiontests/forms/templates/forms/article_form.html8
-rw-r--r--tests/regressiontests/forms/tests/__init__.py2
-rw-r--r--tests/regressiontests/forms/tests/fields.py11
-rw-r--r--tests/regressiontests/forms/tests/forms.py5
-rw-r--r--tests/regressiontests/forms/tests/formsets.py21
-rw-r--r--tests/regressiontests/forms/tests/input_formats.py31
-rw-r--r--tests/regressiontests/forms/tests/widgets.py24
-rw-r--r--tests/regressiontests/forms/urls.py9
-rw-r--r--tests/regressiontests/forms/views.py8
-rw-r--r--tests/regressiontests/generic_views/base.py11
-rw-r--r--tests/regressiontests/generic_views/edit.py9
-rw-r--r--tests/regressiontests/generic_views/forms.py5
-rw-r--r--tests/regressiontests/generic_views/list.py41
-rw-r--r--tests/regressiontests/generic_views/models.py4
-rw-r--r--tests/regressiontests/generic_views/tests.py4
-rw-r--r--tests/regressiontests/generic_views/urls.py8
-rw-r--r--tests/regressiontests/generic_views/views.py11
-rw-r--r--tests/regressiontests/httpwrappers/tests.py11
-rw-r--r--tests/regressiontests/i18n/commands/compilation.py7
-rw-r--r--tests/regressiontests/i18n/commands/extraction.py31
-rw-r--r--tests/regressiontests/i18n/contenttypes/tests.py3
-rw-r--r--tests/regressiontests/i18n/patterns/tests.py5
-rw-r--r--tests/regressiontests/i18n/tests.py11
-rw-r--r--tests/regressiontests/indexes/__init__.py (renamed from tests/modeltests/pagination/__init__.py)0
-rw-r--r--tests/regressiontests/indexes/models.py11
-rw-r--r--tests/regressiontests/indexes/tests.py12
-rw-r--r--tests/regressiontests/initial_sql_regress/sql/simple.sql3
-rw-r--r--tests/regressiontests/initial_sql_regress/tests.py7
-rw-r--r--tests/regressiontests/inspectdb/tests.py11
-rw-r--r--tests/regressiontests/introspection/models.py4
-rw-r--r--tests/regressiontests/introspection/tests.py27
-rw-r--r--tests/regressiontests/logging_tests/logconfig.py7
-rw-r--r--tests/regressiontests/logging_tests/tests.py83
-rw-r--r--tests/regressiontests/m2m_regress/models.py17
-rw-r--r--tests/regressiontests/m2m_regress/tests.py10
-rw-r--r--tests/regressiontests/m2m_through_regress/models.py11
-rw-r--r--tests/regressiontests/m2m_through_regress/tests.py97
-rw-r--r--tests/regressiontests/mail/tests.py17
-rw-r--r--tests/regressiontests/managers_regress/tests.py3
-rw-r--r--tests/regressiontests/middleware/tests.py9
-rw-r--r--tests/regressiontests/model_fields/imagefield.py5
-rw-r--r--tests/regressiontests/model_forms_regress/models.py3
-rw-r--r--tests/regressiontests/model_formsets_regress/tests.py10
-rw-r--r--tests/regressiontests/model_regress/tests.py9
-rw-r--r--tests/regressiontests/multiple_database/tests.py58
-rw-r--r--tests/regressiontests/pagination/__init__.py (renamed from tests/regressiontests/pagination_regress/__init__.py)0
-rw-r--r--tests/regressiontests/pagination/custom.py20
-rw-r--r--tests/regressiontests/pagination/models.py (renamed from tests/modeltests/pagination/models.py)8
-rw-r--r--tests/regressiontests/pagination/tests.py (renamed from tests/regressiontests/pagination_regress/tests.py)147
-rw-r--r--tests/regressiontests/pagination_regress/models.py1
-rw-r--r--tests/regressiontests/queries/models.py23
-rw-r--r--tests/regressiontests/queries/tests.py326
-rw-r--r--tests/regressiontests/requests/tests.py37
-rw-r--r--tests/regressiontests/select_related_onetoone/models.py42
-rw-r--r--tests/regressiontests/select_related_onetoone/tests.py102
-rw-r--r--tests/regressiontests/serializers_regress/models.py13
-rw-r--r--tests/regressiontests/serializers_regress/tests.py15
-rw-r--r--tests/regressiontests/servers/tests.py3
-rw-r--r--tests/regressiontests/settings_tests/tests.py26
-rw-r--r--tests/regressiontests/staticfiles_tests/tests.py29
-rw-r--r--tests/regressiontests/swappable_models/__init__.py0
-rw-r--r--tests/regressiontests/swappable_models/models.py15
-rw-r--r--tests/regressiontests/swappable_models/tests.py46
-rw-r--r--tests/regressiontests/templates/filters.py5
-rw-r--r--tests/regressiontests/templates/loaders.py7
-rw-r--r--tests/regressiontests/templates/response.py18
-rw-r--r--tests/regressiontests/templates/tests.py30
-rw-r--r--tests/regressiontests/test_client_regress/tests.py5
-rw-r--r--tests/regressiontests/test_utils/models.py6
-rw-r--r--tests/regressiontests/test_utils/tests.py40
-rw-r--r--tests/regressiontests/transactions_regress/tests.py27
-rw-r--r--tests/regressiontests/urlpatterns_reverse/tests.py14
-rw-r--r--tests/regressiontests/utils/archive.py3
-rw-r--r--tests/regressiontests/utils/feedgenerator.py8
-rw-r--r--tests/regressiontests/utils/html.py3
-rw-r--r--tests/regressiontests/utils/module_loading.py3
-rw-r--r--tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mobin0 -> 615 bytes
-rw-r--r--tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po40
-rw-r--r--tests/regressiontests/views/templates/jsi18n.html44
-rw-r--r--tests/regressiontests/views/tests/__init__.py2
-rw-r--r--tests/regressiontests/views/tests/debug.py46
-rw-r--r--tests/regressiontests/views/tests/i18n.py72
-rw-r--r--tests/regressiontests/views/urls.py10
-rw-r--r--tests/regressiontests/views/views.py3
-rwxr-xr-xtests/runtests.py11
139 files changed, 2689 insertions, 639 deletions
diff --git a/tests/modeltests/basic/tests.py b/tests/modeltests/basic/tests.py
index ebd70d14d9..1c83b980a7 100644
--- a/tests/modeltests/basic/tests.py
+++ b/tests/modeltests/basic/tests.py
@@ -2,7 +2,7 @@ from __future__ import absolute_import, unicode_literals
from datetime import datetime
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db.models.fields import Field, FieldDoesNotExist
from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six
@@ -128,6 +128,40 @@ class ModelTest(TestCase):
b = Article.objects.get(pk=a.id)
self.assertEqual(a, b)
+ # Create a very similar object
+ a = Article(
+ id=None,
+ headline='Area man programs in Python',
+ pub_date=datetime(2005, 7, 28),
+ )
+ a.save()
+
+ self.assertEqual(Article.objects.count(), 2)
+
+ # Django raises an Article.MultipleObjectsReturned exception if the
+ # lookup matches more than one object
+ self.assertRaisesRegexp(
+ MultipleObjectsReturned,
+ "get\(\) returned more than one Article -- it returned 2!",
+ Article.objects.get,
+ headline__startswith='Area',
+ )
+
+ self.assertRaisesRegexp(
+ MultipleObjectsReturned,
+ "get\(\) returned more than one Article -- it returned 2!",
+ Article.objects.get,
+ pub_date__year=2005,
+ )
+
+ self.assertRaisesRegexp(
+ MultipleObjectsReturned,
+ "get\(\) returned more than one Article -- it returned 2!",
+ Article.objects.get,
+ pub_date__year=2005,
+ pub_date__month=7,
+ )
+
def test_object_creation(self):
# Create an Article.
a = Article(
diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py
index 99eb07e370..a351496442 100644
--- a/tests/modeltests/expressions/tests.py
+++ b/tests/modeltests/expressions/tests.py
@@ -158,6 +158,7 @@ class ExpressionsTests(TestCase):
"Max Mustermann",
],
lambda c: six.text_type(c.point_of_contact),
+ ordered=False
)
c = Company.objects.all()[0]
@@ -170,7 +171,8 @@ class ExpressionsTests(TestCase):
"Foobar Ltd.",
"Test GmbH",
],
- lambda c: c.name
+ lambda c: c.name,
+ ordered=False
)
Company.objects.exclude(
@@ -219,3 +221,42 @@ class ExpressionsTests(TestCase):
)
acme.num_employees = F("num_employees") + 16
self.assertRaises(TypeError, acme.save)
+
+ def test_ticket_18375_join_reuse(self):
+ # Test that reverse multijoin F() references and the lookup target
+ # the same join. Pre #18375 the F() join was generated first, and the
+ # lookup couldn't reuse that join.
+ qs = Employee.objects.filter(
+ company_ceo_set__num_chairs=F('company_ceo_set__num_employees'))
+ self.assertEqual(str(qs.query).count('JOIN'), 1)
+
+ def test_ticket_18375_kwarg_ordering(self):
+ # The next query was dict-randomization dependent - if the "gte=1"
+ # was seen first, then the F() will reuse the join generated by the
+ # gte lookup, if F() was seen first, then it generated a join the
+ # other lookups could not reuse.
+ qs = Employee.objects.filter(
+ company_ceo_set__num_chairs=F('company_ceo_set__num_employees'),
+ company_ceo_set__num_chairs__gte=1)
+ self.assertEqual(str(qs.query).count('JOIN'), 1)
+
+ def test_ticket_18375_kwarg_ordering_2(self):
+ # Another similar case for F() than above. Now we have the same join
+ # in two filter kwargs, one in the lhs lookup, one in F. Here pre
+ # #18375 the amount of joins generated was random if dict
+ # randomization was enabled, that is the generated query dependend
+ # on which clause was seen first.
+ qs = Employee.objects.filter(
+ company_ceo_set__num_employees=F('pk'),
+ pk=F('company_ceo_set__num_employees')
+ )
+ self.assertEqual(str(qs.query).count('JOIN'), 1)
+
+ def test_ticket_18375_chained_filters(self):
+ # Test that F() expressions do not reuse joins from previous filter.
+ qs = Employee.objects.filter(
+ company_ceo_set__num_employees=F('pk')
+ ).filter(
+ company_ceo_set__num_employees=F('company_ceo_set__num_employees')
+ )
+ self.assertEqual(str(qs.query).count('JOIN'), 2)
diff --git a/tests/modeltests/field_subclassing/tests.py b/tests/modeltests/field_subclassing/tests.py
index 48755123f2..0ec317dea5 100644
--- a/tests/modeltests/field_subclassing/tests.py
+++ b/tests/modeltests/field_subclassing/tests.py
@@ -77,7 +77,8 @@ class CustomField(TestCase):
"12",
"23",
],
- lambda m: str(m.data)
+ lambda m: str(m.data),
+ ordered=False
)
def test_field_subclassing(self):
diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py
index f9b0ac8a46..415ed6dcf2 100644
--- a/tests/modeltests/fixtures/tests.py
+++ b/tests/modeltests/fixtures/tests.py
@@ -96,8 +96,8 @@ class FixtureLoadingTests(TestCase):
management.call_command('loaddata', 'fixture6.json', verbosity=0, commit=False)
self.assertQuerysetEqual(Tag.objects.all(), [
'<Tag: <Article: Copyright is fine the way it is> tagged "copyright">',
- '<Tag: <Article: Copyright is fine the way it is> tagged "law">'
- ])
+ '<Tag: <Article: Copyright is fine the way it is> tagged "law">',
+ ], ordered=False)
# Load fixture 7, XML file with dynamic ContentType fields. Testing ManyToOne.
management.call_command('loaddata', 'fixture7.xml', verbosity=0, commit=False)
@@ -105,8 +105,8 @@ class FixtureLoadingTests(TestCase):
'<Tag: <Article: Copyright is fine the way it is> tagged "copyright">',
'<Tag: <Article: Copyright is fine the way it is> tagged "legal">',
'<Tag: <Article: Django conquers world!> tagged "django">',
- '<Tag: <Article: Django conquers world!> tagged "world domination">'
- ])
+ '<Tag: <Article: Django conquers world!> tagged "world domination">',
+ ], ordered=False)
# Load fixture 8, JSON file with dynamic Permission fields. Testing ManyToMany.
management.call_command('loaddata', 'fixture8.json', verbosity=0, commit=False)
@@ -114,7 +114,7 @@ class FixtureLoadingTests(TestCase):
'<Visa: Django Reinhardt Can add user, Can change user, Can delete user>',
'<Visa: Stephane Grappelli Can add user>',
'<Visa: Prince >'
- ])
+ ], ordered=False)
# Load fixture 9, XML file with dynamic Permission fields. Testing ManyToMany.
management.call_command('loaddata', 'fixture9.xml', verbosity=0, commit=False)
@@ -122,7 +122,7 @@ class FixtureLoadingTests(TestCase):
'<Visa: Django Reinhardt Can add user, Can change user, Can delete user>',
'<Visa: Stephane Grappelli Can add user, Can delete user>',
'<Visa: Artist formerly known as "Prince" Can change user>'
- ])
+ ], ordered=False)
self.assertQuerysetEqual(Book.objects.all(), [
'<Book: Achieving self-awareness of Python programs>',
@@ -226,9 +226,9 @@ class FixtureLoadingTests(TestCase):
def test_ambiguous_compressed_fixture(self):
# The name "fixture5" is ambigous, so loading it will raise an error
- with six.assertRaisesRegex(self, management.CommandError,
- "Multiple fixtures named 'fixture5'"):
+ with self.assertRaises(management.CommandError) as cm:
management.call_command('loaddata', 'fixture5', verbosity=0, commit=False)
+ self.assertIn("Multiple fixtures named 'fixture5'", cm.exception.args[0])
def test_db_loading(self):
# Load db fixtures 1 and 2. These will load using the 'default' database identifier implicitly
@@ -250,9 +250,9 @@ class FixtureLoadingTests(TestCase):
# is closed at the end of each test.
if connection.vendor == 'mysql':
connection.cursor().execute("SET sql_mode = 'TRADITIONAL'")
- with six.assertRaisesRegex(self, IntegrityError,
- "Could not load fixtures.Article\(pk=1\): .*$"):
+ with self.assertRaises(IntegrityError) as cm:
management.call_command('loaddata', 'invalid.json', verbosity=0, commit=False)
+ self.assertIn("Could not load fixtures.Article(pk=1):", cm.exception.args[0])
def test_loading_using(self):
# Load db fixtures 1 and 2. These will load using the 'default' database identifier explicitly
@@ -280,7 +280,7 @@ class FixtureLoadingTests(TestCase):
self.assertQuerysetEqual(Tag.objects.all(), [
'<Tag: <Article: Time to reform copyright> tagged "copyright">',
'<Tag: <Article: Time to reform copyright> tagged "law">'
- ])
+ ], ordered=False)
# Dump the current contents of the database as a JSON fixture
self._dumpdata_assert(['fixtures'], '[{"pk": 1, "model": "fixtures.category", "fields": {"description": "Latest news stories", "title": "News Stories"}}, {"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}, {"pk": 3, "model": "fixtures.article", "fields": {"headline": "Time to reform copyright", "pub_date": "2006-06-16T13:00:00"}}, {"pk": 1, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "copyright", "tagged_id": 3}}, {"pk": 2, "model": "fixtures.tag", "fields": {"tagged_type": ["fixtures", "article"], "name": "law", "tagged_id": 3}}, {"pk": 1, "model": "fixtures.person", "fields": {"name": "Django Reinhardt"}}, {"pk": 2, "model": "fixtures.person", "fields": {"name": "Stephane Grappelli"}}, {"pk": 3, "model": "fixtures.person", "fields": {"name": "Prince"}}, {"pk": 10, "model": "fixtures.book", "fields": {"name": "Achieving self-awareness of Python programs", "authors": []}}]', natural_keys=True)
@@ -308,9 +308,9 @@ class FixtureTransactionTests(TransactionTestCase):
# Try to load fixture 2 using format discovery; this will fail
# because there are two fixture2's in the fixtures directory
- with six.assertRaisesRegex(self, management.CommandError,
- "Multiple fixtures named 'fixture2'"):
+ with self.assertRaises(management.CommandError) as cm:
management.call_command('loaddata', 'fixture2', verbosity=0)
+ self.assertIn("Multiple fixtures named 'fixture2'", cm.exception.args[0])
# object list is unaffected
self.assertQuerysetEqual(Article.objects.all(), [
diff --git a/tests/modeltests/generic_relations/tests.py b/tests/modeltests/generic_relations/tests.py
index 14871e4e09..73b0a483a2 100644
--- a/tests/modeltests/generic_relations/tests.py
+++ b/tests/modeltests/generic_relations/tests.py
@@ -169,8 +169,8 @@ class GenericRelationsTests(TestCase):
# Filtering works
self.assertQuerysetEqual(tiger.comparisons.filter(comparative="cooler"), [
"<Comparison: tiger is cooler than cheetah>",
- "<Comparison: tiger is cooler than bear>"
- ])
+ "<Comparison: tiger is cooler than bear>",
+ ], ordered=False)
# Filtering and deleting works
subjective = ["cooler"]
@@ -178,7 +178,7 @@ class GenericRelationsTests(TestCase):
self.assertQuerysetEqual(Comparison.objects.all(), [
"<Comparison: cheetah is faster than tiger>",
"<Comparison: tiger is stronger than cheetah>"
- ])
+ ], ordered=False)
# If we delete cheetah, Comparisons with cheetah as 'first_obj' will be
# deleted since Animal has an explicit GenericRelation to Comparison
diff --git a/tests/modeltests/get_object_or_404/tests.py b/tests/modeltests/get_object_or_404/tests.py
index 280720fd15..3b234c6cd3 100644
--- a/tests/modeltests/get_object_or_404/tests.py
+++ b/tests/modeltests/get_object_or_404/tests.py
@@ -80,3 +80,28 @@ class GetObjectOr404Tests(TestCase):
get_list_or_404(Article.objects.all(), title__icontains="Run"),
[article]
)
+
+ def test_bad_class(self):
+ # Given an argument klass that is not a Model, Manager, or Queryset
+ # raises a helpful ValueError message
+ self.assertRaisesMessage(ValueError,
+ "Object is of type 'str', but must be a Django Model, Manager, "
+ "or QuerySet",
+ get_object_or_404, "Article", title__icontains="Run"
+ )
+
+ class CustomClass(object):
+ pass
+
+ self.assertRaisesMessage(ValueError,
+ "Object is of type 'CustomClass', but must be a Django Model, "
+ "Manager, or QuerySet",
+ get_object_or_404, CustomClass, title__icontains="Run"
+ )
+
+ # Works for lists too
+ self.assertRaisesMessage(ValueError,
+ "Object is of type 'list', but must be a Django Model, Manager, "
+ "or QuerySet",
+ get_list_or_404, [Article], title__icontains="Run"
+ )
diff --git a/tests/modeltests/invalid_models/invalid_models/models.py b/tests/modeltests/invalid_models/invalid_models/models.py
index ccb6396352..3c21e1ddb8 100644
--- a/tests/modeltests/invalid_models/invalid_models/models.py
+++ b/tests/modeltests/invalid_models/invalid_models/models.py
@@ -356,6 +356,13 @@ class HardReferenceModel(models.Model):
m2m_4 = models.ManyToManyField('invalid_models.SwappedModel', related_name='m2m_hardref4')
+class BadIndexTogether1(models.Model):
+ class Meta:
+ index_together = [
+ ["field_that_does_not_exist"],
+ ]
+
+
model_errors = """invalid_models.fielderrors: "charfield": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield2": CharFields require a "max_length" attribute that is a positive integer.
invalid_models.fielderrors: "charfield3": CharFields require a "max_length" attribute that is a positive integer.
@@ -470,6 +477,7 @@ invalid_models.hardreferencemodel: 'm2m_3' defines a relation with the model 'in
invalid_models.hardreferencemodel: 'm2m_4' defines a relation with the model 'invalid_models.SwappedModel', which has been swapped out. Update the relation to point at settings.TEST_SWAPPED_MODEL.
invalid_models.badswappablevalue: TEST_SWAPPED_MODEL_BAD_VALUE is not of the form 'app_label.app_name'.
invalid_models.badswappablemodel: Model has been swapped out for 'not_an_app.Target' which has not been installed or is abstract.
+invalid_models.badindextogether1: "index_together" refers to field_that_does_not_exist, a field that doesn't exist.
"""
if not connection.features.interprets_empty_strings_as_nulls:
diff --git a/tests/modeltests/m2m_recursive/tests.py b/tests/modeltests/m2m_recursive/tests.py
index 5742836929..a3f2c670d6 100644
--- a/tests/modeltests/m2m_recursive/tests.py
+++ b/tests/modeltests/m2m_recursive/tests.py
@@ -28,7 +28,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David"
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -43,7 +44,8 @@ class RecursiveM2MTests(TestCase):
"Anne",
"David"
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with David?
self.assertQuerysetEqual(
@@ -51,7 +53,8 @@ class RecursiveM2MTests(TestCase):
"Anne",
"Chuck",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Bill is already friends with Anne - add Anne again, but in the
# reverse direction
@@ -64,7 +67,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -81,7 +85,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is friends with Bill?
self.assertQuerysetEqual(
@@ -125,7 +130,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@@ -172,7 +178,8 @@ class RecursiveM2MTests(TestCase):
"Anne",
"Chuck",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Bill is already being stalked by Anne - add Anne again, but in the
# reverse direction
@@ -184,7 +191,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
@@ -215,7 +223,8 @@ class RecursiveM2MTests(TestCase):
"Chuck",
"David",
],
- attrgetter("name")
+ attrgetter("name"),
+ ordered=False
)
# Who is stalking Anne?
self.assertQuerysetEqual(
diff --git a/tests/modeltests/many_to_one/tests.py b/tests/modeltests/many_to_one/tests.py
index 4fb19dbc69..44ae689dd4 100644
--- a/tests/modeltests/many_to_one/tests.py
+++ b/tests/modeltests/many_to_one/tests.py
@@ -267,7 +267,9 @@ class ManyToOneTests(TestCase):
["<Reporter: John Smith>"])
self.assertQuerysetEqual(
Reporter.objects.filter(article__headline__startswith='T'),
- ["<Reporter: John Smith>", "<Reporter: John Smith>"])
+ ["<Reporter: John Smith>", "<Reporter: John Smith>"],
+ ordered=False
+ )
self.assertQuerysetEqual(
Reporter.objects.filter(article__headline__startswith='T').distinct(),
["<Reporter: John Smith>"])
@@ -285,7 +287,9 @@ class ManyToOneTests(TestCase):
"<Reporter: John Smith>",
"<Reporter: John Smith>",
"<Reporter: John Smith>",
- ])
+ ],
+ ordered=False
+ )
self.assertQuerysetEqual(
Reporter.objects.filter(article__reporter__first_name__startswith='John').distinct(),
["<Reporter: John Smith>"])
diff --git a/tests/modeltests/model_forms/models.py b/tests/modeltests/model_forms/models.py
index 90b019b611..132da2d318 100644
--- a/tests/modeltests/model_forms/models.py
+++ b/tests/modeltests/model_forms/models.py
@@ -91,15 +91,6 @@ class WriterProfile(models.Model):
def __str__(self):
return "%s is %s" % (self.writer, self.age)
-from django.contrib.localflavor.us.models import PhoneNumberField
-@python_2_unicode_compatible
-class PhoneNumber(models.Model):
- phone = PhoneNumberField()
- description = models.CharField(max_length=20)
-
- def __str__(self):
- return self.phone
-
@python_2_unicode_compatible
class TextFile(models.Model):
description = models.CharField(max_length=20)
diff --git a/tests/modeltests/model_forms/tests.py b/tests/modeltests/model_forms/tests.py
index 947d0cf3c3..9699b155c0 100644
--- a/tests/modeltests/model_forms/tests.py
+++ b/tests/modeltests/model_forms/tests.py
@@ -10,6 +10,7 @@ from django.core.validators import ValidationError
from django.db import connection
from django.db.models.query import EmptyQuerySet
from django.forms.models import model_to_dict
+from django.utils._os import upath
from django.utils.unittest import skipUnless
from django.test import TestCase
from django.utils import six
@@ -17,7 +18,7 @@ from django.utils import six
from .models import (Article, ArticleStatus, BetterWriter, BigInt, Book,
Category, CommaSeparatedInteger, CustomFieldForExclusionModel, DerivedBook,
DerivedPost, ExplicitPK, FlexibleDatePost, ImprovedArticle,
- ImprovedArticleWithParentLink, Inventory, PhoneNumber, Post, Price,
+ ImprovedArticleWithParentLink, Inventory, Post, Price,
Product, TextFile, Writer, WriterProfile, test_images)
if test_images:
@@ -147,10 +148,6 @@ class WriterProfileForm(forms.ModelForm):
class Meta:
model = WriterProfile
-class PhoneNumberForm(forms.ModelForm):
- class Meta:
- model = PhoneNumber
-
class TextFileForm(forms.ModelForm):
class Meta:
model = TextFile
@@ -166,7 +163,7 @@ class ModelFormWithMedia(forms.ModelForm):
'all': ('/some/form/css',)
}
class Meta:
- model = PhoneNumber
+ model = TextFile
class CommaSeparatedIntegerForm(forms.ModelForm):
class Meta:
@@ -561,6 +558,42 @@ class UniqueTest(TestCase):
"slug": "Django 1.0"}, instance=p)
self.assertTrue(form.is_valid())
+class ModelToDictTests(TestCase):
+ """
+ Tests for forms.models.model_to_dict
+ """
+ def test_model_to_dict_many_to_many(self):
+ categories=[
+ Category(name='TestName1', slug='TestName1', url='url1'),
+ Category(name='TestName2', slug='TestName2', url='url2'),
+ Category(name='TestName3', slug='TestName3', url='url3')
+ ]
+ for c in categories:
+ c.save()
+ writer = Writer(name='Test writer')
+ writer.save()
+
+ art = Article(
+ headline='Test article',
+ slug='test-article',
+ pub_date=datetime.date(1988, 1, 4),
+ writer=writer,
+ article='Hello.'
+ )
+ art.save()
+ for c in categories:
+ art.categories.add(c)
+ art.save()
+
+ with self.assertNumQueries(1):
+ d = model_to_dict(art)
+
+ #Ensure all many-to-many categories appear in model_to_dict
+ for c in categories:
+ self.assertIn(c.pk, d['categories'])
+ #Ensure many-to-many relation appears as a list
+ self.assertIsInstance(d['categories'], list)
+
class OldFormForXTests(TestCase):
def test_base_form(self):
self.assertEqual(Category.objects.count(), 0)
@@ -1011,9 +1044,12 @@ class OldFormForXTests(TestCase):
self.assertQuerysetEqual(f.clean([c1.id]), ["Entertainment"])
self.assertQuerysetEqual(f.clean([c2.id]), ["It's a test"])
self.assertQuerysetEqual(f.clean([str(c1.id)]), ["Entertainment"])
- self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"])
- self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"])
- self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"])
+ self.assertQuerysetEqual(f.clean([str(c1.id), str(c2.id)]), ["Entertainment", "It's a test"],
+ ordered=False)
+ self.assertQuerysetEqual(f.clean([c1.id, str(c2.id)]), ["Entertainment", "It's a test"],
+ ordered=False)
+ self.assertQuerysetEqual(f.clean((c1.id, str(c2.id))), ["Entertainment", "It's a test"],
+ ordered=False)
with self.assertRaises(ValidationError):
f.clean(['100'])
with self.assertRaises(ValidationError):
@@ -1024,7 +1060,10 @@ class OldFormForXTests(TestCase):
# Add a Category object *after* the ModelMultipleChoiceField has already been
# instantiated. This proves clean() checks the database during clean() rather
# than caching it at time of instantiation.
- c6 = Category.objects.create(id=6, name='Sixth', url='6th')
+ # Note, we are using an id of 1006 here since tests that run before
+ # this may create categories with primary keys up to 6. Use
+ # a number that is will not conflict.
+ c6 = Category.objects.create(id=1006, name='Sixth', url='6th')
self.assertEqual(c6.name, 'Sixth')
self.assertQuerysetEqual(f.clean([c6.id]), ["Sixth"])
@@ -1109,12 +1148,6 @@ class OldFormForXTests(TestCase):
</select></p>
<p><label for="id_age">Age:</label> <input type="text" name="age" value="65" id="id_age" /></p>''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk))
- def test_phone_number_field(self):
- f = PhoneNumberForm({'phone': '(312) 555-1212', 'description': 'Assistance'})
- self.assertEqual(f.is_valid(), True)
- self.assertEqual(f.cleaned_data['phone'], '312-555-1212')
- self.assertEqual(f.cleaned_data['description'], 'Assistance')
-
def test_file_field(self):
# Test conditions when files is either not given or empty.
@@ -1243,9 +1276,9 @@ class OldFormForXTests(TestCase):
# it comes to validation. This specifically tests that #6302 is fixed for
# both file fields and image fields.
- with open(os.path.join(os.path.dirname(__file__), "test.png"), 'rb') as fp:
+ with open(os.path.join(os.path.dirname(upath(__file__)), "test.png"), 'rb') as fp:
image_data = fp.read()
- with open(os.path.join(os.path.dirname(__file__), "test2.png"), 'rb') as fp:
+ with open(os.path.join(os.path.dirname(upath(__file__)), "test2.png"), 'rb') as fp:
image_data2 = fp.read()
f = ImageFileForm(
diff --git a/tests/modeltests/pagination/tests.py b/tests/modeltests/pagination/tests.py
deleted file mode 100644
index 12ce0e3ecb..0000000000
--- a/tests/modeltests/pagination/tests.py
+++ /dev/null
@@ -1,134 +0,0 @@
-from __future__ import absolute_import, unicode_literals
-
-from datetime import datetime
-
-from django.core.paginator import Paginator, InvalidPage, EmptyPage
-from django.test import TestCase
-from django.utils import six
-
-from .models import Article
-
-
-class CountContainer(object):
- def count(self):
- return 42
-
-class LenContainer(object):
- def __len__(self):
- return 42
-
-class PaginationTests(TestCase):
- def setUp(self):
- # Prepare a list of objects for pagination.
- for x in range(1, 10):
- a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
- a.save()
-
- def test_paginator(self):
- paginator = Paginator(Article.objects.all(), 5)
- self.assertEqual(9, paginator.count)
- self.assertEqual(2, paginator.num_pages)
- self.assertEqual([1, 2], list(paginator.page_range))
-
- def test_first_page(self):
- paginator = Paginator(Article.objects.all(), 5)
- p = paginator.page(1)
- self.assertEqual("<Page 1 of 2>", six.text_type(p))
- self.assertQuerysetEqual(p.object_list, [
- "<Article: Article 1>",
- "<Article: Article 2>",
- "<Article: Article 3>",
- "<Article: Article 4>",
- "<Article: Article 5>"
- ]
- )
- self.assertTrue(p.has_next())
- self.assertFalse(p.has_previous())
- self.assertTrue(p.has_other_pages())
- self.assertEqual(2, p.next_page_number())
- self.assertRaises(InvalidPage, p.previous_page_number)
- self.assertEqual(1, p.start_index())
- self.assertEqual(5, p.end_index())
-
- def test_last_page(self):
- paginator = Paginator(Article.objects.all(), 5)
- p = paginator.page(2)
- self.assertEqual("<Page 2 of 2>", six.text_type(p))
- self.assertQuerysetEqual(p.object_list, [
- "<Article: Article 6>",
- "<Article: Article 7>",
- "<Article: Article 8>",
- "<Article: Article 9>"
- ]
- )
- self.assertFalse(p.has_next())
- self.assertTrue(p.has_previous())
- self.assertTrue(p.has_other_pages())
- self.assertRaises(InvalidPage, p.next_page_number)
- self.assertEqual(1, p.previous_page_number())
- self.assertEqual(6, p.start_index())
- self.assertEqual(9, p.end_index())
-
- def test_empty_page(self):
- paginator = Paginator(Article.objects.all(), 5)
- self.assertRaises(EmptyPage, paginator.page, 0)
- self.assertRaises(EmptyPage, paginator.page, 3)
-
- # Empty paginators with allow_empty_first_page=True.
- paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=True)
- self.assertEqual(0, paginator.count)
- self.assertEqual(1, paginator.num_pages)
- self.assertEqual([1], list(paginator.page_range))
-
- # Empty paginators with allow_empty_first_page=False.
- paginator = Paginator(Article.objects.filter(id=0), 5, allow_empty_first_page=False)
- self.assertEqual(0, paginator.count)
- self.assertEqual(0, paginator.num_pages)
- self.assertEqual([], list(paginator.page_range))
-
- def test_invalid_page(self):
- paginator = Paginator(Article.objects.all(), 5)
- self.assertRaises(InvalidPage, paginator.page, 7)
-
- def test_orphans(self):
- # Add a few more records to test out the orphans feature.
- for x in range(10, 13):
- Article(headline="Article %s" % x, pub_date=datetime(2006, 10, 6)).save()
-
- # With orphans set to 3 and 10 items per page, we should get all 12 items on a single page.
- paginator = Paginator(Article.objects.all(), 10, orphans=3)
- self.assertEqual(1, paginator.num_pages)
-
- # With orphans only set to 1, we should get two pages.
- paginator = Paginator(Article.objects.all(), 10, orphans=1)
- self.assertEqual(2, paginator.num_pages)
-
- def test_paginate_list(self):
- # Paginators work with regular lists/tuples, too -- not just with QuerySets.
- paginator = Paginator([1, 2, 3, 4, 5, 6, 7, 8, 9], 3)
- self.assertEqual(9, paginator.count)
- self.assertEqual(3, paginator.num_pages)
- self.assertEqual([1, 2, 3], list(paginator.page_range))
- p = paginator.page(2)
- self.assertEqual("<Page 2 of 3>", six.text_type(p))
- self.assertEqual([4, 5, 6], p.object_list)
- self.assertTrue(p.has_next())
- self.assertTrue(p.has_previous())
- self.assertTrue(p.has_other_pages())
- self.assertEqual(3, p.next_page_number())
- self.assertEqual(1, p.previous_page_number())
- self.assertEqual(4, p.start_index())
- self.assertEqual(6, p.end_index())
-
- def test_paginate_misc_classes(self):
- # Paginator can be passed other objects with a count() method.
- paginator = Paginator(CountContainer(), 10)
- self.assertEqual(42, paginator.count)
- self.assertEqual(5, paginator.num_pages)
- self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
-
- # Paginator can be passed other objects that implement __len__.
- paginator = Paginator(LenContainer(), 10)
- self.assertEqual(42, paginator.count)
- self.assertEqual(5, paginator.num_pages)
- self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
diff --git a/tests/modeltests/prefetch_related/models.py b/tests/modeltests/prefetch_related/models.py
index 85488f0879..e58997d200 100644
--- a/tests/modeltests/prefetch_related/models.py
+++ b/tests/modeltests/prefetch_related/models.py
@@ -125,6 +125,10 @@ class TaggedItem(models.Model):
related_name='taggeditem_set3')
created_by_fkey = models.PositiveIntegerField(null=True)
created_by = generic.GenericForeignKey('created_by_ct', 'created_by_fkey',)
+ favorite_ct = models.ForeignKey(ContentType, null=True,
+ related_name='taggeditem_set4')
+ favorite_fkey = models.CharField(max_length=64, null=True)
+ favorite = generic.GenericForeignKey('favorite_ct', 'favorite_fkey')
def __str__(self):
return self.tag
@@ -132,7 +136,11 @@ class TaggedItem(models.Model):
class Bookmark(models.Model):
url = models.URLField()
- tags = generic.GenericRelation(TaggedItem)
+ tags = generic.GenericRelation(TaggedItem, related_name='bookmarks')
+ favorite_tags = generic.GenericRelation(TaggedItem,
+ content_type_field='favorite_ct',
+ object_id_field='favorite_fkey',
+ related_name='favorite_bookmarks')
class Comment(models.Model):
diff --git a/tests/modeltests/prefetch_related/tests.py b/tests/modeltests/prefetch_related/tests.py
index 614a5fc1d6..e81560f01f 100644
--- a/tests/modeltests/prefetch_related/tests.py
+++ b/tests/modeltests/prefetch_related/tests.py
@@ -319,6 +319,16 @@ class GenericRelationTests(TestCase):
for t in b.tags.all()]
self.assertEqual(sorted(tags), ["django", "python"])
+ def test_charfield_GFK(self):
+ b = Bookmark.objects.create(url='http://www.djangoproject.com/')
+ t1 = TaggedItem.objects.create(content_object=b, tag='django')
+ t2 = TaggedItem.objects.create(content_object=b, favorite=b, tag='python')
+
+ with self.assertNumQueries(3):
+ bookmark = Bookmark.objects.filter(pk=b.pk).prefetch_related('tags', 'favorite_tags')[0]
+ self.assertEqual(sorted([i.tag for i in bookmark.tags.all()]), ["django", "python"])
+ self.assertEqual([i.tag for i in bookmark.favorite_tags.all()], ["python"])
+
class MultiTableInheritanceTest(TestCase):
diff --git a/tests/modeltests/proxy_model_inheritance/tests.py b/tests/modeltests/proxy_model_inheritance/tests.py
index 39fee7ee6d..239bc67809 100644
--- a/tests/modeltests/proxy_model_inheritance/tests.py
+++ b/tests/modeltests/proxy_model_inheritance/tests.py
@@ -8,6 +8,7 @@ from django.core.management import call_command
from django.db.models.loading import cache, load_app
from django.test import TestCase, TransactionTestCase
from django.test.utils import override_settings
+from django.utils._os import upath
from .models import (ConcreteModel, ConcreteModelSubclass,
ConcreteModelSubclassProxy)
@@ -23,7 +24,7 @@ class ProxyModelInheritanceTests(TransactionTestCase):
def setUp(self):
self.old_sys_path = sys.path[:]
- sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+ sys.path.append(os.path.dirname(os.path.abspath(upath(__file__))))
for app in settings.INSTALLED_APPS:
load_app(app)
diff --git a/tests/modeltests/raw_query/models.py b/tests/modeltests/raw_query/models.py
index 823fc76eee..e7e221dc6e 100644
--- a/tests/modeltests/raw_query/models.py
+++ b/tests/modeltests/raw_query/models.py
@@ -13,7 +13,7 @@ class Author(models.Model):
# treated differently than fields.
for k in kwargs:
assert k in [f.attname for f in self._meta.fields], \
- "Author.__init__ got an unexpected paramater: %s" % k
+ "Author.__init__ got an unexpected parameter: %s" % k
class Book(models.Model):
title = models.CharField(max_length=255)
diff --git a/tests/modeltests/str/models.py b/tests/modeltests/str/models.py
index 488012e861..1c158ea008 100644
--- a/tests/modeltests/str/models.py
+++ b/tests/modeltests/str/models.py
@@ -28,6 +28,14 @@ class Article(models.Model):
return self.headline
@python_2_unicode_compatible
+class BrokenArticle(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateTimeField()
+
+ def __unicode__(self): # instead of __str__
+ return self.headline
+
+@python_2_unicode_compatible
class InternationalArticle(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateTimeField()
diff --git a/tests/modeltests/str/tests.py b/tests/modeltests/str/tests.py
index 2c11ac8c78..31869583aa 100644
--- a/tests/modeltests/str/tests.py
+++ b/tests/modeltests/str/tests.py
@@ -7,7 +7,7 @@ from django.test import TestCase
from django.utils import six
from django.utils.unittest import skipIf
-from .models import Article, InternationalArticle
+from .models import Article, BrokenArticle, InternationalArticle
class SimpleTests(TestCase):
@@ -21,6 +21,16 @@ class SimpleTests(TestCase):
self.assertEqual(str(a), str('Area man programs in Python'))
self.assertEqual(repr(a), str('<Article: Area man programs in Python>'))
+ @skipIf(six.PY3, "tests Model's default __str__ method under Python 2")
+ def test_broken(self):
+ # Regression test for #19362.
+ a = BrokenArticle.objects.create(
+ headline='Girl wins €12.500 in lottery',
+ pub_date=datetime.datetime(2005, 7, 28)
+ )
+ self.assertRaisesRegexp(RuntimeError, "Did you apply "
+ "@python_2_unicode_compatible without defining __str__\?", str, a)
+
def test_international(self):
a = InternationalArticle.objects.create(
headline='Girl wins €12.500 in lottery',
diff --git a/tests/regressiontests/admin_changelist/admin.py b/tests/regressiontests/admin_changelist/admin.py
index 9ecfbc6e12..5751d04bce 100644
--- a/tests/regressiontests/admin_changelist/admin.py
+++ b/tests/regressiontests/admin_changelist/admin.py
@@ -32,6 +32,7 @@ class ParentAdmin(admin.ModelAdmin):
class ChildAdmin(admin.ModelAdmin):
list_display = ['name', 'parent']
list_per_page = 10
+ list_filter = ['parent', 'age']
def queryset(self, request):
return super(ChildAdmin, self).queryset(request).select_related("parent__name")
@@ -90,3 +91,14 @@ class SwallowAdmin(admin.ModelAdmin):
list_display = ('origin', 'load', 'speed')
site.register(Swallow, SwallowAdmin)
+
+class DynamicListFilterChildAdmin(admin.ModelAdmin):
+ list_filter = ('parent', 'name', 'age')
+
+ def get_list_filter(self, request):
+ my_list_filter = super(DynamicListFilterChildAdmin, self).get_list_filter(request)
+ if request.user.username == 'noparents':
+ my_list_filter = list(my_list_filter)
+ my_list_filter.remove('parent')
+ return my_list_filter
+
diff --git a/tests/regressiontests/admin_changelist/tests.py b/tests/regressiontests/admin_changelist/tests.py
index 2b1c1a9bcf..e8d4cbff16 100644
--- a/tests/regressiontests/admin_changelist/tests.py
+++ b/tests/regressiontests/admin_changelist/tests.py
@@ -17,7 +17,7 @@ from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
FilteredChildAdmin, CustomPaginator, site as custom_site,
- SwallowAdmin)
+ SwallowAdmin, DynamicListFilterChildAdmin)
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
UnorderedObject, OrderedObject)
@@ -541,3 +541,26 @@ class ChangeListTests(TestCase):
check_results_order()
OrderedObjectAdmin.ordering = ['id', 'bool']
check_results_order(ascending=True)
+
+ def test_dynamic_list_filter(self):
+ """
+ Regression tests for ticket #17646: dynamic list_filter support.
+ """
+ parent = Parent.objects.create(name='parent')
+ for i in range(10):
+ Child.objects.create(name='child %s' % i, parent=parent)
+
+ user_noparents = self._create_superuser('noparents')
+ user_parents = self._create_superuser('parents')
+
+ # Test with user 'noparents'
+ m = DynamicListFilterChildAdmin(Child, admin.site)
+ request = self._mocked_authenticated_request('/child/', user_noparents)
+ response = m.changelist_view(request)
+ self.assertEqual(response.context_data['cl'].list_filter, ['name', 'age'])
+
+ # Test with user 'parents'
+ m = DynamicListFilterChildAdmin(Child, admin.site)
+ request = self._mocked_authenticated_request('/child/', user_parents)
+ response = m.changelist_view(request)
+ self.assertEqual(response.context_data['cl'].list_filter, ('parent', 'name', 'age'))
diff --git a/tests/regressiontests/admin_filters/tests.py b/tests/regressiontests/admin_filters/tests.py
index 6a36f62cea..4a3e6135c3 100644
--- a/tests/regressiontests/admin_filters/tests.py
+++ b/tests/regressiontests/admin_filters/tests.py
@@ -78,6 +78,21 @@ class DecadeListFilterParameterEndsWith__Isnull(DecadeListFilter):
parameter_name = 'decade__isnull' # Ends with '__isnull"
+class DepartmentListFilterLookupWithNonStringValue(SimpleListFilter):
+ title = 'department'
+ parameter_name = 'department'
+
+ def lookups(self, request, model_admin):
+ return set([
+ (employee.department.id, # Intentionally not a string (Refs #19318)
+ employee.department.code)
+ for employee in model_admin.queryset(request).all()
+ ])
+
+ def queryset(self, request, queryset):
+ if self.value():
+ return queryset.filter(department__id=self.value())
+
class CustomUserAdmin(UserAdmin):
list_filter = ('books_authored', 'books_contributed')
@@ -118,6 +133,10 @@ class EmployeeAdmin(ModelAdmin):
list_filter = ['department']
+class DepartmentFilterEmployeeAdmin(EmployeeAdmin):
+ list_filter = [DepartmentListFilterLookupWithNonStringValue, ]
+
+
class ListFiltersTests(TestCase):
def setUp(self):
@@ -140,6 +159,14 @@ class ListFiltersTests(TestCase):
self.gipsy_book.contributors = [self.bob, self.lisa]
self.gipsy_book.save()
+ # Departments
+ self.dev = Department.objects.create(code='DEV', description='Development')
+ self.design = Department.objects.create(code='DSN', description='Design')
+
+ # Employees
+ self.john = Employee.objects.create(name='John Blue', department=self.dev)
+ self.jack = Employee.objects.create(name='Jack Red', department=self.design)
+
def get_changelist(self, request, model, modeladmin):
return ChangeList(request, model, modeladmin.list_display, modeladmin.list_display_links,
modeladmin.list_filter, modeladmin.date_hierarchy, modeladmin.search_fields,
@@ -638,6 +665,28 @@ class ListFiltersTests(TestCase):
self.assertEqual(choices[2]['selected'], True)
self.assertEqual(choices[2]['query_string'], '?decade__isnull=the+90s')
+ def test_lookup_with_non_string_value(self):
+ """
+ Ensure choices are set the selected class when using non-string values
+ for lookups in SimpleListFilters.
+ Refs #19318
+ """
+
+ modeladmin = DepartmentFilterEmployeeAdmin(Employee, site)
+ request = self.request_factory.get('/', {'department': self.john.pk})
+ changelist = self.get_changelist(request, Employee, modeladmin)
+
+ queryset = changelist.get_query_set(request)
+
+ self.assertEqual(list(queryset), [self.john])
+
+ filterspec = changelist.get_filters(request)[0][-1]
+ self.assertEqual(force_text(filterspec.title), 'department')
+ choices = list(filterspec.choices(changelist))
+ self.assertEqual(choices[2]['display'], 'DEV')
+ self.assertEqual(choices[2]['selected'], True)
+ self.assertEqual(choices[2]['query_string'], '?department=%s' % self.john.pk)
+
def test_fk_with_to_field(self):
"""
Ensure that a filter on a FK respects the FK's to_field attribute.
@@ -645,17 +694,12 @@ class ListFiltersTests(TestCase):
"""
modeladmin = EmployeeAdmin(Employee, site)
- dev = Department.objects.create(code='DEV', description='Development')
- design = Department.objects.create(code='DSN', description='Design')
- john = Employee.objects.create(name='John Blue', department=dev)
- jack = Employee.objects.create(name='Jack Red', department=design)
-
request = self.request_factory.get('/', {})
changelist = self.get_changelist(request, Employee, modeladmin)
# Make sure the correct queryset is returned
queryset = changelist.get_query_set(request)
- self.assertEqual(list(queryset), [jack, john])
+ self.assertEqual(list(queryset), [self.jack, self.john])
filterspec = changelist.get_filters(request)[0][-1]
self.assertEqual(force_text(filterspec.title), 'department')
@@ -680,7 +724,7 @@ class ListFiltersTests(TestCase):
# Make sure the correct queryset is returned
queryset = changelist.get_query_set(request)
- self.assertEqual(list(queryset), [john])
+ self.assertEqual(list(queryset), [self.john])
filterspec = changelist.get_filters(request)[0][-1]
self.assertEqual(force_text(filterspec.title), 'department')
diff --git a/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico b/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico
new file mode 100644
index 0000000000..1db49645b5
--- /dev/null
+++ b/tests/regressiontests/admin_scripts/custom_templates/project_template/ticket-19397-binary-file.ico
Binary files differ
diff --git a/tests/regressiontests/admin_scripts/tests.py b/tests/regressiontests/admin_scripts/tests.py
index 3bb8bb0b50..d0ca9d26df 100644
--- a/tests/regressiontests/admin_scripts/tests.py
+++ b/tests/regressiontests/admin_scripts/tests.py
@@ -19,13 +19,15 @@ from django.conf import settings
from django.db import connection
from django.test.simple import DjangoTestSuiteRunner
from django.utils import unittest
+from django.utils.encoding import force_str, force_text
+from django.utils._os import upath
from django.test import LiveServerTestCase
-test_dir = os.path.dirname(os.path.dirname(__file__))
+test_dir = os.path.dirname(os.path.dirname(upath(__file__)))
class AdminScriptTestCase(unittest.TestCase):
def write_settings(self, filename, apps=None, is_dir=False, sdict=None):
- test_dir = os.path.dirname(os.path.dirname(__file__))
+ test_dir = os.path.dirname(os.path.dirname(upath(__file__)))
if is_dir:
settings_dir = os.path.join(test_dir, filename)
os.mkdir(settings_dir)
@@ -94,6 +96,7 @@ class AdminScriptTestCase(unittest.TestCase):
return paths
def run_test(self, script, args, settings_file=None, apps=None):
+ test_dir = os.path.dirname(os.path.dirname(__file__))
project_dir = os.path.dirname(test_dir)
base_dir = os.path.dirname(project_dir)
ext_backend_base_dirs = self._ext_backend_paths()
@@ -134,11 +137,17 @@ class AdminScriptTestCase(unittest.TestCase):
return out, err
def run_django_admin(self, args, settings_file=None):
- bin_dir = os.path.abspath(os.path.dirname(bin.__file__))
+ bin_dir = os.path.abspath(os.path.dirname(upath(bin.__file__)))
return self.run_test(os.path.join(bin_dir, 'django-admin.py'), args, settings_file)
def run_manage(self, args, settings_file=None):
- conf_dir = os.path.dirname(conf.__file__)
+ def safe_remove(path):
+ try:
+ os.remove(path)
+ except OSError:
+ pass
+
+ conf_dir = os.path.dirname(upath(conf.__file__))
template_manage_py = os.path.join(conf_dir, 'project_template', 'manage.py')
test_manage_py = os.path.join(test_dir, 'manage.py')
@@ -150,13 +159,9 @@ class AdminScriptTestCase(unittest.TestCase):
"{{ project_name }}", "regressiontests")
with open(test_manage_py, 'w') as fp:
fp.write(manage_py_contents)
+ self.addCleanup(safe_remove, test_manage_py)
- stdout, stderr = self.run_test('./manage.py', args, settings_file)
-
- # Cleanup - remove the generated manage.py script
- os.remove(test_manage_py)
-
- return stdout, stderr
+ return self.run_test('./manage.py', args, settings_file)
def assertNoOutput(self, stream):
"Utility assertion: assert that the given stream is empty"
@@ -164,10 +169,12 @@ class AdminScriptTestCase(unittest.TestCase):
def assertOutput(self, stream, msg):
"Utility assertion: assert that the given message exists in the output"
+ stream = force_text(stream)
self.assertTrue(msg in stream, "'%s' does not match actual output text '%s'" % (msg, stream))
def assertNotInOutput(self, stream, msg):
"Utility assertion: assert that the given message doesn't exist in the output"
+ stream = force_text(stream)
self.assertFalse(msg in stream, "'%s' matches actual output text '%s'" % (msg, stream))
##########################################################################
@@ -1410,9 +1417,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
"Make sure the startproject management command creates a project"
args = ['startproject', 'testproject']
testproject_dir = os.path.join(test_dir, 'testproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
@@ -1423,16 +1430,11 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
def test_invalid_project_name(self):
"Make sure the startproject management command validates a project name"
-
- def cleanup(p):
- if os.path.exists(p):
- shutil.rmtree(p)
-
args = ['startproject', '7testproject']
testproject_dir = os.path.join(test_dir, '7testproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(cleanup, testproject_dir)
self.assertOutput(err, "Error: '7testproject' is not a valid project name. Please make sure the name begins with a letter or underscore.")
self.assertFalse(os.path.exists(testproject_dir))
@@ -1441,9 +1443,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
args = ['startproject', 'testproject', 'othertestproject']
testproject_dir = os.path.join(test_dir, 'othertestproject')
os.mkdir(testproject_dir)
+ self.addCleanup(shutil.rmtree, testproject_dir)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'manage.py')))
@@ -1457,9 +1459,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
args = ['startproject', '--template', template_path, 'customtestproject']
testproject_dir = os.path.join(test_dir, 'customtestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'additional_dir')))
@@ -1469,9 +1471,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template' + os.sep)
args = ['startproject', '--template', template_path, 'customtestproject']
testproject_dir = os.path.join(test_dir, 'customtestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'additional_dir')))
@@ -1481,9 +1483,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template.tgz')
args = ['startproject', '--template', template_path, 'tarballtestproject']
testproject_dir = os.path.join(test_dir, 'tarballtestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py')))
@@ -1494,9 +1496,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
args = ['startproject', '--template', template_path, 'tarballtestproject', 'altlocation']
testproject_dir = os.path.join(test_dir, 'altlocation')
os.mkdir(testproject_dir)
+ self.addCleanup(shutil.rmtree, testproject_dir)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py')))
@@ -1507,9 +1509,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
args = ['startproject', '--template', template_url, 'urltestproject']
testproject_dir = os.path.join(test_dir, 'urltestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py')))
@@ -1520,9 +1522,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
args = ['startproject', '--template', template_url, 'urltestproject']
testproject_dir = os.path.join(test_dir, 'urltestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'run.py')))
@@ -1532,9 +1534,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
args = ['startproject', '--template', template_path, 'customtestproject', '-e', 'txt', '-n', 'Procfile']
testproject_dir = os.path.join(test_dir, 'customtestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
self.assertTrue(os.path.exists(os.path.join(testproject_dir, 'additional_dir')))
@@ -1551,12 +1553,12 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
args = ['startproject', '--template', template_path, 'another_project', 'project_dir']
testproject_dir = os.path.join(test_dir, 'project_dir')
os.mkdir(testproject_dir)
- out, err = self.run_django_admin(args)
self.addCleanup(shutil.rmtree, testproject_dir)
+ out, err = self.run_django_admin(args)
self.assertNoOutput(err)
test_manage_py = os.path.join(testproject_dir, 'manage.py')
with open(test_manage_py, 'r') as fp:
- content = fp.read()
+ content = force_text(fp.read())
self.assertIn("project_name = 'another_project'", content)
self.assertIn("project_directory = '%s'" % testproject_dir, content)
@@ -1564,19 +1566,18 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
"Make sure template context variables are not html escaped"
# We're using a custom command so we need the alternate settings
self.write_settings('alternate_settings.py')
+ self.addCleanup(self.remove_settings, 'alternate_settings.py')
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
args = ['custom_startproject', '--template', template_path, 'another_project', 'project_dir', '--extra', '<&>', '--settings=alternate_settings']
testproject_dir = os.path.join(test_dir, 'project_dir')
os.mkdir(testproject_dir)
- out, err = self.run_manage(args)
self.addCleanup(shutil.rmtree, testproject_dir)
+ out, err = self.run_manage(args)
self.assertNoOutput(err)
test_manage_py = os.path.join(testproject_dir, 'additional_dir', 'extra.py')
with open(test_manage_py, 'r') as fp:
content = fp.read()
self.assertIn("<&>", content)
- # tidy up alternate settings
- self.remove_settings('alternate_settings.py')
def test_custom_project_destination_missing(self):
"""
@@ -1596,9 +1597,9 @@ class StartProject(LiveServerTestCase, AdminScriptTestCase):
template_path = os.path.join(test_dir, 'admin_scripts', 'custom_templates', 'project_template')
args = ['startproject', '--template', template_path, '--extension=txt', 'customtestproject']
testproject_dir = os.path.join(test_dir, 'customtestproject')
+ self.addCleanup(shutil.rmtree, testproject_dir, True)
out, err = self.run_django_admin(args)
- self.addCleanup(shutil.rmtree, testproject_dir)
self.assertNoOutput(err)
self.assertTrue(os.path.isdir(testproject_dir))
path = os.path.join(testproject_dir, 'ticket-18091-non-ascii-template.txt')
@@ -1612,8 +1613,8 @@ class DiffSettings(AdminScriptTestCase):
def test_basic(self):
"Runs without error and emits settings diff."
self.write_settings('settings_to_diff.py', sdict={'FOO': '"bar"'})
+ self.addCleanup(self.remove_settings, 'settings_to_diff.py')
args = ['diffsettings', '--settings=settings_to_diff']
out, err = self.run_manage(args)
- self.remove_settings('settings_to_diff.py')
self.assertNoOutput(err)
self.assertOutput(out, "FOO = 'bar' ###")
diff --git a/tests/regressiontests/admin_scripts/urls.py b/tests/regressiontests/admin_scripts/urls.py
index 692638ceca..a45dc3e9a6 100644
--- a/tests/regressiontests/admin_scripts/urls.py
+++ b/tests/regressiontests/admin_scripts/urls.py
@@ -1,7 +1,8 @@
import os
from django.conf.urls import patterns
+from django.utils._os import upath
-here = os.path.dirname(__file__)
+here = os.path.dirname(upath(__file__))
urlpatterns = patterns('',
(r'^custom_templates/(?P<path>.*)$', 'django.views.static.serve', {
diff --git a/tests/regressiontests/admin_util/tests.py b/tests/regressiontests/admin_util/tests.py
index ef8a91d1db..e9e122a9f0 100644
--- a/tests/regressiontests/admin_util/tests.py
+++ b/tests/regressiontests/admin_util/tests.py
@@ -274,6 +274,10 @@ class UtilTests(unittest.TestCase):
six.text_type(log_entry).startswith('Deleted ')
)
+ # Make sure custom action_flags works
+ log_entry.action_flag = 4
+ self.assertEqual(six.text_type(log_entry), 'LogEntry Object')
+
def test_safestring_in_field_label(self):
# safestring should not be escaped
class MyForm(forms.Form):
diff --git a/tests/regressiontests/admin_views/admin.py b/tests/regressiontests/admin_views/admin.py
index a5476e9eb7..1e4e9b5c75 100644
--- a/tests/regressiontests/admin_views/admin.py
+++ b/tests/regressiontests/admin_views/admin.py
@@ -27,7 +27,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
- RelatedPrepopulated, UndeletableObject, Simple)
+ RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice)
def callable_year(dt_value):
@@ -346,7 +346,10 @@ class LinkInline(admin.TabularInline):
model = Link
extra = 1
- readonly_fields = ("posted",)
+ readonly_fields = ("posted", "multiline")
+
+ def multiline(self, instance):
+ return "InlineMultiline\ntest\nstring"
class SubPostInline(admin.TabularInline):
@@ -388,7 +391,10 @@ class PrePopulatedPostAdmin(admin.ModelAdmin):
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'public']
- readonly_fields = ('posted', 'awesomeness_level', 'coolness', 'value', lambda obj: "foo")
+ readonly_fields = (
+ 'posted', 'awesomeness_level', 'coolness', 'value', 'multiline',
+ lambda obj: "foo"
+ )
inlines = [
LinkInline
@@ -402,6 +408,10 @@ class PostAdmin(admin.ModelAdmin):
def value(self, instance):
return 1000
+
+ def multiline(self, instance):
+ return "Multiline\ntest\nstring"
+
value.short_description = 'Value in $US'
@@ -585,6 +595,34 @@ def callable_on_unknown(obj):
class AttributeErrorRaisingAdmin(admin.ModelAdmin):
list_display = [callable_on_unknown, ]
+class MessageTestingAdmin(admin.ModelAdmin):
+ actions = ["message_debug", "message_info", "message_success",
+ "message_warning", "message_error", "message_extra_tags"]
+
+ def message_debug(self, request, selected):
+ self.message_user(request, "Test debug", level="debug")
+
+ def message_info(self, request, selected):
+ self.message_user(request, "Test info", level="info")
+
+ def message_success(self, request, selected):
+ self.message_user(request, "Test success", level="success")
+
+ def message_warning(self, request, selected):
+ self.message_user(request, "Test warning", level="warning")
+
+ def message_error(self, request, selected):
+ self.message_user(request, "Test error", level="error")
+
+ def message_extra_tags(self, request, selected):
+ self.message_user(request, "Test tags", extra_tags="extra_tag")
+
+
+class ChoiceList(admin.ModelAdmin):
+ list_display = ['choice']
+ readonly_fields = ['choice']
+ fields = ['choice']
+
site = admin.AdminSite(name="admin")
site.register(Article, ArticleAdmin)
@@ -660,6 +698,8 @@ site.register(AdminOrderedAdminMethod, AdminOrderedAdminMethodAdmin)
site.register(AdminOrderedCallable, AdminOrderedCallableAdmin)
site.register(Color2, CustomTemplateFilterColorAdmin)
site.register(Simple, AttributeErrorRaisingAdmin)
+site.register(UserMessenger, MessageTestingAdmin)
+site.register(Choice, ChoiceList)
# Register core models we need in our tests
from django.contrib.auth.models import User, Group
diff --git a/tests/regressiontests/admin_views/customadmin.py b/tests/regressiontests/admin_views/customadmin.py
index 031fb50f0f..d69d690af0 100644
--- a/tests/regressiontests/admin_views/customadmin.py
+++ b/tests/regressiontests/admin_views/customadmin.py
@@ -16,7 +16,7 @@ class Admin2(admin.AdminSite):
login_form = forms.CustomAdminAuthenticationForm
login_template = 'custom_admin/login.html'
logout_template = 'custom_admin/logout.html'
- index_template = 'custom_admin/index.html'
+ index_template = ['custom_admin/index.html'] # a list, to test fix for #18697
password_change_template = 'custom_admin/password_change_form.html'
password_change_done_template = 'custom_admin/password_change_done.html'
@@ -40,6 +40,10 @@ class UserLimitedAdmin(UserAdmin):
return qs.filter(is_superuser=False)
+class CustomPwdTemplateUserAdmin(UserAdmin):
+ change_user_password_template = ['admin/auth/user/change_password.html'] # a list, to test fix for #18697
+
+
site = Admin2(name="admin2")
site.register(models.Article, base_admin.ArticleAdmin)
@@ -50,3 +54,6 @@ site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
site.register(User, UserLimitedAdmin)
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)
+
+simple_site = Admin2(name='admin4')
+simple_site.register(User, CustomPwdTemplateUserAdmin)
diff --git a/tests/regressiontests/admin_views/models.py b/tests/regressiontests/admin_views/models.py
index 2b143004d9..c641cfc0e6 100644
--- a/tests/regressiontests/admin_views/models.py
+++ b/tests/regressiontests/admin_views/models.py
@@ -651,8 +651,16 @@ class UndeletableObject(models.Model):
"""
name = models.CharField(max_length=255)
+class UserMessenger(models.Model):
+ """
+ Dummy class for testing message_user functions on ModelAdmin
+ """
class Simple(models.Model):
"""
Simple model with nothing on it for use in testing
"""
+
+class Choice(models.Model):
+ choice = models.IntegerField(blank=True, null=True,
+ choices=((1, 'Yes'), (0, 'No'), (None, 'No opinion')))
diff --git a/tests/regressiontests/admin_views/tests.py b/tests/regressiontests/admin_views/tests.py
index 72dc6a3f97..669695ea9b 100644
--- a/tests/regressiontests/admin_views/tests.py
+++ b/tests/regressiontests/admin_views/tests.py
@@ -33,6 +33,7 @@ from django.utils.cache import get_max_age
from django.utils.encoding import iri_to_uri, force_bytes
from django.utils.html import escape
from django.utils.http import urlencode
+from django.utils._os import upath
from django.utils import six
from django.test.utils import override_settings
@@ -46,11 +47,11 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
- Simple, UndeletableObject)
+ Simple, UndeletableObject, Choice)
ERROR_MESSAGE = "Please enter the correct username and password \
-for a staff account. Note that both fields are case-sensitive."
+for a staff account. Note that both fields may be case-sensitive."
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -633,7 +634,7 @@ class AdminViewFormUrlTest(TestCase):
Refs #17515.
"""
template_dirs = settings.TEMPLATE_DIRS + (
- os.path.join(os.path.dirname(__file__), 'templates'),)
+ os.path.join(os.path.dirname(upath(__file__)), 'templates'),)
with self.settings(TEMPLATE_DIRS=template_dirs):
response = self.client.get("/test_admin/admin/admin_views/color2/")
self.assertTrue('custom_filter_template.html' in [t.name for t in response.templates])
@@ -770,7 +771,10 @@ class CustomModelAdminTest(AdminViewBasicTest):
self.assertContains(response, 'Hello from a custom logout template')
def testCustomAdminSiteIndexViewAndTemplate(self):
- response = self.client.get('/test_admin/admin2/')
+ try:
+ response = self.client.get('/test_admin/admin2/')
+ except TypeError:
+ self.fail('AdminSite.index_template should accept a list of template paths')
self.assertIsInstance(response, TemplateResponse)
self.assertTemplateUsed(response, 'custom_admin/index.html')
self.assertContains(response, 'Hello from a custom index template *bar*')
@@ -792,6 +796,15 @@ class CustomModelAdminTest(AdminViewBasicTest):
response = self.client.get('/test_admin/%s/my_view/' % self.urlbit)
self.assertEqual(response.content, b"Django is a magical pony!")
+ def test_pwd_change_custom_template(self):
+ self.client.login(username='super', password='secret')
+ su = User.objects.get(username='super')
+ try:
+ response = self.client.get('/test_admin/admin4/auth/user/%s/password/' % su.pk)
+ except TypeError:
+ self.fail('ModelAdmin.change_user_password_template should accept a list of template paths')
+ self.assertEqual(response.status_code, 200)
+
def get_perm(Model, perm):
"""Return the permission object, for the Model"""
@@ -3149,6 +3162,11 @@ class ReadonlyTest(TestCase):
self.assertContains(response, "Very awesome.")
self.assertContains(response, "Unkown coolness.")
self.assertContains(response, "foo")
+
+ # Checks that multiline text in a readonly field gets <br /> tags
+ self.assertContains(response, "Multiline<br />test<br />string")
+ self.assertContains(response, "InlineMultiline<br />test<br />string")
+
self.assertContains(response,
formats.localize(datetime.date.today() - datetime.timedelta(days=7))
)
@@ -3198,6 +3216,15 @@ class ReadonlyTest(TestCase):
response = self.client.get('/test_admin/admin2/auth/user/%s/password/' % su.pk)
self.assertEqual(response.status_code, 404)
+ def test_change_form_renders_correct_null_choice_value(self):
+ """
+ Regression test for #17911.
+ """
+ choice = Choice.objects.create(choice=None)
+ response = self.client.get('/test_admin/admin/admin_views/choice/%s/' % choice.pk)
+ self.assertContains(response, '<p>No opinion</p>', html=True)
+ self.assertNotContains(response, '<p>(None)</p>')
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class RawIdFieldsTest(TestCase):
@@ -3378,7 +3405,11 @@ class CSSTest(TestCase):
def tearDown(self):
self.client.logout()
- def test_css_classes(self):
+ def test_field_prefix_css_classes(self):
+ """
+ Ensure that fields have a CSS class name with a 'field-' prefix.
+ Refs #16371.
+ """
response = self.client.get('/test_admin/admin/admin_views/post/add/')
# The main form
@@ -3394,6 +3425,23 @@ class CSSTest(TestCase):
self.assertContains(response, '<td class="field-url">')
self.assertContains(response, '<td class="field-posted">')
+ def test_index_css_classes(self):
+ """
+ Ensure that CSS class names are used for each app and model on the
+ admin index pages.
+ Refs #17050.
+ """
+ # General index page
+ response = self.client.get("/test_admin/admin/")
+ self.assertContains(response, '<div class="app-admin_views module">')
+ self.assertContains(response, '<tr class="model-actor">')
+ self.assertContains(response, '<tr class="model-album">')
+
+ # App index page
+ response = self.client.get("/test_admin/admin/admin_views/")
+ self.assertContains(response, '<div class="app-admin_views module">')
+ self.assertContains(response, '<tr class="model-actor">')
+ self.assertContains(response, '<tr class="model-album">')
try:
import docutils
@@ -3693,3 +3741,61 @@ class AdminViewLogoutTest(TestCase):
self.assertEqual(response.template_name, 'admin/login.html')
self.assertEqual(response.request['PATH_INFO'], '/test_admin/admin/')
self.assertContains(response, '<input type="hidden" name="next" value="/test_admin/admin/" />')
+
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class AdminUserMessageTest(TestCase):
+ urls = "regressiontests.admin_views.urls"
+ fixtures = ['admin-views-users.xml']
+
+ def setUp(self):
+ self.client.login(username='super', password='secret')
+
+ def tearDown(self):
+ self.client.logout()
+
+ def send_message(self, level):
+ """
+ Helper that sends a post to the dummy test methods and asserts that a
+ message with the level has appeared in the response.
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'message_%s' % level,
+ 'index': 0,
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
+ action_data, follow=True)
+ self.assertContains(response,
+ '<li class="%s">Test %s</li>' % (level, level),
+ html=True)
+
+ @override_settings(MESSAGE_LEVEL=10) # Set to DEBUG for this request
+ def test_message_debug(self):
+ self.send_message('debug')
+
+ def test_message_info(self):
+ self.send_message('info')
+
+ def test_message_success(self):
+ self.send_message('success')
+
+ def test_message_warning(self):
+ self.send_message('warning')
+
+ def test_message_error(self):
+ self.send_message('error')
+
+ def test_message_extra_tags(self):
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'message_extra_tags',
+ 'index': 0,
+ }
+
+ response = self.client.post('/test_admin/admin/admin_views/usermessenger/',
+ action_data, follow=True)
+ self.assertContains(response,
+ '<li class="extra_tag info">Test tags</li>',
+ html=True)
diff --git a/tests/regressiontests/admin_views/urls.py b/tests/regressiontests/admin_views/urls.py
index da6e2cbcf9..441834cd2a 100644
--- a/tests/regressiontests/admin_views/urls.py
+++ b/tests/regressiontests/admin_views/urls.py
@@ -11,4 +11,5 @@ urlpatterns = patterns('',
(r'^test_admin/admin/', include(admin.site.urls)),
(r'^test_admin/admin2/', include(customadmin.site.urls)),
(r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')),
+ (r'^test_admin/admin4/', include(customadmin.simple_site.urls)),
)
diff --git a/tests/regressiontests/admin_widgets/models.py b/tests/regressiontests/admin_widgets/models.py
index 2e452798b7..2977b86f3e 100644
--- a/tests/regressiontests/admin_widgets/models.py
+++ b/tests/regressiontests/admin_widgets/models.py
@@ -20,6 +20,7 @@ class Member(models.Model):
@python_2_unicode_compatible
class Band(models.Model):
name = models.CharField(max_length=100)
+ style = models.CharField(max_length=20)
members = models.ManyToManyField(Member)
def __str__(self):
diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py
index 4b115431c1..76aa9f448f 100644
--- a/tests/regressiontests/admin_widgets/tests.py
+++ b/tests/regressiontests/admin_widgets/tests.py
@@ -10,7 +10,7 @@ from django.contrib.admin import widgets
from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.core.files.storage import default_storage
from django.core.files.uploadedfile import SimpleUploadedFile
-from django.db.models import DateField
+from django.db.models import CharField, DateField
from django.test import TestCase as DjangoTestCase
from django.test.utils import override_settings
from django.utils import translation
@@ -112,6 +112,23 @@ class AdminFormfieldForDBFieldTests(TestCase):
self.assertFormfield(models.Event, 'start_date', forms.TextInput,
formfield_overrides={DateField: {'widget': forms.TextInput}})
+ def testFormfieldOverridesWidgetInstances(self):
+ """
+ Test that widget instances in formfield_overrides are not shared between
+ different fields. (#19423)
+ """
+ class BandAdmin(admin.ModelAdmin):
+ formfield_overrides = {
+ CharField: {'widget': forms.TextInput(attrs={'size':'10'})}
+ }
+ ma = BandAdmin(models.Band, admin.site)
+ f1 = ma.formfield_for_dbfield(models.Band._meta.get_field('name'), request=None)
+ f2 = ma.formfield_for_dbfield(models.Band._meta.get_field('style'), request=None)
+ self.assertNotEqual(f1.widget, f2.widget)
+ self.assertEqual(f1.widget.attrs['maxlength'], '100')
+ self.assertEqual(f2.widget.attrs['maxlength'], '20')
+ self.assertEqual(f2.widget.attrs['size'], '10')
+
def testFieldWithChoices(self):
self.assertFormfield(models.Member, 'gender', forms.Select)
@@ -266,6 +283,37 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase):
)
+class AdminURLWidgetTest(DjangoTestCase):
+ def test_render(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', '')),
+ '<input class="vURLField" name="test" type="text" />'
+ )
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example.com')),
+ '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>'
+ )
+
+ def test_render_idn(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example-äüö.com')),
+ '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>'
+ )
+
+ def test_render_quoting(self):
+ w = widgets.AdminURLFieldWidget()
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')),
+ '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>'
+ )
+ self.assertHTMLEqual(
+ conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')),
+ '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>'
+ )
+
+
class AdminFileWidgetTest(DjangoTestCase):
def test_render(self):
band = models.Band.objects.create(name='Linkin Park')
diff --git a/tests/regressiontests/aggregation_regress/tests.py b/tests/regressiontests/aggregation_regress/tests.py
index af0f421502..596ebbfaec 100644
--- a/tests/regressiontests/aggregation_regress/tests.py
+++ b/tests/regressiontests/aggregation_regress/tests.py
@@ -470,7 +470,7 @@ class AggregationTests(TestCase):
# Regression for #15709 - Ensure each group_by field only exists once
# per query
qs = Book.objects.values('publisher').annotate(max_pages=Max('pages')).order_by()
- grouping, gb_params = qs.query.get_compiler(qs.db).get_grouping()
+ grouping, gb_params = qs.query.get_compiler(qs.db).get_grouping([])
self.assertEqual(len(grouping), 1)
def test_duplicate_alias(self):
@@ -889,3 +889,96 @@ class AggregationTests(TestCase):
self.assertIs(qs.query.alias_map['aggregation_regress_book'].join_type, None)
# Check that the query executes without problems.
self.assertEqual(len(qs.exclude(publisher=-1)), 6)
+
+ @skipUnlessDBFeature("allows_group_by_pk")
+ def test_aggregate_duplicate_columns(self):
+ # Regression test for #17144
+
+ results = Author.objects.annotate(num_contacts=Count('book_contact_set'))
+
+ # There should only be one GROUP BY clause, for the `id` column.
+ # `name` and `age` should not be grouped on.
+ grouping, gb_params = results.query.get_compiler(using='default').get_grouping([])
+ self.assertEqual(len(grouping), 1)
+ assert 'id' in grouping[0]
+ assert 'name' not in grouping[0]
+ assert 'age' not in grouping[0]
+
+ # The query group_by property should also only show the `id`.
+ self.assertEqual(results.query.group_by, [('aggregation_regress_author', 'id')])
+
+ # Ensure that we get correct results.
+ self.assertEqual(
+ [(a.name, a.num_contacts) for a in results.order_by('name')],
+ [
+ ('Adrian Holovaty', 1),
+ ('Brad Dayley', 1),
+ ('Jacob Kaplan-Moss', 0),
+ ('James Bennett', 1),
+ ('Jeffrey Forcier', 1),
+ ('Paul Bissex', 0),
+ ('Peter Norvig', 2),
+ ('Stuart Russell', 0),
+ ('Wesley J. Chun', 0),
+ ]
+ )
+
+ @skipUnlessDBFeature("allows_group_by_pk")
+ def test_aggregate_duplicate_columns_only(self):
+ # Works with only() too.
+ results = Author.objects.only('id', 'name').annotate(num_contacts=Count('book_contact_set'))
+ grouping, gb_params = results.query.get_compiler(using='default').get_grouping([])
+ self.assertEqual(len(grouping), 1)
+ assert 'id' in grouping[0]
+ assert 'name' not in grouping[0]
+ assert 'age' not in grouping[0]
+
+ # The query group_by property should also only show the `id`.
+ self.assertEqual(results.query.group_by, [('aggregation_regress_author', 'id')])
+
+ # Ensure that we get correct results.
+ self.assertEqual(
+ [(a.name, a.num_contacts) for a in results.order_by('name')],
+ [
+ ('Adrian Holovaty', 1),
+ ('Brad Dayley', 1),
+ ('Jacob Kaplan-Moss', 0),
+ ('James Bennett', 1),
+ ('Jeffrey Forcier', 1),
+ ('Paul Bissex', 0),
+ ('Peter Norvig', 2),
+ ('Stuart Russell', 0),
+ ('Wesley J. Chun', 0),
+ ]
+ )
+
+ @skipUnlessDBFeature("allows_group_by_pk")
+ def test_aggregate_duplicate_columns_select_related(self):
+ # And select_related()
+ results = Book.objects.select_related('contact').annotate(
+ num_authors=Count('authors'))
+ grouping, gb_params = results.query.get_compiler(using='default').get_grouping([])
+ self.assertEqual(len(grouping), 1)
+ assert 'id' in grouping[0]
+ assert 'name' not in grouping[0]
+ assert 'contact' not in grouping[0]
+
+ # The query group_by property should also only show the `id`.
+ self.assertEqual(results.query.group_by, [('aggregation_regress_book', 'id')])
+
+ # Ensure that we get correct results.
+ self.assertEqual(
+ [(b.name, b.num_authors) for b in results.order_by('name')],
+ [
+ ('Artificial Intelligence: A Modern Approach', 2),
+ ('Paradigms of Artificial Intelligence Programming: Case Studies in Common Lisp', 1),
+ ('Practical Django Projects', 1),
+ ('Python Web Development with Django', 3),
+ ('Sams Teach Yourself Django in 24 Hours', 1),
+ ('The Definitive Guide to Django: Web Development Done Right', 2)
+ ]
+ )
+
+ def test_reverse_join_trimming(self):
+ qs = Author.objects.annotate(Count('book_contact_set__contact'))
+ self.assertIn(' JOIN ', str(qs.query))
diff --git a/tests/regressiontests/app_loading/tests.py b/tests/regressiontests/app_loading/tests.py
index 0e66a5aad3..6dd0be2194 100644
--- a/tests/regressiontests/app_loading/tests.py
+++ b/tests/regressiontests/app_loading/tests.py
@@ -7,13 +7,14 @@ import time
from django.conf import Settings
from django.db.models.loading import cache, load_app, get_model, get_models
+from django.utils._os import upath
from django.utils.unittest import TestCase
class EggLoadingTest(TestCase):
def setUp(self):
self.old_path = sys.path[:]
- self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+ self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__))
# This test adds dummy applications to the app cache. These
# need to be removed in order to prevent bad interactions
diff --git a/tests/regressiontests/backends/tests.py b/tests/regressiontests/backends/tests.py
index d284cfaac8..934c37d147 100644
--- a/tests/regressiontests/backends/tests.py
+++ b/tests/regressiontests/backends/tests.py
@@ -23,9 +23,28 @@ from django.utils import unittest
from . import models
+class DummyBackendTest(TestCase):
+ def test_no_databases(self):
+ """
+ Test that empty DATABASES setting default to the dummy backend.
+ """
+ DATABASES = {}
+ conns = ConnectionHandler(DATABASES)
+ self.assertEqual(conns[DEFAULT_DB_ALIAS].settings_dict['ENGINE'],
+ 'django.db.backends.dummy')
+
+
class OracleChecks(unittest.TestCase):
@unittest.skipUnless(connection.vendor == 'oracle',
+ "No need to check Oracle quote_name semantics")
+ def test_quote_name(self):
+ # Check that '%' chars are escaped for query execution.
+ name = '"SOME%NAME"'
+ quoted_name = connection.ops.quote_name(name)
+ self.assertEquals(quoted_name % (), name)
+
+ @unittest.skipUnless(connection.vendor == 'oracle',
"No need to check Oracle cursor semantics")
def test_dbms_session(self):
# If the backend is Oracle, test that we can call a standard
@@ -540,21 +559,30 @@ class ThreadTests(TestCase):
"""
connections_set = set()
connection.cursor()
- connections_set.add(connection.connection)
+ connections_set.add(connection)
def runner():
- from django.db import connection
+ # Passing django.db.connection between threads doesn't work while
+ # connections[DEFAULT_DB_ALIAS] does.
+ from django.db import connections
+ connection = connections[DEFAULT_DB_ALIAS]
+ # Allow thread sharing so the connection can be closed by the
+ # main thread.
+ connection.allow_thread_sharing = True
connection.cursor()
- connections_set.add(connection.connection)
+ connections_set.add(connection)
for x in range(2):
t = threading.Thread(target=runner)
t.start()
t.join()
- self.assertEqual(len(connections_set), 3)
+ # Check that each created connection got different inner connection.
+ self.assertEqual(
+ len(set([conn.connection for conn in connections_set])),
+ 3)
# Finish by closing the connections opened by the other threads (the
# connection opened in the main thread will automatically be closed on
# teardown).
for conn in connections_set:
- if conn != connection.connection:
+ if conn is not connection:
conn.close()
def test_connections_thread_local(self):
@@ -581,7 +609,7 @@ class ThreadTests(TestCase):
# connection opened in the main thread will automatically be closed on
# teardown).
for conn in connections_set:
- if conn != connection:
+ if conn is not connection:
conn.close()
def test_pass_connection_between_threads(self):
diff --git a/tests/regressiontests/bug639/tests.py b/tests/regressiontests/bug639/tests.py
index b7547696d4..fcc1e0f7d1 100644
--- a/tests/regressiontests/bug639/tests.py
+++ b/tests/regressiontests/bug639/tests.py
@@ -11,6 +11,7 @@ import shutil
from django.core.files.uploadedfile import SimpleUploadedFile
from django.utils import unittest
+from django.utils._os import upath
from .models import Photo, PhotoForm, temp_storage_dir
@@ -23,7 +24,7 @@ class Bug639Test(unittest.TestCase):
called.
"""
# Grab an image for testing.
- filename = os.path.join(os.path.dirname(__file__), "test.jpg")
+ filename = os.path.join(os.path.dirname(upath(__file__)), "test.jpg")
with open(filename, "rb") as fp:
img = fp.read()
diff --git a/tests/regressiontests/bulk_create/tests.py b/tests/regressiontests/bulk_create/tests.py
index 5d61242b9b..d4772934a1 100644
--- a/tests/regressiontests/bulk_create/tests.py
+++ b/tests/regressiontests/bulk_create/tests.py
@@ -82,6 +82,14 @@ class BulkCreateTests(TestCase):
with self.assertRaises(ValueError):
Country.objects.bulk_create([valid_country, invalid_country])
+ def test_batch_same_vals(self):
+ # Sqlite had a problem where all the same-valued models were
+ # collapsed to one insert.
+ Restaurant.objects.bulk_create([
+ Restaurant(name='foo') for i in range(0, 2)
+ ])
+ self.assertEqual(Restaurant.objects.count(), 2)
+
def test_large_batch(self):
with override_settings(DEBUG=True):
connection.queries = []
@@ -95,6 +103,14 @@ class BulkCreateTests(TestCase):
self.assertEqual(TwoFields.objects.filter(f2__gte=901).count(), 101)
@skipUnlessDBFeature('has_bulk_insert')
+ def test_large_single_field_batch(self):
+ # SQLite had a problem with more than 500 UNIONed selects in single
+ # query.
+ Restaurant.objects.bulk_create([
+ Restaurant() for i in range(0, 501)
+ ])
+
+ @skipUnlessDBFeature('has_bulk_insert')
def test_large_batch_efficiency(self):
with override_settings(DEBUG=True):
connection.queries = []
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index a6eff8950b..fc92ecf870 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -257,6 +257,7 @@ class BaseCacheTests(object):
self.assertEqual(self.cache.get('answer'), 42)
self.assertEqual(self.cache.incr('answer', 10), 52)
self.assertEqual(self.cache.get('answer'), 52)
+ self.assertEqual(self.cache.incr('answer', -10), 42)
self.assertRaises(ValueError, self.cache.incr, 'does_not_exist')
def test_decr(self):
@@ -266,8 +267,13 @@ class BaseCacheTests(object):
self.assertEqual(self.cache.get('answer'), 42)
self.assertEqual(self.cache.decr('answer', 10), 32)
self.assertEqual(self.cache.get('answer'), 32)
+ self.assertEqual(self.cache.decr('answer', -10), 42)
self.assertRaises(ValueError, self.cache.decr, 'does_not_exist')
+ def test_close(self):
+ self.assertTrue(hasattr(self.cache, 'close'))
+ self.cache.close()
+
def test_data_types(self):
# Many different data types can be cached
stuff = {
diff --git a/tests/regressiontests/comment_tests/tests/app_api_tests.py b/tests/regressiontests/comment_tests/tests/app_api_tests.py
index 8a63e8c188..a756068790 100644
--- a/tests/regressiontests/comment_tests/tests/app_api_tests.py
+++ b/tests/regressiontests/comment_tests/tests/app_api_tests.py
@@ -4,6 +4,9 @@ from django.conf import settings
from django.contrib import comments
from django.contrib.comments.models import Comment
from django.contrib.comments.forms import CommentForm
+from django.core.exceptions import ImproperlyConfigured
+from django.test.utils import override_settings
+from django.utils import six
from . import CommentTestCase
@@ -14,6 +17,14 @@ class CommentAppAPITests(CommentTestCase):
def testGetCommentApp(self):
self.assertEqual(comments.get_comment_app(), comments)
+ @override_settings(
+ COMMENTS_APP='missing_app',
+ INSTALLED_APPS=list(settings.INSTALLED_APPS) + ['missing_app'],
+ )
+ def testGetMissingCommentApp(self):
+ with six.assertRaisesRegex(self, ImproperlyConfigured, 'missing_app'):
+ _ = comments.get_comment_app()
+
def testGetForm(self):
self.assertEqual(comments.get_form(), CommentForm)
@@ -33,20 +44,14 @@ class CommentAppAPITests(CommentTestCase):
self.assertEqual(comments.get_approve_url(c), "/approve/12345/")
+@override_settings(
+ COMMENTS_APP='regressiontests.comment_tests.custom_comments',
+ INSTALLED_APPS=list(settings.INSTALLED_APPS) + [
+ 'regressiontests.comment_tests.custom_comments'],
+)
class CustomCommentTest(CommentTestCase):
urls = 'regressiontests.comment_tests.urls'
- def setUp(self):
- self.old_comments_app = getattr(settings, 'COMMENTS_APP', None)
- settings.COMMENTS_APP = 'regressiontests.comment_tests.custom_comments'
- settings.INSTALLED_APPS = list(settings.INSTALLED_APPS) + [settings.COMMENTS_APP,]
-
- def tearDown(self):
- del settings.INSTALLED_APPS[-1]
- settings.COMMENTS_APP = self.old_comments_app
- if settings.COMMENTS_APP is None:
- del settings._wrapped.COMMENTS_APP
-
def testGetCommentApp(self):
from regressiontests.comment_tests import custom_comments
self.assertEqual(comments.get_comment_app(), custom_comments)
diff --git a/tests/regressiontests/comment_tests/tests/comment_view_tests.py b/tests/regressiontests/comment_tests/tests/comment_view_tests.py
index 429f3b2bf2..5c1954026d 100644
--- a/tests/regressiontests/comment_tests/tests/comment_view_tests.py
+++ b/tests/regressiontests/comment_tests/tests/comment_view_tests.py
@@ -222,6 +222,13 @@ class CommentViewTests(CommentTestCase):
match = re.search(r"^http://testserver/somewhere/else/\?c=\d+$", location)
self.assertTrue(match != None, "Unexpected redirect location: %s" % location)
+ data["next"] = "http://badserver/somewhere/else/"
+ data["comment"] = "This is another comment with an unsafe next url"
+ response = self.client.post("/post/", data)
+ location = response["Location"]
+ match = post_redirect_re.match(location)
+ self.assertTrue(match != None, "Unsafe redirection to: %s" % location)
+
def testCommentDoneView(self):
a = Article.objects.get(pk=1)
data = self.getValidData(a)
diff --git a/tests/regressiontests/comment_tests/tests/feed_tests.py b/tests/regressiontests/comment_tests/tests/feed_tests.py
index 1ec316eab8..c93d7abf7d 100644
--- a/tests/regressiontests/comment_tests/tests/feed_tests.py
+++ b/tests/regressiontests/comment_tests/tests/feed_tests.py
@@ -1,5 +1,7 @@
from __future__ import absolute_import
+from xml.etree import ElementTree as ET
+
from django.conf import settings
from django.contrib.comments.models import Comment
from django.contrib.contenttypes.models import ContentType
@@ -31,8 +33,21 @@ class CommentFeedTests(CommentTestCase):
response = self.client.get(self.feed_url)
self.assertEqual(response.status_code, 200)
self.assertEqual(response['Content-Type'], 'application/rss+xml; charset=utf-8')
- self.assertContains(response, '<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">')
- self.assertContains(response, '<title>example.com comments</title>')
- self.assertContains(response, '<link>http://example.com/</link>')
- self.assertContains(response, '</rss>')
+
+ rss_elem = ET.fromstring(response.content)
+
+ self.assertEqual(rss_elem.tag, "rss")
+ self.assertEqual(rss_elem.attrib, {"version": "2.0"})
+
+ channel_elem = rss_elem.find("channel")
+
+ title_elem = channel_elem.find("title")
+ self.assertEqual(title_elem.text, "example.com comments")
+
+ link_elem = channel_elem.find("link")
+ self.assertEqual(link_elem.text, "http://example.com/")
+
+ atomlink_elem = channel_elem.find("{http://www.w3.org/2005/Atom}link")
+ self.assertEqual(atomlink_elem.attrib, {"href": "http://example.com/rss/comments/", "rel": "self"})
+
self.assertNotContains(response, "A comment for the second site.")
diff --git a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
index 2ada20a662..0f83d5e210 100644
--- a/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
+++ b/tests/regressiontests/comment_tests/tests/moderation_view_tests.py
@@ -4,7 +4,6 @@ from django.contrib.auth.models import User, Permission
from django.contrib.comments import signals
from django.contrib.comments.models import Comment, CommentFlag
from django.contrib.contenttypes.models import ContentType
-from django.conf import settings
from . import CommentTestCase
@@ -30,6 +29,30 @@ class FlagViewTests(CommentTestCase):
self.assertEqual(c.flags.filter(flag=CommentFlag.SUGGEST_REMOVAL).count(), 1)
return c
+ def testFlagPostNext(self):
+ """
+ POST the flag view, explicitly providing a next url.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk, {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % pk)
+
+ def testFlagPostUnsafeNext(self):
+ """
+ POSTing to the flag view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/flag/%d/" % pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/flagged/?c=%d" % pk)
+
def testFlagPostTwice(self):
"""Users don't get to flag comments more than once."""
c = self.testFlagPost()
@@ -49,7 +72,7 @@ class FlagViewTests(CommentTestCase):
def testFlaggedView(self):
comments = self.createSomeComments()
pk = comments[0].pk
- response = self.client.get("/flagged/", data={"c":pk})
+ response = self.client.get("/flagged/", data={"c": pk})
self.assertTemplateUsed(response, "comments/flagged.html")
def testFlagSignals(self):
@@ -101,6 +124,33 @@ class DeleteViewTests(CommentTestCase):
self.assertTrue(c.is_removed)
self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_DELETION, user__username="normaluser").count(), 1)
+ def testDeletePostNext(self):
+ """
+ POSTing the delete view will redirect to an explicitly provided a next
+ url.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk, {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % pk)
+
+ def testDeletePostUnsafeNext(self):
+ """
+ POSTing to the delete view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ comments = self.createSomeComments()
+ pk = comments[0].pk
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/delete/%d/" % pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/deleted/?c=%d" % pk)
+
def testDeleteSignals(self):
def receive(sender, **kwargs):
received_signals.append(kwargs.get('signal'))
@@ -116,13 +166,13 @@ class DeleteViewTests(CommentTestCase):
def testDeletedView(self):
comments = self.createSomeComments()
pk = comments[0].pk
- response = self.client.get("/deleted/", data={"c":pk})
+ response = self.client.get("/deleted/", data={"c": pk})
self.assertTemplateUsed(response, "comments/deleted.html")
class ApproveViewTests(CommentTestCase):
def testApprovePermissions(self):
- """The delete view should only be accessible to 'moderators'"""
+ """The approve view should only be accessible to 'moderators'"""
comments = self.createSomeComments()
pk = comments[0].pk
self.client.login(username="normaluser", password="normaluser")
@@ -134,7 +184,7 @@ class ApproveViewTests(CommentTestCase):
self.assertEqual(response.status_code, 200)
def testApprovePost(self):
- """POSTing the delete view should mark the comment as removed"""
+ """POSTing the approve view should mark the comment as removed"""
c1, c2, c3, c4 = self.createSomeComments()
c1.is_public = False; c1.save()
@@ -146,6 +196,36 @@ class ApproveViewTests(CommentTestCase):
self.assertTrue(c.is_public)
self.assertEqual(c.flags.filter(flag=CommentFlag.MODERATOR_APPROVAL, user__username="normaluser").count(), 1)
+ def testApprovePostNext(self):
+ """
+ POSTing the approve view will redirect to an explicitly provided a next
+ url.
+ """
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk,
+ {'next': "/go/here/"})
+ self.assertEqual(response["Location"],
+ "http://testserver/go/here/?c=%d" % c1.pk)
+
+ def testApprovePostUnsafeNext(self):
+ """
+ POSTing to the approve view with an unsafe next url will ignore the
+ provided url when redirecting.
+ """
+ c1, c2, c3, c4 = self.createSomeComments()
+ c1.is_public = False; c1.save()
+
+ makeModerator("normaluser")
+ self.client.login(username="normaluser", password="normaluser")
+ response = self.client.post("/approve/%d/" % c1.pk,
+ {'next': "http://elsewhere/bad"})
+ self.assertEqual(response["Location"],
+ "http://testserver/approved/?c=%d" % c1.pk)
+
def testApproveSignals(self):
def receive(sender, **kwargs):
received_signals.append(kwargs.get('signal'))
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index bdf3c867c6..8596f8c801 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -80,13 +80,16 @@ class DefaultFiltersTests(TestCase):
decimal_ctx.prec = old_prec
- # This fails because of Python's float handling. Floats with many zeroes
- # after the decimal point should be passed in as another type such as
- # unicode or Decimal.
- @unittest.expectedFailure
- def test_floatformat_fail(self):
+ def test_floatformat_py2_fail(self):
self.assertEqual(floatformat(1.00000000000000015, 16), '1.0000000000000002')
+ # The test above fails because of Python 2's float handling. Floats with
+ # many zeroes after the decimal point should be passed in as another type
+ # such as unicode or Decimal.
+ if not six.PY3:
+ test_floatformat_py2_fail = unittest.expectedFailure(test_floatformat_py2_fail)
+
+
def test_addslashes(self):
self.assertEqual(addslashes('"double quotes" and \'single quotes\''),
'\\"double quotes\\" and \\\'single quotes\\\'')
@@ -301,7 +304,16 @@ class DefaultFiltersTests(TestCase):
# Check urlize trims trailing period when followed by parenthesis - see #18644
self.assertEqual(urlize('(Go to http://www.example.com/foo.)'),
- '(Go to <a href="http://www.example.com/foo" rel="nofollow">http://www.example.com/foo</a>.)')
+ '(Go to <a href="http://www.example.com/foo" rel="nofollow">http://www.example.com/foo</a>.)')
+
+ # Check urlize doesn't crash when square bracket is appended to url (#19070)
+ self.assertEqual(urlize('[see www.example.com]'),
+ '[see <a href="http://www.example.com" rel="nofollow">www.example.com</a>]' )
+
+ # Check urlize doesn't crash when square bracket is prepended to url (#19070)
+ self.assertEqual(urlize('see test[at[example.com'),
+ 'see <a href="http://test[at[example.com" rel="nofollow">test[at[example.com</a>' )
+
def test_wordcount(self):
self.assertEqual(wordcount(''), 0)
diff --git a/tests/regressiontests/defer_regress/models.py b/tests/regressiontests/defer_regress/models.py
index 444649ac92..7528e3b2c9 100644
--- a/tests/regressiontests/defer_regress/models.py
+++ b/tests/regressiontests/defer_regress/models.py
@@ -55,6 +55,10 @@ class Feature(models.Model):
class SpecialFeature(models.Model):
feature = models.ForeignKey(Feature)
+class OneToOneItem(models.Model):
+ item = models.OneToOneField(Item, related_name="one_to_one_item")
+ name = models.CharField(max_length=15)
+
class ItemAndSimpleItem(models.Model):
item = models.ForeignKey(Item)
simple = models.ForeignKey(SimpleItem)
diff --git a/tests/regressiontests/defer_regress/tests.py b/tests/regressiontests/defer_regress/tests.py
index c77ca32135..d4d722035f 100644
--- a/tests/regressiontests/defer_regress/tests.py
+++ b/tests/regressiontests/defer_regress/tests.py
@@ -9,7 +9,7 @@ from django.db.models.loading import cache
from django.test import TestCase
from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy,
- SimpleItem, Feature, ItemAndSimpleItem, SpecialFeature)
+ SimpleItem, Feature, ItemAndSimpleItem, OneToOneItem, SpecialFeature)
class DeferRegressionTest(TestCase):
@@ -111,6 +111,7 @@ class DeferRegressionTest(TestCase):
Item,
ItemAndSimpleItem,
Leaf,
+ OneToOneItem,
Proxy,
RelatedItem,
ResolveThis,
@@ -147,6 +148,7 @@ class DeferRegressionTest(TestCase):
"Leaf_Deferred_name_value",
"Leaf_Deferred_second_child_id_value",
"Leaf_Deferred_value",
+ "OneToOneItem",
"Proxy",
"RelatedItem",
"RelatedItem_Deferred_",
@@ -182,6 +184,33 @@ class DeferRegressionTest(TestCase):
self.assertEqual(1, qs.count())
self.assertEqual('Foobar', qs[0].name)
+ def test_reverse_one_to_one_relations(self):
+ # Refs #14694. Test reverse relations which are known unique (reverse
+ # side has o2ofield or unique FK) - the o2o case
+ item = Item.objects.create(name="first", value=42)
+ o2o = OneToOneItem.objects.create(item=item, name="second")
+ self.assertEqual(len(Item.objects.defer('one_to_one_item__name')), 1)
+ self.assertEqual(len(Item.objects.select_related('one_to_one_item')), 1)
+ self.assertEqual(len(Item.objects.select_related(
+ 'one_to_one_item').defer('one_to_one_item__name')), 1)
+ self.assertEqual(len(Item.objects.select_related('one_to_one_item').defer('value')), 1)
+ # Make sure that `only()` doesn't break when we pass in a unique relation,
+ # rather than a field on the relation.
+ self.assertEqual(len(Item.objects.only('one_to_one_item')), 1)
+ with self.assertNumQueries(1):
+ i = Item.objects.select_related('one_to_one_item')[0]
+ self.assertEqual(i.one_to_one_item.pk, o2o.pk)
+ self.assertEqual(i.one_to_one_item.name, "second")
+ with self.assertNumQueries(1):
+ i = Item.objects.select_related('one_to_one_item').defer(
+ 'value', 'one_to_one_item__name')[0]
+ self.assertEqual(i.one_to_one_item.pk, o2o.pk)
+ self.assertEqual(i.name, "first")
+ with self.assertNumQueries(1):
+ self.assertEqual(i.one_to_one_item.name, "second")
+ with self.assertNumQueries(1):
+ self.assertEqual(i.value, 42)
+
def test_defer_with_select_related(self):
item1 = Item.objects.create(name="first", value=47)
item2 = Item.objects.create(name="second", value=42)
diff --git a/tests/regressiontests/delete_regress/tests.py b/tests/regressiontests/delete_regress/tests.py
index f94bb2f20c..c3e4a6a0b7 100644
--- a/tests/regressiontests/delete_regress/tests.py
+++ b/tests/regressiontests/delete_regress/tests.py
@@ -3,7 +3,8 @@ from __future__ import absolute_import
import datetime
from django.conf import settings
-from django.db import backend, transaction, DEFAULT_DB_ALIAS, models
+from django.db import transaction, DEFAULT_DB_ALIAS, models
+from django.db.utils import ConnectionHandler
from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature
from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
@@ -17,17 +18,8 @@ from .models import (Book, Award, AwardNote, Person, Child, Toy, PlayedWith,
class DeleteLockingTest(TransactionTestCase):
def setUp(self):
# Create a second connection to the default database
- conn_settings = settings.DATABASES[DEFAULT_DB_ALIAS]
- self.conn2 = backend.DatabaseWrapper({
- 'HOST': conn_settings['HOST'],
- 'NAME': conn_settings['NAME'],
- 'OPTIONS': conn_settings['OPTIONS'],
- 'PASSWORD': conn_settings['PASSWORD'],
- 'PORT': conn_settings['PORT'],
- 'USER': conn_settings['USER'],
- 'TIME_ZONE': settings.TIME_ZONE,
- })
-
+ new_connections = ConnectionHandler(settings.DATABASES)
+ self.conn2 = new_connections[DEFAULT_DB_ALIAS]
# Put both DB connections into managed transaction mode
transaction.enter_transaction_management()
transaction.managed(True)
diff --git a/tests/regressiontests/expressions_regress/tests.py b/tests/regressiontests/expressions_regress/tests.py
index 508a497151..ddb2c83b4f 100644
--- a/tests/regressiontests/expressions_regress/tests.py
+++ b/tests/regressiontests/expressions_regress/tests.py
@@ -26,12 +26,13 @@ class ExpressionsRegressTests(TestCase):
same object.
"""
self.assertQuerysetEqual(
- Number.objects.all(),
- [
- '<Number: -1, -1.000>',
- '<Number: 42, 42.000>',
- '<Number: 1337, 1337.000>'
- ]
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 42, 42.000>',
+ '<Number: 1337, 1337.000>'
+ ],
+ ordered=False
)
def test_increment_value(self):
@@ -44,12 +45,13 @@ class ExpressionsRegressTests(TestCase):
2)
self.assertQuerysetEqual(
- Number.objects.all(),
- [
- '<Number: -1, -1.000>',
- '<Number: 43, 42.000>',
- '<Number: 1338, 1337.000>'
- ]
+ Number.objects.all(),
+ [
+ '<Number: -1, -1.000>',
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ],
+ ordered=False
)
def test_filter_not_equals_other_field(self):
@@ -62,11 +64,12 @@ class ExpressionsRegressTests(TestCase):
.update(integer=F('integer') + 1),
2)
self.assertQuerysetEqual(
- Number.objects.exclude(float=F('integer')),
- [
- '<Number: 43, 42.000>',
- '<Number: 1338, 1337.000>'
- ]
+ Number.objects.exclude(float=F('integer')),
+ [
+ '<Number: 43, 42.000>',
+ '<Number: 1338, 1337.000>'
+ ],
+ ordered=False
)
def test_complex_expressions(self):
diff --git a/tests/regressiontests/extra_regress/tests.py b/tests/regressiontests/extra_regress/tests.py
index f591900afe..1bc6789edd 100644
--- a/tests/regressiontests/extra_regress/tests.py
+++ b/tests/regressiontests/extra_regress/tests.py
@@ -58,13 +58,15 @@ class ExtraRegressTests(TestCase):
('First Revision', 'First Revision'),
('Second Revision', 'First Revision'),
],
- transform=lambda r: (r.title, r.base.title)
+ transform=lambda r: (r.title, r.base.title),
+ ordered=False
)
# Following queryset should return the most recent revision:
self.assertQuerysetEqual(qs & qs2,
[('Second Revision', 'First Revision')],
- transform=lambda r: (r.title, r.base.title)
+ transform=lambda r: (r.title, r.base.title),
+ ordered=False
)
def test_extra_stay_tied(self):
@@ -342,5 +344,6 @@ class ExtraRegressTests(TestCase):
TestObject.objects.extra(
where=["first = 'a' OR second = 'a'", "third = 'a'"],
),
- ['<TestObject: TestObject: a,a,a>', '<TestObject: TestObject: b,a,a>']
+ ['<TestObject: TestObject: a,a,a>', '<TestObject: TestObject: b,a,a>'],
+ ordered=False
)
diff --git a/tests/regressiontests/file_storage/tests.py b/tests/regressiontests/file_storage/tests.py
index 595b65d9f1..b6d3e1ff0b 100644
--- a/tests/regressiontests/file_storage/tests.py
+++ b/tests/regressiontests/file_storage/tests.py
@@ -24,6 +24,7 @@ from django.core.files.uploadedfile import UploadedFile
from django.test import SimpleTestCase
from django.utils import six
from django.utils import unittest
+from django.utils._os import upath
from django.test.utils import override_settings
from ..servers.tests import LiveServerBase
@@ -104,7 +105,7 @@ class FileStorageTests(unittest.TestCase):
"""
storage = self.storage_class(location='')
self.assertEqual(storage.base_location, '')
- self.assertEqual(storage.location, os.getcwd())
+ self.assertEqual(storage.location, upath(os.getcwd()))
def test_file_access_options(self):
"""
@@ -534,7 +535,7 @@ class DimensionClosingBug(unittest.TestCase):
from django.core.files import images
images.open = catching_open
try:
- get_image_dimensions(os.path.join(os.path.dirname(__file__), "test1.png"))
+ get_image_dimensions(os.path.join(os.path.dirname(upath(__file__)), "test1.png"))
finally:
del images.open
self.assertTrue(FileWrapper._closed)
@@ -551,7 +552,7 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
"""
from django.core.files.images import ImageFile
- img_path = os.path.join(os.path.dirname(__file__), "test.png")
+ img_path = os.path.join(os.path.dirname(upath(__file__)), "test.png")
image = ImageFile(open(img_path, 'rb'))
image_pil = Image.open(img_path)
size_1, size_2 = get_image_dimensions(image), get_image_dimensions(image)
@@ -560,6 +561,13 @@ class InconsistentGetImageDimensionsBug(unittest.TestCase):
class ContentFileTestCase(unittest.TestCase):
+ def setUp(self):
+ self.storage_dir = tempfile.mkdtemp()
+ self.storage = FileSystemStorage(self.storage_dir)
+
+ def tearDown(self):
+ shutil.rmtree(self.storage_dir)
+
def test_content_file_default_name(self):
self.assertEqual(ContentFile(b"content").name, None)
@@ -576,7 +584,18 @@ class ContentFileTestCase(unittest.TestCase):
retrieved content is of the same type.
"""
self.assertTrue(isinstance(ContentFile(b"content").read(), bytes))
- self.assertTrue(isinstance(ContentFile("español").read(), six.text_type))
+ if six.PY3:
+ self.assertTrue(isinstance(ContentFile("español").read(), six.text_type))
+ else:
+ self.assertTrue(isinstance(ContentFile("español").read(), bytes))
+
+ def test_content_saving(self):
+ """
+ Test that ContentFile can be saved correctly with the filesystem storage,
+ both if it was initialized with string or unicode content"""
+ self.storage.save('bytes.txt', ContentFile(b"content"))
+ self.storage.save('unicode.txt', ContentFile("español"))
+
class NoNameFileTestCase(unittest.TestCase):
"""
diff --git a/tests/regressiontests/file_uploads/models.py b/tests/regressiontests/file_uploads/models.py
index 28e71c44bc..3336cc9d90 100644
--- a/tests/regressiontests/file_uploads/models.py
+++ b/tests/regressiontests/file_uploads/models.py
@@ -1,12 +1,5 @@
-import tempfile
-import os
-
-from django.core.files.storage import FileSystemStorage
from django.db import models
-temp_storage = FileSystemStorage(tempfile.mkdtemp())
-UPLOAD_TO = os.path.join(temp_storage.location, 'test_upload')
-
class FileModel(models.Model):
- testfile = models.FileField(storage=temp_storage, upload_to='test_upload')
+ testfile = models.FileField(upload_to='test_upload')
diff --git a/tests/regressiontests/file_uploads/tests.py b/tests/regressiontests/file_uploads/tests.py
index 918f77d73c..45e7342abd 100644
--- a/tests/regressiontests/file_uploads/tests.py
+++ b/tests/regressiontests/file_uploads/tests.py
@@ -7,22 +7,36 @@ import hashlib
import json
import os
import shutil
+import tempfile as sys_tempfile
from django.core.files import temp as tempfile
from django.core.files.uploadedfile import SimpleUploadedFile
from django.http.multipartparser import MultiPartParser
from django.test import TestCase, client
+from django.test.utils import override_settings
from django.utils.encoding import force_bytes
from django.utils.six import StringIO
from django.utils import unittest
from . import uploadhandler
-from .models import FileModel, temp_storage, UPLOAD_TO
+from .models import FileModel
UNICODE_FILENAME = 'test-0123456789_中文_Orléans.jpg'
+MEDIA_ROOT = sys_tempfile.mkdtemp()
+UPLOAD_TO = os.path.join(MEDIA_ROOT, 'test_upload')
+@override_settings(MEDIA_ROOT=MEDIA_ROOT)
class FileUploadTests(TestCase):
+ @classmethod
+ def setUpClass(cls):
+ if not os.path.isdir(MEDIA_ROOT):
+ os.makedirs(MEDIA_ROOT)
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(MEDIA_ROOT)
+
def test_simple_upload(self):
with open(__file__, 'rb') as fp:
post_data = {
@@ -60,15 +74,14 @@ class FileUploadTests(TestCase):
self.assertEqual(response.status_code, 200)
- def test_base64_upload(self):
- test_string = "This data will be transmitted base64-encoded."
+ def _test_base64_upload(self, content):
payload = client.FakePayload("\r\n".join([
'--' + client.BOUNDARY,
'Content-Disposition: form-data; name="file"; filename="test.txt"',
'Content-Type: application/octet-stream',
'Content-Transfer-Encoding: base64',
'',]))
- payload.write(b"\r\n" + base64.b64encode(force_bytes(test_string)) + b"\r\n")
+ payload.write(b"\r\n" + base64.b64encode(force_bytes(content)) + b"\r\n")
payload.write('--' + client.BOUNDARY + '--\r\n')
r = {
'CONTENT_LENGTH': len(payload),
@@ -80,10 +93,17 @@ class FileUploadTests(TestCase):
response = self.client.request(**r)
received = json.loads(response.content.decode('utf-8'))
- self.assertEqual(received['file'], test_string)
+ self.assertEqual(received['file'], content)
+
+ def test_base64_upload(self):
+ self._test_base64_upload("This data will be transmitted base64-encoded.")
+
+ def test_big_base64_upload(self):
+ self._test_base64_upload("Big data" * 68000) # > 512Kb
def test_unicode_file_name(self):
- tdir = tempfile.gettempdir()
+ tdir = sys_tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, tdir, True)
# This file contains chinese symbols and an accented char in the name.
with open(os.path.join(tdir, UNICODE_FILENAME), 'w+b') as file1:
@@ -96,11 +116,6 @@ class FileUploadTests(TestCase):
response = self.client.post('/file_uploads/unicode_name/', post_data)
- try:
- os.unlink(file1.name)
- except OSError:
- pass
-
self.assertEqual(response.status_code, 200)
def test_dangerous_file_names(self):
@@ -347,26 +362,28 @@ class FileUploadTests(TestCase):
# shouldn't differ.
self.assertEqual(os.path.basename(obj.testfile.path), 'MiXeD_cAsE.txt')
-class DirectoryCreationTests(unittest.TestCase):
+@override_settings(MEDIA_ROOT=MEDIA_ROOT)
+class DirectoryCreationTests(TestCase):
"""
Tests for error handling during directory creation
via _save_FIELD_file (ticket #6450)
"""
+ @classmethod
+ def setUpClass(cls):
+ if not os.path.isdir(MEDIA_ROOT):
+ os.makedirs(MEDIA_ROOT)
+
+ @classmethod
+ def tearDownClass(cls):
+ shutil.rmtree(MEDIA_ROOT)
+
def setUp(self):
self.obj = FileModel()
- if not os.path.isdir(temp_storage.location):
- os.makedirs(temp_storage.location)
- if os.path.isdir(UPLOAD_TO):
- os.chmod(UPLOAD_TO, 0o700)
- shutil.rmtree(UPLOAD_TO)
-
- def tearDown(self):
- os.chmod(temp_storage.location, 0o700)
- shutil.rmtree(temp_storage.location)
def test_readonly_root(self):
"""Permission errors are not swallowed"""
- os.chmod(temp_storage.location, 0o500)
+ os.chmod(MEDIA_ROOT, 0o500)
+ self.addCleanup(os.chmod, MEDIA_ROOT, 0o700)
try:
self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', b'x'))
except OSError as err:
@@ -378,6 +395,7 @@ class DirectoryCreationTests(unittest.TestCase):
"""The correct IOError is raised when the upload directory name exists but isn't a directory"""
# Create a file with the upload directory name
open(UPLOAD_TO, 'wb').close()
+ self.addCleanup(os.remove, UPLOAD_TO)
with self.assertRaises(IOError) as exc_info:
self.obj.testfile.save('foo.txt', SimpleUploadedFile('foo.txt', b'x'))
# The test needs to be done on a specific string as IOError
diff --git a/tests/regressiontests/file_uploads/views.py b/tests/regressiontests/file_uploads/views.py
index fcf32cecea..eb7b654c09 100644
--- a/tests/regressiontests/file_uploads/views.py
+++ b/tests/regressiontests/file_uploads/views.py
@@ -9,8 +9,8 @@ from django.http import HttpResponse, HttpResponseServerError
from django.utils import six
from django.utils.encoding import force_bytes
-from .models import FileModel, UPLOAD_TO
-from .tests import UNICODE_FILENAME
+from .models import FileModel
+from .tests import UNICODE_FILENAME, UPLOAD_TO
from .uploadhandler import QuotaUploadHandler, ErroringUploadHandler
diff --git a/tests/regressiontests/fixtures_regress/tests.py b/tests/regressiontests/fixtures_regress/tests.py
index 55363bc5b7..988c5acd0c 100644
--- a/tests/regressiontests/fixtures_regress/tests.py
+++ b/tests/regressiontests/fixtures_regress/tests.py
@@ -14,6 +14,8 @@ from django.db.models import signals
from django.test import (TestCase, TransactionTestCase, skipIfDBFeature,
skipUnlessDBFeature)
from django.test.utils import override_settings
+from django.utils.encoding import force_text
+from django.utils._os import upath
from django.utils import six
from django.utils.six import PY3, StringIO
@@ -126,7 +128,7 @@ class TestFixtures(TestCase):
fixture directory.
"""
load_absolute_path = os.path.join(
- os.path.dirname(__file__),
+ os.path.dirname(upath(__file__)),
'fixtures',
'absolute.json'
)
@@ -388,7 +390,7 @@ class TestFixtures(TestCase):
commit=False,
)
- _cur_dir = os.path.dirname(os.path.abspath(__file__))
+ _cur_dir = os.path.dirname(os.path.abspath(upath(__file__)))
@override_settings(FIXTURE_DIRS=[os.path.join(_cur_dir, 'fixtures_1'),
os.path.join(_cur_dir, 'fixtures_2')])
@@ -430,7 +432,7 @@ class TestFixtures(TestCase):
stdout=stdout_output,
)
self.assertTrue("No xml fixture 'this_fixture_doesnt_exist' in" in
- stdout_output.getvalue())
+ force_text(stdout_output.getvalue()))
class NaturalKeyFixtureTests(TestCase):
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index 6e9c269356..bec31d12d7 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -83,4 +83,8 @@ class Group(models.Model):
class Cheese(models.Model):
- name = models.CharField(max_length=100)
+ name = models.CharField(max_length=100)
+
+
+class Article(models.Model):
+ content = models.TextField()
diff --git a/tests/regressiontests/forms/templates/forms/article_form.html b/tests/regressiontests/forms/templates/forms/article_form.html
new file mode 100644
index 0000000000..cde85051f6
--- /dev/null
+++ b/tests/regressiontests/forms/templates/forms/article_form.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+ <form method="post" action=".">
+ {{ form.as_p }}<br>
+ <input id="submit" type="submit">
+ </form>
+</body>
+</html>
diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py
index 8e2150cea3..6708e54c79 100644
--- a/tests/regressiontests/forms/tests/__init__.py
+++ b/tests/regressiontests/forms/tests/__init__.py
@@ -18,4 +18,4 @@ from .regressions import FormsRegressionsTestCase
from .util import FormsUtilTestCase
from .validators import TestFieldWithValidators
from .widgets import (FormsWidgetTestCase, FormsI18NWidgetsTestCase,
- WidgetTests, ClearableFileInputTests)
+ WidgetTests, LiveWidgetTests, ClearableFileInputTests)
diff --git a/tests/regressiontests/forms/tests/fields.py b/tests/regressiontests/forms/tests/fields.py
index 1027afceb1..e17d976fcf 100644
--- a/tests/regressiontests/forms/tests/fields.py
+++ b/tests/regressiontests/forms/tests/fields.py
@@ -36,6 +36,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from django.forms import *
from django.test import SimpleTestCase
from django.utils import six
+from django.utils._os import upath
def fix_os_paths(x):
@@ -928,12 +929,12 @@ class FieldsTests(SimpleTestCase):
# FilePathField ###############################################################
def test_filepathfield_1(self):
- path = os.path.abspath(forms.__file__)
+ path = os.path.abspath(upath(forms.__file__))
path = os.path.dirname(path) + '/'
self.assertTrue(fix_os_paths(path).endswith('/django/forms/'))
def test_filepathfield_2(self):
- path = forms.__file__
+ path = upath(forms.__file__)
path = os.path.dirname(os.path.abspath(path)) + '/'
f = FilePathField(path=path)
f.choices = [p for p in f.choices if p[0].endswith('.py')]
@@ -954,7 +955,7 @@ class FieldsTests(SimpleTestCase):
assert fix_os_paths(f.clean(path + 'fields.py')).endswith('/django/forms/fields.py')
def test_filepathfield_3(self):
- path = forms.__file__
+ path = upath(forms.__file__)
path = os.path.dirname(os.path.abspath(path)) + '/'
f = FilePathField(path=path, match='^.*?\.py$')
f.choices.sort()
@@ -972,7 +973,7 @@ class FieldsTests(SimpleTestCase):
self.assertTrue(got[0].endswith(exp[0]))
def test_filepathfield_4(self):
- path = os.path.abspath(forms.__file__)
+ path = os.path.abspath(upath(forms.__file__))
path = os.path.dirname(path) + '/'
f = FilePathField(path=path, recursive=True, match='^.*?\.py$')
f.choices.sort()
@@ -992,7 +993,7 @@ class FieldsTests(SimpleTestCase):
self.assertTrue(got[0].endswith(exp[0]))
def test_filepathfield_folders(self):
- path = os.path.dirname(__file__) + '/filepath_test_files/'
+ path = os.path.dirname(upath(__file__)) + '/filepath_test_files/'
f = FilePathField(path=path, allow_folders=True, allow_files=False)
f.choices.sort()
expected = [
diff --git a/tests/regressiontests/forms/tests/forms.py b/tests/regressiontests/forms/tests/forms.py
index a8a28ba806..1c83ed04d4 100644
--- a/tests/regressiontests/forms/tests/forms.py
+++ b/tests/regressiontests/forms/tests/forms.py
@@ -269,6 +269,11 @@ class FormsTestCase(TestCase):
f = SignupForm({'email': 'test@example.com', 'get_spam': 'false'}, auto_id=False)
self.assertHTMLEqual(str(f['get_spam']), '<input type="checkbox" name="get_spam" />')
+ # A value of '0' should be interpreted as a True value (#16820)
+ f = SignupForm({'email': 'test@example.com', 'get_spam': '0'})
+ self.assertTrue(f.is_valid())
+ self.assertTrue(f.cleaned_data.get('get_spam'))
+
def test_widget_output(self):
# Any Field can have a Widget class passed to its constructor:
class ContactForm(Form):
diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/regressiontests/forms/tests/formsets.py
index b3ceee551b..bf893c4c1d 100644
--- a/tests/regressiontests/forms/tests/formsets.py
+++ b/tests/regressiontests/forms/tests/formsets.py
@@ -856,6 +856,27 @@ class FormsFormsetTestCase(TestCase):
formset = FavoriteDrinksFormSet(error_class=CustomErrorList)
self.assertEqual(formset.forms[0].error_class, CustomErrorList)
+ def test_formset_calls_forms_is_valid(self):
+ # Regression tests for #18574 -- make sure formsets call
+ # is_valid() on each form.
+
+ class AnotherChoice(Choice):
+ def is_valid(self):
+ self.is_valid_called = True
+ return super(AnotherChoice, self).is_valid()
+
+ AnotherChoiceFormSet = formset_factory(AnotherChoice)
+ data = {
+ 'choices-TOTAL_FORMS': '1', # number of forms rendered
+ 'choices-INITIAL_FORMS': '0', # number of forms with initial data
+ 'choices-MAX_NUM_FORMS': '0', # max number of forms
+ 'choices-0-choice': 'Calexico',
+ 'choices-0-votes': '100',
+ }
+ formset = AnotherChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertTrue(formset.is_valid())
+ self.assertTrue(all([form.is_valid_called for form in formset.forms]))
+
data = {
'choices-TOTAL_FORMS': '1', # the number of forms rendered
diff --git a/tests/regressiontests/forms/tests/input_formats.py b/tests/regressiontests/forms/tests/input_formats.py
index d591804b9d..7a305dbc2b 100644
--- a/tests/regressiontests/forms/tests/input_formats.py
+++ b/tests/regressiontests/forms/tests/input_formats.py
@@ -3,13 +3,14 @@ from datetime import time, date, datetime
from django import forms
from django.test.utils import override_settings
from django.utils.translation import activate, deactivate
-from django.utils.unittest import TestCase
+from django.test import SimpleTestCase
@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"], USE_L10N=True)
-class LocalizedTimeTests(TestCase):
+class LocalizedTimeTests(SimpleTestCase):
def setUp(self):
- activate('de')
+ # nl/formats.py has customized TIME_INPUT_FORMATS
+ activate('nl')
def tearDown(self):
deactivate()
@@ -92,7 +93,7 @@ class LocalizedTimeTests(TestCase):
result = f.clean('13.30.05')
self.assertEqual(result, time(13,30,5))
- # # Check that the parsed result does a round trip to the same format
+ # Check that the parsed result does a round trip to the same format
text = f.widget._format_value(result)
self.assertEqual(text, "13:30:05")
@@ -106,7 +107,7 @@ class LocalizedTimeTests(TestCase):
@override_settings(TIME_INPUT_FORMATS=["%I:%M:%S %p", "%I:%M %p"])
-class CustomTimeInputFormatsTests(TestCase):
+class CustomTimeInputFormatsTests(SimpleTestCase):
def test_timeField(self):
"TimeFields can parse dates in the default format"
f = forms.TimeField()
@@ -198,7 +199,7 @@ class CustomTimeInputFormatsTests(TestCase):
self.assertEqual(text, "01:30:00 PM")
-class SimpleTimeFormatTests(TestCase):
+class SimpleTimeFormatTests(SimpleTestCase):
def test_timeField(self):
"TimeFields can parse dates in the default format"
f = forms.TimeField()
@@ -289,7 +290,7 @@ class SimpleTimeFormatTests(TestCase):
@override_settings(DATE_INPUT_FORMATS=["%d/%m/%Y", "%d-%m-%Y"], USE_L10N=True)
-class LocalizedDateTests(TestCase):
+class LocalizedDateTests(SimpleTestCase):
def setUp(self):
activate('de')
@@ -302,6 +303,9 @@ class LocalizedDateTests(TestCase):
# Parse a date in an unaccepted format; get an error
self.assertRaises(forms.ValidationError, f.clean, '21/12/2010')
+ # ISO formats are accepted, even if not specified in formats.py
+ self.assertEqual(f.clean('2010-12-21'), date(2010,12,21))
+
# Parse a date in a valid format, get a parsed result
result = f.clean('21.12.2010')
self.assertEqual(result, date(2010,12,21))
@@ -390,7 +394,7 @@ class LocalizedDateTests(TestCase):
@override_settings(DATE_INPUT_FORMATS=["%d.%m.%Y", "%d-%m-%Y"])
-class CustomDateInputFormatsTests(TestCase):
+class CustomDateInputFormatsTests(SimpleTestCase):
def test_dateField(self):
"DateFields can parse dates in the default format"
f = forms.DateField()
@@ -481,7 +485,7 @@ class CustomDateInputFormatsTests(TestCase):
text = f.widget._format_value(result)
self.assertEqual(text, "21.12.2010")
-class SimpleDateFormatTests(TestCase):
+class SimpleDateFormatTests(SimpleTestCase):
def test_dateField(self):
"DateFields can parse dates in the default format"
f = forms.DateField()
@@ -572,7 +576,7 @@ class SimpleDateFormatTests(TestCase):
@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"], USE_L10N=True)
-class LocalizedDateTimeTests(TestCase):
+class LocalizedDateTimeTests(SimpleTestCase):
def setUp(self):
activate('de')
@@ -585,6 +589,9 @@ class LocalizedDateTimeTests(TestCase):
# Parse a date in an unaccepted format; get an error
self.assertRaises(forms.ValidationError, f.clean, '1:30:05 PM 21/12/2010')
+ # ISO formats are accepted, even if not specified in formats.py
+ self.assertEqual(f.clean('2010-12-21 13:30:05'), datetime(2010,12,21,13,30,5))
+
# Parse a date in a valid format, get a parsed result
result = f.clean('21.12.2010 13:30:05')
self.assertEqual(result, datetime(2010,12,21,13,30,5))
@@ -673,7 +680,7 @@ class LocalizedDateTimeTests(TestCase):
@override_settings(DATETIME_INPUT_FORMATS=["%I:%M:%S %p %d/%m/%Y", "%I:%M %p %d-%m-%Y"])
-class CustomDateTimeInputFormatsTests(TestCase):
+class CustomDateTimeInputFormatsTests(SimpleTestCase):
def test_dateTimeField(self):
"DateTimeFields can parse dates in the default format"
f = forms.DateTimeField()
@@ -764,7 +771,7 @@ class CustomDateTimeInputFormatsTests(TestCase):
text = f.widget._format_value(result)
self.assertEqual(text, "01:30:00 PM 21/12/2010")
-class SimpleDateTimeFormatTests(TestCase):
+class SimpleDateTimeFormatTests(SimpleTestCase):
def test_dateTimeField(self):
"DateTimeFields can parse dates in the default format"
f = forms.DateTimeField()
diff --git a/tests/regressiontests/forms/tests/widgets.py b/tests/regressiontests/forms/tests/widgets.py
index 4bdd3f76ea..31c0e65c7c 100644
--- a/tests/regressiontests/forms/tests/widgets.py
+++ b/tests/regressiontests/forms/tests/widgets.py
@@ -4,7 +4,9 @@ from __future__ import unicode_literals
import copy
import datetime
+from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.core.files.uploadedfile import SimpleUploadedFile
+from django.core.urlresolvers import reverse
from django.forms import *
from django.forms.widgets import RadioFieldRenderer
from django.utils import formats
@@ -15,6 +17,8 @@ from django.test import TestCase
from django.test.utils import override_settings
from django.utils.encoding import python_2_unicode_compatible
+from ..models import Article
+
class FormsWidgetTestCase(TestCase):
# Each Widget class corresponds to an HTML form widget. A Widget knows how to
@@ -225,6 +229,10 @@ class FormsWidgetTestCase(TestCase):
# checkboxes).
self.assertFalse(w.value_from_datadict({}, {}, 'testing'))
+ value = w.value_from_datadict({'testing': '0'}, {}, 'testing')
+ self.assertIsInstance(value, bool)
+ self.assertTrue(value)
+
self.assertFalse(w._has_changed(None, None))
self.assertFalse(w._has_changed(None, ''))
self.assertFalse(w._has_changed('', None))
@@ -1091,6 +1099,22 @@ class WidgetTests(TestCase):
self.assertFalse(form.is_valid())
+class LiveWidgetTests(AdminSeleniumWebDriverTestCase):
+ urls = 'regressiontests.forms.urls'
+
+ def test_textarea_trailing_newlines(self):
+ """
+ Test that a roundtrip on a ModelForm doesn't alter the TextField value
+ """
+ article = Article.objects.create(content="\nTst\n")
+ self.selenium.get('%s%s' % (self.live_server_url,
+ reverse('article_form', args=[article.pk])))
+ self.selenium.find_element_by_id('submit').submit()
+ article = Article.objects.get(pk=article.pk)
+ # Should be "\nTst\n" after #19251 is fixed
+ self.assertEqual(article.content, "\r\nTst\r\n")
+
+
@python_2_unicode_compatible
class FakeFieldFile(object):
"""
diff --git a/tests/regressiontests/forms/urls.py b/tests/regressiontests/forms/urls.py
new file mode 100644
index 0000000000..b482b6c45c
--- /dev/null
+++ b/tests/regressiontests/forms/urls.py
@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, url
+from django.views.generic.edit import UpdateView
+
+from .views import ArticleFormView
+
+
+urlpatterns = patterns('',
+ url(r'^/model_form/(?P<pk>\d+)/$', ArticleFormView.as_view(), name="article_form"),
+)
diff --git a/tests/regressiontests/forms/views.py b/tests/regressiontests/forms/views.py
new file mode 100644
index 0000000000..4bf384363c
--- /dev/null
+++ b/tests/regressiontests/forms/views.py
@@ -0,0 +1,8 @@
+from django.views.generic.edit import UpdateView
+
+from .models import Article
+
+
+class ArticleFormView(UpdateView):
+ model = Article
+ success_url = '/'
diff --git a/tests/regressiontests/generic_views/base.py b/tests/regressiontests/generic_views/base.py
index 439b0d7327..c7ad7a0deb 100644
--- a/tests/regressiontests/generic_views/base.py
+++ b/tests/regressiontests/generic_views/base.py
@@ -216,6 +216,17 @@ class ViewTest(unittest.TestCase):
response_allows = set(response['Allow'].split(', '))
self.assertEqual(set(expected_methods + ('OPTIONS',)), response_allows)
+ def test_args_kwargs_request_on_self(self):
+ """
+ Test a view only has args, kwargs & request once `as_view`
+ has been called.
+ """
+ bare_view = InstanceView()
+ view = InstanceView.as_view()(self.rf.get('/'))
+ for attribute in ('args', 'kwargs', 'request'):
+ self.assertNotIn(attribute, dir(bare_view))
+ self.assertIn(attribute, dir(view))
+
class TemplateViewTest(TestCase):
urls = 'regressiontests.generic_views.urls'
diff --git a/tests/regressiontests/generic_views/edit.py b/tests/regressiontests/generic_views/edit.py
index 16f4da8efe..0f1eb3cca7 100644
--- a/tests/regressiontests/generic_views/edit.py
+++ b/tests/regressiontests/generic_views/edit.py
@@ -20,6 +20,15 @@ class FormMixinTests(TestCase):
initial_2 = FormMixin().get_initial()
self.assertNotEqual(initial_1, initial_2)
+
+class BasicFormTests(TestCase):
+ urls = 'regressiontests.generic_views.urls'
+
+ def test_post_data(self):
+ res = self.client.post('/contact/', {'name': "Me", 'message': "Hello"})
+ self.assertRedirects(res, 'http://testserver/list/authors/')
+
+
class ModelFormMixinTests(TestCase):
def test_get_form(self):
form_class = views.AuthorGetQuerySetFormView().get_form_class()
diff --git a/tests/regressiontests/generic_views/forms.py b/tests/regressiontests/generic_views/forms.py
index a78242f555..e036ad8afc 100644
--- a/tests/regressiontests/generic_views/forms.py
+++ b/tests/regressiontests/generic_views/forms.py
@@ -11,3 +11,8 @@ class AuthorForm(forms.ModelForm):
class Meta:
model = Author
+
+
+class ContactForm(forms.Form):
+ name = forms.CharField()
+ message = forms.CharField(widget=forms.Textarea)
diff --git a/tests/regressiontests/generic_views/list.py b/tests/regressiontests/generic_views/list.py
index 14dc1d725d..be2721d775 100644
--- a/tests/regressiontests/generic_views/list.py
+++ b/tests/regressiontests/generic_views/list.py
@@ -2,7 +2,9 @@ from __future__ import absolute_import
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
+from django.test.utils import override_settings
from django.views.generic.base import View
+from django.utils.encoding import force_str
from .models import Author, Artist
@@ -99,6 +101,16 @@ class ListViewTests(TestCase):
# Custom pagination allows for 2 orphans on a page size of 5
self.assertEqual(len(res.context['object_list']), 7)
+ def test_paginated_custom_page_kwarg(self):
+ self._make_authors(100)
+ res = self.client.get('/list/authors/paginated/custom_page_kwarg/', {'pagina': '2'})
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'generic_views/author_list.html')
+ self.assertEqual(len(res.context['object_list']), 30)
+ self.assertIs(res.context['author_list'], res.context['object_list'])
+ self.assertEqual(res.context['author_list'][0].name, 'Author 30')
+ self.assertEqual(res.context['page_obj'].number, 2)
+
def test_paginated_custom_paginator_constructor(self):
self._make_authors(7)
res = self.client.get('/list/authors/paginated/custom_constructor/')
@@ -106,8 +118,26 @@ class ListViewTests(TestCase):
# Custom pagination allows for 2 orphans on a page size of 5
self.assertEqual(len(res.context['object_list']), 7)
+ def test_paginated_orphaned_queryset(self):
+ self._make_authors(92)
+ res = self.client.get('/list/authors/paginated-orphaned/')
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['page_obj'].number, 1)
+ res = self.client.get(
+ '/list/authors/paginated-orphaned/', {'page': 'last'})
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['page_obj'].number, 3)
+ res = self.client.get(
+ '/list/authors/paginated-orphaned/', {'page': '3'})
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res.context['page_obj'].number, 3)
+ res = self.client.get(
+ '/list/authors/paginated-orphaned/', {'page': '4'})
+ self.assertEqual(res.status_code, 404)
+
def test_paginated_non_queryset(self):
res = self.client.get('/list/dict/paginated/')
+
self.assertEqual(res.status_code, 200)
self.assertEqual(len(res.context['object_list']), 1)
@@ -171,8 +201,17 @@ class ListViewTests(TestCase):
with self.assertNumQueries(3):
self.client.get('/list/authors/notempty/paginated/')
+ @override_settings(DEBUG=True)
+ def test_paginated_list_view_returns_useful_message_on_invalid_page(self):
+ # test for #19240
+ # tests that source exception's message is included in page
+ self._make_authors(1)
+ res = self.client.get('/list/authors/paginated/2/')
+ self.assertEqual(res.status_code, 404)
+ self.assertEqual(force_str(res.context.get('reason')),
+ "Invalid page (2): That page contains no results")
+
def _make_authors(self, n):
Author.objects.all().delete()
for i in range(n):
Author.objects.create(name='Author %02i' % i, slug='a%s' % i)
-
diff --git a/tests/regressiontests/generic_views/models.py b/tests/regressiontests/generic_views/models.py
index f59389ef78..30c73d67ca 100644
--- a/tests/regressiontests/generic_views/models.py
+++ b/tests/regressiontests/generic_views/models.py
@@ -1,3 +1,4 @@
+from django.core.urlresolvers import reverse
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@@ -14,9 +15,8 @@ class Artist(models.Model):
def __str__(self):
return self.name
- @models.permalink
def get_absolute_url(self):
- return ('artist_detail', (), {'pk': self.id})
+ return reverse('artist_detail', kwargs={'pk': self.id})
@python_2_unicode_compatible
class Author(models.Model):
diff --git a/tests/regressiontests/generic_views/tests.py b/tests/regressiontests/generic_views/tests.py
index c985ad3309..3f80600efe 100644
--- a/tests/regressiontests/generic_views/tests.py
+++ b/tests/regressiontests/generic_views/tests.py
@@ -6,6 +6,6 @@ from .dates import (ArchiveIndexViewTests, YearArchiveViewTests,
MonthArchiveViewTests, WeekArchiveViewTests, DayArchiveViewTests,
DateDetailViewTests)
from .detail import DetailViewTest
-from .edit import (FormMixinTests, ModelFormMixinTests, CreateViewTests,
- UpdateViewTests, DeleteViewTests)
+from .edit import (FormMixinTests, BasicFormTests, ModelFormMixinTests,
+ CreateViewTests, UpdateViewTests, DeleteViewTests)
from .list import ListViewTests
diff --git a/tests/regressiontests/generic_views/urls.py b/tests/regressiontests/generic_views/urls.py
index c72bfecb65..34b37fa7c4 100644
--- a/tests/regressiontests/generic_views/urls.py
+++ b/tests/regressiontests/generic_views/urls.py
@@ -56,6 +56,10 @@ urlpatterns = patterns('',
(r'^detail/nonmodel/1/$',
views.NonModelDetail.as_view()),
+ # FormView
+ (r'^contact/$',
+ views.ContactView.as_view()),
+
# Create/UpdateView
(r'^edit/artists/create/$',
views.ArtistCreate.as_view()),
@@ -133,6 +137,8 @@ urlpatterns = patterns('',
views.AuthorList.as_view(paginate_by=30)),
(r'^list/authors/paginated/(?P<page>\d+)/$',
views.AuthorList.as_view(paginate_by=30)),
+ (r'^list/authors/paginated-orphaned/$',
+ views.AuthorList.as_view(paginate_by=30, paginate_orphans=2)),
(r'^list/authors/notempty/$',
views.AuthorList.as_view(allow_empty=False)),
(r'^list/authors/notempty/paginated/$',
@@ -149,6 +155,8 @@ urlpatterns = patterns('',
views.AuthorList.as_view(queryset=None)),
(r'^list/authors/paginated/custom_class/$',
views.AuthorList.as_view(paginate_by=5, paginator_class=views.CustomPaginator)),
+ (r'^list/authors/paginated/custom_page_kwarg/$',
+ views.AuthorList.as_view(paginate_by=30, page_kwarg='pagina')),
(r'^list/authors/paginated/custom_constructor/$',
views.AuthorListCustomPaginator.as_view()),
diff --git a/tests/regressiontests/generic_views/views.py b/tests/regressiontests/generic_views/views.py
index f7fcf6f509..71e78e82c7 100644
--- a/tests/regressiontests/generic_views/views.py
+++ b/tests/regressiontests/generic_views/views.py
@@ -2,11 +2,11 @@ from __future__ import absolute_import
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.decorators import method_decorator
from django.views import generic
-from .forms import AuthorForm
+from .forms import AuthorForm, ContactForm
from .models import Artist, Author, Book, Page, BookSigning
@@ -75,6 +75,13 @@ class AuthorListCustomPaginator(AuthorList):
orphans=2,
allow_empty_first_page=allow_empty_first_page)
+
+class ContactView(generic.FormView):
+ form_class = ContactForm
+ success_url = reverse_lazy('authors_list')
+ template_name = 'generic_views/form.html'
+
+
class ArtistCreate(generic.CreateView):
model = Artist
diff --git a/tests/regressiontests/httpwrappers/tests.py b/tests/regressiontests/httpwrappers/tests.py
index 553b6ecff4..a601e541c6 100644
--- a/tests/regressiontests/httpwrappers/tests.py
+++ b/tests/regressiontests/httpwrappers/tests.py
@@ -14,6 +14,7 @@ from django.http import (QueryDict, HttpResponse, HttpResponseRedirect,
parse_cookie)
from django.test import TestCase
from django.utils.encoding import smart_str
+from django.utils._os import upath
from django.utils import six
from django.utils import unittest
@@ -149,7 +150,7 @@ class QueryDictTests(unittest.TestCase):
self.assertEqual(q.setdefault('foo', 'bar'), 'bar')
self.assertEqual(q['foo'], 'bar')
self.assertEqual(q.getlist('foo'), ['bar'])
- self.assertEqual(q.urlencode(), 'foo=bar&name=john')
+ self.assertIn(q.urlencode(), ['foo=bar&name=john', 'name=john&foo=bar'])
q.clear()
self.assertEqual(len(q), 0)
@@ -266,14 +267,18 @@ class HttpResponseTests(unittest.TestCase):
# The response also converts unicode or bytes keys to strings, but requires
# them to contain ASCII
r = HttpResponse()
+ del r['Content-Type']
r['foo'] = 'bar'
l = list(r.items())
+ self.assertEqual(len(l), 1)
self.assertEqual(l[0], ('foo', 'bar'))
self.assertIsInstance(l[0][0], str)
r = HttpResponse()
+ del r['Content-Type']
r[b'foo'] = 'bar'
l = list(r.items())
+ self.assertEqual(len(l), 1)
self.assertEqual(l[0], ('foo', 'bar'))
self.assertIsInstance(l[0][0], str)
@@ -479,7 +484,7 @@ class StreamingHttpResponseTests(TestCase):
class FileCloseTests(TestCase):
def test_response(self):
- filename = os.path.join(os.path.dirname(__file__), 'abc.txt')
+ filename = os.path.join(os.path.dirname(upath(__file__)), 'abc.txt')
# file isn't closed until we close the response.
file1 = open(filename)
@@ -512,7 +517,7 @@ class FileCloseTests(TestCase):
self.assertTrue(file2.closed)
def test_streaming_response(self):
- filename = os.path.join(os.path.dirname(__file__), 'abc.txt')
+ filename = os.path.join(os.path.dirname(upath(__file__)), 'abc.txt')
# file isn't closed until we close the response.
file1 = open(filename)
diff --git a/tests/regressiontests/i18n/commands/compilation.py b/tests/regressiontests/i18n/commands/compilation.py
index c6ab77941b..2944469110 100644
--- a/tests/regressiontests/i18n/commands/compilation.py
+++ b/tests/regressiontests/i18n/commands/compilation.py
@@ -4,9 +4,10 @@ from django.core.management import call_command, CommandError
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import translation, six
+from django.utils._os import upath
from django.utils.six import StringIO
-test_dir = os.path.abspath(os.path.dirname(__file__))
+test_dir = os.path.abspath(os.path.dirname(upath(__file__)))
class MessageCompilationTests(TestCase):
@@ -25,9 +26,9 @@ class PoFileTests(MessageCompilationTests):
def test_bom_rejection(self):
os.chdir(test_dir)
- with six.assertRaisesRegex(self, CommandError,
- "file has a BOM \(Byte Order Mark\)"):
+ with self.assertRaises(CommandError) as cm:
call_command('compilemessages', locale=self.LOCALE, stderr=StringIO())
+ self.assertIn("file has a BOM (Byte Order Mark)", cm.exception.args[0])
self.assertFalse(os.path.exists(self.MO_FILE))
diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
index ca2c3cc026..aa5efe1967 100644
--- a/tests/regressiontests/i18n/commands/extraction.py
+++ b/tests/regressiontests/i18n/commands/extraction.py
@@ -1,4 +1,5 @@
# -*- encoding: utf-8 -*-
+from __future__ import unicode_literals
import os
import re
@@ -6,6 +7,8 @@ import shutil
from django.core import management
from django.test import TestCase
+from django.utils.encoding import force_text
+from django.utils._os import upath
from django.utils.six import StringIO
@@ -17,7 +20,7 @@ class ExtractorTests(TestCase):
def setUp(self):
self._cwd = os.getcwd()
- self.test_dir = os.path.abspath(os.path.dirname(__file__))
+ self.test_dir = os.path.abspath(os.path.dirname(upath(__file__)))
def _rmrf(self, dname):
if os.path.commonprefix([self.test_dir, os.path.abspath(dname)]) != self.test_dir:
@@ -55,7 +58,7 @@ class BasicExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertTrue('#. Translators: This comment should be extracted' in po_contents)
self.assertTrue('This comment should not be extracted' not in po_contents)
# Comments in templates
@@ -83,7 +86,7 @@ class BasicExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertMsgId('Literal with a percent symbol at the end %%', po_contents)
self.assertMsgId('Literal with a percent %% symbol in the middle', po_contents)
self.assertMsgId('Completed 50%% of all the tasks', po_contents)
@@ -99,7 +102,7 @@ class BasicExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertMsgId('I think that 100%% is more that 50%% of anything.', po_contents)
self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', po_contents)
self.assertMsgId("Blocktrans extraction shouldn't double escape this: %%, a=%(a)s", po_contents)
@@ -123,7 +126,7 @@ class BasicExtractorTests(ExtractorTests):
stdout = StringIO()
management.call_command('makemessages', locale=LOCALE, stdout=stdout)
os.remove('./code_sample.py')
- self.assertIn("code_sample.py:4", stdout.getvalue())
+ self.assertIn("code_sample.py:4", force_text(stdout.getvalue()))
def test_template_message_context_extractor(self):
"""
@@ -135,7 +138,7 @@ class BasicExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
# {% trans %}
self.assertTrue('msgctxt "Special trans context #1"' in po_contents)
self.assertTrue("Translatable literal #7a" in po_contents)
@@ -161,7 +164,7 @@ class BasicExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
# {% trans %}
self.assertTrue('msgctxt "Context wrapped in double quotes"' in po_contents)
self.assertTrue('msgctxt "Context wrapped in single quotes"' in po_contents)
@@ -216,7 +219,7 @@ class SymlinkExtractorTests(ExtractorTests):
def setUp(self):
self._cwd = os.getcwd()
- self.test_dir = os.path.abspath(os.path.dirname(__file__))
+ self.test_dir = os.path.abspath(os.path.dirname(upath(__file__)))
self.symlinked_dir = os.path.join(self.test_dir, 'templates_symlinked')
def tearDown(self):
@@ -238,7 +241,7 @@ class SymlinkExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0, symlinks=True)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertMsgId('This literal should be included.', po_contents)
self.assertTrue('templates_symlinked/test.html' in po_contents)
@@ -250,7 +253,7 @@ class CopyPluralFormsExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertTrue('Plural-Forms: nplurals=2; plural=(n != 1)' in po_contents)
@@ -261,7 +264,7 @@ class NoWrapExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0, no_wrap=True)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertMsgId('This literal should also be included wrapped or not wrapped depending on the use of the --no-wrap option.', po_contents)
def test_no_wrap_disabled(self):
@@ -269,7 +272,7 @@ class NoWrapExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0, no_wrap=False)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertMsgId('""\n"This literal should also be included wrapped or not wrapped depending on the "\n"use of the --no-wrap option."', po_contents, use_quotes=False)
@@ -280,7 +283,7 @@ class NoLocationExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0, no_location=True)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertFalse('#: templates/test.html:55' in po_contents)
def test_no_location_disabled(self):
@@ -288,5 +291,5 @@ class NoLocationExtractorTests(ExtractorTests):
management.call_command('makemessages', locale=LOCALE, verbosity=0, no_location=False)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
- po_contents = fp.read()
+ po_contents = force_text(fp.read())
self.assertTrue('#: templates/test.html:55' in po_contents)
diff --git a/tests/regressiontests/i18n/contenttypes/tests.py b/tests/regressiontests/i18n/contenttypes/tests.py
index 178232f543..5e8a9823e1 100644
--- a/tests/regressiontests/i18n/contenttypes/tests.py
+++ b/tests/regressiontests/i18n/contenttypes/tests.py
@@ -6,6 +6,7 @@ import os
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.test.utils import override_settings
+from django.utils._os import upath
from django.utils import six
from django.utils import translation
@@ -13,7 +14,7 @@ from django.utils import translation
@override_settings(
USE_I18N=True,
LOCALE_PATHS=(
- os.path.join(os.path.dirname(__file__), 'locale'),
+ os.path.join(os.path.dirname(upath(__file__)), 'locale'),
),
LANGUAGE_CODE='en',
LANGUAGES=(
diff --git a/tests/regressiontests/i18n/patterns/tests.py b/tests/regressiontests/i18n/patterns/tests.py
index 73c9f56711..358cdf65db 100644
--- a/tests/regressiontests/i18n/patterns/tests.py
+++ b/tests/regressiontests/i18n/patterns/tests.py
@@ -7,16 +7,17 @@ from django.core.urlresolvers import reverse, clear_url_caches
from django.test import TestCase
from django.test.utils import override_settings
from django.template import Template, Context
+from django.utils._os import upath
from django.utils import translation
@override_settings(
USE_I18N=True,
LOCALE_PATHS=(
- os.path.join(os.path.dirname(__file__), 'locale'),
+ os.path.join(os.path.dirname(upath(__file__)), 'locale'),
),
TEMPLATE_DIRS=(
- os.path.join(os.path.dirname(__file__), 'templates'),
+ os.path.join(os.path.dirname(upath(__file__)), 'templates'),
),
LANGUAGE_CODE='en',
LANGUAGES=(
diff --git a/tests/regressiontests/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 4054f85ef0..dcc288e600 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -18,6 +18,7 @@ from django.utils.formats import (get_format, date_format, time_format,
number_format)
from django.utils.importlib import import_module
from django.utils.numberformat import format as nformat
+from django.utils._os import upath
from django.utils.safestring import mark_safe, SafeBytes, SafeString, SafeText
from django.utils import six
from django.utils.six import PY3
@@ -44,7 +45,7 @@ from .patterns.tests import (URLRedirectWithoutTrailingSlashTests,
URLPrefixTests, URLResponseTests, URLRedirectTests, PathUnusedTests)
-here = os.path.dirname(os.path.abspath(__file__))
+here = os.path.dirname(os.path.abspath(upath(__file__)))
extended_locale_paths = settings.LOCALE_PATHS + (
os.path.join(here, 'other', 'locale'),
)
@@ -83,6 +84,10 @@ class TranslationTests(TestCase):
s4 = ugettext_lazy('Some other string')
self.assertEqual(False, s == s4)
+ if not six.PY3:
+ # On Python 2, gettext_lazy should not transform a bytestring to unicode
+ self.assertEqual(gettext_lazy(b"test").upper(), b"TEST")
+
def test_lazy_pickle(self):
s1 = ugettext_lazy("test")
self.assertEqual(six.text_type(s1), "test")
@@ -662,8 +667,8 @@ class FormattingTests(TestCase):
with self.settings(USE_L10N=True,
FORMAT_MODULE_PATH='regressiontests.i18n.other.locale'):
with translation.override('de', deactivate=True):
- old = "%r" % get_format_modules(reverse=True)
- new = "%r" % get_format_modules(reverse=True) # second try
+ old = str("%r") % get_format_modules(reverse=True)
+ new = str("%r") % get_format_modules(reverse=True) # second try
self.assertEqual(new, old, 'Value returned by get_formats_modules() must be preserved between calls.')
def test_localize_templatetag_and_filter(self):
diff --git a/tests/modeltests/pagination/__init__.py b/tests/regressiontests/indexes/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/modeltests/pagination/__init__.py
+++ b/tests/regressiontests/indexes/__init__.py
diff --git a/tests/regressiontests/indexes/models.py b/tests/regressiontests/indexes/models.py
new file mode 100644
index 0000000000..9758377f99
--- /dev/null
+++ b/tests/regressiontests/indexes/models.py
@@ -0,0 +1,11 @@
+from django.db import models
+
+
+class Article(models.Model):
+ headline = models.CharField(max_length=100)
+ pub_date = models.DateTimeField()
+
+ class Meta:
+ index_together = [
+ ["headline", "pub_date"],
+ ]
diff --git a/tests/regressiontests/indexes/tests.py b/tests/regressiontests/indexes/tests.py
new file mode 100644
index 0000000000..0dac881fa9
--- /dev/null
+++ b/tests/regressiontests/indexes/tests.py
@@ -0,0 +1,12 @@
+from django.core.management.color import no_style
+from django.db import connections, DEFAULT_DB_ALIAS
+from django.test import TestCase
+
+from .models import Article
+
+
+class IndexesTests(TestCase):
+ def test_index_together(self):
+ connection = connections[DEFAULT_DB_ALIAS]
+ index_sql = connection.creation.sql_indexes_for_model(Article, no_style())
+ self.assertEqual(len(index_sql), 1)
diff --git a/tests/regressiontests/initial_sql_regress/sql/simple.sql b/tests/regressiontests/initial_sql_regress/sql/simple.sql
index 39363baa9a..d82f8381af 100644
--- a/tests/regressiontests/initial_sql_regress/sql/simple.sql
+++ b/tests/regressiontests/initial_sql_regress/sql/simple.sql
@@ -2,7 +2,8 @@
INSERT INTO initial_sql_regress_simple (name) VALUES ('John'); -- another comment
INSERT INTO initial_sql_regress_simple (name) VALUES ('-- Comment Man');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Paul');
-INSERT INTO initial_sql_regress_simple (name) VALUES ('Ringo');
+INSERT INTO
+ initial_sql_regress_simple (name) VALUES ('Ringo');
INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
diff --git a/tests/regressiontests/initial_sql_regress/tests.py b/tests/regressiontests/initial_sql_regress/tests.py
index 03a91cb807..39d8921061 100644
--- a/tests/regressiontests/initial_sql_regress/tests.py
+++ b/tests/regressiontests/initial_sql_regress/tests.py
@@ -1,3 +1,6 @@
+from django.core.management.color import no_style
+from django.core.management.sql import custom_sql_for_model
+from django.db import connections, DEFAULT_DB_ALIAS
from django.test import TestCase
from .models import Simple
@@ -15,10 +18,6 @@ class InitialSQLTests(TestCase):
self.assertEqual(Simple.objects.count(), 0)
def test_custom_sql(self):
- from django.core.management.sql import custom_sql_for_model
- from django.core.management.color import no_style
- from django.db import connections, DEFAULT_DB_ALIAS
-
# Simulate the custom SQL loading by syncdb
connection = connections[DEFAULT_DB_ALIAS]
custom_sql = custom_sql_for_model(Simple, no_style(), connection)
diff --git a/tests/regressiontests/inspectdb/tests.py b/tests/regressiontests/inspectdb/tests.py
index 484e7f4060..028d263337 100644
--- a/tests/regressiontests/inspectdb/tests.py
+++ b/tests/regressiontests/inspectdb/tests.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from django.core.management import call_command
+from django.db import connection
from django.test import TestCase, skipUnlessDBFeature
from django.utils.six import StringIO
@@ -60,14 +61,16 @@ class InspectDBTestCase(TestCase):
self.assertIn("number_45extra = models.CharField", output)
def test_special_column_name_introspection(self):
- """Introspection of column names containing special characters,
- unsuitable for Python identifiers
+ """
+ Introspection of column names containing special characters,
+ unsuitable for Python identifiers
"""
out = StringIO()
call_command('inspectdb', stdout=out)
output = out.getvalue()
+ base_name = 'Field' if connection.vendor != 'oracle' else 'field'
self.assertIn("field = models.IntegerField()", output)
- self.assertIn("field_field = models.IntegerField(db_column='Field_')", output)
- self.assertIn("field_field_0 = models.IntegerField(db_column='Field__')", output)
+ self.assertIn("field_field = models.IntegerField(db_column='%s_')" % base_name, output)
+ self.assertIn("field_field_0 = models.IntegerField(db_column='%s__')" % base_name, output)
self.assertIn("field_field_1 = models.IntegerField(db_column='__field')", output)
self.assertIn("prc_x = models.IntegerField(db_column='prc(%) x')", output)
diff --git a/tests/regressiontests/introspection/models.py b/tests/regressiontests/introspection/models.py
index 6e5beba61d..4de82e47e7 100644
--- a/tests/regressiontests/introspection/models.py
+++ b/tests/regressiontests/introspection/models.py
@@ -17,6 +17,7 @@ class Reporter(models.Model):
def __str__(self):
return "%s %s" % (self.first_name, self.last_name)
+
@python_2_unicode_compatible
class Article(models.Model):
headline = models.CharField(max_length=100)
@@ -28,3 +29,6 @@ class Article(models.Model):
class Meta:
ordering = ('headline',)
+ index_together = [
+ ["headline", "pub_date"],
+ ]
diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
index a54e0c670b..2df946d874 100644
--- a/tests/regressiontests/introspection/tests.py
+++ b/tests/regressiontests/introspection/tests.py
@@ -1,14 +1,19 @@
-from __future__ import absolute_import,unicode_literals
+from __future__ import absolute_import, unicode_literals
from functools import update_wrapper
from django.db import connection
from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
-from django.utils import six
+from django.utils import six, unittest
from .models import Reporter, Article
-#
+if connection.vendor == 'oracle':
+ expectedFailureOnOracle = unittest.expectedFailure
+else:
+ expectedFailureOnOracle = lambda f: f
+
+
# The introspection module is optional, so methods tested here might raise
# NotImplementedError. This is perfectly acceptable behavior for the backend
# in question, but the tests need to handle this without failing. Ideally we'd
@@ -18,7 +23,7 @@ from .models import Reporter, Article
# wrapper that ignores the exception.
#
# The metaclass is just for fun.
-#
+
def ignore_not_implemented(func):
def _inner(*args, **kwargs):
@@ -29,15 +34,16 @@ def ignore_not_implemented(func):
update_wrapper(_inner, func)
return _inner
+
class IgnoreNotimplementedError(type):
def __new__(cls, name, bases, attrs):
- for k,v in attrs.items():
+ for k, v in attrs.items():
if k.startswith('test'):
attrs[k] = ignore_not_implemented(v)
return type.__new__(cls, name, bases, attrs)
-class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)):
+class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)):
def test_table_names(self):
tl = connection.introspection.table_names()
self.assertEqual(tl, sorted(tl))
@@ -89,7 +95,13 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)
[datatype(r[1], r) for r in desc],
['IntegerField', 'CharField', 'CharField', 'CharField', 'BigIntegerField']
)
- # Check also length of CharFields
+
+ # The following test fails on Oracle due to #17202 (can't correctly
+ # inspect the length of character columns).
+ @expectedFailureOnOracle
+ def test_get_table_description_col_lengths(self):
+ cursor = connection.cursor()
+ desc = connection.introspection.get_table_description(cursor, Reporter._meta.db_table)
self.assertEqual(
[r[3] for r in desc if datatype(r[1], r) == 'CharField'],
[30, 30, 75]
@@ -152,6 +164,7 @@ class IntrospectionTests(six.with_metaclass(IgnoreNotimplementedError, TestCase)
self.assertNotIn('first_name', indexes)
self.assertIn('id', indexes)
+
def datatype(dbtype, description):
"""Helper to convert a data type into a string."""
dt = connection.introspection.get_field_type(dbtype, description)
diff --git a/tests/regressiontests/logging_tests/logconfig.py b/tests/regressiontests/logging_tests/logconfig.py
index fc5ea1a0bd..b068103eb9 100644
--- a/tests/regressiontests/logging_tests/logconfig.py
+++ b/tests/regressiontests/logging_tests/logconfig.py
@@ -1,8 +1,15 @@
import logging
from django.conf import settings
+from django.core.mail.backends.base import BaseEmailBackend
+
class MyHandler(logging.Handler):
def __init__(self):
logging.Handler.__init__(self)
self.config = settings.LOGGING
+
+
+class MyEmailBackend(BaseEmailBackend):
+ def send_messages(self, email_messages):
+ pass
diff --git a/tests/regressiontests/logging_tests/tests.py b/tests/regressiontests/logging_tests/tests.py
index e40800efde..07804eb398 100644
--- a/tests/regressiontests/logging_tests/tests.py
+++ b/tests/regressiontests/logging_tests/tests.py
@@ -2,17 +2,23 @@ from __future__ import unicode_literals
import copy
import logging
+import sys
import warnings
-from django.conf import compat_patch_logging_config
+from django.conf import compat_patch_logging_config, LazySettings
from django.core import mail
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
+from django.utils.encoding import force_text
from django.utils.log import CallbackFilter, RequireDebugFalse
from django.utils.six import StringIO
+from django.utils.unittest import skipUnless
from ..admin_scripts.tests import AdminScriptTestCase
+from .logconfig import MyEmailBackend
+
+PYVERS = sys.version_info[:2]
# logging config prior to using filter with mail_admins
OLD_LOGGING = {
@@ -131,6 +137,32 @@ class DefaultLoggingTest(TestCase):
self.logger.error("Hey, this is an error.")
self.assertEqual(output.getvalue(), 'Hey, this is an error.\n')
+@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7")
+class WarningLoggerTests(TestCase):
+ """
+ Tests that warnings output for DeprecationWarnings is enabled
+ and captured to the logging system
+ """
+ def setUp(self):
+ self.logger = logging.getLogger('py.warnings')
+ self.old_stream = self.logger.handlers[0].stream
+
+ def tearDown(self):
+ self.logger.handlers[0].stream = self.old_stream
+
+ @override_settings(DEBUG=True)
+ def test_warnings_capture(self):
+ output = StringIO()
+ self.logger.handlers[0].stream = output
+ warnings.warn('Foo Deprecated', DeprecationWarning)
+ self.assertTrue('Foo Deprecated' in force_text(output.getvalue()))
+
+ def test_warnings_capture_debug_false(self):
+ output = StringIO()
+ self.logger.handlers[0].stream = output
+ warnings.warn('Foo Deprecated', DeprecationWarning)
+ self.assertFalse('Foo Deprecated' in force_text(output.getvalue()))
+
class CallbackFilterTest(TestCase):
def test_sense(self):
@@ -276,6 +308,38 @@ class AdminEmailHandlerTest(TestCase):
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, expected_subject)
+ @override_settings(
+ ADMINS=(('admin', 'admin@example.com'),),
+ DEBUG=False,
+ )
+ def test_uses_custom_email_backend(self):
+ """
+ Refs #19325
+ """
+ message = 'All work and no play makes Jack a dull boy'
+ admin_email_handler = self.get_admin_email_handler(self.logger)
+ mail_admins_called = {'called': False}
+
+ def my_mail_admins(*args, **kwargs):
+ connection = kwargs['connection']
+ self.assertTrue(isinstance(connection, MyEmailBackend))
+ mail_admins_called['called'] = True
+
+ # Monkeypatches
+ orig_mail_admins = mail.mail_admins
+ orig_email_backend = admin_email_handler.email_backend
+ mail.mail_admins = my_mail_admins
+ admin_email_handler.email_backend = (
+ 'regressiontests.logging_tests.logconfig.MyEmailBackend')
+
+ try:
+ self.logger.error(message)
+ self.assertTrue(mail_admins_called['called'])
+ finally:
+ # Revert Monkeypatches
+ mail.mail_admins = orig_mail_admins
+ admin_email_handler.email_backend = orig_email_backend
+
class SettingsConfigTest(AdminScriptTestCase):
"""
@@ -302,3 +366,20 @@ class SettingsConfigTest(AdminScriptTestCase):
out, err = self.run_manage(['validate'])
self.assertNoOutput(err)
self.assertOutput(out, "0 errors found")
+
+
+def dictConfig(config):
+ dictConfig.called = True
+dictConfig.called = False
+
+
+class SettingsConfigureLogging(TestCase):
+ """
+ Test that calling settings.configure() initializes the logging
+ configuration.
+ """
+ def test_configure_initializes_logging(self):
+ settings = LazySettings()
+ settings.configure(
+ LOGGING_CONFIG='regressiontests.logging_tests.tests.dictConfig')
+ self.assertTrue(dictConfig.called)
diff --git a/tests/regressiontests/m2m_regress/models.py b/tests/regressiontests/m2m_regress/models.py
index 7c1108456e..1a4a6df354 100644
--- a/tests/regressiontests/m2m_regress/models.py
+++ b/tests/regressiontests/m2m_regress/models.py
@@ -61,3 +61,20 @@ class Worksheet(models.Model):
class User(models.Model):
name = models.CharField(max_length=30)
friends = models.ManyToManyField(auth.User)
+
+
+class BadModelWithSplit(models.Model):
+ name = models.CharField(max_length=1)
+
+ def split(self):
+ raise RuntimeError('split should not be called')
+
+ class Meta:
+ abstract = True
+
+
+class RegressionModelSplit(BadModelWithSplit):
+ """
+ Model with a split method should not cause an error in add_lazy_relation
+ """
+ others = models.ManyToManyField('self')
diff --git a/tests/regressiontests/m2m_regress/tests.py b/tests/regressiontests/m2m_regress/tests.py
index 92628c1825..3dc1d2417c 100644
--- a/tests/regressiontests/m2m_regress/tests.py
+++ b/tests/regressiontests/m2m_regress/tests.py
@@ -5,7 +5,7 @@ from django.test import TestCase
from django.utils import six
from .models import (SelfRefer, Tag, TagCollection, Entry, SelfReferChild,
- SelfReferChildSibling, Worksheet)
+ SelfReferChildSibling, Worksheet, RegressionModelSplit)
class M2MRegressionTests(TestCase):
@@ -74,7 +74,7 @@ class M2MRegressionTests(TestCase):
c1.tags = [t1, t2]
c1 = TagCollection.objects.get(name='c1')
- self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"])
+ self.assertQuerysetEqual(c1.tags.all(), ["<Tag: t1>", "<Tag: t2>"], ordered=False)
self.assertQuerysetEqual(t1.tag_collections.all(), ["<TagCollection: c1>"])
def test_manager_class_caching(self):
@@ -90,3 +90,9 @@ class M2MRegressionTests(TestCase):
# Get same manager for different instances
self.assertTrue(e1.topics.__class__ is e2.topics.__class__)
self.assertTrue(t1.entry_set.__class__ is t2.entry_set.__class__)
+
+ def test_m2m_abstract_split(self):
+ # Regression for #19236 - an abstract class with a 'split' method
+ # causes a TypeError in add_lazy_relation
+ m1 = RegressionModelSplit(name='1')
+ m1.save()
diff --git a/tests/regressiontests/m2m_through_regress/models.py b/tests/regressiontests/m2m_through_regress/models.py
index 47c24ed5b2..91e1aa8cc1 100644
--- a/tests/regressiontests/m2m_through_regress/models.py
+++ b/tests/regressiontests/m2m_through_regress/models.py
@@ -62,18 +62,21 @@ class B(models.Model):
# Using to_field on the through model
@python_2_unicode_compatible
class Car(models.Model):
- make = models.CharField(max_length=20, unique=True)
+ make = models.CharField(max_length=20, unique=True, null=True)
drivers = models.ManyToManyField('Driver', through='CarDriver')
def __str__(self):
- return self.make
+ return "%s" % self.make
@python_2_unicode_compatible
class Driver(models.Model):
- name = models.CharField(max_length=20, unique=True)
+ name = models.CharField(max_length=20, unique=True, null=True)
def __str__(self):
- return self.name
+ return "%s" % self.name
+
+ class Meta:
+ ordering = ('name',)
@python_2_unicode_compatible
class CarDriver(models.Model):
diff --git a/tests/regressiontests/m2m_through_regress/tests.py b/tests/regressiontests/m2m_through_regress/tests.py
index 458c194f89..17a4525442 100644
--- a/tests/regressiontests/m2m_through_regress/tests.py
+++ b/tests/regressiontests/m2m_through_regress/tests.py
@@ -28,7 +28,8 @@ class M2MThroughTestCase(TestCase):
bob.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(
@@ -51,7 +52,8 @@ class M2MThroughTestCase(TestCase):
frank.group_set.all(), [
"<Group: Rock>",
"<Group: Roll>",
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(
@@ -123,18 +125,105 @@ class ToFieldThroughTests(TestCase):
self.car = Car.objects.create(make="Toyota")
self.driver = Driver.objects.create(name="Ryan Briscoe")
CarDriver.objects.create(car=self.car, driver=self.driver)
+ # We are testing if wrong objects get deleted due to using wrong
+ # field value in m2m queries. So, it is essential that the pk
+ # numberings do not match.
+ # Create one intentionally unused driver to mix up the autonumbering
+ self.unused_driver = Driver.objects.create(name="Barney Gumble")
+ # And two intentionally unused cars.
+ self.unused_car1 = Car.objects.create(make="Trabant")
+ self.unused_car2 = Car.objects.create(make="Wartburg")
def test_to_field(self):
self.assertQuerysetEqual(
self.car.drivers.all(),
["<Driver: Ryan Briscoe>"]
- )
+ )
def test_to_field_reverse(self):
self.assertQuerysetEqual(
self.driver.car_set.all(),
["<Car: Toyota>"]
- )
+ )
+
+ def test_to_field_clear_reverse(self):
+ self.driver.car_set.clear()
+ self.assertQuerysetEqual(
+ self.driver.car_set.all(),[])
+
+ def test_to_field_clear(self):
+ self.car.drivers.clear()
+ self.assertQuerysetEqual(
+ self.car.drivers.all(),[])
+
+ # Low level tests for _add_items and _remove_items. We test these methods
+ # because .add/.remove aren't available for m2m fields with through, but
+ # through is the only way to set to_field currently. We do want to make
+ # sure these methods are ready if the ability to use .add or .remove with
+ # to_field relations is added some day.
+ def test_add(self):
+ self.assertQuerysetEqual(
+ self.car.drivers.all(),
+ ["<Driver: Ryan Briscoe>"]
+ )
+ # Yikes - barney is going to drive...
+ self.car.drivers._add_items('car', 'driver', self.unused_driver)
+ self.assertQuerysetEqual(
+ self.car.drivers.all(),
+ ["<Driver: Barney Gumble>", "<Driver: Ryan Briscoe>"]
+ )
+
+ def test_add_null(self):
+ nullcar = Car.objects.create(make=None)
+ with self.assertRaises(ValueError):
+ nullcar.drivers._add_items('car', 'driver', self.unused_driver)
+
+ def test_add_related_null(self):
+ nulldriver = Driver.objects.create(name=None)
+ with self.assertRaises(ValueError):
+ self.car.drivers._add_items('car', 'driver', nulldriver)
+
+ def test_add_reverse(self):
+ car2 = Car.objects.create(make="Honda")
+ self.assertQuerysetEqual(
+ self.driver.car_set.all(),
+ ["<Car: Toyota>"]
+ )
+ self.driver.car_set._add_items('driver', 'car', car2)
+ self.assertQuerysetEqual(
+ self.driver.car_set.all(),
+ ["<Car: Toyota>", "<Car: Honda>"],
+ ordered=False
+ )
+
+ def test_add_null_reverse(self):
+ nullcar = Car.objects.create(make=None)
+ with self.assertRaises(ValueError):
+ self.driver.car_set._add_items('driver', 'car', nullcar)
+
+ def test_add_null_reverse_related(self):
+ nulldriver = Driver.objects.create(name=None)
+ with self.assertRaises(ValueError):
+ nulldriver.car_set._add_items('driver', 'car', self.car)
+
+ def test_remove(self):
+ self.assertQuerysetEqual(
+ self.car.drivers.all(),
+ ["<Driver: Ryan Briscoe>"]
+ )
+ self.car.drivers._remove_items('car', 'driver', self.driver)
+ self.assertQuerysetEqual(
+ self.car.drivers.all(),[])
+
+ def test_remove_reverse(self):
+ self.assertQuerysetEqual(
+ self.driver.car_set.all(),
+ ["<Car: Toyota>"]
+ )
+ self.driver.car_set._remove_items('driver', 'car', self.car)
+ self.assertQuerysetEqual(
+ self.driver.car_set.all(),[])
+
class ThroughLoadDataTestCase(TestCase):
fixtures = ["m2m_through"]
diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py
index 33898cc1d5..b798cb21aa 100644
--- a/tests/regressiontests/mail/tests.py
+++ b/tests/regressiontests/mail/tests.py
@@ -17,6 +17,7 @@ from django.core.mail.backends import console, dummy, locmem, filebased, smtp
from django.core.mail.message import BadHeaderError
from django.test import TestCase
from django.test.utils import override_settings
+from django.utils.encoding import force_str, force_text
from django.utils.six import PY3, StringIO
from django.utils.translation import ugettext_lazy
@@ -357,6 +358,14 @@ class BaseEmailBackendTests(object):
self.assertEqual(message["from"], "from@example.com")
self.assertEqual(message.get_all("to"), ["to@example.com"])
+ def test_send_unicode(self):
+ email = EmailMessage('Chère maman', 'Je t\'aime très fort', 'from@example.com', ['to@example.com'])
+ num_sent = mail.get_connection().send_messages([email])
+ self.assertEqual(num_sent, 1)
+ message = self.get_the_message()
+ self.assertEqual(message["subject"], '=?utf-8?q?Ch=C3=A8re_maman?=')
+ self.assertEqual(force_text(message.get_payload()), 'Je t\'aime très fort')
+
def test_send_many(self):
email1 = EmailMessage('Subject', 'Content1', 'from@example.com', ['to@example.com'])
email2 = EmailMessage('Subject', 'Content2', 'from@example.com', ['to@example.com'])
@@ -526,8 +535,8 @@ class FileBackendTests(BaseEmailBackendTests, TestCase):
messages = []
for filename in os.listdir(self.tmp_dir):
with open(os.path.join(self.tmp_dir, filename), 'r') as fp:
- session = fp.read().split('\n' + ('-' * 79) + '\n')
- messages.extend(email.message_from_string(str(m)) for m in session if m)
+ session = force_text(fp.read()).split('\n' + ('-' * 79) + '\n')
+ messages.extend(email.message_from_string(force_str(m)) for m in session if m)
return messages
def test_file_sessions(self):
@@ -579,8 +588,8 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
self.stream = sys.stdout = StringIO()
def get_mailbox_content(self):
- messages = self.stream.getvalue().split('\n' + ('-' * 79) + '\n')
- return [email.message_from_string(str(m)) for m in messages if m]
+ messages = force_text(self.stream.getvalue()).split('\n' + ('-' * 79) + '\n')
+ return [email.message_from_string(force_str(m)) for m in messages if m]
def test_console_stream_kwarg(self):
"""
diff --git a/tests/regressiontests/managers_regress/tests.py b/tests/regressiontests/managers_regress/tests.py
index f3721a4c01..45059be4e5 100644
--- a/tests/regressiontests/managers_regress/tests.py
+++ b/tests/regressiontests/managers_regress/tests.py
@@ -61,7 +61,8 @@ class ManagersRegressionTests(TestCase):
self.assertQuerysetEqual(Child4.manager1.all(), [
"<Child4: d1>",
"<Child4: f1>"
- ]
+ ],
+ ordered=False
)
self.assertQuerysetEqual(Child5._default_manager.all(), ["<Child5: fred>"])
self.assertQuerysetEqual(Child6._default_manager.all(), ["<Child6: f1>"])
diff --git a/tests/regressiontests/middleware/tests.py b/tests/regressiontests/middleware/tests.py
index de901f4a80..b8cffd9c92 100644
--- a/tests/regressiontests/middleware/tests.py
+++ b/tests/regressiontests/middleware/tests.py
@@ -294,6 +294,15 @@ class CommonMiddlewareTest(TestCase):
CommonMiddleware().process_response(request, response)
self.assertEqual(len(mail.outbox), 0)
+ # Other tests
+
+ def test_non_ascii_query_string_does_not_crash(self):
+ """Regression test for #15152"""
+ request = self._get_request('slash')
+ request.META['QUERY_STRING'] = 'drink=café'
+ response = CommonMiddleware().process_request(request)
+ self.assertEqual(response.status_code, 301)
+
class ConditionalGetMiddlewareTest(TestCase):
urls = 'regressiontests.middleware.cond_get_urls'
diff --git a/tests/regressiontests/model_fields/imagefield.py b/tests/regressiontests/model_fields/imagefield.py
index 7446f222ff..df0215db3d 100644
--- a/tests/regressiontests/model_fields/imagefield.py
+++ b/tests/regressiontests/model_fields/imagefield.py
@@ -6,6 +6,7 @@ import shutil
from django.core.files import File
from django.core.files.images import ImageFile
from django.test import TestCase
+from django.utils._os import upath
from django.utils.unittest import skipIf
from .models import Image
@@ -43,10 +44,10 @@ class ImageFieldTestMixin(object):
shutil.rmtree(temp_storage_dir)
os.mkdir(temp_storage_dir)
- file_path1 = os.path.join(os.path.dirname(__file__), "4x8.png")
+ file_path1 = os.path.join(os.path.dirname(upath(__file__)), "4x8.png")
self.file1 = self.File(open(file_path1, 'rb'))
- file_path2 = os.path.join(os.path.dirname(__file__), "8x4.png")
+ file_path2 = os.path.join(os.path.dirname(upath(__file__)), "8x4.png")
self.file2 = self.File(open(file_path2, 'rb'))
def tearDown(self):
diff --git a/tests/regressiontests/model_forms_regress/models.py b/tests/regressiontests/model_forms_regress/models.py
index f6e08d24dc..2c2fd39158 100644
--- a/tests/regressiontests/model_forms_regress/models.py
+++ b/tests/regressiontests/model_forms_regress/models.py
@@ -5,6 +5,7 @@ import os
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
+from django.utils._os import upath
class Person(models.Model):
@@ -19,7 +20,7 @@ class Triple(models.Model):
unique_together = (('left', 'middle'), ('middle', 'right'))
class FilePathModel(models.Model):
- path = models.FilePathField(path=os.path.dirname(__file__), match=".*\.py$", blank=True)
+ path = models.FilePathField(path=os.path.dirname(upath(__file__)), match=".*\.py$", blank=True)
@python_2_unicode_compatible
class Publication(models.Model):
diff --git a/tests/regressiontests/model_formsets_regress/tests.py b/tests/regressiontests/model_formsets_regress/tests.py
index 1fbdb9744f..8cadcfc409 100644
--- a/tests/regressiontests/model_formsets_regress/tests.py
+++ b/tests/regressiontests/model_formsets_regress/tests.py
@@ -351,7 +351,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
def should_delete(self):
""" delete form if odd PK """
- return self.instance.id % 2 != 0
+ return self.instance.pk % 2 != 0
NormalFormset = modelformset_factory(User, form=CustomDeleteUserForm, can_delete=True)
DeleteFormset = modelformset_factory(User, form=CustomDeleteUserForm, formset=BaseCustomDeleteModelFormSet)
@@ -392,7 +392,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
data = dict(self.data)
data['form-INITIAL_FORMS'] = 4
data.update(dict(
- ('form-%d-id' % i, user.id)
+ ('form-%d-id' % i, user.pk)
for i,user in enumerate(User.objects.all())
))
formset = self.NormalFormset(data, queryset=User.objects.all())
@@ -409,7 +409,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
data = dict(self.data)
data['form-INITIAL_FORMS'] = 4
data.update(dict(
- ('form-%d-id' % i, user.id)
+ ('form-%d-id' % i, user.pk)
for i,user in enumerate(User.objects.all())
))
data.update(self.delete_all_ids)
@@ -428,7 +428,7 @@ class FormfieldShouldDeleteFormTests(TestCase):
data = dict(self.data)
data['form-INITIAL_FORMS'] = 4
data.update(dict(
- ('form-%d-id' % i, user.id)
+ ('form-%d-id' % i, user.pk)
for i,user in enumerate(User.objects.all())
))
data.update(self.delete_all_ids)
@@ -440,5 +440,5 @@ class FormfieldShouldDeleteFormTests(TestCase):
self.assertEqual(len(User.objects.all()), 2)
# verify no "odd" PKs left
- odd_ids = [user.id for user in User.objects.all() if user.id % 2]
+ odd_ids = [user.pk for user in User.objects.all() if user.pk % 2]
self.assertEqual(len(odd_ids), 0)
diff --git a/tests/regressiontests/model_regress/tests.py b/tests/regressiontests/model_regress/tests.py
index 6a45a83052..c90fe98658 100644
--- a/tests/regressiontests/model_regress/tests.py
+++ b/tests/regressiontests/model_regress/tests.py
@@ -71,7 +71,8 @@ class ModelTests(TestCase):
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__year=1998), [
@@ -85,14 +86,16 @@ class ModelTests(TestCase):
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__month="12"), [
datetime.date(1999, 12, 31),
datetime.date(1998, 12, 31),
],
- attrgetter("when")
+ attrgetter("when"),
+ ordered=False
)
self.assertQuerysetEqual(
Party.objects.filter(when__year="1998"), [
diff --git a/tests/regressiontests/multiple_database/tests.py b/tests/regressiontests/multiple_database/tests.py
index 79fe6beebd..a903ea11a0 100644
--- a/tests/regressiontests/multiple_database/tests.py
+++ b/tests/regressiontests/multiple_database/tests.py
@@ -16,16 +16,6 @@ from django.utils.six import StringIO
from .models import Book, Person, Pet, Review, UserProfile
-def copy_content_types_from_default_to_other():
- # On post_syncdb, content types are created in the 'default' database.
- # However, tests of generic foreign keys require them in 'other' too.
- # The problem is masked on backends that defer constraints checks: at the
- # end of each test, there's a rollback, and constraints are never checked.
- # It only appears on MySQL + InnoDB.
- for ct in ContentType.objects.using('default').all():
- ct.save(using='other')
-
-
class QueryTestCase(TestCase):
multi_db = True
@@ -705,8 +695,6 @@ class QueryTestCase(TestCase):
def test_generic_key_separation(self):
"Generic fields are constrained to a single database"
- copy_content_types_from_default_to_other()
-
# Create a book and author on the default database
pro = Book.objects.create(title="Pro Django",
published=datetime.date(2008, 12, 16))
@@ -734,8 +722,6 @@ class QueryTestCase(TestCase):
def test_generic_key_reverse_operations(self):
"Generic reverse manipulations are all constrained to a single DB"
- copy_content_types_from_default_to_other()
-
dive = Book.objects.using('other').create(title="Dive into Python",
published=datetime.date(2009, 5, 4))
@@ -780,8 +766,6 @@ class QueryTestCase(TestCase):
def test_generic_key_cross_database_protection(self):
"Operations that involve sharing generic key objects across databases raise an error"
- copy_content_types_from_default_to_other()
-
# Create a book and author on the default database
pro = Book.objects.create(title="Pro Django",
published=datetime.date(2008, 12, 16))
@@ -833,8 +817,6 @@ class QueryTestCase(TestCase):
def test_generic_key_deletion(self):
"Cascaded deletions of Generic Key relations issue queries on the right database"
- copy_content_types_from_default_to_other()
-
dive = Book.objects.using('other').create(title="Dive into Python",
published=datetime.date(2009, 5, 4))
review = Review.objects.using('other').create(source="Python Weekly", content_object=dive)
@@ -1402,8 +1384,6 @@ class RouterTestCase(TestCase):
def test_generic_key_cross_database_protection(self):
"Generic Key operations can span databases if they share a source"
- copy_content_types_from_default_to_other()
-
# Create a book and author on the default database
pro = Book.objects.using('default'
).create(title="Pro Django", published=datetime.date(2008, 12, 16))
@@ -1515,8 +1495,6 @@ class RouterTestCase(TestCase):
def test_generic_key_managers(self):
"Generic key relations are represented by managers, and can be controlled like managers"
- copy_content_types_from_default_to_other()
-
pro = Book.objects.using('other').create(title="Pro Django",
published=datetime.date(2008, 12, 16))
@@ -1922,3 +1900,39 @@ class RouterModelArgumentTestCase(TestCase):
pet = Pet.objects.create(owner=person, name='Wart')
# test related FK collection
person.delete()
+
+
+class SyncOnlyDefaultDatabaseRouter(object):
+ def allow_syncdb(self, db, model):
+ return db == DEFAULT_DB_ALIAS
+
+
+class SyncDBTestCase(TestCase):
+ multi_db = True
+
+ def test_syncdb_to_other_database(self):
+ """Regression test for #16039: syncdb with --database option."""
+ cts = ContentType.objects.using('other').filter(app_label='multiple_database')
+
+ count = cts.count()
+ self.assertGreater(count, 0)
+
+ cts.delete()
+ management.call_command('syncdb', verbosity=0, interactive=False,
+ load_initial_data=False, database='other')
+ self.assertEqual(cts.count(), count)
+
+ def test_syncdb_to_other_database_with_router(self):
+ """Regression test for #16039: syncdb with --database option."""
+ cts = ContentType.objects.using('other').filter(app_label='multiple_database')
+
+ cts.delete()
+ try:
+ old_routers = router.routers
+ router.routers = [SyncOnlyDefaultDatabaseRouter()]
+ management.call_command('syncdb', verbosity=0, interactive=False,
+ load_initial_data=False, database='other')
+ finally:
+ router.routers = old_routers
+
+ self.assertEqual(cts.count(), 0)
diff --git a/tests/regressiontests/pagination_regress/__init__.py b/tests/regressiontests/pagination/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/regressiontests/pagination_regress/__init__.py
+++ b/tests/regressiontests/pagination/__init__.py
diff --git a/tests/regressiontests/pagination/custom.py b/tests/regressiontests/pagination/custom.py
new file mode 100644
index 0000000000..47a932c7e3
--- /dev/null
+++ b/tests/regressiontests/pagination/custom.py
@@ -0,0 +1,20 @@
+from django.core.paginator import Paginator, Page
+
+
+class ValidAdjacentNumsPage(Page):
+
+ def next_page_number(self):
+ if not self.has_next():
+ return None
+ return super(ValidAdjacentNumsPage, self).next_page_number()
+
+ def previous_page_number(self):
+ if not self.has_previous():
+ return None
+ return super(ValidAdjacentNumsPage, self).previous_page_number()
+
+
+class ValidAdjacentNumsPaginator(Paginator):
+
+ def _get_page(self, *args, **kwargs):
+ return ValidAdjacentNumsPage(*args, **kwargs)
diff --git a/tests/modeltests/pagination/models.py b/tests/regressiontests/pagination/models.py
index 779d3029ba..9dc8d4b776 100644
--- a/tests/modeltests/pagination/models.py
+++ b/tests/regressiontests/pagination/models.py
@@ -1,11 +1,3 @@
-"""
-30. Object pagination
-
-Django provides a framework for paginating a list of objects in a few lines
-of code. This is often useful for dividing search results or long lists of
-objects into easily readable pages.
-"""
-
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
diff --git a/tests/regressiontests/pagination_regress/tests.py b/tests/regressiontests/pagination/tests.py
index e98352e006..dea5756672 100644
--- a/tests/regressiontests/pagination_regress/tests.py
+++ b/tests/regressiontests/pagination/tests.py
@@ -1,9 +1,18 @@
-from __future__ import unicode_literals
+from __future__ import absolute_import, unicode_literals
-from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
-from django.utils.unittest import TestCase
+from datetime import datetime
-class PaginatorTests(TestCase):
+from django.core.paginator import (Paginator, EmptyPage, InvalidPage,
+ PageNotAnInteger)
+from django.test import TestCase
+from django.utils import six
+from django.utils import unittest
+
+from .models import Article
+from .custom import ValidAdjacentNumsPaginator
+
+
+class PaginationTests(unittest.TestCase):
"""
Tests for the Paginator and Page classes.
"""
@@ -31,15 +40,6 @@ class PaginatorTests(TestCase):
"For '%s', expected %s but got %s. Paginator parameters were: %s"
% (name, expected, got, params))
- def test_invalid_page_number(self):
- """
- Tests that invalid page numbers result in the correct exception being
- raised.
- """
- paginator = Paginator([1, 2, 3], 2)
- self.assertRaises(PageNotAnInteger, paginator.validate_number, None)
- self.assertRaises(PageNotAnInteger, paginator.validate_number, 'x')
-
def test_paginator(self):
"""
Tests the paginator attributes using varying inputs.
@@ -107,6 +107,38 @@ class PaginatorTests(TestCase):
for params, output in tests:
self.check_paginator(params, output)
+ def test_invalid_page_number(self):
+ """
+ Tests that invalid page numbers result in the correct exception being
+ raised.
+ """
+ paginator = Paginator([1, 2, 3], 2)
+ self.assertRaises(InvalidPage, paginator.page, 3)
+ self.assertRaises(PageNotAnInteger, paginator.validate_number, None)
+ self.assertRaises(PageNotAnInteger, paginator.validate_number, 'x')
+ # With no content and allow_empty_first_page=True, 1 is a valid page number
+ paginator = Paginator([], 2)
+ self.assertEqual(paginator.validate_number(1), 1)
+
+ def test_paginate_misc_classes(self):
+ class CountContainer(object):
+ def count(self):
+ return 42
+ # Paginator can be passed other objects with a count() method.
+ paginator = Paginator(CountContainer(), 10)
+ self.assertEqual(42, paginator.count)
+ self.assertEqual(5, paginator.num_pages)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
+
+ # Paginator can be passed other objects that implement __len__.
+ class LenContainer(object):
+ def __len__(self):
+ return 42
+ paginator = Paginator(LenContainer(), 10)
+ self.assertEqual(42, paginator.count)
+ self.assertEqual(5, paginator.num_pages)
+ self.assertEqual([1, 2, 3, 4, 5], list(paginator.page_range))
+
def check_indexes(self, params, page_num, indexes):
"""
Helper method that instantiates a Paginator object from the passed
@@ -168,6 +200,7 @@ class PaginatorTests(TestCase):
for params, first, last in tests:
self.check_indexes(params, 'first', first)
self.check_indexes(params, 'last', last)
+
# When no items and no empty first page, we should get EmptyPage error.
self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 0, False), 1, None)
self.assertRaises(EmptyPage, self.check_indexes, ([], 4, 1, False), 1, None)
@@ -184,3 +217,91 @@ class PaginatorTests(TestCase):
self.assertFalse('a' in page2)
self.assertEqual(''.join(page2), 'fghijk')
self.assertEqual(''.join(reversed(page2)), 'kjihgf')
+
+ def test_get_page_hook(self):
+ """
+ Tests that a Paginator subclass can use the ``_get_page`` hook to
+ return an alternative to the standard Page class.
+ """
+ eleven = 'abcdefghijk'
+ paginator = ValidAdjacentNumsPaginator(eleven, per_page=6)
+ page1 = paginator.page(1)
+ page2 = paginator.page(2)
+ self.assertIsNone(page1.previous_page_number())
+ self.assertEqual(page1.next_page_number(), 2)
+ self.assertEqual(page2.previous_page_number(), 1)
+ self.assertIsNone(page2.next_page_number())
+
+
+class ModelPaginationTests(TestCase):
+ """
+ Test pagination with Django model instances
+ """
+ def setUp(self):
+ # Prepare a list of objects for pagination.
+ for x in range(1, 10):
+ a = Article(headline='Article %s' % x, pub_date=datetime(2005, 7, 29))
+ a.save()
+
+ def test_first_page(self):
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(1)
+ self.assertEqual("<Page 1 of 2>", six.text_type(p))
+ self.assertQuerysetEqual(p.object_list, [
+ "<Article: Article 1>",
+ "<Article: Article 2>",
+ "<Article: Article 3>",
+ "<Article: Article 4>",
+ "<Article: Article 5>"
+ ],
+ ordered=False
+ )
+ self.assertTrue(p.has_next())
+ self.assertFalse(p.has_previous())
+ self.assertTrue(p.has_other_pages())
+ self.assertEqual(2, p.next_page_number())
+ self.assertRaises(InvalidPage, p.previous_page_number)
+ self.assertEqual(1, p.start_index())
+ self.assertEqual(5, p.end_index())
+
+ def test_last_page(self):
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(2)
+ self.assertEqual("<Page 2 of 2>", six.text_type(p))
+ self.assertQuerysetEqual(p.object_list, [
+ "<Article: Article 6>",
+ "<Article: Article 7>",
+ "<Article: Article 8>",
+ "<Article: Article 9>"
+ ],
+ ordered=False
+ )
+ self.assertFalse(p.has_next())
+ self.assertTrue(p.has_previous())
+ self.assertTrue(p.has_other_pages())
+ self.assertRaises(InvalidPage, p.next_page_number)
+ self.assertEqual(1, p.previous_page_number())
+ self.assertEqual(6, p.start_index())
+ self.assertEqual(9, p.end_index())
+
+ def test_page_getitem(self):
+ """
+ Tests proper behaviour of a paginator page __getitem__ (queryset
+ evaluation, slicing, exception raised).
+ """
+ paginator = Paginator(Article.objects.all(), 5)
+ p = paginator.page(1)
+
+ # Make sure object_list queryset is not evaluated by an invalid __getitem__ call.
+ # (this happens from the template engine when using eg: {% page_obj.has_previous %})
+ self.assertIsNone(p.object_list._result_cache)
+ self.assertRaises(TypeError, lambda: p['has_previous'])
+ self.assertIsNone(p.object_list._result_cache)
+
+ # Make sure slicing the Page object with numbers and slice objects work.
+ self.assertEqual(p[0], Article.objects.get(headline='Article 1'))
+ self.assertQuerysetEqual(p[slice(2)], [
+ "<Article: Article 1>",
+ "<Article: Article 2>",
+ ]
+ )
diff --git a/tests/regressiontests/pagination_regress/models.py b/tests/regressiontests/pagination_regress/models.py
deleted file mode 100644
index cde172db68..0000000000
--- a/tests/regressiontests/pagination_regress/models.py
+++ /dev/null
@@ -1 +0,0 @@
-# Models file for tests to run.
diff --git a/tests/regressiontests/queries/models.py b/tests/regressiontests/queries/models.py
index f0178a0256..73b9762150 100644
--- a/tests/regressiontests/queries/models.py
+++ b/tests/regressiontests/queries/models.py
@@ -283,6 +283,7 @@ class SingleObject(models.Model):
class RelatedObject(models.Model):
single = models.ForeignKey(SingleObject, null=True)
+ f = models.IntegerField(null=True)
class Meta:
ordering = ['single']
@@ -311,7 +312,7 @@ class Food(models.Model):
@python_2_unicode_compatible
class Eaten(models.Model):
- food = models.ForeignKey(Food, to_field="name")
+ food = models.ForeignKey(Food, to_field="name", null=True)
meal = models.CharField(max_length=20)
def __str__(self):
@@ -400,3 +401,23 @@ class ModelA(models.Model):
name = models.TextField()
b = models.ForeignKey(ModelB, null=True)
d = models.ForeignKey(ModelD)
+
+@python_2_unicode_compatible
+class Job(models.Model):
+ name = models.CharField(max_length=20, unique=True)
+
+ def __str__(self):
+ return self.name
+
+class JobResponsibilities(models.Model):
+ job = models.ForeignKey(Job, to_field='name')
+ responsibility = models.ForeignKey('Responsibility', to_field='description')
+
+@python_2_unicode_compatible
+class Responsibility(models.Model):
+ description = models.CharField(max_length=20, unique=True)
+ jobs = models.ManyToManyField(Job, through=JobResponsibilities,
+ related_name='responsibilities')
+
+ def __str__(self):
+ return self.description
diff --git a/tests/regressiontests/queries/tests.py b/tests/regressiontests/queries/tests.py
index c2a4ad1caf..75e27769b4 100644
--- a/tests/regressiontests/queries/tests.py
+++ b/tests/regressiontests/queries/tests.py
@@ -23,7 +23,8 @@ from .models import (Annotation, Article, Author, Celebrity, Child, Cover,
Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten,
Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory,
SpecialCategory, OneToOneCategory, NullableName, ProxyCategory,
- SingleObject, RelatedObject, ModelA, ModelD)
+ SingleObject, RelatedObject, ModelA, ModelD, Responsibility, Job,
+ JobResponsibilities)
class BaseQuerysetTest(TestCase):
@@ -243,7 +244,10 @@ class Queries1Tests(BaseQuerysetTest):
q1 = Item.objects.order_by('name')
q2 = Item.objects.filter(id=self.i1.id)
list(q2)
- self.assertEqual(len((q1 & q2).order_by('name').query.tables), 1)
+ combined_query = (q1 & q2).order_by('name').query
+ self.assertEqual(len([
+ t for t in combined_query.tables if combined_query.alias_refcount[t]
+ ]), 1)
def test_order_by_join_unref(self):
"""
@@ -841,11 +845,14 @@ class Queries1Tests(BaseQuerysetTest):
"""
original_ordering = Tag._meta.ordering
Tag._meta.ordering = None
- self.assertQuerysetEqual(
- Tag.objects.all(),
- ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
- )
- Tag._meta.ordering = original_ordering
+ try:
+ self.assertQuerysetEqual(
+ Tag.objects.all(),
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+ ordered=False
+ )
+ finally:
+ Tag._meta.ordering = original_ordering
def test_exclude(self):
self.assertQuerysetEqual(
@@ -880,6 +887,225 @@ class Queries1Tests(BaseQuerysetTest):
Item.objects.filter(Q(tags__name__in=['t4', 't3'])),
[repr(i) for i in Item.objects.filter(~~Q(tags__name__in=['t4', 't3']))])
+ def test_ticket_10790_1(self):
+ # Querying direct fields with isnull should trim the left outer join.
+ # It also should not create INNER JOIN.
+ q = Tag.objects.filter(parent__isnull=True)
+
+ self.assertQuerysetEqual(q, ['<Tag: t1>'])
+ self.assertTrue('JOIN' not in str(q.query))
+
+ q = Tag.objects.filter(parent__isnull=False)
+
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+ )
+ self.assertTrue('JOIN' not in str(q.query))
+
+ q = Tag.objects.exclude(parent__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+ )
+ self.assertTrue('JOIN' not in str(q.query))
+
+ q = Tag.objects.exclude(parent__isnull=False)
+ self.assertQuerysetEqual(q, ['<Tag: t1>'])
+ self.assertTrue('JOIN' not in str(q.query))
+
+ q = Tag.objects.exclude(parent__parent__isnull=False)
+
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>'],
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+ self.assertTrue('INNER JOIN' not in str(q.query))
+
+ def test_ticket_10790_2(self):
+ # Querying across several tables should strip only the last outer join,
+ # while preserving the preceeding inner joins.
+ q = Tag.objects.filter(parent__parent__isnull=False)
+
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t4>', '<Tag: t5>'],
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+ # Querying without isnull should not convert anything to left outer join.
+ q = Tag.objects.filter(parent__parent=self.t1)
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t4>', '<Tag: t5>'],
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+ def test_ticket_10790_3(self):
+ # Querying via indirect fields should populate the left outer join
+ q = NamedCategory.objects.filter(tag__isnull=True)
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+ # join to dumbcategory ptr_id
+ self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+ self.assertQuerysetEqual(q, [])
+
+ # Querying across several tables should strip only the last join, while
+ # preserving the preceding left outer joins.
+ q = NamedCategory.objects.filter(tag__parent__isnull=True)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+ self.assertQuerysetEqual( q, ['<NamedCategory: NamedCategory object>'])
+
+ def test_ticket_10790_4(self):
+ # Querying across m2m field should not strip the m2m table from join.
+ q = Author.objects.filter(item__tags__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a2>', '<Author: a3>'],
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 2)
+ self.assertTrue('INNER JOIN' not in str(q.query))
+
+ q = Author.objects.filter(item__tags__parent__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a3>'],
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 3)
+ self.assertTrue('INNER JOIN' not in str(q.query))
+
+ def test_ticket_10790_5(self):
+ # Querying with isnull=False across m2m field should not create outer joins
+ q = Author.objects.filter(item__tags__isnull=False)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a4>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 2)
+
+ q = Author.objects.filter(item__tags__parent__isnull=False)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a2>', '<Author: a4>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 3)
+
+ q = Author.objects.filter(item__tags__parent__parent__isnull=False)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a4>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 4)
+
+ def test_ticket_10790_6(self):
+ # Querying with isnull=True across m2m field should not create inner joins
+ # and strip last outer join
+ q = Author.objects.filter(item__tags__parent__parent__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a1>', '<Author: a2>', '<Author: a2>',
+ '<Author: a2>', '<Author: a3>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 4)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+ q = Author.objects.filter(item__tags__parent__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a3>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 3)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+ def test_ticket_10790_7(self):
+ # Reverse querying with isnull should not strip the join
+ q = Author.objects.filter(item__isnull=True)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a3>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 1)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+ q = Author.objects.filter(item__isnull=False)
+ self.assertQuerysetEqual(
+ q,
+ ['<Author: a1>', '<Author: a2>', '<Author: a2>', '<Author: a4>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 1)
+
+ def test_ticket_10790_8(self):
+ # Querying with combined q-objects should also strip the left outer join
+ q = Tag.objects.filter(Q(parent__isnull=True) | Q(parent=self.t1))
+ self.assertQuerysetEqual(
+ q,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+ )
+ self.assertTrue(str(q.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q.query).count('INNER JOIN') == 0)
+
+ def test_ticket_10790_combine(self):
+ # Combining queries should not re-populate the left outer join
+ q1 = Tag.objects.filter(parent__isnull=True)
+ q2 = Tag.objects.filter(parent__isnull=False)
+
+ q3 = q1 | q2
+ self.assertQuerysetEqual(
+ q3,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>', '<Tag: t4>', '<Tag: t5>'],
+ )
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+ q3 = q1 & q2
+ self.assertQuerysetEqual(q3, [])
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+ q2 = Tag.objects.filter(parent=self.t1)
+ q3 = q1 | q2
+ self.assertQuerysetEqual(
+ q3,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+ )
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+ q3 = q2 | q1
+ self.assertQuerysetEqual(
+ q3,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+ )
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 0)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+ q1 = Tag.objects.filter(parent__isnull=True)
+ q2 = Tag.objects.filter(parent__parent__isnull=True)
+
+ q3 = q1 | q2
+ self.assertQuerysetEqual(
+ q3,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+ )
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 1)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+ q3 = q2 | q1
+ self.assertQuerysetEqual(
+ q3,
+ ['<Tag: t1>', '<Tag: t2>', '<Tag: t3>']
+ )
+ self.assertTrue(str(q3.query).count('LEFT OUTER JOIN') == 1)
+ self.assertTrue(str(q3.query).count('INNER JOIN') == 0)
+
+
class Queries2Tests(TestCase):
def setUp(self):
Number.objects.create(num=4)
@@ -925,15 +1151,18 @@ class Queries2Tests(TestCase):
self.assertQuerysetEqual(Number.objects.filter(num__gt=12.1), [])
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12.0),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lt=12.1),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__gte=11.9),
@@ -951,23 +1180,28 @@ class Queries2Tests(TestCase):
self.assertQuerysetEqual(Number.objects.filter(num__gte=12.9), [])
self.assertQuerysetEqual(
Number.objects.filter(num__lte=11.9),
- ['<Number: 4>', '<Number: 8>']
+ ['<Number: 4>', '<Number: 8>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.0),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.1),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
self.assertQuerysetEqual(
Number.objects.filter(num__lte=12.9),
- ['<Number: 4>', '<Number: 8>', '<Number: 12>']
+ ['<Number: 4>', '<Number: 8>', '<Number: 12>'],
+ ordered=False
)
def test_ticket7411(self):
@@ -1026,14 +1260,18 @@ class Queries4Tests(BaseQuerysetTest):
Item.objects.create(name='i2', created=datetime.datetime.now(), note=n1, creator=self.a3)
def test_ticket14876(self):
+ # Note: when combining the query we need to have information available
+ # about the join type of the trimmed "creator__isnull" join. If we
+ # don't have that information, then the join is created as INNER JOIN
+ # and results will be incorrect.
q1 = Report.objects.filter(Q(creator__isnull=True) | Q(creator__extra__info='e1'))
q2 = Report.objects.filter(Q(creator__isnull=True)) | Report.objects.filter(Q(creator__extra__info='e1'))
- self.assertQuerysetEqual(q1, ["<Report: r1>", "<Report: r3>"])
+ self.assertQuerysetEqual(q1, ["<Report: r1>", "<Report: r3>"], ordered=False)
self.assertEqual(str(q1.query), str(q2.query))
q1 = Report.objects.filter(Q(creator__extra__info='e1') | Q(creator__isnull=True))
q2 = Report.objects.filter(Q(creator__extra__info='e1')) | Report.objects.filter(Q(creator__isnull=True))
- self.assertQuerysetEqual(q1, ["<Report: r1>", "<Report: r3>"])
+ self.assertQuerysetEqual(q1, ["<Report: r1>", "<Report: r3>"], ordered=False)
self.assertEqual(str(q1.query), str(q2.query))
q1 = Item.objects.filter(Q(creator=self.a1) | Q(creator__report__name='r1')).order_by()
@@ -1046,6 +1284,18 @@ class Queries4Tests(BaseQuerysetTest):
self.assertQuerysetEqual(q1, ["<Item: i1>"])
self.assertEqual(str(q1.query), str(q2.query))
+ def test_combine_join_reuse(self):
+ # Test that we correctly recreate joins having identical connections
+ # in the rhs query, in case the query is ORed together. Related to
+ # ticket #18748
+ Report.objects.create(name='r4', creator=self.a1)
+ q1 = Author.objects.filter(report__name='r5')
+ q2 = Author.objects.filter(report__name='r4').filter(report__name='r1')
+ combined = q1|q2
+ self.assertEquals(str(combined.query).count('JOIN'), 2)
+ self.assertEquals(len(combined), 1)
+ self.assertEquals(combined[0].name, 'a1')
+
def test_ticket7095(self):
# Updates that are filtered on the model being updated are somewhat
# tricky in MySQL. This exercises that case.
@@ -1382,17 +1632,19 @@ class NullableRelOrderingTests(TestCase):
# the join type of already existing joins.
Plaything.objects.create(name="p1")
s = SingleObject.objects.create(name='s')
- r = RelatedObject.objects.create(single=s)
+ r = RelatedObject.objects.create(single=s, f=1)
Plaything.objects.create(name="p2", others=r)
qs = Plaything.objects.all().filter(others__isnull=False).order_by('pk')
+ self.assertTrue('JOIN' not in str(qs.query))
+ qs = Plaything.objects.all().filter(others__f__isnull=False).order_by('pk')
self.assertTrue('INNER' in str(qs.query))
qs = qs.order_by('others__single__name')
# The ordering by others__single__pk will add one new join (to single)
# and that join must be LEFT join. The already existing join to related
# objects must be kept INNER. So, we have both a INNER and a LEFT join
# in the query.
- self.assertTrue('LEFT' in str(qs.query))
- self.assertTrue('INNER' in str(qs.query))
+ self.assertEquals(str(qs.query).count('LEFT'), 1)
+ self.assertEquals(str(qs.query).count('INNER'), 1)
self.assertQuerysetEqual(
qs,
['<Plaything: p2>']
@@ -1443,6 +1695,7 @@ class Queries6Tests(TestCase):
# This next test used to cause really weird PostgreSQL behavior, but it was
# only apparent much later when the full test suite ran.
+ # - Yeah, it leaves global ITER_CHUNK_SIZE to 2 instead of 100...
#@unittest.expectedFailure
def test_slicing_and_cache_interaction(self):
# We can do slicing beyond what is currently in the result cache,
@@ -1970,6 +2223,29 @@ class DefaultValuesInsertTest(TestCase):
except TypeError:
self.fail("Creation of an instance of a model with only the PK field shouldn't error out after bulk insert refactoring (#17056)")
+class ExcludeTest(TestCase):
+ def setUp(self):
+ f1 = Food.objects.create(name='apples')
+ Food.objects.create(name='oranges')
+ Eaten.objects.create(food=f1, meal='dinner')
+ j1 = Job.objects.create(name='Manager')
+ r1 = Responsibility.objects.create(description='Playing golf')
+ j2 = Job.objects.create(name='Programmer')
+ r2 = Responsibility.objects.create(description='Programming')
+ JobResponsibilities.objects.create(job=j1, responsibility=r1)
+ JobResponsibilities.objects.create(job=j2, responsibility=r2)
+
+ def test_to_field(self):
+ self.assertQuerysetEqual(
+ Food.objects.exclude(eaten__meal='dinner'),
+ ['<Food: oranges>'])
+ self.assertQuerysetEqual(
+ Job.objects.exclude(responsibilities__description='Playing golf'),
+ ['<Job: Programmer>'])
+ self.assertQuerysetEqual(
+ Responsibility.objects.exclude(jobs__name='Manager'),
+ ['<Responsibility: Programming>'])
+
class NullInExcludeTest(TestCase):
def setUp(self):
NullableName.objects.create(name='i1')
@@ -2132,3 +2408,13 @@ class NullJoinPromotionOrTest(TestCase):
# so we can use INNER JOIN for it. However, we can NOT use INNER JOIN
# for the b->c join, as a->b is nullable.
self.assertEqual(str(qset.query).count('INNER JOIN'), 1)
+
+class ReverseJoinTrimmingTest(TestCase):
+ def test_reverse_trimming(self):
+ # Check that we don't accidentally trim reverse joins - we can't know
+ # if there is anything on the other side of the join, so trimming
+ # reverse joins can't be done, ever.
+ t = Tag.objects.create()
+ qs = Tag.objects.filter(annotation__tag=t.pk)
+ self.assertIn('INNER JOIN', str(qs.query))
+ self.assertEquals(list(qs), [])
diff --git a/tests/regressiontests/requests/tests.py b/tests/regressiontests/requests/tests.py
index 6522620d5f..adf824dff7 100644
--- a/tests/regressiontests/requests/tests.py
+++ b/tests/regressiontests/requests/tests.py
@@ -1,3 +1,4 @@
+# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
import time
@@ -11,7 +12,7 @@ from django.http import HttpRequest, HttpResponse, parse_cookie, build_request_r
from django.test.client import FakePayload
from django.test.utils import override_settings, str_prefix
from django.utils import unittest
-from django.utils.http import cookie_date
+from django.utils.http import cookie_date, urlencode
from django.utils.timezone import utc
@@ -115,13 +116,15 @@ class RequestsTests(unittest.TestCase):
'12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
]
poisoned_hosts = [
'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld',
- 'example.com:someone@somestie.com:80',
- 'example.com:80/badpath'
+ 'example.com:dr.frankenstein@evil.tld:80',
+ 'example.com:80/badpath',
+ 'example.com: recovermypassword.com',
]
for host in legit_hosts:
@@ -185,13 +188,15 @@ class RequestsTests(unittest.TestCase):
'12.34.56.78:443',
'[2001:19f0:feee::dead:beef:cafe]',
'[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
]
poisoned_hosts = [
'example.com@evil.tld',
'example.com:dr.frankenstein@evil.tld',
'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath'
+ 'example.com:80/badpath',
+ 'example.com: recovermypassword.com',
]
for host in legit_hosts:
@@ -352,6 +357,30 @@ class RequestsTests(unittest.TestCase):
self.assertRaises(Exception, lambda: request.body)
self.assertEqual(request.POST, {})
+ def test_non_ascii_POST(self):
+ payload = FakePayload(urlencode({'key': 'España'}))
+ request = WSGIRequest({
+ 'REQUEST_METHOD': 'POST',
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': 'application/x-www-form-urlencoded',
+ 'wsgi.input': payload,
+ })
+ self.assertEqual(request.POST, {'key': ['España']})
+
+ def test_alternate_charset_POST(self):
+ """
+ Test a POST with non-utf-8 payload encoding.
+ """
+ from django.utils.http import urllib_parse
+ payload = FakePayload(urllib_parse.urlencode({'key': 'España'.encode('latin-1')}))
+ request = WSGIRequest({
+ 'REQUEST_METHOD': 'POST',
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': 'application/x-www-form-urlencoded; charset=iso-8859-1',
+ 'wsgi.input': payload,
+ })
+ self.assertEqual(request.POST, {'key': ['España']})
+
def test_body_after_POST_multipart(self):
"""
Reading body after parsing multipart is not allowed
diff --git a/tests/regressiontests/select_related_onetoone/models.py b/tests/regressiontests/select_related_onetoone/models.py
index 3284defb11..d32faafbb9 100644
--- a/tests/regressiontests/select_related_onetoone/models.py
+++ b/tests/regressiontests/select_related_onetoone/models.py
@@ -51,6 +51,7 @@ class StatDetails(models.Model):
class AdvancedUserStat(UserStat):
karma = models.IntegerField()
+
class Image(models.Model):
name = models.CharField(max_length=100)
@@ -58,3 +59,44 @@ class Image(models.Model):
class Product(models.Model):
name = models.CharField(max_length=100)
image = models.OneToOneField(Image, null=True)
+
+
+@python_2_unicode_compatible
+class Parent1(models.Model):
+ name1 = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name1
+
+
+@python_2_unicode_compatible
+class Parent2(models.Model):
+ # Avoid having two "id" fields in the Child1 subclass
+ id2 = models.AutoField(primary_key=True)
+ name2 = models.CharField(max_length=50)
+
+ def __str__(self):
+ return self.name2
+
+
+@python_2_unicode_compatible
+class Child1(Parent1, Parent2):
+ value = models.IntegerField()
+
+ def __str__(self):
+ return self.name1
+
+
+@python_2_unicode_compatible
+class Child2(Parent1):
+ parent2 = models.OneToOneField(Parent2)
+ value = models.IntegerField()
+
+ def __str__(self):
+ return self.name1
+
+class Child3(Child2):
+ value3 = models.IntegerField()
+
+class Child4(Child1):
+ value4 = models.IntegerField()
diff --git a/tests/regressiontests/select_related_onetoone/tests.py b/tests/regressiontests/select_related_onetoone/tests.py
index 1373f04717..d4a1275e49 100644
--- a/tests/regressiontests/select_related_onetoone/tests.py
+++ b/tests/regressiontests/select_related_onetoone/tests.py
@@ -1,9 +1,11 @@
from __future__ import absolute_import
from django.test import TestCase
+from django.utils import unittest
from .models import (User, UserProfile, UserStat, UserStatResult, StatDetails,
- AdvancedUserStat, Image, Product)
+ AdvancedUserStat, Image, Product, Parent1, Parent2, Child1, Child2, Child3,
+ Child4)
class ReverseSelectRelatedTestCase(TestCase):
@@ -21,6 +23,14 @@ class ReverseSelectRelatedTestCase(TestCase):
advstat = AdvancedUserStat.objects.create(user=user2, posts=200, karma=5,
results=results2)
StatDetails.objects.create(base_stats=advstat, comments=250)
+ p1 = Parent1(name1="Only Parent1")
+ p1.save()
+ c1 = Child1(name1="Child1 Parent1", name2="Child1 Parent2", value=1)
+ c1.save()
+ p2 = Parent2(name2="Child2 Parent2")
+ p2.save()
+ c2 = Child2(name1="Child2 Parent1", parent2=p2, value=2)
+ c2.save()
def test_basic(self):
with self.assertNumQueries(1):
@@ -108,3 +118,93 @@ class ReverseSelectRelatedTestCase(TestCase):
image = Image.objects.select_related('product').get()
with self.assertRaises(Product.DoesNotExist):
image.product
+
+ def test_parent_only(self):
+ with self.assertNumQueries(1):
+ p = Parent1.objects.select_related('child1').get(name1="Only Parent1")
+ with self.assertNumQueries(0):
+ with self.assertRaises(Child1.DoesNotExist):
+ p.child1
+
+ def test_multiple_subclass(self):
+ with self.assertNumQueries(1):
+ p = Parent1.objects.select_related('child1').get(name1="Child1 Parent1")
+ self.assertEqual(p.child1.name2, 'Child1 Parent2')
+
+ def test_onetoone_with_subclass(self):
+ with self.assertNumQueries(1):
+ p = Parent2.objects.select_related('child2').get(name2="Child2 Parent2")
+ self.assertEqual(p.child2.name1, 'Child2 Parent1')
+
+ def test_onetoone_with_two_subclasses(self):
+ with self.assertNumQueries(1):
+ p = Parent2.objects.select_related('child2', "child2__child3").get(name2="Child2 Parent2")
+ self.assertEqual(p.child2.name1, 'Child2 Parent1')
+ with self.assertRaises(Child3.DoesNotExist):
+ p.child2.child3
+ p3 = Parent2(name2="Child3 Parent2")
+ p3.save()
+ c2 = Child3(name1="Child3 Parent1", parent2=p3, value=2, value3=3)
+ c2.save()
+ with self.assertNumQueries(1):
+ p = Parent2.objects.select_related('child2', "child2__child3").get(name2="Child3 Parent2")
+ self.assertEqual(p.child2.name1, 'Child3 Parent1')
+ self.assertEqual(p.child2.child3.value3, 3)
+ self.assertEqual(p.child2.child3.value, p.child2.value)
+ self.assertEqual(p.child2.name1, p.child2.child3.name1)
+
+ def test_multiinheritance_two_subclasses(self):
+ with self.assertNumQueries(1):
+ p = Parent1.objects.select_related('child1', 'child1__child4').get(name1="Child1 Parent1")
+ self.assertEqual(p.child1.name2, 'Child1 Parent2')
+ self.assertEqual(p.child1.name1, p.name1)
+ with self.assertRaises(Child4.DoesNotExist):
+ p.child1.child4
+ Child4(name1='n1', name2='n2', value=1, value4=4).save()
+ with self.assertNumQueries(1):
+ p = Parent2.objects.select_related('child1', 'child1__child4').get(name2="n2")
+ self.assertEqual(p.name2, 'n2')
+ self.assertEqual(p.child1.name1, 'n1')
+ self.assertEqual(p.child1.name2, p.name2)
+ self.assertEqual(p.child1.value, 1)
+ self.assertEqual(p.child1.child4.name1, p.child1.name1)
+ self.assertEqual(p.child1.child4.name2, p.child1.name2)
+ self.assertEqual(p.child1.child4.value, p.child1.value)
+ self.assertEqual(p.child1.child4.value4, 4)
+
+ @unittest.expectedFailure
+ def test_inheritance_deferred(self):
+ c = Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
+ with self.assertNumQueries(1):
+ p = Parent2.objects.select_related('child1').only(
+ 'id2', 'child1__value').get(name2="n2")
+ self.assertEqual(p.id2, c.id2)
+ self.assertEqual(p.child1.value, 1)
+ p = Parent2.objects.select_related('child1').only(
+ 'id2', 'child1__value').get(name2="n2")
+ with self.assertNumQueries(1):
+ self.assertEquals(p.name2, 'n2')
+ p = Parent2.objects.select_related('child1').only(
+ 'id2', 'child1__value').get(name2="n2")
+ with self.assertNumQueries(1):
+ self.assertEquals(p.child1.name2, 'n2')
+
+ @unittest.expectedFailure
+ def test_inheritance_deferred2(self):
+ c = Child4.objects.create(name1='n1', name2='n2', value=1, value4=4)
+ qs = Parent2.objects.select_related('child1', 'child4').only(
+ 'id2', 'child1__value', 'child1__child4__value4')
+ with self.assertNumQueries(1):
+ p = qs.get(name2="n2")
+ self.assertEqual(p.id2, c.id2)
+ self.assertEqual(p.child1.value, 1)
+ self.assertEqual(p.child1.child4.value4, 4)
+ self.assertEqual(p.child1.child4.id2, c.id2)
+ p = qs.get(name2="n2")
+ with self.assertNumQueries(1):
+ self.assertEquals(p.child1.name2, 'n2')
+ p = qs.get(name2="n2")
+ with self.assertNumQueries(1):
+ self.assertEquals(p.child1.name1, 'n1')
+ with self.assertNumQueries(1):
+ self.assertEquals(p.child1.child4.name1, 'n1')
diff --git a/tests/regressiontests/serializers_regress/models.py b/tests/regressiontests/serializers_regress/models.py
index 52223e72d8..af261f9cbc 100644
--- a/tests/regressiontests/serializers_regress/models.py
+++ b/tests/regressiontests/serializers_regress/models.py
@@ -8,7 +8,6 @@ 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
-from django.contrib.localflavor.us.models import USStateField, PhoneNumberField
# The following classes are for testing basic data
# marshalling, including NULL values, where allowed.
@@ -58,9 +57,6 @@ class GenericIPAddressData(models.Model):
class NullBooleanData(models.Model):
data = models.NullBooleanField(null=True)
-class PhoneData(models.Model):
- data = PhoneNumberField(null=True)
-
class PositiveIntegerData(models.Model):
data = models.PositiveIntegerField(null=True)
@@ -79,9 +75,6 @@ class TextData(models.Model):
class TimeData(models.Model):
data = models.TimeField(null=True)
-class USStateData(models.Model):
- data = USStateField(null=True)
-
class Tag(models.Model):
"""A tag on an item."""
data = models.SlugField()
@@ -212,9 +205,6 @@ class GenericIPAddressPKData(models.Model):
# class NullBooleanPKData(models.Model):
# data = models.NullBooleanField(primary_key=True)
-class PhonePKData(models.Model):
- data = PhoneNumberField(primary_key=True)
-
class PositiveIntegerPKData(models.Model):
data = models.PositiveIntegerField(primary_key=True)
@@ -233,9 +223,6 @@ class SmallPKData(models.Model):
# class TimePKData(models.Model):
# data = models.TimeField(primary_key=True)
-class USStatePKData(models.Model):
- data = USStateField(primary_key=True)
-
class ComplexModel(models.Model):
field1 = models.CharField(max_length=10)
field2 = models.CharField(max_length=10)
diff --git a/tests/regressiontests/serializers_regress/tests.py b/tests/regressiontests/serializers_regress/tests.py
index f4b3adaa5e..2583a1ea04 100644
--- a/tests/regressiontests/serializers_regress/tests.py
+++ b/tests/regressiontests/serializers_regress/tests.py
@@ -28,14 +28,14 @@ from django.utils.unittest import skipUnless
from .models import (BooleanData, CharData, DateData, DateTimeData, EmailData,
FileData, FilePathData, DecimalData, FloatData, IntegerData, IPAddressData,
- GenericIPAddressData, NullBooleanData, PhoneData, PositiveIntegerData,
+ GenericIPAddressData, NullBooleanData, PositiveIntegerData,
PositiveSmallIntegerData, SlugData, SmallData, TextData, TimeData,
- USStateData, GenericData, Anchor, UniqueAnchor, FKData, M2MData, O2OData,
+ GenericData, Anchor, UniqueAnchor, FKData, M2MData, O2OData,
FKSelfData, M2MSelfData, FKDataToField, FKDataToO2O, M2MIntermediateData,
Intermediate, BooleanPKData, CharPKData, EmailPKData, FilePathPKData,
DecimalPKData, FloatPKData, IntegerPKData, IPAddressPKData,
- GenericIPAddressPKData, PhonePKData, PositiveIntegerPKData,
- PositiveSmallIntegerPKData, SlugPKData, SmallPKData, USStatePKData,
+ GenericIPAddressPKData, PositiveIntegerPKData,
+ PositiveSmallIntegerPKData, SlugPKData, SmallPKData,
AutoNowDateTimeData, ModifyingSaveData, InheritAbstractModel, BaseModel,
ExplicitInheritBaseModel, InheritBaseModel, ProxyBaseModel,
ProxyProxyBaseModel, BigIntegerData, LengthModel, Tag, ComplexModel,
@@ -218,8 +218,6 @@ test_data = [
(data_obj, 100, NullBooleanData, True),
(data_obj, 101, NullBooleanData, False),
(data_obj, 102, NullBooleanData, None),
- (data_obj, 110, PhoneData, "212-634-5789"),
- (data_obj, 111, PhoneData, None),
(data_obj, 120, PositiveIntegerData, 123456789),
(data_obj, 121, PositiveIntegerData, None),
(data_obj, 130, PositiveSmallIntegerData, 12),
@@ -239,9 +237,6 @@ The end."""),
(data_obj, 162, TextData, None),
(data_obj, 170, TimeData, datetime.time(10,42,37)),
(data_obj, 171, TimeData, None),
- (data_obj, 180, USStateData, "MA"),
- (data_obj, 181, USStateData, None),
- (data_obj, 182, USStateData, ""),
(generic_obj, 200, GenericData, ['Generic Object 1', 'tag1', 'tag2']),
(generic_obj, 201, GenericData, ['Generic Object 2', 'tag2', 'tag3']),
@@ -320,7 +315,6 @@ The end."""),
(pk_obj, 695, GenericIPAddressPKData, "fe80:1424:2223:6cff:fe8a:2e8a:2151:abcd"),
# (pk_obj, 700, NullBooleanPKData, True),
# (pk_obj, 701, NullBooleanPKData, False),
- (pk_obj, 710, PhonePKData, "212-634-5789"),
(pk_obj, 720, PositiveIntegerPKData, 123456789),
(pk_obj, 730, PositiveSmallIntegerPKData, 12),
(pk_obj, 740, SlugPKData, "this-is-a-slug"),
@@ -332,7 +326,6 @@ The end."""),
# Several of them.
# The end."""),
# (pk_obj, 770, TimePKData, datetime.time(10,42,37)),
- (pk_obj, 780, USStatePKData, "MA"),
# (pk_obj, 790, XMLPKData, "<foo></foo>"),
(data_obj, 800, AutoNowDateTimeData, datetime.datetime(2006,6,16,10,42,37)),
diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
index f54e34ce28..1a7552ed11 100644
--- a/tests/regressiontests/servers/tests.py
+++ b/tests/regressiontests/servers/tests.py
@@ -15,11 +15,12 @@ from django.test import LiveServerTestCase
from django.core.servers.basehttp import WSGIServerException
from django.test.utils import override_settings
from django.utils.http import urlencode
+from django.utils._os import upath
from .models import Person
-TEST_ROOT = os.path.dirname(__file__)
+TEST_ROOT = os.path.dirname(upath(__file__))
TEST_SETTINGS = {
'MEDIA_URL': '/media/',
'MEDIA_ROOT': os.path.join(TEST_ROOT, 'media'),
diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py
index aaf8bcffcf..68f620b5c9 100644
--- a/tests/regressiontests/settings_tests/tests.py
+++ b/tests/regressiontests/settings_tests/tests.py
@@ -4,8 +4,9 @@ import warnings
from django.conf import settings, global_settings
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpRequest
-from django.test import TransactionTestCase, TestCase, signals
+from django.test import SimpleTestCase, TransactionTestCase, TestCase, signals
from django.test.utils import override_settings
+from django.utils import unittest, six
@override_settings(TEST='override')
@@ -67,11 +68,6 @@ class ClassDecoratedTestCase(ClassDecoratedTestCaseSuper):
self.fail()
-class SettingGetter(object):
- def __init__(self):
- self.test = getattr(settings, 'TEST', 'undefined')
-
-
class SettingsTests(TestCase):
def setUp(self):
self.testvalue = None
@@ -122,10 +118,20 @@ class SettingsTests(TestCase):
self.assertRaises(AttributeError, getattr, settings, 'TEST')
def test_class_decorator(self):
- self.assertEqual(SettingGetter().test, 'undefined')
- DecoratedSettingGetter = override_settings(TEST='override')(SettingGetter)
- self.assertEqual(DecoratedSettingGetter().test, 'override')
- self.assertRaises(AttributeError, getattr, settings, 'TEST')
+ # SimpleTestCase can be decorated by override_settings, but not ut.TestCase
+ class SimpleTestCaseSubclass(SimpleTestCase):
+ pass
+
+ class UnittestTestCaseSubclass(unittest.TestCase):
+ pass
+
+ decorated = override_settings(TEST='override')(SimpleTestCaseSubclass)
+ self.assertIsInstance(decorated, type)
+ self.assertTrue(issubclass(decorated, SimpleTestCase))
+
+ with six.assertRaisesRegex(self, Exception,
+ "Only subclasses of Django SimpleTestCase*"):
+ decorated = override_settings(TEST='override')(UnittestTestCaseSubclass)
def test_signal_callback_context_manager(self):
self.assertRaises(AttributeError, getattr, settings, 'TEST')
diff --git a/tests/regressiontests/staticfiles_tests/tests.py b/tests/regressiontests/staticfiles_tests/tests.py
index 7ecbccc448..90c8621d0b 100644
--- a/tests/regressiontests/staticfiles_tests/tests.py
+++ b/tests/regressiontests/staticfiles_tests/tests.py
@@ -12,18 +12,17 @@ from django.template import loader, Context
from django.conf import settings
from django.core.cache.backends.base import BaseCache
from django.core.exceptions import ImproperlyConfigured
-from django.core.files.storage import default_storage
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import override_settings
-from django.utils.encoding import smart_text
+from django.utils.encoding import force_text
from django.utils.functional import empty
-from django.utils._os import rmtree_errorhandler
+from django.utils._os import rmtree_errorhandler, upath
from django.utils import six
from django.contrib.staticfiles import finders, storage
-TEST_ROOT = os.path.dirname(__file__)
+TEST_ROOT = os.path.dirname(upath(__file__))
TEST_SETTINGS = {
'DEBUG': True,
'MEDIA_URL': '/media/',
@@ -48,10 +47,9 @@ class BaseStaticFilesTestCase(object):
Test case with a couple utility assertions.
"""
def setUp(self):
- # Clear the cached default_storage out, this is because when it first
- # gets accessed (by some other test), it evaluates settings.MEDIA_ROOT,
+ # Clear the cached staticfiles_storage out, this is because when it first
+ # gets accessed (by some other test), it evaluates settings.STATIC_ROOT,
# since we're planning on changing that we need to clear out the cache.
- default_storage._wrapped = empty
storage.staticfiles_storage._wrapped = empty
# Clear the cached staticfile finders, so they are reinitialized every
# run and pick up changes in settings.STATICFILES_DIRS.
@@ -60,9 +58,9 @@ class BaseStaticFilesTestCase(object):
testfiles_path = os.path.join(TEST_ROOT, 'apps', 'test', 'static', 'test')
# To make sure SVN doesn't hangs itself with the non-ASCII characters
# during checkout, we actually create one file dynamically.
- self._nonascii_filepath = os.path.join(testfiles_path, 'fi\u015fier.txt')
+ self._nonascii_filepath = os.path.join(testfiles_path, '\u2297.txt')
with codecs.open(self._nonascii_filepath, 'w', 'utf-8') as f:
- f.write("fi\u015fier in the app dir")
+ f.write("\u2297 in the app dir")
# And also create the stupid hidden file to dwarf the setup.py's
# package data handling.
self._hidden_filepath = os.path.join(testfiles_path, '.hidden')
@@ -79,7 +77,7 @@ class BaseStaticFilesTestCase(object):
os.unlink(self._backup_filepath)
def assertFileContains(self, filepath, text):
- self.assertIn(text, self._get_file(smart_text(filepath)),
+ self.assertIn(text, self._get_file(force_text(filepath)),
"'%s' not in '%s'" % (text, filepath))
def assertFileNotFound(self, filepath):
@@ -179,7 +177,7 @@ class TestDefaults(object):
"""
Can find a file with non-ASCII character in an app static/ directory.
"""
- self.assertFileContains('test/fişier.txt', 'fişier in the app dir')
+ self.assertFileContains('test/⊗.txt', '⊗ in the app dir')
def test_camelcase_filenames(self):
"""
@@ -197,7 +195,7 @@ class TestFindStatic(CollectionTestCase, TestDefaults):
call_command('findstatic', filepath, all=False, verbosity=0, stdout=out)
out.seek(0)
lines = [l.strip() for l in out.readlines()]
- with codecs.open(smart_text(lines[1].strip()), "r", "utf-8") as f:
+ with codecs.open(force_text(lines[1].strip()), "r", "utf-8") as f:
return f.read()
def test_all_files(self):
@@ -209,8 +207,8 @@ class TestFindStatic(CollectionTestCase, TestDefaults):
out.seek(0)
lines = [l.strip() for l in out.readlines()]
self.assertEqual(len(lines), 3) # three because there is also the "Found <file> here" line
- self.assertIn('project', lines[1])
- self.assertIn('apps', lines[2])
+ self.assertIn('project', force_text(lines[1]))
+ self.assertIn('apps', force_text(lines[2]))
class TestCollection(CollectionTestCase, TestDefaults):
@@ -709,9 +707,6 @@ class TestMiscFinder(TestCase):
"""
A few misc finder tests.
"""
- def setUp(self):
- default_storage._wrapped = empty
-
def test_get_finder(self):
self.assertIsInstance(finders.get_finder(
'django.contrib.staticfiles.finders.FileSystemFinder'),
diff --git a/tests/regressiontests/swappable_models/__init__.py b/tests/regressiontests/swappable_models/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/regressiontests/swappable_models/__init__.py
diff --git a/tests/regressiontests/swappable_models/models.py b/tests/regressiontests/swappable_models/models.py
new file mode 100644
index 0000000000..92692d4396
--- /dev/null
+++ b/tests/regressiontests/swappable_models/models.py
@@ -0,0 +1,15 @@
+from django.db import models
+
+
+class Article(models.Model):
+ title = models.CharField(max_length=100)
+ publication_date = models.DateField()
+
+ class Meta:
+ swappable = 'TEST_ARTICLE_MODEL'
+
+
+class AlternateArticle(models.Model):
+ title = models.CharField(max_length=100)
+ publication_date = models.DateField()
+ byline = models.CharField(max_length=100)
diff --git a/tests/regressiontests/swappable_models/tests.py b/tests/regressiontests/swappable_models/tests.py
new file mode 100644
index 0000000000..75644e32aa
--- /dev/null
+++ b/tests/regressiontests/swappable_models/tests.py
@@ -0,0 +1,46 @@
+from __future__ import absolute_import, unicode_literals
+
+from django.utils.six import StringIO
+
+from django.contrib.auth.models import Permission
+from django.contrib.contenttypes.models import ContentType
+from django.core import management
+from django.db.models.loading import cache
+from django.test import TestCase
+from django.test.utils import override_settings
+
+
+class SwappableModelTests(TestCase):
+ def setUp(self):
+ # This test modifies the installed apps, so we need to make sure
+ # we're not dealing with a cached app list.
+ cache._get_models_cache.clear()
+
+ def tearDown(self):
+ # By fiddling with swappable models, we alter the installed models
+ # cache, so flush it to make sure there are no side effects.
+ cache._get_models_cache.clear()
+
+ @override_settings(TEST_ARTICLE_MODEL='swappable_models.AlternateArticle')
+ def test_generated_data(self):
+ "Permissions and content types are not created for a swapped model"
+
+ # Delete all permissions and content_types
+ Permission.objects.filter(content_type__app_label='swappable_models').delete()
+ ContentType.objects.filter(app_label='swappable_models').delete()
+
+ # Re-run syncdb. This will re-build the permissions and content types.
+ new_io = StringIO()
+ management.call_command('syncdb', load_initial_data=False, interactive=False, stdout=new_io)
+
+ # Check that content types and permissions exist for the swapped model,
+ # but not for the swappable model.
+ apps_models = [(p.content_type.app_label, p.content_type.model)
+ for p in Permission.objects.all()]
+ self.assertIn(('swappable_models', 'alternatearticle'), apps_models)
+ self.assertNotIn(('swappable_models', 'article'), apps_models)
+
+ apps_models = [(ct.app_label, ct.model)
+ for ct in ContentType.objects.all()]
+ self.assertIn(('swappable_models', 'alternatearticle'), apps_models)
+ self.assertNotIn(('swappable_models', 'article'), apps_models)
diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py
index 8b4aebbf22..7ba1681fd5 100644
--- a/tests/regressiontests/templates/filters.py
+++ b/tests/regressiontests/templates/filters.py
@@ -8,7 +8,7 @@ consistent.
"""
from __future__ import unicode_literals
-from datetime import date, datetime, timedelta
+from datetime import date, datetime, time, timedelta
from django.test.utils import str_prefix
from django.utils.tzinfo import LocalTimezone, FixedOffset
@@ -356,6 +356,9 @@ def get_filter_tests():
# Timezone name
'date06': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12, tzinfo=FixedOffset(30))}, '+0030'),
'date07': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12)}, ''),
+ # Ticket 19370: Make sure |date doesn't blow up on a midnight time object
+ 'date08': (r'{{ t|date:"H:i" }}', {'t': time(0, 1)}, '00:01'),
+ 'date09': (r'{{ t|date:"H:i" }}', {'t': time(0, 0)}, '00:00'),
# Tests for #11687 and #16676
'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'),
diff --git a/tests/regressiontests/templates/loaders.py b/tests/regressiontests/templates/loaders.py
index 7fbb0841f9..b77965203f 100644
--- a/tests/regressiontests/templates/loaders.py
+++ b/tests/regressiontests/templates/loaders.py
@@ -18,6 +18,7 @@ from django.template import TemplateDoesNotExist, Context
from django.template.loaders.eggs import Loader as EggLoader
from django.template import loader
from django.utils import unittest, six
+from django.utils._os import upath
from django.utils.six import StringIO
@@ -111,9 +112,9 @@ class CachedLoader(unittest.TestCase):
def test_templatedir_caching(self):
"Check that the template directories form part of the template cache key. Refs #13573"
# Retrive a template specifying a template directory to check
- t1, name = loader.find_template('test.html', (os.path.join(os.path.dirname(__file__), 'templates', 'first'),))
+ t1, name = loader.find_template('test.html', (os.path.join(os.path.dirname(upath(__file__)), 'templates', 'first'),))
# Now retrieve the same template name, but from a different directory
- t2, name = loader.find_template('test.html', (os.path.join(os.path.dirname(__file__), 'templates', 'second'),))
+ t2, name = loader.find_template('test.html', (os.path.join(os.path.dirname(upath(__file__)), 'templates', 'second'),))
# The two templates should not have the same content
self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
@@ -123,7 +124,7 @@ class RenderToStringTest(unittest.TestCase):
def setUp(self):
self._old_TEMPLATE_DIRS = settings.TEMPLATE_DIRS
settings.TEMPLATE_DIRS = (
- os.path.join(os.path.dirname(__file__), 'templates'),
+ os.path.join(os.path.dirname(upath(__file__)), 'templates'),
)
def tearDown(self):
diff --git a/tests/regressiontests/templates/response.py b/tests/regressiontests/templates/response.py
index 93919b95cd..c4da50af6b 100644
--- a/tests/regressiontests/templates/response.py
+++ b/tests/regressiontests/templates/response.py
@@ -11,6 +11,7 @@ from django.template import Template, Context
from django.template.response import (TemplateResponse, SimpleTemplateResponse,
ContentNotRenderedError)
from django.test.utils import override_settings
+from django.utils._os import upath
def test_processor(request):
return {'processors': 'yes'}
@@ -189,9 +190,24 @@ class SimpleTemplateResponseTest(TestCase):
unpickled_response = pickle.loads(pickled_response)
repickled_response = pickle.dumps(unpickled_response)
+ def test_pickling_cookie(self):
+ response = SimpleTemplateResponse('first/test.html', {
+ 'value': 123,
+ 'fn': datetime.now,
+ })
+
+ response.cookies['key'] = 'value'
+
+ response.render()
+ pickled_response = pickle.dumps(response, pickle.HIGHEST_PROTOCOL)
+ unpickled_response = pickle.loads(pickled_response)
+
+ self.assertEqual(unpickled_response.cookies['key'].value, 'value')
+
+
@override_settings(
TEMPLATE_CONTEXT_PROCESSORS=[test_processor_name],
- TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__),'templates')),
+ TEMPLATE_DIRS=(os.path.join(os.path.dirname(upath(__file__)), 'templates')),
)
class TemplateResponseTest(TestCase):
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index c95b5bfa3c..d21434a12e 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -19,16 +19,18 @@ except ImportError: # Python 2
from urlparse import urljoin
from django import template
-from django.template import base as template_base, RequestContext, Template, Context
+from django.template import (base as template_base, Context, RequestContext,
+ Template)
from django.core import urlresolvers
from django.template import loader
from django.template.loaders import app_directories, filesystem, cached
-from django.test import RequestFactory
+from django.test import RequestFactory, TestCase
from django.test.utils import (setup_test_template_loader,
restore_template_loaders, override_settings)
from django.utils import unittest
from django.utils.encoding import python_2_unicode_compatible
from django.utils.formats import date_format
+from django.utils._os import upath
from django.utils.translation import activate, deactivate, ugettext as _
from django.utils.safestring import mark_safe
from django.utils import six
@@ -156,7 +158,7 @@ class UTF8Class:
return 'ŠĐĆŽćžšđ'
@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
-class Templates(unittest.TestCase):
+class Templates(TestCase):
def test_loaders_security(self):
ad_loader = app_directories.Loader()
@@ -364,6 +366,20 @@ class Templates(unittest.TestCase):
with self.assertRaises(urlresolvers.NoReverseMatch):
t.render(c)
+ def test_url_explicit_exception_for_old_syntax_at_run_time(self):
+ # Regression test for #19280
+ t = Template('{% url path.to.view %}') # not quoted = old syntax
+ c = Context()
+ with self.assertRaisesRegexp(urlresolvers.NoReverseMatch,
+ "The syntax changed in Django 1.5, see the docs."):
+ t.render(c)
+
+ def test_url_explicit_exception_for_old_syntax_at_compile_time(self):
+ # Regression test for #19392
+ with self.assertRaisesRegexp(template.TemplateSyntaxError,
+ "The syntax of 'url' changed in Django 1.5, see the docs."):
+ t = Template('{% url my-view %}') # not a variable = old syntax
+
@override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
def test_no_wrapped_exception(self):
"""
@@ -414,7 +430,7 @@ class Templates(unittest.TestCase):
# Set ALLOWED_INCLUDE_ROOTS so that ssi works.
old_allowed_include_roots = settings.ALLOWED_INCLUDE_ROOTS
settings.ALLOWED_INCLUDE_ROOTS = (
- os.path.dirname(os.path.abspath(__file__)),
+ os.path.dirname(os.path.abspath(upath(__file__))),
)
# Warm the URL reversing cache. This ensures we don't pay the cost
@@ -505,7 +521,7 @@ class Templates(unittest.TestCase):
def get_template_tests(self):
# SYNTAX --
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
- basedir = os.path.dirname(os.path.abspath(__file__))
+ basedir = os.path.dirname(os.path.abspath(upath(__file__)))
tests = {
### BASIC SYNTAX ################################################
@@ -1305,7 +1321,7 @@ class Templates(unittest.TestCase):
# retrieving language information
'i18n28_2': ('{% load i18n %}{% get_language_info for "de" as l %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {}, 'de: German/Deutsch bidi=False'),
'i18n29': ('{% load i18n %}{% get_language_info for LANGUAGE_CODE as l %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}', {'LANGUAGE_CODE': 'fi'}, 'fi: Finnish/suomi bidi=False'),
- 'i18n30': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, 'it: Italian/italiano bidi=False; no: Norwegian/Norsk bidi=False; '),
+ 'i18n30': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': ['it', 'no']}, 'it: Italian/italiano bidi=False; no: Norwegian/norsk bidi=False; '),
'i18n31': ('{% load i18n %}{% get_language_info_list for langcodes as langs %}{% for l in langs %}{{ l.code }}: {{ l.name }}/{{ l.name_local }} bidi={{ l.bidi }}; {% endfor %}', {'langcodes': (('sl', 'Slovenian'), ('fa', 'Persian'))}, 'sl: Slovenian/Sloven\u0161\u010dina bidi=False; fa: Persian/\u0641\u0627\u0631\u0633\u06cc bidi=True; '),
'i18n32': ('{% load i18n %}{{ "hu"|language_name }} {{ "hu"|language_name_local }} {{ "hu"|language_bidi }}', {}, 'Hungarian Magyar False'),
'i18n33': ('{% load i18n %}{{ langcode|language_name }} {{ langcode|language_name_local }} {{ langcode|language_bidi }}', {'langcode': 'nl'}, 'Dutch Nederlands False'),
@@ -1640,7 +1656,7 @@ class TemplateTagLoading(unittest.TestCase):
def setUp(self):
self.old_path = sys.path[:]
self.old_apps = settings.INSTALLED_APPS
- self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+ self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__))
self.old_tag_modules = template_base.templatetags_modules
template_base.templatetags_modules = []
diff --git a/tests/regressiontests/test_client_regress/tests.py b/tests/regressiontests/test_client_regress/tests.py
index f424321663..5ba5d3c4b3 100644
--- a/tests/regressiontests/test_client_regress/tests.py
+++ b/tests/regressiontests/test_client_regress/tests.py
@@ -16,12 +16,13 @@ from django.test import Client, TestCase
from django.test.client import encode_file, RequestFactory
from django.test.utils import ContextList, override_settings, str_prefix
from django.template.response import SimpleTemplateResponse
+from django.utils._os import upath
from django.utils.translation import ugettext_lazy
from django.http import HttpResponse
@override_settings(
- TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'templates'),)
+ TEMPLATE_DIRS=(os.path.join(os.path.dirname(upath(__file__)), 'templates'),)
)
class AssertContainsTests(TestCase):
def test_contains(self):
@@ -629,7 +630,7 @@ class TemplateExceptionTests(TestCase):
template_loader.reset()
@override_settings(
- TEMPLATE_DIRS=(os.path.join(os.path.dirname(__file__), 'bad_templates'),)
+ TEMPLATE_DIRS=(os.path.join(os.path.dirname(upath(__file__)), 'bad_templates'),)
)
def test_bad_404_template(self):
"Errors found when rendering 404 error templates are re-raised"
diff --git a/tests/regressiontests/test_utils/models.py b/tests/regressiontests/test_utils/models.py
index 4da7a07bbf..85a1031c02 100644
--- a/tests/regressiontests/test_utils/models.py
+++ b/tests/regressiontests/test_utils/models.py
@@ -1,5 +1,9 @@
from django.db import models
+from django.utils.encoding import python_2_unicode_compatible
-
+@python_2_unicode_compatible
class Person(models.Model):
name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.name
diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
index 95913b5aab..d5d49b2104 100644
--- a/tests/regressiontests/test_utils/tests.py
+++ b/tests/regressiontests/test_utils/tests.py
@@ -54,6 +54,46 @@ class AssertNumQueriesTests(TestCase):
self.assertNumQueries(2, test_func)
+class AssertQuerysetEqualTests(TestCase):
+ def setUp(self):
+ self.p1 = Person.objects.create(name='p1')
+ self.p2 = Person.objects.create(name='p2')
+
+ def test_ordered(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [repr(self.p1), repr(self.p2)]
+ )
+
+ def test_unordered(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [repr(self.p2), repr(self.p1)],
+ ordered=False
+ )
+
+ def test_transform(self):
+ self.assertQuerysetEqual(
+ Person.objects.all().order_by('name'),
+ [self.p1.pk, self.p2.pk],
+ transform=lambda x: x.pk
+ )
+
+ def test_undefined_order(self):
+ # Using an unordered queryset with more than one ordered value
+ # is an error.
+ with self.assertRaises(ValueError):
+ self.assertQuerysetEqual(
+ Person.objects.all(),
+ [repr(self.p1), repr(self.p2)]
+ )
+ # No error for one value.
+ self.assertQuerysetEqual(
+ Person.objects.filter(name='p1'),
+ [repr(self.p1)]
+ )
+
+
class AssertNumQueriesContextManagerTests(TestCase):
urls = 'regressiontests.test_utils.urls'
diff --git a/tests/regressiontests/transactions_regress/tests.py b/tests/regressiontests/transactions_regress/tests.py
index 472e2aafd9..5d1ab2c6f6 100644
--- a/tests/regressiontests/transactions_regress/tests.py
+++ b/tests/regressiontests/transactions_regress/tests.py
@@ -24,17 +24,15 @@ class TestTransactionClosing(TransactionTestCase):
def raw_sql():
"Write a record using raw sql under a commit_on_success decorator"
cursor = connection.cursor()
- cursor.execute("INSERT into transactions_regress_mod (id,fld) values (17,18)")
+ cursor.execute("INSERT into transactions_regress_mod (fld) values (18)")
raw_sql()
# Rollback so that if the decorator didn't commit, the record is unwritten
transaction.rollback()
- try:
- # Check that the record is in the DB
- obj = Mod.objects.get(pk=17)
- self.assertEqual(obj.fld, 18)
- except Mod.DoesNotExist:
- self.fail("transaction with raw sql not committed")
+ self.assertEqual(Mod.objects.count(), 1)
+ # Check that the record is in the DB
+ obj = Mod.objects.all()[0]
+ self.assertEqual(obj.fld, 18)
def test_commit_manually_enforced(self):
"""
@@ -115,19 +113,16 @@ class TestTransactionClosing(TransactionTestCase):
be committed.
"""
cursor = connection.cursor()
- cursor.execute("INSERT into transactions_regress_mod (id,fld) values (1,2)")
+ cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
transaction.rollback()
- cursor.execute("INSERT into transactions_regress_mod (id,fld) values (1,2)")
+ cursor.execute("INSERT into transactions_regress_mod (fld) values (2)")
reuse_cursor_ref()
# Rollback so that if the decorator didn't commit, the record is unwritten
transaction.rollback()
- try:
- # Check that the record is in the DB
- obj = Mod.objects.get(pk=1)
- self.assertEqual(obj.fld, 2)
- except Mod.DoesNotExist:
- self.fail("After ending a transaction, cursor use no longer sets dirty")
+ self.assertEqual(Mod.objects.count(), 1)
+ obj = Mod.objects.all()[0]
+ self.assertEqual(obj.fld, 2)
def test_failing_query_transaction_closed(self):
"""
@@ -145,7 +140,7 @@ class TestTransactionClosing(TransactionTestCase):
"Create a user in a transaction"
user = User.objects.create_user(username='system', password='iamr00t', email='root@SITENAME.com')
# Redundant, just makes sure the user id was read back from DB
- Mod.objects.create(fld=user.id)
+ Mod.objects.create(fld=user.pk)
# Create a user
create_system_user()
diff --git a/tests/regressiontests/urlpatterns_reverse/tests.py b/tests/regressiontests/urlpatterns_reverse/tests.py
index 234897d267..eb3afe8201 100644
--- a/tests/regressiontests/urlpatterns_reverse/tests.py
+++ b/tests/regressiontests/urlpatterns_reverse/tests.py
@@ -171,6 +171,18 @@ class URLPatternReverse(TestCase):
# Reversing None should raise an error, not return the last un-named view.
self.assertRaises(NoReverseMatch, reverse, None)
+ def test_prefix_braces(self):
+ self.assertEqual('/%7B%7Binvalid%7D%7D/includes/non_path_include/',
+ reverse('non_path_include', prefix='/{{invalid}}/'))
+
+ def test_prefix_parenthesis(self):
+ self.assertEqual('/bogus%29/includes/non_path_include/',
+ reverse('non_path_include', prefix='/bogus)/'))
+
+ def test_prefix_format_char(self):
+ self.assertEqual('/bump%2520map/includes/non_path_include/',
+ reverse('non_path_include', prefix='/bump%20map/'))
+
class ResolverTests(unittest.TestCase):
def test_resolver_repr(self):
"""
@@ -225,7 +237,7 @@ class ResolverTests(unittest.TestCase):
self.assertEqual(len(e.args[0]['tried']), len(url_types_names), 'Wrong number of tried URLs returned. Expected %s, got %s.' % (len(url_types_names), len(e.args[0]['tried'])))
for tried, expected in zip(e.args[0]['tried'], url_types_names):
for t, e in zip(tried, expected):
- self.assertTrue(isinstance(t, e['type']), '%s is not an instance of %s' % (t, e['type']))
+ self.assertTrue(isinstance(t, e['type']), str('%s is not an instance of %s') % (t, e['type']))
if 'name' in e:
if not e['name']:
self.assertTrue(t.name is None, 'Expected no URL name but found %s.' % t.name)
diff --git a/tests/regressiontests/utils/archive.py b/tests/regressiontests/utils/archive.py
index 5575f340f6..8861b4a577 100644
--- a/tests/regressiontests/utils/archive.py
+++ b/tests/regressiontests/utils/archive.py
@@ -4,9 +4,10 @@ import tempfile
from django.utils import unittest
from django.utils.archive import Archive, extract
+from django.utils._os import upath
-TEST_DIR = os.path.join(os.path.dirname(__file__), 'archives')
+TEST_DIR = os.path.join(os.path.dirname(upath(__file__)), 'archives')
class ArchiveTester(object):
diff --git a/tests/regressiontests/utils/feedgenerator.py b/tests/regressiontests/utils/feedgenerator.py
index b646a5997d..bcd53bb2a0 100644
--- a/tests/regressiontests/utils/feedgenerator.py
+++ b/tests/regressiontests/utils/feedgenerator.py
@@ -104,10 +104,14 @@ class FeedgeneratorTest(unittest.TestCase):
feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr')
self.assertEqual(feed.feed['feed_url'], None)
feed_content = feed.writeString('utf-8')
- self.assertNotIn('<atom:link href=', feed_content)
+ self.assertNotIn('<atom:link', feed_content)
+ self.assertNotIn('href="/feed/"', feed_content)
+ self.assertNotIn('rel="self"', feed_content)
def test_feed_with_feed_url_gets_rendered_with_atom_link(self):
feed = feedgenerator.Rss201rev2Feed('title', '/link/', 'descr', feed_url='/feed/')
self.assertEqual(feed.feed['feed_url'], '/feed/')
feed_content = feed.writeString('utf-8')
- self.assertIn('<atom:link href="/feed/" rel="self"></atom:link>', feed_content)
+ self.assertIn('<atom:link', feed_content)
+ self.assertIn('href="/feed/"', feed_content)
+ self.assertIn('rel="self"', feed_content)
diff --git a/tests/regressiontests/utils/html.py b/tests/regressiontests/utils/html.py
index 6a93dff85e..a0226c4765 100644
--- a/tests/regressiontests/utils/html.py
+++ b/tests/regressiontests/utils/html.py
@@ -65,6 +65,9 @@ class TestUtilsHtml(unittest.TestCase):
('<f', '<f'),
('</fe', '</fe'),
('<x>b<y>', 'b'),
+ ('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'),
+ ('a<p a >b</p>c', 'abc'),
+ ('d<a:b c:d>e</p>f', 'def'),
)
for value, output in items:
self.check_output(f, value, output)
diff --git a/tests/regressiontests/utils/module_loading.py b/tests/regressiontests/utils/module_loading.py
index dffb51966c..3fc92b0862 100644
--- a/tests/regressiontests/utils/module_loading.py
+++ b/tests/regressiontests/utils/module_loading.py
@@ -6,6 +6,7 @@ from zipimport import zipimporter
from django.utils import unittest
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
+from django.utils._os import upath
class DefaultLoader(unittest.TestCase):
@@ -50,7 +51,7 @@ class DefaultLoader(unittest.TestCase):
class EggLoader(unittest.TestCase):
def setUp(self):
self.old_path = sys.path[:]
- self.egg_dir = '%s/eggs' % os.path.dirname(__file__)
+ self.egg_dir = '%s/eggs' % os.path.dirname(upath(__file__))
def tearDown(self):
sys.path = self.old_path
diff --git a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo b/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo
new file mode 100644
index 0000000000..34ba691029
--- /dev/null
+++ b/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.mo
Binary files differ
diff --git a/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po b/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po
new file mode 100644
index 0000000000..88cc35e88f
--- /dev/null
+++ b/tests/regressiontests/views/locale/de/LC_MESSAGES/djangojs.po
@@ -0,0 +1,40 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: django tests\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2010-02-14 17:33+0100\n"
+"PO-Revision-Date: 2011-01-21 21:37-0300\n"
+"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
+"Language-Team: de <de@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+#: models.py:7
+msgctxt "month name"
+msgid "May"
+msgstr "Mai"
+
+#: models.py:9
+msgctxt "verb"
+msgid "May"
+msgstr "Kann"
+
+#: models.py:11
+msgid "%s item"
+msgid_plural "%s items"
+msgstr[0] "%s Element"
+msgstr[1] "%s Elemente"
+
+#: models.py:11
+msgctxt "search"
+msgid "%s result"
+msgid_plural "%s results"
+msgstr[0] "%s Resultat"
+msgstr[1] "%s Resultate"
diff --git a/tests/regressiontests/views/templates/jsi18n.html b/tests/regressiontests/views/templates/jsi18n.html
new file mode 100644
index 0000000000..d6093c8ef4
--- /dev/null
+++ b/tests/regressiontests/views/templates/jsi18n.html
@@ -0,0 +1,44 @@
+<html>
+<head>
+ <script type="text/javascript" src="/jsi18n_admin/"></script>
+</head>
+
+<body>
+ <p id="gettext">
+ <script type="text/javascript">
+ document.write(gettext("Remove"));
+ </script>
+ </p>
+
+ <p id="ngettext_sing">
+ <script type="text/javascript">
+ document.write(interpolate(ngettext("%s item", "%s items", 1), [1]));
+ </script>
+ </p>
+
+ <p id="ngettext_plur">
+ <script type="text/javascript">
+ document.write(interpolate(ngettext("%s item", "%s items", 455), [455]));
+ </script>
+ </p>
+
+ <p id="pgettext">
+ <script type="text/javascript">
+ document.write(pgettext("verb", "May"));
+ </script>
+ </p>
+
+ <p id="npgettext_sing">
+ <script type="text/javascript">
+ document.write(interpolate(npgettext("search", "%s result", "%s results", 1), [1]));
+ </script>
+ </p>
+
+ <p id="npgettext_plur">
+ <script type="text/javascript">
+ document.write(interpolate(npgettext("search", "%s result", "%s results", 455), [455]));
+ </script>
+ </p>
+
+</body>
+</html>
diff --git a/tests/regressiontests/views/tests/__init__.py b/tests/regressiontests/views/tests/__init__.py
index 12d0c59014..17f3f4562d 100644
--- a/tests/regressiontests/views/tests/__init__.py
+++ b/tests/regressiontests/views/tests/__init__.py
@@ -4,7 +4,7 @@ from .debug import (DebugViewTests, ExceptionReporterTests,
ExceptionReporterTests, PlainTextReportTests, ExceptionReporterFilterTests,
AjaxResponseExceptionReporterFilter)
from .defaults import DefaultsTests
-from .i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage
+from .i18n import JsI18NTests, I18NTests, JsI18NTestsMultiPackage, JavascriptI18nTests
from .shortcuts import ShortcutTests
from .specials import URLHandling
from .static import StaticHelperTest, StaticUtilsTests, StaticTests
diff --git a/tests/regressiontests/views/tests/debug.py b/tests/regressiontests/views/tests/debug.py
index e616d184b8..4fdaad5010 100644
--- a/tests/regressiontests/views/tests/debug.py
+++ b/tests/regressiontests/views/tests/debug.py
@@ -14,6 +14,7 @@ from django.core.urlresolvers import reverse
from django.test import TestCase, RequestFactory
from django.test.utils import (override_settings, setup_test_template_loader,
restore_template_loaders)
+from django.utils.encoding import force_text
from django.views.debug import ExceptionReporter
from .. import BrokenException, except_args
@@ -306,15 +307,16 @@ class ExceptionReportTestMixin(object):
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
# Frames vars are never shown in plain text email reports.
- self.assertNotIn('cooked_eggs', email.body)
- self.assertNotIn('scrambled', email.body)
- self.assertNotIn('sauce', email.body)
- self.assertNotIn('worcestershire', email.body)
+ body = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body)
+ self.assertNotIn('scrambled', body)
+ self.assertNotIn('sauce', body)
+ self.assertNotIn('worcestershire', body)
if check_for_POST_params:
for k, v in self.breakfast_data.items():
# All POST parameters are shown.
- self.assertIn(k, email.body)
- self.assertIn(v, email.body)
+ self.assertIn(k, body)
+ self.assertIn(v, body)
def verify_safe_email(self, view, check_for_POST_params=True):
"""
@@ -327,20 +329,21 @@ class ExceptionReportTestMixin(object):
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
# Frames vars are never shown in plain text email reports.
- self.assertNotIn('cooked_eggs', email.body)
- self.assertNotIn('scrambled', email.body)
- self.assertNotIn('sauce', email.body)
- self.assertNotIn('worcestershire', email.body)
+ body = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body)
+ self.assertNotIn('scrambled', body)
+ self.assertNotIn('sauce', body)
+ self.assertNotIn('worcestershire', body)
if check_for_POST_params:
for k, v in self.breakfast_data.items():
# All POST parameters' names are shown.
- self.assertIn(k, email.body)
+ self.assertIn(k, body)
# Non-sensitive POST parameters' values are shown.
- self.assertIn('baked-beans-value', email.body)
- self.assertIn('hash-brown-value', email.body)
+ self.assertIn('baked-beans-value', body)
+ self.assertIn('hash-brown-value', body)
# Sensitive POST parameters' values are not shown.
- self.assertNotIn('sausage-value', email.body)
- self.assertNotIn('bacon-value', email.body)
+ self.assertNotIn('sausage-value', body)
+ self.assertNotIn('bacon-value', body)
def verify_paranoid_email(self, view):
"""
@@ -353,15 +356,16 @@ class ExceptionReportTestMixin(object):
self.assertEqual(len(mail.outbox), 1)
email = mail.outbox[0]
# Frames vars are never shown in plain text email reports.
- self.assertNotIn('cooked_eggs', email.body)
- self.assertNotIn('scrambled', email.body)
- self.assertNotIn('sauce', email.body)
- self.assertNotIn('worcestershire', email.body)
+ body = force_text(email.body)
+ self.assertNotIn('cooked_eggs', body)
+ self.assertNotIn('scrambled', body)
+ self.assertNotIn('sauce', body)
+ self.assertNotIn('worcestershire', body)
for k, v in self.breakfast_data.items():
# All POST parameters' names are shown.
- self.assertIn(k, email.body)
+ self.assertIn(k, body)
# No POST parameters' values are shown.
- self.assertNotIn(v, email.body)
+ self.assertNotIn(v, body)
class ExceptionReporterFilterTests(TestCase, ExceptionReportTestMixin):
diff --git a/tests/regressiontests/views/tests/i18n.py b/tests/regressiontests/views/tests/i18n.py
index 601df6d512..b1dc8808a1 100644
--- a/tests/regressiontests/views/tests/i18n.py
+++ b/tests/regressiontests/views/tests/i18n.py
@@ -6,11 +6,18 @@ from os import path
from django.conf import settings
from django.core.urlresolvers import reverse
-from django.test import TestCase
-from django.utils import six
-from django.utils.translation import override, get_language
+from django.test import LiveServerTestCase, TestCase
+from django.test.utils import override_settings
+from django.utils import six, unittest
+from django.utils._os import upath
+from django.utils.translation import override
from django.utils.text import javascript_quote
+try:
+ from selenium.webdriver.firefox import webdriver as firefox
+except ImportError:
+ firefox = None
+
from ..urls import locale_dir
@@ -18,13 +25,28 @@ class I18NTests(TestCase):
""" Tests django views in django/views/i18n.py """
def test_setlang(self):
- """The set_language view can be used to change the session language"""
+ """
+ The set_language view can be used to change the session language.
+
+ The user is redirected to the 'next' argument if provided.
+ """
for lang_code, lang_name in settings.LANGUAGES:
post_data = dict(language=lang_code, next='/views/')
response = self.client.post('/views/i18n/setlang/', data=post_data)
self.assertRedirects(response, 'http://testserver/views/')
self.assertEqual(self.client.session['django_language'], lang_code)
+ def test_setlang_unsafe_next(self):
+ """
+ The set_language view only redirects to the 'next' argument if it is
+ "safe".
+ """
+ lang_code, lang_name = settings.LANGUAGES[0]
+ post_data = dict(language=lang_code, next='//unsafe/redirection/')
+ response = self.client.post('/views/i18n/setlang/', data=post_data)
+ self.assertEqual(response['Location'], 'http://testserver/')
+ self.assertEqual(self.client.session['django_language'], lang_code)
+
def test_setlang_reversal(self):
self.assertEqual(reverse('set_language'), '/views/i18n/setlang/')
@@ -146,9 +168,49 @@ class JsI18NTestsMultiPackage(TestCase):
def testI18NWithLocalePaths(self):
extended_locale_paths = settings.LOCALE_PATHS + (
path.join(path.dirname(
- path.dirname(path.abspath(__file__))), 'app3', 'locale'),)
+ path.dirname(path.abspath(upath(__file__)))), 'app3', 'locale'),)
with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths):
with override('es-ar'):
response = self.client.get('/views/jsi18n/')
self.assertContains(response,
javascript_quote('este texto de app3 debe ser traducido'))
+
+
+@unittest.skipUnless(firefox, 'Selenium not installed')
+class JavascriptI18nTests(LiveServerTestCase):
+ urls = 'regressiontests.views.urls'
+
+ @classmethod
+ def setUpClass(cls):
+ cls.selenium = firefox.WebDriver()
+ super(JavascriptI18nTests, cls).setUpClass()
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.selenium.quit()
+ super(JavascriptI18nTests, cls).tearDownClass()
+
+ @override_settings(LANGUAGE_CODE='de')
+ def test_javascript_gettext(self):
+ extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views']
+ with self.settings(INSTALLED_APPS=extended_apps):
+ self.selenium.get('%s%s' % (self.live_server_url, '/jsi18n_template/'))
+
+ elem = self.selenium.find_element_by_id("gettext")
+ self.assertEqual(elem.text, "Entfernen")
+ elem = self.selenium.find_element_by_id("ngettext_sing")
+ self.assertEqual(elem.text, "1 Element")
+ elem = self.selenium.find_element_by_id("ngettext_plur")
+ self.assertEqual(elem.text, "455 Elemente")
+ elem = self.selenium.find_element_by_id("pgettext")
+ self.assertEqual(elem.text, "Kann")
+ elem = self.selenium.find_element_by_id("npgettext_sing")
+ self.assertEqual(elem.text, "1 Resultat")
+ elem = self.selenium.find_element_by_id("npgettext_plur")
+ self.assertEqual(elem.text, "455 Resultate")
+
+ def test_escaping(self):
+ extended_apps = list(settings.INSTALLED_APPS) + ['regressiontests.views']
+ with self.settings(INSTALLED_APPS=extended_apps):
+ response = self.client.get('%s%s' % (self.live_server_url, '/jsi18n_admin/'))
+ self.assertContains(response, '\\x04')
diff --git a/tests/regressiontests/views/urls.py b/tests/regressiontests/views/urls.py
index 90d2382f71..2c06557ae9 100644
--- a/tests/regressiontests/views/urls.py
+++ b/tests/regressiontests/views/urls.py
@@ -4,11 +4,12 @@ from __future__ import absolute_import
from os import path
from django.conf.urls import patterns, url, include
+from django.utils._os import upath
from . import views
-base_dir = path.dirname(path.abspath(__file__))
+base_dir = path.dirname(path.abspath(upath(__file__)))
media_dir = path.join(base_dir, 'media')
locale_dir = path.join(base_dir, 'locale')
@@ -32,6 +33,11 @@ js_info_dict_multi_packages2 = {
'packages': ('regressiontests.views.app3', 'regressiontests.views.app4'),
}
+js_info_dict_admin = {
+ 'domain': 'djangojs',
+ 'packages': ('django.contrib.admin', 'regressiontests.views'),
+}
+
urlpatterns = patterns('',
(r'^$', views.index_page),
@@ -51,6 +57,8 @@ urlpatterns = patterns('',
(r'^jsi18n_english_translation/$', 'django.views.i18n.javascript_catalog', js_info_dict_english_translation),
(r'^jsi18n_multi_packages1/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages1),
(r'^jsi18n_multi_packages2/$', 'django.views.i18n.javascript_catalog', js_info_dict_multi_packages2),
+ (r'^jsi18n_admin/$', 'django.views.i18n.javascript_catalog', js_info_dict_admin),
+ (r'^jsi18n_template/$', views.jsi18n),
# Static views
(r'^site_media/(?P<path>.*)$', 'django.views.static.serve', {'document_root': media_dir}),
diff --git a/tests/regressiontests/views/views.py b/tests/regressiontests/views/views.py
index 2836d1bdde..ed9d61144a 100644
--- a/tests/regressiontests/views/views.py
+++ b/tests/regressiontests/views/views.py
@@ -51,6 +51,9 @@ def template_exception(request, n):
return render_to_response('debug/template_exception.html',
{'arg': except_args[int(n)]})
+def jsi18n(request):
+ return render_to_response('jsi18n.html')
+
# Some views to exercise the shortcuts
def render_to_response_view(request):
diff --git a/tests/runtests.py b/tests/runtests.py
index a81fee6858..8c56e273b5 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -7,6 +7,7 @@ import tempfile
import warnings
from django import contrib
+from django.utils._os import upath
from django.utils import six
# databrowse is deprecated, but we still want to run its tests
@@ -19,8 +20,8 @@ REGRESSION_TESTS_DIR_NAME = 'regressiontests'
TEST_TEMPLATE_DIR = 'templates'
-RUNTESTS_DIR = os.path.dirname(__file__)
-CONTRIB_DIR = os.path.dirname(contrib.__file__)
+RUNTESTS_DIR = os.path.dirname(upath(__file__))
+CONTRIB_DIR = os.path.dirname(upath(contrib.__file__))
MODEL_TEST_DIR = os.path.join(RUNTESTS_DIR, MODEL_TESTS_DIR_NAME)
REGRESSION_TEST_DIR = os.path.join(RUNTESTS_DIR, REGRESSION_TESTS_DIR_NAME)
TEMP_DIR = tempfile.mkdtemp(prefix='django_')
@@ -192,7 +193,7 @@ def bisect_tests(bisection_label, options, test_labels):
pass
subprocess_args = [
- sys.executable, __file__, '--settings=%s' % options.settings]
+ sys.executable, upath(__file__), '--settings=%s' % options.settings]
if options.failfast:
subprocess_args.append('--failfast')
if options.verbosity:
@@ -253,7 +254,7 @@ def paired_tests(paired_test, options, test_labels):
pass
subprocess_args = [
- sys.executable, __file__, '--settings=%s' % options.settings]
+ sys.executable, upath(__file__), '--settings=%s' % options.settings]
if options.failfast:
subprocess_args.append('--failfast')
if options.verbosity:
@@ -277,7 +278,7 @@ if __name__ == "__main__":
usage = "%prog [options] [module module module ...]"
parser = OptionParser(usage=usage)
parser.add_option(
- '-v','--verbosity', action='store', dest='verbosity', default='1',
+ '-v', '--verbosity', action='store', dest='verbosity', default='1',
type='choice', choices=['0', '1', '2', '3'],
help='Verbosity level; 0=minimal output, 1=normal output, 2=all '
'output')