[Python-modules-team] Bug#807654: jessie-pu: package python-django/1.7.11-1

Raphael Hertzog hertzog at debian.org
Fri Dec 11 10:56:58 UTC 2015


Package: release.debian.org
Severity: normal
Tags: jessie
User: release.debian.org at packages.debian.org
Usertags: pu

Hello,

I would like to update python-django in jessie to the latest upstream bug
fix release in the 1.7.x branch, aka 1.7.11. It should also be the last
upstream release in that branch since it's now unsupported upstream.

I already argued for that during the freeze but it's still the same now:
Django is a sane upstream that does not add new features in branches
of released versions, they only fix bugs and security issues. They have
a large test suite and it's easier for us to maintain Django over the
next 4 years to be as close as possible to the last released version.

Here are some pointers about upstream's policies for stable releases:
https://docs.djangoproject.com/en/1.9/internals/release-process/
https://docs.djangoproject.com/en/1.9/misc/api-stability/

Much like you trust PostgreSQL upstream devs to be sane, I would like
you to trust the Django upstream devs.

I attach a filtered debdiff, I dropped all the changes to test code and
all the changes to the documentation. There are also some harmless changes
related to the switch of the python-modules team to git-dpm too.

Please let me know if I can upload the package.

(I want to do the same for wheezy with 1.4.22, but I'll wait the outcome
of this one before doing the work for wheezy, so it would be nice from you
to have a timely answer)

-- System Information:
Debian Release: stretch/sid
  APT prefers squeeze-lts
  APT policy: (500, 'squeeze-lts'), (500, 'oldoldstable'), (500, 'unstable'), (500, 'testing'), (500, 'stable'), (500, 'oldstable'), (1, 'experimental')
Architecture: amd64 (x86_64)
Foreign Architectures: i386

Kernel: Linux 4.2.0-1-amd64 (SMP w/4 CPU cores)
Locale: LANG=fr_FR.utf8, LC_CTYPE=fr_FR.utf8 (charmap=UTF-8)
Shell: /bin/sh linked to /bin/dash
Init: systemd (via /run/systemd/system)

-- 
Raphaël Hertzog ◈ Debian Developer

Support Debian LTS: http://www.freexian.com/services/debian-lts.html
Learn to master Debian: http://debian-handbook.info/get/
-------------- next part --------------
diff -Nru python-django-1.7.7/debian/changelog python-django-1.7.11/debian/changelog
--- python-django-1.7.7/debian/changelog	2015-11-22 20:36:09.000000000 +0100
+++ python-django-1.7.11/debian/changelog	2015-12-11 10:51:59.000000000 +0100
@@ -1,3 +1,10 @@
+python-django (1.7.11-1) jessie; urgency=medium
+
+  * New upstream release incorporating former security updates and
+    multiple bugfixes.
+
+ -- Raphaël Hertzog <hertzog at debian.org>  Fri, 11 Dec 2015 10:44:42 +0100
+
 python-django (1.7.7-1+deb8u3) jessie-security; urgency=high
 
   * SECURITY UPDATE:
diff -Nru python-django-1.7.7/debian/.git-dpm python-django-1.7.11/debian/.git-dpm
--- python-django-1.7.7/debian/.git-dpm	1970-01-01 01:00:00.000000000 +0100
+++ python-django-1.7.11/debian/.git-dpm	2015-12-11 10:51:59.000000000 +0100
@@ -0,0 +1,11 @@
+# see git-dpm(1) from git-dpm package
+e4cd95fc1d5322f9bff209890b71f57ee9d36e62
+e4cd95fc1d5322f9bff209890b71f57ee9d36e62
+2d07f4b16101fcc8973128c4e4920b41f87175ee
+2d07f4b16101fcc8973128c4e4920b41f87175ee
+python-django_1.7.11.orig.tar.gz
+f9abaf7eacec73bc1c5e6080e2778a7174ebf9d4
+7586798
+debianTag="debian/%e%v"
+patchedTag="patched/%e%v"
+upstreamTag="upstream/%e%u"
diff -Nru python-django-1.7.7/debian/patches/02_disable-sources-in-sphinxdoc.diff python-django-1.7.11/debian/patches/02_disable-sources-in-sphinxdoc.diff
--- python-django-1.7.7/debian/patches/02_disable-sources-in-sphinxdoc.diff	2015-03-23 20:52:49.000000000 +0100
+++ python-django-1.7.11/debian/patches/02_disable-sources-in-sphinxdoc.diff	2015-12-11 10:51:59.000000000 +0100
@@ -1,15 +1,25 @@
-Description: Disable creation of _sources directory by Sphinx
+From 09dc3a85f5a42a5695687c8105cc39f884c19931 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Rapha=C3=ABl=20Hertzog?= <hertzog at debian.org>
+Date: Sun, 11 Oct 2015 12:06:13 +1100
+Subject: Disable creation of _sources directory by Sphinx
+
  We do this to save some space as the sources of the documentation
  are not really useful in a binary package.
  .
  This is a Debian specific patch.
 Forwarded: not-needed
-Author: Raphaël Hertzog <hertzog at debian.org>
 Origin: vendor
 
+Patch-Name: 02_disable-sources-in-sphinxdoc.diff
+---
+ docs/conf.py | 5 ++++-
+ 1 file changed, 4 insertions(+), 1 deletion(-)
+
+diff --git a/docs/conf.py b/docs/conf.py
+index 6df8dd8..90e4d69 100644
 --- a/docs/conf.py
 +++ b/docs/conf.py
-@@ -196,7 +196,10 @@ html_additional_pages = {}
+@@ -200,7 +200,10 @@ html_additional_pages = {}
  #html_split_index = False
  
  # If true, links to the reST sources are added to the pages.
diff -Nru python-django-1.7.7/debian/patches/03_manpage.diff python-django-1.7.11/debian/patches/03_manpage.diff
--- python-django-1.7.7/debian/patches/03_manpage.diff	2015-03-23 20:52:49.000000000 +0100
+++ python-django-1.7.11/debian/patches/03_manpage.diff	2015-12-11 10:51:59.000000000 +0100
@@ -1,12 +1,23 @@
-Description: Update manual page to refer to django-admin instead of django-admin.py
+From fc3577d95827449a95635f3c733b164b8bbd43cd Mon Sep 17 00:00:00 2001
+From: Brett Parker <iDunno at sommitrealweird.co.uk>
+Date: Sun, 11 Oct 2015 12:06:14 +1100
+Subject: Update manual page to refer to django-admin instead of
+ django-admin.py
+
  Update the manual page to speak of django-admin instead of
  django-admin.py as that's the name used by the Debian package.
  .
  This is a Debian specific patch.
 Forwarded: not-needed
-Author: Brett Parker <iDunno at sommitrealweird.co.uk>
 Origin: vendor
 
+Patch-Name: 03_manpage.diff
+---
+ docs/man/django-admin.1 | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1
+index c9932ac..bdb6438 100644
 --- a/docs/man/django-admin.1
 +++ b/docs/man/django-admin.1
 @@ -1,8 +1,8 @@
diff -Nru python-django-1.7.7/debian/patches/06_use_debian_geoip_database_as_default.diff python-django-1.7.11/debian/patches/06_use_debian_geoip_database_as_default.diff
--- python-django-1.7.7/debian/patches/06_use_debian_geoip_database_as_default.diff	2015-03-23 20:52:49.000000000 +0100
+++ python-django-1.7.11/debian/patches/06_use_debian_geoip_database_as_default.diff	2015-12-11 10:51:59.000000000 +0100
@@ -1,12 +1,22 @@
-Description: Use Debian GeoIP database path as default
+From e4cd95fc1d5322f9bff209890b71f57ee9d36e62 Mon Sep 17 00:00:00 2001
+From: Tapio Rantala <tapio.rantala at iki.fi>
+Date: Sun, 11 Oct 2015 12:06:15 +1100
+Subject: Use Debian GeoIP database path as default
+
  Default to Debian standard path for GeoIP directory and for GeoIP city
  file. Avoids the need to declare them in each project.
  .
  This is a Debian specific patch.
 Bug-Debian: http://bugs.debian.org/645094
 Forwarded: not-needed
