summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2013-07-22 19:04:25 +0100
committerAndrew Godwin <andrew@aeracode.org>2013-07-22 19:04:25 +0100
commit03ec3219a04093da6c2e82436cee5c451df4f8aa (patch)
tree69e3cc2857b7bc41f269363b0056b361129da118 /tests
parent3a6580e485c11fc5502ffd1bb53347459db43421 (diff)
parent29a09b3638e625458b5e07ed0956451432421acf (diff)
Merge branch 'master' into schema-alteration
Conflicts: django/db/backends/mysql/introspection.py django/db/backends/oracle/creation.py django/db/backends/postgresql_psycopg2/creation.py django/db/models/base.py django/db/models/loading.py
Diffstat (limited to 'tests')
-rw-r--r--tests/admin_inlines/admin.py20
-rw-r--r--tests/admin_inlines/models.py20
-rw-r--r--tests/admin_inlines/tests.py18
-rw-r--r--tests/admin_views/admin.py5
-rw-r--r--tests/admin_views/tests.py74
-rw-r--r--tests/admin_views/urls.py1
-rw-r--r--tests/admin_widgets/tests.py60
-rw-r--r--tests/backends/tests.py33
-rw-r--r--tests/basic/tests.py24
-rw-r--r--tests/db_backends/__init__.py0
-rw-r--r--tests/db_backends/tests.py36
-rw-r--r--tests/file_uploads/tests.py21
-rw-r--r--tests/file_uploads/urls.py1
-rw-r--r--tests/file_uploads/views.py13
-rw-r--r--tests/fixtures_model_package/tests.py12
-rw-r--r--tests/fixtures_regress/tests.py25
-rw-r--r--tests/forms_tests/tests/test_extra.py2
-rw-r--r--tests/forms_tests/tests/test_fields.py12
-rw-r--r--tests/forms_tests/views.py1
-rw-r--r--tests/get_or_create/tests.py65
-rw-r--r--tests/logging_tests/tests.py2
-rw-r--r--tests/mail/tests.py55
-rw-r--r--tests/model_fields/tests.py13
-rw-r--r--tests/model_formsets/tests.py2
-rw-r--r--tests/model_package/models/article.py3
-rw-r--r--tests/model_package/models/publication.py3
-rw-r--r--tests/model_package/tests.py3
-rw-r--r--tests/queries/tests.py7
-rw-r--r--tests/requests/tests.py419
-rw-r--r--tests/requirements/base.txt6
-rw-r--r--tests/requirements/mysql.txt1
-rw-r--r--tests/requirements/oracle.txt1
-rw-r--r--tests/requirements/postgres.txt1
-rw-r--r--tests/requirements/py2.txt2
-rw-r--r--tests/requirements/py3.txt2
-rwxr-xr-xtests/runtests.py23
-rw-r--r--tests/select_for_update/tests.py14
-rw-r--r--tests/settings_tests/tests.py23
-rw-r--r--tests/syndication/feeds.py20
-rw-r--r--tests/syndication/fixtures/feeddata.json21
-rw-r--r--tests/syndication/models.py6
-rw-r--r--tests/syndication/tests.py78
-rw-r--r--tests/syndication/urls.py1
-rw-r--r--tests/template_tests/test_context.py9
-rw-r--r--tests/test_runner/tests.py61
-rw-r--r--tests/test_utils/tests.py28
-rw-r--r--tests/transactions_regress/tests.py7
-rw-r--r--tests/validators/tests.py1
48 files changed, 909 insertions, 346 deletions
diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py
index 2f88248ca4..62f9e04e5b 100644
--- a/tests/admin_inlines/admin.py
+++ b/tests/admin_inlines/admin.py
@@ -12,8 +12,26 @@ class BookInline(admin.TabularInline):
model = Author.books.through
+class NonAutoPKBookTabularInline(admin.TabularInline):
+ model = NonAutoPKBook
+
+
+class NonAutoPKBookStackedInline(admin.StackedInline):
+ model = NonAutoPKBook
+
+
+class EditablePKBookTabularInline(admin.TabularInline):
+ model = EditablePKBook
+
+
+class EditablePKBookStackedInline(admin.StackedInline):
+ model = EditablePKBook
+
+
class AuthorAdmin(admin.ModelAdmin):
- inlines = [BookInline]
+ inlines = [BookInline,
+ NonAutoPKBookTabularInline, NonAutoPKBookStackedInline,
+ EditablePKBookTabularInline, EditablePKBookStackedInline]
class InnerInline(admin.StackedInline):
diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py
index d4ba0ab6bc..dde5cf79e3 100644
--- a/tests/admin_inlines/models.py
+++ b/tests/admin_inlines/models.py
@@ -3,6 +3,7 @@ Testing of admin inline formsets.
"""
from __future__ import unicode_literals
+import random
from django.db import models
from django.contrib.contenttypes.models import ContentType
@@ -48,6 +49,25 @@ class Author(models.Model):
books = models.ManyToManyField(Book)
+class NonAutoPKBook(models.Model):
+ rand_pk = models.IntegerField(primary_key=True, editable=False)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+ def save(self, *args, **kwargs):
+ while not self.rand_pk:
+ test_pk = random.randint(1, 99999)
+ if not NonAutoPKBook.objects.filter(rand_pk=test_pk).exists():
+ self.rand_pk = test_pk
+ super(NonAutoPKBook, self).save(*args, **kwargs)
+
+
+class EditablePKBook(models.Model):
+ manual_pk = models.IntegerField(primary_key=True)
+ author = models.ForeignKey(Author)
+ title = models.CharField(max_length=50)
+
+
class Holder(models.Model):
dummy = models.IntegerField()
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 41dc0e32de..465b224d4f 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -211,6 +211,24 @@ class TestInline(TestCase):
self.assertContains(response, max_forms_input % 2)
self.assertContains(response, total_forms_hidden)
+ def test_inline_nonauto_noneditable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '<input id="id_nonautopkbook_set-0-rand_pk" name="nonautopkbook_set-0-rand_pk" type="hidden" />',
+ html=True)
+ self.assertContains(response,
+ '<input id="id_nonautopkbook_set-2-0-rand_pk" name="nonautopkbook_set-2-0-rand_pk" type="hidden" />',
+ html=True)
+
+ def test_inline_editable_pk(self):
+ response = self.client.get('/admin/admin_inlines/author/add/')
+ self.assertContains(response,
+ '<input class="vIntegerField" id="id_editablepkbook_set-0-manual_pk" name="editablepkbook_set-0-manual_pk" type="text" />',
+ html=True, count=1)
+ self.assertContains(response,
+ '<input class="vIntegerField" id="id_editablepkbook_set-2-0-manual_pk" name="editablepkbook_set-2-0-manual_pk" type="text" />',
+ html=True, count=1)
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class TestInlineMedia(TestCase):
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index a6ad7cc0bc..039abb819b 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -777,3 +777,8 @@ from django.contrib.auth.models import User, Group
from django.contrib.auth.admin import UserAdmin, GroupAdmin
site.register(User, UserAdmin)
site.register(Group, GroupAdmin)
+
+# Used to test URL namespaces
+site2 = admin.AdminSite(name="namespaced_admin")
+site2.register(User, UserAdmin)
+site2.register(Group, GroupAdmin)
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 101fcd90f6..7decf6f471 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -26,7 +26,6 @@ from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.models import Group, User, Permission
from django.contrib.contenttypes.models import ContentType
-from django.core.urlresolvers import reverse
from django.db import connection
from django.forms.util import ErrorList
from django.template.response import TemplateResponse
@@ -53,6 +52,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
Simple, UndeletableObject, Choice, ShortMessage, Telegram)
+from .admin import site, site2
ERROR_MESSAGE = "Please enter the correct username and password \
@@ -3824,6 +3824,59 @@ class CSSTest(TestCase):
self.assertContains(response, '<tr class="model-actor">')
self.assertContains(response, '<tr class="model-album">')
+ def testAppModelInFormBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by change_form template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/section/add/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInListBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by change_list template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/section/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInDeleteConfirmationBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by delete_confirmation
+ template
+ """
+ response = self.client.get(
+ '/test_admin/admin/admin_views/section/1/delete/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
+ def testAppModelInAppIndexBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by app_index template
+ """
+ response = self.client.get('/test_admin/admin/admin_views/')
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, '<body class="app-admin_views ')
+
+ def testAppModelInDeleteSelectedConfirmationBodyClass(self):
+ """
+ Ensure app and model tag are correcly read by
+ delete_selected_confirmation template
+ """
+ action_data = {
+ ACTION_CHECKBOX_NAME: [1],
+ 'action': 'delete_selected',
+ 'index': 0,
+ }
+ response = self.client.post('/test_admin/admin/admin_views/section/',
+ action_data)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response,
+ '<body class="app-admin_views model-section ')
+
try:
import docutils
except ImportError:
@@ -4186,6 +4239,7 @@ class AdminUserMessageTest(TestCase):
class AdminKeepChangeListFiltersTests(TestCase):
urls = "admin_views.urls"
fixtures = ['admin-views-users.xml']
+ admin_site = site
def setUp(self):
self.client.login(username='super', password='secret')
@@ -4209,13 +4263,15 @@ class AdminKeepChangeListFiltersTests(TestCase):
def get_changelist_url(self):
return '%s?%s' % (
- reverse('admin:auth_user_changelist'),
+ reverse('admin:auth_user_changelist',
+ current_app=self.admin_site.name),
self.get_changelist_filters_querystring(),
)
def get_add_url(self):
return '%s?%s' % (
- reverse('admin:auth_user_add'),
+ reverse('admin:auth_user_add',
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4223,7 +4279,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_change', args=(user_id,)),
+ reverse('admin:auth_user_change', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4231,7 +4288,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_history', args=(user_id,)),
+ reverse('admin:auth_user_history', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4239,7 +4297,8 @@ class AdminKeepChangeListFiltersTests(TestCase):
if user_id is None:
user_id = self.get_sample_user_id()
return "%s?%s" % (
- reverse('admin:auth_user_delete', args=(user_id,)),
+ reverse('admin:auth_user_delete', args=(user_id,),
+ current_app=self.admin_site.name),
self.get_preserved_filters_querystring(),
)
@@ -4333,3 +4392,6 @@ class AdminKeepChangeListFiltersTests(TestCase):
# Test redirect on "Delete".
response = self.client.post(self.get_delete_url(), {'post': 'yes'})
self.assertRedirects(response, self.get_changelist_url())
+
+class NamespacedAdminKeepChangeListFiltersTests(AdminKeepChangeListFiltersTests):
+ admin_site = site2
diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py
index 441834cd2a..763c83a450 100644
--- a/tests/admin_views/urls.py
+++ b/tests/admin_views/urls.py
@@ -12,4 +12,5 @@ urlpatterns = patterns('',
(r'^test_admin/admin2/', include(customadmin.site.urls)),
(r'^test_admin/admin3/', include(admin.site.urls), dict(form_url='pony')),
(r'^test_admin/admin4/', include(customadmin.simple_site.urls)),
+ (r'^test_admin/admin5/', include(admin.site2.urls)),
)
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 8edb2f5904..870aa6d455 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -1,7 +1,7 @@
# encoding: utf-8
from __future__ import absolute_import, unicode_literals
-from datetime import datetime
+from datetime import datetime, timedelta
from unittest import TestCase
from django import forms
@@ -526,6 +526,64 @@ class DateTimePickerSeleniumIETests(DateTimePickerSeleniumFirefoxTests):
webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
+@override_settings(TIME_ZONE='Asia/Singapore')
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
+class DateTimePickerShortcutsSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
+ available_apps = ['admin_widgets'] + AdminSeleniumWebDriverTestCase.available_apps
+ fixtures = ['admin-widgets-users.xml']
+ urls = "admin_widgets.urls"
+ webdriver_class = 'selenium.webdriver.firefox.webdriver.WebDriver'
+
+ def test_date_time_picker_shortcuts(self):
+ """
+ Ensure that date/time/datetime picker shortcuts work in the current time zone.
+ Refs #20663.
+
+ This test case is fairly tricky, it relies on selenium still running the browser
+ in the default time zone "America/Chicago" despite `override_settings` changing
+ the time zone to "Asia/Singapore".
+ """
+ self.admin_login(username='super', password='secret', login_url='/')
+
+ now = datetime.now()
+ error_margin = timedelta(seconds=10)
+
+ self.selenium.get('%s%s' % (self.live_server_url,
+ '/admin_widgets/member/add/'))
+
+ self.selenium.find_element_by_id('id_name').send_keys('test')
+
+ # Click on the "today" and "now" shortcuts.
+ shortcuts = self.selenium.find_elements_by_css_selector(
+ '.field-birthdate .datetimeshortcuts')
+
+ for shortcut in shortcuts:
+ shortcut.find_element_by_tag_name('a').click()
+
+ # Check that there is a time zone mismatch warning.
+ # Warning: This would effectively fail if the TIME_ZONE defined in the
+ # settings has the same UTC offset as "Asia/Singapore" because the
+ # mismatch warning would be rightfully missing from the page.
+ self.selenium.find_elements_by_css_selector(
+ '.field-birthdate .timezonewarning')
+
+ # Submit the form.
+ self.selenium.find_element_by_tag_name('form').submit()
+ self.wait_page_loaded()
+
+ # Make sure that "now" in javascript is within 10 seconds
+ # from "now" on the server side.
+ member = models.Member.objects.get(name='test')
+ self.assertGreater(member.birthdate, now - error_margin)
+ self.assertLess(member.birthdate, now + error_margin)
+
+class DateTimePickerShortcutsSeleniumChromeTests(DateTimePickerShortcutsSeleniumFirefoxTests):
+ webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
+
+class DateTimePickerShortcutsSeleniumIETests(DateTimePickerShortcutsSeleniumFirefoxTests):
+ webdriver_class = 'selenium.webdriver.ie.webdriver.WebDriver'
+
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class HorizontalVerticalFilterSeleniumFirefoxTests(AdminSeleniumWebDriverTestCase):
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index b1bfa4cb21..ec592f0188 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -641,8 +641,7 @@ class FkConstraintsTests(TransactionTestCase):
"""
When constraint checks are disabled, should be able to write bad data without IntegrityErrors.
"""
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -654,17 +653,13 @@ class FkConstraintsTests(TransactionTestCase):
connection.enable_constraint_checking()
except IntegrityError:
self.fail("IntegrityError should not have occurred.")
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ transaction.set_rollback(True)
def test_disable_constraint_checks_context_manager(self):
"""
When constraint checks are disabled (using context manager), should be able to write bad data without IntegrityErrors.
"""
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
@@ -675,31 +670,23 @@ class FkConstraintsTests(TransactionTestCase):
a.save()
except IntegrityError:
self.fail("IntegrityError should not have occurred.")
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ transaction.set_rollback(True)
def test_check_constraints(self):
"""
Constraint checks should raise an IntegrityError when bad data is in the DB.
"""
- try:
- transaction.set_autocommit(False)
+ with transaction.atomic():
# Create an Article.
models.Article.objects.create(headline="Test article", pub_date=datetime.datetime(2010, 9, 4), reporter=self.r)
# Retrive it from the DB
a = models.Article.objects.get(headline="Test article")
a.reporter_id = 30
- try:
- with connection.constraint_checks_disabled():
- a.save()
- with self.assertRaises(IntegrityError):
- connection.check_constraints()
- finally:
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
+ with connection.constraint_checks_disabled():
+ a.save()
+ with self.assertRaises(IntegrityError):
+ connection.check_constraints()
+ transaction.set_rollback(True)
class ThreadTests(TestCase):
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index 4b79c47be5..fb21b11279 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -6,7 +6,7 @@ import threading
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
from django.db import connections, DEFAULT_DB_ALIAS
from django.db.models.fields import Field, FieldDoesNotExist
-from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet
+from django.db.models.query import QuerySet, EmptyQuerySet, ValuesListQuerySet, MAX_GET_RESULTS
from django.test import TestCase, TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature
from django.utils import six
from django.utils.translation import ugettext_lazy
@@ -155,6 +155,28 @@ class ModelTest(TestCase):
pub_date__month=7,
)
+ def test_multiple_objects_max_num_fetched(self):
+ """
+ #6785 - get() should fetch a limited number of results.
+ """
+ Article.objects.bulk_create(
+ Article(headline='Area %s' % i, pub_date=datetime(2005, 7, 28))
+ for i in range(MAX_GET_RESULTS)
+ )
+ six.assertRaisesRegex(self,
+ MultipleObjectsReturned,
+ "get\(\) returned more than one Article -- it returned %d!" % MAX_GET_RESULTS,
+ Article.objects.get,
+ headline__startswith='Area',
+ )
+ Article.objects.create(headline='Area %s' % MAX_GET_RESULTS, pub_date=datetime(2005, 7, 28))
+ six.assertRaisesRegex(self,
+ MultipleObjectsReturned,
+ "get\(\) returned more than one Article -- it returned more than %d!" % MAX_GET_RESULTS,
+ Article.objects.get,
+ headline__startswith='Area',
+ )
+
def test_object_creation(self):
# Create an Article.
a = Article(
diff --git a/tests/db_backends/__init__.py b/tests/db_backends/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/db_backends/__init__.py
diff --git a/tests/db_backends/tests.py b/tests/db_backends/tests.py
new file mode 100644
index 0000000000..052ee87254
--- /dev/null
+++ b/tests/db_backends/tests.py
@@ -0,0 +1,36 @@
+from django.test import TestCase
+from django.db.backends import BaseDatabaseWrapper
+
+
+class DummyDatabaseWrapper(BaseDatabaseWrapper):
+ pass
+
+
+class DummyObject(object):
+ alias = None
+
+
+class DbBackendTests(TestCase):
+ def test_compare_db_wrapper_with_another_object(self):
+ wrapper = BaseDatabaseWrapper({})
+ self.assertFalse(wrapper == 'not-a-db-wrapper')
+
+ def test_compare_db_wrapper_with_another_object_with_alias(self):
+ wrapper = BaseDatabaseWrapper({})
+ obj = DummyObject()
+ obj.alias = wrapper.alias = 'foobar'
+ self.assertFalse(wrapper == obj)
+
+ def test_negate_compare_db_wrapper_with_another_object(self):
+ wrapper = BaseDatabaseWrapper({})
+ self.assertTrue(wrapper != 'not-a-db-wrapper')
+
+ def test_compare_db_wrappers(self):
+ wrapper1 = DummyDatabaseWrapper({})
+ wrapper2 = BaseDatabaseWrapper({})
+
+ wrapper1.alias = wrapper2.alias = 'foo'
+ self.assertTrue(wrapper1 == wrapper2)
+
+ wrapper1.alias = 'bar'
+ self.assertFalse(wrapper1 == wrapper2)
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index 66bc4465a2..f5a9c10be4 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -187,6 +187,27 @@ class FileUploadTests(TestCase):
got = json.loads(self.client.request(**r).content.decode('utf-8'))
self.assertTrue(len(got['file']) < 256, "Got a long file name (%s characters)." % len(got['file']))
+ def test_content_type_extra(self):
+ """Uploaded files may have content type parameters available."""
+ tdir = tempfile.gettempdir()
+
+ no_content_type = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir)
+ no_content_type.write(b'something')
+ no_content_type.seek(0)
+
+ simple_file = tempfile.NamedTemporaryFile(suffix=".ctype_extra", dir=tdir)
+ simple_file.write(b'something')
+ simple_file.seek(0)
+ simple_file.content_type = 'text/plain; test-key=test_value'
+
+ response = self.client.post('/file_uploads/echo_content_type_extra/', {
+ 'no_content_type': no_content_type,
+ 'simple_file': simple_file,
+ })
+ received = json.loads(response.content.decode('utf-8'))
+ self.assertEqual(received['no_content_type'], {})
+ self.assertEqual(received['simple_file'], {'test-key': 'test_value'})
+
def test_truncated_multipart_handled_gracefully(self):
"""
If passed an incomplete multipart message, MultiPartParser does not
diff --git a/tests/file_uploads/urls.py b/tests/file_uploads/urls.py
index fc5576828f..96efaaa5d8 100644
--- a/tests/file_uploads/urls.py
+++ b/tests/file_uploads/urls.py
@@ -10,6 +10,7 @@ urlpatterns = patterns('',
(r'^verify/$', views.file_upload_view_verify),
(r'^unicode_name/$', views.file_upload_unicode_name),
(r'^echo/$', views.file_upload_echo),
+ (r'^echo_content_type_extra/$', views.file_upload_content_type_extra),
(r'^echo_content/$', views.file_upload_echo_content),
(r'^quota/$', views.file_upload_quota),
(r'^quota/broken/$', views.file_upload_quota_broken),
diff --git a/tests/file_uploads/views.py b/tests/file_uploads/views.py
index eb7b654c09..d1bd2cf44e 100644
--- a/tests/file_uploads/views.py
+++ b/tests/file_uploads/views.py
@@ -7,7 +7,7 @@ import os
from django.core.files.uploadedfile import UploadedFile
from django.http import HttpResponse, HttpResponseServerError
from django.utils import six
-from django.utils.encoding import force_bytes
+from django.utils.encoding import force_bytes, smart_str
from .models import FileModel
from .tests import UNICODE_FILENAME, UPLOAD_TO
@@ -136,3 +136,14 @@ def file_upload_filename_case_view(request):
obj = FileModel()
obj.testfile.save(file.name, file)
return HttpResponse('%d' % obj.pk)
+
+def file_upload_content_type_extra(request):
+ """
+ Simple view to echo back extra content-type parameters.
+ """
+ params = {}
+ for file_name, uploadedfile in request.FILES.items():
+ params[file_name] = dict([
+ (k, smart_str(v)) for k, v in uploadedfile.content_type_extra.items()
+ ])
+ return HttpResponse(json.dumps(params))
diff --git a/tests/fixtures_model_package/tests.py b/tests/fixtures_model_package/tests.py
index 4b00e6dfb9..ad82267da3 100644
--- a/tests/fixtures_model_package/tests.py
+++ b/tests/fixtures_model_package/tests.py
@@ -30,8 +30,7 @@ class TestNoInitialDataLoading(TransactionTestCase):
available_apps = ['fixtures_model_package']
def test_syncdb(self):
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
Book.objects.all().delete()
management.call_command(
@@ -40,9 +39,6 @@ class TestNoInitialDataLoading(TransactionTestCase):
load_initial_data=False
)
self.assertQuerysetEqual(Book.objects.all(), [])
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
def test_flush(self):
@@ -54,8 +50,7 @@ class TestNoInitialDataLoading(TransactionTestCase):
lambda a: a.name
)
- transaction.set_autocommit(False)
- try:
+ with transaction.atomic():
management.call_command(
'flush',
verbosity=0,
@@ -63,9 +58,6 @@ class TestNoInitialDataLoading(TransactionTestCase):
load_initial_data=False
)
self.assertQuerysetEqual(Book.objects.all(), [])
- transaction.rollback()
- finally:
- transaction.set_autocommit(True)
class FixtureTestCase(TestCase):
diff --git a/tests/fixtures_regress/tests.py b/tests/fixtures_regress/tests.py
index a9d67ec9a2..a6bad5716d 100644
--- a/tests/fixtures_regress/tests.py
+++ b/tests/fixtures_regress/tests.py
@@ -660,22 +660,15 @@ class TestTicket11101(TransactionTestCase):
'django.contrib.contenttypes',
]
- def ticket_11101(self):
- management.call_command(
- 'loaddata',
- 'thingy.json',
- verbosity=0,
- )
- self.assertEqual(Thingy.objects.count(), 1)
- transaction.rollback()
- self.assertEqual(Thingy.objects.count(), 0)
- transaction.commit()
-
@skipUnlessDBFeature('supports_transactions')
def test_ticket_11101(self):
"""Test that fixtures can be rolled back (ticket #11101)."""
- transaction.set_autocommit(False)
- try:
- self.ticket_11101()
- finally:
- transaction.set_autocommit(True)
+ with transaction.atomic():
+ management.call_command(
+ 'loaddata',
+ 'thingy.json',
+ verbosity=0,
+ )
+ self.assertEqual(Thingy.objects.count(), 1)
+ transaction.set_rollback(True)
+ self.assertEqual(Thingy.objects.count(), 0)
diff --git a/tests/forms_tests/tests/test_extra.py b/tests/forms_tests/tests/test_extra.py
index ea0f063c30..d439e2223c 100644
--- a/tests/forms_tests/tests/test_extra.py
+++ b/tests/forms_tests/tests/test_extra.py
@@ -391,7 +391,7 @@ class FormsExtraTestCase(TestCase, AssertFormErrorsMixin):
def decompress(self, value):
if value:
data = value.split(',')
- return [data[0], data[1], datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")]
+ return [data[0], list(data[1]), datetime.datetime.strptime(data[2], "%Y-%m-%d %H:%M:%S")]
return [None, None, None]
def format_output(self, rendered_widgets):
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index f7d83503f4..f02593e488 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -374,6 +374,17 @@ class FieldsTests(SimpleTestCase):
self.assertEqual(f.clean('.01'), Decimal(".01"))
self.assertRaisesMessage(ValidationError, "'Ensure that there are no more than 0 digits before the decimal point.'", f.clean, '1.1')
+ def test_decimalfield_widget_attrs(self):
+ f = DecimalField(max_digits=6, decimal_places=2)
+ self.assertEqual(f.widget_attrs(Widget()), {})
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '0.01'})
+ f = DecimalField(max_digits=10, decimal_places=0)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1'})
+ f = DecimalField(max_digits=19, decimal_places=19)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': '1e-19'})
+ f = DecimalField(max_digits=20)
+ self.assertEqual(f.widget_attrs(NumberInput()), {'step': 'any'})
+
def test_decimalfield_localized(self):
"""
Make sure localized DecimalField's widget renders to a text input with
@@ -1236,6 +1247,7 @@ class FieldsTests(SimpleTestCase):
def test_splitdatetimefield_changed(self):
f = SplitDateTimeField(input_date_formats=['%d/%m/%Y'])
+ self.assertFalse(f._has_changed(['11/01/2012', '09:18:15'], ['11/01/2012', '09:18:15']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['2008-05-06', '12:40:00']))
self.assertFalse(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:40']))
self.assertTrue(f._has_changed(datetime.datetime(2008, 5, 6, 12, 40, 00), ['06/05/2008', '12:41']))
diff --git a/tests/forms_tests/views.py b/tests/forms_tests/views.py
index 4bf384363c..2081e0af14 100644
--- a/tests/forms_tests/views.py
+++ b/tests/forms_tests/views.py
@@ -6,3 +6,4 @@ from .models import Article
class ArticleFormView(UpdateView):
model = Article
success_url = '/'
+ fields = '__all__'
diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py
index 847a6dec01..0f766ab128 100644
--- a/tests/get_or_create/tests.py
+++ b/tests/get_or_create/tests.py
@@ -131,3 +131,68 @@ class GetOrCreateThroughManyToMany(TestCase):
Tag.objects.create(text='foo')
a_thing = Thing.objects.create(name='a')
self.assertRaises(IntegrityError, a_thing.tags.get_or_create, text='foo')
+
+
+class UpdateOrCreateTests(TestCase):
+
+ def test_update(self):
+ Person.objects.create(
+ first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)
+ )
+ p, created = Person.objects.update_or_create(
+ first_name='John', last_name='Lennon', defaults={
+ 'birthday': date(1940, 10, 10)
+ }
+ )
+ self.assertFalse(created)
+ self.assertEqual(p.first_name, 'John')
+ self.assertEqual(p.last_name, 'Lennon')
+ self.assertEqual(p.birthday, date(1940, 10, 10))
+
+ def test_create(self):
+ p, created = Person.objects.update_or_create(
+ first_name='John', last_name='Lennon', defaults={
+ 'birthday': date(1940, 10, 10)
+ }
+ )
+ self.assertTrue(created)
+ self.assertEqual(p.first_name, 'John')
+ self.assertEqual(p.last_name, 'Lennon')
+ self.assertEqual(p.birthday, date(1940, 10, 10))
+
+ def test_create_twice(self):
+ params = {
+ 'first_name': 'John',
+ 'last_name': 'Lennon',
+ 'birthday': date(1940, 10, 10),
+ }
+ Person.objects.update_or_create(**params)
+ # If we execute the exact same statement, it won't create a Person.
+ p, created = Person.objects.update_or_create(**params)
+ self.assertFalse(created)
+
+ def test_integrity(self):
+ # If you don't specify a value or default value for all required
+ # fields, you will get an error.
+ self.assertRaises(IntegrityError,
+ Person.objects.update_or_create, first_name="Tom", last_name="Smith")
+
+ def test_mananual_primary_key_test(self):
+ # If you specify an existing primary key, but different other fields,
+ # then you will get an error and data will not be updated.
+ ManualPrimaryKeyTest.objects.create(id=1, data="Original")
+ self.assertRaises(IntegrityError,
+ ManualPrimaryKeyTest.objects.update_or_create, id=1, data="Different"
+ )
+ self.assertEqual(ManualPrimaryKeyTest.objects.get(id=1).data, "Original")
+
+ def test_error_contains_full_traceback(self):
+ # update_or_create should raise IntegrityErrors with the full traceback.
+ # This is tested by checking that a known method call is in the traceback.
+ # We cannot use assertRaises/assertRaises here because we need to inspect
+ # the actual traceback. Refs #16340.
+ try:
+ ManualPrimaryKeyTest.objects.update_or_create(id=1, data="Different")
+ except IntegrityError as e:
+ formatted_traceback = traceback.format_exc()
+ self.assertIn('obj.save', formatted_traceback)
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index cae2860306..8ba4702aba 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -19,7 +19,6 @@ from admin_scripts.tests import AdminScriptTestCase
from .logconfig import MyEmailBackend
-PYVERS = sys.version_info[:2]
# logging config prior to using filter with mail_admins
OLD_LOGGING = {
@@ -87,7 +86,6 @@ class DefaultLoggingTest(TestCase):
self.logger.error("Hey, this is an error.")
self.assertEqual(output.getvalue(), 'Hey, this is an error.\n')
-@skipUnless(PYVERS > (2,6), "warnings captured only in Python >= 2.7")
class WarningLoggerTests(TestCase):
"""
Tests that warnings output for DeprecationWarnings is enabled
diff --git a/tests/mail/tests.py b/tests/mail/tests.py
index c90dc7e22a..822c572b0f 100644
--- a/tests/mail/tests.py
+++ b/tests/mail/tests.py
@@ -9,6 +9,8 @@ import smtpd
import sys
import tempfile
import threading
+from smtplib import SMTPException
+from ssl import SSLError
from django.core import mail
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
@@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
+class FakeSMTPChannel(smtpd.SMTPChannel):
+
+ def collect_incoming_data(self, data):
+ try:
+ super(FakeSMTPChannel, self).collect_incoming_data(data)
+ except UnicodeDecodeError:
+ # ignore decode error in SSL/TLS connection tests as we only care
+ # whether the connection attempt was made
+ pass
+
+
class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
"""
Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
"""
+ channel_class = FakeSMTPChannel
def __init__(self, *args, **kwargs):
threading.Thread.__init__(self)
@@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
backend.close()
except Exception as e:
self.fail("close() unexpectedly raised an exception: %s" % e)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_use_settings(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_tls)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_override_settings(self):
+ backend = smtp.EmailBackend(use_tls=False)
+ self.assertFalse(backend.use_tls)
+
+ def test_email_tls_default_disabled(self):
+ backend = smtp.EmailBackend()
+ self.assertFalse(backend.use_tls)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_use_settings(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_ssl)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_override_settings(self):
+ backend = smtp.EmailBackend(use_ssl=False)
+ self.assertFalse(backend.use_ssl)
+
+ def test_email_ssl_default_disabled(self):
+ backend = smtp.EmailBackend()
+ self.assertFalse(backend.use_ssl)
+
+ @override_settings(EMAIL_USE_TLS=True)
+ def test_email_tls_attempts_starttls(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_tls)
+ self.assertRaisesMessage(SMTPException,
+ 'STARTTLS extension not supported by server.', backend.open)
+
+ @override_settings(EMAIL_USE_SSL=True)
+ def test_email_ssl_attempts_ssl_connection(self):
+ backend = smtp.EmailBackend()
+ self.assertTrue(backend.use_ssl)
+ self.assertRaises(SSLError, backend.open)
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 6546c49ec7..1ce5eba3e9 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -468,3 +468,16 @@ class BinaryFieldTests(test.TestCase):
def test_max_length(self):
dm = DataModel(short_data=self.binary_data*4)
self.assertRaises(ValidationError, dm.full_clean)
+
+class GenericIPAddressFieldTests(test.TestCase):
+ def test_genericipaddressfield_formfield_protocol(self):
+ """
+ Test that GenericIPAddressField with a specified protocol does not
+ generate a formfield with no specified protocol. See #20740.
+ """
+ model_field = models.GenericIPAddressField(protocol='IPv4')
+ form_field = model_field.formfield()
+ self.assertRaises(ValidationError, form_field.clean, '::1')
+ model_field = models.GenericIPAddressField(protocol='IPv6')
+ form_field = model_field.formfield()
+ self.assertRaises(ValidationError, form_field.clean, '127.0.0.1')
diff --git a/tests/model_formsets/tests.py b/tests/model_formsets/tests.py
index 09a3ba1778..4411bbf59a 100644
--- a/tests/model_formsets/tests.py
+++ b/tests/model_formsets/tests.py
@@ -576,7 +576,7 @@ class ModelFormsetTest(TestCase):
formset = AuthorBooksFormSet2(instance=author)
self.assertEqual(len(formset.forms), 1)
self.assertHTMLEqual(formset.forms[0].as_p(),
- '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" /></p>\n'
+ '<p><label for="id_bookwithcustompk_set-0-my_pk">My pk:</label> <input id="id_bookwithcustompk_set-0-my_pk" type="number" name="bookwithcustompk_set-0-my_pk" step="1" /></p>\n'
'<p><label for="id_bookwithcustompk_set-0-title">Title:</label> <input id="id_bookwithcustompk_set-0-title" type="text" name="bookwithcustompk_set-0-title" maxlength="100" /><input type="hidden" name="bookwithcustompk_set-0-author" value="1" id="id_bookwithcustompk_set-0-author" /></p>')
data = {
diff --git a/tests/model_package/models/article.py b/tests/model_package/models/article.py
index f70c971c7e..4dfcdc1056 100644
--- a/tests/model_package/models/article.py
+++ b/tests/model_package/models/article.py
@@ -6,6 +6,3 @@ class Article(models.Model):
sites = models.ManyToManyField(Site)
headline = models.CharField(max_length=100)
publications = models.ManyToManyField("model_package.Publication", null=True, blank=True,)
-
- class Meta:
- app_label = 'model_package'
diff --git a/tests/model_package/models/publication.py b/tests/model_package/models/publication.py
index 29828b9295..b0a49a94a1 100644
--- a/tests/model_package/models/publication.py
+++ b/tests/model_package/models/publication.py
@@ -3,6 +3,3 @@ from django.db import models
class Publication(models.Model):
title = models.CharField(max_length=30)
-
- class Meta:
- app_label = 'model_package'
diff --git a/tests/model_package/tests.py b/tests/model_package/tests.py
index 5cf464858b..5d856a9608 100644
--- a/tests/model_package/tests.py
+++ b/tests/model_package/tests.py
@@ -14,9 +14,6 @@ class Advertisment(models.Model):
"model_package.Publication", null=True, blank=True
)
- class Meta:
- app_label = 'model_package'
-
class ModelPackageTests(TestCase):
def test_model_packages(self):
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 6520eb0176..87d54b637e 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2148,13 +2148,6 @@ class ConditionalTests(BaseQuerysetTest):
t4 = Tag.objects.create(name='t4', parent=t3)
t5 = Tag.objects.create(name='t5', parent=t3)
-
- # In Python 2.6 beta releases, exceptions raised in __len__ are swallowed
- # (Python issue 1242657), so these cases return an empty list, rather than
- # raising an exception. Not a lot we can do about that, unfortunately, due to
- # the way Python handles list() calls internally. Thus, we skip the tests for
- # Python 2.6.
- @unittest.skipIf(sys.version_info[:2] == (2, 6), "Python version is 2.6")
def test_infinite_loop(self):
# If you're not careful, it's possible to introduce infinite loops via
# default ordering on foreign keys in a cycle. We detect that.
diff --git a/tests/requests/tests.py b/tests/requests/tests.py
index b00eec87a9..8c56e48f58 100644
--- a/tests/requests/tests.py
+++ b/tests/requests/tests.py
@@ -3,6 +3,7 @@ from __future__ import unicode_literals
from datetime import datetime, timedelta
from io import BytesIO
+from itertools import chain
import time
from unittest import skipIf
import warnings
@@ -122,221 +123,6 @@ class RequestsTests(SimpleTestCase):
self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
'http://www.example.com/path/with:colons')
- @override_settings(
- USE_X_FORWARDED_HOST=False,
- ALLOWED_HOSTS=[
- 'forward.com', 'example.com', 'internal.com', '12.34.56.78',
- '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com',
- '.multitenant.com', 'INSENSITIVE.com',
- ])
- def test_http_get_host(self):
- # Check if X_FORWARDED_HOST is provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_X_FORWARDED_HOST': 'forward.com',
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- # X_FORWARDED_HOST is ignored.
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if X_FORWARDED_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if HTTP_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'internal.com')
-
- # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 8042,
- }
- self.assertEqual(request.get_host(), 'internal.com:8042')
-
- # Poisoned host headers are rejected as suspicious
- legit_hosts = [
- 'example.com',
- 'example.com:80',
- '12.34.56.78',
- '12.34.56.78:443',
- '[2001:19f0:feee::dead:beef:cafe]',
- '[2001:19f0:feee::dead:beef:cafe]:8080',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- 'anything.multitenant.com',
- 'multitenant.com',
- 'insensitive.com',
- ]
-
- poisoned_hosts = [
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- 'other.com', # not in ALLOWED_HOSTS
- ]
-
- for host in legit_hosts:
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- for host in poisoned_hosts:
- with self.assertRaises(SuspiciousOperation):
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*'])
- def test_http_get_host_with_x_forwarded_host(self):
- # Check if X_FORWARDED_HOST is provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_X_FORWARDED_HOST': 'forward.com',
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- # X_FORWARDED_HOST is obeyed.
- self.assertEqual(request.get_host(), 'forward.com')
-
- # Check if X_FORWARDED_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'example.com')
-
- # Check if HTTP_HOST isn't provided.
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 80,
- }
- self.assertEqual(request.get_host(), 'internal.com')
-
- # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
- request = HttpRequest()
- request.META = {
- 'SERVER_NAME': 'internal.com',
- 'SERVER_PORT': 8042,
- }
- self.assertEqual(request.get_host(), 'internal.com:8042')
-
- # Poisoned host headers are rejected as suspicious
- legit_hosts = [
- 'example.com',
- 'example.com:80',
- '12.34.56.78',
- '12.34.56.78:443',
- '[2001:19f0:feee::dead:beef:cafe]',
- '[2001:19f0:feee::dead:beef:cafe]:8080',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- ]
-
- poisoned_hosts = [
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- ]
-
- for host in legit_hosts:
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
- for host in poisoned_hosts:
- with self.assertRaises(SuspiciousOperation):
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': host,
- }
- request.get_host()
-
-
- @override_settings(DEBUG=True, ALLOWED_HOSTS=[])
- def test_host_validation_disabled_in_debug_mode(self):
- """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
- request = HttpRequest()
- request.META = {
- 'HTTP_HOST': 'example.com',
- }
- self.assertEqual(request.get_host(), 'example.com')
-
-
- @override_settings(ALLOWED_HOSTS=[])
- def test_get_host_suggestion_of_allowed_host(self):
- """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS."""
- msg_invalid_host = "Invalid HTTP_HOST header: %r."
- msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS."
-
- for host in [ # Valid-looking hosts
- 'example.com',
- '12.34.56.78',
- '[2001:19f0:feee::dead:beef:cafe]',
- 'xn--4ca9at.com', # Punnycode for öäü.com
- ]:
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_suggestion % (host, host),
- request.get_host
- )
-
- for domain, port in [ # Valid-looking hosts with a port number
- ('example.com', 80),
- ('12.34.56.78', 443),
- ('[2001:19f0:feee::dead:beef:cafe]', 8080),
- ]:
- host = '%s:%s' % (domain, port)
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_suggestion % (host, domain),
- request.get_host
- )
-
- for host in [ # Invalid hosts
- 'example.com@evil.tld',
- 'example.com:dr.frankenstein@evil.tld',
- 'example.com:dr.frankenstein@evil.tld:80',
- 'example.com:80/badpath',
- 'example.com: recovermypassword.com',
- ]:
- request = HttpRequest()
- request.META = {'HTTP_HOST': host}
- self.assertRaisesMessage(
- SuspiciousOperation,
- msg_invalid_host % host,
- request.get_host
- )
-
-
def test_near_expiration(self):
"Cookie will expire when an near expiration time is provided"
response = HttpResponse()
@@ -678,8 +464,209 @@ class RequestsTests(SimpleTestCase):
request.FILES
+class HostValidationTests(SimpleTestCase):
+ poisoned_hosts = [
+ 'example.com@evil.tld',
+ 'example.com:dr.frankenstein@evil.tld',
+ 'example.com:dr.frankenstein@evil.tld:80',
+ 'example.com:80/badpath',
+ 'example.com: recovermypassword.com',
+ ]
+
+ @override_settings(
+ USE_X_FORWARDED_HOST=False,
+ ALLOWED_HOSTS=[
+ 'forward.com', 'example.com', 'internal.com', '12.34.56.78',
+ '[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com',
+ '.multitenant.com', 'INSENSITIVE.com',
+ ])
+ def test_http_get_host(self):
+ # Check if X_FORWARDED_HOST is provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_X_FORWARDED_HOST': 'forward.com',
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ # X_FORWARDED_HOST is ignored.
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if X_FORWARDED_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if HTTP_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'internal.com')
+
+ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 8042,
+ }
+ self.assertEqual(request.get_host(), 'internal.com:8042')
+
+ legit_hosts = [
+ 'example.com',
+ 'example.com:80',
+ '12.34.56.78',
+ '12.34.56.78:443',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ '[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ 'anything.multitenant.com',
+ 'multitenant.com',
+ 'insensitive.com',
+ ]
+
+ for host in legit_hosts:
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ # Poisoned host headers are rejected as suspicious
+ for host in chain(self.poisoned_hosts, ['other.com']):
+ with self.assertRaises(SuspiciousOperation):
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ @override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*'])
+ def test_http_get_host_with_x_forwarded_host(self):
+ # Check if X_FORWARDED_HOST is provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_X_FORWARDED_HOST': 'forward.com',
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ # X_FORWARDED_HOST is obeyed.
+ self.assertEqual(request.get_host(), 'forward.com')
+
+ # Check if X_FORWARDED_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+ # Check if HTTP_HOST isn't provided.
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 80,
+ }
+ self.assertEqual(request.get_host(), 'internal.com')
+
+ # Check if HTTP_HOST isn't provided, and we're on a nonstandard port
+ request = HttpRequest()
+ request.META = {
+ 'SERVER_NAME': 'internal.com',
+ 'SERVER_PORT': 8042,
+ }
+ self.assertEqual(request.get_host(), 'internal.com:8042')
+
+ # Poisoned host headers are rejected as suspicious
+ legit_hosts = [
+ 'example.com',
+ 'example.com:80',
+ '12.34.56.78',
+ '12.34.56.78:443',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ '[2001:19f0:feee::dead:beef:cafe]:8080',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ ]
+
+ for host in legit_hosts:
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+ for host in self.poisoned_hosts:
+ with self.assertRaises(SuspiciousOperation):
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': host,
+ }
+ request.get_host()
+
+
+ @override_settings(DEBUG=True, ALLOWED_HOSTS=[])
+ def test_host_validation_disabled_in_debug_mode(self):
+ """If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
+ request = HttpRequest()
+ request.META = {
+ 'HTTP_HOST': 'example.com',
+ }
+ self.assertEqual(request.get_host(), 'example.com')
+
+
+ @override_settings(ALLOWED_HOSTS=[])
+ def test_get_host_suggestion_of_allowed_host(self):
+ """get_host() makes helpful suggestions if a valid-looking host is not in ALLOWED_HOSTS."""
+ msg_invalid_host = "Invalid HTTP_HOST header: %r."
+ msg_suggestion = msg_invalid_host + "You may need to add %r to ALLOWED_HOSTS."
+
+ for host in [ # Valid-looking hosts
+ 'example.com',
+ '12.34.56.78',
+ '[2001:19f0:feee::dead:beef:cafe]',
+ 'xn--4ca9at.com', # Punnycode for öäü.com
+ ]:
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_suggestion % (host, host),
+ request.get_host
+ )
+
+ for domain, port in [ # Valid-looking hosts with a port number
+ ('example.com', 80),
+ ('12.34.56.78', 443),
+ ('[2001:19f0:feee::dead:beef:cafe]', 8080),
+ ]:
+ host = '%s:%s' % (domain, port)
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_suggestion % (host, domain),
+ request.get_host
+ )
+
+ for host in self.poisoned_hosts:
+ request = HttpRequest()
+ request.META = {'HTTP_HOST': host}
+ self.assertRaisesMessage(
+ SuspiciousOperation,
+ msg_invalid_host % host,
+ request.get_host
+ )
+
+
@skipIf(connection.vendor == 'sqlite'
- and connection.settings_dict['NAME'] in ('', ':memory:'),
+ and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'),
"Cannot establish two connections to an in-memory SQLite database.")
class DatabaseConnectionHandlingTests(TransactionTestCase):
diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt
new file mode 100644
index 0000000000..89bd8230b2
--- /dev/null
+++ b/tests/requirements/base.txt
@@ -0,0 +1,6 @@
+bcrypt
+docutils
+Pillow
+PyYAML
+pytz
+selenium
diff --git a/tests/requirements/mysql.txt b/tests/requirements/mysql.txt
new file mode 100644
index 0000000000..c7a2347407
--- /dev/null
+++ b/tests/requirements/mysql.txt
@@ -0,0 +1 @@
+MySQL-python
diff --git a/tests/requirements/oracle.txt b/tests/requirements/oracle.txt
new file mode 100644
index 0000000000..ae5b7349cd
--- /dev/null
+++ b/tests/requirements/oracle.txt
@@ -0,0 +1 @@
+cx_oracle
diff --git a/tests/requirements/postgres.txt b/tests/requirements/postgres.txt
new file mode 100644
index 0000000000..658130bb2c
--- /dev/null
+++ b/tests/requirements/postgres.txt
@@ -0,0 +1 @@
+psycopg2
diff --git a/tests/requirements/py2.txt b/tests/requirements/py2.txt
new file mode 100644
index 0000000000..a9f6804039
--- /dev/null
+++ b/tests/requirements/py2.txt
@@ -0,0 +1,2 @@
+-r base.txt
+python-memcached
diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt
new file mode 100644
index 0000000000..ced3eed101
--- /dev/null
+++ b/tests/requirements/py3.txt
@@ -0,0 +1,2 @@
+-r base.txt
+python3-memcached
diff --git a/tests/runtests.py b/tests/runtests.py
index b604155190..8d5b375fb3 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -22,6 +22,8 @@ TEMP_DIR = tempfile.mkdtemp(prefix='django_')
os.environ['DJANGO_TEST_TEMP_DIR'] = TEMP_DIR
SUBDIRS_TO_SKIP = [
+ 'data',
+ 'requirements',
'templates',
'test_discovery_sample',
'test_discovery_sample2',
@@ -49,10 +51,18 @@ ALWAYS_INSTALLED_APPS = [
def get_test_modules():
+ from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
modules = []
- for modpath, dirpath in (
+ discovery_paths = [
(None, RUNTESTS_DIR),
- (CONTRIB_MODULE_PATH, CONTRIB_DIR)):
+ (CONTRIB_MODULE_PATH, CONTRIB_DIR)
+ ]
+ if HAS_SPATIAL_DB:
+ discovery_paths.append(
+ ('django.contrib.gis.tests', os.path.join(CONTRIB_DIR, 'gis', 'tests'))
+ )
+
+ for modpath, dirpath in discovery_paths:
for f in os.listdir(dirpath):
if ('.' in f or
# Python 3 byte code dirs (PEP 3147)
@@ -64,6 +74,7 @@ def get_test_modules():
modules.append((modpath, f))
return modules
+
def get_installed():
from django.db.models.loading import get_apps
return [app.__name__.rsplit('.', 1)[0] for app in get_apps()]
@@ -125,14 +136,6 @@ def setup(verbosity, test_labels):
bits = bits[:1]
test_labels_set.add('.'.join(bits))
- # If GeoDjango, then we'll want to add in the test applications
- # that are a part of its test suite.
- from django.contrib.gis.tests.utils import HAS_SPATIAL_DB
- if HAS_SPATIAL_DB:
- from django.contrib.gis.tests import geo_apps
- test_modules.extend(geo_apps())
- settings.INSTALLED_APPS.extend(['django.contrib.gis', 'django.contrib.sitemaps'])
-
for modpath, module_name in test_modules:
if modpath:
module_label = '.'.join([modpath, module_name])
diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py
index b8a8ad19bf..3204d74224 100644
--- a/tests/select_for_update/tests.py
+++ b/tests/select_for_update/tests.py
@@ -99,15 +99,8 @@ class SelectForUpdateTests(TransactionTestCase):
list(Person.objects.all().select_for_update(nowait=True))
self.assertTrue(self.has_for_update_sql(connection, nowait=True))
- # In Python 2.6 beta and some final releases, exceptions raised in __len__
- # are swallowed (Python issue 1242657), so these cases return an empty
- # list, rather than raising an exception. Not a lot we can do about that,
- # unfortunately, due to the way Python handles list() calls internally.
- # Python 2.6.1 is the "in the wild" version affected by this, so we skip
- # the test for that version.
@requires_threading
@skipUnlessDBFeature('has_select_for_update_nowait')
- @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1")
def test_nowait_raises_error_on_block(self):
"""
If nowait is specified, we expect an error to be raised rather
@@ -128,15 +121,8 @@ class SelectForUpdateTests(TransactionTestCase):
self.end_blocking_transaction()
self.assertIsInstance(status[-1], DatabaseError)
- # In Python 2.6 beta and some final releases, exceptions raised in __len__
- # are swallowed (Python issue 1242657), so these cases return an empty
- # list, rather than raising an exception. Not a lot we can do about that,
- # unfortunately, due to the way Python handles list() calls internally.
- # Python 2.6.1 is the "in the wild" version affected by this, so we skip
- # the test for that version.
@skipIfDBFeature('has_select_for_update_nowait')
@skipUnlessDBFeature('has_select_for_update')
- @unittest.skipIf(sys.version_info[:3] == (2, 6, 1), "Python version is 2.6.1")
def test_unsupported_nowait_raises_error(self):
"""
If a SELECT...FOR UPDATE NOWAIT is run on a database backend
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index bfd8d607a9..87ed4d4f5e 100644
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -203,6 +203,29 @@ class SettingsTests(TestCase):
'ALLOWED_INCLUDE_ROOTS', '/var/www/ssi/')
+class TestComplexSettingOverride(TestCase):
+ def setUp(self):
+ self.old_warn_override_settings = signals.COMPLEX_OVERRIDE_SETTINGS.copy()
+ signals.COMPLEX_OVERRIDE_SETTINGS.add('TEST_WARN')
+
+ def tearDown(self):
+ signals.COMPLEX_OVERRIDE_SETTINGS = self.old_warn_override_settings
+ self.assertFalse('TEST_WARN' in signals.COMPLEX_OVERRIDE_SETTINGS)
+
+ def test_complex_override_warning(self):
+ """Regression test for #19031"""
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+
+ override = override_settings(TEST_WARN='override')
+ override.enable()
+ self.assertEqual('override', settings.TEST_WARN)
+ override.disable()
+
+ self.assertEqual(len(w), 1)
+ self.assertEqual('Overriding setting TEST_WARN can lead to unexpected behaviour.', str(w[-1].message))
+
+
class TrailingSlashURLTests(TestCase):
"""
Tests for the MEDIA_URL and STATIC_URL settings.
diff --git a/tests/syndication/feeds.py b/tests/syndication/feeds.py
index 0956820bf0..1cd5c3d988 100644
--- a/tests/syndication/feeds.py
+++ b/tests/syndication/feeds.py
@@ -33,7 +33,10 @@ class TestRss2Feed(views.Feed):
return "Overridden description: %s" % item
def item_pubdate(self, item):
- return item.date
+ return item.published
+
+ def item_updateddate(self, item):
+ return item.updated
item_author_name = 'Sally Smith'
item_author_email = 'test@example.com'
@@ -72,6 +75,17 @@ class TestAtomFeed(TestRss2Feed):
subtitle = TestRss2Feed.description
+class TestLatestFeed(TestRss2Feed):
+ """
+ A feed where the latest entry date is an `updated` element.
+ """
+ feed_type = feedgenerator.Atom1Feed
+ subtitle = TestRss2Feed.description
+
+ def items(self):
+ return Entry.objects.exclude(pk=5)
+
+
class ArticlesFeed(TestRss2Feed):
"""
A feed to test no link being defined. Articles have no get_absolute_url()
@@ -115,7 +129,7 @@ class NaiveDatesFeed(TestAtomFeed):
A feed with naive (non-timezone-aware) dates.
"""
def item_pubdate(self, item):
- return item.date
+ return item.published
class TZAwareDatesFeed(TestAtomFeed):
@@ -126,7 +140,7 @@ class TZAwareDatesFeed(TestAtomFeed):
# Provide a weird offset so that the test can know it's getting this
# specific offset and not accidentally getting on from
# settings.TIME_ZONE.
- return item.date.replace(tzinfo=tzinfo.FixedOffset(42))
+ return item.published.replace(tzinfo=tzinfo.FixedOffset(42))
class TestFeedUrlFeed(TestAtomFeed):
diff --git a/tests/syndication/fixtures/feeddata.json b/tests/syndication/fixtures/feeddata.json
index 167115c925..52e5028fcd 100644
--- a/tests/syndication/fixtures/feeddata.json
+++ b/tests/syndication/fixtures/feeddata.json
@@ -4,7 +4,8 @@
"pk": 1,
"fields": {
"title": "My first entry",
- "date": "1850-01-01 12:30:00"
+ "updated": "1850-01-01 12:30:00",
+ "published": "1066-09-25 20:15:00"
}
},
{
@@ -12,7 +13,8 @@
"pk": 2,
"fields": {
"title": "My second entry",
- "date": "2008-01-02 12:30:00"
+ "updated": "2008-01-02 12:30:00",
+ "published": "2006-03-17 18:00:00"
}
},
{
@@ -20,7 +22,8 @@
"pk": 3,
"fields": {
"title": "My third entry",
- "date": "2008-01-02 13:30:00"
+ "updated": "2008-01-02 13:30:00",
+ "published": "2005-06-14 10:45:00"
}
},
{
@@ -28,7 +31,17 @@
"pk": 4,
"fields": {
"title": "A & B < C > D",
- "date": "2008-01-03 13:30:00"
+ "updated": "2008-01-03 13:30:00",
+ "published": "2005-11-25 12:11:23"
+ }
+ },
+ {
+ "model": "syndication.entry",
+ "pk": 5,
+ "fields": {
+ "title": "My last entry",
+ "updated": "2013-01-20 00:00:00",
+ "published": "2013-03-25 20:00:00"
}
},
{
diff --git a/tests/syndication/models.py b/tests/syndication/models.py
index 10b3fe3a0c..166acd69ca 100644
--- a/tests/syndication/models.py
+++ b/tests/syndication/models.py
@@ -5,10 +5,11 @@ from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Entry(models.Model):
title = models.CharField(max_length=200)
- date = models.DateTimeField()
+ updated = models.DateTimeField()
+ published = models.DateTimeField()
class Meta:
- ordering = ('date',)
+ ordering = ('updated',)
def __str__(self):
return self.title
@@ -24,4 +25,3 @@ class Article(models.Model):
def __str__(self):
return self.title
-
diff --git a/tests/syndication/tests.py b/tests/syndication/tests.py
index 1627f7182b..d3b0058d53 100644
--- a/tests/syndication/tests.py
+++ b/tests/syndication/tests.py
@@ -58,7 +58,7 @@ class SyndicationFeedTest(FeedTestCase):
chan = chan_elem[0]
# Find the last build date
- d = Entry.objects.latest('date').date
+ d = Entry.objects.latest('published').published
ltz = tzinfo.LocalTimezone(d)
last_build_date = rfc2822_date(d.replace(tzinfo=ltz))
@@ -88,7 +88,7 @@ class SyndicationFeedTest(FeedTestCase):
)
# Find the pubdate of the first feed item
- d = Entry.objects.get(pk=1).date
+ d = Entry.objects.get(pk=1).published
ltz = tzinfo.LocalTimezone(d)
pub_date = rfc2822_date(d.replace(tzinfo=ltz))
@@ -203,10 +203,61 @@ class SyndicationFeedTest(FeedTestCase):
entries = feed.getElementsByTagName('entry')
self.assertEqual(len(entries), Entry.objects.count())
for entry in entries:
- self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'category', 'updated', 'rights', 'author'])
+ self.assertChildNodes(entry, [
+ 'title',
+ 'link',
+ 'id',
+ 'summary',
+ 'category',
+ 'updated',
+ 'published',
+ 'rights',
+ 'author',
+ ])
summary = entry.getElementsByTagName('summary')[0]
self.assertEqual(summary.getAttribute('type'), 'html')
+ def test_atom_feed_published_and_updated_elements(self):
+ """
+ Test that the published and updated elements are not
+ the same and now adhere to RFC 4287.
+ """
+ response = self.client.get('/syndication/atom/')
+ feed = minidom.parseString(response.content).firstChild
+ entries = feed.getElementsByTagName('entry')
+
+ published = entries[0].getElementsByTagName('published')[0].firstChild.wholeText
+ updated = entries[0].getElementsByTagName('updated')[0].firstChild.wholeText
+
+ self.assertNotEqual(published, updated)
+
+ def test_latest_post_date(self):
+ """
+ Test that both the published and updated dates are
+ considered when determining the latest post date.
+ """
+ # this feed has a `published` element with the latest date
+ response = self.client.get('/syndication/atom/')
+ feed = minidom.parseString(response.content).firstChild
+ updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText
+
+ d = Entry.objects.latest('published').published
+ ltz = tzinfo.LocalTimezone(d)
+ latest_published = rfc3339_date(d.replace(tzinfo=ltz))
+
+ self.assertEqual(updated, latest_published)
+
+ # this feed has an `updated` element with the latest date
+ response = self.client.get('/syndication/latest/')
+ feed = minidom.parseString(response.content).firstChild
+ updated = feed.getElementsByTagName('updated')[0].firstChild.wholeText
+
+ d = Entry.objects.exclude(pk=5).latest('updated').updated
+ ltz = tzinfo.LocalTimezone(d)
+ latest_updated = rfc3339_date(d.replace(tzinfo=ltz))
+
+ self.assertEqual(updated, latest_updated)
+
def test_custom_feed_generator(self):
response = self.client.get('/syndication/custom/')
feed = minidom.parseString(response.content).firstChild
@@ -219,7 +270,18 @@ class SyndicationFeedTest(FeedTestCase):
self.assertEqual(len(entries), Entry.objects.count())
for entry in entries:
self.assertEqual(entry.getAttribute('bacon'), 'yum')
- self.assertChildNodes(entry, ['title', 'link', 'id', 'summary', 'ministry', 'rights', 'author', 'updated', 'category'])
+ self.assertChildNodes(entry, [
+ 'title',
+ 'link',
+ 'id',
+ 'summary',
+ 'ministry',
+ 'rights',
+ 'author',
+ 'updated',
+ 'published',
+ 'category',
+ ])
summary = entry.getElementsByTagName('summary')[0]
self.assertEqual(summary.getAttribute('type'), 'html')
@@ -245,7 +307,7 @@ class SyndicationFeedTest(FeedTestCase):
doc = minidom.parseString(response.content)
updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
- d = Entry.objects.latest('date').date
+ d = Entry.objects.latest('published').published
ltz = tzinfo.LocalTimezone(d)
latest = rfc3339_date(d.replace(tzinfo=ltz))
@@ -257,12 +319,12 @@ class SyndicationFeedTest(FeedTestCase):
"""
response = self.client.get('/syndication/aware-dates/')
doc = minidom.parseString(response.content)
- updated = doc.getElementsByTagName('updated')[0].firstChild.wholeText
- self.assertEqual(updated[-6:], '+00:42')
+ published = doc.getElementsByTagName('published')[0].firstChild.wholeText
+ self.assertEqual(published[-6:], '+00:42')
def test_feed_last_modified_time(self):
response = self.client.get('/syndication/naive-dates/')
- self.assertEqual(response['Last-Modified'], 'Thu, 03 Jan 2008 19:30:00 GMT')
+ self.assertEqual(response['Last-Modified'], 'Tue, 26 Mar 2013 01:00:00 GMT')
# No last-modified when feed has no item_pubdate
response = self.client.get('/syndication/no_pubdate/')
diff --git a/tests/syndication/urls.py b/tests/syndication/urls.py
index 1dd7e92332..06a75a4e68 100644
--- a/tests/syndication/urls.py
+++ b/tests/syndication/urls.py
@@ -15,6 +15,7 @@ urlpatterns = patterns('django.contrib.syndication.views',
(r'^syndication/rss091/$', feeds.TestRss091Feed()),
(r'^syndication/no_pubdate/$', feeds.TestNoPubdateFeed()),
(r'^syndication/atom/$', feeds.TestAtomFeed()),
+ (r'^syndication/latest/$', feeds.TestLatestFeed()),
(r'^syndication/custom/$', feeds.TestCustomFeed()),
(r'^syndication/naive-dates/$', feeds.NaiveDatesFeed()),
(r'^syndication/aware-dates/$', feeds.TZAwareDatesFeed()),
diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py
index 224b94d060..ca167a73f3 100644
--- a/tests/template_tests/test_context.py
+++ b/tests/template_tests/test_context.py
@@ -16,3 +16,12 @@ class ContextTests(TestCase):
self.assertEqual(c.pop(), {"a": 2})
self.assertEqual(c["a"], 1)
self.assertEqual(c.get("foo", 42), 42)
+
+ with c.push():
+ c['a'] = 2
+ self.assertEqual(c['a'], 2)
+ self.assertEqual(c['a'], 1)
+
+ with c.push(a=3):
+ self.assertEqual(c['a'], 3)
+ self.assertEqual(c['a'], 1)
diff --git a/tests/test_runner/tests.py b/tests/test_runner/tests.py
index 4e9e44bc12..72200e40d3 100644
--- a/tests/test_runner/tests.py
+++ b/tests/test_runner/tests.py
@@ -291,6 +291,65 @@ class DummyBackendTest(unittest.TestCase):
db.connections = old_db_connections
+class AliasedDefaultTestSetupTest(unittest.TestCase):
+ def test_setup_aliased_default_database(self):
+ """
+ Test that setup_datebases() doesn't fail when 'default' is aliased
+ """
+ runner_instance = runner.DiscoverRunner(verbosity=0)
+ old_db_connections = db.connections
+ try:
+ db.connections = db.ConnectionHandler({
+ 'default': {
+ 'NAME': 'dummy'
+ },
+ 'aliased': {
+ 'NAME': 'dummy'
+ }
+ })
+ old_config = runner_instance.setup_databases()
+ runner_instance.teardown_databases(old_config)
+ except Exception as e:
+ self.fail("setup_databases/teardown_databases unexpectedly raised "
+ "an error: %s" % e)
+ finally:
+ db.connections = old_db_connections
+
+
+class AliasedDatabaseTeardownTest(unittest.TestCase):
+ def test_setup_aliased_databases(self):
+ from django.db.backends.dummy.base import DatabaseCreation
+
+ runner_instance = runner.DiscoverRunner(verbosity=0)
+ old_db_connections = db.connections
+ old_destroy_test_db = DatabaseCreation.destroy_test_db
+ old_create_test_db = DatabaseCreation.create_test_db
+ try:
+ destroyed_names = []
+ DatabaseCreation.destroy_test_db = lambda self, old_database_name, verbosity=1: destroyed_names.append(old_database_name)
+ DatabaseCreation.create_test_db = lambda self, verbosity=1, autoclobber=False: self._get_test_db_name()
+
+ db.connections = db.ConnectionHandler({
+ 'default': {
+ 'ENGINE': 'django.db.backends.dummy',
+ 'NAME': 'dbname',
+ },
+ 'other': {
+ 'ENGINE': 'django.db.backends.dummy',
+ 'NAME': 'dbname',
+ }
+ })
+
+ old_config = runner_instance.setup_databases()
+ runner_instance.teardown_databases(old_config)
+
+ self.assertEqual(destroyed_names.count('dbname'), 1)
+ finally:
+ DatabaseCreation.create_test_db = old_create_test_db
+ DatabaseCreation.destroy_test_db = old_destroy_test_db
+ db.connections = old_db_connections
+
+
class DeprecationDisplayTest(AdminScriptTestCase):
# tests for 19546
def setUp(self):
@@ -308,8 +367,6 @@ class DeprecationDisplayTest(AdminScriptTestCase):
self.assertIn("DeprecationWarning: warning from test", err)
self.assertIn("DeprecationWarning: module-level warning from deprecation_app", err)
- @unittest.skipIf(sys.version_info[:2] == (2, 6),
- "On Python 2.6, DeprecationWarnings are visible anyway")
def test_runner_deprecation_verbosity_zero(self):
args = ['test', '--settings=settings', '--verbosity=0']
out, err = self.run_django_admin(args)
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index 2c2bc24d72..24aa433dd3 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -2,13 +2,12 @@
from __future__ import absolute_import, unicode_literals
import unittest
-from unittest import skip
from django.db import connection
from django.forms import EmailField, IntegerField
from django.http import HttpResponse
from django.template.loader import render_to_string
-from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
+from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
from django.test.html import HTMLParseError, parse_html
from django.test.utils import CaptureQueriesContext, IgnoreAllDeprecationWarningsMixin
from django.utils import six
@@ -27,6 +26,29 @@ class SkippingTestCase(TestCase):
self.assertRaises(ValueError, test_func)
+class SkippingClassTestCase(TestCase):
+ def test_skip_class_unless_db_feature(self):
+ @skipUnlessDBFeature("__class__")
+ class NotSkippedTests(unittest.TestCase):
+ def test_dummy(self):
+ return
+
+ @skipIfDBFeature("__class__")
+ class SkippedTests(unittest.TestCase):
+ def test_will_be_skipped(self):
+ self.fail("We should never arrive here.")
+
+ test_suite = unittest.TestSuite()
+ test_suite.addTest(NotSkippedTests('test_dummy'))
+ try:
+ test_suite.addTest(SkippedTests('test_will_be_skipped'))
+ except unittest.SkipTest:
+ self.fail("SkipTest should not be raised at this stage")
+ result = unittest.TextTestRunner(stream=six.StringIO()).run(test_suite)
+ self.assertEqual(result.testsRun, 2)
+ self.assertEqual(len(result.skipped), 1)
+
+
class AssertNumQueriesTests(TestCase):
urls = 'test_utils.urls'
@@ -561,7 +583,7 @@ class SkippingExtraTests(TestCase):
with self.assertNumQueries(0):
super(SkippingExtraTests, self).__call__(result)
- @skip("Fixture loading should not be performed for skipped tests.")
+ @unittest.skip("Fixture loading should not be performed for skipped tests.")
def test_fixtures_are_skipped(self):
pass
diff --git a/tests/transactions_regress/tests.py b/tests/transactions_regress/tests.py
index bd9e78b033..01f8fc4186 100644
--- a/tests/transactions_regress/tests.py
+++ b/tests/transactions_regress/tests.py
@@ -191,10 +191,9 @@ class TestTransactionClosing(IgnoreDeprecationWarningsMixin, TransactionTestCase
"""
self.test_failing_query_transaction_closed()
-@skipIf(connection.vendor == 'sqlite' and
- (connection.settings_dict['NAME'] == ':memory:' or
- not connection.settings_dict['NAME']),
- 'Test uses multiple connections, but in-memory sqlite does not support this')
+@skipIf(connection.vendor == 'sqlite'
+ and connection.settings_dict['TEST_NAME'] in (None, '', ':memory:'),
+ "Cannot establish two connections to an in-memory SQLite database.")
class TestNewConnection(IgnoreDeprecationWarningsMixin, TransactionTestCase):
"""
Check that new connections don't have special behaviour.
diff --git a/tests/validators/tests.py b/tests/validators/tests.py
index ec3e2dc8c1..9be49d7726 100644
--- a/tests/validators/tests.py
+++ b/tests/validators/tests.py
@@ -46,6 +46,7 @@ TEST_DATA = (
(validate_email, 'example@-invalid.com', ValidationError),
(validate_email, 'example@inv-.alid-.com', ValidationError),
(validate_email, 'example@inv-.-alid.com', ValidationError),
+ (validate_email, 'test@example.com\n\n<script src="x.js">', ValidationError),
# Quoted-string format (CR not allowed)
(validate_email, '"\\\011"@here.com', None),
(validate_email, '"\\\012"@here.com', ValidationError),