Date: Wed, 17 Feb 2016 15:18:24 +0100
Subject: [PATCH 450/756] [1.9.x] Fixed #26231 -- Used .get_username in admin
login template.
Backport of bbe136e1a2f9cbf3fd10d49fbe8558a5b394752c from master
---
django/contrib/admin/templates/admin/login.html | 2 +-
docs/releases/1.9.3.txt | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/django/contrib/admin/templates/admin/login.html b/django/contrib/admin/templates/admin/login.html
index 7a0d650036ad..adb58d683c55 100644
--- a/django/contrib/admin/templates/admin/login.html
+++ b/django/contrib/admin/templates/admin/login.html
@@ -34,7 +34,7 @@
{% if user.is_authenticated %}
-{% blocktrans with username=request.user.username trimmed %}
+{% blocktrans with username=request.user.get_username trimmed %}
You are authenticated as {{ username }}, but are not authorized to
access this page. Would you like to login to a different account?
{% endblocktrans %}
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index cd7d67486420..ef9568102424 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -42,3 +42,7 @@ Bugfixes
regressed in Django 1.9 (:ticket:`26253`).
* Fixed ``BoundField`` to reallow slices of subwidgets (:ticket:`26267`).
+
+* Changed the admin's "permission denied" message in the login template to use
+ ``get_username`` instead of ``username`` to support custom user models
+ (:ticket:`26231`).
From cd46947ddb6719c819e75464d6aa0a10a6c10fad Mon Sep 17 00:00:00 2001
From: Ivan Tsouvarev
Date: Fri, 26 Feb 2016 09:49:02 +0300
Subject: [PATCH 451/756] [1.9.x] Fixed #26280 -- Fixed cached template loader
crash when loading nonexistent template.
Backport of 8890c533e0b53cb0021bd5faf15668430cd3075a from master
---
django/template/loaders/cached.py | 2 +-
docs/releases/1.9.3.txt | 3 +++
tests/template_tests/test_loaders.py | 12 ++++++++++++
3 files changed, 16 insertions(+), 1 deletion(-)
diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py
index c13346b9612a..5bf5c10586c3 100644
--- a/django/template/loaders/cached.py
+++ b/django/template/loaders/cached.py
@@ -131,7 +131,7 @@ def load_template(self, template_name, template_dirs=None):
template_tuple = self.template_cache.get(key)
# A cached previous failure:
if template_tuple is TemplateDoesNotExist:
- raise TemplateDoesNotExist
+ raise TemplateDoesNotExist(template_name)
elif template_tuple is None:
template, origin = self.find_template(template_name, template_dirs)
if not hasattr(template, 'render'):
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index ef9568102424..625899daa025 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -46,3 +46,6 @@ Bugfixes
* Changed the admin's "permission denied" message in the login template to use
``get_username`` instead of ``username`` to support custom user models
(:ticket:`26231`).
+
+* Fixed a crash when passing a nonexistent template name to the cached template
+ loader's ``load_template()`` method (:ticket:`26280`).
diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py
index 36147935649e..11f20c6debff 100644
--- a/tests/template_tests/test_loaders.py
+++ b/tests/template_tests/test_loaders.py
@@ -87,6 +87,18 @@ def test_load_template_missing(self):
"Cached loader failed to cache the TemplateDoesNotExist exception",
)
+ @ignore_warnings(category=RemovedInDjango20Warning)
+ def test_load_nonexistent_cached_template(self):
+ loader = self.engine.template_loaders[0]
+ template_name = 'nonexistent.html'
+
+ # fill the template cache
+ with self.assertRaises(TemplateDoesNotExist):
+ loader.find_template(template_name)
+
+ with self.assertRaisesMessage(TemplateDoesNotExist, template_name):
+ loader.get_template(template_name)
+
def test_templatedir_caching(self):
"""
#13573 -- Template directories should be part of the cache key.
From ba6f83ec95dbd265ae3d493c8d6375f36052d12e Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Fri, 18 Dec 2015 14:49:23 -0500
Subject: [PATCH 452/756] [1.9.x] Fixed #26286 -- Prevented content type
managers from sharing their cache.
This should prevent managers methods from returning content type instances
registered to foreign apps now that these managers are also attached to models
created during migration phases.
Thanks Tim for the review.
Refs #23822.
Backport of 3938b3ccaa85f1c366909a4839696007726a09da from master
---
django/contrib/contenttypes/models.py | 20 +++++++++++---------
docs/releases/1.8.10.txt | 3 +++
docs/releases/1.9.3.txt | 3 +++
tests/contenttypes_tests/test_models.py | 14 +++++++++++++-
4 files changed, 30 insertions(+), 10 deletions(-)
diff --git a/django/contrib/contenttypes/models.py b/django/contrib/contenttypes/models.py
index 27a5388a702e..8d459f65a2ac 100644
--- a/django/contrib/contenttypes/models.py
+++ b/django/contrib/contenttypes/models.py
@@ -13,13 +13,15 @@
class ContentTypeManager(models.Manager):
use_in_migrations = True
- # Cache to avoid re-looking up ContentType objects all over the place.
- # This cache is shared by all the get_for_* methods.
- _cache = {}
+ def __init__(self, *args, **kwargs):
+ super(ContentTypeManager, self).__init__(*args, **kwargs)
+ # Cache shared by all the get_for_* methods to speed up
+ # ContentType retrieval.
+ self._cache = {}
def get_by_natural_key(self, app_label, model):
try:
- ct = self.__class__._cache[self.db][(app_label, model)]
+ ct = self._cache[self.db][(app_label, model)]
except KeyError:
ct = self.get(app_label=app_label, model=model)
self._add_to_cache(self.db, ct)
@@ -34,7 +36,7 @@ def _get_opts(self, model, for_concrete_model):
def _get_from_cache(self, opts):
key = (opts.app_label, opts.model_name)
- return self.__class__._cache[self.db][key]
+ return self._cache[self.db][key]
def create(self, **kwargs):
if 'name' in kwargs:
@@ -129,7 +131,7 @@ def get_for_id(self, id):
(though ContentTypes are obviously not created on-the-fly by get_by_id).
"""
try:
- ct = self.__class__._cache[self.db][id]
+ ct = self._cache[self.db][id]
except KeyError:
# This could raise a DoesNotExist; that's correct behavior and will
# make sure that only correct ctypes get stored in the cache dict.
@@ -144,15 +146,15 @@ def clear_cache(self):
django.contrib.contenttypes.management.update_contenttypes for where
this gets called).
"""
- self.__class__._cache.clear()
+ self._cache.clear()
def _add_to_cache(self, using, ct):
"""Insert a ContentType into the cache."""
# Note it's possible for ContentType objects to be stale; model_class() will return None.
# Hence, there is no reliance on model._meta.app_label here, just using the model fields instead.
key = (ct.app_label, ct.model)
- self.__class__._cache.setdefault(using, {})[key] = ct
- self.__class__._cache.setdefault(using, {})[ct.id] = ct
+ self._cache.setdefault(using, {})[key] = ct
+ self._cache.setdefault(using, {})[ct.id] = ct
@python_2_unicode_compatible
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index c9fbd0470d70..b1319ff80204 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -26,3 +26,6 @@ Bugfixes
``URLValidator`` to fix a regression in Django 1.8 (:ticket:`26204`).
* Fixed ``BoundField`` to reallow slices of subwidgets (:ticket:`26267`).
+
+* Prevented ``ContentTypeManager`` instances from sharing their cache
+ (:ticket:`26286`).
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index 625899daa025..e0447d2afeff 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -49,3 +49,6 @@ Bugfixes
* Fixed a crash when passing a nonexistent template name to the cached template
loader's ``load_template()`` method (:ticket:`26280`).
+
+* Prevented ``ContentTypeManager`` instances from sharing their cache
+ (:ticket:`26286`).
diff --git a/tests/contenttypes_tests/test_models.py b/tests/contenttypes_tests/test_models.py
index eb436b6a64a1..75cea0a4d864 100644
--- a/tests/contenttypes_tests/test_models.py
+++ b/tests/contenttypes_tests/test_models.py
@@ -2,7 +2,7 @@
import warnings
-from django.contrib.contenttypes.models import ContentType
+from django.contrib.contenttypes.models import ContentType, ContentTypeManager
from django.contrib.contenttypes.views import shortcut
from django.contrib.sites.shortcuts import get_current_site
from django.db.utils import IntegrityError, OperationalError, ProgrammingError
@@ -170,6 +170,18 @@ def test_get_for_concrete_models(self):
DeferredProxyModel: proxy_model_ct,
})
+ def test_cache_not_shared_between_managers(self):
+ with self.assertNumQueries(1):
+ ContentType.objects.get_for_model(ContentType)
+ with self.assertNumQueries(0):
+ ContentType.objects.get_for_model(ContentType)
+ other_manager = ContentTypeManager()
+ other_manager.model = ContentType
+ with self.assertNumQueries(1):
+ other_manager.get_for_model(ContentType)
+ with self.assertNumQueries(0):
+ other_manager.get_for_model(ContentType)
+
@override_settings(ALLOWED_HOSTS=['example.com'])
def test_shortcut_view(self):
"""
From e96cdc6f99923621f543751d4cc0fddef1514422 Mon Sep 17 00:00:00 2001
From: inondle
Date: Fri, 26 Feb 2016 13:44:22 -0800
Subject: [PATCH 453/756] [1.9.x] Fixed #26275 -- Noted difference between o
and Y date format chars.
Backport of 5fb9756eba01237cc0e550da689b9b79c51c96ed from master
---
docs/ref/templates/builtins.txt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
index ad8fadfb7d04..e67dc637c39f 100644
--- a/docs/ref/templates/builtins.txt
+++ b/docs/ref/templates/builtins.txt
@@ -1349,8 +1349,9 @@ n Month without leading zeros. ``'1'`` to ``'12'``
N Month abbreviation in Associated Press ``'Jan.'``, ``'Feb.'``, ``'March'``, ``'May'``
style. Proprietary extension.
o ISO-8601 week-numbering year, ``'1999'``
- corresponding to
- the ISO-8601 week number (W)
+ corresponding to the ISO-8601 week
+ number (W) which uses leap weeks. See Y
+ for the more common year format.
O Difference to Greenwich time in hours. ``'+0200'``
P Time, in 12-hour hours, minutes and ``'1 a.m.'``, ``'1:30 p.m.'``, ``'midnight'``, ``'noon'``, ``'12:30 p.m.'``
'a.m.'/'p.m.', with minutes left off
From e9234569f69e0afcc97b3b1ff5293bad1678a076 Mon Sep 17 00:00:00 2001
From: Shai Berger
Date: Sat, 27 Feb 2016 17:55:10 +0200
Subject: [PATCH 454/756] [1.9.x] Fixed docs: release-process, Supported
Versions section, concrete example
Security & data loss fixes are applied to the two last feature releases,
not just one.
Thanks Loic Bistuer and Tim Graham for review
Backport of 3dd4e92+72e5778 from master
---
docs/internals/release-process.txt | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/docs/internals/release-process.txt b/docs/internals/release-process.txt
index e56ac9ab2af5..8c18de97a1b7 100644
--- a/docs/internals/release-process.txt
+++ b/docs/internals/release-process.txt
@@ -161,8 +161,9 @@ Django 5.1 and 5.2. At this point in time:
released as 5.1.1, 5.1.2, etc.
* Security fixes and bug fixes for data loss issues will be applied to
- ``master`` and to the ``stable/5.1.x``, and ``stable/4.2.x`` (LTS) branches.
- They will trigger the release of ``5.1.1``, ``4.2.1``, etc.
+ ``master`` and to the ``stable/5.1.x``, ``stable/5.0.x``, and
+ ``stable/4.2.x`` (LTS) branches. They will trigger the release of ``5.1.1``,
+ ``5.0.5``, ``4.2.8``, etc.
* Documentation fixes will be applied to master, and, if easily backported, to
the latest stable branch, ``5.1.x``.
From 48cf7516409c668bf11e24e6b7cd8eaea13ae3b8 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Mon, 22 Feb 2016 16:05:47 -0500
Subject: [PATCH 455/756] [1.9.x] Fixed #26186 -- Documented how app relative
relationships of abstract models behave.
This partially reverts commit bc7d201bdbaeac14a49f51a9ef292d6312b4c45e.
Thanks Tim for the review.
Refs #25858.
Backport of 0223e213dd690b6b6e0669f836a20efb10998c83 from master
---
django/db/models/fields/related.py | 21 +++-------
docs/ref/models/fields.txt | 29 +++++++++++++
docs/releases/1.9.3.txt | 4 ++
tests/model_fields/tests.py | 66 +++++++++++++++++-------------
4 files changed, 77 insertions(+), 43 deletions(-)
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index 31434864cd9e..5d9b881c16b6 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -40,7 +40,7 @@
RECURSIVE_RELATIONSHIP_CONSTANT = 'self'
-def resolve_relation(scope_model, relation, resolve_recursive_relationship=True):
+def resolve_relation(scope_model, relation):
"""
Transform relation into a model or fully-qualified model string of the form
"app_label.ModelName", relative to scope_model.
@@ -55,11 +55,12 @@ def resolve_relation(scope_model, relation, resolve_recursive_relationship=True)
"""
# Check for recursive relations
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
- if resolve_recursive_relationship:
- relation = scope_model
+ relation = scope_model
+
# Look for an "app.Model" relation
- elif isinstance(relation, six.string_types) and '.' not in relation:
- relation = "%s.%s" % (scope_model._meta.app_label, relation)
+ if isinstance(relation, six.string_types):
+ if "." not in relation:
+ relation = "%s.%s" % (scope_model._meta.app_label, relation)
return relation
@@ -303,11 +304,6 @@ def resolve_related_class(model, related, field):
field.remote_field.model = related
field.do_related_class(related, model)
lazy_related_operation(resolve_related_class, cls, self.remote_field.model, field=self)
- else:
- # Bind a lazy reference to the app in which the model is defined.
- self.remote_field.model = resolve_relation(
- cls, self.remote_field.model, resolve_recursive_relationship=False
- )
def get_forward_related_filter(self, obj):
"""
@@ -1584,11 +1580,6 @@ def resolve_through_model(_, model, field):
lazy_related_operation(resolve_through_model, cls, self.remote_field.through, field=self)
elif not cls._meta.swapped:
self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
- else:
- # Bind a lazy reference to the app in which the model is defined.
- self.remote_field.through = resolve_relation(
- cls, self.remote_field.through, resolve_recursive_relationship=False
- )
# Add the descriptor for the m2m relation.
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index d8d047e3c987..20c50a0b12b0 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -1155,6 +1155,35 @@ you can use the name of the model, rather than the model object itself::
# ...
pass
+Relationships defined this way on :ref:`abstract models
+` are resolved when the model is subclassed as a
+concrete model and are not relative to the abstract model's ``app_label``:
+
+.. snippet::
+ :filename: products/models.py
+
+ from django.db import models
+
+ class AbstractCar(models.Model):
+ manufacturer = models.ForeignKey('Manufacturer', on_delete=models.CASCADE)
+
+ class Meta:
+ abstract = True
+
+.. snippet::
+ :filename: production/models.py
+
+ from django.db import models
+ from products.models import AbstractCar
+
+ class Manufacturer(models.Model):
+ pass
+
+ class Car(AbstractCar):
+ pass
+
+ # Car.manufacturer will point to `production.Manufacturer` here.
+
To refer to models defined in another application, you can explicitly specify
a model with the full application label. For example, if the ``Manufacturer``
model above is defined in another application called ``production``, you'd
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index e0447d2afeff..e94d555ec065 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -52,3 +52,7 @@ Bugfixes
* Prevented ``ContentTypeManager`` instances from sharing their cache
(:ticket:`26286`).
+
+* Reverted a change in Django 1.9.2 (:ticket:`25858`) that prevented relative
+ lazy relationships defined on abstract models to be resolved according to
+ their concrete model's ``app_label`` (:ticket:`26186`).
diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py
index 5aafdb284491..cc6639b03234 100644
--- a/tests/model_fields/tests.py
+++ b/tests/model_fields/tests.py
@@ -251,24 +251,29 @@ class Meta:
def test_abstract_model_app_relative_foreign_key(self):
test_apps = Apps(['model_fields', 'model_fields.tests'])
- class Refered(models.Model):
- class Meta:
- apps = test_apps
- app_label = 'model_fields'
-
class AbstractReferent(models.Model):
reference = models.ForeignKey('Refered', on_delete=models.CASCADE)
class Meta:
+ apps = test_apps
app_label = 'model_fields'
abstract = True
- class ConcreteReferent(AbstractReferent):
- class Meta:
- apps = test_apps
- app_label = 'tests'
+ def assert_app_model_resolved(label):
+ class Refered(models.Model):
+ class Meta:
+ apps = test_apps
+ app_label = label
+
+ class ConcreteReferent(AbstractReferent):
+ class Meta:
+ apps = test_apps
+ app_label = label
+
+ self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
- self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
+ assert_app_model_resolved('model_fields')
+ assert_app_model_resolved('tests')
class ManyToManyFieldTests(test.SimpleTestCase):
@@ -295,33 +300,38 @@ class Meta:
def test_abstract_model_app_relative_foreign_key(self):
test_apps = Apps(['model_fields', 'model_fields.tests'])
- class Refered(models.Model):
+ class AbstractReferent(models.Model):
+ reference = models.ManyToManyField('Refered', through='Through')
+
class Meta:
apps = test_apps
app_label = 'model_fields'
+ abstract = True
- class Through(models.Model):
- refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
- referent = models.ForeignKey('tests.ConcreteReferent', on_delete=models.CASCADE)
+ def assert_app_model_resolved(label):
+ class Refered(models.Model):
+ class Meta:
+ apps = test_apps
+ app_label = label
- class Meta:
- apps = test_apps
- app_label = 'model_fields'
+ class Through(models.Model):
+ refered = models.ForeignKey('Refered', on_delete=models.CASCADE)
+ referent = models.ForeignKey('ConcreteReferent', on_delete=models.CASCADE)
- class AbstractReferent(models.Model):
- reference = models.ManyToManyField('Refered', through='Through')
+ class Meta:
+ apps = test_apps
+ app_label = label
- class Meta:
- app_label = 'model_fields'
- abstract = True
+ class ConcreteReferent(AbstractReferent):
+ class Meta:
+ apps = test_apps
+ app_label = label
- class ConcreteReferent(AbstractReferent):
- class Meta:
- apps = test_apps
- app_label = 'tests'
+ self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
+ self.assertEqual(ConcreteReferent.reference.through, Through)
- self.assertEqual(ConcreteReferent._meta.get_field('reference').related_model, Refered)
- self.assertEqual(ConcreteReferent.reference.through, Through)
+ assert_app_model_resolved('model_fields')
+ assert_app_model_resolved('tests')
class TextFieldTests(test.TestCase):
From 46c6ac1ffc619a5f0c2acf43fd48f85e1971b8a8 Mon Sep 17 00:00:00 2001
From: Taranjeet
Date: Tue, 1 Mar 2016 15:07:22 +0530
Subject: [PATCH 456/756] [1.9.x] Fixed typos in docs/ref/models/meta.txt.
Backport of 11a8207d4294b46561ce34b37803f191014509af from master
---
docs/ref/models/meta.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/ref/models/meta.txt b/docs/ref/models/meta.txt
index b0868d341c60..9e4d9435a8ca 100644
--- a/docs/ref/models/meta.txt
+++ b/docs/ref/models/meta.txt
@@ -179,11 +179,11 @@ can be made to convert your code to the new API:
then check if:
- ``f.auto_created == False``, because the new ``get_field()``
- API will find "reverse" relations), and:
+ API will find "reverse" relations, and:
- ``f.is_relation and f.related_model is None``, because the new
``get_field()`` API will find
- :class:`~django.contrib.contenttypes.fields.GenericForeignKey` relations;
+ :class:`~django.contrib.contenttypes.fields.GenericForeignKey` relations.
* ``MyModel._meta.get_field_by_name(name)`` returns a tuple of these four
values with the following replacements:
From 73d8e646d743aad79d6067ae3d8facf83b1e76a4 Mon Sep 17 00:00:00 2001
From: acemaster
Date: Wed, 3 Feb 2016 23:59:45 +0530
Subject: [PATCH 457/756] [1.9.x] Fixed #26165 -- Added some FAQs about CSRF
protection.
Thanks Florian Apolloner and Shai Berger for review.
Backport of a1b1688c7d6c1a6d307bd22669bd20f08e948f8d from master
---
docs/ref/csrf.txt | 57 +++++++++++++++++++++++++++++++++++++++++------
1 file changed, 50 insertions(+), 7 deletions(-)
diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt
index 53ed8a547220..ca94b2155207 100644
--- a/docs/ref/csrf.txt
+++ b/docs/ref/csrf.txt
@@ -240,12 +240,16 @@ The CSRF protection is based on the following things:
This check is done by ``CsrfViewMiddleware``.
4. In addition, for HTTPS requests, strict referer checking is done by
- ``CsrfViewMiddleware``. This is necessary to address a Man-In-The-Middle
- attack that is possible under HTTPS when using a session independent nonce,
- due to the fact that HTTP 'Set-Cookie' headers are (unfortunately) accepted
- by clients that are talking to a site under HTTPS. (Referer checking is not
- done for HTTP requests because the presence of the Referer header is not
- reliable enough under HTTP.)
+ ``CsrfViewMiddleware``. This means that even if a subdomain can set or
+ modify cookies on your domain, it can't force a user to post to your
+ application since that request won't come from your own exact domain.
+
+ This also addresses a man-in-the-middle attack that's possible under HTTPS
+ when using a session independent nonce, due to the fact that HTTP
+ ``Set-Cookie`` headers are (unfortunately) accepted by clients even when
+ they are talking to a site under HTTPS. (Referer checking is not done for
+ HTTP requests because the presence of the ``Referer`` header isn't reliable
+ enough under HTTP.)
If the :setting:`CSRF_COOKIE_DOMAIN` setting is set, the referer is compared
against it. This setting supports subdomains. For example,
@@ -263,7 +267,15 @@ It deliberately ignores GET requests (and other requests that are defined as
'safe' by :rfc:`2616`). These requests ought never to have any potentially
dangerous side effects , and so a CSRF attack with a GET request ought to be
harmless. :rfc:`2616` defines POST, PUT and DELETE as 'unsafe', and all other
-methods are assumed to be unsafe, for maximum protection.
+methods are also assumed to be unsafe, for maximum protection.
+
+The CSRF protection cannot protect against man-in-the-middle attacks, so use
+:ref:`HTTPS ` with
+:ref:`http-strict-transport-security`. It also assumes :ref:`validation of
+the HOST header ` and that there aren't any
+:ref:`cross-site scripting vulnerabilities ` on your site
+(because XSS vulnerabilities already let an attacker do anything a CSRF
+vulnerability allows and much worse).
.. versionchanged:: 1.9
@@ -462,3 +474,34 @@ A number of settings can be used to control Django's CSRF behavior:
* :setting:`CSRF_FAILURE_VIEW`
* :setting:`CSRF_HEADER_NAME`
* :setting:`CSRF_TRUSTED_ORIGINS`
+
+Frequently Asked Questions
+==========================
+
+Is posting an arbitrary CSRF token pair (cookie and POST data) a vulnerability?
+-------------------------------------------------------------------------------
+
+No, this is by design. Without a man-in-the-middle attack, there is no way for
+an attacker to send a CSRF token cookie to a victim's browser, so a successful
+attack would need to obtain the victim's browser's cookie via XSS or similar,
+in which case an attacker usually doesn't need CSRF attacks.
+
+Some security audit tools flag this as a problem but as mentioned before, an
+attacker cannot steal a user's browser's CSRF cookie. "Stealing" or modifying
+*your own* token using Firebug, Chrome dev tools, etc. isn't a vulnerability.
+
+Is the fact that Django's CSRF protection isn't linked to a session a problem?
+------------------------------------------------------------------------------
+
+No, this is by design. Not linking CSRF protection to a session allows using
+the protection on sites such as a `pastebin` that allow submissions from
+anonymous users which don't have a session.
+
+Why not use a new token for each request?
+-----------------------------------------
+
+Generating a new token for each request is problematic from a UI perspective
+because it invalidates all previous forms. Most users would be very unhappy to
+find that opening a new tab on your site has invalidated the form they had
+just spent time filling out in another tab or that a form they accessed via
+the back button could not be filled out.
From 468c6a3b6348fc3701ac569eeb119ddd146cf7c6 Mon Sep 17 00:00:00 2001
From: Michal Petrucha
Date: Sat, 20 Feb 2016 18:04:50 +0100
Subject: [PATCH 458/756] [1.9.x] Fixed #26217 -- Added a warning about format
strings to WeekArchiveView docs.
Backport of fe8ea3ba3ba709b3d6c39da046f0883a296e6441 from master
---
.../class-based-views/generic-date-based.txt | 32 ++++++++++++-------
1 file changed, 20 insertions(+), 12 deletions(-)
diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt
index d2d7b153fca4..8e93182baefc 100644
--- a/docs/ref/class-based-views/generic-date-based.txt
+++ b/docs/ref/class-based-views/generic-date-based.txt
@@ -333,6 +333,15 @@ views for displaying drilldown pages for date-based data.
* Uses a default ``template_name_suffix`` of ``_archive_week``.
+ * The ``week_format`` attribute is a :func:`~time.strptime` format string
+ used to parse the week number. The following values are supported:
+
+ * ``'%U'``: Based on the United States week system where the week
+ begins on Sunday. This is the default value.
+
+ * ``'%V'``: Similar to ``'%U'``, except it assumes that the week
+ begins on Monday. This is not the same as the ISO 8601 week number.
+
**Example myapp/views.py**::
from django.views.generic.dates import WeekArchiveView
@@ -372,24 +381,23 @@ views for displaying drilldown pages for date-based data.
{% if previous_week %}
- Previous Week: {{ previous_week|date:"F Y" }}
+ Previous Week: {{ previous_week|date:"W" }} of year {{ previous_week|date:"Y" }}
{% endif %}
{% if previous_week and next_week %}--{% endif %}
{% if next_week %}
- Next week: {{ next_week|date:"F Y" }}
+ Next week: {{ next_week|date:"W" }} of year {{ next_week|date:"Y" }}
{% endif %}
- In this example, you are outputting the week number. The default
- ``week_format`` in the ``WeekArchiveView`` uses week format ``'%U'``
- which is based on the United States week system where the week begins on a
- Sunday. The ``'%W'`` format uses the ISO week format and its week
- begins on a Monday. The ``'%W'`` format is the same in both the
- :func:`~time.strftime` and the :tfilter:`date`.
-
- However, the :tfilter:`date` template filter does not have an equivalent
- output format that supports the US based week system. The :tfilter:`date`
- filter ``'%U'`` outputs the number of seconds since the Unix epoch.
+ In this example, you are outputting the week number. Keep in mind that week
+ numbers computed by the :tfilter:`date` template filter with the ``'W'``
+ format character are not always the same as those computed by
+ :func:`~time.strftime` and :func:`~time.strptime` with the ``'%W'`` format
+ string. For year 2015, for example, week numbers output by :tfilter:`date`
+ are higher by one compared to those output by :func:`~time.strftime`. There
+ isn't an equivalent for the ``'%U'`` :func:`~time.strftime` format string
+ in :tfilter:`date`. Therefore, you should avoid using :tfilter:`date` to
+ generate URLs for ``WeekArchiveView``.
``DayArchiveView``
==================
From 7e799217c5cf1ba365af41c801d5cfbadea18fa9 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 19 Feb 2016 11:33:17 -0500
Subject: [PATCH 459/756] [1.9.x] Added stub release notes for security issues.
---
docs/releases/1.8.10.txt | 4 ++--
docs/releases/1.9.3.txt | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index b1319ff80204..d93f24372032 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -2,9 +2,9 @@
Django 1.8.10 release notes
===========================
-*Under development*
+*March 1, 2015*
-Django 1.8.10 fixes several bugs in 1.8.9.
+Django 1.8.10 fixes two security issues and several bugs in 1.8.9.
Bugfixes
========
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index e94d555ec065..8cc57d8a814c 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -2,9 +2,9 @@
Django 1.9.3 release notes
==========================
-*Under development*
+*March 1, 2015*
-Django 1.9.3 fixes several bugs in 1.9.2.
+Django 1.9.3 fixes two security issues and several bugs in 1.9.2.
Bugfixes
========
From fc6d147a63f89795dbcdecb0559256470fff4380 Mon Sep 17 00:00:00 2001
From: Mark Striemer
Date: Mon, 22 Feb 2016 16:50:23 -0500
Subject: [PATCH 460/756] [1.9.x] Fixed CVE-2016-2512 -- Prevented spoofing
is_safe_url() with basic auth.
This is a security fix.
---
django/utils/http.py | 8 ++++++--
docs/releases/1.8.10.txt | 16 ++++++++++++++++
docs/releases/1.9.3.txt | 16 ++++++++++++++++
tests/utils_tests/test_http.py | 12 ++++++++++++
4 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/django/utils/http.py b/django/utils/http.py
index 70bcbd90ac3b..fb18f48f771a 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -290,8 +290,12 @@ def is_safe_url(url, host=None):
url = url.strip()
if not url:
return False
- # Chrome treats \ completely as /
- url = url.replace('\\', '/')
+ # Chrome treats \ completely as / in paths but it could be part of some
+ # basic auth credentials so we need to check both URLs.
+ return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
+
+
+def _is_safe_url(url, host):
# Chrome considers any URL with more than two slashes to be absolute, but
# urlparse is not so flexible. Treat any url with three slashes as unsafe.
if url.startswith('///'):
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index d93f24372032..73c7cc04a4df 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -6,6 +6,22 @@ Django 1.8.10 release notes
Django 1.8.10 fixes two security issues and several bugs in 1.8.9.
+CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth
+===============================================================================================================
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login` and :doc:`i18n `)
+to redirect the user to an "on success" URL. The security check for these
+redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs
+with basic authentication credentials "safe" when they shouldn't be.
+
+For example, a URL like ``http://mysite.example.com\@attacker.com`` would be
+considered safe if the request's host is ``http://mysite.example.com``, but
+redirecting to this URL sends the user to ``attacker.com``.
+
+Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
+targets and puts such a URL into a link, they could suffer from an XSS attack.
+
Bugfixes
========
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index 8cc57d8a814c..056122b16e39 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -6,6 +6,22 @@ Django 1.9.3 release notes
Django 1.9.3 fixes two security issues and several bugs in 1.9.2.
+CVE-2016-2512: Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth
+===============================================================================================================
+
+Django relies on user input in some cases (e.g.
+:func:`django.contrib.auth.views.login` and :doc:`i18n `)
+to redirect the user to an "on success" URL. The security check for these
+redirects (namely ``django.utils.http.is_safe_url()``) considered some URLs
+with basic authentication credentials "safe" when they shouldn't be.
+
+For example, a URL like ``http://mysite.example.com\@attacker.com`` would be
+considered safe if the request's host is ``http://mysite.example.com``, but
+redirecting to this URL sends the user to ``attacker.com``.
+
+Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
+targets and puts such a URL into a link, they could suffer from an XSS attack.
+
Bugfixes
========
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index 6051818958d3..ad9a1666c35a 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -92,6 +92,11 @@ def test_is_safe_url(self):
'javascript:alert("XSS")',
'\njavascript:alert(x)',
'\x08//example.com',
+ r'http://otherserver\@example.com',
+ r'http:\\testserver\@example.com',
+ r'http://testserver\me:pass@example.com',
+ r'http://testserver\@example.com',
+ r'http:\\testserver\confirm\me@example.com',
'\n'):
self.assertFalse(http.is_safe_url(bad_url, host='testserver'), "%s should be blocked" % bad_url)
for good_url in ('/view/?param=http://example.com',
@@ -101,8 +106,15 @@ def test_is_safe_url(self):
'https://testserver/',
'HTTPS://testserver/',
'//testserver/',
+ 'http://testserver/confirm?email=me@example.com',
'/url%20with%20spaces/'):
self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url)
+ # Valid basic auth credentials are allowed.
+ self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver'))
+ # A path without host is allowed.
+ self.assertTrue(http.is_safe_url('/confirm/me@example.com'))
+ # Basic auth without host is not allowed.
+ self.assertFalse(http.is_safe_url(r'http://testserver\@example.com'))
def test_urlsafe_base64_roundtrip(self):
bytestring = b'foo'
From af7d09b0c5c6ab68e629fd9baf736f9dd203b18e Mon Sep 17 00:00:00 2001
From: Florian Apolloner
Date: Sat, 13 Feb 2016 21:09:46 +0100
Subject: [PATCH 461/756] [1.9.x] Fixed CVE-2016-2513 -- Fixed user enumeration
timing attack during login.
This is a security fix.
---
django/contrib/auth/hashers.py | 77 +++++++++++++++++++++++---------
docs/releases/1.8.10.txt | 33 ++++++++++++++
docs/releases/1.9.3.txt | 33 ++++++++++++++
docs/topics/auth/passwords.txt | 31 +++++++++++++
tests/auth_tests/test_hashers.py | 58 +++++++++++++++++++++++-
5 files changed, 211 insertions(+), 21 deletions(-)
diff --git a/django/contrib/auth/hashers.py b/django/contrib/auth/hashers.py
index 93d4598ccec0..05ac8493acb7 100644
--- a/django/contrib/auth/hashers.py
+++ b/django/contrib/auth/hashers.py
@@ -4,6 +4,7 @@
import binascii
import hashlib
import importlib
+import warnings
from collections import OrderedDict
from django.conf import settings
@@ -46,10 +47,17 @@ def check_password(password, encoded, setter=None, preferred='default'):
preferred = get_hasher(preferred)
hasher = identify_hasher(encoded)
- must_update = hasher.algorithm != preferred.algorithm
- if not must_update:
- must_update = preferred.must_update(encoded)
+ hasher_changed = hasher.algorithm != preferred.algorithm
+ must_update = hasher_changed or preferred.must_update(encoded)
is_correct = hasher.verify(password, encoded)
+
+ # If the hasher didn't change (we don't protect against enumeration if it
+ # does) and the password should get updated, try to close the timing gap
+ # between the work factor of the current encoded password and the default
+ # work factor.
+ if not is_correct and not hasher_changed and must_update:
+ hasher.harden_runtime(password, encoded)
+
if setter and is_correct and must_update:
setter(password)
return is_correct
@@ -216,6 +224,19 @@ def safe_summary(self, encoded):
def must_update(self, encoded):
return False
+ def harden_runtime(self, password, encoded):
+ """
+ Bridge the runtime gap between the work factor supplied in `encoded`
+ and the work factor suggested by this hasher.
+
+ Taking PBKDF2 as an example, if `encoded` contains 20000 iterations and
+ `self.iterations` is 30000, this method should run password through
+ another 10000 iterations of PBKDF2. Similar approaches should exist
+ for any hasher that has a work factor. If not, this method should be
+ defined as a no-op to silence the warning.
+ """
+ warnings.warn('subclasses of BasePasswordHasher should provide a harden_runtime() method')
+
class PBKDF2PasswordHasher(BasePasswordHasher):
"""
@@ -258,6 +279,12 @@ def must_update(self, encoded):
algorithm, iterations, salt, hash = encoded.split('$', 3)
return int(iterations) != self.iterations
+ def harden_runtime(self, password, encoded):
+ algorithm, iterations, salt, hash = encoded.split('$', 3)
+ extra_iterations = self.iterations - int(iterations)
+ if extra_iterations > 0:
+ self.encode(password, salt, extra_iterations)
+
class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
"""
@@ -308,23 +335,8 @@ def encode(self, password, salt):
def verify(self, password, encoded):
algorithm, data = encoded.split('$', 1)
assert algorithm == self.algorithm
- bcrypt = self._load_library()
-
- # Hash the password prior to using bcrypt to prevent password truncation
- # See: https://code.djangoproject.com/ticket/20138
- if self.digest is not None:
- # We use binascii.hexlify here because Python3 decided that a hex encoded
- # bytestring is somehow a unicode.
- password = binascii.hexlify(self.digest(force_bytes(password)).digest())
- else:
- password = force_bytes(password)
-
- # Ensure that our data is a bytestring
- data = force_bytes(data)
- # force_bytes() necessary for py-bcrypt compatibility
- hashpw = force_bytes(bcrypt.hashpw(password, data))
-
- return constant_time_compare(data, hashpw)
+ encoded_2 = self.encode(password, force_bytes(data))
+ return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
algorithm, empty, algostr, work_factor, data = encoded.split('$', 4)
@@ -341,6 +353,16 @@ def must_update(self, encoded):
algorithm, empty, algostr, rounds, data = encoded.split('$', 4)
return int(rounds) != self.rounds
+ def harden_runtime(self, password, encoded):
+ _, data = encoded.split('$', 1)
+ salt = data[:29] # Length of the salt in bcrypt.
+ rounds = data.split('$')[2]
+ # work factor is logarithmic, adding one doubles the load.
+ diff = 2**(self.rounds - int(rounds)) - 1
+ while diff > 0:
+ self.encode(password, force_bytes(salt))
+ diff -= 1
+
class BCryptPasswordHasher(BCryptSHA256PasswordHasher):
"""
@@ -388,6 +410,9 @@ def safe_summary(self, encoded):
(_('hash'), mask_hash(hash)),
])
+ def harden_runtime(self, password, encoded):
+ pass
+
class MD5PasswordHasher(BasePasswordHasher):
"""
@@ -416,6 +441,9 @@ def safe_summary(self, encoded):
(_('hash'), mask_hash(hash)),
])
+ def harden_runtime(self, password, encoded):
+ pass
+
class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
"""
@@ -448,6 +476,9 @@ def safe_summary(self, encoded):
(_('hash'), mask_hash(hash)),
])
+ def harden_runtime(self, password, encoded):
+ pass
+
class UnsaltedMD5PasswordHasher(BasePasswordHasher):
"""
@@ -481,6 +512,9 @@ def safe_summary(self, encoded):
(_('hash'), mask_hash(encoded, show=3)),
])
+ def harden_runtime(self, password, encoded):
+ pass
+
class CryptPasswordHasher(BasePasswordHasher):
"""
@@ -515,3 +549,6 @@ def safe_summary(self, encoded):
(_('salt'), salt),
(_('hash'), mask_hash(data, show=3)),
])
+
+ def harden_runtime(self, password, encoded):
+ pass
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index 73c7cc04a4df..d57afc470d3f 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -22,6 +22,39 @@ redirecting to this URL sends the user to ``attacker.com``.
Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
targets and puts such a URL into a link, they could suffer from an XSS attack.
+CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade
+================================================================================================
+
+In each major version of Django since 1.6, the default number of iterations for
+the ``PBKDF2PasswordHasher`` and its subclasses has increased. This improves
+the security of the password as the speed of hardware increases, however, it
+also creates a timing difference between a login request for a user with a
+password encoded in an older number of iterations and login request for a
+nonexistent user (which runs the default hasher's default number of iterations
+since Django 1.6).
+
+This only affects users who haven't logged in since the iterations were
+increased. The first time a user logs in after an iterations increase, their
+password is updated with the new iterations and there is no longer a timing
+difference.
+
+The new ``BasePasswordHasher.harden_runtime()`` method allows hashers to bridge
+the runtime gap between the work factor (e.g. iterations) supplied in existing
+encoded passwords and the default work factor of the hasher. This method
+is implemented for ``PBKDF2PasswordHasher`` and ``BCryptPasswordHasher``.
+The number of rounds for the latter hasher hasn't changed since Django 1.4, but
+some projects may subclass it and increase the work factor as needed.
+
+A warning will be emitted for any :ref:`third-party password hashers that don't
+implement ` a ``harden_runtime()`` method.
+
+If you have different password hashes in your database (such as SHA1 hashes
+from users who haven't logged in since the default hasher switched to PBKDF2
+in Django 1.4), the timing difference on a login request for these users may be
+even greater and this fix doesn't remedy that difference (or any difference
+when changing hashers). You may be able to :ref:`upgrade those hashes
+` to prevent a timing attack for that case.
+
Bugfixes
========
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index 056122b16e39..355eaf96e962 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -22,6 +22,39 @@ redirecting to this URL sends the user to ``attacker.com``.
Also, if a developer relies on ``is_safe_url()`` to provide safe redirect
targets and puts such a URL into a link, they could suffer from an XSS attack.
+CVE-2016-2513: User enumeration through timing difference on password hasher work factor upgrade
+================================================================================================
+
+In each major version of Django since 1.6, the default number of iterations for
+the ``PBKDF2PasswordHasher`` and its subclasses has increased. This improves
+the security of the password as the speed of hardware increases, however, it
+also creates a timing difference between a login request for a user with a
+password encoded in an older number of iterations and login request for a
+nonexistent user (which runs the default hasher's default number of iterations
+since Django 1.6).
+
+This only affects users who haven't logged in since the iterations were
+increased. The first time a user logs in after an iterations increase, their
+password is updated with the new iterations and there is no longer a timing
+difference.
+
+The new ``BasePasswordHasher.harden_runtime()`` method allows hashers to bridge
+the runtime gap between the work factor (e.g. iterations) supplied in existing
+encoded passwords and the default work factor of the hasher. This method
+is implemented for ``PBKDF2PasswordHasher`` and ``BCryptPasswordHasher``.
+The number of rounds for the latter hasher hasn't changed since Django 1.4, but
+some projects may subclass it and increase the work factor as needed.
+
+A warning will be emitted for any :ref:`third-party password hashers that don't
+implement ` a ``harden_runtime()`` method.
+
+If you have different password hashes in your database (such as SHA1 hashes
+from users who haven't logged in since the default hasher switched to PBKDF2
+in Django 1.4), the timing difference on a login request for these users may be
+even greater and this fix doesn't remedy that difference (or any difference
+when changing hashers). You may be able to :ref:`upgrade those hashes
+` to prevent a timing attack for that case.
+
Bugfixes
========
diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt
index 17d47d8a0b98..2da25ecf6ccc 100644
--- a/docs/topics/auth/passwords.txt
+++ b/docs/topics/auth/passwords.txt
@@ -195,6 +195,14 @@ unmentioned algorithms won't be able to upgrade. Hashed passwords will be
updated when increasing (or decreasing) the number of PBKDF2 iterations or
bcrypt rounds.
+Be aware that if all the passwords in your database aren't encoded in the
+default hasher's algorithm, you may be vulnerable to a user enumeration timing
+attack due to a difference between the duration of a login request for a user
+with a password encoded in a non-default algorithm and the duration of a login
+request for a nonexistent user (which runs the default hasher). You may be able
+to mitigate this by :ref:`upgrading older password hashes
+`.
+
.. versionchanged:: 1.9
Passwords updates when changing the number of bcrypt rounds was added.
@@ -288,6 +296,29 @@ Include any other hashers that your site uses in this list.
.. _bcrypt: https://en.wikipedia.org/wiki/Bcrypt
.. _`bcrypt library`: https://pypi.python.org/pypi/bcrypt/
+.. _write-your-own-password-hasher:
+
+Writing your own hasher
+-----------------------
+
+.. versionadded:: 1.9.3
+
+If you write your own password hasher that contains a work factor such as a
+number of iterations, you should implement a
+``harden_runtime(self, password, encoded)`` method to bridge the runtime gap
+between the work factor supplied in the ``encoded`` password and the default
+work factor of the hasher. This prevents a user enumeration timing attack due
+to difference between a login request for a user with a password encoded in an
+older number of iterations and a nonexistent user (which runs the default
+hasher's default number of iterations).
+
+Taking PBKDF2 as example, if ``encoded`` contains 20,000 iterations and the
+hasher's default ``iterations`` is 30,000, the method should run ``password``
+through another 10,000 iterations of PBKDF2.
+
+If your hasher doesn't have a work factor, implement the method as a no-op
+(``pass``).
+
Manually managing a user's password
===================================
diff --git a/tests/auth_tests/test_hashers.py b/tests/auth_tests/test_hashers.py
index 93a66a545b08..e67534a34406 100644
--- a/tests/auth_tests/test_hashers.py
+++ b/tests/auth_tests/test_hashers.py
@@ -10,9 +10,10 @@
check_password, get_hasher, identify_hasher, is_password_usable,
make_password,
)
-from django.test import SimpleTestCase
+from django.test import SimpleTestCase, mock
from django.test.utils import override_settings
from django.utils import six
+from django.utils.encoding import force_bytes
try:
import crypt
@@ -209,6 +210,28 @@ def setter(password):
finally:
hasher.rounds = old_rounds
+ @skipUnless(bcrypt, "bcrypt not installed")
+ def test_bcrypt_harden_runtime(self):
+ hasher = get_hasher('bcrypt')
+ self.assertEqual('bcrypt', hasher.algorithm)
+
+ with mock.patch.object(hasher, 'rounds', 4):
+ encoded = make_password('letmein', hasher='bcrypt')
+
+ with mock.patch.object(hasher, 'rounds', 6), \
+ mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
+ hasher.harden_runtime('wrong_password', encoded)
+
+ # Increasing rounds from 4 to 6 means an increase of 4 in workload,
+ # therefore hardening should run 3 times to make the timing the
+ # same (the original encode() call already ran once).
+ self.assertEqual(hasher.encode.call_count, 3)
+
+ # Get the original salt (includes the original workload factor)
+ algorithm, data = encoded.split('$', 1)
+ expected_call = (('wrong_password', force_bytes(data[:29])),)
+ self.assertEqual(hasher.encode.call_args_list, [expected_call] * 3)
+
def test_unusable(self):
encoded = make_password(None)
self.assertEqual(len(encoded), len(UNUSABLE_PASSWORD_PREFIX) + UNUSABLE_PASSWORD_SUFFIX_LENGTH)
@@ -316,6 +339,25 @@ def setter(password):
finally:
hasher.iterations = old_iterations
+ def test_pbkdf2_harden_runtime(self):
+ hasher = get_hasher('default')
+ self.assertEqual('pbkdf2_sha256', hasher.algorithm)
+
+ with mock.patch.object(hasher, 'iterations', 1):
+ encoded = make_password('letmein')
+
+ with mock.patch.object(hasher, 'iterations', 6), \
+ mock.patch.object(hasher, 'encode', side_effect=hasher.encode):
+ hasher.harden_runtime('wrong_password', encoded)
+
+ # Encode should get called once ...
+ self.assertEqual(hasher.encode.call_count, 1)
+
+ # ... with the original salt and 5 iterations.
+ algorithm, iterations, salt, hash = encoded.split('$', 3)
+ expected_call = (('wrong_password', salt, 5),)
+ self.assertEqual(hasher.encode.call_args, expected_call)
+
def test_pbkdf2_upgrade_new_hasher(self):
hasher = get_hasher('default')
self.assertEqual('pbkdf2_sha256', hasher.algorithm)
@@ -344,6 +386,20 @@ def setter(password):
self.assertTrue(check_password('letmein', encoded, setter))
self.assertTrue(state['upgraded'])
+ def test_check_password_calls_harden_runtime(self):
+ hasher = get_hasher('default')
+ encoded = make_password('letmein')
+
+ with mock.patch.object(hasher, 'harden_runtime'), \
+ mock.patch.object(hasher, 'must_update', return_value=True):
+ # Correct password supplied, no hardening needed
+ check_password('letmein', encoded)
+ self.assertEqual(hasher.harden_runtime.call_count, 0)
+
+ # Wrong password supplied, hardening needed
+ check_password('wrong_password', encoded)
+ self.assertEqual(hasher.harden_runtime.call_count, 1)
+
def test_load_library_no_algorithm(self):
with self.assertRaises(ValueError) as e:
BasePasswordHasher()._load_library()
From 37935743edbf60201adb1b53b56b8cafa754c69a Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 1 Mar 2016 11:38:39 -0500
Subject: [PATCH 462/756] [1.9.x] Bumped version for 1.9.3 release.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index 3c44b25cb942..0dceeaa65571 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (1, 9, 3, 'alpha', 0)
+VERSION = (1, 9, 3, 'final', 0)
__version__ = get_version(VERSION)
From 31ca830a29db6374b05f7d393909f1fdddceb357 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 1 Mar 2016 12:22:10 -0500
Subject: [PATCH 463/756] [1.9.x] Post-release version bump.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index 0dceeaa65571..942857ad34a0 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (1, 9, 3, 'final', 0)
+VERSION = (1, 9, 4, 'alpha', 0)
__version__ = get_version(VERSION)
From a53ee2bbf4e6676ba67160a04d02be63c2bff767 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 1 Mar 2016 12:32:42 -0500
Subject: [PATCH 464/756] [1.9.x] Added CVE-2016-2512/2513 to security release
archive.
Backport of 24fc9352183c449a8b11d1c7b442e70aa61a8800 from master
---
docs/releases/security.txt | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/docs/releases/security.txt b/docs/releases/security.txt
index ddb4871a7b7b..c8d29ef7ba6b 100644
--- a/docs/releases/security.txt
+++ b/docs/releases/security.txt
@@ -691,8 +691,8 @@ Versions affected
* Django 1.8 `(patch) `__
* Django 1.7 `(patch) `__
-February 1, 2016 -- CVE-2016-2048
----------------------------------
+February 1, 2016 - CVE-2016-2048
+--------------------------------
`CVE-2016-2048 `_:
User with "change" but not "add" permission can create objects for ``ModelAdmin``’s with ``save_as=True``.
@@ -702,3 +702,29 @@ Versions affected
~~~~~~~~~~~~~~~~~
* Django 1.9 `(patch) `__
+
+March 1, 2016 - CVE-2016-2512
+-----------------------------
+
+`CVE-2016-2512 `_:
+Malicious redirect and possible XSS attack via user-supplied redirect URLs containing basic auth.
+`Full description `__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 1.9 `(patch) `__
+* Django 1.8 `(patch) `__
+
+March 1, 2016 - CVE-2016-2513
+-----------------------------
+
+`CVE-2016-2513 `_:
+User enumeration through timing difference on password hasher work factor upgrade.
+`Full description `__
+
+Versions affected
+~~~~~~~~~~~~~~~~~
+
+* Django 1.9 `(patch) `__
+* Django 1.8 `(patch) `__
From e0ea4edca0801fec2fa1422f52c3377321651f42 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 1 Mar 2016 12:39:01 -0500
Subject: [PATCH 465/756] [1.9.x] Added stub release notes for 1.9.4.
Backport of 2e895d2870860e9855b79fcda41693783671ed12 from master
---
docs/releases/1.9.4.txt | 12 ++++++++++++
docs/releases/index.txt | 1 +
2 files changed, 13 insertions(+)
create mode 100644 docs/releases/1.9.4.txt
diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt
new file mode 100644
index 000000000000..15adcee3290e
--- /dev/null
+++ b/docs/releases/1.9.4.txt
@@ -0,0 +1,12 @@
+==========================
+Django 1.9.4 release notes
+==========================
+
+*Under development*
+
+Django 1.9.4 fixes several bugs in 1.9.3.
+
+Bugfixes
+========
+
+* ...
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index adbda90109f5..baf72288b9a8 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 1.9.4
1.9.3
1.9.2
1.9.1
From 6679cdd92c71a77f1809c180174de026a6c17918 Mon Sep 17 00:00:00 2001
From: Alasdair Nicol
Date: Tue, 1 Mar 2016 16:02:09 +0000
Subject: [PATCH 466/756] [1.9.x] Fixed #26303 -- Updated links to mod_wsgi
docs.
Backport of 8c42cf0cbd87f344748423f235938dd6ade03f55 from master
---
docs/howto/deployment/wsgi/apache-auth.txt | 4 ++--
docs/howto/deployment/wsgi/modwsgi.txt | 4 ++--
tests/auth_tests/test_handlers.py | 7 +++----
3 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/docs/howto/deployment/wsgi/apache-auth.txt b/docs/howto/deployment/wsgi/apache-auth.txt
index 9246c081e7be..e6fea98c85b0 100644
--- a/docs/howto/deployment/wsgi/apache-auth.txt
+++ b/docs/howto/deployment/wsgi/apache-auth.txt
@@ -97,8 +97,8 @@ Requests beginning with ``/secret/`` will now require a user to authenticate.
The mod_wsgi `access control mechanisms documentation`_ provides additional
details and information about alternative methods of authentication.
-.. _Defining Application Groups: https://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Defining_Application_Groups
-.. _access control mechanisms documentation: https://code.google.com/p/modwsgi/wiki/AccessControlMechanisms
+.. _Defining Application Groups: https://modwsgi.readthedocs.org/en/develop/user-guides/configuration-guidelines.html#defining-application-groups
+.. _access control mechanisms documentation: https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html
Authorization with ``mod_wsgi`` and Django groups
-------------------------------------------------
diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt
index a3d5e8a571c7..988ff8a389d5 100644
--- a/docs/howto/deployment/wsgi/modwsgi.txt
+++ b/docs/howto/deployment/wsgi/modwsgi.txt
@@ -140,7 +140,7 @@ to the configuration above:
See the official mod_wsgi documentation for `details on setting up daemon
mode`_.
-.. _details on setting up daemon mode: https://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide#Delegation_To_Daemon_Process
+.. _details on setting up daemon mode: https://modwsgi.readthedocs.org/en/develop/user-guides/quick-configuration-guide.html#delegation-to-daemon-process
.. _serving-files:
@@ -198,7 +198,7 @@ If you are using a version of Apache older than 2.4, replace
.. More details on configuring a mod_wsgi site to serve static files can be found
.. in the mod_wsgi documentation on `hosting static files`_.
-.. _hosting static files: https://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines#Hosting_Of_Static_Files
+.. _hosting static files: https://modwsgi.readthedocs.org/en/develop/user-guides/configuration-guidelines.html#hosting-of-static-files
.. _serving-the-admin-files:
diff --git a/tests/auth_tests/test_handlers.py b/tests/auth_tests/test_handlers.py
index e2ceab9c4e48..6228607fae52 100644
--- a/tests/auth_tests/test_handlers.py
+++ b/tests/auth_tests/test_handlers.py
@@ -23,7 +23,7 @@ class ModWsgiHandlerTestCase(TransactionTestCase):
def test_check_password(self):
"""
Verify that check_password returns the correct values as per
- http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
+ https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-authentication-provider
"""
User.objects.create_user('test', 'test@example.com', 'test')
@@ -44,8 +44,7 @@ def test_check_password(self):
def test_check_password_custom_user(self):
"""
Verify that check_password returns the correct values as per
- http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Authentication_Provider
-
+ https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-authentication-provider
with custom user installed
"""
@@ -63,7 +62,7 @@ def test_check_password_custom_user(self):
def test_groups_for_user(self):
"""
Check that groups_for_user returns correct values as per
- http://code.google.com/p/modwsgi/wiki/AccessControlMechanisms#Apache_Group_Authorisation
+ https://modwsgi.readthedocs.org/en/develop/user-guides/access-control-mechanisms.html#apache-group-authorisation
"""
user1 = User.objects.create_user('test', 'test@example.com', 'test')
User.objects.create_user('test1', 'test1@example.com', 'test1')
From fddd79dacd6e9e24a7c439b841e952ef093d1ef2 Mon Sep 17 00:00:00 2001
From: Dmitry Dygalo
Date: Wed, 2 Mar 2016 08:15:53 +0100
Subject: [PATCH 467/756] [1.9.x] Fixed typo in 1.9.3/1.8.10 release date.
Backport of 5155c2b4587629c4bc77a11846e5b9d3ba5a43ef from master
---
docs/releases/1.8.10.txt | 2 +-
docs/releases/1.9.3.txt | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/releases/1.8.10.txt b/docs/releases/1.8.10.txt
index d57afc470d3f..77d5c48ff6b8 100644
--- a/docs/releases/1.8.10.txt
+++ b/docs/releases/1.8.10.txt
@@ -2,7 +2,7 @@
Django 1.8.10 release notes
===========================
-*March 1, 2015*
+*March 1, 2016*
Django 1.8.10 fixes two security issues and several bugs in 1.8.9.
diff --git a/docs/releases/1.9.3.txt b/docs/releases/1.9.3.txt
index 355eaf96e962..ad99cd863c7d 100644
--- a/docs/releases/1.9.3.txt
+++ b/docs/releases/1.9.3.txt
@@ -2,7 +2,7 @@
Django 1.9.3 release notes
==========================
-*March 1, 2015*
+*March 1, 2016*
Django 1.9.3 fixes two security issues and several bugs in 1.9.2.
From c6d39c644d7ed990aef30b41c9fd5569f200089c Mon Sep 17 00:00:00 2001
From: Alasdair Nicol
Date: Wed, 2 Mar 2016 15:48:13 +0000
Subject: [PATCH 468/756] [1.9.x] Fixed #26309 -- Documented that login URL
settings no longer support dotted paths.
Backport of 2404d209a5e8c4573927e14587735562b79e13ed from master
---
docs/internals/deprecation.txt | 3 +++
docs/ref/settings.txt | 27 ++++++++++++++-------------
docs/releases/1.8.txt | 3 +++
3 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 4b90a822332e..28b25270d971 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -131,6 +131,9 @@ details on these changes.
* The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted
Python path will be removed.
+* The ability to use a dotted Python path for the ``LOGIN_URL`` and
+ ``LOGIN_REDIRECT_URL`` settings will be removed.
+
* Support for :py:mod:`optparse` will be dropped for custom management commands
(replaced by :py:mod:`argparse`).
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index d6da94ff7d89..360fd7dcaf34 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2795,9 +2795,14 @@ 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.
-This setting also accepts view function names and :ref:`named 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).
+This setting also accepts :ref:`named 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).
+
+.. deprecated:: 1.8
+
+ The setting may also be a dotted Python path to a view function. Support
+ for this will be removed in Django 1.10.
.. setting:: LOGIN_URL
@@ -2809,18 +2814,14 @@ Default: ``'/accounts/login/'``
The URL where requests are redirected for login, especially when using the
:func:`~django.contrib.auth.decorators.login_required` decorator.
-This setting also accepts view function names and :ref:`named 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
+This setting also accepts :ref:`named 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).
-LOGOUT_URL
-----------
-
-Default: ``'/accounts/logout/'``
+.. deprecated:: 1.8
-LOGIN_URL counterpart.
+ The setting may also be a dotted Python path to a view function. Support
+ for this will be removed in Django 1.10.
.. setting:: PASSWORD_RESET_TIMEOUT_DAYS
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index ccc6bbdc51c8..c4b6752c97e0 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -1356,6 +1356,9 @@ to ensure compatibility when reversing by Python path is removed in Django 1.10.
Similarly for GIS sitemaps, add ``name='django.contrib.gis.sitemaps.views.kml'``
or ``name='django.contrib.gis.sitemaps.views.kmz'``.
+If you are using a Python path for the :setting:`LOGIN_URL` or
+:setting:`LOGIN_REDIRECT_URL` setting, use the name of the ``url()`` instead.
+
.. _security issue: https://www.djangoproject.com/weblog/2014/apr/21/security/#s-issue-unexpected-code-execution-using-reverse
Aggregate methods and modules
From 377ca697a133e6063be3dff09b83b1c533d91950 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 3 Mar 2016 19:34:31 -0500
Subject: [PATCH 469/756] [1.9.x] Fixed #26321 -- Added missing "for_save"
parameter in expressions example.
Thanks tomaszn for the patch.
Backport of de8a11ba18d5902c668d4db47c38c9c6bdf9c1da from master
---
docs/ref/models/expressions.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt
index a580ca426e11..223c0f6460bd 100644
--- a/docs/ref/models/expressions.txt
+++ b/docs/ref/models/expressions.txt
@@ -460,7 +460,7 @@ calling the appropriate methods on the wrapped expression.
Tells Django that this expression contains an aggregate and that a
``GROUP BY`` clause needs to be added to the query.
- .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False)
+ .. method:: resolve_expression(query=None, allow_joins=True, reuse=None, summarize=False, for_save=False)
Provides the chance to do any pre-processing or validation of
the expression before it's added to the query. ``resolve_expression()``
@@ -591,11 +591,11 @@ Now we implement the pre-processing and validation. Since we do not have
any of our own validation at this point, we just delegate to the nested
expressions::
- def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False):
+ def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False):
c = self.copy()
c.is_summary = summarize
for pos, expression in enumerate(self.expressions):
- c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize)
+ c.expressions[pos] = expression.resolve_expression(query, allow_joins, reuse, summarize, for_save)
return c
Next, we write the method responsible for generating the SQL::
From 2a9ce3627179425f20c56ae43cac65d9f67c69df Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 4 Mar 2016 09:47:43 -0500
Subject: [PATCH 470/756] [1.9.x] Added stub release notes for 1.8.11.
Backport of 2f0c785a4c2353a3035ba6022cec5e25fb9d569b from master
---
docs/releases/1.8.11.txt | 12 ++++++++++++
docs/releases/index.txt | 1 +
2 files changed, 13 insertions(+)
create mode 100644 docs/releases/1.8.11.txt
diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt
new file mode 100644
index 000000000000..9837371f7bcf
--- /dev/null
+++ b/docs/releases/1.8.11.txt
@@ -0,0 +1,12 @@
+===========================
+Django 1.8.11 release notes
+===========================
+
+*Under development*
+
+Django 1.8.11 fixes several bugs in 1.8.10.
+
+Bugfixes
+========
+
+* ...
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index baf72288b9a8..0224a2e6c12a 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -36,6 +36,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 1.8.11
1.8.10
1.8.9
1.8.8
From 067d8c3500cfe63627b2f5d342f49451d2e68307 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 4 Mar 2016 14:16:56 -0500
Subject: [PATCH 471/756] [1.9.x] Fixed typo in docs/releases/1.9.1.txt.
Backport of cecbf1bdef04e00e6947f47d96198aa57c2a0dc3 from master
---
docs/releases/1.9.1.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/releases/1.9.1.txt b/docs/releases/1.9.1.txt
index b336419ebe56..27c22c000106 100644
--- a/docs/releases/1.9.1.txt
+++ b/docs/releases/1.9.1.txt
@@ -36,7 +36,7 @@ Bugfixes
to work on both Python 2 and Python 3.
* Prevented ``QuerySet.delete()`` from crashing on MySQL when querying across
- relations (:ticket`25882`).
+ relations (:ticket:`25882`).
* Fixed evaluation of zero-length slices of ``QuerySet.values()``
(:ticket:`25894`).
From 78f48300567b816b3c8177c33bef1a3ea6b36987 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 4 Mar 2016 15:41:52 +0100
Subject: [PATCH 472/756] [1.9.x] Fixed #26308 -- Prevented crash with binary
URLs in is_safe_url()
This fixes a regression introduced by c5544d28923.
Thanks John Eskew for the reporti and Tim Graham for the review.
Backport of ada7a4aef from master.
---
django/utils/http.py | 2 ++
docs/releases/1.8.11.txt | 10 +++-------
docs/releases/1.9.4.txt | 10 +++-------
tests/utils_tests/test_http.py | 12 ++++++++++++
4 files changed, 20 insertions(+), 14 deletions(-)
diff --git a/django/utils/http.py b/django/utils/http.py
index fb18f48f771a..e925cfc543ff 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -290,6 +290,8 @@ def is_safe_url(url, host=None):
url = url.strip()
if not url:
return False
+ if six.PY2:
+ url = force_text(url, errors='replace')
# Chrome treats \ completely as / in paths but it could be part of some
# basic auth credentials so we need to check both URLs.
return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt
index 9837371f7bcf..b01807129b8d 100644
--- a/docs/releases/1.8.11.txt
+++ b/docs/releases/1.8.11.txt
@@ -2,11 +2,7 @@
Django 1.8.11 release notes
===========================
-*Under development*
+*March 4, 2016*
-Django 1.8.11 fixes several bugs in 1.8.10.
-
-Bugfixes
-========
-
-* ...
+Django 1.8.11 fixes a regression on Python 2 in the 1.8.10 security release
+where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`).
diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt
index 15adcee3290e..d3f66bb7a1a5 100644
--- a/docs/releases/1.9.4.txt
+++ b/docs/releases/1.9.4.txt
@@ -2,11 +2,7 @@
Django 1.9.4 release notes
==========================
-*Under development*
+*March 4, 2016*
-Django 1.9.4 fixes several bugs in 1.9.3.
-
-Bugfixes
-========
-
-* ...
+Django 1.9.4 fixes a regression on Python 2 in the 1.9.3 security release
+where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`).
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index ad9a1666c35a..a7c282aedc2b 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -1,3 +1,4 @@
+# -*- encoding: utf-8 -*-
from __future__ import unicode_literals
import sys
@@ -109,6 +110,17 @@ def test_is_safe_url(self):
'http://testserver/confirm?email=me@example.com',
'/url%20with%20spaces/'):
self.assertTrue(http.is_safe_url(good_url, host='testserver'), "%s should be allowed" % good_url)
+
+ if six.PY2:
+ # Check binary URLs, regression tests for #26308
+ self.assertTrue(
+ http.is_safe_url(b'https://testserver/', host='testserver'),
+ "binary URLs should be allowed on Python 2"
+ )
+ self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver'))
+ self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver'))
+ self.assertTrue(http.is_safe_url('àview'.encode('latin-1'), host='testserver'))
+
# Valid basic auth credentials are allowed.
self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver'))
# A path without host is allowed.
From 9c195d45a64b0d2baee218e617ca3a762efc0bf5 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 4 Mar 2016 23:33:35 +0100
Subject: [PATCH 473/756] [1.9.x] Added safety to URL decoding in is_safe_url()
on Python 2
The errors='replace' parameter to force_text altered the URL before checking
it, which wasn't considered sane. Refs 24fc935218 and ada7a4aef.
Backport of 552f03869e from master.
---
django/utils/http.py | 5 ++++-
docs/releases/1.8.11.txt | 2 +-
docs/releases/1.9.4.txt | 2 +-
tests/utils_tests/test_http.py | 2 +-
4 files changed, 7 insertions(+), 4 deletions(-)
diff --git a/django/utils/http.py b/django/utils/http.py
index e925cfc543ff..62e854da8a28 100644
--- a/django/utils/http.py
+++ b/django/utils/http.py
@@ -291,7 +291,10 @@ def is_safe_url(url, host=None):
if not url:
return False
if six.PY2:
- url = force_text(url, errors='replace')
+ try:
+ url = force_text(url)
+ except UnicodeDecodeError:
+ return False
# Chrome treats \ completely as / in paths but it could be part of some
# basic auth credentials so we need to check both URLs.
return _is_safe_url(url, host) and _is_safe_url(url.replace('\\', '/'), host)
diff --git a/docs/releases/1.8.11.txt b/docs/releases/1.8.11.txt
index b01807129b8d..f33149b9e76f 100644
--- a/docs/releases/1.8.11.txt
+++ b/docs/releases/1.8.11.txt
@@ -2,7 +2,7 @@
Django 1.8.11 release notes
===========================
-*March 4, 2016*
+*March 5, 2016*
Django 1.8.11 fixes a regression on Python 2 in the 1.8.10 security release
where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`).
diff --git a/docs/releases/1.9.4.txt b/docs/releases/1.9.4.txt
index d3f66bb7a1a5..36e4ea329a67 100644
--- a/docs/releases/1.9.4.txt
+++ b/docs/releases/1.9.4.txt
@@ -2,7 +2,7 @@
Django 1.9.4 release notes
==========================
-*March 4, 2016*
+*March 5, 2016*
Django 1.9.4 fixes a regression on Python 2 in the 1.9.3 security release
where ``utils.http.is_safe_url()`` crashes on bytestring URLs (:ticket:`26308`).
diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py
index a7c282aedc2b..dc117869addc 100644
--- a/tests/utils_tests/test_http.py
+++ b/tests/utils_tests/test_http.py
@@ -119,7 +119,7 @@ def test_is_safe_url(self):
)
self.assertFalse(http.is_safe_url(b'\x08//example.com', host='testserver'))
self.assertTrue(http.is_safe_url('àview/'.encode('utf-8'), host='testserver'))
- self.assertTrue(http.is_safe_url('àview'.encode('latin-1'), host='testserver'))
+ self.assertFalse(http.is_safe_url('àview'.encode('latin-1'), host='testserver'))
# Valid basic auth credentials are allowed.
self.assertTrue(http.is_safe_url(r'http://user:pass@testserver/', host='user:pass@testserver'))
From e16a0b0998e456d011327a28ce68ba8afbddfa9a Mon Sep 17 00:00:00 2001
From: Bob McDonald
Date: Fri, 4 Mar 2016 10:24:23 +0900
Subject: [PATCH 474/756] [1.9.x] Fixed #26312 -- Documented "create database"
requirement in tutorial 2.
Backport of b388c294eb5745b3e01dedf0220636fae87ea9a2 from master
---
docs/intro/tutorial02.txt | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index fbf15e8d1b49..53daae91353a 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -41,11 +41,16 @@ If you are not using SQLite as your database, additional settings such as
:setting:`USER`, :setting:`PASSWORD`, and :setting:`HOST` must be added.
For more details, see the reference documentation for :setting:`DATABASES`.
-.. note::
+.. admonition:: For non-SQLite users
- If you're using PostgreSQL or MySQL, make sure you've created a database by
- this point. Do that with "``CREATE DATABASE database_name;``" within your
- database's interactive prompt.
+ If you're using a database besides SQLite, make sure you've created a
+ database by this point. Do that with "``CREATE DATABASE database_name;``"
+ within your database's interactive prompt.
+
+ Also make sure that the database user provided in :file:`mysite/settings.py`
+ has "create database" privileges. This allows automatic creation of a
+ :ref:`test database ` which will be needed in a later
+ tutorial.
If you're using SQLite, you don't need to create anything beforehand - the
database file will be created automatically when it is needed.
From dafddb6b8c0eb778072bec1ccd536bafad0eb936 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 5 Mar 2016 09:24:08 -0500
Subject: [PATCH 475/756] [1.9.x] Bumped version for 1.9.4 release.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index 942857ad34a0..e0c468ff2a63 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (1, 9, 4, 'alpha', 0)
+VERSION = (1, 9, 4, 'final', 0)
__version__ = get_version(VERSION)
From ae26a822cc30715956fee2b19ac2384785b347ec Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 5 Mar 2016 09:41:28 -0500
Subject: [PATCH 476/756] [1.9.x] Post-release version bump.
---
django/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/__init__.py b/django/__init__.py
index e0c468ff2a63..4c75ce658bb1 100644
--- a/django/__init__.py
+++ b/django/__init__.py
@@ -1,6 +1,6 @@
from django.utils.version import get_version
-VERSION = (1, 9, 4, 'final', 0)
+VERSION = (1, 9, 5, 'alpha', 0)
__version__ = get_version(VERSION)
From 51b7f102134a4b8d5c484ff44d50526a955223e2 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 5 Mar 2016 10:00:40 -0500
Subject: [PATCH 477/756] [1.9.x] Added stub release notes for 1.9.5/1.8.12.
Backport of c960af4adb87f8ce87f5698902b68e8332e448cb from master
---
docs/releases/1.8.12.txt | 12 ++++++++++++
docs/releases/1.9.5.txt | 12 ++++++++++++
docs/releases/index.txt | 2 ++
3 files changed, 26 insertions(+)
create mode 100644 docs/releases/1.8.12.txt
create mode 100644 docs/releases/1.9.5.txt
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
new file mode 100644
index 000000000000..74879bd046f8
--- /dev/null
+++ b/docs/releases/1.8.12.txt
@@ -0,0 +1,12 @@
+===========================
+Django 1.8.12 release notes
+===========================
+
+*Under development*
+
+Django 1.8.12 several bugs in 1.8.11.
+
+Bugfixes
+========
+
+* ...
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
new file mode 100644
index 000000000000..edff4db6bc4e
--- /dev/null
+++ b/docs/releases/1.9.5.txt
@@ -0,0 +1,12 @@
+==========================
+Django 1.9.5 release notes
+==========================
+
+*Under development*
+
+Django 1.9.5 fixes several bugs in 1.9.4.
+
+Bugfixes
+========
+
+* ...
diff --git a/docs/releases/index.txt b/docs/releases/index.txt
index 0224a2e6c12a..03439d6c053d 100644
--- a/docs/releases/index.txt
+++ b/docs/releases/index.txt
@@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 1.9.5
1.9.4
1.9.3
1.9.2
@@ -36,6 +37,7 @@ versions of the documentation contain the release notes for any later releases.
.. toctree::
:maxdepth: 1
+ 1.8.12
1.8.11
1.8.10
1.8.9
From 52b06546c07545b592a9205acfc4049292d52766 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 5 Mar 2016 10:02:29 -0500
Subject: [PATCH 478/756] [1.9.x] Fixed typo in docs/releases/1.8.12.txt.
Backport of bc0410d98adcb70ad91f37fa9fee9a7ae71faa18 from master
---
docs/releases/1.8.12.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
index 74879bd046f8..332c6091a591 100644
--- a/docs/releases/1.8.12.txt
+++ b/docs/releases/1.8.12.txt
@@ -4,7 +4,7 @@ Django 1.8.12 release notes
*Under development*
-Django 1.8.12 several bugs in 1.8.11.
+Django 1.8.12 fixes several bugs in 1.8.11.
Bugfixes
========
From 72134a04004271a0bd3e853cca3f54f9efc68be2 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 5 Mar 2016 10:58:45 -0500
Subject: [PATCH 479/756] [1.9.x] Refs #26312 -- Reworded tutorial 2 to avoid
spelling "error".
Backport of 9ed4a788aa8d6ba6a57a2daa15253c3047048dfb from master
---
docs/intro/tutorial02.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt
index 53daae91353a..1d4e16fa4ff1 100644
--- a/docs/intro/tutorial02.txt
+++ b/docs/intro/tutorial02.txt
@@ -41,7 +41,7 @@ If you are not using SQLite as your database, additional settings such as
:setting:`USER`, :setting:`PASSWORD`, and :setting:`HOST` must be added.
For more details, see the reference documentation for :setting:`DATABASES`.
-.. admonition:: For non-SQLite users
+.. admonition:: For databases other than SQLite
If you're using a database besides SQLite, make sure you've created a
database by this point. Do that with "``CREATE DATABASE database_name;``"
From d38da1cc401f61ff06aeda31e2b1b1f3be242efd Mon Sep 17 00:00:00 2001
From: Michal Petrucha
Date: Sun, 6 Mar 2016 00:47:26 +0100
Subject: [PATCH 480/756] [1.9.x] Refs #26217 -- Fixed typo in
docs/ref/class-based-views/generic-date-based.txt.
Backport of 2109975e901440da70e29d0f330a600bc2d37e9a from master
---
docs/ref/class-based-views/generic-date-based.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/class-based-views/generic-date-based.txt b/docs/ref/class-based-views/generic-date-based.txt
index 8e93182baefc..0aab34b375d5 100644
--- a/docs/ref/class-based-views/generic-date-based.txt
+++ b/docs/ref/class-based-views/generic-date-based.txt
@@ -339,7 +339,7 @@ views for displaying drilldown pages for date-based data.
* ``'%U'``: Based on the United States week system where the week
begins on Sunday. This is the default value.
- * ``'%V'``: Similar to ``'%U'``, except it assumes that the week
+ * ``'%W'``: Similar to ``'%U'``, except it assumes that the week
begins on Monday. This is not the same as the ISO 8601 week number.
**Example myapp/views.py**::
From 4702c1ac98b284cacffcf5fbb0db9b85f0c2f8ba Mon Sep 17 00:00:00 2001
From: George Marshall
Date: Sun, 6 Mar 2016 00:48:06 -0800
Subject: [PATCH 481/756] [1.9.x] Fixed #26331 -- Fixed test function names
with typos
Backport of 75614f6d4c1a3fe779a75eb3e787452cccd1d814 from master
---
tests/cache/tests.py | 2 +-
tests/signing/tests.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index 7fd9808ca694..b1df2c3eb994 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -1329,7 +1329,7 @@ def test_caches_with_unset_timeout_set_expiring_key(self):
self.assertIsNotNone(cache._expire_info[cache_key])
@override_settings(CACHES=NEVER_EXPIRING_CACHES_SETTINGS)
- def text_caches_set_with_timeout_as_none_set_non_expiring_key(self):
+ def test_caches_set_with_timeout_as_none_set_non_expiring_key(self):
"""Memory caches that have the TIMEOUT parameter set to `None` will set
a non expiring key by default.
"""
diff --git a/tests/signing/tests.py b/tests/signing/tests.py
index a8de1b0cbc27..88f82b0e26a1 100644
--- a/tests/signing/tests.py
+++ b/tests/signing/tests.py
@@ -58,7 +58,7 @@ def test_sign_unsign(self):
self.assertNotEqual(force_str(example), signed)
self.assertEqual(example, signer.unsign(signed))
- def unsign_detects_tampering(self):
+ def test_unsign_detects_tampering(self):
"unsign should raise an exception if the value has been tampered with"
signer = signing.Signer('predictable-secret')
value = 'Another string'
From 809eb5ddeeca388b2a1d339f7d5ee1f29119ecea Mon Sep 17 00:00:00 2001
From: John-Mark Bell
Date: Mon, 7 Mar 2016 12:06:46 +0000
Subject: [PATCH 482/756] [1.9.x] Fixed #26325 -- Made MultiPartParser ignore
filenames that normalize to an empty string.
Backport of 4b129ac81f4fa38004950d0b307f81d1e9b44af8 from master
---
django/http/multipartparser.py | 5 +++--
docs/releases/1.8.12.txt | 4 +++-
docs/releases/1.9.5.txt | 4 +++-
tests/file_uploads/tests.py | 35 ++++++++++++++++++++++++++++++++++
4 files changed, 44 insertions(+), 4 deletions(-)
diff --git a/django/http/multipartparser.py b/django/http/multipartparser.py
index 375584e4f30f..67d0fc48d50a 100644
--- a/django/http/multipartparser.py
+++ b/django/http/multipartparser.py
@@ -181,10 +181,11 @@ def parse(self):
elif item_type == FILE:
# This is a file, use the handler...
file_name = disposition.get('filename')
+ if file_name:
+ file_name = force_text(file_name, encoding, errors='replace')
+ file_name = self.IE_sanitize(unescape_entities(file_name))
if not file_name:
continue
- file_name = force_text(file_name, encoding, errors='replace')
- file_name = self.IE_sanitize(unescape_entities(file_name))
content_type, content_type_extra = meta_data.get('content-type', ('', {}))
content_type = content_type.strip()
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
index 332c6091a591..26735b827866 100644
--- a/docs/releases/1.8.12.txt
+++ b/docs/releases/1.8.12.txt
@@ -9,4 +9,6 @@ Django 1.8.12 fixes several bugs in 1.8.11.
Bugfixes
========
-* ...
+* Made ``MultiPartParser`` ignore filenames that normalize to an empty string
+ to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input
+ (:ticket:`26325`).
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index edff4db6bc4e..211cbba6ba10 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -9,4 +9,6 @@ Django 1.9.5 fixes several bugs in 1.9.4.
Bugfixes
========
-* ...
+* Made ``MultiPartParser`` ignore filenames that normalize to an empty string
+ to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input
+ (:ticket:`26325`).
diff --git a/tests/file_uploads/tests.py b/tests/file_uploads/tests.py
index ccecfce5c17d..09c2c41c8831 100644
--- a/tests/file_uploads/tests.py
+++ b/tests/file_uploads/tests.py
@@ -179,6 +179,41 @@ def test_unicode_name_rfc2231(self):
response = self.client.request(**r)
self.assertEqual(response.status_code, 200)
+ def test_blank_filenames(self):
+ """
+ Receiving file upload when filename is blank (before and after
+ sanitization) should be okay.
+ """
+ # The second value is normalized to an empty name by
+ # MultiPartParser.IE_sanitize()
+ filenames = ['', 'C:\\Windows\\']
+
+ payload = client.FakePayload()
+ for i, name in enumerate(filenames):
+ payload.write('\r\n'.join([
+ '--' + client.BOUNDARY,
+ 'Content-Disposition: form-data; name="file%s"; filename="%s"' % (i, name),
+ 'Content-Type: application/octet-stream',
+ '',
+ 'You got pwnd.\r\n'
+ ]))
+ payload.write('\r\n--' + client.BOUNDARY + '--\r\n')
+
+ r = {
+ 'CONTENT_LENGTH': len(payload),
+ 'CONTENT_TYPE': client.MULTIPART_CONTENT,
+ 'PATH_INFO': '/echo/',
+ 'REQUEST_METHOD': 'POST',
+ 'wsgi.input': payload,
+ }
+ response = self.client.request(**r)
+ self.assertEqual(response.status_code, 200)
+
+ # Empty filenames should be ignored
+ received = json.loads(response.content.decode('utf-8'))
+ for i, name in enumerate(filenames):
+ self.assertIsNone(received.get('file%s' % i))
+
def test_dangerous_file_names(self):
"""Uploaded file names should be sanitized before ever reaching the view."""
# This test simulates possible directory traversal attacks by a
From 76926f343a91e47bf5dc801373e9da2747d08e29 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Przemys=C5=82aw=20Suliga?=
Date: Sun, 6 Mar 2016 11:34:23 +0100
Subject: [PATCH 483/756] [1.9.x] Fixed #26332 -- Fixed a race condition in
BaseCache.get_or_set().
Backport of 96ec67a7cf89a136e793305343c5bba8521cdb47 from master
---
django/core/cache/backends/base.py | 10 +++++-----
docs/releases/1.9.5.txt | 4 ++++
tests/cache/tests.py | 9 ++++++++-
3 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index 75a131ac3a07..12351c5bcd5e 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -154,8 +154,7 @@ def get_or_set(self, key, default=None, timeout=DEFAULT_TIMEOUT, version=None):
also be any callable. If timeout is given, that timeout will be used
for the key; otherwise the default cache timeout will be used.
- Returns the value of the key stored or retrieved on success,
- False on error.
+ Return the value of the key stored or retrieved.
"""
if default is None:
raise ValueError('You need to specify a value.')
@@ -163,9 +162,10 @@ def get_or_set(self, key, default=None, timeout=DEFAULT_TIMEOUT, version=None):
if val is None:
if callable(default):
default = default()
- val = self.add(key, default, timeout=timeout, version=version)
- if val:
- return self.get(key, default, version)
+ self.add(key, default, timeout=timeout, version=version)
+ # Fetch the value again to avoid a race condition if another caller
+ # added a value between the first get() and the add() above.
+ return self.get(key, default, version=version)
return val
def has_key(self, key, version=None):
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 211cbba6ba10..d14b1ac98e62 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -12,3 +12,7 @@ Bugfixes
* Made ``MultiPartParser`` ignore filenames that normalize to an empty string
to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input
(:ticket:`26325`).
+
+* Fixed a race condition in ``BaseCache.get_or_set()`` (:ticket:`26332`). It
+ now returns the ``default`` value instead of ``False`` if there's an error
+ when trying to add the value to the cache.
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index b1df2c3eb994..0a76b82bcc1c 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -30,7 +30,7 @@
from django.template.context_processors import csrf
from django.template.response import TemplateResponse
from django.test import (
- RequestFactory, SimpleTestCase, TestCase, TransactionTestCase,
+ RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, mock,
override_settings,
)
from django.test.signals import setting_changed
@@ -915,6 +915,13 @@ def test_get_or_set_version(self):
self.assertEqual(cache.get_or_set('brian', 1979, version=2), 1979)
self.assertIsNone(cache.get('brian', version=3))
+ def test_get_or_set_racing(self):
+ with mock.patch('%s.%s' % (settings.CACHES['default']['BACKEND'], 'add')) as cache_add:
+ # Simulate cache.add() failing to add a value. In that case, the
+ # default value should be returned.
+ cache_add.return_value = False
+ self.assertEqual(cache.get_or_set('key', 'default'), 'default')
+
@override_settings(CACHES=caches_setting_for_tests(
BACKEND='django.core.cache.backends.db.DatabaseCache',
From e2483c9bdf685281221e01cdeb2f3fa52655b701 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Tue, 8 Mar 2016 13:15:58 -0500
Subject: [PATCH 484/756] [1.9.x] Fixed a dead link in
django/contrib/sitemaps/__init__.py.
Backport of 09e5409cb5b7ed427d4c751fbe2594b485794104 from master
---
django/contrib/sitemaps/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/contrib/sitemaps/__init__.py b/django/contrib/sitemaps/__init__.py
index 1b626cd98433..891890e1abe0 100644
--- a/django/contrib/sitemaps/__init__.py
+++ b/django/contrib/sitemaps/__init__.py
@@ -45,7 +45,7 @@ def ping_google(sitemap_url=None, ping_url=PING_URL):
class Sitemap(object):
# This limit is defined by Google. See the index documentation at
- # http://sitemaps.org/protocol.php#index.
+ # http://www.sitemaps.org/protocol.html#index.
limit = 50000
# If protocol is None, the URLs in the sitemap will use the protocol
From 0e80ac467293c659588769ec0587a5bf5f084b49 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Tue, 8 Mar 2016 21:28:00 +0100
Subject: [PATCH 485/756] [1.9.x] Fixed #26256 -- Added note about primary key
serialization
Thanks Sonu kumar for the report and Tim Graham for the review.
Backport of c5fda55edc from master.
---
docs/topics/serialization.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt
index 5db8ca569b56..398880ca9398 100644
--- a/docs/topics/serialization.txt
+++ b/docs/topics/serialization.txt
@@ -59,7 +59,8 @@ specify a ``fields`` argument to the serializer::
data = serializers.serialize('xml', SomeModel.objects.all(), fields=('name','size'))
In this example, only the ``name`` and ``size`` attributes of each model will
-be serialized.
+be serialized. The primary key is always serialized as the ``pk`` element in the
+resulting output; it never appears in the ``fields`` part.
.. note::
From 0bae867cd2b25b8e2227bf454f3407cfae5693b5 Mon Sep 17 00:00:00 2001
From: Tim Osborn
Date: Wed, 9 Mar 2016 14:45:06 +1100
Subject: [PATCH 486/756] [1.9.x] Fixed indenting in "Serving files in
development" code example
Backport of 8fb3a2877bac1ecf0e5665389d60eab637d1622e from master
---
docs/ref/views.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/views.txt b/docs/ref/views.txt
index 133a79814b45..fb3918064dfc 100644
--- a/docs/ref/views.txt
+++ b/docs/ref/views.txt
@@ -35,7 +35,7 @@ built-in handling for user-uploaded files, but you can have Django serve your
url(r'^media/(?P.*)$', serve, {
'document_root': settings.MEDIA_ROOT,
}),
- ]
+ ]
Note, the snippet assumes your :setting:`MEDIA_URL` has a value of
``'/media/'``. This will call the :func:`~django.views.static.serve` view,
From 05ca9a68b59d5d683501e709c1ab629c3705fecb Mon Sep 17 00:00:00 2001
From: girish ramnani
Date: Wed, 9 Mar 2016 01:07:34 +0530
Subject: [PATCH 487/756] [1.9.x] Fixed #26302 -- Removed incorrect statement
about virtual hosts.
Backport of 593ecfe1352bce7daa7a2ca530a46de2f0133319 from master
---
docs/howto/deployment/wsgi/modwsgi.txt | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt
index 988ff8a389d5..96c7e4829391 100644
--- a/docs/howto/deployment/wsgi/modwsgi.txt
+++ b/docs/howto/deployment/wsgi/modwsgi.txt
@@ -25,12 +25,11 @@ Basic configuration
===================
Once you've got mod_wsgi installed and activated, edit your Apache server's
-`httpd.conf`_ file (or a `virtual host`_ file) and add the following. If you
-are using a version of Apache older than 2.4, replace ``Require all granted``
-with ``Allow from all`` and also add the line ``Order deny,allow`` above it.
+`httpd.conf`_ file and add the following. If you are using a version of Apache
+older than 2.4, replace ``Require all granted`` with ``Allow from all`` and
+also add the line ``Order deny,allow`` above it.
.. _httpd.conf: https://wiki.apache.org/httpd/DistrosDefaultLayout
-.. _virtual host: https://httpd.apache.org/docs/current/en/vhosts/
.. code-block:: apache
From e8d94cda9b4f30f6724f919882b61eb3279efafc Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 9 Mar 2016 10:00:27 -0500
Subject: [PATCH 488/756] [1.9.x] Wrapped some lines and added links to
docs/ref/contrib/sitemaps.txt.
Backport of a496d10a8c3b8b3080f0daa7348b33aed790a967 from master
---
docs/ref/contrib/sitemaps.txt | 37 ++++++++++++++++++++---------------
1 file changed, 21 insertions(+), 16 deletions(-)
diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt
index 8d8b411b5331..a5eac078244c 100644
--- a/docs/ref/contrib/sitemaps.txt
+++ b/docs/ref/contrib/sitemaps.txt
@@ -123,7 +123,7 @@ Note:
corresponding to a sitemap property (:attr:`~Sitemap.location`,
:attr:`~Sitemap.lastmod`, :attr:`~Sitemap.changefreq`, and
:attr:`~Sitemap.priority`).
-* :attr:`~Sitemap.lastmod` should return a Python ``datetime`` object.
+* :attr:`~Sitemap.lastmod` should return a :class:`~datetime.datetime`.
* There is no :attr:`~Sitemap.location` method in this example, but you
can provide it in order to specify the URL for your object. By default,
:attr:`~Sitemap.location()` calls ``get_absolute_url()`` on each object
@@ -173,11 +173,11 @@ Note:
**Optional.** Either a method or attribute.
- If it's a method, it should take one argument -- an object as returned by
- :attr:`~Sitemap.items()` -- and return that object's last-modified date/time, as a Python
- ``datetime.datetime`` object.
+ If it's a method, it should take one argument -- an object as returned
+ by :attr:`~Sitemap.items()` -- and return that object's last-modified
+ date/time as a :class:`~datetime.datetime`.
- If it's an attribute, its value should be a Python ``datetime.datetime`` object
+ If it's an attribute, its value should be a :class:`~datetime.datetime`
representing the last-modified date/time for *every* object returned by
:attr:`~Sitemap.items()`.
@@ -192,13 +192,15 @@ Note:
**Optional.** Either a method or attribute.
- If it's a method, it should take one argument -- an object as returned by
- :attr:`~Sitemap.items()` -- and return that object's change frequency, as a Python string.
+ If it's a method, it should take one argument -- an object as returned
+ by :attr:`~Sitemap.items()` -- and return that object's change
+ frequency as a string.
- If it's an attribute, its value should be a string representing the change
- frequency of *every* object returned by :attr:`~Sitemap.items()`.
+ If it's an attribute, its value should be a string representing the
+ change frequency of *every* object returned by :attr:`~Sitemap.items()`.
- Possible values for :attr:`~Sitemap.changefreq`, whether you use a method or attribute, are:
+ Possible values for :attr:`~Sitemap.changefreq`, whether you use a
+ method or attribute, are:
* ``'always'``
* ``'hourly'``
@@ -212,14 +214,17 @@ Note:
**Optional.** Either a method or attribute.
- If it's a method, it should take one argument -- an object as returned by
- :attr:`~Sitemap.items()` -- and return that object's priority, as either a string or float.
+ If it's a method, it should take one argument -- an object as returned
+ by :attr:`~Sitemap.items()` -- and return that object's priority as
+ either a string or float.
- If it's an attribute, its value should be either a string or float representing
- the priority of *every* object returned by :attr:`~Sitemap.items()`.
+ If it's an attribute, its value should be either a string or float
+ representing the priority of *every* object returned by
+ :attr:`~Sitemap.items()`.
- Example values for :attr:`~Sitemap.priority`: ``0.4``, ``1.0``. The default priority of a
- page is ``0.5``. See the `sitemaps.org documentation`_ for more.
+ Example values for :attr:`~Sitemap.priority`: ``0.4``, ``1.0``. The
+ default priority of a page is ``0.5``. See the `sitemaps.org
+ documentation`_ for more.
.. _sitemaps.org documentation: http://www.sitemaps.org/protocol.html#prioritydef
From 43bb6727d09347f8c4c1a6541e2086a1a69811e1 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 9 Mar 2016 11:21:33 -0500
Subject: [PATCH 489/756] [1.9.x] Fixed #26339 -- Updated
Question.was_published_recently() in tutorial 7 to reflect changes in
tutorial 5.
Backport of 602a38d87e4b0d9c5e43678c33208627ca84ce2a from master
---
docs/intro/tutorial07.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/intro/tutorial07.txt b/docs/intro/tutorial07.txt
index ecfd90d7f2c4..cc39abf2471b 100644
--- a/docs/intro/tutorial07.txt
+++ b/docs/intro/tutorial07.txt
@@ -232,7 +232,8 @@ attributes, as follows:
class Question(models.Model):
# ...
def was_published_recently(self):
- return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
+ now = timezone.now()
+ return now - datetime.timedelta(days=1) <= self.pub_date <= now
was_published_recently.admin_order_field = 'pub_date'
was_published_recently.boolean = True
was_published_recently.short_description = 'Published recently?'
From 301edde86d07fe27a4aa002b1546d2a3002190dc Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Wed, 9 Mar 2016 12:18:21 -0500
Subject: [PATCH 490/756] [1.9.x] Fixed #26255 -- Fixed orphaned include()
reference following tutorial reordering.
Backport of 4323676ea5ab6994feb1385522665069d84f397b from master
---
docs/intro/tutorial01.txt | 14 ++++++++++++++
docs/intro/tutorial03.txt | 28 +++++-----------------------
2 files changed, 19 insertions(+), 23 deletions(-)
diff --git a/docs/intro/tutorial01.txt b/docs/intro/tutorial01.txt
index 0bdc593ec93b..de86571df305 100644
--- a/docs/intro/tutorial01.txt
+++ b/docs/intro/tutorial01.txt
@@ -298,6 +298,20 @@ an :func:`~django.conf.urls.include` in the ``urlpatterns`` list, so you have:
url(r'^admin/', admin.site.urls),
]
+The :func:`~django.conf.urls.include` function allows referencing other
+URLconfs. Note that the regular expressions for the
+:func:`~django.conf.urls.include` function doesn't have a ``$`` (end-of-string
+match character) but rather a trailing slash. Whenever Django encounters
+:func:`~django.conf.urls.include`, it chops off whatever part of the URL
+matched up to that point and sends the remaining string to the included URLconf
+for further processing.
+
+The idea behind :func:`~django.conf.urls.include` is to make it easy to
+plug-and-play URLs. Since polls are in their own URLconf
+(``polls/urls.py``), they can be placed under "/polls/", or under
+"/fun_polls/", or under "/content/polls/", or any other path root, and the
+app will still work.
+
.. admonition:: When to use :func:`~django.conf.urls.include()`
You should always use ``include()`` when you include other URL patterns.
diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt
index ef2ad2dbef2e..b3973a49e23a 100644
--- a/docs/intro/tutorial03.txt
+++ b/docs/intro/tutorial03.txt
@@ -106,29 +106,11 @@ placeholder results and voting pages.
When somebody requests a page from your website -- say, "/polls/34/", Django
will load the ``mysite.urls`` Python module because it's pointed to by the
:setting:`ROOT_URLCONF` setting. It finds the variable named ``urlpatterns``
-and traverses the regular expressions in order. The
-:func:`~django.conf.urls.include` functions we are using simply reference
-other URLconfs. Note that the regular expressions for the
-:func:`~django.conf.urls.include` functions don't have a ``$`` (end-of-string
-match character) but rather a trailing slash. Whenever Django encounters
-:func:`~django.conf.urls.include`, it chops off whatever part of the URL
-matched up to that point and sends the remaining string to the included
-URLconf for further processing.
-
-The idea behind :func:`~django.conf.urls.include` is to make it easy to
-plug-and-play URLs. Since polls are in their own URLconf
-(``polls/urls.py``), they can be placed under "/polls/", or under
-"/fun_polls/", or under "/content/polls/", or any other path root, and the
-app will still work.
-
-Here's what happens if a user goes to "/polls/34/" in this system:
-
-* Django will find the match at ``'^polls/'``
-
-* Then, Django will strip off the matching text (``"polls/"``) and send the
- remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for
- further processing which matches ``r'^(?P[0-9]+)/$'`` resulting in a
- call to the ``detail()`` view like so::
+and traverses the regular expressions in order. After finding the match at
+``'^polls/'``, it strips off the matching text (``"polls/"``) and sends the
+remaining text -- ``"34/"`` -- to the 'polls.urls' URLconf for further
+processing. There it matches ``r'^(?P[0-9]+)/$'``, resulting in a
+call to the ``detail()`` view like so::
detail(request=, question_id='34')
From 5b6c751230c7ec33eebc564c15ab299ef8f6852a Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 10 Mar 2016 09:22:09 -0500
Subject: [PATCH 491/756] [1.9.x] Fixed #26324 -- Fixed DurationField with
fractional seconds on SQLite.
Backport of 4f0cd0fd162122da96978b357ac9fc9534529410 from master
---
django/db/models/fields/__init__.py | 3 ++-
docs/releases/1.8.12.txt | 3 +++
docs/releases/1.9.5.txt | 3 +++
tests/model_fields/test_durationfield.py | 6 ++++++
4 files changed, 14 insertions(+), 1 deletion(-)
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index df2e3b45de01..cf7a3c32ac5e 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -1665,7 +1665,8 @@ def get_db_prep_value(self, value, connection, prepared=False):
return value
if value is None:
return None
- return value.total_seconds() * 1000000
+ # Discard any fractional microseconds due to floating point arithmetic.
+ return int(round(value.total_seconds() * 1000000))
def get_db_converters(self, connection):
converters = []
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
index 26735b827866..0052a90b0d52 100644
--- a/docs/releases/1.8.12.txt
+++ b/docs/releases/1.8.12.txt
@@ -12,3 +12,6 @@ Bugfixes
* Made ``MultiPartParser`` ignore filenames that normalize to an empty string
to fix crash in ``MemoryFileUploadHandler`` on specially crafted user input
(:ticket:`26325`).
+
+* Fixed data loss on SQLite where ``DurationField`` values with fractional
+ seconds could be saved as ``None`` (:ticket:`26324`).
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index d14b1ac98e62..55fd794d789d 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -16,3 +16,6 @@ Bugfixes
* Fixed a race condition in ``BaseCache.get_or_set()`` (:ticket:`26332`). It
now returns the ``default`` value instead of ``False`` if there's an error
when trying to add the value to the cache.
+
+* Fixed data loss on SQLite where ``DurationField`` values with fractional
+ seconds could be saved as ``None`` (:ticket:`26324`).
diff --git a/tests/model_fields/test_durationfield.py b/tests/model_fields/test_durationfield.py
index fb26baadc9ba..82c5c875bf3a 100644
--- a/tests/model_fields/test_durationfield.py
+++ b/tests/model_fields/test_durationfield.py
@@ -22,6 +22,12 @@ def test_create_empty(self):
loaded = NullDurationModel.objects.get()
self.assertEqual(loaded.field, None)
+ def test_fractional_seconds(self):
+ value = datetime.timedelta(seconds=2.05)
+ d = DurationModel.objects.create(field=value)
+ d.refresh_from_db()
+ self.assertEqual(d.field, value)
+
class TestQuerying(TestCase):
From c5e258eed30d326374e3b138a64d7cf8831fbe1a Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 11 Mar 2016 12:53:12 -0500
Subject: [PATCH 492/756] [1.9.x] Removed unneeded GeoManagers in tests.
Backport of 9027fac8414c30db640c4592ee083f12bb8ee5a6 from master
---
django/contrib/gis/sitemaps/views.py | 3 +--
tests/gis_tests/geo3d/models.py | 2 --
tests/gis_tests/geoadmin/models.py | 2 --
tests/gis_tests/geoapp/models.py | 8 --------
tests/gis_tests/inspectapp/models.py | 4 ----
tests/gis_tests/layermap/models.py | 2 --
tests/gis_tests/models.py | 1 -
tests/gis_tests/relatedapp/models.py | 3 ---
tests/gis_tests/relatedapp/tests.py | 6 +++---
9 files changed, 4 insertions(+), 27 deletions(-)
diff --git a/django/contrib/gis/sitemaps/views.py b/django/contrib/gis/sitemaps/views.py
index c5fd1aa904e4..948c3ec36c17 100644
--- a/django/contrib/gis/sitemaps/views.py
+++ b/django/contrib/gis/sitemaps/views.py
@@ -13,8 +13,7 @@ def kml(request, label, model, field_name=None, compress=False, using=DEFAULT_DB
"""
This view generates KML for the given app label, model, and field name.
- The model's default manager must be GeoManager, and the field name
- must be that of a geographic field.
+ The field name must be that of a geographic field.
"""
placemarks = []
try:
diff --git a/tests/gis_tests/geo3d/models.py b/tests/gis_tests/geo3d/models.py
index fb741ca05c13..e816cb3dad24 100644
--- a/tests/gis_tests/geo3d/models.py
+++ b/tests/gis_tests/geo3d/models.py
@@ -47,8 +47,6 @@ class Polygon3D(NamedModel):
class SimpleModel(models.Model):
- objects = models.GeoManager()
-
class Meta:
abstract = True
required_db_features = ['gis_enabled']
diff --git a/tests/gis_tests/geoadmin/models.py b/tests/gis_tests/geoadmin/models.py
index 99dbae5d918e..2a5bea6e5d29 100644
--- a/tests/gis_tests/geoadmin/models.py
+++ b/tests/gis_tests/geoadmin/models.py
@@ -10,8 +10,6 @@ class City(models.Model):
name = models.CharField(max_length=30)
point = models.PointField()
- objects = models.GeoManager()
-
class Meta:
app_label = 'geoadmin'
required_db_features = ['gis_enabled']
diff --git a/tests/gis_tests/geoapp/models.py b/tests/gis_tests/geoapp/models.py
index d6ca4f501075..36f15a021d1e 100644
--- a/tests/gis_tests/geoapp/models.py
+++ b/tests/gis_tests/geoapp/models.py
@@ -35,10 +35,6 @@ class PennsylvaniaCity(City):
county = models.CharField(max_length=30)
founded = models.DateTimeField(null=True)
- # TODO: This should be implicitly inherited.
-
- objects = models.GeoManager()
-
class Meta:
app_label = 'geoapp'
required_db_features = ['gis_enabled']
@@ -77,8 +73,6 @@ class Meta:
class Truth(models.Model):
val = models.BooleanField(default=False)
- objects = models.GeoManager()
-
class Meta:
required_db_features = ['gis_enabled']
@@ -90,8 +84,6 @@ class Feature(NamedModel):
class MinusOneSRID(models.Model):
geom = models.PointField(srid=-1) # Minus one SRID.
- objects = models.GeoManager()
-
class Meta:
required_db_features = ['gis_enabled']
diff --git a/tests/gis_tests/inspectapp/models.py b/tests/gis_tests/inspectapp/models.py
index 75c4bcfa229d..4936a0e704a9 100644
--- a/tests/gis_tests/inspectapp/models.py
+++ b/tests/gis_tests/inspectapp/models.py
@@ -13,8 +13,6 @@ class AllOGRFields(models.Model):
geom = models.PolygonField()
point = models.PointField()
- objects = models.GeoManager()
-
class Meta:
required_db_features = ['gis_enabled']
@@ -24,7 +22,5 @@ class Fields3D(models.Model):
line = models.LineStringField(dim=3)
poly = models.PolygonField(dim=3)
- objects = models.GeoManager()
-
class Meta:
required_db_features = ['gis_enabled']
diff --git a/tests/gis_tests/layermap/models.py b/tests/gis_tests/layermap/models.py
index 3457e0e2d564..1e313049fbf4 100644
--- a/tests/gis_tests/layermap/models.py
+++ b/tests/gis_tests/layermap/models.py
@@ -7,8 +7,6 @@
class NamedModel(models.Model):
name = models.CharField(max_length=25)
- objects = models.GeoManager()
-
class Meta:
abstract = True
required_db_features = ['gis_enabled']
diff --git a/tests/gis_tests/models.py b/tests/gis_tests/models.py
index c1572588456f..aaedf0f8a7b9 100644
--- a/tests/gis_tests/models.py
+++ b/tests/gis_tests/models.py
@@ -13,7 +13,6 @@ def __init__(self, dim=None, srid=None, geography=None, spatial_index=True, *arg
# raised if GDAL isn't installed.
models.OriginalRasterField = models.RasterField
except ImproperlyConfigured:
- models.GeoManager = models.Manager
models.GeometryField = DummyField
models.LineStringField = DummyField
models.MultiPointField = DummyField
diff --git a/tests/gis_tests/relatedapp/models.py b/tests/gis_tests/relatedapp/models.py
index 606b40c52a6a..0052e1797f59 100644
--- a/tests/gis_tests/relatedapp/models.py
+++ b/tests/gis_tests/relatedapp/models.py
@@ -33,8 +33,6 @@ def __str__(self):
class AugmentedLocation(Location):
extra_text = models.TextField(blank=True)
- objects = models.GeoManager()
-
class DirectoryEntry(SimpleModel):
listing_text = models.CharField(max_length=50)
@@ -55,7 +53,6 @@ def __str__(self):
return self.name
-# These use the GeoManager but do not have any geographic fields.
class Author(SimpleModel):
name = models.CharField(max_length=100)
dob = models.DateField()
diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py
index 19b0f89eab2e..402170d5c382 100644
--- a/tests/gis_tests/relatedapp/tests.py
+++ b/tests/gis_tests/relatedapp/tests.py
@@ -250,7 +250,7 @@ def test10_combine(self):
# ORA-22901: cannot compare nested table or VARRAY or LOB attributes of an object type
@no_oracle
def test12a_count(self):
- "Testing `Count` aggregate use with the `GeoManager` on geo-fields."
+ "Testing `Count` aggregate on geo-fields."
# The City, 'Fort Worth' uses the same location as Dallas.
dallas = City.objects.get(name='Dallas')
@@ -259,7 +259,7 @@ def test12a_count(self):
self.assertEqual(2, loc.num_cities)
def test12b_count(self):
- "Testing `Count` aggregate use with the `GeoManager` on non geo-fields. See #11087."
+ "Testing `Count` aggregate on non geo-fields."
# Should only be one author (Trevor Paglen) returned by this query, and
# the annotation should have 3 for the number of books, see #11087.
# Also testing with a values(), see #11489.
@@ -284,7 +284,7 @@ def test13c_count(self):
# TODO: The phantom model does appear on Oracle.
@no_oracle
def test13_select_related_null_fk(self):
- "Testing `select_related` on a nullable ForeignKey via `GeoManager`. See #11381."
+ "Testing `select_related` on a nullable ForeignKey."
Book.objects.create(title='Without Author')
b = Book.objects.select_related('author').get(title='Without Author')
# Should be `None`, and not a 'dummy' model.
From 16691ed24cce00ac0b90b4ecfcaa31a071727725 Mon Sep 17 00:00:00 2001
From: Noenglish Professorbut
Date: Fri, 11 Mar 2016 17:17:01 -0800
Subject: [PATCH 493/756] [1.9.x] Fixed a few docstring typos.
Backport of f8d20da0479b88db5bb5a2a30fa769cbf6d0a5bf from master
---
django/contrib/gis/db/backends/postgis/operations.py | 2 +-
django/contrib/gis/db/backends/spatialite/operations.py | 2 +-
django/db/migrations/autodetector.py | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py
index d56b7bea91ca..318c88fd1d1e 100644
--- a/django/contrib/gis/db/backends/postgis/operations.py
+++ b/django/contrib/gis/db/backends/postgis/operations.py
@@ -199,7 +199,7 @@ def convert_extent3d(self, box3d, srid):
def convert_geom(self, hex, geo_field):
"""
- Converts the geometry returned from PostGIS aggretates.
+ Converts the geometry returned from PostGIS aggregates.
"""
if hex:
return Geometry(hex, srid=geo_field.srid)
diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py
index 127ea7b15d62..45e7a304fbcf 100644
--- a/django/contrib/gis/db/backends/spatialite/operations.py
+++ b/django/contrib/gis/db/backends/spatialite/operations.py
@@ -176,7 +176,7 @@ def convert_geom(self, wkt, geo_field):
def geo_db_type(self, f):
"""
- Returns None because geometry columnas are added via the
+ Returns None because geometry columns are added via the
`AddGeometryColumn` stored procedure on SpatiaLite.
"""
return None
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 513bb543d100..62ea80be202c 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -38,7 +38,7 @@ def __init__(self, from_state, to_state, questioner=None):
def changes(self, graph, trim_to_apps=None, convert_apps=None, migration_name=None):
"""
- Main entry point to produce a list of appliable changes.
+ Main entry point to produce a list of applicable changes.
Takes a graph to base names on and an optional set of apps
to try and restrict to (restriction is not guaranteed)
"""
From 421ad283d3a0c9814e6cf5b87b393dce09f59474 Mon Sep 17 00:00:00 2001
From: Duane Hilton
Date: Sat, 12 Mar 2016 09:15:57 -0700
Subject: [PATCH 494/756] [1.9.x] Fixed #26239 -- Added a note about how
auto_now works with QuerySet.update().
Backport of ca5c05ddbe91f4aae38c4543240dbda5f1fb1db2 from master
---
docs/ref/models/fields.txt | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index 20c50a0b12b0..9de9206bc8b8 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -490,6 +490,12 @@ optional arguments:
for "last-modified" timestamps. Note that the current date is *always*
used; it's not just a default value that you can override.
+ The field is only automatically updated when calling :meth:`Model.save()
+ `. The field isn't updated when making updates
+ to other fields in other ways such as :meth:`QuerySet.update()
+ `, though you can specify a custom
+ value for the field in an update like that.
+
.. attribute:: DateField.auto_now_add
Automatically set the field to now when the object is first created. Useful
From d3c2d82ddef4b2bcf9d3d0b1ac59d7d65f5a7406 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 12 Mar 2016 12:17:21 -0500
Subject: [PATCH 495/756] [1.9.x] Fixed #26345 -- Clarified which RangesFields
always return a canonical form.
Backport of b3610f38facb33704c1fd77590c6a2fa07c40fa7 from master
---
docs/ref/contrib/postgres/fields.txt | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt
index 20b68b588a61..a70cebea08a3 100644
--- a/docs/ref/contrib/postgres/fields.txt
+++ b/docs/ref/contrib/postgres/fields.txt
@@ -586,7 +586,7 @@ suitable for.
All of the range fields translate to :ref:`psycopg2 Range objects
` in python, but also accept tuples as input if no bounds
information is necessary. The default is lower bound included, upper bound
-excluded.
+excluded; that is, ``[)``.
``IntegerRangeField``
---------------------
@@ -598,6 +598,10 @@ excluded.
the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in
Python.
+ Regardless of the bounds specified when saving the data, PostgreSQL always
+ returns a range in a canonical form that includes the lower bound and
+ excludes the upper bound; that is ``[)``.
+
``BigIntegerRangeField``
------------------------
@@ -608,6 +612,10 @@ excluded.
in the database and a :class:`~psycopg2:psycopg2.extras.NumericRange` in
Python.
+ Regardless of the bounds specified when saving the data, PostgreSQL always
+ returns a range in a canonical form that includes the lower bound and
+ excludes the upper bound; that is ``[)``.
+
``FloatRangeField``
-------------------
@@ -636,6 +644,10 @@ excluded.
:class:`~django.db.models.DateField`. Represented by a ``daterange`` in the
database and a :class:`~psycopg2:psycopg2.extras.DateRange` in Python.
+ Regardless of the bounds specified when saving the data, PostgreSQL always
+ returns a range in a canonical form that includes the lower bound and
+ excludes the upper bound; that is ``[)``.
+
Querying Range Fields
---------------------
From 9ed08da6c6bd45e18c743c80a086403a5d57a308 Mon Sep 17 00:00:00 2001
From: Jakub Wilk
Date: Sun, 13 Mar 2016 19:47:58 +0100
Subject: [PATCH 496/756] [1.9.x] Fixed typos in docs.
Backport of 402da9ab7b2bae807b7ea30c23ef524b0aeb1903 from master
---
docs/internals/howto-release-django.txt | 2 +-
docs/ref/utils.txt | 2 +-
docs/releases/1.0.txt | 2 +-
docs/releases/security.txt | 2 +-
docs/topics/conditional-view-processing.txt | 2 +-
docs/topics/performance.txt | 2 +-
6 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt
index 0da90ae374b2..7edd69211f2e 100644
--- a/docs/internals/howto-release-django.txt
+++ b/docs/internals/howto-release-django.txt
@@ -368,7 +368,7 @@ You're almost done! All that's left to do now is:
New stable branch tasks
=======================
-There are several items to do in the time following a the creation of a new
+There are several items to do in the time following the creation of a new
stable branch (often following an alpha release). Some of these tasks don't
need to be done by the releaser.
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index e85d69d46230..a763fdc63939 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -157,7 +157,7 @@ The functions defined in this module share the following properties:
decorate methods or classes; in the latter case, ``name`` is the name
of the method to be decorated and is required.
- ``decorator`` may also be a a list or tuple of functions. They are wrapped
+ ``decorator`` may also be a list or tuple of functions. They are wrapped
in reverse order so that the call order is the order in which the functions
appear in the list/tuple.
diff --git a/docs/releases/1.0.txt b/docs/releases/1.0.txt
index 162caaa3a961..3d0043a79770 100644
--- a/docs/releases/1.0.txt
+++ b/docs/releases/1.0.txt
@@ -5,7 +5,7 @@ Django 1.0 release notes
Welcome to Django 1.0!
We've been looking forward to this moment for over three years, and it's finally
-here. Django 1.0 represents a the largest milestone in Django's development to
+here. Django 1.0 represents the largest milestone in Django's development to
date: a Web framework that a group of perfectionists can truly be proud of.
Django 1.0 represents over three years of community development as an Open
diff --git a/docs/releases/security.txt b/docs/releases/security.txt
index c8d29ef7ba6b..58e16748f644 100644
--- a/docs/releases/security.txt
+++ b/docs/releases/security.txt
@@ -202,7 +202,7 @@ Versions affected
September 9, 2011 - CVE-2011-4137
---------------------------------
-`CVE-2011-4137 `_: Denial-of-service via via ``URLField.verify_exists``. `Full description `__
+`CVE-2011-4137 `_: Denial-of-service via ``URLField.verify_exists``. `Full description `__
Versions affected
~~~~~~~~~~~~~~~~~
diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt
index 1cc15572f3e3..96c21e15dd9e 100644
--- a/docs/topics/conditional-view-processing.txt
+++ b/docs/topics/conditional-view-processing.txt
@@ -172,7 +172,7 @@ For example, consider the following exchange between the client and server:
the version it is trying to update.
4. Server checks to see if the resource has changed, by computing the ETag
the same way it does for a ``GET`` request (using the same function).
- If the resource *has* changed, it will return a 412 status code code,
+ If the resource *has* changed, it will return a 412 status code,
meaning "precondition failed".
5. Client sends a ``GET`` request to ``/foo/``, after receiving a 412
response, to retrieve an updated version of the content before updating
diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt
index c529a0530bb2..27949193776b 100644
--- a/docs/topics/performance.txt
+++ b/docs/topics/performance.txt
@@ -171,7 +171,7 @@ final steps towards producing well-performing code, not a shortcut.
:class:`~django.utils.functional.cached_property`
-------------------------------------------------
-It's common to have to call a class instances's method more than once. If
+It's common to have to call a class instance's method more than once. If
that function is expensive, then doing so can be wasteful.
Using the :class:`~django.utils.functional.cached_property` decorator saves the
From 7b2ee75745a5304b222b71b3273ff14a40da47f4 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Sat, 12 Mar 2016 17:44:08 -0500
Subject: [PATCH 497/756] [1.9.x] Fixed #26294 -- Clarified call_command()'s
handling of args and options.
Backport of 5695c142d282f4681a8d43bb55ec49f11f3fc40e from master
---
docs/ref/django-admin.txt | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index e9f3c1659d8f..2144bba219a9 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -1749,10 +1749,15 @@ To call a management command from code use ``call_command``.
the name of the command to call.
``*args``
- a list of arguments accepted by the command.
+ a list of arguments accepted by the command. Arguments are passed to the
+ argument parser, so you can use the same style as you would on the command
+ line. For example, ``call_command('flush', 'verbosity=0')``.
``**options``
- named options accepted on the command-line.
+ named options accepted on the command-line. Options are passed to the command
+ without triggering the argument parser, which means you'll need to pass the
+ correct type. For example, ``call_command('flush', verbosity=0)`` (zero must
+ be an integer rather than a string).
Examples::
From 27bed94e9cb4eb712ddda465e6bbd3aca5f15ce4 Mon Sep 17 00:00:00 2001
From: Moritz Sichert
Date: Sun, 13 Mar 2016 13:31:11 +0100
Subject: [PATCH 498/756] [1.9.x] Fixed #25804 -- Documented additions to
Jinja2 context.
Backport of 6aef986cdbff72809b39880132959ef74827492f from master
---
docs/topics/templates.txt | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 5e5dee516802..77c3b4ce4c8b 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -456,10 +456,13 @@ adds defaults that differ from Jinja2's for a few options:
* ``'auto_reload'``: ``settings.DEBUG``
* ``'undefined'``: ``DebugUndefined if settings.DEBUG else Undefined``
-The default configuration is purposefully kept to a minimum. The ``Jinja2``
-backend doesn't create a Django-flavored environment. It doesn't know about
-Django context processors, filters, and tags. In order to use Django-specific
-APIs, you must configure them into the environment.
+The default configuration is purposefully kept to a minimum. If a template is
+rendered with a request (e.g. when using :py:func:`~django.shortcuts.render`),
+the ``Jinja2`` backend adds the globals ``request``, ``csrf_input``, and
+``csrf_token`` to the context. Apart from that, this backend doesn't create a
+Django-flavored environment. It doesn't know about Django context processors,
+filters, and tags. In order to use Django-specific APIs, you must configure
+them into the environment.
For example, you can create ``myproject/jinja2.py`` with this content::
From bf7a35c329858d1309e70f421a308f8aced1c444 Mon Sep 17 00:00:00 2001
From: Jon Dufresne
Date: Sun, 13 Mar 2016 17:19:46 -0700
Subject: [PATCH 499/756] [1.9.x] Fixed test_dumpdata_progressbar to use the
instantiated StringIO object.
Backport of cacc7e85e17b3d00e7ed856d8bbadb8f870bb5d6 from master
---
tests/fixtures/tests.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/tests/fixtures/tests.py b/tests/fixtures/tests.py
index 86164e03bcb6..31b6f16c8a07 100644
--- a/tests/fixtures/tests.py
+++ b/tests/fixtures/tests.py
@@ -478,6 +478,7 @@ def test_dumpdata_progressbar(self):
options['verbosity'] = 0
new_io = six.StringIO()
new_io.isatty = lambda: True
+ options.update({'stdout': new_io, 'stderr': new_io})
management.call_command('dumpdata', 'fixtures', **options)
self.assertEqual(new_io.getvalue(), '')
From b50e4ffe7eb9d8797d38f28ae3f51fb443056214 Mon Sep 17 00:00:00 2001
From: Adam Alton
Date: Mon, 14 Mar 2016 17:59:19 +0000
Subject: [PATCH 500/756] [1.9.x] Removed unnecessary filter kwarg from .get()
in a test.
Backport of 38086c83aca881aa72bc2eba1e6eadaa76529ed0 from master
---
tests/model_inheritance/tests.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py
index 3c4f71d93432..1869864a9a6f 100644
--- a/tests/model_inheritance/tests.py
+++ b/tests/model_inheritance/tests.py
@@ -226,10 +226,8 @@ def test_inherited_does_not_exist_exception(self):
def test_inherited_multiple_objects_returned_exception(self):
# MultipleObjectsReturned is also inherited.
- self.assertRaises(
- Place.MultipleObjectsReturned,
- Restaurant.objects.get, id__lt=12321
- )
+ with self.assertRaises(Place.MultipleObjectsReturned):
+ Restaurant.objects.get()
def test_related_objects_for_inherited_models(self):
# Related objects work just as they normally do.
From c6424efbc6114eeefe7ec7545de7e127ed189e92 Mon Sep 17 00:00:00 2001
From: Vincenzo Pandolfo
Date: Mon, 14 Mar 2016 20:21:05 +0000
Subject: [PATCH 501/756] [1.9.x] Fixed #26334 -- Removed whitespace stripping
from contrib.auth password fields.
Backport of d0fe6c915665fa3220e84bd691ba7002a357e5c5 from master
---
django/contrib/auth/forms.py | 9 +++++-
docs/releases/1.9.5.txt | 6 ++++
tests/auth_tests/test_forms.py | 56 ++++++++++++++++++++++++++++++++++
3 files changed, 70 insertions(+), 1 deletion(-)
diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
index b1cedc916c01..380dc2b001e0 100644
--- a/django/contrib/auth/forms.py
+++ b/django/contrib/auth/forms.py
@@ -69,9 +69,11 @@ class UserCreationForm(forms.ModelForm):
'password_mismatch': _("The two password fields didn't match."),
}
password1 = forms.CharField(label=_("Password"),
+ strip=False,
widget=forms.PasswordInput)
password2 = forms.CharField(label=_("Password confirmation"),
widget=forms.PasswordInput,
+ strip=False,
help_text=_("Enter the same password as before, for verification."))
class Meta:
@@ -127,7 +129,7 @@ class AuthenticationForm(forms.Form):
username/password logins.
"""
username = forms.CharField(max_length=254)
- password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
+ password = forms.CharField(label=_("Password"), strip=False, widget=forms.PasswordInput)
error_messages = {
'invalid_login': _("Please enter a correct %(username)s and password. "
@@ -269,8 +271,10 @@ class SetPasswordForm(forms.Form):
}
new_password1 = forms.CharField(label=_("New password"),
widget=forms.PasswordInput,
+ strip=False,
help_text=password_validation.password_validators_help_text_html())
new_password2 = forms.CharField(label=_("New password confirmation"),
+ strip=False,
widget=forms.PasswordInput)
def __init__(self, user, *args, **kwargs):
@@ -307,6 +311,7 @@ class PasswordChangeForm(SetPasswordForm):
"Please enter it again."),
})
old_password = forms.CharField(label=_("Old password"),
+ strip=False,
widget=forms.PasswordInput)
field_order = ['old_password', 'new_password1', 'new_password2']
@@ -335,11 +340,13 @@ class AdminPasswordChangeForm(forms.Form):
password1 = forms.CharField(
label=_("Password"),
widget=forms.PasswordInput,
+ strip=False,
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password (again)"),
widget=forms.PasswordInput,
+ strip=False,
help_text=_("Enter the same password as before, for verification."),
)
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 55fd794d789d..7e8efb6fec8c 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -19,3 +19,9 @@ Bugfixes
* Fixed data loss on SQLite where ``DurationField`` values with fractional
seconds could be saved as ``None`` (:ticket:`26324`).
+
+* The forms in ``contrib.auth`` no longer strip trailing and leading whitespace
+ from the password fields (:ticket:`26334`). The change requires users who set
+ their password to something with such whitespace after a site updated to
+ Django 1.9 to reset their password. It provides backwards-compatibility for
+ earlier versions of Django.
diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py
index 918fe3801a78..e902cdd339b2 100644
--- a/tests/auth_tests/test_forms.py
+++ b/tests/auth_tests/test_forms.py
@@ -169,6 +169,17 @@ class Meta(UserCreationForm.Meta):
form = CustomUserCreationForm(data)
self.assertTrue(form.is_valid())
+ def test_password_whitespace_not_stripped(self):
+ data = {
+ 'username': 'testuser',
+ 'password1': ' testpassword ',
+ 'password2': ' testpassword ',
+ }
+ form = UserCreationForm(data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['password1'], data['password1'])
+ self.assertEqual(form.cleaned_data['password2'], data['password2'])
+
@override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
class AuthenticationFormTest(TestDataMixin, TestCase):
@@ -279,6 +290,15 @@ class CustomAuthenticationForm(AuthenticationForm):
form = CustomAuthenticationForm()
self.assertEqual(form.fields['username'].label, "")
+ def test_password_whitespace_not_stripped(self):
+ data = {
+ 'username': 'testuser',
+ 'password': ' pass ',
+ }
+ form = AuthenticationForm(None, data)
+ form.is_valid() # Not necessary to have valid credentails for the test.
+ self.assertEqual(form.cleaned_data['password'], data['password'])
+
@override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
class SetPasswordFormTest(TestDataMixin, TestCase):
@@ -330,6 +350,17 @@ def test_validates_password(self):
form["new_password2"].errors
)
+ def test_password_whitespace_not_stripped(self):
+ user = User.objects.get(username='testclient')
+ data = {
+ 'new_password1': ' password ',
+ 'new_password2': ' password ',
+ }
+ form = SetPasswordForm(user, data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
+ self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
+
@override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
class PasswordChangeFormTest(TestDataMixin, TestCase):
@@ -381,6 +412,20 @@ def test_field_order(self):
self.assertEqual(list(PasswordChangeForm(user, {}).fields),
['old_password', 'new_password1', 'new_password2'])
+ def test_password_whitespace_not_stripped(self):
+ user = User.objects.get(username='testclient')
+ user.set_password(' oldpassword ')
+ data = {
+ 'old_password': ' oldpassword ',
+ 'new_password1': ' pass ',
+ 'new_password2': ' pass ',
+ }
+ form = PasswordChangeForm(user, data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['old_password'], data['old_password'])
+ self.assertEqual(form.cleaned_data['new_password1'], data['new_password1'])
+ self.assertEqual(form.cleaned_data['new_password2'], data['new_password2'])
+
@override_settings(USE_TZ=False, PASSWORD_HASHERS=['django.contrib.auth.hashers.SHA1PasswordHasher'])
class UserChangeFormTest(TestDataMixin, TestCase):
@@ -673,3 +718,14 @@ def test_success(self, password_changed):
self.assertEqual(password_changed.call_count, 0)
form.save()
self.assertEqual(password_changed.call_count, 1)
+
+ def test_password_whitespace_not_stripped(self):
+ user = User.objects.get(username='testclient')
+ data = {
+ 'password1': ' pass ',
+ 'password2': ' pass ',
+ }
+ form = AdminPasswordChangeForm(user, data)
+ self.assertTrue(form.is_valid())
+ self.assertEqual(form.cleaned_data['password1'], data['password1'])
+ self.assertEqual(form.cleaned_data['password2'], data['password2'])
From 0dc3822f56a0db83f9fc0b1ed320bbeb477eca63 Mon Sep 17 00:00:00 2001
From: Duane Hilton
Date: Mon, 14 Mar 2016 20:40:02 -0600
Subject: [PATCH 502/756] [1.9.x] Fixed #26290 -- Documented that a QuerySet
for pagination should be ordered.
Backport of f8b23e52e86307428da2cf928bf4f1d9fdbd2694 from master
---
docs/topics/pagination.txt | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt
index 7fb31324fb9f..a45b8fabd607 100644
--- a/docs/topics/pagination.txt
+++ b/docs/topics/pagination.txt
@@ -140,8 +140,11 @@ Required arguments
------------------
``object_list``
- A list, tuple, Django ``QuerySet``, or other sliceable object with a
- ``count()`` or ``__len__()`` method.
+ A list, tuple, ``QuerySet``, or other sliceable object with a ``count()``
+ or ``__len__()`` method. For consistent pagination, ``QuerySet``\s should
+ be ordered, e.g. with an :meth:`~django.db.models.query.QuerySet.order_by`
+ clause or with a default :attr:`~django.db.models.Options.ordering` on the
+ model.
``per_page``
The maximum number of items to include on a page, not including orphans
From 2ed2b508ceac3d5d7d98483fca96cfdbb8a9d98d Mon Sep 17 00:00:00 2001
From: Andrew Abraham
Date: Tue, 15 Mar 2016 13:30:41 -0700
Subject: [PATCH 503/756] [1.9.x] Fixed DiscoverRunner failfast parameter
default in docs.
Backport of f2d9caa625084b0b96d60153f6b26ba43a1ab835 from master
---
docs/topics/testing/advanced.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt
index 5c84393182eb..92d1b61d21bb 100644
--- a/docs/topics/testing/advanced.txt
+++ b/docs/topics/testing/advanced.txt
@@ -372,7 +372,7 @@ behavior. This class defines the ``run_tests()`` entry point, plus a
selection of other methods that are used to by ``run_tests()`` to set up,
execute and tear down the test suite.
-.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=True, keepdb=False, reverse=False, debug_sql=False, **kwargs)
+.. class:: DiscoverRunner(pattern='test*.py', top_level=None, verbosity=1, interactive=True, failfast=False, keepdb=False, reverse=False, debug_sql=False, **kwargs)
``DiscoverRunner`` will search for tests in any file matching ``pattern``.
From f49cfb76c7b24a4fa5f8ac36dfa0a82ab66336c5 Mon Sep 17 00:00:00 2001
From: Alex Hill
Date: Wed, 9 Mar 2016 11:35:39 +0800
Subject: [PATCH 504/756] [1.9.x] Fixed #26306 -- Fixed memory leak in cached
template loader.
Backport of ecb59cc6579402b68ddfd4499bf30edacf5963be from master
---
django/template/backends/django.py | 17 +++++++++--
django/template/loaders/cached.py | 27 +++++++++++++++--
docs/releases/1.9.5.txt | 2 ++
tests/template_tests/test_loaders.py | 43 +++++++++++++++++++++++++---
4 files changed, 79 insertions(+), 10 deletions(-)
diff --git a/django/template/backends/django.py b/django/template/backends/django.py
index deca90c28e68..70b39b543e95 100644
--- a/django/template/backends/django.py
+++ b/django/template/backends/django.py
@@ -97,13 +97,24 @@ def render(self, context=None, request=None):
reraise(exc, self.backend)
-def reraise(exc, backend):
+def copy_exception(exc, backend=None):
"""
- Reraise TemplateDoesNotExist while maintaining template debug information.
+ Create a new TemplateDoesNotExist. Preserve its declared attributes and
+ template debug data but discard __traceback__, __context__, and __cause__
+ to make this object suitable for keeping around (in a cache, for example).
"""
- new = exc.__class__(*exc.args, tried=exc.tried, backend=backend)
+ backend = backend or exc.backend
+ new = exc.__class__(*exc.args, tried=exc.tried, backend=backend, chain=exc.chain)
if hasattr(exc, 'template_debug'):
new.template_debug = exc.template_debug
+ return new
+
+
+def reraise(exc, backend):
+ """
+ Reraise TemplateDoesNotExist while maintaining template debug information.
+ """
+ new = copy_exception(exc, backend)
six.reraise(exc.__class__, new, sys.exc_info()[2])
diff --git a/django/template/loaders/cached.py b/django/template/loaders/cached.py
index 5bf5c10586c3..70c55feb5142 100644
--- a/django/template/loaders/cached.py
+++ b/django/template/loaders/cached.py
@@ -7,6 +7,7 @@
import warnings
from django.template import Origin, Template, TemplateDoesNotExist
+from django.template.backends.django import copy_exception
from django.utils.deprecation import RemovedInDjango20Warning
from django.utils.encoding import force_bytes
from django.utils.inspect import func_supports_parameter
@@ -27,11 +28,31 @@ def get_contents(self, origin):
return origin.loader.get_contents(origin)
def get_template(self, template_name, template_dirs=None, skip=None):
+ """
+ Perform the caching that gives this loader its name. Often many of the
+ templates attempted will be missing, so memory use is of concern here.
+ To keep it in check, caching behavior is a little complicated when a
+ template is not found. See ticket #26306 for more details.
+
+ With template debugging disabled, cache the TemplateDoesNotExist class
+ for every missing template and raise a new instance of it after
+ fetching it from the cache.
+
+ With template debugging enabled, a unique TemplateDoesNotExist object
+ is cached for each missing template to preserve debug data. When
+ raising an exception, Python sets __traceback__, __context__, and
+ __cause__ attributes on it. Those attributes can contain references to
+ all sorts of objects up the call chain and caching them creates a
+ memory leak. Thus, unraised copies of the exceptions are cached and
+ copies of those copies are raised after they're fetched from the cache.
+ """
key = self.cache_key(template_name, template_dirs, skip)
cached = self.get_template_cache.get(key)
if cached:
- if isinstance(cached, TemplateDoesNotExist):
- raise cached
+ if isinstance(cached, type) and issubclass(cached, TemplateDoesNotExist):
+ raise cached(template_name)
+ elif isinstance(cached, TemplateDoesNotExist):
+ raise copy_exception(cached)
return cached
try:
@@ -39,7 +60,7 @@ def get_template(self, template_name, template_dirs=None, skip=None):
template_name, template_dirs, skip,
)
except TemplateDoesNotExist as e:
- self.get_template_cache[key] = e
+ self.get_template_cache[key] = copy_exception(e) if self.engine.debug else TemplateDoesNotExist
raise
else:
self.get_template_cache[key] = template
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 7e8efb6fec8c..d74a2d9abefe 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -25,3 +25,5 @@ Bugfixes
their password to something with such whitespace after a site updated to
Django 1.9 to reset their password. It provides backwards-compatibility for
earlier versions of Django.
+
+* Fixed a memory leak in the cached template loader (:ticket:`26306`).
diff --git a/tests/template_tests/test_loaders.py b/tests/template_tests/test_loaders.py
index 11f20c6debff..35921e047204 100644
--- a/tests/template_tests/test_loaders.py
+++ b/tests/template_tests/test_loaders.py
@@ -49,11 +49,46 @@ def test_get_template(self):
self.assertEqual(template.origin.template_name, 'index.html')
self.assertEqual(template.origin.loader, self.engine.template_loaders[0].loaders[0])
- def test_get_template_missing(self):
+ def test_get_template_missing_debug_off(self):
+ """
+ With template debugging disabled, the raw TemplateDoesNotExist class
+ should be cached when a template is missing. See ticket #26306 and
+ docstrings in the cached loader for details.
+ """
+ self.engine.debug = False
with self.assertRaises(TemplateDoesNotExist):
- self.engine.get_template('doesnotexist.html')
- e = self.engine.template_loaders[0].get_template_cache['doesnotexist.html']
- self.assertEqual(e.args[0], 'doesnotexist.html')
+ self.engine.get_template('prod-template-missing.html')
+ e = self.engine.template_loaders[0].get_template_cache['prod-template-missing.html']
+ self.assertEqual(e, TemplateDoesNotExist)
+
+ def test_get_template_missing_debug_on(self):
+ """
+ With template debugging enabled, a TemplateDoesNotExist instance
+ should be cached when a template is missing.
+ """
+ self.engine.debug = True
+ with self.assertRaises(TemplateDoesNotExist):
+ self.engine.get_template('debug-template-missing.html')
+ e = self.engine.template_loaders[0].get_template_cache['debug-template-missing.html']
+ self.assertIsInstance(e, TemplateDoesNotExist)
+ self.assertEqual(e.args[0], 'debug-template-missing.html')
+
+ @unittest.skipIf(six.PY2, "Python 2 doesn't set extra exception attributes")
+ def test_cached_exception_no_traceback(self):
+ """
+ When a TemplateDoesNotExist instance is cached, the cached instance
+ should not contain the __traceback__, __context__, or __cause__
+ attributes that Python sets when raising exceptions.
+ """
+ self.engine.debug = True
+ with self.assertRaises(TemplateDoesNotExist):
+ self.engine.get_template('no-traceback-in-cache.html')
+ e = self.engine.template_loaders[0].get_template_cache['no-traceback-in-cache.html']
+
+ error_msg = "Cached TemplateDoesNotExist must not have been thrown."
+ self.assertIsNone(e.__traceback__, error_msg)
+ self.assertIsNone(e.__context__, error_msg)
+ self.assertIsNone(e.__cause__, error_msg)
@ignore_warnings(category=RemovedInDjango20Warning)
def test_load_template(self):
From b4bb2ad13d178dd2db484c7721fd47aa3d285908 Mon Sep 17 00:00:00 2001
From: Berker Peksag
Date: Mon, 14 Mar 2016 05:17:05 +0200
Subject: [PATCH 505/756] [1.9.x] Fixed #26297 -- Fixed `collectstatic --clear`
crash if storage doesn't implement path().
Backport of 28bcff82c5ed4694f4761c303294ffafbd7096ce from master
---
.../management/commands/collectstatic.py | 14 ++++---
docs/releases/1.9.5.txt | 3 ++
tests/staticfiles_tests/storage.py | 37 +++++++++++++++++++
tests/staticfiles_tests/test_management.py | 5 +++
4 files changed, 54 insertions(+), 5 deletions(-)
diff --git a/django/contrib/staticfiles/management/commands/collectstatic.py b/django/contrib/staticfiles/management/commands/collectstatic.py
index e6b6198952f1..793c211f42e9 100644
--- a/django/contrib/staticfiles/management/commands/collectstatic.py
+++ b/django/contrib/staticfiles/management/commands/collectstatic.py
@@ -218,12 +218,16 @@ def clear_dir(self, path):
smart_text(fpath), level=1)
else:
self.log("Deleting '%s'" % smart_text(fpath), level=1)
- full_path = self.storage.path(fpath)
- if not os.path.exists(full_path) and os.path.lexists(full_path):
- # Delete broken symlinks
- os.unlink(full_path)
- else:
+ try:
+ full_path = self.storage.path(fpath)
+ except NotImplementedError:
self.storage.delete(fpath)
+ else:
+ if not os.path.exists(full_path) and os.path.lexists(full_path):
+ # Delete broken symlinks
+ os.unlink(full_path)
+ else:
+ self.storage.delete(fpath)
for d in dirs:
self.clear_dir(os.path.join(path, d))
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index d74a2d9abefe..98dd1799a80d 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -27,3 +27,6 @@ Bugfixes
earlier versions of Django.
* Fixed a memory leak in the cached template loader (:ticket:`26306`).
+
+* Fixed a regression that caused ``collectstatic --clear`` to fail if the
+ storage doesn't implement ``path()`` (:ticket:`26297`).
diff --git a/tests/staticfiles_tests/storage.py b/tests/staticfiles_tests/storage.py
index fe4811a49573..925f4b7fed49 100644
--- a/tests/staticfiles_tests/storage.py
+++ b/tests/staticfiles_tests/storage.py
@@ -1,5 +1,8 @@
+import errno
+import os
from datetime import datetime
+from django.conf import settings
from django.contrib.staticfiles.storage import CachedStaticFilesStorage
from django.core.files import storage
@@ -22,6 +25,40 @@ def modified_time(self, name):
return datetime.date(1970, 1, 1)
+class PathNotImplementedStorage(storage.Storage):
+
+ def _save(self, name, content):
+ return 'dummy'
+
+ def _path(self, name):
+ return os.path.join(settings.STATIC_ROOT, name)
+
+ def exists(self, name):
+ return os.path.exists(self._path(name))
+
+ def listdir(self, path):
+ path = self._path(path)
+ directories, files = [], []
+ for entry in os.listdir(path):
+ if os.path.isdir(os.path.join(path, entry)):
+ directories.append(entry)
+ else:
+ files.append(entry)
+ return directories, files
+
+ def delete(self, name):
+ name = self._path(name)
+ if os.path.exists(name):
+ try:
+ os.remove(name)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ def path(self, name):
+ raise NotImplementedError
+
+
class SimpleCachedStaticFilesStorage(CachedStaticFilesStorage):
def file_hash(self, name, content=None):
diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py
index 94166f9888d2..b6c2a9fe87c0 100644
--- a/tests/staticfiles_tests/test_management.py
+++ b/tests/staticfiles_tests/test_management.py
@@ -166,6 +166,11 @@ def test_dir_not_exists(self, **kwargs):
shutil.rmtree(six.text_type(settings.STATIC_ROOT))
super(TestCollectionClear, self).run_collectstatic(clear=True)
+ @override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.PathNotImplementedStorage')
+ def test_handle_path_notimplemented(self):
+ self.run_collectstatic()
+ self.assertFileNotFound('cleared.txt')
+
class TestCollectionExcludeNoDefaultIgnore(CollectionTestCase, TestDefaults):
"""
From f6f24af1f43b722dda9818297c1c75bcabc8f5aa Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 18 Mar 2016 11:37:20 -0400
Subject: [PATCH 506/756] [1.9.x] Fixed #26375 -- Used a more generic name in a
reusable template example.
Backport of 1d0abeaf757518808aabe9c85a2beaaa340fbc43 from master
---
docs/ref/class-based-views/generic-editing.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/class-based-views/generic-editing.txt b/docs/ref/class-based-views/generic-editing.txt
index 3def731ee73d..3bcd9fea99dc 100644
--- a/docs/ref/class-based-views/generic-editing.txt
+++ b/docs/ref/class-based-views/generic-editing.txt
@@ -132,7 +132,7 @@ editing content:
``UpdateView``
From 5f5d654609c7b9b70438d684bc01ac6cbfc656a2 Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 18 Mar 2016 10:09:19 +0100
Subject: [PATCH 507/756] [1.9.x] Added missing stacklevel for management
command deprecation warning
---
django/core/management/base.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/django/core/management/base.py b/django/core/management/base.py
index a2fe1f915d59..b9cc2c029ba4 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -262,7 +262,7 @@ def store_as_int(option, opt_str, value, parser):
# Backwards compatibility: use deprecated optparse module
warnings.warn("OptionParser usage for Django management commands "
"is deprecated, use ArgumentParser instead",
- RemovedInDjango110Warning)
+ RemovedInDjango110Warning, stacklevel=3)
parser = OptionParser(prog=prog_name,
usage=self.usage(subcommand),
version=self.get_version())
From 2d178b3d2cbed7d7a21cf0de3bd9da9330c90755 Mon Sep 17 00:00:00 2001
From: Amine
Date: Sat, 19 Mar 2016 20:34:43 +0100
Subject: [PATCH 508/756] [1.9.x] Fixed a broken link in
docs/internals/contributing/writing-documentation.txt.
Backport of 8b5a4fa941ba6e308b5ebd831b7953440fc0ee9b from master
---
docs/internals/contributing/writing-documentation.txt | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt
index 6c4e583866c1..8cb50392f2ff 100644
--- a/docs/internals/contributing/writing-documentation.txt
+++ b/docs/internals/contributing/writing-documentation.txt
@@ -220,10 +220,8 @@ documentation:
Django-specific markup
======================
-Besides the `Sphinx built-in markup`__, Django's docs defines some extra
-description units:
-
-__ http://sphinx-doc.org/markup/
+Besides the :ref:`Sphinx built-in markup `, Django's
+docs defines some extra description units:
* Settings::
From 4e8c26531925227872ce29bc0cdee3329be4a216 Mon Sep 17 00:00:00 2001
From: Jason Parrott
Date: Thu, 17 Mar 2016 22:45:00 +0900
Subject: [PATCH 509/756] [1.9.x] Fixed #26373 -- Fixed reverse lookup crash
with a ForeignKey to_field in a subquery.
Backport of 4c1c93032f4a015cbb4b33958603d18ac43515b4 from master
---
django/db/models/query.py | 2 +-
docs/releases/1.9.5.txt | 4 ++++
tests/queries/tests.py | 4 ++++
3 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/django/db/models/query.py b/django/db/models/query.py
index cb02085cc577..3787e34abc1c 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1113,7 +1113,7 @@ def _prepare(self, field):
# if they are set up to select only a single field.
if len(self._fields or self.model._meta.concrete_fields) > 1:
raise TypeError('Cannot use multi-field values as a filter value.')
- else:
+ elif self.model != field.model:
# If the query is used as a subquery for a ForeignKey with non-pk
# target field, make sure to select the target field in the subquery.
foreign_fields = getattr(field, 'foreign_related_fields', ())
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 98dd1799a80d..37157b3e5630 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -30,3 +30,7 @@ Bugfixes
* Fixed a regression that caused ``collectstatic --clear`` to fail if the
storage doesn't implement ``path()`` (:ticket:`26297`).
+
+* Fixed a crash when using a reverse lookup with a subquery when a
+ ``ForeignKey`` has a ``to_field`` set to something other than the primary key
+ (:ticket:`26373`).
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 7c306c9f15b1..05564a590921 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -2474,6 +2474,10 @@ def test_in_subquery(self):
set(Eaten.objects.filter(food__in=Food.objects.filter(name='apple').values('eaten__meal'))),
set()
)
+ self.assertEqual(
+ set(Food.objects.filter(eaten__in=Eaten.objects.filter(meal='lunch'))),
+ {apple}
+ )
def test_reverse_in(self):
apple = Food.objects.create(name="apple")
From 379bab354472b882fed250d0dceb73644d52e220 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 17 Mar 2016 12:45:23 -0400
Subject: [PATCH 510/756] [1.9.x] Fixed #26265 -- Clarified RadioSelect
container's HTML id.
Backport of 53e8ab580f7c0fcfc589ba0b1b6cc2556080e0b2 from master
---
docs/ref/forms/api.txt | 5 +++++
docs/ref/forms/widgets.txt | 8 ++++----
2 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 072525ff53c3..94ecd2b5ccfe 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -835,6 +835,11 @@ The field-specific output honors the form object's ``auto_id`` setting::
Attributes of ``BoundField``
----------------------------
+.. attribute:: BoundField.auto_id
+
+ The HTML ID attribute for this ``BoundField``. Returns an empty string
+ if :attr:`Form.auto_id` is ``False``.
+
.. attribute:: BoundField.data
This property returns the data for this :class:`~django.forms.BoundField`
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index 9504d64f14e2..ba4c1becf90c 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -682,8 +682,8 @@ Selector and checkbox widgets
simply includes ``{{ myform.beatles }}`` -- they'll be output in a ````
with ``- `` tags, as above.
- The outer ``
`` container will receive the ``id`` attribute defined on
- the widget.
+ The outer ```` container receives the ``id`` attribute of the widget,
+ if defined, or :attr:`BoundField.auto_id` otherwise.
When looping over the radio buttons, the ``label`` and ``input`` tags include
``for`` and ``id`` attributes, respectively. Each radio button has an
@@ -704,8 +704,8 @@ Selector and checkbox widgets
...
- The outer ```` container will receive the ``id`` attribute defined on
- the widget.
+ The outer ```` container receives the ``id`` attribute of the widget,
+ if defined, or :attr:`BoundField.auto_id` otherwise.
Like :class:`RadioSelect`, you can now loop over the individual checkboxes making
up the lists. See the documentation of :class:`RadioSelect` for more details.
From 4e80ec6a7bb59f6b6ef7ad8153d715098b2c9463 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 21 Mar 2016 08:09:13 -0400
Subject: [PATCH 511/756] [1.9.x] Fixed #26376 -- Clarifed meaning of 'search'
in TemplateResponseMixin.get_template_names() docs.
Backport of 6dd503851c91197364f7c7b915cabb604fadb755 from master
---
docs/ref/class-based-views/mixins-simple.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/class-based-views/mixins-simple.txt b/docs/ref/class-based-views/mixins-simple.txt
index 6130b824a676..1d41b25ad437 100644
--- a/docs/ref/class-based-views/mixins-simple.txt
+++ b/docs/ref/class-based-views/mixins-simple.txt
@@ -105,7 +105,7 @@ Simple mixins
.. method:: get_template_names()
Returns a list of template names to search for when rendering the
- template.
+ template. The first template that is found will be used.
If :attr:`template_name` is specified, the default implementation will
return a list containing :attr:`template_name` (if it is specified).
From 367c092085d1f721fa39219168d9a1ff451ac083 Mon Sep 17 00:00:00 2001
From: Akshesh
Date: Mon, 29 Feb 2016 16:17:14 +0530
Subject: [PATCH 512/756] [1.9.x] Fixed grammatical typos in gis docs.
Backport of 2b31f14d89cb144fad10389824828e90fd1a2dcc from master
---
docs/ref/contrib/gis/geoquerysets.txt | 7 ++-----
docs/ref/contrib/gis/install/index.txt | 2 +-
2 files changed, 3 insertions(+), 6 deletions(-)
diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt
index 72078eb52a38..5e20d98dadb0 100644
--- a/docs/ref/contrib/gis/geoquerysets.txt
+++ b/docs/ref/contrib/gis/geoquerysets.txt
@@ -11,10 +11,7 @@ GeoQuerySet API Reference
Spatial Lookups
===============
-Just like when using the :ref:`queryset-api`, interaction
-with ``GeoQuerySet`` by :ref:`chaining filters `.
-Instead of the regular Django :ref:`field-lookups`, the
-spatial lookups in this section are available for :class:`GeometryField`.
+The spatial lookups in this section are available for :class:`GeometryField`.
For an introduction, see the :ref:`spatial lookups introduction
`. For an overview of what lookups are
@@ -294,7 +291,7 @@ SpatiaLite SQL equivalent::
Oracle
~~~~~~
-Here the relation pattern is comprised at least one of the nine relation
+Here the relation pattern is comprised of at least one of the nine relation
strings: ``TOUCH``, ``OVERLAPBDYDISJOINT``, ``OVERLAPBDYINTERSECT``,
``EQUAL``, ``INSIDE``, ``COVEREDBY``, ``CONTAINS``, ``COVERS``, ``ON``, and
``ANYINTERACT``. Multiple strings may be combined with the logical Boolean
diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt
index 0bb476c41c28..b7ba8e594da7 100644
--- a/docs/ref/contrib/gis/install/index.txt
+++ b/docs/ref/contrib/gis/install/index.txt
@@ -94,7 +94,7 @@ Add ``django.contrib.gis`` to :setting:`INSTALLED_APPS`
Like other Django contrib applications, you will *only* need to add
:mod:`django.contrib.gis` to :setting:`INSTALLED_APPS` in your settings.
-This is the so that ``gis`` templates can be located -- if not done, then
+This is so that the ``gis`` templates can be located -- if not done, then
features such as the geographic admin or KML sitemaps will not function properly.
Troubleshooting
From 7dee62ff6833c1dbbf013b7f264f24226612a81b Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 21 Mar 2016 19:56:15 -0400
Subject: [PATCH 513/756] [1.9.x] Fixed #26392 -- Corrected
login_required/permission_required stacking example.
Backport of c41737dc00e2c8d34046b9f856f23fcc0cb4a82c from master
---
docs/topics/auth/default.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt
index b1b049dd18d9..9f804de46404 100644
--- a/docs/topics/auth/default.txt
+++ b/docs/topics/auth/default.txt
@@ -718,8 +718,8 @@ The ``permission_required`` decorator
from django.contrib.auth.decorators import login_required, permission_required
- @permission_required('polls.can_vote', raise_exception=True)
@login_required
+ @permission_required('polls.can_vote', raise_exception=True)
def my_view(request):
...
From ccc367fd48655b8709a01653b224e5ffa19c9dee Mon Sep 17 00:00:00 2001
From: ieatkittens
Date: Fri, 18 Mar 2016 15:21:41 -0400
Subject: [PATCH 514/756] [1.9.x] Fixed #26293 -- Fixed CommonMiddleware to
process PREPEND_WWW and APPEND_SLASH independently.
Backport of 9390da7fb6e251eaa9a785692f987296cb14523f from master
---
django/middleware/common.py | 21 +++++++++++----------
docs/releases/1.9.5.txt | 3 +++
tests/middleware/tests.py | 7 +------
3 files changed, 15 insertions(+), 16 deletions(-)
diff --git a/django/middleware/common.py b/django/middleware/common.py
index 50acf32b6c9e..b842dd8a0fef 100644
--- a/django/middleware/common.py
+++ b/django/middleware/common.py
@@ -54,18 +54,19 @@ def process_request(self, request):
# Check for a redirect based on settings.PREPEND_WWW
host = request.get_host()
+ must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.')
+ redirect_url = ('%s://www.%s' % (request.scheme, host)) if must_prepend else ''
- if settings.PREPEND_WWW and host and not host.startswith('www.'):
- host = 'www.' + host
-
- # Check if we also need to append a slash so we can do it all
- # with a single redirect.
- if self.should_redirect_with_slash(request):
- path = self.get_full_path_with_slash(request)
- else:
- path = request.get_full_path()
+ # Check if a slash should be appended
+ if self.should_redirect_with_slash(request):
+ path = self.get_full_path_with_slash(request)
+ else:
+ path = request.get_full_path()
- return self.response_redirect_class('%s://%s%s' % (request.scheme, host, path))
+ # Return a redirect if necessary
+ if redirect_url or path != request.get_full_path():
+ redirect_url += path
+ return self.response_redirect_class(redirect_url)
def should_redirect_with_slash(self, request):
"""
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 37157b3e5630..c88e92031f41 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -34,3 +34,6 @@ Bugfixes
* Fixed a crash when using a reverse lookup with a subquery when a
``ForeignKey`` has a ``to_field`` set to something other than the primary key
(:ticket:`26373`).
+
+* Fixed a regression in ``CommonMiddleware`` that caused spurious warnings in
+ logs on requests missing a trailing slash (:ticket:`26293`).
diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py
index bf96f05048db..2452470b818f 100644
--- a/tests/middleware/tests.py
+++ b/tests/middleware/tests.py
@@ -67,10 +67,8 @@ def test_append_slash_redirect(self):
APPEND_SLASH should redirect slashless URLs to a valid pattern.
"""
request = self.rf.get('/slash')
- response = HttpResponseNotFound()
- r = CommonMiddleware().process_response(request, response)
+ r = CommonMiddleware().process_request(request)
self.assertEqual(r.status_code, 301)
- self.assertEqual(r.url, '/slash/')
@override_settings(APPEND_SLASH=True)
def test_append_slash_redirect_querystring(self):
@@ -301,9 +299,6 @@ def test_non_ascii_query_string_does_not_crash(self):
request = self.rf.get('/slash')
request.META['QUERY_STRING'] = force_str('drink=café')
r = CommonMiddleware().process_request(request)
- self.assertIsNone(r)
- response = HttpResponseNotFound()
- r = CommonMiddleware().process_response(request, response)
self.assertEqual(r.status_code, 301)
def test_response_redirect_class(self):
From 9d0c8d3dd9dce720f0b91d30508278e45235a3a9 Mon Sep 17 00:00:00 2001
From: Tim Shaffer
Date: Fri, 25 Mar 2016 13:14:52 -0400
Subject: [PATCH 515/756] [1.9.x] Fixed typo in docs/topics/db/aggregation.txt.
Backport of 8550566af6321fe111ac1fe6497720e6ac291824 from master
---
docs/topics/db/aggregation.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt
index 4b23ca6be8ab..c5dddf29c6cb 100644
--- a/docs/topics/db/aggregation.txt
+++ b/docs/topics/db/aggregation.txt
@@ -294,7 +294,7 @@ file::
>>> Author.objects.aggregate(average_rating=Avg('book__rating'))
-(The resulting dictionary will have a key called ``'average__rating'``. If no
+(The resulting dictionary will have a key called ``'average_rating'``. If no
such alias were specified, it would be the rather long ``'book__rating__avg'``.)
Aggregations and other ``QuerySet`` clauses
From 1f15d442bf59218fb62fd95cc065da1d9a14b577 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 24 Mar 2016 16:22:35 -0400
Subject: [PATCH 516/756] [1.9.x] Fixed #26387 -- Restored the functionality of
the admin's raw_id_fields in list_editable.
Backport of acfaec3db5ba39de52f6e607e74343dccf72fba1 from master
---
.../admin/js/admin/RelatedObjectLookups.js | 8 ++++++++
.../admin/templates/admin/change_form.html | 8 --------
docs/releases/1.8.12.txt | 3 +++
docs/releases/1.9.5.txt | 3 +++
tests/admin_views/admin.py | 8 ++++++++
tests/admin_views/tests.py | 17 +++++++++++++++++
6 files changed, 39 insertions(+), 8 deletions(-)
diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
index e661e0bcf465..4ac9baa8ee78 100644
--- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
+++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js
@@ -176,6 +176,14 @@
}
});
$('.related-widget-wrapper select').trigger('change');
+ $('.related-lookup').click(function(e) {
+ e.preventDefault();
+ var event = $.Event('django:lookup-related');
+ $(this).trigger(event);
+ if (!event.isDefaultPrevented()) {
+ showRelatedObjectLookupPopup(this);
+ }
+ });
});
})(django.jQuery);
diff --git a/django/contrib/admin/templates/admin/change_form.html b/django/contrib/admin/templates/admin/change_form.html
index 993f6dfd1bf7..22d49dc8dec0 100644
--- a/django/contrib/admin/templates/admin/change_form.html
+++ b/django/contrib/admin/templates/admin/change_form.html
@@ -79,14 +79,6 @@
showAddAnotherPopup(this);
}
});
- $('.related-lookup').click(function(e) {
- e.preventDefault();
- var event = $.Event('django:lookup-related');
- $(this).trigger(event);
- if (!event.isDefaultPrevented()) {
- showRelatedObjectLookupPopup(this);
- }
- });
{% if adminform and add %}
$('form#{{ opts.model_name }}_form :input:visible:enabled:first').focus()
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
index 0052a90b0d52..47581b3f1911 100644
--- a/docs/releases/1.8.12.txt
+++ b/docs/releases/1.8.12.txt
@@ -15,3 +15,6 @@ Bugfixes
* Fixed data loss on SQLite where ``DurationField`` values with fractional
seconds could be saved as ``None`` (:ticket:`26324`).
+
+* Restored the functionality of the admin's ``raw_id_fields`` in
+ ``list_editable`` (:ticket:`26387`).
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index c88e92031f41..6f073c5c076f 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -37,3 +37,6 @@ Bugfixes
* Fixed a regression in ``CommonMiddleware`` that caused spurious warnings in
logs on requests missing a trailing slash (:ticket:`26293`).
+
+* Restored the functionality of the admin's ``raw_id_fields`` in
+ ``list_editable`` (:ticket:`26387`).
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index b3f7d05ca8e0..e14ac592c805 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -1008,5 +1008,13 @@ def get_formsets_with_inlines(self, request, obj=None):
site2 = admin.AdminSite(name="namespaced_admin")
site2.register(User, UserAdmin)
site2.register(Group, GroupAdmin)
+site2.register(ParentWithUUIDPK)
+site2.register(
+ RelatedWithUUIDPKModel,
+ list_display=['pk', 'parent'],
+ list_editable=['parent'],
+ raw_id_fields=['parent'],
+)
+
site7 = admin.AdminSite(name="admin7")
site7.register(Article, ArticleAdmin2)
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 039ebec8f7a4..98830d40c018 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -4667,6 +4667,23 @@ def test_inline_uuid_pk_add_with_popup(self):
self.assertEqual(select.first_selected_option.text, uuid_id)
self.assertEqual(select.first_selected_option.get_attribute('value'), uuid_id)
+ def test_list_editable_raw_id_fields(self):
+ parent = ParentWithUUIDPK.objects.create(title='test')
+ parent2 = ParentWithUUIDPK.objects.create(title='test2')
+ RelatedWithUUIDPKModel.objects.create(parent=parent)
+ self.admin_login(username='super', password='secret', login_url=reverse('admin:index'))
+ change_url = reverse('admin:admin_views_relatedwithuuidpkmodel_changelist', current_app=site2.name)
+ self.selenium.get(self.live_server_url + change_url)
+ self.selenium.find_element_by_id('lookup_id_form-0-parent').click()
+ self.wait_for_popup()
+ self.selenium.switch_to.window(self.selenium.window_handles[-1])
+ # Select "parent2" in the popup.
+ self.selenium.find_element_by_link_text(str(parent2.pk)).click()
+ self.selenium.switch_to.window(self.selenium.window_handles[0])
+ # The newly selected pk should appear in the raw id input.
+ value = self.selenium.find_element_by_id('id_form-0-parent').get_attribute('value')
+ self.assertEqual(value, str(parent2.pk))
+
class SeleniumAdminViewsChromeTests(SeleniumAdminViewsFirefoxTests):
webdriver_class = 'selenium.webdriver.chrome.webdriver.WebDriver'
From 6ec2ab1cda16c81430da8be134d77efa061e133d Mon Sep 17 00:00:00 2001
From: Aymeric Augustin
Date: Fri, 25 Mar 2016 20:49:18 +0100
Subject: [PATCH 517/756] [1.9.x] Fixed #26408 -- Updated link to DEP 182.
Thanks kaifeldhoff for the report.
Backport of 7b1ce7f from master
---
docs/topics/templates.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 77c3b4ce4c8b..8cae6019790c 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -847,5 +847,5 @@ Django provides many :ref:`built-in context processors `.
Implementing a custom context processor is as simple as defining a function.
.. _Jinja2: http://jinja.pocoo.org/
-.. _DEP 182: https://github.com/django/deps/blob/master/accepted/0182-multiple-template-engines.rst
+.. _DEP 182: https://github.com/django/deps/blob/master/final/0182-multiple-template-engines.rst
.. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar
From 844f071c660c068244ab0c4667390194f1b4fc92 Mon Sep 17 00:00:00 2001
From: Francisco Capdevila
Date: Sat, 26 Mar 2016 18:34:18 -0300
Subject: [PATCH 518/756] [1.9.x] Fixed typo in
docs/ref/contrib/postgres/aggregates.txt.
Backport of 82243e5150f67b6fe8c6f8f650bf3b492823c3e7 from master
---
docs/ref/contrib/postgres/aggregates.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/ref/contrib/postgres/aggregates.txt b/docs/ref/contrib/postgres/aggregates.txt
index aa6d09170514..30974989c28e 100644
--- a/docs/ref/contrib/postgres/aggregates.txt
+++ b/docs/ref/contrib/postgres/aggregates.txt
@@ -118,7 +118,7 @@ field or an expression returning a numeric data. Both are required.
.. class:: RegrAvgY(y, x)
- Returns the average of the independent variable (``sum(y)/N``) as a
+ Returns the average of the dependent variable (``sum(y)/N``) as a
``float``, or ``None`` if there aren't any matching rows.
``RegrCount``
From 5fbb5426254c7e9b7f697317415589371c80d6de Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Mon, 28 Mar 2016 11:19:25 -0400
Subject: [PATCH 519/756] [1.9.x] Sorted single letter imports per the latest
version of isort.
Backport of 1c8c0837c61a9e9eb2129df29f75be92e47e926c from master
---
django/db/models/__init__.py | 4 ++--
django/db/models/query.py | 4 ++--
django/db/models/sql/query.py | 2 +-
tests/aggregation/tests.py | 2 +-
tests/aggregation_regress/tests.py | 2 +-
tests/annotations/tests.py | 2 +-
tests/delete/tests.py | 4 ++--
tests/expressions/tests.py | 2 +-
tests/expressions_case/tests.py | 2 +-
tests/generic_relations_regress/tests.py | 2 +-
tests/gis_tests/relatedapp/tests.py | 2 +-
tests/gis_tests/test_measure.py | 2 +-
tests/queries/tests.py | 6 +++---
tests/select_related_regress/tests.py | 2 +-
tests/update/tests.py | 2 +-
15 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py
index 5d9e14ab4619..07fe6bcf1b11 100644
--- a/django/db/models/__init__.py
+++ b/django/db/models/__init__.py
@@ -7,7 +7,7 @@
CASCADE, DO_NOTHING, PROTECT, SET, SET_DEFAULT, SET_NULL, ProtectedError,
)
from django.db.models.expressions import ( # NOQA
- F, Case, Expression, ExpressionWrapper, Func, Value, When,
+ Case, Expression, ExpressionWrapper, F, Func, Value, When,
)
from django.db.models.fields import * # NOQA
from django.db.models.fields.files import FileField, ImageField # NOQA
@@ -15,7 +15,7 @@
from django.db.models.fields.subclassing import SubfieldBase # NOQA
from django.db.models.lookups import Lookup, Transform # NOQA
from django.db.models.manager import Manager # NOQA
-from django.db.models.query import Q, Prefetch, QuerySet # NOQA
+from django.db.models.query import Prefetch, Q, QuerySet # NOQA
# Imports that would create circular imports if sorted
from django.db.models.base import Model # NOQA isort:skip
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 3787e34abc1c..33c40cb3baf3 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -16,10 +16,10 @@
from django.db.models import sql
from django.db.models.constants import LOOKUP_SEP
from django.db.models.deletion import Collector
-from django.db.models.expressions import F, Date, DateTime
+from django.db.models.expressions import Date, DateTime, F
from django.db.models.fields import AutoField
from django.db.models.query_utils import (
- Q, InvalidQuery, check_rel_lookup_compatibility, deferred_class_factory,
+ InvalidQuery, Q, check_rel_lookup_compatibility, deferred_class_factory,
)
from django.db.models.sql.constants import CURSOR
from django.utils import six, timezone
diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
index 29fe885cc656..dec7d9ff72b0 100644
--- a/django/db/models/sql/query.py
+++ b/django/db/models/sql/query.py
@@ -19,7 +19,7 @@
from django.db.models.expressions import Col, Ref
from django.db.models.fields.related_lookups import MultiColSource
from django.db.models.query_utils import (
- Q, PathInfo, check_rel_lookup_compatibility, refs_expression,
+ PathInfo, Q, check_rel_lookup_compatibility, refs_expression,
)
from django.db.models.sql.constants import (
INNER, LOUTER, ORDER_DIR, ORDER_PATTERN, QUERY_TERMS, SINGLE,
diff --git a/tests/aggregation/tests.py b/tests/aggregation/tests.py
index 7053acacc310..a7ee52529e7f 100644
--- a/tests/aggregation/tests.py
+++ b/tests/aggregation/tests.py
@@ -7,7 +7,7 @@
from django.core.exceptions import FieldError
from django.db import connection
from django.db.models import (
- F, Aggregate, Avg, Count, DecimalField, DurationField, FloatField, Func,
+ Aggregate, Avg, Count, DecimalField, DurationField, F, FloatField, Func,
IntegerField, Max, Min, Sum, Value,
)
from django.test import TestCase, ignore_warnings
diff --git a/tests/aggregation_regress/tests.py b/tests/aggregation_regress/tests.py
index 13735942763f..e164a93cd28b 100644
--- a/tests/aggregation_regress/tests.py
+++ b/tests/aggregation_regress/tests.py
@@ -9,7 +9,7 @@
from django.core.exceptions import FieldError
from django.db import connection
from django.db.models import (
- F, Q, Avg, Count, Max, StdDev, Sum, Value, Variance,
+ Avg, Count, F, Max, Q, StdDev, Sum, Value, Variance,
)
from django.test import TestCase, skipUnlessAnyDBFeature, skipUnlessDBFeature
from django.test.utils import Approximate
diff --git a/tests/annotations/tests.py b/tests/annotations/tests.py
index 5ff4df100d23..4d9d13ad75a5 100644
--- a/tests/annotations/tests.py
+++ b/tests/annotations/tests.py
@@ -5,7 +5,7 @@
from django.core.exceptions import FieldDoesNotExist, FieldError
from django.db.models import (
- F, BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, Func,
+ BooleanField, CharField, Count, DateTimeField, ExpressionWrapper, F, Func,
IntegerField, Sum, Value,
)
from django.db.models.functions import Lower
diff --git a/tests/delete/tests.py b/tests/delete/tests.py
index e874321744df..f618f68adb36 100644
--- a/tests/delete/tests.py
+++ b/tests/delete/tests.py
@@ -8,8 +8,8 @@
from django.utils.six.moves import range
from .models import (
- A, M, MR, R, S, T, Avatar, Base, Child, HiddenUser, HiddenUserProfile,
- M2MFrom, M2MTo, MRNull, Parent, RChild, User, create_a, get_default_r,
+ MR, A, Avatar, Base, Child, HiddenUser, HiddenUserProfile, M, M2MFrom,
+ M2MTo, MRNull, Parent, R, RChild, S, T, User, create_a, get_default_r,
)
diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py
index f68892782f6a..a53048938070 100644
--- a/tests/expressions/tests.py
+++ b/tests/expressions/tests.py
@@ -11,7 +11,7 @@
Avg, Count, Max, Min, StdDev, Sum, Variance,
)
from django.db.models.expressions import (
- F, Case, Col, Date, DateTime, ExpressionWrapper, Func, OrderBy, Random,
+ Case, Col, Date, DateTime, ExpressionWrapper, F, Func, OrderBy, Random,
RawSQL, Ref, Value, When,
)
from django.db.models.functions import (
diff --git a/tests/expressions_case/tests.py b/tests/expressions_case/tests.py
index f9cdfc5d5c1a..30e8a704aefb 100644
--- a/tests/expressions_case/tests.py
+++ b/tests/expressions_case/tests.py
@@ -8,7 +8,7 @@
from django.core.exceptions import FieldError
from django.db import connection, models
-from django.db.models import F, Q, Max, Min, Sum, Value
+from django.db.models import F, Max, Min, Q, Sum, Value
from django.db.models.expressions import Case, When
from django.test import TestCase
from django.utils import six
diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py
index b6782fe13fad..73195640c403 100644
--- a/tests/generic_relations_regress/tests.py
+++ b/tests/generic_relations_regress/tests.py
@@ -5,7 +5,7 @@
from django.test import TestCase, skipIfDBFeature
from .models import (
- A, B, C, D, Address, Board, CharLink, Company, Contact, Content, Developer,
+ A, Address, B, Board, C, CharLink, Company, Contact, Content, D, Developer,
Guild, HasLinkThing, Link, Node, Note, OddRelation1, OddRelation2,
Organization, Person, Place, Related, Restaurant, Tag, Team, TextLink,
)
diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py
index 402170d5c382..a424b93e845d 100644
--- a/tests/gis_tests/relatedapp/tests.py
+++ b/tests/gis_tests/relatedapp/tests.py
@@ -1,6 +1,6 @@
from __future__ import unicode_literals
-from django.contrib.gis.db.models import F, Collect, Count, Extent, Union
+from django.contrib.gis.db.models import Collect, Count, Extent, F, Union
from django.contrib.gis.geometry.backend import Geometry
from django.contrib.gis.geos import GEOSGeometry, MultiPoint, Point
from django.db import connection
diff --git a/tests/gis_tests/test_measure.py b/tests/gis_tests/test_measure.py
index 771c10fe9dfe..4a7ca992c610 100644
--- a/tests/gis_tests/test_measure.py
+++ b/tests/gis_tests/test_measure.py
@@ -5,7 +5,7 @@
import unittest
-from django.contrib.gis.measure import A, D, Area, Distance
+from django.contrib.gis.measure import A, Area, D, Distance
class DistanceTest(unittest.TestCase):
diff --git a/tests/queries/tests.py b/tests/queries/tests.py
index 05564a590921..7ccd50f2344e 100644
--- a/tests/queries/tests.py
+++ b/tests/queries/tests.py
@@ -8,7 +8,7 @@
from django.core.exceptions import FieldError
from django.db import DEFAULT_DB_ALIAS, connection
-from django.db.models import F, Q, Count
+from django.db.models import Count, F, Q
from django.db.models.sql.constants import LOUTER
from django.db.models.sql.datastructures import EmptyResultSet
from django.db.models.sql.where import NothingNode, WhereNode
@@ -18,7 +18,7 @@
from django.utils.six.moves import range
from .models import (
- FK1, X, Annotation, Article, Author, BaseA, Book, CategoryItem,
+ FK1, Annotation, Article, Author, BaseA, Book, CategoryItem,
CategoryRelationship, Celebrity, Channel, Chapter, Child, ChildObjectA,
Classroom, Company, Cover, CustomPk, CustomPkTag, Detail, DumbCategory,
Eaten, Employment, ExtraInfo, Fan, Food, Identifier, Individual, Item, Job,
@@ -30,7 +30,7 @@
RelatedIndividual, RelatedObject, Report, ReservedName, Responsibility,
School, SharedConnection, SimpleCategory, SingleObject, SpecialCategory,
Staff, StaffUser, Student, Tag, Task, Ticket21203Child, Ticket21203Parent,
- Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid,
+ Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid, X,
)
diff --git a/tests/select_related_regress/tests.py b/tests/select_related_regress/tests.py
index e575697cb151..640add4638a4 100644
--- a/tests/select_related_regress/tests.py
+++ b/tests/select_related_regress/tests.py
@@ -4,7 +4,7 @@
from django.utils import six
from .models import (
- A, B, C, Building, Chick, Child, Class, Client, ClientStatus, Connection,
+ A, B, Building, C, Chick, Child, Class, Client, ClientStatus, Connection,
Country, Device, Enrollment, Hen, Item, Organizer, Person, Port,
SpecialClient, State, Student, TUser,
)
diff --git a/tests/update/tests.py b/tests/update/tests.py
index 1ed316c9587f..010151d61e9c 100644
--- a/tests/update/tests.py
+++ b/tests/update/tests.py
@@ -2,7 +2,7 @@
from django.test import TestCase
-from .models import A, B, D, Bar, DataPoint, Foo, RelatedPoint
+from .models import A, B, Bar, D, DataPoint, Foo, RelatedPoint
class SimpleTest(TestCase):
From 97c5539a81c71f314314bc9796e7e87c41ae43b8 Mon Sep 17 00:00:00 2001
From: Joshua Pereyda
Date: Mon, 28 Mar 2016 11:26:55 -0700
Subject: [PATCH 520/756] [1.9.x] Fixed #26416 -- Fixed references to previous
tutorial numbers in docs/intro/reusable-apps.txt.
Backport of 0c0e8f0a62eb369bb05ff770ad53656831c8c01a from master
---
docs/intro/reusable-apps.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt
index 4f77e8bc5173..fee59a37fab5 100644
--- a/docs/intro/reusable-apps.txt
+++ b/docs/intro/reusable-apps.txt
@@ -2,11 +2,11 @@
Advanced tutorial: How to write reusable apps
=============================================
-This advanced tutorial begins where :doc:`Tutorial 6 `
+This advanced tutorial begins where :doc:`Tutorial 7 `
left off. We'll be turning our Web-poll into a standalone Python package
you can reuse in new projects and share with other people.
-If you haven't recently completed Tutorials 1–6, we encourage you to review
+If you haven't recently completed Tutorials 1–7, we encourage you to review
these so that your example project matches the one described below.
Reusability matters
From 026574e03c6b6fd20a45f97b0470afb70e41fda4 Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Mon, 28 Mar 2016 14:14:24 -0400
Subject: [PATCH 521/756] [1.9.x] Fixed #26413 -- Fixed a regression with
abstract model inheritance and explicit parent links.
Thanks Trac alias trkjgrdg for the report and Tim for investigation and review.
Backport of 67cf5efa31acb2916034afb15610b700695dfcb0 from master
---
django/db/models/base.py | 10 +++++++++-
docs/releases/1.9.5.txt | 3 +++
tests/model_inheritance/tests.py | 23 ++++++++++++++++++++++-
3 files changed, 34 insertions(+), 2 deletions(-)
diff --git a/django/db/models/base.py b/django/db/models/base.py
index e1a94d34d160..5635fabd6a13 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -245,13 +245,21 @@ def __new__(cls, name, bases, attrs):
field = None
new_class._meta.parents[base] = field
else:
+ base_parents = base._meta.parents.copy()
+
# .. and abstract ones.
for field in parent_fields:
new_field = copy.deepcopy(field)
new_class.add_to_class(field.name, new_field)
+ # Replace parent links defined on this base by the new
+ # field as it will be appropriately resolved if required.
+ if field.one_to_one:
+ for parent, parent_link in base_parents.items():
+ if field == parent_link:
+ base_parents[parent] = new_field
# Pass any non-abstract parent classes onto child.
- new_class._meta.parents.update(base._meta.parents)
+ new_class._meta.parents.update(base_parents)
# Inherit managers from the abstract base classes.
new_class.copy_managers(base._meta.abstract_managers)
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 6f073c5c076f..7c50e4883d22 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -40,3 +40,6 @@ Bugfixes
* Restored the functionality of the admin's ``raw_id_fields`` in
``list_editable`` (:ticket:`26387`).
+
+* Fixed a regression with abstract model inheritance and explicit parent links
+ (:ticket:`26413`).
diff --git a/tests/model_inheritance/tests.py b/tests/model_inheritance/tests.py
index 1869864a9a6f..fad47a69eca4 100644
--- a/tests/model_inheritance/tests.py
+++ b/tests/model_inheritance/tests.py
@@ -2,9 +2,10 @@
from operator import attrgetter
+from django.apps.registry import Apps
from django.core.exceptions import FieldError, ValidationError
from django.core.management import call_command
-from django.db import connection
+from django.db import connection, models
from django.test import TestCase, TransactionTestCase
from django.test.utils import CaptureQueriesContext
from django.utils import six
@@ -130,6 +131,26 @@ def test_mixin_init(self):
m = MixinModel()
self.assertEqual(m.other_attr, 1)
+ def test_abstract_parent_link(self):
+ test_apps = Apps(['model_inheritance'])
+
+ class A(models.Model):
+ class Meta:
+ apps = test_apps
+
+ class B(A):
+ a = models.OneToOneField('A', parent_link=True, on_delete=models.CASCADE)
+
+ class Meta:
+ apps = test_apps
+ abstract = True
+
+ class C(B):
+ class Meta:
+ apps = test_apps
+
+ self.assertIs(C._meta.parents[A], C._meta.get_field('a'))
+
class ModelInheritanceDataTests(TestCase):
@classmethod
From ed87af32662bf7a3829e679831e6e019b9d85112 Mon Sep 17 00:00:00 2001
From: Alex Hill
Date: Tue, 29 Mar 2016 16:58:04 +0800
Subject: [PATCH 522/756] [1.9.x] Fixed #26384 -- Fixed renaming the PK on a
model with a self-referential FK on SQLite.
Backport of 4b2cf1cd27587a30b3b081091627d7ee13141afe from master
---
django/db/backends/sqlite3/schema.py | 10 +++++++++-
docs/releases/1.9.5.txt | 3 +++
tests/schema/models.py | 5 +++++
tests/schema/tests.py | 15 +++++++++++++--
4 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/django/db/backends/sqlite3/schema.py b/django/db/backends/sqlite3/schema.py
index 6f0c8184ad25..9d9eb3c3ba28 100644
--- a/django/db/backends/sqlite3/schema.py
+++ b/django/db/backends/sqlite3/schema.py
@@ -76,8 +76,16 @@ def _remake_table(self, model, create_fields=[], delete_fields=[], alter_fields=
3. copy the data from the old renamed table to the new table
4. delete the "app_model__old" table
"""
+ # Self-referential fields must be recreated rather than copied from
+ # the old model to ensure their remote_field.field_name doesn't refer
+ # to an altered field.
+ def is_self_referential(f):
+ return f.is_relation and f.remote_field.model is model
# Work out the new fields dict / mapping
- body = {f.name: f for f in model._meta.local_concrete_fields}
+ body = {
+ f.name: f.clone() if is_self_referential(f) else f
+ for f in model._meta.local_concrete_fields
+ }
# Since mapping might mix column names and default values,
# its values must be already quoted.
mapping = {f.column: self.quote_name(f.column) for f in model._meta.local_concrete_fields}
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index 7c50e4883d22..fc2c6cea032b 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -43,3 +43,6 @@ Bugfixes
* Fixed a regression with abstract model inheritance and explicit parent links
(:ticket:`26413`).
+
+* Fixed a migrations crash on SQLite when renaming the primary key of a model
+ containing a ``ForeignKey`` to ``'self'`` (:ticket:`26384`).
diff --git a/tests/schema/models.py b/tests/schema/models.py
index adfa50542037..7a0c452d62ae 100644
--- a/tests/schema/models.py
+++ b/tests/schema/models.py
@@ -177,3 +177,8 @@ class UniqueTest(models.Model):
class Meta:
apps = new_apps
unique_together = ["year", "slug"]
+
+
+class Node(models.Model):
+ node_id = models.AutoField(primary_key=True)
+ parent = models.ForeignKey('self', models.CASCADE, null=True, blank=True)
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index ab95b0c79eaa..24007352b964 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -27,8 +27,8 @@
from .models import (
Author, AuthorWithDefaultHeight, AuthorWithEvenLongerName, Book,
BookForeignObj, BookWeak, BookWithLongName, BookWithO2O, BookWithoutAuthor,
- BookWithSlug, IntegerPK, Note, NoteRename, Tag, TagIndexed, TagM2MTest,
- TagUniqueRename, Thing, UniqueTest, new_apps,
+ BookWithSlug, IntegerPK, Node, Note, NoteRename, Tag, TagIndexed,
+ TagM2MTest, TagUniqueRename, Thing, UniqueTest, new_apps,
)
@@ -1818,3 +1818,14 @@ def test_alter_field_add_db_index_to_charfield_with_unique(self):
self.get_constraints_for_column(Tag, 'slug'),
['schema_tag_slug_2c418ba3_like', 'schema_tag_slug_key']
)
+
+ def test_alter_pk_with_self_referential_field(self):
+ """
+ Changing the primary key field name of a model with a self-referential
+ foreign key (#26384).
+ """
+ old_field = Node._meta.get_field('node_id')
+ new_field = AutoField(primary_key=True)
+ new_field.set_attributes_from_name('id')
+ with connection.schema_editor() as editor:
+ editor.alter_field(Node, old_field, new_field)
From d58a324bea2df202bc09bd887f564c3f8e35b2ec Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 31 Mar 2016 08:31:03 -0400
Subject: [PATCH 523/756] [1.9.x] Fixed #26410 -- Added a docs example for
loader.render_to_string().
Backport of a65fc6df891ab449e9c24160f6f6a1d6d2ccb282 from master
---
docs/topics/templates.txt | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt
index 8cae6019790c..fb64f20c6ed5 100644
--- a/docs/topics/templates.txt
+++ b/docs/topics/templates.txt
@@ -309,6 +309,11 @@ templates, Django provides a shortcut function which automates the process.
The ``request`` argument was added.
+ Usage example::
+
+ from django.template.loader import render_to_string
+ rendered = render_to_string('my_template.html', {'foo': 'bar'})
+
See also the :func:`~django.shortcuts.render()` shortcut which calls
:func:`render_to_string()` and feeds the result into an
:class:`~django.http.HttpResponse` suitable for returning from a view.
From a0e3cbaa2bcbab682dfc3fb2998c3b43b21a3741 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 31 Mar 2016 11:49:12 -0400
Subject: [PATCH 524/756] [1.9.x] Refs #26384, #24995 -- Skipped a schema test
on older MySQL versions.
Backport of f3595b25496691966d4ff858a3b395735ad85a6e from master
---
tests/schema/tests.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/tests/schema/tests.py b/tests/schema/tests.py
index 24007352b964..eb657f4fbe69 100644
--- a/tests/schema/tests.py
+++ b/tests/schema/tests.py
@@ -1819,6 +1819,10 @@ def test_alter_field_add_db_index_to_charfield_with_unique(self):
['schema_tag_slug_2c418ba3_like', 'schema_tag_slug_key']
)
+ @unittest.skipIf(
+ connection.vendor == 'mysql' and connection.mysql_version < (5, 6, 6),
+ 'Skip known bug renaming primary keys on older MySQL versions (#24995).'
+ )
def test_alter_pk_with_self_referential_field(self):
"""
Changing the primary key field name of a model with a self-referential
From bca42c709fe6e72e8f5c0a46681aab400bddb427 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 31 Mar 2016 13:21:32 -0400
Subject: [PATCH 525/756] [1.9.x] Removed some docs that should have been
removed along with PROFANITIES_LIST.
Backport of 12dee89d9c1fbe6b47984c544505aa8ff77e92bc from master
---
docs/ref/settings.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 360fd7dcaf34..43262a0d6ea2 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -1034,8 +1034,8 @@ environment, such as all the currently defined Django settings (from
``settings.py``).
As a security measure, Django will *not* include settings that might be
-sensitive (or offensive), such as :setting:`SECRET_KEY`. Specifically, it will
-exclude any setting whose name includes any of the following:
+sensitive, such as :setting:`SECRET_KEY`. Specifically, it will exclude any
+setting whose name includes any of the following:
* ``'API'``
* ``'KEY'``
From cac320bfaa1320795f4e630d315b02bc4c252231 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Thu, 31 Mar 2016 13:41:06 -0400
Subject: [PATCH 526/756] [1.9.x] Fixed #26436 -- Added a link to the settings
filtering in the error reporting howto.
Thanks mlissner for the suggestion.
Backport of 8928823b135b9ce2c7b18f4da0d2602b113a247e from master
---
docs/howto/error-reporting.txt | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt
index fc043375d400..9ae194d4e0d1 100644
--- a/docs/howto/error-reporting.txt
+++ b/docs/howto/error-reporting.txt
@@ -143,10 +143,12 @@ exception raised, each `traceback frame`_’s local variables, and the
However, sometimes certain types of information may be too sensitive and thus
may not be appropriate to be kept track of, for example a user's password or
-credit card number. So Django offers a set of function decorators to help you
-control which information should be filtered out of error reports in a
-production environment (that is, where :setting:`DEBUG` is set to ``False``):
-:func:`sensitive_variables` and :func:`sensitive_post_parameters`.
+credit card number. So in addition to filtering out settings that appear to be
+sensitive as described in the :setting:`DEBUG` documentation, Django offers a
+set of function decorators to help you control which information should be
+filtered out of error reports in a production environment (that is, where
+:setting:`DEBUG` is set to ``False``): :func:`sensitive_variables` and
+:func:`sensitive_post_parameters`.
.. _`full traceback`: https://en.wikipedia.org/wiki/Stack_trace
.. _`traceback frame`: https://en.wikipedia.org/wiki/Stack_frame
From a304e7ddff23b14b8e6546a92eb5250d9cf6eaff Mon Sep 17 00:00:00 2001
From: Simon Charette
Date: Thu, 31 Mar 2016 18:27:21 -0400
Subject: [PATCH 527/756] [1.9.x] Fixed #26438 -- Fixed multiple .objects typos
in the docs.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Thanks Pablo Oubiña for the report.
Backport of 64aba7a8aba06b8be52a1a099b44e1d3be4bdd26 from master
---
docs/topics/db/optimization.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt
index 788e44705056..446ec872563d 100644
--- a/docs/topics/db/optimization.txt
+++ b/docs/topics/db/optimization.txt
@@ -161,7 +161,7 @@ So using the :ref:`example Weblog models `::
will be quicker than:
- >>> entry = Entry.object.get(headline="News Item Title")
+ >>> entry = Entry.objects.get(headline="News Item Title")
because ``id`` is indexed by the database and is guaranteed to be unique.
From 6a8ba2eef45ccc3022c88510bffd29734d71c39e Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Sat, 26 Mar 2016 20:11:57 +0100
Subject: [PATCH 528/756] [1.9.x] Fixed #25532 -- Properly redisplayed
JSONField form input values
Thanks David Szotten for the report and Tommy Beadle for code inspiration.
Thanks Tim Graham for the review.
Partial backport of db19619545 from master.
---
django/contrib/postgres/forms/jsonb.py | 15 +++++++++++++
docs/releases/1.9.5.txt | 3 +++
tests/postgres_tests/test_json.py | 29 ++++++++++++++++++++++++++
3 files changed, 47 insertions(+)
diff --git a/django/contrib/postgres/forms/jsonb.py b/django/contrib/postgres/forms/jsonb.py
index 8eefc1499371..415288dc90ec 100644
--- a/django/contrib/postgres/forms/jsonb.py
+++ b/django/contrib/postgres/forms/jsonb.py
@@ -1,11 +1,16 @@
import json
from django import forms
+from django.utils import six
from django.utils.translation import ugettext_lazy as _
__all__ = ['JSONField']
+class InvalidJSONInput(six.text_type):
+ pass
+
+
class JSONField(forms.CharField):
default_error_messages = {
'invalid': _("'%(value)s' value must be valid JSON."),
@@ -27,5 +32,15 @@ def to_python(self, value):
params={'value': value},
)
+ def bound_data(self, data, initial):
+ if self.disabled:
+ return initial
+ try:
+ return json.loads(data)
+ except ValueError:
+ return InvalidJSONInput(data)
+
def prepare_value(self, value):
+ if isinstance(value, InvalidJSONInput):
+ return value
return json.dumps(value)
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index fc2c6cea032b..ddd5fd5551c3 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -46,3 +46,6 @@ Bugfixes
* Fixed a migrations crash on SQLite when renaming the primary key of a model
containing a ``ForeignKey`` to ``'self'`` (:ticket:`26384`).
+
+* Fixed ``JSONField`` inadvertently escaping its contents when displaying values
+ after failed form validation (:ticket:`25532`).
diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py
index 59e5fe3fe782..11b6a349aaf9 100644
--- a/tests/postgres_tests/test_json.py
+++ b/tests/postgres_tests/test_json.py
@@ -3,7 +3,9 @@
from django.core import exceptions, serializers
from django.db import connection
+from django.forms import CharField, Form
from django.test import TestCase
+from django.utils.html import escape
from . import PostgreSQLTestCase
from .models import JSONModel
@@ -258,7 +260,34 @@ def test_formfield(self):
form_field = model_field.formfield()
self.assertIsInstance(form_field, forms.JSONField)
+ def test_formfield_disabled(self):
+ class JsonForm(Form):
+ name = CharField()
+ jfield = forms.JSONField(disabled=True)
+
+ form = JsonForm({'name': 'xyz', 'jfield': '["bar"]'}, initial={'jfield': ['foo']})
+ self.assertIn('["foo"]', form.as_p())
+
def test_prepare_value(self):
field = forms.JSONField()
self.assertEqual(field.prepare_value({'a': 'b'}), '{"a": "b"}')
self.assertEqual(field.prepare_value(None), 'null')
+ self.assertEqual(field.prepare_value('foo'), '"foo"')
+
+ def test_redisplay_wrong_input(self):
+ """
+ When displaying a bound form (typically due to invalid input), the form
+ should not overquote JSONField inputs.
+ """
+ class JsonForm(Form):
+ name = CharField(max_length=2)
+ jfield = forms.JSONField()
+
+ # JSONField input is fine, name is too long
+ form = JsonForm({'name': 'xyz', 'jfield': '["foo"]'})
+ self.assertIn('["foo"]', form.as_p())
+
+ # This time, the JSONField input is wrong
+ form = JsonForm({'name': 'xy', 'jfield': '{"foo"}'})
+ # Appears once in the textarea and once in the error message
+ self.assertEqual(form.as_p().count(escape('{"foo"}')), 2)
From 97ccab126cb395f3ed5d31ebb37995dcbe27329f Mon Sep 17 00:00:00 2001
From: Claude Paroz
Date: Fri, 1 Apr 2016 15:51:16 +0200
Subject: [PATCH 529/756] [1.9.x] Refs #25532 -- Removed a failing test on
Django 1.9
That test is failing on Django 1.9, that issue has been fixed on master
only as it touches a part of form validation that is too important to
touch for a stable release.
---
tests/postgres_tests/test_json.py | 8 --------
1 file changed, 8 deletions(-)
diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py
index 11b6a349aaf9..9e24303465be 100644
--- a/tests/postgres_tests/test_json.py
+++ b/tests/postgres_tests/test_json.py
@@ -260,14 +260,6 @@ def test_formfield(self):
form_field = model_field.formfield()
self.assertIsInstance(form_field, forms.JSONField)
- def test_formfield_disabled(self):
- class JsonForm(Form):
- name = CharField()
- jfield = forms.JSONField(disabled=True)
-
- form = JsonForm({'name': 'xyz', 'jfield': '["bar"]'}, initial={'jfield': ['foo']})
- self.assertIn('["foo"]', form.as_p())
-
def test_prepare_value(self):
field = forms.JSONField()
self.assertEqual(field.prepare_value({'a': 'b'}), '{"a": "b"}')
From d8725878ed04373ab34ca89efd998911d69b9563 Mon Sep 17 00:00:00 2001
From: Tim Graham
Date: Fri, 1 Apr 2016 13:29:43 -0400
Subject: [PATCH 530/756] [1.9.x] Added release date for 1.9.5 and 1.8.12.
Backport of 93539ba2f42fe56bacefd09a9e8f93b31565f746 from master
---
docs/releases/1.8.12.txt | 2 +-
docs/releases/1.9.5.txt | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/docs/releases/1.8.12.txt b/docs/releases/1.8.12.txt
index 47581b3f1911..d83e095f36b8 100644
--- a/docs/releases/1.8.12.txt
+++ b/docs/releases/1.8.12.txt
@@ -2,7 +2,7 @@
Django 1.8.12 release notes
===========================
-*Under development*
+*April 1, 2016*
Django 1.8.12 fixes several bugs in 1.8.11.
diff --git a/docs/releases/1.9.5.txt b/docs/releases/1.9.5.txt
index ddd5fd5551c3..bf2c303d70a8 100644
--- a/docs/releases/1.9.5.txt
+++ b/docs/releases/1.9.5.txt
@@ -2,7 +2,7 @@
Django 1.9.5 release notes
==========================
-*Under development*
+*April 1, 2016*
Django 1.9.5 fixes several bugs in 1.9.4.
From b29316c54bb3465265ff931e807229f13349457d Mon Sep 17 00:00:00 2001
From: Tim Graham