-Author: Tapio Rantala <tapio.rantala at iki.fi>
 
+Patch-Name: 06_use_debian_geoip_database_as_default.diff
+---
+ django/contrib/gis/geoip/base.py | 19 ++++++++++---------
+ 1 file changed, 10 insertions(+), 9 deletions(-)
+
+diff --git a/django/contrib/gis/geoip/base.py b/django/contrib/gis/geoip/base.py
+index 9295030..0b05f43 100644
 --- a/django/contrib/gis/geoip/base.py
 +++ b/django/contrib/gis/geoip/base.py
 @@ -67,7 +67,8 @@ class GeoIP(object):
@@ -25,13 +35,12 @@
  
 -        * country: The name of the GeoIP country data file.  Defaults to
 -            'GeoIP.dat'; overrides the GEOIP_COUNTRY settings attribute.
--
--        * city: The name of the GeoIP city data file.  Defaults to
--            'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
 +        * country: The name of the GeoIP country data file. Overrides
 +            the GEOIP_COUNTRY settings attribute. If neither is set,
 +            defaults to 'GeoIP.dat'
-+
+ 
+-        * city: The name of the GeoIP city data file.  Defaults to
+-            'GeoLiteCity.dat'; overrides the GEOIP_CITY settings attribute.
 +        * city: The name of the GeoIP city data file. Overrides the
 +            GEOIP_CITY settings attribute. If neither is set, defaults
 +            to 'GeoIPCity.dat'.
