summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2013-06-07 11:15:34 +0100
committerAndrew Godwin <andrew@aeracode.org>2013-06-07 11:15:34 +0100
commit3c296382b8dea5de7f4e1e11b66bd7cecaf2ee51 (patch)
tree0ca12593be82971691ffca01a836d00d3fcb3bd4 /tests
parent7609e0b42e0014a6ad0adf9dafc7018cb268070e (diff)
parent357d62d9f2972bf1bc21e5835c12c849143e06af (diff)
Merge remote-tracking branch 'core/master' into schema-alteration
Conflicts: django/db/models/fields/related.py
Diffstat (limited to 'tests')
-rw-r--r--tests/admin_changelist/admin.py5
-rw-r--r--tests/admin_changelist/tests.py70
-rw-r--r--tests/admin_docs/__init__.py (renamed from tests/special_headers/__init__.py)0
-rw-r--r--tests/admin_docs/fixtures/data.xml (renamed from tests/special_headers/fixtures/data.xml)3
-rw-r--r--tests/admin_docs/models.py0
-rw-r--r--tests/admin_docs/tests.py45
-rw-r--r--tests/admin_docs/urls.py11
-rw-r--r--tests/admin_docs/views.py (renamed from tests/special_headers/views.py)10
-rw-r--r--tests/admin_inlines/admin.py17
-rw-r--r--tests/admin_inlines/models.py6
-rw-r--r--tests/admin_inlines/tests.py20
-rw-r--r--tests/admin_scripts/tests.py39
-rw-r--r--tests/admin_util/tests.py37
-rw-r--r--tests/admin_validation/tests.py80
-rw-r--r--tests/admin_views/admin.py37
-rw-r--r--tests/admin_views/models.py9
-rw-r--r--tests/admin_views/tests.py136
-rw-r--r--tests/admin_widgets/tests.py12
-rw-r--r--tests/aggregation_regress/tests.py8
-rw-r--r--tests/backends/tests.py11
-rw-r--r--tests/base/models.py4
-rw-r--r--tests/basic/models.py8
-rw-r--r--tests/basic/tests.py34
-rw-r--r--tests/cache/tests.py44
-rw-r--r--tests/commands_sql/models.py6
-rw-r--r--tests/commands_sql/tests.py36
-rw-r--r--tests/comment_tests/tests/test_comment_form.py2
-rw-r--r--tests/comment_tests/tests/test_templatetags.py2
-rw-r--r--tests/csrf_tests/tests.py64
-rw-r--r--tests/custom_managers/tests.py4
-rw-r--r--tests/datatypes/tests.py6
-rw-r--r--tests/decorators/tests.py10
-rw-r--r--tests/defaultfilters/tests.py94
-rw-r--r--tests/defer_regress/models.py19
-rw-r--r--tests/defer_regress/tests.py122
-rw-r--r--tests/dispatch/tests/test_dispatcher.py2
-rw-r--r--tests/field_defaults/tests.py2
-rw-r--r--tests/field_subclassing/tests.py10
-rw-r--r--tests/file_storage/tests.py6
-rw-r--r--tests/fixtures/tests.py68
-rw-r--r--tests/fixtures_model_package/tests.py9
-rw-r--r--tests/fixtures_regress/tests.py18
-rw-r--r--tests/foreign_object/tests.py5
-rw-r--r--tests/forms_tests/tests/test_extra.py8
-rw-r--r--tests/forms_tests/tests/test_fields.py9
-rw-r--r--tests/forms_tests/tests/test_forms.py19
-rw-r--r--tests/forms_tests/tests/test_formsets.py56
-rw-r--r--tests/forms_tests/tests/test_regressions.py3
-rw-r--r--tests/forms_tests/tests/tests.py42
-rw-r--r--tests/generic_relations/models.py19
-rw-r--r--tests/generic_relations/tests.py140
-rw-r--r--tests/generic_relations_regress/models.py33
-rw-r--r--tests/generic_relations_regress/tests.py103
-rw-r--r--tests/generic_views/test_base.py43
-rw-r--r--tests/generic_views/test_detail.py2
-rw-r--r--tests/generic_views/test_edit.py14
-rw-r--r--tests/generic_views/test_list.py2
-rw-r--r--tests/generic_views/views.py16
-rw-r--r--tests/get_earliest_or_latest/tests.py31
-rw-r--r--tests/get_or_create/models.py14
-rw-r--r--tests/get_or_create/tests.py56
-rw-r--r--tests/handlers/tests.py9
-rw-r--r--tests/handlers/urls.py1
-rw-r--r--tests/handlers/views.py8
-rw-r--r--tests/http_utils/tests.py27
-rw-r--r--tests/i18n/__init__.py18
-rw-r--r--tests/i18n/commands/extraction.py9
-rw-r--r--tests/i18n/commands/templates/xxx_ignored.html2
-rw-r--r--tests/i18n/commands/tests.py24
-rw-r--r--tests/i18n/contenttypes/tests.py4
-rw-r--r--tests/i18n/patterns/tests.py4
-rw-r--r--tests/i18n/patterns/urls/default.py1
-rw-r--r--tests/i18n/patterns/urls/included.py9
-rw-r--r--tests/i18n/tests.py148
-rw-r--r--tests/i18n/urls.py3
-rw-r--r--tests/invalid_models/invalid_models/models.py6
-rw-r--r--tests/logging_tests/tests.py26
-rw-r--r--tests/logging_tests/urls.py10
-rw-r--r--tests/logging_tests/views.py11
-rw-r--r--tests/mail/tests.py12
-rw-r--r--tests/middleware/tests.py33
-rw-r--r--tests/model_fields/tests.py14
-rw-r--r--tests/model_forms/models.py16
-rw-r--r--tests/model_forms/tests.py70
-rw-r--r--tests/model_forms_regress/tests.py35
-rw-r--r--tests/model_formsets_regress/tests.py23
-rw-r--r--tests/model_validation/__init__.py0
-rw-r--r--tests/model_validation/models.py27
-rw-r--r--tests/model_validation/tests.py13
-rw-r--r--tests/modeladmin/tests.py267
-rw-r--r--tests/multiple_database/tests.py27
-rw-r--r--tests/one_to_one/tests.py3
-rw-r--r--tests/pagination/tests.py3
-rw-r--r--tests/prefetch_related/models.py20
-rw-r--r--tests/prefetch_related/tests.py35
-rw-r--r--tests/queries/tests.py106
-rw-r--r--tests/requests/tests.py45
-rw-r--r--tests/responses/__init__.py0
-rw-r--r--tests/responses/models.py0
-rw-r--r--tests/responses/tests.py15
-rw-r--r--tests/select_related/tests.py4
-rw-r--r--tests/special_headers/models.py5
-rw-r--r--tests/special_headers/templates/special_headers/article_detail.html1
-rw-r--r--tests/special_headers/tests.py62
-rw-r--r--tests/special_headers/urls.py13
-rw-r--r--tests/string_lookup/tests.py4
-rw-r--r--tests/syncdb_signals/__init__.py0
-rw-r--r--tests/syncdb_signals/models.py11
-rw-r--r--tests/syncdb_signals/tests.py74
-rw-r--r--tests/template_tests/filters.py59
-rw-r--r--tests/template_tests/templates/included_base.html3
-rw-r--r--tests/template_tests/templates/included_content.html11
-rw-r--r--tests/template_tests/tests.py20
-rw-r--r--tests/test_client/urls.py1
-rw-r--r--tests/test_client/views.py46
-rw-r--r--tests/test_client_regress/models.py12
-rw-r--r--tests/test_client_regress/tests.py288
-rw-r--r--tests/test_client_regress/views.py7
-rw-r--r--tests/test_runner/test_discover_runner.py37
-rw-r--r--tests/transactions/tests.py14
-rw-r--r--tests/transactions_regress/tests.py4
-rw-r--r--tests/unmanaged_models/tests.py8
-rw-r--r--tests/urlpatterns_reverse/tests.py10
-rw-r--r--tests/urlpatterns_reverse/urls_error_handlers.py1
-rw-r--r--tests/urlpatterns_reverse/urls_error_handlers_callables.py1
-rw-r--r--tests/utils_tests/test_baseconv.py4
-rw-r--r--tests/utils_tests/test_html.py11
-rw-r--r--tests/utils_tests/test_safestring.py53
-rw-r--r--tests/utils_tests/test_simplelazyobject.py24
-rw-r--r--tests/utils_tests/test_timesince.py71
-rw-r--r--tests/validation/models.py2
-rw-r--r--tests/validation/tests.py16
-rw-r--r--tests/view_tests/tests/test_debug.py60
-rw-r--r--tests/view_tests/tests/test_static.py2
-rw-r--r--tests/view_tests/urls.py2
-rw-r--r--tests/view_tests/views.py4
136 files changed, 2951 insertions, 819 deletions
diff --git a/tests/admin_changelist/admin.py b/tests/admin_changelist/admin.py
index 8387ba77a1..175b1972c9 100644
--- a/tests/admin_changelist/admin.py
+++ b/tests/admin_changelist/admin.py
@@ -67,6 +67,11 @@ class ChordsBandAdmin(admin.ModelAdmin):
list_filter = ['members']
+class InvitationAdmin(admin.ModelAdmin):
+ list_display = ('band', 'player')
+ list_select_related = ('player',)
+
+
class DynamicListDisplayChildAdmin(admin.ModelAdmin):
list_display = ('parent', 'name', 'age')
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index fd433967d3..7f3f0d162e 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -4,6 +4,7 @@ import datetime
from django.contrib import admin
from django.contrib.admin.options import IncorrectLookupParameters
+from django.contrib.admin.templatetags.admin_list import pagination
from django.contrib.admin.views.main import ChangeList, SEARCH_VAR, ALL_VAR
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
@@ -17,7 +18,7 @@ from .admin import (ChildAdmin, QuartetAdmin, BandAdmin, ChordsBandAdmin,
GroupAdmin, ParentAdmin, DynamicListDisplayChildAdmin,
DynamicListDisplayLinksChildAdmin, CustomPaginationAdmin,
FilteredChildAdmin, CustomPaginator, site as custom_site,
- SwallowAdmin, DynamicListFilterChildAdmin)
+ SwallowAdmin, DynamicListFilterChildAdmin, InvitationAdmin)
from .models import (Event, Child, Parent, Genre, Band, Musician, Group,
Quartet, Membership, ChordsMusician, ChordsBand, Invitation, Swallow,
UnorderedObject, OrderedObject, CustomIdUser)
@@ -45,9 +46,32 @@ class ChangeListTests(TestCase):
m = ChildAdmin(Child, admin.site)
request = self.factory.get('/child/')
cl = ChangeList(request, Child, m.list_display, m.list_display_links,
- m.list_filter, m.date_hierarchy, m.search_fields,
- m.list_select_related, m.list_per_page, m.list_max_show_all, m.list_editable, m)
- self.assertEqual(cl.queryset.query.select_related, {'parent': {'name': {}}})
+ m.list_filter, m.date_hierarchy, m.search_fields,
+ m.list_select_related, m.list_per_page,
+ m.list_max_show_all, m.list_editable, m)
+ self.assertEqual(cl.queryset.query.select_related, {
+ 'parent': {'name': {}}
+ })
+
+ def test_select_related_as_tuple(self):
+ ia = InvitationAdmin(Invitation, admin.site)
+ request = self.factory.get('/invitation/')
+ cl = ChangeList(request, Child, ia.list_display, ia.list_display_links,
+ ia.list_filter, ia.date_hierarchy, ia.search_fields,
+ ia.list_select_related, ia.list_per_page,
+ ia.list_max_show_all, ia.list_editable, ia)
+ self.assertEqual(cl.queryset.query.select_related, {'player': {}})
+
+ def test_select_related_as_empty_tuple(self):
+ ia = InvitationAdmin(Invitation, admin.site)
+ ia.list_select_related = ()
+ request = self.factory.get('/invitation/')
+ cl = ChangeList(request, Child, ia.list_display, ia.list_display_links,
+ ia.list_filter, ia.date_hierarchy, ia.search_fields,
+ ia.list_select_related, ia.list_per_page,
+ ia.list_max_show_all, ia.list_editable, ia)
+ self.assertEqual(cl.queryset.query.select_related, False)
+
def test_result_list_empty_changelist_value(self):
"""
@@ -564,6 +588,44 @@ class ChangeListTests(TestCase):
response = m.changelist_view(request)
self.assertEqual(response.context_data['cl'].list_filter, ('parent', 'name', 'age'))
+ def test_pagination_page_range(self):
+ """
+ Regression tests for ticket #15653: ensure the number of pages
+ generated for changelist views are correct.
+ """
+ # instantiating and setting up ChangeList object
+ m = GroupAdmin(Group, admin.site)
+ request = self.factory.get('/group/')
+ cl = ChangeList(request, Group, m.list_display,
+ m.list_display_links, m.list_filter, m.date_hierarchy,
+ m.search_fields, m.list_select_related, m.list_per_page,
+ m.list_max_show_all, m.list_editable, m)
+ per_page = cl.list_per_page = 10
+
+ for page_num, objects_count, expected_page_range in [
+ (0, per_page, []),
+ (0, per_page * 2, list(range(2))),
+ (5, per_page * 11, list(range(11))),
+ (5, per_page * 12, [0, 1, 2, 3, 4, 5, 6, 7, 8, '.', 10, 11]),
+ (6, per_page * 12, [0, 1, '.', 3, 4, 5, 6, 7, 8, 9, 10, 11]),
+ (6, per_page * 13, [0, 1, '.', 3, 4, 5, 6, 7, 8, 9, '.', 11, 12]),
+ ]:
+ # assuming we have exactly `objects_count` objects
+ Group.objects.all().delete()
+ for i in range(objects_count):
+ Group.objects.create(name='test band')
+
+ # setting page number and calculating page range
+ cl.page_num = page_num
+ cl.get_results(request)
+ real_page_range = pagination(cl)['page_range']
+
+ self.assertListEqual(
+ expected_page_range,
+ list(real_page_range),
+ )
+
+
class AdminLogNodeTestCase(TestCase):
def test_get_admin_log_templatetag_custom_user(self):
diff --git a/tests/special_headers/__init__.py b/tests/admin_docs/__init__.py
index e69de29bb2..e69de29bb2 100644
--- a/tests/special_headers/__init__.py
+++ b/tests/admin_docs/__init__.py
diff --git a/tests/special_headers/fixtures/data.xml b/tests/admin_docs/fixtures/data.xml
index 7e60d45199..aba8f4aace 100644
--- a/tests/special_headers/fixtures/data.xml
+++ b/tests/admin_docs/fixtures/data.xml
@@ -14,7 +14,4 @@
<field to="auth.group" name="groups" rel="ManyToManyRel"></field>
<field to="auth.permission" name="user_permissions" rel="ManyToManyRel"></field>
</object>
- <object pk="1" model="special_headers.article">
- <field type="TextField" name="text">text</field>
- </object>
</django-objects>
diff --git a/tests/admin_docs/models.py b/tests/admin_docs/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/admin_docs/models.py
diff --git a/tests/admin_docs/tests.py b/tests/admin_docs/tests.py
new file mode 100644
index 0000000000..aeb527c7b9
--- /dev/null
+++ b/tests/admin_docs/tests.py
@@ -0,0 +1,45 @@
+from django.contrib.auth.models import User
+from django.test import TestCase
+from django.test.utils import override_settings
+
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class XViewMiddlewareTest(TestCase):
+ fixtures = ['data.xml']
+ urls = 'admin_docs.urls'
+
+ def test_xview_func(self):
+ user = User.objects.get(username='super')
+ response = self.client.head('/xview/func/')
+ self.assertFalse('X-View' in response)
+ self.client.login(username='super', password='secret')
+ response = self.client.head('/xview/func/')
+ self.assertTrue('X-View' in response)
+ self.assertEqual(response['X-View'], 'admin_docs.views.xview')
+ user.is_staff = False
+ user.save()
+ response = self.client.head('/xview/func/')
+ self.assertFalse('X-View' in response)
+ user.is_staff = True
+ user.is_active = False
+ user.save()
+ response = self.client.head('/xview/func/')
+ self.assertFalse('X-View' in response)
+
+ def test_xview_class(self):
+ user = User.objects.get(username='super')
+ response = self.client.head('/xview/class/')
+ self.assertFalse('X-View' in response)
+ self.client.login(username='super', password='secret')
+ response = self.client.head('/xview/class/')
+ self.assertTrue('X-View' in response)
+ self.assertEqual(response['X-View'], 'admin_docs.views.XViewClass')
+ user.is_staff = False
+ user.save()
+ response = self.client.head('/xview/class/')
+ self.assertFalse('X-View' in response)
+ user.is_staff = True
+ user.is_active = False
+ user.save()
+ response = self.client.head('/xview/class/')
+ self.assertFalse('X-View' in response)
diff --git a/tests/admin_docs/urls.py b/tests/admin_docs/urls.py
new file mode 100644
index 0000000000..3c3a8fe5d8
--- /dev/null
+++ b/tests/admin_docs/urls.py
@@ -0,0 +1,11 @@
+# coding: utf-8
+from __future__ import absolute_import
+
+from django.conf.urls import patterns
+
+from . import views
+
+urlpatterns = patterns('',
+ (r'^xview/func/$', views.xview_dec(views.xview)),
+ (r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())),
+)
diff --git a/tests/special_headers/views.py b/tests/admin_docs/views.py
index a8bbd6542e..e47177c37f 100644
--- a/tests/special_headers/views.py
+++ b/tests/admin_docs/views.py
@@ -1,21 +1,13 @@
-from django.core.xheaders import populate_xheaders
from django.http import HttpResponse
from django.utils.decorators import decorator_from_middleware
from django.views.generic import View
-from django.middleware.doc import XViewMiddleware
-
-from .models import Article
+from django.contrib.admindocs.middleware import XViewMiddleware
xview_dec = decorator_from_middleware(XViewMiddleware)
def xview(request):
return HttpResponse()
-def xview_xheaders(request, object_id):
- response = HttpResponse()
- populate_xheaders(request, response, Article, 1)
- return response
-
class XViewClass(View):
def get(self, request):
return HttpResponse()
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 44671d0ac4..2f88248ca4 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -129,6 +129,22 @@ class ChildModel1Inline(admin.TabularInline):
class ChildModel2Inline(admin.StackedInline):
model = ChildModel2
+# admin for #19425 and #18388
+class BinaryTreeAdmin(admin.TabularInline):
+ model = BinaryTree
+
+ def get_extra(self, request, obj=None, **kwargs):
+ extra = 2
+ if obj:
+ return extra - obj.binarytree_set.count()
+ return extra
+
+ def get_max_num(self, request, obj=None, **kwargs):
+ max_num = 3
+ if obj:
+ return max_num - obj.binarytree_set.count()
+ return max_num
+
# admin for #19524
class SightingInline(admin.TabularInline):
model = Sighting
@@ -150,4 +166,5 @@ site.register(Author, AuthorAdmin)
site.register(CapoFamiglia, inlines=[ConsigliereInline, SottoCapoInline, ReadOnlyInlineInline])
site.register(ProfileCollection, inlines=[ProfileInline])
site.register(ParentModelWithCustomPk, inlines=[ChildModel1Inline, ChildModel2Inline])
+site.register(BinaryTree, inlines=[BinaryTreeAdmin])
site.register(ExtraTerrestrial, inlines=[SightingInline])
diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py
index 82c1c3f078..d4ba0ab6bc 100644
--- a/tests/admin_inlines/models.py
+++ b/tests/admin_inlines/models.py
@@ -183,6 +183,12 @@ class ChildModel2(models.Model):
def get_absolute_url(self):
return '/child_model2/'
+
+# Models for #19425
+class BinaryTree(models.Model):
+ name = models.CharField(max_length=100)
+ parent = models.ForeignKey('self', null=True, blank=True)
+
# Models for #19524
class LifeForm(models.Model):
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 714a2f1c61..78ccf074d5 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -12,7 +12,7 @@ from .admin import InnerInline, TitleInline, site
from .models import (Holder, Inner, Holder2, Inner2, Holder3, Inner3, Person,
OutfitItem, Fashionista, Teacher, Parent, Child, Author, Book, Profile,
ProfileCollection, ParentModelWithCustomPk, ChildModel1, ChildModel2,
- Sighting, Title, Novel, Chapter, FootNote)
+ Sighting, Title, Novel, Chapter, FootNote, BinaryTree)
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
@@ -193,6 +193,24 @@ class TestInline(TestCase):
self.assertEqual(response.status_code, 302)
self.assertEqual(Sighting.objects.filter(et__name='Martian').count(), 1)
+ def test_custom_get_extra_form(self):
+ bt_head = BinaryTree.objects.create(name="Tree Head")
+ bt_child = BinaryTree.objects.create(name="First Child", parent=bt_head)
+
+ # The maximum number of forms should respect 'get_max_num' on the
+ # ModelAdmin
+ max_forms_input = '<input id="id_binarytree_set-MAX_NUM_FORMS" name="binarytree_set-MAX_NUM_FORMS" type="hidden" value="%d" />'
+ # The total number of forms will remain the same in either case
+ total_forms_hidden = '<input id="id_binarytree_set-TOTAL_FORMS" name="binarytree_set-TOTAL_FORMS" type="hidden" value="2" />'
+
+ response = self.client.get('/admin/admin_inlines/binarytree/add/')
+ self.assertContains(response, max_forms_input % 3)
+ self.assertContains(response, total_forms_hidden)
+
+ response = self.client.get("/admin/admin_inlines/binarytree/%d/" % bt_head.id)
+ self.assertContains(response, max_forms_input % 2)
+ self.assertContains(response, total_forms_hidden)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class TestInlineMedia(TestCase):
diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py
index c8986770f3..9e6b1f557b 100644
--- a/tests/admin_scripts/tests.py
+++ b/tests/admin_scripts/tests.py
@@ -1305,13 +1305,15 @@ class CommandTypes(AdminScriptTestCase):
sys.stderr = err = StringIO()
try:
command.execute = lambda args: args # This will trigger TypeError
- with self.assertRaises(SystemExit):
+
+ # If the Exception is not CommandError it should always
+ # raise the original exception.
+ with self.assertRaises(TypeError):
command.run_from_argv(['', ''])
- err_message = err.getvalue()
- # Exceptions other than CommandError automatically output the traceback
- self.assertIn("Traceback", err_message)
- self.assertIn("TypeError", err_message)
+ # If the Exception is CommandError and --traceback is not present
+ # this command should raise a SystemExit and don't print any
+ # traceback to the stderr.
command.execute = raise_command_error
err.truncate(0)
with self.assertRaises(SystemExit):
@@ -1320,12 +1322,12 @@ class CommandTypes(AdminScriptTestCase):
self.assertNotIn("Traceback", err_message)
self.assertIn("CommandError", err_message)
+ # If the Exception is CommandError and --traceback is present
+ # this command should raise the original CommandError as if it
+ # were not a CommandError.
err.truncate(0)
- with self.assertRaises(SystemExit):
+ with self.assertRaises(CommandError):
command.run_from_argv(['', '', '--traceback'])
- err_message = err.getvalue()
- self.assertIn("Traceback (most recent call last)", err_message)
- self.assertIn("CommandError", err_message)
finally:
sys.stderr = old_stderr
@@ -1680,3 +1682,22 @@ class DiffSettings(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "### STATIC_URL = None")
+
+class Dumpdata(AdminScriptTestCase):
+ """Tests for dumpdata management command."""
+
+ def setUp(self):
+ self.write_settings('settings.py')
+
+ def tearDown(self):
+ self.remove_settings('settings.py')
+
+ def test_pks_parsing(self):
+ """Regression for #20509
+
+ Test would raise an exception rather than printing an error message.
+ """
+ args = ['dumpdata', '--pks=1']
+ out, err = self.run_manage(args)
+ self.assertOutput(err, "You can only use --pks option with one model")
+ self.assertNoOutput(out)
diff --git a/tests/admin_util/tests.py b/tests/admin_util/tests.py
index 7898f200b5..4a9a203f50 100644
--- a/tests/admin_util/tests.py
+++ b/tests/admin_util/tests.py
@@ -11,8 +11,7 @@ from django.contrib.admin.views.main import EMPTY_CHANGELIST_VALUE
from django.contrib.sites.models import Site
from django.db import models, DEFAULT_DB_ALIAS
from django import forms
-from django.test import TestCase
-from django.utils import unittest
+from django.test import SimpleTestCase, TestCase
from django.utils.formats import localize
from django.utils.safestring import mark_safe
from django.utils import six
@@ -82,7 +81,7 @@ class NestedObjectsTests(TestCase):
# One for Location, one for Guest, and no query for EventGuide
n.collect(objs)
-class UtilTests(unittest.TestCase):
+class UtilTests(SimpleTestCase):
def test_values_from_lookup_field(self):
"""
Regression test for #12654: lookup_field
@@ -151,7 +150,7 @@ class UtilTests(unittest.TestCase):
# handling.
display_value = display_for_field(None, models.NullBooleanField())
expected = '<img src="%sadmin/img/icon-unknown.gif" alt="None" />' % settings.STATIC_URL
- self.assertEqual(display_value, expected)
+ self.assertHTMLEqual(display_value, expected)
display_value = display_for_field(None, models.DecimalField())
self.assertEqual(display_value, EMPTY_CHANGELIST_VALUE)
@@ -236,6 +235,20 @@ class UtilTests(unittest.TestCase):
("not Really the Model", MockModelAdmin.test_from_model)
)
+ def test_label_for_property(self):
+ # NOTE: cannot use @property decorator, because of
+ # AttributeError: 'property' object has no attribute 'short_description'
+ class MockModelAdmin(object):
+ def my_property(self):
+ return "this if from property"
+ my_property.short_description = 'property short description'
+ test_from_property = property(my_property)
+
+ self.assertEqual(
+ label_for_field("test_from_property", Article, model_admin=MockModelAdmin),
+ 'property short description'
+ )
+
def test_related_name(self):
"""
Regression test for #13963
@@ -285,10 +298,10 @@ class UtilTests(unittest.TestCase):
cb = forms.BooleanField(label=mark_safe('<i>cb</i>'))
form = MyForm()
- self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
- '<label for="id_text" class="required inline"><i>text</i>:</label>')
- self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
+ self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+ '<label for="id_text" class="required inline"><i>text</i>:</label>')
+ self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+ '<label for="id_cb" class="vCheckboxLabel required inline"><i>cb</i></label>')
# normal strings needs to be escaped
class MyForm(forms.Form):
@@ -296,10 +309,10 @@ class UtilTests(unittest.TestCase):
cb = forms.BooleanField(label='&cb')
form = MyForm()
- self.assertEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
- '<label for="id_text" class="required inline">&amp;text:</label>')
- self.assertEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
- '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>')
+ self.assertHTMLEqual(helpers.AdminField(form, 'text', is_first=False).label_tag(),
+ '<label for="id_text" class="required inline">&amp;text:</label>')
+ self.assertHTMLEqual(helpers.AdminField(form, 'cb', is_first=False).label_tag(),
+ '<label for="id_cb" class="vCheckboxLabel required inline">&amp;cb</label>')
def test_flatten_fieldsets(self):
"""
diff --git a/tests/admin_validation/tests.py b/tests/admin_validation/tests.py
index 16f73c6390..5eee3e7105 100644
--- a/tests/admin_validation/tests.py
+++ b/tests/admin_validation/tests.py
@@ -2,7 +2,6 @@ from __future__ import absolute_import
from django import forms
from django.contrib import admin
-from django.contrib.admin.validation import validate, validate_inline
from django.core.exceptions import ImproperlyConfigured
from django.test import TestCase
@@ -38,13 +37,13 @@ class ValidationTestCase(TestCase):
"fields": ["title", "original_release"],
}),
]
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_custom_modelforms_with_fields_fieldsets(self):
"""
# Regression test for #8027: custom ModelForms with fields/fieldsets
"""
- validate(ValidFields, Song)
+ ValidFields.validate(Song)
def test_custom_get_form_with_fieldsets(self):
"""
@@ -52,7 +51,7 @@ class ValidationTestCase(TestCase):
is overridden.
Refs #19445.
"""
- validate(ValidFormFieldsets, Song)
+ ValidFormFieldsets.validate(Song)
def test_exclude_values(self):
"""
@@ -62,16 +61,16 @@ class ValidationTestCase(TestCase):
exclude = ('foo')
self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFields1.exclude' must be a list or tuple.",
- validate,
- ExcludedFields1, Book)
+ ExcludedFields1.validate,
+ Book)
def test_exclude_duplicate_values(self):
class ExcludedFields2(admin.ModelAdmin):
exclude = ('name', 'name')
self.assertRaisesMessage(ImproperlyConfigured,
"There are duplicate field(s) in ExcludedFields2.exclude",
- validate,
- ExcludedFields2, Book)
+ ExcludedFields2.validate,
+ Book)
def test_exclude_in_inline(self):
class ExcludedFieldsInline(admin.TabularInline):
@@ -84,8 +83,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'ExcludedFieldsInline.exclude' must be a list or tuple.",
- validate,
- ExcludedFieldsAlbumAdmin, Album)
+ ExcludedFieldsAlbumAdmin.validate,
+ Album)
def test_exclude_inline_model_admin(self):
"""
@@ -102,8 +101,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"SongInline cannot exclude the field 'album' - this is the foreign key to the parent model admin_validation.Album.",
- validate,
- AlbumAdmin, Album)
+ AlbumAdmin.validate,
+ Album)
def test_app_label_in_admin_validation(self):
"""
@@ -114,8 +113,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'RawIdNonexistingAdmin.raw_id_fields' refers to field 'nonexisting' that is missing from model 'admin_validation.Album'.",
- validate,
- RawIdNonexistingAdmin, Album)
+ RawIdNonexistingAdmin.validate,
+ Album)
def test_fk_exclusion(self):
"""
@@ -127,28 +126,35 @@ class ValidationTestCase(TestCase):
model = TwoAlbumFKAndAnE
exclude = ("e",)
fk_name = "album1"
- validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
+ MyAdmin.validate(Album)
+
def test_inline_self_validation(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
self.assertRaisesMessage(Exception,
"<class 'admin_validation.models.TwoAlbumFKAndAnE'> has more than 1 ForeignKey to <class 'admin_validation.models.Album'>",
- validate_inline,
- TwoAlbumFKAndAnEInline, None, Album)
+ MyAdmin.validate, Album)
def test_inline_with_specified(self):
class TwoAlbumFKAndAnEInline(admin.TabularInline):
model = TwoAlbumFKAndAnE
fk_name = "album1"
- validate_inline(TwoAlbumFKAndAnEInline, None, Album)
+
+ class MyAdmin(admin.ModelAdmin):
+ inlines = [TwoAlbumFKAndAnEInline]
+ MyAdmin.validate(Album)
def test_readonly(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = ("title",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_on_method(self):
def my_function(obj):
@@ -157,7 +163,7 @@ class ValidationTestCase(TestCase):
class SongAdmin(admin.ModelAdmin):
readonly_fields = (my_function,)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_on_modeladmin(self):
class SongAdmin(admin.ModelAdmin):
@@ -166,13 +172,13 @@ class ValidationTestCase(TestCase):
def readonly_method_on_modeladmin(self, obj):
pass
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_method_on_model(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = ("readonly_method_on_model",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_nonexistant_field(self):
class SongAdmin(admin.ModelAdmin):
@@ -180,8 +186,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"SongAdmin.readonly_fields[1], 'nonexistant' is not a callable or an attribute of 'SongAdmin' or found in the model 'Song'.",
- validate,
- SongAdmin, Song)
+ SongAdmin.validate,
+ Song)
def test_nonexistant_field_on_inline(self):
class CityInline(admin.TabularInline):
@@ -190,8 +196,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"CityInline.readonly_fields[0], 'i_dont_exist' is not a callable or an attribute of 'CityInline' or found in the model 'City'.",
- validate_inline,
- CityInline, None, State)
+ CityInline.validate,
+ City)
def test_extra(self):
class SongAdmin(admin.ModelAdmin):
@@ -199,13 +205,13 @@ class ValidationTestCase(TestCase):
if instance.title == "Born to Run":
return "Best Ever!"
return "Status unknown."
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_readonly_lambda(self):
class SongAdmin(admin.ModelAdmin):
readonly_fields = (lambda obj: "test",)
- validate(SongAdmin, Song)
+ SongAdmin.validate(Song)
def test_graceful_m2m_fail(self):
"""
@@ -219,8 +225,8 @@ class ValidationTestCase(TestCase):
self.assertRaisesMessage(ImproperlyConfigured,
"'BookAdmin.fields' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
- validate,
- BookAdmin, Book)
+ BookAdmin.validate,
+ Book)
def test_cannot_include_through(self):
class FieldsetBookAdmin(admin.ModelAdmin):
@@ -230,20 +236,20 @@ class ValidationTestCase(TestCase):
)
self.assertRaisesMessage(ImproperlyConfigured,
"'FieldsetBookAdmin.fieldsets[1][1]['fields']' can't include the ManyToManyField field 'authors' because 'authors' manually specifies a 'through' model.",
- validate,
- FieldsetBookAdmin, Book)
+ FieldsetBookAdmin.validate,
+ Book)
def test_nested_fields(self):
class NestedFieldsAdmin(admin.ModelAdmin):
fields = ('price', ('name', 'subtitle'))
- validate(NestedFieldsAdmin, Book)
+ NestedFieldsAdmin.validate(Book)
def test_nested_fieldsets(self):
class NestedFieldsetAdmin(admin.ModelAdmin):
fieldsets = (
('Main', {'fields': ('price', ('name', 'subtitle'))}),
)
- validate(NestedFieldsetAdmin, Book)
+ NestedFieldsetAdmin.validate(Book)
def test_explicit_through_override(self):
"""
@@ -260,7 +266,7 @@ class ValidationTestCase(TestCase):
# If the through model is still a string (and hasn't been resolved to a model)
# the validation will fail.
- validate(BookAdmin, Book)
+ BookAdmin.validate(Book)
def test_non_model_fields(self):
"""
@@ -274,7 +280,7 @@ class ValidationTestCase(TestCase):
form = SongForm
fields = ['title', 'extra_data']
- validate(FieldsOnFormOnlyAdmin, Song)
+ FieldsOnFormOnlyAdmin.validate(Song)
def test_non_model_first_field(self):
"""
@@ -292,4 +298,4 @@ class ValidationTestCase(TestCase):
form = SongForm
fields = ['extra_data', 'title']
- validate(FieldsOnFormOnlyAdmin, Song)
+ FieldsOnFormOnlyAdmin.validate(Song)
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index cc7585cd2d..a6ad7cc0bc 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -9,11 +9,13 @@ from django.contrib import admin
from django.contrib.admin.views.main import ChangeList
from django.core.files.storage import FileSystemStorage
from django.core.mail import EmailMessage
+from django.core.servers.basehttp import FileWrapper
from django.conf.urls import patterns, url
from django.db import models
from django.forms.models import BaseModelFormSet
-from django.http import HttpResponse
+from django.http import HttpResponse, StreamingHttpResponse
from django.contrib.admin import BooleanFieldListFilter
+from django.utils.six import StringIO
from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Widget, DooHickey, Grommet, Whatsit, FancyDoodad, Category, Link,
@@ -24,7 +26,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Gadget, Villain, SuperVillain, Plot, PlotDetails, CyclicOne, CyclicTwo,
WorkHour, Reservation, FoodDelivery, RowLevelChangePermissionModel, Paper,
CoverLetter, Story, OtherStory, Book, Promo, ChapterXtra1, Pizza, Topping,
- Album, Question, Answer, ComplexSortedPerson, PrePopulatedPostLargeSlug,
+ Album, Question, Answer, ComplexSortedPerson, PluggableSearchPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice,
@@ -149,7 +151,7 @@ class InquisitionAdmin(admin.ModelAdmin):
class SketchAdmin(admin.ModelAdmin):
- raw_id_fields = ('inquisition',)
+ raw_id_fields = ('inquisition', 'defendant0', 'defendant1')
class FabricAdmin(admin.ModelAdmin):
@@ -238,8 +240,20 @@ def redirect_to(modeladmin, request, selected):
redirect_to.short_description = 'Redirect to (Awesome action)'
+def download(modeladmin, request, selected):
+ buf = StringIO('This is the content of the file')
+ return StreamingHttpResponse(FileWrapper(buf))
+download.short_description = 'Download subscription'
+
+
+def no_perm(modeladmin, request, selected):
+ return HttpResponse(content='No permission to perform this action',
+ status=403)
+no_perm.short_description = 'No permission to run'
+
+
class ExternalSubscriberAdmin(admin.ModelAdmin):
- actions = [redirect_to, external_mail]
+ actions = [redirect_to, external_mail, download, no_perm]
class Podcast(Media):
@@ -530,6 +544,20 @@ class ComplexSortedPersonAdmin(admin.ModelAdmin):
colored_name.admin_order_field = 'name'
+class PluggableSearchPersonAdmin(admin.ModelAdmin):
+ list_display = ('name', 'age')
+ search_fields = ('name',)
+
+ def get_search_results(self, request, queryset, search_term):
+ queryset, use_distinct = super(PluggableSearchPersonAdmin, self).get_search_results(request, queryset, search_term)
+ try:
+ search_term_as_int = int(search_term)
+ queryset |= self.model.objects.filter(age=search_term_as_int)
+ except:
+ pass
+ return queryset, use_distinct
+
+
class AlbumAdmin(admin.ModelAdmin):
list_filter = ['title']
@@ -733,6 +761,7 @@ site.register(Question)
site.register(Answer)
site.register(PrePopulatedPost, PrePopulatedPostAdmin)
site.register(ComplexSortedPerson, ComplexSortedPersonAdmin)
+site.register(PluggableSearchPerson, PluggableSearchPersonAdmin)
site.register(PrePopulatedPostLargeSlug, PrePopulatedPostLargeSlugAdmin)
site.register(AdminOrderedField, AdminOrderedFieldAdmin)
site.register(AdminOrderedModelMethod, AdminOrderedModelMethodAdmin)
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index 1916949f63..5cc6f6251a 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -137,6 +137,7 @@ class Thing(models.Model):
class Actor(models.Model):
name = models.CharField(max_length=50)
age = models.IntegerField()
+ title = models.CharField(max_length=50, null=True)
def __str__(self):
return self.name
@@ -158,6 +159,8 @@ class Sketch(models.Model):
'leader__age': 27,
'expected': False,
})
+ defendant0 = models.ForeignKey(Actor, limit_choices_to={'title__isnull': False}, related_name='as_defendant0')
+ defendant1 = models.ForeignKey(Actor, limit_choices_to={'title__isnull': True}, related_name='as_defendant1')
def __str__(self):
return self.title
@@ -591,6 +594,12 @@ class ComplexSortedPerson(models.Model):
age = models.PositiveIntegerField()
is_employee = models.NullBooleanField()
+
+class PluggableSearchPerson(models.Model):
+ name = models.CharField(max_length=100)
+ age = models.PositiveIntegerField()
+
+
class PrePopulatedPostLargeSlug(models.Model):
"""
Regression test for #15938: a large max_length for the slugfield must not
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 8e678a72b3..8c8a65318c 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -11,7 +11,6 @@ except ImportError: # Python 2
from django.conf import settings, global_settings
from django.core import mail
-from django.core.exceptions import SuspiciousOperation
from django.core.files import temp as tempfile
from django.core.urlresolvers import reverse
# Register auth models with the admin.
@@ -30,6 +29,7 @@ from django.db import connection
from django.forms.util import ErrorList
from django.template.response import TemplateResponse
from django.test import TestCase
+from django.test.utils import patch_logger
from django.utils import formats, translation, unittest
from django.utils.cache import get_max_age
from django.utils.encoding import iri_to_uri, force_bytes
@@ -46,7 +46,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
DooHickey, FancyDoodad, Whatsit, Category, Post, Plot, FunkyTag, Chapter,
Book, Promo, WorkHour, Employee, Question, Answer, Inquisition, Actor,
FoodDelivery, RowLevelChangePermissionModel, Paper, CoverLetter, Story,
- OtherStory, ComplexSortedPerson, Parent, Child, AdminOrderedField,
+ OtherStory, ComplexSortedPerson, PluggableSearchPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
Simple, UndeletableObject, Choice, ShortMessage, Telegram)
@@ -468,8 +468,12 @@ class AdminViewBasicTest(TestCase):
self.assertContains(response, '4 articles')
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'false'})
self.assertContains(response, '3 articles')
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': '0'})
+ self.assertContains(response, '3 articles')
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': 'true'})
self.assertContains(response, '1 article')
+ response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit, {'section__isnull': '1'})
+ self.assertContains(response, '1 article')
def testLogoutAndPasswordChangeURLs(self):
response = self.client.get('/test_admin/%s/admin_views/article/' % self.urlbit)
@@ -543,20 +547,21 @@ class AdminViewBasicTest(TestCase):
self.assertContains(response, '%Y-%m-%d %H:%M:%S')
def test_disallowed_filtering(self):
- self.assertRaises(SuspiciousOperation,
- self.client.get, "/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy"
- )
+ with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as calls:
+ response = self.client.get("/test_admin/admin/admin_views/album/?owner__email__startswith=fuzzy")
+ self.assertEqual(response.status_code, 400)
+ self.assertEqual(len(calls), 1)
- try:
- self.client.get("/test_admin/admin/admin_views/thing/?color__value__startswith=red")
- self.client.get("/test_admin/admin/admin_views/thing/?color__value=red")
- except SuspiciousOperation:
- self.fail("Filters are allowed if explicitly included in list_filter")
+ # Filters are allowed if explicitly included in list_filter
+ response = self.client.get("/test_admin/admin/admin_views/thing/?color__value__startswith=red")
+ self.assertEqual(response.status_code, 200)
+ response = self.client.get("/test_admin/admin/admin_views/thing/?color__value=red")
+ self.assertEqual(response.status_code, 200)
- try:
- self.client.get("/test_admin/admin/admin_views/person/?age__gt=30")
- except SuspiciousOperation:
- self.fail("Filters should be allowed if they involve a local field without the need to whitelist them in list_filter or date_hierarchy.")
+ # Filters should be allowed if they involve a local field without the
+ # need to whitelist them in list_filter or date_hierarchy.
+ response = self.client.get("/test_admin/admin/admin_views/person/?age__gt=30")
+ self.assertEqual(response.status_code, 200)
e1 = Employee.objects.create(name='Anonymous', gender=1, age=22, alive=True, code='123')
e2 = Employee.objects.create(name='Visitor', gender=2, age=19, alive=True, code='124')
@@ -574,10 +579,9 @@ class AdminViewBasicTest(TestCase):
ForeignKey 'limit_choices_to' should be allowed, otherwise raw_id_fields
can break.
"""
- try:
- self.client.get("/test_admin/admin/admin_views/inquisition/?leader__name=Palin&leader__age=27")
- except SuspiciousOperation:
- self.fail("Filters should be allowed if they are defined on a ForeignKey pointing to this model")
+ # Filters should be allowed if they are defined on a ForeignKey pointing to this model
+ response = self.client.get("/test_admin/admin/admin_views/inquisition/?leader__name=Palin&leader__age=27")
+ self.assertEqual(response.status_code, 200)
def test_hide_change_password(self):
"""
@@ -1995,7 +1999,7 @@ class AdminViewListEditable(TestCase):
}
response = self.client.post('/test_admin/admin/admin_views/person/', data)
non_form_errors = response.context['cl'].formset.non_form_errors()
- self.assertTrue(isinstance(non_form_errors, ErrorList))
+ self.assertIsInstance(non_form_errors, ErrorList)
self.assertEqual(str(non_form_errors), str(ErrorList(["Grace is not a Zombie"])))
def test_list_editable_ordering(self):
@@ -2202,6 +2206,20 @@ class AdminSearchTest(TestCase):
self.assertContains(response, "\n0 persons\n")
self.assertNotContains(response, "Guido")
+ def test_pluggable_search(self):
+ p1 = PluggableSearchPerson.objects.create(name="Bob", age=10)
+ p2 = PluggableSearchPerson.objects.create(name="Amy", age=20)
+
+ response = self.client.get('/test_admin/admin/admin_views/pluggablesearchperson/?q=Bob')
+ # confirm the search returned one object
+ self.assertContains(response, "\n1 pluggable search person\n")
+ self.assertContains(response, "Bob")
+
+ response = self.client.get('/test_admin/admin/admin_views/pluggablesearchperson/?q=20')
+ # confirm the search returned one object
+ self.assertContains(response, "\n1 pluggable search person\n")
+ self.assertContains(response, "Amy")
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminInheritedInlinesTest(TestCase):
@@ -2414,6 +2432,29 @@ class AdminActionsTest(TestCase):
response = self.client.post(url, action_data)
self.assertRedirects(response, url)
+ def test_custom_function_action_streaming_response(self):
+ """Tests a custom action that returns a StreamingHttpResponse."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'download',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ content = b''.join(response.streaming_content)
+ self.assertEqual(content, b'This is the content of the file')
+ self.assertEqual(response.status_code, 200)
+
+ def test_custom_function_action_no_perm_response(self):
+ """Tests a custom action that returns an HttpResponse with 403 code."""
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'no_perm',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/externalsubscriber/', action_data)
+ self.assertEqual(response.status_code, 403)
+ self.assertEqual(response.content, b'No permission to perform this action')
+
def test_actions_ordering(self):
"""
Ensure that actions are ordered as expected.
@@ -2422,9 +2463,13 @@ class AdminActionsTest(TestCase):
response = self.client.get('/test_admin/admin/admin_views/externalsubscriber/')
self.assertContains(response, '''<label>Action: <select name="action">
<option value="" selected="selected">---------</option>
-<option value="delete_selected">Delete selected external subscribers</option>
+<option value="delete_selected">Delete selected external
+subscribers</option>
<option value="redirect_to">Redirect to (Awesome action)</option>
-<option value="external_mail">External mail (Another awesome action)</option>
+<option value="external_mail">External mail (Another awesome
+action)</option>
+<option value="download">Download subscription</option>
+<option value="no_perm">No permission to run</option>
</select>''', html=True)
def test_model_without_action(self):
@@ -3494,7 +3539,6 @@ class RawIdFieldsTest(TestCase):
def test_limit_choices_to(self):
"""Regression test for 14880"""
- # This includes tests integers, strings and booleans in the lookup query string
actor = Actor.objects.create(name="Palin", age=27)
inquisition1 = Inquisition.objects.create(expected=True,
leader=actor,
@@ -3510,11 +3554,57 @@ class RawIdFieldsTest(TestCase):
# Handle relative links
popup_url = urljoin(response.request['PATH_INFO'], popup_url)
- # Get the popup
+ # Get the popup and verify the correct objects show up in the resulting
+ # page. This step also tests integers, strings and booleans in the
+ # lookup query string; in model we define inquisition field to have a
+ # limit_choices_to option that includes a filter on a string field
+ # (inquisition__actor__name), a filter on an integer field
+ # (inquisition__actor__age), and a filter on a boolean field
+ # (inquisition__expected).
response2 = self.client.get(popup_url)
self.assertContains(response2, "Spain")
self.assertNotContains(response2, "England")
+ def test_limit_choices_to_isnull_false(self):
+ """Regression test for 20182"""
+ Actor.objects.create(name="Palin", age=27)
+ Actor.objects.create(name="Kilbraken", age=50, title="Judge")
+ response = self.client.get('/test_admin/admin/admin_views/sketch/add/')
+ # Find the link
+ m = re.search(br'<a href="([^"]*)"[^>]* id="lookup_id_defendant0"', response.content)
+ self.assertTrue(m) # Got a match
+ popup_url = m.groups()[0].decode().replace("&amp;", "&")
+
+ # Handle relative links
+ popup_url = urljoin(response.request['PATH_INFO'], popup_url)
+ # Get the popup and verify the correct objects show up in the resulting
+ # page. This step tests field__isnull=0 gets parsed correctly from the
+ # lookup query string; in model we define defendant0 field to have a
+ # limit_choices_to option that includes "actor__title__isnull=False".
+ response2 = self.client.get(popup_url)
+ self.assertContains(response2, "Kilbraken")
+ self.assertNotContains(response2, "Palin")
+
+ def test_limit_choices_to_isnull_true(self):
+ """Regression test for 20182"""
+ Actor.objects.create(name="Palin", age=27)
+ Actor.objects.create(name="Kilbraken", age=50, title="Judge")
+ response = self.client.get('/test_admin/admin/admin_views/sketch/add/')
+ # Find the link
+ m = re.search(br'<a href="([^"]*)"[^>]* id="lookup_id_defendant1"', response.content)
+ self.assertTrue(m) # Got a match
+ popup_url = m.groups()[0].decode().replace("&amp;", "&")
+
+ # Handle relative links
+ popup_url = urljoin(response.request['PATH_INFO'], popup_url)
+ # Get the popup and verify the correct objects show up in the resulting
+ # page. This step tests field__isnull=1 gets parsed correctly from the
+ # lookup query string; in model we define defendant1 field to have a
+ # limit_choices_to option that includes "actor__title__isnull=True".
+ response2 = self.client.get(popup_url)
+ self.assertNotContains(response2, "Kilbraken")
+ self.assertContains(response2, "Palin")
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class UserAdminTest(TestCase):
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 4e09922893..98f41c1490 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -13,6 +13,7 @@ from django.core.files.uploadedfile import SimpleUploadedFile
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 six
from django.utils import translation
from django.utils.html import conditional_escape
from django.utils.unittest import TestCase
@@ -139,6 +140,17 @@ class AdminFormfieldForDBFieldTests(TestCase):
def testInheritance(self):
self.assertFormfield(models.Album, 'backside_art', widgets.AdminFileWidget)
+ def test_m2m_widgets(self):
+ """m2m fields help text as it applies to admin app (#9321)."""
+ class AdvisorAdmin(admin.ModelAdmin):
+ filter_vertical=['companies']
+
+ self.assertFormfield(models.Advisor, 'companies', widgets.FilteredSelectMultiple,
+ filter_vertical=['companies'])
+ ma = AdvisorAdmin(models.Advisor, admin.site)
+ f = ma.formfield_for_dbfield(models.Advisor._meta.get_field('companies'), request=None)
+ self.assertEqual(six.text_type(f.help_text), ' Hold down "Control", or "Command" on a Mac, to select more than one.')
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class AdminFormfieldForDBFieldWithRequestTests(DjangoTestCase):
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index 6c5a43664a..80441b9a60 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -581,6 +581,7 @@ class AggregationTests(TestCase):
6
)
+ # Note: intentionally no order_by(), that case needs tests, too.
publishers = Publisher.objects.filter(id__in=[1, 2])
self.assertEqual(
sorted(p.name for p in publishers),
@@ -591,10 +592,15 @@ class AggregationTests(TestCase):
)
publishers = publishers.annotate(n_books=Count("book"))
+ sorted_publishers = sorted(publishers, key=lambda x: x.name)
self.assertEqual(
- publishers[0].n_books,
+ sorted_publishers[0].n_books,
2
)
+ self.assertEqual(
+ sorted_publishers[1].n_books,
+ 1
+ )
self.assertEqual(
sorted(p.name for p in publishers),
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index 81b2851403..08cae0f7c3 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -8,7 +8,7 @@ import threading
from django.conf import settings
from django.core.management.color import no_style
-from django.db import (backend, connection, connections, DEFAULT_DB_ALIAS,
+from django.db import (connection, connections, DEFAULT_DB_ALIAS,
DatabaseError, IntegrityError, transaction)
from django.db.backends.signals import connection_created
from django.db.backends.postgresql_psycopg2 import version as pg_version
@@ -50,7 +50,8 @@ class OracleChecks(unittest.TestCase):
def test_dbms_session(self):
# If the backend is Oracle, test that we can call a standard
# stored procedure through our cursor wrapper.
- convert_unicode = backend.convert_unicode
+ from django.db.backends.oracle.base import convert_unicode
+
cursor = connection.cursor()
cursor.callproc(convert_unicode('DBMS_SESSION.SET_IDENTIFIER'),
[convert_unicode('_django_testing!')])
@@ -60,8 +61,10 @@ class OracleChecks(unittest.TestCase):
def test_cursor_var(self):
# If the backend is Oracle, test that we can pass cursor variables
# as query parameters.
+ from django.db.backends.oracle.base import Database
+
cursor = connection.cursor()
- var = cursor.var(backend.Database.STRING)
+ var = cursor.var(Database.STRING)
cursor.execute("BEGIN %s := 'X'; END; ", [var])
self.assertEqual(var.getvalue(), 'X')
@@ -172,7 +175,7 @@ class LastExecutedQueryTest(TestCase):
sql, params = persons.query.sql_with_params()
cursor = persons.query.get_compiler('default').execute_sql(None)
last_sql = cursor.db.ops.last_executed_query(cursor, sql, params)
- self.assertTrue(isinstance(last_sql, six.text_type))
+ self.assertIsInstance(last_sql, six.text_type)
@unittest.skipUnless(connection.vendor == 'sqlite',
"This test is specific to SQLite.")
diff --git a/tests/base/models.py b/tests/base/models.py
index bddb406820..d47ddcfd66 100644
--- a/tests/base/models.py
+++ b/tests/base/models.py
@@ -14,8 +14,10 @@ class CustomBaseModel(models.base.ModelBase):
class MyModel(six.with_metaclass(CustomBaseModel, models.Model)):
- """Model subclass with a custom base using six.with_metaclass."""
+ """Model subclass with a custom base using six.with_metaclass."""
+# This is done to ensure that for Python2 only, defining metaclasses
+# still does not fail to create the model.
if not six.PY3:
class MyModel(models.Model):
diff --git a/tests/basic/models.py b/tests/basic/models.py
index 660beddf49..1bffcb9cda 100644
--- a/tests/basic/models.py
+++ b/tests/basic/models.py
@@ -18,3 +18,11 @@ class Article(models.Model):
def __str__(self):
return self.headline
+
+@python_2_unicode_compatible
+class SelfRef(models.Model):
+ selfref = models.ForeignKey('self', null=True, blank=True,
+ related_name='+')
+
+ def __str__(self):
+ return SelfRef.objects.get(selfref=self).pk
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index ccbb9bd423..6bf46cce9b 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -11,7 +11,7 @@ from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnle
from django.utils import six
from django.utils.translation import ugettext_lazy
-from .models import Article
+from .models import Article, SelfRef
class ModelTest(TestCase):
@@ -87,23 +87,14 @@ class ModelTest(TestCase):
# parameters don't match any object.
six.assertRaisesRegex(self,
ObjectDoesNotExist,
- "Article matching query does not exist. Lookup parameters were "
- "{'id__exact': 2000}",
+ "Article matching query does not exist.",
Article.objects.get,
id__exact=2000,
)
# To avoid dict-ordering related errors check only one lookup
# in single assert.
- six.assertRaisesRegex(self,
- ObjectDoesNotExist,
- ".*'pub_date__year': 2005.*",
- Article.objects.get,
- pub_date__year=2005,
- pub_date__month=8,
- )
- six.assertRaisesRegex(self,
+ self.assertRaises(
ObjectDoesNotExist,
- ".*'pub_date__month': 8.*",
Article.objects.get,
pub_date__year=2005,
pub_date__month=8,
@@ -111,8 +102,7 @@ class ModelTest(TestCase):
six.assertRaisesRegex(self,
ObjectDoesNotExist,
- "Article matching query does not exist. Lookup parameters were "
- "{'pub_date__week_day': 6}",
+ "Article matching query does not exist.",
Article.objects.get,
pub_date__week_day=6,
)
@@ -442,7 +432,7 @@ class ModelTest(TestCase):
Article.objects.all()[0:-5]
except Exception as e:
error = e
- self.assertTrue(isinstance(error, AssertionError))
+ self.assertIsInstance(error, AssertionError)
self.assertEqual(str(error), "Negative indexing is not supported.")
# An Article instance doesn't have access to the "objects" attribute.
@@ -647,15 +637,15 @@ class ModelTest(TestCase):
# Can't be instantiated
with self.assertRaises(TypeError):
EmptyQuerySet()
- self.assertTrue(isinstance(Article.objects.none(), EmptyQuerySet))
+ self.assertIsInstance(Article.objects.none(), EmptyQuerySet)
def test_emptyqs_values(self):
# test for #15959
Article.objects.create(headline='foo', pub_date=datetime.now())
with self.assertNumQueries(0):
qs = Article.objects.none().values_list('pk')
- self.assertTrue(isinstance(qs, EmptyQuerySet))
- self.assertTrue(isinstance(qs, ValuesListQuerySet))
+ self.assertIsInstance(qs, EmptyQuerySet)
+ self.assertIsInstance(qs, ValuesListQuerySet)
self.assertEqual(len(qs), 0)
def test_emptyqs_customqs(self):
@@ -670,7 +660,7 @@ class ModelTest(TestCase):
qs = qs.none()
with self.assertNumQueries(0):
self.assertEqual(len(qs), 0)
- self.assertTrue(isinstance(qs, EmptyQuerySet))
+ self.assertIsInstance(qs, EmptyQuerySet)
self.assertEqual(qs.do_something(), 'did something')
def test_emptyqs_values_order(self):
@@ -689,6 +679,12 @@ class ModelTest(TestCase):
with self.assertNumQueries(0):
self.assertEqual(len(Article.objects.none().distinct('headline', 'pub_date')), 0)
+ def test_ticket_20278(self):
+ sr = SelfRef.objects.create()
+ with self.assertRaises(ObjectDoesNotExist):
+ SelfRef.objects.get(selfref=sr)
+
+
class ConcurrentSaveTests(TransactionTestCase):
@skipUnlessDBFeature('test_db_allows_multiple_connections')
def test_concurrent_delete_with_save(self):
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 00c51638b7..da80c48058 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -28,8 +28,8 @@ from django.middleware.cache import (FetchFromCacheMiddleware,
from django.template import Template
from django.template.response import TemplateResponse
from django.test import TestCase, TransactionTestCase, RequestFactory
-from django.test.utils import override_settings, six
-from django.utils import timezone, translation, unittest
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
+from django.utils import six, timezone, translation, unittest
from django.utils.cache import (patch_vary_headers, get_cache_key,
learn_cache_key, patch_cache_control, patch_response_headers)
from django.utils.encoding import force_text
@@ -441,6 +441,34 @@ class BaseCacheTests(object):
self.assertEqual(self.cache.get('key3'), 'sausage')
self.assertEqual(self.cache.get('key4'), 'lobster bisque')
+ def test_forever_timeout(self):
+ '''
+ Passing in None into timeout results in a value that is cached forever
+ '''
+ self.cache.set('key1', 'eggs', None)
+ self.assertEqual(self.cache.get('key1'), 'eggs')
+
+ self.cache.add('key2', 'ham', None)
+ self.assertEqual(self.cache.get('key2'), 'ham')
+
+ self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, None)
+ self.assertEqual(self.cache.get('key3'), 'sausage')
+ self.assertEqual(self.cache.get('key4'), 'lobster bisque')
+
+ def test_zero_timeout(self):
+ '''
+ Passing in None into timeout results in a value that is cached forever
+ '''
+ self.cache.set('key1', 'eggs', 0)
+ self.assertEqual(self.cache.get('key1'), None)
+
+ self.cache.add('key2', 'ham', 0)
+ self.assertEqual(self.cache.get('key2'), None)
+
+ self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 0)
+ self.assertEqual(self.cache.get('key3'), None)
+ self.assertEqual(self.cache.get('key4'), None)
+
def test_float_timeout(self):
# Make sure a timeout given as a float doesn't crash anything.
self.cache.set("key1", "spam", 100.2)
@@ -482,13 +510,13 @@ class BaseCacheTests(object):
# memcached does not allow whitespace or control characters in keys
self.cache.set('key with spaces', 'value')
self.assertEqual(len(w), 2)
- self.assertTrue(isinstance(w[0].message, CacheKeyWarning))
+ self.assertIsInstance(w[0].message, CacheKeyWarning)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
# memcached limits key length to 250
self.cache.set('a' * 251, 'value')
self.assertEqual(len(w), 1)
- self.assertTrue(isinstance(w[0].message, CacheKeyWarning))
+ self.assertIsInstance(w[0].message, CacheKeyWarning)
finally:
self.cache.key_func = old_func
@@ -1069,10 +1097,10 @@ class GetCacheTests(unittest.TestCase):
def test_simple(self):
cache = get_cache('locmem://')
from django.core.cache.backends.locmem import LocMemCache
- self.assertTrue(isinstance(cache, LocMemCache))
+ self.assertIsInstance(cache, LocMemCache)
from django.core.cache import cache
- self.assertTrue(isinstance(cache, get_cache('default').__class__))
+ self.assertIsInstance(cache, get_cache('default').__class__)
cache = get_cache(
'django.core.cache.backends.dummy.DummyCache', **{'TIMEOUT': 120})
@@ -1564,9 +1592,10 @@ def hello_world_view(request, value):
},
},
)
-class CacheMiddlewareTest(TestCase):
+class CacheMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TestCase):
def setUp(self):
+ super(CacheMiddlewareTest, self).setUp()
self.factory = RequestFactory()
self.default_cache = get_cache('default')
self.other_cache = get_cache('other')
@@ -1574,6 +1603,7 @@ class CacheMiddlewareTest(TestCase):
def tearDown(self):
self.default_cache.clear()
self.other_cache.clear()
+ super(CacheMiddlewareTest, self).tearDown()
def test_constructor(self):
"""
diff --git a/tests/commands_sql/models.py b/tests/commands_sql/models.py
index 089aa96f30..d8f372b403 100644
--- a/tests/commands_sql/models.py
+++ b/tests/commands_sql/models.py
@@ -3,5 +3,11 @@ from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
+class Comment(models.Model):
+ pass
+
+
+@python_2_unicode_compatible
class Book(models.Model):
title = models.CharField(max_length=100, db_index=True)
+ comments = models.ManyToManyField(Comment)
diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py
index 831e9a27c4..e083d3977a 100644
--- a/tests/commands_sql/tests.py
+++ b/tests/commands_sql/tests.py
@@ -12,41 +12,43 @@ from django.utils import six
class SQLCommandsTestCase(TestCase):
"""Tests for several functions in django/core/management/sql.py"""
+ def count_ddl(self, output, cmd):
+ return len([o for o in output if o.startswith(cmd)])
+
def test_sql_create(self):
app = models.get_app('commands_sql')
output = sql_create(app, no_style(), connections[DEFAULT_DB_ALIAS])
+ create_tables = [o for o in output if o.startswith('CREATE TABLE')]
+ self.assertEqual(len(create_tables), 3)
# Lower so that Oracle's upper case tbl names wont break
- sql = output[0].lower()
+ sql = create_tables[-1].lower()
six.assertRegex(self, sql, r'^create table .commands_sql_book.*')
def test_sql_delete(self):
app = models.get_app('commands_sql')
output = sql_delete(app, no_style(), connections[DEFAULT_DB_ALIAS])
- # Oracle produces DROP SEQUENCE and DROP TABLE for this command.
- if connections[DEFAULT_DB_ALIAS].vendor == 'oracle':
- sql = output[1].lower()
- else:
- sql = output[0].lower()
- six.assertRegex(self, sql, r'^drop table .commands_sql_book.*')
+ drop_tables = [o for o in output if o.startswith('DROP TABLE')]
+ self.assertEqual(len(drop_tables), 3)
+ # Lower so that Oracle's upper case tbl names wont break
+ sql = drop_tables[-1].lower()
+ six.assertRegex(self, sql, r'^drop table .commands_sql_comment.*')
def test_sql_indexes(self):
app = models.get_app('commands_sql')
output = sql_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS])
- # PostgreSQL creates two indexes
- self.assertIn(len(output), [1, 2])
- self.assertTrue(output[0].startswith("CREATE INDEX"))
+ # PostgreSQL creates one additional index for CharField
+ self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4])
def test_sql_destroy_indexes(self):
app = models.get_app('commands_sql')
output = sql_destroy_indexes(app, no_style(), connections[DEFAULT_DB_ALIAS])
- # PostgreSQL creates two indexes
- self.assertIn(len(output), [1, 2])
- self.assertTrue(output[0].startswith("DROP INDEX"))
+ # PostgreSQL creates one additional index for CharField
+ self.assertIn(self.count_ddl(output, 'DROP INDEX'), [3, 4])
def test_sql_all(self):
app = models.get_app('commands_sql')
output = sql_all(app, no_style(), connections[DEFAULT_DB_ALIAS])
- # PostgreSQL creates two indexes
- self.assertIn(len(output), [2, 3])
- self.assertTrue(output[0].startswith('CREATE TABLE'))
- self.assertTrue(output[1].startswith('CREATE INDEX'))
+
+ self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3)
+ # PostgreSQL creates one additional index for CharField
+ self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4])
diff --git a/tests/comment_tests/tests/test_comment_form.py b/tests/comment_tests/tests/test_comment_form.py
index 39ba57928d..a30f13a073 100644
--- a/tests/comment_tests/tests/test_comment_form.py
+++ b/tests/comment_tests/tests/test_comment_form.py
@@ -54,7 +54,7 @@ class CommentFormTests(CommentTestCase):
def testGetCommentObject(self):
f = self.testValidPost()
c = f.get_comment_object()
- self.assertTrue(isinstance(c, Comment))
+ self.assertIsInstance(c, Comment)
self.assertEqual(c.content_object, Article.objects.get(pk=1))
self.assertEqual(c.comment, "This is my comment")
c.save()
diff --git a/tests/comment_tests/tests/test_templatetags.py b/tests/comment_tests/tests/test_templatetags.py
index 185f6de297..1971c21a58 100644
--- a/tests/comment_tests/tests/test_templatetags.py
+++ b/tests/comment_tests/tests/test_templatetags.py
@@ -32,7 +32,7 @@ class CommentTemplateTagTests(CommentTestCase):
t = "{% load comments %}" + (tag or "{% get_comment_form for comment_tests.article a.id as form %}")
ctx, out = self.render(t, a=Article.objects.get(pk=1))
self.assertEqual(out, "")
- self.assertTrue(isinstance(ctx["form"], CommentForm))
+ self.assertIsInstance(ctx["form"], CommentForm)
def testGetCommentFormFromLiteral(self):
self.testGetCommentForm("{% get_comment_form for comment_tests.article 1 as form %}")
diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py
index 5300b2172a..841b24bb42 100644
--- a/tests/csrf_tests/tests.py
+++ b/tests/csrf_tests/tests.py
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import logging
from django.conf import settings
from django.core.context_processors import csrf
@@ -78,18 +79,18 @@ class CsrfViewMiddlewareTest(TestCase):
def _check_token_present(self, response, csrf_id=None):
self.assertContains(response, "name='csrfmiddlewaretoken' value='%s'" % (csrf_id or self._csrf_id))
- def test_process_view_token_too_long(self):
- """
- Check that if the token is longer than expected, it is ignored and
- a new token is created.
- """
- req = self._get_GET_no_csrf_cookie_request()
- req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 10000000
- CsrfViewMiddleware().process_view(req, token_view, (), {})
- resp = token_view(req)
- resp2 = CsrfViewMiddleware().process_response(req, resp)
- csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
- self.assertEqual(len(csrf_cookie.value), CSRF_KEY_LENGTH)
+ def test_process_view_token_too_long(self):
+ """
+ Check that if the token is longer than expected, it is ignored and
+ a new token is created.
+ """
+ req = self._get_GET_no_csrf_cookie_request()
+ req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 10000000
+ CsrfViewMiddleware().process_view(req, token_view, (), {})
+ resp = token_view(req)
+ resp2 = CsrfViewMiddleware().process_response(req, resp)
+ csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)
+ self.assertEqual(len(csrf_cookie.value), CSRF_KEY_LENGTH)
def test_process_response_get_token_used(self):
"""
@@ -284,6 +285,19 @@ class CsrfViewMiddlewareTest(TestCase):
self.assertEqual(403, req2.status_code)
@override_settings(ALLOWED_HOSTS=['www.example.com'])
+ def test_https_malformed_referer(self):
+ """
+ Test that a POST HTTPS request with a bad referer is rejected
+ """
+ req = self._get_POST_request_with_token()
+ req._is_secure_override = True
+ req.META['HTTP_HOST'] = 'www.example.com'
+ req.META['HTTP_REFERER'] = 'http://http://www.example.com/'
+ req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
+ self.assertNotEqual(None, req2)
+ self.assertEqual(403, req2.status_code)
+
+ @override_settings(ALLOWED_HOSTS=['www.example.com'])
def test_https_good_referer(self):
"""
Test that a POST HTTPS request with a good referer is accepted
@@ -340,3 +354,29 @@ class CsrfViewMiddlewareTest(TestCase):
resp2 = CsrfViewMiddleware().process_response(req, resp)
self.assertTrue(resp2.cookies.get(settings.CSRF_COOKIE_NAME, False))
self.assertTrue('Cookie' in resp2.get('Vary',''))
+
+ def test_ensures_csrf_cookie_no_logging(self):
+ """
+ Tests that ensure_csrf_cookie doesn't log warnings. See #19436.
+ """
+ @ensure_csrf_cookie
+ def view(request):
+ # Doesn't insert a token or anything
+ return HttpResponse(content="")
+
+ class TestHandler(logging.Handler):
+ def emit(self, record):
+ raise Exception("This shouldn't have happened!")
+
+ logger = logging.getLogger('django.request')
+ test_handler = TestHandler()
+ old_log_level = logger.level
+ try:
+ logger.addHandler(test_handler)
+ logger.setLevel(logging.WARNING)
+
+ req = self._get_GET_no_csrf_cookie_request()
+ resp = view(req)
+ finally:
+ logger.removeHandler(test_handler)
+ logger.setLevel(old_log_level)
diff --git a/tests/custom_managers/tests.py b/tests/custom_managers/tests.py
index 294920de2b..4fe79fe3fb 100644
--- a/tests/custom_managers/tests.py
+++ b/tests/custom_managers/tests.py
@@ -19,7 +19,7 @@ class CustomManagerTests(TestCase):
)
# The RelatedManager used on the 'books' descriptor extends the default
# manager
- self.assertTrue(isinstance(p2.books, PublishedBookManager))
+ self.assertIsInstance(p2.books, PublishedBookManager)
b1 = Book.published_objects.create(
title="How to program", author="Rodney Dangerfield", is_published=True
@@ -34,7 +34,7 @@ class CustomManagerTests(TestCase):
# The RelatedManager used on the 'authors' descriptor extends the
# default manager
- self.assertTrue(isinstance(b2.authors, PersonManager))
+ self.assertIsInstance(b2.authors, PersonManager)
self.assertQuerysetEqual(
Book.published_objects.all(), [
diff --git a/tests/datatypes/tests.py b/tests/datatypes/tests.py
index f0ec5f3c0a..b6b52dedf2 100644
--- a/tests/datatypes/tests.py
+++ b/tests/datatypes/tests.py
@@ -74,7 +74,7 @@ class DataTypesTestCase(TestCase):
database should be unicode."""
d = Donut.objects.create(name='Jelly Donut', review='Outstanding')
newd = Donut.objects.get(id=d.id)
- self.assertTrue(isinstance(newd.review, six.text_type))
+ self.assertIsInstance(newd.review, six.text_type)
@skipIfDBFeature('supports_timezones')
def test_error_on_timezone(self):
@@ -90,7 +90,7 @@ class DataTypesTestCase(TestCase):
a Python datetime.date, not a datetime.datetime"""
b = RumBaba.objects.create()
# Verify we didn't break DateTimeField behavior
- self.assertTrue(isinstance(b.baked_timestamp, datetime.datetime))
+ self.assertIsInstance(b.baked_timestamp, datetime.datetime)
# We need to test this this way because datetime.datetime inherits
# from datetime.date:
- self.assertTrue(isinstance(b.baked_date, datetime.date) and not isinstance(b.baked_date, datetime.datetime))
+ self.assertIsInstance(b.baked_date, datetime.date) and not isinstance(b.baked_date, datetime.datetime)
diff --git a/tests/decorators/tests.py b/tests/decorators/tests.py
index 8a3f340e9f..05016be231 100644
--- a/tests/decorators/tests.py
+++ b/tests/decorators/tests.py
@@ -126,15 +126,15 @@ class DecoratorsTest(TestCase):
my_safe_view = require_safe(my_view)
request = HttpRequest()
request.method = 'GET'
- self.assertTrue(isinstance(my_safe_view(request), HttpResponse))
+ self.assertIsInstance(my_safe_view(request), HttpResponse)
request.method = 'HEAD'
- self.assertTrue(isinstance(my_safe_view(request), HttpResponse))
+ self.assertIsInstance(my_safe_view(request), HttpResponse)
request.method = 'POST'
- self.assertTrue(isinstance(my_safe_view(request), HttpResponseNotAllowed))
+ self.assertIsInstance(my_safe_view(request), HttpResponseNotAllowed)
request.method = 'PUT'
- self.assertTrue(isinstance(my_safe_view(request), HttpResponseNotAllowed))
+ self.assertIsInstance(my_safe_view(request), HttpResponseNotAllowed)
request.method = 'DELETE'
- self.assertTrue(isinstance(my_safe_view(request), HttpResponseNotAllowed))
+ self.assertIsInstance(my_safe_view(request), HttpResponseNotAllowed)
# For testing method_decorator, a decorator that assumes a single argument.
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index 21734faf95..d0009c6e66 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -11,6 +11,8 @@ from django.utils import unittest, translation
from django.utils.safestring import SafeData
from django.utils.encoding import python_2_unicode_compatible
+from i18n import TransRealMixin
+
class DefaultFiltersTests(TestCase):
@@ -306,13 +308,13 @@ class DefaultFiltersTests(TestCase):
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>.)')
- # Check urlize doesn't crash when square bracket is appended to url (#19070)
+ # Check urlize handles brackets properly (#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>' )
+ self.assertEqual(urlize('[http://168.192.0.1](http://168.192.0.1)'),
+ '[<a href="http://168.192.0.1](http://168.192.0.1)" rel="nofollow">http://168.192.0.1](http://168.192.0.1)</a>')
# Check urlize works with IPv4/IPv6 addresses
self.assertEqual(urlize('http://192.168.0.15/api/9'),
@@ -360,7 +362,7 @@ class DefaultFiltersTests(TestCase):
escaped = force_escape('<some html & special characters > here')
self.assertEqual(
escaped, '&lt;some html &amp; special characters &gt; here')
- self.assertTrue(isinstance(escaped, SafeData))
+ self.assertIsInstance(escaped, SafeData)
self.assertEqual(
force_escape('<some html & special characters > here ĐÅ€£'),
'&lt;some html &amp; special characters &gt; here'\
@@ -527,24 +529,26 @@ class DefaultFiltersTests(TestCase):
def test_timesince(self):
# real testing is done in timesince.py, where we can provide our own 'now'
+ # NOTE: \xa0 avoids wrapping between value and unit
self.assertEqual(
timesince_filter(datetime.datetime.now() - datetime.timedelta(1)),
- '1 day')
+ '1\xa0day')
self.assertEqual(
timesince_filter(datetime.datetime(2005, 12, 29),
datetime.datetime(2005, 12, 30)),
- '1 day')
+ '1\xa0day')
def test_timeuntil(self):
+ # NOTE: \xa0 avoids wrapping between value and unit
self.assertEqual(
timeuntil_filter(datetime.datetime.now() + datetime.timedelta(1, 1)),
- '1 day')
+ '1\xa0day')
self.assertEqual(
timeuntil_filter(datetime.datetime(2005, 12, 30),
datetime.datetime(2005, 12, 29)),
- '1 day')
+ '1\xa0day')
def test_default(self):
self.assertEqual(default("val", "default"), 'val')
@@ -574,43 +578,23 @@ class DefaultFiltersTests(TestCase):
'get out of town')
def test_filesizeformat(self):
- self.assertEqual(filesizeformat(1023), '1023 bytes')
- self.assertEqual(filesizeformat(1024), '1.0 KB')
- self.assertEqual(filesizeformat(10*1024), '10.0 KB')
- self.assertEqual(filesizeformat(1024*1024-1), '1024.0 KB')
- self.assertEqual(filesizeformat(1024*1024), '1.0 MB')
- self.assertEqual(filesizeformat(1024*1024*50), '50.0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024), '1.0 GB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0 TB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0 PB')
+ # NOTE: \xa0 avoids wrapping between value and unit
+ self.assertEqual(filesizeformat(1023), '1023\xa0bytes')
+ self.assertEqual(filesizeformat(1024), '1.0\xa0KB')
+ self.assertEqual(filesizeformat(10*1024), '10.0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024-1), '1024.0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024), '1.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*50), '50.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024-1), '1024.0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024), '1.0\xa0GB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024), '1.0\xa0TB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024*1024), '1.0\xa0PB')
self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000),
- '2000.0 PB')
- self.assertEqual(filesizeformat(complex(1,-1)), '0 bytes')
- self.assertEqual(filesizeformat(""), '0 bytes')
+ '2000.0\xa0PB')
+ self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0bytes')
+ self.assertEqual(filesizeformat(""), '0\xa0bytes')
self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"),
- '0 bytes')
-
- def test_localized_filesizeformat(self):
- with self.settings(USE_L10N=True):
- with translation.override('de', deactivate=True):
- self.assertEqual(filesizeformat(1023), '1023 Bytes')
- self.assertEqual(filesizeformat(1024), '1,0 KB')
- self.assertEqual(filesizeformat(10*1024), '10,0 KB')
- self.assertEqual(filesizeformat(1024*1024-1), '1024,0 KB')
- self.assertEqual(filesizeformat(1024*1024), '1,0 MB')
- self.assertEqual(filesizeformat(1024*1024*50), '50,0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0 MB')
- self.assertEqual(filesizeformat(1024*1024*1024), '1,0 GB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0 TB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024*1024),
- '1,0 PB')
- self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000),
- '2000,0 PB')
- self.assertEqual(filesizeformat(complex(1,-1)), '0 Bytes')
- self.assertEqual(filesizeformat(""), '0 Bytes')
- self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"),
- '0 Bytes')
+ '0\xa0bytes')
def test_pluralize(self):
self.assertEqual(pluralize(1), '')
@@ -656,3 +640,27 @@ class DefaultFiltersTests(TestCase):
self.assertEqual(removetags(123, 'a'), '123')
self.assertEqual(striptags(123), '123')
+
+class DefaultFiltersI18NTests(TransRealMixin, TestCase):
+
+ def test_localized_filesizeformat(self):
+ # NOTE: \xa0 avoids wrapping between value and unit
+ with self.settings(USE_L10N=True):
+ with translation.override('de', deactivate=True):
+ self.assertEqual(filesizeformat(1023), '1023\xa0Bytes')
+ self.assertEqual(filesizeformat(1024), '1,0\xa0KB')
+ self.assertEqual(filesizeformat(10*1024), '10,0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024-1), '1024,0\xa0KB')
+ self.assertEqual(filesizeformat(1024*1024), '1,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*50), '50,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024-1), '1024,0\xa0MB')
+ self.assertEqual(filesizeformat(1024*1024*1024), '1,0\xa0GB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024), '1,0\xa0TB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024*1024),
+ '1,0\xa0PB')
+ self.assertEqual(filesizeformat(1024*1024*1024*1024*1024*2000),
+ '2000,0\xa0PB')
+ self.assertEqual(filesizeformat(complex(1,-1)), '0\xa0Bytes')
+ self.assertEqual(filesizeformat(""), '0\xa0Bytes')
+ self.assertEqual(filesizeformat("\N{GREEK SMALL LETTER ALPHA}"),
+ '0\xa0Bytes')
diff --git a/tests/defer_regress/models.py b/tests/defer_regress/models.py
index 7528e3b2c9..0170221cb9 100644
--- a/tests/defer_regress/models.py
+++ b/tests/defer_regress/models.py
@@ -62,3 +62,22 @@ class OneToOneItem(models.Model):
class ItemAndSimpleItem(models.Model):
item = models.ForeignKey(Item)
simple = models.ForeignKey(SimpleItem)
+
+class Profile(models.Model):
+ profile1 = models.CharField(max_length=1000, default='profile1')
+
+class Location(models.Model):
+ location1 = models.CharField(max_length=1000, default='location1')
+
+class Item(models.Model):
+ pass
+
+class Request(models.Model):
+ profile = models.ForeignKey(Profile, null=True, blank=True)
+ location = models.ForeignKey(Location)
+ items = models.ManyToManyField(Item)
+
+ request1 = models.CharField(default='request1', max_length=1000)
+ request2 = models.CharField(default='request2', max_length=1000)
+ request3 = models.CharField(default='request3', max_length=1000)
+ request4 = models.CharField(default='request4', max_length=1000)
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py
index d4d722035f..ad2546794c 100644
--- a/tests/defer_regress/tests.py
+++ b/tests/defer_regress/tests.py
@@ -8,8 +8,9 @@ from django.db.models import Count
from django.db.models.loading import cache
from django.test import TestCase
-from .models import (ResolveThis, Item, RelatedItem, Child, Leaf, Proxy,
- SimpleItem, Feature, ItemAndSimpleItem, OneToOneItem, SpecialFeature)
+from .models import (
+ ResolveThis, Item, RelatedItem, Child, Leaf, Proxy, SimpleItem, Feature,
+ ItemAndSimpleItem, OneToOneItem, SpecialFeature, Location, Request)
class DeferRegressionTest(TestCase):
@@ -76,7 +77,9 @@ class DeferRegressionTest(TestCase):
self.assertEqual(results[0].child.name, "c1")
self.assertEqual(results[0].second_child.name, "c2")
- results = Leaf.objects.only("name", "child", "second_child", "child__name", "second_child__name").select_related()
+ results = Leaf.objects.only(
+ "name", "child", "second_child", "child__name", "second_child__name"
+ ).select_related()
self.assertEqual(results[0].child.name, "c1")
self.assertEqual(results[0].second_child.name, "c2")
@@ -98,29 +101,36 @@ class DeferRegressionTest(TestCase):
i2 = s["item"]
self.assertFalse(i2._deferred)
+ # Regression for #16409 - make sure defer() and only() work with annotate()
+ self.assertIsInstance(
+ list(SimpleItem.objects.annotate(Count('feature')).defer('name')),
+ list)
+ self.assertIsInstance(
+ list(SimpleItem.objects.annotate(Count('feature')).only('name')),
+ list)
+
+ def test_ticket_11936(self):
# Regression for #11936 - loading.get_models should not return deferred
# models by default.
- klasses = sorted(
- cache.get_models(cache.get_app("defer_regress")),
- key=lambda klass: klass.__name__
- )
- self.assertEqual(
- klasses, [
- Child,
- Feature,
- Item,
- ItemAndSimpleItem,
- Leaf,
- OneToOneItem,
- Proxy,
- RelatedItem,
- ResolveThis,
- SimpleItem,
- SpecialFeature,
- ]
+ # Run a couple of defer queries so that app-cache must contain some
+ # deferred classes. It might contain a lot more classes depending on
+ # the order the tests are ran.
+ list(Item.objects.defer("name"))
+ list(Child.objects.defer("value"))
+ klasses = set(
+ map(
+ attrgetter("__name__"),
+ cache.get_models(cache.get_app("defer_regress"))
+ )
)
+ self.assertIn("Child", klasses)
+ self.assertIn("Item", klasses)
+ self.assertNotIn("Child_Deferred_value", klasses)
+ self.assertNotIn("Item_Deferred_name", klasses)
+ self.assertFalse(any(
+ k._deferred for k in cache.get_models(cache.get_app("defer_regress"))))
- klasses = sorted(
+ klasses_with_deferred = set(
map(
attrgetter("__name__"),
cache.get_models(
@@ -128,40 +138,22 @@ class DeferRegressionTest(TestCase):
),
)
)
- # FIXME: This is dependent on the order in which tests are run --
- # this test case has to be the first, otherwise a LOT more classes
- # appear.
- self.assertEqual(
- klasses, [
- "Child",
- "Child_Deferred_value",
- "Feature",
- "Item",
- "ItemAndSimpleItem",
- "Item_Deferred_name",
- "Item_Deferred_name_other_value_text",
- "Item_Deferred_name_other_value_value",
- "Item_Deferred_other_value_text_value",
- "Item_Deferred_text_value",
- "Leaf",
- "Leaf_Deferred_child_id_second_child_id_value",
- "Leaf_Deferred_name_value",
- "Leaf_Deferred_second_child_id_value",
- "Leaf_Deferred_value",
- "OneToOneItem",
- "Proxy",
- "RelatedItem",
- "RelatedItem_Deferred_",
- "RelatedItem_Deferred_item_id",
- "ResolveThis",
- "SimpleItem",
- "SpecialFeature",
- ]
+ self.assertIn("Child", klasses_with_deferred)
+ self.assertIn("Item", klasses_with_deferred)
+ self.assertIn("Child_Deferred_value", klasses_with_deferred)
+ self.assertIn("Item_Deferred_name", klasses_with_deferred)
+ self.assertTrue(any(
+ k._deferred for k in cache.get_models(
+ cache.get_app("defer_regress"), include_deferred=True))
)
# Regression for #16409 - make sure defer() and only() work with annotate()
- self.assertIsInstance(list(SimpleItem.objects.annotate(Count('feature')).defer('name')), list)
- self.assertIsInstance(list(SimpleItem.objects.annotate(Count('feature')).only('name')), list)
+ self.assertIsInstance(
+ list(SimpleItem.objects.annotate(Count('feature')).defer('name')),
+ list)
+ self.assertIsInstance(
+ list(SimpleItem.objects.annotate(Count('feature')).only('name')),
+ list)
def test_only_and_defer_usage_on_proxy_models(self):
# Regression for #15790 - only() broken for proxy models
@@ -179,7 +171,7 @@ class DeferRegressionTest(TestCase):
self.assertEqual(dp.value, proxy.value, msg=msg)
def test_resolve_columns(self):
- rt = ResolveThis.objects.create(num=5.0, name='Foobar')
+ ResolveThis.objects.create(num=5.0, name='Foobar')
qs = ResolveThis.objects.defer('num')
self.assertEqual(1, qs.count())
self.assertEqual('Foobar', qs[0].name)
@@ -215,7 +207,7 @@ class DeferRegressionTest(TestCase):
item1 = Item.objects.create(name="first", value=47)
item2 = Item.objects.create(name="second", value=42)
simple = SimpleItem.objects.create(name="simple", value="23")
- related = ItemAndSimpleItem.objects.create(item=item1, simple=simple)
+ ItemAndSimpleItem.objects.create(item=item1, simple=simple)
obj = ItemAndSimpleItem.objects.defer('item').select_related('simple').get()
self.assertEqual(obj.item, item1)
@@ -242,7 +234,23 @@ class DeferRegressionTest(TestCase):
def test_deferred_class_factory(self):
from django.db.models.query_utils import deferred_class_factory
- new_class = deferred_class_factory(Item,
+ new_class = deferred_class_factory(
+ Item,
('this_is_some_very_long_attribute_name_so_modelname_truncation_is_triggered',))
- self.assertEqual(new_class.__name__,
+ self.assertEqual(
+ new_class.__name__,
'Item_Deferred_this_is_some_very_long_attribute_nac34b1f495507dad6b02e2cb235c875e')
+
+class DeferAnnotateSelectRelatedTest(TestCase):
+ def test_defer_annotate_select_related(self):
+ location = Location.objects.create()
+ Request.objects.create(location=location)
+ self.assertIsInstance(list(Request.objects
+ .annotate(Count('items')).select_related('profile', 'location')
+ .only('profile', 'location')), list)
+ self.assertIsInstance(list(Request.objects
+ .annotate(Count('items')).select_related('profile', 'location')
+ .only('profile__profile1', 'location__location1')), list)
+ self.assertIsInstance(list(Request.objects
+ .annotate(Count('items')).select_related('profile', 'location')
+ .defer('request1', 'request2', 'request3', 'request4')), list)
diff --git a/tests/dispatch/tests/test_dispatcher.py b/tests/dispatch/tests/test_dispatcher.py
index 5f8f92acaf..a1d4c7e176 100644
--- a/tests/dispatch/tests/test_dispatcher.py
+++ b/tests/dispatch/tests/test_dispatcher.py
@@ -108,7 +108,7 @@ class DispatcherTests(unittest.TestCase):
a_signal.connect(fails)
result = a_signal.send_robust(sender=self, val="test")
err = result[0][1]
- self.assertTrue(isinstance(err, ValueError))
+ self.assertIsInstance(err, ValueError)
self.assertEqual(err.args, ('this',))
a_signal.disconnect(fails)
self._testIsClean(a_signal)
diff --git a/tests/field_defaults/tests.py b/tests/field_defaults/tests.py
index 5d9b45610e..69dabb5168 100644
--- a/tests/field_defaults/tests.py
+++ b/tests/field_defaults/tests.py
@@ -14,6 +14,6 @@ class DefaultTests(TestCase):
now = datetime.now()
a.save()
- self.assertTrue(isinstance(a.id, six.integer_types))
+ self.assertIsInstance(a.id, six.integer_types)
self.assertEqual(a.headline, "Default headline")
self.assertTrue((now - a.pub_date).seconds < 5)
diff --git a/tests/field_subclassing/tests.py b/tests/field_subclassing/tests.py
index 9331ff2f3f..4945cff1bf 100644
--- a/tests/field_subclassing/tests.py
+++ b/tests/field_subclassing/tests.py
@@ -11,21 +11,21 @@ class CustomField(TestCase):
def test_defer(self):
d = DataModel.objects.create(data=[1, 2, 3])
- self.assertTrue(isinstance(d.data, list))
+ self.assertIsInstance(d.data, list)
d = DataModel.objects.get(pk=d.pk)
- self.assertTrue(isinstance(d.data, list))
+ self.assertIsInstance(d.data, list)
self.assertEqual(d.data, [1, 2, 3])
d = DataModel.objects.defer("data").get(pk=d.pk)
- self.assertTrue(isinstance(d.data, list))
+ self.assertIsInstance(d.data, list)
self.assertEqual(d.data, [1, 2, 3])
# Refetch for save
d = DataModel.objects.defer("data").get(pk=d.pk)
d.save()
d = DataModel.objects.get(pk=d.pk)
- self.assertTrue(isinstance(d.data, list))
+ self.assertIsInstance(d.data, list)
self.assertEqual(d.data, [1, 2, 3])
def test_custom_field(self):
@@ -44,7 +44,7 @@ class CustomField(TestCase):
# The data loads back from the database correctly and 'data' has the
# right type.
m1 = MyModel.objects.get(pk=m.pk)
- self.assertTrue(isinstance(m1.data, Small))
+ self.assertIsInstance(m1.data, Small)
self.assertEqual(str(m1.data), "12")
# We can do normal filtering on the custom field (and will get an error
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index e4b71dba82..406a4d79b6 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -588,11 +588,11 @@ class ContentFileTestCase(unittest.TestCase):
Test that ContentFile can accept both bytes and unicode and that the
retrieved content is of the same type.
"""
- self.assertTrue(isinstance(ContentFile(b"content").read(), bytes))
+ self.assertIsInstance(ContentFile(b"content").read(), bytes)
if six.PY3:
- self.assertTrue(isinstance(ContentFile("español").read(), six.text_type))
+ self.assertIsInstance(ContentFile("español").read(), six.text_type)
else:
- self.assertTrue(isinstance(ContentFile("español").read(), bytes))
+ self.assertIsInstance(ContentFile("español").read(), bytes)
def test_content_saving(self):
"""
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index 103612198e..1b4d6eeeef 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -1,5 +1,7 @@
from __future__ import absolute_import
+import warnings
+
from django.contrib.sites.models import Site
from django.core import management
from django.db import connection, IntegrityError
@@ -25,14 +27,15 @@ class TestCaseFixtureLoadingTests(TestCase):
class DumpDataAssertMixin(object):
def _dumpdata_assert(self, args, output, format='json', natural_keys=False,
- use_base_manager=False, exclude_list=[]):
+ use_base_manager=False, exclude_list=[], primary_keys=''):
new_io = six.StringIO()
management.call_command('dumpdata', *args, **{'format': format,
'stdout': new_io,
'stderr': new_io,
'use_natural_keys': natural_keys,
'use_base_manager': use_base_manager,
- 'exclude': exclude_list})
+ 'exclude': exclude_list,
+ 'primary_keys': primary_keys})
command_output = new_io.getvalue().strip()
if format == "json":
self.assertJSONEqual(command_output, output)
@@ -137,8 +140,19 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
'<Book: Music for all ages by Artist formerly known as "Prince" and Django Reinhardt>'
])
- # Load a fixture that doesn't exist
- management.call_command('loaddata', 'unknown.json', verbosity=0, commit=False)
+ # Loading a fixture that doesn't exist emits a warning
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ management.call_command('loaddata', 'unknown.json', verbosity=0,
+ commit=False)
+ self.assertEqual(len(w), 1)
+ self.assertTrue(w[0].message, "No fixture named 'unknown' found.")
+
+ # An attempt to load a nonexistent 'initial_data' fixture isn't an error
+ with warnings.catch_warnings(record=True) as w:
+ management.call_command('loaddata', 'initial_data.json', verbosity=0,
+ commit=False)
+ self.assertEqual(len(w), 0)
# object list is unaffected
self.assertQuerysetEqual(Article.objects.all(), [
@@ -211,6 +225,45 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
# even those normally filtered by the manager
self._dumpdata_assert(['fixtures.Spy'], '[{"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": true}}, {"pk": %d, "model": "fixtures.spy", "fields": {"cover_blown": false}}]' % (spy2.pk, spy1.pk), use_base_manager=True)
+ def test_dumpdata_with_pks(self):
+ management.call_command('loaddata', 'fixture1.json', verbosity=0, commit=False)
+ management.call_command('loaddata', 'fixture2.json', verbosity=0, commit=False)
+ self._dumpdata_assert(
+ ['fixtures.Article'],
+ '[{"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": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}]',
+ primary_keys='2,3'
+ )
+
+ self._dumpdata_assert(
+ ['fixtures.Article'],
+ '[{"pk": 2, "model": "fixtures.article", "fields": {"headline": "Poker has no place on ESPN", "pub_date": "2006-06-16T12:00:00"}}]',
+ primary_keys='2'
+ )
+
+ with six.assertRaisesRegex(self, management.CommandError,
+ "You can only use --pks option with one model"):
+ self._dumpdata_assert(
+ ['fixtures'],
+ '[{"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": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}]',
+ primary_keys='2,3'
+ )
+
+ with six.assertRaisesRegex(self, management.CommandError,
+ "You can only use --pks option with one model"):
+ self._dumpdata_assert(
+ '',
+ '[{"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": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}]',
+ primary_keys='2,3'
+ )
+
+ with six.assertRaisesRegex(self, management.CommandError,
+ "You can only use --pks option with one model"):
+ self._dumpdata_assert(
+ ['fixtures.Article', 'fixtures.category'],
+ '[{"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": "Copyright is fine the way it is", "pub_date": "2006-06-16T14:00:00"}}]',
+ primary_keys='2,3'
+ )
+
def test_compress_format_loading(self):
# Load fixture 4 (compressed), using format specification
management.call_command('loaddata', 'fixture4.json', verbosity=0, commit=False)
@@ -273,10 +326,11 @@ class FixtureLoadingTests(DumpDataAssertMixin, TestCase):
def test_unmatched_identifier_loading(self):
# Try to load db fixture 3. This won't load because the database identifier doesn't match
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False)
- self.assertQuerysetEqual(Article.objects.all(), [])
+ with warnings.catch_warnings(record=True):
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0, commit=False)
- management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False)
+ with warnings.catch_warnings(record=True):
+ management.call_command('loaddata', 'db_fixture_3', verbosity=0, using='default', commit=False)
self.assertQuerysetEqual(Article.objects.all(), [])
def test_output_formats(self):
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index c250f647ce..e0a35e300d 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -1,5 +1,7 @@
from __future__ import unicode_literals
+import warnings
+
from django.core import management
from django.db import transaction
from django.test import TestCase, TransactionTestCase
@@ -100,7 +102,12 @@ class FixtureTestCase(TestCase):
)
# Load a fixture that doesn't exist
- management.call_command("loaddata", "unknown.json", verbosity=0, commit=False)
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ management.call_command("loaddata", "unknown.json", verbosity=0, commit=False)
+ self.assertEqual(len(w), 1)
+ self.assertTrue(w[0].message, "No fixture named 'unknown' found.")
+
self.assertQuerysetEqual(
Article.objects.all(), [
"Django conquers world!",
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index 02e923e386..52526ec338 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -4,6 +4,7 @@ from __future__ import absolute_import, unicode_literals
import os
import re
+import warnings
from django.core.serializers.base import DeserializationError
from django.core import management
@@ -441,14 +442,15 @@ class TestFixtures(TestCase):
def test_loaddata_not_existant_fixture_file(self):
stdout_output = StringIO()
- management.call_command(
- 'loaddata',
- 'this_fixture_doesnt_exist',
- verbosity=2,
- commit=False,
- stdout=stdout_output,
- )
- self.assertTrue("No xml fixture 'this_fixture_doesnt_exist' in" in
+ with warnings.catch_warnings(record=True):
+ management.call_command(
+ 'loaddata',
+ 'this_fixture_doesnt_exist',
+ verbosity=2,
+ commit=False,
+ stdout=stdout_output,
+ )
+ self.assertTrue("No fixture 'this_fixture_doesnt_exist' in" in
force_text(stdout_output.getvalue()))
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index 55dd6a0f47..69636ee49b 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -316,6 +316,11 @@ class MultiColumnFKTests(TestCase):
list(Article.objects.filter(active_translation__abstract=None)),
[a1, a2])
+ def test_foreign_key_raises_informative_does_not_exist(self):
+ referrer = ArticleTranslation()
+ with self.assertRaisesMessage(Article.DoesNotExist, 'ArticleTranslation has no article'):
+ referrer.article
+
class FormsTests(TestCase):
# ForeignObjects should not have any form fields, currently the user needs
# to manually deal with the foreignobject relation.
diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py
index a83cdfc05f..ea0f063c30 100644
--- a/tests/forms_tests/tests/test_extra.py
+++ b/tests/forms_tests/tests/test_extra.py
@@ -569,6 +569,14 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
f = GenericIPAddressField(unpack_ipv4=True)
self.assertEqual(f.clean(' ::ffff:0a0a:0a0a'), '10.10.10.10')
+ def test_slugfield_normalization(self):
+ f = SlugField()
+ self.assertEqual(f.clean(' aa-bb-cc '), 'aa-bb-cc')
+
+ def test_urlfield_normalization(self):
+ f = URLField()
+ self.assertEqual(f.clean('http://example.com/ '), 'http://example.com/')
+
def test_smart_text(self):
class Test:
if six.PY3:
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index 7516de29b4..47c637befa 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -125,6 +125,15 @@ class FieldsTests(SimpleTestCase):
self.assertEqual(f.max_length, None)
self.assertEqual(f.min_length, 10)
+ def test_charfield_length_not_int(self):
+ """
+ Ensure that setting min_length or max_length to something that is not a
+ number returns an exception.
+ """
+ self.assertRaises(ValueError, CharField, min_length='a')
+ self.assertRaises(ValueError, CharField, max_length='a')
+ self.assertRaises(ValueError, CharField, 'a')
+
def test_charfield_widget_attrs(self):
"""
Ensure that CharField.widget_attrs() always returns a dictionary.
diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 45e62a492c..1a3fb44a66 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1398,7 +1398,7 @@ class FormsTestCase(TestCase):
birthday = DateField()
def add_prefix(self, field_name):
- return self.prefix and '%s-prefix-%s' % (self.prefix, field_name) or field_name
+ return '%s-prefix-%s' % (self.prefix, field_name) if self.prefix else field_name
p = Person(prefix='foo')
self.assertHTMLEqual(p.as_ul(), """<li><label for="id_foo-prefix-first_name">First name:</label> <input type="text" name="foo-prefix-first_name" id="id_foo-prefix-first_name" /></li>
@@ -1846,3 +1846,20 @@ class FormsTestCase(TestCase):
self.assertHTMLEqual(boundfield.label_tag(), 'Field')
self.assertHTMLEqual(boundfield.label_tag('Custom&'), 'Custom&amp;')
+
+ def test_boundfield_label_tag_custom_widget_id_for_label(self):
+ class CustomIdForLabelTextInput(TextInput):
+ def id_for_label(self, id):
+ return 'custom_' + id
+
+ class EmptyIdForLabelTextInput(TextInput):
+ def id_for_label(self, id):
+ return None
+
+ class SomeForm(Form):
+ custom = CharField(widget=CustomIdForLabelTextInput)
+ empty = CharField(widget=EmptyIdForLabelTextInput)
+
+ form = SomeForm()
+ self.assertHTMLEqual(form['custom'].label_tag(), '<label for="custom_id_custom">Custom</label>')
+ self.assertHTMLEqual(form['empty'].label_tag(), '<label>Empty</label>')
diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py
index 4ac3c5ecf1..1e9e7db30c 100644
--- a/tests/forms_tests/tests/test_formsets.py
+++ b/tests/forms_tests/tests/test_formsets.py
@@ -1,8 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
+import datetime
+
from django.forms import (CharField, DateField, FileField, Form, IntegerField,
- ValidationError, formsets)
+ SplitDateTimeField, ValidationError, formsets)
from django.forms.formsets import BaseFormSet, formset_factory
from django.forms.util import ErrorList
from django.test import TestCase
@@ -45,6 +47,13 @@ FavoriteDrinksFormSet = formset_factory(FavoriteDrinkForm,
formset=BaseFavoriteDrinksFormSet, extra=3)
+# Used in ``test_formset_splitdatetimefield``.
+class SplitDateTimeForm(Form):
+ when = SplitDateTimeField(initial=datetime.datetime.now)
+
+SplitDateTimeFormSet = formset_factory(SplitDateTimeForm)
+
+
class FormsFormsetTestCase(TestCase):
def test_basic_formset(self):
# A FormSet constructor takes the same arguments as Form. Let's create a FormSet
@@ -882,6 +891,19 @@ class FormsFormsetTestCase(TestCase):
self.assertEqual(len(formset.forms), 0)
self.assertTrue(formset)
+ def test_formset_splitdatetimefield(self):
+ """
+ Formset should also work with SplitDateTimeField(initial=datetime.datetime.now).
+ Regression test for #18709.
+ """
+ data = {
+ 'form-TOTAL_FORMS': '1',
+ 'form-INITIAL_FORMS': '0',
+ 'form-0-when_0': '1904-06-16',
+ 'form-0-when_1': '15:51:33',
+ }
+ formset = SplitDateTimeFormSet(data)
+ self.assertTrue(formset.is_valid())
def test_formset_error_class(self):
# Regression tests for #16479 -- formsets form use ErrorList instead of supplied error_class
@@ -972,6 +994,38 @@ class FormsFormsetTestCase(TestCase):
finally:
formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM
+ def test_non_form_errors_run_full_clean(self):
+ # Regression test for #11160
+ # If non_form_errors() is called without calling is_valid() first,
+ # it should ensure that full_clean() is called.
+ class BaseCustomFormSet(BaseFormSet):
+ def clean(self):
+ raise ValidationError("This is a non-form error")
+
+ ChoiceFormSet = formset_factory(Choice, formset=BaseCustomFormSet)
+ formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
+ self.assertIsInstance(formset.non_form_errors(), ErrorList)
+ self.assertEqual(list(formset.non_form_errors()),
+ ['This is a non-form error'])
+
+ def test_validate_max_ignores_forms_marked_for_deletion(self):
+ class CheckForm(Form):
+ field = IntegerField()
+
+ data = {
+ 'check-TOTAL_FORMS': '2',
+ 'check-INITIAL_FORMS': '0',
+ 'check-MAX_NUM_FORMS': '1',
+ 'check-0-field': '200',
+ 'check-0-DELETE': '',
+ 'check-1-field': '50',
+ 'check-1-DELETE': 'on',
+ }
+ CheckFormSet = formset_factory(CheckForm, max_num=1, validate_max=True,
+ can_delete=True)
+ formset = CheckFormSet(data, prefix='check')
+ self.assertTrue(formset.is_valid())
+
data = {
'choices-TOTAL_FORMS': '1', # the number of forms rendered
diff --git a/tests/forms_tests/tests/test_regressions.py b/tests/forms_tests/tests/test_regressions.py
index 74509a0f1a..ea138d32d5 100644
--- a/tests/forms_tests/tests/test_regressions.py
+++ b/tests/forms_tests/tests/test_regressions.py
@@ -8,9 +8,10 @@ from django.test import TestCase
from django.utils.translation import ugettext_lazy, override
from forms_tests.models import Cheese
+from i18n import TransRealMixin
-class FormsRegressionsTestCase(TestCase):
+class FormsRegressionsTestCase(TransRealMixin, TestCase):
def test_class(self):
# Tests to prevent against recurrences of earlier bugs.
extra_attrs = {'class': 'special'}
diff --git a/tests/forms_tests/tests/tests.py b/tests/forms_tests/tests/tests.py
index deda4822b8..2616ddaf7d 100644
--- a/tests/forms_tests/tests/tests.py
+++ b/tests/forms_tests/tests/tests.py
@@ -5,7 +5,7 @@ import datetime
from django.core.files.uploadedfile import SimpleUploadedFile
from django.db import models
-from django.forms import Form, ModelForm, FileField, ModelChoiceField
+from django.forms import Form, ModelForm, FileField, ModelChoiceField, CharField
from django.forms.models import ModelFormMetaclass
from django.test import TestCase
from django.utils import six
@@ -26,6 +26,14 @@ class OptionalMultiChoiceModelForm(ModelForm):
fields = '__all__'
+class ChoiceFieldExclusionForm(ModelForm):
+ multi_choice = CharField(max_length=50)
+
+ class Meta:
+ exclude = ['multi_choice']
+ model = ChoiceFieldModel
+
+
class FileForm(Form):
file1 = FileField()
@@ -52,9 +60,9 @@ class TestTicket14567(TestCase):
form = OptionalMultiChoiceModelForm({'multi_choice_optional': '', 'multi_choice': [option.pk]})
self.assertTrue(form.is_valid())
# Check that the empty value is a QuerySet
- self.assertTrue(isinstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet))
+ self.assertIsInstance(form.cleaned_data['multi_choice_optional'], models.query.QuerySet)
# While we're at it, test whether a QuerySet is returned if there *is* a value.
- self.assertTrue(isinstance(form.cleaned_data['multi_choice'], models.query.QuerySet))
+ self.assertIsInstance(form.cleaned_data['multi_choice'], models.query.QuerySet)
class ModelFormCallableModelDefault(TestCase):
@@ -221,3 +229,31 @@ class RelatedModelFormTests(TestCase):
model=A
self.assertTrue(issubclass(ModelFormMetaclass(str('Form'), (ModelForm,), {'Meta': Meta}), ModelForm))
+
+
+class ManyToManyExclusionTestCase(TestCase):
+ def test_m2m_field_exclusion(self):
+ # Issue 12337. save_instance should honor the passed-in exclude keyword.
+ opt1 = ChoiceOptionModel.objects.create(id=1, name='default')
+ opt2 = ChoiceOptionModel.objects.create(id=2, name='option 2')
+ opt3 = ChoiceOptionModel.objects.create(id=3, name='option 3')
+ initial = {
+ 'choice': opt1,
+ 'choice_int': opt1,
+ }
+ data = {
+ 'choice': opt2.pk,
+ 'choice_int': opt2.pk,
+ 'multi_choice': 'string data!',
+ 'multi_choice_int': [opt1.pk],
+ }
+ instance = ChoiceFieldModel.objects.create(**initial)
+ instance.multi_choice = instance.multi_choice_int = [opt2, opt3]
+ form = ChoiceFieldExclusionForm(data=data, instance=instance)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['multi_choice'], data['multi_choice'])
+ form.save()
+ self.assertEqual(form.instance.choice.pk, data['choice'])
+ self.assertEqual(form.instance.choice_int.pk, data['choice_int'])
+ self.assertEqual(list(form.instance.multi_choice.all()), [opt2, opt3])
+ self.assertEqual([obj.pk for obj in form.instance.multi_choice_int.all()], data['multi_choice_int'])
diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py
index 2acb982b9a..211df8aa3d 100644
--- a/tests/generic_relations/models.py
+++ b/tests/generic_relations/models.py
@@ -102,3 +102,22 @@ class Rock(Mineral):
class ManualPK(models.Model):
id = models.IntegerField(primary_key=True)
tags = generic.GenericRelation(TaggedItem)
+
+
+class ForProxyModelModel(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ obj = generic.GenericForeignKey(for_concrete_model=False)
+ title = models.CharField(max_length=255, null=True)
+
+class ForConcreteModelModel(models.Model):
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ obj = generic.GenericForeignKey()
+
+class ConcreteRelatedModel(models.Model):
+ bases = generic.GenericRelation(ForProxyModelModel, for_concrete_model=False)
+
+class ProxyRelatedModel(ConcreteRelatedModel):
+ class Meta:
+ proxy = True
diff --git a/tests/generic_relations/tests.py b/tests/generic_relations/tests.py
index 79c7bc6184..734b2e5143 100644
--- a/tests/generic_relations/tests.py
+++ b/tests/generic_relations/tests.py
@@ -6,7 +6,9 @@ from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from .models import (TaggedItem, ValuableTaggedItem, Comparison, Animal,
- Vegetable, Mineral, Gecko, Rock, ManualPK)
+ Vegetable, Mineral, Gecko, Rock, ManualPK,
+ ForProxyModelModel, ForConcreteModelModel,
+ ProxyRelatedModel, ConcreteRelatedModel)
class GenericRelationsTests(TestCase):
@@ -245,6 +247,22 @@ class GenericRelationsTests(TestCase):
TaggedItem.objects.create(content_object=granite, tag="countertop")
self.assertEqual(Rock.objects.filter(tags__tag="countertop").count(), 1)
+ def test_generic_inline_formsets_initial(self):
+ """
+ Test for #17927 Initial values support for BaseGenericInlineFormSet.
+ """
+ quartz = Mineral.objects.create(name="Quartz", hardness=7)
+
+ GenericFormSet = generic_inlineformset_factory(TaggedItem, extra=1)
+ ctype = ContentType.objects.get_for_model(quartz)
+ initial_data = [{
+ 'tag': 'lizard',
+ 'content_type': ctype.pk,
+ 'object_id': quartz.pk,
+ }]
+ formset = GenericFormSet(initial=initial_data)
+ self.assertEqual(formset.forms[0].initial, initial_data[0])
+
class CustomWidget(forms.TextInput):
pass
@@ -256,12 +274,120 @@ class TaggedItemForm(forms.ModelForm):
widgets = {'tag': CustomWidget}
class GenericInlineFormsetTest(TestCase):
- """
- Regression for #14572: Using base forms with widgets
- defined in Meta should not raise errors.
- """
-
def test_generic_inlineformset_factory(self):
+ """
+ Regression for #14572: Using base forms with widgets
+ defined in Meta should not raise errors.
+ """
Formset = generic_inlineformset_factory(TaggedItem, TaggedItemForm)
form = Formset().forms[0]
- self.assertTrue(isinstance(form['tag'].field.widget, CustomWidget))
+ self.assertIsInstance(form['tag'].field.widget, CustomWidget)
+
+ def test_save_new_for_proxy(self):
+ Formset = generic_inlineformset_factory(ForProxyModelModel,
+ fields='__all__', for_concrete_model=False)
+
+ instance = ProxyRelatedModel.objects.create()
+
+ data = {
+ 'form-TOTAL_FORMS': '1',
+ 'form-INITIAL_FORMS': '0',
+ 'form-MAX_NUM_FORMS': '',
+ 'form-0-title': 'foo',
+ }
+
+ formset = Formset(data, instance=instance, prefix='form')
+ self.assertTrue(formset.is_valid())
+
+ new_obj, = formset.save()
+ self.assertEqual(new_obj.obj, instance)
+
+ def test_save_new_for_concrete(self):
+ Formset = generic_inlineformset_factory(ForProxyModelModel,
+ fields='__all__', for_concrete_model=True)
+
+ instance = ProxyRelatedModel.objects.create()
+
+ data = {
+ 'form-TOTAL_FORMS': '1',
+ 'form-INITIAL_FORMS': '0',
+ 'form-MAX_NUM_FORMS': '',
+ 'form-0-title': 'foo',
+ }
+
+ formset = Formset(data, instance=instance, prefix='form')
+ self.assertTrue(formset.is_valid())
+
+ new_obj, = formset.save()
+ self.assertNotIsInstance(new_obj.obj, ProxyRelatedModel)
+
+
+class ProxyRelatedModelTest(TestCase):
+ def test_default_behavior(self):
+ """
+ The default for for_concrete_model should be True
+ """
+ base = ForConcreteModelModel()
+ base.obj = rel = ProxyRelatedModel.objects.create()
+ base.save()
+
+ base = ForConcreteModelModel.objects.get(pk=base.pk)
+ rel = ConcreteRelatedModel.objects.get(pk=rel.pk)
+ self.assertEqual(base.obj, rel)
+
+ def test_works_normally(self):
+ """
+ When for_concrete_model is False, we should still be able to get
+ an instance of the concrete class.
+ """
+ base = ForProxyModelModel()
+ base.obj = rel = ConcreteRelatedModel.objects.create()
+ base.save()
+
+ base = ForProxyModelModel.objects.get(pk=base.pk)
+ self.assertEqual(base.obj, rel)
+
+ def test_proxy_is_returned(self):
+ """
+ Instances of the proxy should be returned when
+ for_concrete_model is False.
+ """
+ base = ForProxyModelModel()
+ base.obj = ProxyRelatedModel.objects.create()
+ base.save()
+
+ base = ForProxyModelModel.objects.get(pk=base.pk)
+ self.assertIsInstance(base.obj, ProxyRelatedModel)
+
+ def test_query(self):
+ base = ForProxyModelModel()
+ base.obj = rel = ConcreteRelatedModel.objects.create()
+ base.save()
+
+ self.assertEqual(rel, ConcreteRelatedModel.objects.get(bases__id=base.id))
+
+ def test_query_proxy(self):
+ base = ForProxyModelModel()
+ base.obj = rel = ProxyRelatedModel.objects.create()
+ base.save()
+
+ self.assertEqual(rel, ProxyRelatedModel.objects.get(bases__id=base.id))
+
+ def test_generic_relation(self):
+ base = ForProxyModelModel()
+ base.obj = ProxyRelatedModel.objects.create()
+ base.save()
+
+ base = ForProxyModelModel.objects.get(pk=base.pk)
+ rel = ProxyRelatedModel.objects.get(pk=base.obj.pk)
+ self.assertEqual(base, rel.bases.get())
+
+ def test_generic_relation_set(self):
+ base = ForProxyModelModel()
+ base.obj = ConcreteRelatedModel.objects.create()
+ base.save()
+ newrel = ConcreteRelatedModel.objects.create()
+
+ newrel.bases = [base]
+ newrel = ConcreteRelatedModel.objects.get(pk=newrel.pk)
+ self.assertEqual(base, newrel.bases.get())
diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py
index 2795471f7f..d716f09058 100644
--- a/tests/generic_relations_regress/models.py
+++ b/tests/generic_relations_regress/models.py
@@ -122,3 +122,36 @@ class Tag(models.Model):
class Board(models.Model):
name = models.CharField(primary_key=True, max_length=15)
+
+class HasLinks(models.Model):
+ links = generic.GenericRelation(Link)
+
+ class Meta:
+ abstract = True
+
+class HasLinkThing(HasLinks):
+ pass
+
+class A(models.Model):
+ flag = models.NullBooleanField()
+ content_type = models.ForeignKey(ContentType)
+ object_id = models.PositiveIntegerField()
+ content_object = generic.GenericForeignKey('content_type', 'object_id')
+
+class B(models.Model):
+ a = generic.GenericRelation(A)
+
+ class Meta:
+ ordering = ('id',)
+
+class C(models.Model):
+ b = models.ForeignKey(B)
+
+ class Meta:
+ ordering = ('id',)
+
+class D(models.Model):
+ b = models.ForeignKey(B, null=True)
+
+ class Meta:
+ ordering = ('id',)
diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py
index 474113584a..75edff34f2 100644
--- a/tests/generic_relations_regress/tests.py
+++ b/tests/generic_relations_regress/tests.py
@@ -2,9 +2,10 @@ from django.db.models import Q
from django.db.utils import IntegrityError
from django.test import TestCase, skipIfDBFeature
-from .models import (Address, Place, Restaurant, Link, CharLink, TextLink,
+from .models import (
+ Address, Place, Restaurant, Link, CharLink, TextLink,
Person, Contact, Note, Organization, OddRelation1, OddRelation2, Company,
- Developer, Team, Guild, Tag, Board)
+ Developer, Team, Guild, Tag, Board, HasLinkThing, A, B, C, D)
class GenericRelationTests(TestCase):
@@ -28,9 +29,9 @@ class GenericRelationTests(TestCase):
originating model of a query. See #12664.
"""
p = Person.objects.create(account=23, name='Chef')
- a = Address.objects.create(street='123 Anywhere Place',
- city='Conifer', state='CO',
- zipcode='80433', content_object=p)
+ Address.objects.create(street='123 Anywhere Place',
+ city='Conifer', state='CO',
+ zipcode='80433', content_object=p)
qs = Person.objects.filter(addresses__zipcode='80433')
self.assertEqual(1, qs.count())
@@ -38,12 +39,12 @@ class GenericRelationTests(TestCase):
def test_charlink_delete(self):
oddrel = OddRelation1.objects.create(name='clink')
- cl = CharLink.objects.create(content_object=oddrel)
+ CharLink.objects.create(content_object=oddrel)
oddrel.delete()
def test_textlink_delete(self):
oddrel = OddRelation2.objects.create(name='tlink')
- tl = TextLink.objects.create(content_object=oddrel)
+ TextLink.objects.create(content_object=oddrel)
oddrel.delete()
def test_q_object_or(self):
@@ -61,12 +62,12 @@ class GenericRelationTests(TestCase):
"""
note_contact = Contact.objects.create()
org_contact = Contact.objects.create()
- note = Note.objects.create(note='note', content_object=note_contact)
+ Note.objects.create(note='note', content_object=note_contact)
org = Organization.objects.create(name='org name')
org.contacts.add(org_contact)
# search with a non-matching note and a matching org name
qs = Contact.objects.filter(Q(notes__note__icontains=r'other note') |
- Q(organizations__name__icontains=r'org name'))
+ Q(organizations__name__icontains=r'org name'))
self.assertTrue(org_contact in qs)
# search again, with the same query parameters, in reverse order
qs = Contact.objects.filter(
@@ -90,8 +91,8 @@ class GenericRelationTests(TestCase):
p1 = Place.objects.create(name="South Park")
p2 = Place.objects.create(name="The City")
c = Company.objects.create(name="Chubby's Intl.")
- l1 = Link.objects.create(content_object=p1)
- l2 = Link.objects.create(content_object=c)
+ Link.objects.create(content_object=p1)
+ Link.objects.create(content_object=c)
places = list(Place.objects.order_by('links__id'))
def count_places(place):
@@ -114,8 +115,10 @@ class GenericRelationTests(TestCase):
try:
note = Note(note='Deserve a bonus', content_object=team1)
except Exception as e:
- if issubclass(type(e), Exception) and str(e) == 'Impossible arguments to GFK.get_content_type!':
- self.fail("Saving model with GenericForeignKey to model instance whose __len__ method returns 0 shouldn't fail.")
+ if (issubclass(type(e), Exception) and
+ str(e) == 'Impossible arguments to GFK.get_content_type!'):
+ self.fail("Saving model with GenericForeignKey to model instance whose "
+ "__len__ method returns 0 shouldn't fail.")
raise e
note.save()
@@ -135,3 +138,77 @@ class GenericRelationTests(TestCase):
b1 = Board.objects.create(name='')
tag = Tag(label='VP', content_object=b1)
tag.save()
+
+ def test_ticket_20378(self):
+ hs1 = HasLinkThing.objects.create()
+ hs2 = HasLinkThing.objects.create()
+ l1 = Link.objects.create(content_object=hs1)
+ l2 = Link.objects.create(content_object=hs2)
+ self.assertQuerysetEqual(
+ HasLinkThing.objects.filter(links=l1),
+ [hs1], lambda x: x)
+ self.assertQuerysetEqual(
+ HasLinkThing.objects.filter(links=l2),
+ [hs2], lambda x: x)
+ self.assertQuerysetEqual(
+ HasLinkThing.objects.exclude(links=l2),
+ [hs1], lambda x: x)
+ self.assertQuerysetEqual(
+ HasLinkThing.objects.exclude(links=l1),
+ [hs2], lambda x: x)
+
+ def test_ticket_20564(self):
+ b1 = B.objects.create()
+ b2 = B.objects.create()
+ b3 = B.objects.create()
+ c1 = C.objects.create(b=b1)
+ c2 = C.objects.create(b=b2)
+ c3 = C.objects.create(b=b3)
+ A.objects.create(flag=None, content_object=b1)
+ A.objects.create(flag=True, content_object=b2)
+ self.assertQuerysetEqual(
+ C.objects.filter(b__a__flag=None),
+ [c1, c3], lambda x: x
+ )
+ self.assertQuerysetEqual(
+ C.objects.exclude(b__a__flag=None),
+ [c2], lambda x: x
+ )
+
+ def test_ticket_20564_nullable_fk(self):
+ b1 = B.objects.create()
+ b2 = B.objects.create()
+ b3 = B.objects.create()
+ d1 = D.objects.create(b=b1)
+ d2 = D.objects.create(b=b2)
+ d3 = D.objects.create(b=b3)
+ d4 = D.objects.create()
+ A.objects.create(flag=None, content_object=b1)
+ A.objects.create(flag=True, content_object=b1)
+ A.objects.create(flag=True, content_object=b2)
+ self.assertQuerysetEqual(
+ D.objects.exclude(b__a__flag=None),
+ [d2], lambda x: x
+ )
+ self.assertQuerysetEqual(
+ D.objects.filter(b__a__flag=None),
+ [d1, d3, d4], lambda x: x
+ )
+ self.assertQuerysetEqual(
+ B.objects.filter(a__flag=None),
+ [b1, b3], lambda x: x
+ )
+ self.assertQuerysetEqual(
+ B.objects.exclude(a__flag=None),
+ [b2], lambda x: x
+ )
+
+ def test_extra_join_condition(self):
+ # A crude check that content_type_id is taken in account in the
+ # join/subquery condition.
+ self.assertIn("content_type_id", str(B.objects.exclude(a__flag=None).query).lower())
+ # No need for any joins - the join from inner query can be trimmed in
+ # this case (but not in the above case as no a objects at all for given
+ # B would then fail).
+ self.assertNotIn(" join ", str(B.objects.exclude(a__flag=True).query).lower())
+ self.assertIn("content_type_id", str(B.objects.exclude(a__flag=True).query).lower())
diff --git a/tests/generic_views/test_base.py b/tests/generic_views/test_base.py
index 0e84e17132..ffd9b1b480 100644
--- a/tests/generic_views/test_base.py
+++ b/tests/generic_views/test_base.py
@@ -278,7 +278,7 @@ class TemplateViewTest(TestCase):
response = self.client.get('/template/simple/bar/')
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo'], 'bar')
- self.assertTrue(isinstance(response.context['view'], View))
+ self.assertIsInstance(response.context['view'], View)
def test_extra_template_params(self):
"""
@@ -288,7 +288,7 @@ class TemplateViewTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context['foo'], 'bar')
self.assertEqual(response.context['key'], 'value')
- self.assertTrue(isinstance(response.context['view'], View))
+ self.assertIsInstance(response.context['view'], View)
def test_cached_views(self):
"""
@@ -384,6 +384,12 @@ class RedirectViewTest(unittest.TestCase):
self.assertEqual(response.status_code, 301)
self.assertEqual(response.url, '/bar/')
+ def test_redirect_PATCH(self):
+ "Default is a permanent redirect"
+ response = RedirectView.as_view(url='/bar/')(self.rf.patch('/foo/'))
+ self.assertEqual(response.status_code, 301)
+ self.assertEqual(response.url, '/bar/')
+
def test_redirect_DELETE(self):
"Default is a permanent redirect"
response = RedirectView.as_view(url='/bar/')(self.rf.delete('/foo/'))
@@ -411,3 +417,36 @@ class GetContextDataTest(unittest.TestCase):
# test that kwarg overrides values assigned higher up
context = test_view.get_context_data(test_name='test_value')
self.assertEqual(context['test_name'], 'test_value')
+
+ def test_object_at_custom_name_in_context_data(self):
+ # Checks 'pony' key presence in dict returned by get_context_date
+ test_view = views.CustomSingleObjectView()
+ test_view.context_object_name = 'pony'
+ context = test_view.get_context_data()
+ self.assertEqual(context['pony'], test_view.object)
+
+ def test_object_in_get_context_data(self):
+ # Checks 'object' key presence in dict returned by get_context_date #20234
+ test_view = views.CustomSingleObjectView()
+ context = test_view.get_context_data()
+ self.assertEqual(context['object'], test_view.object)
+
+
+class UseMultipleObjectMixinTest(unittest.TestCase):
+ rf = RequestFactory()
+
+ def test_use_queryset_from_view(self):
+ test_view = views.CustomMultipleObjectMixinView()
+ test_view.get(self.rf.get('/'))
+ # Don't pass queryset as argument
+ context = test_view.get_context_data()
+ self.assertEqual(context['object_list'], test_view.queryset)
+
+ def test_overwrite_queryset(self):
+ test_view = views.CustomMultipleObjectMixinView()
+ test_view.get(self.rf.get('/'))
+ queryset = [{'name': 'Lennon'}, {'name': 'Ono'}]
+ self.assertNotEqual(test_view.queryset, queryset)
+ # Overwrite the view's queryset with queryset from kwarg
+ context = test_view.get_context_data(object_list=queryset)
+ self.assertEqual(context['object_list'], queryset)
diff --git a/tests/generic_views/test_detail.py b/tests/generic_views/test_detail.py
index faee05688e..3a97d27995 100644
--- a/tests/generic_views/test_detail.py
+++ b/tests/generic_views/test_detail.py
@@ -15,7 +15,7 @@ class DetailViewTest(TestCase):
res = self.client.get('/detail/obj/')
self.assertEqual(res.status_code, 200)
self.assertEqual(res.context['object'], {'foo': 'bar'})
- self.assertTrue(isinstance(res.context['view'], View))
+ self.assertIsInstance(res.context['view'], View)
self.assertTemplateUsed(res, 'generic_views/detail.html')
def test_detail_by_pk(self):
diff --git a/tests/generic_views/test_edit.py b/tests/generic_views/test_edit.py
index 54eab7ffa4..435e48ba99 100644
--- a/tests/generic_views/test_edit.py
+++ b/tests/generic_views/test_edit.py
@@ -43,8 +43,8 @@ class CreateViewTests(TestCase):
def test_create(self):
res = self.client.get('/edit/authors/create/')
self.assertEqual(res.status_code, 200)
- self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
- self.assertTrue(isinstance(res.context['view'], View))
+ self.assertIsInstance(res.context['form'], forms.ModelForm)
+ self.assertIsInstance(res.context['view'], View)
self.assertFalse('object' in res.context)
self.assertFalse('author' in res.context)
self.assertTemplateUsed(res, 'generic_views/author_form.html')
@@ -89,7 +89,7 @@ class CreateViewTests(TestCase):
def test_create_with_special_properties(self):
res = self.client.get('/edit/authors/create/special/')
self.assertEqual(res.status_code, 200)
- self.assertTrue(isinstance(res.context['form'], views.AuthorForm))
+ self.assertIsInstance(res.context['form'], views.AuthorForm)
self.assertFalse('object' in res.context)
self.assertFalse('author' in res.context)
self.assertTemplateUsed(res, 'generic_views/form.html')
@@ -165,7 +165,7 @@ class UpdateViewTests(TestCase):
)
res = self.client.get('/edit/author/%d/update/' % a.pk)
self.assertEqual(res.status_code, 200)
- self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
+ self.assertIsInstance(res.context['form'], forms.ModelForm)
self.assertEqual(res.context['object'], Author.objects.get(pk=a.pk))
self.assertEqual(res.context['author'], Author.objects.get(pk=a.pk))
self.assertTemplateUsed(res, 'generic_views/author_form.html')
@@ -247,7 +247,7 @@ class UpdateViewTests(TestCase):
)
res = self.client.get('/edit/author/%d/update/special/' % a.pk)
self.assertEqual(res.status_code, 200)
- self.assertTrue(isinstance(res.context['form'], views.AuthorForm))
+ self.assertIsInstance(res.context['form'], views.AuthorForm)
self.assertEqual(res.context['object'], Author.objects.get(pk=a.pk))
self.assertEqual(res.context['thingy'], Author.objects.get(pk=a.pk))
self.assertFalse('author' in res.context)
@@ -279,8 +279,8 @@ class UpdateViewTests(TestCase):
)
res = self.client.get('/edit/author/update/')
self.assertEqual(res.status_code, 200)
- self.assertTrue(isinstance(res.context['form'], forms.ModelForm))
- self.assertTrue(isinstance(res.context['view'], View))
+ self.assertIsInstance(res.context['form'], forms.ModelForm)
+ self.assertIsInstance(res.context['view'], View)
self.assertEqual(res.context['object'], Author.objects.get(pk=a.pk))
self.assertEqual(res.context['author'], Author.objects.get(pk=a.pk))
self.assertTemplateUsed(res, 'generic_views/author_form.html')
diff --git a/tests/generic_views/test_list.py b/tests/generic_views/test_list.py
index cc4d2f5966..a77a6418a3 100644
--- a/tests/generic_views/test_list.py
+++ b/tests/generic_views/test_list.py
@@ -24,7 +24,7 @@ class ListViewTests(TestCase):
self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res, 'generic_views/author_list.html')
self.assertEqual(list(res.context['object_list']), list(Author.objects.all()))
- self.assertTrue(isinstance(res.context['view'], View))
+ self.assertIsInstance(res.context['view'], View)
self.assertIs(res.context['author_list'], res.context['object_list'])
self.assertIsNone(res.context['paginator'])
self.assertIsNone(res.context['page_obj'])
diff --git a/tests/generic_views/views.py b/tests/generic_views/views.py
index aa8777e8c6..fd331f14b7 100644
--- a/tests/generic_views/views.py
+++ b/tests/generic_views/views.py
@@ -1,7 +1,6 @@
from __future__ import absolute_import
from django.contrib.auth.decorators import login_required
-from django.contrib.messages.views import SuccessMessageMixin
from django.core.paginator import Paginator
from django.core.urlresolvers import reverse, reverse_lazy
from django.utils.decorators import method_decorator
@@ -201,6 +200,17 @@ class BookDetailGetObjectCustomQueryset(BookDetail):
return super(BookDetailGetObjectCustomQueryset,self).get_object(
queryset=Book.objects.filter(pk=2))
+
+class CustomMultipleObjectMixinView(generic.list.MultipleObjectMixin, generic.View):
+ queryset = [
+ {'name': 'John'},
+ {'name': 'Yoko'},
+ ]
+
+ def get(self, request):
+ self.object_list = self.get_queryset()
+
+
class CustomContextView(generic.detail.SingleObjectMixin, generic.View):
model = Book
object = Book(name='dummy')
@@ -216,6 +226,10 @@ class CustomContextView(generic.detail.SingleObjectMixin, generic.View):
def get_context_object_name(self, obj):
return "test_name"
+class CustomSingleObjectView(generic.detail.SingleObjectMixin, generic.View):
+ model = Book
+ object = Book(name="dummy")
+
class BookSigningConfig(object):
model = BookSigning
date_field = 'event_date'
diff --git a/tests/get_earliest_or_latest/tests.py b/tests/get_earliest_or_latest/tests.py
index 6317a0974c..8d16af9587 100644
--- a/tests/get_earliest_or_latest/tests.py
+++ b/tests/get_earliest_or_latest/tests.py
@@ -121,3 +121,34 @@ class EarliestOrLatestTests(TestCase):
p2 = Person.objects.create(name="Stephanie", birthday=datetime(1960, 2, 3))
self.assertRaises(AssertionError, Person.objects.latest)
self.assertEqual(Person.objects.latest("birthday"), p2)
+
+ def test_first(self):
+ p1 = Person.objects.create(name="Bob", birthday=datetime(1950, 1, 1))
+ p2 = Person.objects.create(name="Alice", birthday=datetime(1961, 2, 3))
+ self.assertEqual(
+ Person.objects.first(), p1)
+ self.assertEqual(
+ Person.objects.order_by('name').first(), p2)
+ self.assertEqual(
+ Person.objects.filter(birthday__lte=datetime(1955, 1, 1)).first(),
+ p1)
+ self.assertIs(
+ Person.objects.filter(birthday__lte=datetime(1940, 1, 1)).first(),
+ None)
+
+ def test_last(self):
+ p1 = Person.objects.create(
+ name="Alice", birthday=datetime(1950, 1, 1))
+ p2 = Person.objects.create(
+ name="Bob", birthday=datetime(1960, 2, 3))
+ # Note: by default PK ordering.
+ self.assertEqual(
+ Person.objects.last(), p2)
+ self.assertEqual(
+ Person.objects.order_by('-name').last(), p1)
+ self.assertEqual(
+ Person.objects.filter(birthday__lte=datetime(1955, 1, 1)).last(),
+ p1)
+ self.assertIs(
+ Person.objects.filter(birthday__lte=datetime(1940, 1, 1)).last(),
+ None)
diff --git a/tests/get_or_create/models.py b/tests/get_or_create/models.py
index 82905de4f8..1a85de2e74 100644
--- a/tests/get_or_create/models.py
+++ b/tests/get_or_create/models.py
@@ -21,6 +21,11 @@ class Person(models.Model):
def __str__(self):
return '%s %s' % (self.first_name, self.last_name)
+
+class DefaultPerson(models.Model):
+ first_name = models.CharField(max_length=100, default="Anonymous")
+
+
class ManualPrimaryKeyTest(models.Model):
id = models.IntegerField(primary_key=True)
data = models.CharField(max_length=100)
@@ -28,3 +33,12 @@ class ManualPrimaryKeyTest(models.Model):
class Profile(models.Model):
person = models.ForeignKey(Person, primary_key=True)
+
+
+class Tag(models.Model):
+ text = models.CharField(max_length=255, unique=True)
+
+
+class Thing(models.Model):
+ name = models.CharField(max_length=256)
+ tags = models.ManyToManyField(Tag)
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index e9cce9bbde..36c248b169 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -2,14 +2,17 @@ from __future__ import absolute_import
from datetime import date
import traceback
+import warnings
-from django.db import IntegrityError
+from django.db import IntegrityError, DatabaseError
+from django.utils.encoding import DjangoUnicodeDecodeError
from django.test import TestCase, TransactionTestCase
-from .models import Person, ManualPrimaryKeyTest, Profile
+from .models import DefaultPerson, Person, ManualPrimaryKeyTest, Profile, Tag, Thing
class GetOrCreateTests(TestCase):
+
def test_get_or_create(self):
p = Person.objects.create(
first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)
@@ -64,6 +67,30 @@ class GetOrCreateTests(TestCase):
formatted_traceback = traceback.format_exc()
self.assertIn('obj.save', formatted_traceback)
+ def test_savepoint_rollback(self):
+ # Regression test for #20463: the database connection should still be
+ # usable after a DataError or ProgrammingError in .get_or_create().
+ try:
+ # Hide warnings when broken data is saved with a warning (MySQL).
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore')
+ Person.objects.get_or_create(
+ birthday=date(1970, 1, 1),
+ defaults={'first_name': b"\xff", 'last_name': b"\xff"})
+ except (DatabaseError, DjangoUnicodeDecodeError):
+ Person.objects.create(
+ first_name="Bob", last_name="Ross", birthday=date(1950, 1, 1))
+ else:
+ self.skipTest("This backend accepts broken utf-8.")
+
+ def test_get_or_create_empty(self):
+ # Regression test for #16137: get_or_create does not require kwargs.
+ try:
+ DefaultPerson.objects.get_or_create()
+ except AssertionError:
+ self.fail("If all the attributes on a model have defaults, we "
+ "shouldn't need to pass any arguments.")
+
class GetOrCreateTransactionTests(TransactionTestCase):
@@ -77,3 +104,28 @@ class GetOrCreateTransactionTests(TransactionTestCase):
pass
else:
self.skipTest("This backend does not support integrity checks.")
+
+
+class GetOrCreateThroughManyToMany(TestCase):
+
+ def test_get_get_or_create(self):
+ tag = Tag.objects.create(text='foo')
+ a_thing = Thing.objects.create(name='a')
+ a_thing.tags.add(tag)
+ obj, created = a_thing.tags.get_or_create(text='foo')
+
+ self.assertFalse(created)
+ self.assertEqual(obj.pk, tag.pk)
+
+ def test_create_get_or_create(self):
+ a_thing = Thing.objects.create(name='a')
+ obj, created = a_thing.tags.get_or_create(text='foo')
+
+ self.assertTrue(created)
+ self.assertEqual(obj.text, 'foo')
+ self.assertIn(obj, a_thing.tags.all())
+
+ def test_something(self):
+ Tag.objects.create(text='foo')
+ a_thing = Thing.objects.create(name='a')
+ self.assertRaises(IntegrityError, a_thing.tags.get_or_create, text='foo')
diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py
index 3680eecdd2..397b63647a 100644
--- a/tests/handlers/tests.py
+++ b/tests/handlers/tests.py
@@ -61,6 +61,7 @@ class TransactionsPerRequestTests(TransactionTestCase):
connection.settings_dict['ATOMIC_REQUESTS'] = old_atomic_requests
self.assertContains(response, 'False')
+
class SignalsTests(TestCase):
urls = 'handlers.urls'
@@ -89,3 +90,11 @@ class SignalsTests(TestCase):
self.assertEqual(self.signals, ['started'])
self.assertEqual(b''.join(response.streaming_content), b"streaming content")
self.assertEqual(self.signals, ['started', 'finished'])
+
+
+class HandlerSuspiciousOpsTest(TestCase):
+ urls = 'handlers.urls'
+
+ def test_suspiciousop_in_view_returns_400(self):
+ response = self.client.get('/suspicious/')
+ self.assertEqual(response.status_code, 400)
diff --git a/tests/handlers/urls.py b/tests/handlers/urls.py
index 29858055ab..eaf764b00b 100644
--- a/tests/handlers/urls.py
+++ b/tests/handlers/urls.py
@@ -9,4 +9,5 @@ urlpatterns = patterns('',
url(r'^streaming/$', views.streaming),
url(r'^in_transaction/$', views.in_transaction),
url(r'^not_in_transaction/$', views.not_in_transaction),
+ url(r'^suspicious/$', views.suspicious),
)
diff --git a/tests/handlers/views.py b/tests/handlers/views.py
index 22d9ea4c7d..1b75b27043 100644
--- a/tests/handlers/views.py
+++ b/tests/handlers/views.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
-from django.db import connection
+from django.core.exceptions import SuspiciousOperation
+from django.db import connection, transaction
from django.http import HttpResponse, StreamingHttpResponse
def regular(request):
@@ -12,6 +13,9 @@ def streaming(request):
def in_transaction(request):
return HttpResponse(str(connection.in_atomic_block))
+@transaction.non_atomic_requests
def not_in_transaction(request):
return HttpResponse(str(connection.in_atomic_block))
-not_in_transaction.transactions_per_request = False
+
+def suspicious(request):
+ raise SuspiciousOperation('dubious')
diff --git a/tests/http_utils/tests.py b/tests/http_utils/tests.py
index 7dfd24d721..9f99c94da7 100644
--- a/tests/http_utils/tests.py
+++ b/tests/http_utils/tests.py
@@ -1,10 +1,24 @@
from __future__ import unicode_literals
+import io
+import gzip
+
from django.http import HttpRequest, HttpResponse, StreamingHttpResponse
from django.http.utils import conditional_content_removal
from django.test import TestCase
+# based on Python 3.3's gzip.compress
+def gzip_compress(data):
+ buf = io.BytesIO()
+ f = gzip.GzipFile(fileobj=buf, mode='wb', compresslevel=0)
+ try:
+ f.write(data)
+ finally:
+ f.close()
+ return buf.getvalue()
+
+
class HttpUtilTests(TestCase):
def test_conditional_content_removal(self):
@@ -33,6 +47,19 @@ class HttpUtilTests(TestCase):
conditional_content_removal(req, res)
self.assertEqual(b''.join(res), b'')
+ # Issue #20472
+ abc = gzip_compress(b'abc')
+ res = HttpResponse(abc, status=304)
+ res['Content-Encoding'] = 'gzip'
+ conditional_content_removal(req, res)
+ self.assertEqual(res.content, b'')
+
+ res = StreamingHttpResponse([abc], status=304)
+ res['Content-Encoding'] = 'gzip'
+ conditional_content_removal(req, res)
+ self.assertEqual(b''.join(res), b'')
+
+
# Strip content for HEAD requests.
req.method = 'HEAD'
diff --git a/tests/i18n/__init__.py b/tests/i18n/__init__.py
index e69de29bb2..c5aaa31fe3 100644
--- a/tests/i18n/__init__.py
+++ b/tests/i18n/__init__.py
@@ -0,0 +1,18 @@
+from threading import local
+
+
+class TransRealMixin(object):
+ """This is the only way to reset the translation machinery. Otherwise
+ the test suite occasionally fails because of global state pollution
+ between tests."""
+ def flush_caches(self):
+ from django.utils.translation import trans_real
+ trans_real._translations = {}
+ trans_real._active = local()
+ trans_real._default = None
+ trans_real._accepted = {}
+ trans_real._checked_languages = {}
+
+ def tearDown(self):
+ self.flush_caches()
+ super(TransRealMixin, self).tearDown()
diff --git a/tests/i18n/commands/extraction.py b/tests/i18n/commands/extraction.py
index 7c482e58fb..8696ae453b 100644
--- a/tests/i18n/commands/extraction.py
+++ b/tests/i18n/commands/extraction.py
@@ -279,17 +279,22 @@ class IgnoredExtractorTests(ExtractorTests):
def test_ignore_option(self):
os.chdir(self.test_dir)
- pattern1 = os.path.join('ignore_dir', '*')
+ ignore_patterns = [
+ os.path.join('ignore_dir', '*'),
+ 'xxx_*',
+ ]
stdout = StringIO()
management.call_command('makemessages', locale=LOCALE, verbosity=2,
- ignore_patterns=[pattern1], stdout=stdout)
+ ignore_patterns=ignore_patterns, stdout=stdout)
data = stdout.getvalue()
self.assertTrue("ignoring directory ignore_dir" in data)
+ self.assertTrue("ignoring file xxx_ignored.html" in data)
self.assertTrue(os.path.exists(self.PO_FILE))
with open(self.PO_FILE, 'r') as fp:
po_contents = fp.read()
self.assertMsgId('This literal should be included.', po_contents)
self.assertNotMsgId('This should be ignored.', po_contents)
+ self.assertNotMsgId('This should be ignored too.', po_contents)
class SymlinkExtractorTests(ExtractorTests):
diff --git a/tests/i18n/commands/templates/xxx_ignored.html b/tests/i18n/commands/templates/xxx_ignored.html
new file mode 100644
index 0000000000..a41cbe202a
--- /dev/null
+++ b/tests/i18n/commands/templates/xxx_ignored.html
@@ -0,0 +1,2 @@
+{% load i18n %}
+{% trans "This should be ignored too." %}
diff --git a/tests/i18n/commands/tests.py b/tests/i18n/commands/tests.py
deleted file mode 100644
index f9e3c20fff..0000000000
--- a/tests/i18n/commands/tests.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import os
-import re
-from subprocess import Popen, PIPE
-
-from django.core.management.utils import find_command
-
-can_run_extraction_tests = False
-can_run_compilation_tests = False
-
-# checks if it can find xgettext on the PATH and
-# imports the extraction tests if yes
-xgettext_cmd = find_command('xgettext')
-if xgettext_cmd:
- p = Popen('%s --version' % xgettext_cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=os.name != 'nt', universal_newlines=True)
- output = p.communicate()[0]
- match = re.search(r'(?P<major>\d+)\.(?P<minor>\d+)', output)
- if match:
- xversion = (int(match.group('major')), int(match.group('minor')))
- if xversion >= (0, 15):
- can_run_extraction_tests = True
- del p
-
-if find_command('msgfmt'):
- can_run_compilation_tests = True
diff --git a/tests/i18n/contenttypes/tests.py b/tests/i18n/contenttypes/tests.py
index 5e8a9823e1..cbac9ec5da 100644
--- a/tests/i18n/contenttypes/tests.py
+++ b/tests/i18n/contenttypes/tests.py
@@ -10,6 +10,8 @@ from django.utils._os import upath
from django.utils import six
from django.utils import translation
+from i18n import TransRealMixin
+
@override_settings(
USE_I18N=True,
@@ -22,7 +24,7 @@ from django.utils import translation
('fr', 'French'),
),
)
-class ContentTypeTests(TestCase):
+class ContentTypeTests(TransRealMixin, TestCase):
def test_verbose_name(self):
company_type = ContentType.objects.get(app_label='i18n', model='company')
with translation.override('en'):
diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py
index d334e4fe2d..85eb7db084 100644
--- a/tests/i18n/patterns/tests.py
+++ b/tests/i18n/patterns/tests.py
@@ -52,8 +52,10 @@ class URLPrefixTests(URLTestCaseBase):
def test_not_prefixed(self):
with translation.override('en'):
self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+ self.assertEqual(reverse('not-prefixed-included-url'), '/not-prefixed-include/foo/')
with translation.override('nl'):
self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
+ self.assertEqual(reverse('not-prefixed-included-url'), '/not-prefixed-include/foo/')
def test_prefixed(self):
with translation.override('en'):
@@ -183,7 +185,7 @@ class URLRedirectTests(URLTestCaseBase):
class URLVaryAcceptLanguageTests(URLTestCaseBase):
"""
Tests that 'Accept-Language' is not added to the Vary header when using
- prefixed URLs.
+ prefixed URLs.
"""
def test_no_prefix_response(self):
response = self.client.get('/not-prefixed/')
diff --git a/tests/i18n/patterns/urls/default.py b/tests/i18n/patterns/urls/default.py
index 0edaea48ad..ff52e26227 100644
--- a/tests/i18n/patterns/urls/default.py
+++ b/tests/i18n/patterns/urls/default.py
@@ -8,6 +8,7 @@ view = TemplateView.as_view(template_name='dummy.html')
urlpatterns = patterns('',
url(r'^not-prefixed/$', view, name='not-prefixed'),
+ url(r'^not-prefixed-include/', include('i18n.patterns.urls.included')),
url(_(r'^translated/$'), view, name='no-prefix-translated'),
url(_(r'^translated/(?P<slug>[\w-]+)/$'), view, name='no-prefix-translated-slug'),
)
diff --git a/tests/i18n/patterns/urls/included.py b/tests/i18n/patterns/urls/included.py
new file mode 100644
index 0000000000..3f3d1325dd
--- /dev/null
+++ b/tests/i18n/patterns/urls/included.py
@@ -0,0 +1,9 @@
+from django.conf.urls import patterns, url
+from django.views.generic import TemplateView
+
+
+view = TemplateView.as_view(template_name='dummy.html')
+
+urlpatterns = patterns('',
+ url(r'^foo/$', view, name='not-prefixed-included-url'),
+)
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
index 1022c8d2f1..019ed88cdb 100644
--- a/tests/i18n/tests.py
+++ b/tests/i18n/tests.py
@@ -8,6 +8,7 @@ import pickle
from threading import local
from django.conf import settings
+from django.core.management.utils import find_command
from django.template import Template, Context
from django.template.base import TemplateSyntaxError
from django.test import TestCase, RequestFactory
@@ -30,19 +31,20 @@ from django.utils.translation import (activate, deactivate,
ngettext, ngettext_lazy,
ungettext, ungettext_lazy,
pgettext, pgettext_lazy,
- npgettext, npgettext_lazy)
+ npgettext, npgettext_lazy,
+ check_for_language)
-from .commands.tests import can_run_extraction_tests, can_run_compilation_tests
-if can_run_extraction_tests:
+if find_command('xgettext'):
from .commands.extraction import (ExtractorTests, BasicExtractorTests,
JavascriptExtractorTests, IgnoredExtractorTests, SymlinkExtractorTests,
CopyPluralFormsExtractorTests, NoWrapExtractorTests,
NoLocationExtractorTests, KeepPotFileExtractorTests,
MultipleLocaleExtractionTests)
-if can_run_compilation_tests:
+if find_command('msgfmt'):
from .commands.compilation import (PoFileTests, PoFileContentsTests,
PercentRenderingTests, MultipleLocaleCompilationTests,
CompilationErrorHandling)
+from . import TransRealMixin
from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
from .models import Company, TestModel
@@ -52,7 +54,8 @@ extended_locale_paths = settings.LOCALE_PATHS + (
os.path.join(here, 'other', 'locale'),
)
-class TranslationTests(TestCase):
+
+class TranslationTests(TransRealMixin, TestCase):
def test_override(self):
activate('de')
@@ -333,10 +336,43 @@ class TranslationTests(TestCase):
self.assertEqual(rendered, 'My other name is James.')
+class TranslationThreadSafetyTests(TestCase):
+ """Specifically not using TransRealMixin here to test threading."""
+
+ def setUp(self):
+ self._old_language = get_language()
+ self._translations = trans_real._translations
+
+ # here we rely on .split() being called inside the _fetch()
+ # in trans_real.translation()
+ class sideeffect_str(str):
+ def split(self, *args, **kwargs):
+ res = str.split(self, *args, **kwargs)
+ trans_real._translations['en-YY'] = None
+ return res
+
+ trans_real._translations = {sideeffect_str('en-XX'): None}
+
+ def tearDown(self):
+ trans_real._translations = self._translations
+ activate(self._old_language)
+
+ def test_bug14894_translation_activate_thread_safety(self):
+ translation_count = len(trans_real._translations)
+ try:
+ translation.activate('pl')
+ except RuntimeError:
+ self.fail('translation.activate() is not thread-safe')
+
+ # make sure sideeffect_str actually added a new translation
+ self.assertLess(translation_count, len(trans_real._translations))
+
+
@override_settings(USE_L10N=True)
-class FormattingTests(TestCase):
+class FormattingTests(TransRealMixin, TestCase):
def setUp(self):
+ super(FormattingTests, self).setUp()
self.n = decimal.Decimal('66666.666')
self.f = 99999.999
self.d = datetime.date(2009, 12, 31)
@@ -738,9 +774,10 @@ class FormattingTests(TestCase):
self.assertEqual(template2.render(context), output2)
self.assertEqual(template3.render(context), output3)
-class MiscTests(TestCase):
+class MiscTests(TransRealMixin, TestCase):
def setUp(self):
+ super(MiscTests, self).setUp()
self.rf = RequestFactory()
def test_parse_spec_http_header(self):
@@ -884,17 +921,15 @@ class MiscTests(TestCase):
self.assertEqual(t_plur.render(Context({'percent': 42, 'num': 4})), '%(percent)s% represents 4 objects')
-class ResolutionOrderI18NTests(TestCase):
+class ResolutionOrderI18NTests(TransRealMixin, TestCase):
def setUp(self):
- # Okay, this is brutal, but we have no other choice to fully reset
- # the translation framework
- trans_real._active = local()
- trans_real._translations = {}
+ super(ResolutionOrderI18NTests, self).setUp()
activate('de')
def tearDown(self):
deactivate()
+ super(ResolutionOrderI18NTests, self).tearDown()
def assertUgettext(self, msgid, msgstr):
result = ugettext(msgid)
@@ -967,15 +1002,17 @@ class TestLanguageInfo(TestCase):
six.assertRaisesRegex(self, KeyError, r"Unknown language code xx-xx and xx\.", get_language_info, 'xx-xx')
-class MultipleLocaleActivationTests(TestCase):
+class MultipleLocaleActivationTests(TransRealMixin, TestCase):
"""
Tests for template rendering behavior when multiple locales are activated
during the lifetime of the same process.
"""
def setUp(self):
+ super(MultipleLocaleActivationTests, self).setUp()
self._old_language = get_language()
def tearDown(self):
+ super(MultipleLocaleActivationTests, self).tearDown()
activate(self._old_language)
def test_single_locale_activation(self):
@@ -1104,7 +1141,7 @@ class MultipleLocaleActivationTests(TestCase):
'django.middleware.common.CommonMiddleware',
),
)
-class LocaleMiddlewareTests(TestCase):
+class LocaleMiddlewareTests(TransRealMixin, TestCase):
urls = 'i18n.urls'
@@ -1114,3 +1151,86 @@ class LocaleMiddlewareTests(TestCase):
self.assertContains(response, "Oui/Non")
response = self.client.get('/en/streaming/')
self.assertContains(response, "Yes/No")
+
+ @override_settings(
+ MIDDLEWARE_CLASSES=(
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ ),
+ )
+ def test_session_language(self):
+ """
+ Check that language is stored in session if missing.
+ """
+ # Create an empty session
+ engine = import_module(settings.SESSION_ENGINE)
+ session = engine.SessionStore()
+ session.save()
+ self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
+
+ # Clear the session data before request
+ session.save()
+ response = self.client.get('/en/simple/')
+ self.assertEqual(self.client.session['django_language'], 'en')
+
+ # Clear the session data before request
+ session.save()
+ response = self.client.get('/fr/simple/')
+ self.assertEqual(self.client.session['django_language'], 'fr')
+
+ # Check that language is not changed in session
+ response = self.client.get('/en/simple/')
+ self.assertEqual(self.client.session['django_language'], 'fr')
+
+
+@override_settings(
+ USE_I18N=True,
+ LANGUAGES=(
+ ('bg', 'Bulgarian'),
+ ('en-us', 'English'),
+ ('pt-br', 'Portugese (Brazil)'),
+ ),
+ MIDDLEWARE_CLASSES=(
+ 'django.middleware.locale.LocaleMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ ),
+)
+class CountrySpecificLanguageTests(TransRealMixin, TestCase):
+
+ urls = 'i18n.urls'
+
+ def setUp(self):
+ super(CountrySpecificLanguageTests, self).setUp()
+ self.rf = RequestFactory()
+
+ def test_check_for_language(self):
+ self.assertTrue(check_for_language('en'))
+ self.assertTrue(check_for_language('en-us'))
+ self.assertTrue(check_for_language('en-US'))
+
+ def test_get_language_from_request(self):
+ # issue 19919
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.8,bg;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('en-us', lang)
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'bg-bg,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('bg', lang)
+
+ def test_specific_language_codes(self):
+ # issue 11915
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('pt-br', lang)
+ r = self.rf.get('/')
+ r.COOKIES = {}
+ r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-pt,en-US;q=0.8,en;q=0.6,ru;q=0.4'}
+ lang = get_language_from_request(r)
+ self.assertEqual('pt-br', lang)
diff --git a/tests/i18n/urls.py b/tests/i18n/urls.py
index c118265dab..c7d01897f5 100644
--- a/tests/i18n/urls.py
+++ b/tests/i18n/urls.py
@@ -1,9 +1,10 @@
from __future__ import unicode_literals
from django.conf.urls.i18n import i18n_patterns
-from django.http import StreamingHttpResponse
+from django.http import HttpResponse, StreamingHttpResponse
from django.utils.translation import ugettext_lazy as _
urlpatterns = i18n_patterns('',
+ (r'^simple/$', lambda r: HttpResponse()),
(r'^streaming/$', lambda r: StreamingHttpResponse([_("Yes"), "/", _("No")])),
)
diff --git a/tests/invalid_models/invalid_models/models.py b/tests/invalid_models/invalid_models/models.py
index 3c21e1ddb8..e1bac9cfb2 100644
--- a/tests/invalid_models/invalid_models/models.py
+++ b/tests/invalid_models/invalid_models/models.py
@@ -25,6 +25,7 @@ class FieldErrors(models.Model):
index = models.CharField(max_length=10, db_index='bad')
field_ = models.CharField(max_length=10)
nullbool = models.BooleanField(null=True)
+ generic_ip_notnull_blank = models.GenericIPAddressField(null=False, blank=True)
class Target(models.Model):
@@ -375,11 +376,12 @@ invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits
invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute.
invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute.
invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list).
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
-invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-tuples.
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
+invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).
invalid_models.fielderrors: "index": "db_index" should be either None, True or False.
invalid_models.fielderrors: "field_": Field names cannot end with underscores, because this would lead to ambiguous queryset filters.
invalid_models.fielderrors: "nullbool": BooleanFields do not accept null values. Use a NullBooleanField instead.
+invalid_models.fielderrors: "generic_ip_notnull_blank": GenericIPAddressField can not accept blank values if null values are not allowed, as blank values are stored as null.
invalid_models.clash1: Accessor for field 'foreign' clashes with field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
invalid_models.clash1: Accessor for field 'foreign' clashes with related m2m field 'Target.clash1_set'. Add a related_name argument to the definition for 'foreign'.
invalid_models.clash1: Reverse query name for field 'foreign' clashes with field 'Target.clash1'. Add a related_name argument to the definition for 'foreign'.
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index 27ae651042..0c2d269464 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -8,9 +8,10 @@ import warnings
from django.conf import LazySettings
from django.core import mail
from django.test import TestCase, RequestFactory
-from django.test.utils import override_settings
+from django.test.utils import override_settings, patch_logger
from django.utils.encoding import force_text
-from django.utils.log import CallbackFilter, RequireDebugFalse, RequireDebugTrue
+from django.utils.log import (CallbackFilter, RequireDebugFalse,
+ RequireDebugTrue)
from django.utils.six import StringIO
from django.utils.unittest import skipUnless
@@ -293,7 +294,7 @@ class AdminEmailHandlerTest(TestCase):
def my_mail_admins(*args, **kwargs):
connection = kwargs['connection']
- self.assertTrue(isinstance(connection, MyEmailBackend))
+ self.assertIsInstance(connection, MyEmailBackend)
mail_admins_called['called'] = True
# Monkeypatches
@@ -354,3 +355,22 @@ class SettingsConfigureLogging(TestCase):
settings.configure(
LOGGING_CONFIG='logging_tests.tests.dictConfig')
self.assertTrue(dictConfig.called)
+
+
+class SecurityLoggerTest(TestCase):
+
+ urls = 'logging_tests.urls'
+
+ def test_suspicious_operation_creates_log_message(self):
+ with self.settings(DEBUG=True):
+ with patch_logger('django.security.SuspiciousOperation', 'error') as calls:
+ response = self.client.get('/suspicious/')
+ self.assertEqual(len(calls), 1)
+ self.assertEqual(calls[0], 'dubious')
+
+ def test_suspicious_operation_uses_sublogger(self):
+ with self.settings(DEBUG=True):
+ with patch_logger('django.security.DisallowedHost', 'error') as calls:
+ response = self.client.get('/suspicious_spec/')
+ self.assertEqual(len(calls), 1)
+ self.assertEqual(calls[0], 'dubious')
diff --git a/tests/logging_tests/urls.py b/tests/logging_tests/urls.py
new file mode 100644
index 0000000000..c738bd565c
--- /dev/null
+++ b/tests/logging_tests/urls.py
@@ -0,0 +1,10 @@
+from __future__ import unicode_literals
+
+from django.conf.urls import patterns, url
+
+from . import views
+
+urlpatterns = patterns('',
+ url(r'^suspicious/$', views.suspicious),
+ url(r'^suspicious_spec/$', views.suspicious_spec),
+)
diff --git a/tests/logging_tests/views.py b/tests/logging_tests/views.py
new file mode 100644
index 0000000000..c685bcc005
--- /dev/null
+++ b/tests/logging_tests/views.py
@@ -0,0 +1,11 @@
+from __future__ import unicode_literals
+
+from django.core.exceptions import SuspiciousOperation, DisallowedHost
+
+
+def suspicious(request):
+ raise SuspiciousOperation('dubious')
+
+
+def suspicious_spec(request):
+ raise DisallowedHost('dubious')
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index 0a843db9e7..c90dc7e22a 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -251,16 +251,16 @@ class MailTests(TestCase):
def test_backend_arg(self):
"""Test backend argument of mail.get_connection()"""
- self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend))
- self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend))
- self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend))
- self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend))
+ self.assertIsInstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend)
+ self.assertIsInstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend)
+ self.assertIsInstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend)
+ self.assertIsInstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend)
tmp_dir = tempfile.mkdtemp()
try:
- self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend))
+ self.assertIsInstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend)
finally:
shutil.rmtree(tmp_dir)
- self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend))
+ self.assertIsInstance(mail.get_connection(), locmem.EmailBackend)
@override_settings(
EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend',
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index f2f7f4df66..265eb97c36 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -18,13 +18,11 @@ from django.middleware.http import ConditionalGetMiddleware
from django.middleware.gzip import GZipMiddleware
from django.middleware.transaction import TransactionMiddleware
from django.test import TransactionTestCase, TestCase, RequestFactory
-from django.test.utils import override_settings
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
from django.utils import six
from django.utils.encoding import force_str
from django.utils.six.moves import xrange
-from django.utils.unittest import expectedFailure
-
-from transactions.tests import IgnorePendingDeprecationWarningsMixin
+from django.utils.unittest import expectedFailure, skipIf
from .models import Band
@@ -320,6 +318,33 @@ class BrokenLinkEmailsMiddlewareTest(TestCase):
BrokenLinkEmailsMiddleware().process_response(self.req, self.resp)
self.assertEqual(len(mail.outbox), 0)
+ @skipIf(six.PY3, "HTTP_REFERER is str type on Python 3")
+ def test_404_error_nonascii_referrer(self):
+ # Such referer strings should not happen, but anyway, if it happens,
+ # let's not crash
+ self.req.META['HTTP_REFERER'] = b'http://testserver/c/\xd0\xbb\xd0\xb8/'
+ BrokenLinkEmailsMiddleware().process_response(self.req, self.resp)
+ self.assertEqual(len(mail.outbox), 1)
+
+ def test_custom_request_checker(self):
+ class SubclassedMiddleware(BrokenLinkEmailsMiddleware):
+ ignored_user_agent_patterns = (re.compile(r'Spider.*'),
+ re.compile(r'Robot.*'))
+ def is_ignorable_request(self, request, uri, domain, referer):
+ '''Check user-agent in addition to normal checks.'''
+ if super(SubclassedMiddleware, self).is_ignorable_request(request, uri, domain, referer):
+ return True
+ user_agent = request.META['HTTP_USER_AGENT']
+ return any(pattern.search(user_agent) for pattern in
+ self.ignored_user_agent_patterns)
+
+ self.req.META['HTTP_REFERER'] = '/another/url/'
+ self.req.META['HTTP_USER_AGENT'] = 'Spider machine 3.4'
+ SubclassedMiddleware().process_response(self.req, self.resp)
+ self.assertEqual(len(mail.outbox), 0)
+ self.req.META['HTTP_USER_AGENT'] = 'My user agent'
+ SubclassedMiddleware().process_response(self.req, self.resp)
+ self.assertEqual(len(mail.outbox), 1)
class ConditionalGetMiddlewareTest(TestCase):
urls = 'middleware.cond_get_urls'
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index e1e38d0ec7..ccff8b8cfa 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -193,28 +193,28 @@ class BooleanFieldTests(unittest.TestCase):
b.bfield = True
b.save()
b2 = BooleanModel.objects.get(pk=b.pk)
- self.assertTrue(isinstance(b2.bfield, bool))
+ self.assertIsInstance(b2.bfield, bool)
self.assertEqual(b2.bfield, True)
b3 = BooleanModel()
b3.bfield = False
b3.save()
b4 = BooleanModel.objects.get(pk=b3.pk)
- self.assertTrue(isinstance(b4.bfield, bool))
+ self.assertIsInstance(b4.bfield, bool)
self.assertEqual(b4.bfield, False)
b = NullBooleanModel()
b.nbfield = True
b.save()
b2 = NullBooleanModel.objects.get(pk=b.pk)
- self.assertTrue(isinstance(b2.nbfield, bool))
+ self.assertIsInstance(b2.nbfield, bool)
self.assertEqual(b2.nbfield, True)
b3 = NullBooleanModel()
b3.nbfield = False
b3.save()
b4 = NullBooleanModel.objects.get(pk=b3.pk)
- self.assertTrue(isinstance(b4.nbfield, bool))
+ self.assertIsInstance(b4.nbfield, bool)
self.assertEqual(b4.nbfield, False)
# http://code.djangoproject.com/ticket/13293
@@ -371,11 +371,11 @@ class BigIntegerFieldTests(test.TestCase):
def test_types(self):
b = BigInt(value = 0)
- self.assertTrue(isinstance(b.value, six.integer_types))
+ self.assertIsInstance(b.value, six.integer_types)
b.save()
- self.assertTrue(isinstance(b.value, six.integer_types))
+ self.assertIsInstance(b.value, six.integer_types)
b = BigInt.objects.all()[0]
- self.assertTrue(isinstance(b.value, six.integer_types))
+ self.assertIsInstance(b.value, six.integer_types)
def test_coercing(self):
BigInt.objects.create(value ='10')
diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py
index a79d9b8c5b..610dc34001 100644
--- a/tests/model_forms/models.py
+++ b/tests/model_forms/models.py
@@ -207,7 +207,17 @@ class Post(models.Model):
posted = models.DateField()
def __str__(self):
- return self.name
+ return self.title
+
+@python_2_unicode_compatible
+class DateTimePost(models.Model):
+ title = models.CharField(max_length=50, unique_for_date='posted', blank=True)
+ slug = models.CharField(max_length=50, unique_for_year='posted', blank=True)
+ subtitle = models.CharField(max_length=50, unique_for_month='posted', blank=True)
+ posted = models.DateTimeField(editable=False)
+
+ def __str__(self):
+ return self.title
class DerivedPost(Post):
pass
@@ -255,3 +265,7 @@ class Colour(models.Model):
class ColourfulItem(models.Model):
name = models.CharField(max_length=50)
colours = models.ManyToManyField(Colour)
+
+class ArticleStatusNote(models.Model):
+ name = models.CharField(max_length=20)
+ status = models.ManyToManyField(ArticleStatus)
diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py
index c5db011404..58dde13a8a 100644
--- a/tests/model_forms/tests.py
+++ b/tests/model_forms/tests.py
@@ -24,7 +24,7 @@ from .models import (Article, ArticleStatus, BetterAuthor, BigInt,
DerivedPost, ExplicitPK, FlexibleDatePost, ImprovedArticle,
ImprovedArticleWithParentLink, Inventory, Post, Price,
Product, TextFile, AuthorProfile, Colour, ColourfulItem,
- test_images)
+ ArticleStatusNote, DateTimePost, test_images)
if test_images:
from .models import ImageFile, OptionalImageFile
@@ -76,6 +76,12 @@ class PostForm(forms.ModelForm):
fields = '__all__'
+class DateTimePostForm(forms.ModelForm):
+ class Meta:
+ model = DateTimePost
+ fields = '__all__'
+
+
class DerivedPostForm(forms.ModelForm):
class Meta:
model = DerivedPost
@@ -234,6 +240,20 @@ class ColourfulItemForm(forms.ModelForm):
model = ColourfulItem
fields = '__all__'
+# model forms for testing work on #9321:
+
+class StatusNoteForm(forms.ModelForm):
+ class Meta:
+ model = ArticleStatusNote
+ fields = '__all__'
+
+
+class StatusNoteCBM2mForm(forms.ModelForm):
+ class Meta:
+ model = ArticleStatusNote
+ fields = '__all__'
+ widgets = {'status': forms.CheckboxSelectMultiple}
+
class ModelFormBaseTest(TestCase):
def test_base_form(self):
@@ -274,8 +294,8 @@ class ModelFormBaseTest(TestCase):
model = Category
fields = '__all__'
- self.assertTrue(isinstance(ReplaceField.base_fields['url'],
- forms.fields.BooleanField))
+ self.assertIsInstance(ReplaceField.base_fields['url'],
+ forms.fields.BooleanField)
def test_replace_field_variant_2(self):
# Should have the same result as before,
@@ -287,8 +307,8 @@ class ModelFormBaseTest(TestCase):
model = Category
fields = ['url']
- self.assertTrue(isinstance(ReplaceField.base_fields['url'],
- forms.fields.BooleanField))
+ self.assertIsInstance(ReplaceField.base_fields['url'],
+ forms.fields.BooleanField)
def test_replace_field_variant_3(self):
# Should have the same result as before,
@@ -300,8 +320,8 @@ class ModelFormBaseTest(TestCase):
model = Category
fields = [] # url will still appear, since it is explicit above
- self.assertTrue(isinstance(ReplaceField.base_fields['url'],
- forms.fields.BooleanField))
+ self.assertIsInstance(ReplaceField.base_fields['url'],
+ forms.fields.BooleanField)
def test_override_field(self):
class AuthorForm(forms.ModelForm):
@@ -668,6 +688,23 @@ class UniqueTest(TestCase):
self.assertEqual(len(form.errors), 1)
self.assertEqual(form.errors['posted'], ['This field is required.'])
+ def test_unique_for_date_in_exclude(self):
+ """If the date for unique_for_* constraints is excluded from the
+ ModelForm (in this case 'posted' has editable=False, then the
+ constraint should be ignored."""
+ p = DateTimePost.objects.create(title="Django 1.0 is released",
+ slug="Django 1.0", subtitle="Finally",
+ posted=datetime.datetime(2008, 9, 3, 10, 10, 1))
+ # 'title' has unique_for_date='posted'
+ form = DateTimePostForm({'title': "Django 1.0 is released", 'posted': '2008-09-03'})
+ self.assertTrue(form.is_valid())
+ # 'slug' has unique_for_year='posted'
+ form = DateTimePostForm({'slug': "Django 1.0", 'posted': '2008-01-01'})
+ self.assertTrue(form.is_valid())
+ # 'subtitle' has unique_for_month='posted'
+ form = DateTimePostForm({'subtitle': "Finally", 'posted': '2008-09-30'})
+ self.assertTrue(form.is_valid())
+
def test_inherited_unique_for_date(self):
p = Post.objects.create(title="Django 1.0 is released",
slug="Django 1.0", subtitle="Finally", posted=datetime.date(2008, 9, 3))
@@ -1677,3 +1714,22 @@ class OldFormForXTests(TestCase):
<option value="%(blue_pk)s">Blue</option>
</select> <span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span></p>"""
% {'blue_pk': colour.pk})
+
+
+class M2mHelpTextTest(TestCase):
+ """Tests for ticket #9321."""
+ def test_multiple_widgets(self):
+ """Help text of different widgets for ManyToManyFields model fields"""
+ dreaded_help_text = '<span class="helptext"> Hold down "Control", or "Command" on a Mac, to select more than one.</span>'
+
+ # Default widget (SelectMultiple):
+ std_form = StatusNoteForm()
+ self.assertInHTML(dreaded_help_text, std_form.as_p())
+
+ # Overridden widget (CheckboxSelectMultiple, a subclass of
+ # SelectMultiple but with a UI that doesn't involve Control/Command
+ # keystrokes to extend selection):
+ form = StatusNoteCBM2mForm()
+ html = form.as_p()
+ self.assertInHTML('<ul id="id_status">', html)
+ self.assertInHTML(dreaded_help_text, html, count=0)
diff --git a/tests/model_forms_regress/tests.py b/tests/model_forms_regress/tests.py
index 0e033e033f..80900a59e0 100644
--- a/tests/model_forms_regress/tests.py
+++ b/tests/model_forms_regress/tests.py
@@ -92,6 +92,41 @@ class OverrideCleanTests(TestCase):
self.assertEqual(form.instance.left, 1)
+
+class PartiallyLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = ('left', 'right',)
+
+
+class FullyLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = "__all__"
+
+class LocalizedModelFormTest(TestCase):
+ def test_model_form_applies_localize_to_some_fields(self):
+ f = PartiallyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
+ self.assertTrue(f.is_valid())
+ self.assertTrue(f.fields['left'].localize)
+ self.assertFalse(f.fields['middle'].localize)
+ self.assertTrue(f.fields['right'].localize)
+
+ def test_model_form_applies_localize_to_all_fields(self):
+ f = FullyLocalizedTripleForm({'left': 10, 'middle': 10, 'right': 10})
+ self.assertTrue(f.is_valid())
+ self.assertTrue(f.fields['left'].localize)
+ self.assertTrue(f.fields['middle'].localize)
+ self.assertTrue(f.fields['right'].localize)
+
+ def test_model_form_refuses_arbitrary_string(self):
+ with self.assertRaises(TypeError):
+ class BrokenLocalizedTripleForm(forms.ModelForm):
+ class Meta:
+ model = Triple
+ localized_fields = "foo"
+
+
# Regression test for #12960.
# Make sure the cleaned_data returned from ModelForm.clean() is applied to the
# model instance.
diff --git a/tests/model_formsets_regress/tests.py b/tests/model_formsets_regress/tests.py
index 38ebd9d24b..179f79fbcb 100644
--- a/tests/model_formsets_regress/tests.py
+++ b/tests/model_formsets_regress/tests.py
@@ -236,11 +236,11 @@ class FormsetTests(TestCase):
formset = Formset(data)
# check if the returned error classes are correct
# note: formset.errors returns a list as documented
- self.assertTrue(isinstance(formset.errors, list))
- self.assertTrue(isinstance(formset.non_form_errors(), ErrorList))
+ self.assertIsInstance(formset.errors, list)
+ self.assertIsInstance(formset.non_form_errors(), ErrorList)
for form in formset.forms:
- self.assertTrue(isinstance(form.errors, ErrorDict))
- self.assertTrue(isinstance(form.non_field_errors(), ErrorList))
+ self.assertIsInstance(form.errors, ErrorDict)
+ self.assertIsInstance(form.non_field_errors(), ErrorList)
def test_initial_data(self):
User.objects.create(username="bibi", serial=1)
@@ -273,6 +273,7 @@ class UserSiteForm(forms.ModelForm):
'id': CustomWidget,
'data': CustomWidget,
}
+ localized_fields = ('data',)
class Callback(object):
@@ -295,21 +296,25 @@ class FormfieldCallbackTests(TestCase):
def test_inlineformset_factory_default(self):
Formset = inlineformset_factory(User, UserSite, form=UserSiteForm, fields="__all__")
form = Formset().forms[0]
- self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
- self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertIsInstance(form['id'].field.widget, CustomWidget)
+ self.assertIsInstance(form['data'].field.widget, CustomWidget)
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def test_modelformset_factory_default(self):
Formset = modelformset_factory(UserSite, form=UserSiteForm)
form = Formset().forms[0]
- self.assertTrue(isinstance(form['id'].field.widget, CustomWidget))
- self.assertTrue(isinstance(form['data'].field.widget, CustomWidget))
+ self.assertIsInstance(form['id'].field.widget, CustomWidget)
+ self.assertIsInstance(form['data'].field.widget, CustomWidget)
+ self.assertFalse(form.fields['id'].localize)
+ self.assertTrue(form.fields['data'].localize)
def assertCallbackCalled(self, callback):
id_field, user_field, data_field = UserSite._meta.fields
expected_log = [
(id_field, {'widget': CustomWidget}),
(user_field, {}),
- (data_field, {'widget': CustomWidget}),
+ (data_field, {'widget': CustomWidget, 'localize': True}),
]
self.assertEqual(callback.log, expected_log)
diff --git a/tests/model_validation/__init__.py b/tests/model_validation/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/model_validation/__init__.py
diff --git a/tests/model_validation/models.py b/tests/model_validation/models.py
new file mode 100644
index 0000000000..9a2a5f7cd0
--- /dev/null
+++ b/tests/model_validation/models.py
@@ -0,0 +1,27 @@
+from django.db import models
+
+
+class ThingItem(object):
+
+ def __init__(self, value, display):
+ self.value = value
+ self.display = display
+
+ def __iter__(self):
+ return (x for x in [self.value, self.display])
+
+ def __len__(self):
+ return 2
+
+
+class Things(object):
+
+ def __iter__(self):
+ return (x for x in [ThingItem(1, 2), ThingItem(3, 4)])
+
+
+class ThingWithIterableChoices(models.Model):
+
+ # Testing choices= Iterable of Iterables
+ # See: https://code.djangoproject.com/ticket/20430
+ thing = models.CharField(max_length=100, blank=True, choices=Things())
diff --git a/tests/model_validation/tests.py b/tests/model_validation/tests.py
new file mode 100644
index 0000000000..ffd0d89412
--- /dev/null
+++ b/tests/model_validation/tests.py
@@ -0,0 +1,13 @@
+from django.core import management
+from django.test import TestCase
+from django.utils import six
+
+
+class ModelValidationTest(TestCase):
+
+ def test_models_validate(self):
+ # All our models should validate properly
+ # Validation Tests:
+ # * choices= Iterable of Iterables
+ # See: https://code.djangoproject.com/ticket/20430
+ management.call_command("validate", stdout=six.StringIO())
diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py
index 0d933bc1f9..805b57c070 100644
--- a/tests/modeladmin/tests.py
+++ b/tests/modeladmin/tests.py
@@ -7,7 +7,6 @@ from django.conf import settings
from django.contrib.admin.options import (ModelAdmin, TabularInline,
HORIZONTAL, VERTICAL)
from django.contrib.admin.sites import AdminSite
-from django.contrib.admin.validation import validate
from django.contrib.admin.widgets import AdminDateWidget, AdminRadioSelect
from django.contrib.admin import (SimpleListFilter,
BooleanFieldListFilter)
@@ -65,6 +64,30 @@ class ModelAdminTests(TestCase):
self.assertEqual(ma.get_fieldsets(request, self.band),
[(None, {'fields': ['name', 'bio', 'sign_date']})])
+ def test_get_fieldsets(self):
+ # Test that get_fieldsets is called when figuring out form fields.
+ # Refs #18681.
+
+ class BandAdmin(ModelAdmin):
+ def get_fieldsets(self, request, obj=None):
+ return [(None, {'fields': ['name', 'bio']})]
+
+ ma = BandAdmin(Band, self.site)
+ form = ma.get_form(None)
+ self.assertEqual(form._meta.fields, ['name', 'bio'])
+
+ class InlineBandAdmin(TabularInline):
+ model = Concert
+ fk_name = 'main_band'
+ can_delete = False
+
+ def get_fieldsets(self, request, obj=None):
+ return [(None, {'fields': ['day', 'transport']})]
+
+ ma = InlineBandAdmin(Band, self.site)
+ form = ma.get_formset(None).form
+ self.assertEqual(form._meta.fields, ['day', 'transport'])
+
def test_field_arguments(self):
# If we specify the fields argument, fieldsets_add and fielsets_change should
# just stick the fields into a formsets structure and return it.
@@ -523,8 +546,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -534,8 +556,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -545,15 +566,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.raw_id_fields\[0\]', 'name' must be either a ForeignKey or ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
raw_id_fields = ('users',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fieldsets_validation(self):
@@ -563,8 +583,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -574,8 +593,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -585,8 +603,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]' does not have exactly two elements.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -596,8 +613,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.fieldsets\[0\]\[1\]' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -607,15 +623,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'fields' key is required in ValidationTestModelAdmin.fieldsets\[0\]\[1\] field options dict.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
fieldsets = (("General", {"fields": ("name",)}),)
@@ -624,8 +639,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"Both fieldsets and fields are specified in ValidationTestModelAdmin.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -635,8 +649,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fieldsets",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -646,8 +659,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"There are duplicate field\(s\) in ValidationTestModelAdmin.fields",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -662,8 +674,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"ValidationTestModelAdmin.form does not inherit from BaseModelForm.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -676,7 +687,7 @@ class ValidationTests(unittest.TestCase):
}),
)
- validate(BandAdmin, Band)
+ BandAdmin.validate(Band)
class AdminBandForm(forms.ModelForm):
delete = forms.BooleanField()
@@ -690,7 +701,7 @@ class ValidationTests(unittest.TestCase):
}),
)
- validate(BandAdmin, Band)
+ BandAdmin.validate(Band)
def test_filter_vertical_validation(self):
@@ -700,8 +711,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -711,8 +721,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -722,15 +731,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_vertical\[0\]' must be a ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
filter_vertical = ("users",)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_filter_horizontal_validation(self):
@@ -740,8 +748,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -751,8 +758,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -762,15 +768,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.filter_horizontal\[0\]' must be a ManyToManyField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
filter_horizontal = ("users",)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_radio_fields_validation(self):
@@ -780,8 +785,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -791,8 +795,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -802,8 +805,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['name'\]' is neither an instance of ForeignKey nor does have choices set.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -813,15 +815,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.radio_fields\['state'\]' is neither admin.HORIZONTAL nor admin.VERTICAL.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
radio_fields = {"state": VERTICAL}
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_prepopulated_fields_validation(self):
@@ -831,8 +832,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' must be a dictionary.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -842,8 +842,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -853,8 +852,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['slug'\]\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -864,15 +862,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.prepopulated_fields\['users'\]' is either a DateTimeField, ForeignKey or ManyToManyField. This isn't allowed.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
prepopulated_fields = {"slug": ("name",)}
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_validation(self):
@@ -882,8 +879,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -893,8 +889,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
str_prefix("ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'."),
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -904,8 +899,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -917,7 +911,7 @@ class ValidationTests(unittest.TestCase):
pass
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_display_links_validation(self):
@@ -927,8 +921,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -938,8 +931,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'non_existent_field' which is not defined in 'list_display'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -949,8 +941,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_display_links\[0\]' refers to 'name' which is not defined in 'list_display'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -963,7 +954,7 @@ class ValidationTests(unittest.TestCase):
list_display = ('name', 'decade_published_in', 'a_method', a_callable)
list_display_links = ('name', 'decade_published_in', 'a_method', a_callable)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_filter_validation(self):
@@ -973,8 +964,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -984,8 +974,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' refers to 'non_existent_field' which does not refer to a Field.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -998,8 +987,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'RandomClass' which is not a descendant of ListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1009,8 +997,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'RandomClass' which is not of type FieldListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1028,8 +1015,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]\[1\]' is 'AwesomeFilter' which is not of type FieldListFilter.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1039,8 +1025,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.list_filter\[0\]' is 'BooleanFieldListFilter' which is of type FieldListFilter but is not associated with a field name.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1049,7 +1034,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
list_filter = ('is_active', AwesomeFilter, ('is_active', BooleanFieldListFilter), 'no')
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_per_page_validation(self):
@@ -1058,16 +1043,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_per_page' should be a integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_per_page' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_per_page = 100
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_show_all_allowed_validation(self):
@@ -1076,16 +1060,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_max_show_all' should be an integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_max_show_all' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_max_show_all = 200
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_search_fields_validation(self):
@@ -1095,8 +1078,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.search_fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1108,8 +1090,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1119,15 +1100,14 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.date_hierarchy is neither an instance of DateField nor DateTimeField.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
date_hierarchy = 'pub_date'
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_ordering_validation(self):
@@ -1137,8 +1117,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1148,8 +1127,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering\[0\]' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1159,43 +1137,43 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.ordering' has the random ordering marker '\?', but contains other fields as well. Please either remove '\?' or the other fields.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('?',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('band__name',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
class ValidationTestModelAdmin(ModelAdmin):
ordering = ('name',)
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_list_select_related_validation(self):
class ValidationTestModelAdmin(ModelAdmin):
list_select_related = 1
- six.assertRaisesRegex(self,
+ six.assertRaisesRegex(
+ self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.list_select_related' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.list_select_related' should be either a "
+ "bool, a tuple or a list",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
list_select_related = False
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_as_validation(self):
@@ -1204,16 +1182,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.save_as' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.save_as' should be a bool.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
save_as = True
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_save_on_top_validation(self):
@@ -1222,16 +1199,15 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestModelAdmin.save_on_top' should be a boolean.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestModelAdmin.save_on_top' should be a bool.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
class ValidationTestModelAdmin(ModelAdmin):
save_on_top = True
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_inlines_validation(self):
@@ -1241,8 +1217,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1255,8 +1230,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\]' does not inherit from BaseModelAdmin.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1269,8 +1243,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'model' is a required attribute of 'ValidationTestModelAdmin.inlines\[0\]'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1286,8 +1259,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestModelAdmin.inlines\[0\].model' does not inherit from models.Model.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1297,7 +1269,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_fields_validation(self):
@@ -1311,8 +1283,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.fields' must be a list or tuple.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1328,8 +1299,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.fk_name' refers to field 'non_existent_field' that is missing from model 'modeladmin.ValidationTestInlineModel'.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1340,7 +1310,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_extra_validation(self):
@@ -1353,9 +1323,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestInline.extra' should be a integer.",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestInline.extra' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1366,7 +1335,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_max_num_validation(self):
@@ -1379,9 +1348,8 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
- "'ValidationTestInline.max_num' should be an integer or None \(default\).",
- validate,
- ValidationTestModelAdmin,
+ "'ValidationTestInline.max_num' should be a int.",
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1392,7 +1360,7 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
def test_formset_validation(self):
@@ -1409,8 +1377,7 @@ class ValidationTests(unittest.TestCase):
six.assertRaisesRegex(self,
ImproperlyConfigured,
"'ValidationTestInline.formset' does not inherit from BaseModelFormSet.",
- validate,
- ValidationTestModelAdmin,
+ ValidationTestModelAdmin.validate,
ValidationTestModel,
)
@@ -1424,4 +1391,4 @@ class ValidationTests(unittest.TestCase):
class ValidationTestModelAdmin(ModelAdmin):
inlines = [ValidationTestInline]
- validate(ValidationTestModelAdmin, ValidationTestModel)
+ ValidationTestModelAdmin.validate(ValidationTestModel)
diff --git a/tests/multiple_database/tests.py b/tests/multiple_database/tests.py
index 2bff7b3b66..e72fb6a4f9 100644
--- a/tests/multiple_database/tests.py
+++ b/tests/multiple_database/tests.py
@@ -5,11 +5,13 @@ import pickle
from operator import attrgetter
import warnings
+from django.conf import settings
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.core import management
from django.db import connections, router, DEFAULT_DB_ALIAS
from django.db.models import signals
+from django.db.utils import ConnectionRouter
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.six import StringIO
@@ -918,6 +920,7 @@ class QueryTestCase(TestCase):
published=datetime.date(2009, 5, 4),
extra_arg=True)
+
class TestRouter(object):
# A test router. The behavior is vaguely master/slave, but the
# databases aren't assumed to propagate changes.
@@ -972,6 +975,30 @@ class WriteRouter(object):
def db_for_write(self, model, **hints):
return 'writer'
+
+class ConnectionRouterTestCase(TestCase):
+ @override_settings(DATABASE_ROUTERS=[
+ 'multiple_database.tests.TestRouter',
+ 'multiple_database.tests.WriteRouter'])
+ def test_router_init_default(self):
+ router = ConnectionRouter()
+ self.assertListEqual([r.__class__.__name__ for r in router.routers],
+ ['TestRouter', 'WriteRouter'])
+
+ def test_router_init_arg(self):
+ router = ConnectionRouter([
+ 'multiple_database.tests.TestRouter',
+ 'multiple_database.tests.WriteRouter'
+ ])
+ self.assertListEqual([r.__class__.__name__ for r in router.routers],
+ ['TestRouter', 'WriteRouter'])
+
+ # Init with instances instead of strings
+ router = ConnectionRouter([TestRouter(), WriteRouter()])
+ self.assertListEqual([r.__class__.__name__ for r in router.routers],
+ ['TestRouter', 'WriteRouter'])
+
+
class RouterTestCase(TestCase):
multi_db = True
diff --git a/tests/one_to_one/tests.py b/tests/one_to_one/tests.py
index 6ee78520bd..a36764b788 100644
--- a/tests/one_to_one/tests.py
+++ b/tests/one_to_one/tests.py
@@ -22,7 +22,8 @@ class OneToOneTests(TestCase):
# A Place can access its restaurant, if available.
self.assertEqual(repr(self.p1.restaurant), '<Restaurant: Demon Dogs the restaurant>')
# p2 doesn't have an associated restaurant.
- self.assertRaises(Restaurant.DoesNotExist, getattr, self.p2, 'restaurant')
+ with self.assertRaisesMessage(Restaurant.DoesNotExist, 'Place has no restaurant'):
+ self.p2.restaurant
def test_setter(self):
# Set the place using assignment notation. Because place is the primary
diff --git a/tests/pagination/tests.py b/tests/pagination/tests.py
index dea5756672..1dea4526e3 100644
--- a/tests/pagination/tests.py
+++ b/tests/pagination/tests.py
@@ -297,6 +297,7 @@ class ModelPaginationTests(TestCase):
self.assertIsNone(p.object_list._result_cache)
self.assertRaises(TypeError, lambda: p['has_previous'])
self.assertIsNone(p.object_list._result_cache)
+ self.assertNotIsInstance(p.object_list, list)
# Make sure slicing the Page object with numbers and slice objects work.
self.assertEqual(p[0], Article.objects.get(headline='Article 1'))
@@ -305,3 +306,5 @@ class ModelPaginationTests(TestCase):
"<Article: Article 2>",
]
)
+ # After __getitem__ is called, object_list is a list
+ self.assertIsInstance(p.object_list, list)
diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py
index 81c569844f..82bf85e401 100644
--- a/tests/prefetch_related/models.py
+++ b/tests/prefetch_related/models.py
@@ -195,3 +195,23 @@ class Employee(models.Model):
class Meta:
ordering = ['id']
+
+
+### Ticket 19607
+
+@python_2_unicode_compatible
+class LessonEntry(models.Model):
+ name1 = models.CharField(max_length=200)
+ name2 = models.CharField(max_length=200)
+
+ def __str__(self):
+ return "%s %s" % (self.name1, self.name2)
+
+
+@python_2_unicode_compatible
+class WordEntry(models.Model):
+ lesson_entry = models.ForeignKey(LessonEntry)
+ name = models.CharField(max_length=200)
+
+ def __str__(self):
+ return "%s (%s)" % (self.name, self.id)
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index e81560f01f..2e3fee6be6 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -8,7 +8,8 @@ from django.utils import six
from .models import (Author, Book, Reader, Qualification, Teacher, Department,
TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge,
- BookWithYear, BookReview, Person, House, Room, Employee, Comment)
+ BookWithYear, BookReview, Person, House, Room, Employee, Comment,
+ LessonEntry, WordEntry)
class PrefetchRelatedTests(TestCase):
@@ -106,6 +107,16 @@ class PrefetchRelatedTests(TestCase):
qs = Book.objects.prefetch_related('first_time_authors')
[b.first_time_authors.exists() for b in qs]
+ def test_in_and_prefetch_related(self):
+ """
+ Regression test for #20242 - QuerySet "in" didn't work the first time
+ when using prefetch_related. This was fixed by the removal of chunked
+ reads from QuerySet iteration in
+ 70679243d1786e03557c28929f9762a119e3ac14.
+ """
+ qs = Book.objects.prefetch_related('first_time_authors')
+ self.assertTrue(qs[0] in qs)
+
def test_clear(self):
"""
Test that we can clear the behavior by calling prefetch_related()
@@ -618,3 +629,25 @@ class MultiDbTests(TestCase):
ages = ", ".join(str(a.authorwithage.age) for a in A.prefetch_related('authorwithage'))
self.assertEqual(ages, "50, 49")
+
+
+class Ticket19607Tests(TestCase):
+
+ def setUp(self):
+
+ for id, name1, name2 in [
+ (1, 'einfach', 'simple'),
+ (2, 'schwierig', 'difficult'),
+ ]:
+ LessonEntry.objects.create(id=id, name1=name1, name2=name2)
+
+ for id, lesson_entry_id, name in [
+ (1, 1, 'einfach'),
+ (2, 1, 'simple'),
+ (3, 2, 'schwierig'),
+ (4, 2, 'difficult'),
+ ]:
+ WordEntry.objects.create(id=id, lesson_entry_id=lesson_entry_id, name=name)
+
+ def test_bug(self):
+ list(WordEntry.objects.prefetch_related('lesson_entry', 'lesson_entry__wordentry_set'))
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index cdc26248c9..481b690c20 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -9,7 +9,6 @@ from django.conf import settings
from django.core.exceptions import FieldError
from django.db import DatabaseError, connection, connections, DEFAULT_DB_ALIAS
from django.db.models import Count, F, Q
-from django.db.models.query import ITER_CHUNK_SIZE
from django.db.models.sql.where import WhereNode, EverythingNode, NothingNode
from django.db.models.sql.datastructures import EmptyResultSet
from django.test import TestCase, skipUnlessDBFeature
@@ -18,7 +17,7 @@ from django.utils import unittest
from django.utils.datastructures import SortedDict
from .models import (Annotation, Article, Author, Celebrity, Child, Cover,
- Detail, DumbCategory, ExtraInfo, Fan, Item, LeafA, LoopX, LoopZ,
+ Detail, DumbCategory, ExtraInfo, Fan, Item, LeafA, Join, LeafB, LoopX, LoopZ,
ManagedModel, Member, NamedCategory, Note, Number, Plaything, PointerA,
Ranking, Related, Report, ReservedName, Tag, TvChef, Valid, X, Food, Eaten,
Node, ObjectA, ObjectB, ObjectC, CategoryItem, SimpleCategory,
@@ -1112,6 +1111,17 @@ class Queries1Tests(BaseQuerysetTest):
['<Report: r1>']
)
+ def test_ticket_20250(self):
+ # A negated Q along with an annotated queryset failed in Django 1.4
+ qs = Author.objects.annotate(Count('item'))
+ qs = qs.filter(~Q(extra__value=0))
+
+ self.assertTrue('SELECT' in str(qs.query))
+ self.assertQuerysetEqual(
+ qs,
+ ['<Author: a1>', '<Author: a2>', '<Author: a3>', '<Author: a4>']
+ )
+
class Queries2Tests(TestCase):
def setUp(self):
@@ -1211,16 +1221,6 @@ class Queries2Tests(TestCase):
ordered=False
)
- def test_ticket7411(self):
- # Saving to db must work even with partially read result set in another
- # cursor.
- for num in range(2 * ITER_CHUNK_SIZE + 1):
- _ = Number.objects.create(num=num)
-
- for i, obj in enumerate(Number.objects.all()):
- obj.save()
- if i > 10: break
-
def test_ticket7759(self):
# Count should work with a partially read result set.
count = Number.objects.count()
@@ -1700,31 +1700,6 @@ class Queries6Tests(TestCase):
ann1.notes.add(n1)
ann2 = Annotation.objects.create(name='a2', tag=t4)
- # 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,
- # too.
-
- # We need to mess with the implementation internals a bit here to decrease the
- # cache fill size so that we don't read all the results at once.
- from django.db.models import query
- query.ITER_CHUNK_SIZE = 2
- qs = Tag.objects.all()
-
- # Fill the cache with the first chunk.
- self.assertTrue(bool(qs))
- self.assertEqual(len(qs._result_cache), 2)
-
- # Query beyond the end of the cache and check that it is filled out as required.
- self.assertEqual(repr(qs[4]), '<Tag: t5>')
- self.assertEqual(len(qs._result_cache), 5)
-
- # But querying beyond the end of the result set will fail.
- self.assertRaises(IndexError, lambda: qs[100])
-
def test_parallel_iterators(self):
# Test that parallel iterators work.
qs = Tag.objects.all()
@@ -2533,6 +2508,21 @@ class WhereNodeTest(TestCase):
w = WhereNode(children=[empty_w, NothingNode()], connector='OR')
self.assertRaises(EmptyResultSet, w.as_sql, qn, connection)
+
+class IteratorExceptionsTest(TestCase):
+ def test_iter_exceptions(self):
+ qs = ExtraInfo.objects.only('author')
+ with self.assertRaises(AttributeError):
+ list(qs)
+
+ def test_invalid_qs_list(self):
+ # Test for #19895 - second iteration over invalid queryset
+ # raises errors.
+ qs = Article.objects.order_by('invalid_column')
+ self.assertRaises(FieldError, list, qs)
+ self.assertRaises(FieldError, list, qs)
+
+
class NullJoinPromotionOrTest(TestCase):
def setUp(self):
self.d1 = ModelD.objects.create(name='foo')
@@ -2831,3 +2821,45 @@ class EmptyStringPromotionTests(TestCase):
self.assertIn('LEFT OUTER JOIN', str(qs.query))
else:
self.assertNotIn('LEFT OUTER JOIN', str(qs.query))
+
+class ValuesSubqueryTests(TestCase):
+ def test_values_in_subquery(self):
+ # Check that if a values() queryset is used, then the given values
+ # will be used instead of forcing use of the relation's field.
+ o1 = Order.objects.create(id=-2)
+ o2 = Order.objects.create(id=-1)
+ oi1 = OrderItem.objects.create(order=o1, status=0)
+ oi1.status = oi1.pk
+ oi1.save()
+ OrderItem.objects.create(order=o2, status=0)
+
+ # The query below should match o1 as it has related order_item
+ # with id == status.
+ self.assertQuerysetEqual(
+ Order.objects.filter(items__in=OrderItem.objects.values_list('status')),
+ [o1.pk], lambda x: x.pk)
+
+class DoubleInSubqueryTests(TestCase):
+ def test_double_subquery_in(self):
+ lfa1 = LeafA.objects.create(data='foo')
+ lfa2 = LeafA.objects.create(data='bar')
+ lfb1 = LeafB.objects.create(data='lfb1')
+ lfb2 = LeafB.objects.create(data='lfb2')
+ Join.objects.create(a=lfa1, b=lfb1)
+ Join.objects.create(a=lfa2, b=lfb2)
+ leaf_as = LeafA.objects.filter(data='foo').values_list('pk', flat=True)
+ joins = Join.objects.filter(a__in=leaf_as).values_list('b__id', flat=True)
+ qs = LeafB.objects.filter(pk__in=joins)
+ self.assertQuerysetEqual(
+ qs, [lfb1], lambda x: x)
+
+class Ticket18785Tests(unittest.TestCase):
+ def test_ticket_18785(self):
+ # Test join trimming from ticket18785
+ qs = Item.objects.exclude(
+ note__isnull=False
+ ).filter(
+ name='something', creator__extra__isnull=True
+ ).order_by()
+ self.assertEqual(1, str(qs.query).count('INNER JOIN'))
+ self.assertEqual(0, str(qs.query).count('OUTER JOIN'))
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index daf426ea47..4d730bb561 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -503,9 +503,9 @@ class RequestsTests(SimpleTestCase):
})
self.assertEqual(request.POST, {'key': ['España']})
- def test_body_after_POST_multipart(self):
+ def test_body_after_POST_multipart_form_data(self):
"""
- Reading body after parsing multipart is not allowed
+ Reading body after parsing multipart/form-data is not allowed
"""
# Because multipart is used for large amounts fo data i.e. file uploads,
# we don't want the data held in memory twice, and we don't want to
@@ -524,6 +524,29 @@ class RequestsTests(SimpleTestCase):
self.assertEqual(request.POST, {'name': ['value']})
self.assertRaises(Exception, lambda: request.body)
+ def test_body_after_POST_multipart_related(self):
+ """
+ Reading body after parsing multipart that isn't form-data is allowed
+ """
+ # Ticket #9054
+ # There are cases in which the multipart data is related instead of
+ # being a binary upload, in which case it should still be accessible
+ # via body.
+ payload_data = b"\r\n".join([
+ b'--boundary',
+ b'Content-ID: id; name="name"',
+ b'',
+ b'value',
+ b'--boundary--'
+ b''])
+ payload = FakePayload(payload_data)
+ request = WSGIRequest({'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': 'multipart/related; boundary=boundary',
+ 'CONTENT_LENGTH': len(payload),
+ 'wsgi.input': payload})
+ self.assertEqual(request.POST, {})
+ self.assertEqual(request.body, payload_data)
+
def test_POST_multipart_with_content_length_zero(self):
"""
Multipart POST requests with Content-Length >= 0 are valid and need to be handled.
@@ -636,6 +659,24 @@ class RequestsTests(SimpleTestCase):
with self.assertRaises(UnreadablePostError):
request.body
+ def test_FILES_connection_error(self):
+ """
+ If wsgi.input.read() raises an exception while trying to read() the
+ FILES, the exception should be identifiable (not a generic IOError).
+ """
+ class ExplodingBytesIO(BytesIO):
+ def read(self, len=0):
+ raise IOError("kaboom!")
+
+ payload = b'x'
+ request = WSGIRequest({'REQUEST_METHOD': 'POST',
+ 'CONTENT_TYPE': 'multipart/form-data; boundary=foo_',
+ 'CONTENT_LENGTH': len(payload),
+ 'wsgi.input': ExplodingBytesIO(payload)})
+
+ with self.assertRaises(UnreadablePostError):
+ request.FILES
+
@skipIf(connection.vendor == 'sqlite'
and connection.settings_dict['NAME'] in ('', ':memory:'),
diff --git a/tests/responses/__init__.py b/tests/responses/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/responses/__init__.py
diff --git a/tests/responses/models.py b/tests/responses/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/responses/models.py
diff --git a/tests/responses/tests.py b/tests/responses/tests.py
new file mode 100644
index 0000000000..e5320f5af9
--- /dev/null
+++ b/tests/responses/tests.py
@@ -0,0 +1,15 @@
+from django.http import HttpResponse
+import unittest
+
+class HttpResponseTests(unittest.TestCase):
+
+ def test_status_code(self):
+ resp = HttpResponse(status=418)
+ self.assertEqual(resp.status_code, 418)
+ self.assertEqual(resp.reason_phrase, "I'M A TEAPOT")
+
+ def test_reason_phrase(self):
+ reason = "I'm an anarchist coffee pot on crack."
+ resp = HttpResponse(status=814, reason=reason)
+ self.assertEqual(resp.status_code, 814)
+ self.assertEqual(resp.reason_phrase, reason)
diff --git a/tests/select_related/tests.py b/tests/select_related/tests.py
index 27d65fecb1..baa141d123 100644
--- a/tests/select_related/tests.py
+++ b/tests/select_related/tests.py
@@ -172,3 +172,7 @@ class SelectRelatedTests(TestCase):
Species.objects.select_related,
'genus__family__order', depth=4
)
+
+ def test_none_clears_list(self):
+ queryset = Species.objects.select_related('genus').select_related(None)
+ self.assertEqual(queryset.query.select_related, False)
diff --git a/tests/special_headers/models.py b/tests/special_headers/models.py
deleted file mode 100644
index e05c5a6920..0000000000
--- a/tests/special_headers/models.py
+++ /dev/null
@@ -1,5 +0,0 @@
-from django.db import models
-
-
-class Article(models.Model):
- text = models.TextField()
diff --git a/tests/special_headers/templates/special_headers/article_detail.html b/tests/special_headers/templates/special_headers/article_detail.html
deleted file mode 100644
index 3cbd38cb7e..0000000000
--- a/tests/special_headers/templates/special_headers/article_detail.html
+++ /dev/null
@@ -1 +0,0 @@
-{{ object }}
diff --git a/tests/special_headers/tests.py b/tests/special_headers/tests.py
deleted file mode 100644
index b4b704ae21..0000000000
--- a/tests/special_headers/tests.py
+++ /dev/null
@@ -1,62 +0,0 @@
-from django.contrib.auth.models import User
-from django.test import TestCase
-from django.test.utils import override_settings
-
-
-@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
-class SpecialHeadersTest(TestCase):
- fixtures = ['data.xml']
- urls = 'special_headers.urls'
-
- def test_xheaders(self):
- user = User.objects.get(username='super')
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
- self.client.login(username='super', password='secret')
- response = self.client.get('/special_headers/article/1/')
- self.assertTrue('X-Object-Type' in response)
- user.is_staff = False
- user.save()
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.get('/special_headers/article/1/')
- self.assertFalse('X-Object-Type' in response)
-
- def test_xview_func(self):
- user = User.objects.get(username='super')
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
- self.client.login(username='super', password='secret')
- response = self.client.head('/special_headers/xview/func/')
- self.assertTrue('X-View' in response)
- self.assertEqual(response['X-View'], 'special_headers.views.xview')
- user.is_staff = False
- user.save()
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.head('/special_headers/xview/func/')
- self.assertFalse('X-View' in response)
-
- def test_xview_class(self):
- user = User.objects.get(username='super')
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
- self.client.login(username='super', password='secret')
- response = self.client.head('/special_headers/xview/class/')
- self.assertTrue('X-View' in response)
- self.assertEqual(response['X-View'], 'special_headers.views.XViewClass')
- user.is_staff = False
- user.save()
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
- user.is_staff = True
- user.is_active = False
- user.save()
- response = self.client.head('/special_headers/xview/class/')
- self.assertFalse('X-View' in response)
diff --git a/tests/special_headers/urls.py b/tests/special_headers/urls.py
deleted file mode 100644
index f7ba141207..0000000000
--- a/tests/special_headers/urls.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-
-from django.conf.urls import patterns
-
-from . import views
-from .models import Article
-
-urlpatterns = patterns('',
- (r'^special_headers/article/(?P<object_id>\d+)/$', views.xview_xheaders),
- (r'^special_headers/xview/func/$', views.xview_dec(views.xview)),
- (r'^special_headers/xview/class/$', views.xview_dec(views.XViewClass.as_view())),
-)
diff --git a/tests/string_lookup/tests.py b/tests/string_lookup/tests.py
index 02f766adce..b011720ddf 100644
--- a/tests/string_lookup/tests.py
+++ b/tests/string_lookup/tests.py
@@ -73,9 +73,11 @@ class StringLookupTests(TestCase):
"""
Regression test for #708
- "like" queries on IP address fields require casting to text (on PostgreSQL).
+ "like" queries on IP address fields require casting with HOST() (on PostgreSQL).
"""
a = Article(name='IP test', text='The body', submitted_from='192.0.2.100')
a.save()
self.assertEqual(repr(Article.objects.filter(submitted_from__contains='192.0.2')),
repr([a]))
+ # Test that the searches do not match the subnet mask (/32 in this case)
+ self.assertEqual(Article.objects.filter(submitted_from__contains='32').count(), 0) \ No newline at end of file
diff --git a/tests/syncdb_signals/__init__.py b/tests/syncdb_signals/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/syncdb_signals/__init__.py
diff --git a/tests/syncdb_signals/models.py b/tests/syncdb_signals/models.py
new file mode 100644
index 0000000000..c41d993e94
--- /dev/null
+++ b/tests/syncdb_signals/models.py
@@ -0,0 +1,11 @@
+# from django.db import models
+
+
+# class Author(models.Model):
+# name = models.CharField(max_length=100)
+
+# class Meta:
+# ordering = ['name']
+
+# def __unicode__(self):
+# return self.name
diff --git a/tests/syncdb_signals/tests.py b/tests/syncdb_signals/tests.py
new file mode 100644
index 0000000000..d9be3b65d4
--- /dev/null
+++ b/tests/syncdb_signals/tests.py
@@ -0,0 +1,74 @@
+from django.db.models import signals
+from django.test import TestCase
+from django.core import management
+from django.utils import six
+
+from shared_models import models
+
+
+PRE_SYNCDB_ARGS = ['app', 'create_models', 'verbosity', 'interactive', 'db']
+SYNCDB_DATABASE = 'default'
+SYNCDB_VERBOSITY = 1
+SYNCDB_INTERACTIVE = False
+
+
+class PreSyncdbReceiver(object):
+ def __init__(self):
+ self.call_counter = 0
+ self.call_args = None
+
+ def __call__(self, signal, sender, **kwargs):
+ self.call_counter = self.call_counter + 1
+ self.call_args = kwargs
+
+
+class OneTimeReceiver(object):
+ """
+ Special receiver for handle the fact that test runner calls syncdb for
+ several databases and several times for some of them.
+ """
+
+ def __init__(self):
+ self.call_counter = 0
+ self.call_args = None
+
+ def __call__(self, signal, sender, **kwargs):
+ # Although test runner calls syncdb for several databases,
+ # testing for only one of them is quite sufficient.
+ if kwargs['db'] == SYNCDB_DATABASE:
+ self.call_counter = self.call_counter + 1
+ self.call_args = kwargs
+ # we need to test only one call of syncdb
+ signals.pre_syncdb.disconnect(pre_syncdb_receiver, sender=models)
+
+
+# We connect receiver here and not in unit test code because we need to
+# connect receiver before test runner creates database. That is, sequence of
+# actions would be:
+#
+# 1. Test runner imports this module.
+# 2. We connect receiver.
+# 3. Test runner calls syncdb for create default database.
+# 4. Test runner execute our unit test code.
+pre_syncdb_receiver = OneTimeReceiver()
+signals.pre_syncdb.connect(pre_syncdb_receiver, sender=models)
+
+
+class SyncdbSignalTests(TestCase):
+ def test_pre_syncdb_call_time(self):
+ self.assertEqual(pre_syncdb_receiver.call_counter, 1)
+
+ def test_pre_syncdb_args(self):
+ r = PreSyncdbReceiver()
+ signals.pre_syncdb.connect(r, sender=models)
+ management.call_command('syncdb', database=SYNCDB_DATABASE,
+ verbosity=SYNCDB_VERBOSITY, interactive=SYNCDB_INTERACTIVE,
+ load_initial_data=False, stdout=six.StringIO())
+
+ args = r.call_args
+ self.assertEqual(r.call_counter, 1)
+ self.assertEqual(set(args), set(PRE_SYNCDB_ARGS))
+ self.assertEqual(args['app'], models)
+ self.assertEqual(args['verbosity'], SYNCDB_VERBOSITY)
+ self.assertEqual(args['interactive'], SYNCDB_INTERACTIVE)
+ self.assertEqual(args['db'], 'default')
diff --git a/tests/template_tests/filters.py b/tests/template_tests/filters.py
index 7ba1681fd5..68ef15d827 100644
--- a/tests/template_tests/filters.py
+++ b/tests/template_tests/filters.py
@@ -35,59 +35,60 @@ def get_filter_tests():
now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone
today = date.today()
+ # NOTE: \xa0 avoids wrapping between value and unit
return {
# Default compare with datetime.now()
- 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
- 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'),
- 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'),
+ 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1\xa0minute'),
+ 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1\xa0day'),
+ 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1\xa0hour, 25\xa0minutes'),
# Compare to a given parameter
- 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1 day'),
- 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1 minute'),
+ 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1\xa0day'),
+ 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1\xa0minute'),
# Check that timezone is respected
- 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8 hours'),
+ 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8\xa0hours'),
# Regression for #7443
- 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1 week'),
- 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1 week'),
- 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'),
- 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'),
+ 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1\xa0week'),
+ 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1\xa0week'),
+ 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0\xa0minutes'),
# Ensures that differing timezones are calculated correctly
- 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'),
- 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'),
- 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'),
- 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'),
+ 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0\xa0minutes'),
+ 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0\xa0minutes'),
+ 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0\xa0minutes'),
+ 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0\xa0minutes'),
'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''),
'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''),
# Regression for #9065 (two date objects).
- 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0 minutes'),
- 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1 day'),
+ 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0\xa0minutes'),
+ 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1\xa0day'),
# Default compare with datetime.now()
- 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
- 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
- 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
+ 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2\xa0minutes'),
+ 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1\xa0day'),
+ 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8\xa0hours, 10\xa0minutes'),
# Compare to a given parameter
- 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'),
- 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'),
+ 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1\xa0day'),
+ 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1\xa0minute'),
# Regression for #7443
- 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0 minutes'),
- 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0 minutes'),
- 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'),
- 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'),
+ 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0\xa0minutes'),
+ 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1\xa0week'),
+ 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1\xa0week'),
# Ensures that differing timezones are calculated correctly
- 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'),
- 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'),
+ 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0\xa0minutes'),
+ 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0\xa0minutes'),
# Regression for #9065 (two date objects).
- 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0 minutes'),
- 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1 day'),
+ 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0\xa0minutes'),
+ 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1\xa0day'),
'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"),
'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"&lt;a&gt;\&#39; <a>\'"),
diff --git a/tests/template_tests/templates/included_base.html b/tests/template_tests/templates/included_base.html
new file mode 100644
index 0000000000..eae222cf9d
--- /dev/null
+++ b/tests/template_tests/templates/included_base.html
@@ -0,0 +1,3 @@
+{% block content %}
+ {% block error_here %}{% endblock %}
+{% endblock %}
diff --git a/tests/template_tests/templates/included_content.html b/tests/template_tests/templates/included_content.html
new file mode 100644
index 0000000000..bfc87c0425
--- /dev/null
+++ b/tests/template_tests/templates/included_content.html
@@ -0,0 +1,11 @@
+{% extends "included_base.html" %}
+
+{% block content %}
+ content
+ {{ block.super }}
+{% endblock %}
+
+{% block error_here %}
+ error here
+ {% url "non_existing_url" %}
+{% endblock %}
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index 2aeaee9464..206c648398 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -36,6 +36,7 @@ from django.utils.safestring import mark_safe
from django.utils import six
from django.utils.tzinfo import LocalTimezone
+from i18n import TransRealMixin
try:
from .loaders import RenderToStringTest, EggLoaderTest
@@ -154,8 +155,8 @@ class UTF8Class:
def __str__(self):
return 'ŠĐĆŽćžšđ'
-@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
-class Templates(TestCase):
+
+class TemplateLoaderTests(TestCase):
def test_loaders_security(self):
ad_loader = app_directories.Loader()
@@ -347,6 +348,9 @@ class Templates(TestCase):
loader.template_source_loaders = old_loaders
settings.TEMPLATE_DEBUG = old_td
+
+class TemplateRegressionTests(TestCase):
+
def test_token_smart_split(self):
# Regression test for #7027
token = template.Token(template.TOKEN_BLOCK, 'sometag _("Page not found") value|yesno:_("yes,no")')
@@ -444,6 +448,18 @@ class Templates(TestCase):
output = template.render(Context({}))
self.assertEqual(output, '1st time')
+ def test_super_errors(self):
+ """
+ Test behavior of the raise errors into included blocks.
+ See #18169
+ """
+ t = loader.get_template('included_content.html')
+ with self.assertRaises(urlresolvers.NoReverseMatch):
+ t.render(Context({}))
+
+
+@override_settings(MEDIA_URL="/media/", STATIC_URL="/static/")
+class TemplateTests(TransRealMixin, TestCase):
def test_templates(self):
template_tests = self.get_template_tests()
filter_tests = filters.get_filter_tests()
diff --git a/tests/test_client/urls.py b/tests/test_client/urls.py
index 67c475eaff..bd395ca552 100644
--- a/tests/test_client/urls.py
+++ b/tests/test_client/urls.py
@@ -21,6 +21,7 @@ urlpatterns = patterns('',
(r'^bad_view/$', views.bad_view),
(r'^form_view/$', views.form_view),
(r'^form_view_with_template/$', views.form_view_with_template),
+ (r'^formset_view/$', views.formset_view),
(r'^login_protected_view/$', views.login_protected_view),
(r'^login_protected_method_view/$', views.login_protected_method_view),
(r'^login_protected_view_custom_redirect/$', views.login_protected_view_changed_redirect),
diff --git a/tests/test_client/views.py b/tests/test_client/views.py
index f760466497..76296cb80d 100644
--- a/tests/test_client/views.py
+++ b/tests/test_client/views.py
@@ -7,7 +7,8 @@ from xml.dom.minidom import parseString
from django.contrib.auth.decorators import login_required, permission_required
from django.core import mail
from django.forms import fields
-from django.forms.forms import Form
+from django.forms.forms import Form, ValidationError
+from django.forms.formsets import formset_factory, BaseFormSet
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseNotFound
from django.shortcuts import render_to_response
from django.template import Context, Template
@@ -95,6 +96,12 @@ class TestForm(Form):
single = fields.ChoiceField(choices=TestChoices)
multi = fields.MultipleChoiceField(choices=TestChoices)
+ def clean(self):
+ cleaned_data = self.cleaned_data
+ if cleaned_data.get("text") == "Raise non-field error":
+ raise ValidationError("Non-field error.")
+ return cleaned_data
+
def form_view(request):
"A view that tests a simple form"
if request.method == 'POST':
@@ -130,6 +137,43 @@ def form_view_with_template(request):
}
)
+class BaseTestFormSet(BaseFormSet):
+ def clean(self):
+ """Checks that no two email addresses are the same."""
+ if any(self.errors):
+ # Don't bother validating the formset unless each form is valid
+ return
+
+ emails = []
+ for i in range(0, self.total_form_count()):
+ form = self.forms[i]
+ email = form.cleaned_data['email']
+ if email in emails:
+ raise ValidationError(
+ "Forms in a set must have distinct email addresses."
+ )
+ emails.append(email)
+
+TestFormSet = formset_factory(TestForm, BaseTestFormSet)
+
+def formset_view(request):
+ "A view that tests a simple formset"
+ if request.method == 'POST':
+ formset = TestFormSet(request.POST)
+ if formset.is_valid():
+ t = Template('Valid POST data.', name='Valid POST Template')
+ c = Context()
+ else:
+ t = Template('Invalid POST data. {{ my_formset.errors }}',
+ name='Invalid POST Template')
+ c = Context({'my_formset': formset})
+ else:
+ formset = TestForm(request.GET)
+ t = Template('Viewing base formset. {{ my_formset }}.',
+ name='Formset GET Template')
+ c = Context({'my_formset': formset})
+ return HttpResponse(t.render(c))
+
def login_protected_view(request):
"A simple view that is login protected."
t = Template('This is a login protected test. Username is {{ user.username }}.', name='Login Template')
diff --git a/tests/test_client_regress/models.py b/tests/test_client_regress/models.py
index e69de29bb2..b72d4480bf 100644
--- a/tests/test_client_regress/models.py
+++ b/tests/test_client_regress/models.py
@@ -0,0 +1,12 @@
+from django.db import models
+from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
+
+
+class CustomUser(AbstractBaseUser):
+ email = models.EmailField(verbose_name='email address', max_length=255, unique=True)
+ custom_objects = BaseUserManager()
+
+ USERNAME_FIELD = 'email'
+
+ class Meta:
+ app_label = 'test_client_regress'
diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py
index 2582b210c4..67e66fa52d 100644
--- a/tests/test_client_regress/tests.py
+++ b/tests/test_client_regress/tests.py
@@ -6,10 +6,8 @@ from __future__ import unicode_literals
import os
-from django.conf import settings
-from django.core.exceptions import SuspiciousOperation
from django.core.urlresolvers import reverse
-from django.template import (TemplateDoesNotExist, TemplateSyntaxError,
+from django.template import (TemplateSyntaxError,
Context, Template, loader)
import django.template.context
from django.test import Client, TestCase
@@ -19,7 +17,11 @@ from django.template.response import SimpleTemplateResponse
from django.utils._os import upath
from django.utils.translation import ugettext_lazy
from django.http import HttpResponse
+from django.contrib.auth.signals import user_logged_out, user_logged_in
+from django.contrib.auth.models import User
+from .models import CustomUser
+from .views import CustomTestException
@override_settings(
TEMPLATE_DIRS=(os.path.join(os.path.dirname(upath(__file__)), 'templates'),)
@@ -543,6 +545,197 @@ class AssertFormErrorTests(TestCase):
except AssertionError as e:
self.assertIn("abc: The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )", str(e))
+class AssertFormsetErrorTests(TestCase):
+ msg_prefixes = [("", {}), ("abc: ", {"msg_prefix": "abc"})]
+ def setUp(self):
+ """Makes response object for testing field and non-field errors"""
+ # For testing field and non-field errors
+ self.response_form_errors = self.getResponse({
+ 'form-TOTAL_FORMS': '2',
+ 'form-INITIAL_FORMS': '2',
+ 'form-0-text': 'Raise non-field error',
+ 'form-0-email': 'not an email address',
+ 'form-0-value': 37,
+ 'form-0-single': 'b',
+ 'form-0-multi': ('b','c','e'),
+ 'form-1-text': 'Hello World',
+ 'form-1-email': 'email@domain.com',
+ 'form-1-value': 37,
+ 'form-1-single': 'b',
+ 'form-1-multi': ('b','c','e'),
+ })
+ # For testing non-form errors
+ self.response_nonform_errors = self.getResponse({
+ 'form-TOTAL_FORMS': '2',
+ 'form-INITIAL_FORMS': '2',
+ 'form-0-text': 'Hello World',
+ 'form-0-email': 'email@domain.com',
+ 'form-0-value': 37,
+ 'form-0-single': 'b',
+ 'form-0-multi': ('b','c','e'),
+ 'form-1-text': 'Hello World',
+ 'form-1-email': 'email@domain.com',
+ 'form-1-value': 37,
+ 'form-1-single': 'b',
+ 'form-1-multi': ('b','c','e'),
+ })
+
+ def getResponse(self, post_data):
+ response = self.client.post('/test_client/formset_view/', post_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, "Invalid POST Template")
+ return response
+
+ def test_unknown_formset(self):
+ "An assertion is raised if the formset name is unknown"
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'wrong_formset',
+ 0,
+ 'Some_field',
+ 'Some error.',
+ **kwargs)
+ self.assertIn(prefix + "The formset 'wrong_formset' was not "
+ "used to render the response",
+ str(cm.exception))
+
+ def test_unknown_field(self):
+ "An assertion is raised if the field name is unknown"
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 0,
+ 'Some_field',
+ 'Some error.',
+ **kwargs)
+ self.assertIn(prefix + "The formset 'my_formset', "
+ "form 0 in context 0 "
+ "does not contain the field 'Some_field'",
+ str(cm.exception))
+
+ def test_no_error_field(self):
+ "An assertion is raised if the field doesn't have any errors"
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 1,
+ 'value',
+ 'Some error.',
+ **kwargs)
+ self.assertIn(prefix + "The field 'value' "
+ "on formset 'my_formset', form 1 "
+ "in context 0 contains no errors",
+ str(cm.exception))
+
+ def test_unknown_error(self):
+ "An assertion is raised if the field doesn't contain the specified error"
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 0,
+ 'email',
+ 'Some error.',
+ **kwargs)
+ self.assertIn(str_prefix(prefix + "The field 'email' "
+ "on formset 'my_formset', form 0 in context 0 does not "
+ "contain the error 'Some error.' (actual errors: "
+ "[%(_)s'Enter a valid email address.'])"),
+ str(cm.exception))
+
+ def test_field_error(self):
+ "No assertion is raised if the field contains the provided error"
+ for prefix, kwargs in self.msg_prefixes:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 0,
+ 'email',
+ ['Enter a valid email address.'],
+ **kwargs)
+
+ def test_no_nonfield_error(self):
+ "An assertion is raised if the formsets non-field errors doesn't contain any errors."
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 1,
+ None,
+ 'Some error.',
+ **kwargs)
+ self.assertIn(prefix + "The formset 'my_formset', form 1 in "
+ "context 0 does not contain any "
+ "non-field errors.",
+ str(cm.exception))
+
+ def test_unknown_nonfield_error(self):
+ "An assertion is raised if the formsets non-field errors doesn't contain the provided error."
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 0,
+ None,
+ 'Some error.',
+ **kwargs)
+ self.assertIn(str_prefix(prefix +
+ "The formset 'my_formset', form 0 in context 0 does not "
+ "contain the non-field error 'Some error.' (actual errors: "
+ "[%(_)s'Non-field error.'])"), str(cm.exception))
+
+ def test_nonfield_error(self):
+ "No assertion is raised if the formsets non-field errors contains the provided error."
+ for prefix, kwargs in self.msg_prefixes:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ 0,
+ None,
+ 'Non-field error.',
+ **kwargs)
+
+ def test_no_nonform_error(self):
+ "An assertion is raised if the formsets non-form errors doesn't contain any errors."
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_form_errors,
+ 'my_formset',
+ None,
+ None,
+ 'Some error.',
+ **kwargs)
+ self.assertIn(prefix + "The formset 'my_formset' in context 0 "
+ "does not contain any non-form errors.",
+ str(cm.exception))
+
+ def test_unknown_nonform_error(self):
+ "An assertion is raised if the formsets non-form errors doesn't contain the provided error."
+ for prefix, kwargs in self.msg_prefixes:
+ with self.assertRaises(AssertionError) as cm:
+ self.assertFormsetError(self.response_nonform_errors,
+ 'my_formset',
+ None,
+ None,
+ 'Some error.',
+ **kwargs)
+ self.assertIn(str_prefix(prefix +
+ "The formset 'my_formset' in context 0 does not contain the "
+ "non-form error 'Some error.' (actual errors: [%(_)s'Forms "
+ "in a set must have distinct email addresses.'])"), str(cm.exception))
+
+ def test_nonform_error(self):
+ "No assertion is raised if the formsets non-form errors contains the provided error."
+ for prefix, kwargs in self.msg_prefixes:
+ self.assertFormsetError(self.response_nonform_errors,
+ 'my_formset',
+ None,
+ None,
+ 'Forms in a set must have distinct email '
+ 'addresses.',
+ **kwargs)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class LoginTests(TestCase):
fixtures = ['testdata']
@@ -619,7 +812,7 @@ class ExceptionTests(TestCase):
try:
response = self.client.get("/test_client_regress/staff_only/")
self.fail("General users should not be able to visit this page")
- except SuspiciousOperation:
+ except CustomTestException:
pass
# At this point, an exception has been raised, and should be cleared.
@@ -629,7 +822,7 @@ class ExceptionTests(TestCase):
self.assertTrue(login, 'Could not log in')
try:
self.client.get("/test_client_regress/staff_only/")
- except SuspiciousOperation:
+ except CustomTestException:
self.fail("Staff should be able to visit this page")
@@ -706,6 +899,21 @@ class ContextTests(TestCase):
except KeyError as e:
self.assertEqual(e.args[0], 'does-not-exist')
+ def test_contextlist_keys(self):
+ c1 = Context()
+ c1.update({'hello': 'world', 'goodbye': 'john'})
+ c1.update({'hello': 'dolly', 'dolly': 'parton'})
+ c2 = Context()
+ c2.update({'goodbye': 'world', 'python': 'rocks'})
+ c2.update({'goodbye': 'dolly'})
+
+ l = ContextList([c1, c2])
+ # None, True and False are builtins of BaseContext, and present
+ # in every Context without needing to be added.
+ self.assertEqual(set(['None', 'True', 'False', 'hello', 'goodbye',
+ 'python', 'dolly']),
+ l.keys())
+
def test_15368(self):
# Need to insert a context processor that assumes certain things about
# the request instance. This triggers a bug caused by some ways of
@@ -756,6 +964,76 @@ class SessionTests(TestCase):
self.client.logout()
self.client.logout()
+ def test_logout_with_user(self):
+ """Logout should send user_logged_out signal if user was logged in."""
+ def listener(*args, **kwargs):
+ listener.executed = True
+ self.assertEqual(kwargs['sender'], User)
+ listener.executed = False
+
+ user_logged_out.connect(listener)
+ self.client.login(username='testclient', password='password')
+ self.client.logout()
+ user_logged_out.disconnect(listener)
+ self.assertTrue(listener.executed)
+
+ @override_settings(AUTH_USER_MODEL='test_client_regress.CustomUser')
+ def test_logout_with_custom_user(self):
+ """Logout should send user_logged_out signal if custom user was logged in."""
+ def listener(*args, **kwargs):
+ self.assertEqual(kwargs['sender'], CustomUser)
+ listener.executed = True
+ listener.executed = False
+ u = CustomUser.custom_objects.create(email='test@test.com')
+ u.set_password('password')
+ u.save()
+
+ user_logged_out.connect(listener)
+ self.client.login(username='test@test.com', password='password')
+ self.client.logout()
+ user_logged_out.disconnect(listener)
+ self.assertTrue(listener.executed)
+
+ def test_logout_without_user(self):
+ """Logout should send signal even if user not authenticated."""
+ def listener(user, *args, **kwargs):
+ listener.user = user
+ listener.executed = True
+ listener.executed = False
+
+ user_logged_out.connect(listener)
+ self.client.login(username='incorrect', password='password')
+ self.client.logout()
+ user_logged_out.disconnect(listener)
+
+ self.assertTrue(listener.executed)
+ self.assertIsNone(listener.user)
+
+ def test_login_with_user(self):
+ """Login should send user_logged_in signal on successful login."""
+ def listener(*args, **kwargs):
+ listener.executed = True
+ listener.executed = False
+
+ user_logged_in.connect(listener)
+ self.client.login(username='testclient', password='password')
+ user_logged_out.disconnect(listener)
+
+ self.assertTrue(listener.executed)
+
+ def test_login_without_signal(self):
+ """Login shouldn't send signal if user wasn't logged in"""
+ def listener(*args, **kwargs):
+ listener.executed = True
+ listener.executed = False
+
+ user_logged_in.connect(listener)
+ self.client.login(username='incorrect', password='password')
+ user_logged_in.disconnect(listener)
+
+ self.assertFalse(listener.executed)
+
+
class RequestMethodTests(TestCase):
def test_get(self):
"Request a view via request method GET"
diff --git a/tests/test_client_regress/views.py b/tests/test_client_regress/views.py
index 7e86ffd8ca..71e5b526e5 100644
--- a/tests/test_client_regress/views.py
+++ b/tests/test_client_regress/views.py
@@ -3,12 +3,15 @@ import json
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
-from django.core.exceptions import SuspiciousOperation
from django.shortcuts import render_to_response
from django.core.serializers.json import DjangoJSONEncoder
from django.test.client import CONTENT_TYPE_RE
from django.template import RequestContext
+
+class CustomTestException(Exception):
+ pass
+
def no_template_view(request):
"A simple view that expects a GET request, and returns a rendered template"
return HttpResponse("No template used. Sample content: twice once twice. Content ends.")
@@ -18,7 +21,7 @@ def staff_only_view(request):
if request.user.is_staff:
return HttpResponse('')
else:
- raise SuspiciousOperation()
+ raise CustomTestException()
def get_view(request):
"A simple login protected view"
diff --git a/tests/test_runner/test_discover_runner.py b/tests/test_runner/test_discover_runner.py
index 3dc364b351..1a0fb88367 100644
--- a/tests/test_runner/test_discover_runner.py
+++ b/tests/test_runner/test_discover_runner.py
@@ -1,5 +1,22 @@
+from contextlib import contextmanager
+import os
+import sys
+
from django.test import TestCase
from django.test.runner import DiscoverRunner
+from django.utils.unittest import expectedFailure
+
+try:
+ import unittest2
+except ImportError:
+ unittest2 = None
+
+
+def expectedFailureIf(condition):
+ """Marks a test as an expected failure if ``condition`` is met."""
+ if condition:
+ return expectedFailure
+ return lambda func: func
class DiscoverRunnerTest(TestCase):
@@ -32,6 +49,9 @@ class DiscoverRunnerTest(TestCase):
self.assertEqual(count, 1)
+ # this test fails if unittest2 is installed from PyPI on Python 2.6
+ # refs https://code.djangoproject.com/ticket/20437
+ @expectedFailureIf(sys.version_info < (2, 7) and unittest2)
def test_dotted_test_method_vanilla_unittest(self):
count = DiscoverRunner().build_suite(
["test_discovery_sample.tests_sample.TestVanillaUnittest.test_sample"],
@@ -61,8 +81,19 @@ class DiscoverRunnerTest(TestCase):
self.assertEqual(count, 1)
def test_file_path(self):
- count = DiscoverRunner().build_suite(
- ["test_discovery_sample/"],
- ).countTestCases()
+ @contextmanager
+ def change_cwd_to_tests():
+ """Change CWD to tests directory (one level up from this file)"""
+ current_dir = os.path.abspath(os.path.dirname(__file__))
+ tests_dir = os.path.join(current_dir, '..')
+ old_cwd = os.getcwd()
+ os.chdir(tests_dir)
+ yield
+ os.chdir(old_cwd)
+
+ with change_cwd_to_tests():
+ count = DiscoverRunner().build_suite(
+ ["test_discovery_sample/"],
+ ).countTestCases()
self.assertEqual(count, 4)
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index aeb9bc3d2c..0f16a9c805 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -5,6 +5,7 @@ import warnings
from django.db import connection, transaction, IntegrityError
from django.test import TransactionTestCase, skipUnlessDBFeature
+from django.test.utils import IgnorePendingDeprecationWarningsMixin
from django.utils import six
from django.utils.unittest import skipIf, skipUnless
@@ -319,19 +320,6 @@ class AtomicMiscTests(TransactionTestCase):
transaction.atomic(Callable())
-class IgnorePendingDeprecationWarningsMixin(object):
-
- def setUp(self):
- super(IgnorePendingDeprecationWarningsMixin, self).setUp()
- self.catch_warnings = warnings.catch_warnings()
- self.catch_warnings.__enter__()
- warnings.filterwarnings("ignore", category=PendingDeprecationWarning)
-
- def tearDown(self):
- self.catch_warnings.__exit__(*sys.exc_info())
- super(IgnorePendingDeprecationWarningsMixin, self).tearDown()
-
-
class TransactionTests(IgnorePendingDeprecationWarningsMixin, TransactionTestCase):
def create_a_reporter_then_fail(self, first, last):
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index fb3f257dab..5339b4a8ea 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -4,11 +4,9 @@ from django.db import (connection, connections, transaction, DEFAULT_DB_ALIAS, D
IntegrityError)
from django.db.transaction import commit_on_success, commit_manually, TransactionManagementError
from django.test import TransactionTestCase, skipUnlessDBFeature
-from django.test.utils import override_settings
+from django.test.utils import override_settings, IgnorePendingDeprecationWarningsMixin
from django.utils.unittest import skipIf, skipUnless
-from transactions.tests import IgnorePendingDeprecationWarningsMixin
-
from .models import Mod, M2mA, M2mB, SubMod
class ModelInheritanceTests(TransactionTestCase):
diff --git a/tests/unmanaged_models/tests.py b/tests/unmanaged_models/tests.py
index 64e33bbb47..d7cf961a37 100644
--- a/tests/unmanaged_models/tests.py
+++ b/tests/unmanaged_models/tests.py
@@ -23,14 +23,14 @@ class SimpleTests(TestCase):
# ... and pull it out via the other set.
a2 = A02.objects.all()[0]
- self.assertTrue(isinstance(a2, A02))
+ self.assertIsInstance(a2, A02)
self.assertEqual(a2.f_a, "foo")
b2 = B02.objects.all()[0]
- self.assertTrue(isinstance(b2, B02))
+ self.assertIsInstance(b2, B02)
self.assertEqual(b2.f_a, "fred")
- self.assertTrue(isinstance(b2.fk_a, A02))
+ self.assertIsInstance(b2.fk_a, A02)
self.assertEqual(b2.fk_a.f_a, "foo")
self.assertEqual(list(C02.objects.filter(f_a=None)), [])
@@ -38,7 +38,7 @@ class SimpleTests(TestCase):
resp = list(C02.objects.filter(mm_a=a.id))
self.assertEqual(len(resp), 1)
- self.assertTrue(isinstance(resp[0], C02))
+ self.assertIsInstance(resp[0], C02)
self.assertEqual(resp[0].f_a, 'barney')
diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py
index 24f96fac7c..f54c796d30 100644
--- a/tests/urlpatterns_reverse/tests.py
+++ b/tests/urlpatterns_reverse/tests.py
@@ -246,7 +246,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']), str('%s is not an instance of %s') % (t, e['type']))
+ self.assertIsInstance(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)
@@ -278,11 +278,11 @@ class ReverseShortcutTests(TestCase):
return "/hi-there/"
res = redirect(FakeObj())
- self.assertTrue(isinstance(res, HttpResponseRedirect))
+ self.assertIsInstance(res, HttpResponseRedirect)
self.assertEqual(res.url, '/hi-there/')
res = redirect(FakeObj(), permanent=True)
- self.assertTrue(isinstance(res, HttpResponsePermanentRedirect))
+ self.assertIsInstance(res, HttpResponsePermanentRedirect)
self.assertEqual(res.url, '/hi-there/')
def test_redirect_to_view_name(self):
@@ -516,7 +516,7 @@ class RequestURLconfTests(TestCase):
b''.join(self.client.get('/second_test/'))
class ErrorHandlerResolutionTests(TestCase):
- """Tests for handler404 and handler500"""
+ """Tests for handler400, handler404 and handler500"""
def setUp(self):
from django.core.urlresolvers import RegexURLResolver
@@ -528,12 +528,14 @@ class ErrorHandlerResolutionTests(TestCase):
def test_named_handlers(self):
from .views import empty_view
handler = (empty_view, {})
+ self.assertEqual(self.resolver.resolve400(), handler)
self.assertEqual(self.resolver.resolve404(), handler)
self.assertEqual(self.resolver.resolve500(), handler)
def test_callable_handers(self):
from .views import empty_view
handler = (empty_view, {})
+ self.assertEqual(self.callable_resolver.resolve400(), handler)
self.assertEqual(self.callable_resolver.resolve404(), handler)
self.assertEqual(self.callable_resolver.resolve500(), handler)
diff --git a/tests/urlpatterns_reverse/urls_error_handlers.py b/tests/urlpatterns_reverse/urls_error_handlers.py
index be4f42afbc..7146fdf43c 100644
--- a/tests/urlpatterns_reverse/urls_error_handlers.py
+++ b/tests/urlpatterns_reverse/urls_error_handlers.py
@@ -4,5 +4,6 @@ from django.conf.urls import patterns
urlpatterns = patterns('')
+handler400 = 'urlpatterns_reverse.views.empty_view'
handler404 = 'urlpatterns_reverse.views.empty_view'
handler500 = 'urlpatterns_reverse.views.empty_view'
diff --git a/tests/urlpatterns_reverse/urls_error_handlers_callables.py b/tests/urlpatterns_reverse/urls_error_handlers_callables.py
index fe2d3137e9..befeccaf45 100644
--- a/tests/urlpatterns_reverse/urls_error_handlers_callables.py
+++ b/tests/urlpatterns_reverse/urls_error_handlers_callables.py
@@ -9,5 +9,6 @@ from .views import empty_view
urlpatterns = patterns('')
+handler400 = empty_view
handler404 = empty_view
handler500 = empty_view
diff --git a/tests/utils_tests/test_baseconv.py b/tests/utils_tests/test_baseconv.py
index cc413b4e8e..d69a3a6412 100644
--- a/tests/utils_tests/test_baseconv.py
+++ b/tests/utils_tests/test_baseconv.py
@@ -1,4 +1,4 @@
-from unittest import TestCase
+from django.utils.unittest import TestCase
from django.utils.baseconv import base2, base16, base36, base56, base62, base64, BaseConverter
from django.utils.six.moves import xrange
@@ -39,4 +39,4 @@ class TestBaseConv(TestCase):
def test_exception(self):
self.assertRaises(ValueError, BaseConverter, 'abc', sign='a')
- self.assertTrue(isinstance(BaseConverter('abc', sign='d'), BaseConverter))
+ self.assertIsInstance(BaseConverter('abc', sign='d'), BaseConverter)
diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py
index 090cc32d1c..b973f1c64f 100644
--- a/tests/utils_tests/test_html.py
+++ b/tests/utils_tests/test_html.py
@@ -5,6 +5,7 @@ import os
from django.utils import html
from django.utils._os import upath
+from django.utils.encoding import force_text
from django.utils.unittest import TestCase
@@ -63,10 +64,15 @@ class TestUtilsHtml(TestCase):
def test_strip_tags(self):
f = html.strip_tags
items = (
+ ('<p>See: &#39;&eacute; is an apostrophe followed by e acute</p>',
+ 'See: &#39;&eacute; is an apostrophe followed by e acute'),
('<adf>a', 'a'),
('</adf>a', 'a'),
('<asdf><asdf>e', 'e'),
- ('<f', '<f'),
+ ('hi, <f x', 'hi, <f x'),
+ ('234<235, right?', '234<235, right?'),
+ ('a4<a5 right?', 'a4<a5 right?'),
+ ('b7>b2!', 'b7>b2!'),
('</fe', '</fe'),
('<x>b<y>', 'b'),
('a<p onclick="alert(\'<test>\')">b</p>c', 'abc'),
@@ -81,8 +87,9 @@ class TestUtilsHtml(TestCase):
for filename in ('strip_tags1.html', 'strip_tags2.txt'):
path = os.path.join(os.path.dirname(upath(__file__)), 'files', filename)
with open(path, 'r') as fp:
+ content = force_text(fp.read())
start = datetime.now()
- stripped = html.strip_tags(fp.read())
+ stripped = html.strip_tags(content)
elapsed = datetime.now() - start
self.assertEqual(elapsed.seconds, 0)
self.assertIn("Please try again.", stripped)
diff --git a/tests/utils_tests/test_safestring.py b/tests/utils_tests/test_safestring.py
new file mode 100644
index 0000000000..a6f0c0a01c
--- /dev/null
+++ b/tests/utils_tests/test_safestring.py
@@ -0,0 +1,53 @@
+from __future__ import absolute_import, unicode_literals
+
+
+from django.template import Template, Context
+from django.test import TestCase
+from django.utils.encoding import force_text, force_bytes
+from django.utils.functional import lazy, Promise
+from django.utils.html import escape, conditional_escape
+from django.utils.safestring import mark_safe, mark_for_escaping
+from django.utils import six
+from django.utils import translation
+
+lazystr = lazy(force_text, six.text_type)
+lazybytes = lazy(force_bytes, bytes)
+
+
+class SafeStringTest(TestCase):
+ def assertRenderEqual(self, tpl, expected, **context):
+ context = Context(context)
+ tpl = Template(tpl)
+ self.assertEqual(tpl.render(context), expected)
+
+ def test_mark_safe(self):
+ s = mark_safe('a&b')
+
+ self.assertRenderEqual('{{ s }}', 'a&b', s=s)
+ self.assertRenderEqual('{{ s|force_escape }}', 'a&amp;b', s=s)
+
+ def test_mark_safe_lazy(self):
+ s = lazystr('a&b')
+ b = lazybytes(b'a&b')
+
+ self.assertIsInstance(mark_safe(s), Promise)
+ self.assertIsInstance(mark_safe(b), Promise)
+ self.assertRenderEqual('{{ s }}', 'a&b', s=mark_safe(s))
+
+ def test_mark_for_escaping(self):
+ s = mark_for_escaping('a&b')
+ self.assertRenderEqual('{{ s }}', 'a&amp;b', s=s)
+ self.assertRenderEqual('{{ s }}', 'a&amp;b', s=mark_for_escaping(s))
+
+ def test_mark_for_escaping_lazy(self):
+ s = lazystr('a&b')
+ b = lazybytes(b'a&b')
+
+ self.assertIsInstance(mark_for_escaping(s), Promise)
+ self.assertIsInstance(mark_for_escaping(b), Promise)
+ self.assertRenderEqual('{% autoescape off %}{{ s }}{% endautoescape %}', 'a&amp;b', s=mark_for_escaping(s))
+
+ def test_regression_20296(self):
+ s = mark_safe(translation.ugettext_lazy("username"))
+ with translation.override('fr'):
+ self.assertRenderEqual('{{ s }}', "nom d'utilisateur", s=s)
diff --git a/tests/utils_tests/test_simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py
index f925e01eb6..4c01bd3adf 100644
--- a/tests/utils_tests/test_simplelazyobject.py
+++ b/tests/utils_tests/test_simplelazyobject.py
@@ -68,7 +68,7 @@ class TestUtilsSimpleLazyObject(TestCase):
# Second, for an evaluated SimpleLazyObject
name = x.name # evaluate
- self.assertTrue(isinstance(x._wrapped, _ComplexObject))
+ self.assertIsInstance(x._wrapped, _ComplexObject)
# __repr__ contains __repr__ of wrapped object
self.assertEqual("<SimpleLazyObject: %r>" % x._wrapped, repr(x))
@@ -161,3 +161,25 @@ class TestUtilsSimpleLazyObject(TestCase):
self.assertNotEqual(lazy1, lazy3)
self.assertTrue(lazy1 != lazy3)
self.assertFalse(lazy1 != lazy2)
+
+ def test_pickle_py2_regression(self):
+ from django.contrib.auth.models import User
+
+ # See ticket #20212
+ user = User.objects.create_user('johndoe', 'john@example.com', 'pass')
+ x = SimpleLazyObject(lambda: user)
+
+ # This would fail with "TypeError: can't pickle instancemethod objects",
+ # only on Python 2.X.
+ pickled = pickle.dumps(x)
+
+ # Try the variant protocol levels.
+ pickled = pickle.dumps(x, 0)
+ pickled = pickle.dumps(x, 1)
+ pickled = pickle.dumps(x, 2)
+
+ if not six.PY3:
+ import cPickle
+
+ # This would fail with "TypeError: expected string or Unicode object, NoneType found".
+ pickled = cPickle.dumps(x)
diff --git a/tests/utils_tests/test_timesince.py b/tests/utils_tests/test_timesince.py
index 5e641a42c4..cdb95e6877 100644
--- a/tests/utils_tests/test_timesince.py
+++ b/tests/utils_tests/test_timesince.py
@@ -21,32 +21,33 @@ class TimesinceTests(unittest.TestCase):
def test_equal_datetimes(self):
""" equal datetimes. """
- self.assertEqual(timesince(self.t, self.t), '0 minutes')
+ # NOTE: \xa0 avoids wrapping between value and unit
+ self.assertEqual(timesince(self.t, self.t), '0\xa0minutes')
def test_ignore_microseconds_and_seconds(self):
""" Microseconds and seconds are ignored. """
self.assertEqual(timesince(self.t, self.t+self.onemicrosecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t+self.onesecond),
- '0 minutes')
+ '0\xa0minutes')
def test_other_units(self):
""" Test other units. """
self.assertEqual(timesince(self.t, self.t+self.oneminute),
- '1 minute')
- self.assertEqual(timesince(self.t, self.t+self.onehour), '1 hour')
- self.assertEqual(timesince(self.t, self.t+self.oneday), '1 day')
- self.assertEqual(timesince(self.t, self.t+self.oneweek), '1 week')
+ '1\xa0minute')
+ self.assertEqual(timesince(self.t, self.t+self.onehour), '1\xa0hour')
+ self.assertEqual(timesince(self.t, self.t+self.oneday), '1\xa0day')
+ self.assertEqual(timesince(self.t, self.t+self.oneweek), '1\xa0week')
self.assertEqual(timesince(self.t, self.t+self.onemonth),
- '1 month')
- self.assertEqual(timesince(self.t, self.t+self.oneyear), '1 year')
+ '1\xa0month')
+ self.assertEqual(timesince(self.t, self.t+self.oneyear), '1\xa0year')
def test_multiple_units(self):
""" Test multiple units. """
self.assertEqual(timesince(self.t,
- self.t+2*self.oneday+6*self.onehour), '2 days, 6 hours')
+ self.t+2*self.oneday+6*self.onehour), '2\xa0days, 6\xa0hours')
self.assertEqual(timesince(self.t,
- self.t+2*self.oneweek+2*self.oneday), '2 weeks, 2 days')
+ self.t+2*self.oneweek+2*self.oneday), '2\xa0weeks, 2\xa0days')
def test_display_first_unit(self):
"""
@@ -55,10 +56,10 @@ class TimesinceTests(unittest.TestCase):
"""
self.assertEqual(timesince(self.t,
self.t+2*self.oneweek+3*self.onehour+4*self.oneminute),
- '2 weeks')
+ '2\xa0weeks')
self.assertEqual(timesince(self.t,
- self.t+4*self.oneday+5*self.oneminute), '4 days')
+ self.t+4*self.oneday+5*self.oneminute), '4\xa0days')
def test_display_second_before_first(self):
"""
@@ -66,30 +67,30 @@ class TimesinceTests(unittest.TestCase):
get 0 minutes.
"""
self.assertEqual(timesince(self.t, self.t-self.onemicrosecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onesecond),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneminute),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onehour),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneday),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneweek),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.onemonth),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t, self.t-self.oneyear),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-2*self.oneday-6*self.onehour), '0 minutes')
+ self.t-2*self.oneday-6*self.onehour), '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-2*self.oneweek-2*self.oneday), '0 minutes')
+ self.t-2*self.oneweek-2*self.oneday), '0\xa0minutes')
self.assertEqual(timesince(self.t,
self.t-2*self.oneweek-3*self.onehour-4*self.oneminute),
- '0 minutes')
+ '0\xa0minutes')
self.assertEqual(timesince(self.t,
- self.t-4*self.oneday-5*self.oneminute), '0 minutes')
+ self.t-4*self.oneday-5*self.oneminute), '0\xa0minutes')
def test_different_timezones(self):
""" When using two different timezones. """
@@ -97,28 +98,28 @@ class TimesinceTests(unittest.TestCase):
now_tz = datetime.datetime.now(LocalTimezone(now))
now_tz_i = datetime.datetime.now(FixedOffset((3 * 60) + 15))
- self.assertEqual(timesince(now), '0 minutes')
- self.assertEqual(timesince(now_tz), '0 minutes')
- self.assertEqual(timeuntil(now_tz, now_tz_i), '0 minutes')
+ self.assertEqual(timesince(now), '0\xa0minutes')
+ self.assertEqual(timesince(now_tz), '0\xa0minutes')
+ self.assertEqual(timeuntil(now_tz, now_tz_i), '0\xa0minutes')
def test_date_objects(self):
""" Both timesince and timeuntil should work on date objects (#17937). """
today = datetime.date.today()
- self.assertEqual(timesince(today + self.oneday), '0 minutes')
- self.assertEqual(timeuntil(today - self.oneday), '0 minutes')
+ self.assertEqual(timesince(today + self.oneday), '0\xa0minutes')
+ self.assertEqual(timeuntil(today - self.oneday), '0\xa0minutes')
def test_both_date_objects(self):
""" Timesince should work with both date objects (#9672) """
today = datetime.date.today()
- self.assertEqual(timeuntil(today + self.oneday, today), '1 day')
- self.assertEqual(timeuntil(today - self.oneday, today), '0 minutes')
- self.assertEqual(timeuntil(today + self.oneweek, today), '1 week')
+ self.assertEqual(timeuntil(today + self.oneday, today), '1\xa0day')
+ self.assertEqual(timeuntil(today - self.oneday, today), '0\xa0minutes')
+ self.assertEqual(timeuntil(today + self.oneweek, today), '1\xa0week')
def test_naive_datetime_with_tzinfo_attribute(self):
class naive(datetime.tzinfo):
def utcoffset(self, dt):
return None
future = datetime.datetime(2080, 1, 1, tzinfo=naive())
- self.assertEqual(timesince(future), '0 minutes')
+ self.assertEqual(timesince(future), '0\xa0minutes')
past = datetime.datetime(1980, 1, 1, tzinfo=naive())
- self.assertEqual(timeuntil(past), '0 minutes')
+ self.assertEqual(timeuntil(past), '0\xa0minutes')
diff --git a/tests/validation/models.py b/tests/validation/models.py
index db083290fb..e95a1e0744 100644
--- a/tests/validation/models.py
+++ b/tests/validation/models.py
@@ -95,7 +95,7 @@ class GenericIPAddressTestModel(models.Model):
blank=True, null=True)
class GenericIPAddrUnpackUniqueTest(models.Model):
- generic_v4unpack_ip = models.GenericIPAddressField(blank=True, unique=True, unpack_ipv4=True)
+ generic_v4unpack_ip = models.GenericIPAddressField(null=True, blank=True, unique=True, unpack_ipv4=True)
# A model can't have multiple AutoFields
diff --git a/tests/validation/tests.py b/tests/validation/tests.py
index b571e0c298..9ddf796c2b 100644
--- a/tests/validation/tests.py
+++ b/tests/validation/tests.py
@@ -109,9 +109,9 @@ class GenericIPAddressFieldTests(ValidationTestCase):
def test_correct_generic_ip_passes(self):
giptm = GenericIPAddressTestModel(generic_ip="1.2.3.4")
- self.assertEqual(None, giptm.full_clean())
+ self.assertIsNone(giptm.full_clean())
giptm = GenericIPAddressTestModel(generic_ip="2001::2")
- self.assertEqual(None, giptm.full_clean())
+ self.assertIsNone(giptm.full_clean())
def test_invalid_generic_ip_raises_error(self):
giptm = GenericIPAddressTestModel(generic_ip="294.4.2.1")
@@ -121,7 +121,7 @@ class GenericIPAddressFieldTests(ValidationTestCase):
def test_correct_v4_ip_passes(self):
giptm = GenericIPAddressTestModel(v4_ip="1.2.3.4")
- self.assertEqual(None, giptm.full_clean())
+ self.assertIsNone(giptm.full_clean())
def test_invalid_v4_ip_raises_error(self):
giptm = GenericIPAddressTestModel(v4_ip="294.4.2.1")
@@ -131,7 +131,7 @@ class GenericIPAddressFieldTests(ValidationTestCase):
def test_correct_v6_ip_passes(self):
giptm = GenericIPAddressTestModel(v6_ip="2001::2")
- self.assertEqual(None, giptm.full_clean())
+ self.assertIsNone(giptm.full_clean())
def test_invalid_v6_ip_raises_error(self):
giptm = GenericIPAddressTestModel(v6_ip="1.2.3.4")
@@ -151,10 +151,16 @@ class GenericIPAddressFieldTests(ValidationTestCase):
giptm = GenericIPAddressTestModel(generic_ip="::ffff:10.10.10.10")
giptm.save()
giptm = GenericIPAddressTestModel(generic_ip="10.10.10.10")
- self.assertEqual(None, giptm.full_clean())
+ self.assertIsNone(giptm.full_clean())
# These two are the same, because we are doing IPv4 unpacking
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="::ffff:18.52.18.52")
giptm.save()
giptm = GenericIPAddrUnpackUniqueTest(generic_v4unpack_ip="18.52.18.52")
self.assertFailsValidation(giptm.full_clean, ['generic_v4unpack_ip',])
+
+ def test_empty_generic_ip_passes(self):
+ giptm = GenericIPAddressTestModel(generic_ip="")
+ self.assertIsNone(giptm.full_clean())
+ giptm = GenericIPAddressTestModel(generic_ip=None)
+ self.assertIsNone(giptm.full_clean())
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index b44cd88abe..f686eee0e0 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -5,7 +5,9 @@ from __future__ import absolute_import, unicode_literals
import inspect
import os
+import shutil
import sys
+from tempfile import NamedTemporaryFile, mkdtemp, mkstemp
from django.core import mail
from django.core.files.uploadedfile import SimpleUploadedFile
@@ -13,13 +15,14 @@ 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.utils.encoding import force_text, force_bytes
from django.views.debug import ExceptionReporter
from .. import BrokenException, except_args
from ..views import (sensitive_view, non_sensitive_view, paranoid_view,
custom_exception_reporter_filter_view, sensitive_method_view,
sensitive_args_function_caller, sensitive_kwargs_function_caller)
+from django.utils.unittest import skipIf
@override_settings(DEBUG=True, TEMPLATE_DEBUG=True)
@@ -77,9 +80,38 @@ class DebugViewTests(TestCase):
raising_loc)
def test_template_loader_postmortem(self):
- response = self.client.get(reverse('raises_template_does_not_exist'))
- template_path = os.path.join('templates', 'i_dont_exist.html')
- self.assertContains(response, template_path, status_code=500)
+ """Tests for not existing file"""
+ template_name = "notfound.html"
+ with NamedTemporaryFile(prefix=template_name) as tempfile:
+ tempdir = os.path.dirname(tempfile.name)
+ template_path = os.path.join(tempdir, template_name)
+ with override_settings(TEMPLATE_DIRS=(tempdir,)):
+ response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
+ self.assertContains(response, "%s (File does not exist)" % template_path, status_code=500, count=1)
+
+ @skipIf(sys.platform == "win32", "Python on Windows doesn't have working os.chmod() and os.access().")
+ def test_template_loader_postmortem_notreadable(self):
+ """Tests for not readable file"""
+ with NamedTemporaryFile() as tempfile:
+ template_name = tempfile.name
+ tempdir = os.path.dirname(tempfile.name)
+ template_path = os.path.join(tempdir, template_name)
+ os.chmod(template_path, 0o0222)
+ with override_settings(TEMPLATE_DIRS=(tempdir,)):
+ response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
+ self.assertContains(response, "%s (File is not readable)" % template_path, status_code=500, count=1)
+
+ def test_template_loader_postmortem_notafile(self):
+ """Tests for not being a file"""
+ try:
+ template_path = mkdtemp()
+ template_name = os.path.basename(template_path)
+ tempdir = os.path.dirname(template_path)
+ with override_settings(TEMPLATE_DIRS=(tempdir,)):
+ response = self.client.get(reverse('raises_template_does_not_exist', kwargs={"path": template_name}))
+ self.assertContains(response, "%s (Not a file)" % template_path, status_code=500, count=1)
+ finally:
+ shutil.rmtree(template_path)
class ExceptionReporterTests(TestCase):
@@ -122,13 +154,31 @@ class ExceptionReporterTests(TestCase):
self.assertIn('<h2>Request information</h2>', html)
self.assertIn('<p>Request data not supplied</p>', html)
+ def test_eol_support(self):
+ """Test that the ExceptionReporter supports Unix, Windows and Macintosh EOL markers"""
+ LINES = list('print %d' % i for i in range(1, 6))
+ reporter = ExceptionReporter(None, None, None, None)
+
+ for newline in ['\n', '\r\n', '\r']:
+ fd, filename = mkstemp(text=False)
+ os.write(fd, force_bytes(newline.join(LINES)+newline))
+ os.close(fd)
+
+ try:
+ self.assertEqual(
+ reporter._get_lines_from_file(filename, 3, 2),
+ (1, LINES[1:3], LINES[3], LINES[4:])
+ )
+ finally:
+ os.unlink(filename)
+
def test_no_exception(self):
"An exception report can be generated for just a request"
request = self.rf.get('/test_view/')
reporter = ExceptionReporter(request, None, None, None)
html = reporter.get_traceback_html()
self.assertIn('<h1>Report at /test_view/</h1>', html)
- self.assertIn('<pre class="exception_value">No exception supplied</pre>', html)
+ self.assertIn('<pre class="exception_value">No exception message supplied</pre>', html)
self.assertIn('<th>Request Method:</th>', html)
self.assertIn('<th>Request URL:</th>', html)
self.assertNotIn('<th>Exception Type:</th>', html)
diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py
index bdd9fbfc0b..6104ad063e 100644
--- a/tests/view_tests/tests/test_static.py
+++ b/tests/view_tests/tests/test_static.py
@@ -60,7 +60,7 @@ class StaticTests(TestCase):
# This is 24h before max Unix time. Remember to fix Django and
# update this test well before 2038 :)
)
- self.assertTrue(isinstance(response, HttpResponseNotModified))
+ self.assertIsInstance(response, HttpResponseNotModified)
def test_invalid_if_modified_since(self):
"""Handle bogus If-Modified-Since values gracefully
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
index 52e2eb474e..d792e47ddf 100644
--- a/tests/view_tests/urls.py
+++ b/tests/view_tests/urls.py
@@ -66,5 +66,5 @@ urlpatterns = patterns('',
urlpatterns += patterns('view_tests.views',
url(r'view_exception/(?P<n>\d+)/$', 'view_exception', name='view_exception'),
url(r'template_exception/(?P<n>\d+)/$', 'template_exception', name='template_exception'),
- url(r'^raises_template_does_not_exist/$', 'raises_template_does_not_exist', name='raises_template_does_not_exist'),
+ url(r'^raises_template_does_not_exist/(?P<path>.+)$', 'raises_template_does_not_exist', name='raises_template_does_not_exist'),
)
diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py
index 50ad98ac2d..1cfafa4333 100644
--- a/tests/view_tests/views.py
+++ b/tests/view_tests/views.py
@@ -112,11 +112,11 @@ def render_view_with_current_app_conflict(request):
'bar': 'BAR',
}, current_app="foobar_app", context_instance=RequestContext(request))
-def raises_template_does_not_exist(request):
+def raises_template_does_not_exist(request, path='i_dont_exist.html'):
# We need to inspect the HTML generated by the fancy 500 debug view but
# the test client ignores it, so we send it explicitly.
try:
- return render_to_response('i_dont_exist.html')
+ return render_to_response(path)
except TemplateDoesNotExist:
return technical_500_response(request, *sys.exc_info())