summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndrew Godwin <andrew@aeracode.org>2013-08-19 18:30:48 +0100
committerAndrew Godwin <andrew@aeracode.org>2013-08-19 18:30:48 +0100
commitb6a957f0ba8a2ed1b24d7ee042a9c4beaf51ab03 (patch)
tree87d42b9e8d3d4c1516b53eee1d9332735762826a
parent52edc16086e3c28a78c31975bb4da2f9450590b4 (diff)
parent3c0300405009b82b52fd15483371097221662fcd (diff)
Merge remote-tracking branch 'core/master' into schema-alteration
Conflicts: docs/ref/django-admin.txt
-rw-r--r--AUTHORS5
-rw-r--r--django/conf/global_settings.py5
-rw-r--r--django/contrib/admin/static/admin/css/dashboard.css4
-rw-r--r--django/contrib/admin/util.py26
-rw-r--r--django/contrib/admin/widgets.py4
-rw-r--r--django/contrib/auth/decorators.py6
-rw-r--r--django/contrib/auth/models.py4
-rw-r--r--django/contrib/auth/tests/test_decorators.py58
-rw-r--r--django/contrib/auth/tests/test_forms.py21
-rw-r--r--django/contrib/auth/tests/test_management.py11
-rw-r--r--django/contrib/auth/tests/test_models.py26
-rw-r--r--django/contrib/auth/tests/test_views.py8
-rw-r--r--django/contrib/gis/db/backends/postgis/introspection.py8
-rw-r--r--django/contrib/gis/db/models/sql/query.py4
-rw-r--r--django/contrib/gis/tests/geoapp/models.py2
-rw-r--r--django/contrib/humanize/tests.py29
-rw-r--r--django/core/checks/compatibility/django_1_6_0.py29
-rw-r--r--django/core/files/storage.py11
-rw-r--r--django/db/backends/postgresql_psycopg2/introspection.py4
-rw-r--r--django/db/models/base.py25
-rw-r--r--django/db/models/query.py5
-rw-r--r--django/db/models/sql/compiler.py5
-rw-r--r--django/db/models/sql/query.py160
-rw-r--r--django/forms/forms.py4
-rw-r--r--django/forms/widgets.py15
-rw-r--r--django/template/base.py5
-rw-r--r--django/template/defaulttags.py35
-rw-r--r--django/test/client.py68
-rw-r--r--django/utils/functional.py15
-rw-r--r--django/utils/http.py10
-rw-r--r--django/views/debug.py6
-rw-r--r--docs/howto/custom-model-fields.txt4
-rw-r--r--docs/howto/deployment/index.txt3
-rw-r--r--docs/howto/deployment/wsgi/apache-auth.txt2
-rw-r--r--docs/howto/deployment/wsgi/modwsgi.txt2
-rw-r--r--docs/internals/contributing/writing-code/working-with-git.txt2
-rw-r--r--docs/internals/deprecation.txt13
-rw-r--r--docs/internals/howto-release-django.txt9
-rw-r--r--docs/internals/security.txt6
-rw-r--r--docs/intro/tutorial01.txt2
-rw-r--r--docs/intro/tutorial02.txt9
-rw-r--r--docs/ref/class-based-views/base.txt6
-rw-r--r--docs/ref/class-based-views/generic-date-based.txt8
-rw-r--r--docs/ref/class-based-views/mixins-date-based.txt5
-rw-r--r--docs/ref/class-based-views/mixins-multiple-object.txt2
-rw-r--r--docs/ref/class-based-views/mixins-simple.txt4
-rw-r--r--docs/ref/contrib/admin/actions.txt4
-rw-r--r--docs/ref/contrib/admin/admindocs.txt2
-rw-r--r--docs/ref/contrib/admin/index.txt8
-rw-r--r--docs/ref/contrib/auth.txt9
-rw-r--r--docs/ref/contrib/contenttypes.txt23
-rw-r--r--docs/ref/contrib/gis/geoquerysets.txt4
-rw-r--r--docs/ref/contrib/gis/geos.txt12
-rw-r--r--docs/ref/contrib/messages.txt11
-rw-r--r--docs/ref/contrib/sites.txt7
-rw-r--r--docs/ref/contrib/staticfiles.txt4
-rw-r--r--docs/ref/databases.txt4
-rw-r--r--docs/ref/django-admin.txt10
-rw-r--r--docs/ref/files/file.txt4
-rw-r--r--docs/ref/forms/api.txt5
-rw-r--r--docs/ref/forms/fields.txt9
-rw-r--r--docs/ref/forms/validation.txt10
-rw-r--r--docs/ref/forms/widgets.txt70
-rw-r--r--docs/ref/models/fields.txt11
-rw-r--r--docs/ref/models/instances.txt36
-rw-r--r--docs/ref/models/options.txt2
-rw-r--r--docs/ref/models/querysets.txt40
-rw-r--r--docs/ref/request-response.txt8
-rw-r--r--docs/ref/settings.txt43
-rw-r--r--docs/ref/signals.txt13
-rw-r--r--docs/ref/template-response.txt23
-rw-r--r--docs/ref/templates/api.txt5
-rw-r--r--docs/ref/templates/builtins.txt22
-rw-r--r--docs/ref/unicode.txt30
-rw-r--r--docs/ref/utils.txt14
-rw-r--r--docs/releases/1.3.3.txt11
-rw-r--r--docs/releases/1.3.4.txt37
-rw-r--r--docs/releases/1.3.5.txt60
-rw-r--r--docs/releases/1.3.6.txt78
-rw-r--r--docs/releases/1.3.7.txt13
-rw-r--r--docs/releases/1.4.2.txt9
-rw-r--r--docs/releases/1.4.3.txt60
-rw-r--r--docs/releases/1.4.4.txt88
-rw-r--r--docs/releases/1.4.5.txt13
-rw-r--r--docs/releases/1.4.6.txt31
-rw-r--r--docs/releases/1.5.2.txt62
-rw-r--r--docs/releases/1.5.txt2
-rw-r--r--docs/releases/1.7.txt31
-rw-r--r--docs/releases/index.txt10
-rw-r--r--docs/topics/auth/customizing.txt6
-rw-r--r--docs/topics/auth/default.txt19
-rw-r--r--docs/topics/class-based-views/intro.txt1
-rw-r--r--docs/topics/class-based-views/mixins.txt2
-rw-r--r--docs/topics/db/managers.txt4
-rw-r--r--docs/topics/db/multi-db.txt5
-rw-r--r--docs/topics/db/queries.txt11
-rw-r--r--docs/topics/http/file-uploads.txt7
-rw-r--r--docs/topics/http/middleware.txt5
-rw-r--r--docs/topics/http/sessions.txt10
-rw-r--r--docs/topics/http/shortcuts.txt14
-rw-r--r--docs/topics/i18n/translation.txt2
-rw-r--r--docs/topics/logging.txt7
-rw-r--r--docs/topics/pagination.txt14
-rw-r--r--docs/topics/serialization.txt11
-rw-r--r--docs/topics/signals.txt4
-rw-r--r--docs/topics/testing/advanced.txt2
-rw-r--r--docs/topics/testing/overview.txt54
-rw-r--r--tests/admin_views/admin.py10
-rw-r--r--tests/admin_views/models.py14
-rw-r--r--tests/admin_views/tests.py44
-rw-r--r--tests/admin_widgets/tests.py72
-rw-r--r--tests/aggregation/tests.py25
-rw-r--r--tests/aggregation_regress/models.py2
-rw-r--r--tests/basic/tests.py4
-rw-r--r--tests/check/models.py10
-rw-r--r--tests/check/tests.py20
-rw-r--r--tests/comment_tests/models.py2
-rw-r--r--tests/custom_managers/models.py4
-rw-r--r--tests/defaultfilters/tests.py37
-rw-r--r--tests/defer/tests.py6
-rw-r--r--tests/file_storage/tests.py12
-rw-r--r--tests/foreign_object/tests.py19
-rw-r--r--tests/forms_tests/tests/test_fields.py18
-rw-r--r--tests/forms_tests/tests/test_widgets.py16
-rw-r--r--tests/generic_relations/models.py2
-rw-r--r--tests/i18n/tests.py35
-rw-r--r--tests/inspectdb/models.py2
-rw-r--r--tests/logging_tests/tests.py19
-rw-r--r--tests/model_fields/models.py4
-rw-r--r--tests/model_fields/tests.py23
-rw-r--r--tests/model_formsets/models.py6
-rw-r--r--tests/model_inheritance/models.py8
-rw-r--r--tests/model_inheritance/tests.py91
-rw-r--r--tests/model_inheritance_regress/models.py21
-rw-r--r--tests/model_inheritance_regress/tests.py16
-rw-r--r--tests/model_inheritance_select_related/models.py4
-rw-r--r--tests/modeladmin/models.py2
-rw-r--r--tests/one_to_one/models.py4
-rw-r--r--tests/one_to_one_regress/models.py4
-rw-r--r--tests/one_to_one_regress/tests.py35
-rw-r--r--tests/proxy_models/admin.py6
-rw-r--r--tests/proxy_models/fixtures/myhorses.json20
-rw-r--r--tests/proxy_models/models.py6
-rw-r--r--tests/proxy_models/tests.py30
-rw-r--r--tests/proxy_models/urls.py7
-rw-r--r--tests/queries/tests.py26
-rw-r--r--tests/raw_query/models.py2
-rw-r--r--tests/reverse_single_related/models.py2
-rwxr-xr-xtests/runtests.py1
-rw-r--r--tests/serializers_regress/models.py4
-rw-r--r--tests/servers/another_app/__init__.py0
-rw-r--r--tests/servers/another_app/models.py0
-rw-r--r--tests/servers/another_app/static/another_app/another_app_static_file.txt1
-rw-r--r--tests/servers/tests.py8
-rw-r--r--tests/template_tests/test_context.py11
-rw-r--r--tests/template_tests/tests.py7
-rw-r--r--tests/test_client_regress/tests.py8
-rw-r--r--tests/test_client_regress/urls.py1
-rw-r--r--tests/test_client_regress/views.py13
-rw-r--r--tests/test_utils/tests.py71
-rw-r--r--tests/transactions/tests.py179
-rw-r--r--tests/utils_tests/test_simplelazyobject.py13
-rw-r--r--tests/view_tests/tests/test_debug.py16
-rw-r--r--tests/view_tests/tests/test_i18n.py64
-rw-r--r--tests/view_tests/tests/test_static.py8
-rw-r--r--tests/view_tests/urls.py1
-rw-r--r--tests/view_tests/views.py8
167 files changed, 1861 insertions, 1064 deletions
diff --git a/AUTHORS b/AUTHORS
index 15f3e8dbf4..059310d5c6 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -317,6 +317,7 @@ answer newbie questions, and generally made Django that much better:
Michael Josephson <http://www.sdjournal.com/>
jpellerin@gmail.com
junzhang.jn@gmail.com
+ Krzysztof Jurewicz <krzysztof.jurewicz@gmail.com>
Xia Kai <http://blog.xiaket.org/>
Antti Kaihola <http://djangopeople.net/akaihola/>
Peter van Kampen
@@ -418,6 +419,7 @@ answer newbie questions, and generally made Django that much better:
Christian Metts
michal@plovarna.cz
Justin Michalicek <jmichalicek@gmail.com>
+ Bojan Mihelac <bmihelac@mihelac.org>
Slawek Mikula <slawek dot mikula at gmail dot com>
Katie Miller <katie@sub50.com>
Shawn Milochik <shawn@milochik.com>
@@ -440,6 +442,7 @@ answer newbie questions, and generally made Django that much better:
Gopal Narayanan <gopastro@gmail.com>
Fraser Nevett <mail@nevett.org>
Sam Newman <http://www.magpiebrain.com/>
+ Alasdair Nicol <http://al.sdair.co.uk/>
Ryan Niemeyer <https://profiles.google.com/ryan.niemeyer/about>
Filip Noetzel <http://filip.noetzel.co.uk/>
Afonso Fernández Nogueira <fonzzo.django@gmail.com>
@@ -537,6 +540,7 @@ answer newbie questions, and generally made Django that much better:
Brenton Simpson <http://theillustratedlife.com>
Jozko Skrablin <jozko.skrablin@gmail.com>
Ben Slavin <benjamin.slavin@gmail.com>
+ Jonathan Slenders
sloonz <simon.lipp@insa-lyon.fr>
Paul Smith <blinkylights23@gmail.com>
Steven L. Smith (fvox13) <steven@stevenlsmith.com>
@@ -573,6 +577,7 @@ answer newbie questions, and generally made Django that much better:
Aaron Swartz <http://www.aaronsw.com/>
Ville Säävuori <http://www.unessa.net/>
Mart Sõmermaa <http://mrts.pri.ee/>
+ Susan Tan <susan.tan.fleckerl@gmail.com>
Christian Tanzer <tanzer@swing.co.at>
Tyler Tarabula <tyler.tarabula@gmail.com>
Tyson Tate <tyson@fallingbullets.com>
diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
index 364aa10320..4e8430aceb 100644
--- a/django/conf/global_settings.py
+++ b/django/conf/global_settings.py
@@ -313,6 +313,11 @@ FILE_UPLOAD_TEMP_DIR = None
# you'd pass directly to os.chmod; see http://docs.python.org/lib/os-file-dir.html.
FILE_UPLOAD_PERMISSIONS = None
+# The numeric mode to assign to newly-created directories, when uploading files.
+# The value should be a mode as you'd pass to os.chmod;
+# see http://docs.python.org/lib/os-file-dir.html.
+FILE_UPLOAD_DIRECTORY_PERMISSIONS = None
+
# Python module path where user will place custom format definition.
# The directory where this setting is pointing should contain subdirectories
# named as the locales, containing a formats.py file
diff --git a/django/contrib/admin/static/admin/css/dashboard.css b/django/contrib/admin/static/admin/css/dashboard.css
index ceefe1525f..05808bcb0a 100644
--- a/django/contrib/admin/static/admin/css/dashboard.css
+++ b/django/contrib/admin/static/admin/css/dashboard.css
@@ -23,8 +23,8 @@ ul.actionlist li {
list-style-type: none;
}
-ul.actionlist li.changelink {
+ul.actionlist li {
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-} \ No newline at end of file
+}
diff --git a/django/contrib/admin/util.py b/django/contrib/admin/util.py
index dd9047c428..a36c8c13c8 100644
--- a/django/contrib/admin/util.py
+++ b/django/contrib/admin/util.py
@@ -16,7 +16,7 @@ from django.utils import timezone
from django.utils.encoding import force_str, force_text, smart_text
from django.utils import six
from django.utils.translation import ungettext
-from django.core.urlresolvers import reverse
+from django.core.urlresolvers import reverse, NoReverseMatch
def lookup_needs_distinct(opts, lookup_path):
"""
@@ -113,12 +113,20 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
has_admin = obj.__class__ in admin_site._registry
opts = obj._meta
+ no_edit_link = '%s: %s' % (capfirst(opts.verbose_name),
+ force_text(obj))
+
if has_admin:
- admin_url = reverse('%s:%s_%s_change'
- % (admin_site.name,
- opts.app_label,
- opts.model_name),
- None, (quote(obj._get_pk_val()),))
+ try:
+ admin_url = reverse('%s:%s_%s_change'
+ % (admin_site.name,
+ opts.app_label,
+ opts.model_name),
+ None, (quote(obj._get_pk_val()),))
+ except NoReverseMatch:
+ # Change url doesn't exist -- don't display link to edit
+ return no_edit_link
+
p = '%s.%s' % (opts.app_label,
get_permission_codename('delete', opts))
if not user.has_perm(p):
@@ -131,8 +139,7 @@ def get_deleted_objects(objs, opts, user, admin_site, using):
else:
# Don't display link to edit, because it either has no
# admin or is edited inline.
- return '%s: %s' % (capfirst(opts.verbose_name),
- force_text(obj))
+ return no_edit_link
to_delete = collector.nested(format_callback)
@@ -155,9 +162,6 @@ class NestedObjects(Collector):
if source_attr:
self.add_edge(getattr(obj, source_attr), obj)
else:
- if obj._meta.proxy:
- # Take concrete model's instance to avoid mismatch in edges
- obj = obj._meta.concrete_model(pk=obj.pk)
self.add_edge(None, obj)
try:
return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs)
diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py
index c4b15cdd6a..5773db6394 100644
--- a/django/contrib/admin/widgets.py
+++ b/django/contrib/admin/widgets.py
@@ -305,9 +305,9 @@ class AdminURLFieldWidget(forms.URLInput):
html = super(AdminURLFieldWidget, self).render(name, value, attrs)
if value:
value = force_text(self._format_value(value))
- final_attrs = {'href': mark_safe(smart_urlquote(value))}
+ final_attrs = {'href': smart_urlquote(value)}
html = format_html(
- '<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>',
+ '<p class="url">{0} <a{1}>{2}</a><br />{3} {4}</p>',
_('Currently:'), flatatt(final_attrs), value,
_('Change:'), html
)
diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py
index 11518193e7..24e31144b1 100644
--- a/django/contrib/auth/decorators.py
+++ b/django/contrib/auth/decorators.py
@@ -64,8 +64,12 @@ def permission_required(perm, login_url=None, raise_exception=False):
is raised.
"""
def check_perms(user):
+ if not isinstance(perm, (list, tuple)):
+ perms = (perm, )
+ else:
+ perms = perm
# First check if the user has the permission (even anon users)
- if user.has_perm(perm):
+ if user.has_perms(perms):
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
index cf3d37e5c3..cf1ca8ca1f 100644
--- a/django/contrib/auth/models.py
+++ b/django/contrib/auth/models.py
@@ -400,11 +400,11 @@ class AbstractUser(AbstractBaseUser, PermissionsMixin):
"Returns the short name for the user."
return self.first_name
- def email_user(self, subject, message, from_email=None):
+ def email_user(self, subject, message, from_email=None, **kwargs):
"""
Sends an email to this User.
"""
- send_mail(subject, message, from_email, [self.email])
+ send_mail(subject, message, from_email, [self.email], **kwargs)
class User(AbstractUser):
diff --git a/django/contrib/auth/tests/test_decorators.py b/django/contrib/auth/tests/test_decorators.py
index 6d6d335354..22ad933644 100644
--- a/django/contrib/auth/tests/test_decorators.py
+++ b/django/contrib/auth/tests/test_decorators.py
@@ -1,7 +1,12 @@
from django.conf import settings
-from django.contrib.auth.decorators import login_required
+from django.contrib.auth import models
+from django.contrib.auth.decorators import login_required, permission_required
from django.contrib.auth.tests.test_views import AuthViewsTestCase
from django.contrib.auth.tests.utils import skipIfCustomUser
+from django.core.exceptions import PermissionDenied
+from django.http import HttpResponse
+from django.test import TestCase
+from django.test.client import RequestFactory
@skipIfCustomUser
@@ -49,3 +54,54 @@ class LoginRequiredTestCase(AuthViewsTestCase):
"""
self.testLoginRequired(view_url='/login_required_login_url/',
login_url='/somewhere/')
+
+
+class PermissionsRequiredDecoratorTest(TestCase):
+ """
+ Tests for the permission_required decorator
+ """
+ def setUp(self):
+ self.user = models.User.objects.create(username='joe', password='qwerty')
+ self.factory = RequestFactory()
+ # Add permissions auth.add_customuser and auth.change_customuser
+ perms = models.Permission.objects.filter(codename__in=('add_customuser', 'change_customuser'))
+ self.user.user_permissions.add(*perms)
+
+ def test_many_permissions_pass(self):
+
+ @permission_required(['auth.add_customuser', 'auth.change_customuser'])
+ def a_view(request):
+ return HttpResponse()
+ request = self.factory.get('/rand')
+ request.user = self.user
+ resp = a_view(request)
+ self.assertEqual(resp.status_code, 200)
+
+ def test_single_permission_pass(self):
+
+ @permission_required('auth.add_customuser')
+ def a_view(request):
+ return HttpResponse()
+ request = self.factory.get('/rand')
+ request.user = self.user
+ resp = a_view(request)
+ self.assertEqual(resp.status_code, 200)
+
+ def test_permissioned_denied_redirect(self):
+
+ @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'])
+ def a_view(request):
+ return HttpResponse()
+ request = self.factory.get('/rand')
+ request.user = self.user
+ resp = a_view(request)
+ self.assertEqual(resp.status_code, 302)
+
+ def test_permissioned_denied_exception_raised(self):
+
+ @permission_required(['auth.add_customuser', 'auth.change_customuser', 'non-existant-permission'], raise_exception=True)
+ def a_view(request):
+ return HttpResponse()
+ request = self.factory.get('/rand')
+ request.user = self.user
+ self.assertRaises(PermissionDenied, a_view, request)
diff --git a/django/contrib/auth/tests/test_forms.py b/django/contrib/auth/tests/test_forms.py
index eef366f184..fa21d2f917 100644
--- a/django/contrib/auth/tests/test_forms.py
+++ b/django/contrib/auth/tests/test_forms.py
@@ -121,17 +121,16 @@ class AuthenticationFormTest(TestCase):
[force_text(form.error_messages['inactive'])])
def test_inactive_user_i18n(self):
- with self.settings(USE_I18N=True):
- with translation.override('pt-br', deactivate=True):
- # The user is inactive.
- data = {
- 'username': 'inactive',
- 'password': 'password',
- }
- form = AuthenticationForm(None, data)
- self.assertFalse(form.is_valid())
- self.assertEqual(form.non_field_errors(),
- [force_text(form.error_messages['inactive'])])
+ with self.settings(USE_I18N=True), translation.override('pt-br', deactivate=True):
+ # The user is inactive.
+ data = {
+ 'username': 'inactive',
+ 'password': 'password',
+ }
+ form = AuthenticationForm(None, data)
+ self.assertFalse(form.is_valid())
+ self.assertEqual(form.non_field_errors(),
+ [force_text(form.error_messages['inactive'])])
def test_custom_login_allowed_policy(self):
# The user is inactive, but our custom form policy allows him to log in.
diff --git a/django/contrib/auth/tests/test_management.py b/django/contrib/auth/tests/test_management.py
index 3711f52dea..91a6a589c1 100644
--- a/django/contrib/auth/tests/test_management.py
+++ b/django/contrib/auth/tests/test_management.py
@@ -239,21 +239,22 @@ class PermissionTestCase(TestCase):
create_permissions(models, [], verbosity=0)
def test_default_permissions(self):
+ permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
models.Permission._meta.permissions = [
('my_custom_permission', 'Some permission'),
]
create_permissions(models, [], verbosity=0)
# add/change/delete permission by default + custom permission
- self.assertEqual(models.Permission.objects.filter(content_type=
- ContentType.objects.get_by_natural_key('auth', 'permission')
+ self.assertEqual(models.Permission.objects.filter(
+ content_type=permission_content_type,
).count(), 4)
- models.Permission.objects.all().delete()
+ models.Permission.objects.filter(content_type=permission_content_type).delete()
models.Permission._meta.default_permissions = []
create_permissions(models, [], verbosity=0)
# custom permission only since default permissions is empty
- self.assertEqual(models.Permission.objects.filter(content_type=
- ContentType.objects.get_by_natural_key('auth', 'permission')
+ self.assertEqual(models.Permission.objects.filter(
+ content_type=permission_content_type,
).count(), 1)
diff --git a/django/contrib/auth/tests/test_models.py b/django/contrib/auth/tests/test_models.py
index fa20775a8d..1373a3c1c1 100644
--- a/django/contrib/auth/tests/test_models.py
+++ b/django/contrib/auth/tests/test_models.py
@@ -1,6 +1,7 @@
from django.contrib.auth import get_user_model
-from django.contrib.auth.models import Group, User, UserManager
+from django.contrib.auth.models import AbstractUser, Group, User, UserManager
from django.contrib.auth.tests.utils import skipIfCustomUser
+from django.core import mail
from django.db.models.signals import post_save
from django.test import TestCase
from django.test.utils import override_settings
@@ -73,6 +74,29 @@ class UserManagerTestCase(TestCase):
User.objects.create_user, username='')
+class AbstractUserTestCase(TestCase):
+ def test_email_user(self):
+ # valid send_mail parameters
+ kwargs = {
+ "fail_silently": False,
+ "auth_user": None,
+ "auth_password": None,
+ "connection": None,
+ "html_message": None,
+ }
+ abstract_user = AbstractUser(email='foo@bar.com')
+ abstract_user.email_user(subject="Subject here",
+ message="This is a message", from_email="from@domain.com", **kwargs)
+ # Test that one message has been sent.
+ self.assertEqual(len(mail.outbox), 1)
+ # Verify that test email contains the correct attributes:
+ message = mail.outbox[0]
+ self.assertEqual(message.subject, "Subject here")
+ self.assertEqual(message.body, "This is a message")
+ self.assertEqual(message.from_email, "from@domain.com")
+ self.assertEqual(message.to, [abstract_user.email])
+
+
class IsActiveTestCase(TestCase):
"""
Tests the behavior of the guaranteed is_active attribute
diff --git a/django/contrib/auth/tests/test_views.py b/django/contrib/auth/tests/test_views.py
index 22ccbfd225..7839b0b9f9 100644
--- a/django/contrib/auth/tests/test_views.py
+++ b/django/contrib/auth/tests/test_views.py
@@ -446,7 +446,8 @@ class LoginTest(AuthViewsTestCase):
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
- '//example.com'):
+ '//example.com',
+ 'javascript:alert("XSS")'):
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': login_url,
@@ -467,6 +468,7 @@ class LoginTest(AuthViewsTestCase):
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
+ 'HTTPS:///',
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
@@ -661,7 +663,8 @@ class LogoutTest(AuthViewsTestCase):
for bad_url in ('http://example.com',
'https://example.com',
'ftp://exampel.com',
- '//example.com'):
+ '//example.com',
+ 'javascript:alert("XSS")'):
nasty_url = '%(url)s?%(next)s=%(bad_url)s' % {
'url': logout_url,
'next': REDIRECT_FIELD_NAME,
@@ -680,6 +683,7 @@ class LogoutTest(AuthViewsTestCase):
'/view?param=ftp://exampel.com',
'view/?param=//example.com',
'https:///',
+ 'HTTPS:///',
'//testserver/',
'/url%20with%20spaces/'): # see ticket #12534
safe_url = '%(url)s?%(next)s=%(good_url)s' % {
diff --git a/django/contrib/gis/db/backends/postgis/introspection.py b/django/contrib/gis/db/backends/postgis/introspection.py
index 7962d19ff9..7df09d0937 100644
--- a/django/contrib/gis/db/backends/postgis/introspection.py
+++ b/django/contrib/gis/db/backends/postgis/introspection.py
@@ -9,6 +9,14 @@ class PostGISIntrospection(DatabaseIntrospection):
# introspection is actually performed.
postgis_types_reverse = {}
+ ignored_tables = DatabaseIntrospection.ignored_tables + [
+ 'geography_columns',
+ 'geometry_columns',
+ 'raster_columns',
+ 'spatial_ref_sys',
+ 'raster_overviews',
+ ]
+
def get_postgis_types(self):
"""
Returns a dictionary with keys that are the PostgreSQL object
diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py
index 5877f2975a..93a7642bbb 100644
--- a/django/contrib/gis/db/models/sql/query.py
+++ b/django/contrib/gis/db/models/sql/query.py
@@ -76,7 +76,7 @@ class GeoQuery(sql.Query):
return super(GeoQuery, self).convert_values(value, field, connection)
return value
- def get_aggregation(self, using):
+ def get_aggregation(self, using, force_subq=False):
# Remove any aggregates marked for reduction from the subquery
# and move them to the outer AggregateQuery.
connection = connections[using]
@@ -84,7 +84,7 @@ class GeoQuery(sql.Query):
if isinstance(aggregate, gis_aggregates.GeoAggregate):
if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
self.extra_select_fields[alias] = GeomField()
- return super(GeoQuery, self).get_aggregation(using)
+ return super(GeoQuery, self).get_aggregation(using, force_subq)
def resolve_aggregate(self, value, aggregate, connection):
"""
diff --git a/django/contrib/gis/tests/geoapp/models.py b/django/contrib/gis/tests/geoapp/models.py
index abde509c8b..fa83859063 100644
--- a/django/contrib/gis/tests/geoapp/models.py
+++ b/django/contrib/gis/tests/geoapp/models.py
@@ -40,7 +40,7 @@ class Track(models.Model):
def __str__(self): return self.name
class Truth(models.Model):
- val = models.BooleanField()
+ val = models.BooleanField(default=False)
objects = models.GeoManager()
if not spatialite:
diff --git a/django/contrib/humanize/tests.py b/django/contrib/humanize/tests.py
index f1fcbf2bb3..02f73afadb 100644
--- a/django/contrib/humanize/tests.py
+++ b/django/contrib/humanize/tests.py
@@ -77,15 +77,14 @@ class HumanizeTests(TransRealMixin, TestCase):
'100', '1,000', '10,123', '10,311', '1,000,000', '1,234,567.1234567', '1,234,567.1234567',
None)
- with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False):
- with translation.override('en'):
- self.humanize_tester(test_list, result_list, 'intcomma')
+ with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=False), \
+ translation.override('en'):
+ self.humanize_tester(test_list, result_list, 'intcomma')
def test_intcomma_without_number_grouping(self):
# Regression for #17414
- with translation.override('ja'):
- with self.settings(USE_L10N=True):
- self.humanize_tester([100], ['100'], 'intcomma')
+ with translation.override('ja'), self.settings(USE_L10N=True):
+ self.humanize_tester([100], ['100'], 'intcomma')
def test_intword(self):
test_list = ('100', '1000000', '1200000', '1290000',
@@ -104,18 +103,18 @@ class HumanizeTests(TransRealMixin, TestCase):
'100', '1000', '10123', '10311', '1000000', None)
result_list = ('100', '1.000', '10.123', '10.311', '1.000.000', '1.234.567,25',
'100', '1.000', '10.123', '10.311', '1.000.000', None)
- with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
- with translation.override('de'):
- self.humanize_tester(test_list, result_list, 'intcomma')
+ with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
+ translation.override('de'):
+ self.humanize_tester(test_list, result_list, 'intcomma')
def test_i18n_intword(self):
test_list = ('100', '1000000', '1200000', '1290000',
'1000000000', '2000000000', '6000000000000')
result_list = ('100', '1,0 Million', '1,2 Millionen', '1,3 Millionen',
'1,0 Milliarde', '2,0 Milliarden', '6,0 Billionen')
- with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True):
- with translation.override('de'):
- self.humanize_tester(test_list, result_list, 'intword')
+ with self.settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True), \
+ translation.override('de'):
+ self.humanize_tester(test_list, result_list, 'intword')
def test_apnumber(self):
test_list = [str(x) for x in range(1, 11)]
@@ -162,9 +161,9 @@ class HumanizeTests(TransRealMixin, TestCase):
orig_humanize_datetime, humanize.datetime = humanize.datetime, MockDateTime
try:
- with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True):
- with translation.override('en'):
- self.humanize_tester([dt], ['yesterday'], 'naturalday')
+ with override_settings(TIME_ZONE="America/Chicago", USE_TZ=True), \
+ translation.override('en'):
+ self.humanize_tester([dt], ['yesterday'], 'naturalday')
finally:
humanize.datetime = orig_humanize_datetime
diff --git a/django/core/checks/compatibility/django_1_6_0.py b/django/core/checks/compatibility/django_1_6_0.py
index 1998c5ba77..e38b2d32ec 100644
--- a/django/core/checks/compatibility/django_1_6_0.py
+++ b/django/core/checks/compatibility/django_1_6_0.py
@@ -1,5 +1,6 @@
from __future__ import unicode_literals
+from django.db import models
def check_test_runner():
"""
@@ -24,6 +25,31 @@ def check_test_runner():
]
return ' '.join(message)
+def check_boolean_field_default_value():
+ """
+ Checks if there are any BooleanFields without a default value, &
+ warns the user that the default has changed from False to Null.
+ """
+ fields = []
+ for cls in models.get_models():
+ opts = cls._meta
+ for f in opts.local_fields:
+ if isinstance(f, models.BooleanField) and not f.has_default():
+ fields.append(
+ '%s.%s: "%s"' % (opts.app_label, opts.object_name, f.name)
+ )
+ if fields:
+ fieldnames = ", ".join(fields)
+ message = [
+ "You have not set a default value for one or more BooleanFields:",
+ "%s." % fieldnames,
+ "In Django 1.6 the default value of BooleanField was changed from",
+ "False to Null when Field.default isn't defined. See",
+ "https://docs.djangoproject.com/en/1.6/ref/models/fields/#booleanfield"
+ "for more information."
+ ]
+ return ' '.join(message)
+
def run_checks():
"""
@@ -31,7 +57,8 @@ def run_checks():
messages from all the relevant check functions for this version of Django.
"""
checks = [
- check_test_runner()
+ check_test_runner(),
+ check_boolean_field_default_value(),
]
# Filter out the ``None`` or empty strings.
return [output for output in checks if output]
diff --git a/django/core/files/storage.py b/django/core/files/storage.py
index 5d301a317c..5e587da2da 100644
--- a/django/core/files/storage.py
+++ b/django/core/files/storage.py
@@ -172,7 +172,16 @@ class FileSystemStorage(Storage):
directory = os.path.dirname(full_path)
if not os.path.exists(directory):
try:
- os.makedirs(directory)
+ if settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS is not None:
+ # os.makedirs applies the global umask, so we reset it,
+ # for consistency with FILE_UPLOAD_PERMISSIONS behavior.
+ old_umask = os.umask(0)
+ try:
+ os.makedirs(directory, settings.FILE_UPLOAD_DIRECTORY_PERMISSIONS)
+ finally:
+ os.umask(old_umask)
+ else:
+ os.makedirs(directory)
except OSError as e:
if e.errno != errno.EEXIST:
raise
diff --git a/django/db/backends/postgresql_psycopg2/introspection.py b/django/db/backends/postgresql_psycopg2/introspection.py
index 3e2574b0c1..57d9a67abf 100644
--- a/django/db/backends/postgresql_psycopg2/introspection.py
+++ b/django/db/backends/postgresql_psycopg2/introspection.py
@@ -26,6 +26,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
1700: 'DecimalField',
}
+ ignored_tables = []
+
def get_table_list(self, cursor):
"Returns a list of table names in the current database."
cursor.execute("""
@@ -35,7 +37,7 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
WHERE c.relkind IN ('r', 'v', '')
AND n.nspname NOT IN ('pg_catalog', 'pg_toast')
AND pg_catalog.pg_table_is_visible(c.oid)""")
- return [row[0] for row in cursor.fetchall()]
+ return [row[0] for row in cursor.fetchall() if row[0] not in self.ignored_tables]
def get_table_description(self, cursor, table_name):
"Returns a description of the table, with the DB-API cursor.description interface."
diff --git a/django/db/models/base.py b/django/db/models/base.py
index a2937f12c6..6a21544baf 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -184,10 +184,21 @@ class ModelBase(type):
else:
new_class._meta.concrete_model = new_class
- # Do the appropriate setup for any model parents.
- o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
- if isinstance(f, OneToOneField)])
+ # Collect the parent links for multi-table inheritance.
+ parent_links = {}
+ for base in reversed([new_class] + parents):
+ # Conceptually equivalent to `if base is Model`.
+ if not hasattr(base, '_meta'):
+ continue
+ # Skip concrete parent classes.
+ if base != new_class and not base._meta.abstract:
+ continue
+ # Locate OneToOneField instances.
+ for field in base._meta.local_fields:
+ if isinstance(field, OneToOneField):
+ parent_links[field.rel.to] = field
+ # Do the appropriate setup for any model parents.
for base in parents:
original_base = base
if not hasattr(base, '_meta'):
@@ -208,8 +219,8 @@ class ModelBase(type):
if not base._meta.abstract:
# Concrete classes...
base = base._meta.concrete_model
- if base in o2o_map:
- field = o2o_map[base]
+ if base in parent_links:
+ field = parent_links[base]
elif not is_proxy:
attr_name = '%s_ptr' % base._meta.model_name
field = OneToOneField(base, name=attr_name,
@@ -448,7 +459,9 @@ class Model(six.with_metaclass(ModelBase)):
return '%s object' % self.__class__.__name__
def __eq__(self, other):
- return isinstance(other, self.__class__) and self._get_pk_val() == other._get_pk_val()
+ return (isinstance(other, Model) and
+ self._meta.concrete_model == other._meta.concrete_model and
+ self._get_pk_val() == other._get_pk_val())
def __ne__(self, other):
return not self.__eq__(other)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 4069c04a14..836d394e9b 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -313,14 +313,13 @@ class QuerySet(object):
kwargs[arg.default_alias] = arg
query = self.query.clone()
-
+ force_subq = query.low_mark != 0 or query.high_mark is not None
aggregate_names = []
for (alias, aggregate_expr) in kwargs.items():
query.add_aggregate(aggregate_expr, self.model, alias,
is_summary=True)
aggregate_names.append(alias)
-
- return query.get_aggregation(using=self.db)
+ return query.get_aggregation(using=self.db, force_subq=force_subq)
def count(self):
"""
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
index e17cb3f616..54b4e86245 100644
--- a/django/db/models/sql/compiler.py
+++ b/django/db/models/sql/compiler.py
@@ -167,7 +167,6 @@ class SQLCompiler(object):
if obj.low_mark == 0 and obj.high_mark is None:
# If there is no slicing in use, then we can safely drop all ordering
obj.clear_ordering(True)
- obj.bump_prefix()
return obj.get_compiler(connection=self.connection).as_sql()
def get_columns(self, with_aliases=False):
@@ -808,13 +807,14 @@ class SQLCompiler(object):
return result
def as_subquery_condition(self, alias, columns, qn):
+ inner_qn = self.quote_name_unless_alias
qn2 = self.connection.ops.quote_name
if len(columns) == 1:
sql, params = self.as_sql()
return '%s.%s IN (%s)' % (qn(alias), qn2(columns[0]), sql), params
for index, select_col in enumerate(self.query.select):
- lhs = '%s.%s' % (qn(select_col.col[0]), qn2(select_col.col[1]))
+ lhs = '%s.%s' % (inner_qn(select_col.col[0]), qn2(select_col.col[1]))
rhs = '%s.%s' % (qn(alias), qn2(columns[index]))
self.query.where.add(
QueryWrapper('%s = %s' % (lhs, rhs), []), 'AND')
@@ -1010,7 +1010,6 @@ class SQLUpdateCompiler(SQLCompiler):
# We need to use a sub-select in the where clause to filter on things
# from other tables.
query = self.query.clone(klass=Query)
- query.bump_prefix()
query.extra = {}
query.select = []
query.add_fields([query.get_meta().pk.name])
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 15f2643495..1d19c5b9a8 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -97,6 +97,7 @@ class Query(object):
LOUTER = 'LEFT OUTER JOIN'
alias_prefix = 'T'
+ subq_aliases = frozenset([alias_prefix])
query_terms = QUERY_TERMS
aggregates_module = base_aggregates_module
@@ -273,6 +274,10 @@ class Query(object):
else:
obj.used_aliases = set()
obj.filter_is_sticky = False
+ if 'alias_prefix' in self.__dict__:
+ obj.alias_prefix = self.alias_prefix
+ if 'subq_aliases' in self.__dict__:
+ obj.subq_aliases = self.subq_aliases.copy()
obj.__dict__.update(kwargs)
if hasattr(obj, '_setup_query'):
@@ -310,7 +315,7 @@ class Query(object):
# Return value depends on the type of the field being processed.
return self.convert_values(value, aggregate.field, connection)
- def get_aggregation(self, using):
+ def get_aggregation(self, using, force_subq=False):
"""
Returns the dictionary with the values of the existing aggregations.
"""
@@ -320,18 +325,26 @@ class Query(object):
# If there is a group by clause, aggregating does not add useful
# information but retrieves only the first row. Aggregate
# over the subquery instead.
- if self.group_by is not None:
+ if self.group_by is not None or force_subq:
from django.db.models.sql.subqueries import AggregateQuery
query = AggregateQuery(self.model)
-
obj = self.clone()
+ if not force_subq:
+ # In forced subq case the ordering and limits will likely
+ # affect the results.
+ obj.clear_ordering(True)
+ obj.clear_limits()
+ obj.select_for_update = False
+ obj.select_related = False
+ obj.related_select_cols = []
+ relabels = dict((t, 'subquery') for t in self.tables)
# Remove any aggregates marked for reduction from the subquery
# and move them to the outer AggregateQuery.
for alias, aggregate in self.aggregate_select.items():
if aggregate.is_summary:
- query.aggregate_select[alias] = aggregate
+ query.aggregate_select[alias] = aggregate.relabeled_clone(relabels)
del obj.aggregate_select[alias]
try:
@@ -780,28 +793,22 @@ class Query(object):
data = data._replace(lhs_alias=change_map[lhs])
self.alias_map[alias] = data
- def bump_prefix(self, exceptions=()):
+ def bump_prefix(self, outer_query):
"""
- Changes the alias prefix to the next letter in the alphabet and
- relabels all the aliases. Even tables that previously had no alias will
- get an alias after this call (it's mostly used for nested queries and
- the outer query will already be using the non-aliased table name).
-
- Subclasses who create their own prefix should override this method to
- produce a similar result (a new prefix and relabelled aliases).
-
- The 'exceptions' parameter is a container that holds alias names which
- should not be changed.
+ Changes the alias prefix to the next letter in the alphabet in a way
+ that the outer query's aliases and this query's aliases will not
+ conflict. Even tables that previously had no alias will get an alias
+ after this call.
"""
- current = ord(self.alias_prefix)
- assert current < ord('Z')
- prefix = chr(current + 1)
- self.alias_prefix = prefix
+ self.alias_prefix = chr(ord(self.alias_prefix) + 1)
+ while self.alias_prefix in self.subq_aliases:
+ self.alias_prefix = chr(ord(self.alias_prefix) + 1)
+ assert self.alias_prefix < 'Z'
+ self.subq_aliases = self.subq_aliases.union([self.alias_prefix])
+ outer_query.subq_aliases = outer_query.subq_aliases.union(self.subq_aliases)
change_map = OrderedDict()
for pos, alias in enumerate(self.tables):
- if alias in exceptions:
- continue
- new_alias = '%s%d' % (prefix, pos)
+ new_alias = '%s%d' % (self.alias_prefix, pos)
change_map[alias] = new_alias
self.tables[pos] = new_alias
self.change_aliases(change_map)
@@ -1005,6 +1012,65 @@ class Query(object):
# Add the aggregate to the query
aggregate.add_to_query(self, alias, col=col, source=source, is_summary=is_summary)
+ def prepare_lookup_value(self, value, lookup_type, can_reuse):
+ # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
+ # uses of None as a query value.
+ if value is None:
+ if lookup_type != 'exact':
+ raise ValueError("Cannot use None as a query value")
+ lookup_type = 'isnull'
+ value = True
+ elif callable(value):
+ value = value()
+ elif isinstance(value, ExpressionNode):
+ # If value is a query expression, evaluate it
+ value = SQLEvaluator(value, self, reuse=can_reuse)
+ if hasattr(value, 'query') and hasattr(value.query, 'bump_prefix'):
+ value = value._clone()
+ value.query.bump_prefix(self)
+ if hasattr(value, 'bump_prefix'):
+ value = value.clone()
+ value.bump_prefix(self)
+ # For Oracle '' is equivalent to null. The check needs to be done
+ # at this stage because join promotion can't be done at compiler
+ # stage. Using DEFAULT_DB_ALIAS isn't nice, but it is the best we
+ # can do here. Similar thing is done in is_nullable(), too.
+ if (connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls and
+ lookup_type == 'exact' and value == ''):
+ value = True
+ lookup_type = 'isnull'
+ return value, lookup_type
+
+ def solve_lookup_type(self, lookup):
+ """
+ Solve the lookup type from the lookup (eg: 'foobar__id__icontains')
+ """
+ lookup_type = 'exact' # Default lookup type
+ lookup_parts = lookup.split(LOOKUP_SEP)
+ num_parts = len(lookup_parts)
+ if (len(lookup_parts) > 1 and lookup_parts[-1] in self.query_terms
+ and lookup not in self.aggregates):
+ # Traverse the lookup query to distinguish related fields from
+ # lookup types.
+ lookup_model = self.model
+ for counter, field_name in enumerate(lookup_parts):
+ try:
+ lookup_field = lookup_model._meta.get_field(field_name)
+ except FieldDoesNotExist:
+ # Not a field. Bail out.
+ lookup_type = lookup_parts.pop()
+ break
+ # Unless we're at the end of the list of lookups, let's attempt
+ # to continue traversing relations.
+ if (counter + 1) < num_parts:
+ try:
+ lookup_model = lookup_field.rel.to
+ except AttributeError:
+ # Not a related field. Bail out.
+ lookup_type = lookup_parts.pop()
+ break
+ return lookup_type, lookup_parts
+
def build_filter(self, filter_expr, branch_negated=False, current_negated=False,
can_reuse=None):
"""
@@ -1033,58 +1099,15 @@ class Query(object):
is responsible for unreffing the joins used.
"""
arg, value = filter_expr
- parts = arg.split(LOOKUP_SEP)
+ lookup_type, parts = self.solve_lookup_type(arg)
if not parts:
raise FieldError("Cannot parse keyword query %r" % arg)
# Work out the lookup type and remove it from the end of 'parts',
# if necessary.
- lookup_type = 'exact' # Default lookup type
- num_parts = len(parts)
- if (len(parts) > 1 and parts[-1] in self.query_terms
- and arg not in self.aggregates):
- # Traverse the lookup query to distinguish related fields from
- # lookup types.
- lookup_model = self.model
- for counter, field_name in enumerate(parts):
- try:
- lookup_field = lookup_model._meta.get_field(field_name)
- except FieldDoesNotExist:
- # Not a field. Bail out.
- lookup_type = parts.pop()
- break
- # Unless we're at the end of the list of lookups, let's attempt
- # to continue traversing relations.
- if (counter + 1) < num_parts:
- try:
- lookup_model = lookup_field.rel.to
- except AttributeError:
- # Not a related field. Bail out.
- lookup_type = parts.pop()
- break
+ value, lookup_type = self.prepare_lookup_value(value, lookup_type, can_reuse)
clause = self.where_class()
- # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
- # uses of None as a query value.
- if value is None:
- if lookup_type != 'exact':
- raise ValueError("Cannot use None as a query value")
- lookup_type = 'isnull'
- value = True
- elif callable(value):
- value = value()
- elif isinstance(value, ExpressionNode):
- # If value is a query expression, evaluate it
- value = SQLEvaluator(value, self, reuse=can_reuse)
- # For Oracle '' is equivalent to null. The check needs to be done
- # at this stage because join promotion can't be done at compiler
- # stage. Using DEFAULT_DB_ALIAS isn't nice, but it is the best we
- # can do here. Similar thing is done in is_nullable(), too.
- if (connections[DEFAULT_DB_ALIAS].features.interprets_empty_strings_as_nulls and
- lookup_type == 'exact' and value == ''):
- value = True
- lookup_type = 'isnull'
-
for alias, aggregate in self.aggregates.items():
if alias in (parts[0], LOOKUP_SEP.join(parts)):
clause.add((aggregate, lookup_type, value), AND)
@@ -1096,7 +1119,7 @@ class Query(object):
try:
field, sources, opts, join_list, path = self.setup_joins(
- parts, opts, alias, can_reuse, allow_many,)
+ parts, opts, alias, can_reuse, allow_many,)
if can_reuse is not None:
can_reuse.update(join_list)
except MultiJoin as e:
@@ -1404,7 +1427,6 @@ class Query(object):
# Generate the inner query.
query = Query(self.model)
query.where.add(query.build_filter(filter_expr), AND)
- query.bump_prefix()
query.clear_ordering(True)
# Try to have as simple as possible subquery -> trim leading joins from
# the subquery.
diff --git a/django/forms/forms.py b/django/forms/forms.py
index c2b700ce77..ec51507981 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -434,7 +434,9 @@ class BoundField(object):
This really is only useful for RadioSelect widgets, so that you can
iterate over individual radio buttons in a template.
"""
- for subwidget in self.field.widget.subwidgets(self.html_name, self.value()):
+ id_ = self.field.widget.attrs.get('id') or self.auto_id
+ attrs = {'id': id_} if id_ else {}
+ for subwidget in self.field.widget.subwidgets(self.html_name, self.value(), attrs):
yield subwidget
def __len__(self):
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 0a5059a9c2..98d47d0b00 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -601,16 +601,15 @@ class ChoiceInput(SubWidget):
self.choice_value = force_text(choice[0])
self.choice_label = force_text(choice[1])
self.index = index
+ if 'id' in self.attrs:
+ self.attrs['id'] += "_%d" % self.index
def __str__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
- name = name or self.name
- value = value or self.value
- attrs = attrs or self.attrs
- if 'id' in self.attrs:
- label_for = format_html(' for="{0}_{1}"', self.attrs['id'], self.index)
+ if self.id_for_label:
+ label_for = format_html(' for="{0}"', self.id_for_label)
else:
label_for = ''
return format_html('<label{0}>{1} {2}</label>', label_for, self.tag(), self.choice_label)
@@ -619,13 +618,15 @@ class ChoiceInput(SubWidget):
return self.value == self.choice_value
def tag(self):
- if 'id' in self.attrs:
- self.attrs['id'] = '%s_%s' % (self.attrs['id'], self.index)
final_attrs = dict(self.attrs, type=self.input_type, name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return format_html('<input{0} />', flatatt(final_attrs))
+ @property
+ def id_for_label(self):
+ return self.attrs.get('id', '')
+
class RadioChoiceInput(ChoiceInput):
input_type = 'radio'
diff --git a/django/template/base.py b/django/template/base.py
index ed4196012a..382b85aefd 100644
--- a/django/template/base.py
+++ b/django/template/base.py
@@ -6,7 +6,7 @@ from importlib import import_module
from inspect import getargspec
from django.conf import settings
-from django.template.context import (Context, RequestContext,
+from django.template.context import (BaseContext, Context, RequestContext,
ContextPopException)
from django.utils.itercompat import is_iterable
from django.utils.text import (smart_split, unescape_string_literal,
@@ -765,6 +765,9 @@ class Variable(object):
current = current[bit]
except (TypeError, AttributeError, KeyError, ValueError):
try: # attribute lookup
+ # Don't return class attributes if the class is the context:
+ if isinstance(current, BaseContext) and getattr(type(current), bit):
+ raise AttributeError
current = getattr(current, bit)
except (TypeError, AttributeError):
try: # list-index lookup
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index 5c9490f749..921a3594cc 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -458,10 +458,11 @@ class VerbatimNode(Node):
return self.content
class WidthRatioNode(Node):
- def __init__(self, val_expr, max_expr, max_width):
+ def __init__(self, val_expr, max_expr, max_width, asvar=None):
self.val_expr = val_expr
self.max_expr = max_expr
self.max_width = max_width
+ self.asvar = asvar
def render(self, context):
try:
@@ -480,7 +481,13 @@ class WidthRatioNode(Node):
return '0'
except (ValueError, TypeError):
return ''
- return str(int(round(ratio)))
+ result = str(int(round(ratio)))
+
+ if self.asvar:
+ context[self.asvar] = result
+ return ''
+ else:
+ return result
class WithNode(Node):
def __init__(self, var, name, nodelist, extra_context=None):
@@ -1353,20 +1360,34 @@ def widthratio(parser, token):
For example::
- <img src='bar.gif' height='10' width='{% widthratio this_value max_value max_width %}' />
+ <img src="bar.png" alt="Bar"
+ height="10" width="{% widthratio this_value max_value max_width %}" />
If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100,
the image in the above example will be 88 pixels wide
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
+
+ In some cases you might want to capture the result of widthratio in a
+ variable. It can be useful for instance in a blocktrans like this::
+
+ {% widthratio this_value max_value max_width as width %}
+ {% blocktrans %}The width is: {{ width }}{% endblocktrans %}
"""
bits = token.split_contents()
- if len(bits) != 4:
- raise TemplateSyntaxError("widthratio takes three arguments")
- tag, this_value_expr, max_value_expr, max_width = bits
+ if len(bits) == 4:
+ tag, this_value_expr, max_value_expr, max_width = bits
+ asvar = None
+ elif len(bits) == 6:
+ tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits
+ if as_ != 'as':
+ raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecting 'as' keyword")
+ else:
+ raise TemplateSyntaxError("widthratio takes at least three arguments")
return WidthRatioNode(parser.compile_filter(this_value_expr),
parser.compile_filter(max_value_expr),
- parser.compile_filter(max_width))
+ parser.compile_filter(max_width),
+ asvar=asvar)
@register.tag('with')
def do_with(parser, token):
diff --git a/django/test/client.py b/django/test/client.py
index 754d4d73f8..3c58eae4b5 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -37,6 +37,7 @@ BOUNDARY = 'BoUnDaRyStRiNg'
MULTIPART_CONTENT = 'multipart/form-data; boundary=%s' % BOUNDARY
CONTENT_TYPE_RE = re.compile('.*; charset=([\w\d-]+);?')
+
class FakePayload(object):
"""
A wrapper around BytesIO that restricts what can be read since data from
@@ -123,6 +124,7 @@ class ClientHandler(BaseHandler):
return response
+
def store_rendered_templates(store, signal, sender, template, context, **kwargs):
"""
Stores templates and contexts that are rendered.
@@ -133,6 +135,7 @@ def store_rendered_templates(store, signal, sender, template, context, **kwargs)
store.setdefault('templates', []).append(template)
store.setdefault('context', ContextList()).append(copy(context))
+
def encode_multipart(boundary, data):
"""
Encodes multipart POST data from a dictionary of form values.
@@ -178,6 +181,7 @@ def encode_multipart(boundary, data):
])
return b'\r\n'.join(lines)
+
def encode_file(boundary, key, file):
to_bytes = lambda s: force_bytes(s, settings.DEFAULT_CHARSET)
if hasattr(file, 'content_type'):
@@ -189,8 +193,8 @@ def encode_file(boundary, key, file):
content_type = 'application/octet-stream'
return [
to_bytes('--%s' % boundary),
- to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"' \
- % (key, os.path.basename(file.name))),
+ to_bytes('Content-Disposition: form-data; name="%s"; filename="%s"'
+ % (key, os.path.basename(file.name))),
to_bytes('Content-Type: %s' % content_type),
b'',
file.read()
@@ -274,14 +278,11 @@ class RequestFactory(object):
def get(self, path, data={}, **extra):
"Construct a GET request."
- parsed = urlparse(path)
r = {
- 'PATH_INFO': self._get_path(parsed),
- 'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
- 'REQUEST_METHOD': str('GET'),
+ 'QUERY_STRING': urlencode(data, doseq=True),
}
r.update(extra)
- return self.request(**r)
+ return self.generic('GET', path, **r)
def post(self, path, data={}, content_type=MULTIPART_CONTENT,
**extra):
@@ -289,32 +290,19 @@ class RequestFactory(object):
post_data = self._encode_data(data, content_type)
- parsed = urlparse(path)
- r = {
- 'CONTENT_LENGTH': len(post_data),
- 'CONTENT_TYPE': content_type,
- 'PATH_INFO': self._get_path(parsed),
- 'QUERY_STRING': force_str(parsed[4]),
- 'REQUEST_METHOD': str('POST'),
- 'wsgi.input': FakePayload(post_data),
- }
- r.update(extra)
- return self.request(**r)
+ return self.generic('POST', path, post_data, content_type, **extra)
def head(self, path, data={}, **extra):
"Construct a HEAD request."
- parsed = urlparse(path)
r = {
- 'PATH_INFO': self._get_path(parsed),
- 'QUERY_STRING': urlencode(data, doseq=True) or force_str(parsed[4]),
- 'REQUEST_METHOD': str('HEAD'),
+ 'QUERY_STRING': urlencode(data, doseq=True),
}
r.update(extra)
- return self.request(**r)
+ return self.generic('HEAD', path, **r)
def options(self, path, data='', content_type='application/octet-stream',
- **extra):
+ **extra):
"Construct an OPTIONS request."
return self.generic('OPTIONS', path, data, content_type, **extra)
@@ -324,22 +312,22 @@ class RequestFactory(object):
return self.generic('PUT', path, data, content_type, **extra)
def patch(self, path, data='', content_type='application/octet-stream',
- **extra):
+ **extra):
"Construct a PATCH request."
return self.generic('PATCH', path, data, content_type, **extra)
def delete(self, path, data='', content_type='application/octet-stream',
- **extra):
+ **extra):
"Construct a DELETE request."
return self.generic('DELETE', path, data, content_type, **extra)
def generic(self, method, path,
data='', content_type='application/octet-stream', **extra):
+ """Constructs an arbitrary HTTP request."""
parsed = urlparse(path)
data = force_bytes(data, settings.DEFAULT_CHARSET)
r = {
'PATH_INFO': self._get_path(parsed),
- 'QUERY_STRING': force_str(parsed[4]),
'REQUEST_METHOD': str(method),
}
if data:
@@ -349,8 +337,12 @@ class RequestFactory(object):
'wsgi.input': FakePayload(data),
})
r.update(extra)
+ # If QUERY_STRING is absent or empty, we want to extract it from the URL.
+ if not r.get('QUERY_STRING'):
+ r['QUERY_STRING'] = force_str(parsed[4])
return self.request(**r)
+
class Client(RequestFactory):
"""
A class that can act as a client for testing purposes.
@@ -392,7 +384,6 @@ class Client(RequestFactory):
return {}
session = property(_session)
-
def request(self, **request):
"""
The master request method. Composes the environment dictionary
@@ -406,7 +397,8 @@ class Client(RequestFactory):
# callback function.
data = {}
on_template_render = curry(store_rendered_templates, data)
- signals.template_rendered.connect(on_template_render, dispatch_uid="template-render")
+ signal_uid = "template-render-%s" % id(request)
+ signals.template_rendered.connect(on_template_render, dispatch_uid=signal_uid)
# Capture exceptions created by the handler.
got_request_exception.connect(self.store_exc_info, dispatch_uid="request-exception")
try:
@@ -452,7 +444,7 @@ class Client(RequestFactory):
return response
finally:
- signals.template_rendered.disconnect(dispatch_uid="template-render")
+ signals.template_rendered.disconnect(dispatch_uid=signal_uid)
got_request_exception.disconnect(dispatch_uid="request-exception")
def get(self, path, data={}, follow=False, **extra):
@@ -484,12 +476,11 @@ class Client(RequestFactory):
return response
def options(self, path, data='', content_type='application/octet-stream',
- follow=False, **extra):
+ follow=False, **extra):
"""
Request a response from the server using OPTIONS.
"""
- response = super(Client, self).options(path,
- data=data, content_type=content_type, **extra)
+ response = super(Client, self).options(path, data=data, content_type=content_type, **extra)
if follow:
response = self._handle_redirects(response, **extra)
return response
@@ -499,14 +490,13 @@ class Client(RequestFactory):
"""
Send a resource to the server using PUT.
"""
- response = super(Client, self).put(path,
- data=data, content_type=content_type, **extra)
+ response = super(Client, self).put(path, data=data, content_type=content_type, **extra)
if follow:
response = self._handle_redirects(response, **extra)
return response
def patch(self, path, data='', content_type='application/octet-stream',
- follow=False, **extra):
+ follow=False, **extra):
"""
Send a resource to the server using PATCH.
"""
@@ -517,12 +507,12 @@ class Client(RequestFactory):
return response
def delete(self, path, data='', content_type='application/octet-stream',
- follow=False, **extra):
+ follow=False, **extra):
"""
Send a DELETE request to the server.
"""
- response = super(Client, self).delete(path,
- data=data, content_type=content_type, **extra)
+ response = super(Client, self).delete(
+ path, data=data, content_type=content_type, **extra)
if follow:
response = self._handle_redirects(response, **extra)
return response
diff --git a/django/utils/functional.py b/django/utils/functional.py
index e23bd3ff80..9cc703fe84 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -263,17 +263,12 @@ class LazyObject(object):
__dir__ = new_method_proxy(dir)
# Dictionary methods support
- @new_method_proxy
- def __getitem__(self, key):
- return self[key]
+ __getitem__ = new_method_proxy(operator.getitem)
+ __setitem__ = new_method_proxy(operator.setitem)
+ __delitem__ = new_method_proxy(operator.delitem)
- @new_method_proxy
- def __setitem__(self, key, value):
- self[key] = value
-
- @new_method_proxy
- def __delitem__(self, key):
- del self[key]
+ __len__ = new_method_proxy(len)
+ __contains__ = new_method_proxy(operator.contains)
# Workaround for http://bugs.python.org/issue12370
diff --git a/django/utils/http.py b/django/utils/http.py
index 4647d89847..9b36ab91d7 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -109,8 +109,7 @@ def http_date(epoch_seconds=None):
Outputs a string in the format 'Wdy, DD Mon YYYY HH:MM:SS GMT'.
"""
- rfcdate = formatdate(epoch_seconds)
- return '%s GMT' % rfcdate[:25]
+ return formatdate(epoch_seconds, usegmt=True)
def parse_http_date(date):
"""
@@ -253,11 +252,12 @@ def same_origin(url1, url2):
def is_safe_url(url, host=None):
"""
Return ``True`` if the url is a safe redirection (i.e. it doesn't point to
- a different host).
+ a different host and uses a safe scheme).
Always returns ``False`` on an empty url.
"""
if not url:
return False
- netloc = urllib_parse.urlparse(url)[1]
- return not netloc or netloc == host
+ url_info = urllib_parse.urlparse(url)
+ return (not url_info.netloc or url_info.netloc == host) and \
+ (not url_info.scheme or url_info.scheme in ['http', 'https'])
diff --git a/django/views/debug.py b/django/views/debug.py
index 2129a83d67..16f75df8c3 100644
--- a/django/views/debug.py
+++ b/django/views/debug.py
@@ -227,7 +227,7 @@ class ExceptionReporter(object):
return "File exists"
def get_traceback_data(self):
- "Return a Context instance containing traceback information."
+ """Return a dictionary containing traceback information."""
if self.exc_type and issubclass(self.exc_type, TemplateDoesNotExist):
from django.template.loader import template_source_loaders
@@ -295,13 +295,13 @@ class ExceptionReporter(object):
def get_traceback_html(self):
"Return HTML version of debug 500 HTTP error page."
t = Template(TECHNICAL_500_TEMPLATE, name='Technical 500 template')
- c = Context(self.get_traceback_data())
+ c = Context(self.get_traceback_data(), use_l10n=False)
return t.render(c)
def get_traceback_text(self):
"Return plain text version of debug 500 HTTP error page."
t = Template(TECHNICAL_500_TEXT_TEMPLATE, name='Technical 500 template')
- c = Context(self.get_traceback_data(), autoescape=False)
+ c = Context(self.get_traceback_data(), autoescape=False, use_l10n=False)
return t.render(c)
def get_template_exception_info(self):
diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt
index af652de78c..6493c18f99 100644
--- a/docs/howto/custom-model-fields.txt
+++ b/docs/howto/custom-model-fields.txt
@@ -295,9 +295,9 @@ validation process will break.
Therefore, you must ensure that the form field used to represent your
custom field performs whatever input validation and data cleaning is
necessary to convert user-provided form input into a
-`to_python()`-compatible model field value. This may require writing a
+``to_python()``-compatible model field value. This may require writing a
custom form field, and/or implementing the :meth:`.formfield` method on
-your field to return a form field class whose `to_python()` returns the
+your field to return a form field class whose ``to_python()`` returns the
correct datatype.
Documenting your custom field
diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt
index 8b0368ac67..688bae2397 100644
--- a/docs/howto/deployment/index.txt
+++ b/docs/howto/deployment/index.txt
@@ -28,6 +28,7 @@ the easiest, fastest, and most stable deployment choice.
* `Chapter 12 of the Django Book (second edition)`_ discusses deployment
and especially scaling in more detail. However, note that this edition
was written against Django version 1.1 and has not been updated since
- `mod_python` was first deprecated, then completely removed in Django 1.5.
+ ``mod_python`` was first deprecated, then completely removed in
+ Django 1.5.
.. _chapter 12 of the django book (second edition): http://djangobook.com/en/2.0/chapter12/
diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt
index d06e89cd0e..80219ffdf4 100644
--- a/docs/howto/deployment/wsgi/apache-auth.txt
+++ b/docs/howto/deployment/wsgi/apache-auth.txt
@@ -16,7 +16,7 @@ version >= 2.2 and mod_wsgi >= 2.0. For example, you could:
.. note::
If you have installed a :ref:`custom User model <auth-custom-user>` and
- want to use this default auth handler, it must support an `is_active`
+ want to use this default auth handler, it must support an ``is_active``
attribute. If you want to use group based authorization, your custom user
must have a relation named 'groups', referring to a related object that has
a 'name' field. You can also specify your own custom mod_wsgi
diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt
index 2cbcd8ce7e..1d11e94685 100644
--- a/docs/howto/deployment/wsgi/modwsgi.txt
+++ b/docs/howto/deployment/wsgi/modwsgi.txt
@@ -193,7 +193,7 @@ other approaches:
configuration).
2. Use an ``Alias`` directive, as demonstrated above, to alias the appropriate
- URL (probably :setting:`STATIC_URL` + `admin/`) to the actual location of
+ URL (probably :setting:`STATIC_URL` + ``admin/``) to the actual location of
the admin files.
3. Copy the admin static files so that they live within your Apache
diff --git a/docs/internals/contributing/writing-code/working-with-git.txt b/docs/internals/contributing/writing-code/working-with-git.txt
index dcfdd9e85b..32fc459e70 100644
--- a/docs/internals/contributing/writing-code/working-with-git.txt
+++ b/docs/internals/contributing/writing-code/working-with-git.txt
@@ -157,7 +157,7 @@ using interactive rebase::
The HEAD~2 above is shorthand for two latest commits. The above command
will open an editor showing the two commits, prefixed with the word "pick".
-Change the second line to "squash" instead. This will keep the
+Change "pick" on the second line to "squash" instead. This will keep the
first commit, and squash the second commit into the first one. Save and quit
the editor. A second editor window should open, so you can reword the
commit message for the commit now that it includes both your steps.
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index f0ac66bce8..7b8298597d 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -370,8 +370,11 @@ these changes.
* Remove the backward compatible shims introduced to rename the attributes
``ChangeList.root_query_set`` and ``ChangeList.query_set``.
-* ``django.conf.urls.shortcut`` and ``django.views.defaults.shortcut`` will be
- removed.
+* ``django.views.defaults.shortcut`` will be removed, as part of the
+ goal of removing all ``django.contrib`` references from the core
+ Django codebase. Instead use
+ ``django.contrib.contenttypes.views.shortcut``. ``django.conf.urls.shortcut``
+ will also be removed.
* Support for the Python Imaging Library (PIL) module will be removed, as it
no longer appears to be actively maintained & does not work on Python 3.
@@ -442,11 +445,5 @@ these changes.
2.0
---
-* ``django.views.defaults.shortcut()``. This function has been moved
- to ``django.contrib.contenttypes.views.shortcut()`` as part of the
- goal of removing all ``django.contrib`` references from the core
- Django codebase. The old shortcut will be removed in the 2.0
- release.
-
* ``ssi`` and ``url`` template tags will be removed from the ``future`` template
tag library (used during the 1.3/1.4 deprecation period).
diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt
index 5bda2e8add..a926de27ab 100644
--- a/docs/internals/howto-release-django.txt
+++ b/docs/internals/howto-release-django.txt
@@ -83,10 +83,11 @@ A few items need to be taken care of before even beginning the release process.
This stuff starts about a week before the release; most of it can be done
any time leading up to the actual release:
-#. If this is a security release, send out pre-notification **one week**
- before the release. We maintain a list of who gets these pre-notification
- emails at *FIXME WHERE?*. This email should be signed by the key you'll use
- for the release, and should include patches for each issue being fixed.
+#. If this is a security release, send out pre-notification **one week** before
+ the release. We maintain a list of who gets these pre-notification emails in
+ the private ``django-core`` repository. This email should be signed by the
+ key you'll use for the release, and should include patches for each issue
+ being fixed.
#. As the release approaches, watch Trac to make sure no release blockers
are left for the upcoming release.
diff --git a/docs/internals/security.txt b/docs/internals/security.txt
index 486b2c9968..327a6a5f60 100644
--- a/docs/internals/security.txt
+++ b/docs/internals/security.txt
@@ -108,8 +108,12 @@ On the day of disclosure, we will take the following steps:
relevant patches and new releases, and crediting the reporter of
the issue (if the reporter wishes to be publicly identified).
+4. Post a notice to the `django-announce`_ mailing list that links to the blog
+ post.
+
.. _the Python Package Index: http://pypi.python.org/pypi
.. _the official Django development blog: https://www.djangoproject.com/weblog/
+.. _django-announce: http://groups.google.com/group/django-announce
If a reported issue is believed to be particularly time-sensitive --
due to a known exploit in the wild, for example -- the time between
@@ -214,4 +218,4 @@ If you are added to the notification list, security-related emails
will be sent to you by Django's release manager, and all notification
emails will be signed with the same key used to sign Django releases;
that key has the ID ``0x3684C0C08C8B2AE1``, and is available from most
-commonly-used keyservers. \ No newline at end of file
+commonly-used keyservers.
diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt
index f2b01758aa..b99d3be3e2 100644
--- a/docs/intro/tutorial01.txt
+++ b/docs/intro/tutorial01.txt
@@ -600,7 +600,7 @@ for your own sanity when dealing with the interactive prompt, but also because
objects' representations are used throughout Django's automatically-generated
admin.
-.. admonition:: `__unicode__` or `__str__`?
+.. admonition:: ``__unicode__`` or ``__str__``?
On Python 3, things are simpler, just use
:meth:`~django.db.models.Model.__str__` and forget about
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index c5c5f8f288..3ffe475e1b 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -385,15 +385,6 @@ search terms, Django will search the ``question`` field. You can use as many
fields as you'd like -- although because it uses a ``LIKE`` query behind the
scenes, keep it reasonable, to keep your database happy.
-Finally, because ``Poll`` objects have dates, it'd be convenient to be able to
-drill down by date. Add this line::
-
- date_hierarchy = 'pub_date'
-
-That adds hierarchical navigation, by date, to the top of the change list page.
-At top level, it displays all available years. Then it drills down to months
-and, ultimately, days.
-
Now's also a good time to note that change lists give you free pagination. The
default is to display 100 items per page. Change-list pagination, search boxes,
filters, date-hierarchies and column-header-ordering all work together like you
diff --git a/docs/ref/class-based-views/base.txt b/docs/ref/class-based-views/base.txt
index 24058311a4..0db1e15ea9 100644
--- a/docs/ref/class-based-views/base.txt
+++ b/docs/ref/class-based-views/base.txt
@@ -104,12 +104,6 @@ TemplateView
Renders a given template, with the context containing parameters captured
in the URL.
- .. versionchanged:: 1.5
-
- The context used to be populated with a ``{{ params }}`` dictionary of
- the parameters captured in the URL. Now those parameters are first-level
- context variables.
-
**Ancestors (MRO)**
This view inherits methods and attributes from the following views:
diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt
index 205d556463..427d8f6ceb 100644
--- a/docs/ref/class-based-views/generic-date-based.txt
+++ b/docs/ref/class-based-views/generic-date-based.txt
@@ -138,24 +138,16 @@ YearArchiveView
* ``year``: A :class:`~datetime.date` object
representing the given year.
- .. versionchanged:: 1.5
-
- Previously, this returned a string.
-
* ``next_year``: A :class:`~datetime.date` object
representing the first day of the next year, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
- .. versionadded:: 1.5
-
* ``previous_year``: A :class:`~datetime.date` object
representing the first day of the previous year, according to
:attr:`~BaseDateListView.allow_empty` and
:attr:`~DateMixin.allow_future`.
- .. versionadded:: 1.5
-
**Notes**
* Uses a default ``template_name_suffix`` of ``_archive_year``.
diff --git a/docs/ref/class-based-views/mixins-date-based.txt b/docs/ref/class-based-views/mixins-date-based.txt
index 1162b2a194..48faf28a44 100644
--- a/docs/ref/class-based-views/mixins-date-based.txt
+++ b/docs/ref/class-based-views/mixins-date-based.txt
@@ -328,8 +328,3 @@ BaseDateListView
:meth:`~BaseDateListView.get_date_list_period` is used. ``date_type``
and ``ordering`` are simply passed to
:meth:`QuerySet.dates()<django.db.models.query.QuerySet.dates>`.
-
- .. versionchanged:: 1.5
-
- The ``ordering`` parameter was added, and the default order was
- changed to ascending.
diff --git a/docs/ref/class-based-views/mixins-multiple-object.txt b/docs/ref/class-based-views/mixins-multiple-object.txt
index 67ca5429fa..5a2ae85751 100644
--- a/docs/ref/class-based-views/mixins-multiple-object.txt
+++ b/docs/ref/class-based-views/mixins-multiple-object.txt
@@ -90,8 +90,6 @@ MultipleObjectMixin
.. attribute:: page_kwarg
- .. versionadded:: 1.5
-
A string specifying the name to use for the page parameter.
The view will expect this prameter to be available either as a query
string parameter (via ``request.GET``) or as a kwarg variable specified
diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt
index 377c85cc3b..3b12ee7689 100644
--- a/docs/ref/class-based-views/mixins-simple.txt
+++ b/docs/ref/class-based-views/mixins-simple.txt
@@ -7,8 +7,6 @@ ContextMixin
.. class:: django.views.generic.base.ContextMixin
- .. versionadded:: 1.5
-
**Methods**
.. method:: get_context_data(**kwargs)
@@ -77,8 +75,6 @@ TemplateResponseMixin
.. attribute:: content_type
- .. versionadded:: 1.5
-
The content type to use for the response. ``content_type`` is passed
as a keyword argument to ``response_class``. Default is ``None`` --
meaning that Django uses :setting:`DEFAULT_CONTENT_TYPE`.
diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt
index 58d8244525..b982aa64f7 100644
--- a/docs/ref/contrib/admin/actions.txt
+++ b/docs/ref/contrib/admin/actions.txt
@@ -269,8 +269,8 @@ Making actions available site-wide
admin.site.add_action(export_selected_objects)
- This makes the `export_selected_objects` action globally available as an
- action named `"export_selected_objects"`. You can explicitly give the action
+ This makes the ``export_selected_objects`` action globally available as an
+ action named "export_selected_objects". You can explicitly give the action
a name -- good if you later want to programmatically :ref:`remove the action
<disabling-admin-actions>` -- by passing a second argument to
:meth:`AdminSite.add_action()`::
diff --git a/docs/ref/contrib/admin/admindocs.txt b/docs/ref/contrib/admin/admindocs.txt
index 6deb7bdbf8..8809502288 100644
--- a/docs/ref/contrib/admin/admindocs.txt
+++ b/docs/ref/contrib/admin/admindocs.txt
@@ -156,6 +156,6 @@ Edit this object
Using these bookmarklets requires that you are either logged into the
:mod:`Django admin <django.contrib.admin>` as a
:class:`~django.contrib.auth.models.User` with
-:attr:`~django.contrib.auth.models.User.is_staff` set to `True`, or that the
+:attr:`~django.contrib.auth.models.User.is_staff` set to ``True``, or that the
``XViewMiddleware`` is installed and you are accessing the site from an IP
address listed in :setting:`INTERNAL_IPS`.
diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt
index 8feb574efd..ba5fa3bff6 100644
--- a/docs/ref/contrib/admin/index.txt
+++ b/docs/ref/contrib/admin/index.txt
@@ -1235,8 +1235,6 @@ templates used by the :class:`ModelAdmin` views:
.. method:: ModelAdmin.get_list_filter(self, request)
- .. versionadded:: 1.5
-
The ``get_list_filter`` method is given the ``HttpRequest`` and is expected
to return the same kind of sequence type as for the
:attr:`~ModelAdmin.list_filter` attribute.
@@ -1251,8 +1249,6 @@ templates used by the :class:`ModelAdmin` views:
.. method:: ModelAdmin.get_inline_instances(self, request, obj=None)
- .. versionadded:: 1.5
-
The ``get_inline_instances`` method is given the ``HttpRequest`` and the
``obj`` being edited (or ``None`` on an add form) and is expected to return
a ``list`` or ``tuple`` of :class:`~django.contrib.admin.InlineModelAdmin`
@@ -1506,10 +1502,6 @@ templates used by the :class:`ModelAdmin` views:
Sends a message to the user using the :mod:`django.contrib.messages`
backend. See the :ref:`custom ModelAdmin example <custom-admin-action>`.
- .. versionchanged:: 1.5
-
- Keyword arguments were added in Django 1.5.
-
Keyword arguments allow you to change the message level, add extra CSS
tags, or fail silently if the ``contrib.messages`` framework is not
installed. These keyword arguments match those for
diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt
index 4fe87e9872..799d3277ab 100644
--- a/docs/ref/contrib/auth.txt
+++ b/docs/ref/contrib/auth.txt
@@ -215,11 +215,16 @@ Methods
(the Django app label). If the user is inactive, this method will
always return ``False``.
- .. method:: email_user(subject, message, from_email=None)
+ .. method:: email_user(subject, message, from_email=None, **kwargs)
Sends an email to the user. If ``from_email`` is ``None``, Django uses
the :setting:`DEFAULT_FROM_EMAIL`.
+ .. versionchanged:: 1.7
+
+ Any ``**kwargs`` are passed to the underlying
+ :meth:`~django.core.mail.send_mail()` call.
+
Manager methods
---------------
@@ -384,8 +389,6 @@ can be used for notification when a user logs in or out.
.. function:: user_login_failed
- .. versionadded:: 1.5
-
Sent when the user failed to login successfully
``sender``
diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt
index 89c4a88e00..21e65f168b 100644
--- a/docs/ref/contrib/contenttypes.txt
+++ b/docs/ref/contrib/contenttypes.txt
@@ -199,14 +199,18 @@ The ``ContentTypeManager``
Takes either a model class or an instance of a model, and returns the
:class:`~django.contrib.contenttypes.models.ContentType` instance
- representing that model.
+ representing that model. ``for_concrete_model=False`` allows fetching
+ the :class:`~django.contrib.contenttypes.models.ContentType` of a proxy
+ model.
.. method:: get_for_models(*models[, for_concrete_models=True])
Takes a variadic number of model classes, and returns a dictionary
mapping the model classes to the
:class:`~django.contrib.contenttypes.models.ContentType` instances
- representing them.
+ representing them. ``for_concrete_models=False`` allows fetching the
+ :class:`~django.contrib.contenttypes.models.ContentType` of proxy
+ models.
.. method:: get_by_natural_key(app_label, model)
@@ -232,21 +236,6 @@ lookup::
.. _generic-relations:
-.. versionadded:: 1.5
-
- Prior to Django 1.5,
- :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_model` and
- :meth:`~django.contrib.contenttypes.models.ContentTypeManager.get_for_models`
- always returned the :class:`~django.contrib.contenttypes.models.ContentType`
- associated with the concrete model of the specified one(s). That means there
- was no way to retrieve the
- :class:`~django.contrib.contenttypes.models.ContentType` of a proxy model
- using those methods. As of Django 1.5 you can now pass a boolean flag –
- ``for_concrete_model`` and ``for_concrete_models`` respectively – to specify
- wether or not you want to retrieve the
- :class:`~django.contrib.contenttypes.models.ContentType` for the concrete or
- direct model.
-
Generic relations
=================
diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt
index 5e51e4cbf3..66afc3d377 100644
--- a/docs/ref/contrib/gis/geoquerysets.txt
+++ b/docs/ref/contrib/gis/geoquerysets.txt
@@ -949,10 +949,6 @@ __ http://geohash.org/
*Availability*: PostGIS, SpatiaLite
-.. versionchanged:: 1.5
-
- ``geojson`` support for Spatialite > 3.0 has been added.
-
Attaches a ``geojson`` attribute to every model in the queryset that contains the
`GeoJSON`__ representation of the geometry.
diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt
index 12e4e55165..a9c947407f 100644
--- a/docs/ref/contrib/gis/geos.txt
+++ b/docs/ref/contrib/gis/geos.txt
@@ -276,10 +276,6 @@ that the SRID value is not included in this representation
because it is not a part of the OGC specification (use the
:attr:`GEOSGeometry.hexewkb` property instead).
-.. versionchanged:: 1.5
-
- Prior to Django 1.5, the Z value of the geometry was dropped.
-
.. attribute:: GEOSGeometry.hexewkb
Returns the EWKB of this Geometry in hexadecimal form. This is an
@@ -325,10 +321,6 @@ Returns the WKB (Well-Known Binary) representation of this Geometry
as a Python buffer. SRID value is not included, use the
:attr:`GEOSGeometry.ewkb` property instead.
-.. versionchanged:: 1.5
-
- Prior to Django 1.5, the Z value of the geometry was dropped.
-
.. _ewkb:
.. attribute:: GEOSGeometry.ewkb
@@ -426,8 +418,6 @@ geometry that do not make up other.
.. method:: GEOSGeometry.interpolate(distance)
.. method:: GEOSGeometry.interpolate_normalized(distance)
-.. versionadded:: 1.5
-
Given a distance (float), returns the point (or closest point) within the
geometry (:class:`LineString` or :class:`MultiLineString`) at that distance.
The normalized version takes the distance as a float between 0 (origin) and 1
@@ -443,8 +433,6 @@ geometry and other.
.. method:: GEOSGeometry.project(point)
.. method:: GEOSGeometry.project_normalized(point)
-.. versionadded:: 1.5
-
Returns the distance (float) from the origin of the geometry
(:class:`LineString` or :class:`MultiLineString`) to the point projected on the
geometry (that is to a point of the line the closest to the given point).
diff --git a/docs/ref/contrib/messages.txt b/docs/ref/contrib/messages.txt
index 608c37bb7f..2014c7c183 100644
--- a/docs/ref/contrib/messages.txt
+++ b/docs/ref/contrib/messages.txt
@@ -54,20 +54,21 @@ Storage backends
The messages framework can use different backends to store temporary messages.
-Django provides three built-in storage classes:
+Django provides three built-in storage classes in
+:mod:`django.contrib.messages`:
-.. class:: django.contrib.messages.storage.session.SessionStorage
+.. class:: storage.session.SessionStorage
This class stores all messages inside of the request's session. Therefore
it requires Django's ``contrib.sessions`` application.
-.. class:: django.contrib.messages.storage.cookie.CookieStorage
+.. class:: storage.cookie.CookieStorage
This class stores the message data in a cookie (signed with a secret hash
to prevent manipulation) to persist notifications across requests. Old
messages are dropped if the cookie data size would exceed 2048 bytes.
-.. class:: django.contrib.messages.storage.fallback.FallbackStorage
+.. class:: storage.fallback.FallbackStorage
This class first uses ``CookieStorage``, and falls back to using
``SessionStorage`` for the messages that could not fit in a single cookie.
@@ -83,6 +84,8 @@ path, for example::
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
+.. class:: storage.base.BaseStorage
+
To write your own storage class, subclass the ``BaseStorage`` class in
``django.contrib.messages.storage.base`` and implement the ``_get`` and
``_store`` methods.
diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt
index f6480cae3a..fd2e917c1d 100644
--- a/docs/ref/contrib/sites.txt
+++ b/docs/ref/contrib/sites.txt
@@ -417,9 +417,10 @@ Here's how Django uses the sites framework:
:class:`~django.contrib.sites.models.Site` name to the template as
``{{ site_name }}``.
-* The shortcut view (``django.views.defaults.shortcut``) uses the domain
- of the current :class:`~django.contrib.sites.models.Site` object when
- calculating an object's URL.
+* The shortcut view (``django.contrib.contenttypes.views.shortcut``)
+ uses the domain of the current
+ :class:`~django.contrib.sites.models.Site` object when calculating
+ an object's URL.
* In the admin framework, the "view on site" link uses the current
:class:`~django.contrib.sites.models.Site` to work out the domain for the
diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt
index f96c2e8a1b..b9963414f4 100644
--- a/docs/ref/contrib/staticfiles.txt
+++ b/docs/ref/contrib/staticfiles.txt
@@ -255,8 +255,6 @@ CachedStaticFilesStorage
.. method:: file_hash(name, content=None)
- .. versionadded:: 1.5
-
The method that is used when creating the hashed name of a file.
Needs to return a hash for the given file name and content.
By default it calculates a MD5 hash from the content's chunks as
@@ -290,8 +288,6 @@ The previous example is equal to calling the ``url`` method of an instance of
useful when using a non-local storage backend to deploy files as documented
in :ref:`staticfiles-from-cdn`.
-.. versionadded:: 1.5
-
If you'd like to retrieve a static URL without displaying it, you can use a
slightly different call:
diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt
index 60153eb735..707184c3ac 100644
--- a/docs/ref/databases.txt
+++ b/docs/ref/databases.txt
@@ -191,10 +191,6 @@ Django supports MySQL 5.0.3 and higher.
`MySQL 5.0`_ adds the ``information_schema`` database, which contains detailed
data on all database schema. Django's ``inspectdb`` feature uses it.
-.. versionchanged:: 1.5
-
- The minimum version requirement of MySQL 5.0.3 was set in Django 1.5.
-
Django expects the database to support Unicode (UTF-8 encoding) and delegates to
it the task of enforcing transactions and referential integrity. It is important
to be aware of the fact that the two latter ones aren't actually enforced by
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 27b68e4f1c..1385648d5d 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -254,8 +254,6 @@ to flush.
``--no-initial-data``
~~~~~~~~~~~~~~~~~~~~~
-.. versionadded:: 1.5
-
Use ``--no-initial-data`` to avoid loading the initial_data fixture.
@@ -332,8 +330,6 @@ onto which the data will be loaded.
.. django-admin-option:: --ignorenonexistent
-.. versionadded:: 1.5
-
The :djadminopt:`--ignorenonexistent` option can be used to ignore fields that
may have been removed from models since the fixture was originally generated.
@@ -903,10 +899,6 @@ behavior you can use the ``--no-startup`` option. e.g.::
django-admin.py shell --plain --no-startup
-.. versionadded:: 1.5
-
- The ``--interface`` option was added in Django 1.5.
-
.. versionadded:: 1.6
The ``--no-startup`` option was added in Django 1.6.
@@ -1337,8 +1329,6 @@ clearsessions
.. django-admin:: clearsessions
-.. versionadded:: 1.5
-
Can be run as a cron job or directly to clean out expired sessions.
``django.contrib.sitemaps``
diff --git a/docs/ref/files/file.txt b/docs/ref/files/file.txt
index 7562f9b6bf..6ca7ec121a 100644
--- a/docs/ref/files/file.txt
+++ b/docs/ref/files/file.txt
@@ -100,10 +100,6 @@ The ``ContentFile`` Class
f1 = ContentFile("esta sentencia está en español")
f2 = ContentFile(b"these are bytes")
- .. versionchanged:: 1.5
-
- ContentFile also accepts Unicode strings.
-
.. currentmodule:: django.core.files.images
The ``ImageFile`` Class
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 780cb5d4f7..f084273cd9 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -211,11 +211,6 @@ only the valid fields::
>>> f.cleaned_data
{'cc_myself': True, 'message': u'Hi there'}
-.. versionchanged:: 1.5
-
-Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when
-the ``Form`` didn't validate.
-
``cleaned_data`` will always *only* contain a key for fields defined in the
``Form``, even if you pass extra data when you define the ``Form``. In this
example, we pass a bunch of extra fields to the ``ContactForm`` constructor,
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index e7c6612a72..0e4d352132 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -573,16 +573,12 @@ For each field, we describe the default widget used if you don't specify
.. attribute:: allow_files
- .. versionadded:: 1.5
-
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
whether files in the specified location should be included. Either this or
:attr:`allow_folders` must be ``True``.
.. attribute:: allow_folders
- .. versionadded:: 1.5
-
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
whether folders in the specified location should be included. Either this or
:attr:`allow_files` must be ``True``.
@@ -1065,11 +1061,6 @@ objects (in the case of ``ModelMultipleChoiceField``) into the
* Error message keys: ``required``, ``list``, ``invalid_choice``,
``invalid_pk_value``
- .. versionchanged:: 1.5
-
- The empty and normalized values were changed to be consistently
- ``QuerySets`` instead of ``[]`` and ``QuerySet`` respectively.
-
.. versionchanged:: 1.6
The ``invalid_choice`` message may contain ``%(value)s`` and the
diff --git a/docs/ref/forms/validation.txt b/docs/ref/forms/validation.txt
index 255b665c42..94e9308c9a 100644
--- a/docs/ref/forms/validation.txt
+++ b/docs/ref/forms/validation.txt
@@ -450,11 +450,5 @@ entries in ``_errors``.
Secondly, once we have decided that the combined data in the two fields we are
considering aren't valid, we must remember to remove them from the
-``cleaned_data``.
-
-.. versionchanged:: 1.5
-
- Django used to remove the ``cleaned_data`` attribute entirely if there were
- any errors in the form. Since version 1.5, ``cleaned_data`` is present even if
- the form doesn't validate, but it contains only field values that did
- validate.
+``cleaned_data``. `cleaned_data`` is present even if the form doesn't
+validate, but it contains only field values that did validate.
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index 080d1fea86..47e14043fc 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -525,11 +525,6 @@ Selector and checkbox widgets
A callable that takes the value of the CheckBoxInput and returns
``True`` if the checkbox should be checked for that value.
- .. versionchanged:: 1.5
-
- Exceptions from ``check_test`` used to be silenced by its caller,
- this is no longer the case, they will propagate upwards.
-
``Select``
~~~~~~~~~~
@@ -590,25 +585,26 @@ Selector and checkbox widgets
.. code-block:: html
<div class="myradio">
- <label><input type="radio" name="beatles" value="john" /> John</label>
+ <label for="id_beatles_0"><input id="id_beatles_0" name="beatles" type="radio" value="john" /> John</label>
</div>
<div class="myradio">
- <label><input type="radio" name="beatles" value="paul" /> Paul</label>
+ <label for="id_beatles_1"><input id="id_beatles_1" name="beatles" type="radio" value="paul" /> Paul</label>
</div>
<div class="myradio">
- <label><input type="radio" name="beatles" value="george" /> George</label>
+ <label for="id_beatles_2"><input id="id_beatles_2" name="beatles" type="radio" value="george" /> George</label>
</div>
<div class="myradio">
- <label><input type="radio" name="beatles" value="ringo" /> Ringo</label>
+ <label for="id_beatles_3"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" /> Ringo</label>
</div>
That included the ``<label>`` tags. To get more granular, you can use each
- radio button's ``tag`` and ``choice_label`` attributes. For example, this template...
+ radio button's ``tag``, ``choice_label`` and ``id_for_label`` attributes.
+ For example, this template...
.. code-block:: html+django
{% for radio in myform.beatles %}
- <label>
+ <label for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
<span class="radio">{{ radio.tag }}</span>
</label>
@@ -618,31 +614,41 @@ Selector and checkbox widgets
.. code-block:: html
- <label>
- John
- <span class="radio"><input type="radio" name="beatles" value="john" /></span>
- </label>
- <label>
- Paul
- <span class="radio"><input type="radio" name="beatles" value="paul" /></span>
- </label>
- <label>
- George
- <span class="radio"><input type="radio" name="beatles" value="george" /></span>
- </label>
- <label>
- Ringo
- <span class="radio"><input type="radio" name="beatles" value="ringo" /></span>
- </label>
+ <label for="id_beatles_0">
+ John
+ <span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" /></span>
+ </label>
+
+ <label for="id_beatles_1">
+ Paul
+ <span class="radio"><input id="id_beatles_1" name="beatles" type="radio" value="paul" /></span>
+ </label>
- If you decide not to loop over the radio buttons -- e.g., if your template simply includes
- ``{{ myform.beatles }}`` -- they'll be output in a ``<ul>`` with ``<li>`` tags, as above.
+ <label for="id_beatles_2">
+ George
+ <span class="radio"><input id="id_beatles_2" name="beatles" type="radio" value="george" /></span>
+ </label>
+
+ <label for="id_beatles_3">
+ Ringo
+ <span class="radio"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" /></span>
+ </label>
+
+ If you decide not to loop over the radio buttons -- e.g., if your template
+ simply includes ``{{ myform.beatles }}`` -- they'll be output in a ``<ul>``
+ with ``<li>`` tags, as above.
.. versionchanged:: 1.6
The outer ``<ul>`` container will now receive the ``id`` attribute defined on
the widget.
+.. versionchanged:: 1.7
+
+ When looping over the radio buttons, the ``label`` and ``input`` tags include
+ ``for`` and ``id`` attributes, respectively. Each radio button has an
+ ``id_for_label`` attribute to output the element's ID.
+
``CheckboxSelectMultiple``
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -666,6 +672,12 @@ the widget.
Like :class:`RadioSelect`, you can now loop over the individual checkboxes making
up the lists. See the documentation of :class:`RadioSelect` for more details.
+.. versionchanged:: 1.7
+
+ When looping over the checkboxes, the ``label`` and ``input`` tags include
+ ``for`` and ``id`` attributes, respectively. Each checkbox has an
+ ``id_for_label`` attribute to output the element's ID.
+
.. _file-upload-widgets:
File upload widgets
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 01215884c4..a9673ce3d2 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -757,21 +757,16 @@ directory on the filesystem. Has three special arguments, of which the first is
.. attribute:: FilePathField.allow_files
- .. versionadded:: 1.5
-
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
whether files in the specified location should be included. Either this or
:attr:`~FilePathField.allow_folders` must be ``True``.
.. attribute:: FilePathField.allow_folders
- .. versionadded:: 1.5
-
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
whether folders in the specified location should be included. Either this
or :attr:`~FilePathField.allow_files` must be ``True``.
-
Of course, these arguments can be used together.
The one potential gotcha is that :attr:`~FilePathField.match` applies to the
@@ -980,12 +975,6 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional
:attr:`~CharField.max_length` argument. If you don't specify
:attr:`~CharField.max_length`, a default of 200 is used.
-.. versionadded:: 1.5
-
- The current value of the field will be displayed as a clickable link above the
- input widget.
-
-
Relationship fields
===================
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index cb8570afdc..015393a408 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -388,8 +388,6 @@ For more details, see the documentation on :ref:`F() expressions
Specifying which fields to save
-------------------------------
-.. versionadded:: 1.5
-
If ``save()`` is passed a list of field names in keyword argument
``update_fields``, only the fields named in that list will be updated.
This may be desirable if you want to update just one or a few fields on
@@ -494,6 +492,40 @@ using ``__str__()`` like this::
# first_name and last_name will be unicode strings.
return force_bytes('%s %s' % (self.first_name, self.last_name))
+``__eq__``
+----------
+
+.. method:: Model.__eq__()
+
+The equality method is defined such that instances with the same primary
+key value and the same concrete class are considered equal. For proxy
+models, concrete class is defined as the model's first non-proxy parent;
+for all other models it is simply the model's class.
+
+For example::
+
+ form django.db import models
+
+ class MyModel(models.Model):
+ id = models.AutoField(primary_key=True)
+
+ class MyProxyModel(MyModel):
+ class Meta:
+ proxy = True
+
+ class MultitableInherited(MyModel):
+ pass
+
+ MyModel(id=1) == MyModel(id=1)
+ MyModel(id=1) == MyProxyModel(id=1)
+ MyModel(id=1) != MultitableInherited(id=1)
+ MyModel(id=1) != MyModel(id=2)
+
+.. versionchanged:: 1.7
+
+ In previous versions only instances of the exact same class and same
+ primary key value were considered equal.
+
``get_absolute_url``
--------------------
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index f49ba7a5c0..baa24f63cb 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -286,8 +286,6 @@ Django quotes column and table names behind the scenes.
.. attribute:: Options.index_together
- .. versionadded:: 1.5
-
Sets of field names that, taken together, are indexed::
index_together = [
diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt
index 0f08022179..910f7d94d5 100644
--- a/docs/ref/models/querysets.txt
+++ b/docs/ref/models/querysets.txt
@@ -913,7 +913,7 @@ needed by ``select_related``), it is able to detect that the ``best_pizza``
objects have already been fetched, and it will skip fetching them again.
Chaining ``prefetch_related`` calls will accumulate the lookups that are
-prefetched. To clear any ``prefetch_related`` behavior, pass `None` as a
+prefetched. To clear any ``prefetch_related`` behavior, pass ``None`` as a
parameter::
>>> non_prefetched = qs.prefetch_related(None)
@@ -1149,13 +1149,11 @@ to ``defer()``::
# Load all fields immediately.
my_queryset.defer(None)
-.. versionchanged:: 1.5
-
- Some fields in a model won't be deferred, even if you ask for them. You can
- never defer the loading of the primary key. If you are using
- :meth:`select_related()` to retrieve related models, you shouldn't defer the
- loading of the field that connects from the primary model to the related
- one, doing so will result in an error.
+Some fields in a model won't be deferred, even if you ask for them. You can
+never defer the loading of the primary key. If you are using
+:meth:`select_related()` to retrieve related models, you shouldn't defer the
+loading of the field that connects from the primary model to the related
+one, doing so will result in an error.
.. note::
@@ -1178,8 +1176,6 @@ to ``defer()``::
reader, is slightly faster and consumes a little less memory in the Python
process.
-.. versionchanged:: 1.5
-
.. note::
When calling :meth:`~django.db.models.Model.save()` for instances with
@@ -1227,16 +1223,14 @@ All of the cautions in the note for the :meth:`defer` documentation apply to
``only()`` as well. Use it cautiously and only after exhausting your other
options.
-.. versionchanged:: 1.5
-
- Using :meth:`only` and omitting a field requested using
- :meth:`select_related` is an error as well.
+Using :meth:`only` and omitting a field requested using :meth:`select_related`
+is an error as well.
- .. note::
+.. note::
- When calling :meth:`~django.db.models.Model.save()` for instances with
- deferred fields, only the loaded fields will be saved. See
- :meth:`~django.db.models.Model.save()` for more details.
+ When calling :meth:`~django.db.models.Model.save()` for instances with
+ deferred fields, only the loaded fields will be saved. See
+ :meth:`~django.db.models.Model.save()` for more details.
using
~~~~~
@@ -1567,10 +1561,6 @@ The ``batch_size`` parameter controls how many objects are created in single
query. The default is to create all objects in one batch, except for SQLite
where the default is such that at maximum 999 variables per query is used.
-.. versionadded:: 1.5
-
- The ``batch_size`` parameter was added in version 1.5.
-
count
~~~~~
@@ -1900,10 +1890,6 @@ methods on your models. It does, however, emit the
:data:`~django.db.models.signals.post_delete` signals for all deleted objects
(including cascaded deletions).
-.. versionadded:: 1.5
-
- Allow fast-path deletion of objects.
-
Django needs to fetch objects into memory to send signals and handle cascades.
However, if there are no cascades and no signals, then Django may take a
fast-path and delete objects without fetching into memory. For large
@@ -1911,7 +1897,7 @@ deletes this can result in significantly reduced memory usage. The amount of
executed queries can be reduced, too.
ForeignKeys which are set to :attr:`~django.db.models.ForeignKey.on_delete`
-DO_NOTHING do not prevent taking the fast-path in deletion.
+``DO_NOTHING`` do not prevent taking the fast-path in deletion.
Note that the queries generated in object deletion is an implementation
detail subject to change.
diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt
index c57a1470d6..a4ed6a2645 100644
--- a/docs/ref/request-response.txt
+++ b/docs/ref/request-response.txt
@@ -93,10 +93,6 @@ All attributes should be considered read-only, unless stated otherwise below.
non-form data posted in the request, access this through the
:attr:`HttpRequest.body` attribute instead.
- .. versionchanged:: 1.5
-
- Before Django 1.5, HttpRequest.POST contained non-form data.
-
It's possible that a request can come in via POST with an empty ``POST``
dictionary -- if, say, a form is requested via the POST HTTP method but
does not include form data. Therefore, you shouldn't use ``if request.POST``
@@ -196,8 +192,6 @@ All attributes should be considered read-only, unless stated otherwise below.
.. attribute:: HttpRequest.resolver_match
- .. versionadded:: 1.5
-
An instance of :class:`~django.core.urlresolvers.ResolverMatch` representing
the resolved url. This attribute is only set after url resolving took place,
which means it's available in all views but not in middleware methods which
@@ -824,8 +818,6 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in
StreamingHttpResponse objects
=============================
-.. versionadded:: 1.5
-
.. class:: StreamingHttpResponse
The :class:`StreamingHttpResponse` class is used to stream a response from
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 38d7275aed..424f7d5795 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1108,6 +1108,19 @@ Default: ``2621440`` (i.e. 2.5 MB).
The maximum size (in bytes) that an upload will be before it gets streamed to
the file system. See :doc:`/topics/files` for details.
+.. setting:: FILE_UPLOAD_DIRECTORY_PERMISSIONS
+
+FILE_UPLOAD_DIRECTORY_PERMISSIONS
+---------------------------------
+
+.. versionadded:: 1.7
+
+Default: ``None``
+
+The numeric mode to apply to directories created in the process of
+uploading files. This value mirrors the functionality and caveats of
+the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
+
.. setting:: FILE_UPLOAD_PERMISSIONS
FILE_UPLOAD_PERMISSIONS
@@ -1529,6 +1542,8 @@ unpredictable value.
:djadmin:`django-admin.py startproject <startproject>` automatically adds a
randomly-generated ``SECRET_KEY`` to each new project.
+Django will refuse to start if :setting:`SECRET_KEY` is not set.
+
.. warning::
**Keep this value secret.**
@@ -1537,10 +1552,6 @@ randomly-generated ``SECRET_KEY`` to each new project.
security protections, and can lead to privilege escalation and remote code
execution vulnerabilities.
-.. versionchanged:: 1.5
-
- Django will now refuse to start if :setting:`SECRET_KEY` is not set.
-
.. setting:: SECURE_PROXY_SSL_HEADER
SECURE_PROXY_SSL_HEADER
@@ -2095,13 +2106,9 @@ The URL where requests are redirected after login when the
This is used by the :func:`~django.contrib.auth.decorators.login_required`
decorator, for example.
-.. versionchanged:: 1.5
-
- This setting now also accepts view function names and
- :ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
- configuration duplication since you no longer have to define the URL in two
- places (``settings`` and URLconf).
- For backward compatibility reasons the default remains unchanged.
+This setting also accepts view function names and :ref:`named URL patterns
+<naming-url-patterns>` which can be used to reduce configuration duplication
+since you don't have to define the URL in two places (``settings`` and URLconf).
.. setting:: LOGIN_URL
@@ -2113,13 +2120,9 @@ Default: ``'/accounts/login/'``
The URL where requests are redirected for login, especially when using the
:func:`~django.contrib.auth.decorators.login_required` decorator.
-.. versionchanged:: 1.5
-
- This setting now also accepts view function names and
- :ref:`named URL patterns <naming-url-patterns>` which can be used to reduce
- configuration duplication since you no longer have to define the URL in two
- places (``settings`` and URLconf).
- For backward compatibility reasons the default remains unchanged.
+This setting also accepts view function names and :ref:`named URL patterns
+<naming-url-patterns>` which can be used to reduce configuration duplication
+since you don't have to define the URL in two places (``settings`` and URLconf).
.. setting:: LOGOUT_URL
@@ -2249,7 +2252,9 @@ Controls where Django stores message data. Valid values are:
See :ref:`message storage backends <message-storage-backends>` for more details.
-The backends that use cookies -- ``CookieStorage`` and ``FallbackStorage`` --
+The backends that use cookies --
+:class:`~django.contrib.messages.storage.cookie.CookieStorage` and
+:class:`~django.contrib.messages.storage.fallback.FallbackStorage` --
use the value of :setting:`SESSION_COOKIE_DOMAIN` when setting their cookies.
.. setting:: MESSAGE_TAGS
diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt
index ca1668000c..8381a49a09 100644
--- a/docs/ref/signals.txt
+++ b/docs/ref/signals.txt
@@ -118,8 +118,6 @@ Arguments sent with this signal:
``using``
The database alias being used.
-.. versionadded:: 1.5
-
``update_fields``
The set of fields to update explicitly specified in the ``save()`` method.
``None`` if this argument was not used in the ``save()`` call.
@@ -153,8 +151,6 @@ Arguments sent with this signal:
``using``
The database alias being used.
-.. versionadded:: 1.5
-
``update_fields``
The set of fields to update explicitly specified in the ``save()`` method.
``None`` if this argument was not used in the ``save()`` call.
@@ -522,14 +518,7 @@ request_finished
.. data:: django.core.signals.request_finished
:module:
-Sent when Django finishes processing an HTTP request.
-
-.. versionchanged:: 1.5
-
- Before Django 1.5, this signal was sent before delivering content to the
- client. In order to accommodate :ref:`streaming responses
- <httpresponse-streaming>`, it is now sent after the response has been fully
- delivered to the client.
+Sent when Django finishes delvering an HTTP response to the client.
.. note::
diff --git a/docs/ref/template-response.txt b/docs/ref/template-response.txt
index a8fb8dfee6..47c012e12a 100644
--- a/docs/ref/template-response.txt
+++ b/docs/ref/template-response.txt
@@ -75,16 +75,10 @@ Methods
The HTTP Status code for the response.
``content_type``
-
- .. versionchanged:: 1.5
-
- Historically, this parameter was only called ``mimetype`` (now
- deprecated), but since this is actually the value included in the HTTP
- ``Content-Type`` header, it can also include the character set
- encoding, which makes it more than just a MIME type specification. If
- ``content_type`` is specified, then its value is used. Otherwise,
- :setting:`DEFAULT_CONTENT_TYPE` is used.
-
+ The value included in the HTTP ``Content-Type`` header, including the
+ MIME type specification and the character set encoding. If
+ ``content_type`` is specified, then its value is used. Otherwise,
+ :setting:`DEFAULT_CONTENT_TYPE` is used.
.. method:: SimpleTemplateResponse.resolve_context(context)
@@ -167,13 +161,8 @@ Methods
The HTTP Status code for the response.
``content_type``
-
- .. versionchanged:: 1.5
-
- Historically, this parameter was only called ``mimetype`` (now
- deprecated), but since this is actually the value included in the HTTP
- ``Content-Type`` header, it can also include the character set
- encoding, which makes it more than just a MIME type specification. If
+ The value included in the HTTP ``Content-Type`` header, including the
+ MIME type specification and the character set encoding. If
``content_type`` is specified, then its value is used. Otherwise,
:setting:`DEFAULT_CONTENT_TYPE` is used.
diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt
index 87822fe7f8..05f8c249c5 100644
--- a/docs/ref/templates/api.txt
+++ b/docs/ref/templates/api.txt
@@ -274,11 +274,6 @@ Builtin variables
Every context contains ``True``, ``False`` and ``None``. As you would expect,
these variables resolve to the corresponding Python objects.
-.. versionadded:: 1.5
-
- Before Django 1.5, these variables weren't a special case, and they
- resolved to ``None`` unless you defined them in the context.
-
Playing with Context objects
----------------------------
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index afb6e4b123..5b02ab22d1 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -1059,22 +1059,14 @@ by the context as to the current application.
.. warning::
- Don't forget to put quotes around the function path or pattern name!
-
- .. versionchanged:: 1.5
-
- The first parameter used not to be quoted, which was inconsistent with
- other template tags. Since Django 1.5, it is evaluated according to
- the usual rules: it can be a quoted string or a variable that will be
- looked up in the context.
+ Don't forget to put quotes around the function path or pattern name,
+ otherwise the value will be interpreted as a context variable!
.. templatetag:: verbatim
verbatim
^^^^^^^^
-.. versionadded:: 1.5
-
Stops the template engine from rendering the contents of this block tag.
A common use is to allow a Javascript template layer that collides with
@@ -1108,6 +1100,14 @@ If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100, the
image in the above example will be 88 pixels wide
(because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
+.. versionchanged:: 1.7
+
+In some cases you might want to capture the result of ``widthratio`` in a
+variable. It can be useful, for instance, in a :ttag:`blocktrans` like this::
+
+ {% widthratio this_value max_value max_width as width %}
+ {% blocktrans %}The width is: {{ width }}{% endblocktrans %}
+
.. templatetag:: with
with
@@ -2403,8 +2403,6 @@ It is also able to consume standard context variables, e.g. assuming a
If you'd like to retrieve a static URL without displaying it, you can use a
slightly different call:
-.. versionadded:: 1.5
-
.. code-block:: html+django
{% load static %}
diff --git a/docs/ref/unicode.txt b/docs/ref/unicode.txt
index c9534c12c9..306a2dfb48 100644
--- a/docs/ref/unicode.txt
+++ b/docs/ref/unicode.txt
@@ -45,28 +45,26 @@ rendering or anywhere else -- you have two choices for encoding those strings.
You can use Unicode strings, or you can use normal strings (sometimes called
"bytestrings") that are encoded using UTF-8.
-.. versionchanged:: 1.5
+In Python 3, the logic is reversed, that is normal strings are Unicode, and
+when you want to specifically create a bytestring, you have to prefix the
+string with a 'b'. As we are doing in Django code from version 1.5,
+we recommend that you import ``unicode_literals`` from the __future__ library
+in your code. Then, when you specifically want to create a bytestring literal,
+prefix the string with 'b'.
- In Python 3, the logic is reversed, that is normal strings are Unicode, and
- when you want to specifically create a bytestring, you have to prefix the
- string with a 'b'. As we are doing in Django code from version 1.5,
- we recommend that you import ``unicode_literals`` from the __future__ library
- in your code. Then, when you specifically want to create a bytestring literal,
- prefix the string with 'b'.
+Python 2 legacy::
- Python 2 legacy::
+ my_string = "This is a bytestring"
+ my_unicode = u"This is an Unicode string"
- my_string = "This is a bytestring"
- my_unicode = u"This is an Unicode string"
+Python 2 with unicode literals or Python 3::
- Python 2 with unicode literals or Python 3::
-
- from __future__ import unicode_literals
+ from __future__ import unicode_literals
- my_string = b"This is a bytestring"
- my_unicode = "This is an Unicode string"
+ my_string = b"This is a bytestring"
+ my_unicode = "This is an Unicode string"
- See also :doc:`Python 3 compatibility </topics/python3>`.
+See also :doc:`Python 3 compatibility </topics/python3>`.
.. warning::
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index d31de35006..a093de18b7 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -204,8 +204,6 @@ The functions defined in this module share the following properties:
.. function:: smart_text(s, encoding='utf-8', strings_only=False, errors='strict')
- .. versionadded:: 1.5
-
Returns a text object representing ``s`` -- ``unicode`` on Python 2 and
``str`` on Python 3. Treats bytestrings using the ``encoding`` codec.
@@ -225,8 +223,6 @@ The functions defined in this module share the following properties:
.. function:: force_text(s, encoding='utf-8', strings_only=False, errors='strict')
- .. versionadded:: 1.5
-
Similar to ``smart_text``, except that lazy instances are resolved to
strings, rather than kept as lazy objects.
@@ -239,8 +235,6 @@ The functions defined in this module share the following properties:
.. function:: smart_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
- .. versionadded:: 1.5
-
Returns a bytestring version of ``s``, encoded as specified in
``encoding``.
@@ -249,8 +243,6 @@ The functions defined in this module share the following properties:
.. function:: force_bytes(s, encoding='utf-8', strings_only=False, errors='strict')
- .. versionadded:: 1.5
-
Similar to ``smart_bytes``, except that lazy instances are resolved to
bytestrings, rather than kept as lazy objects.
@@ -745,8 +737,6 @@ appropriate entities.
.. class:: SafeBytes
- .. versionadded:: 1.5
-
A ``bytes`` subclass that has been specifically marked as "safe"
(requires no further escaping) for HTML output purposes.
@@ -758,8 +748,6 @@ appropriate entities.
.. class:: SafeText
- .. versionadded:: 1.5
-
A ``str`` (in Python 3) or ``unicode`` (in Python 2) subclass
that has been specifically marked as "safe" for HTML output purposes.
@@ -977,8 +965,6 @@ For a complete discussion on the usage of the following see the
``None``, the :ref:`current time zone <default-current-time-zone>` is unset
on entry with :func:`deactivate()` instead.
-.. versionadded:: 1.5
-
.. function:: localtime(value, timezone=None)
Converts an aware :class:`~datetime.datetime` to a different time zone,
diff --git a/docs/releases/1.3.3.txt b/docs/releases/1.3.3.txt
new file mode 100644
index 0000000000..437cbfb412
--- /dev/null
+++ b/docs/releases/1.3.3.txt
@@ -0,0 +1,11 @@
+==========================
+Django 1.3.3 release notes
+==========================
+
+*August 1, 2012*
+
+Following Monday's security release of :doc:`Django 1.3.2 </releases/1.3.2>`,
+we began receiving reports that one of the fixes applied was breaking Python
+2.4 compatibility for Django 1.3. Since Python 2.4 is a supported Python
+version for that release series, this release fixes compatibility with
+Python 2.4.
diff --git a/docs/releases/1.3.4.txt b/docs/releases/1.3.4.txt
new file mode 100644
index 0000000000..3a174b3d44
--- /dev/null
+++ b/docs/releases/1.3.4.txt
@@ -0,0 +1,37 @@
+==========================
+Django 1.3.4 release notes
+==========================
+
+*October 17, 2012*
+
+This is the fourth release in the Django 1.3 series.
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Some attacks against this are beyond Django's ability to control, and
+require the web server to be properly configured; Django's documentation has
+for some time contained notes advising users on such configuration.
+
+Django's own built-in parsing of the Host header is, however, still vulnerable,
+as was reported to us recently. The Host header parsing in Django 1.3.3 and
+Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
+incorrectly handling username/password information in the header. Thus, for
+example, the following Host header would be accepted by Django when running on
+"validsite.com"::
+
+ Host: validsite.com:random@evilsite.com
+
+Using this, an attacker can cause parts of Django -- particularly the
+password-reset mechanism -- to generate and display arbitrary URLs to users.
+
+To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
+Host headers which contain potentially dangerous content (such as
+username/password pairs) now raise the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Details of this issue were initially posted online as a `security advisory`_.
+
+.. _security advisory: https://www.djangoproject.com/weblog/2012/oct/17/security/
diff --git a/docs/releases/1.3.5.txt b/docs/releases/1.3.5.txt
new file mode 100644
index 0000000000..65c403209d
--- /dev/null
+++ b/docs/releases/1.3.5.txt
@@ -0,0 +1,60 @@
+==========================
+Django 1.3.5 release notes
+==========================
+
+*December 10, 2012*
+
+Django 1.3.5 addresses two security issues present in previous Django releases
+in the 1.3 series.
+
+Please be aware that this security release is slightly different from previous
+ones. Both issues addressed here have been dealt with in prior security updates
+to Django. In one case, we have received ongoing reports of problems, and in
+the other we've chosen to take further steps to tighten up Django's code in
+response to independent discovery of potential problems from multiple sources.
+
+Host header poisoning
+---------------------
+
+Several earlier Django security releases focused on the issue of poisoning the
+HTTP Host header, causing Django to generate URLs pointing to arbitrary,
+potentially-malicious domains.
+
+In response to further input received and reports of continuing issues
+following the previous release, we're taking additional steps to tighten Host
+header validation. Rather than attempt to accommodate all features HTTP
+supports here, Django's Host header validation attempts to support a smaller,
+but far more common, subset:
+
+* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
+ ('.').
+* IP addresses -- both IPv4 and IPv6 -- are permitted.
+* Port, if specified, is numeric.
+
+Any deviation from this will now be rejected, raising the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Redirect poisoning
+------------------
+
+Also following up on a previous issue: in July of this year, we made changes to
+Django's HTTP redirect classes, performing additional validation of the scheme
+of the URL to redirect to (since, both within Django's own supplied
+applications and many third-party applications, accepting a user-supplied
+redirect target is a common pattern).
+
+Since then, two independent audits of the code turned up further potential
+problems. So, similar to the Host-header issue, we are taking steps to provide
+tighter validation in response to reported problems (primarily with third-party
+applications, but to a certain extent also within Django itself). This comes in
+two parts:
+
+1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
+function takes a URL and a hostname, and checks that the URL is either
+relative, or if absolute matches the supplied hostname. This function is
+intended for use whenever user-supplied redirect targets are accepted, to
+ensure that such redirects cannot lead to arbitrary third-party sites.
+
+2. All of Django's own built-in views -- primarily in the authentication system
+-- which allow user-supplied redirect targets now use ``is_safe_url`` to
+validate the supplied URL.
diff --git a/docs/releases/1.3.6.txt b/docs/releases/1.3.6.txt
new file mode 100644
index 0000000000..d55199a882
--- /dev/null
+++ b/docs/releases/1.3.6.txt
@@ -0,0 +1,78 @@
+==========================
+Django 1.3.6 release notes
+==========================
+
+*February 19, 2013*
+
+Django 1.3.6 fixes four security issues present in previous Django releases in
+the 1.3 series.
+
+This is the sixth bugfix/security release in the Django 1.3 series.
+
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Django's documentation has for some time contained notes advising users
+on how to configure webservers to ensure that only valid Host headers can reach
+the Django application. However, it has been reported to us that even with the
+recommended webserver configurations there are still techniques available for
+tricking many common webservers into supplying the application with an
+incorrect and possibly malicious Host header.
+
+For this reason, Django 1.3.6 adds a new setting, ``ALLOWED_HOSTS``, which
+should contain an explicit list of valid host/domain names for this site. A
+request with a Host header not matching an entry in this list will raise
+``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
+see the documentation for the :setting:`ALLOWED_HOSTS` setting.
+
+The default value for this setting in Django 1.3.6 is ``['*']`` (matching any
+host), for backwards-compatibility, but we strongly encourage all sites to set
+a more restrictive value.
+
+This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
+
+
+XML deserialization
+-------------------
+
+The XML parser in the Python standard library is vulnerable to a number of
+attacks via external entities and entity expansion. Django uses this parser for
+deserializing XML-formatted database fixtures. The fixture deserializer is not
+intended for use with untrusted data, but in order to err on the side of safety
+in Django 1.3.6 the XML deserializer refuses to parse an XML document with a
+DTD (DOCTYPE definition), which closes off these attack avenues.
+
+These issues in the Python standard library are CVE-2013-1664 and
+CVE-2013-1665. More information available `from the Python security team`_.
+
+Django's XML serializer does not create documents with a DTD, so this should
+not cause any issues with the typical round-trip from ``dumpdata`` to
+``loaddata``, but if you feed your own XML documents to the ``loaddata``
+management command, you will need to ensure they do not contain a DTD.
+
+.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
+
+
+Formset memory exhaustion
+-------------------------
+
+Previous versions of Django did not validate or limit the form-count data
+provided by the client in a formset's management form, making it possible to
+exhaust a server's available memory by forcing it to create very large numbers
+of forms.
+
+In Django 1.3.6, all formsets have a strictly-enforced maximum number of forms
+(1000 by default, though it can be set higher via the ``max_num`` formset
+factory argument).
+
+
+Admin history view information leakage
+--------------------------------------
+
+In previous versions of Django, an admin user without change permission on a
+model could still view the unicode representation of instances via their admin
+history log. Django 1.3.6 now limits the admin history log view for an object
+to users with change permission for that model.
diff --git a/docs/releases/1.3.7.txt b/docs/releases/1.3.7.txt
new file mode 100644
index 0000000000..3cccfcfb1c
--- /dev/null
+++ b/docs/releases/1.3.7.txt
@@ -0,0 +1,13 @@
+==========================
+Django 1.3.7 release notes
+==========================
+
+*February 20, 2013*
+
+Django 1.3.7 corrects a packaging problem with yesterday's :doc:`1.3.6 release
+</releases/1.3.6>`.
+
+The release contained stray ``.pyc`` files that caused "bad magic number"
+errors when running with some versions of Python. This releases corrects this,
+and also fixes a bad documentation link in the project template ``settings.py``
+file generated by ``manage.py startproject``.
diff --git a/docs/releases/1.4.2.txt b/docs/releases/1.4.2.txt
index 07eec39764..a6150f56c3 100644
--- a/docs/releases/1.4.2.txt
+++ b/docs/releases/1.4.2.txt
@@ -17,7 +17,7 @@ for some time contained notes advising users on such configuration.
Django's own built-in parsing of the Host header is, however, still vulnerable,
as was reported to us recently. The Host header parsing in Django 1.3.3 and
-Django 1.4.1 -- specifically, django.http.HttpRequest.get_host() -- was
+Django 1.4.1 -- specifically, ``django.http.HttpRequest.get_host()`` -- was
incorrectly handling username/password information in the header. Thus, for
example, the following Host header would be accepted by Django when running on
"validsite.com"::
@@ -27,9 +27,10 @@ example, the following Host header would be accepted by Django when running on
Using this, an attacker can cause parts of Django -- particularly the
password-reset mechanism -- to generate and display arbitrary URLs to users.
-To remedy this, the parsing in HttpRequest.get_host() is being modified; Host
-headers which contain potentially dangerous content (such as username/password
-pairs) now raise the exception django.core.exceptions.SuspiciousOperation
+To remedy this, the parsing in ``HttpRequest.get_host()`` is being modified;
+Host headers which contain potentially dangerous content (such as
+username/password pairs) now raise the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
Details of this issue were initially posted online as a `security advisory`_.
diff --git a/docs/releases/1.4.3.txt b/docs/releases/1.4.3.txt
new file mode 100644
index 0000000000..aadf623c3c
--- /dev/null
+++ b/docs/releases/1.4.3.txt
@@ -0,0 +1,60 @@
+==========================
+Django 1.4.3 release notes
+==========================
+
+*December 10, 2012*
+
+Django 1.4.3 addresses two security issues present in previous Django releases
+in the 1.4 series.
+
+Please be aware that this security release is slightly different from previous
+ones. Both issues addressed here have been dealt with in prior security updates
+to Django. In one case, we have received ongoing reports of problems, and in
+the other we've chosen to take further steps to tighten up Django's code in
+response to independent discovery of potential problems from multiple sources.
+
+Host header poisoning
+---------------------
+
+Several earlier Django security releases focused on the issue of poisoning the
+HTTP Host header, causing Django to generate URLs pointing to arbitrary,
+potentially-malicious domains.
+
+In response to further input received and reports of continuing issues
+following the previous release, we're taking additional steps to tighten Host
+header validation. Rather than attempt to accommodate all features HTTP
+supports here, Django's Host header validation attempts to support a smaller,
+but far more common, subset:
+
+* Hostnames must consist of characters [A-Za-z0-9] plus hyphen ('-') or dot
+ ('.').
+* IP addresses -- both IPv4 and IPv6 -- are permitted.
+* Port, if specified, is numeric.
+
+Any deviation from this will now be rejected, raising the exception
+:exc:`django.core.exceptions.SuspiciousOperation`.
+
+Redirect poisoning
+------------------
+
+Also following up on a previous issue: in July of this year, we made changes to
+Django's HTTP redirect classes, performing additional validation of the scheme
+of the URL to redirect to (since, both within Django's own supplied
+applications and many third-party applications, accepting a user-supplied
+redirect target is a common pattern).
+
+Since then, two independent audits of the code turned up further potential
+problems. So, similar to the Host-header issue, we are taking steps to provide
+tighter validation in response to reported problems (primarily with third-party
+applications, but to a certain extent also within Django itself). This comes in
+two parts:
+
+1. A new utility function, ``django.utils.http.is_safe_url``, is added; this
+function takes a URL and a hostname, and checks that the URL is either
+relative, or if absolute matches the supplied hostname. This function is
+intended for use whenever user-supplied redirect targets are accepted, to
+ensure that such redirects cannot lead to arbitrary third-party sites.
+
+2. All of Django's own built-in views -- primarily in the authentication system
+-- which allow user-supplied redirect targets now use ``is_safe_url`` to
+validate the supplied URL.
diff --git a/docs/releases/1.4.4.txt b/docs/releases/1.4.4.txt
new file mode 100644
index 0000000000..c5fcbc3e39
--- /dev/null
+++ b/docs/releases/1.4.4.txt
@@ -0,0 +1,88 @@
+==========================
+Django 1.4.4 release notes
+==========================
+
+*February 19, 2013*
+
+Django 1.4.4 fixes four security issues present in previous Django releases in
+the 1.4 series, as well as several other bugs and numerous documentation
+improvements.
+
+This is the fourth bugfix/security release in the Django 1.4 series.
+
+
+Host header poisoning
+---------------------
+
+Some parts of Django -- independent of end-user-written applications -- make
+use of full URLs, including domain name, which are generated from the HTTP Host
+header. Django's documentation has for some time contained notes advising users
+on how to configure webservers to ensure that only valid Host headers can reach
+the Django application. However, it has been reported to us that even with the
+recommended webserver configurations there are still techniques available for
+tricking many common webservers into supplying the application with an
+incorrect and possibly malicious Host header.
+
+For this reason, Django 1.4.4 adds a new setting, ``ALLOWED_HOSTS``, containing
+an explicit list of valid host/domain names for this site. A request with a
+Host header not matching an entry in this list will raise
+``SuspiciousOperation`` if ``request.get_host()`` is called. For full details
+see the documentation for the :setting:`ALLOWED_HOSTS` setting.
+
+The default value for this setting in Django 1.4.4 is ``['*']`` (matching any
+host), for backwards-compatibility, but we strongly encourage all sites to set
+a more restrictive value.
+
+This host validation is disabled when ``DEBUG`` is ``True`` or when running tests.
+
+
+XML deserialization
+-------------------
+
+The XML parser in the Python standard library is vulnerable to a number of
+attacks via external entities and entity expansion. Django uses this parser for
+deserializing XML-formatted database fixtures. This deserializer is not
+intended for use with untrusted data, but in order to err on the side of safety
+in Django 1.4.4 the XML deserializer refuses to parse an XML document with a
+DTD (DOCTYPE definition), which closes off these attack avenues.
+
+These issues in the Python standard library are CVE-2013-1664 and
+CVE-2013-1665. More information available `from the Python security team`_.
+
+Django's XML serializer does not create documents with a DTD, so this should
+not cause any issues with the typical round-trip from ``dumpdata`` to
+``loaddata``, but if you feed your own XML documents to the ``loaddata``
+management command, you will need to ensure they do not contain a DTD.
+
+.. _from the Python security team: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
+
+
+Formset memory exhaustion
+-------------------------
+
+Previous versions of Django did not validate or limit the form-count data
+provided by the client in a formset's management form, making it possible to
+exhaust a server's available memory by forcing it to create very large numbers
+of forms.
+
+In Django 1.4.4, all formsets have a strictly-enforced maximum number of forms
+(1000 by default, though it can be set higher via the ``max_num`` formset
+factory argument).
+
+
+Admin history view information leakage
+--------------------------------------
+
+In previous versions of Django, an admin user without change permission on a
+model could still view the unicode representation of instances via their admin
+history log. Django 1.4.4 now limits the admin history log view for an object
+to users with change permission for that model.
+
+
+Other bugfixes and changes
+==========================
+
+* Prevented transaction state from leaking from one request to the next (#19707).
+* Changed a SQL command syntax to be MySQL 4 compatible (#19702).
+* Added backwards-compatibility with old unsalted MD5 passwords (#18144).
+* Numerous documentation improvements and fixes.
diff --git a/docs/releases/1.4.5.txt b/docs/releases/1.4.5.txt
new file mode 100644
index 0000000000..9ba5235f79
--- /dev/null
+++ b/docs/releases/1.4.5.txt
@@ -0,0 +1,13 @@
+==========================
+Django 1.4.5 release notes
+==========================
+
+*February 20, 2013*
+
+Django 1.4.5 corrects a packaging problem with yesterday's :doc:`1.4.4 release
+</releases/1.4.4>`.
+
+The release contained stray ``.pyc`` files that caused "bad magic number"
+errors when running with some versions of Python. This releases corrects this,
+and also fixes a bad documentation link in the project template ``settings.py``
+file generated by ``manage.py startproject``.
diff --git a/docs/releases/1.4.6.txt b/docs/releases/1.4.6.txt
new file mode 100644
index 0000000000..575e9fa75a
--- /dev/null
+++ b/docs/releases/1.4.6.txt
@@ -0,0 +1,31 @@
+==========================
+Django 1.4.6 release notes
+==========================
+
+*August 13, 2013*
+
+Django 1.4.6 fixes one security issue present in previous Django releases in
+the 1.4 series, as well as one other bug.
+
+This is the sixth bugfix/security release in the Django 1.4 series.
+
+Mitigated possible XSS attack via user-supplied redirect URLs
+-------------------------------------------------------------
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
+:doc:`i18n </topics/i18n/index>`) to redirect the user to an "on success" URL.
+The security checks for these redirects (namely
+``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
+and as such allowed ``javascript:...`` URLs to be entered. If a developer
+relied on ``is_safe_url()`` to provide safe redirect targets and put such a
+URL into a link, he could suffer from a XSS attack. This bug doesn't affect
+Django currently, since we only put this URL into the ``Location`` response
+header and browsers seem to ignore JavaScript there.
+
+Bugfixes
+========
+
+* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
+ decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
+ '_original_allowed_hosts'`` exception, it's probably fixed (#20636).
diff --git a/docs/releases/1.5.2.txt b/docs/releases/1.5.2.txt
new file mode 100644
index 0000000000..710f16555c
--- /dev/null
+++ b/docs/releases/1.5.2.txt
@@ -0,0 +1,62 @@
+==========================
+Django 1.5.2 release notes
+==========================
+
+*August 13, 2013*
+
+This is Django 1.5.2, a bugfix and security release for Django 1.5.
+
+Mitigated possible XSS attack via user-supplied redirect URLs
+-------------------------------------------------------------
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login`, :mod:`django.contrib.comments`, and
+:doc:`i18n </topics/i18n/index>`) to redirect the user to an "on success" URL.
+The security checks for these redirects (namely
+``django.util.http.is_safe_url()``) didn't check if the scheme is ``http(s)``
+and as such allowed ``javascript:...`` URLs to be entered. If a developer
+relied on ``is_safe_url()`` to provide safe redirect targets and put such a
+URL into a link, he could suffer from a XSS attack. This bug doesn't affect
+Django currently, since we only put this URL into the ``Location`` response
+header and browsers seem to ignore JavaScript there.
+
+XSS vulnerability in :mod:`django.contrib.admin`
+------------------------------------------------
+
+If a :class:`~django.db.models.URLField` is used in Django 1.5, it displays the
+current value of the field and a link to the target on the admin change page.
+The display routine of this widget was flawed and allowed for XSS.
+
+Bugfixes
+========
+
+* Fixed a crash with :meth:`~django.db.models.query.QuerySet.prefetch_related`
+ (#19607) as well as some ``pickle`` regressions with ``prefetch_related``
+ (#20157 and #20257).
+* Fixed a regression in :mod:`django.contrib.gis` in the Google Map output on
+ Python 3 (#20773).
+* Made ``DjangoTestSuiteRunner.setup_databases`` properly handle aliases for
+ the default database (#19940) and prevented ``teardown_databases`` from
+ attempting to tear down aliases (#20681).
+* Fixed the ``django.core.cache.backends.memcached.MemcachedCache`` backend's
+ ``get_many()`` method on Python 3 (#20722).
+* Fixed :mod:`django.contrib.humanize` translation syntax errors. Affected
+ languages: Mexican Spanish, Mongolian, Romanian, Turkish (#20695).
+* Added support for wheel packages (#19252).
+* The CSRF token now rotates when a user logs in.
+* Some Python 3 compatibility fixes including #20212 and #20025.
+* Fixed some rare cases where :meth:`~django.db.models.query.QuerySet.get`
+ exceptions recursed infinitely (#20278).
+* :djadmin:`makemessages` no longer crashes with ``UnicodeDecodeError``
+ (#20354).
+* Fixed ``geojson`` detection with Spatialite.
+* :meth:`~django.test.SimpleTestCase.assertContains` once again works with
+ binary content (#20237).
+* Fixed :class:`~django.db.models.ManyToManyField` if it has a unicode ``name``
+ parameter (#20207).
+* Ensured that the WSGI request's path is correctly based on the
+ ``SCRIPT_NAME`` environment variable or the :setting:`FORCE_SCRIPT_NAME`
+ setting, regardless of whether or not either has a trailing slash (#20169).
+* Fixed an obscure bug with the :func:`~django.test.utils.override_settings`
+ decorator. If you hit an ``AttributeError: 'Settings' object has no attribute
+ '_original_allowed_hosts'`` exception, it's probably fixed (#20636).
diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
index a4b03bc197..16d45de45f 100644
--- a/docs/releases/1.5.txt
+++ b/docs/releases/1.5.txt
@@ -2,6 +2,8 @@
Django 1.5 release notes
========================
+*February 26, 2013*
+
Welcome to Django 1.5!
These release notes cover the `new features`_, as well
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 3b1fe0c40e..a7b26c07fc 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -130,7 +130,7 @@ Minor features
context level.
* The :class:`~django.utils.feedgenerator.Atom1Feed` syndication feed's
- ``updated`` element now utilizes `updateddate` instead of ``pubdate``,
+ ``updated`` element now utilizes ``updateddate`` instead of ``pubdate``,
allowing the ``published`` element to be included in the feed (which
relies on ``pubdate``).
@@ -190,6 +190,30 @@ Minor features
``Meta`` option allows you to customize (or disable) creation of the default
add, change, and delete permissions.
+* The :func:`~django.contrib.auth.decorators.permission_required` decorator can
+ take a list of permissions as well as a single permission.
+
+* The new :setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS` setting controls
+ the file system permissions of directories created during file upload, like
+ :setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves.
+
+* Explicit :class:`~django.db.models.OneToOneField` for
+ :ref:`multi-table-inheritance` are now discovered in abstract classes.
+
+* The ``<label>`` and ``<input>`` tags rendered by
+ :class:`~django.forms.RadioSelect` and
+ :class:`~django.forms.CheckboxSelectMultiple` when looping over the radio
+ buttons or checkboxes now include ``for`` and ``id`` attributes, respectively.
+ Each radio button or checkbox includes an ``id_for_label`` attribute to
+ output the element's ID.
+
+* Any ``**kwargs`` passed to
+ :meth:`~django.contrib.auth.models.User.email_user()` are passed to the
+ underlying :meth:`~django.core.mail.send_mail()` call.
+
+* The :ttag:`widthratio` template tag now accepts an "as" parameter to capture
+ the result in a variable.
+
Backwards incompatible changes in 1.7
=====================================
@@ -237,6 +261,11 @@ Miscellaneous
removes the ability for visitors to generate spurious HTTP 500 errors by
requesting static files that don't exist or haven't been collected yet.
+* The :meth:`django.db.models.Model.__eq__` method is now defined in a
+ way where instances of a proxy model and its base model are considered
+ equal when primary keys match. Previously only instances of exact same
+ class were considered equal on primary key match.
+
Features deprecated in 1.7
==========================
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 39439ff9aa..c3ba5cf478 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -36,6 +36,7 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.5.2
1.5.1
1.5
@@ -44,6 +45,10 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.4.6
+ 1.4.5
+ 1.4.4
+ 1.4.3
1.4.2
1.4.1
1.4
@@ -53,6 +58,11 @@ Final releases
.. toctree::
:maxdepth: 1
+ 1.3.7
+ 1.3.6
+ 1.3.5
+ 1.3.4
+ 1.3.3
1.3.2
1.3.1
1.3
diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt
index e92e775bda..746e92e637 100644
--- a/docs/topics/auth/customizing.txt
+++ b/docs/topics/auth/customizing.txt
@@ -359,8 +359,6 @@ the extra database load.
Substituting a custom User model
================================
-.. versionadded:: 1.5
-
Some kinds of projects may have authentication requirements for which Django's
built-in :class:`~django.contrib.auth.models.User` model is not always
appropriate. For instance, on some sites it makes more sense to use an email
@@ -684,13 +682,13 @@ auth views.
* :class:`~django.contrib.auth.forms.AuthenticationForm`
Works with any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`,
- and will adapt to use the field defined in `USERNAME_FIELD`.
+ and will adapt to use the field defined in ``USERNAME_FIELD``.
* :class:`~django.contrib.auth.forms.PasswordResetForm`
Assumes that the user model has an integer primary key, has a field named
``email`` that can be used to identify the user, and a boolean field
- named `is_active` to prevent password resets for inactive users.
+ named ``is_active`` to prevent password resets for inactive users.
* :class:`~django.contrib.auth.forms.SetPasswordForm`
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index 902adc9e32..4d86a7330e 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -434,12 +434,10 @@ The login_required decorator
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
- .. versionchanged:: 1.5
-
- The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts
- view function names and :ref:`named URL patterns <naming-url-patterns>`.
- This allows you to freely remap your login view within your URLconf
- without having to update the setting.
+ The :setting:`settings.LOGIN_URL <LOGIN_URL>` also accepts view function
+ names and :ref:`named URL patterns <naming-url-patterns>`. This allows you
+ to freely remap your login view within your URLconf without having to
+ update the setting.
.. note::
@@ -528,6 +526,11 @@ The permission_required decorator
(HTTP Forbidden) view<http_forbidden_view>` instead of redirecting to the
login page.
+ .. versionchanged:: 1.7
+
+ The :func:`~django.contrib.auth.decorators.permission_required`
+ decorator can take a list of permissions as well as a single permission.
+
Applying permissions to generic views
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1182,10 +1185,6 @@ Thus, you can check permissions in template ``{% if %}`` statements:
<p>You don't have permission to do anything in the foo app.</p>
{% endif %}
-.. versionadded:: 1.5
-
- Permission lookup by "if in".
-
It is possible to also look permissions up by ``{% if in %}`` statements.
For example:
diff --git a/docs/topics/class-based-views/intro.txt b/docs/topics/class-based-views/intro.txt
index a65b887921..5986ff2ea7 100644
--- a/docs/topics/class-based-views/intro.txt
+++ b/docs/topics/class-based-views/intro.txt
@@ -198,6 +198,7 @@ A similar class-based view might look like::
from django.http import HttpResponseRedirect
from django.shortcuts import render
+ from django.views.generic.base import View
from .forms import MyForm
diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt
index fdd5d42c5d..fb0b388b57 100644
--- a/docs/topics/class-based-views/mixins.txt
+++ b/docs/topics/class-based-views/mixins.txt
@@ -55,8 +55,6 @@ interface to working with templates in class-based views.
override this to provide more flexible defaults when dealing with actual
objects.
-.. versionadded:: 1.5
-
:class:`~django.views.generic.base.ContextMixin`
Every built in view which needs context data, such as for rendering a
template (including ``TemplateResponseMixin`` above), should call
diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt
index 2d3f35db8b..c97149cfd1 100644
--- a/docs/topics/db/managers.txt
+++ b/docs/topics/db/managers.txt
@@ -265,8 +265,8 @@ Methods are copied according to the following rules:
- Public methods are copied by default.
- Private methods (starting with an underscore) are not copied by default.
-- Methods with a `queryset_only` attribute set to `False` are always copied.
-- Methods with a `queryset_only` attribute set to `True` are never copied.
+- Methods with a ``queryset_only`` attribute set to ``False`` are always copied.
+- Methods with a ``queryset_only`` attribute set to ``True`` are never copied.
For example::
diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt
index 6e19844b5c..c098aa33e3 100644
--- a/docs/topics/db/multi-db.txt
+++ b/docs/topics/db/multi-db.txt
@@ -689,11 +689,6 @@ In addition, some objects are automatically created just after
- three ``Permission`` for each model (including those not stored in that
database).
-.. versionchanged:: 1.5
-
- Previously, ``ContentType`` and ``Permission`` instances were created only
- in the default database.
-
For common setups with multiple databases, it isn't useful to have these
objects in more than one database. Common setups include master / slave and
connecting to external databases. Therefore, it's recommended:
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index 9a0d0ce6b9..30335f266d 100644
--- a/docs/topics/db/queries.txt
+++ b/docs/topics/db/queries.txt
@@ -639,20 +639,11 @@ that were modified more than 3 days after they were published::
>>> from datetime import timedelta
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
-.. versionadded:: 1.5
-
- ``.bitand()`` and ``.bitor()``
-
-The ``F()`` objects now support bitwise operations by ``.bitand()`` and
+The ``F()`` objects support bitwise operations by ``.bitand()`` and
``.bitor()``, for example::
>>> F('somefield').bitand(16)
-.. versionchanged:: 1.5
-
- The previously undocumented operators ``&`` and ``|`` no longer produce
- bitwise operations, use ``.bitand()`` and ``.bitor()`` instead.
-
The pk lookup shortcut
----------------------
diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt
index 2cdab9ea9b..d88524ee20 100644
--- a/docs/topics/http/file-uploads.txt
+++ b/docs/topics/http/file-uploads.txt
@@ -132,7 +132,7 @@ upload behavior.
Changing upload handler behavior
--------------------------------
-Three settings control Django's file upload behavior:
+There are a few settings which control Django's file upload behavior:
:setting:`FILE_UPLOAD_MAX_MEMORY_SIZE`
The maximum size, in bytes, for files that will be uploaded into memory.
@@ -167,6 +167,11 @@ Three settings control Django's file upload behavior:
**Always prefix the mode with a 0.**
+:setting:`FILE_UPLOAD_DIRECTORY_PERMISSIONS`
+ The numeric mode to apply to directories created in the process of
+ uploading files. This value mirrors the functionality and caveats of
+ the :setting:`FILE_UPLOAD_PERMISSIONS` setting.
+
:setting:`FILE_UPLOAD_HANDLERS`
The actual handlers for uploaded files. Changing this setting allows
complete customization -- even replacement -- of Django's upload
diff --git a/docs/topics/http/middleware.txt b/docs/topics/http/middleware.txt
index 6bb7ccb8f8..da3f7ce4c1 100644
--- a/docs/topics/http/middleware.txt
+++ b/docs/topics/http/middleware.txt
@@ -204,11 +204,6 @@ reverse order, from the bottom up. This means classes defined at the end of
Dealing with streaming responses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-.. versionchanged:: 1.5
-
- ``response`` may also be an :class:`~django.http.StreamingHttpResponse`
- object.
-
Unlike :class:`~django.http.HttpResponse`,
:class:`~django.http.StreamingHttpResponse` does not have a ``content``
attribute. As a result, middleware can no longer assume that all responses
diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt
index 3ee7af9539..6ac17ccbd8 100644
--- a/docs/topics/http/sessions.txt
+++ b/docs/topics/http/sessions.txt
@@ -70,10 +70,6 @@ If you have multiple caches defined in :setting:`CACHES`, Django will use the
default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the
name of that cache.
-.. versionchanged:: 1.5
-
- The :setting:`SESSION_CACHE_ALIAS` setting was added.
-
Once your cache is configured, you've got two choices for how to store data in
the cache:
@@ -302,8 +298,6 @@ You can edit it multiple times.
.. method:: SessionBase.clear_expired
- .. versionadded:: 1.5
-
Removes expired sessions from the session store. This class method is
called by :djadmin:`clearsessions`.
@@ -469,9 +463,7 @@ cookie will be sent on every request.
Similarly, the ``expires`` part of a session cookie is updated each time the
session cookie is sent.
-.. versionchanged:: 1.5
-
- The session is not saved if the response's status code is 500.
+The session is not saved if the response's status code is 500.
.. _browser-length-vs-persistent-sessions:
diff --git a/docs/topics/http/shortcuts.txt b/docs/topics/http/shortcuts.txt
index 5c8725172a..8a80affb2a 100644
--- a/docs/topics/http/shortcuts.txt
+++ b/docs/topics/http/shortcuts.txt
@@ -21,7 +21,7 @@ introduce controlled coupling for convenience's sake.
:class:`~django.http.HttpResponse` object with that rendered text.
:func:`render()` is the same as a call to
- :func:`render_to_response()` with a `context_instance` argument that
+ :func:`render_to_response()` with a ``context_instance`` argument that
forces the use of a :class:`~django.template.RequestContext`.
Required arguments
@@ -50,10 +50,6 @@ Optional arguments
The MIME type to use for the resulting document. Defaults to the value of
the :setting:`DEFAULT_CONTENT_TYPE` setting.
- .. versionchanged:: 1.5
-
- This parameter used to be called ``mimetype``.
-
``status``
The status code for the response. Defaults to ``200``.
@@ -129,11 +125,6 @@ Optional arguments
The MIME type to use for the resulting document. Defaults to the value of
the :setting:`DEFAULT_CONTENT_TYPE` setting.
- .. versionchanged:: 1.5
-
- This parameter used to be called ``mimetype``.
-
-
Example
-------
@@ -169,7 +160,8 @@ This example is equivalent to::
The arguments could be:
- * A model: the model's `get_absolute_url()` function will be called.
+ * A model: the model's `:meth:`~django.db.models.Model.get_absolute_url()`
+ function will be called.
* A view name, possibly with arguments: :func:`urlresolvers.reverse
<django.core.urlresolvers.reverse>` will be used to reverse-resolve the
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index e724837ce1..6436e7dcf9 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -1031,8 +1031,6 @@ in django-statici18n_.
Internationalization: in URL patterns
=====================================
-.. versionadded:: 1.4
-
.. module:: django.conf.urls.i18n
Django provides two mechanisms to internationalize URL patterns:
diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt
index 66c517d615..21eac6e120 100644
--- a/docs/topics/logging.txt
+++ b/docs/topics/logging.txt
@@ -581,8 +581,6 @@ logging module.
.. class:: RequireDebugTrue()
- .. versionadded:: 1.5
-
This filter is similar to :class:`RequireDebugFalse`, except that records are
passed only when :setting:`DEBUG` is ``True``.
@@ -599,10 +597,5 @@ All messages reaching the ``django`` catch-all logger when :setting:`DEBUG` is
``True`` are sent to the console. They are simply discarded (sent to
``NullHandler``) when :setting:`DEBUG` is ``False``.
-.. versionchanged:: 1.5
-
- Before Django 1.5, all messages reaching the ``django`` logger were
- discarded, regardless of :setting:`DEBUG`.
-
See also :ref:`Configuring logging <configuring-logging>` to learn how you can
complement or replace this default logging configuration.
diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt
index 9da71563c3..f3c8e35c25 100644
--- a/docs/topics/pagination.txt
+++ b/docs/topics/pagination.txt
@@ -248,19 +248,13 @@ Methods
.. method:: Page.next_page_number()
- Returns the next page number.
-
- .. versionchanged:: 1.5
-
- Raises :exc:`InvalidPage` if next page doesn't exist.
+ Returns the next page number. Raises :exc:`InvalidPage` if next page
+ doesn't exist.
.. method:: Page.previous_page_number()
- Returns the previous page number.
-
- .. versionchanged:: 1.5
-
- Raises :exc:`InvalidPage` if previous page doesn't exist.
+ Returns the previous page number. Raises :exc:`InvalidPage` if previous
+ page doesn't exist.
.. method:: Page.start_index()
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index d3fda16479..0f24715599 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -141,14 +141,11 @@ sure that they are "appropriate" for saving before doing so. Of course, if you
trust your data source you could just save the object and move on.
The Django object itself can be inspected as ``deserialized_object.object``.
+If fields in the serialized data do not exist on a model, a
+``DeserializationError`` will be raised unless the ``ignorenonexistent``
+argument is passed in as ``True``::
-.. versionadded:: 1.5
-
- If fields in the serialized data do not exist on a model,
- a ``DeserializationError`` will be raised unless the ``ignorenonexistent``
- argument is passed in as True::
-
- serializers.deserialize("xml", data, ignorenonexistent=True)
+ serializers.deserialize("xml", data, ignorenonexistent=True)
.. _serialization-formats:
diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt
index a97fb2f14f..587590096c 100644
--- a/docs/topics/signals.txt
+++ b/docs/topics/signals.txt
@@ -132,10 +132,6 @@ Now, our ``my_callback`` function will be called each time a request finishes.
Note that ``receiver`` can also take a list of signals to connect a function
to.
-.. versionchanged:: 1.5
-
- The ability to pass a list of signals was added.
-
.. admonition:: Where should this code live?
You can put signal handling and registration code anywhere you like.
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index 99af951866..d8d59c6872 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -217,8 +217,6 @@ Advanced features of ``TransactionTestCase``
.. attribute:: TransactionTestCase.reset_sequences
- .. versionadded:: 1.5
-
Setting ``reset_sequences = True`` on a ``TransactionTestCase`` will make
sure sequences are always reset before the test run::
diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt
index 457d10eec0..dc12244d72 100644
--- a/docs/topics/testing/overview.txt
+++ b/docs/topics/testing/overview.txt
@@ -219,12 +219,6 @@ the Django test runner reorders tests in the following way:
* Then any other tests (e.g. doctests) that may alter the database without
restoring it to its original state are run.
-.. versionchanged:: 1.5
-
- Before Django 1.5, the only guarantee was that
- :class:`~django.test.TestCase` tests were always ran first, before any other
- tests.
-
.. note::
The new ordering of tests may reveal unexpected dependencies on test case
@@ -587,11 +581,6 @@ Use the ``django.test.client.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``.
- .. versionchanged:: 1.5
-
- :meth:`Client.options` used to process ``data`` like
- :meth:`Client.get`.
-
The ``follow`` and ``extra`` arguments act the same as for
:meth:`Client.get`.
@@ -603,11 +592,6 @@ Use the ``django.test.client.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``.
- .. versionchanged:: 1.5
-
- :meth:`Client.put` used to process ``data`` like
- :meth:`Client.post`.
-
The ``follow`` and ``extra`` arguments act the same as for
:meth:`Client.get`.
@@ -627,15 +611,9 @@ Use the ``django.test.client.Client`` class to make requests.
When ``data`` is provided, it is used as the request body, and
a ``Content-Type`` header is set to ``content_type``.
- .. versionchanged:: 1.5
-
- :meth:`Client.delete` used to process ``data`` like
- :meth:`Client.get`.
-
The ``follow`` and ``extra`` arguments act the same as for
:meth:`Client.get`.
-
.. method:: Client.login(**credentials)
If your site uses Django's :doc:`authentication system</topics/auth/index>`
@@ -931,25 +909,6 @@ to test the effects of commit and rollback:
database. This can cause your tests to pass or fail unexpectedly. Always
use ``TransactionTestCase`` when testing transactional behavior.
-.. versionchanged:: 1.5
-
- Prior to 1.5, :class:`~django.test.TransactionTestCase` flushed the
- database tables *before* each test. In Django 1.5, this is instead done
- *after* the test has been run.
-
- When the flush took place before the test, it was guaranteed that primary
- key values started at one in :class:`~django.test.TransactionTestCase`
- tests.
-
- Tests should not depend on this behavior, but for legacy tests that do,
- the :attr:`~TransactionTestCase.reset_sequences` attribute can be used
- until the test has been properly updated.
-
-.. versionchanged:: 1.5
-
- The order in which tests are run has changed. See `Order in which tests are
- executed`_.
-
``TransactionTestCase`` inherits from :class:`~django.test.SimpleTestCase`.
TestCase
@@ -975,11 +934,6 @@ additions, including:
* Django-specific assertions for testing for things like redirection and form
errors.
-.. versionchanged:: 1.5
-
- The order in which tests are run has changed. See `Order in which tests are
- executed`_.
-
``TestCase`` inherits from :class:`~django.test.TransactionTestCase`.
.. _live-test-server:
@@ -1636,8 +1590,6 @@ your test suite.
.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None)
- .. versionadded:: 1.5
-
Asserts that the strings ``xml1`` and ``xml2`` are equal. The
comparison is based on XML semantics. Similarly to
:meth:`~SimpleTestCase.assertHTMLEqual`, the comparison is
@@ -1649,8 +1601,6 @@ your test suite.
.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None)
- .. versionadded:: 1.5
-
Asserts that the strings ``xml1`` and ``xml2`` are *not* equal. The
comparison is based on XML semantics. See
:meth:`~SimpleTestCase.assertXMLEqual` for details.
@@ -1659,8 +1609,6 @@ your test suite.
.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='')
- .. versionadded:: 1.5
-
Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one.
If the ``count`` integer argument is specified, then additionally the number
@@ -1671,8 +1619,6 @@ your test suite.
.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None)
- .. versionadded:: 1.5
-
Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal.
Usual JSON non-significant whitespace rules apply as the heavyweight is
delegated to the :mod:`json` library.
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index df8ced949e..193b64505c 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -29,7 +29,7 @@ from .models import (Article, Chapter, Account, Media, Child, Parent, Picture,
Album, Question, Answer, ComplexSortedPerson, PluggableSearchPerson, PrePopulatedPostLargeSlug,
AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod,
AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated,
- RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice,
+ RelatedPrepopulated, UndeletableObject, UnchangeableObject, UserMessenger, Simple, Choice,
ShortMessage, Telegram)
@@ -656,6 +656,13 @@ class UndeletableObjectAdmin(admin.ModelAdmin):
return super(UndeletableObjectAdmin, self).change_view(*args, **kwargs)
+class UnchangeableObjectAdmin(admin.ModelAdmin):
+ def get_urls(self):
+ # Disable change_view, but leave other urls untouched
+ urlpatterns = super(UnchangeableObjectAdmin, self).get_urls()
+ return [p for p in urlpatterns if not p.name.endswith("_change")]
+
+
def callable_on_unknown(obj):
return obj.unknown
@@ -741,6 +748,7 @@ site.register(Report, ReportAdmin)
site.register(MainPrepopulated, MainPrepopulatedAdmin)
site.register(UnorderedObject, UnorderedObjectAdmin)
site.register(UndeletableObject, UndeletableObjectAdmin)
+site.register(UnchangeableObject, UnchangeableObjectAdmin)
# We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2.
# That way we cover all four cases:
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index a1131210d7..1accf46819 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -115,7 +115,7 @@ class ModelWithStringPrimaryKey(models.Model):
@python_2_unicode_compatible
class Color(models.Model):
value = models.CharField(max_length=10)
- warm = models.BooleanField()
+ warm = models.BooleanField(default=False)
def __str__(self):
return self.value
@@ -144,7 +144,7 @@ class Actor(models.Model):
@python_2_unicode_compatible
class Inquisition(models.Model):
- expected = models.BooleanField()
+ expected = models.BooleanField(default=False)
leader = models.ForeignKey(Actor)
country = models.CharField(max_length=20)
@@ -376,7 +376,7 @@ class Link(models.Model):
class PrePopulatedPost(models.Model):
title = models.CharField(max_length=100)
- published = models.BooleanField()
+ published = models.BooleanField(default=False)
slug = models.SlugField()
@@ -607,7 +607,7 @@ class PrePopulatedPostLargeSlug(models.Model):
the javascript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
"""
title = models.CharField(max_length=100)
- published = models.BooleanField()
+ published = models.BooleanField(default=False)
slug = models.SlugField(max_length=1000)
class AdminOrderedField(models.Model):
@@ -674,6 +674,12 @@ class UndeletableObject(models.Model):
"""
name = models.CharField(max_length=255)
+class UnchangeableObject(models.Model):
+ """
+ Model whose change_view is disabled in admin
+ Refs #20640.
+ """
+
class UserMessenger(models.Model):
"""
Dummy class for testing message_user functions on ModelAdmin
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 3ee06751fb..8f83324a37 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -15,7 +15,6 @@ from django.core import mail
from django.core.files import temp as tempfile
from django.core.urlresolvers import reverse
# Register auth models with the admin.
-from django.contrib import admin
from django.contrib.auth import get_permission_codename
from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME
from django.contrib.admin.models import LogEntry, DELETION
@@ -51,7 +50,7 @@ from .models import (Article, BarAccount, CustomArticle, EmptyModel, FooAccount,
OtherStory, ComplexSortedPerson, PluggableSearchPerson, Parent, Child, AdminOrderedField,
AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable,
Report, MainPrepopulated, RelatedPrepopulated, UnorderedObject,
- Simple, UndeletableObject, Choice, ShortMessage, Telegram)
+ Simple, UndeletableObject, UnchangeableObject, Choice, ShortMessage, Telegram)
from .admin import site, site2
@@ -524,31 +523,28 @@ class AdminViewBasicTest(AdminViewBasicTestCase):
if the default language is non-English but the selected language
is English. See #13388 and #3594 for more details.
"""
- with self.settings(LANGUAGE_CODE='fr'):
- with translation.override('en-us'):
- response = self.client.get('/test_admin/admin/jsi18n/')
- self.assertNotContains(response, 'Choisir une heure')
+ with self.settings(LANGUAGE_CODE='fr'), translation.override('en-us'):
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertNotContains(response, 'Choisir une heure')
def testI18NLanguageNonEnglishFallback(self):
"""
Makes sure that the fallback language is still working properly
in cases where the selected language cannot be found.
"""
- with self.settings(LANGUAGE_CODE='fr'):
- with translation.override('none'):
- response = self.client.get('/test_admin/admin/jsi18n/')
- self.assertContains(response, 'Choisir une heure')
+ with self.settings(LANGUAGE_CODE='fr'), translation.override('none'):
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertContains(response, 'Choisir une heure')
def testL10NDeactivated(self):
"""
Check if L10N is deactivated, the JavaScript i18n view doesn't
return localized date/time formats. Refs #14824.
"""
- with self.settings(LANGUAGE_CODE='ru', USE_L10N=False):
- with translation.override('none'):
- response = self.client.get('/test_admin/admin/jsi18n/')
- self.assertNotContains(response, '%d.%m.%Y %H:%M:%S')
- self.assertContains(response, '%Y-%m-%d %H:%M:%S')
+ with self.settings(LANGUAGE_CODE='ru', USE_L10N=False), translation.override('none'):
+ response = self.client.get('/test_admin/admin/jsi18n/')
+ self.assertNotContains(response, '%d.%m.%Y %H:%M:%S')
+ self.assertContains(response, '%Y-%m-%d %H:%M:%S')
def test_disallowed_filtering(self):
with patch_logger('django.security.DisallowedModelAdminLookup', 'error') as calls:
@@ -2425,6 +2421,24 @@ class AdminActionsTest(TestCase):
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Because.</a></li>' % a1.pk, html=True)
self.assertContains(response, '<li>Answer: <a href="/test_admin/admin/admin_views/answer/%s/">Yes.</a></li>' % a2.pk, html=True)
+ def test_model_admin_default_delete_action_no_change_url(self):
+ """
+ Default delete action shouldn't break if a user's ModelAdmin removes the url for change_view.
+
+ Regression test for #20640
+ """
+ obj = UnchangeableObject.objects.create()
+ action_data = {
+ ACTION_CHECKBOX_NAME: obj.pk,
+ "action": "delete_selected",
+ "index": "0",
+ }
+ response = self.client.post('/test_admin/admin/admin_views/unchangeableobject/', action_data)
+ # No 500 caused by NoReverseMatch
+ self.assertEqual(response.status_code, 200)
+ # The page shouldn't display a link to the nonexistent change page
+ self.assertContains(response, "<li>Unchangeable object: UnchangeableObject object</li>", 1, html=True)
+
def test_custom_function_mail_action(self):
"Tests a custom action defined in a function"
action_data = {
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index 5a88df1e57..d2ecf46358 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -16,7 +16,6 @@ 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 . import models
from .widgetadmin import site as widget_admin_site
@@ -238,14 +237,14 @@ class FilteredSelectMultipleWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.FilteredSelectMultiple('test', False)
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'test')),
+ w.render('test', 'test'),
'<select multiple="multiple" name="test" class="selectfilter">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 0, "%(ADMIN_STATIC_PREFIX)s"); });</script>\n' % admin_static_prefix()
)
def test_stacked_render(self):
w = widgets.FilteredSelectMultiple('test', True)
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'test')),
+ w.render('test', 'test'),
'<select multiple="multiple" name="test" class="selectfilterstacked">\n</select><script type="text/javascript">addEvent(window, "load", function(e) {SelectFilter.init("id_test", "test", 1, "%(ADMIN_STATIC_PREFIX)s"); });</script>\n' % admin_static_prefix()
)
@@ -257,13 +256,13 @@ class AdminDateWidgetTest(DjangoTestCase):
"""
w = widgets.AdminDateWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'<input value="2007-12-01" type="text" class="vDateField" name="test" size="10" />',
)
# pass attrs to widget
w = widgets.AdminDateWidget(attrs={'size': 20, 'class': 'myDateField'})
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'<input value="2007-12-01" type="text" class="myDateField" name="test" size="20" />',
)
@@ -275,13 +274,13 @@ class AdminTimeWidgetTest(DjangoTestCase):
"""
w = widgets.AdminTimeWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'<input value="09:30:00" type="text" class="vTimeField" name="test" size="8" />',
)
# pass attrs to widget
w = widgets.AdminTimeWidget(attrs={'size': 20, 'class': 'myTimeField'})
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'<input value="09:30:00" type="text" class="myTimeField" name="test" size="20" />',
)
@@ -289,50 +288,55 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.AdminSplitDateTime()
self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
'<p class="datetime">Date: <input value="2007-12-01" type="text" class="vDateField" name="test_0" size="10" /><br />Time: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
)
def test_localization(self):
w = widgets.AdminSplitDateTime()
- with self.settings(USE_L10N=True):
- with translation.override('de-at'):
- w.is_localized = True
- self.assertHTMLEqual(
- conditional_escape(w.render('test', datetime(2007, 12, 1, 9, 30))),
- '<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
- )
+ with self.settings(USE_L10N=True), translation.override('de-at'):
+ w.is_localized = True
+ self.assertHTMLEqual(
+ w.render('test', datetime(2007, 12, 1, 9, 30)),
+ '<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>',
+ )
class AdminURLWidgetTest(DjangoTestCase):
def test_render(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', '')),
+ w.render('test', ''),
'<input class="vURLField" name="test" type="url" />'
)
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'http://example.com')),
+ w.render('test', 'http://example.com'),
'<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="url" value="http://example.com" /></p>'
)
def test_render_idn(self):
w = widgets.AdminURLFieldWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', 'http://example-äüö.com')),
- '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="url" value="http://example-äüö.com" /></p>'
+ w.render('test', 'http://example-äüö.com'),
+ '<p class="url">Currently: <a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="url" value="http://example-äüö.com" /></p>'
)
def test_render_quoting(self):
+ # WARNING: Don't use assertHTMLEqual in that testcase!
+ # assertHTMLEqual will get rid of some escapes which are tested here!
w = widgets.AdminURLFieldWidget()
- self.assertHTMLEqual(
- conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')),
- '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change:<input class="vURLField" name="test" type="url" value="http://example.com/<sometag>some text</sometag>" /></p>'
+ self.assertEqual(
+ w.render('test', 'http://example.com/<sometag>some text</sometag>'),
+ '<p class="url">Currently: <a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change: <input class="vURLField" name="test" type="url" value="http://example.com/&lt;sometag&gt;some text&lt;/sometag&gt;" /></p>'
)
- self.assertHTMLEqual(
- conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')),
- '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change:<input class="vURLField" name="test" type="url" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>'
+ self.assertEqual(
+ w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>'),
+ '<p class="url">Currently: <a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;</a><br />Change: <input class="vURLField" name="test" type="url" value="http://example-äüö.com/&lt;sometag&gt;some text&lt;/sometag&gt;" /></p>'
+ )
+ self.assertEqual(
+ w.render('test', 'http://www.example.com/%C3%A4"><script>alert("XSS!")</script>"'),
+ '<p class="url">Currently: <a href="http://www.example.com/%C3%A4%22%3E%3Cscript%3Ealert(%22XSS!%22)%3C/script%3E%22">http://www.example.com/%C3%A4&quot;&gt;&lt;script&gt;alert(&quot;XSS!&quot;)&lt;/script&gt;&quot;</a><br />Change: <input class="vURLField" name="test" type="url" value="http://www.example.com/%C3%A4&quot;&gt;&lt;script&gt;alert(&quot;XSS!&quot;)&lt;/script&gt;&quot;" /></p>'
)
@@ -345,12 +349,12 @@ class AdminFileWidgetTest(DjangoTestCase):
w = widgets.AdminFileWidget()
self.assertHTMLEqual(
- conditional_escape(w.render('test', album.cover_art)),
+ w.render('test', album.cover_art),
'<p class="file-upload">Currently: <a href="%(STORAGE_URL)salbums/hybrid_theory.jpg">albums\hybrid_theory.jpg</a> <span class="clearable-file-input"><input type="checkbox" name="test-clear" id="test-clear_id" /> <label for="test-clear_id">Clear</label></span><br />Change: <input type="file" name="test" /></p>' % { 'STORAGE_URL': default_storage.url('') },
)
self.assertHTMLEqual(
- conditional_escape(w.render('test', SimpleUploadedFile('test', b'content'))),
+ w.render('test', SimpleUploadedFile('test', b'content')),
'<input type="file" name="test" />',
)
@@ -365,7 +369,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('test', band.pk, attrs={})),
+ w.render('test', band.pk, attrs={}),
'<input type="text" name="test" value="%(bandpk)s" class="vForeignKeyRawIdAdminField" /><a href="/widget_admin/admin_widgets/band/?t=id" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_STATIC_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>&nbsp;<strong>Linkin Park</strong>' % dict(admin_static_prefix(), bandpk=band.pk)
)
@@ -393,7 +397,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('honeycomb_widget', big_honeycomb.pk, attrs={})),
+ w.render('honeycomb_widget', big_honeycomb.pk, attrs={}),
'<input type="text" name="honeycomb_widget" value="%(hcombpk)s" />&nbsp;<strong>Honeycomb object</strong>' % {'hcombpk': big_honeycomb.pk}
)
@@ -406,7 +410,7 @@ class ForeignKeyRawIdWidgetTest(DjangoTestCase):
w = widgets.ForeignKeyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('individual_widget', subject1.pk, attrs={})),
+ w.render('individual_widget', subject1.pk, attrs={}),
'<input type="text" name="individual_widget" value="%(subj1pk)s" />&nbsp;<strong>Individual object</strong>' % {'subj1pk': subject1.pk}
)
@@ -438,12 +442,12 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('test', [m1.pk, m2.pk], attrs={})),
+ w.render('test', [m1.pk, m2.pk], attrs={}),
'<input type="text" name="test" value="%(m1pk)s,%(m2pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="/static/admin/img/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_static_prefix(), m1pk=m1.pk, m2pk=m2.pk)
)
self.assertHTMLEqual(
- conditional_escape(w.render('test', [m1.pk])),
+ w.render('test', [m1.pk]),
'<input type="text" name="test" value="%(m1pk)s" class="vManyToManyRawIdAdminField" /><a href="/widget_admin/admin_widgets/member/" class="related-lookup" id="lookup_id_test" onclick="return showRelatedObjectLookupPopup(this);"> <img src="%(ADMIN_STATIC_PREFIX)simg/selector-search.gif" width="16" height="16" alt="Lookup" /></a>' % dict(admin_static_prefix(), m1pk=m1.pk)
)
@@ -459,12 +463,12 @@ class ManyToManyRawIdWidgetTest(DjangoTestCase):
w = widgets.ManyToManyRawIdWidget(rel, widget_admin_site)
self.assertHTMLEqual(
- conditional_escape(w.render('company_widget1', [c1.pk, c2.pk], attrs={})),
+ w.render('company_widget1', [c1.pk, c2.pk], attrs={}),
'<input type="text" name="company_widget1" value="%(c1pk)s,%(c2pk)s" />' % {'c1pk': c1.pk, 'c2pk': c2.pk}
)
self.assertHTMLEqual(
- conditional_escape(w.render('company_widget2', [c1.pk])),
+ w.render('company_widget2', [c1.pk]),
'<input type="text" name="company_widget2" value="%(c1pk)s" />' % {'c1pk': c1.pk}
)
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index 4d46cae766..7d2490a77c 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
import datetime
from decimal import Decimal
+from django.db import connection
from django.db.models import Avg, Sum, Count, Max, Min
from django.test import TestCase, Approximate
+from django.test.utils import CaptureQueriesContext
from .models import Author, Publisher, Book, Store
@@ -617,3 +619,26 @@ class BaseAggregateTestCase(TestCase):
# Check internal state
self.assertIsNone(annotated_books.query.alias_map["aggregation_book"].join_type)
self.assertIsNone(excluded_books.query.alias_map["aggregation_book"].join_type)
+
+ def test_ticket12886(self):
+ """
+ Check that aggregation over sliced queryset works correctly.
+ """
+ qs = Book.objects.all().order_by('-rating')[0:3]
+ vals = qs.aggregate(average_top3_rating=Avg('rating'))['average_top3_rating']
+ self.assertAlmostEqual(vals, 4.5, places=2)
+
+ def test_ticket11881(self):
+ """
+ Check that subqueries do not needlessly contain ORDER BY, SELECT FOR UPDATE
+ or select_related() stuff.
+ """
+ qs = Book.objects.all().select_for_update().order_by(
+ 'pk').select_related('publisher').annotate(max_pk=Max('pk'))
+ with CaptureQueriesContext(connection) as captured_queries:
+ qs.aggregate(avg_pk=Avg('max_pk'))
+ self.assertEqual(len(captured_queries), 1)
+ qstr = captured_queries[0]['sql'].lower()
+ self.assertNotIn('for update', qstr)
+ self.assertNotIn('order by', qstr)
+ self.assertEqual(qstr.count(' join '), 0)
diff --git a/tests/aggregation_regress/models.py b/tests/aggregation_regress/models.py
index 047c871c39..dfef691882 100644
--- a/tests/aggregation_regress/models.py
+++ b/tests/aggregation_regress/models.py
@@ -64,7 +64,7 @@ class Store(models.Model):
class Entries(models.Model):
EntryID = models.AutoField(primary_key=True, db_column='Entry ID')
Entry = models.CharField(unique=True, max_length=50)
- Exclude = models.BooleanField()
+ Exclude = models.BooleanField(default=False)
class Clues(models.Model):
diff --git a/tests/basic/tests.py b/tests/basic/tests.py
index 55ed6a436f..2b051621ef 100644
--- a/tests/basic/tests.py
+++ b/tests/basic/tests.py
@@ -707,6 +707,10 @@ class ModelTest(TestCase):
with self.assertRaises(ObjectDoesNotExist):
SelfRef.objects.get(selfref=sr)
+ def test_eq(self):
+ self.assertNotEqual(Article(id=1), object())
+ self.assertNotEqual(object(), Article(id=1))
+
class ConcurrentSaveTests(TransactionTestCase):
diff --git a/tests/check/models.py b/tests/check/models.py
index 78a10abba6..212b01bdd2 100644
--- a/tests/check/models.py
+++ b/tests/check/models.py
@@ -1 +1,9 @@
-# Stubby.
+from django.db import models
+
+class Book(models.Model):
+ title = models.CharField(max_length=250)
+ is_published = models.BooleanField(default=False)
+
+class BlogPost(models.Model):
+ title = models.CharField(max_length=250)
+ is_published = models.BooleanField(default=False)
diff --git a/tests/check/tests.py b/tests/check/tests.py
index 98495e38ae..3faf086557 100644
--- a/tests/check/tests.py
+++ b/tests/check/tests.py
@@ -2,8 +2,10 @@ from django.core.checks.compatibility import base
from django.core.checks.compatibility import django_1_6_0
from django.core.management.commands import check
from django.core.management import call_command
+from django.db.models.fields import NOT_PROVIDED
from django.test import TestCase
+from .models import Book
class StubCheckModule(object):
# Has no ``run_checks`` attribute & will trigger a warning.
@@ -53,6 +55,24 @@ class CompatChecksTestCase(TestCase):
with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
self.assertEqual(len(django_1_6_0.run_checks()), 0)
+ def test_boolean_field_default_value(self):
+ with self.settings(TEST_RUNNER='myapp.test.CustomRunnner'):
+ # We patch the field's default value to trigger the warning
+ boolean_field = Book._meta.get_field('is_published')
+ old_default = boolean_field.default
+ try:
+ boolean_field.default = NOT_PROVIDED
+ result = django_1_6_0.run_checks()
+ self.assertEqual(len(result), 1)
+ self.assertTrue("You have not set a default value for one or more BooleanFields" in result[0])
+ self.assertTrue('check.Book: "is_published"' in result[0])
+ # We did not patch the BlogPost.is_published field so
+ # there should not be a warning about it
+ self.assertFalse('check.BlogPost' in result[0])
+ finally:
+ # Restore the ``default``
+ boolean_field.default = old_default
+
def test_check_compatibility(self):
with self.settings(TEST_RUNNER='django.test.runner.DiscoverRunner'):
result = base.check_compatibility()
diff --git a/tests/comment_tests/models.py b/tests/comment_tests/models.py
index 472b66decd..a2b27e7e87 100644
--- a/tests/comment_tests/models.py
+++ b/tests/comment_tests/models.py
@@ -30,7 +30,7 @@ class Entry(models.Model):
title = models.CharField(max_length=250)
body = models.TextField()
pub_date = models.DateField()
- enable_comments = models.BooleanField()
+ enable_comments = models.BooleanField(default=False)
def __str__(self):
return self.title
diff --git a/tests/custom_managers/models.py b/tests/custom_managers/models.py
index 44d5eb70da..cba375f4d7 100644
--- a/tests/custom_managers/models.py
+++ b/tests/custom_managers/models.py
@@ -67,7 +67,7 @@ CustomManager = BaseCustomManager.from_queryset(CustomQuerySet)
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
- fun = models.BooleanField()
+ fun = models.BooleanField(default=False)
objects = PersonManager()
custom_queryset_default_manager = CustomQuerySet.as_manager()
@@ -80,7 +80,7 @@ class Person(models.Model):
class Book(models.Model):
title = models.CharField(max_length=50)
author = models.CharField(max_length=30)
- is_published = models.BooleanField()
+ is_published = models.BooleanField(default=False)
published_objects = PublishedBookManager()
authors = models.ManyToManyField(Person, related_name='books')
diff --git a/tests/defaultfilters/tests.py b/tests/defaultfilters/tests.py
index 725b673903..7db872001d 100644
--- a/tests/defaultfilters/tests.py
+++ b/tests/defaultfilters/tests.py
@@ -647,22 +647,21 @@ 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')
+ with self.settings(USE_L10N=True), 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/tests.py b/tests/defer/tests.py
index 1df312e1bf..b66b173f7a 100644
--- a/tests/defer/tests.py
+++ b/tests/defer/tests.py
@@ -183,3 +183,9 @@ class DeferTests(TestCase):
with self.assertNumQueries(0):
bc_deferred.id
self.assertEqual(bc_deferred.pk, bc_deferred.id)
+
+ def test_eq(self):
+ s1 = Secondary.objects.create(first="x1", second="y1")
+ s1_defer = Secondary.objects.only('pk').get(pk=s1.pk)
+ self.assertEqual(s1, s1_defer)
+ self.assertEqual(s1_defer, s1)
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index cdd9720374..2a6f602e3e 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -462,6 +462,18 @@ class FileStoragePermissions(unittest.TestCase):
mode = os.stat(self.storage.path(fname))[0] & 0o777
self.assertEqual(mode, 0o666 & ~self.umask)
+ @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=0o765)
+ def test_file_upload_directory_permissions(self):
+ name = self.storage.save("the_directory/the_file", ContentFile("data"))
+ dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
+ self.assertEqual(dir_mode, 0o765)
+
+ @override_settings(FILE_UPLOAD_DIRECTORY_PERMISSIONS=None)
+ def test_file_upload_directory_default_permissions(self):
+ name = self.storage.save("the_directory/the_file", ContentFile("data"))
+ dir_mode = os.stat(os.path.dirname(self.storage.path(name)))[0] & 0o777
+ self.assertEqual(dir_mode, 0o777 & ~self.umask)
+
class FileStoragePathParsing(unittest.TestCase):
def setUp(self):
self.storage_dir = tempfile.mkdtemp()
diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
index cd81cc68a2..77582162a8 100644
--- a/tests/foreign_object/tests.py
+++ b/tests/foreign_object/tests.py
@@ -132,7 +132,6 @@ class MultiColumnFKTests(TestCase):
],
attrgetter('person_id')
)
-
self.assertQuerysetEqual(
Membership.objects.filter(person__in=Person.objects.filter(name='Jim')), [
self.jim.id,
@@ -140,6 +139,24 @@ class MultiColumnFKTests(TestCase):
attrgetter('person_id')
)
+ def test_double_nested_query(self):
+ m1 = Membership.objects.create(membership_country_id=self.usa.id, person_id=self.bob.id,
+ group_id=self.cia.id)
+ m2 = Membership.objects.create(membership_country_id=self.usa.id, person_id=self.jim.id,
+ group_id=self.cia.id)
+ Friendship.objects.create(from_friend_country_id=self.usa.id, from_friend_id=self.bob.id,
+ to_friend_country_id=self.usa.id, to_friend_id=self.jim.id)
+ self.assertQuerysetEqual(Membership.objects.filter(
+ person__in=Person.objects.filter(
+ from_friend__in=Friendship.objects.filter(
+ to_friend__in=Person.objects.all()))),
+ [m1], lambda x: x)
+ self.assertQuerysetEqual(Membership.objects.exclude(
+ person__in=Person.objects.filter(
+ from_friend__in=Friendship.objects.filter(
+ to_friend__in=Person.objects.all()))),
+ [m2], lambda x: x)
+
def test_select_related_foreignkey_forward_works(self):
Membership.objects.create(membership_country=self.usa, person=self.bob, group=self.cia)
Membership.objects.create(membership_country=self.usa, person=self.jim, group=self.democrat)
diff --git a/tests/forms_tests/tests/test_fields.py b/tests/forms_tests/tests/test_fields.py
index f02593e488..a4e81d31ae 100644
--- a/tests/forms_tests/tests/test_fields.py
+++ b/tests/forms_tests/tests/test_fields.py
@@ -286,11 +286,10 @@ class FieldsTests(SimpleTestCase):
n = 4.35
self.assertFalse(f._has_changed(n, '4.3500'))
- with translation.override('fr'):
- with self.settings(USE_L10N=True):
- f = FloatField(localize=True)
- localized_n = formats.localize_input(n) # -> '4,35' in French
- self.assertFalse(f._has_changed(n, localized_n))
+ with translation.override('fr'), self.settings(USE_L10N=True):
+ f = FloatField(localize=True)
+ localized_n = formats.localize_input(n) # -> '4,35' in French
+ self.assertFalse(f._has_changed(n, localized_n))
# DecimalField ################################################################
@@ -399,11 +398,10 @@ class FieldsTests(SimpleTestCase):
self.assertFalse(f._has_changed(d, '0.10'))
self.assertTrue(f._has_changed(d, '0.101'))
- with translation.override('fr'):
- with self.settings(USE_L10N=True):
- f = DecimalField(max_digits=2, decimal_places=2, localize=True)
- localized_d = formats.localize_input(d) # -> '0,1' in French
- self.assertFalse(f._has_changed(d, localized_d))
+ with translation.override('fr'), self.settings(USE_L10N=True):
+ f = DecimalField(max_digits=2, decimal_places=2, localize=True)
+ localized_d = formats.localize_input(d) # -> '0,1' in French
+ self.assertFalse(f._has_changed(d, localized_d))
# DateField ###################################################################
diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py
index 06ee2d6c53..3f11771d7a 100644
--- a/tests/forms_tests/tests/test_widgets.py
+++ b/tests/forms_tests/tests/test_widgets.py
@@ -833,6 +833,22 @@ beatle J R Ringo False""")
with self.assertRaises(IndexError):
r[42]
+ def test_subwidget(self):
+ # Each subwidget tag gets a separate ID when the widget has an ID specified
+ self.assertHTMLEqual("\n".join([c.tag() for c in CheckboxSelectMultiple(attrs={'id': 'abc'}).subwidgets('letters', list('ac'), choices=zip(list('abc'), list('ABC')))]), """<input checked="checked" type="checkbox" name="letters" value="a" id="abc_0" />
+<input type="checkbox" name="letters" value="b" id="abc_1" />
+<input checked="checked" type="checkbox" name="letters" value="c" id="abc_2" />""")
+
+ # Each subwidget tag does not get an ID if the widget does not have an ID specified
+ self.assertHTMLEqual("\n".join([c.tag() for c in CheckboxSelectMultiple().subwidgets('letters', list('ac'), choices=zip(list('abc'), list('ABC')))]), """<input checked="checked" type="checkbox" name="letters" value="a" />
+<input type="checkbox" name="letters" value="b" />
+<input checked="checked" type="checkbox" name="letters" value="c" />""")
+
+ # The id_for_label property of the subwidget should return the ID that is used on the subwidget's tag
+ self.assertHTMLEqual("\n".join(['<input type="checkbox" name="letters" value="%s" id="%s" />' % (c.choice_value, c.id_for_label) for c in CheckboxSelectMultiple(attrs={'id': 'abc'}).subwidgets('letters', [], choices=zip(list('abc'), list('ABC')))]), """<input type="checkbox" name="letters" value="a" id="abc_0" />
+<input type="checkbox" name="letters" value="b" id="abc_1" />
+<input type="checkbox" name="letters" value="c" id="abc_2" />""")
+
def test_multi(self):
class MyMultiWidget(MultiWidget):
def decompress(self, value):
diff --git a/tests/generic_relations/models.py b/tests/generic_relations/models.py
index 211df8aa3d..d819a53c0e 100644
--- a/tests/generic_relations/models.py
+++ b/tests/generic_relations/models.py
@@ -92,7 +92,7 @@ class GeckoManager(models.Manager):
return super(GeckoManager, self).get_queryset().filter(has_tail=True)
class Gecko(models.Model):
- has_tail = models.BooleanField()
+ has_tail = models.BooleanField(default=False)
objects = GeckoManager()
# To test fix for #11263
diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py
index 05d1065a75..12e325b773 100644
--- a/tests/i18n/tests.py
+++ b/tests/i18n/tests.py
@@ -746,11 +746,11 @@ class FormattingTests(TransRealMixin, TestCase):
self.assertEqual('.', get_format('DECIMAL_SEPARATOR', lang='en'))
def test_get_format_modules_stability(self):
- with self.settings(FORMAT_MODULE_PATH='i18n.other.locale'):
- with translation.override('de', deactivate=True):
- old = str("%r") % get_format_modules(reverse=True)
- new = str("%r") % get_format_modules(reverse=True) # second try
- self.assertEqual(new, old, 'Value returned by get_formats_modules() must be preserved between calls.')
+ with self.settings(FORMAT_MODULE_PATH='i18n.other.locale'), \
+ translation.override('de', deactivate=True):
+ old = str("%r") % get_format_modules(reverse=True)
+ new = str("%r") % get_format_modules(reverse=True) # second try
+ self.assertEqual(new, old, 'Value returned by get_formats_modules() must be preserved between calls.')
def test_localize_templatetag_and_filter(self):
"""
@@ -1062,9 +1062,8 @@ class MultipleLocaleActivationTests(TransRealMixin, TestCase):
def test_multiple_locale_filter(self):
with translation.override('de'):
t = Template("{% load i18n %}{{ 0|yesno:_('yes,no,maybe') }}")
- with translation.override(self._old_language):
- with translation.override('nl'):
- self.assertEqual(t.render(Context({})), 'nee')
+ with translation.override(self._old_language), translation.override('nl'):
+ self.assertEqual(t.render(Context({})), 'nee')
def test_multiple_locale_filter_deactivate(self):
with translation.override('de', deactivate=True):
@@ -1083,9 +1082,8 @@ class MultipleLocaleActivationTests(TransRealMixin, TestCase):
def test_multiple_locale(self):
with translation.override('de'):
t = Template("{{ _('No') }}")
- with translation.override(self._old_language):
- with translation.override('nl'):
- self.assertEqual(t.render(Context({})), 'Nee')
+ with translation.override(self._old_language), translation.override('nl'):
+ self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate(self):
with translation.override('de', deactivate=True):
@@ -1104,9 +1102,8 @@ class MultipleLocaleActivationTests(TransRealMixin, TestCase):
def test_multiple_locale_loadi18n(self):
with translation.override('de'):
t = Template("{% load i18n %}{{ _('No') }}")
- with translation.override(self._old_language):
- with translation.override('nl'):
- self.assertEqual(t.render(Context({})), 'Nee')
+ with translation.override(self._old_language), translation.override('nl'):
+ self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_loadi18n_deactivate(self):
with translation.override('de', deactivate=True):
@@ -1125,9 +1122,8 @@ class MultipleLocaleActivationTests(TransRealMixin, TestCase):
def test_multiple_locale_trans(self):
with translation.override('de'):
t = Template("{% load i18n %}{% trans 'No' %}")
- with translation.override(self._old_language):
- with translation.override('nl'):
- self.assertEqual(t.render(Context({})), 'Nee')
+ with translation.override(self._old_language), translation.override('nl'):
+ self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_trans(self):
with translation.override('de', deactivate=True):
@@ -1146,9 +1142,8 @@ class MultipleLocaleActivationTests(TransRealMixin, TestCase):
def test_multiple_locale_btrans(self):
with translation.override('de'):
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
- with translation.override(self._old_language):
- with translation.override('nl'):
- self.assertEqual(t.render(Context({})), 'Nee')
+ with translation.override(self._old_language), translation.override('nl'):
+ self.assertEqual(t.render(Context({})), 'Nee')
def test_multiple_locale_deactivate_btrans(self):
with translation.override('de', deactivate=True):
diff --git a/tests/inspectdb/models.py b/tests/inspectdb/models.py
index 2862f7f3dc..c25202f248 100644
--- a/tests/inspectdb/models.py
+++ b/tests/inspectdb/models.py
@@ -37,7 +37,7 @@ class SpecialColumnName(models.Model):
class ColumnTypes(models.Model):
id = models.AutoField(primary_key=True)
big_int_field = models.BigIntegerField()
- bool_field = models.BooleanField()
+ bool_field = models.BooleanField(default=False)
null_bool_field = models.NullBooleanField()
char_field = models.CharField(max_length=10)
comma_separated_int_field = models.CommaSeparatedIntegerField(max_length=99)
diff --git a/tests/logging_tests/tests.py b/tests/logging_tests/tests.py
index 8ba4702aba..ef9f979e7e 100644
--- a/tests/logging_tests/tests.py
+++ b/tests/logging_tests/tests.py
@@ -355,20 +355,19 @@ class SettingsConfigureLogging(TestCase):
self.assertTrue(dictConfig.called)
+@override_settings(DEBUG=True)
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')
+ 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')
+ 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/model_fields/models.py b/tests/model_fields/models.py
index 2d602d6412..8c304cc726 100644
--- a/tests/model_fields/models.py
+++ b/tests/model_fields/models.py
@@ -58,7 +58,7 @@ class NullBooleanModel(models.Model):
nbfield = models.NullBooleanField()
class BooleanModel(models.Model):
- bfield = models.BooleanField()
+ bfield = models.BooleanField(default=None)
string = models.CharField(max_length=10, default='abc')
class FksToBooleans(models.Model):
@@ -72,7 +72,7 @@ class RenamedField(models.Model):
class VerboseNameField(models.Model):
id = models.AutoField("verbose pk", primary_key=True)
field1 = models.BigIntegerField("verbose field1")
- field2 = models.BooleanField("verbose field2")
+ field2 = models.BooleanField("verbose field2", default=False)
field3 = models.CharField("verbose field3", max_length=10)
field4 = models.CommaSeparatedIntegerField("verbose field4", max_length=99)
field5 = models.DateField("verbose field5")
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 378e6280cc..429337f31c 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -12,7 +12,7 @@ from django.db.models.fields import (
AutoField, BigIntegerField, BinaryField, BooleanField, CharField,
CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField,
EmailField, FilePathField, FloatField, IntegerField, IPAddressField,
- GenericIPAddressField, NullBooleanField, PositiveIntegerField,
+ GenericIPAddressField, NOT_PROVIDED, NullBooleanField, PositiveIntegerField,
PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField,
TimeField, URLField)
from django.db.models.fields.files import FileField, ImageField
@@ -275,10 +275,23 @@ class BooleanFieldTests(unittest.TestCase):
Check that a BooleanField defaults to None -- which isn't
a valid value (#15124).
"""
- b = BooleanModel()
- self.assertIsNone(b.bfield)
- with self.assertRaises(IntegrityError):
- b.save()
+ # Patch the boolean field's default value. We give it a default
+ # value when defining the model to satisfy the check tests
+ # #20895.
+ boolean_field = BooleanModel._meta.get_field('bfield')
+ self.assertTrue(boolean_field.has_default())
+ old_default = boolean_field.default
+ try:
+ boolean_field.default = NOT_PROVIDED
+ # check patch was succcessful
+ self.assertFalse(boolean_field.has_default())
+ b = BooleanModel()
+ self.assertIsNone(b.bfield)
+ with self.assertRaises(IntegrityError):
+ b.save()
+ finally:
+ boolean_field.default = old_default
+
nb = NullBooleanModel()
self.assertIsNone(nb.nbfield)
nb.save() # no error
diff --git a/tests/model_formsets/models.py b/tests/model_formsets/models.py
index ae152448ab..adeb3455a4 100644
--- a/tests/model_formsets/models.py
+++ b/tests/model_formsets/models.py
@@ -117,7 +117,7 @@ class OwnerProfile(models.Model):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_pizza = models.BooleanField()
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return self.name
@@ -141,11 +141,11 @@ class Price(models.Model):
unique_together = (('price', 'quantity'),)
class MexicanRestaurant(Restaurant):
- serves_tacos = models.BooleanField()
+ serves_tacos = models.BooleanField(default=False)
class ClassyMexicanRestaurant(MexicanRestaurant):
restaurant = models.OneToOneField(MexicanRestaurant, parent_link=True, primary_key=True)
- tacos_are_yummy = models.BooleanField()
+ tacos_are_yummy = models.BooleanField(default=False)
# models for testing unique_together validation when a fk is involved and
# using inlineformset_factory.
diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py
index 2101f394f7..106645d23c 100644
--- a/tests/model_inheritance/models.py
+++ b/tests/model_inheritance/models.py
@@ -63,7 +63,7 @@ class Attachment(models.Model):
return self.content
class Comment(Attachment):
- is_spam = models.BooleanField()
+ is_spam = models.BooleanField(default=False)
class Link(Attachment):
url = models.URLField()
@@ -96,8 +96,8 @@ class Rating(models.Model):
@python_2_unicode_compatible
class Restaurant(Place, Rating):
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
chef = models.ForeignKey(Chef, null=True, blank=True)
class Meta(Rating.Meta):
@@ -108,7 +108,7 @@ class Restaurant(Place, Rating):
@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
- serves_gnocchi = models.BooleanField()
+ serves_gnocchi = models.BooleanField(default=False)
def __str__(self):
return "%s the italian restaurant" % self.name
diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py
index eb50d30cf8..b8ab0c8581 100644
--- a/tests/model_inheritance/tests.py
+++ b/tests/model_inheritance/tests.py
@@ -8,8 +8,9 @@ from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from django.utils import six
-from .models import (Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place,
- Post, Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel)
+from .models import (
+ Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, Post,
+ Restaurant, Student, StudentWorker, Supplier, Worker, MixinModel)
class ModelInheritanceTests(TestCase):
@@ -20,7 +21,7 @@ class ModelInheritanceTests(TestCase):
# information for programming purposes, but still completely
# independent separate models at the database level.
w1 = Worker.objects.create(name="Fred", age=35, job="Quarry worker")
- w2 = Worker.objects.create(name="Barney", age=34, job="Quarry worker")
+ Worker.objects.create(name="Barney", age=34, job="Quarry worker")
s = Student.objects.create(name="Pebbles", age=5, school_class="1B")
@@ -48,10 +49,12 @@ class ModelInheritanceTests(TestCase):
# A StudentWorker which does not exist is both a Student and Worker
# which does not exist.
- self.assertRaises(Student.DoesNotExist,
+ self.assertRaises(
+ Student.DoesNotExist,
StudentWorker.objects.get, pk=12321321
)
- self.assertRaises(Worker.DoesNotExist,
+ self.assertRaises(
+ Worker.DoesNotExist,
StudentWorker.objects.get, pk=12321321
)
@@ -67,10 +70,12 @@ class ModelInheritanceTests(TestCase):
sw2.age = 24
sw2.save()
- self.assertRaises(Student.MultipleObjectsReturned,
+ self.assertRaises(
+ Student.MultipleObjectsReturned,
StudentWorker.objects.get, pk__lt=sw2.pk + 100
)
- self.assertRaises(Worker.MultipleObjectsReturned,
+ self.assertRaises(
+ Worker.MultipleObjectsReturned,
StudentWorker.objects.get, pk__lt=sw2.pk + 100
)
@@ -85,16 +90,16 @@ class ModelInheritanceTests(TestCase):
# The Post model doesn't have an attribute called
# 'attached_%(class)s_set'.
- self.assertRaises(AttributeError,
- getattr, post, "attached_%(class)s_set"
+ self.assertRaises(
+ AttributeError, getattr, post, "attached_%(class)s_set"
)
# The Place/Restaurant/ItalianRestaurant models all exist as
# independent models. However, the subclasses also have transparent
# access to the fields of their ancestors.
# Create a couple of Places.
- p1 = Place.objects.create(name="Master Shakes", address="666 W. Jersey")
- p2 = Place.objects.create(name="Ace Harware", address="1013 N. Ashland")
+ Place.objects.create(name="Master Shakes", address="666 W. Jersey")
+ Place.objects.create(name="Ace Harware", address="1013 N. Ashland")
# Test constructor for Restaurant.
r = Restaurant.objects.create(
@@ -134,11 +139,13 @@ class ModelInheritanceTests(TestCase):
# the right order.
self.assertEqual(
[f.name for f in Restaurant._meta.fields],
- ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs", "serves_pizza", "chef"]
+ ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
+ "serves_pizza", "chef"]
)
self.assertEqual(
[f.name for f in ItalianRestaurant._meta.fields],
- ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs", "serves_pizza", "chef", "restaurant_ptr", "serves_gnocchi"],
+ ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
+ "serves_pizza", "chef", "restaurant_ptr", "serves_gnocchi"],
)
self.assertEqual(Restaurant._meta.ordering, ["-rating"])
@@ -146,8 +153,8 @@ class ModelInheritanceTests(TestCase):
# Restaurant object cannot access that reverse relation, since it's not
# part of the Place-Supplier Hierarchy.
self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), [])
- self.assertRaises(FieldError,
- Restaurant.objects.filter, supplier__name="foo"
+ self.assertRaises(
+ FieldError, Restaurant.objects.filter, supplier__name="foo"
)
# Parent fields can be used directly in filters on the child model.
@@ -185,16 +192,19 @@ class ModelInheritanceTests(TestCase):
# This won't work because the Demon Dogs restaurant is not an Italian
# restaurant.
- self.assertRaises(ItalianRestaurant.DoesNotExist,
+ self.assertRaises(
+ ItalianRestaurant.DoesNotExist,
lambda: p.restaurant.italianrestaurant
)
# An ItalianRestaurant which does not exist is also a Place which does
# not exist.
- self.assertRaises(Place.DoesNotExist,
+ self.assertRaises(
+ Place.DoesNotExist,
ItalianRestaurant.objects.get, name="The Noodle Void"
)
# MultipleObjectsReturned is also inherited.
- self.assertRaises(Place.MultipleObjectsReturned,
+ self.assertRaises(
+ Place.MultipleObjectsReturned,
Restaurant.objects.get, id__lt=12321
)
@@ -207,8 +217,8 @@ class ModelInheritanceTests(TestCase):
# This won't work because the Place we select is not a Restaurant (it's
# a Supplier).
p = Place.objects.get(name="Joe's Chickens")
- self.assertRaises(Restaurant.DoesNotExist,
- lambda: p.restaurant
+ self.assertRaises(
+ Restaurant.DoesNotExist, lambda: p.restaurant
)
self.assertEqual(p.supplier, s1)
@@ -233,10 +243,10 @@ class ModelInheritanceTests(TestCase):
attrgetter("name"),
)
- park1 = ParkingLot.objects.create(
+ ParkingLot.objects.create(
name="Main St", address="111 Main St", main_site=s1
)
- park2 = ParkingLot.objects.create(
+ ParkingLot.objects.create(
name="Well Lit", address="124 Sesame St", main_site=ir
)
@@ -268,11 +278,11 @@ class ModelInheritanceTests(TestCase):
# select_related works with fields from the parent object as if they
# were a normal part of the model.
- self.assertNumQueries(2,
- lambda: ItalianRestaurant.objects.all()[0].chef
+ self.assertNumQueries(
+ 2, lambda: ItalianRestaurant.objects.all()[0].chef
)
- self.assertNumQueries(1,
- lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
+ self.assertNumQueries(
+ 1, lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
)
def test_mixin_init(self):
@@ -318,3 +328,32 @@ class ModelInheritanceTests(TestCase):
sql = query['sql']
if 'UPDATE' in sql:
self.assertEqual(expected_sql, sql)
+
+ def test_eq(self):
+ # Equality doesn't transfer in multitable inheritance.
+ self.assertNotEqual(Place(id=1), Restaurant(id=1))
+ self.assertNotEqual(Restaurant(id=1), Place(id=1))
+
+ def test_ticket_12567(self):
+ r = Restaurant.objects.create(name='n1', address='a1')
+ s = Supplier.objects.create(name='s1', address='a2')
+ self.assertQuerysetEqual(
+ Place.objects.filter(supplier__isnull=False),
+ [Place.objects.get(pk=s.pk)],
+ lambda x: x
+ )
+ self.assertQuerysetEqual(
+ Place.objects.filter(supplier__isnull=True),
+ [Place.objects.get(pk=r.pk)],
+ lambda x: x
+ )
+ self.assertQuerysetEqual(
+ Place.objects.exclude(supplier__isnull=False),
+ [Place.objects.get(pk=r.pk)],
+ lambda x: x
+ )
+ self.assertQuerysetEqual(
+ Place.objects.exclude(supplier__isnull=True),
+ [Place.objects.get(pk=s.pk)],
+ lambda x: x
+ )
diff --git a/tests/model_inheritance_regress/models.py b/tests/model_inheritance_regress/models.py
index 811c8175bb..bf076a47c6 100644
--- a/tests/model_inheritance_regress/models.py
+++ b/tests/model_inheritance_regress/models.py
@@ -18,15 +18,15 @@ class Place(models.Model):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.name
@python_2_unicode_compatible
class ItalianRestaurant(Restaurant):
- serves_gnocchi = models.BooleanField()
+ serves_gnocchi = models.BooleanField(default=False)
def __str__(self):
return "%s the italian restaurant" % self.name
@@ -50,6 +50,19 @@ class ParkingLot3(Place):
primary_key = models.AutoField(primary_key=True)
parent = models.OneToOneField(Place, parent_link=True)
+class ParkingLot4(models.Model):
+ # Test parent_link connector can be discovered in abstract classes.
+ parent = models.OneToOneField(Place, parent_link=True)
+
+ class Meta:
+ abstract = True
+
+class ParkingLot4A(ParkingLot4, Place):
+ pass
+
+class ParkingLot4B(Place, ParkingLot4):
+ pass
+
class Supplier(models.Model):
restaurant = models.ForeignKey(Restaurant)
@@ -171,7 +184,7 @@ class Station(SearchableLocation):
class BusStation(Station):
bus_routes = models.CommaSeparatedIntegerField(max_length=128)
- inbound = models.BooleanField()
+ inbound = models.BooleanField(default=False)
class TrainStation(Station):
zone = models.IntegerField()
diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py
index 10a1230685..7f78fc7a98 100644
--- a/tests/model_inheritance_regress/tests.py
+++ b/tests/model_inheritance_regress/tests.py
@@ -14,7 +14,8 @@ from .models import (Place, Restaurant, ItalianRestaurant, ParkingLot,
ParkingLot2, ParkingLot3, Supplier, Wholesaler, Child, SelfRefParent,
SelfRefChild, ArticleWithAuthor, M2MChild, QualityControl, DerivedM,
Person, BirthdayParty, BachelorParty, MessyBachelorParty,
- InternalCertificationAudit, BusStation, TrainStation, User, Profile)
+ InternalCertificationAudit, BusStation, TrainStation, User, Profile,
+ ParkingLot4A, ParkingLot4B)
class ModelInheritanceTest(TestCase):
@@ -311,6 +312,19 @@ class ModelInheritanceTest(TestCase):
ParkingLot3._meta.get_ancestor_link(Place).name,
"parent")
+ def test_use_explicit_o2o_to_parent_from_abstract_model(self):
+ self.assertEqual(ParkingLot4A._meta.pk.name, "parent")
+ ParkingLot4A.objects.create(
+ name="Parking4A",
+ address='21 Jump Street',
+ )
+
+ self.assertEqual(ParkingLot4B._meta.pk.name, "parent")
+ ParkingLot4A.objects.create(
+ name="Parking4B",
+ address='21 Jump Street',
+ )
+
def test_all_fields_from_abstract_base_class(self):
"""
Regression tests for #7588
diff --git a/tests/model_inheritance_select_related/models.py b/tests/model_inheritance_select_related/models.py
index 6b28772620..46c67cf07d 100644
--- a/tests/model_inheritance_select_related/models.py
+++ b/tests/model_inheritance_select_related/models.py
@@ -20,8 +20,8 @@ class Place(models.Model):
@python_2_unicode_compatible
class Restaurant(Place):
- serves_sushi = models.BooleanField()
- serves_steak = models.BooleanField()
+ serves_sushi = models.BooleanField(default=False)
+ serves_steak = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.name
diff --git a/tests/modeladmin/models.py b/tests/modeladmin/models.py
index fdbcabd187..4789e35a43 100644
--- a/tests/modeladmin/models.py
+++ b/tests/modeladmin/models.py
@@ -32,7 +32,7 @@ class ValidationTestModel(models.Model):
slug = models.SlugField()
users = models.ManyToManyField(User)
state = models.CharField(max_length=2, choices=(("CO", "Colorado"), ("WA", "Washington")))
- is_active = models.BooleanField()
+ is_active = models.BooleanField(default=False)
pub_date = models.DateTimeField()
band = models.ForeignKey(Band)
no = models.IntegerField(verbose_name="Number", blank=True, null=True) # This field is intentionally 2 characters long. See #16080.
diff --git a/tests/one_to_one/models.py b/tests/one_to_one/models.py
index 9599496cb7..ff809be22d 100644
--- a/tests/one_to_one/models.py
+++ b/tests/one_to_one/models.py
@@ -22,8 +22,8 @@ class Place(models.Model):
@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place, primary_key=True)
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
diff --git a/tests/one_to_one_regress/models.py b/tests/one_to_one_regress/models.py
index 058baafc82..9b65edf2ab 100644
--- a/tests/one_to_one_regress/models.py
+++ b/tests/one_to_one_regress/models.py
@@ -15,8 +15,8 @@ class Place(models.Model):
@python_2_unicode_compatible
class Restaurant(models.Model):
place = models.OneToOneField(Place)
- serves_hot_dogs = models.BooleanField()
- serves_pizza = models.BooleanField()
+ serves_hot_dogs = models.BooleanField(default=False)
+ serves_pizza = models.BooleanField(default=False)
def __str__(self):
return "%s the restaurant" % self.place.name
diff --git a/tests/one_to_one_regress/tests.py b/tests/one_to_one_regress/tests.py
index 7d82194abb..0e20f19acb 100644
--- a/tests/one_to_one_regress/tests.py
+++ b/tests/one_to_one_regress/tests.py
@@ -141,12 +141,10 @@ class OneToOneRegressionTests(TestCase):
"""
p = Place(name='Zombie Cats', address='Not sure')
p.save()
- with self.assertNumQueries(1):
- with self.assertRaises(Restaurant.DoesNotExist):
- p.restaurant
- with self.assertNumQueries(0):
- with self.assertRaises(Restaurant.DoesNotExist):
- p.restaurant
+ with self.assertNumQueries(1), self.assertRaises(Restaurant.DoesNotExist):
+ p.restaurant
+ with self.assertNumQueries(0), self.assertRaises(Restaurant.DoesNotExist):
+ p.restaurant
def test_reverse_object_cached_when_related_is_accessed(self):
"""
@@ -199,9 +197,8 @@ class OneToOneRegressionTests(TestCase):
self.assertEqual(self.p1.undergroundbar, b)
b.place = None
b.save()
- with self.assertNumQueries(0):
- with self.assertRaises(UndergroundBar.DoesNotExist):
- self.p1.undergroundbar
+ with self.assertNumQueries(0), self.assertRaises(UndergroundBar.DoesNotExist):
+ self.p1.undergroundbar
def test_get_reverse_on_unsaved_object(self):
"""
@@ -213,24 +210,21 @@ class OneToOneRegressionTests(TestCase):
p = Place()
# When there's no instance of the origin of the one-to-one
- with self.assertNumQueries(0):
- with self.assertRaises(UndergroundBar.DoesNotExist):
- p.undergroundbar
+ with self.assertNumQueries(0), self.assertRaises(UndergroundBar.DoesNotExist):
+ p.undergroundbar
UndergroundBar.objects.create()
# When there's one instance of the origin
# (p.undergroundbar used to return that instance)
- with self.assertNumQueries(0):
- with self.assertRaises(UndergroundBar.DoesNotExist):
- p.undergroundbar
+ with self.assertNumQueries(0), self.assertRaises(UndergroundBar.DoesNotExist):
+ p.undergroundbar
UndergroundBar.objects.create()
# When there are several instances of the origin
- with self.assertNumQueries(0):
- with self.assertRaises(UndergroundBar.DoesNotExist):
- p.undergroundbar
+ with self.assertNumQueries(0), self.assertRaises(UndergroundBar.DoesNotExist):
+ p.undergroundbar
def test_set_reverse_on_unsaved_object(self):
"""
@@ -239,6 +233,5 @@ class OneToOneRegressionTests(TestCase):
"""
p = Place()
b = UndergroundBar.objects.create()
- with self.assertNumQueries(0):
- with self.assertRaises(ValueError):
- p.undergroundbar = b
+ with self.assertNumQueries(0), self.assertRaises(ValueError):
+ p.undergroundbar = b
diff --git a/tests/proxy_models/admin.py b/tests/proxy_models/admin.py
new file mode 100644
index 0000000000..d2b6319301
--- /dev/null
+++ b/tests/proxy_models/admin.py
@@ -0,0 +1,6 @@
+from django.contrib import admin
+
+from .models import TrackerUser, ProxyTrackerUser
+
+admin.site.register(TrackerUser)
+admin.site.register(ProxyTrackerUser)
diff --git a/tests/proxy_models/fixtures/myhorses.json b/tests/proxy_models/fixtures/myhorses.json
index 15943064c8..97e190a8a2 100644
--- a/tests/proxy_models/fixtures/myhorses.json
+++ b/tests/proxy_models/fixtures/myhorses.json
@@ -1,6 +1,24 @@
[
{
"pk": 100,
+ "model": "auth.user",
+ "fields": {
+ "username": "super",
+ "first_name": "Super",
+ "last_name": "User",
+ "is_active": true,
+ "is_superuser": true,
+ "is_staff": true,
+ "last_login": "2007-05-30 13:20:10",
+ "groups": [],
+ "user_permissions": [],
+ "password": "sha1$995a3$6011485ea3834267d719b4c801409b8b1ddd0158",
+ "email": "super@example.com",
+ "date_joined": "2007-05-30 13:20:10"
+ }
+ },
+ {
+ "pk": 100,
"model": "proxy_models.BaseUser",
"fields": {
"name": "Django Pony"
@@ -21,4 +39,4 @@
"assignee": 100
}
}
-] \ No newline at end of file
+]
diff --git a/tests/proxy_models/models.py b/tests/proxy_models/models.py
index ffb36657e1..2e5741ab6b 100644
--- a/tests/proxy_models/models.py
+++ b/tests/proxy_models/models.py
@@ -117,9 +117,13 @@ class StateProxy(State):
# Proxy models still works with filters (on related fields)
# and select_related, even when mixed with model inheritance
+@python_2_unicode_compatible
class BaseUser(models.Model):
name = models.CharField(max_length=255)
+ def __str__(self):
+ return ':'.join((self.__class__.__name__, self.name,))
+
class TrackerUser(BaseUser):
status = models.CharField(max_length=50)
@@ -134,7 +138,7 @@ class Issue(models.Model):
assignee = models.ForeignKey(TrackerUser)
def __str__(self):
- return ':'.join((self.__class__.__name__,self.summary,))
+ return ':'.join((self.__class__.__name__, self.summary,))
class Bug(Issue):
version = models.CharField(max_length=50)
diff --git a/tests/proxy_models/tests.py b/tests/proxy_models/tests.py
index 240198cc39..f0c0d046e1 100644
--- a/tests/proxy_models/tests.py
+++ b/tests/proxy_models/tests.py
@@ -10,12 +10,14 @@ from django.db import models, DEFAULT_DB_ALIAS
from django.db.models import signals
from django.db.models.loading import cache
from django.test import TestCase
+from django.test.utils import override_settings
from .models import (MyPerson, Person, StatusPerson, LowerStatusPerson,
MyPersonProxy, Abstract, OtherPerson, User, UserProxy, UserProxyProxy,
Country, State, StateProxy, TrackerUser, BaseUser, Bug, ProxyTrackerUser,
Improvement, ProxyProxyBug, ProxyBug, ProxyImprovement, Issue)
+from .admin import admin as force_admin_model_registration
class ProxyModelTests(TestCase):
@@ -362,9 +364,14 @@ class ProxyModelTests(TestCase):
p = MyPerson.objects.get(pk=100)
self.assertEqual(p.name, 'Elvis Presley')
+ def test_eq(self):
+ self.assertEqual(MyPerson(id=100), Person(id=100))
+
+@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class ProxyModelAdminTests(TestCase):
fixtures = ['myhorses']
+ urls = 'proxy_models.urls'
def test_cascade_delete_proxy_model_admin_warning(self):
"""
@@ -380,3 +387,26 @@ class ProxyModelAdminTests(TestCase):
self.assertTrue(tracker_user in collector.edges.get(None, ()))
self.assertTrue(base_user in collector.edges.get(None, ()))
self.assertTrue(issue in collector.edges.get(tracker_user, ()))
+
+ def test_delete_str_in_model_admin(self):
+ """
+ Test if the admin delete page shows the correct string representation
+ for a proxy model.
+ """
+ user = TrackerUser.objects.get(name='Django Pony')
+ proxy = ProxyTrackerUser.objects.get(name='Django Pony')
+
+ user_str = (
+ 'Tracker user: <a href="/admin/proxy_models/trackeruser/%s/">%s</a>' % (user.pk, user))
+ proxy_str = (
+ 'Proxy tracker user: <a href="/admin/proxy_models/proxytrackeruser/%s/">%s</a>' %
+ (proxy.pk, proxy))
+
+ self.client.login(username='super', password='secret')
+ response = self.client.get('/admin/proxy_models/trackeruser/%s/delete/' % (user.pk,))
+ delete_str = response.context['deleted_objects'][0]
+ self.assertEqual(delete_str, user_str)
+ response = self.client.get('/admin/proxy_models/proxytrackeruser/%s/delete/' % (proxy.pk,))
+ delete_str = response.context['deleted_objects'][0]
+ self.assertEqual(delete_str, proxy_str)
+ self.client.logout()
diff --git a/tests/proxy_models/urls.py b/tests/proxy_models/urls.py
new file mode 100644
index 0000000000..644a34f556
--- /dev/null
+++ b/tests/proxy_models/urls.py
@@ -0,0 +1,7 @@
+from django.conf.urls import patterns, include
+
+from django.contrib import admin
+
+urlpatterns = patterns('',
+ (r'^admin/', include(admin.site.urls)),
+)
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 6e03b0d7f6..4d9ffb353f 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -27,7 +27,6 @@ from .models import (
BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book,
MyObject, Order, OrderItem)
-
class BaseQuerysetTest(TestCase):
def assertValueQuerysetEqual(self, qs, values):
return self.assertQuerysetEqual(qs, values, transform=lambda x: x)
@@ -84,6 +83,19 @@ class Queries1Tests(BaseQuerysetTest):
Cover.objects.create(title="first", item=i4)
Cover.objects.create(title="second", item=self.i2)
+ def test_subquery_condition(self):
+ qs1 = Tag.objects.filter(pk__lte=0)
+ qs2 = Tag.objects.filter(parent__in=qs1)
+ qs3 = Tag.objects.filter(parent__in=qs2)
+ self.assertEqual(qs3.query.subq_aliases, set(['T', 'U', 'V']))
+ self.assertIn('v0', str(qs3.query).lower())
+ qs4 = qs3.filter(parent__in=qs1)
+ self.assertEqual(qs4.query.subq_aliases, set(['T', 'U', 'V']))
+ # It is possible to reuse U for the second subquery, no need to use W.
+ self.assertNotIn('w0', str(qs4.query).lower())
+ # So, 'U0."id"' is referenced twice.
+ self.assertTrue(str(qs4.query).lower().count('u0'), 2)
+
def test_ticket1050(self):
self.assertQuerysetEqual(
Item.objects.filter(tags__isnull=True),
@@ -810,7 +822,7 @@ class Queries1Tests(BaseQuerysetTest):
# Make sure bump_prefix() (an internal Query method) doesn't (re-)break. It's
# sufficient that this query runs without error.
qs = Tag.objects.values_list('id', flat=True).order_by('id')
- qs.query.bump_prefix()
+ qs.query.bump_prefix(qs.query)
first = qs[0]
self.assertEqual(list(qs), list(range(first, first+5)))
@@ -2940,6 +2952,16 @@ class Ticket20788Tests(TestCase):
self.assertQuerysetEqual(
sentences_not_in_pub, [book2], lambda x: x)
+class Ticket12807Tests(TestCase):
+ def test_ticket_12807(self):
+ p1 = Paragraph.objects.create()
+ p2 = Paragraph.objects.create()
+ # The ORed condition below should have no effect on the query - the
+ # ~Q(pk__in=[]) will always be True.
+ qs = Paragraph.objects.filter((Q(pk=p2.pk) | ~Q(pk__in=[])) & Q(pk=p1.pk))
+ self.assertQuerysetEqual(qs, [p1], lambda x: x)
+
+
class RelatedLookupTypeTests(TestCase):
def test_wrong_type_lookup(self):
oa = ObjectA.objects.create(name="oa")
diff --git a/tests/raw_query/models.py b/tests/raw_query/models.py
index e7e221dc6e..33f754958e 100644
--- a/tests/raw_query/models.py
+++ b/tests/raw_query/models.py
@@ -18,7 +18,7 @@ class Author(models.Model):
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author)
- paperback = models.BooleanField()
+ paperback = models.BooleanField(default=False)
opening_line = models.TextField()
class Coffee(models.Model):
diff --git a/tests/reverse_single_related/models.py b/tests/reverse_single_related/models.py
index 30ba345120..5d53e04772 100644
--- a/tests/reverse_single_related/models.py
+++ b/tests/reverse_single_related/models.py
@@ -6,7 +6,7 @@ class SourceManager(models.Manager):
return super(SourceManager, self).get_queryset().filter(is_public=True)
class Source(models.Model):
- is_public = models.BooleanField()
+ is_public = models.BooleanField(default=False)
objects = SourceManager()
class Item(models.Model):
diff --git a/tests/runtests.py b/tests/runtests.py
index 53318a7461..adfc77b13b 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -60,6 +60,7 @@ ALWAYS_INSTALLED_APPS = [
'staticfiles_tests',
'staticfiles_tests.apps.test',
'staticfiles_tests.apps.no_label',
+ 'servers.another_app',
]
diff --git a/tests/serializers_regress/models.py b/tests/serializers_regress/models.py
index 21a3448a8e..ab3d3063a4 100644
--- a/tests/serializers_regress/models.py
+++ b/tests/serializers_regress/models.py
@@ -16,7 +16,7 @@ class BinaryData(models.Model):
data = models.BinaryField(null=True)
class BooleanData(models.Model):
- data = models.BooleanField()
+ data = models.BooleanField(default=False)
class CharData(models.Model):
data = models.CharField(max_length=30, null=True)
@@ -166,7 +166,7 @@ class Intermediate(models.Model):
# or all database backends.
class BooleanPKData(models.Model):
- data = models.BooleanField(primary_key=True)
+ data = models.BooleanField(primary_key=True, default=False)
class CharPKData(models.Model):
data = models.CharField(max_length=30, primary_key=True)
diff --git a/tests/servers/another_app/__init__.py b/tests/servers/another_app/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/servers/another_app/__init__.py
diff --git a/tests/servers/another_app/models.py b/tests/servers/another_app/models.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tests/servers/another_app/models.py
diff --git a/tests/servers/another_app/static/another_app/another_app_static_file.txt b/tests/servers/another_app/static/another_app/another_app_static_file.txt
new file mode 100644
index 0000000000..a2784fa8e2
--- /dev/null
+++ b/tests/servers/another_app/static/another_app/another_app_static_file.txt
@@ -0,0 +1 @@
+static file from another_app
diff --git a/tests/servers/tests.py b/tests/servers/tests.py
index 1f02a88d5a..0340873013 100644
--- a/tests/servers/tests.py
+++ b/tests/servers/tests.py
@@ -145,6 +145,14 @@ class LiveServerViews(LiveServerBase):
f = self.urlopen('/static/example_static_file.txt')
self.assertEqual(f.read().rstrip(b'\r\n'), b'example static file')
+ def test_collectstatic_emulation(self):
+ """
+ Test LiveServerTestCase use of staticfiles' serve() allows it to
+ discover app's static assets without having to collectstatic first.
+ """
+ f = self.urlopen('/static/another_app/another_app_static_file.txt')
+ self.assertEqual(f.read().rstrip(b'\r\n'), b'static file from another_app')
+
def test_media_files(self):
"""
Ensure that the LiveServerTestCase serves media files.
diff --git a/tests/template_tests/test_context.py b/tests/template_tests/test_context.py
index ca167a73f3..7bcfb7f9f2 100644
--- a/tests/template_tests/test_context.py
+++ b/tests/template_tests/test_context.py
@@ -2,7 +2,7 @@
from unittest import TestCase
-from django.template import Context
+from django.template import Context, Variable, VariableDoesNotExist
class ContextTests(TestCase):
@@ -25,3 +25,12 @@ class ContextTests(TestCase):
with c.push(a=3):
self.assertEqual(c['a'], 3)
self.assertEqual(c['a'], 1)
+
+ def test_resolve_on_context_method(self):
+ # Regression test for #17778
+ empty_context = Context()
+ self.assertRaises(VariableDoesNotExist,
+ Variable('no_such_variable').resolve, empty_context)
+ self.assertRaises(VariableDoesNotExist,
+ Variable('new').resolve, empty_context)
+ self.assertEqual(Variable('new').resolve(Context({'new': 'foo'})), 'foo')
diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
index aa0283be79..034280f08d 100644
--- a/tests/template_tests/tests.py
+++ b/tests/template_tests/tests.py
@@ -1575,6 +1575,13 @@ class TemplateTests(TransRealMixin, TestCase):
# Test whitespace in filter argument
'widthratio15': ('{% load custom %}{% widthratio a|noop:"x y" b 0 %}', {'a':50,'b':100}, '0'),
+ # Widthratio with variable assignment
+ 'widthratio16': ('{% widthratio a b 100 as variable %}-{{ variable }}-', {'a':50,'b':100}, '-50-'),
+ 'widthratio17': ('{% widthratio a b 100 as variable %}-{{ variable }}-', {'a':100,'b':100}, '-100-'),
+
+ 'widthratio18': ('{% widthratio a b 100 as %}', { }, template.TemplateSyntaxError),
+ 'widthratio19': ('{% widthratio a b 100 not_as variable %}', { }, template.TemplateSyntaxError),
+
### WITH TAG ########################################################
'with01': ('{% with key=dict.key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'),
'legacywith01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key': 50}}, '50'),
diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py
index 67e66fa52d..857d6d83c0 100644
--- a/tests/test_client_regress/tests.py
+++ b/tests/test_client_regress/tests.py
@@ -925,6 +925,14 @@ class ContextTests(TestCase):
finally:
django.template.context._standard_context_processors = None
+ def test_nested_requests(self):
+ """
+ response.context is not lost when view call another view.
+ """
+ response = self.client.get("/test_client_regress/nested_view/")
+ self.assertEqual(response.context.__class__, Context)
+ self.assertEqual(response.context['nested'], 'yes')
+
@override_settings(PASSWORD_HASHERS=('django.contrib.auth.hashers.SHA1PasswordHasher',))
class SessionTests(TestCase):
diff --git a/tests/test_client_regress/urls.py b/tests/test_client_regress/urls.py
index 6a0b330e02..1bde10315d 100644
--- a/tests/test_client_regress/urls.py
+++ b/tests/test_client_regress/urls.py
@@ -11,6 +11,7 @@ urlpatterns = patterns('',
(r'^request_data/$', views.request_data),
(r'^request_data_extended/$', views.request_data, {'template':'extended.html', 'data':'bacon'}),
url(r'^arg_view/(?P<name>.+)/$', views.view_with_argument, name='arg_view'),
+ url(r'^nested_view/$', views.nested_view, name='nested_view'),
(r'^login_protected_redirect_view/$', views.login_protected_redirect_view),
(r'^redirects/$', RedirectView.as_view(url='/test_client_regress/redirects/further/')),
(r'^redirects/further/$', RedirectView.as_view(url='/test_client_regress/redirects/further/more/')),
diff --git a/tests/test_client_regress/views.py b/tests/test_client_regress/views.py
index 71e5b526e5..d7c54cf2f7 100644
--- a/tests/test_client_regress/views.py
+++ b/tests/test_client_regress/views.py
@@ -5,8 +5,10 @@ from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseRedirect
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
+from django.test import Client
+from django.test.client import CONTENT_TYPE_RE
+from django.test.utils import setup_test_environment
class CustomTestException(Exception):
@@ -52,6 +54,15 @@ def view_with_argument(request, name):
else:
return HttpResponse('Howdy, %s' % name)
+def nested_view(request):
+ """
+ A view that uses test client to call another view.
+ """
+ setup_test_environment()
+ c = Client()
+ c.get("/test_client_regress/no_template_view")
+ return render_to_response('base.html', {'nested':'yes'})
+
def login_protected_redirect_view(request):
"A view that redirects all requests to the GET view"
return HttpResponseRedirect('/test_client_regress/get_view/')
diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py
index eb850cc3be..1b3c78b2e2 100644
--- a/tests/test_utils/tests.py
+++ b/tests/test_utils/tests.py
@@ -152,9 +152,8 @@ class CaptureQueriesContextManagerTests(TestCase):
self.assertEqual(2, len(captured_queries))
def test_failure(self):
- with self.assertRaises(TypeError):
- with CaptureQueriesContext(connection):
- raise TypeError
+ with self.assertRaises(TypeError), CaptureQueriesContext(connection):
+ raise TypeError
def test_with_client(self):
with CaptureQueriesContext(connection) as captured_queries:
@@ -190,14 +189,12 @@ class AssertNumQueriesContextManagerTests(TestCase):
Person.objects.count()
def test_failure(self):
- with self.assertRaises(AssertionError) as exc_info:
- with self.assertNumQueries(2):
- Person.objects.count()
+ with self.assertRaises(AssertionError) as exc_info, self.assertNumQueries(2):
+ Person.objects.count()
self.assertIn("1 queries executed, 2 expected", str(exc_info.exception))
- with self.assertRaises(TypeError):
- with self.assertNumQueries(4000):
- raise TypeError
+ with self.assertRaises(TypeError), self.assertNumQueries(4000):
+ raise TypeError
def test_with_client(self):
person = Person.objects.create(name="test")
@@ -232,13 +229,13 @@ class AssertTemplateUsedContextManagerTests(TestCase):
render_to_string('template_used/base.html')
def test_nested_usage(self):
- with self.assertTemplateUsed('template_used/base.html'):
- with self.assertTemplateUsed('template_used/include.html'):
- render_to_string('template_used/include.html')
+ with self.assertTemplateUsed('template_used/base.html'), \
+ self.assertTemplateUsed('template_used/include.html'):
+ render_to_string('template_used/include.html')
- with self.assertTemplateUsed('template_used/extends.html'):
- with self.assertTemplateUsed('template_used/base.html'):
- render_to_string('template_used/extends.html')
+ with self.assertTemplateUsed('template_used/extends.html'), \
+ self.assertTemplateUsed('template_used/base.html'):
+ render_to_string('template_used/extends.html')
with self.assertTemplateUsed('template_used/base.html'):
with self.assertTemplateUsed('template_used/alternative.html'):
@@ -258,38 +255,34 @@ class AssertTemplateUsedContextManagerTests(TestCase):
pass
def test_error_message(self):
- with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'):
- with self.assertTemplateUsed('template_used/base.html'):
- pass
+ with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'), \
+ self.assertTemplateUsed('template_used/base.html'):
+ pass
- with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'):
- with self.assertTemplateUsed(template_name='template_used/base.html'):
- pass
+ with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html'), \
+ self.assertTemplateUsed(template_name='template_used/base.html'):
+ pass
- with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html.*template_used/alternative\.html$'):
- with self.assertTemplateUsed('template_used/base.html'):
- render_to_string('template_used/alternative.html')
+ with six.assertRaisesRegex(self, AssertionError, r'^template_used/base\.html.*template_used/alternative\.html$'), \
+ self.assertTemplateUsed('template_used/base.html'):
+ render_to_string('template_used/alternative.html')
def test_failure(self):
- with self.assertRaises(TypeError):
- with self.assertTemplateUsed():
- pass
+ with self.assertRaises(TypeError), self.assertTemplateUsed():
+ pass
- with self.assertRaises(AssertionError):
- with self.assertTemplateUsed(''):
- pass
+ with self.assertRaises(AssertionError), self.assertTemplateUsed(''):
+ pass
- with self.assertRaises(AssertionError):
- with self.assertTemplateUsed(''):
- render_to_string('template_used/base.html')
+ with self.assertRaises(AssertionError), self.assertTemplateUsed(''):
+ render_to_string('template_used/base.html')
- with self.assertRaises(AssertionError):
- with self.assertTemplateUsed(template_name=''):
- pass
+ with self.assertRaises(AssertionError), self.assertTemplateUsed(template_name=''):
+ pass
- with self.assertRaises(AssertionError):
- with self.assertTemplateUsed('template_used/base.html'):
- render_to_string('template_used/alternative.html')
+ with self.assertRaises(AssertionError), \
+ self.assertTemplateUsed('template_used/base.html'):
+ render_to_string('template_used/alternative.html')
class HTMLEqualTests(TestCase):
diff --git a/tests/transactions/tests.py b/tests/transactions/tests.py
index 9cf8b4d742..d7cbbd0f3c 100644
--- a/tests/transactions/tests.py
+++ b/tests/transactions/tests.py
@@ -65,10 +65,9 @@ class AtomicTests(TransactionTestCase):
self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
def test_rollback(self):
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_nested_commit_commit(self):
@@ -82,30 +81,27 @@ class AtomicTests(TransactionTestCase):
def test_nested_commit_rollback(self):
with transaction.atomic():
Reporter.objects.create(first_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
def test_nested_rollback_commit(self):
- with six.assertRaisesRegex(self, Exception, "Oops"):
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
with transaction.atomic():
- Reporter.objects.create(last_name="Tintin")
- with transaction.atomic():
- Reporter.objects.create(last_name="Haddock")
- raise Exception("Oops, that's his first name")
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_nested_rollback_rollback(self):
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(last_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
- raise Exception("Oops, that's his first name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic():
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_merged_commit_commit(self):
@@ -119,31 +115,29 @@ class AtomicTests(TransactionTestCase):
def test_merged_commit_rollback(self):
with transaction.atomic():
Reporter.objects.create(first_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), \
+ transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
# Writes in the outer block are rolled back too.
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_merged_rollback_commit(self):
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(last_name="Tintin")
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(last_name="Haddock")
- raise Exception("Oops, that's his first name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_merged_rollback_rollback(self):
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic():
- Reporter.objects.create(last_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
- raise Exception("Oops, that's his first name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), transaction.atomic():
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_reuse_commit_commit(self):
@@ -159,32 +153,29 @@ class AtomicTests(TransactionTestCase):
atomic = transaction.atomic()
with atomic:
Reporter.objects.create(first_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with atomic:
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), atomic:
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
self.assertQuerysetEqual(Reporter.objects.all(), ['<Reporter: Tintin>'])
def test_reuse_rollback_commit(self):
atomic = transaction.atomic()
- with six.assertRaisesRegex(self, Exception, "Oops"):
+ with six.assertRaisesRegex(self, Exception, "Oops"), atomic:
+ Reporter.objects.create(last_name="Tintin")
with atomic:
- Reporter.objects.create(last_name="Tintin")
- with atomic:
- Reporter.objects.create(last_name="Haddock")
- raise Exception("Oops, that's his first name")
+ Reporter.objects.create(last_name="Haddock")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_reuse_rollback_rollback(self):
atomic = transaction.atomic()
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with atomic:
- Reporter.objects.create(last_name="Tintin")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with atomic:
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
- raise Exception("Oops, that's his first name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), atomic:
+ Reporter.objects.create(last_name="Tintin")
+ with six.assertRaisesRegex(self, Exception, "Oops"):
+ with atomic:
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
+ raise Exception("Oops, that's his first name")
self.assertQuerysetEqual(Reporter.objects.all(), [])
def test_force_rollback(self):
@@ -200,10 +191,9 @@ class AtomicTests(TransactionTestCase):
Reporter.objects.create(first_name="Tintin")
sid = transaction.savepoint()
# trigger a database error inside an inner atomic without savepoint
- with self.assertRaises(DatabaseError):
- with transaction.atomic(savepoint=False):
- connection.cursor().execute(
- "SELECT no_such_col FROM transactions_reporter")
+ with self.assertRaises(DatabaseError), transaction.atomic(savepoint=False):
+ connection.cursor().execute(
+ "SELECT no_such_col FROM transactions_reporter")
transaction.savepoint_rollback(sid)
# atomic block should rollback, but prevent it, as we just did it.
self.assertTrue(transaction.get_rollback())
@@ -263,10 +253,10 @@ class AtomicMergeTests(TransactionTestCase):
Reporter.objects.create(first_name="Tintin")
with transaction.atomic(savepoint=False):
Reporter.objects.create(first_name="Archibald", last_name="Haddock")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(first_name="Tournesol")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), \
+ transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Tournesol")
+ raise Exception("Oops, that's his last name")
# It wasn't possible to roll back
self.assertEqual(Reporter.objects.count(), 3)
# It wasn't possible to roll back
@@ -279,10 +269,10 @@ class AtomicMergeTests(TransactionTestCase):
Reporter.objects.create(first_name="Tintin")
with transaction.atomic():
Reporter.objects.create(first_name="Archibald", last_name="Haddock")
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(first_name="Tournesol")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), \
+ transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Tournesol")
+ raise Exception("Oops, that's his last name")
# It wasn't possible to roll back
self.assertEqual(Reporter.objects.count(), 3)
# The first block with a savepoint must roll back
@@ -293,10 +283,10 @@ class AtomicMergeTests(TransactionTestCase):
with transaction.atomic():
Reporter.objects.create(first_name="Tintin")
# Inner block without a savepoint fails
- with six.assertRaisesRegex(self, Exception, "Oops"):
- with transaction.atomic(savepoint=False):
- Reporter.objects.create(first_name="Haddock")
- raise Exception("Oops, that's his last name")
+ with six.assertRaisesRegex(self, Exception, "Oops"), \
+ transaction.atomic(savepoint=False):
+ Reporter.objects.create(first_name="Haddock")
+ raise Exception("Oops, that's his last name")
# It wasn't possible to roll back
self.assertEqual(Reporter.objects.count(), 2)
# Inner block with a savepoint succeeds
@@ -316,9 +306,9 @@ class AtomicErrorsTests(TransactionTestCase):
def test_atomic_prevents_setting_autocommit(self):
autocommit = transaction.get_autocommit()
- with transaction.atomic():
- with self.assertRaises(transaction.TransactionManagementError):
- transaction.set_autocommit(not autocommit)
+ with transaction.atomic(), \
+ self.assertRaises(transaction.TransactionManagementError):
+ transaction.set_autocommit(not autocommit)
# Make sure autocommit wasn't changed.
self.assertEqual(connection.autocommit, autocommit)
@@ -552,9 +542,8 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
The autocommit context manager works exactly the same as the default
behavior.
"""
- with self.assertRaises(Exception):
- with transaction.autocommit():
- self.create_reporter_and_fail()
+ with self.assertRaises(Exception), transaction.autocommit():
+ self.create_reporter_and_fail()
self.assertEqual(Reporter.objects.count(), 1)
@@ -563,9 +552,8 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
"""
The autocommit context manager also works with a using argument.
"""
- with self.assertRaises(Exception):
- with transaction.autocommit(using="default"):
- self.create_reporter_and_fail()
+ with self.assertRaises(Exception), transaction.autocommit(using="default"):
+ self.create_reporter_and_fail()
self.assertEqual(Reporter.objects.count(), 1)
@@ -575,9 +563,8 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
With the commit_on_success context manager, the transaction is only
committed if the block doesn't throw an exception.
"""
- with self.assertRaises(Exception):
- with transaction.commit_on_success():
- self.create_reporter_and_fail()
+ with self.assertRaises(Exception), transaction.commit_on_success():
+ self.create_reporter_and_fail()
self.assertEqual(Reporter.objects.count(), 0)
@@ -586,9 +573,8 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
"""
The commit_on_success context manager also works with a using argument.
"""
- with self.assertRaises(Exception):
- with transaction.commit_on_success(using="default"):
- self.create_reporter_and_fail()
+ with self.assertRaises(Exception), transaction.commit_on_success(using="default"):
+ self.create_reporter_and_fail()
self.assertEqual(Reporter.objects.count(), 0)
@@ -633,18 +619,18 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
"""
If you forget, you'll get bad errors.
"""
- with self.assertRaises(transaction.TransactionManagementError):
- with transaction.commit_manually():
- Reporter.objects.create(first_name="Scott", last_name="Browning")
+ with self.assertRaises(transaction.TransactionManagementError), \
+ transaction.commit_manually():
+ Reporter.objects.create(first_name="Scott", last_name="Browning")
@skipUnlessDBFeature('supports_transactions')
def test_manually_managed_with_using(self):
"""
The commit_manually function also works with a using argument.
"""
- with self.assertRaises(transaction.TransactionManagementError):
- with transaction.commit_manually(using="default"):
- Reporter.objects.create(first_name="Walter", last_name="Cronkite")
+ with self.assertRaises(transaction.TransactionManagementError), \
+ transaction.commit_manually(using="default"):
+ Reporter.objects.create(first_name="Walter", last_name="Cronkite")
@skipUnlessDBFeature('requires_rollback_on_dirty_transaction')
def test_bad_sql(self):
@@ -654,8 +640,7 @@ class TransactionContextManagerTests(IgnoreDeprecationWarningsMixin, Transaction
be rolled back. The bug is only visible using the psycopg2 backend,
though the fix is generally a good idea.
"""
- with self.assertRaises(IntegrityError):
- with transaction.commit_on_success():
- cursor = connection.cursor()
- cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
+ with self.assertRaises(IntegrityError), transaction.commit_on_success():
+ cursor = connection.cursor()
+ cursor.execute("INSERT INTO transactions_reporter (first_name, last_name) VALUES ('Douglas', 'Adams');")
transaction.rollback()
diff --git a/tests/utils_tests/test_simplelazyobject.py b/tests/utils_tests/test_simplelazyobject.py
index 7b681d9290..f16b5e4da1 100644
--- a/tests/utils_tests/test_simplelazyobject.py
+++ b/tests/utils_tests/test_simplelazyobject.py
@@ -136,6 +136,9 @@ class TestUtilsSimpleLazyObject(TestCase):
self.assertEqual(lazydict['one'], 1)
lazydict['one'] = -1
self.assertEqual(lazydict['one'], -1)
+ self.assertTrue('one' in lazydict)
+ self.assertFalse('two' in lazydict)
+ self.assertEqual(len(lazydict), 1)
del lazydict['one']
with self.assertRaises(KeyError):
lazydict['one']
@@ -183,3 +186,13 @@ class TestUtilsSimpleLazyObject(TestCase):
# This would fail with "TypeError: expected string or Unicode object, NoneType found".
pickled = cPickle.dumps(x)
+
+ def test_list_set(self):
+ lazy_list = SimpleLazyObject(lambda: [1, 2, 3, 4, 5])
+ lazy_set = SimpleLazyObject(lambda: set([1, 2, 3, 4]))
+ self.assertTrue(1 in lazy_list)
+ self.assertTrue(1 in lazy_set)
+ self.assertFalse(6 in lazy_list)
+ self.assertFalse(6 in lazy_set)
+ self.assertEqual(len(lazy_list), 5)
+ self.assertEqual(len(lazy_set), 4)
diff --git a/tests/view_tests/tests/test_debug.py b/tests/view_tests/tests/test_debug.py
index 0159886918..e941f4ae0e 100644
--- a/tests/view_tests/tests/test_debug.py
+++ b/tests/view_tests/tests/test_debug.py
@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import inspect
import os
+import re
import shutil
import sys
from tempfile import NamedTemporaryFile, mkdtemp, mkstemp
@@ -69,6 +70,21 @@ class DebugViewTests(TestCase):
self.assertRaises(BrokenException, self.client.get,
reverse('view_exception', args=(n,)))
+ def test_non_l10ned_numeric_ids(self):
+ """
+ Numeric IDs and fancy traceback context blocks line numbers shouldn't be localized.
+ """
+ with self.settings(DEBUG=True, USE_L10N=True):
+ response = self.client.get('/views/raises500/')
+ # We look for a HTML fragment of the form
+ # '<div class="context" id="c38123208">', not '<div class="context" id="c38,123,208"'
+ self.assertContains(response, '<div class="context" id="', status_code=500)
+ match = re.search(b'<div class="context" id="(?P<id>[^"]+)">', response.content)
+ self.assertFalse(match is None)
+ id_repr = match.group('id')
+ self.assertFalse(re.search(b'[^c\d]', id_repr),
+ "Numeric IDs in debug response HTML page shouldn't be localized (value: %s)." % id_repr)
+
def test_template_exceptions(self):
for n in range(len(except_args)):
try:
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index cfee4f5093..50677947b6 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -85,20 +85,18 @@ class JsI18NTests(TestCase):
languages and you've set settings.LANGUAGE_CODE to some other language
than English.
"""
- with self.settings(LANGUAGE_CODE='es'):
- with override('en-us'):
- response = self.client.get('/views/jsi18n/')
- self.assertNotContains(response, 'esto tiene que ser traducido')
+ with self.settings(LANGUAGE_CODE='es'), override('en-us'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertNotContains(response, 'esto tiene que ser traducido')
def test_jsi18n_fallback_language(self):
"""
Let's make sure that the fallback language is still working properly
in cases where the selected language cannot be found.
"""
- with self.settings(LANGUAGE_CODE='fr'):
- with override('fi'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response, 'il faut le traduire')
+ with self.settings(LANGUAGE_CODE='fr'), override('fi'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response, 'il faut le traduire')
def testI18NLanguageNonEnglishDefault(self):
"""
@@ -107,10 +105,9 @@ class JsI18NTests(TestCase):
is English and there is not 'en' translation available. See #13388,
#3594 and #13726 for more details.
"""
- with self.settings(LANGUAGE_CODE='fr'):
- with override('en-us'):
- response = self.client.get('/views/jsi18n/')
- self.assertNotContains(response, 'Choisir une heure')
+ with self.settings(LANGUAGE_CODE='fr'), override('en-us'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertNotContains(response, 'Choisir une heure')
def test_nonenglish_default_english_userpref(self):
"""
@@ -119,20 +116,19 @@ class JsI18NTests(TestCase):
with the proper English translations. See #13726 for more details.
"""
extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app0']
- with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
- with override('en-us'):
- response = self.client.get('/views/jsi18n_english_translation/')
- self.assertContains(response, javascript_quote('this app0 string is to be translated'))
+ with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps), \
+ override('en-us'):
+ response = self.client.get('/views/jsi18n_english_translation/')
+ self.assertContains(response, javascript_quote('this app0 string is to be translated'))
def testI18NLanguageNonEnglishFallback(self):
"""
Makes sure that the fallback language is still working properly
in cases where the selected language cannot be found.
"""
- with self.settings(LANGUAGE_CODE='fr'):
- with override('none'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response, 'Choisir une heure')
+ with self.settings(LANGUAGE_CODE='fr'), override('none'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response, 'Choisir une heure')
class JsI18NTestsMultiPackage(TestCase):
@@ -149,10 +145,11 @@ class JsI18NTestsMultiPackage(TestCase):
#3594 and #13514 for more details.
"""
extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app1', 'view_tests.app2']
- with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps):
- with override('fr'):
- response = self.client.get('/views/jsi18n_multi_packages1/')
- self.assertContains(response, javascript_quote('il faut traduire cette chaîne de caractères de app1'))
+ with self.settings(LANGUAGE_CODE='en-us', INSTALLED_APPS=extended_apps), \
+ override('fr'):
+ response = self.client.get('/views/jsi18n_multi_packages1/')
+ self.assertContains(response,
+ javascript_quote('il faut traduire cette chaîne de caractères de app1'))
def testI18NDifferentNonEnLangs(self):
"""
@@ -160,20 +157,21 @@ class JsI18NTestsMultiPackage(TestCase):
English.
"""
extended_apps = list(settings.INSTALLED_APPS) + ['view_tests.app3', 'view_tests.app4']
- with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps):
- with override('es-ar'):
- response = self.client.get('/views/jsi18n_multi_packages2/')
- self.assertContains(response, javascript_quote('este texto de app3 debe ser traducido'))
+ with self.settings(LANGUAGE_CODE='fr', INSTALLED_APPS=extended_apps), \
+ override('es-ar'):
+ response = self.client.get('/views/jsi18n_multi_packages2/')
+ self.assertContains(response,
+ javascript_quote('este texto de app3 debe ser traducido'))
def testI18NWithLocalePaths(self):
extended_locale_paths = settings.LOCALE_PATHS + (
path.join(path.dirname(
path.dirname(path.abspath(upath(__file__)))), 'app3', 'locale'),)
- with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths):
- with override('es-ar'):
- response = self.client.get('/views/jsi18n/')
- self.assertContains(response,
- javascript_quote('este texto de app3 debe ser traducido'))
+ with self.settings(LANGUAGE_CODE='es-ar', LOCALE_PATHS=extended_locale_paths), \
+ override('es-ar'):
+ response = self.client.get('/views/jsi18n/')
+ self.assertContains(response,
+ javascript_quote('este texto de app3 debe ser traducido'))
skip_selenium = not os.environ.get('DJANGO_SELENIUM_TESTS', False)
diff --git a/tests/view_tests/tests/test_static.py b/tests/view_tests/tests/test_static.py
index d2f3f47fa7..8f67890c11 100644
--- a/tests/view_tests/tests/test_static.py
+++ b/tests/view_tests/tests/test_static.py
@@ -6,7 +6,7 @@ import unittest
from django.conf.urls.static import static
from django.http import HttpResponseNotModified
-from django.test import TestCase
+from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.utils.http import http_date
from django.views.static import was_modified_since
@@ -16,7 +16,7 @@ from ..urls import media_dir
@override_settings(DEBUG=True)
-class StaticTests(TestCase):
+class StaticTests(SimpleTestCase):
"""Tests django views in django/views/static.py"""
prefix = 'site_media'
@@ -94,6 +94,10 @@ class StaticTests(TestCase):
self.assertEqual(len(response_content),
int(response['Content-Length']))
+ def test_404(self):
+ response = self.client.get('/views/%s/non_existing_resource' % self.prefix)
+ self.assertEqual(404, response.status_code)
+
class StaticHelperTest(StaticTests):
"""
diff --git a/tests/view_tests/urls.py b/tests/view_tests/urls.py
index f2c910368d..4db3e262ed 100644
--- a/tests/view_tests/urls.py
+++ b/tests/view_tests/urls.py
@@ -49,6 +49,7 @@ urlpatterns = patterns('',
(r'raises400/$', views.raises400),
(r'raises403/$', views.raises403),
(r'raises404/$', views.raises404),
+ (r'raises500/$', views.raises500),
# i18n views
(r'^i18n/', include('django.conf.urls.i18n')),
diff --git a/tests/view_tests/views.py b/tests/view_tests/views.py
index 0bac7d9321..04924bc9f7 100644
--- a/tests/view_tests/views.py
+++ b/tests/view_tests/views.py
@@ -31,6 +31,14 @@ def raises(request):
except Exception:
return technical_500_response(request, *sys.exc_info())
+def raises500(request):
+ # 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:
+ raise Exception
+ except Exception:
+ return technical_500_response(request, *sys.exc_info())
+
def raises400(request):
raise SuspiciousOperation