diff -Nru python-django-1.7.7/debian/patches/date-leak-1.7.diff python-django-1.7.11/debian/patches/date-leak-1.7.diff
--- python-django-1.7.7/debian/patches/date-leak-1.7.diff	2015-11-22 20:35:27.000000000 +0100
+++ python-django-1.7.11/debian/patches/date-leak-1.7.diff	1970-01-01 01:00:00.000000000 +0100
@@ -1,56 +0,0 @@
-commit 8a01c6b53169ee079cb21ac5919fdafcc8c5e172
-Author: Florian Apolloner <florian at apolloner.eu>
-Date:   Wed Nov 11 20:10:55 2015 +0100
-
-    [1.7.x] Fixed a settings leak possibility in the date template filter.
-    
-    This is a security fix.
-
---- a/django/utils/formats.py
-+++ b/django/utils/formats.py
-@@ -31,6 +31,24 @@
- }
- 
- 
-+FORMAT_SETTINGS = frozenset([
-+    'DECIMAL_SEPARATOR',
-+    'THOUSAND_SEPARATOR',
-+    'NUMBER_GROUPING',
-+    'FIRST_DAY_OF_WEEK',
-+    'MONTH_DAY_FORMAT',
-+    'TIME_FORMAT',
-+    'DATE_FORMAT',
-+    'DATETIME_FORMAT',
-+    'SHORT_DATE_FORMAT',
-+    'SHORT_DATETIME_FORMAT',
-+    'YEAR_MONTH_FORMAT',
-+    'DATE_INPUT_FORMATS',
-+    'TIME_INPUT_FORMATS',
-+    'DATETIME_INPUT_FORMATS',
-+])
-+
-+
- def reset_format_cache():
-     """Clear any cached formats.
- 
-@@ -85,6 +103,8 @@
-     be localized (or not), overriding the value of settings.USE_L10N.
-     """
-     format_type = force_str(format_type)
-+    if format_type not in FORMAT_SETTINGS:
-+        return format_type
-     if use_l10n or (use_l10n is None and settings.USE_L10N):
-         if lang is None:
-             lang = get_language()
---- a/tests/i18n/tests.py
-+++ b/tests/i18n/tests.py
-@@ -828,6 +828,9 @@
-                 '<input id="id_date_added" name="date_added" type="hidden" value="31.12.2009 06:00:00" />; <input id="id_cents_paid" name="cents_paid" type="hidden" value="59,47" />'
-             )
- 
-+    def test_format_arbitrary_settings(self):
-+        self.assertEqual(get_format('DEBUG'), 'DEBUG')
-+
- 
- class MiscTests(TestCase):
- 
diff -Nru python-django-1.7.7/debian/patches/newlines-1.7.x.diff python-django-1.7.11/debian/patches/newlines-1.7.x.diff
--- python-django-1.7.7/debian/patches/newlines-1.7.x.diff	2015-07-07 07:10:34.000000000 +0200
+++ python-django-1.7.11/debian/patches/newlines-1.7.x.diff	1970-01-01 01:00:00.000000000 +0100
@@ -1,149 +0,0 @@
-commit 6e4164b083adb5c974c7ded0f3aeae5188e52b5a
-Author: Tim Graham <timograham at gmail.com>
-Date:   Fri Jun 12 13:49:31 2015 -0400
-
-    [1.7.x] Prevented newlines from being accepted in some validators.
-    
-    This is a security fix; disclosure to follow shortly.
-    
-    Thanks to Sjoerd Job Postmus for the report and draft patch.
-
-Index: python-django-1.7.7/django/core/validators.py
-===================================================================
---- python-django-1.7.7.orig/django/core/validators.py
-+++ python-django-1.7.7/django/core/validators.py
-@@ -73,7 +73,7 @@ class URLValidator(RegexValidator):
-         r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|'  # ...or ipv4
-         r'\[?[A-F0-9]*:[A-F0-9:]+\]?)'  # ...or ipv6
-         r'(?::\d+)?'  # optional port
--        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
-+        r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
-     message = _('Enter a valid URL.')
-     schemes = ['http', 'https', 'ftp', 'ftps']
- 
-@@ -107,12 +107,15 @@ class URLValidator(RegexValidator):
-         else:
-             url = value
- 
-+integer_validator = RegexValidator(
-+    re.compile('^-?\d+\Z'),
-+    message=_('Enter a valid integer.'),
-+    code='invalid',
-+)
-+
- 
- def validate_integer(value):
--    try:
--        int(value)
--    except (ValueError, TypeError):
--        raise ValidationError(_('Enter a valid integer.'), code='invalid')
-+    return integer_validator(value)
- 
- 
- @deconstructible
-@@ -120,15 +123,15 @@ class EmailValidator(object):
-     message = _('Enter a valid email address.')
-     code = 'invalid'
-     user_regex = re.compile(
--        r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"  # dot-atom
--        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)',  # quoted-string
-+        r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"  # dot-atom
-+        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)',  # quoted-string
-         re.IGNORECASE)
-     domain_regex = re.compile(
--        r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$',
-+        r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
-         re.IGNORECASE)
-     literal_regex = re.compile(
-         # literal form, ipv4 or ipv6 address (SMTP 4.1.3)
--        r'\[([A-f0-9:\.]+)\]$',
-+        r'\[([A-f0-9:\.]+)\]\Z',
-         re.IGNORECASE)
-     domain_whitelist = ['localhost']
- 
-@@ -181,10 +184,10 @@ class EmailValidator(object):
- 
- validate_email = EmailValidator()
- 
--slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
-+slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z')
- validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
- 
--ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
-+ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z')
- validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
- 
- 
-@@ -225,7 +228,7 @@ def ip_address_validators(protocol, unpa
-         raise ValueError("The protocol '%s' is unknown. Supported: %s"
-                          % (protocol, list(ip_address_validator_map)))
- 
--comma_separated_int_list_re = re.compile('^[\d,]+$')
-+comma_separated_int_list_re = re.compile('^[\d,]+\Z')
- validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
- 
- 
-Index: python-django-1.7.7/tests/validators/tests.py
-===================================================================
---- python-django-1.7.7.orig/tests/validators/tests.py
-+++ python-django-1.7.7/tests/validators/tests.py
-@@ -25,10 +25,12 @@ TEST_DATA = (
-     (validate_integer, '42', None),
-     (validate_integer, '-42', None),
-     (validate_integer, -42, None),
--    (validate_integer, -42.5, None),
- 
-+    (validate_integer, -42.5, ValidationError),
-     (validate_integer, None, ValidationError),
-     (validate_integer, 'a', ValidationError),
-+    (validate_integer, '\n42', ValidationError),
-+    (validate_integer, '42\n', ValidationError),
- 
-     (validate_email, 'email at here.com', None),
-     (validate_email, 'weirder-email at here.and.there.com', None),
-@@ -66,6 +68,11 @@ TEST_DATA = (
-     (validate_email, '"\\\011"@here.com', None),
-     (validate_email, '"\\\012"@here.com', ValidationError),
-     (validate_email, 'trailingdot at shouldfail.com.', ValidationError),
-+    # Trailing newlines in username or domain not allowed
-+    (validate_email, 'a at b.com\n', ValidationError),
-+    (validate_email, 'a\n at b.com', ValidationError),
-+    (validate_email, '"test at test"\n at example.com', ValidationError),
-+    (validate_email, 'a@[127.0.0.1]\n', ValidationError),
- 
-     (validate_slug, 'slug-ok', None),
-     (validate_slug, 'longer-slug-still-ok', None),
-@@ -78,6 +85,7 @@ TEST_DATA = (
-     (validate_slug, 'some at mail.com', ValidationError),
-     (validate_slug, '你好', ValidationError),
-     (validate_slug, '\n', ValidationError),
-+    (validate_slug, 'trailing-newline\n', ValidationError),
- 
-     (validate_ipv4_address, '1.1.1.1', None),
-     (validate_ipv4_address, '255.0.0.0', None),
-@@ -87,6 +95,7 @@ TEST_DATA = (
-     (validate_ipv4_address, '25.1.1.', ValidationError),
-     (validate_ipv4_address, '25,1,1,1', ValidationError),
-     (validate_ipv4_address, '25.1 .1.1', ValidationError),
-+    (validate_ipv4_address, '1.1.1.1\n', ValidationError),
- 
-     # validate_ipv6_address uses django.utils.ipv6, which
-     # is tested in much greater detail in its own testcase
-@@ -120,6 +129,7 @@ TEST_DATA = (
-     (validate_comma_separated_integer_list, '', ValidationError),
-     (validate_comma_separated_integer_list, 'a,b,c', ValidationError),
-     (validate_comma_separated_integer_list, '1, 2, 3', ValidationError),
-+    (validate_comma_separated_integer_list, '1,2,3\n', ValidationError),
- 
-     (MaxValueValidator(10), 10, None),
-     (MaxValueValidator(10), -10, None),
-@@ -181,6 +191,9 @@ TEST_DATA = (
-     (URLValidator(), 'file://localhost/path', ValidationError),
-     (URLValidator(), 'git://example.com/', ValidationError),
-     (URLValidator(EXTENDED_SCHEMES), 'git://-invalid.com', ValidationError),
-+    # Trailing newlines not accepted
-+    (URLValidator(), 'http://www.djangoproject.com/\n', ValidationError),
-+    (URLValidator(), 'http://[::ffff:192.9.5.5]\n', ValidationError),
- 
-     (BaseValidator(True), True, None),
-     (BaseValidator(True), False, ValidationError),
diff -Nru python-django-1.7.7/debian/patches/series python-django-1.7.11/debian/patches/series
--- python-django-1.7.7/debian/patches/series	2015-11-22 20:35:02.000000000 +0100
+++ python-django-1.7.11/debian/patches/series	2015-12-11 10:51:59.000000000 +0100
@@ -1,7 +1,3 @@
 02_disable-sources-in-sphinxdoc.diff
 03_manpage.diff
 06_use_debian_geoip_database_as_default.diff
-newlines-1.7.x.diff
-session-1.7.x.diff
-session-store-1.7.x.diff
-date-leak-1.7.diff
diff -Nru python-django-1.7.7/debian/patches/session-1.7.x.diff python-django-1.7.11/debian/patches/session-1.7.x.diff
--- python-django-1.7.7/debian/patches/session-1.7.x.diff	2015-07-07 07:10:49.000000000 +0200
+++ python-django-1.7.11/debian/patches/session-1.7.x.diff	1970-01-01 01:00:00.000000000 +0100
@@ -1,155 +0,0 @@
-commit ac4a54705fb9cdde832d07667843b45b208f9aad
-Author: Carl Meyer <carl at oddbird.net>
-Date:   Wed Jun 10 15:45:20 2015 -0600
-
-    [1.7.x] Fixed #19324 -- Avoided creating a session record when loading the session.
-    
-    The session record is now only created if/when the session is modified. This
-    prevents a potential DoS via creation of many empty session records.
-    
-    This is a security fix; disclosure to follow shortly.
-
-Index: python-django-1.7.7/django/contrib/sessions/backends/cache.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/cache.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/cache.py
-@@ -27,7 +27,7 @@ class SessionStore(SessionBase):
-             session_data = None
-         if session_data is not None:
-             return session_data
--        self.create()
-+        self._session_key = None
-         return {}
- 
-     def create(self):
-@@ -49,6 +49,8 @@ class SessionStore(SessionBase):
-             "It is likely that the cache is unavailable.")
- 
-     def save(self, must_create=False):
-+        if self.session_key is None:
-+            return self.create()
-         if must_create:
-             func = self._cache.add
-         else:
-@@ -60,7 +62,7 @@ class SessionStore(SessionBase):
-             raise CreateError
- 
-     def exists(self, session_key):
--        return (KEY_PREFIX + session_key) in self._cache
-+        return session_key and (KEY_PREFIX + session_key) in self._cache
- 
-     def delete(self, session_key=None):
-         if session_key is None:
-Index: python-django-1.7.7/django/contrib/sessions/backends/cached_db.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/cached_db.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/cached_db.py
-@@ -51,12 +51,12 @@ class SessionStore(DBStore):
-                     logger = logging.getLogger('django.security.%s' %
-                             e.__class__.__name__)
-                     logger.warning(force_text(e))
--                self.create()
-+                self._session_key = None
-                 data = {}
-         return data
- 
-     def exists(self, session_key):
--        if (KEY_PREFIX + session_key) in self._cache:
-+        if session_key and (KEY_PREFIX + session_key) in self._cache:
-             return True
-         return super(SessionStore, self).exists(session_key)
- 
-Index: python-django-1.7.7/django/contrib/sessions/backends/db.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/db.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/db.py
-@@ -26,7 +26,7 @@ class SessionStore(SessionBase):
-                 logger = logging.getLogger('django.security.%s' %
-                         e.__class__.__name__)
-                 logger.warning(force_text(e))
--            self.create()
-+            self._session_key = None
-             return {}
- 
-     def exists(self, session_key):
-@@ -43,7 +43,6 @@ class SessionStore(SessionBase):
-                 # Key wasn't unique. Try again.
-                 continue
-             self.modified = True
--            self._session_cache = {}
-             return
- 
-     def save(self, must_create=False):
-@@ -53,6 +52,8 @@ class SessionStore(SessionBase):
-         create a *new* entry (as opposed to possibly updating an existing
-         entry).
-         """
-+        if self.session_key is None:
-+            return self.create()
-         obj = Session(
-             session_key=self._get_or_create_session_key(),
-             session_data=self.encode(self._get_session(no_load=must_create)),
-Index: python-django-1.7.7/django/contrib/sessions/backends/file.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/file.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/file.py
-@@ -96,7 +96,7 @@ class SessionStore(SessionBase):
-                     self.delete()
-                     self.create()
-         except (IOError, SuspiciousOperation):
--            self.create()
-+            self._session_key = None
-         return session_data
- 
-     def create(self):
-@@ -107,10 +107,11 @@ class SessionStore(SessionBase):
-             except CreateError:
-                 continue
-             self.modified = True
--            self._session_cache = {}
-             return
- 
-     def save(self, must_create=False):
-+        if self.session_key is None:
-+            return self.create()
-         # Get the session data now, before we start messing
-         # with the file it is stored within.
-         session_data = self._get_session(no_load=must_create)
-Index: python-django-1.7.7/django/contrib/sessions/tests.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/tests.py
-+++ python-django-1.7.7/django/contrib/sessions/tests.py
-@@ -171,6 +171,11 @@ class SessionTestsMixin(object):
-         self.assertNotEqual(self.session.session_key, prev_key)
-         self.assertEqual(list(self.session.items()), prev_data)
- 
-+    def test_save_doesnt_clear_data(self):
-+        self.session['a'] = 'b'
-+        self.session.save()
-+        self.assertEqual(self.session['a'], 'b')
-+
-     def test_invalid_key(self):
-         # Submitting an invalid session key (either by guessing, or if the db has
-         # removed the key) results in a new key being generated.
-@@ -306,6 +311,21 @@ class SessionTestsMixin(object):
-                 self.session.delete(old_session_key)
-                 self.session.delete(new_session_key)
- 
-+    def test_session_load_does_not_create_record(self):
-+        """
-+        Loading an unknown session key does not create a session record.
-+
-+        Creating session records on load is a DOS vulnerability.
-+        """
-+        if self.backend is CookieSession:
-+            raise unittest.SkipTest("Cookie backend doesn't have an external store to create records in.")
-+        session = self.backend('someunknownkey')
-+        session.load()
-+
-+        self.assertFalse(session.exists(session.session_key))
-+        # provided unknown key was cycled, not reused
-+        self.assertNotEqual(session.session_key, 'someunknownkey')
-+
- 
- class DatabaseSessionTests(SessionTestsMixin, TestCase):
- 
diff -Nru python-django-1.7.7/debian/patches/session-store-1.7.x.diff python-django-1.7.11/debian/patches/session-store-1.7.x.diff
--- python-django-1.7.7/debian/patches/session-store-1.7.x.diff	2015-08-18 06:50:57.000000000 +0200
+++ python-django-1.7.11/debian/patches/session-store-1.7.x.diff	1970-01-01 01:00:00.000000000 +0100
@@ -1,246 +0,0 @@
-commit 26bc57fa31071144c7adcb765b5dac7d0a3c25de
-Author: Tim Graham <timograham at gmail.com>
-Date:   Wed Aug 5 17:44:48 2015 -0400
-
-    [1.7.x] Fixed DoS possiblity in contrib.auth.views.logout()
-    
-    Refs #20936 -- When logging out/ending a session, don't create a new, empty session.
-    
-    Previously, when logging out, the existing session was overwritten by a
-    new sessionid instead of deleting the session altogether.
-    
-    This behavior added overhead by creating a new session record in
-    whichever backend was in use: db, cache, etc.
-    
-    This extra session is unnecessary at the time since no session data is
-    meant to be preserved when explicitly logging out.
-    
-    Backport of 393c0e24223c701edeb8ce7dc9d0f852f0c081ad,
-    088579638b160f3716dc81d194be70c72743593f, and
-    2dee853ed4def42b7ef1b3b472b395055543cc00 from master
-    
-    Thanks Florian Apolloner and Carl Meyer for review.
-    
-    This is a security fix.
-
-Index: python-django-1.7.7/django/contrib/sessions/backends/base.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/base.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/base.py
-@@ -142,6 +142,13 @@ class SessionBase(object):
-         self.accessed = True
-         self.modified = True
- 
-+    def is_empty(self):
-+        "Returns True when there is no session_key and the session is empty"
-+        try:
-+            return not bool(self._session_key) and not self._session_cache
-+        except AttributeError:
-+            return True
-+
-     def _get_new_session_key(self):
-         "Returns session key that isn't being used."
-         while True:
-@@ -268,7 +275,7 @@ class SessionBase(object):
-         """
-         self.clear()
-         self.delete()
--        self.create()
-+        self._session_key = None
- 
-     def cycle_key(self):
-         """
-Index: python-django-1.7.7/django/contrib/sessions/backends/cached_db.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/backends/cached_db.py
-+++ python-django-1.7.7/django/contrib/sessions/backends/cached_db.py
-@@ -79,7 +79,7 @@ class SessionStore(DBStore):
-         """
-         self.clear()
-         self.delete(self.session_key)
--        self.create()
-+        self._session_key = None
- 
- 
- # At bottom to avoid circular import
-Index: python-django-1.7.7/django/contrib/sessions/middleware.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/middleware.py
-+++ python-django-1.7.7/django/contrib/sessions/middleware.py
-@@ -18,32 +18,40 @@ class SessionMiddleware(object):
-     def process_response(self, request, response):
-         """
-         If request.session was modified, or if the configuration is to save the
--        session every time, save the changes and set a session cookie.
-+        session every time, save the changes and set a session cookie or delete
-+        the session cookie if the session has been emptied.
-         """
-         try:
-             accessed = request.session.accessed
-             modified = request.session.modified
-+            empty = request.session.is_empty()
-         except AttributeError:
-             pass
-         else:
--            if accessed:
--                patch_vary_headers(response, ('Cookie',))
--            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
--                if request.session.get_expire_at_browser_close():
--                    max_age = None
--                    expires = None
--                else:
--                    max_age = request.session.get_expiry_age()
--                    expires_time = time.time() + max_age
--                    expires = cookie_date(expires_time)
--                # Save the session data and refresh the client cookie.
--                # Skip session save for 500 responses, refs #3881.
--                if response.status_code != 500:
--                    request.session.save()
--                    response.set_cookie(settings.SESSION_COOKIE_NAME,
--                            request.session.session_key, max_age=max_age,
--                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
--                            path=settings.SESSION_COOKIE_PATH,
--                            secure=settings.SESSION_COOKIE_SECURE or None,
--                            httponly=settings.SESSION_COOKIE_HTTPONLY or None)
-+            # First check if we need to delete this cookie.
-+            # The session should be deleted only if the session is entirely empty
-+            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
-+                response.delete_cookie(settings.SESSION_COOKIE_NAME,
-+                    domain=settings.SESSION_COOKIE_DOMAIN)
-+            else:
-+                if accessed:
-+                    patch_vary_headers(response, ('Cookie',))
-+                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
-+                    if request.session.get_expire_at_browser_close():
-+                        max_age = None
-+                        expires = None
-+                    else:
-+                        max_age = request.session.get_expiry_age()
-+                        expires_time = time.time() + max_age
-+                        expires = cookie_date(expires_time)
-+                    # Save the session data and refresh the client cookie.
-+                    # Skip session save for 500 responses, refs #3881.
-+                    if response.status_code != 500:
-+                        request.session.save()
-+                        response.set_cookie(settings.SESSION_COOKIE_NAME,
-+                                request.session.session_key, max_age=max_age,
-+                                expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
-+                                path=settings.SESSION_COOKIE_PATH,
-+                                secure=settings.SESSION_COOKIE_SECURE or None,
-+                                httponly=settings.SESSION_COOKIE_HTTPONLY or None)
-         return response
-Index: python-django-1.7.7/django/contrib/sessions/tests.py
-===================================================================
---- python-django-1.7.7.orig/django/contrib/sessions/tests.py
-+++ python-django-1.7.7/django/contrib/sessions/tests.py
-@@ -159,6 +159,7 @@ class SessionTestsMixin(object):
-         self.session.flush()
-         self.assertFalse(self.session.exists(prev_key))
-         self.assertNotEqual(self.session.session_key, prev_key)
-+        self.assertIsNone(self.session.session_key)
-         self.assertTrue(self.session.modified)
-         self.assertTrue(self.session.accessed)
- 
-@@ -589,6 +590,75 @@ class SessionMiddlewareTests(unittest.Te
-         # Check that the value wasn't saved above.
-         self.assertNotIn('hello', request.session.load())
- 
-+    def test_session_delete_on_end(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Before deleting, there has to be an existing cookie
-+        request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # Check that the cookie was deleted, not recreated.
-+        # A deleted cookie header looks like:
-+        #  Set-Cookie: sessionid=; expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
-+        self.assertEqual(
-+            'Set-Cookie: {0}=; expires=Thu, 01-Jan-1970 00:00:00 GMT; '
-+            'Max-Age=0; Path=/'.format(settings.SESSION_COOKIE_NAME),
-+            str(response.cookies[settings.SESSION_COOKIE_NAME])
-+        )
-+
-+    @override_settings(SESSION_COOKIE_DOMAIN='.example.local')
-+    def test_session_delete_on_end_with_custom_domain(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Before deleting, there has to be an existing cookie
-+        request.COOKIES[settings.SESSION_COOKIE_NAME] = 'abc'
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # Check that the cookie was deleted, not recreated.
-+        # A deleted cookie header with a custom domain looks like:
-+        #  Set-Cookie: sessionid=; Domain=.example.local;
-+        #              expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/
-+        self.assertEqual(
-+            'Set-Cookie: {}=; Domain=.example.local; expires=Thu, '
-+            '01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/'.format(
-+                settings.SESSION_COOKIE_NAME,
-+            ),
-+            str(response.cookies[settings.SESSION_COOKIE_NAME])
-+        )
-+
-+    def test_flush_empty_without_session_cookie_doesnt_set_cookie(self):
-+        request = RequestFactory().get('/')
-+        response = HttpResponse('Session test')
-+        middleware = SessionMiddleware()
-+
-+        # Simulate a request that ends the session
-+        middleware.process_request(request)
-+        request.session.flush()
-+
-+        # Handle the response through the middleware
-+        response = middleware.process_response(request, response)
-+
-+        # A cookie should not be set.
-+        self.assertEqual(response.cookies, {})
-+        # The session is accessed so "Vary: Cookie" should be set.
-+        self.assertEqual(response['Vary'], 'Cookie')
-+
- 
- class CookieSessionTests(SessionTestsMixin, TestCase):
- 
-Index: python-django-1.7.7/docs/topics/http/sessions.txt
-===================================================================
---- python-django-1.7.7.orig/docs/topics/http/sessions.txt
-+++ python-django-1.7.7/docs/topics/http/sessions.txt
-@@ -226,12 +226,18 @@ You can edit it multiple times.
- 
-     .. method:: flush()
- 
--      Delete the current session data from the session and regenerate the
--      session key value that is sent back to the user in the cookie. This is
--      used if you want to ensure that the previous session data can't be
--      accessed again from the user's browser (for example, the
-+      Deletes the current session data from the session and deletes the session
-+      cookie. This is used if you want to ensure that the previous session data
-+      can't be accessed again from the user's browser (for example, the
-       :func:`django.contrib.auth.logout()` function calls it).
- 
-+      .. versionchanged:: 1.7.10
-+
-+          Deletion of the session cookie was added. Previously, the behavior
-+          was to regenerate the session key value that was sent back to the
-+          user in the cookie, but this could be a denial-of-service
-+          vulnerability.
-+
-     .. method:: set_test_cookie()
- 
-       Sets a test cookie to determine whether the user's browser supports
diff -Nru python-django-1.7.7/django/apps/registry.py python-django-1.7.11/django/apps/registry.py
--- python-django-1.7.7/django/apps/registry.py	2015-03-19 00:40:06.000000000 +0100
+++ python-django-1.7.11/django/apps/registry.py	2015-11-24 17:55:00.000000000 +0100
@@ -213,7 +213,7 @@
                 warnings.warn(
                     "Model '%s.%s' was already registered. "
                     "Reloading models is not advised as it can lead to inconsistencies, "
-                    "most notably with related models." % (model_name, app_label),
+                    "most notably with related models." % (app_label, model_name),
                     RuntimeWarning, stacklevel=2)
             else:
                 raise RuntimeError(
diff -Nru python-django-1.7.7/django/contrib/sessions/backends/base.py python-django-1.7.11/django/contrib/sessions/backends/base.py
--- python-django-1.7.7/django/contrib/sessions/backends/base.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/backends/base.py	2015-11-24 17:55:00.000000000 +0100
@@ -142,6 +142,13 @@
         self.accessed = True
         self.modified = True
 
+    def is_empty(self):
+        "Returns True when there is no session_key and the session is empty"
+        try:
+            return not bool(self._session_key) and not self._session_cache
+        except AttributeError:
+            return True
+
     def _get_new_session_key(self):
         "Returns session key that isn't being used."
         while True:
@@ -268,7 +275,7 @@
         """
         self.clear()
         self.delete()
-        self.create()
+        self._session_key = None
 
     def cycle_key(self):
         """
diff -Nru python-django-1.7.7/django/contrib/sessions/backends/cached_db.py python-django-1.7.11/django/contrib/sessions/backends/cached_db.py
--- python-django-1.7.7/django/contrib/sessions/backends/cached_db.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/backends/cached_db.py	2015-11-24 17:55:00.000000000 +0100
@@ -51,12 +51,12 @@
                     logger = logging.getLogger('django.security.%s' %
                             e.__class__.__name__)
                     logger.warning(force_text(e))
-                self.create()
+                self._session_key = None
                 data = {}
         return data
 
     def exists(self, session_key):
-        if (KEY_PREFIX + session_key) in self._cache:
+        if session_key and (KEY_PREFIX + session_key) in self._cache:
             return True
         return super(SessionStore, self).exists(session_key)
 
@@ -79,7 +79,7 @@
         """
         self.clear()
         self.delete(self.session_key)
-        self.create()
+        self._session_key = None
 
 
 # At bottom to avoid circular import
diff -Nru python-django-1.7.7/django/contrib/sessions/backends/cache.py python-django-1.7.11/django/contrib/sessions/backends/cache.py
--- python-django-1.7.7/django/contrib/sessions/backends/cache.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/backends/cache.py	2015-11-24 17:55:00.000000000 +0100
@@ -27,7 +27,7 @@
             session_data = None
         if session_data is not None:
             return session_data
-        self.create()
+        self._session_key = None
         return {}
 
     def create(self):
@@ -49,6 +49,8 @@
             "It is likely that the cache is unavailable.")
 
     def save(self, must_create=False):
+        if self.session_key is None:
+            return self.create()
         if must_create:
             func = self._cache.add
         else:
@@ -60,7 +62,7 @@
             raise CreateError
 
     def exists(self, session_key):
-        return (KEY_PREFIX + session_key) in self._cache
+        return session_key and (KEY_PREFIX + session_key) in self._cache
 
     def delete(self, session_key=None):
         if session_key is None:
diff -Nru python-django-1.7.7/django/contrib/sessions/backends/db.py python-django-1.7.11/django/contrib/sessions/backends/db.py
--- python-django-1.7.7/django/contrib/sessions/backends/db.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/backends/db.py	2015-11-24 17:55:00.000000000 +0100
@@ -26,7 +26,7 @@
                 logger = logging.getLogger('django.security.%s' %
                         e.__class__.__name__)
                 logger.warning(force_text(e))
-            self.create()
+            self._session_key = None
             return {}
 
     def exists(self, session_key):
@@ -43,7 +43,6 @@
                 # Key wasn't unique. Try again.
                 continue
             self.modified = True
-            self._session_cache = {}
             return
 
     def save(self, must_create=False):
@@ -53,6 +52,8 @@
         create a *new* entry (as opposed to possibly updating an existing
         entry).
         """
+        if self.session_key is None:
+            return self.create()
         obj = Session(
             session_key=self._get_or_create_session_key(),
             session_data=self.encode(self._get_session(no_load=must_create)),
diff -Nru python-django-1.7.7/django/contrib/sessions/backends/file.py python-django-1.7.11/django/contrib/sessions/backends/file.py
--- python-django-1.7.7/django/contrib/sessions/backends/file.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/backends/file.py	2015-11-24 17:55:00.000000000 +0100
@@ -96,7 +96,7 @@
                     self.delete()
                     self.create()
         except (IOError, SuspiciousOperation):
-            self.create()
+            self._session_key = None
         return session_data
 
     def create(self):
@@ -107,10 +107,11 @@
             except CreateError:
                 continue
             self.modified = True
-            self._session_cache = {}
             return
 
     def save(self, must_create=False):
+        if self.session_key is None:
+            return self.create()
         # Get the session data now, before we start messing
         # with the file it is stored within.
         session_data = self._get_session(no_load=must_create)
diff -Nru python-django-1.7.7/django/contrib/sessions/middleware.py python-django-1.7.11/django/contrib/sessions/middleware.py
--- python-django-1.7.7/django/contrib/sessions/middleware.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/contrib/sessions/middleware.py	2015-11-24 17:55:00.000000000 +0100
@@ -18,32 +18,40 @@
     def process_response(self, request, response):
         """
         If request.session was modified, or if the configuration is to save the
-        session every time, save the changes and set a session cookie.
+        session every time, save the changes and set a session cookie or delete
+        the session cookie if the session has been emptied.
         """
         try:
             accessed = request.session.accessed
             modified = request.session.modified
+            empty = request.session.is_empty()
         except AttributeError:
             pass
         else:
-            if accessed:
-                patch_vary_headers(response, ('Cookie',))
-            if modified or settings.SESSION_SAVE_EVERY_REQUEST:
-                if request.session.get_expire_at_browser_close():
-                    max_age = None
-                    expires = None
-                else:
-                    max_age = request.session.get_expiry_age()
-                    expires_time = time.time() + max_age
-                    expires = cookie_date(expires_time)
-                # Save the session data and refresh the client cookie.
-                # Skip session save for 500 responses, refs #3881.
-                if response.status_code != 500:
-                    request.session.save()
-                    response.set_cookie(settings.SESSION_COOKIE_NAME,
-                            request.session.session_key, max_age=max_age,
-                            expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
-                            path=settings.SESSION_COOKIE_PATH,
-                            secure=settings.SESSION_COOKIE_SECURE or None,
-                            httponly=settings.SESSION_COOKIE_HTTPONLY or None)
+            # First check if we need to delete this cookie.
+            # The session should be deleted only if the session is entirely empty
+            if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
+                response.delete_cookie(settings.SESSION_COOKIE_NAME,
+                    domain=settings.SESSION_COOKIE_DOMAIN)
+            else:
+                if accessed:
+                    patch_vary_headers(response, ('Cookie',))
+                if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
+                    if request.session.get_expire_at_browser_close():
+                        max_age = None
+                        expires = None
+                    else:
+                        max_age = request.session.get_expiry_age()
+                        expires_time = time.time() + max_age
+                        expires = cookie_date(expires_time)
+                    # Save the session data and refresh the client cookie.
+                    # Skip session save for 500 responses, refs #3881.
+                    if response.status_code != 500:
+                        request.session.save()
+                        response.set_cookie(settings.SESSION_COOKIE_NAME,
+                                request.session.session_key, max_age=max_age,
+                                expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
+                                path=settings.SESSION_COOKIE_PATH,
+                                secure=settings.SESSION_COOKIE_SECURE or None,
+                                httponly=settings.SESSION_COOKIE_HTTPONLY or None)
         return response
diff -Nru python-django-1.7.7/django/core/validators.py python-django-1.7.11/django/core/validators.py
--- python-django-1.7.7/django/core/validators.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/core/validators.py	2015-11-24 17:55:00.000000000 +0100
@@ -73,7 +73,7 @@
         r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|'  # ...or ipv4
         r'\[?[A-F0-9]*:[A-F0-9:]+\]?)'  # ...or ipv6
         r'(?::\d+)?'  # optional port
-        r'(?:/?|[/?]\S+)$', re.IGNORECASE)
+        r'(?:/?|[/?]\S+)\Z', re.IGNORECASE)
     message = _('Enter a valid URL.')
     schemes = ['http', 'https', 'ftp', 'ftps']
 
@@ -107,12 +107,15 @@
         else:
             url = value
 
+integer_validator = RegexValidator(
+    re.compile('^-?\d+\Z'),
+    message=_('Enter a valid integer.'),
+    code='invalid',
+)
+
 
 def validate_integer(value):
-    try:
-        int(value)
-    except (ValueError, TypeError):
-        raise ValidationError(_('Enter a valid integer.'), code='invalid')
+    return integer_validator(value)
 
 
 @deconstructible
@@ -120,15 +123,15 @@
     message = _('Enter a valid email address.')
     code = 'invalid'
     user_regex = re.compile(
-        r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$"  # dot-atom
-        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)',  # quoted-string
+        r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"  # dot-atom
+        r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)',  # quoted-string
         re.IGNORECASE)
     domain_regex = re.compile(
-        r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))$',
+        r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,}(?<!-))\Z',
         re.IGNORECASE)
     literal_regex = re.compile(
         # literal form, ipv4 or ipv6 address (SMTP 4.1.3)
-        r'\[([A-f0-9:\.]+)\]$',
+        r'\[([A-f0-9:\.]+)\]\Z',
         re.IGNORECASE)
     domain_whitelist = ['localhost']
 
@@ -181,10 +184,10 @@
 
 validate_email = EmailValidator()
 
-slug_re = re.compile(r'^[-a-zA-Z0-9_]+$')
+slug_re = re.compile(r'^[-a-zA-Z0-9_]+\Z')
 validate_slug = RegexValidator(slug_re, _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."), 'invalid')
 
-ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}$')
+ipv4_re = re.compile(r'^(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\Z')
 validate_ipv4_address = RegexValidator(ipv4_re, _('Enter a valid IPv4 address.'), 'invalid')
 
 
@@ -225,7 +228,7 @@
         raise ValueError("The protocol '%s' is unknown. Supported: %s"
                          % (protocol, list(ip_address_validator_map)))
 
-comma_separated_int_list_re = re.compile('^[\d,]+$')
+comma_separated_int_list_re = re.compile('^[\d,]+\Z')
 validate_comma_separated_integer_list = RegexValidator(comma_separated_int_list_re, _('Enter only digits separated by commas.'), 'invalid')
 
 
diff -Nru python-django-1.7.7/django/db/backends/mysql/schema.py python-django-1.7.11/django/db/backends/mysql/schema.py
--- python-django-1.7.7/django/db/backends/mysql/schema.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/backends/mysql/schema.py	2015-11-24 17:55:00.000000000 +0100
@@ -48,3 +48,22 @@
                 'table': self.quote_name(model._meta.db_table),
                 'column': self.quote_name(field.column),
             }, [effective_default])
+
+    def _set_field_new_type_null_status(self, field, new_type):
+        """
+        Keep the null property of the old field. If it has changed, it will be
+        handled separately.
+        """
+        if field.null:
+            new_type += " NULL"
+        else:
+            new_type += " NOT NULL"
+        return new_type
+
+    def _alter_column_type_sql(self, table, old_field, new_field, new_type):
+        new_type = self._set_field_new_type_null_status(old_field, new_type)
+        return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, old_field, new_field, new_type)
+
+    def _rename_field_sql(self, table, old_field, new_field, new_type):
+        new_type = self._set_field_new_type_null_status(old_field, new_type)
+        return super(DatabaseSchemaEditor, self)._rename_field_sql(table, old_field, new_field, new_type)
diff -Nru python-django-1.7.7/django/db/backends/postgresql_psycopg2/schema.py python-django-1.7.11/django/db/backends/postgresql_psycopg2/schema.py
--- python-django-1.7.7/django/db/backends/postgresql_psycopg2/schema.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/backends/postgresql_psycopg2/schema.py	2015-11-24 17:55:00.000000000 +0100
@@ -34,11 +34,12 @@
                         model, [field], suffix='_like', sql=self.sql_create_text_index))
         return output
 
-    def _alter_column_type_sql(self, table, column, type):
+    def _alter_column_type_sql(self, table, old_field, new_field, new_type):
         """
         Makes ALTER TYPE with SERIAL make sense.
         """
-        if type.lower() == "serial":
+        if new_type.lower() == "serial":
+            column = new_field.column
             sequence_name = "%s_%s_seq" % (table, column)
             return (
                 (
@@ -82,4 +83,6 @@
                 ],
             )
         else:
-            return super(DatabaseSchemaEditor, self)._alter_column_type_sql(table, column, type)
+            return super(DatabaseSchemaEditor, self)._alter_column_type_sql(
+                table, old_field, new_field, new_type
+            )
diff -Nru python-django-1.7.7/django/db/backends/schema.py python-django-1.7.11/django/db/backends/schema.py
--- python-django-1.7.7/django/db/backends/schema.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/backends/schema.py	2015-11-24 17:55:00.000000000 +0100
@@ -13,6 +13,14 @@
 logger = getLogger('django.db.backends.schema')
 
 
+def _related_objects(old_field, new_field):
+    # Returns (old_relation, new_relation) tuples.
+    return zip(
+        old_field.model._meta.get_all_related_objects(),
+        new_field.model._meta.get_all_related_objects()
+    )
+
+
 class BaseDatabaseSchemaEditor(object):
     """
     This class (and its subclasses) are responsible for emitting schema-changing
@@ -438,12 +446,17 @@
         old_type = old_db_params['type']
         new_db_params = new_field.db_parameters(connection=self.connection)
         new_type = new_db_params['type']
-        if (old_type is None and old_field.rel is None) or (new_type is None and new_field.rel is None):
-            raise ValueError("Cannot alter field %s into %s - they do not properly define db_type (are you using PostGIS 1.5 or badly-written custom fields?)" % (
-                old_field,
-                new_field,
-            ))
-        elif old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and old_field.rel.through._meta.auto_created and new_field.rel.through._meta.auto_created):
+        if ((old_type is None and old_field.rel is None) or
+                (new_type is None and new_field.rel is None)):
+            raise ValueError(
+                "Cannot alter field %s into %s - they do not properly define "
+                "db_type (are you using PostGIS 1.5 or badly-written custom "
+                "fields?)" % (old_field, new_field)
+            )
+        elif old_type is None and new_type is None and (
+                old_field.rel.through and new_field.rel.through and
+                old_field.rel.through._meta.auto_created and
+                new_field.rel.through._meta.auto_created):
             return self._alter_many_to_many(model, old_field, new_field, strict)
         elif old_type is None and new_type is None and (old_field.rel.through and new_field.rel.through and not old_field.rel.through._meta.auto_created and not new_field.rel.through._meta.auto_created):
             # Both sides have through models; this is a no-op.
@@ -487,10 +500,12 @@
         # Drop incoming FK constraints if we're a primary key and things are going
         # to change.
         if old_field.primary_key and new_field.primary_key and old_type != new_type:
-            for rel in new_field.model._meta.get_all_related_objects():
-                rel_fk_names = self._constraint_names(rel.model, [rel.field.column], foreign_key=True)
+            for _old_rel, new_rel in _related_objects(old_field, new_field):
+                rel_fk_names = self._constraint_names(
+                    new_rel.model, [new_rel.field.column], foreign_key=True
+                )
                 for fk_name in rel_fk_names:
-                    self.execute(self._delete_constraint_sql(self.sql_delete_fk, rel.model, fk_name))
+                    self.execute(self._delete_constraint_sql(self.sql_delete_fk, new_rel.model, fk_name))
         # Removed an index? (no strict check, as multiple indexes are possible)
         if (old_field.db_index and not new_field.db_index and
                 not old_field.unique and not
@@ -512,19 +527,16 @@
                 self.execute(self._delete_constraint_sql(self.sql_delete_check, model, constraint_name))
         # Have they renamed the column?
         if old_field.column != new_field.column:
-            self.execute(self.sql_rename_column % {
-                "table": self.quote_name(model._meta.db_table),
-                "old_column": self.quote_name(old_field.column),
-                "new_column": self.quote_name(new_field.column),
-                "type": new_type,
-            })
+            self.execute(self._rename_field_sql(model._meta.db_table, old_field, new_field, new_type))
         # Next, start accumulating actions to do
         actions = []
         null_actions = []
         post_actions = []
         # Type change?
         if old_type != new_type:
-            fragment, other_actions = self._alter_column_type_sql(model._meta.db_table, new_field.column, new_type)
+            fragment, other_actions = self._alter_column_type_sql(
+                model._meta.db_table, old_field, new_field, new_type
+            )
             actions.append(fragment)
             post_actions.extend(other_actions)
         # When changing a column NULL constraint to NOT NULL with a given
@@ -637,7 +649,7 @@
         # referring to us.
         rels_to_update = []
         if old_field.primary_key and new_field.primary_key and old_type != new_type:
-            rels_to_update.extend(new_field.model._meta.get_all_related_objects())
+            rels_to_update.extend(_related_objects(old_field, new_field))
         # Changed to become primary key?
         # Note that we don't detect unsetting of a PK, as we assume another field
         # will always come along and replace it.
@@ -660,20 +672,23 @@
                 }
             )
             # Update all referencing columns
-            rels_to_update.extend(new_field.model._meta.get_all_related_objects())
+            rels_to_update.extend(_related_objects(old_field, new_field))
         # Handle our type alters on the other end of rels from the PK stuff above
-        for rel in rels_to_update:
-            rel_db_params = rel.field.db_parameters(connection=self.connection)
+        for old_rel, new_rel in rels_to_update:
+            rel_db_params = new_rel.field.db_parameters(connection=self.connection)
             rel_type = rel_db_params['type']
+            fragment, other_actions = self._alter_column_type_sql(
+                new_rel.model._meta.db_table, old_rel.field, new_rel.field, rel_type
+            )
             self.execute(
                 self.sql_alter_column % {
-                    "table": self.quote_name(rel.model._meta.db_table),
-                    "changes": self.sql_alter_column_type % {
-                        "column": self.quote_name(rel.field.column),
-                        "type": rel_type,
-                    }
-                }
+                    "table": self.quote_name(new_rel.model._meta.db_table),
+                    "changes": fragment[0],
+                },
+                fragment[1],
             )
+            for sql, params in other_actions:
+                self.execute(sql, params)
         # Does it have a foreign key?
         if (new_field.rel and
                 (fks_dropped or not old_field.rel or not old_field.db_constraint) and
@@ -707,7 +722,7 @@
         if self.connection.features.connection_persists_old_columns:
             self.connection.close()
 
-    def _alter_column_type_sql(self, table, column, type):
+    def _alter_column_type_sql(self, table, old_field, new_field, new_type):
         """
         Hook to specialize column type alteration for different backends,
         for cases when a creation type is different to an alteration type
@@ -720,8 +735,8 @@
         return (
             (
                 self.sql_alter_column_type % {
-                    "column": self.quote_name(column),
-                    "type": type,
+                    "column": self.quote_name(new_field.column),
+                    "type": new_type,
                 },
                 [],
             ),
@@ -821,6 +836,14 @@
             output.append(self._create_index_sql(model, fields, suffix="_idx"))
         return output
 
+    def _rename_field_sql(self, table, old_field, new_field, new_type):
+        return self.sql_rename_column % {
+            "table": self.quote_name(table),
+            "old_column": self.quote_name(old_field.column),
+            "new_column": self.quote_name(new_field.column),
+            "type": new_type,
+        }
+
     def _create_fk_sql(self, model, field, suffix):
         from_table = model._meta.db_table
         from_column = field.column
diff -Nru python-django-1.7.7/django/db/backends/sqlite3/introspection.py python-django-1.7.11/django/db/backends/sqlite3/introspection.py
--- python-django-1.7.7/django/db/backends/sqlite3/introspection.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/backends/sqlite3/introspection.py	2015-11-24 17:55:00.000000000 +0100
@@ -201,7 +201,10 @@
         constraints = {}
         # Get the index info
         cursor.execute("PRAGMA index_list(%s)" % self.connection.ops.quote_name(table_name))
-        for number, index, unique in cursor.fetchall():
+        for row in cursor.fetchall():
+            # Sqlite3 3.8.9+ has 5 columns, however older versions only give 3
+            # columns. Discard last 2 columns if there.
+            number, index, unique = row[:3]
             # Get the index info for that index
             cursor.execute('PRAGMA index_info(%s)' % self.connection.ops.quote_name(index))
             for index_rank, column_rank, column in cursor.fetchall():
diff -Nru python-django-1.7.7/django/db/models/query.py python-django-1.7.11/django/db/models/query.py
--- python-django-1.7.7/django/db/models/query.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/models/query.py	2015-11-24 17:55:00.000000000 +0100
@@ -10,7 +10,7 @@
 from django.core import exceptions
 from django.db import connections, router, transaction, IntegrityError
 from django.db.models.constants import LOOKUP_SEP
-from django.db.models.fields import AutoField, Empty
+from django.db.models.fields import AutoField, Empty, FieldDoesNotExist
 from django.db.models.query_utils import (Q, select_related_descend,
     deferred_class_factory, InvalidQuery)
 from django.db.models.deletion import Collector
@@ -1906,10 +1906,31 @@
         rel_attr_val = rel_obj_attr(rel_obj)
         rel_obj_cache.setdefault(rel_attr_val, []).append(rel_obj)
 
+    to_attr, as_attr = lookup.get_current_to_attr(level)
+    # Make sure `to_attr` does not conflict with a field.
+    if as_attr and instances:
+        # We assume that objects retrieved are homogeneous (which is the premise
+        # of prefetch_related), so what applies to first object applies to all.
+        model = instances[0].__class__
+        opts = model._meta
+        conflicts = False
+        try:
+            opts.get_field(to_attr)
+        except FieldDoesNotExist:
+            for related_m2m in opts.get_all_related_many_to_many_objects():
+                if related_m2m.get_accessor_name() == to_attr:
+                    conflicts = True
+                    break
+        else:
+            conflicts = True
+        if conflicts:
+            msg = 'to_attr={} conflicts with a field on the {} model.'
+            raise ValueError(msg.format(to_attr, model.__name__))
+
     for obj in instances:
         instance_attr_val = instance_attr(obj)
         vals = rel_obj_cache.get(instance_attr_val, [])
-        to_attr, as_attr = lookup.get_current_to_attr(level)
+
         if single:
             val = vals[0] if vals else None
             to_attr = to_attr if as_attr else cache_name
diff -Nru python-django-1.7.7/django/db/models/sql/compiler.py python-django-1.7.11/django/db/models/sql/compiler.py
--- python-django-1.7.7/django/db/models/sql/compiler.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/db/models/sql/compiler.py	2015-11-24 17:55:00.000000000 +0100
@@ -56,7 +56,8 @@
         if name in self.quote_cache:
             return self.quote_cache[name]
         if ((name in self.query.alias_map and name not in self.query.table_map) or
-                name in self.query.extra_select or name in self.query.external_aliases):
+                name in self.query.extra_select or (
+                    name in self.query.external_aliases and name not in self.query.table_map)):
             self.quote_cache[name] = name
             return name
         r = self.connection.ops.quote_name(name)
diff -Nru python-django-1.7.7/django/__init__.py python-django-1.7.11/django/__init__.py
--- python-django-1.7.7/django/__init__.py	2015-03-19 00:40:21.000000000 +0100
+++ python-django-1.7.11/django/__init__.py	2015-11-24 18:08:13.000000000 +0100
@@ -1,4 +1,4 @@
-VERSION = (1, 7, 7, 'final', 0)
+VERSION = (1, 7, 11, 'final', 0)
 
 
 def get_version(*args, **kwargs):
diff -Nru python-django-1.7.7/django/test/testcases.py python-django-1.7.11/django/test/testcases.py
--- python-django-1.7.7/django/test/testcases.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/test/testcases.py	2015-11-24 17:55:00.000000000 +0100
@@ -558,8 +558,7 @@
             msg_prefix + "Template '%s' was used unexpectedly in rendering"
             " the response" % template_name)
 
-    def assertRaisesMessage(self, expected_exception, expected_message,
-                           callable_obj=None, *args, **kwargs):
+    def assertRaisesMessage(self, expected_exception, expected_message, *args, **kwargs):
         """
         Asserts that the message in a raised exception matches the passed
         value.
@@ -567,12 +566,17 @@
         Args:
             expected_exception: Exception class expected to be raised.
             expected_message: expected error message string value.
-            callable_obj: Function to be called.
-            args: Extra args.
+            args: Function to be called and extra positional args.
             kwargs: Extra kwargs.
         """
+        # callable_obj was a documented kwarg in older version of Django, but
+        # had to be removed due to a bad fix for http://bugs.python.org/issue24134
+        # inadvertently making its way into Python 2.7.10.
+        callable_obj = kwargs.pop('callable_obj', None)
+        if callable_obj:
+            args = (callable_obj,) + args
         return six.assertRaisesRegex(self, expected_exception,
-                re.escape(expected_message), callable_obj, *args, **kwargs)
+                re.escape(expected_message), *args, **kwargs)
 
     def assertFieldOutput(self, fieldclass, valid, invalid, field_args=None,
             field_kwargs=None, empty_value=''):
diff -Nru python-django-1.7.7/django/utils/formats.py python-django-1.7.11/django/utils/formats.py
--- python-django-1.7.7/django/utils/formats.py	2015-03-19 00:40:07.000000000 +0100
+++ python-django-1.7.11/django/utils/formats.py	2015-11-24 18:08:13.000000000 +0100
@@ -31,6 +31,24 @@
 }
 
 
+FORMAT_SETTINGS = frozenset([
+    'DECIMAL_SEPARATOR',
+    'THOUSAND_SEPARATOR',
+    'NUMBER_GROUPING',
+    'FIRST_DAY_OF_WEEK',
+    'MONTH_DAY_FORMAT',
+    'TIME_FORMAT',
+    'DATE_FORMAT',
+    'DATETIME_FORMAT',
+    'SHORT_DATE_FORMAT',
+    'SHORT_DATETIME_FORMAT',
+    'YEAR_MONTH_FORMAT',
+    'DATE_INPUT_FORMATS',
+    'TIME_INPUT_FORMATS',
+    'DATETIME_INPUT_FORMATS',
+])
+
+
 def reset_format_cache():
     """Clear any cached formats.
 
@@ -85,6 +103,8 @@
     be localized (or not), overriding the value of settings.USE_L10N.
     """
     format_type = force_str(format_type)
+    if format_type not in FORMAT_SETTINGS:
+        return format_type
     if use_l10n or (use_l10n is None and settings.USE_L10N):
         if lang is None:
             lang = get_language()
diff -Nru python-django-1.7.7/Django.egg-info/PKG-INFO python-django-1.7.11/Django.egg-info/PKG-INFO
--- python-django-1.7.7/Django.egg-info/PKG-INFO	2015-03-19 00:40:31.000000000 +0100
+++ python-django-1.7.11/Django.egg-info/PKG-INFO	2015-11-24 18:08:16.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Django
-Version: 1.7.7
+Version: 1.7.11
 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
 Home-page: http://www.djangoproject.com/
 Author: Django Software Foundation
diff -Nru python-django-1.7.7/Django.egg-info/SOURCES.txt python-django-1.7.11/Django.egg-info/SOURCES.txt
--- python-django-1.7.7/Django.egg-info/SOURCES.txt	2015-03-19 00:40:32.000000000 +0100
+++ python-django-1.7.11/Django.egg-info/SOURCES.txt	2015-11-24 18:08:17.000000000 +0100
@@ -3987,6 +3987,8 @@
 docs/releases/1.4.19.txt
 docs/releases/1.4.2.txt
 docs/releases/1.4.20.txt
+docs/releases/1.4.21.txt
+docs/releases/1.4.22.txt
 docs/releases/1.4.3.txt
 docs/releases/1.4.4.txt
 docs/releases/1.4.5.txt
@@ -4021,12 +4023,16 @@
 docs/releases/1.6.9.txt
 docs/releases/1.6.txt
 docs/releases/1.7.1.txt
+docs/releases/1.7.10.txt
+docs/releases/1.7.11.txt
 docs/releases/1.7.2.txt
 docs/releases/1.7.3.txt
 docs/releases/1.7.4.txt
 docs/releases/1.7.5.txt
 docs/releases/1.7.6.txt
 docs/releases/1.7.7.txt
+docs/releases/1.7.8.txt
+docs/releases/1.7.9.txt
 docs/releases/1.7.txt
 docs/releases/index.txt
 docs/releases/security.txt
diff -Nru python-django-1.7.7/PKG-INFO python-django-1.7.11/PKG-INFO
--- python-django-1.7.7/PKG-INFO	2015-03-19 00:40:34.000000000 +0100
+++ python-django-1.7.11/PKG-INFO	2015-11-24 18:08:19.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: Django
-Version: 1.7.7
+Version: 1.7.11
 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
 Home-page: http://www.djangoproject.com/
 Author: Django Software Foundation
diff -Nru python-django-1.7.7/setup.cfg python-django-1.7.11/setup.cfg
--- python-django-1.7.7/setup.cfg	2015-03-19 00:40:34.000000000 +0100
+++ python-django-1.7.11/setup.cfg	2015-11-24 18:08:19.000000000 +0100
@@ -14,7 +14,7 @@
 universal = 1
 
 [egg_info]
+tag_svn_revision = 0
 tag_build = 
 tag_date = 0
-tag_svn_revision = 0
 


More information about the Python-modules-team mailing list