diff -Nru python-django-1.6.1/AUTHORS python-django-1.6.11/AUTHORS --- python-django-1.6.1/AUTHORS 2013-12-12 19:37:58.000000000 +0000 +++ python-django-1.6.11/AUTHORS 2015-03-18 23:50:42.000000000 +0000 @@ -60,6 +60,7 @@ Mathieu Agopian Roberto Aguilar ajs + Akis Kesoglou alang@bright-green.com A S Alam Andi Albrecht @@ -396,6 +397,7 @@ Javier Mansilla masonsimon+django@gmail.com Manuzhai + Moayad Mardini Petr Marhoun Petar Marić Nuno Mariz @@ -599,6 +601,7 @@ David Tulig Justine Tunney Amit Upadhyay + valtron Adam Vandenberg Geert Vanderkelen Vasil Vangelovski @@ -657,7 +660,7 @@ Ian Bicking for convincing Adrian to ditch code generation. - Mark Pilgrim for "Dive Into Python" (http://diveintopython.net, + Mark Pilgrim for "Dive Into Python" (http://www.diveintopython.net, http://www.diveintopython3.net). Guido van Rossum for creating Python. diff -Nru python-django-1.6.1/debian/changelog python-django-1.6.11/debian/changelog --- python-django-1.6.1/debian/changelog 2016-03-07 13:50:01.000000000 +0000 +++ python-django-1.6.11/debian/changelog 2019-01-08 19:00:38.000000000 +0000 @@ -1,3 +1,83 @@ +python-django (1.6.11-0ubuntu1.3) trusty-security; urgency=medium + + * SECURITY UPDATE: content spoofing in the default 404 page + - debian/patches/CVE-2019-3498.patch: properly quote string in + django/views/defaults.py. + - CVE-2019-3498 + + -- Marc Deslauriers Tue, 08 Jan 2019 14:00:29 -0500 + +python-django (1.6.11-0ubuntu1.2) trusty-security; urgency=medium + + * SECURITY UPDATE: DoS in urlize and urlizetrunc template filters + - debian/patches/CVE-2018-7536.patch: fix backtracking in + django/utils/html.py, add test to tests/utils_tests/test_html.py. + - CVE-2018-7536 + * SECURITY UPDATE: DoS in truncatechars_html and truncatewords_html + template filters + - debian/patches/CVE-2018-7537.patch: fix backtracking in + django/utils/text.py, add test to tests/utils_tests/test_text.py. + - CVE-2018-7537 + + -- Marc Deslauriers Mon, 05 Mar 2018 15:52:37 +0100 + +python-django (1.6.11-0ubuntu1.1) trusty-security; urgency=medium + + * SECURITY UPDATE: Open redirect and possible XSS attack via + user-supplied numeric redirect URLs + - debian/patches/CVE-2017-7233.patch: fix is_safe_url() with numeric + URLs in django/utils/http.py, added tests to + tests/utils_tests/test_http.py. + - CVE-2017-7233 + * SECURITY UPDATE: Open redirect vulnerability in + django.views.static.serve() + - debian/patches/CVE-2017-7234.patch: remove redirect from + django/views/static.py. + - CVE-2017-7234 + + -- Marc Deslauriers Wed, 29 Mar 2017 07:38:12 -0400 + +python-django (1.6.11-0ubuntu1) trusty; urgency=medium + + * Update to final upstream 1.6 microrelease (LP: #1644346) + * Drop patches included upstream: + - debian/patches/07_translation_encoding_fix.diff, ticket21869.diff, + CVE-2014-0472.patch, CVE-2014-0473.patch, CVE-2014-0474.patch, + CVE-2014-0472-regression.patch, drop_fix_ie_for_vary_1_6.diff, + is_safe_url_1_6.diff, CVE-2014-0480.patch, CVE-2014-0481.patch, + CVE-2014-0482.patch, CVE-2014-0483.patch, CVE-2014-0483-bug23329.patch, + CVE-2014-0483-bug23431.patch, CVE-2015-0219.patch, CVE-2015-0220.patch, + CVE-2015-0221.patch, CVE-2015-0222.patch, CVE-2015-2316.patch, and + CVE-2015-2317.patch + + -- Scott Kitterman Wed, 23 Nov 2016 14:41:31 -0500 + +python-django (1.6.1-2ubuntu0.16) trusty-security; urgency=medium + + * SECURITY UPDATE: user with hardcoded password created when running + tests on Oracle + - debian/patches/CVE-2016-9013.patch: remove hardcoded password in + django/db/backends/oracle/creation.py, added note to + docs/ref/settings.txt. + - CVE-2016-9013 + * SECURITY UPDATE: DNS rebinding vulnerability when DEBUG=True + - debian/patches/CVE-2016-9014.patch: properly check ALLOWED_HOSTS in + django/http/request.py, updated docs/ref/settings.txt, added test to + tests/requests/tests.py. + - CVE-2016-9014 + + -- Marc Deslauriers Mon, 31 Oct 2016 10:14:20 -0400 + +python-django (1.6.1-2ubuntu0.15) trusty-security; urgency=medium + + * SECURITY UPDATE: CSRF protection bypass on a site with Google Analytics + - debian/patches/CVE-2016-7401.patch: simplify cookie parsing in + django/http/cookie.py, add tests to tests/httpwrappers/tests.py, + tests/requests/tests.py. + - CVE-2016-7401 + + -- Marc Deslauriers Mon, 26 Sep 2016 07:36:53 -0400 + python-django (1.6.1-2ubuntu0.14) trusty-security; urgency=medium * SECURITY REGRESSION: is_safe_url() with non-unicode url (LP: #1553251) diff -Nru python-django-1.6.1/debian/patches/02_disable-sources-in-sphinxdoc.diff python-django-1.6.11/debian/patches/02_disable-sources-in-sphinxdoc.diff --- python-django-1.6.1/debian/patches/02_disable-sources-in-sphinxdoc.diff 2012-03-31 11:14:53.000000000 +0000 +++ python-django-1.6.11/debian/patches/02_disable-sources-in-sphinxdoc.diff 2016-11-09 17:07:14.000000000 +0000 @@ -7,9 +7,11 @@ Author: Raphaël Hertzog Origin: vendor ---- a/docs/conf.py -+++ b/docs/conf.py -@@ -168,7 +168,10 @@ html_additional_pages = {} +Index: python-django-1.6.11/docs/conf.py +=================================================================== +--- python-django-1.6.11.orig/docs/conf.py 2016-11-09 12:06:52.320439986 -0500 ++++ python-django-1.6.11/docs/conf.py 2016-11-09 12:06:52.316439985 -0500 +@@ -190,7 +190,10 @@ #html_split_index = False # If true, links to the reST sources are added to the pages. diff -Nru python-django-1.6.1/debian/patches/07_translation_encoding_fix.diff python-django-1.6.11/debian/patches/07_translation_encoding_fix.diff --- python-django-1.6.1/debian/patches/07_translation_encoding_fix.diff 2014-01-29 14:58:20.000000000 +0000 +++ python-django-1.6.11/debian/patches/07_translation_encoding_fix.diff 1970-01-01 00:00:00.000000000 +0000 @@ -1,34 +0,0 @@ -Description: Remove doubly-UTF-8-encoded comment from German translations. - -We instead replace the offending line with the attribution information -currently available in master. - -Author: Luke Faraone -Origin: vendor -Bug: https://code.djangoproject.com/ticket/21601 -Bug-Debian: http://bugs.debian.org/729194 -Forwarded: https://code.djangoproject.com/attachment/ticket/21601/21601.diff -Last-Update: 2013-12-12 - ---- python-django-1.6.1.orig/django/contrib/admindocs/locale/de/LC_MESSAGES/django.po -+++ python-django-1.6.1/django/contrib/admindocs/locale/de/LC_MESSAGES/django.po -@@ -1,7 +1,7 @@ - # This file is distributed under the same license as the Django package. - # - # Translators: --# Jannis Š, 2013 -+# Jannis Leidel , 2013 - msgid "" - msgstr "" - "Project-Id-Version: django-core\n" ---- python-django-1.6.1.orig/django/contrib/auth/locale/de/LC_MESSAGES/django.po -+++ python-django-1.6.1/django/contrib/auth/locale/de/LC_MESSAGES/django.po -@@ -3,7 +3,7 @@ - # Translators: - # André Hagenbruch , 2011 - # apollo13 , 2012 --# Jannis Š, 2013 -+# Jannis Leidel , 2013 - # Markus Holtermann , 2013 - msgid "" - msgstr "" diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0472.patch python-django-1.6.11/debian/patches/CVE-2014-0472.patch --- python-django-1.6.1/debian/patches/CVE-2014-0472.patch 2014-04-19 13:02:01.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0472.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,147 +0,0 @@ -Description: fix unexpected code execution using reverse() -Origin: provided by upstream -Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309779 - -Index: python-django-1.6.1/django/core/urlresolvers.py -=================================================================== ---- python-django-1.6.1.orig/django/core/urlresolvers.py 2014-04-19 08:48:23.470155540 -0400 -+++ python-django-1.6.1/django/core/urlresolvers.py 2014-04-19 08:48:23.462155540 -0400 -@@ -243,6 +243,10 @@ - self._reverse_dict = {} - self._namespace_dict = {} - self._app_dict = {} -+ # set of dotted paths to all functions and classes that are used in -+ # urlpatterns -+ self._callback_strs = set() -+ self._populated = False - - def __repr__(self): - if isinstance(self.urlconf_name, list) and len(self.urlconf_name): -@@ -260,6 +264,15 @@ - apps = {} - language_code = get_language() - for pattern in reversed(self.url_patterns): -+ if hasattr(pattern, '_callback_str'): -+ self._callback_strs.add(pattern._callback_str) -+ elif hasattr(pattern, '_callback'): -+ callback = pattern._callback -+ if not hasattr(callback, '__name__'): -+ lookup_str = callback.__module__ + "." + callback.__class__.__name__ -+ else: -+ lookup_str = callback.__module__ + "." + callback.__name__ -+ self._callback_strs.add(lookup_str) - p_pattern = pattern.regex.pattern - if p_pattern.startswith('^'): - p_pattern = p_pattern[1:] -@@ -280,6 +293,7 @@ - namespaces[namespace] = (p_pattern + prefix, sub_pattern) - for app_name, namespace_list in pattern.app_dict.items(): - apps.setdefault(app_name, []).extend(namespace_list) -+ self._callback_strs.update(pattern._callback_strs) - else: - bits = normalize(p_pattern) - lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args)) -@@ -288,6 +302,7 @@ - self._reverse_dict[language_code] = lookups - self._namespace_dict[language_code] = namespaces - self._app_dict[language_code] = apps -+ self._populated = True - - @property - def reverse_dict(self): -@@ -380,8 +395,12 @@ - text_args = [force_text(v) for v in args] - text_kwargs = dict((k, force_text(v)) for (k, v) in kwargs.items()) - -+ if not self._populated: -+ self._populate() -+ - try: -- lookup_view = get_callable(lookup_view, True) -+ if lookup_view in self._callback_strs: -+ lookup_view = get_callable(lookup_view, True) - except (ImportError, AttributeError) as e: - raise NoReverseMatch("Error importing '%s': %s." % (lookup_view, e)) - possibilities = self.reverse_dict.getlist(lookup_view) -Index: python-django-1.6.1/tests/urlpatterns_reverse/nonimported_module.py -=================================================================== ---- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ python-django-1.6.1/tests/urlpatterns_reverse/nonimported_module.py 2014-04-19 08:48:23.462155540 -0400 -@@ -0,0 +1,3 @@ -+def view(request): -+ """Stub view""" -+ pass -Index: python-django-1.6.1/tests/urlpatterns_reverse/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/urlpatterns_reverse/tests.py 2014-04-19 08:48:23.470155540 -0400 -+++ python-django-1.6.1/tests/urlpatterns_reverse/tests.py 2014-04-19 08:48:23.466155540 -0400 -@@ -1,8 +1,11 @@ -+# -*- coding: utf-8 -*- - """ - Unit tests for reverse URL lookups. - """ - from __future__ import absolute_import, unicode_literals - -+import sys -+ - from django.conf import settings - from django.contrib.auth.models import User - from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist -@@ -313,6 +316,25 @@ - self.assertEqual(res.url, '/foo/') - res = redirect('http://example.com/') - self.assertEqual(res.url, 'http://example.com/') -+ # Assert that we can redirect using UTF-8 strings -+ res = redirect('/æøå/abc/') -+ self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5/abc/') -+ # Assert that no imports are attempted when dealing with a relative path -+ # (previously, the below would resolve in a UnicodeEncodeError from __import__ ) -+ res = redirect('/æøå.abc/') -+ self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5.abc/') -+ res = redirect('os.path') -+ self.assertEqual(res.url, 'os.path') -+ -+ def test_no_illegal_imports(self): -+ # modules that are not listed in urlpatterns should not be importable -+ redirect("urlpatterns_reverse.nonimported_module.view") -+ self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) -+ -+ def test_reverse_by_path_nested(self): -+ # Views that are added to urlpatterns using include() should be -+ # reversable by doted path. -+ self.assertEqual(reverse('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') - - def test_redirect_view_object(self): - from .views import absolute_kwargs_view -@@ -641,4 +663,3 @@ - # swallow it. - self.assertRaises(AttributeError, get_callable, - 'urlpatterns_reverse.views_broken.i_am_broken') -- -Index: python-django-1.6.1/tests/urlpatterns_reverse/urls.py -=================================================================== ---- python-django-1.6.1.orig/tests/urlpatterns_reverse/urls.py 2014-04-19 08:48:23.470155540 -0400 -+++ python-django-1.6.1/tests/urlpatterns_reverse/urls.py 2014-04-19 08:48:23.466155540 -0400 -@@ -7,6 +7,7 @@ - - other_patterns = patterns('', - url(r'non_path_include/$', empty_view, name='non_path_include'), -+ url(r'nested_path/$', 'urlpatterns_reverse.views.nested_view'), - ) - - urlpatterns = patterns('', -Index: python-django-1.6.1/tests/urlpatterns_reverse/views.py -=================================================================== ---- python-django-1.6.1.orig/tests/urlpatterns_reverse/views.py 2014-04-19 08:48:23.470155540 -0400 -+++ python-django-1.6.1/tests/urlpatterns_reverse/views.py 2014-04-19 08:48:23.466155540 -0400 -@@ -16,6 +16,10 @@ - def defaults_view(request, arg1, arg2): - pass - -+def nested_view(request): -+ pass -+ -+ - def erroneous_view(request): - import non_existent - diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0472-regression.patch python-django-1.6.11/debian/patches/CVE-2014-0472-regression.patch --- python-django-1.6.1/debian/patches/CVE-2014-0472-regression.patch 2014-04-23 03:04:57.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0472-regression.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,82 +0,0 @@ -From b63ae5c60619a257ad57cf6043e71f681283e47b Mon Sep 17 00:00:00 2001 -From: Preston Timmons -Date: Tue, 22 Apr 2014 20:19:46 +0000 -Subject: [PATCH] Fixed #22486 -- Reverse raises AttributeError on partial - functions. - -Create the lookup_str from the original function whenever a partial -is provided as an argument to a url pattern. ---- - django/core/urlresolvers.py | 4 ++++ - tests/urlpatterns_reverse/urls.py | 6 +++++- - tests/urlpatterns_reverse/views.py | 10 ++++++++++ - 3 files changed, 19 insertions(+), 1 deletion(-) - -Index: python-django-1.6.1/django/core/urlresolvers.py -=================================================================== ---- python-django-1.6.1.orig/django/core/urlresolvers.py 2014-04-22 23:03:19.520345704 -0400 -+++ python-django-1.6.1/django/core/urlresolvers.py 2014-04-22 23:03:52.492346238 -0400 -@@ -8,6 +8,7 @@ - """ - from __future__ import unicode_literals - -+import functools - import re - from threading import local - -@@ -268,6 +269,9 @@ - self._callback_strs.add(pattern._callback_str) - elif hasattr(pattern, '_callback'): - callback = pattern._callback -+ if isinstance(callback, functools.partial): -+ callback = callback.func -+ - if not hasattr(callback, '__name__'): - lookup_str = callback.__module__ + "." + callback.__class__.__name__ - else: -Index: python-django-1.6.1/tests/urlpatterns_reverse/urls.py -=================================================================== ---- python-django-1.6.1.orig/tests/urlpatterns_reverse/urls.py 2014-04-22 23:03:19.520345704 -0400 -+++ python-django-1.6.1/tests/urlpatterns_reverse/urls.py 2014-04-22 23:04:27.060346799 -0400 -@@ -2,7 +2,7 @@ - - from django.conf.urls import patterns, url, include - --from .views import empty_view, absolute_kwargs_view -+from .views import empty_view, empty_view_partial, empty_view_wrapped, absolute_kwargs_view - - - other_patterns = patterns('', -@@ -53,6 +53,10 @@ - include('urlpatterns_reverse.included_urls')), - url('', include('urlpatterns_reverse.extra_urls')), - -+ # Partials should be fine. -+ url(r'^partial/', empty_view_partial, name="partial"), -+ url(r'^partial_wrapped/', empty_view_wrapped, name="partial_wrapped"), -+ - # This is non-reversible, but we shouldn't blow up when parsing it. - url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"), - -Index: python-django-1.6.1/tests/urlpatterns_reverse/views.py -=================================================================== ---- python-django-1.6.1.orig/tests/urlpatterns_reverse/views.py 2014-04-22 23:03:19.520345704 -0400 -+++ python-django-1.6.1/tests/urlpatterns_reverse/views.py 2014-04-22 23:03:19.512345704 -0400 -@@ -1,3 +1,5 @@ -+from functools import partial, update_wrapper -+ - from django.http import HttpResponse - from django.views.generic import RedirectView - from django.core.urlresolvers import reverse_lazy -@@ -45,3 +47,11 @@ - - def bad_view(request, *args, **kwargs): - raise ValueError("I don't think I'm getting good value for this view") -+ -+ -+empty_view_partial = partial(empty_view, template_name="template.html") -+ -+ -+empty_view_wrapped = update_wrapper( -+ partial(empty_view, template_name="template.html"), empty_view, -+) diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0473.patch python-django-1.6.11/debian/patches/CVE-2014-0473.patch --- python-django-1.6.1/debian/patches/CVE-2014-0473.patch 2014-04-19 13:02:54.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0473.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -Description: fix caching of anonymous pages could reveal CSRF token -Origin: commit 95893e4b4225a7ec8cd4a13fbcc0b329a9517c0d -Author: Aymeric Augustin -Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309782 - -diff --git a/django/middleware/cache.py b/django/middleware/cache.py -index e13a8c3..7bbc167 100644 ---- a/django/middleware/cache.py -+++ b/django/middleware/cache.py -@@ -47,7 +47,8 @@ import warnings - - from django.conf import settings - from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS --from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age -+from django.utils.cache import (get_cache_key, get_max_age, has_vary_header, -+ learn_cache_key, patch_response_headers) - - - class UpdateCacheMiddleware(object): -@@ -90,8 +91,15 @@ class UpdateCacheMiddleware(object): - if not self._should_update_cache(request, response): - # We don't need to update the cache, just return. - return response -+ - if response.streaming or response.status_code != 200: - return response -+ -+ # Don't cache responses that set a user-specific (and maybe security -+ # sensitive) cookie in response to a cookie-less request. -+ if not request.COOKIES and response.cookies and has_vary_header(response, 'Cookie'): -+ return response -+ - # Try to get the timeout from the "max-age" section of the "Cache- - # Control" header before reverting to using the default cache_timeout - # length. -diff --git a/tests/cache/tests.py b/tests/cache/tests.py -index 7413a4a..04ef21b 100644 ---- a/tests/cache/tests.py -+++ b/tests/cache/tests.py -@@ -19,12 +19,14 @@ from django.core import management - from django.core.cache import get_cache - from django.core.cache.backends.base import (CacheKeyWarning, - InvalidCacheBackendError) -+from django.core.context_processors import csrf - from django.db import router, transaction - from django.core.cache.utils import make_template_fragment_key - from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, - QueryDict) - from django.middleware.cache import (FetchFromCacheMiddleware, - UpdateCacheMiddleware, CacheMiddleware) -+from django.middleware.csrf import CsrfViewMiddleware - from django.template import Template - from django.template.response import TemplateResponse - from django.test import TestCase, TransactionTestCase, RequestFactory -@@ -1578,6 +1580,10 @@ def hello_world_view(request, value): - return HttpResponse('Hello World %s' % value) - - -+def csrf_view(request): -+ return HttpResponse(csrf(request)['csrf_token']) -+ -+ - @override_settings( - CACHE_MIDDLEWARE_ALIAS='other', - CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix', -@@ -1797,6 +1803,28 @@ class CacheMiddlewareTest(IgnorePendingDeprecationWarningsMixin, TestCase): - response = other_with_prefix_view(request, '16') - self.assertEqual(response.content, b'Hello World 16') - -+ def test_sensitive_cookie_not_cached(self): -+ """ -+ Django must prevent caching of responses that set a user-specific (and -+ maybe security sensitive) cookie in response to a cookie-less request. -+ """ -+ csrf_middleware = CsrfViewMiddleware() -+ cache_middleware = CacheMiddleware() -+ -+ request = self.factory.get('/view/') -+ self.assertIsNone(cache_middleware.process_request(request)) -+ -+ csrf_middleware.process_view(request, csrf_view, (), {}) -+ -+ response = csrf_view(request) -+ -+ response = csrf_middleware.process_response(request, response) -+ response = cache_middleware.process_response(request, response) -+ -+ # Inserting a CSRF cookie in a cookie-less request prevented caching. -+ self.assertIsNone(cache_middleware.process_request(request)) -+ -+ - @override_settings( - CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix', - CACHE_MIDDLEWARE_SECONDS=1, diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0474.patch python-django-1.6.11/debian/patches/CVE-2014-0474.patch --- python-django-1.6.1/debian/patches/CVE-2014-0474.patch 2014-04-19 13:03:20.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0474.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,255 +0,0 @@ -Description: fix MySQL typecasting issue -Origin: provided by upstream -Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/python-django/+bug/1309784 - -Index: python-django-1.6.1/django/db/models/fields/__init__.py -=================================================================== ---- python-django-1.6.1.orig/django/db/models/fields/__init__.py 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/django/db/models/fields/__init__.py 2014-04-19 08:48:00.990155175 -0400 -@@ -1013,6 +1013,12 @@ - kwargs['max_length'] = kwargs.get('max_length', 100) - Field.__init__(self, verbose_name, name, **kwargs) - -+ def get_prep_value(self, value): -+ value = super(FilePathField, self).get_prep_value(value) -+ if value is None: -+ return None -+ return six.text_type(value) -+ - def formfield(self, **kwargs): - defaults = { - 'path': self.path, -@@ -1120,6 +1126,12 @@ - kwargs['max_length'] = 15 - Field.__init__(self, *args, **kwargs) - -+ def get_prep_value(self, value): -+ value = super(IPAddressField, self).get_prep_value(value) -+ if value is None: -+ return None -+ return six.text_type(value) -+ - def get_internal_type(self): - return "IPAddressField" - -@@ -1158,12 +1170,14 @@ - return value or None - - def get_prep_value(self, value): -+ if value is None: -+ return value - if value and ':' in value: - try: - return clean_ipv6_address(value, self.unpack_ipv4) - except exceptions.ValidationError: - pass -- return value -+ return six.text_type(value) - - def formfield(self, **kwargs): - defaults = { -Index: python-django-1.6.1/docs/howto/custom-model-fields.txt -=================================================================== ---- python-django-1.6.1.orig/docs/howto/custom-model-fields.txt 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/docs/howto/custom-model-fields.txt 2014-04-19 08:48:00.990155175 -0400 -@@ -501,6 +501,16 @@ - return ''.join([''.join(l) for l in (value.north, - value.east, value.south, value.west)]) - -+.. warning:: -+ -+ If your custom field uses the ``CHAR``, ``VARCHAR`` or ``TEXT`` -+ types for MySQL, you must make sure that :meth:`.get_prep_value` -+ always returns a string type. MySQL performs flexible and unexpected -+ matching when a query is performed on these types and the provided -+ value is an integer, which can cause queries to include unexpected -+ objects in their results. This problem cannot occur if you always -+ return a string type from :meth:`.get_prep_value`. -+ - Converting query values to database values - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Index: python-django-1.6.1/docs/ref/databases.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/databases.txt 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/docs/ref/databases.txt 2014-04-19 08:48:00.990155175 -0400 -@@ -484,6 +484,22 @@ - statement. If ``select_for_update()`` is used with ``nowait=True`` then a - ``DatabaseError`` will be raised. - -+Automatic typecasting can cause unexpected results -+-------------------------------------------------- -+ -+When performing a query on a string type, but with an integer value, MySQL will -+coerce the types of all values in the table to an integer before performing the -+comparison. If your table contains the values ``'abc'``, ``'def'`` and you -+query for ``WHERE mycolumn=0``, both rows will match. Similarly, ``WHERE mycolumn=1`` -+will match the value ``'abc1'``. Therefore, string type fields included in Django -+will always cast the value to a string before using it in a query. -+ -+If you implement custom model fields that inherit from :class:`~django.db.models.Field` -+directly, are overriding :meth:`~django.db.models.Field.get_prep_value`, or use -+:meth:`extra() ` or -+:meth:`raw() `, you should ensure that you -+perform the appropriate typecasting. -+ - .. _sqlite-notes: - - SQLite notes -Index: python-django-1.6.1/docs/ref/models/querysets.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/models/querysets.txt 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/docs/ref/models/querysets.txt 2014-04-19 08:48:00.990155175 -0400 -@@ -1132,6 +1132,16 @@ - - Entry.objects.extra(where=['headline=%s'], params=['Lennon']) - -+.. warning:: -+ -+ If you are performing queries on MySQL, note that MySQL's silent type coercion -+ may cause unexpected results when mixing types. If you query on a string -+ type column, but with an integer value, MySQL will coerce the types of all values -+ in the table to an integer before performing the comparison. For example, if your -+ table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``, -+ both rows will match. To prevent this, perform the correct typecasting -+ before using the value in a query. -+ - defer - ~~~~~ - -Index: python-django-1.6.1/docs/topics/db/sql.txt -=================================================================== ---- python-django-1.6.1.orig/docs/topics/db/sql.txt 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/docs/topics/db/sql.txt 2014-04-19 08:48:00.990155175 -0400 -@@ -66,6 +66,16 @@ - database, but does nothing to enforce that. If the query does not - return rows, a (possibly cryptic) error will result. - -+.. warning:: -+ -+ If you are performing queries on MySQL, note that MySQL's silent type coercion -+ may cause unexpected results when mixing types. If you query on a string -+ type column, but with an integer value, MySQL will coerce the types of all values -+ in the table to an integer before performing the comparison. For example, if your -+ table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``, -+ both rows will match. To prevent this, perform the correct typecasting -+ before using the value in a query. -+ - Mapping query fields to model fields - ------------------------------------ - -Index: python-django-1.6.1/tests/model_fields/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/model_fields/tests.py 2014-04-19 08:48:00.998155176 -0400 -+++ python-django-1.6.1/tests/model_fields/tests.py 2014-04-19 08:48:00.994155175 -0400 -@@ -7,7 +7,14 @@ - from django import forms - from django.core.exceptions import ValidationError - from django.db import connection, models, IntegrityError --from django.db.models.fields.files import FieldFile -+from django.db.models.fields import ( -+ AutoField, BigIntegerField, BinaryField, BooleanField, CharField, -+ CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField, -+ EmailField, FilePathField, FloatField, IntegerField, IPAddressField, -+ GenericIPAddressField, NullBooleanField, PositiveIntegerField, -+ PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, -+ TimeField, URLField) -+from django.db.models.fields.files import FileField, ImageField - from django.utils import six - from django.utils import unittest - -@@ -494,6 +501,94 @@ - self.assertRaises(ValidationError, form_field.clean, '127.0.0.1') - - -+class PrepValueTest(test.TestCase): -+ def test_AutoField(self): -+ self.assertIsInstance(AutoField(primary_key=True).get_prep_value(1), int) -+ -+ @unittest.skipIf(six.PY3, "Python 3 has no `long` type.") -+ def test_BigIntegerField(self): -+ self.assertIsInstance(BigIntegerField().get_prep_value(long(9999999999999999999)), long) -+ -+ def test_BinaryField(self): -+ self.assertIsInstance(BinaryField().get_prep_value(b''), bytes) -+ -+ def test_BooleanField(self): -+ self.assertIsInstance(BooleanField().get_prep_value(True), bool) -+ -+ def test_CharField(self): -+ self.assertIsInstance(CharField().get_prep_value(''), six.text_type) -+ self.assertIsInstance(CharField().get_prep_value(0), six.text_type) -+ -+ def test_CommaSeparatedIntegerField(self): -+ self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value('1,2'), six.text_type) -+ self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(0), six.text_type) -+ -+ def test_DateField(self): -+ self.assertIsInstance(DateField().get_prep_value(datetime.date.today()), datetime.date) -+ -+ def test_DateTimeField(self): -+ self.assertIsInstance(DateTimeField().get_prep_value(datetime.datetime.now()), datetime.datetime) -+ -+ def test_DecimalField(self): -+ self.assertIsInstance(DecimalField().get_prep_value(Decimal('1.2')), Decimal) -+ -+ def test_EmailField(self): -+ self.assertIsInstance(EmailField().get_prep_value('mailbox@domain.com'), six.text_type) -+ -+ def test_FileField(self): -+ self.assertIsInstance(FileField().get_prep_value('filename.ext'), six.text_type) -+ self.assertIsInstance(FileField().get_prep_value(0), six.text_type) -+ -+ def test_FilePathField(self): -+ self.assertIsInstance(FilePathField().get_prep_value('tests.py'), six.text_type) -+ self.assertIsInstance(FilePathField().get_prep_value(0), six.text_type) -+ -+ def test_FloatField(self): -+ self.assertIsInstance(FloatField().get_prep_value(1.2), float) -+ -+ def test_ImageField(self): -+ self.assertIsInstance(ImageField().get_prep_value('filename.ext'), six.text_type) -+ -+ def test_IntegerField(self): -+ self.assertIsInstance(IntegerField().get_prep_value(1), int) -+ -+ def test_IPAddressField(self): -+ self.assertIsInstance(IPAddressField().get_prep_value('127.0.0.1'), six.text_type) -+ self.assertIsInstance(IPAddressField().get_prep_value(0), six.text_type) -+ -+ def test_GenericIPAddressField(self): -+ self.assertIsInstance(GenericIPAddressField().get_prep_value('127.0.0.1'), six.text_type) -+ self.assertIsInstance(GenericIPAddressField().get_prep_value(0), six.text_type) -+ -+ def test_NullBooleanField(self): -+ self.assertIsInstance(NullBooleanField().get_prep_value(True), bool) -+ -+ def test_PositiveIntegerField(self): -+ self.assertIsInstance(PositiveIntegerField().get_prep_value(1), int) -+ -+ def test_PositiveSmallIntegerField(self): -+ self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(1), int) -+ -+ def test_SlugField(self): -+ self.assertIsInstance(SlugField().get_prep_value('slug'), six.text_type) -+ self.assertIsInstance(SlugField().get_prep_value(0), six.text_type) -+ -+ def test_SmallIntegerField(self): -+ self.assertIsInstance(SmallIntegerField().get_prep_value(1), int) -+ -+ def test_TextField(self): -+ self.assertIsInstance(TextField().get_prep_value('Abc'), six.text_type) -+ self.assertIsInstance(TextField().get_prep_value(0), six.text_type) -+ -+ def test_TimeField(self): -+ self.assertIsInstance( -+ TimeField().get_prep_value(datetime.datetime.now().time()), -+ datetime.time) -+ -+ def test_URLField(self): -+ self.assertIsInstance(URLField().get_prep_value('http://domain.com'), six.text_type) -+ -+ - class CustomFieldTests(unittest.TestCase): - - def test_14786(self): diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0480.patch python-django-1.6.11/debian/patches/CVE-2014-0480.patch --- python-django-1.6.1/debian/patches/CVE-2014-0480.patch 2014-09-09 17:35:34.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0480.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,59 +0,0 @@ -From da051da8df5e69944745072611351d4cfc6435d5 Mon Sep 17 00:00:00 2001 -From: Florian Apolloner -Date: Thu, 17 Jul 2014 21:59:28 +0200 -Subject: [PATCH] [1.6.x] Prevented reverse() from generating URLs pointing to - other hosts. - -This is a security fix. Disclosure following shortly. ---- - django/core/urlresolvers.py | 6 +++++- - docs/releases/1.4.14.txt | 13 +++++++++++++ - docs/releases/1.5.9.txt | 13 +++++++++++++ - docs/releases/1.6.6.txt | 13 +++++++++++++ - tests/urlpatterns_reverse/tests.py | 3 +++ - tests/urlpatterns_reverse/urls.py | 3 +++ - 6 files changed, 50 insertions(+), 1 deletion(-) - -diff --git a/django/core/urlresolvers.py b/django/core/urlresolvers.py -index b3008e8..4f251ab 100644 ---- a/django/core/urlresolvers.py -+++ b/django/core/urlresolvers.py -@@ -435,7 +435,11 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): - candidate_pat = prefix_norm.replace('%', '%%') + result - if re.search('^%s%s' % (prefix_norm, pattern), candidate_pat % candidate_subs, re.UNICODE): - candidate_subs = dict((k, urlquote(v)) for (k, v) in candidate_subs.items()) -- return candidate_pat % candidate_subs -+ url = candidate_pat % candidate_subs -+ # Don't allow construction of scheme relative urls. -+ if url.startswith('//'): -+ url = '/%%2F%s' % url[2:] -+ return url - # lookup_view can be URL label, or dotted path, or callable, Any of - # these can be passed in at the top, but callables are not friendly in - # error messages. -diff --git a/tests/urlpatterns_reverse/tests.py b/tests/urlpatterns_reverse/tests.py -index 249acc1..7df4acf 100644 ---- a/tests/urlpatterns_reverse/tests.py -+++ b/tests/urlpatterns_reverse/tests.py -@@ -147,6 +147,9 @@ - ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}), - ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}), - ('defaults', NoReverseMatch, [], {'arg2': 1}), -+ -+ # Security tests -+ ('security', '/%2Fexample.com/security/', ['/example.com'], {}), - ) - - class NoURLPatternsTests(TestCase): -diff --git a/tests/urlpatterns_reverse/urls.py b/tests/urlpatterns_reverse/urls.py -index ac112c6..74c881e 100644 ---- a/tests/urlpatterns_reverse/urls.py -+++ b/tests/urlpatterns_reverse/urls.py -@@ -71,4 +71,7 @@ - (r'defaults_view2/(?P\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'), - - url('^includes/', include(other_patterns)), -+ -+ # Security tests -+ url('(.+)/security/$', empty_view, name='security'), - ) diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0481.patch python-django-1.6.11/debian/patches/CVE-2014-0481.patch --- python-django-1.6.1/debian/patches/CVE-2014-0481.patch 2014-09-09 17:35:42.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0481.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,209 +0,0 @@ -From dd0c3f4ee1a30c1a1e6055061c6ba6e58c6b54d1 Mon Sep 17 00:00:00 2001 -From: Tim Graham -Date: Fri, 8 Aug 2014 10:20:08 -0400 -Subject: [PATCH] [1.6.x] Fixed #23157 -- Removed O(n) algorithm when uploading - duplicate file names. - -This is a security fix. Disclosure following shortly. ---- - django/core/files/storage.py | 11 +++++------ - docs/howto/custom-file-storage.txt | 13 +++++++++++-- - docs/ref/files/storage.txt | 11 +++++++++++ - docs/releases/1.4.14.txt | 20 ++++++++++++++++++++ - docs/releases/1.5.9.txt | 20 ++++++++++++++++++++ - docs/releases/1.6.6.txt | 20 ++++++++++++++++++++ - tests/file_storage/tests.py | 21 +++++++++++++-------- - tests/files/tests.py | 22 +++++++++++++--------- - 8 files changed, 113 insertions(+), 25 deletions(-) - -Index: python-django-1.6.1/django/core/files/storage.py -=================================================================== ---- python-django-1.6.1.orig/django/core/files/storage.py 2014-09-09 13:35:39.572751632 -0400 -+++ python-django-1.6.1/django/core/files/storage.py 2014-09-09 13:35:39.568751632 -0400 -@@ -1,12 +1,12 @@ - import os - import errno --import itertools - from datetime import datetime - - from django.conf import settings - from django.core.exceptions import SuspiciousFileOperation - from django.core.files import locks, File - from django.core.files.move import file_move_safe -+from django.utils.crypto import get_random_string - from django.utils.encoding import force_text, filepath_to_uri - from django.utils.functional import LazyObject - from django.utils.module_loading import import_by_path -@@ -67,13 +67,12 @@ - """ - dir_name, file_name = os.path.split(name) - file_root, file_ext = os.path.splitext(file_name) -- # If the filename already exists, add an underscore and a number (before -- # the file extension, if one exists) to the filename until the generated -- # filename doesn't exist. -- count = itertools.count(1) -+ # If the filename already exists, add an underscore and a random 7 -+ # character alphanumeric string (before the file extension, if one -+ # exists) to the filename until the generated filename doesn't exist. - while self.exists(name): - # file_ext includes the dot. -- name = os.path.join(dir_name, "%s_%s%s" % (file_root, next(count), file_ext)) -+ name = os.path.join(dir_name, "%s_%s%s" % (file_root, get_random_string(7), file_ext)) - - return name - -Index: python-django-1.6.1/docs/howto/custom-file-storage.txt -=================================================================== ---- python-django-1.6.1.orig/docs/howto/custom-file-storage.txt 2014-09-09 13:35:39.572751632 -0400 -+++ python-django-1.6.1/docs/howto/custom-file-storage.txt 2014-09-09 13:35:39.568751632 -0400 -@@ -83,5 +83,14 @@ - will have already cleaned to a filename valid for the storage system, according - to the ``get_valid_name()`` method described above. - --The code provided on ``Storage`` simply appends ``"_1"``, ``"_2"``, etc. to the --filename until it finds one that's available in the destination directory. -+.. versionchanged:: 1.6.6 -+ -+ If a file with ``name`` already exists, an underscore plus a random 7 -+ character alphanumeric string is appended to the filename before the -+ extension. -+ -+ Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, -+ etc.) was appended to the filename until an avaible name in the destination -+ directory was found. A malicious user could exploit this deterministic -+ algorithm to create a denial-of-service attack. This change was also made -+ in Django 1.5.9 and 1.4.14. -Index: python-django-1.6.1/docs/ref/files/storage.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/files/storage.txt 2014-09-09 13:35:39.572751632 -0400 -+++ python-django-1.6.1/docs/ref/files/storage.txt 2014-09-09 13:35:39.568751632 -0400 -@@ -81,6 +81,17 @@ - available for new content to be written to on the target storage - system. - -+ .. versionchanged:: 1.6.6 -+ -+ If a file with ``name`` already exists, an underscore plus a random 7 -+ character alphanumeric string is appended to the filename before the -+ extension. -+ -+ Previously, an underscore followed by a number (e.g. ``"_1"``, ``"_2"``, -+ etc.) was appended to the filename until an avaible name in the -+ destination directory was found. A malicious user could exploit this -+ deterministic algorithm to create a denial-of-service attack. This -+ change was also made in Django 1.5.9 and 1.4.14. - - .. method:: get_valid_name(name) - -Index: python-django-1.6.1/tests/file_storage/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/file_storage/tests.py 2014-09-09 13:35:39.572751632 -0400 -+++ python-django-1.6.1/tests/file_storage/tests.py 2014-09-09 13:35:39.568751632 -0400 -@@ -35,6 +35,9 @@ - Image = None - - -+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' -+ -+ - class GetStorageClassTests(SimpleTestCase): - - def test_get_filesystem_storage(self): -@@ -430,10 +433,9 @@ - self.thread.start() - name = self.save_file('conflict') - self.thread.join() -- self.assertTrue(self.storage.exists('conflict')) -- self.assertTrue(self.storage.exists('conflict_1')) -- self.storage.delete('conflict') -- self.storage.delete('conflict_1') -+ files = sorted(os.listdir(self.storage_dir)) -+ self.assertEqual(files[0], 'conflict') -+ six.assertRegex(self, files[1], 'conflict_%s' % FILE_SUFFIX_REGEX) - - @unittest.skipIf(sys.platform.startswith('win'), "Windows only partially supports umasks and chmod.") - class FileStoragePermissions(unittest.TestCase): -@@ -477,9 +479,10 @@ - self.storage.save('dotted.path/test', ContentFile("1")) - self.storage.save('dotted.path/test', ContentFile("2")) - -+ files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path'))) - self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) -- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) -- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1'))) -+ self.assertEqual(files[0], 'test') -+ six.assertRegex(self, files[1], 'test_%s' % FILE_SUFFIX_REGEX) - - def test_first_character_dot(self): - """ -@@ -489,8 +492,10 @@ - self.storage.save('dotted.path/.test', ContentFile("1")) - self.storage.save('dotted.path/.test', ContentFile("2")) - -- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) -- self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1'))) -+ files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path'))) -+ self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) -+ self.assertEqual(files[0], '.test') -+ six.assertRegex(self, files[1], '.test_%s' % FILE_SUFFIX_REGEX) - - class DimensionClosingBug(unittest.TestCase): - """ -Index: python-django-1.6.1/tests/files/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/files/tests.py 2014-09-09 13:35:39.572751632 -0400 -+++ python-django-1.6.1/tests/files/tests.py 2014-09-09 13:35:39.568751632 -0400 -@@ -12,12 +12,15 @@ - from django.core.files.uploadedfile import SimpleUploadedFile - from django.core.files.temp import NamedTemporaryFile - from django.test import TestCase --from django.utils import unittest -+from django.utils import six, unittest - from django.utils.six import StringIO - - from .models import Storage, temp_storage, temp_storage_location - - -+FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' -+ -+ - class FileStorageTests(TestCase): - def tearDown(self): - shutil.rmtree(temp_storage_location) -@@ -63,27 +66,28 @@ - # Save another file with the same name. - obj2 = Storage() - obj2.normal.save("django_test.txt", ContentFile("more content")) -- self.assertEqual(obj2.normal.name, "tests/django_test_1.txt") -+ obj2_name = obj2.normal.name -+ six.assertRegex(self, obj2_name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) - self.assertEqual(obj2.normal.size, 12) - - # Push the objects into the cache to make sure they pickle properly - cache.set("obj1", obj1) - cache.set("obj2", obj2) -- self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt") -+ six.assertRegex(self, cache.get("obj2").normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) - - # Deleting an object does not delete the file it uses. - obj2.delete() - obj2.normal.save("django_test.txt", ContentFile("more content")) -- self.assertEqual(obj2.normal.name, "tests/django_test_2.txt") -+ self.assertNotEqual(obj2_name, obj2.normal.name) -+ six.assertRegex(self, obj2.normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) - - # Multiple files with the same name get _N appended to them. -- objs = [Storage() for i in range(3)] -+ objs = [Storage() for i in range(2)] - for o in objs: - o.normal.save("multiple_files.txt", ContentFile("Same Content")) -- self.assertEqual( -- [o.normal.name for o in objs], -- ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"] -- ) -+ names = [o.normal.name for o in objs] -+ self.assertEqual(names[0], "tests/multiple_files.txt") -+ six.assertRegex(self, names[1], "tests/multiple_files_%s.txt" % FILE_SUFFIX_REGEX) - for o in objs: - o.delete() - diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0482.patch python-django-1.6.11/debian/patches/CVE-2014-0482.patch --- python-django-1.6.1/debian/patches/CVE-2014-0482.patch 2014-09-09 17:35:48.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0482.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,94 +0,0 @@ -From 0268b855f9eab3377f2821164ef3e66037789e09 Mon Sep 17 00:00:00 2001 -From: Preston Holmes -Date: Mon, 11 Aug 2014 12:04:53 -0400 -Subject: [PATCH] [1.6.x] Fixed #23066 -- Modified RemoteUserMiddleware to - logout on REMOTE_USE change. - -This is a security fix. Disclosure following shortly. ---- - django/contrib/auth/middleware.py | 28 +++++++++++++++++++-------- - django/contrib/auth/tests/test_remote_user.py | 18 +++++++++++++++++ - docs/releases/1.4.14.txt | 9 +++++++++ - docs/releases/1.5.9.txt | 9 +++++++++ - docs/releases/1.6.6.txt | 9 +++++++++ - 5 files changed, 65 insertions(+), 8 deletions(-) - -diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py -index f38efdd..023e082 100644 ---- a/django/contrib/auth/middleware.py -+++ b/django/contrib/auth/middleware.py -@@ -53,14 +53,7 @@ def process_request(self, request): - # authenticated remote-user, or return (leaving request.user set to - # AnonymousUser by the AuthenticationMiddleware). - if request.user.is_authenticated(): -- try: -- stored_backend = load_backend(request.session.get( -- auth.BACKEND_SESSION_KEY, '')) -- if isinstance(stored_backend, RemoteUserBackend): -- auth.logout(request) -- except ImproperlyConfigured as e: -- # backend failed to load -- auth.logout(request) -+ self._remove_invalid_user(request) - return - # If the user is already authenticated and that user is the user we are - # getting passed in the headers, then the correct user is already -@@ -68,6 +61,11 @@ def process_request(self, request): - if request.user.is_authenticated(): - if request.user.get_username() == self.clean_username(username, request): - return -+ else: -+ # An authenticated user is associated with the request, but -+ # it does not match the authorized user in the header. -+ self._remove_invalid_user(request) -+ - # We are seeing this user for the first time in this session, attempt - # to authenticate the user. - user = auth.authenticate(remote_user=username) -@@ -89,3 +87,17 @@ def clean_username(self, username, request): - except AttributeError: # Backend has no clean_username method. - pass - return username -+ -+ def _remove_invalid_user(self, request): -+ """ -+ Removes the current authenticated user in the request which is invalid -+ but only if the user is authenticated via the RemoteUserBackend. -+ """ -+ try: -+ stored_backend = load_backend(request.session.get(auth.BACKEND_SESSION_KEY, '')) -+ except ImproperlyConfigured: -+ # backend failed to load -+ auth.logout(request) -+ else: -+ if isinstance(stored_backend, RemoteUserBackend): -+ auth.logout(request) -diff --git a/django/contrib/auth/tests/test_remote_user.py b/django/contrib/auth/tests/test_remote_user.py -index 5c5024e..642a6e9 100644 ---- a/django/contrib/auth/tests/test_remote_user.py -+++ b/django/contrib/auth/tests/test_remote_user.py -@@ -118,6 +118,24 @@ def test_header_disappears(self): - response = self.client.get('/remote_user/') - self.assertEqual(response.context['user'].username, 'modeluser') - -+ def test_user_switch_forces_new_login(self): -+ """ -+ Tests that if the username in the header changes between requests -+ that the original user is logged out -+ """ -+ User.objects.create(username='knownuser') -+ # Known user authenticates -+ response = self.client.get('/remote_user/', -+ **{'REMOTE_USER': self.known_user}) -+ self.assertEqual(response.context['user'].username, 'knownuser') -+ # During the session, the REMOTE_USER changes to a different user. -+ response = self.client.get('/remote_user/', -+ **{'REMOTE_USER': "newnewuser"}) -+ # Ensure that the current user is not the prior remote_user -+ # In backends that create a new user, username is "newnewuser" -+ # In backends that do not create new users, it is '' (anonymous user) -+ self.assertNotEqual(response.context['user'].username, 'knownuser') -+ - def tearDown(self): - """Restores settings to avoid breaking other tests.""" - settings.MIDDLEWARE_CLASSES = self.curr_middleware diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0483-bug23329.patch python-django-1.6.11/debian/patches/CVE-2014-0483-bug23329.patch --- python-django-1.6.1/debian/patches/CVE-2014-0483-bug23329.patch 2014-09-09 17:37:03.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0483-bug23329.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,113 +0,0 @@ -From e3453b61c6269d7868ceb404abaea5ad2569778f Mon Sep 17 00:00:00 2001 -From: Simon Charette -Date: Thu, 21 Aug 2014 11:55:23 -0400 -Subject: [PATCH] [1.6.x] Fixed #23329 -- Allowed inherited and m2m fields to - be referenced in the admin. - -Thanks to Trac alias Markush2010 and ross for the detailed reports. - -Backport of 3cbb759 from master ---- - django/contrib/admin/options.py | 10 ++++++---- - docs/releases/1.4.15.txt | 13 +++++++++++++ - docs/releases/1.5.10.txt | 13 +++++++++++++ - docs/releases/1.6.7.txt | 13 +++++++++++++ - docs/releases/index.txt | 3 +++ - tests/admin_views/admin.py | 5 ++++- - tests/admin_views/models.py | 17 +++++++++++++++++ - tests/admin_views/tests.py | 9 +++++++++ - 8 files changed, 78 insertions(+), 5 deletions(-) - create mode 100644 docs/releases/1.4.15.txt - create mode 100644 docs/releases/1.5.10.txt - create mode 100644 docs/releases/1.6.7.txt - -Index: python-django-1.6.1/django/contrib/admin/options.py -=================================================================== ---- python-django-1.6.1.orig/django/contrib/admin/options.py 2014-09-09 13:37:00.428753797 -0400 -+++ python-django-1.6.1/django/contrib/admin/options.py 2014-09-09 13:37:00.420753797 -0400 -@@ -336,11 +336,13 @@ - return False - - # Make sure at least one of the models registered for this site -- # references this field. -+ # references this field through a FK or a M2M relationship. - registered_models = self.admin_site._registry -- for related_object in opts.get_all_related_objects(): -- if (related_object.model in registered_models and -- field in related_object.field.foreign_related_fields): -+ for related_object in (opts.get_all_related_objects() + -+ opts.get_all_related_many_to_many_objects()): -+ related_model = related_object.model -+ if (any(issubclass(model, related_model) for model in registered_models) and -+ related_object.field.rel.get_related_field() == field): - return True - - return False -Index: python-django-1.6.1/tests/admin_views/admin.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/admin.py 2014-09-09 13:37:00.428753797 -0400 -+++ python-django-1.6.1/tests/admin_views/admin.py 2014-09-09 13:37:00.420753797 -0400 -@@ -30,7 +30,7 @@ - AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, - AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, - RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice, -- ShortMessage, Telegram) -+ ShortMessage, Telegram, ReferencedByParent, ChildOfReferer, M2MReference) - - - def callable_year(dt_value): -@@ -745,6 +745,9 @@ - site.register(MainPrepopulated, MainPrepopulatedAdmin) - site.register(UnorderedObject, UnorderedObjectAdmin) - site.register(UndeletableObject, UndeletableObjectAdmin) -+site.register(ReferencedByParent) -+site.register(ChildOfReferer) -+site.register(M2MReference) - - # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. - # That way we cover all four cases: -Index: python-django-1.6.1/tests/admin_views/models.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/models.py 2014-09-09 13:37:00.428753797 -0400 -+++ python-django-1.6.1/tests/admin_views/models.py 2014-09-09 13:37:00.420753797 -0400 -@@ -687,3 +687,20 @@ - class Choice(models.Model): - choice = models.IntegerField(blank=True, null=True, - choices=((1, 'Yes'), (0, 'No'), (None, 'No opinion'))) -+ -+# Models for #23329 -+class ReferencedByParent(models.Model): -+ pass -+ -+ -+class ParentWithFK(models.Model): -+ fk = models.ForeignKey(ReferencedByParent) -+ -+ -+class ChildOfReferer(ParentWithFK): -+ pass -+ -+ -+class M2MReference(models.Model): -+ ref = models.ManyToManyField('self') -+ -Index: python-django-1.6.1/tests/admin_views/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/tests.py 2014-09-09 13:37:00.428753797 -0400 -+++ python-django-1.6.1/tests/admin_views/tests.py 2014-09-09 13:37:00.424753797 -0400 -@@ -594,6 +594,15 @@ - response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'}) - self.assertEqual(response.status_code, 200) - -+ # Specifying a field referenced by another model though a m2m should be allowed. -+ response = self.client.get("/test_admin/admin/admin_views/m2mreference/", {TO_FIELD_VAR: 'id'}) -+ self.assertEqual(response.status_code, 200) -+ -+ # Specifying a field that is not refered by any other model directly registered -+ # to this admin site but registered through inheritance should be allowed. -+ response = self.client.get("/test_admin/admin/admin_views/referencedbyparent/", {TO_FIELD_VAR: 'id'}) -+ self.assertEqual(response.status_code, 200) -+ - def test_allowed_filtering_15103(self): - """ - Regressions test for ticket 15103 - filtering on fields defined in a diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0483-bug23431.patch python-django-1.6.11/debian/patches/CVE-2014-0483-bug23431.patch --- python-django-1.6.1/debian/patches/CVE-2014-0483-bug23431.patch 2014-09-09 17:37:16.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0483-bug23431.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,138 +0,0 @@ -From a7af6ad96a35634383c2d73fa049127e85a886a6 Mon Sep 17 00:00:00 2001 -From: Simon Charette -Date: Thu, 4 Sep 2014 17:04:53 -0400 -Subject: [PATCH] [1.6.x] Fixed #23431 -- Allowed inline and hidden references - to admin fields. - -This fixes a regression introduced by the 53ff096982 security fix. - -Thanks to @a1tus for the report and Tim for the review. - -refs #23329. - -Backport of 342ccbd from master ---- - django/contrib/admin/options.py | 13 +++++++++++-- - docs/releases/1.4.16.txt | 13 +++++++++++++ - docs/releases/1.5.11.txt | 13 +++++++++++++ - docs/releases/1.6.8.txt | 12 ++++++++++++ - docs/releases/index.txt | 3 +++ - tests/admin_views/admin.py | 13 ++++++++++++- - tests/admin_views/models.py | 12 ++++++++++++ - tests/admin_views/tests.py | 7 ++++++- - 8 files changed, 82 insertions(+), 4 deletions(-) - create mode 100644 docs/releases/1.4.16.txt - create mode 100644 docs/releases/1.5.11.txt - create mode 100644 docs/releases/1.6.8.txt - -Index: python-django-1.6.1/django/contrib/admin/options.py -=================================================================== ---- python-django-1.6.1.orig/django/contrib/admin/options.py 2014-09-09 13:37:13.704754153 -0400 -+++ python-django-1.6.1/django/contrib/admin/options.py 2014-09-09 13:37:13.696754152 -0400 -@@ -328,6 +328,10 @@ - return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy - - def to_field_allowed(self, request, to_field): -+ """ -+ Returns True if the model associated with this admin should be -+ allowed to be referenced by the specified field. -+ """ - opts = self.model._meta - - try: -@@ -337,8 +341,13 @@ - - # Make sure at least one of the models registered for this site - # references this field through a FK or a M2M relationship. -- registered_models = self.admin_site._registry -- for related_object in (opts.get_all_related_objects() + -+ registered_models = set() -+ for model, admin in self.admin_site._registry.items(): -+ registered_models.add(model) -+ for inline in admin.inlines: -+ registered_models.add(inline.model) -+ -+ for related_object in (opts.get_all_related_objects(include_hidden=True) + - opts.get_all_related_many_to_many_objects()): - related_model = related_object.model - if (any(issubclass(model, related_model) for model in registered_models) and -Index: python-django-1.6.1/tests/admin_views/admin.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/admin.py 2014-09-09 13:37:13.704754153 -0400 -+++ python-django-1.6.1/tests/admin_views/admin.py 2014-09-09 13:37:13.696754152 -0400 -@@ -30,7 +30,8 @@ - AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, - AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, - RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice, -- ShortMessage, Telegram, ReferencedByParent, ChildOfReferer, M2MReference) -+ ShortMessage, Telegram, ReferencedByParent, ChildOfReferer, M2MReference, -+ ReferencedByInline, InlineReference, InlineReferer) - - - def callable_year(dt_value): -@@ -696,6 +697,14 @@ - fields = ['choice'] - - -+class InlineReferenceInline(admin.TabularInline): -+ model = InlineReference -+ -+ -+class InlineRefererAdmin(admin.ModelAdmin): -+ inlines = [InlineReferenceInline] -+ -+ - site = admin.AdminSite(name="admin") - site.register(Article, ArticleAdmin) - site.register(CustomArticle, CustomArticleAdmin) -@@ -748,6 +757,8 @@ - site.register(ReferencedByParent) - site.register(ChildOfReferer) - site.register(M2MReference) -+site.register(ReferencedByInline) -+site.register(InlineReferer, InlineRefererAdmin) - - # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. - # That way we cover all four cases: -Index: python-django-1.6.1/tests/admin_views/models.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/models.py 2014-09-09 13:37:13.704754153 -0400 -+++ python-django-1.6.1/tests/admin_views/models.py 2014-09-09 13:37:13.696754152 -0400 -@@ -704,3 +704,15 @@ - class M2MReference(models.Model): - ref = models.ManyToManyField('self') - -+# Models for #23431 -+class ReferencedByInline(models.Model): -+ pass -+ -+ -+class InlineReference(models.Model): -+ fk = models.ForeignKey(ReferencedByInline, related_name='hidden+') -+ -+ -+class InlineReferer(models.Model): -+ refs = models.ManyToManyField(InlineReference) -+ -Index: python-django-1.6.1/tests/admin_views/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/tests.py 2014-09-09 13:37:13.704754153 -0400 -+++ python-django-1.6.1/tests/admin_views/tests.py 2014-09-09 13:37:13.696754152 -0400 -@@ -598,11 +598,16 @@ - response = self.client.get("/test_admin/admin/admin_views/m2mreference/", {TO_FIELD_VAR: 'id'}) - self.assertEqual(response.status_code, 200) - -- # Specifying a field that is not refered by any other model directly registered -+ # #23329 - Specifying a field that is not refered by any other model directly registered - # to this admin site but registered through inheritance should be allowed. - response = self.client.get("/test_admin/admin/admin_views/referencedbyparent/", {TO_FIELD_VAR: 'id'}) - self.assertEqual(response.status_code, 200) - -+ # #23431 - Specifying a field that is only refered to by a inline of a registered -+ # model should be allowed. -+ response = self.client.get("/test_admin/admin/admin_views/referencedbyinline/", {TO_FIELD_VAR: 'id'}) -+ self.assertEqual(response.status_code, 200) -+ - def test_allowed_filtering_15103(self): - """ - Regressions test for ticket 15103 - filtering on fields defined in a diff -Nru python-django-1.6.1/debian/patches/CVE-2014-0483.patch python-django-1.6.11/debian/patches/CVE-2014-0483.patch --- python-django-1.6.1/debian/patches/CVE-2014-0483.patch 2014-09-09 17:35:59.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2014-0483.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,146 +0,0 @@ -From f7c494f2506250b8cb5923714360a3642ed63e0f Mon Sep 17 00:00:00 2001 -From: Simon Charette -Date: Thu, 7 Aug 2014 00:18:10 -0400 -Subject: [PATCH] [1.6.x] Prevented data leakage in contrib.admin via query - string manipulation. - -This is a security fix. Disclosure following shortly. ---- - django/contrib/admin/exceptions.py | 5 +++++ - django/contrib/admin/options.py | 18 ++++++++++++++++++ - django/contrib/admin/views/main.py | 7 +++++-- - docs/ref/exceptions.txt | 1 + - docs/releases/1.4.14.txt | 15 +++++++++++++++ - docs/releases/1.5.9.txt | 15 +++++++++++++++ - docs/releases/1.6.6.txt | 15 +++++++++++++++ - tests/admin_views/tests.py | 23 ++++++++++++++++++++--- - 8 files changed, 94 insertions(+), 5 deletions(-) - -Index: python-django-1.6.1/django/contrib/admin/exceptions.py -=================================================================== ---- python-django-1.6.1.orig/django/contrib/admin/exceptions.py 2014-09-09 13:35:56.416752083 -0400 -+++ python-django-1.6.1/django/contrib/admin/exceptions.py 2014-09-09 13:35:56.408752083 -0400 -@@ -4,3 +4,8 @@ - class DisallowedModelAdminLookup(SuspiciousOperation): - """Invalid filter was passed to admin view via URL querystring""" - pass -+ -+ -+class DisallowedModelAdminToField(SuspiciousOperation): -+ """Invalid to_field was passed to admin view via URL query string""" -+ pass -Index: python-django-1.6.1/django/contrib/admin/options.py -=================================================================== ---- python-django-1.6.1.orig/django/contrib/admin/options.py 2014-09-09 13:35:56.416752083 -0400 -+++ python-django-1.6.1/django/contrib/admin/options.py 2014-09-09 13:35:56.408752083 -0400 -@@ -327,6 +327,24 @@ - clean_lookup = LOOKUP_SEP.join(parts) - return clean_lookup in self.list_filter or clean_lookup == self.date_hierarchy - -+ def to_field_allowed(self, request, to_field): -+ opts = self.model._meta -+ -+ try: -+ field = opts.get_field(to_field) -+ except FieldDoesNotExist: -+ return False -+ -+ # Make sure at least one of the models registered for this site -+ # references this field. -+ registered_models = self.admin_site._registry -+ for related_object in opts.get_all_related_objects(): -+ if (related_object.model in registered_models and -+ field in related_object.field.foreign_related_fields): -+ return True -+ -+ return False -+ - def has_add_permission(self, request): - """ - Returns True if the given request has permission to add an object. -Index: python-django-1.6.1/django/contrib/admin/views/main.py -=================================================================== ---- python-django-1.6.1.orig/django/contrib/admin/views/main.py 2014-09-09 13:35:56.416752083 -0400 -+++ python-django-1.6.1/django/contrib/admin/views/main.py 2014-09-09 13:35:56.408752083 -0400 -@@ -14,7 +14,7 @@ - from django.utils.http import urlencode - - from django.contrib.admin import FieldListFilter --from django.contrib.admin.exceptions import DisallowedModelAdminLookup -+from django.contrib.admin.exceptions import DisallowedModelAdminLookup, DisallowedModelAdminToField - from django.contrib.admin.options import IncorrectLookupParameters, IS_POPUP_VAR - from django.contrib.admin.util import (quote, get_fields_from_path, - lookup_needs_distinct, prepare_lookup_value) -@@ -90,7 +90,10 @@ - self.page_num = 0 - self.show_all = ALL_VAR in request.GET - self.is_popup = _is_changelist_popup(request) -- self.to_field = request.GET.get(TO_FIELD_VAR) -+ to_field = request.GET.get(TO_FIELD_VAR) -+ if to_field and not model_admin.to_field_allowed(request, to_field): -+ raise DisallowedModelAdminToField("The field %s cannot be referenced." % to_field) -+ self.to_field = to_field - self.params = dict(request.GET.items()) - if PAGE_VAR in self.params: - del self.params[PAGE_VAR] -Index: python-django-1.6.1/docs/ref/exceptions.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/exceptions.txt 2014-09-09 13:35:56.416752083 -0400 -+++ python-django-1.6.1/docs/ref/exceptions.txt 2014-09-09 13:35:56.412752083 -0400 -@@ -56,6 +56,7 @@ - - * DisallowedHost - * DisallowedModelAdminLookup -+ * DisallowedModelAdminToField - * DisallowedRedirect - * InvalidSessionKey - * SuspiciousFileOperation -Index: python-django-1.6.1/tests/admin_views/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/admin_views/tests.py 2014-09-09 13:35:56.416752083 -0400 -+++ python-django-1.6.1/tests/admin_views/tests.py 2014-09-09 13:35:56.412752083 -0400 -@@ -14,6 +14,7 @@ - from django.contrib import admin - from django.contrib.auth import get_permission_codename - from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME -+from django.contrib.admin.views.main import TO_FIELD_VAR - from django.contrib.admin.models import LogEntry, DELETION - from django.contrib.admin.sites import LOGIN_FORM_KEY - from django.contrib.admin.util import quote -@@ -576,6 +577,23 @@ - response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk) - self.assertEqual(response.status_code, 200) - -+ def test_disallowed_to_field(self): -+ with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: -+ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'}) -+ self.assertEqual(response.status_code, 400) -+ self.assertEqual(len(calls), 1) -+ -+ # Specifying a field that is not refered by any other model registered -+ # to this admin site should raise an exception. -+ with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: -+ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'}) -+ self.assertEqual(response.status_code, 400) -+ self.assertEqual(len(calls), 1) -+ -+ # Specifying a field referenced by another model should be allowed. -+ response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'id'}) -+ self.assertEqual(response.status_code, 200) -+ - def test_allowed_filtering_15103(self): - """ - Regressions test for ticket 15103 - filtering on fields defined in a -@@ -2203,10 +2221,9 @@ - """Ensure that the to_field GET parameter is preserved when a search - is performed. Refs #10918. - """ -- from django.contrib.admin.views.main import TO_FIELD_VAR -- response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR) -+ response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR) - self.assertContains(response, "\n1 user\n") -- self.assertContains(response, '', html=True) -+ self.assertContains(response, '' % TO_FIELD_VAR, html=True) - - def test_exact_matches(self): - response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar') diff -Nru python-django-1.6.1/debian/patches/CVE-2015-0219.patch python-django-1.6.11/debian/patches/CVE-2015-0219.patch --- python-django-1.6.1/debian/patches/CVE-2015-0219.patch 2015-01-13 12:47:23.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2015-0219.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,124 +0,0 @@ -Description: fix WSGI header spoofing via underscore/dash conflation -Author: Carl Meyer - -Index: python-django-1.6.1/django/core/servers/basehttp.py -=================================================================== ---- python-django-1.6.1.orig/django/core/servers/basehttp.py 2015-01-13 07:47:20.530014471 -0500 -+++ python-django-1.6.1/django/core/servers/basehttp.py 2015-01-13 07:47:20.526014442 -0500 -@@ -157,6 +157,17 @@ - - sys.stderr.write(msg) - -+ def get_environ(self): -+ # Strip all headers with underscores in the name before constructing -+ # the WSGI environ. This prevents header-spoofing based on ambiguity -+ # between underscores and dashes both normalized to underscores in WSGI -+ # env vars. Nginx and Apache 2.4+ both do this as well. -+ for k, v in self.headers.items(): -+ if '_' in k: -+ del self.headers[k] -+ -+ return super(WSGIRequestHandler, self).get_environ() -+ - - def run(addr, port, wsgi_handler, ipv6=False, threading=False): - server_address = (addr, port) -Index: python-django-1.6.1/docs/howto/auth-remote-user.txt -=================================================================== ---- python-django-1.6.1.orig/docs/howto/auth-remote-user.txt 2015-01-13 07:47:20.530014471 -0500 -+++ python-django-1.6.1/docs/howto/auth-remote-user.txt 2015-01-13 07:47:20.530014471 -0500 -@@ -72,6 +72,22 @@ - - .. class:: django.contrib.auth.backends.RemoteUserBackend - -+.. warning:: -+ -+ Be very careful if using a ``RemoteUserMiddleware`` subclass with a custom -+ HTTP header. You must be sure that your front-end web server always sets or -+ strips that header based on the appropriate authentication checks, never -+ permitting an end-user to submit a fake (or "spoofed") header value. Since -+ the HTTP headers ``X-Auth-User`` and ``X-Auth_User`` (for example) both -+ normalize to the ``HTTP_X_AUTH_USER`` key in ``request.META``, you must -+ also check that your web server doesn't allow a spoofed header using -+ underscores in place of dashes. -+ -+ This warning doesn't apply to ``RemoteUserMiddleware`` in its default -+ configuration with ``header = 'REMOTE_USER'``, since a key that doesn't -+ start with ``HTTP_`` in ``request.META`` can only be set by your WSGI -+ server, not directly from an HTTP request header. -+ - If you need more control, you can create your own authentication backend - that inherits from ``RemoteUserBackend`` and overrides certain parts: - -Index: python-django-1.6.1/tests/servers/test_basehttp.py -=================================================================== ---- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ python-django-1.6.1/tests/servers/test_basehttp.py 2015-01-13 07:47:20.530014471 -0500 -@@ -0,0 +1,67 @@ -+import sys -+ -+from django.core.servers.basehttp import WSGIRequestHandler -+from django.test import TestCase -+from django.utils.six import BytesIO, StringIO -+ -+ -+class Stub(object): -+ def __init__(self, **kwargs): -+ self.__dict__.update(kwargs) -+ -+ -+class WSGIRequestHandlerTestCase(TestCase): -+ -+ def test_strips_underscore_headers(self): -+ """WSGIRequestHandler ignores headers containing underscores. -+ -+ This follows the lead of nginx and Apache 2.4, and is to avoid -+ ambiguity between dashes and underscores in mapping to WSGI environ, -+ which can have security implications. -+ """ -+ def test_app(environ, start_response): -+ """A WSGI app that just reflects its HTTP environ.""" -+ start_response('200 OK', []) -+ http_environ_items = sorted( -+ '%s:%s' % (k, v) for k, v in environ.items() -+ if k.startswith('HTTP_') -+ ) -+ yield (','.join(http_environ_items)).encode('utf-8') -+ -+ rfile = BytesIO() -+ rfile.write(b"GET / HTTP/1.0\r\n") -+ rfile.write(b"Some-Header: good\r\n") -+ rfile.write(b"Some_Header: bad\r\n") -+ rfile.write(b"Other_Header: bad\r\n") -+ rfile.seek(0) -+ -+ # WSGIRequestHandler closes the output file; we need to make this a -+ # no-op so we can still read its contents. -+ class UnclosableBytesIO(BytesIO): -+ def close(self): -+ pass -+ -+ wfile = UnclosableBytesIO() -+ -+ def makefile(mode, *a, **kw): -+ if mode == 'rb': -+ return rfile -+ elif mode == 'wb': -+ return wfile -+ -+ request = Stub(makefile=makefile) -+ server = Stub(base_environ={}, get_app=lambda: test_app) -+ -+ # We don't need to check stderr, but we don't want it in test output -+ old_stderr = sys.stderr -+ sys.stderr = StringIO() -+ try: -+ # instantiating a handler runs the request as side effect -+ WSGIRequestHandler(request, '192.168.0.2', server) -+ finally: -+ sys.stderr = old_stderr -+ -+ wfile.seek(0) -+ body = list(wfile.readlines())[-1] -+ -+ self.assertEqual(body, b'HTTP_SOME_HEADER:good') diff -Nru python-django-1.6.1/debian/patches/CVE-2015-0220.patch python-django-1.6.11/debian/patches/CVE-2015-0220.patch --- python-django-1.6.1/debian/patches/CVE-2015-0220.patch 2015-01-13 12:47:29.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2015-0220.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -Description: fix mitigated possible XSS attack via user-supplied redirect URLs -Author: Tim Graham - -Index: python-django-1.6.6/django/utils/http.py -=================================================================== ---- python-django-1.6.6.orig/django/utils/http.py 2014-08-20 16:13:48.000000000 -0400 -+++ python-django-1.6.6/django/utils/http.py 2015-01-13 07:30:50.854918976 -0500 -@@ -256,6 +256,7 @@ - """ - if not url: - return False -+ url = url.strip() - # Chrome treats \ completely as / - url = url.replace('\\', '/') - # Chrome considers any URL with more than two slashes to be absolute, but -Index: python-django-1.6.6/tests/utils_tests/test_http.py -=================================================================== ---- python-django-1.6.6.orig/tests/utils_tests/test_http.py 2014-08-20 16:13:48.000000000 -0400 -+++ python-django-1.6.6/tests/utils_tests/test_http.py 2015-01-13 07:30:50.854918976 -0500 -@@ -109,7 +109,8 @@ - 'http:/\//example.com', - 'http:\/example.com', - 'http:/\example.com', -- 'javascript:alert("XSS")'): -+ 'javascript:alert("XSS")', -+ '\njavascript:alert(x)'): - 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', - '/view/?param=https://example.com', diff -Nru python-django-1.6.1/debian/patches/CVE-2015-0221.patch python-django-1.6.11/debian/patches/CVE-2015-0221.patch --- python-django-1.6.1/debian/patches/CVE-2015-0221.patch 2015-01-13 12:47:34.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2015-0221.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,61 +0,0 @@ -Description: fix denial-of-service attack against django.views.static.serve -Author: Tim Graham - -Index: python-django-1.6.6/django/views/static.py -=================================================================== ---- python-django-1.6.6.orig/django/views/static.py 2014-08-20 16:13:48.000000000 -0400 -+++ python-django-1.6.6/django/views/static.py 2015-01-13 07:31:42.007288607 -0500 -@@ -17,6 +17,9 @@ - from django.utils.six.moves.urllib.parse import unquote - from django.utils.translation import ugettext as _, ugettext_noop - -+STREAM_CHUNK_SIZE = 4096 -+ -+ - def serve(request, path, document_root=None, show_indexes=False): - """ - Serve static files below a given point in the directory structure. -@@ -60,7 +63,8 @@ - return HttpResponseNotModified() - content_type, encoding = mimetypes.guess_type(fullpath) - content_type = content_type or 'application/octet-stream' -- response = CompatibleStreamingHttpResponse(open(fullpath, 'rb'), -+ f = open(fullpath, 'rb') -+ response = CompatibleStreamingHttpResponse(iter(lambda: f.read(STREAM_CHUNK_SIZE), b''), - content_type=content_type) - response["Last-Modified"] = http_date(statobj.st_mtime) - if stat.S_ISREG(statobj.st_mode): -Index: python-django-1.6.6/tests/view_tests/media/long-line.txt -=================================================================== ---- /dev/null 1970-01-01 00:00:00.000000000 +0000 -+++ python-django-1.6.6/tests/view_tests/media/long-line.txt 2015-01-13 07:31:42.007288607 -0500 -@@ -0,0 +1 @@ -+lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod tempor incididunt ut labore et dolore magna aliqua hic tempora est veritatis culpa fugiat doloribus fugit in sed harum veniam porro eveniet maxime labore assumenda non illum possimus aut vero laudantium cum magni numquam dolorem explicabo quidem quasi nesciunt ipsum deleniti facilis neque similique nisi ad magnam accusamus quae provident dolor ab atque modi laboriosam fuga suscipit ea beatae ipsam consequatur saepe dolore nulla error quo iusto expedita nemo commodi aspernatur aliquam enim reiciendis rerum necessitatibus recusandae sint amet placeat temporibus autem iste deserunt esse dolores reprehenderit doloremque pariatur velit maiores repellat dignissimos asperiores aperiam alias a corporis id praesentium voluptatibus soluta voluptatem sit molestiae quas odio facere nostrum laborum incidunt eaque nihil ullam rem mollitia at cumque iure tenetur tempore totam repudiandae quisquam quod architecto officia vitae consectetur cupiditate molestias delectus voluptates earum et impedit quibusdam odit sequi perferendis eius perspiciatis eos quam quaerat officiis sunt ratione consequuntur quia quis obcaecati repellendus exercitationem vel minima libero blanditiis eligendi minus dicta voluptas excepturi nam eum inventore voluptatum ducimus sapiente dolorum itaque ipsa qui omnis debitis voluptate quos aliquid accusantium ex illo corrupti ut adipisci natus animi distinctio optio nobis unde similique excepturi vero culpa molestias fugit dolorum non amet iure inventore nihil suscipit explicabo veritatis officiis distinctio nesciunt saepe incidunt reprehenderit porro vitae cumque alias ut deleniti expedita ratione odio magnam eligendi a nostrum laborum minus esse sit libero quaerat qui id illo voluptates soluta neque odit dolore consectetur ducimus nulla est nisi impedit quia sapiente ullam temporibus ipsam repudiandae delectus fugiat blanditiis maxime voluptatibus aspernatur ea ipsum quisquam sunt eius ipsa accusantium enim corporis earum sed sequi dicta accusamus dignissimos illum pariatur quos aut reiciendis obcaecati perspiciatis consequuntur nam modi praesentium cum repellat possimus iste atque quidem architecto recusandae harum eaque sint quae optio voluptate quod quasi beatae magni necessitatibus facilis aperiam repellendus nemo aliquam et quibusdam debitis itaque cupiditate laboriosam unde tempora commodi laudantium in placeat ad vel maiores aliquid hic tempore provident quas officia adipisci rem corrupti iusto natus eum rerum at ex quam eveniet totam dolor assumenda error eos doloribus labore fuga facere deserunt ab dolores consequatur veniam animi exercitationem asperiores mollitia minima numquam voluptatem voluptatum nobis molestiae voluptas omnis velit quis quo tenetur perferendis autem dolorem doloremque sequi vitae laudantium magnam quae adipisci expedita doloribus minus perferendis vero animi at quos iure facere nihil veritatis consectetur similique porro tenetur nobis fugiat quo ducimus qui soluta maxime placeat error sunt ullam quaerat provident eos minima ab harum ratione inventore unde sint dolorum deserunt veniam laborum quasi suscipit facilis eveniet voluptatibus est ipsum sapiente omnis vel repellat perspiciatis illo voluptate aliquid magni alias modi odit ea a voluptatem reiciendis recusandae mollitia eius distinctio amet atque voluptates obcaecati deleniti eligendi commodi debitis dolore laboriosam nam illum pariatur earum exercitationem velit in quas explicabo fugit asperiores itaque quam sit dolorem beatae quod cumque necessitatibus tempora dolores hic aperiam ex tempore ut neque maiores ad dicta voluptatum eum officia assumenda reprehenderit nisi cum molestiae et iusto quidem consequuntur repellendus saepe corrupti numquam culpa rerum incidunt dolor impedit iste sed non praesentium ipsam consequatur eaque possimus quia quibusdam excepturi aspernatur voluptas quisquam autem molestias aliquam corporis delectus nostrum labore nesciunt blanditiis quis enim accusamus nulla architecto fuga natus ipsa repudiandae cupiditate temporibus aut libero optio id officiis esse dignissimos odio totam doloremque accusantium nemo rem repudiandae aliquam accusamus autem minima reiciendis debitis quis ut ducimus quas dolore ratione neque velit repellat natus est error ea nam consequuntur rerum excepturi aspernatur quaerat cumque voluptatibus rem quasi eos unde architecto animi sunt veritatis delectus nulla at iusto repellendus dolorum obcaecati commodi earum assumenda quisquam cum officiis modi ab tempora harum vitae voluptatem explicabo alias maxime nostrum iure consectetur incidunt laudantium distinctio deleniti iste facere fugit libero illo nobis expedita perferendis labore similique beatae sint dicta dignissimos sapiente dolor soluta perspiciatis aut ad illum facilis totam necessitatibus eveniet temporibus reprehenderit quidem fugiat magni dolorem doloribus quibusdam eligendi fuga quae recusandae eum amet dolores asperiores voluptas inventore officia sit vel id vero nihil optio nisi magnam deserunt odit corrupti adipisci aliquid odio enim pariatur cupiditate suscipit voluptatum corporis porro mollitia eaque quia non quod consequatur ipsa nesciunt itaque exercitationem molestias molestiae atque in numquam quo ipsam nemo ex tempore ipsum saepe esse sed veniam a voluptates placeat accusantium quos laboriosam voluptate provident hic sequi quam doloremque eius impedit omnis possimus laborum tenetur praesentium et minus ullam blanditiis culpa qui aperiam maiores quidem numquam nulla -Index: python-django-1.6.6/tests/view_tests/tests/test_static.py -=================================================================== ---- python-django-1.6.6.orig/tests/view_tests/tests/test_static.py 2014-08-20 16:13:48.000000000 -0400 -+++ python-django-1.6.6/tests/view_tests/tests/test_static.py 2015-01-13 07:31:42.007288607 -0500 -@@ -9,7 +9,7 @@ - from django.test import TestCase - from django.test.utils import override_settings - from django.utils.http import http_date --from django.views.static import was_modified_since -+from django.views.static import was_modified_since, STREAM_CHUNK_SIZE - - from .. import urls - from ..urls import media_dir -@@ -33,6 +33,14 @@ - self.assertEqual(len(response_content), int(response['Content-Length'])) - self.assertEqual(mimetypes.guess_type(file_path)[1], response.get('Content-Encoding', None)) - -+ def test_chunked(self): -+ "The static view should stream files in chunks to avoid large memory usage" -+ response = self.client.get('/views/%s/%s' % (self.prefix, 'long-line.txt')) -+ first_chunk = next(response.streaming_content) -+ self.assertEqual(len(first_chunk), STREAM_CHUNK_SIZE) -+ second_chunk = next(response.streaming_content) -+ self.assertEqual(len(second_chunk), 1451) -+ - def test_unknown_mime_type(self): - response = self.client.get('/views/%s/file.unknown' % self.prefix) - self.assertEqual('application/octet-stream', response['Content-Type']) diff -Nru python-django-1.6.1/debian/patches/CVE-2015-0222.patch python-django-1.6.11/debian/patches/CVE-2015-0222.patch --- python-django-1.6.1/debian/patches/CVE-2015-0222.patch 2015-01-13 12:47:43.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2015-0222.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -Description: fix database denial-of-service with ModelMultipleChoiceField -Author: Tim Graham - -Index: python-django-1.6.1/django/forms/models.py -=================================================================== ---- python-django-1.6.1.orig/django/forms/models.py 2015-01-13 07:47:40.154160157 -0500 -+++ python-django-1.6.1/django/forms/models.py 2015-01-13 07:47:40.150160126 -0500 -@@ -1170,8 +1170,7 @@ - def to_python(self, value): - if not value: - return [] -- to_py = super(ModelMultipleChoiceField, self).to_python -- return [to_py(val) for val in value] -+ return list(self._check_values(value)) - - def clean(self, value): - if self.required and not value: -@@ -1180,7 +1179,29 @@ - return self.queryset.none() - if not isinstance(value, (list, tuple)): - raise ValidationError(self.error_messages['list'], code='list') -+ qs = self._check_values(value) -+ # Since this overrides the inherited ModelChoiceField.clean -+ # we run custom validators here -+ self.run_validators(value) -+ return qs -+ -+ def _check_values(self, value): -+ """ -+ Given a list of possible PK values, returns a QuerySet of the -+ corresponding objects. Raises a ValidationError if a given value is -+ invalid (not a valid PK, not in the queryset, etc.) -+ """ - key = self.to_field_name or 'pk' -+ # deduplicate given values to avoid creating many querysets or -+ # requiring the database backend deduplicate efficiently. -+ try: -+ value = frozenset(value) -+ except TypeError: -+ # list of lists isn't hashable, for example -+ raise ValidationError( -+ self.error_messages['list'], -+ code='list', -+ ) - for pk in value: - try: - self.queryset.filter(**{key: pk}) -@@ -1199,9 +1220,6 @@ - code='invalid_choice', - params={'value': val}, - ) -- # Since this overrides the inherited ModelChoiceField.clean -- # we run custom validators here -- self.run_validators(value) - return qs - - def prepare_value(self, value): -Index: python-django-1.6.1/tests/model_forms/tests.py -=================================================================== ---- python-django-1.6.1.orig/tests/model_forms/tests.py 2015-01-13 07:47:40.154160157 -0500 -+++ python-django-1.6.1/tests/model_forms/tests.py 2015-01-13 07:47:40.150160126 -0500 -@@ -1381,6 +1381,27 @@ -

-

''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) - -+ def test_show_hidden_initial_changed_queries_efficiently(self): -+ class WriterForm(forms.Form): -+ persons = forms.ModelMultipleChoiceField( -+ show_hidden_initial=True, queryset=Writer.objects.all()) -+ -+ writers = (Writer.objects.create(name=str(x)) for x in range(0, 50)) -+ writer_pks = tuple(x.pk for x in writers) -+ form = WriterForm(data={'initial-persons': writer_pks}) -+ with self.assertNumQueries(1): -+ self.assertTrue(form.has_changed()) -+ -+ def test_clean_does_deduplicate_values(self): -+ class WriterForm(forms.Form): -+ persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all()) -+ -+ person1 = Writer.objects.create(name="Person 1") -+ form = WriterForm(data={}) -+ queryset = form.fields['persons'].clean([str(person1.pk)] * 50) -+ sql, params = queryset.query.sql_with_params() -+ self.assertEqual(len(params), 1) -+ - def test_file_field(self): - # Test conditions when files is either not given or empty. - diff -Nru python-django-1.6.1/debian/patches/CVE-2015-2316.patch python-django-1.6.11/debian/patches/CVE-2015-2316.patch --- python-django-1.6.1/debian/patches/CVE-2015-2316.patch 2015-03-20 14:36:18.000000000 +0000 +++ python-django-1.6.11/debian/patches/CVE-2015-2316.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,155 +0,0 @@ -Description: fix denial-of-service possibility with strip_tags -Origin: backport, https://github.com/django/django/commit/d1503afd66ca8f2f8d3819ba8a60727e0ee66cec -Origin: backport, https://github.com/django/django/commit/f05f5c231a916082b5fa2e0980fbf7abeb390032 -Origin: backport, https://github.com/django/django/commit/c9b2feffeed46765ed6c4b74066059e2a6541735 -Origin: backport, https://github.com/django/django/commit/b6b3cb9899214a23ebb0f4ebf0e0b300b0ee524f - -Index: python-django-1.6.1/django/utils/html.py -=================================================================== ---- python-django-1.6.1.orig/django/utils/html.py 2013-12-12 14:37:59.000000000 -0500 -+++ python-django-1.6.1/django/utils/html.py 2015-03-20 10:34:26.957545358 -0400 -@@ -115,7 +115,10 @@ - - class MLStripper(HTMLParser): - def __init__(self): -- HTMLParser.__init__(self) -+ if six.PY2: -+ HTMLParser.__init__(self) -+ else: -+ HTMLParser.__init__(self, strict=False) - self.reset() - self.fed = [] - def handle_data(self, d): -@@ -127,16 +130,39 @@ - def get_data(self): - return ''.join(self.fed) - --def strip_tags(value): -- """Returns the given HTML with all tags stripped.""" -+ -+def _strip_once(value): -+ """ -+ Internal tag stripping utility used by strip_tags. -+ """ - s = MLStripper() - try: - s.feed(value) -- s.close() - except HTMLParseError: - return value -+ try: -+ s.close() -+ except (HTMLParseError, UnboundLocalError) as err: -+ # UnboundLocalError because of http://bugs.python.org/issue17802 -+ # on Python 3.2, triggered by strict=False mode of HTMLParser -+ return s.get_data() + s.rawdata - else: - return s.get_data() -+ -+ -+def strip_tags(value): -+ """Returns the given HTML with all tags stripped.""" -+ while True: -+ if not ('<' in value or '>' in value): -+ return value -+ new_value = _strip_once(value) -+ if len(new_value) >= len(value): -+ # _strip_once was not able to detect more tags or length increased -+ # due to http://bugs.python.org/issue20288 -+ # (affects Python 2 < 2.7.7 and Python 3 < 3.3.5) -+ return value -+ else: -+ value = new_value - strip_tags = allow_lazy(strip_tags) - - def remove_tags(html, tags): -Index: python-django-1.6.1/docs/ref/templates/builtins.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/templates/builtins.txt 2013-12-12 14:37:59.000000000 -0500 -+++ python-django-1.6.1/docs/ref/templates/builtins.txt 2015-03-20 10:34:16.045456760 -0400 -@@ -1997,7 +1997,7 @@ - striptags - ^^^^^^^^^ - --Strips all [X]HTML tags. -+Makes all possible efforts to strip all [X]HTML tags. - - For example:: - -@@ -2006,6 +2006,16 @@ - If ``value`` is ``"Joel a slug"``, the - output will be ``"Joel is a slug"``. - -+.. admonition:: No safety guarantee -+ -+ Note that ``striptags`` doesn't give any guarantee about its output being -+ entirely HTML safe, particularly with non valid HTML input. So **NEVER** -+ apply the ``safe`` filter to a ``striptags`` output. -+ If you are looking for something more robust, you can use the ``bleach`` -+ Python library, notably its `clean`_ method. -+ -+.. _clean: http://bleach.readthedocs.org/en/latest/clean.html -+ - .. templatefilter:: time - - time -Index: python-django-1.6.1/docs/ref/utils.txt -=================================================================== ---- python-django-1.6.1.orig/docs/ref/utils.txt 2013-12-12 14:37:59.000000000 -0500 -+++ python-django-1.6.1/docs/ref/utils.txt 2015-03-20 10:34:16.045456760 -0400 -@@ -616,17 +616,23 @@ - - .. function:: strip_tags(value) - -- Removes anything that looks like an html tag from the string, that is -- anything contained within ``<>``. -+ Tries to remove anything that looks like an HTML tag from the string, that -+ is anything contained within ``<>``. -+ Absolutely NO guaranty is provided about the resulting string being entirely -+ HTML safe. So NEVER mark safe the result of a ``strip_tag`` call without -+ escaping it first, for example with :func:`~django.utils.html.escape`. - - For example:: - - strip_tags(value) - -- If ``value`` is ``"Joel a slug"`` the -- return value will be ``"Joel is a slug"``. Note that ``strip_tags`` result -- may still contain unsafe HTML content, so you might use -- :func:`~django.utils.html.escape` to make it a safe string. -+ If ``value`` is ``"Joel a slug"`` -+ the return value will be ``"Joel is a slug"``. -+ -+ If you are looking for a more robust solution, take a look at the `bleach`_ -+ Python library. -+ -+ .. _bleach: https://pypi.python.org/pypi/bleach - - .. versionchanged:: 1.6 - -Index: python-django-1.6.1/tests/utils_tests/test_html.py -=================================================================== ---- python-django-1.6.1.orig/tests/utils_tests/test_html.py 2013-08-13 13:17:36.000000000 -0400 -+++ python-django-1.6.1/tests/utils_tests/test_html.py 2015-03-20 10:34:26.957545358 -0400 -@@ -80,10 +80,21 @@ - ('a

b

c', 'abc'), - ('de

f', 'def'), - ('foobar', 'foobar'), -+ # caused infinite loop on Pythons not patched with -+ # http://bugs.python.org/issue20288 -+ ('&gotcha&#;<>', '&gotcha&#;<>'), - ) - for value, output in items: - self.check_output(f, value, output) - -+ # Some convoluted syntax for which parsing may differ between python versions -+ output = html.strip_tags('ript>test</script>') -+ self.assertNotIn('&h') -+ self.assertNotIn('ript>"`` won't be safe even if + you apply ``|removetags:"script"``. So if the input is user provided, + **NEVER** apply the ``safe`` filter to a ``removetags`` output. If you are + looking for something more robust, you can use the ``bleach`` Python + library, notably its `clean`_ method. + +.. _clean: http://bleach.readthedocs.org/en/latest/clean.html .. templatefilter:: rjust @@ -1997,7 +2040,7 @@ striptags ^^^^^^^^^ -Strips all [X]HTML tags. +Makes all possible efforts to strip all [X]HTML tags. For example:: @@ -2006,6 +2049,16 @@ If ``value`` is ``"Joel a slug"``, the output will be ``"Joel is a slug"``. +.. admonition:: No safety guarantee + + Note that ``striptags`` doesn't give any guarantee about its output being + HTML safe, particularly with non valid HTML input. So **NEVER** apply the + ``safe`` filter to a ``striptags`` output. If you are looking for something + more robust, you can use the ``bleach`` Python library, notably its + `clean`_ method. + +.. _clean: http://bleach.readthedocs.org/en/latest/clean.html + .. templatefilter:: time time @@ -2454,10 +2507,9 @@ .. highlight:: html+django -If you're not using :class:`~django.template.RequestContext`, or if you need -more control over exactly where and how :setting:`STATIC_URL` is injected -into the template, you can use the :ttag:`get_static_prefix` template tag -instead:: +You should prefer the :ttag:`static` template tag, but if you need more control +over exactly where and how :setting:`STATIC_URL` is injected into the template, +you can use the :ttag:`get_static_prefix` template tag:: {% load static %} Hi! @@ -2481,6 +2533,8 @@ Similar to the :ttag:`get_static_prefix`, ``get_media_prefix`` populates a template variable with the media prefix :setting:`MEDIA_URL`, e.g.:: - + {% load static %} + + +By storing the value in a data attribute, we ensure it's escaped appropriately +if we want to use it in a JavaScript context. diff -Nru python-django-1.6.1/docs/ref/templates/index.txt python-django-1.6.11/docs/ref/templates/index.txt --- python-django-1.6.1/docs/ref/templates/index.txt 2013-06-04 21:37:51.000000000 +0000 +++ python-django-1.6.11/docs/ref/templates/index.txt 2015-03-18 23:40:08.000000000 +0000 @@ -5,7 +5,8 @@ Django's template engine provides a powerful mini-language for defining the user-facing layer of your application, encouraging a clean separation of application and presentation logic. Templates can be maintained by anyone with -an understanding of HTML; no knowledge of Python is required. +an understanding of HTML; no knowledge of Python is required. For introductory +material, see :doc:`/topics/templates` topic guide. .. toctree:: :maxdepth: 2 diff -Nru python-django-1.6.1/docs/ref/unicode.txt python-django-1.6.11/docs/ref/unicode.txt --- python-django-1.6.1/docs/ref/unicode.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/ref/unicode.txt 2015-03-18 23:50:43.000000000 +0000 @@ -17,8 +17,8 @@ a more restrictive encoding -- for example, latin1 (iso8859-1) -- you won't be able to store certain characters in the database, and information will be lost. -* MySQL users, refer to the `MySQL manual`_ (section 9.1.3.2 for MySQL 5.1) - for details on how to set or alter the database character set encoding. +* MySQL users, refer to the `MySQL manual`_ for details on how to set or alter + the database character set encoding. * PostgreSQL users, refer to the `PostgreSQL manual`_ (section 22.3.2 in PostgreSQL 9) for details on creating databases with the correct encoding. @@ -26,7 +26,7 @@ * SQLite users, there is nothing you need to do. SQLite always uses UTF-8 for internal encoding. -.. _MySQL manual: http://dev.mysql.com/doc/refman/5.1/en/charset-database.html +.. _MySQL manual: http://dev.mysql.com/doc/refman/5.6/en/charset-database.html .. _PostgreSQL manual: http://www.postgresql.org/docs/current/static/multibyte.html All of Django's database backends automatically convert Unicode strings into diff -Nru python-django-1.6.1/docs/ref/urlresolvers.txt python-django-1.6.11/docs/ref/urlresolvers.txt --- python-django-1.6.1/docs/ref/urlresolvers.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/ref/urlresolvers.txt 2015-03-18 23:40:08.000000000 +0000 @@ -12,17 +12,41 @@ .. function:: reverse(viewname, [urlconf=None, args=None, kwargs=None, current_app=None]) -``viewname`` is either the function name (either a function reference, or the -string version of the name, if you used that form in ``urlpatterns``) or the -:ref:`URL pattern name `. Normally, you won't need to -worry about the ``urlconf`` parameter and will only pass in the positional and -keyword arguments to use in the URL matching. For example:: +``viewname`` can be a string containing the Python path to the view object, a +:ref:`URL pattern name `, or the callable view object. +For example, given the following ``url``:: + + url(r'^archive/$', 'news.views.archive', name='news_archive') + +you can use any of the following to reverse the URL:: + + # using the Python path + reverse('news.views.archive') + + # using the named URL + reverse('news_archive') + + # passing a callable object + from news import views + reverse(views.archive) + +If the URL accepts arguments, you may pass them in ``args``. For example:: from django.core.urlresolvers import reverse def myview(request): return HttpResponseRedirect(reverse('arch-summary', args=[1945])) +You can also pass ``kwargs`` instead of ``args``. For example:: + + >>> reverse('admin:app_list', kwargs={'app_label': 'auth'}) + '/admin/auth/' + +``args`` and ``kwargs`` cannot be passed to ``reverse()`` at the same time. + +If no match can be made, ``reverse()`` raises a +:class:`~django.core.urlresolvers.NoReverseMatch` exception. + The ``reverse()`` function can reverse a large variety of regular expression patterns for URLs, but not every possible one. The main restriction at the moment is that the pattern cannot contain alternative choices using the @@ -36,12 +60,9 @@ namespaces into URLs on specific application instances, according to the :ref:`namespaced URL resolution strategy `. -You can use ``kwargs`` instead of ``args``. For example:: +The ``urlconf`` argument is the URLconf module containing the url patterns to +use for reversing. By default, the root URLconf for the current thread is used. - >>> reverse('admin:app_list', kwargs={'app_label': 'auth'}) - '/admin/auth/' - -``args`` and ``kwargs`` cannot be passed to ``reverse()`` at the same time. .. admonition:: Make sure your views are all correct. @@ -143,6 +164,11 @@ i.e., if the namespace is ``foo:bar``, then namespaces will be ``['foo', 'bar']``. + .. attribute:: ResolverMatch.view_name + + The name of the view that matches the URL, including the namespace if + there is one. + A :class:`ResolverMatch` object can then be interrogated to provide information about the URL pattern that matches a URL:: @@ -158,9 +184,9 @@ One possible use of :func:`~django.core.urlresolvers.resolve` would be to test whether a view would raise a ``Http404`` error before redirecting to it:: - from urlparse import urlparse from django.core.urlresolvers import resolve from django.http import HttpResponseRedirect, Http404 + from django.utils.six.moves.urllib.parse import urlparse def myview(request): next = request.META.get('HTTP_REFERER', None) or '/' diff -Nru python-django-1.6.1/docs/ref/urls.txt python-django-1.6.11/docs/ref/urls.txt --- python-django-1.6.1/docs/ref/urls.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/ref/urls.txt 2015-03-18 23:40:08.000000000 +0000 @@ -76,6 +76,9 @@ url(regex, view, kwargs=None, name=None, prefix='') +The ``kwargs`` parameter allows you to pass additional arguments to the view +function or method. See :ref:`views-extra-options` for an example. + See :ref:`Naming URL patterns ` for why the ``name`` parameter is useful. @@ -123,7 +126,7 @@ that should be called if the HTTP client has sent a request that caused an error condition and a response with a status code of 400. -By default, this is ``'django.views.defaults.permission_denied'``. That default +By default, this is ``'django.views.defaults.bad_request'``. That default value should suffice. See the documentation about :ref:`the 400 (bad request) view diff -Nru python-django-1.6.1/docs/ref/utils.txt python-django-1.6.11/docs/ref/utils.txt --- python-django-1.6.1/docs/ref/utils.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/ref/utils.txt 2015-03-18 23:50:43.000000000 +0000 @@ -616,17 +616,24 @@ .. function:: strip_tags(value) - Removes anything that looks like an html tag from the string, that is - anything contained within ``<>``. + Tries to remove anything that looks like an HTML tag from the string, that + is anything contained within ``<>``. + + Absolutely NO guarantee is provided about the resulting string being + HTML safe. So NEVER mark safe the result of a ``strip_tag`` call without + escaping it first, for example with :func:`~django.utils.html.escape`. For example:: strip_tags(value) - If ``value`` is ``"Joel a slug"`` the - return value will be ``"Joel is a slug"``. Note that ``strip_tags`` result - may still contain unsafe HTML content, so you might use - :func:`~django.utils.html.escape` to make it a safe string. + If ``value`` is ``"Joel a slug"`` + the return value will be ``"Joel is a slug"``. + + If you are looking for a more robust solution, take a look at the `bleach`_ + Python library. + + .. _bleach: https://pypi.python.org/pypi/bleach .. versionchanged:: 1.6 @@ -636,6 +643,13 @@ Removes a space-separated list of [X]HTML tag names from the output. + Absolutely NO guarantee is provided about the resulting string being HTML + safe. In particular, it doesn't work recursively, so the output of + ``remove_tags("ript>alert('XSS')ript>", "script")`` + won't remove the "nested" script tags. So if the ``value`` is untrusted, + NEVER mark safe the result of a ``remove_tags()`` call without escaping it + first, for example with :func:`~django.utils.html.escape`. + For example:: remove_tags(value, "b span") @@ -675,7 +689,7 @@ .. function:: urlencode(query, doseq=0) A version of Python's urllib.urlencode() function that can operate on - unicode strings. The parameters are first case to UTF-8 encoded strings + unicode strings. The parameters are first cast to UTF-8 encoded strings and then encoded as per normal. .. function:: cookie_date(epoch_seconds=None) @@ -790,6 +804,9 @@ Can be called multiple times on a single string. + For building up fragments of HTML, you should normally be using + :func:`django.utils.html.format_html` instead. + String marked safe will become unsafe again if modified. For example:: >>> mystr = 'Hello World ' @@ -1020,7 +1037,7 @@ current point in time. Exactly what's returned depends on the value of :setting:`USE_TZ`: - * If :setting:`USE_TZ` is ``False``, this will be be a + * If :setting:`USE_TZ` is ``False``, this will be a :ref:`naive ` datetime (i.e. a datetime without an associated timezone) that represents the current time in the system's local timezone. diff -Nru python-django-1.6.1/docs/releases/1.0-alpha-1.txt python-django-1.6.11/docs/releases/1.0-alpha-1.txt --- python-django-1.6.1/docs/releases/1.0-alpha-1.txt 2013-06-04 21:37:51.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.0-alpha-1.txt 2015-03-18 23:50:43.000000000 +0000 @@ -77,7 +77,7 @@ uploading process as well as streaming uploads of large files. Along with these improvements and additions, we've made a number of -of backwards-incompatible changes to the framework, as features have been +backwards-incompatible changes to the framework, as features have been fleshed out and APIs have been finalized for the 1.0 release. A complete guide to these changes will be available as part of the final Django 1.0 release, and a comprehensive list of backwards-incompatible diff -Nru python-django-1.6.1/docs/releases/1.1.txt python-django-1.6.11/docs/releases/1.1.txt --- python-django-1.6.1/docs/releases/1.1.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.1.txt 2015-03-18 23:50:43.000000000 +0000 @@ -50,7 +50,7 @@ As a result of this change, you will not be able to use the ``reset`` management command on any table made by a 64-bit machine. This is because the -the new generated name will not match the historically generated name; as a +new generated name will not match the historically generated name; as a result, the SQL constructed by the reset command will be invalid. If you need to reset an application that was created with 64-bit constraints, diff -Nru python-django-1.6.1/docs/releases/1.2-alpha-1.txt python-django-1.6.11/docs/releases/1.2-alpha-1.txt --- python-django-1.6.1/docs/releases/1.2-alpha-1.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.2-alpha-1.txt 2015-03-18 23:50:43.000000000 +0000 @@ -520,7 +520,7 @@ :attr:`django.contrib.admin.ModelAdmin.readonly_fields` has been added to enable non-editable fields in add/change pages for models and inlines. Field -and calculated values can be displayed along side editable fields. +and calculated values can be displayed alongside editable fields. Customizable syntax highlighting -------------------------------- diff -Nru python-django-1.6.1/docs/releases/1.2.txt python-django-1.6.11/docs/releases/1.2.txt --- python-django-1.6.1/docs/releases/1.2.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.2.txt 2015-03-18 23:50:43.000000000 +0000 @@ -721,8 +721,9 @@ :ref:`django.forms.formsets.formset_factory() ` and :ref:`django.forms.models.modelformset_factory() ` functions has changed slightly. This -change also affects the way the ``max_num`` argument is :ref:`used for -inline admin objects ` +change also affects the way the +:attr:`~django.contrib.admin.InlineModelAdmin.max_num` argument is used for +inline admin objects. Previously, the default value for ``max_num`` was ``0`` (zero). FormSets then used the boolean value of ``max_num`` to determine if a diff -Nru python-django-1.6.1/docs/releases/1.4.11.txt python-django-1.6.11/docs/releases/1.4.11.txt --- python-django-1.6.1/docs/releases/1.4.11.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.11.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,110 @@ +=========================== +Django 1.4.11 release notes +=========================== + +*April 21, 2014* + +Django 1.4.11 fixes three security issues in 1.4.10. Additionally, +Django's vendored version of six, :mod:`django.utils.six`, has been +upgraded to the latest release (1.6.1). + +Unexpected code execution using ``reverse()`` +============================================= + +Django's URL handling is based on a mapping of regex patterns +(representing the URLs) to callable views, and Django's own processing +consists of matching a requested URL against those patterns to +determine the appropriate view to invoke. + +Django also provides a convenience function -- +:func:`~django.core.urlresolvers.reverse` -- which performs this process +in the opposite direction. The ``reverse()`` function takes +information about a view and returns a URL which would invoke that +view. Use of ``reverse()`` is encouraged for application developers, +as the output of ``reverse()`` is always based on the current URL +patterns, meaning developers do not need to change other code when +making changes to URLs. + +One argument signature for ``reverse()`` is to pass a dotted Python +path to the desired view. In this situation, Django will import the +module indicated by that dotted path as part of generating the +resulting URL. If such a module has import-time side effects, those +side effects will occur. + +Thus it is possible for an attacker to cause unexpected code +execution, given the following conditions: + +1. One or more views are present which construct a URL based on user + input (commonly, a "next" parameter in a querystring indicating + where to redirect upon successful completion of an action). + +2. One or more modules are known to an attacker to exist on the + server's Python import path, which perform code execution with side + effects on importing. + +To remedy this, ``reverse()`` will now only accept and import dotted +paths based on the view-containing modules listed in the project's :doc:`URL +pattern configuration `, so as to ensure that only modules +the developer intended to be imported in this fashion can or will be imported. + +Caching of anonymous pages could reveal CSRF token +================================================== + +Django includes both a :doc:`caching framework ` and a system +for :doc:`preventing cross-site request forgery (CSRF) attacks +`. The CSRF-protection system is based on a random nonce +sent to the client in a cookie which must be sent by the client on future +requests and, in forms, a hidden value which must be submitted back with the +form. + +The caching framework includes an option to cache responses to +anonymous (i.e., unauthenticated) clients. + +When the first anonymous request to a given page is by a client which +did not have a CSRF cookie, the cache framework will also cache the +CSRF cookie and serve the same nonce to other anonymous clients who +do not have a CSRF cookie. This can allow an attacker to obtain a +valid CSRF cookie value and perform attacks which bypass the check for +the cookie. + +To remedy this, the caching framework will no longer cache such +responses. The heuristic for this will be: + +1. If the incoming request did not submit any cookies, and + +2. If the response did send one or more cookies, and + +3. If the ``Vary: Cookie`` header is set on the response, then the + response will not be cached. + +MySQL typecasting +================= + +The MySQL database is known to "typecast" on certain queries; for +example, when querying a table which contains string values, but using +a query which filters based on an integer value, MySQL will first +silently coerce the strings to integers and return a result based on that. + +If a query is performed without first converting values to the +appropriate type, this can produce unexpected results, similar to what +would occur if the query itself had been manipulated. + +Django's model field classes are aware of their own types and most +such classes perform explicit conversion of query arguments to the +correct database-level type before querying. However, three model +field classes did not correctly convert their arguments: + +* :class:`~django.db.models.FilePathField` +* :class:`~django.db.models.GenericIPAddressField` +* :class:`~django.db.models.IPAddressField` + +These three fields have been updated to convert their arguments to the +correct types before querying. + +Additionally, developers of custom model fields are now warned via +documentation to ensure their custom field classes will perform +appropriate type conversions, and users of the :meth:`raw() +` and :meth:`extra() +` query methods -- which allow the +developer to supply raw SQL or SQL fragments -- will be advised to ensure they +perform appropriate manual type conversions prior to executing queries. diff -Nru python-django-1.6.1/docs/releases/1.4.12.txt python-django-1.6.11/docs/releases/1.4.12.txt --- python-django-1.6.1/docs/releases/1.4.12.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.12.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,14 @@ +=========================== +Django 1.4.12 release notes +=========================== + +*April 28, 2014* + +Django 1.4.12 fixes a regression in the 1.4.11 security release. + +Bugfixes +======== + +* Restored the ability to :meth:`~django.core.urlresolvers.reverse` views + created using :func:`functools.partial()` + (`#22486 `_) diff -Nru python-django-1.6.1/docs/releases/1.4.13.txt python-django-1.6.11/docs/releases/1.4.13.txt --- python-django-1.6.1/docs/releases/1.4.13.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.13.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,47 @@ +=========================== +Django 1.4.13 release notes +=========================== + +*May 14, 2014* + +Django 1.4.13 fixes two security issues in 1.4.12. + +Caches may incorrectly be allowed to store and serve private data +================================================================= + +In certain situations, Django may allow caches to store private data +related to a particular session and then serve that data to requests +with a different session, or no session at all. This can lead to +information disclosure and can be a vector for cache poisoning. + +When using Django sessions, Django will set a ``Vary: Cookie`` header to +ensure caches do not serve cached data to requests from other sessions. +However, older versions of Internet Explorer (most likely only Internet +Explorer 6, and Internet Explorer 7 if run on Windows XP or Windows Server +2003) are unable to handle the ``Vary`` header in combination with many content +types. Therefore, Django would remove the header if the request was made by +Internet Explorer. + +To remedy this, the special behavior for these older Internet Explorer versions +has been removed, and the ``Vary`` header is no longer stripped from the response. +In addition, modifications to the ``Cache-Control`` header for all Internet Explorer +requests with a ``Content-Disposition`` header have also been removed as they +were found to have similar issues. + +Malformed redirect URLs from user input not correctly validated +=============================================================== + +The validation for redirects did not correctly validate some malformed URLs, +which are accepted by some browsers. This allows a user to be redirected to +an unsafe URL unexpectedly. + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +:doc:`i18n `) to redirect the user to an "on success" URL. +The security checks for these redirects (namely +``django.util.http.is_safe_url()``) did not correctly validate some malformed +URLs, such as `http:\\\\\\djangoproject.com`, which are accepted by some browsers +with more liberal URL parsing. + +To remedy this, the validation in ``is_safe_url()`` has been tightened to be able +to handle and correctly validate these malformed URLs. diff -Nru python-django-1.6.1/docs/releases/1.4.14.txt python-django-1.6.11/docs/releases/1.4.14.txt --- python-django-1.6.1/docs/releases/1.4.14.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.14.txt 2015-01-06 01:20:47.000000000 +0000 @@ -0,0 +1,64 @@ +=========================== +Django 1.4.14 release notes +=========================== + +*August 20, 2014* + +Django 1.4.14 fixes several security issues in 1.4.13. + +:func:`~django.core.urlresolvers.reverse()` could generate URLs pointing to other hosts +======================================================================================= + +In certain situations, URL reversing could generate scheme-relative URLs (URLs +starting with two slashes), which could unexpectedly redirect a user to a +different host. An attacker could exploit this, for example, by redirecting +users to a phishing site designed to ask for user's passwords. + +To remedy this, URL reversing now ensures that no URL starts with two slashes +(//), replacing the second slash with its URL encoded counterpart (%2F). This +approach ensures that semantics stay the same, while making the URL relative to +the domain and not to the scheme. + +File upload denial-of-service +============================= + +Before this release, Django's file upload handing in its default configuration +may degrade to producing a huge number of ``os.stat()`` system calls when a +duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce +a huge data-dependent slowdown that slowly worsens over time. The net result is +that given enough time, a user with the ability to upload files can cause poor +performance in the upload handler, eventually causing it to become very slow +simply by uploading 0-byte files. At this point, even a slow network connection +and few HTTP requests would be all that is necessary to make a site unavailable. + +We've remedied the issue by changing the algorithm for generating file names +if a file with the uploaded name already exists. +:meth:`Storage.get_available_name() +` now appends an +underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), +rather than iterating through an underscore followed by a number (e.g. ``"_1"``, +``"_2"``, etc.). + +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. + +Data leakage via query string manipulation in ``contrib.admin`` +=============================================================== + +In older versions of Django it was possible to reveal any field's data by +modifying the "popup" and "to_field" parameters of the query string on an admin +change form page. For example, requesting a URL like +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed +viewing the password hash of each user. While the admin requires users to have +permissions to view the change form pages in the first place, this could leak +data if you rely on users having access to view only certain fields on a model. + +To address the issue, an exception will now be raised if a ``to_field`` value +that isn't a related field to a model that has been registered with the admin +is specified. diff -Nru python-django-1.6.1/docs/releases/1.4.15.txt python-django-1.6.11/docs/releases/1.4.15.txt --- python-django-1.6.1/docs/releases/1.4.15.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.15.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,13 @@ +=========================== +Django 1.4.15 release notes +=========================== + +*September 2, 2014* + +Django 1.4.15 fixes a regression in the 1.4.14 security release. + +Bugfixes +======== + +* Allowed inherited and m2m fields to be referenced in the admin + (`#22486 `_) diff -Nru python-django-1.6.1/docs/releases/1.4.16.txt python-django-1.6.11/docs/releases/1.4.16.txt --- python-django-1.6.1/docs/releases/1.4.16.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.16.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,20 @@ +=========================== +Django 1.4.16 release notes +=========================== + +*October 22, 2014* + +Django 1.4.16 fixes a couple regressions in the 1.4.14 security release and a +bug preventing the use of some GEOS versions with GeoDjango. + +Bugfixes +======== + +* Allowed related many-to-many fields to be referenced in the admin + (`#23604 `_). + +* Allowed inline and hidden references to admin fields + (`#23431 `_). + +* Fixed parsing of the GEOS version string + (`#20036 `_). diff -Nru python-django-1.6.1/docs/releases/1.4.17.txt python-django-1.6.11/docs/releases/1.4.17.txt --- python-django-1.6.1/docs/releases/1.4.17.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.17.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,17 @@ +=========================== +Django 1.4.17 release notes +=========================== + +*January 2, 2015* + +Django 1.4.17 fixes a regression in the 1.4.14 security release. + +Additionally, Django's vendored version of six, :mod:`django.utils.six`, has +been upgraded to the latest release (1.9.0). + +Bugfixes +======== + +* Fixed a regression with dynamically generated inlines and allowed field + references in the admin + (`#23754 `_). diff -Nru python-django-1.6.1/docs/releases/1.4.18.txt python-django-1.6.11/docs/releases/1.4.18.txt --- python-django-1.6.1/docs/releases/1.4.18.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.18.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,68 @@ +=========================== +Django 1.4.18 release notes +=========================== + +*January 13, 2015* + +Django 1.4.18 fixes several security issues in 1.4.17 as well as a regression +on Python 2.5 in the 1.4.17 release. + +WSGI header spoofing via underscore/dash conflation +=================================================== + +When HTTP headers are placed into the WSGI environ, they are normalized by +converting to uppercase, converting all dashes to underscores, and prepending +`HTTP_`. For instance, a header ``X-Auth-User`` would become +``HTTP_X_AUTH_USER`` in the WSGI environ (and thus also in Django's +``request.META`` dictionary). + +Unfortunately, this means that the WSGI environ cannot distinguish between +headers containing dashes and headers containing underscores: ``X-Auth-User`` +and ``X-Auth_User`` both become ``HTTP_X_AUTH_USER``. This means that if a +header is used in a security-sensitive way (for instance, passing +authentication information along from a front-end proxy), even if the proxy +carefully strips any incoming value for ``X-Auth-User``, an attacker may be +able to provide an ``X-Auth_User`` header (with underscore) and bypass this +protection. + +In order to prevent such attacks, both Nginx and Apache 2.4+ strip all headers +containing underscores from incoming requests by default. Django's built-in +development server now does the same. Django's development server is not +recommended for production use, but matching the behavior of common production +servers reduces the surface area for behavior changes during deployment. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.util.http.is_safe_url()``) didn't strip leading +whitespace on the tested URL and as such considered URLs like +``\njavascript:...`` safe. If a developer relied on ``is_safe_url()`` to +provide safe redirect targets and put such a URL into a link, they could suffer +from a XSS attack. This bug doesn't affect Django currently, since we only put +this URL into the ``Location`` response header and browsers seem to ignore +JavaScript there. + +Denial-of-service attack against ``django.views.static.serve`` +============================================================== + +In older versions of Django, the :func:`django.views.static.serve` view read +the files it served one line at a time. Therefore, a big file with no newlines +would result in memory usage equal to the size of that file. An attacker could +exploit this and launch a denial-of-service attack by simultaneously requesting +many large files. This view now reads the file in chunks to prevent large +memory usage. + +Note, however, that this view has always carried a warning that it is not +hardened for production use and should be used only as a development aid. Now +may be a good time to audit your project and serve your files in production +using a real front-end web server if you are not doing so. + +Bugfixes +======== + +* To maintain compatibility with Python 2.5, Django's vendored version of six, + :mod:`django.utils.six`, has been downgraded to 1.8.0 which is the last + version to support Python 2.5. diff -Nru python-django-1.6.1/docs/releases/1.4.19.txt python-django-1.6.11/docs/releases/1.4.19.txt --- python-django-1.6.1/docs/releases/1.4.19.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.19.txt 2015-01-27 17:27:56.000000000 +0000 @@ -0,0 +1,16 @@ +=========================== +Django 1.4.19 release notes +=========================== + +*January 27, 2015* + +Django 1.4.19 fixes a regression in the 1.4.18 security release. + +Bugfixes +======== + +* ``GZipMiddleware`` now supports streaming responses. As part of the 1.4.18 + security release, the ``django.views.static.serve()`` function was altered + to stream the files it serves. Unfortunately, the ``GZipMiddleware`` consumed + the stream prematurely and prevented files from being served properly + (`#24158 `_). diff -Nru python-django-1.6.1/docs/releases/1.4.20.txt python-django-1.6.11/docs/releases/1.4.20.txt --- python-django-1.6.1/docs/releases/1.4.20.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.4.20.txt 2015-03-18 23:50:46.000000000 +0000 @@ -0,0 +1,26 @@ +=========================== +Django 1.4.20 release notes +=========================== + +*March 18, 2015* + +Django 1.4.20 fixes one security issue in 1.4.19. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, 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 as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``. diff -Nru python-django-1.6.1/docs/releases/1.5.10.txt python-django-1.6.11/docs/releases/1.5.10.txt --- python-django-1.6.1/docs/releases/1.5.10.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.10.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,13 @@ +=========================== +Django 1.5.10 release notes +=========================== + +*September 2, 2014* + +Django 1.5.10 fixes a regression in the 1.5.9 security release. + +Bugfixes +======== + +* Allowed inherited and m2m fields to be referenced in the admin + (`#22486 `_) diff -Nru python-django-1.6.1/docs/releases/1.5.11.txt python-django-1.6.11/docs/releases/1.5.11.txt --- python-django-1.6.1/docs/releases/1.5.11.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.11.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,16 @@ +=========================== +Django 1.5.11 release notes +=========================== + +*October 22, 2014* + +Django 1.5.11 fixes a couple regressions in the 1.5.9 security release. + +Bugfixes +======== + +* Allowed related many-to-many fields to be referenced in the admin + (`#23604 `_). + +* Allowed inline and hidden references to admin fields + (`#23431 `_). diff -Nru python-django-1.6.1/docs/releases/1.5.12.txt python-django-1.6.11/docs/releases/1.5.12.txt --- python-django-1.6.1/docs/releases/1.5.12.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.12.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,14 @@ +=========================== +Django 1.5.12 release notes +=========================== + +*January 2, 2015* + +Django 1.5.12 fixes a regression in the 1.5.9 security release. + +Bugfixes +======== + +* Fixed a regression with dynamically generated inlines and allowed field + references in the admin + (`#23754 `_). diff -Nru python-django-1.6.1/docs/releases/1.5.6.txt python-django-1.6.11/docs/releases/1.5.6.txt --- python-django-1.6.1/docs/releases/1.5.6.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.6.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,119 @@ +========================== +Django 1.5.6 release notes +========================== + +*April 21, 2014* + +Django 1.5.6 fixes several bugs in 1.5.5, including three security +issues. + +Unexpected code execution using ``reverse()`` +============================================= + +Django's URL handling is based on a mapping of regex patterns +(representing the URLs) to callable views, and Django's own processing +consists of matching a requested URL against those patterns to +determine the appropriate view to invoke. + +Django also provides a convenience function -- +:func:`~django.core.urlresolvers.reverse` -- which performs this process +in the opposite direction. The ``reverse()`` function takes +information about a view and returns a URL which would invoke that +view. Use of ``reverse()`` is encouraged for application developers, +as the output of ``reverse()`` is always based on the current URL +patterns, meaning developers do not need to change other code when +making changes to URLs. + +One argument signature for ``reverse()`` is to pass a dotted Python +path to the desired view. In this situation, Django will import the +module indicated by that dotted path as part of generating the +resulting URL. If such a module has import-time side effects, those +side effects will occur. + +Thus it is possible for an attacker to cause unexpected code +execution, given the following conditions: + +1. One or more views are present which construct a URL based on user + input (commonly, a "next" parameter in a querystring indicating + where to redirect upon successful completion of an action). + +2. One or more modules are known to an attacker to exist on the + server's Python import path, which perform code execution with side + effects on importing. + +To remedy this, ``reverse()`` will now only accept and import dotted +paths based on the view-containing modules listed in the project's :doc:`URL +pattern configuration `, so as to ensure that only modules +the developer intended to be imported in this fashion can or will be imported. + +Caching of anonymous pages could reveal CSRF token +================================================== + +Django includes both a :doc:`caching framework ` and a system +for :doc:`preventing cross-site request forgery (CSRF) attacks +`. The CSRF-protection system is based on a random nonce +sent to the client in a cookie which must be sent by the client on future +requests and, in forms, a hidden value which must be submitted back with the +form. + +The caching framework includes an option to cache responses to +anonymous (i.e., unauthenticated) clients. + +When the first anonymous request to a given page is by a client which +did not have a CSRF cookie, the cache framework will also cache the +CSRF cookie and serve the same nonce to other anonymous clients who +do not have a CSRF cookie. This can allow an attacker to obtain a +valid CSRF cookie value and perform attacks which bypass the check for +the cookie. + +To remedy this, the caching framework will no longer cache such +responses. The heuristic for this will be: + +1. If the incoming request did not submit any cookies, and + +2. If the response did send one or more cookies, and + +3. If the ``Vary: Cookie`` header is set on the response, then the + response will not be cached. + +MySQL typecasting +================= + +The MySQL database is known to "typecast" on certain queries; for +example, when querying a table which contains string values, but using +a query which filters based on an integer value, MySQL will first +silently coerce the strings to integers and return a result based on that. + +If a query is performed without first converting values to the +appropriate type, this can produce unexpected results, similar to what +would occur if the query itself had been manipulated. + +Django's model field classes are aware of their own types and most +such classes perform explicit conversion of query arguments to the +correct database-level type before querying. However, three model +field classes did not correctly convert their arguments: + +* :class:`~django.db.models.FilePathField` +* :class:`~django.db.models.GenericIPAddressField` +* :class:`~django.db.models.IPAddressField` + +These three fields have been updated to convert their arguments to the +correct types before querying. + +Additionally, developers of custom model fields are now warned via +documentation to ensure their custom field classes will perform +appropriate type conversions, and users of the :meth:`raw() +` and :meth:`extra() +` query methods -- which allow the +developer to supply raw SQL or SQL fragments -- will be advised to ensure they +perform appropriate manual type conversions prior to executing queries. + +Bugfixes +======== + +* Fixed :class:`~django.contrib.auth.backends.ModelBackend` raising + ``UnboundLocalError`` if :func:`~django.contrib.auth.get_user_model` + raised an error (#21439). + +Additionally, Django's vendored version of six, :mod:`django.utils.six`, +has been upgraded to the latest release (1.6.1). diff -Nru python-django-1.6.1/docs/releases/1.5.7.txt python-django-1.6.11/docs/releases/1.5.7.txt --- python-django-1.6.1/docs/releases/1.5.7.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.7.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,14 @@ +========================== +Django 1.5.7 release notes +========================== + +*April 28, 2014* + +Django 1.5.7 fixes a regression in the 1.5.6 security release. + +Bugfixes +======== + +* Restored the ability to :meth:`~django.core.urlresolvers.reverse` views + created using :func:`functools.partial()` + (`#22486 `_) diff -Nru python-django-1.6.1/docs/releases/1.5.8.txt python-django-1.6.11/docs/releases/1.5.8.txt --- python-django-1.6.1/docs/releases/1.5.8.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.8.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,47 @@ +========================== +Django 1.5.8 release notes +========================== + +*May 14, 2014* + +Django 1.5.8 fixes two security issues in 1.5.8. + +Caches may incorrectly be allowed to store and serve private data +================================================================= + +In certain situations, Django may allow caches to store private data +related to a particular session and then serve that data to requests +with a different session, or no session at all. This can lead to +information disclosure and can be a vector for cache poisoning. + +When using Django sessions, Django will set a ``Vary: Cookie`` header to +ensure caches do not serve cached data to requests from other sessions. +However, older versions of Internet Explorer (most likely only Internet +Explorer 6, and Internet Explorer 7 if run on Windows XP or Windows Server +2003) are unable to handle the ``Vary`` header in combination with many content +types. Therefore, Django would remove the header if the request was made by +Internet Explorer. + +To remedy this, the special behavior for these older Internet Explorer versions +has been removed, and the ``Vary`` header is no longer stripped from the response. +In addition, modifications to the ``Cache-Control`` header for all Internet Explorer +requests with a ``Content-Disposition`` header have also been removed as they +were found to have similar issues. + +Malformed redirect URLs from user input not correctly validated +=============================================================== + +The validation for redirects did not correctly validate some malformed URLs, +which are accepted by some browsers. This allows a user to be redirected to +an unsafe URL unexpectedly. + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +:doc:`i18n `) to redirect the user to an "on success" URL. +The security checks for these redirects (namely +``django.util.http.is_safe_url()``) did not correctly validate some malformed +URLs, such as `http:\\\\\\djangoproject.com`, which are accepted by some browsers +with more liberal URL parsing. + +To remedy this, the validation in ``is_safe_url()`` has been tightened to be able +to handle and correctly validate these malformed URLs. diff -Nru python-django-1.6.1/docs/releases/1.5.9.txt python-django-1.6.11/docs/releases/1.5.9.txt --- python-django-1.6.1/docs/releases/1.5.9.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.9.txt 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,64 @@ +========================== +Django 1.5.9 release notes +========================== + +*August 20, 2014* + +Django 1.5.9 fixes several security issues in 1.5.8. + +:func:`~django.core.urlresolvers.reverse()` could generate URLs pointing to other hosts +======================================================================================= + +In certain situations, URL reversing could generate scheme-relative URLs (URLs +starting with two slashes), which could unexpectedly redirect a user to a +different host. An attacker could exploit this, for example, by redirecting +users to a phishing site designed to ask for user's passwords. + +To remedy this, URL reversing now ensures that no URL starts with two slashes +(//), replacing the second slash with its URL encoded counterpart (%2F). This +approach ensures that semantics stay the same, while making the URL relative to +the domain and not to the scheme. + +File upload denial-of-service +============================= + +Before this release, Django's file upload handing in its default configuration +may degrade to producing a huge number of ``os.stat()`` system calls when a +duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce +a huge data-dependent slowdown that slowly worsens over time. The net result is +that given enough time, a user with the ability to upload files can cause poor +performance in the upload handler, eventually causing it to become very slow +simply by uploading 0-byte files. At this point, even a slow network connection +and few HTTP requests would be all that is necessary to make a site unavailable. + +We've remedied the issue by changing the algorithm for generating file names +if a file with the uploaded name already exists. +:meth:`Storage.get_available_name() +` now appends an +underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), +rather than iterating through an underscore followed by a number (e.g. ``"_1"``, +``"_2"``, etc.). + +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. + +Data leakage via query string manipulation in ``contrib.admin`` +=============================================================== + +In older versions of Django it was possible to reveal any field's data by +modifying the "popup" and "to_field" parameters of the query string on an admin +change form page. For example, requesting a URL like +``/admin/auth/user/?pop=1&t=password`` and viewing the page's HTML allowed +viewing the password hash of each user. While the admin requires users to have +permissions to view the change form pages in the first place, this could leak +data if you rely on users having access to view only certain fields on a model. + +To address the issue, an exception will now be raised if a ``to_field`` value +that isn't a related field to a model that has been registered with the admin +is specified. diff -Nru python-django-1.6.1/docs/releases/1.5-alpha-1.txt python-django-1.6.11/docs/releases/1.5-alpha-1.txt --- python-django-1.6.1/docs/releases/1.5-alpha-1.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5-alpha-1.txt 2015-03-18 23:50:43.000000000 +0000 @@ -559,7 +559,7 @@ * Uploaded files are no longer created as executable by default. If you need them to be executable change :setting:`FILE_UPLOAD_PERMISSIONS` to your - needs. The new default value is ``0666`` (octal) and the current umask value + needs. The new default value is ``0o666`` (octal) and the current umask value is first masked out. * The :ref:`F() expressions ` supported bitwise operators by diff -Nru python-django-1.6.1/docs/releases/1.5-beta-1.txt python-django-1.6.11/docs/releases/1.5-beta-1.txt --- python-django-1.6.1/docs/releases/1.5-beta-1.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5-beta-1.txt 2015-03-18 23:50:43.000000000 +0000 @@ -598,7 +598,7 @@ * Uploaded files are no longer created as executable by default. If you need them to be executable change :setting:`FILE_UPLOAD_PERMISSIONS` to your - needs. The new default value is ``0666`` (octal) and the current umask value + needs. The new default value is ``0o666`` (octal) and the current umask value is first masked out. * The :ref:`F() expressions ` supported bitwise operators by diff -Nru python-django-1.6.1/docs/releases/1.5.txt python-django-1.6.11/docs/releases/1.5.txt --- python-django-1.6.1/docs/releases/1.5.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.5.txt 2015-03-18 23:50:43.000000000 +0000 @@ -354,7 +354,7 @@ .. warning:: In addition to the changes outlined in this section, be sure to review the - :doc:`deprecation plan ` for any features that + :ref:`deprecation plan ` for any features that have been removed. If you haven't updated your code within the deprecation timeline for a given feature, its removal may appear as a backwards incompatible change. @@ -676,7 +676,7 @@ * Uploaded files are no longer created as executable by default. If you need them to be executable change :setting:`FILE_UPLOAD_PERMISSIONS` to your - needs. The new default value is ``0666`` (octal) and the current umask value + needs. The new default value is ``0o666`` (octal) and the current umask value is first masked out. * The :ref:`F() expressions ` supported bitwise operators by diff -Nru python-django-1.6.1/docs/releases/1.6.10.txt python-django-1.6.11/docs/releases/1.6.10.txt --- python-django-1.6.1/docs/releases/1.6.10.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.10.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,69 @@ +=========================== +Django 1.6.10 release notes +=========================== + +*January 13, 2015* + +Django 1.6.10 fixes several security issues in 1.6.9. + +WSGI header spoofing via underscore/dash conflation +=================================================== + +When HTTP headers are placed into the WSGI environ, they are normalized by +converting to uppercase, converting all dashes to underscores, and prepending +`HTTP_`. For instance, a header ``X-Auth-User`` would become +``HTTP_X_AUTH_USER`` in the WSGI environ (and thus also in Django's +``request.META`` dictionary). + +Unfortunately, this means that the WSGI environ cannot distinguish between +headers containing dashes and headers containing underscores: ``X-Auth-User`` +and ``X-Auth_User`` both become ``HTTP_X_AUTH_USER``. This means that if a +header is used in a security-sensitive way (for instance, passing +authentication information along from a front-end proxy), even if the proxy +carefully strips any incoming value for ``X-Auth-User``, an attacker may be +able to provide an ``X-Auth_User`` header (with underscore) and bypass this +protection. + +In order to prevent such attacks, both Nginx and Apache 2.4+ strip all headers +containing underscores from incoming requests by default. Django's built-in +development server now does the same. Django's development server is not +recommended for production use, but matching the behavior of common production +servers reduces the surface area for behavior changes during deployment. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.util.http.is_safe_url()``) didn't strip leading +whitespace on the tested URL and as such considered URLs like +``\njavascript:...`` safe. If a developer relied on ``is_safe_url()`` to +provide safe redirect targets and put such a URL into a link, they could suffer +from a XSS attack. This bug doesn't affect Django currently, since we only put +this URL into the ``Location`` response header and browsers seem to ignore +JavaScript there. + +Denial-of-service attack against ``django.views.static.serve`` +============================================================== + +In older versions of Django, the :func:`django.views.static.serve` view read +the files it served one line at a time. Therefore, a big file with no newlines +would result in memory usage equal to the size of that file. An attacker could +exploit this and launch a denial-of-service attack by simultaneously requesting +many large files. This view now reads the file in chunks to prevent large +memory usage. + +Note, however, that this view has always carried a warning that it is not +hardened for production use and should be used only as a development aid. Now +may be a good time to audit your project and serve your files in production +using a real front-end web server if you are not doing so. + +Database denial-of-service with ``ModelMultipleChoiceField`` +============================================================ + +Given a form that uses ``ModelMultipleChoiceField`` and +``show_hidden_initial=True`` (not a documented API), it was possible for a user +to cause an unreasonable number of SQL queries by submitting duplicate values +for the field's data. The validation logic in ``ModelMultipleChoiceField`` now +deduplicates submitted values to address this issue. diff -Nru python-django-1.6.1/docs/releases/1.6.11.txt python-django-1.6.11/docs/releases/1.6.11.txt --- python-django-1.6.1/docs/releases/1.6.11.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.11.txt 2015-03-18 23:50:46.000000000 +0000 @@ -0,0 +1,43 @@ +=========================== +Django 1.6.11 release notes +=========================== + +*March 18, 2015* + +Django 1.6.11 fixes two security issues in 1.6.10. + +Denial-of-service possibility with ``strip_tags()`` +=================================================== + +Last year :func:`~django.utils.html.strip_tags` was changed to work +iteratively. The problem is that the size of the input it's processing can +increase on each iteration which results in an infinite loop in +``strip_tags()``. This issue only affects versions of Python that haven't +received `a bugfix in HTMLParser `_; namely +Python < 2.7.7 and 3.3.5. Some operating system vendors have also backported +the fix for the Python bug into their packages of earlier versions. + +To remedy this issue, ``strip_tags()`` will now return the original input if +it detects the length of the string it's processing increases. Remember that +absolutely NO guarantee is provided about the results of ``strip_tags()`` being +HTML safe. So NEVER mark safe the result of a ``strip_tags()`` call without +escaping it first, for example with :func:`~django.utils.html.escape`. + +Mitigated possible XSS attack via user-supplied redirect URLs +============================================================= + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login` and :doc:`i18n `) +to redirect the user to an "on success" URL. The security checks for these +redirects (namely ``django.utils.http.is_safe_url()``) accepted URLs with +leading control characters and so considered URLs like ``\x08javascript:...`` +safe. This issue doesn't affect Django currently, since we only put this URL +into the ``Location`` response header and browsers seem to ignore JavaScript +there. Browsers we tested also treat URLs prefixed with control characters such +as ``%08//example.com`` as relative paths so redirection to an unsafe target +isn't a problem either. + +However, 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 as some browsers such as Google Chrome ignore control +characters at the start of a URL in an anchor ``href``. diff -Nru python-django-1.6.1/docs/releases/1.6.1.txt python-django-1.6.11/docs/releases/1.6.1.txt --- python-django-1.6.1/docs/releases/1.6.1.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.1.txt 2015-03-09 15:58:12.000000000 +0000 @@ -2,7 +2,7 @@ Django 1.6.1 release notes ========================== -*Under development* +*December 12, 2013* This is Django 1.6.1, a bugfix release for Django 1.6. In addition to the bug fixes listed below, translations submitted since the 1.6 release are also diff -Nru python-django-1.6.1/docs/releases/1.6.2.txt python-django-1.6.11/docs/releases/1.6.2.txt --- python-django-1.6.1/docs/releases/1.6.2.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.2.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,52 @@ +========================== +Django 1.6.2 release notes +========================== + +*February 6, 2014* + +This is Django 1.6.2, a bugfix release for Django 1.6. Django 1.6.2 fixes +several bugs in 1.6.1: + +* Prevented the base geometry object of a prepared geometry to be garbage + collected, which could lead to crash Django + (`#21662 `_). + +* Fixed a crash when executing the :djadmin:`changepassword` command when the + user object representation contained non-ASCII characters + (`#21627 `_). + +* The :djadmin:`collectstatic` command will raise an error rather than + default to using the current working directory if :setting:`STATIC_ROOT` is + not set. Combined with the ``--clear`` option, the previous behavior could + wipe anything below the current working directory + (`#21581 `_). + +* Fixed mail encoding on Python 3.3.3+ + (`#21093 `_). + +* Fixed an issue where when + ``settings.DATABASES['default']['AUTOCOMMIT'] = False``, the connection + wasn't in autocommit mode but Django pretended it was. + +* Fixed a regression in multiple-table inheritance ``exclude()`` queries + (`#21787 `_). + +* Added missing items to ``django.utils.timezone.__all__`` + (`#21880 `_). + +* Fixed a field misalignment issue with ``select_related()`` and model + inheritance + (`#21413 `_). + +* Fixed join promotion for negated ``AND`` conditions + (`#21748 `_). + +* Oracle database introspection now works with boolean and float fields + (`#19884 `_). + +* Fixed an issue where lazy objects weren't actually marked as safe when passed + through :func:`~django.utils.safestring.mark_safe` and could end up being + double-escaped (`#21882 `_). + +Additionally, Django's vendored version of six, :mod:`django.utils.six` has been +upgraded to the latest release (1.5.2). diff -Nru python-django-1.6.1/docs/releases/1.6.3.txt python-django-1.6.11/docs/releases/1.6.3.txt --- python-django-1.6.1/docs/releases/1.6.3.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.3.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,186 @@ +========================== +Django 1.6.3 release notes +========================== + +*April 21, 2014* + +Django 1.6.3 fixes several bugs in 1.6.2, including three security issues, +and makes one backwards-incompatible change: + +Unexpected code execution using ``reverse()`` +============================================= + +Django's URL handling is based on a mapping of regex patterns +(representing the URLs) to callable views, and Django's own processing +consists of matching a requested URL against those patterns to +determine the appropriate view to invoke. + +Django also provides a convenience function -- +:func:`~django.core.urlresolvers.reverse` -- which performs this process +in the opposite direction. The ``reverse()`` function takes +information about a view and returns a URL which would invoke that +view. Use of ``reverse()`` is encouraged for application developers, +as the output of ``reverse()`` is always based on the current URL +patterns, meaning developers do not need to change other code when +making changes to URLs. + +One argument signature for ``reverse()`` is to pass a dotted Python +path to the desired view. In this situation, Django will import the +module indicated by that dotted path as part of generating the +resulting URL. If such a module has import-time side effects, those +side effects will occur. + +Thus it is possible for an attacker to cause unexpected code +execution, given the following conditions: + +1. One or more views are present which construct a URL based on user + input (commonly, a "next" parameter in a querystring indicating + where to redirect upon successful completion of an action). + +2. One or more modules are known to an attacker to exist on the + server's Python import path, which perform code execution with side + effects on importing. + +To remedy this, ``reverse()`` will now only accept and import dotted +paths based on the view-containing modules listed in the project's :doc:`URL +pattern configuration `, so as to ensure that only modules +the developer intended to be imported in this fashion can or will be imported. + +Caching of anonymous pages could reveal CSRF token +================================================== + +Django includes both a :doc:`caching framework ` and a system +for :doc:`preventing cross-site request forgery (CSRF) attacks +`. The CSRF-protection system is based on a random nonce +sent to the client in a cookie which must be sent by the client on future +requests and, in forms, a hidden value which must be submitted back with the +form. + +The caching framework includes an option to cache responses to +anonymous (i.e., unauthenticated) clients. + +When the first anonymous request to a given page is by a client which +did not have a CSRF cookie, the cache framework will also cache the +CSRF cookie and serve the same nonce to other anonymous clients who +do not have a CSRF cookie. This can allow an attacker to obtain a +valid CSRF cookie value and perform attacks which bypass the check for +the cookie. + +To remedy this, the caching framework will no longer cache such +responses. The heuristic for this will be: + +1. If the incoming request did not submit any cookies, and + +2. If the response did send one or more cookies, and + +3. If the ``Vary: Cookie`` header is set on the response, then the + response will not be cached. + +MySQL typecasting +================= + +The MySQL database is known to "typecast" on certain queries; for +example, when querying a table which contains string values, but using +a query which filters based on an integer value, MySQL will first +silently coerce the strings to integers and return a result based on that. + +If a query is performed without first converting values to the +appropriate type, this can produce unexpected results, similar to what +would occur if the query itself had been manipulated. + +Django's model field classes are aware of their own types and most +such classes perform explicit conversion of query arguments to the +correct database-level type before querying. However, three model +field classes did not correctly convert their arguments: + +* :class:`~django.db.models.FilePathField` +* :class:`~django.db.models.GenericIPAddressField` +* :class:`~django.db.models.IPAddressField` + +These three fields have been updated to convert their arguments to the +correct types before querying. + +Additionally, developers of custom model fields are now warned via +documentation to ensure their custom field classes will perform +appropriate type conversions, and users of the :meth:`raw() +` and :meth:`extra() +` query methods -- which allow the +developer to supply raw SQL or SQL fragments -- will be advised to ensure they +perform appropriate manual type conversions prior to executing queries. + +``select_for_update()`` requires a transaction +============================================== + +Historically, queries that use +:meth:`~django.db.models.query.QuerySet.select_for_update()` could be +executed in autocommit mode, outside of a transaction. Before Django +1.6, Django's automatic transactions mode allowed this to be used to +lock records until the next write operation. Django 1.6 introduced +database-level autocommit; since then, execution in such a context +voids the effect of ``select_for_update()``. It is, therefore, assumed +now to be an error and raises an exception. + +This change was made because such errors can be caused by including an +app which expects global transactions (e.g. :setting:`ATOMIC_REQUESTS +` set to ``True``), or Django's old autocommit +behavior, in a project which runs without them; and further, such +errors may manifest as data-corruption bugs. + +This change may cause test failures if you use ``select_for_update()`` +in a test class which is a subclass of +:class:`~django.test.TransactionTestCase` rather than +:class:`~django.test.TestCase`. + +Other bugfixes and changes +========================== + +* Content retrieved from the GeoIP library is now properly decoded from its + default ``iso-8859-1`` encoding + (`#21996 `_). + +* Fixed ``AttributeError`` when using + :meth:`~django.db.models.query.QuerySet.bulk_create` with ``ForeignObject`` + (`#21566 `_). + +* Fixed crash of ``QuerySet``\s that use ``F() + timedelta()`` when their query + was compiled more once + (`#21643 `_). + +* Prevented custom ``widget`` class attribute of + :class:`~django.forms.IntegerField` subclasses from being overwritten by the + code in their ``__init__`` method + (`#22245 `_). + +* Improved :func:`~django.utils.html.strip_tags` accuracy (but it still cannot + guarantee an HTML-safe result, as stated in the documentation). + +* Fixed a regression in the :mod:`django.contrib.gis` SQL compiler for + non-concrete fields (`#22250 `_). + +* Fixed :attr:`ModelAdmin.preserve_filters + ` when running a site with + a URL prefix (`#21795 `_). + +* Fixed a crash in the ``find_command`` management utility when the ``PATH`` + environment variable wasn't set + (`#22256 `_). + +* Fixed :djadmin:`changepassword` on Windows + (`#22364 `_). + +* Avoided shadowing deadlock exceptions on MySQL + (`#22291 `_). + +* Wrapped database exceptions in ``_set_autocommit`` + (`#22321 `_). + +* Fixed atomicity when closing a database connection or when the database server + disconnects (`#21239 `_ and + `#21202 `_) + +* Fixed regression in ``prefetch_related`` that caused the related objects + query to include an unnecessary join + (`#21760 `_). + +Additionally, Django's vendored version of six, :mod:`django.utils.six` has been +upgraded to the latest release (1.6.1). diff -Nru python-django-1.6.1/docs/releases/1.6.4.txt python-django-1.6.11/docs/releases/1.6.4.txt --- python-django-1.6.1/docs/releases/1.6.4.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.4.txt 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,22 @@ +========================== +Django 1.6.4 release notes +========================== + +*April 28, 2014* + +Django 1.6.4 fixes several bugs in 1.6.3. + +Bugfixes +======== + +* Added backwards compatibility support for the :mod:`django.contrib.messages` + cookie format of Django 1.4 and earlier to facilitate upgrading to 1.6 from + 1.4 (`#22426 `_). + +* Restored the ability to :meth:`~django.core.urlresolvers.reverse` views + created using :func:`functools.partial()` + (`#22486 `_). + +* Fixed the ``object_id`` of the ``LogEntry`` that's created after a user + password change in the admin + (`#22515 `_). diff -Nru python-django-1.6.1/docs/releases/1.6.5.txt python-django-1.6.11/docs/releases/1.6.5.txt --- python-django-1.6.1/docs/releases/1.6.5.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.5.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,63 @@ +========================== +Django 1.6.5 release notes +========================== + +*May 14, 2014* + +Django 1.6.5 fixes two security issues and several bugs in 1.6.4. + +Issue: Caches may incorrectly be allowed to store and serve private data +======================================================================== + +In certain situations, Django may allow caches to store private data +related to a particular session and then serve that data to requests +with a different session, or no session at all. This can lead to +information disclosure and can be a vector for cache poisoning. + +When using Django sessions, Django will set a ``Vary: Cookie`` header to +ensure caches do not serve cached data to requests from other sessions. +However, older versions of Internet Explorer (most likely only Internet +Explorer 6, and Internet Explorer 7 if run on Windows XP or Windows Server +2003) are unable to handle the ``Vary`` header in combination with many content +types. Therefore, Django would remove the header if the request was made by +Internet Explorer. + +To remedy this, the special behavior for these older Internet Explorer versions +has been removed, and the ``Vary`` header is no longer stripped from the response. +In addition, modifications to the ``Cache-Control`` header for all Internet Explorer +requests with a ``Content-Disposition`` header have also been removed as they +were found to have similar issues. + +Issue: Malformed redirect URLs from user input not correctly validated +====================================================================== + +The validation for redirects did not correctly validate some malformed URLs, +which are accepted by some browsers. This allows a user to be redirected to +an unsafe URL unexpectedly. + +Django relies on user input in some cases (e.g. +:func:`django.contrib.auth.views.login`, ``django.contrib.comments``, and +:doc:`i18n `) to redirect the user to an "on success" URL. +The security checks for these redirects (namely +``django.util.http.is_safe_url()``) did not correctly validate some malformed +URLs, such as `http:\\\\\\djangoproject.com`, which are accepted by some browsers +with more liberal URL parsing. + +To remedy this, the validation in ``is_safe_url()`` has been tightened to be able +to handle and correctly validate these malformed URLs. + +Bugfixes +======== + +* Made the ``year_lookup_bounds_for_datetime_field`` Oracle backend method + Python 3 compatible (`#22551 `_). + +* Fixed ``pgettext_lazy`` crash when receiving bytestring content on Python 2 + (`#22565 `_). + +* Fixed the SQL generated when filtering by a negated ``Q`` object that contains + a ``F`` object. (`#22429 `_). + +* Avoided overwriting data fetched by ``select_related()`` in certain cases + which could cause minor performance regressions + (`#22508 `_). diff -Nru python-django-1.6.1/docs/releases/1.6.6.txt python-django-1.6.11/docs/releases/1.6.6.txt --- python-django-1.6.1/docs/releases/1.6.6.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.6.txt 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,97 @@ +========================== +Django 1.6.6 release notes +========================== + +*August 20, 2014* + +Django 1.6.6 fixes several security issues and bugs in 1.6.5. + +:func:`~django.core.urlresolvers.reverse()` could generate URLs pointing to other hosts +======================================================================================= + +In certain situations, URL reversing could generate scheme-relative URLs (URLs +starting with two slashes), which could unexpectedly redirect a user to a +different host. An attacker could exploit this, for example, by redirecting +users to a phishing site designed to ask for user's passwords. + +To remedy this, URL reversing now ensures that no URL starts with two slashes +(//), replacing the second slash with its URL encoded counterpart (%2F). This +approach ensures that semantics stay the same, while making the URL relative to +the domain and not to the scheme. + +File upload denial-of-service +============================= + +Before this release, Django's file upload handing in its default configuration +may degrade to producing a huge number of ``os.stat()`` system calls when a +duplicate filename is uploaded. Since ``stat()`` may invoke IO, this may produce +a huge data-dependent slowdown that slowly worsens over time. The net result is +that given enough time, a user with the ability to upload files can cause poor +performance in the upload handler, eventually causing it to become very slow +simply by uploading 0-byte files. At this point, even a slow network connection +and few HTTP requests would be all that is necessary to make a site unavailable. + +We've remedied the issue by changing the algorithm for generating file names +if a file with the uploaded name already exists. +:meth:`Storage.get_available_name() +` now appends an +underscore plus a random 7 character alphanumeric string (e.g. ``"_x3a1gho"``), +rather than iterating through an underscore followed by a number (e.g. ``"_1"``, +``"_2"``, etc.). + +``RemoteUserMiddleware`` session hijacking +========================================== + +When using the :class:`~django.contrib.auth.middleware.RemoteUserMiddleware` +and the ``RemoteUserBackend``, a change to the ``REMOTE_USER`` header between +requests without an intervening logout could result in the prior user's session +being co-opted by the subsequent user. The middleware now logs the user out on +a failed login attempt. + +Data leakage via query string manipulation in ``contrib.admin`` +=============================================================== + +In older versions of Django it was possible to reveal any field's data by +modifying the "popup" and "to_field" parameters of the query string on an admin +change form page. For example, requesting a URL like +``/admin/auth/user/?_popup=1&t=password`` and viewing the page's HTML allowed +viewing the password hash of each user. While the admin requires users to have +permissions to view the change form pages in the first place, this could leak +data if you rely on users having access to view only certain fields on a model. + +To address the issue, an exception will now be raised if a ``to_field`` value +that isn't a related field to a model that has been registered with the admin +is specified. + +Bugfixes +======== + +* Corrected email and URL validation to reject a trailing dash + (:ticket:`22579`). + +* Prevented indexes on PostgreSQL virtual fields (:ticket:`22514`). + +* Prevented edge case where values of FK fields could be initialized with a + wrong value when an inline model formset is created for a relationship + defined to point to a field other than the PK (:ticket:`13794`). + +* Restored ``pre_delete`` signals for ``GenericRelation`` cascade deletion + (:ticket:`22998`). + +* Fixed transaction handling when specifying non-default database in + ``createcachetable`` and ``flush`` (:ticket:`23089`). + +* Fixed the "ORA-01843: not a valid month" errors when using Unicode + with older versions of Oracle server (:ticket:`20292`). + +* Restored bug fix for sending unicode email with Python 2.6.5 and below + (:ticket:`19107`). + +* Prevented ``UnicodeDecodeError`` in ``runserver`` with non-UTF-8 and + non-English locale (:ticket:`23265`). + +* Fixed JavaScript errors while editing multi-geometry objects in the OpenLayers + widget (:ticket:`23137`, :ticket:`23293`). + +* Prevented a crash on Python 3 with query strings containing unencoded + non-ASCII characters (:ticket:`22996`). diff -Nru python-django-1.6.1/docs/releases/1.6.7.txt python-django-1.6.11/docs/releases/1.6.7.txt --- python-django-1.6.1/docs/releases/1.6.7.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.7.txt 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,16 @@ +========================== +Django 1.6.7 release notes +========================== + +*September 2, 2014* + +Django 1.6.7 fixes several bugs in 1.6.6, including a regression related to +a security fix in that release. + +Bugfixes +======== + +* Allowed inherited and m2m fields to be referenced in the admin + (:ticket:`23329`). +* Fixed a crash when using ``QuerySet.defer()`` with ``select_related()`` + (:ticket:`23370`). diff -Nru python-django-1.6.1/docs/releases/1.6.8.txt python-django-1.6.11/docs/releases/1.6.8.txt --- python-django-1.6.1/docs/releases/1.6.8.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.8.txt 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,15 @@ +========================== +Django 1.6.8 release notes +========================== + +*October 22, 2014* + +Django 1.6.8 fixes a couple regressions in the 1.6.6 security release. + +Bugfixes +======== + +* Allowed related many-to-many fields to be referenced in the admin + (:ticket:`23604`). + +* Allowed inline and hidden references to admin fields (:ticket:`23431`). diff -Nru python-django-1.6.1/docs/releases/1.6.9.txt python-django-1.6.11/docs/releases/1.6.9.txt --- python-django-1.6.1/docs/releases/1.6.9.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.9.txt 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,16 @@ +========================== +Django 1.6.9 release notes +========================== + +*January 2, 2015* + +Django 1.6.9 fixes a regression in the 1.6.6 security release. + +Additionally, Django's vendored version of six, :mod:`django.utils.six`, has +been upgraded to the latest release (1.9.0). + +Bugfixes +======== + +* Fixed a regression with dynamically generated inlines and allowed field + references in the admin (:ticket:`23754`). diff -Nru python-django-1.6.1/docs/releases/1.6.txt python-django-1.6.11/docs/releases/1.6.txt --- python-django-1.6.1/docs/releases/1.6.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/1.6.txt 2015-03-18 23:50:43.000000000 +0000 @@ -44,6 +44,8 @@ Django 1.6 will be the final release series to support Python 2.6; beginning with Django 1.7, the minimum supported Python version will be 2.7. +Python 3.4 is not supported, but support will be added in Django 1.7. + What's new in Django 1.6 ======================== @@ -380,7 +382,7 @@ .. warning:: In addition to the changes outlined in this section, be sure to review the - :doc:`deprecation plan ` for any features that + :ref:`deprecation plan ` for any features that have been removed. If you haven't updated your code within the deprecation timeline for a given feature, its removal may appear as a backwards incompatible change. @@ -444,6 +446,17 @@ .. _recommendations in the Python documentation: http://docs.python.org/2/library/doctest.html#unittest-api +Removal of ``django.contrib.gis.tests.GeoDjangoTestSuiteRunner`` GeoDjango custom test runner +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is for developers working on the GeoDjango application itself and related +to the item above about changes in the test runners: + +The ``django.contrib.gis.tests.GeoDjangoTestSuiteRunner`` test runner has been +removed and the standalone GeoDjango tests execution setup it implemented isn't +supported anymore. To run the GeoDjango tests simply use the new +``DiscoverRunner`` and specify the ``django.contrib.gis`` app. + Custom User models in tests ~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -480,7 +493,7 @@ tables with `mysql_tzinfo_to_sql`_. .. _pytz: http://pytz.sourceforge.net/ -.. _mysql_tzinfo_to_sql: http://dev.mysql.com/doc/refman/5.5/en/mysql-tzinfo-to-sql.html +.. _mysql_tzinfo_to_sql: http://dev.mysql.com/doc/refman/5.6/en/mysql-tzinfo-to-sql.html Addition of ``QuerySet.datetimes()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -586,7 +599,8 @@ Django 1.6. If you worked around this bug by applying URL quoting before passing arguments to :func:`~django.core.urlresolvers.reverse`, this may result in double-quoting. If this happens, simply remove the URL quoting from -your code. +your code. You will also have to replace special characters in URLs used in +:func:`~django.test.SimpleTestCase.assertRedirects` with their encoded versions. Storage of IP addresses in the comments app ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -639,21 +653,24 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ HTML rendering of model form fields corresponding to -:class:`~django.db.models.ManyToManyField` ORM model fields used to get the -hard-coded sentence +:class:`~django.db.models.ManyToManyField` model fields used to get the +hard-coded sentence: *Hold down "Control", or "Command" on a Mac, to select more than one.* (or its translation to the active locale) imposed as the help legend shown along them if neither :attr:`model ` nor :attr:`form -` ``help_text`` attribute was specified by the -user (or appended to, if ``help_text`` was provided.) +` ``help_text`` attributes were specified by the +user (or this string was appended to any ``help_text`` that was provided). -This happened always, possibly even with form fields implementing user -interactions that don't involve a keyboard and/or a mouse and was handled at the -model field layer. - -Starting with Django 1.6 this doesn't happen anymore. +Since this happened at the model layer, there was no way to prevent the text +from appearing in cases where it wasn't applicable such as form fields that +implement user interactions that don't involve a keyboard and/or a mouse. + +Starting with Django 1.6, as an ad-hoc temporary backward-compatibility +provision, the logic to add the "Hold down..." sentence has been moved to the +model form field layer and modified to add the text only when the associated +widget is :class:`~django.forms.SelectMultiple` or selected subclasses. The change can affect you in a backward incompatible way if you employ custom model form fields and/or widgets for ``ManyToManyField`` model fields whose UIs @@ -666,11 +683,6 @@ and :doc:`widgets ` aren't affected but need to be aware of what's described in :ref:`m2m-help_text-deprecation` below. -This is because, as an ad-hoc temporary backward-compatibility provision, the -described non-standard behavior has been preserved but moved to the model form -field layer and occurs only when the associated widget is -:class:`~django.forms.SelectMultiple` or selected subclasses. - QuerySet iteration ~~~~~~~~~~~~~~~~~~ @@ -826,11 +838,11 @@ Django 1.6 contains many changes to the ORM. These changes fall mostly in three categories: - 1. Bug fixes (e.g. proper join clauses for generic relations, query - combining, join promotion, and join trimming fixes) - 2. Preparation for new features. For example the ORM is now internally ready - for multicolumn foreign keys. - 3. General cleanup. +1. Bug fixes (e.g. proper join clauses for generic relations, query combining, + join promotion, and join trimming fixes) +2. Preparation for new features. For example the ORM is now internally ready + for multicolumn foreign keys. +3. General cleanup. These changes can result in some compatibility problems. For example, some queries will now generate different table aliases. This can affect @@ -904,8 +916,8 @@ from a method when rendering a template, it is not silenced. For example, ``{{ obj.view_href }}`` will cause template rendering to fail if ``view_href()`` raises ``NoReverseMatch``. There is no change to the - ``{% url %}`` tag, it causes template rendering to fail like always when - ``NoReverseMatch`` is raised. + :ttag:`{% url %}` tag, it causes template rendering to fail like always + when ``NoReverseMatch`` is raised. * :meth:`django.test.client.Client.logout` now calls :meth:`django.contrib.auth.logout` which will send the @@ -1009,7 +1021,7 @@ Django 1.6 starts a process to correct this inconsistency. The ``future`` template library provides alternate implementations of :ttag:`cycle` and :ttag:`firstof` that autoescape their inputs. If you're using these tags, -you're encourage to include the following line at the top of your templates to +you're encouraged to include the following line at the top of your templates to enable the new behavior:: {% load cycle from future %} @@ -1030,10 +1042,11 @@ ``CACHE_MIDDLEWARE_ANONYMOUS_ONLY`` setting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``CacheMiddleware`` used to provide a way to cache requests only if they -weren't made by a logged-in user. This mechanism was largely ineffective -because the middleware correctly takes into account the ``Vary: Cookie`` HTTP -header, and this header is being set on a variety of occasions, such as: +``CacheMiddleware`` and ``UpdateCacheMiddleware`` used to provide a way to +cache requests only if they weren't made by a logged-in user. This mechanism +was largely ineffective because the middleware correctly takes into account the +``Vary: Cookie`` HTTP header, and this header is being set on a variety of +occasions, such as: * accessing the session, or * using CSRF protection, which is turned on by default, or @@ -1079,7 +1092,9 @@ ``Model._meta.get_(add|change|delete)_permission`` methods were deprecated. Even if they were not part of the public API they'll also go through -a regular deprecation path. +a regular deprecation path. You can replace them with +``django.contrib.auth.get_permission_codename('action', Model._meta)`` where +``'action'`` is ``'add'``, ``'change'``, or ``'delete'``. ``get_query_set`` and similar methods renamed to ``get_queryset`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1087,6 +1102,53 @@ Methods that return a ``QuerySet`` such as ``Manager.get_query_set`` or ``ModelAdmin.queryset`` have been renamed to ``get_queryset``. +If you are writing a library that implements, for example, a +``Manager.get_query_set`` method, and you need to support old Django versions, +you should rename the method and conditionally add an alias with the old name:: + + class CustomManager(models.Manager): + def get_queryset(self): + pass # ... + + if django.VERSION < (1, 6): + get_query_set = get_queryset + + # For Django >= 1.6, models.Manager provides a get_query_set fallback + # that emits a warning when used. + +If you are writing a library that needs to call the ``get_queryset`` method and +must support old Django versions, you should write:: + + get_queryset = (some_manager.get_query_set + if hasattr(some_manager, 'get_query_set') + else some_manager.get_queryset) + return get_queryset() # etc + +In the general case of a custom manager that both implements its own +``get_queryset`` method and calls that method, and needs to work with older Django +versions, and libraries that have not been updated yet, it is useful to define +a ``get_queryset_compat`` method as below and use it internally to your manager:: + + class YourCustomManager(models.Manager): + def get_queryset(self): + return YourCustomQuerySet() # for example + + get_query_set = get_queryset + + def active(self): # for example + return self.get_queryset_compat().filter(active=True) + + def get_queryset_compat(self): + get_queryset = (self.get_query_set + if hasattr(self, 'get_query_set') + else self.get_queryset) + return get_queryset() + +This helps to minimize the changes that are needed, but also works correctly in +the case of subclasses (such as ``RelatedManagers`` from Django 1.5) which might +override either ``get_query_set`` or ``get_queryset``. + + ``shortcut`` view and URLconf ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1151,7 +1213,7 @@ For this reason, if you use these views for editing models, you must also supply the ``fields`` attribute (new in Django 1.6), which is a list of model fields and works in the same way as the :class:`~django.forms.ModelForm` -``Meta.fields`` attribute. Alternatively, you can set set the ``form_class`` +``Meta.fields`` attribute. Alternatively, you can set the ``form_class`` attribute to a ``ModelForm`` that explicitly defines the fields to be used. Defining an ``UpdateView`` or ``CreateView`` subclass to be used with a model but without an explicit list of fields is deprecated. diff -Nru python-django-1.6.1/docs/releases/index.txt python-django-1.6.11/docs/releases/index.txt --- python-django-1.6.1/docs/releases/index.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/index.txt 2015-03-18 23:50:46.000000000 +0000 @@ -15,6 +15,9 @@ Final releases ============== +Below are release notes through Django |version| and its minor releases. Newer +versions of the documentation contain the release notes for any later releases. + .. _development_release_notes: 1.6 release @@ -22,6 +25,16 @@ .. toctree:: :maxdepth: 1 + 1.6.11 + 1.6.10 + 1.6.9 + 1.6.8 + 1.6.7 + 1.6.6 + 1.6.5 + 1.6.4 + 1.6.3 + 1.6.2 1.6.1 1.6 @@ -30,6 +43,13 @@ .. toctree:: :maxdepth: 1 + 1.5.12 + 1.5.11 + 1.5.10 + 1.5.9 + 1.5.8 + 1.5.7 + 1.5.6 1.5.5 1.5.4 1.5.3 @@ -42,6 +62,16 @@ .. toctree:: :maxdepth: 1 + 1.4.20 + 1.4.19 + 1.4.18 + 1.4.17 + 1.4.16 + 1.4.15 + 1.4.14 + 1.4.13 + 1.4.12 + 1.4.11 1.4.10 1.4.9 1.4.8 diff -Nru python-django-1.6.1/docs/releases/security.txt python-django-1.6.11/docs/releases/security.txt --- python-django-1.6.1/docs/releases/security.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/releases/security.txt 2015-03-18 23:40:08.000000000 +0000 @@ -349,9 +349,9 @@ Versions affected ----------------- - * Django 1.3: `(patch) `__ +* Django 1.3: `(patch) `__ - * Django 1.4: `(patch) `__ +* Django 1.4: `(patch) `__ February 19, 2013 - No CVE ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -448,3 +448,156 @@ * Django 1.4 `(patch `__ and `Python compatibility fix) `__ * Django 1.5 `(patch) `__ + + +April 21, 2014 - CVE-2014-0472 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0472 `_: Unexpected code execution using ``reverse()``. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +April 21, 2014 - CVE-2014-0473 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0473 `_: Caching of anonymous pages could reveal CSRF token. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +April 21, 2014 - CVE-2014-0474 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0474 `_: MySQL typecasting causes unexpected query results. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +May 18, 2014 - CVE-2014-1418 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-1418 `_: Caches may be allowed to store and serve private data. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +May 18, 2014 - CVE-2014-3730 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-3730 `_: Malformed URLs from user input incorrectly validated. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +August 20, 2014 - CVE-2014-0480 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0480 `_: reverse() can generate URLs pointing to other hosts. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +August 20, 2014 - CVE-2014-0481 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0481 `_: File upload denial of service. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +August 20, 2014 - CVE-2014-0482 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0482 `_: RemoteUserMiddleware session hijacking. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ + + +August 20, 2014 - CVE-2014-0483 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +`CVE-2014-0483 `_: Data leakage via querystring manipulation in admin. `Full description `__ + +Versions affected +----------------- + +* Django 1.4 `(patch) `__ + +* Django 1.5 `(patch) `__ + +* Django 1.6 `(patch) `__ + +* Django 1.7 `(patch) `__ diff -Nru python-django-1.6.1/docs/_theme/djangodocs/static/djangodocs.css python-django-1.6.11/docs/_theme/djangodocs/static/djangodocs.css --- python-django-1.6.1/docs/_theme/djangodocs/static/djangodocs.css 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/_theme/djangodocs/static/djangodocs.css 2015-03-18 23:50:43.000000000 +0000 @@ -117,7 +117,8 @@ /*** versoinadded/changes ***/ div.versionadded, div.versionchanged { } -div.versionadded span.title, div.versionchanged span.title, div.deprecated span.title { font-weight: bold; } +div.versionadded span.title, div.versionchanged span.title, span.versionmodified { font-weight: bold; } +div.deprecated { color:#555; } /*** p-links ***/ a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; visibility: hidden; } diff -Nru python-django-1.6.1/docs/topics/auth/customizing.txt python-django-1.6.11/docs/topics/auth/customizing.txt --- python-django-1.6.1/docs/topics/auth/customizing.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/auth/customizing.txt 2015-03-18 23:50:43.000000000 +0000 @@ -5,7 +5,7 @@ The authentication that comes with Django is good enough for most common cases, but you may have needs not met by the out-of-the-box defaults. To customize authentication to your projects needs involves understanding what points of the -provided system are extendible or replaceable. This document provides details +provided system are extensible or replaceable. This document provides details about how the auth system can be customized. :ref:`Authentication backends ` provide an extensible @@ -451,6 +451,8 @@ class Article(models.Model): author = models.ForeignKey(settings.AUTH_USER_MODEL) +.. _specifying-custom-user-model: + Specifying a custom User model ------------------------------ @@ -502,7 +504,7 @@ as the identifying field:: class MyUser(AbstractBaseUser): - identifier = models.CharField(max_length=40, unique=True, db_index=True) + identifier = models.CharField(max_length=40, unique=True) ... USERNAME_FIELD = 'identifier' @@ -530,8 +532,9 @@ .. note:: - ``REQUIRED_FIELDS`` must contain all required fields on your User - model, but should *not* contain the ``USERNAME_FIELD``. + ``REQUIRED_FIELDS`` must contain all required fields on your + ``User`` model, but should *not* contain the ``USERNAME_FIELD`` or + ``password`` as these fields will always be prompted for. .. attribute:: is_active @@ -593,7 +596,7 @@ .. versionchanged:: 1.6 In Django 1.4 and 1.5, a blank string was unintentionally stored - as an unsable password as well. + as an unusable password as well. .. method:: models.AbstractBaseUser.check_password(raw_password) @@ -690,10 +693,12 @@ ------------------------------- If you're entirely happy with Django's :class:`~django.contrib.auth.models.User` -model and you just want to add some additional profile information, you can +model and you just want to add some additional profile information, you could simply subclass ``django.contrib.auth.models.AbstractUser`` and add your -custom profile fields. This class provides the full implementation of the -default :class:`~django.contrib.auth.models.User` as an :ref:`abstract model +custom profile fields, although we'd recommend a separate model as described in +the "Model design considerations" note of :ref:`specifying-custom-user-model`. +``AbstractUser`` provides the full implementation of the default +:class:`~django.contrib.auth.models.User` as an :ref:`abstract model `. .. _custom-users-and-the-built-in-auth-forms: @@ -726,9 +731,9 @@ * :class:`~django.contrib.auth.forms.PasswordResetForm` - Assumes that the user model has an integer primary key, has a field named - ``email`` that can be used to identify the user, and a boolean field - named `is_active` to prevent password resets for inactive users. + Assumes that the user model has a field named ``email`` that can be used to + identify the user and a boolean field named ``is_active`` to prevent + password resets for inactive users. * :class:`~django.contrib.auth.forms.SetPasswordForm` @@ -803,7 +808,7 @@ .. method:: models.PermissionsMixin.get_group_permissions(obj=None) - Returns a set of permission strings that the user has, through his/her + Returns a set of permission strings that the user has, through their groups. If ``obj`` is passed in, only returns the group permissions for @@ -991,7 +996,6 @@ verbose_name='email address', max_length=255, unique=True, - db_index=True, ) date_of_birth = models.DateField() is_active = models.BooleanField(default=True) @@ -1078,7 +1082,7 @@ class Meta: model = MyUser - fields = ['email', 'password', 'date_of_birth', 'is_active', 'is_admin'] + fields = ('email', 'password', 'date_of_birth', 'is_active', 'is_admin') def clean_password(self): # Regardless of what the user provides, return the initial value. diff -Nru python-django-1.6.1/docs/topics/auth/default.txt python-django-1.6.11/docs/topics/auth/default.txt --- python-django-1.6.1/docs/topics/auth/default.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/auth/default.txt 2015-03-18 23:50:43.000000000 +0000 @@ -7,14 +7,13 @@ This document explains the usage of Django's authentication system in its default configuration. This configuration has evolved to serve the most common project needs, handling a reasonably wide range of tasks, and has a careful -implementation of passwords and permissions, and can handle many projects as -is. For projects where authentication needs differ from the default, Django -supports extensive :doc:`extension and customization -` of authentication. +implementation of passwords and permissions. For projects where authentication +needs differ from the default, Django supports extensive :doc:`extension and +customization ` of authentication. -Django authentication provides both authentication and authorization, together +Django authentication provides both authentication and authorization together and is generally referred to as the authentication system, as these features -somewhat coupled. +are somewhat coupled. .. _user-objects: @@ -25,17 +24,18 @@ authentication system. They typically represent the people interacting with your site and are used to enable things like restricting access, registering user profiles, associating content with creators etc. Only one class of user -exists in Django's authentication framework, i.e., 'superusers' or admin -'staff' users are just user objects with special attributes set, not different -classes of user objects. +exists in Django's authentication framework, i.e., :attr:`'superusers' +` or admin :attr:`'staff' +` users are just user objects with +special attributes set, not different classes of user objects. The primary attributes of the default user are: -* username -* password -* email -* first name -* last name +* :attr:`~django.contrib.auth.models.User.username` +* :attr:`~django.contrib.auth.models.User.password` +* :attr:`~django.contrib.auth.models.User.email` +* :attr:`~django.contrib.auth.models.User.first_name` +* :attr:`~django.contrib.auth.models.User.last_name` See the :class:`full API documentation ` for full reference, the documentation that follows is more task oriented. @@ -136,6 +136,16 @@ # the authentication system was unable to verify the username and password print("The username and password were incorrect.") + .. note:: + + This is a low level way to authenticate a set of credentials; for + example, it's used by the + :class:`~django.contrib.auth.middleware.RemoteUserMiddleware`. Unless + you are writing your own authentication system, you probably won't use + this. Rather if you are looking for a way to limit access to logged in + users, see the :func:`~django.contrib.auth.decorators.login_required` + decorator. + .. _topic-authorization: Permissions and Authorization @@ -248,6 +258,39 @@ attribute or to a :class:`~django.contrib.auth.models.Group` via its ``permissions`` attribute. +Permission caching +------------------ + +The :class:`~django.contrib.auth.backends.ModelBackend` caches permissions on +the ``User`` object after the first time they need to be fetched for a +permissions check. This is typically fine for the request-response cycle since +permissions are not typically checked immediately after they are added (in the +admin, for example). If you are adding permissions and checking them immediately +afterward, in a test or view for example, the easiest solution is to re-fetch +the ``User`` from the database. For example:: + + from django.contrib.auth.models import Permission, User + from django.shortcuts import get_object_or_404 + + def user_gains_perms(request, user_id): + user = get_object_or_404(User, pk=user_id) + # any permission check will cache the current set of permissions + user.has_perm('myapp.change_bar') + + permission = Permission.objects.get(codename='change_bar') + user.user_permissions.add(permission) + + # Checking the cached permission set + user.has_perm('myapp.change_bar') # False + + # Request new instance of User + user = get_object_or_404(User, pk=user_id) + + # Permission cache is repopulated from the database + user.has_perm('myapp.change_bar') # True + + ... + .. _auth-web-requests: Authentication in Web requests @@ -460,7 +503,7 @@ return HttpResponse("You can't vote in this poll.") # ... -.. function:: user_passes_test(func, [login_url=None]) +.. function:: user_passes_test(func, [login_url=None, redirect_field_name=REDIRECT_FIELD_NAME]) As a shortcut, you can use the convenient ``user_passes_test`` decorator:: @@ -481,9 +524,19 @@ automatically check that the :class:`~django.contrib.auth.models.User` is not anonymous. - :func:`~django.contrib.auth.decorators.user_passes_test()` takes an - optional ``login_url`` argument, which lets you specify the URL for your - login page (:setting:`settings.LOGIN_URL ` by default). + :func:`~django.contrib.auth.decorators.user_passes_test` takes two + optional arguments: + + ``login_url`` + Lets you specify the URL that users who don't pass the test will be + redirected to. It may be a login page and defaults to + :setting:`settings.LOGIN_URL ` if you don't specify one. + + ``redirect_field_name`` + Same as for :func:`~django.contrib.auth.decorators.login_required`. + Setting it to ``None`` removes it from the URL, which you may want to do + if you are redirecting users that don't pass the test to a non-login + page where there's no "next page". For example:: @@ -907,6 +960,11 @@ **URL name:** ``password_reset_done`` + .. note:: + + If the email address provided does not exist in the system, the user is inactive, or has an unusable password, + the user will still be redirected to this view but no email will be sent. + **Optional arguments:** * ``template_name``: The full name of a template to use. @@ -1052,7 +1110,7 @@ .. class:: SetPasswordForm - A form that lets a user change his/her password without entering the old + A form that lets a user change their password without entering the old password. .. class:: UserChangeForm @@ -1070,7 +1128,7 @@ Authentication data in templates -------------------------------- -The currently logged-in user and his/her permissions are made available in the +The currently logged-in user and their permissions are made available in the :doc:`template context ` when you use :class:`~django.template.RequestContext`. diff -Nru python-django-1.6.1/docs/topics/auth/passwords.txt python-django-1.6.11/docs/topics/auth/passwords.txt --- python-django-1.6.1/docs/topics/auth/passwords.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/auth/passwords.txt 2015-03-18 23:50:43.000000000 +0000 @@ -220,7 +220,7 @@ to be an unusable password, resulting in this method returning ``False`` for such a password. -.. function:: make_password(password[, salt, hashers]) +.. function:: make_password(password, salt=None, hasher='default') Creates a hashed password in the format used by this application. It takes one mandatory argument: the password in plain-text. Optionally, you can diff -Nru python-django-1.6.1/docs/topics/cache.txt python-django-1.6.11/docs/topics/cache.txt --- python-django-1.6.1/docs/topics/cache.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/cache.txt 2015-03-18 23:50:43.000000000 +0000 @@ -34,7 +34,7 @@ specific views, you can cache only the pieces that are difficult to produce, or you can cache your entire site. -Django also works well with "upstream" caches, such as `Squid +Django also works well with "downstream" caches, such as `Squid `_ and browser-based caches. These are the types of caches that you don't directly control but to which you can provide hints (via HTTP headers) about which parts of your site should be cached, and how. @@ -278,8 +278,9 @@ This is the default cache if another is not specified in your settings file. If you want the speed advantages of in-memory caching but don't have the capability of running Memcached, consider the local-memory cache backend. This cache is -multi-process and thread-safe. To use it, set :setting:`BACKEND ` -to ``"django.core.cache.backends.locmem.LocMemCache"``. For example:: +per-process (see below) and thread-safe. To use it, set :setting:`BACKEND +` to ``"django.core.cache.backends.locmem.LocMemCache"``. For +example:: CACHES = { 'default': { @@ -410,6 +411,8 @@ Invalid arguments are silently ignored, as are invalid values of known arguments. +.. _the-per-site-cache: + The per-site cache ================== @@ -441,14 +444,14 @@ the site, or some other string that is unique to this Django instance, to prevent key collisions. Use an empty string if you don't care. -The cache middleware caches GET and HEAD responses with status 200, where the request -and response headers allow. Responses to requests for the same URL with different -query parameters are considered to be unique pages and are cached separately. -The cache middleware expects that a HEAD request is answered with the same -response headers as the corresponding GET request; in which case it can return -a cached GET response for HEAD request. +``FetchFromCacheMiddleware`` caches GET and HEAD responses with status 200, +where the request and response headers allow. Responses to requests for the same +URL with different query parameters are considered to be unique pages and are +cached separately. This middleware expects that a HEAD request is answered with +the same response headers as the corresponding GET request; in which case it can +return a cached GET response for HEAD request. -Additionally, the cache middleware automatically sets a few headers in each +Additionally, ``UpdateCacheMiddleware`` automatically sets a few headers in each :class:`~django.http.HttpResponse`: * Sets the ``Last-Modified`` header to the current date/time when a fresh @@ -536,9 +539,9 @@ def my_view(request): ... -The two settings can also be combined. If you specify a ``cache`` *and* -a ``key_prefix``, you will get all the settings of the requested cache -alias, but with the key_prefix overridden. +The ``key_prefix and ``cache`` arguments may be specified together. The +``key_prefix`` argument and the :setting:`KEY_PREFIX ` +specified under :setting:`CACHES` will be concatenated. Specifying per-view cache in the URLconf ---------------------------------------- @@ -938,15 +941,15 @@ ...and use the dotted Python path to this class in the :setting:`BACKEND ` portion of your :setting:`CACHES` setting. -Upstream caches -=============== +Downstream caches +================= So far, this document has focused on caching your *own* data. But another type -of caching is relevant to Web development, too: caching performed by "upstream" -caches. These are systems that cache pages for users even before the request -reaches your Web site. +of caching is relevant to Web development, too: caching performed by +"downstream" caches. These are systems that cache pages for users even before +the request reaches your Web site. -Here are a few examples of upstream caches: +Here are a few examples of downstream caches: * Your ISP may cache certain pages, so if you requested a page from http://example.com/, your ISP would send you the page without having to @@ -964,7 +967,7 @@ subsequent requests to that page, without even contacting the Web page again to see whether it has changed. -Upstream caching is a nice efficiency boost, but there's a danger to it: +Downstream caching is a nice efficiency boost, but there's a danger to it: Many Web pages' contents differ based on authentication and a host of other variables, and cache systems that blindly save pages based purely on URLs could expose incorrect or sensitive data to subsequent visitors to those pages. @@ -976,7 +979,7 @@ not cool. Fortunately, HTTP provides a solution to this problem. A number of HTTP headers -exist to instruct upstream caches to differ their cache contents depending on +exist to instruct downstream caches to differ their cache contents depending on designated variables, and to tell caching mechanisms not to cache particular pages. We'll look at some of these headers in the sections that follow. @@ -1022,7 +1025,7 @@ def my_view(request): # ... -This tells upstream caches to vary on *both*, which means each combination of +This tells downstream caches to vary on *both*, which means each combination of user-agent and cookie will get its own cache value. For example, a request with the user-agent ``Mozilla`` and the cookie value ``foo=bar`` will be considered different from a request with the user-agent ``Mozilla`` and the cookie value diff -Nru python-django-1.6.1/docs/topics/class-based-views/generic-display.txt python-django-1.6.11/docs/topics/class-based-views/generic-display.txt --- python-django-1.6.1/docs/topics/class-based-views/generic-display.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/class-based-views/generic-display.txt 2015-03-18 23:50:43.000000000 +0000 @@ -1,8 +1,8 @@ .. _Generic views: -========================= -Class-based generic views -========================= +================================== +Built-in Class-based generic views +================================== Writing Web applications can be monotonous, because we repeat certain patterns again and again. Django tries to take away some of that monotony at the model diff -Nru python-django-1.6.1/docs/topics/class-based-views/generic-editing.txt python-django-1.6.11/docs/topics/class-based-views/generic-editing.txt --- python-django-1.6.1/docs/topics/class-based-views/generic-editing.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/class-based-views/generic-editing.txt 2015-03-18 23:50:43.000000000 +0000 @@ -181,7 +181,7 @@ the foreign key relation to the model:: # models.py - from django.contrib.auth import User + from django.contrib.auth.models import User from django.db import models class Author(models.Model): diff -Nru python-django-1.6.1/docs/topics/class-based-views/intro.txt python-django-1.6.11/docs/topics/class-based-views/intro.txt --- python-django-1.6.1/docs/topics/class-based-views/intro.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/topics/class-based-views/intro.txt 2015-03-18 23:50:43.000000000 +0000 @@ -71,7 +71,7 @@ In a class-based view, this would become:: from django.http import HttpResponse - from django.views.generic.base import View + from django.views.generic import View class MyView(View): def get(self, request): @@ -113,7 +113,7 @@ ``greeting`` like this:: from django.http import HttpResponse - from django.views.generic.base import View + from django.views.generic import View class GreetingView(View): greeting = "Good Day" @@ -198,7 +198,7 @@ from django.http import HttpResponseRedirect from django.shortcuts import render - from django.views.generic.base import View + from django.views.generic import View from .forms import MyForm diff -Nru python-django-1.6.1/docs/topics/class-based-views/mixins.txt python-django-1.6.11/docs/topics/class-based-views/mixins.txt --- python-django-1.6.1/docs/topics/class-based-views/mixins.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/class-based-views/mixins.txt 2015-03-18 23:50:43.000000000 +0000 @@ -119,7 +119,7 @@ :meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` as discussed above. It actually provides a fairly sophisticated set of options, but the main one that most people are going to use is -``/_detail.html``. The ``_detail`` part can be changed +``/_detail.html``. The ``_detail`` part can be changed by setting :attr:`~django.views.generic.detail.SingleObjectTemplateResponseMixin.template_name_suffix` on a subclass to something else. (For instance, the :doc:`generic edit @@ -165,7 +165,7 @@ above, this overrides ``get_template_names()`` to provide :meth:`a range of options `, with the most commonly-used being -``/_list.html``, with the ``_list`` part again +``/_list.html``, with the ``_list`` part again being taken from the :attr:`~django.views.generic.list.MultipleObjectTemplateResponseMixin.template_name_suffix` attribute. (The date based generic views use suffixes such as ``_archive``, diff -Nru python-django-1.6.1/docs/topics/db/aggregation.txt python-django-1.6.11/docs/topics/db/aggregation.txt --- python-django-1.6.1/docs/topics/db/aggregation.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/aggregation.txt 2015-03-18 23:50:43.000000000 +0000 @@ -179,6 +179,17 @@ modified using any other ``QuerySet`` operation, including ``filter()``, ``order_by()``, or even additional calls to ``annotate()``. +.. admonition:: If in doubt, inspect the SQL query! + + In order to understand what happens in your query, consider inspecting the + ``query`` property of your ``QuerySet``. + + For instance, combining multiple aggregations with ``annotate()`` will + yield the wrong results, as `multiple tables are cross joined`_, + resulting in duplicate row aggregations. + +.. _multiple tables are cross joined: https://code.djangoproject.com/ticket/10060 + Joins and aggregates ==================== diff -Nru python-django-1.6.1/docs/topics/db/examples/one_to_one.txt python-django-1.6.11/docs/topics/db/examples/one_to_one.txt --- python-django-1.6.1/docs/topics/db/examples/one_to_one.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/examples/one_to_one.txt 2015-03-18 23:50:43.000000000 +0000 @@ -64,10 +64,17 @@ p2 doesn't have an associated restaurant:: - >>> p2.restaurant - Traceback (most recent call last): - ... - DoesNotExist: Restaurant matching query does not exist. + >>> from django.core.exceptions import ObjectDoesNotExist + >>> try: + >>> p2.restaurant + >>> except ObjectDoesNotExist: + >>> print("There is no restaurant here.") + There is no restaurant here. + +You can also use ``hasattr`` to avoid the need for exception catching:: + + >>> hasattr(p2, 'restaurant') + False Set the place using assignment notation. Because place is the primary key on Restaurant, the save will create a new restaurant:: diff -Nru python-django-1.6.1/docs/topics/db/managers.txt python-django-1.6.11/docs/topics/db/managers.txt --- python-django-1.6.1/docs/topics/db/managers.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/managers.txt 2015-03-18 23:50:43.000000000 +0000 @@ -169,6 +169,11 @@ This example allows you to request ``Person.men.all()``, ``Person.women.all()``, and ``Person.people.all()``, yielding predictable results. +.. _default-managers: + +Default managers +~~~~~~~~~~~~~~~~ + If you use custom ``Manager`` objects, take note that the first ``Manager`` Django encounters (in the order in which they're defined in the model) has a special status. Django interprets the first ``Manager`` defined in a class as diff -Nru python-django-1.6.1/docs/topics/db/models.txt python-django-1.6.11/docs/topics/db/models.txt --- python-django-1.6.1/docs/topics/db/models.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/models.txt 2015-03-18 23:50:43.000000000 +0000 @@ -212,6 +212,23 @@ unless you want to override the default primary-key behavior. For more, see :ref:`automatic-primary-key-fields`. + The primary key field is read-only. If you change the value of the primary + key on an existing object and then save it, a new object will be created + alongside the old one. For example:: + + from django.db import models + + class Fruit(models.Model): + name = models.CharField(max_length=100, primary_key=True) + + .. code-block:: pycon + + >>> fruit = Fruit.objects.create(name='Apple') + >>> fruit.name = 'Pear' + >>> fruit.save() + >>> Fruit.objects.values_list('name', flat=True) + ['Apple', 'Pear'] + :attr:`~Field.unique` If ``True``, this field must be unique throughout the table. @@ -684,9 +701,6 @@ first_name = models.CharField(max_length=50) last_name = models.CharField(max_length=50) birth_date = models.DateField() - address = models.CharField(max_length=100) - city = models.CharField(max_length=50) - state = models.CharField(max_length=2) # yes, this is America-centric def baby_boomer_status(self): "Returns the person's baby-boomer status." @@ -698,10 +712,6 @@ else: return "Post-boomer" - def is_midwestern(self): - "Returns True if this person is from the Midwest." - return self.state in ('IL', 'WI', 'MI', 'IN', 'OH', 'IA', 'MO') - def _get_full_name(self): "Returns the person's full name." return '%s %s' % (self.first_name, self.last_name) @@ -818,11 +828,14 @@ ================= Model inheritance in Django works almost identically to the way normal -class inheritance works in Python. The only decision you have to make -is whether you want the parent models to be models in their own right -(with their own database tables), or if the parents are just holders -of common information that will only be visible through the child -models. +class inheritance works in Python, but the basics at the beginning of the page +should still be followed. That means the base class should subclass +:class:`django.db.models.Model`. + +The only decision you have to make is whether you want the parent models to be +models in their own right (with their own database tables), or if the parents +are just holders of common information that will only be visible through the +child models. There are three styles of inheritance that are possible in Django. @@ -1048,19 +1061,27 @@ default :attr:`~django.db.models.ForeignKey.related_name` value for :class:`~django.db.models.ForeignKey` and :class:`~django.db.models.ManyToManyField` relations. If you -are putting those types of relations on a subclass of another model, -you **must** specify the -:attr:`~django.db.models.ForeignKey.related_name` attribute on each -such field. If you forget, Django will raise an error when you run -:djadmin:`validate` or :djadmin:`syncdb`. +are putting those types of relations on a subclass of the parent model, you +**must** specify the :attr:`~django.db.models.ForeignKey.related_name` +attribute on each such field. If you forget, Django will raise a validation +error. For example, using the above ``Place`` class again, let's create another subclass with a :class:`~django.db.models.ManyToManyField`:: class Supplier(Place): - # Must specify related_name on all relations. - customers = models.ManyToManyField(Restaurant, related_name='provider') + customers = models.ManyToManyField(Place) + +This results in the error:: + + Reverse query name for 'Supplier.customers' clashes with reverse query + name for 'Supplier.place_ptr'. + HINT: Add or change a related_name argument to the definition for + 'Supplier.customers' or 'Supplier.place_ptr'. + +Adding ``related_name`` to the ``customers`` field as follows would resolve the +error: ``models.ManyToManyField(Place, related_name='provider')``. Specifying the parent link field ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1285,3 +1306,9 @@ Django will raise a :exc:`~django.core.exceptions.FieldError` if you override any model field in any ancestor model. + +.. seealso:: + + :doc:`The Models Reference ` + Covers all the model related APIs including model fields, related + objects, and ``QuerySet``. diff -Nru python-django-1.6.1/docs/topics/db/multi-db.txt python-django-1.6.11/docs/topics/db/multi-db.txt --- python-django-1.6.1/docs/topics/db/multi-db.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/multi-db.txt 2015-03-18 23:50:43.000000000 +0000 @@ -695,6 +695,6 @@ .. warning:: - If you're synchronizing content types to more that one database, be aware + If you're synchronizing content types to more than one database, be aware that their primary keys may not match across databases. This may result in data corruption or data loss. diff -Nru python-django-1.6.1/docs/topics/db/optimization.txt python-django-1.6.11/docs/topics/db/optimization.txt --- python-django-1.6.1/docs/topics/db/optimization.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/optimization.txt 2015-03-18 23:50:43.000000000 +0000 @@ -36,11 +36,19 @@ ...including: -* Indexes. This is a number one priority, *after* you have determined from +* Indexes_. This is a number one priority, *after* you have determined from profiling what indexes should be added. Use - :attr:`django.db.models.Field.db_index` or + :attr:`Field.db_index ` or :attr:`Meta.index_together ` to add - these from Django. + these from Django. Consider adding indexes to fields that you frequently + query using :meth:`~django.db.models.query.QuerySet.filter()`, + :meth:`~django.db.models.query.QuerySet.exclude()`, + :meth:`~django.db.models.query.QuerySet.order_by()`, etc. as indexes may help + to speed up lookups. Note that determining the best indexes is a complex + database-dependent topic that will depend on your particular application. + The overhead of maintaining an index may outweigh any gains in query speed. + +.. _Indexes: http://en.wikipedia.org/wiki/Database_index * Appropriate use of field types. diff -Nru python-django-1.6.1/docs/topics/db/queries.txt python-django-1.6.11/docs/topics/db/queries.txt --- python-django-1.6.1/docs/topics/db/queries.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/queries.txt 2015-03-18 23:50:43.000000000 +0000 @@ -93,8 +93,8 @@ Given a ``Blog`` instance ``b5`` that has already been saved to the database, this example changes its name and updates its record in the database:: - >> b5.name = 'New name' - >> b5.save() + >>> b5.name = 'New name' + >>> b5.save() This performs an ``UPDATE`` SQL statement behind the scenes. Django doesn't hit the database until you explicitly call :meth:`~django.db.models.Model.save`. @@ -254,9 +254,9 @@ Example:: - >> q1 = Entry.objects.filter(headline__startswith="What") - >> q2 = q1.exclude(pub_date__gte=datetime.date.today()) - >> q3 = q1.filter(pub_date__gte=datetime.date.today()) + >>> q1 = Entry.objects.filter(headline__startswith="What") + >>> q2 = q1.exclude(pub_date__gte=datetime.date.today()) + >>> q3 = q1.filter(pub_date__gte=datetime.date.today()) These three ``QuerySets`` are separate. The first is a base :class:`~django.db.models.query.QuerySet` containing all entries that contain a @@ -397,7 +397,9 @@ >>> Entry.objects.filter(pub_date__lte='2006-01-01') -translates (roughly) into the following SQL:: +translates (roughly) into the following SQL: + +.. code-block:: sql SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01'; diff -Nru python-django-1.6.1/docs/topics/db/sql.txt python-django-1.6.11/docs/topics/db/sql.txt --- python-django-1.6.1/docs/topics/db/sql.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/sql.txt 2015-03-18 23:50:43.000000000 +0000 @@ -13,6 +13,14 @@ __ `performing raw queries`_ __ `executing custom SQL directly`_ +.. warning:: + + You should be very careful whenever you write raw SQL. Every time you use + it, you should properly escape any parameters that the user can control + by using ``params`` in order to protect against SQL injection attacks. + Please read more about :ref:`SQL injection protection + `. + .. _executing-raw-queries: Performing raw queries @@ -23,11 +31,12 @@ .. method:: Manager.raw(raw_query, params=None, translations=None) -This method method takes a raw SQL query, executes it, and returns a +This method takes a raw SQL query, executes it, and returns a ``django.db.models.query.RawQuerySet`` instance. This ``RawQuerySet`` instance -can be iterated over just like an normal QuerySet to provide object instances. +can be iterated over just like a normal +:class:`~django.db.models.query.QuerySet` to provide object instances. -This is best illustrated with an example. Suppose you've got the following model:: +This is best illustrated with an example. Suppose you have the following model:: class Person(models.Model): first_name = models.CharField(...) @@ -66,6 +75,27 @@ database, but does nothing to enforce that. If the query does not return rows, a (possibly cryptic) error will result. +.. warning:: + + If you are performing queries on MySQL, note that MySQL's silent type coercion + may cause unexpected results when mixing types. If you query on a string + type column, but with an integer value, MySQL will coerce the types of all values + in the table to an integer before performing the comparison. For example, if your + table contains the values ``'abc'``, ``'def'`` and you query for ``WHERE mycolumn=0``, + both rows will match. To prevent this, perform the correct typecasting + before using the value in a query. + +.. warning:: + + While a ``RawQuerySet`` instance can be iterated over like a normal + :class:`~django.db.models.query.QuerySet`, ``RawQuerySet`` doesn't + implement all methods you can use with ``QuerySet``. For example, + ``__bool__()`` and ``__len__()`` are not defined in ``RawQuerySet``, and + thus all ``RawQuerySet`` instances are considered ``True``. The reason + these methods are not implemented in ``RawQuerySet`` is that implementing + them without internal caching would be a performance drawback and adding + such caching would be backward incompatible. + Mapping query fields to model fields ------------------------------------ @@ -108,7 +138,7 @@ >>> first_person = Person.objects.raw('SELECT * from myapp_person')[0] However, the indexing and slicing are not performed at the database level. If -you have a big amount of ``Person`` objects in your database, it is more +you have a large number of ``Person`` objects in your database, it is more efficient to limit the query at the SQL level:: >>> first_person = Person.objects.raw('SELECT * from myapp_person LIMIT 1')[0] @@ -173,7 +203,7 @@ placeholders will be replaced with parameters from the ``params`` argument. -.. note:: Dictionary params not supported with SQLite +.. note:: Dictionary params are not supported with the SQLite backend; with this backend, you must pass parameters as a list. diff -Nru python-django-1.6.1/docs/topics/db/transactions.txt python-django-1.6.11/docs/topics/db/transactions.txt --- python-django-1.6.1/docs/topics/db/transactions.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/db/transactions.txt 2015-03-18 23:50:43.000000000 +0000 @@ -13,14 +13,17 @@ ------------------------------------- Django's default behavior is to run in autocommit mode. Each query is -immediately committed to the database. :ref:`See below for details -`. +immediately committed to the database, unless a transaction is active. +:ref:`See below for details `. Django uses transactions or savepoints automatically to guarantee the integrity of ORM operations that require multiple queries, especially :ref:`delete() ` and :ref:`update() ` queries. +Django's :class:`~django.test.TestCase` class also wraps each test in a +transaction for performance reasons. + .. versionchanged:: 1.6 Previous version of Django featured :ref:`a more complicated default @@ -219,7 +222,7 @@ Open transactions have a performance cost for your database server. To minimize this overhead, keep your transactions as short as possible. This - is especially important of you're using :func:`atomic` in long-running + is especially important if you're using :func:`atomic` in long-running processes, outside of Django's request / response cycle. Autocommit @@ -231,13 +234,15 @@ -------------------------- In the SQL standards, each SQL query starts a transaction, unless one is -already in progress. Such transactions must then be committed or rolled back. +already active. Such transactions must then be explicitly committed or rolled +back. This isn't always convenient for application developers. To alleviate this problem, most databases provide an autocommit mode. When autocommit is turned -on, each SQL query is wrapped in its own transaction. In other words, the -transaction is not only automatically started, but also automatically -committed. +on and no transaction is active, each SQL query gets wrapped in its own +transaction. In other words, not only does each such query start a +transaction, but the transaction also gets automatically committed or rolled +back, depending on whether the query succeeded. :pep:`249`, the Python Database API Specification v2.0, requires autocommit to be initially turned off. Django overrides this default and turns autocommit @@ -469,7 +474,7 @@ as they're called. If your MySQL setup *does* support transactions, Django will handle transactions as explained in this document. -.. _information on MySQL transactions: http://dev.mysql.com/doc/refman/5.0/en/sql-syntax-transactions.html +.. _information on MySQL transactions: http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-transactions.html Handling exceptions within PostgreSQL transactions -------------------------------------------------- @@ -517,8 +522,8 @@ transaction. For example:: a.save() # Succeeds, and never undone by savepoint rollback + sid = transaction.savepoint() try: - sid = transaction.savepoint() b.save() # Could throw exception transaction.savepoint_commit(sid) except IntegrityError: @@ -597,7 +602,7 @@ rollbacks, and switches back to auto mode. So :func:`commit_on_success` really has two effects: it changes the -transaction state and it defines an transaction block. Nesting will give the +transaction state and it defines a transaction block. Nesting will give the expected results in terms of transaction state, but not in terms of transaction semantics. Most often, the inner block will commit, breaking the atomicity of the outer block. @@ -717,7 +722,10 @@ If you were relying on "automatic transactions" to provide locking between :meth:`~django.db.models.query.QuerySet.select_for_update` and a subsequent write operation — an extremely fragile design, but nonetheless possible — you -must wrap the relevant code in :func:`atomic`. +must wrap the relevant code in :func:`atomic`. Since Django 1.6.3, executing +a query with :meth:`~django.db.models.query.QuerySet.select_for_update` in +autocommit mode will raise a +:exc:`~django.db.transaction.TransactionManagementError`. Using a high isolation level ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff -Nru python-django-1.6.1/docs/topics/email.txt python-django-1.6.11/docs/topics/email.txt --- python-django-1.6.1/docs/topics/email.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/email.txt 2015-03-18 23:50:43.000000000 +0000 @@ -174,6 +174,7 @@ "/contact/thanks/" when it's done:: from django.core.mail import send_mail, BadHeaderError + from django.http import HttpResponse, HttpResponseRedirect def send_email(request): subject = request.POST.get('subject', '') @@ -275,7 +276,8 @@ specified when the email was constructed, that connection will be used. Otherwise, an instance of the default backend will be instantiated and used. If the keyword argument ``fail_silently`` is ``True``, exceptions - raised while sending the message will be quashed. + raised while sending the message will be quashed. An empty list of + recipients will not raise an exception. * ``message()`` constructs a ``django.core.mail.SafeMIMEText`` object (a subclass of Python's ``email.MIMEText.MIMEText`` class) or a @@ -364,7 +366,7 @@ The email backend class has the following methods: -* ``open()`` instantiates an long-lived email-sending connection. +* ``open()`` instantiates a long-lived email-sending connection. * ``close()`` closes the current email-sending connection. @@ -559,8 +561,8 @@ connection.close() -Testing email sending -===================== +Configuring email for development +================================= There are times when you do not want Django to send emails at all. For example, while developing a Web site, you probably don't want @@ -568,13 +570,13 @@ emails will be sent to the right people under the right conditions, and that those emails will contain the correct content. -The easiest way to test your project's use of email is to use the ``console`` -email backend. This backend redirects all email to stdout, allowing you to -inspect the content of mail. - -The ``file`` email backend can also be useful during development -- this backend -dumps the contents of every SMTP connection to a file that can be inspected -at your leisure. +The easiest way to configure email for local development is to use the +:ref:`console ` email backend. This backend +redirects all email to stdout, allowing you to inspect the content of mail. + +The :ref:`file ` email backend can also be useful +during development -- this backend dumps the contents of every SMTP connection +to a file that can be inspected at your leisure. Another approach is to use a "dumb" SMTP server that receives the emails locally and displays them to the terminal, but does not actually send @@ -585,7 +587,8 @@ This command will start a simple SMTP server listening on port 1025 of localhost. This server simply prints to standard output all email headers and the email body. You then only need to set the :setting:`EMAIL_HOST` and -:setting:`EMAIL_PORT` accordingly, and you are set. +:setting:`EMAIL_PORT` accordingly. For a more detailed discussion of SMTP +server options, see the Python documentation for the :mod:`smtpd` module. -For a more detailed discussion of testing and processing of emails locally, -see the Python documentation for the :mod:`smtpd` module. +For information about unit-testing the sending of emails in your application, +see the :ref:`topics-testing-email` section of the testing documentation. diff -Nru python-django-1.6.1/docs/topics/forms/formsets.txt python-django-1.6.11/docs/topics/forms/formsets.txt --- python-django-1.6.1/docs/topics/forms/formsets.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/forms/formsets.txt 2015-03-18 23:50:43.000000000 +0000 @@ -88,8 +88,7 @@ ------------------------------------ The ``max_num`` parameter to :func:`~django.forms.formsets.formset_factory` -gives you the ability to limit the maximum number of empty forms the formset -will display:: +gives you the ability to limit the number of forms the formset will display:: >>> from django.forms.formsets import formset_factory >>> from myapp.forms import ArticleForm @@ -100,16 +99,22 @@ -If the value of ``max_num`` is greater than the number of existing -objects, up to ``extra`` additional blank forms will be added to the formset, -so long as the total number of forms does not exceed ``max_num``. +If the value of ``max_num`` is greater than the number of existing items in the +initial data, up to ``extra`` additional blank forms will be added to the +formset, so long as the total number of forms does not exceed ``max_num``. For +example, if ``extra=2`` and ``max_num=2`` and the formset is initialized with +one ``initial`` item, a form for the initial item and one blank form will be +displayed. + +If the number of items in the initial data exceeds ``max_num``, all initial +data forms will be displayed regardless of the value of ``max_num`` and no +extra forms will be displayed. For example, if ``extra=3`` and ``max_num=1`` +and the formset is initialized with two initial items, two forms with the +initial data will be displayed. A ``max_num`` value of ``None`` (the default) puts a high limit on the number of forms displayed (1000). In practice this is equivalent to no limit. -If the number of forms in the initial data exceeds ``max_num``, all initial -data forms will be displayed regardless. (No extra forms will be displayed.) - By default, ``max_num`` only affects how many forms are displayed and does not affect validation. If ``validate_max=True`` is passed to the :func:`~django.forms.formsets.formset_factory`, then ``max_num`` will affect @@ -166,7 +171,7 @@ .. currentmodule:: django.forms.formsets.BaseFormSet -.. method:: total_error_count(self) +.. method:: total_error_count() .. versionadded:: 1.6 @@ -555,9 +560,9 @@
{{ formset.management_form }} {% for form in formset %} - {{ form.id }}
  • {{ form.title }}
  • +
  • {{ form.pub_date }}
  • {% if formset.can_delete %}
  • {{ form.DELETE }}
  • {% endif %} diff -Nru python-django-1.6.1/docs/topics/forms/index.txt python-django-1.6.11/docs/topics/forms/index.txt --- python-django-1.6.1/docs/topics/forms/index.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/forms/index.txt 2015-03-18 23:50:43.000000000 +0000 @@ -96,6 +96,7 @@ def contact(request): if request.method == 'POST': # If the form has been submitted... + # ContactForm was defined in the previous section form = ContactForm(request.POST) # A form bound to the POST data if form.is_valid(): # All validation rules pass # Process the data in form.cleaned_data diff -Nru python-django-1.6.1/docs/topics/forms/modelforms.txt python-django-1.6.11/docs/topics/forms/modelforms.txt --- python-django-1.6.1/docs/topics/forms/modelforms.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/forms/modelforms.txt 2015-03-18 23:50:43.000000000 +0000 @@ -497,6 +497,8 @@ For example if you wanted to customize the wording of all user facing strings for the ``name`` field:: + from django.utils.translation import ugettext_lazy as _ + class AuthorForm(ModelForm): class Meta: model = Author @@ -534,7 +536,7 @@ If you want to specify a field's validators, you can do so by defining the field declaratively and setting its ``validators`` parameter:: - from django.forms import ModelForm, DateField + from django.forms import ModelForm, CharField from myapp.models import Article class ArticleForm(ModelForm): @@ -747,6 +749,30 @@ >>> AuthorFormSet(queryset=Author.objects.none()) +Changing the ``form`` +--------------------- + +By default, when you use ``modelformset_factory``, a model form will +be created using :func:`~django.forms.models.modelform_factory`. +Often, it can be useful to specify a custom model form. For example, +you can create a custom model form that has custom validation:: + + class AuthorForm(forms.ModelForm): + class Meta: + model = Author + fields = ('name', 'title') + + def clean_name(self): + # custom validation for the name field + ... + +Then, pass your model form to the factory function:: + + AuthorFormSet = modelformset_factory(Author, form=AuthorForm) + +It is not always necessary to define a custom model form. The +``modelformset_factory`` function has several arguments which are +passed through to ``modelform_factory``, which are described below. Controlling which fields are used with ``fields`` and ``exclude`` ----------------------------------------------------------------- diff -Nru python-django-1.6.1/docs/topics/http/file-uploads.txt python-django-1.6.11/docs/topics/http/file-uploads.txt --- python-django-1.6.1/docs/topics/http/file-uploads.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/http/file-uploads.txt 2015-03-18 23:50:43.000000000 +0000 @@ -155,15 +155,18 @@ most Unix-like systems). :setting:`FILE_UPLOAD_PERMISSIONS` - The numeric mode (i.e. ``0644``) to set newly uploaded files to. For + The numeric mode (i.e. ``0o644``) to set newly uploaded files to. For more information about what these modes mean, see the documentation for :func:`os.chmod`. If this isn't given or is ``None``, you'll get operating-system dependent behavior. On most platforms, temporary files will have a mode - of ``0600``, and files saved from memory will be saved using the + of ``0o600``, and files saved from memory will be saved using the system's standard umask. + For security reasons, these permissions aren't applied to the temporary + files that are stored in :setting:`FILE_UPLOAD_TEMP_DIR`. + .. warning:: If you're not familiar with file modes, please note that the leading @@ -365,7 +368,7 @@ Custom file upload handlers **must** define the following methods: -``FileUploadHandler.receive_data_chunk(self, raw_data, start)`` +``FileUploadHandler.receive_data_chunk(raw_data, start)`` Receives a "chunk" of data from the file upload. ``raw_data`` is a byte string containing the uploaded data. @@ -385,7 +388,7 @@ If you raise a ``StopUpload`` or a ``SkipFile`` exception, the upload will abort or the file will be completely skipped. -``FileUploadHandler.file_complete(self, file_size)`` +``FileUploadHandler.file_complete(file_size)`` Called when a file has finished uploading. The handler should return an ``UploadedFile`` object that will be stored @@ -410,7 +413,7 @@ The default is 64*2\ :sup:`10` bytes, or 64 KB. -``FileUploadHandler.new_file(self, field_name, file_name, content_type, content_length, charset)`` +``FileUploadHandler.new_file(field_name, file_name, content_type, content_length, charset)`` Callback signaling that a new file upload is starting. This is called before any data has been fed to any upload handlers. @@ -430,10 +433,10 @@ This method may raise a ``StopFutureHandlers`` exception to prevent future handlers from handling this file. -``FileUploadHandler.upload_complete(self)`` +``FileUploadHandler.upload_complete()`` Callback signaling that the entire upload (all files) has completed. -``FileUploadHandler.handle_raw_input(self, input_data, META, content_length, boundary, encoding)`` +``FileUploadHandler.handle_raw_input(input_data, META, content_length, boundary, encoding)`` Allows the handler to completely override the parsing of the raw HTTP input. diff -Nru python-django-1.6.1/docs/topics/http/middleware.txt python-django-1.6.11/docs/topics/http/middleware.txt --- python-django-1.6.1/docs/topics/http/middleware.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/http/middleware.txt 2015-03-18 23:50:43.000000000 +0000 @@ -85,7 +85,7 @@ ``process_request`` ------------------- -.. method:: process_request(self, request) +.. method:: process_request(request) ``request`` is an :class:`~django.http.HttpRequest` object. @@ -106,7 +106,7 @@ ``process_view`` ---------------- -.. method:: process_view(self, request, view_func, view_args, view_kwargs) +.. method:: process_view(request, view_func, view_args, view_kwargs) ``request`` is an :class:`~django.http.HttpRequest` object. ``view_func`` is the Python function that Django is about to use. (It's the actual function @@ -146,7 +146,7 @@ ``process_template_response`` ----------------------------- -.. method:: process_template_response(self, request, response) +.. method:: process_template_response(request, response) ``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is the :class:`~django.template.response.TemplateResponse` object (or equivalent) @@ -173,7 +173,7 @@ ``process_response`` -------------------- -.. method:: process_response(self, request, response) +.. method:: process_response(request, response) ``request`` is an :class:`~django.http.HttpRequest` object. ``response`` is the :class:`~django.http.HttpResponse` or @@ -226,7 +226,7 @@ Response middleware may wrap it in a new generator, but must not consume it. Wrapping is typically implemented as follows:: - def wrap_streaming_content(content) + def wrap_streaming_content(content): for chunk in content: yield alter_content(chunk) @@ -235,7 +235,7 @@ ``process_exception`` --------------------- -.. method:: process_exception(self, request, exception) +.. method:: process_exception(request, exception) ``request`` is an :class:`~django.http.HttpRequest` object. ``exception`` is an ``Exception`` object raised by the view function. diff -Nru python-django-1.6.1/docs/topics/http/sessions.txt python-django-1.6.11/docs/topics/http/sessions.txt --- python-django-1.6.1/docs/topics/http/sessions.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/http/sessions.txt 2015-03-18 23:50:43.000000000 +0000 @@ -64,7 +64,9 @@ cache backend. The local-memory cache backend doesn't retain data long enough to be a good choice, and it'll be faster to use file or database sessions directly instead of sending everything through the file or - database cache backends. + database cache backends. Additionally, the local-memory cache backend is + NOT multi-process safe, therefore probably not a good choice for production + environments. If you have multiple caches defined in :setting:`CACHES`, Django will use the default cache. To use another cache, set :setting:`SESSION_CACHE_ALIAS` to the @@ -120,15 +122,8 @@ .. note:: - When using cookies-based sessions :mod:`django.contrib.sessions` can be - removed from :setting:`INSTALLED_APPS` setting because data is loaded - from the key itself and not from the database, so there is no need for the - creation and usage of ``django.contrib.sessions.models.Session`` table. - -.. note:: - It's recommended to leave the :setting:`SESSION_COOKIE_HTTPONLY` setting - ``True`` to prevent tampering of the stored data from JavaScript. + on ``True`` to prevent access to the stored data from JavaScript. .. warning:: @@ -218,17 +213,17 @@ Example: ``fav_color = request.session.pop('fav_color')`` - .. method:: keys + .. method:: keys() - .. method:: items + .. method:: items() - .. method:: setdefault + .. method:: setdefault() - .. method:: clear + .. method:: clear() It also has these methods: - .. method:: flush + .. 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 @@ -236,21 +231,21 @@ accessed again from the user's browser (for example, the :func:`django.contrib.auth.logout()` function calls it). - .. method:: set_test_cookie + .. method:: set_test_cookie() Sets a test cookie to determine whether the user's browser supports cookies. Due to the way cookies work, you won't be able to test this until the user's next page request. See `Setting test cookies`_ below for more information. - .. method:: test_cookie_worked + .. method:: test_cookie_worked() Returns either ``True`` or ``False``, depending on whether the user's browser accepted the test cookie. Due to the way cookies work, you'll have to call ``set_test_cookie()`` on a previous, separate page request. See `Setting test cookies`_ below for more information. - .. method:: delete_test_cookie + .. method:: delete_test_cookie() Deletes the test cookie. Use this to clean up after yourself. @@ -279,7 +274,7 @@ purposes. Session expiration is computed from the last time the session was *modified*. - .. method:: get_expiry_age + .. method:: get_expiry_age() Returns the number of seconds until this session expires. For sessions with no custom expiration (or those set to expire at browser close), this @@ -290,11 +285,11 @@ - ``modification``: last modification of the session, as a :class:`~datetime.datetime` object. Defaults to the current time. - ``expiry``: expiry information for the session, as a - :class:`~datetime.datetime` object, an :func:`int` (in seconds), or + :class:`~datetime.datetime` object, an :class:`int` (in seconds), or ``None``. Defaults to the value stored in the session by :meth:`set_expiry`, if there is one, or ``None``. - .. method:: get_expiry_date + .. method:: get_expiry_date() Returns the date this session will expire. For sessions with no custom expiration (or those set to expire at browser close), this will equal the @@ -302,19 +297,19 @@ This function accepts the same keyword argumets as :meth:`get_expiry_age`. - .. method:: get_expire_at_browser_close + .. method:: get_expire_at_browser_close() Returns either ``True`` or ``False``, depending on whether the user's session cookie will expire when the user's Web browser is closed. - .. method:: clear_expired + .. method:: clear_expired() .. versionadded:: 1.5 Removes expired sessions from the session store. This class method is called by :djadmin:`clearsessions`. - .. method:: cycle_key + .. method:: cycle_key() Creates a new session key while retaining the current session data. :func:`django.contrib.auth.login()` calls this method to mitigate against @@ -653,8 +648,8 @@ ================ Subdomains within a site are able to set cookies on the client for the whole -domain. This makes session fixation possible if all subdomains are not -controlled by trusted users (or, are at least unable to set cookies). +domain. This makes session fixation possible if cookies are permitted from +subdomains not controlled by trusted users. For example, an attacker could log into ``good.example.com`` and get a valid session for his account. If the attacker has control over ``bad.example.com``, diff -Nru python-django-1.6.1/docs/topics/http/shortcuts.txt python-django-1.6.11/docs/topics/http/shortcuts.txt --- python-django-1.6.1/docs/topics/http/shortcuts.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/http/shortcuts.txt 2015-03-18 23:50:43.000000000 +0000 @@ -24,6 +24,11 @@ :func:`render_to_response()` with a `context_instance` argument that forces the use of a :class:`~django.template.RequestContext`. + Django does not provide a shortcut function which returns a + :class:`~django.template.response.TemplateResponse` because the constructor + of :class:`~django.template.response.TemplateResponse` offers the same level + of convenience as :func:`render()`. + Required arguments ------------------ @@ -145,7 +150,7 @@ def my_view(request): # View code here... return render_to_response('myapp/index.html', {"foo": "bar"}, - mimetype="application/xhtml+xml") + content_type="application/xhtml+xml") This example is equivalent to:: diff -Nru python-django-1.6.1/docs/topics/http/urls.txt python-django-1.6.11/docs/topics/http/urls.txt --- python-django-1.6.1/docs/topics/http/urls.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/http/urls.txt 2015-03-18 23:50:43.000000000 +0000 @@ -51,11 +51,17 @@ 3. Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL. -4. Once one of the regexes matches, Django imports and calls the given - view, which is a simple Python function (or a :doc:`class based view - `). The view gets passed an - :class:`~django.http.HttpRequest` as its first argument and any values - captured in the regex as remaining arguments. +4. Once one of the regexes matches, Django imports and calls the given view, + which is a simple Python function (or a :doc:`class based view + `). The view gets passed the following + arguments: + + * An instance of :class:`~django.http.HttpRequest`. + * If the matched regular expression returned no named groups, then the + matches from the regular expression are provided as positional arguments. + * The keyword arguments are made up of any named groups matched by the + regular expression, overridden by any arguments specified in the optional + ``kwargs`` argument to :func:`django.conf.urls.url`. 5. If no regex matches, or if an exception is raised during any point in this process, Django invokes an appropriate @@ -106,7 +112,7 @@ * ``/articles/2003/03/03/`` would match the final pattern. Django would call the function ``news.views.article_detail(request, '2003', '03', '03')``. -.. _Dive Into Python's explanation: http://diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3 +.. _Dive Into Python's explanation: http://www.diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3 Named groups ============ @@ -178,8 +184,8 @@ methods -- ``POST``, ``GET``, ``HEAD``, etc. -- will be routed to the same function for the same URL. -Notes on capturing text in URLs -=============================== +Captured arguments are always strings +===================================== Each captured argument is sent to the view as a plain Python string, regardless of what sort of match the regular expression makes. For example, in this @@ -190,6 +196,9 @@ ...the ``year`` argument to ``news.views.year_archive()`` will be a string, not an integer, even though the ``\d{4}`` will only match integer strings. +Specifying defaults for view arguments +====================================== + A convenient trick is to specify default parameters for your views' arguments. Here's an example URLconf and view:: @@ -866,7 +875,7 @@ url(r'^advanced/$', 'apps.help.views.views.advanced'), ) - url(r'^help/', include(help_patterns, 'bar', 'foo')), + url(r'^help/', include((help_patterns, 'bar', 'foo'))), This will include the nominated URL patterns into the given application and instance namespace. @@ -877,3 +886,8 @@ site, plus the application namespace ``'admin'``, and the name of the admin instance. It is this ``urls`` attribute that you ``include()`` into your projects ``urlpatterns`` when you deploy an Admin instance. + +Be sure to pass a tuple to ``include()``. If you simply pass three arguments: +``include(help_patterns, 'bar', 'foo')``, Django won't throw an error but due +to the signature of ``include()``, ``'bar'`` will be the instance namespace and +``'foo'`` will be the application namespace instead of vice versa. diff -Nru python-django-1.6.1/docs/topics/i18n/timezones.txt python-django-1.6.11/docs/topics/i18n/timezones.txt --- python-django-1.6.1/docs/topics/i18n/timezones.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/i18n/timezones.txt 2015-03-18 23:50:43.000000000 +0000 @@ -30,7 +30,9 @@ Time zone support is disabled by default. To enable it, set :setting:`USE_TZ = True ` in your settings file. Installing pytz_ is highly recommended, -but not mandatory. It's as simple as: +but may not be mandatory depending on your particular database backend, +operating system and time zone. If you encounter an exception querying dates +or times, please try installing it before filing a bug. It's as simple as: .. code-block:: bash @@ -76,20 +78,13 @@ now = datetime.datetime.now() -When time zone support is enabled, Django uses time-zone-aware datetime -objects. If your code creates datetime objects, they should be aware too. In -this mode, the example above becomes:: +When time zone support is enabled (:setting:`USE_TZ=True `), Django uses +time-zone-aware datetime objects. If your code creates datetime objects, they +should be aware too. In this mode, the example above becomes:: - import datetime - from django.utils.timezone import utc - - now = datetime.datetime.utcnow().replace(tzinfo=utc) - -.. note:: + from django.utils import timezone - :mod:`django.utils.timezone` provides a - :func:`~django.utils.timezone.now()` function that returns a naive or - aware datetime object according to the value of :setting:`USE_TZ`. + now = timezone.now() .. warning:: @@ -107,7 +102,8 @@ lead to questionable usefulness". Django only supports naive time objects and will raise an exception if you - attempt to save an aware time object. + attempt to save an aware time object, as a timezone for a time with no + associated date does not make sense. .. _naive-datetime-objects: @@ -553,7 +549,7 @@ >>> import datetime >>> from django.utils import timezone >>> naive = datetime.datetime.utcnow() - >>> aware = naive.replace(tzinfo=timezone.utc) + >>> aware = timezone.now() >>> naive == aware Traceback (most recent call last): ... @@ -649,6 +645,12 @@ >>> local.date() datetime.date(2012, 3, 3) +4. **I get an error** "``Are time zone definitions for your database and pytz + installed?``" **pytz is installed, so I guess the problem is my database?** + + If you are using MySQL, see the :ref:`mysql-time-zone-definitions` section + of the MySQL notes for instructions on loading time zone definitions. + Usage ----- diff -Nru python-django-1.6.1/docs/topics/i18n/translation.txt python-django-1.6.11/docs/topics/i18n/translation.txt --- python-django-1.6.1/docs/topics/i18n/translation.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/i18n/translation.txt 2015-03-18 23:50:43.000000000 +0000 @@ -124,7 +124,7 @@ This technique lets language-specific translations reorder the placeholder text. For example, an English translation may be ``"Today is November 26."``, while a Spanish translation may be ``"Hoy es 26 de Noviembre."`` -- with the -the month and the day placeholders swapped. +month and the day placeholders swapped. For this reason, you should use named-string interpolation (e.g., ``%(day)s``) instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you @@ -206,7 +206,9 @@ In this example the number of objects is passed to the translation languages as the ``count`` variable. -Lets see a slightly more complex usage example:: +Note that pluralization is complicated and works differently in each language. +Comparing ``count`` to 1 isn't always the correct rule. This code looks +sophisticated, but will produce incorrect results for some languages:: from django.utils.translation import ungettext from myapp.models import Report @@ -218,42 +220,45 @@ name = Report._meta.verbose_name_plural text = ungettext( - 'There is %(count)d %(name)s available.', - 'There are %(count)d %(name)s available.', - count + 'There is %(count)d %(name)s available.', + 'There are %(count)d %(name)s available.', + count ) % { 'count': count, 'name': name } -Here we reuse localizable, hopefully already translated literals (contained in -the ``verbose_name`` and ``verbose_name_plural`` model ``Meta`` options) for -other parts of the sentence so all of it is consistently based on the -cardinality of the elements at play. +Don't try to implement your own singular-or-plural logic, it won't be correct. +In a case like this, consider something like the following:: + + text = ungettext( + 'There is %(count)d %(name)s object available.', + 'There are %(count)d %(name)s objects available.', + count + ) % { + 'count': count, + 'name': Report._meta.verbose_name, + } .. _pluralization-var-notes: .. note:: - When using this technique, make sure you use a single name for every - extrapolated variable included in the literal. In the example above note how - we used the ``name`` Python variable in both translation strings. This - example would fail:: + When using ``ungettext()``, make sure you use a single name for every + extrapolated variable included in the literal. In the examples above, note + how we used the ``name`` Python variable in both translation strings. This + example, besides being incorrect in some languages as noted above, would + fail:: - from django.utils.translation import ungettext - from myapp.models import Report - - count = Report.objects.count() - d = { - 'count': count, + text = ungettext( + 'There is %(count)d %(name)s available.', + 'There are %(count)d %(plural_name)s available.', + count + ) % { + 'count': Report.objects.count(), 'name': Report._meta.verbose_name, 'plural_name': Report._meta.verbose_name_plural } - text = ungettext( - 'There is %(count)d %(name)s available.', - 'There are %(count)d %(plural_name)s available.', - count - ) % d You would get an error when running :djadmin:`django-admin.py compilemessages `:: @@ -557,7 +562,7 @@ It's not possible to mix a template variable inside a string within ``{% trans %}``. If your translations require strings with variables (placeholders), use -``{% blocktrans %}`` instead. +:ttag:`{% blocktrans %}` instead. If you'd like to retrieve a translated string without displaying it, you can @@ -745,7 +750,7 @@ msgid "Go" msgstr "" - #. Translators: + #. Translators: This is a text of the base template # path/to/template/file.html:103 msgid "Ambiguous translatable block of text" msgstr "" @@ -841,7 +846,7 @@ * ``{{ LANGUAGE_CODE|language_name }}`` ("German") * ``{{ LANGUAGE_CODE|language_name_local }}`` ("Deutsch") -* ``{{ LANGUAGE_CODE|bidi }}`` (False) +* ``{{ LANGUAGE_CODE|language_bidi }}`` (False) .. _Django templates: ../templates_python/ @@ -1009,7 +1014,7 @@ you're using ETags (:setting:`USE_ETAGS = True `), you're already covered. Otherwise, you can apply :ref:`conditional decorators `. In the following example, the cache is invalidated -whenever your restart your application server. +whenever you restart your application server. .. code-block:: python @@ -1216,10 +1221,9 @@ django-admin.py makemessages -l de -...where ``de`` is the language code for the message file you want to create. -The language code, in this case, is in :term:`locale format`. For -example, it's ``pt_BR`` for Brazilian Portuguese and ``de_AT`` for Austrian -German. +...where ``de`` is the :term:`locale name` for the message file you want to +create. For example, ``pt_BR`` for Brazilian Portuguese, ``de_AT`` for Austrian +German or ``id`` for Indonesian. The script should be run from one of two places: @@ -1547,7 +1551,7 @@ files ` and their compiled versions (``.mo``) exist. If you want to let each individual user specify which language he or she -prefers, then you also need to use use the ``LocaleMiddleware``. +prefers, then you also need to use the ``LocaleMiddleware``. ``LocaleMiddleware`` enables language selection based on data from the request. It customizes content for each user. diff -Nru python-django-1.6.1/docs/topics/install.txt python-django-1.6.11/docs/topics/install.txt --- python-django-1.6.1/docs/topics/install.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/install.txt 2015-03-18 23:50:43.000000000 +0000 @@ -22,12 +22,8 @@ .. admonition:: Python on Windows - On Windows, you might need to adjust your ``PATH`` environment variable - to include paths to Python executable and additional scripts. For example, - if your Python is installed in ``C:\Python27\``, the following paths need - to be added to ``PATH``:: - - C:\Python27\;C:\Python27\Scripts; + If you are just starting with Django and using Windows, you may find + :doc:`/howto/windows` useful. Install Apache and mod_wsgi ============================= @@ -87,19 +83,9 @@ recommended to develop with the same database as you plan on using in production. -In addition to the officially supported databases, there are backends provided -by 3rd parties that allow you to use other databases with Django: - -* `Sybase SQL Anywhere`_ -* `IBM DB2`_ -* `Microsoft SQL Server 2005`_ -* Firebird_ -* ODBC_ - -The Django versions and ORM features supported by these unofficial backends -vary considerably. Queries regarding the specific capabilities of these -unofficial backends, along with any support queries, should be directed to the -support channels provided by each 3rd party project. +In addition to the officially supported databases, there are :ref:`backends +provided by 3rd parties ` that allow you to use other +databases with Django. In addition to a database backend, you'll need to make sure your Python database bindings are installed. @@ -186,14 +172,6 @@ It's easy, no matter which way you choose. -Installing a distribution-specific package -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Check the :doc:`distribution specific notes ` to see if -your platform/distribution provides official Django packages/installers. -Distribution-provided packages will typically allow for automatic installation -of dependencies and easy upgrade paths. - .. _installing-official-release: Installing an official release with ``pip`` @@ -258,6 +236,15 @@ .. _bsdtar: http://gnuwin32.sourceforge.net/packages/bsdtar.htm .. _7-zip: http://www.7-zip.org/ +Installing a distribution-specific package +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Check the :doc:`distribution specific notes ` to see if +your platform/distribution provides official Django packages/installers. +Distribution-provided packages will typically allow for automatic installation +of dependencies and easy upgrade paths; however, these packages will rarely +contain the latest release of Django. + .. _installing-development-version: Installing the development version diff -Nru python-django-1.6.1/docs/topics/logging.txt python-django-1.6.11/docs/topics/logging.txt --- python-django-1.6.1/docs/topics/logging.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/logging.txt 2015-03-18 23:50:43.000000000 +0000 @@ -113,8 +113,9 @@ Ultimately, a log record needs to be rendered as text. Formatters describe the exact format of that text. A formatter usually consists -of a Python formatting string; however, you can also write custom -formatters to implement specific formatting behavior. +of a Python formatting string containing +:ref:`LogRecord attributes `; however, +you can also write custom formatters to implement specific formatting behavior. Using logging ============= @@ -237,15 +238,40 @@ .. _dictConfig format: http://docs.python.org/library/logging.config.html#configuration-dictionary-schema -.. _a third-party library: http://bitbucket.org/vinay.sajip/dictconfig - -An example ----------- +Examples +-------- The full documentation for `dictConfig format`_ is the best source of information about logging configuration dictionaries. However, to give -you a taste of what is possible, here is an example of a fairly -complex logging setup, configured using :func:`logging.config.dictConfig`:: +you a taste of what is possible, here are a couple examples. + +First, here's a simple configuration which writes all request logging from the +:ref:`django-request-logger` logger to a local file:: + + LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': '/path/to/django/debug.log', + }, + }, + 'loggers': { + 'django.request': { + 'handlers': ['file'], + 'level': 'DEBUG', + 'propagate': True, + }, + }, + } + +If you use this example, be sure to change the ``'filename'`` path to a +location that's writable by the user that's running the Django application. + +Second, here's an example of a fairly complex logging setup, configured using +:func:`logging.config.dictConfig`:: LOGGING = { 'version': 1, @@ -269,7 +295,7 @@ 'level': 'DEBUG', 'class': 'django.utils.log.NullHandler', }, - 'console':{ + 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' @@ -405,6 +431,8 @@ ``django`` is the catch-all logger. No messages are posted directly to this logger. +.. _django-request-logger: + ``django.request`` ~~~~~~~~~~~~~~~~~~ @@ -423,9 +451,9 @@ ``django.db.backends`` ~~~~~~~~~~~~~~~~~~~~~~ -Messages relating to the interaction of code with the database. -For example, every SQL statement executed by a request is logged -at the ``DEBUG`` level to this logger. +Messages relating to the interaction of code with the database. For example, +every application-level SQL statement executed by a request is logged at the +``DEBUG`` level to this logger. Messages to this logger have the following extra context: @@ -437,6 +465,11 @@ ``settings.DEBUG`` is set to ``True``, regardless of the logging level or handlers that are installed. +This logging does not include framework-level initialization (e.g. +``SET TIMEZONE``) or transaction management queries (e.g. ``BEGIN``, +``COMMIT``, and ``ROLLBACK``). Turn on query logging in your database if you +wish the view all database queries. + ``django.security.*`` ~~~~~~~~~~~~~~~~~~~~~~ diff -Nru python-django-1.6.1/docs/topics/python3.txt python-django-1.6.11/docs/topics/python3.txt --- python-django-1.6.1/docs/topics/python3.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/python3.txt 2015-03-18 23:50:43.000000000 +0000 @@ -78,8 +78,8 @@ String handling --------------- -Python 2's :func:`unicode` type was renamed :func:`str` in Python 3, -:func:`str` was renamed ``bytes()``, and :func:`basestring` disappeared. +Python 2's :func:`unicode` type was renamed ``str()`` in Python 3, +``str()`` was renamed ``bytes()``, and :func:`basestring` disappeared. six_ provides :ref:`tools ` to deal with these changes. @@ -138,7 +138,7 @@ :meth:`~object.__unicode__` methods. If these methods exist, they must return ``str`` (bytes) and ``unicode`` (text) respectively. -The ``print`` statement and the :func:`str` built-in call +The ``print`` statement and the :class:`str` built-in call :meth:`~object.__str__` to determine the human-readable representation of an object. The :func:`unicode` built-in calls :meth:`~object.__unicode__` if it exists, and otherwise falls back to :meth:`~object.__str__` and decodes the @@ -233,7 +233,7 @@ my_bytestring = b"This is a bytestring" If you need a byte string literal under Python 2 and a unicode string literal -under Python 3, use the :func:`str` builtin:: +under Python 3, use the :class:`str` builtin:: str('my string') @@ -359,7 +359,10 @@ ``xrange`` ~~~~~~~~~~ -Import ``six.moves.xrange`` wherever you use ``xrange``. +If you use ``xrange`` on Python 2, import ``six.moves.range`` and use that +instead. You can also import ``six.moves.xrange`` (it's equivalent to +``six.moves.range``) but the first technique allows you to simply drop the +import when dropping support for Python 2. Moved modules ~~~~~~~~~~~~~ @@ -385,20 +388,4 @@ -------------------------------- The version of six bundled with Django (``django.utils.six``) includes a few -extras. - -.. function:: assertRaisesRegex(testcase, *args, **kwargs) - - This replaces ``testcase.assertRaisesRegexp`` on Python 2, and - ``testcase.assertRaisesRegex`` on Python 3. ``assertRaisesRegexp`` still - exists in current Python 3 versions, but issues a warning. - -.. function:: assertRegex(testcase, *args, **kwargs) - - This replaces ``testcase.assertRegexpMatches`` on Python 2, and - ``testcase.assertRegex`` on Python 3. ``assertRegexpMatches`` still - exists in current Python 3 versions, but issues a warning. - - -In addition to six' defaults moves, Django's version provides ``thread`` as -``_thread`` and ``dummy_thread`` as ``_dummy_thread``. +customizations for internal use only. diff -Nru python-django-1.6.1/docs/topics/security.txt python-django-1.6.11/docs/topics/security.txt --- python-django-1.6.1/docs/topics/security.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/security.txt 2015-03-18 23:50:43.000000000 +0000 @@ -79,6 +79,7 @@ Be very careful with marking views with the ``csrf_exempt`` decorator unless it is absolutely necessary. +.. _sql-injection-protection: SQL injection protection ======================== @@ -236,11 +237,11 @@ you can take to mitigate these attacks: 1. One class of attacks can be prevented by always serving user uploaded - content from a distinct Top Level Domain (TLD). This prevents any - exploit blocked by `same-origin policy`_ protections such as cross site - scripting. For example, if your site runs on ``example.com``, you would - want to serve uploaded content (the :setting:`MEDIA_URL` setting) from - something like ``usercontent-example.com``. It's *not* sufficient to + content from a distinct top-level or second-level domain. This prevents + any exploit blocked by `same-origin policy`_ protections such as cross + site scripting. For example, if your site runs on ``example.com``, you + would want to serve uploaded content (the :setting:`MEDIA_URL` setting) + from something like ``usercontent-example.com``. It's *not* sufficient to serve content from a subdomain like ``usercontent.example.com``. 2. Beyond this, applications may choose to define a whitelist of allowable diff -Nru python-django-1.6.1/docs/topics/settings.txt python-django-1.6.11/docs/topics/settings.txt --- python-django-1.6.1/docs/topics/settings.txt 2013-08-13 17:17:09.000000000 +0000 +++ python-django-1.6.11/docs/topics/settings.txt 2015-03-18 23:40:08.000000000 +0000 @@ -46,7 +46,7 @@ ``mysite.settings``. Note that the settings module should be on the Python `import search path`_. -.. _import search path: http://diveintopython.net/getting_to_know_python/everything_is_an_object.html +.. _import search path: http://www.diveintopython.net/getting_to_know_python/everything_is_an_object.html The django-admin.py utility --------------------------- @@ -267,3 +267,8 @@ ``DJANGO_SETTINGS_MODULE``. Not both, and not neither. .. _@login_required: ../authentication/#the-login-required-decorator + +.. seealso:: + + :doc:`The Settings Reference ` + Contains the complete list of core and contrib app settings. diff -Nru python-django-1.6.1/docs/topics/signals.txt python-django-1.6.11/docs/topics/signals.txt --- python-django-1.6.1/docs/topics/signals.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/signals.txt 2015-03-18 23:50:43.000000000 +0000 @@ -227,7 +227,8 @@ This declares a ``pizza_done`` signal that will provide receivers with ``toppings`` and ``size`` arguments. -Remember that you're allowed to change this list of arguments at any time, so getting the API right on the first try isn't necessary. +Remember that you're allowed to change this list of arguments at any time, so +getting the API right on the first try isn't necessary. Sending signals --------------- @@ -238,8 +239,8 @@ .. method:: Signal.send_robust(sender, **kwargs) To send a signal, call either :meth:`Signal.send` or :meth:`Signal.send_robust`. -You must provide the ``sender`` argument, and may provide as many other keyword -arguments as you like. +You must provide the ``sender`` argument (which is a class most of the time), +and may provide as many other keyword arguments as you like. For example, here's how sending our ``pizza_done`` signal might look: @@ -249,7 +250,7 @@ ... def send_pizza(self, toppings, size): - pizza_done.send(sender=self, toppings=toppings, size=size) + pizza_done.send(sender=self.__class__, toppings=toppings, size=size) ... Both ``send()`` and ``send_robust()`` return a list of tuple pairs diff -Nru python-django-1.6.1/docs/topics/templates.txt python-django-1.6.11/docs/topics/templates.txt --- python-django-1.6.1/docs/topics/templates.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/templates.txt 2015-03-18 23:50:43.000000000 +0000 @@ -166,13 +166,13 @@ If ``value`` is ``['a', 'b', 'c', 'd']``, the output will be ``4``. -:tfilter:`striptags` - Strips all [X]HTML tags. For example:: +:tfilter:`filesizeformat` + Formats the value like a "human-readable" file size (i.e. ``'13 KB'``, + ``'4.1 MB'``, ``'102 bytes'``, etc). For example:: - {{ value|striptags }} + {{ value|filesizeformat }} - If ``value`` is ``"Joel a - slug"``, the output will be ``"Joel is a slug"``. + If ``value`` is 123456789, the output would be ``117.7 MB``. Again, these are just a few examples; see the :ref:`built-in filter reference ` for the complete list. @@ -211,18 +211,23 @@ {% endfor %}
-:ttag:`if` and ``else`` +:ttag:`if`, ``elif``, and ``else`` Evaluates a variable, and if that variable is "true" the contents of the block are displayed:: {% if athlete_list %} Number of athletes: {{ athlete_list|length }} + {% elif athlete_in_locker_room_list %} + Athletes should be out of the locker room soon! {% else %} No athletes. {% endif %} In the above, if ``athlete_list`` is not empty, the number of athletes - will be displayed by the ``{{ athlete_list|length }}`` variable. + will be displayed by the ``{{ athlete_list|length }}`` variable. Otherwise, + if ``athlete_in_locker_room_list`` is not empty, the message "Athletes + should be out..." will be displayed. If both lists are empty, + "No athletes." will be displayed. You can also use filters and various operators in the :ttag:`if` tag:: @@ -652,20 +657,23 @@ =============================== Certain applications provide custom tag and filter libraries. To access them in -a template, use the :ttag:`load` tag:: - - {% load comments %} - - {% comment_form for blogs.entries entry.id with is_public yes %} - -In the above, the :ttag:`load` tag loads the ``comments`` tag library, which then -makes the ``comment_form`` tag available for use. Consult the documentation -area in your admin to find the list of custom libraries in your installation. +a template, ensure the application is in :setting:`INSTALLED_APPS` (we'd add +``'django.contrib.humanize'`` for this example), and then use the :ttag:`load` +tag in a template:: + + {% load humanize %} + + {{ 45000|intcomma }} + +In the above, the :ttag:`load` tag loads the ``humanize`` tag library, which then +makes the ``intcomma`` filter available for use. If you've enabled +:mod:`django.contrib.admindocs`, you can consult the documentation area in your +admin to find the list of custom libraries in your installation. The :ttag:`load` tag can take multiple library names, separated by spaces. Example:: - {% load comments i18n %} + {% load humanize i18n %} See :doc:`/howto/custom-template-tags` for information on writing your own custom template libraries. @@ -677,9 +685,15 @@ available to the current template -- not any parent or child templates along the template-inheritance path. -For example, if a template ``foo.html`` has ``{% load comments %}``, a child +For example, if a template ``foo.html`` has ``{% load humanize %}``, a child template (e.g., one that has ``{% extends "foo.html" %}``) will *not* have -access to the comments template tags and filters. The child template is -responsible for its own ``{% load comments %}``. +access to the humanize template tags and filters. The child template is +responsible for its own ``{% load humanize %}``. This is a feature for the sake of maintainability and sanity. + +.. seealso:: + + :doc:`The Templates Reference ` + Covers built-in tags, built-in filters, using an alternative template, + language, and more. diff -Nru python-django-1.6.1/docs/topics/testing/advanced.txt python-django-1.6.11/docs/topics/testing/advanced.txt --- python-django-1.6.1/docs/topics/testing/advanced.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/testing/advanced.txt 2015-03-18 23:50:43.000000000 +0000 @@ -52,7 +52,7 @@ # Create an instance of a GET request. request = self.factory.get('/customer/details') - # Recall that middleware are not suported. You can simulate a + # Recall that middleware are not supported. You can simulate a # logged-in user by setting request.user manually. request.user = self.user @@ -363,21 +363,9 @@ Run the test suite. - ``test_labels`` is a list of strings describing the tests to be run. A test - label can take one of four forms: - - * ``path.to.test_module.TestCase.test_method`` -- Run a single test method - in a test case. - * ``path.to.test_module.TestCase`` -- Run all the test methods in a test - case. - * ``path.to.module`` -- Search for and run all tests in the named Python - package or module. - * ``path/to/directory`` -- Search for and run all tests below the named - directory. - - If ``test_labels`` has a value of ``None``, the test runner will search for - tests in all files below the current directory whose names match its - ``pattern`` (see above). + ``test_labels`` allows you to specify which tests to run and supports + several formats (see :meth:`DiscoverRunner.build_suite` for a list of + supported formats). ``extra_tests`` is a list of extra ``TestCase`` instances to add to the suite that is executed by the test runner. These extra tests are run @@ -396,15 +384,20 @@ Constructs a test suite that matches the test labels provided. ``test_labels`` is a list of strings describing the tests to be run. A test - label can take one of three forms: + label can take one of four forms: - * ``app.TestCase.test_method`` -- Run a single test method in a test + * ``path.to.test_module.TestCase.test_method`` -- Run a single test method + in a test case. + * ``path.to.test_module.TestCase`` -- Run all the test methods in a test case. - * ``app.TestCase`` -- Run all the test methods in a test case. - * ``app`` -- Search for and run all tests in the named application. + * ``path.to.module`` -- Search for and run all tests in the named Python + package or module. + * ``path/to/directory`` -- Search for and run all tests below the named + directory. - If ``test_labels`` has a value of ``None``, the test runner should run - search for tests in all the applications in :setting:`INSTALLED_APPS`. + If ``test_labels`` has a value of ``None``, the test runner will search for + tests in all files below the current directory whose names match its + ``pattern`` (see above). ``extra_tests`` is a list of extra ``TestCase`` instances to add to the suite that is executed by the test runner. These extra tests are run diff -Nru python-django-1.6.1/docs/topics/testing/index.txt python-django-1.6.11/docs/topics/testing/index.txt --- python-django-1.6.1/docs/topics/testing/index.txt 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/docs/topics/testing/index.txt 2015-03-09 15:58:12.000000000 +0000 @@ -2,12 +2,6 @@ Testing in Django ================= -.. toctree:: - :hidden: - - overview - advanced - Automated testing is an extremely useful bug-killing tool for the modern Web developer. You can use a collection of tests -- a **test suite** -- to solve, or avoid, a number of problems: @@ -28,9 +22,6 @@ The best part is, it's really easy. -Where to go from here -===================== - The preferred way to write tests in Django is using the :mod:`unittest` module built in to the Python standard library. This is covered in detail in the :doc:`overview` document. @@ -38,3 +29,10 @@ You can also use any *other* Python test framework; Django provides an API and tools for that kind of integration. They are described in the :ref:`other-testing-frameworks` section of :doc:`advanced`. + +.. toctree:: + :maxdepth: 1 + + overview + tools + advanced diff -Nru python-django-1.6.1/docs/topics/testing/overview.txt python-django-1.6.11/docs/topics/testing/overview.txt --- python-django-1.6.1/docs/topics/testing/overview.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/docs/topics/testing/overview.txt 2015-03-18 23:50:43.000000000 +0000 @@ -1,14 +1,15 @@ -=========================== -Testing Django applications -=========================== +========================= +Writing and running tests +========================= .. module:: django.test :synopsis: Testing tools for Django applications. .. seealso:: - The :doc:`testing tutorial ` and the - :doc:`advanced testing topics `. + The :doc:`testing tutorial `, the :doc:`testing tools + reference `, and the :doc:`advanced testing topics + `. This document is split into two primary sections. First, we explain how to write tests with Django. Then, we explain how to run them. @@ -320,1517 +321,3 @@ Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing algorithm used in fixtures, if any. - -Testing tools -============= - -Django provides a small set of tools that come in handy when writing tests. - -.. _test-client: - -The test client ---------------- - -.. module:: django.test.client - :synopsis: Django's test client. - -The test client is a Python class that acts as a dummy Web browser, allowing -you to test your views and interact with your Django-powered application -programmatically. - -Some of the things you can do with the test client are: - -* Simulate GET and POST requests on a URL and observe the response -- - everything from low-level HTTP (result headers and status codes) to - page content. - -* See the chain of redirects (if any) and check the URL and status code at - each step. - -* Test that a given request is rendered by a given Django template, with - a template context that contains certain values. - -Note that the test client is not intended to be a replacement for Selenium_ or -other "in-browser" frameworks. Django's test client has a different focus. In -short: - -* Use Django's test client to establish that the correct template is being - rendered and that the template is passed the correct context data. - -* Use in-browser frameworks like Selenium_ to test *rendered* HTML and the - *behavior* of Web pages, namely JavaScript functionality. Django also - provides special support for those frameworks; see the section on - :class:`~django.test.LiveServerTestCase` for more details. - -A comprehensive test suite should use a combination of both test types. - -Overview and a quick example -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To use the test client, instantiate ``django.test.client.Client`` and retrieve -Web pages:: - - >>> from django.test.client import Client - >>> c = Client() - >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) - >>> response.status_code - 200 - >>> response = c.get('/customer/details/') - >>> response.content - '>> c.get('/login/') - - This is incorrect:: - - >>> c.get('http://www.example.com/login/') - - The test client is not capable of retrieving Web pages that are not - powered by your Django project. If you need to retrieve other Web pages, - use a Python standard library module such as :mod:`urllib` or - :mod:`urllib2`. - -* To resolve URLs, the test client uses whatever URLconf is pointed-to by - your :setting:`ROOT_URLCONF` setting. - -* Although the above example would work in the Python interactive - interpreter, some of the test client's functionality, notably the - template-related functionality, is only available *while tests are - running*. - - The reason for this is that Django's test runner performs a bit of black - magic in order to determine which template was loaded by a given view. - This black magic (essentially a patching of Django's template system in - memory) only happens during test running. - -* By default, the test client will disable any CSRF checks - performed by your site. - - If, for some reason, you *want* the test client to perform CSRF - checks, you can create an instance of the test client that - enforces CSRF checks. To do this, pass in the - ``enforce_csrf_checks`` argument when you construct your - client:: - - >>> from django.test import Client - >>> csrf_client = Client(enforce_csrf_checks=True) - -Making requests -~~~~~~~~~~~~~~~ - -Use the ``django.test.client.Client`` class to make requests. - -.. class:: Client(enforce_csrf_checks=False, **defaults) - - It requires no arguments at time of construction. However, you can use - keywords arguments to specify some default headers. For example, this will - send a ``User-Agent`` HTTP header in each request:: - - >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') - - The values from the ``extra`` keywords arguments passed to - :meth:`~django.test.client.Client.get()`, - :meth:`~django.test.client.Client.post()`, etc. have precedence over - the defaults passed to the class constructor. - - The ``enforce_csrf_checks`` argument can be used to test CSRF - protection (see above). - - Once you have a ``Client`` instance, you can call any of the following - methods: - - .. method:: Client.get(path, data={}, follow=False, **extra) - - - Makes a GET request on the provided ``path`` and returns a ``Response`` - object, which is documented below. - - The key-value pairs in the ``data`` dictionary are used to create a GET - data payload. For example:: - - >>> c = Client() - >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}) - - ...will result in the evaluation of a GET request equivalent to:: - - /customers/details/?name=fred&age=7 - - The ``extra`` keyword arguments parameter can be used to specify - headers to be sent in the request. For example:: - - >>> c = Client() - >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, - ... HTTP_X_REQUESTED_WITH='XMLHttpRequest') - - ...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the - details view, which is a good way to test code paths that use the - :meth:`django.http.HttpRequest.is_ajax()` method. - - .. admonition:: CGI specification - - The headers sent via ``**extra`` should follow CGI_ specification. - For example, emulating a different "Host" header as sent in the - HTTP request from the browser to the server should be passed - as ``HTTP_HOST``. - - .. _CGI: http://www.w3.org/CGI/ - - If you already have the GET arguments in URL-encoded form, you can - use that encoding instead of using the data argument. For example, - the previous GET request could also be posed as:: - - >>> c = Client() - >>> c.get('/customers/details/?name=fred&age=7') - - If you provide a URL with both an encoded GET data and a data argument, - the data argument will take precedence. - - If you set ``follow`` to ``True`` the client will follow any redirects - and a ``redirect_chain`` attribute will be set in the response object - containing tuples of the intermediate urls and status codes. - - If you had a URL ``/redirect_me/`` that redirected to ``/next/``, that - redirected to ``/final/``, this is what you'd see:: - - >>> response = c.get('/redirect_me/', follow=True) - >>> response.redirect_chain - [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] - - .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) - - Makes a POST request on the provided ``path`` and returns a - ``Response`` object, which is documented below. - - The key-value pairs in the ``data`` dictionary are used to submit POST - data. For example:: - - >>> c = Client() - >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) - - ...will result in the evaluation of a POST request to this URL:: - - /login/ - - ...with this POST data:: - - name=fred&passwd=secret - - If you provide ``content_type`` (e.g. :mimetype:`text/xml` for an XML - payload), the contents of ``data`` will be sent as-is in the POST - request, using ``content_type`` in the HTTP ``Content-Type`` header. - - If you don't provide a value for ``content_type``, the values in - ``data`` will be transmitted with a content type of - :mimetype:`multipart/form-data`. In this case, the key-value pairs in - ``data`` will be encoded as a multipart message and used to create the - POST data payload. - - To submit multiple values for a given key -- for example, to specify - the selections for a ``', - '') - - ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be - raised if one of them cannot be parsed. - - Output in case of error can be customized with the ``msg`` argument. - -.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) - - Asserts that the strings ``html1`` and ``html2`` are *not* equal. The - comparison is based on HTML semantics. See - :meth:`~SimpleTestCase.assertHTMLEqual` for details. - - ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be - raised if one of them cannot be parsed. - - Output in case of error can be customized with the ``msg`` argument. - -.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) - - .. versionadded:: 1.5 - - Asserts that the strings ``xml1`` and ``xml2`` are equal. The - comparison is based on XML semantics. Similarily to - :meth:`~SimpleTestCase.assertHTMLEqual`, the comparison is - made on parsed content, hence only semantic differences are considered, not - syntax differences. When unvalid XML is passed in any parameter, an - ``AssertionError`` is always raised, even if both string are identical. - - Output in case of error can be customized with the ``msg`` argument. - -.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) - - .. versionadded:: 1.5 - - Asserts that the strings ``xml1`` and ``xml2`` are *not* equal. The - comparison is based on XML semantics. See - :meth:`~SimpleTestCase.assertXMLEqual` for details. - - Output in case of error can be customized with the ``msg`` argument. - -.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='') - - .. versionadded:: 1.5 - - Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one. - - If the ``count`` integer argument is specified, then additionally the number - of ``needle`` occurrences will be strictly verified. - - Whitespace in most cases is ignored, and attribute ordering is not - significant. The passed-in arguments must be valid HTML. - -.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) - - .. versionadded:: 1.5 - - Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal. - Usual JSON non-significant whitespace rules apply as the heavyweight is - delegated to the :mod:`json` library. - - Output in case of error can be customized with the ``msg`` argument. - -.. method:: TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True) - - Asserts that a queryset ``qs`` returns a particular list of values ``values``. - - The comparison of the contents of ``qs`` and ``values`` is performed using - the function ``transform``; by default, this means that the ``repr()`` of - each value is compared. Any other callable can be used if ``repr()`` doesn't - provide a unique or helpful comparison. - - By default, the comparison is also ordering dependent. If ``qs`` doesn't - provide an implicit ordering, you can set the ``ordered`` parameter to - ``False``, which turns the comparison into a Python set comparison. - - .. versionchanged:: 1.6 - - The method now checks for undefined order and raises ``ValueError`` - if undefined order is spotted. The ordering is seen as undefined if - the given ``qs`` isn't ordered and the comparison is against more - than one ordered values. - -.. method:: TransactionTestCase.assertNumQueries(num, func, *args, **kwargs) - - Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that - ``num`` database queries are executed. - - If a ``"using"`` key is present in ``kwargs`` it is used as the database - alias for which to check the number of queries. If you wish to call a - function with a ``using`` parameter you can do it by wrapping the call with - a ``lambda`` to add an extra parameter:: - - self.assertNumQueries(7, lambda: my_function(using=7)) - - You can also use this as a context manager:: - - with self.assertNumQueries(2): - Person.objects.create(name="Aaron") - Person.objects.create(name="Daniel") - -.. _topics-testing-email: - -Email services --------------- - -If any of your Django views send email using :doc:`Django's email -functionality `, you probably don't want to send email each time -you run a test using that view. For this reason, Django's test runner -automatically redirects all Django-sent email to a dummy outbox. This lets you -test every aspect of sending email -- from the number of messages sent to the -contents of each message -- without actually sending the messages. - -The test runner accomplishes this by transparently replacing the normal -email backend with a testing backend. -(Don't worry -- this has no effect on any other email senders outside of -Django, such as your machine's mail server, if you're running one.) - -.. currentmodule:: django.core.mail - -.. data:: django.core.mail.outbox - -During test running, each outgoing email is saved in -``django.core.mail.outbox``. This is a simple list of all -:class:`~django.core.mail.EmailMessage` instances that have been sent. -The ``outbox`` attribute is a special attribute that is created *only* when -the ``locmem`` email backend is used. It doesn't normally exist as part of the -:mod:`django.core.mail` module and you can't import it directly. The code -below shows how to access this attribute correctly. - -Here's an example test that examines ``django.core.mail.outbox`` for length -and contents:: - - from django.core import mail - from django.test import TestCase - - class EmailTest(TestCase): - def test_send_email(self): - # Send message. - mail.send_mail('Subject here', 'Here is the message.', - 'from@example.com', ['to@example.com'], - fail_silently=False) - - # Test that one message has been sent. - self.assertEqual(len(mail.outbox), 1) - - # Verify that the subject of the first message is correct. - self.assertEqual(mail.outbox[0].subject, 'Subject here') - -As noted :ref:`previously `, the test outbox is emptied -at the start of every test in a Django ``*TestCase``. To empty the outbox -manually, assign the empty list to ``mail.outbox``:: - - from django.core import mail - - # Empty the test outbox - mail.outbox = [] - -.. _skipping-tests: - -Skipping tests --------------- - -.. currentmodule:: django.test - -The unittest library provides the :func:`@skipIf ` and -:func:`@skipUnless ` decorators to allow you to skip tests -if you know ahead of time that those tests are going to fail under certain -conditions. - -For example, if your test requires a particular optional library in order to -succeed, you could decorate the test case with :func:`@skipIf -`. Then, the test runner will report that the test wasn't -executed and why, instead of failing the test or omitting the test altogether. - -To supplement these test skipping behaviors, Django provides two -additional skip decorators. Instead of testing a generic boolean, -these decorators check the capabilities of the database, and skip the -test if the database doesn't support a specific named feature. - -The decorators use a string identifier to describe database features. -This string corresponds to attributes of the database connection -features class. See ``django.db.backends.BaseDatabaseFeatures`` -class for a full list of database features that can be used as a basis -for skipping tests. - -.. function:: skipIfDBFeature(feature_name_string) - -Skip the decorated test if the named database feature is supported. - -For example, the following test will not be executed if the database -supports transactions (e.g., it would *not* run under PostgreSQL, but -it would under MySQL with MyISAM tables):: - - class MyTests(TestCase): - @skipIfDBFeature('supports_transactions') - def test_transaction_behavior(self): - # ... conditional test code - -.. function:: skipUnlessDBFeature(feature_name_string) - -Skip the decorated test if the named database feature is *not* -supported. - -For example, the following test will only be executed if the database -supports transactions (e.g., it would run under PostgreSQL, but *not* -under MySQL with MyISAM tables):: - - class MyTests(TestCase): - @skipUnlessDBFeature('supports_transactions') - def test_transaction_behavior(self): - # ... conditional test code diff -Nru python-django-1.6.1/docs/topics/testing/tools.txt python-django-1.6.11/docs/topics/testing/tools.txt --- python-django-1.6.1/docs/topics/testing/tools.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/docs/topics/testing/tools.txt 2015-03-18 23:50:43.000000000 +0000 @@ -0,0 +1,1557 @@ +============= +Testing tools +============= + +.. currentmodule:: django.test + +Django provides a small set of tools that come in handy when writing tests. + +.. _test-client: + +The test client +--------------- + +.. module:: django.test.client + :synopsis: Django's test client. + +The test client is a Python class that acts as a dummy Web browser, allowing +you to test your views and interact with your Django-powered application +programmatically. + +Some of the things you can do with the test client are: + +* Simulate GET and POST requests on a URL and observe the response -- + everything from low-level HTTP (result headers and status codes) to + page content. + +* See the chain of redirects (if any) and check the URL and status code at + each step. + +* Test that a given request is rendered by a given Django template, with + a template context that contains certain values. + +Note that the test client is not intended to be a replacement for Selenium_ or +other "in-browser" frameworks. Django's test client has a different focus. In +short: + +* Use Django's test client to establish that the correct template is being + rendered and that the template is passed the correct context data. + +* Use in-browser frameworks like Selenium_ to test *rendered* HTML and the + *behavior* of Web pages, namely JavaScript functionality. Django also + provides special support for those frameworks; see the section on + :class:`~django.test.LiveServerTestCase` for more details. + +A comprehensive test suite should use a combination of both test types. + +Overview and a quick example +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To use the test client, instantiate ``django.test.client.Client`` and retrieve +Web pages:: + + >>> from django.test.client import Client + >>> c = Client() + >>> response = c.post('/login/', {'username': 'john', 'password': 'smith'}) + >>> response.status_code + 200 + >>> response = c.get('/customer/details/') + >>> response.content + '>> c.get('/login/') + + This is incorrect:: + + >>> c.get('http://www.example.com/login/') + + The test client is not capable of retrieving Web pages that are not + powered by your Django project. If you need to retrieve other Web pages, + use a Python standard library module such as :mod:`urllib` or + :mod:`urllib2`. + +* To resolve URLs, the test client uses whatever URLconf is pointed-to by + your :setting:`ROOT_URLCONF` setting. + +* Although the above example would work in the Python interactive + interpreter, some of the test client's functionality, notably the + template-related functionality, is only available *while tests are + running*. + + The reason for this is that Django's test runner performs a bit of black + magic in order to determine which template was loaded by a given view. + This black magic (essentially a patching of Django's template system in + memory) only happens during test running. + +* By default, the test client will disable any CSRF checks + performed by your site. + + If, for some reason, you *want* the test client to perform CSRF + checks, you can create an instance of the test client that + enforces CSRF checks. To do this, pass in the + ``enforce_csrf_checks`` argument when you construct your + client:: + + >>> from django.test import Client + >>> csrf_client = Client(enforce_csrf_checks=True) + +Making requests +~~~~~~~~~~~~~~~ + +Use the ``django.test.client.Client`` class to make requests. + +.. class:: Client(enforce_csrf_checks=False, **defaults) + + It requires no arguments at time of construction. However, you can use + keywords arguments to specify some default headers. For example, this will + send a ``User-Agent`` HTTP header in each request:: + + >>> c = Client(HTTP_USER_AGENT='Mozilla/5.0') + + The values from the ``extra`` keywords arguments passed to + :meth:`~django.test.client.Client.get()`, + :meth:`~django.test.client.Client.post()`, etc. have precedence over + the defaults passed to the class constructor. + + The ``enforce_csrf_checks`` argument can be used to test CSRF + protection (see above). + + Once you have a ``Client`` instance, you can call any of the following + methods: + + .. method:: Client.get(path, data={}, follow=False, **extra) + + + Makes a GET request on the provided ``path`` and returns a ``Response`` + object, which is documented below. + + The key-value pairs in the ``data`` dictionary are used to create a GET + data payload. For example:: + + >>> c = Client() + >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}) + + ...will result in the evaluation of a GET request equivalent to:: + + /customers/details/?name=fred&age=7 + + The ``extra`` keyword arguments parameter can be used to specify + headers to be sent in the request. For example:: + + >>> c = Client() + >>> c.get('/customers/details/', {'name': 'fred', 'age': 7}, + ... HTTP_X_REQUESTED_WITH='XMLHttpRequest') + + ...will send the HTTP header ``HTTP_X_REQUESTED_WITH`` to the + details view, which is a good way to test code paths that use the + :meth:`django.http.HttpRequest.is_ajax()` method. + + .. admonition:: CGI specification + + The headers sent via ``**extra`` should follow CGI_ specification. + For example, emulating a different "Host" header as sent in the + HTTP request from the browser to the server should be passed + as ``HTTP_HOST``. + + .. _CGI: http://www.w3.org/CGI/ + + If you already have the GET arguments in URL-encoded form, you can + use that encoding instead of using the data argument. For example, + the previous GET request could also be posed as:: + + >>> c = Client() + >>> c.get('/customers/details/?name=fred&age=7') + + If you provide a URL with both an encoded GET data and a data argument, + the data argument will take precedence. + + If you set ``follow`` to ``True`` the client will follow any redirects + and a ``redirect_chain`` attribute will be set in the response object + containing tuples of the intermediate urls and status codes. + + If you had a URL ``/redirect_me/`` that redirected to ``/next/``, that + redirected to ``/final/``, this is what you'd see:: + + >>> response = c.get('/redirect_me/', follow=True) + >>> response.redirect_chain + [(u'http://testserver/next/', 302), (u'http://testserver/final/', 302)] + + .. method:: Client.post(path, data={}, content_type=MULTIPART_CONTENT, follow=False, **extra) + + Makes a POST request on the provided ``path`` and returns a + ``Response`` object, which is documented below. + + The key-value pairs in the ``data`` dictionary are used to submit POST + data. For example:: + + >>> c = Client() + >>> c.post('/login/', {'name': 'fred', 'passwd': 'secret'}) + + ...will result in the evaluation of a POST request to this URL:: + + /login/ + + ...with this POST data:: + + name=fred&passwd=secret + + If you provide ``content_type`` (e.g. :mimetype:`text/xml` for an XML + payload), the contents of ``data`` will be sent as-is in the POST + request, using ``content_type`` in the HTTP ``Content-Type`` header. + + If you don't provide a value for ``content_type``, the values in + ``data`` will be transmitted with a content type of + :mimetype:`multipart/form-data`. In this case, the key-value pairs in + ``data`` will be encoded as a multipart message and used to create the + POST data payload. + + To submit multiple values for a given key -- for example, to specify + the selections for a ``', + '') + + ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be + raised if one of them cannot be parsed. + + Output in case of error can be customized with the ``msg`` argument. + +.. method:: SimpleTestCase.assertHTMLNotEqual(html1, html2, msg=None) + + Asserts that the strings ``html1`` and ``html2`` are *not* equal. The + comparison is based on HTML semantics. See + :meth:`~SimpleTestCase.assertHTMLEqual` for details. + + ``html1`` and ``html2`` must be valid HTML. An ``AssertionError`` will be + raised if one of them cannot be parsed. + + Output in case of error can be customized with the ``msg`` argument. + +.. method:: SimpleTestCase.assertXMLEqual(xml1, xml2, msg=None) + + .. versionadded:: 1.5 + + Asserts that the strings ``xml1`` and ``xml2`` are equal. The + comparison is based on XML semantics. Similarily to + :meth:`~SimpleTestCase.assertHTMLEqual`, the comparison is + made on parsed content, hence only semantic differences are considered, not + syntax differences. When unvalid XML is passed in any parameter, an + ``AssertionError`` is always raised, even if both string are identical. + + Output in case of error can be customized with the ``msg`` argument. + +.. method:: SimpleTestCase.assertXMLNotEqual(xml1, xml2, msg=None) + + .. versionadded:: 1.5 + + Asserts that the strings ``xml1`` and ``xml2`` are *not* equal. The + comparison is based on XML semantics. See + :meth:`~SimpleTestCase.assertXMLEqual` for details. + + Output in case of error can be customized with the ``msg`` argument. + +.. method:: SimpleTestCase.assertInHTML(needle, haystack, count=None, msg_prefix='') + + .. versionadded:: 1.5 + + Asserts that the HTML fragment ``needle`` is contained in the ``haystack`` one. + + If the ``count`` integer argument is specified, then additionally the number + of ``needle`` occurrences will be strictly verified. + + Whitespace in most cases is ignored, and attribute ordering is not + significant. The passed-in arguments must be valid HTML. + +.. method:: SimpleTestCase.assertJSONEqual(raw, expected_data, msg=None) + + .. versionadded:: 1.5 + + Asserts that the JSON fragments ``raw`` and ``expected_data`` are equal. + Usual JSON non-significant whitespace rules apply as the heavyweight is + delegated to the :mod:`json` library. + + Output in case of error can be customized with the ``msg`` argument. + +.. method:: TransactionTestCase.assertQuerysetEqual(qs, values, transform=repr, ordered=True) + + Asserts that a queryset ``qs`` returns a particular list of values ``values``. + + The comparison of the contents of ``qs`` and ``values`` is performed using + the function ``transform``; by default, this means that the ``repr()`` of + each value is compared. Any other callable can be used if ``repr()`` doesn't + provide a unique or helpful comparison. + + By default, the comparison is also ordering dependent. If ``qs`` doesn't + provide an implicit ordering, you can set the ``ordered`` parameter to + ``False``, which turns the comparison into a Python set comparison. + + .. versionchanged:: 1.6 + + The method now checks for undefined order and raises ``ValueError`` + if undefined order is spotted. The ordering is seen as undefined if + the given ``qs`` isn't ordered and the comparison is against more + than one ordered values. + +.. method:: TransactionTestCase.assertNumQueries(num, func, *args, **kwargs) + + Asserts that when ``func`` is called with ``*args`` and ``**kwargs`` that + ``num`` database queries are executed. + + If a ``"using"`` key is present in ``kwargs`` it is used as the database + alias for which to check the number of queries. If you wish to call a + function with a ``using`` parameter you can do it by wrapping the call with + a ``lambda`` to add an extra parameter:: + + self.assertNumQueries(7, lambda: my_function(using=7)) + + You can also use this as a context manager:: + + with self.assertNumQueries(2): + Person.objects.create(name="Aaron") + Person.objects.create(name="Daniel") + +.. _topics-testing-email: + +Email services +-------------- + +If any of your Django views send email using :doc:`Django's email +functionality `, you probably don't want to send email each time +you run a test using that view. For this reason, Django's test runner +automatically redirects all Django-sent email to a dummy outbox. This lets you +test every aspect of sending email -- from the number of messages sent to the +contents of each message -- without actually sending the messages. + +The test runner accomplishes this by transparently replacing the normal +email backend with a testing backend. +(Don't worry -- this has no effect on any other email senders outside of +Django, such as your machine's mail server, if you're running one.) + +.. currentmodule:: django.core.mail + +.. data:: django.core.mail.outbox + +During test running, each outgoing email is saved in +``django.core.mail.outbox``. This is a simple list of all +:class:`~django.core.mail.EmailMessage` instances that have been sent. +The ``outbox`` attribute is a special attribute that is created *only* when +the ``locmem`` email backend is used. It doesn't normally exist as part of the +:mod:`django.core.mail` module and you can't import it directly. The code +below shows how to access this attribute correctly. + +Here's an example test that examines ``django.core.mail.outbox`` for length +and contents:: + + from django.core import mail + from django.test import TestCase + + class EmailTest(TestCase): + def test_send_email(self): + # Send message. + mail.send_mail('Subject here', 'Here is the message.', + 'from@example.com', ['to@example.com'], + fail_silently=False) + + # Test that one message has been sent. + self.assertEqual(len(mail.outbox), 1) + + # Verify that the subject of the first message is correct. + self.assertEqual(mail.outbox[0].subject, 'Subject here') + +As noted :ref:`previously `, the test outbox is emptied +at the start of every test in a Django ``*TestCase``. To empty the outbox +manually, assign the empty list to ``mail.outbox``:: + + from django.core import mail + + # Empty the test outbox + mail.outbox = [] + +.. _topics-testing-management-commands: + +Management Commands +------------------- + +Management commands can be tested with the +:func:`~django.core.management.call_command` function. The output can be +redirected into a ``StringIO`` instance:: + + from django.core.management import call_command + from django.test import TestCase + from django.utils.six import StringIO + + class ClosepollTest(TestCase): + def test_command_output(self): + out = StringIO() + call_command('closepoll', stdout=out) + self.assertIn('Expected output', out.getvalue()) + +.. _skipping-tests: + +Skipping tests +-------------- + +.. currentmodule:: django.test + +The unittest library provides the :func:`@skipIf ` and +:func:`@skipUnless ` decorators to allow you to skip tests +if you know ahead of time that those tests are going to fail under certain +conditions. + +For example, if your test requires a particular optional library in order to +succeed, you could decorate the test case with :func:`@skipIf +`. Then, the test runner will report that the test wasn't +executed and why, instead of failing the test or omitting the test altogether. + +To supplement these test skipping behaviors, Django provides two +additional skip decorators. Instead of testing a generic boolean, +these decorators check the capabilities of the database, and skip the +test if the database doesn't support a specific named feature. + +The decorators use a string identifier to describe database features. +This string corresponds to attributes of the database connection +features class. See ``django.db.backends.BaseDatabaseFeatures`` +class for a full list of database features that can be used as a basis +for skipping tests. + +.. function:: skipIfDBFeature(feature_name_string) + +Skip the decorated test if the named database feature is supported. + +For example, the following test will not be executed if the database +supports transactions (e.g., it would *not* run under PostgreSQL, but +it would under MySQL with MyISAM tables):: + + class MyTests(TestCase): + @skipIfDBFeature('supports_transactions') + def test_transaction_behavior(self): + # ... conditional test code + +.. function:: skipUnlessDBFeature(feature_name_string) + +Skip the decorated test if the named database feature is *not* +supported. + +For example, the following test will only be executed if the database +supports transactions (e.g., it would run under PostgreSQL, but *not* +under MySQL with MyISAM tables):: + + class MyTests(TestCase): + @skipUnlessDBFeature('supports_transactions') + def test_transaction_behavior(self): + # ... conditional test code diff -Nru python-django-1.6.1/PKG-INFO python-django-1.6.11/PKG-INFO --- python-django-1.6.1/PKG-INFO 2013-12-12 19:49:02.000000000 +0000 +++ python-django-1.6.11/PKG-INFO 2015-03-18 23:51:03.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: Django -Version: 1.6.1 +Version: 1.6.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.6.1/scripts/manage_translations.py python-django-1.6.11/scripts/manage_translations.py --- python-django-1.6.1/scripts/manage_translations.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/scripts/manage_translations.py 2015-03-18 23:50:43.000000000 +0000 @@ -47,9 +47,9 @@ def _tx_resource_for_name(name): """ Return the Transifex resource name """ if name == 'core': - return "django-core.core" + return "django.core" else: - return "django-core.contrib-%s" % name + return "django.contrib-%s" % name def _check_diff(cat_name, base_path): """ diff -Nru python-django-1.6.1/tests/admin_views/admin.py python-django-1.6.11/tests/admin_views/admin.py --- python-django-1.6.1/tests/admin_views/admin.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/admin_views/admin.py 2015-03-18 23:50:43.000000000 +0000 @@ -30,7 +30,8 @@ AdminOrderedField, AdminOrderedModelMethod, AdminOrderedAdminMethod, AdminOrderedCallable, Report, Color2, UnorderedObject, MainPrepopulated, RelatedPrepopulated, UndeletableObject, UserMessenger, Simple, Choice, - ShortMessage, Telegram) + ShortMessage, Telegram, ReferencedByParent, ChildOfReferer, M2MReference, + ReferencedByInline, InlineReference, InlineReferer, Recipe, Ingredient, NotReferenced) def callable_year(dt_value): @@ -696,6 +697,14 @@ fields = ['choice'] +class InlineReferenceInline(admin.TabularInline): + model = InlineReference + + +class InlineRefererAdmin(admin.ModelAdmin): + inlines = [InlineReferenceInline] + + site = admin.AdminSite(name="admin") site.register(Article, ArticleAdmin) site.register(CustomArticle, CustomArticleAdmin) @@ -745,6 +754,10 @@ site.register(MainPrepopulated, MainPrepopulatedAdmin) site.register(UnorderedObject, UnorderedObjectAdmin) site.register(UndeletableObject, UndeletableObjectAdmin) +site.register(ReferencedByParent) +site.register(ChildOfReferer) +site.register(ReferencedByInline) +site.register(InlineReferer, InlineRefererAdmin) # We intentionally register Promo and ChapterXtra1 but not Chapter nor ChapterXtra2. # That way we cover all four cases: @@ -775,6 +788,9 @@ site.register(Simple, AttributeErrorRaisingAdmin) site.register(UserMessenger, MessageTestingAdmin) site.register(Choice, ChoiceList) +site.register(Recipe) +site.register(Ingredient) +site.register(NotReferenced) # Register core models we need in our tests from django.contrib.auth.models import User, Group diff -Nru python-django-1.6.1/tests/admin_views/models.py python-django-1.6.11/tests/admin_views/models.py --- python-django-1.6.1/tests/admin_views/models.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/admin_views/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -687,3 +687,51 @@ class Choice(models.Model): choice = models.IntegerField(blank=True, null=True, choices=((1, 'Yes'), (0, 'No'), (None, 'No opinion'))) + +# Models for #23329 +class ReferencedByParent(models.Model): + name = models.CharField(max_length=20, unique=True) + + +class ParentWithFK(models.Model): + fk = models.ForeignKey( + ReferencedByParent, to_field='name', related_name='hidden+', + ) + + +class ChildOfReferer(ParentWithFK): + pass + + +class M2MReference(models.Model): + ref = models.ManyToManyField('self') + + +# Models for #23431 +class ReferencedByInline(models.Model): + name = models.CharField(max_length=20, unique=True) + + +class InlineReference(models.Model): + fk = models.ForeignKey( + ReferencedByInline, to_field='name', related_name='hidden+', + ) + + +class InlineReferer(models.Model): + refs = models.ManyToManyField(InlineReference) + + +# Models for #23604 +class Recipe(models.Model): + pass + + +class Ingredient(models.Model): + recipes = models.ManyToManyField(Recipe) + + +# Model for #23839 +class NotReferenced(models.Model): + # Don't point any FK at this model. + pass diff -Nru python-django-1.6.1/tests/admin_views/tests.py python-django-1.6.11/tests/admin_views/tests.py --- python-django-1.6.1/tests/admin_views/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/admin_views/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -9,17 +9,19 @@ from django.core import mail from django.core.exceptions import ImproperlyConfigured from django.core.files import temp as tempfile -from django.core.urlresolvers import reverse +from django.core.urlresolvers import get_script_prefix, reverse, set_script_prefix # Register auth models with the admin. from django.contrib import admin from django.contrib.auth import get_permission_codename from django.contrib.admin.helpers import ACTION_CHECKBOX_NAME +from django.contrib.admin.views.main import TO_FIELD_VAR from django.contrib.admin.models import LogEntry, DELETION from django.contrib.admin.sites import LOGIN_FORM_KEY +from django.contrib.admin.templatetags.admin_urls import add_preserved_filters +from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.admin.util import quote from django.contrib.admin.validation import ModelAdminValidator from django.contrib.admin.views.main import IS_POPUP_VAR -from django.contrib.admin.tests import AdminSeleniumWebDriverTestCase from django.contrib.auth import REDIRECT_FIELD_NAME from django.contrib.auth.models import Group, User, Permission from django.contrib.contenttypes.models import ContentType @@ -33,10 +35,10 @@ from django.utils import translation from django.utils import unittest from django.utils.cache import get_max_age -from django.utils.encoding import iri_to_uri, force_bytes +from django.utils.encoding import iri_to_uri, force_bytes, force_text from django.utils.html import escape from django.utils.http import urlencode, urlquote -from django.utils.six.moves.urllib.parse import urljoin +from django.utils.six.moves.urllib.parse import parse_qsl, urljoin, urlparse from django.utils._os import upath from django.utils import six @@ -576,6 +578,45 @@ response = self.client.get("/test_admin/admin/admin_views/workhour/?employee__person_ptr__exact=%d" % e1.pk) self.assertEqual(response.status_code, 200) + def test_disallowed_to_field(self): + with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: + response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'missing_field'}) + self.assertEqual(response.status_code, 400) + self.assertEqual(len(calls), 1) + + # Specifying a field that is not refered by any other model registered + # to this admin site should raise an exception. + with patch_logger('django.security.DisallowedModelAdminToField', 'error') as calls: + response = self.client.get("/test_admin/admin/admin_views/section/", {TO_FIELD_VAR: 'name'}) + self.assertEqual(response.status_code, 400) + self.assertEqual(len(calls), 1) + + # #23839 - Primary key should always be allowed, even if the referenced model isn't registered. + response = self.client.get("/test_admin/admin/admin_views/notreferenced/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + + # Specifying a field referenced by another model though a m2m should be allowed. + # XXX: We're not testing against a non-primary key field since the admin doesn't + # support it yet, ref #23862 + response = self.client.get("/test_admin/admin/admin_views/recipe/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + + # #23604 - Specifying a field referenced through a reverse m2m relationship should be allowed. + # XXX: We're not testing against a non-primary key field since the admin doesn't + # support it yet, ref #23862 + response = self.client.get("/test_admin/admin/admin_views/ingredient/", {TO_FIELD_VAR: 'id'}) + self.assertEqual(response.status_code, 200) + + # #23329 - Specifying a field that is not refered by any other model directly registered + # to this admin site but registered through inheritance should be allowed. + response = self.client.get("/test_admin/admin/admin_views/referencedbyparent/", {TO_FIELD_VAR: 'name'}) + self.assertEqual(response.status_code, 200) + + # #23431 - Specifying a field that is only refered to by a inline of a registered + # model should be allowed. + response = self.client.get("/test_admin/admin/admin_views/referencedbyinline/", {TO_FIELD_VAR: 'name'}) + self.assertEqual(response.status_code, 200) + def test_allowed_filtering_15103(self): """ Regressions test for ticket 15103 - filtering on fields defined in a @@ -2203,10 +2244,9 @@ """Ensure that the to_field GET parameter is preserved when a search is performed. Refs #10918. """ - from django.contrib.admin.views.main import TO_FIELD_VAR - response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=username' % TO_FIELD_VAR) + response = self.client.get('/test_admin/admin/auth/user/?q=joe&%s=id' % TO_FIELD_VAR) self.assertContains(response, "\n1 user\n") - self.assertContains(response, '', html=True) + self.assertContains(response, '' % TO_FIELD_VAR, html=True) def test_exact_matches(self): response = self.client.get('/test_admin/admin/admin_views/recommendation/?q=bar') @@ -4224,11 +4264,68 @@ def tearDown(self): self.client.logout() - def get_changelist_filters_querystring(self): - return urlencode({ + def assertURLEqual(self, url1, url2): + """ + Assert that two URLs are equal despite the ordering + of their querystring. Refs #22360. + """ + parsed_url1 = urlparse(url1) + path1 = parsed_url1.path + parsed_qs1 = dict(parse_qsl(parsed_url1.query)) + + parsed_url2 = urlparse(url2) + path2 = parsed_url2.path + parsed_qs2 = dict(parse_qsl(parsed_url2.query)) + + for parsed_qs in [parsed_qs1, parsed_qs2]: + if '_changelist_filters' in parsed_qs: + changelist_filters = parsed_qs['_changelist_filters'] + parsed_filters = dict(parse_qsl(changelist_filters)) + parsed_qs['_changelist_filters'] = parsed_filters + + self.assertEqual(path1, path2) + self.assertEqual(parsed_qs1, parsed_qs2) + + def test_assert_url_equal(self): + # Test equality. + self.assertURLEqual( + 'http://testserver/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0', + 'http://testserver/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0' + ) + + # Test inequality. + with self.assertRaises(AssertionError): + self.assertURLEqual( + 'http://testserver/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0', + 'http://testserver/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D1%26is_superuser__exact%3D1' + ) + + # Ignore scheme and host. + self.assertURLEqual( + 'http://testserver/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0', + '/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0' + ) + + # Ignore ordering of querystring. + self.assertURLEqual( + '/test_admin/admin/auth/user/?is_staff__exact=0&is_superuser__exact=0', + '/test_admin/admin/auth/user/?is_superuser__exact=0&is_staff__exact=0' + ) + + # Ignore ordering of _changelist_filters. + self.assertURLEqual( + '/test_admin/admin/auth/user/105/?_changelist_filters=is_staff__exact%3D0%26is_superuser__exact%3D0', + '/test_admin/admin/auth/user/105/?_changelist_filters=is_superuser__exact%3D0%26is_staff__exact%3D0' + ) + + def get_changelist_filters(self): + return { 'is_superuser__exact': 0, 'is_staff__exact': 0, - }) + } + + def get_changelist_filters_querystring(self): + return urlencode(self.get_changelist_filters()) def get_preserved_filters_querystring(self): return urlencode({ @@ -4284,8 +4381,11 @@ self.assertEqual(response.status_code, 200) # Check the `change_view` link has the correct querystring. - detail_link = """joepublic""" % self.get_change_url() - self.assertContains(response, detail_link, count=1) + detail_link = re.search( + 'joepublic', + force_text(response.content) + ) + self.assertURLEqual(detail_link.group(1), self.get_change_url()) def test_change_view(self): # Get the `change_view`. @@ -4293,16 +4393,25 @@ self.assertEqual(response.status_code, 200) # Check the form action. - form_action = """""" % self.get_preserved_filters_querystring() - self.assertContains(response, form_action, count=1) + form_action = re.search( + '', + force_text(response.content) + ) + self.assertURLEqual(form_action.group(1), '?%s' % self.get_preserved_filters_querystring()) # Check the history link. - history_link = """History""" % self.get_history_url() - self.assertContains(response, history_link, count=1) + history_link = re.search( + 'History', + force_text(response.content) + ) + self.assertURLEqual(history_link.group(1), self.get_history_url()) # Check the delete link. - delete_link = """Delete""" % (self.get_delete_url()) - self.assertContains(response, delete_link, count=1) + delete_link = re.search( + 'Delete', + force_text(response.content) + ) + self.assertURLEqual(delete_link.group(1), self.get_delete_url()) # Test redirect on "Save". post_data = { @@ -4315,19 +4424,31 @@ post_data['_save'] = 1 response = self.client.post(self.get_change_url(), data=post_data) - self.assertRedirects(response, self.get_changelist_url()) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_changelist_url() + ) post_data.pop('_save') # Test redirect on "Save and continue". post_data['_continue'] = 1 response = self.client.post(self.get_change_url(), data=post_data) - self.assertRedirects(response, self.get_change_url()) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_change_url() + ) post_data.pop('_continue') # Test redirect on "Save and add new". post_data['_addanother'] = 1 response = self.client.post(self.get_change_url(), data=post_data) - self.assertRedirects(response, self.get_add_url()) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_add_url() + ) post_data.pop('_addanother') def test_add_view(self): @@ -4336,39 +4457,82 @@ self.assertEqual(response.status_code, 200) # Check the form action. - form_action = """""" % self.get_preserved_filters_querystring() - self.assertContains(response, form_action, count=1) + form_action = re.search( + '', + force_text(response.content) + ) + self.assertURLEqual(form_action.group(1), '?%s' % self.get_preserved_filters_querystring()) - # Test redirect on "Save". post_data = { 'username': 'dummy', 'password1': 'test', 'password2': 'test', } + # Test redirect on "Save". post_data['_save'] = 1 response = self.client.post(self.get_add_url(), data=post_data) - self.assertRedirects(response, self.get_change_url(User.objects.latest('pk').pk)) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_change_url(User.objects.latest('pk').pk) + ) post_data.pop('_save') # Test redirect on "Save and continue". post_data['username'] = 'dummy2' post_data['_continue'] = 1 response = self.client.post(self.get_add_url(), data=post_data) - self.assertRedirects(response, self.get_change_url(User.objects.latest('pk').pk)) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_change_url(User.objects.latest('pk').pk) + ) post_data.pop('_continue') # Test redirect on "Save and add new". post_data['username'] = 'dummy3' post_data['_addanother'] = 1 response = self.client.post(self.get_add_url(), data=post_data) - self.assertRedirects(response, self.get_add_url()) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_add_url() + ) post_data.pop('_addanother') def test_delete_view(self): # Test redirect on "Delete". response = self.client.post(self.get_delete_url(), {'post': 'yes'}) - self.assertRedirects(response, self.get_changelist_url()) + self.assertEqual(response.status_code, 302) + self.assertURLEqual( + response.url, + self.get_changelist_url() + ) + + def test_url_prefix(self): + context = { + 'preserved_filters': self.get_preserved_filters_querystring(), + 'opts': User._meta, + } + + url = reverse('admin:auth_user_changelist', current_app=self.admin_site.name) + self.assertURLEqual( + self.get_changelist_url(), + add_preserved_filters(context, url), + ) + + original_prefix = get_script_prefix() + try: + set_script_prefix('/prefix/') + url = reverse('admin:auth_user_changelist', current_app=self.admin_site.name) + self.assertURLEqual( + self.get_changelist_url(), + add_preserved_filters(context, url), + ) + finally: + set_script_prefix(original_prefix) + class NamespacedAdminKeepChangeListFiltersTests(AdminKeepChangeListFiltersTests): admin_site = site2 diff -Nru python-django-1.6.1/tests/backends/tests.py python-django-1.6.11/tests/backends/tests.py --- python-django-1.6.1/tests/backends/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/backends/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,6 +2,7 @@ # Unit and doctests for specific database backends. from __future__ import absolute_import, unicode_literals +import copy import datetime from decimal import Decimal import threading @@ -315,16 +316,18 @@ self.assertEqual(pg_version.get_version(conn), 80300) -class PostgresNewConnectionTest(TestCase): - """ - #17062: PostgreSQL shouldn't roll back SET TIME ZONE, even if the first - transaction is rolled back. - """ +class PostgresNewConnectionTests(TestCase): + @unittest.skipUnless( connection.vendor == 'postgresql', "This test applies only to PostgreSQL") def test_connect_and_rollback(self): - new_connections = ConnectionHandler(settings.DATABASES) + """ + PostgreSQL shouldn't roll back SET TIME ZONE, even if the first + transaction is rolled back (#17062). + """ + databases = copy.deepcopy(settings.DATABASES) + new_connections = ConnectionHandler(databases) new_connection = new_connections[DEFAULT_DB_ALIAS] try: # Ensure the database default time zone is different than @@ -349,10 +352,26 @@ tz = cursor.fetchone()[0] self.assertEqual(new_tz, tz) finally: - try: - new_connection.close() - except DatabaseError: - pass + new_connection.close() + + @unittest.skipUnless( + connection.vendor == 'postgresql', + "This test applies only to PostgreSQL") + def test_connect_non_autocommit(self): + """ + The connection wrapper shouldn't believe that autocommit is enabled + after setting the time zone when AUTOCOMMIT is False (#21452). + """ + databases = copy.deepcopy(settings.DATABASES) + databases[DEFAULT_DB_ALIAS]['AUTOCOMMIT'] = False + new_connections = ConnectionHandler(databases) + new_connection = new_connections[DEFAULT_DB_ALIAS] + try: + # Open a database connection. + new_connection.cursor() + self.assertFalse(new_connection.get_autocommit()) + finally: + new_connection.close() # This test needs to run outside of a transaction, otherwise closing the @@ -469,7 +488,7 @@ def create_squares_with_executemany(self, args): self.create_squares(args, 'format', True) - def create_squares(self, args, paramstyle, multiple): + def create_squares(self, args, paramstyle, multiple): cursor = connection.cursor() opts = models.Square._meta tbl = connection.introspection.table_name_converter(opts.db_table) @@ -541,7 +560,7 @@ # same test for DebugCursorWrapper self.create_squares(args, 'pyformat', multiple=True) self.assertEqual(models.Square.objects.count(), 9) - + def test_unicode_fetches(self): #6254: fetchone, fetchmany, fetchall return strings as unicode objects qn = connection.ops.quote_name @@ -592,6 +611,29 @@ with self.assertRaises(DatabaseError): cursor.execute(query) + # Unfortunately with sqlite3 the in-memory test database cannot be closed. + @skipUnlessDBFeature('test_db_allows_multiple_connections') + def test_is_usable_after_database_disconnects(self): + """ + Test that is_usable() doesn't crash when the database disconnects. + + Regression for #21553. + """ + # Open a connection to the database. + connection.cursor().close() + # Emulate a connection close by the database. + connection._close() + # Even then is_usable() should not raise an exception. + try: + self.assertFalse(connection.is_usable()) + finally: + # Clean up the mess created by connection._close(). Since the + # connection is already closed, this crashes on some backends. + try: + connection.close() + except Exception: + pass + # We don't make these tests conditional because that means we would need to # check and differentiate between: diff -Nru python-django-1.6.1/tests/bug639/models.py python-django-1.6.11/tests/bug639/models.py --- python-django-1.6.1/tests/bug639/models.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/tests/bug639/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,3 +1,4 @@ +import os import tempfile from django.core.files.storage import FileSystemStorage @@ -5,7 +6,7 @@ from django.forms import ModelForm -temp_storage_dir = tempfile.mkdtemp() +temp_storage_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) temp_storage = FileSystemStorage(temp_storage_dir) class Photo(models.Model): diff -Nru python-django-1.6.1/tests/cache/tests.py python-django-1.6.11/tests/cache/tests.py --- python-django-1.6.1/tests/cache/tests.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/tests/cache/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -19,12 +19,14 @@ from django.core.cache import get_cache from django.core.cache.backends.base import (CacheKeyWarning, InvalidCacheBackendError) -from django.db import router, transaction +from django.core.context_processors import csrf +from django.db import connections, router, transaction from django.core.cache.utils import make_template_fragment_key from django.http import (HttpResponse, HttpRequest, StreamingHttpResponse, QueryDict) from django.middleware.cache import (FetchFromCacheMiddleware, UpdateCacheMiddleware, CacheMiddleware) +from django.middleware.csrf import CsrfViewMiddleware from django.template import Template from django.template.response import TemplateResponse from django.test import TestCase, TransactionTestCase, RequestFactory @@ -910,9 +912,16 @@ database='default', verbosity=0, interactive=False) # cache table should be created on 'other' - # one query is used to create the table and another one the index - with self.assertNumQueries(2, using='other'): - management.call_command('createcachetable', 'cache_table', + # Queries: + # 1: create savepoint (if transactional DDL is supported) + # 2: create the table + # 3: create the index + # 4: release savepoint (if transactional DDL is supported) + from django.db import connections + num = 4 if connections['other'].features.can_rollback_ddl else 2 + with self.assertNumQueries(num, using='other'): + management.call_command('createcachetable', + 'cache_table', database='other', verbosity=0, interactive=False) finally: @@ -1578,6 +1587,10 @@ return HttpResponse('Hello World %s' % value) +def csrf_view(request): + return HttpResponse(csrf(request)['csrf_token']) + + @override_settings( CACHE_MIDDLEWARE_ALIAS='other', CACHE_MIDDLEWARE_KEY_PREFIX='middlewareprefix', @@ -1797,6 +1810,28 @@ response = other_with_prefix_view(request, '16') self.assertEqual(response.content, b'Hello World 16') + def test_sensitive_cookie_not_cached(self): + """ + Django must prevent caching of responses that set a user-specific (and + maybe security sensitive) cookie in response to a cookie-less request. + """ + csrf_middleware = CsrfViewMiddleware() + cache_middleware = CacheMiddleware() + + request = self.factory.get('/view/') + self.assertIsNone(cache_middleware.process_request(request)) + + csrf_middleware.process_view(request, csrf_view, (), {}) + + response = csrf_view(request) + + response = csrf_middleware.process_response(request, response) + response = cache_middleware.process_response(request, response) + + # Inserting a CSRF cookie in a cookie-less request prevented caching. + self.assertIsNone(cache_middleware.process_request(request)) + + @override_settings( CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix', CACHE_MIDDLEWARE_SECONDS=1, @@ -1930,4 +1965,3 @@ key = make_template_fragment_key('spam', ['abc:def%']) self.assertEqual(key, 'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469') - diff -Nru python-django-1.6.1/tests/expressions_regress/tests.py python-django-1.6.11/tests/expressions_regress/tests.py --- python-django-1.6.1/tests/expressions_regress/tests.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/tests/expressions_regress/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -259,6 +259,20 @@ self.days_long.append(e4.completed-e4.assigned) self.expnames = [e.name for e in Experiment.objects.all()] + def test_multiple_query_compilation(self): + # Ticket #21643 + queryset = Experiment.objects.filter(end__lt=F('start') + datetime.timedelta(hours=1)) + q1 = str(queryset.query) + q2 = str(queryset.query) + self.assertEqual(q1, q2) + + def test_query_clone(self): + # Ticket #21643 + qs = Experiment.objects.filter(end__lt=F('start') + datetime.timedelta(hours=1)) + qs2 = qs.all() + list(qs) + list(qs2) + def test_delta_add(self): for i in range(len(self.deltas)): delta = self.deltas[i] diff -Nru python-django-1.6.1/tests/files/tests.py python-django-1.6.11/tests/files/tests.py --- python-django-1.6.1/tests/files/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/files/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,5 +1,6 @@ from __future__ import absolute_import +from io import BytesIO import os import gzip import shutil @@ -12,12 +13,15 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.temp import NamedTemporaryFile from django.test import TestCase -from django.utils import unittest +from django.utils import six, unittest from django.utils.six import StringIO from .models import Storage, temp_storage, temp_storage_location +FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' + + class FileStorageTests(TestCase): def tearDown(self): shutil.rmtree(temp_storage_location) @@ -63,27 +67,28 @@ # Save another file with the same name. obj2 = Storage() obj2.normal.save("django_test.txt", ContentFile("more content")) - self.assertEqual(obj2.normal.name, "tests/django_test_1.txt") + obj2_name = obj2.normal.name + six.assertRegex(self, obj2_name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) self.assertEqual(obj2.normal.size, 12) # Push the objects into the cache to make sure they pickle properly cache.set("obj1", obj1) cache.set("obj2", obj2) - self.assertEqual(cache.get("obj2").normal.name, "tests/django_test_1.txt") + six.assertRegex(self, cache.get("obj2").normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) # Deleting an object does not delete the file it uses. obj2.delete() obj2.normal.save("django_test.txt", ContentFile("more content")) - self.assertEqual(obj2.normal.name, "tests/django_test_2.txt") + self.assertNotEqual(obj2_name, obj2.normal.name) + six.assertRegex(self, obj2.normal.name, "tests/django_test_%s.txt" % FILE_SUFFIX_REGEX) # Multiple files with the same name get _N appended to them. - objs = [Storage() for i in range(3)] + objs = [Storage() for i in range(2)] for o in objs: o.normal.save("multiple_files.txt", ContentFile("Same Content")) - self.assertEqual( - [o.normal.name for o in objs], - ["tests/multiple_files.txt", "tests/multiple_files_1.txt", "tests/multiple_files_2.txt"] - ) + names = [o.normal.name for o in objs] + self.assertEqual(names[0], "tests/multiple_files.txt") + six.assertRegex(self, names[1], "tests/multiple_files_%s.txt" % FILE_SUFFIX_REGEX) for o in objs: o.delete() @@ -164,6 +169,14 @@ self.assertFalse(hasattr(file, 'mode')) g = gzip.GzipFile(fileobj=file) + def test_file_iteration(self): + """ + File objects should yield lines when iterated over. + Refs #22107. + """ + file = File(BytesIO(b'one\ntwo\nthree')) + self.assertEqual(list(file), [b'one\n', b'two\n', b'three']) + class FileMoveSafeTests(unittest.TestCase): def test_file_move_overwrite(self): diff -Nru python-django-1.6.1/tests/file_storage/tests.py python-django-1.6.11/tests/file_storage/tests.py --- python-django-1.6.1/tests/file_storage/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/file_storage/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -35,6 +35,9 @@ Image = None +FILE_SUFFIX_REGEX = '[A-Za-z0-9]{7}' + + class GetStorageClassTests(SimpleTestCase): def test_get_filesystem_storage(self): @@ -430,10 +433,9 @@ self.thread.start() name = self.save_file('conflict') self.thread.join() - self.assertTrue(self.storage.exists('conflict')) - self.assertTrue(self.storage.exists('conflict_1')) - self.storage.delete('conflict') - self.storage.delete('conflict_1') + files = sorted(os.listdir(self.storage_dir)) + self.assertEqual(files[0], 'conflict') + six.assertRegex(self, files[1], 'conflict_%s' % FILE_SUFFIX_REGEX) @unittest.skipIf(sys.platform.startswith('win'), "Windows only partially supports umasks and chmod.") class FileStoragePermissions(unittest.TestCase): @@ -477,9 +479,10 @@ self.storage.save('dotted.path/test', ContentFile("1")) self.storage.save('dotted.path/test', ContentFile("2")) + files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path'))) self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) - self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test'))) - self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/test_1'))) + self.assertEqual(files[0], 'test') + six.assertRegex(self, files[1], 'test_%s' % FILE_SUFFIX_REGEX) def test_first_character_dot(self): """ @@ -489,8 +492,10 @@ self.storage.save('dotted.path/.test', ContentFile("1")) self.storage.save('dotted.path/.test', ContentFile("2")) - self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test'))) - self.assertTrue(os.path.exists(os.path.join(self.storage_dir, 'dotted.path/.test_1'))) + files = sorted(os.listdir(os.path.join(self.storage_dir, 'dotted.path'))) + self.assertFalse(os.path.exists(os.path.join(self.storage_dir, 'dotted_.path'))) + self.assertEqual(files[0], '.test') + six.assertRegex(self, files[1], '.test_%s' % FILE_SUFFIX_REGEX) class DimensionClosingBug(unittest.TestCase): """ diff -Nru python-django-1.6.1/tests/file_uploads/tests.py python-django-1.6.11/tests/file_uploads/tests.py --- python-django-1.6.1/tests/file_uploads/tests.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/tests/file_uploads/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -23,7 +23,7 @@ UNICODE_FILENAME = 'test-0123456789_中文_Orléans.jpg' -MEDIA_ROOT = sys_tempfile.mkdtemp() +MEDIA_ROOT = sys_tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) UPLOAD_TO = os.path.join(MEDIA_ROOT, 'test_upload') @override_settings(MEDIA_ROOT=MEDIA_ROOT) diff -Nru python-django-1.6.1/tests/foreign_object/tests.py python-django-1.6.11/tests/foreign_object/tests.py --- python-django-1.6.1/tests/foreign_object/tests.py 2013-08-13 17:17:35.000000000 +0000 +++ python-django-1.6.11/tests/foreign_object/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -4,7 +4,7 @@ from .models import ( Country, Person, Group, Membership, Friendship, Article, ArticleTranslation, ArticleTag, ArticleIdea, NewsArticle) -from django.test import TestCase +from django.test import TestCase, skipUnlessDBFeature from django.utils.translation import activate from django.core.exceptions import FieldError from django import forms @@ -362,6 +362,13 @@ 'active_translation')[0].active_translation.title, "foo") + @skipUnlessDBFeature('has_bulk_insert') + def test_batch_create_foreign_object(self): + """ See: https://code.djangoproject.com/ticket/21566 """ + objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)] + Person.objects.bulk_create(objs, 10) + + class FormsTests(TestCase): # ForeignObjects should not have any form fields, currently the user needs # to manually deal with the foreignobject relation. diff -Nru python-django-1.6.1/tests/forms_tests/tests/test_fields.py python-django-1.6.11/tests/forms_tests/tests/test_fields.py --- python-django-1.6.1/tests/forms_tests/tests/test_fields.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/forms_tests/tests/test_fields.py 2015-03-18 23:50:43.000000000 +0000 @@ -234,6 +234,18 @@ f1 = IntegerField(localize=True) self.assertWidgetRendersTo(f1, '') + def test_integerfield_subclass(self): + """ + Test that class-defined widget is not overwritten by __init__ (#22245). + """ + class MyIntegerField(IntegerField): + widget = Textarea + + f = MyIntegerField() + self.assertEqual(f.widget.__class__, Textarea) + f = MyIntegerField(localize=True) + self.assertEqual(f.widget.__class__, Textarea) + # FloatField ################################################################## def test_floatfield_1(self): diff -Nru python-django-1.6.1/tests/generic_relations_regress/models.py python-django-1.6.11/tests/generic_relations_regress/models.py --- python-django-1.6.1/tests/generic_relations_regress/models.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/generic_relations_regress/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,6 +2,7 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.utils.encoding import python_2_unicode_compatible +from django.db.models.deletion import ProtectedError __all__ = ('Link', 'Place', 'Restaurant', 'Person', 'Address', @@ -164,3 +165,26 @@ class Meta: ordering = ('id',) + + +# Ticket #22998 + +class Node(models.Model): + content_type = models.ForeignKey(ContentType) + object_id = models.PositiveIntegerField() + content = generic.GenericForeignKey('content_type', 'object_id') + + +class Content(models.Model): + nodes = generic.GenericRelation(Node) + related_obj = models.ForeignKey('Related', on_delete=models.CASCADE) + + +class Related(models.Model): + pass + + +def prevent_deletes(sender, instance, **kwargs): + raise ProtectedError("Not allowed to delete.", [instance]) + +models.signals.pre_delete.connect(prevent_deletes, sender=Node) diff -Nru python-django-1.6.1/tests/generic_relations_regress/tests.py python-django-1.6.11/tests/generic_relations_regress/tests.py --- python-django-1.6.1/tests/generic_relations_regress/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/generic_relations_regress/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,11 +2,14 @@ from django.db.utils import IntegrityError from django.test import TestCase, skipIfDBFeature from django.forms.models import modelform_factory +from django.db.models.deletion import ProtectedError from .models import ( Address, Place, Restaurant, Link, CharLink, TextLink, Person, Contact, Note, Organization, OddRelation1, OddRelation2, Company, - Developer, Team, Guild, Tag, Board, HasLinkThing, A, B, C, D) + Developer, Team, Guild, Tag, Board, HasLinkThing, A, B, C, D, + Related, Content, Node, +) class GenericRelationTests(TestCase): @@ -223,3 +226,13 @@ form.save() links = HasLinkThing._meta.get_field_by_name('links')[0].field self.assertEqual(links.save_form_data_calls, 1) + + def test_ticket_22998(self): + related = Related.objects.create() + content = Content.objects.create(related_obj=related) + Node.objects.create(content=content) + + # deleting the Related cascades to the Content cascades to the Node, + # where the pre_delete signal should fire and prevent deletion. + with self.assertRaises(ProtectedError): + related.delete() diff -Nru python-django-1.6.1/tests/handlers/tests.py python-django-1.6.11/tests/handlers/tests.py --- python-django-1.6.1/tests/handlers/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/handlers/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -42,14 +42,30 @@ self.assertEqual(response.status_code, 400) def test_non_ascii_query_string(self): - """Test that non-ASCII query strings are properly decoded (#20530).""" + """ + Test that non-ASCII query strings are properly decoded (#20530, #22996). + """ environ = RequestFactory().get('/').environ - raw_query_string = 'want=café' - if six.PY3: - raw_query_string = raw_query_string.encode('utf-8').decode('iso-8859-1') - environ['QUERY_STRING'] = raw_query_string - request = WSGIRequest(environ) - self.assertEqual(request.GET['want'], "café") + raw_query_strings = [ + b'want=caf%C3%A9', # This is the proper way to encode 'café' + b'want=caf\xc3\xa9', # UA forgot to quote bytes + b'want=caf%E9', # UA quoted, but not in UTF-8 + b'want=caf\xe9', # UA forgot to convert Latin-1 to UTF-8 and to quote (typical of MSIE) + ] + got = [] + for raw_query_string in raw_query_strings: + if six.PY3: + # Simulate http.server.BaseHTTPRequestHandler.parse_request handling of raw request + environ['QUERY_STRING'] = str(raw_query_string, 'iso-8859-1') + else: + environ['QUERY_STRING'] = raw_query_string + request = WSGIRequest(environ) + got.append(request.GET['want']) + if six.PY2: + self.assertListEqual(got, ['café', 'café', 'caf\ufffd', 'caf\ufffd']) + else: + # On Python 3, %E9 is converted to the unicode replacement character by parse_qsl + self.assertListEqual(got, ['café', 'café', 'caf\ufffd', 'café']) def test_non_ascii_cookie(self): """Test that non-ASCII cookies set in JavaScript are properly decoded (#20557).""" diff -Nru python-django-1.6.1/tests/i18n/commands/extraction.py python-django-1.6.11/tests/i18n/commands/extraction.py --- python-django-1.6.1/tests/i18n/commands/extraction.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/i18n/commands/extraction.py 2015-03-18 23:50:43.000000000 +0000 @@ -5,6 +5,7 @@ import os import re import shutil +import sys import warnings from django.core import management @@ -14,7 +15,7 @@ from django.utils import six from django.utils.six import StringIO from django.utils.translation import TranslatorCommentWarning -from django.utils.unittest import SkipTest +from django.utils.unittest import expectedFailure, SkipTest LOCALE='de' @@ -141,6 +142,10 @@ self.assertIn("UnicodeDecodeError: skipped file not_utf8.txt in .", force_text(stdout.getvalue())) + # This issue is fixed in 1.8+ (#23312). + if six.PY3 and sys.platform.startswith('win'): + test_unicode_decode_error = expectedFailure(test_unicode_decode_error) + def test_extraction_warning(self): """test xgettext warning about multiple bare interpolation placeholders""" os.chdir(self.test_dir) diff -Nru python-django-1.6.1/tests/i18n/tests.py python-django-1.6.11/tests/i18n/tests.py --- python-django-1.6.1/tests/i18n/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/i18n/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -33,6 +33,7 @@ pgettext, pgettext_lazy, npgettext, npgettext_lazy, check_for_language) +from django.utils.unittest import skipUnless if find_command('xgettext'): from .commands.extraction import (ExtractorTests, BasicExtractorTests, @@ -88,9 +89,20 @@ s4 = ugettext_lazy('Some other string') self.assertEqual(False, s == s4) - if six.PY2: - # On Python 2, gettext_lazy should not transform a bytestring to unicode - self.assertEqual(gettext_lazy(b"test").upper(), b"TEST") + @skipUnless(six.PY2, "No more bytestring translations on PY3") + def test_lazy_and_bytestrings(self): + # On Python 2, (n)gettext_lazy should not transform a bytestring to unicode + self.assertEqual(gettext_lazy(b"test").upper(), b"TEST") + self.assertEqual((ngettext_lazy(b"%d test", b"%d tests") % 1).upper(), b"1 TEST") + + # Other versions of lazy functions always return unicode + self.assertEqual(ugettext_lazy(b"test").upper(), "TEST") + self.assertEqual((ungettext_lazy(b"%d test", b"%d tests") % 1).upper(), "1 TEST") + self.assertEqual(pgettext_lazy(b"context", b"test").upper(), "TEST") + self.assertEqual( + (npgettext_lazy(b"context", b"%d test", b"%d tests") % 1).upper(), + "1 TEST" + ) def test_lazy_pickle(self): s1 = ugettext_lazy("test") diff -Nru python-django-1.6.1/tests/indexes/models.py python-django-1.6.11/tests/indexes/models.py --- python-django-1.6.1/tests/indexes/models.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/indexes/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,10 +2,35 @@ from django.db import models +class CurrentTranslation(models.ForeignObject): + """ + Creates virtual relation to the translation with model cache enabled. + """ + # Avoid validation + requires_unique_target = False + + def __init__(self, to, from_fields, to_fields, **kwargs): + # Disable reverse relation + kwargs['related_name'] = '+' + # Set unique to enable model cache. + kwargs['unique'] = True + super(CurrentTranslation, self).__init__(to, from_fields, to_fields, **kwargs) + + +class ArticleTranslation(models.Model): + + article = models.ForeignKey('indexes.Article') + language = models.CharField(max_length=10, unique=True) + content = models.TextField() + + class Article(models.Model): headline = models.CharField(max_length=100) pub_date = models.DateTimeField() + # Add virtual relation to the ArticleTranslation model. + translation = CurrentTranslation(ArticleTranslation, ['id'], ['article']) + class Meta: index_together = [ ["headline", "pub_date"], diff -Nru python-django-1.6.1/tests/indexes/tests.py python-django-1.6.11/tests/indexes/tests.py --- python-django-1.6.1/tests/indexes/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/indexes/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,5 +1,5 @@ from django.core.management.color import no_style -from django.db import connections, DEFAULT_DB_ALIAS +from django.db import connection, connections, DEFAULT_DB_ALIAS from django.test import TestCase from django.utils.unittest import skipUnless @@ -25,3 +25,10 @@ # unique=True and db_index=True should only create the varchar-specific # index (#19441). self.assertIn('("slug" varchar_pattern_ops)', index_sql[4]) + + @skipUnless(connection.vendor == 'postgresql', + "This is a postgresql-specific issue") + def test_postgresql_virtual_relation_indexes(self): + """Test indexes are not created for related objects""" + index_sql = connection.creation.sql_indexes_for_model(Article, no_style()) + self.assertEqual(len(index_sql), 1) diff -Nru python-django-1.6.1/tests/inspectdb/tests.py python-django-1.6.11/tests/inspectdb/tests.py --- python-django-1.6.1/tests/inspectdb/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/inspectdb/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -29,19 +29,49 @@ # inspected self.assertNotIn("class DjangoContentType(models.Model):", out.getvalue(), msg=error_message) - # Inspecting oracle DB doesn't produce correct results, see #19884 - @expectedFailureOnOracle - def test_field_types(self): - """Test introspection of various Django field types""" + def make_field_type_asserter(self): + """Call inspectdb and return a function to validate a field type in its output""" out = StringIO() call_command('inspectdb', - table_name_filter=lambda tn:tn.startswith('inspectdb_columntypes'), + table_name_filter=lambda tn: tn.startswith('inspectdb_columntypes'), stdout=out) output = out.getvalue() + def assertFieldType(name, definition): out_def = re.search(r'^\s*%s = (models.*)$' % name, output, re.MULTILINE).groups()[0] self.assertEqual(definition, out_def) + return assertFieldType + + # Inspecting oracle DB doesn't produce correct results, see #19884 + @expectedFailureOnOracle + def test_field_types(self): + """Test introspection of various Django field types""" + assertFieldType = self.make_field_type_asserter() + + assertFieldType('char_field', "models.CharField(max_length=10)") + assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)") + assertFieldType('date_field', "models.DateField()") + assertFieldType('date_time_field', "models.DateTimeField()") + assertFieldType('email_field', "models.CharField(max_length=75)") + assertFieldType('file_field', "models.CharField(max_length=100)") + assertFieldType('file_path_field', "models.CharField(max_length=100)") + if connection.vendor == 'postgresql': + # Only PostgreSQL has a specific type + assertFieldType('ip_address_field', "models.GenericIPAddressField()") + assertFieldType('gen_ip_adress_field', "models.GenericIPAddressField()") + else: + assertFieldType('ip_address_field', "models.CharField(max_length=15)") + assertFieldType('gen_ip_adress_field', "models.CharField(max_length=39)") + assertFieldType('slug_field', "models.CharField(max_length=50)") + assertFieldType('text_field', "models.TextField()") + assertFieldType('time_field', "models.TimeField()") + assertFieldType('url_field', "models.CharField(max_length=200)") + + def test_number_field_types(self): + """Test introspection of various Django field types""" + assertFieldType = self.make_field_type_asserter() + assertFieldType('id', "models.IntegerField(primary_key=True)") assertFieldType('big_int_field', "models.BigIntegerField()") if connection.vendor == 'mysql': @@ -51,28 +81,14 @@ else: assertFieldType('bool_field', "models.BooleanField()") assertFieldType('null_bool_field', "models.NullBooleanField()") - assertFieldType('char_field', "models.CharField(max_length=10)") - assertFieldType('comma_separated_int_field', "models.CharField(max_length=99)") - assertFieldType('date_field', "models.DateField()") - assertFieldType('date_time_field', "models.DateTimeField()") if connection.vendor == 'sqlite': # Guessed arguments, see #5014 assertFieldType('decimal_field', "models.DecimalField(max_digits=10, decimal_places=5) " "# max_digits and decimal_places have been guessed, as this database handles decimal fields as float") else: assertFieldType('decimal_field', "models.DecimalField(max_digits=6, decimal_places=1)") - assertFieldType('email_field', "models.CharField(max_length=75)") - assertFieldType('file_field', "models.CharField(max_length=100)") - assertFieldType('file_path_field', "models.CharField(max_length=100)") assertFieldType('float_field', "models.FloatField()") assertFieldType('int_field', "models.IntegerField()") - if connection.vendor == 'postgresql': - # Only PostgreSQL has a specific type - assertFieldType('ip_address_field', "models.GenericIPAddressField()") - assertFieldType('gen_ip_adress_field', "models.GenericIPAddressField()") - else: - assertFieldType('ip_address_field', "models.CharField(max_length=15)") - assertFieldType('gen_ip_adress_field', "models.CharField(max_length=39)") if connection.vendor == 'sqlite': assertFieldType('pos_int_field', "models.PositiveIntegerField()") assertFieldType('pos_small_int_field', "models.PositiveSmallIntegerField()") @@ -83,14 +99,10 @@ assertFieldType('pos_small_int_field', "models.SmallIntegerField()") else: assertFieldType('pos_small_int_field', "models.IntegerField()") - assertFieldType('slug_field', "models.CharField(max_length=50)") if connection.vendor in ('sqlite', 'postgresql'): assertFieldType('small_int_field', "models.SmallIntegerField()") else: assertFieldType('small_int_field', "models.IntegerField()") - assertFieldType('text_field', "models.TextField()") - assertFieldType('time_field', "models.TimeField()") - assertFieldType('url_field', "models.CharField(max_length=200)") @skipUnlessDBFeature('can_introspect_foreign_keys') def test_attribute_name_not_python_keyword(self): diff -Nru python-django-1.6.1/tests/mail/tests.py python-django-1.6.11/tests/mail/tests.py --- python-django-1.6.1/tests/mail/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/mail/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,7 +2,7 @@ from __future__ import unicode_literals import asyncore -import email +from email.mime.text import MIMEText import os import shutil import smtpd @@ -15,14 +15,39 @@ EmailMultiAlternatives, send_mail, send_mass_mail) from django.core.mail.backends import console, dummy, locmem, filebased, smtp from django.core.mail.message import BadHeaderError -from django.test import TestCase +from django.test import SimpleTestCase from django.test.utils import override_settings -from django.utils.encoding import force_str, force_text -from django.utils.six import PY3, StringIO +from django.utils.encoding import force_text, force_bytes +from django.utils.six import PY3, StringIO, binary_type from django.utils.translation import ugettext_lazy +if PY3: + from email.utils import parseaddr + from email import message_from_bytes, message_from_binary_file +else: + from email.Utils import parseaddr + from email import (message_from_string as message_from_bytes, + message_from_file as message_from_binary_file) -class MailTests(TestCase): + +class HeadersCheckMixin(object): + + def assertMessageHasHeaders(self, message, headers): + """ + Check that :param message: has all :param headers: headers. + + :param message: can be an instance of an email.Message subclass or a + string with the contens of an email message. + :param headers: should be a set of (header-name, header-value) tuples. + """ + if isinstance(message, binary_type): + message = message_from_bytes(message) + msg_headers = set(message.items()) + self.assertTrue(headers.issubset(msg_headers), msg='Message is missing ' + 'the following headers: %s' % (headers - msg_headers),) + + +class MailTests(HeadersCheckMixin, SimpleTestCase): """ Non-backend specific tests. """ @@ -191,8 +216,18 @@ msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) msg.encoding = 'iso-8859-1' msg.attach_alternative(html_content, "text/html") - self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') - self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n

Firstname S=FCrname is a great guy.

') + payload0 = msg.message().get_payload(0) + self.assertMessageHasHeaders(payload0, set(( + ('MIME-Version', '1.0'), + ('Content-Type', 'text/plain; charset="iso-8859-1"'), + ('Content-Transfer-Encoding', 'quoted-printable')))) + self.assertTrue(payload0.as_bytes().endswith(b'\n\nFirstname S=FCrname is a great guy.')) + payload1 = msg.message().get_payload(1) + self.assertMessageHasHeaders(payload1, set(( + ('MIME-Version', '1.0'), + ('Content-Type', 'text/html; charset="iso-8859-1"'), + ('Content-Transfer-Encoding', 'quoted-printable')))) + self.assertTrue(payload1.as_bytes().endswith(b'\n\n

Firstname S=FCrname is a great guy.

')) def test_attachments(self): """Regression test for #9367""" @@ -203,8 +238,8 @@ msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers) msg.attach_alternative(html_content, "text/html") msg.attach("an attachment.pdf", b"%PDF-1.4.%...", mimetype="application/pdf") - msg_str = msg.message().as_string() - message = email.message_from_string(msg_str) + msg_bytes = msg.message().as_bytes() + message = message_from_bytes(msg_bytes) self.assertTrue(message.is_multipart()) self.assertEqual(message.get_content_type(), 'multipart/mixed') self.assertEqual(message.get_default_type(), 'text/plain') @@ -220,8 +255,8 @@ msg = EmailMessage(subject, content, from_email, [to], headers=headers) # Unicode in file name msg.attach("une pièce jointe.pdf", b"%PDF-1.4.%...", mimetype="application/pdf") - msg_str = msg.message().as_string() - message = email.message_from_string(msg_str) + msg_bytes = msg.message().as_bytes() + message = message_from_bytes(msg_bytes) payload = message.get_payload() self.assertEqual(payload[1].get_filename(), 'une pièce jointe.pdf') @@ -303,31 +338,31 @@ # Regression for #13433 - Make sure that EmailMessage doesn't mangle # 'From ' in message body. email = EmailMessage('Subject', 'From the future', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - self.assertFalse('>From the future' in email.message().as_string()) + self.assertFalse(b'>From the future' in email.message().as_bytes()) def test_dont_base64_encode(self): # Ticket #3472 # Shouldn't use Base64 encoding at all msg = EmailMessage('Subject', 'UTF-8 encoded body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - self.assertFalse('Content-Transfer-Encoding: base64' in msg.message().as_string()) + self.assertFalse(b'Content-Transfer-Encoding: base64' in msg.message().as_bytes()) # Ticket #11212 # Shouldn't use quoted printable, should detect it can represent content with 7 bit data msg = EmailMessage('Subject', 'Body with only ASCII characters.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - s = msg.message().as_string() - self.assertFalse('Content-Transfer-Encoding: quoted-printable' in s) - self.assertTrue('Content-Transfer-Encoding: 7bit' in s) + s = msg.message().as_bytes() + self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) + self.assertTrue(b'Content-Transfer-Encoding: 7bit' in s) # Shouldn't use quoted printable, should detect it can represent content with 8 bit data msg = EmailMessage('Subject', 'Body with latin characters: àáä.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - s = msg.message().as_string() - self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s) - self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s) + s = msg.message().as_bytes() + self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) + self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) msg = EmailMessage('Subject', 'Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) - s = msg.message().as_string() - self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s) - self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s) + s = msg.message().as_bytes() + self.assertFalse(b'Content-Transfer-Encoding: quoted-printable' in s) + self.assertTrue(b'Content-Transfer-Encoding: 8bit' in s) class BaseEmailBackendTests(object): @@ -374,7 +409,7 @@ self.assertEqual(num_sent, 1) message = self.get_the_message() self.assertEqual(message["subject"], '=?utf-8?q?Ch=C3=A8re_maman?=') - self.assertEqual(force_text(message.get_payload()), 'Je t\'aime très fort') + self.assertEqual(force_text(message.get_payload(decode=True)), 'Je t\'aime très fort') def test_send_many(self): email1 = EmailMessage('Subject', 'Content1', 'from@example.com', ['to@example.com']) @@ -460,7 +495,7 @@ email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com'], cc=['cc@example.com']) mail.get_connection().send_messages([email]) message = self.get_the_message() - self.assertStartsWith(message.as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nCc: cc@example.com\nDate: ') + self.assertStartsWith(message.as_string(), 'MIME-Version: 1.0\nContent-Type: text/plain; charset="utf-8"\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nCc: cc@example.com\nDate: ') def test_idn_send(self): """ @@ -503,7 +538,7 @@ self.fail("close() unexpectedly raised an exception: %s" % e) -class LocmemBackendTests(BaseEmailBackendTests, TestCase): +class LocmemBackendTests(BaseEmailBackendTests, SimpleTestCase): email_backend = 'django.core.mail.backends.locmem.EmailBackend' def get_mailbox_content(self): @@ -533,7 +568,7 @@ send_mail('Subject\nMultiline', 'Content', 'from@example.com', ['to@example.com']) -class FileBackendTests(BaseEmailBackendTests, TestCase): +class FileBackendTests(BaseEmailBackendTests, SimpleTestCase): email_backend = 'django.core.mail.backends.filebased.EmailBackend' def setUp(self): @@ -554,9 +589,9 @@ def get_mailbox_content(self): messages = [] for filename in os.listdir(self.tmp_dir): - with open(os.path.join(self.tmp_dir, filename), 'r') as fp: - session = force_text(fp.read()).split('\n' + ('-' * 79) + '\n') - messages.extend(email.message_from_string(force_str(m)) for m in session if m) + with open(os.path.join(self.tmp_dir, filename), 'rb') as fp: + session = fp.read().split(force_bytes('\n' + ('-' * 79) + '\n', encoding='ascii')) + messages.extend(message_from_bytes(m) for m in session if m) return messages def test_file_sessions(self): @@ -566,8 +601,8 @@ connection.send_messages([msg]) self.assertEqual(len(os.listdir(self.tmp_dir)), 1) - with open(os.path.join(self.tmp_dir, os.listdir(self.tmp_dir)[0])) as fp: - message = email.message_from_file(fp) + with open(os.path.join(self.tmp_dir, os.listdir(self.tmp_dir)[0]), 'rb') as fp: + message = message_from_binary_file(fp) self.assertEqual(message.get_content_type(), 'text/plain') self.assertEqual(message.get('subject'), 'Subject') self.assertEqual(message.get('from'), 'from@example.com') @@ -590,7 +625,7 @@ connection.close() -class ConsoleBackendTests(BaseEmailBackendTests, TestCase): +class ConsoleBackendTests(HeadersCheckMixin, BaseEmailBackendTests, SimpleTestCase): email_backend = 'django.core.mail.backends.console.EmailBackend' def setUp(self): @@ -608,8 +643,8 @@ self.stream = sys.stdout = StringIO() def get_mailbox_content(self): - messages = force_text(self.stream.getvalue()).split('\n' + ('-' * 79) + '\n') - return [email.message_from_string(force_str(m)) for m in messages if m] + messages = self.stream.getvalue().split(str('\n' + ('-' * 79) + '\n')) + return [message_from_bytes(force_bytes(m)) for m in messages if m] def test_console_stream_kwarg(self): """ @@ -618,7 +653,15 @@ s = StringIO() connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s) send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) - self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) + message = force_bytes(s.getvalue().split('\n' + ('-' * 79) + '\n')[0]) + self.assertMessageHasHeaders(message, set(( + ('MIME-Version', '1.0'), + ('Content-Type', 'text/plain; charset="utf-8"'), + ('Content-Transfer-Encoding', '7bit'), + ('Subject', 'Subject'), + ('From', 'from@example.com'), + ('To', 'to@example.com')))) + self.assertIn(b'\nDate: ', message) class FakeSMTPServer(smtpd.SMTPServer, threading.Thread): @@ -636,11 +679,10 @@ self.sink_lock = threading.Lock() def process_message(self, peer, mailfrom, rcpttos, data): - m = email.message_from_string(data) if PY3: - maddr = email.utils.parseaddr(m.get('from'))[1] - else: - maddr = email.Utils.parseaddr(m.get('from'))[1] + data = data.encode('utf-8') + m = message_from_bytes(data) + maddr = parseaddr(m.get('from'))[1] if mailfrom != maddr: return "553 '%s' != '%s'" % (mailfrom, maddr) with self.sink_lock: @@ -674,7 +716,7 @@ self.join() -class SMTPBackendTests(BaseEmailBackendTests, TestCase): +class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase): email_backend = 'django.core.mail.backends.smtp.EmailBackend' @classmethod diff -Nru python-django-1.6.1/tests/model_fields/models.py python-django-1.6.11/tests/model_fields/models.py --- python-django-1.6.1/tests/model_fields/models.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/model_fields/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -61,6 +61,12 @@ bfield = models.BooleanField(default=None) string = models.CharField(max_length=10, default='abc') +class DateTimeModel(models.Model): + d = models.DateField() + dt = models.DateTimeField() + t = models.TimeField() + + class FksToBooleans(models.Model): """Model wih FKs to models with {Null,}BooleanField's, #15040""" bf = models.ForeignKey(BooleanModel) @@ -132,7 +138,7 @@ attr_class = TestImageFieldFile # Set up a temp directory for file storage. - temp_storage_dir = tempfile.mkdtemp() + temp_storage_dir = tempfile.mkdtemp(dir=os.environ['DJANGO_TEST_TEMP_DIR']) temp_storage = FileSystemStorage(temp_storage_dir) temp_upload_to_dir = os.path.join(temp_storage.location, 'tests') diff -Nru python-django-1.6.1/tests/model_fields/tests.py python-django-1.6.11/tests/model_fields/tests.py --- python-django-1.6.1/tests/model_fields/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/model_fields/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -7,13 +7,20 @@ from django import forms from django.core.exceptions import ValidationError from django.db import connection, models, IntegrityError -from django.db.models.fields.files import FieldFile +from django.db.models.fields import ( + AutoField, BigIntegerField, BinaryField, BooleanField, CharField, + CommaSeparatedIntegerField, DateField, DateTimeField, DecimalField, + EmailField, FilePathField, FloatField, IntegerField, IPAddressField, + GenericIPAddressField, NullBooleanField, PositiveIntegerField, + PositiveSmallIntegerField, SlugField, SmallIntegerField, TextField, + TimeField, URLField) +from django.db.models.fields.files import FileField, ImageField from django.utils import six from django.utils import unittest from .models import (Foo, Bar, Whiz, BigD, BigS, Image, BigInt, Post, NullBooleanModel, BooleanModel, DataModel, Document, RenamedField, - VerboseNameField, FksToBooleans) + DateTimeModel, VerboseNameField, FksToBooleans) class BasicFieldTests(test.TestCase): @@ -147,6 +154,18 @@ self.assertEqual(f.to_python('01:02:03.999999'), datetime.time(1, 2, 3, 999999)) + @test.skipUnlessDBFeature("supports_microsecond_precision") + def test_datetimes_save_completely(self): + dat = datetime.date(2014, 3, 12) + datetim = datetime.datetime(2014, 3, 12, 21, 22, 23, 240000) + tim = datetime.time(21, 22, 23, 240000) + DateTimeModel.objects.create(d=dat, dt=datetim, t=tim) + obj = DateTimeModel.objects.first() + self.assertTrue(obj) + self.assertEqual(obj.d, dat) + self.assertEqual(obj.dt, datetim) + self.assertEqual(obj.t, tim) + class BooleanFieldTests(unittest.TestCase): def _test_get_db_prep_lookup(self, f): from django.db import connection @@ -494,6 +513,94 @@ self.assertRaises(ValidationError, form_field.clean, '127.0.0.1') +class PrepValueTest(test.TestCase): + def test_AutoField(self): + self.assertIsInstance(AutoField(primary_key=True).get_prep_value(1), int) + + @unittest.skipIf(six.PY3, "Python 3 has no `long` type.") + def test_BigIntegerField(self): + self.assertIsInstance(BigIntegerField().get_prep_value(long(9999999999999999999)), long) + + def test_BinaryField(self): + self.assertIsInstance(BinaryField().get_prep_value(b''), bytes) + + def test_BooleanField(self): + self.assertIsInstance(BooleanField().get_prep_value(True), bool) + + def test_CharField(self): + self.assertIsInstance(CharField().get_prep_value(''), six.text_type) + self.assertIsInstance(CharField().get_prep_value(0), six.text_type) + + def test_CommaSeparatedIntegerField(self): + self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value('1,2'), six.text_type) + self.assertIsInstance(CommaSeparatedIntegerField().get_prep_value(0), six.text_type) + + def test_DateField(self): + self.assertIsInstance(DateField().get_prep_value(datetime.date.today()), datetime.date) + + def test_DateTimeField(self): + self.assertIsInstance(DateTimeField().get_prep_value(datetime.datetime.now()), datetime.datetime) + + def test_DecimalField(self): + self.assertIsInstance(DecimalField().get_prep_value(Decimal('1.2')), Decimal) + + def test_EmailField(self): + self.assertIsInstance(EmailField().get_prep_value('mailbox@domain.com'), six.text_type) + + def test_FileField(self): + self.assertIsInstance(FileField().get_prep_value('filename.ext'), six.text_type) + self.assertIsInstance(FileField().get_prep_value(0), six.text_type) + + def test_FilePathField(self): + self.assertIsInstance(FilePathField().get_prep_value('tests.py'), six.text_type) + self.assertIsInstance(FilePathField().get_prep_value(0), six.text_type) + + def test_FloatField(self): + self.assertIsInstance(FloatField().get_prep_value(1.2), float) + + def test_ImageField(self): + self.assertIsInstance(ImageField().get_prep_value('filename.ext'), six.text_type) + + def test_IntegerField(self): + self.assertIsInstance(IntegerField().get_prep_value(1), int) + + def test_IPAddressField(self): + self.assertIsInstance(IPAddressField().get_prep_value('127.0.0.1'), six.text_type) + self.assertIsInstance(IPAddressField().get_prep_value(0), six.text_type) + + def test_GenericIPAddressField(self): + self.assertIsInstance(GenericIPAddressField().get_prep_value('127.0.0.1'), six.text_type) + self.assertIsInstance(GenericIPAddressField().get_prep_value(0), six.text_type) + + def test_NullBooleanField(self): + self.assertIsInstance(NullBooleanField().get_prep_value(True), bool) + + def test_PositiveIntegerField(self): + self.assertIsInstance(PositiveIntegerField().get_prep_value(1), int) + + def test_PositiveSmallIntegerField(self): + self.assertIsInstance(PositiveSmallIntegerField().get_prep_value(1), int) + + def test_SlugField(self): + self.assertIsInstance(SlugField().get_prep_value('slug'), six.text_type) + self.assertIsInstance(SlugField().get_prep_value(0), six.text_type) + + def test_SmallIntegerField(self): + self.assertIsInstance(SmallIntegerField().get_prep_value(1), int) + + def test_TextField(self): + self.assertIsInstance(TextField().get_prep_value('Abc'), six.text_type) + self.assertIsInstance(TextField().get_prep_value(0), six.text_type) + + def test_TimeField(self): + self.assertIsInstance( + TimeField().get_prep_value(datetime.datetime.now().time()), + datetime.time) + + def test_URLField(self): + self.assertIsInstance(URLField().get_prep_value('http://domain.com'), six.text_type) + + class CustomFieldTests(unittest.TestCase): def test_14786(self): diff -Nru python-django-1.6.1/tests/model_forms/tests.py python-django-1.6.11/tests/model_forms/tests.py --- python-django-1.6.1/tests/model_forms/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/model_forms/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1381,6 +1381,27 @@

''' % (w_woodward.pk, w_bernstein.pk, bw.pk, w_royko.pk)) + def test_show_hidden_initial_changed_queries_efficiently(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField( + show_hidden_initial=True, queryset=Writer.objects.all()) + + writers = (Writer.objects.create(name=str(x)) for x in range(0, 50)) + writer_pks = tuple(x.pk for x in writers) + form = WriterForm(data={'initial-persons': writer_pks}) + with self.assertNumQueries(1): + self.assertTrue(form.has_changed()) + + def test_clean_does_deduplicate_values(self): + class WriterForm(forms.Form): + persons = forms.ModelMultipleChoiceField(queryset=Writer.objects.all()) + + person1 = Writer.objects.create(name="Person 1") + form = WriterForm(data={}) + queryset = form.fields['persons'].clean([str(person1.pk)] * 50) + sql, params = queryset.query.sql_with_params() + self.assertEqual(len(params), 1) + def test_file_field(self): # Test conditions when files is either not given or empty. diff -Nru python-django-1.6.1/tests/model_formsets_regress/models.py python-django-1.6.11/tests/model_formsets_regress/models.py --- python-django-1.6.1/tests/model_formsets_regress/models.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/model_formsets_regress/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -10,6 +10,15 @@ user = models.ForeignKey(User, to_field="username") data = models.IntegerField() +class UserProfile(models.Model): + user = models.ForeignKey(User, unique=True, to_field="username") + about = models.TextField() + +class ProfileNetwork(models.Model): + profile = models.ForeignKey(UserProfile, to_field="user") + network = models.IntegerField() + identifier = models.IntegerField() + class Place(models.Model): name = models.CharField(max_length=50) diff -Nru python-django-1.6.1/tests/model_formsets_regress/tests.py python-django-1.6.11/tests/model_formsets_regress/tests.py --- python-django-1.6.1/tests/model_formsets_regress/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/model_formsets_regress/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -7,7 +7,10 @@ from django.test import TestCase from django.utils import six -from .models import User, UserSite, Restaurant, Manager, Network, Host +from .models import ( + User, UserSite, UserProfile, ProfileNetwork, Restaurant, Manager, Network, + Host, +) class InlineFormsetTests(TestCase): @@ -154,6 +157,37 @@ else: self.fail('Errors found on formset:%s' % form_set.errors) + def test_inline_model_with_to_field(self): + """ + #13794 --- An inline model with a to_field of a formset with instance + has working relations. + """ + FormSet = inlineformset_factory(User, UserSite, exclude=('is_superuser',)) + + user = User.objects.create(username="guido", serial=1337) + UserSite.objects.create(user=user, data=10) + formset = FormSet(instance=user) + + # Testing the inline model's relation + self.assertEqual(formset[0].instance.user_id, "guido") + + def test_inline_model_with_to_field_to_rel(self): + """ + #13794 --- An inline model with a to_field to a related field of a + formset with instance has working relations. + """ + FormSet = inlineformset_factory(UserProfile, ProfileNetwork, exclude=[]) + + user = User.objects.create(username="guido", serial=1337, pk=1) + self.assertEqual(user.pk, 1) + profile = UserProfile.objects.create(user=user, about="about", pk=2) + self.assertEqual(profile.pk, 2) + ProfileNetwork.objects.create(profile=profile, network=10, identifier=10) + formset = FormSet(instance=profile) + + # Testing the inline model's relation + self.assertEqual(formset[0].instance.profile_id, 1) + def test_formset_with_none_instance(self): "A formset with instance=None can be created. Regression for #11872" Form = modelform_factory(User, fields="__all__") diff -Nru python-django-1.6.1/tests/model_inheritance/tests.py python-django-1.6.11/tests/model_inheritance/tests.py --- python-django-1.6.1/tests/model_inheritance/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/model_inheritance/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -275,6 +275,43 @@ lambda: ItalianRestaurant.objects.select_related("chef")[0].chef ) + def test_select_related_defer(self): + """ + #23370 - Should be able to defer child fields when using + select_related() from parent to child. + """ + Restaurant.objects.create( + name="Demon Dogs", + address="944 W. Fullerton", + serves_hot_dogs=True, + serves_pizza=False, + rating=2, + ) + ItalianRestaurant.objects.create( + name="Ristorante Miron", + address="1234 W. Ash", + serves_hot_dogs=False, + serves_pizza=False, + serves_gnocchi=True, + rating=4, + ) + + qs = (Restaurant.objects + .select_related("italianrestaurant") + .defer("italianrestaurant__serves_gnocchi") + .order_by("rating")) + + # Test that the field was actually defered + with self.assertNumQueries(2): + objs = list(qs.all()) + self.assertTrue(objs[1].italianrestaurant.serves_gnocchi) + + # Test that model fields where assigned correct values + self.assertEqual(qs[0].name, 'Demon Dogs') + self.assertEqual(qs[0].rating, 2) + self.assertEqual(qs[1].italianrestaurant.name, 'Ristorante Miron') + self.assertEqual(qs[1].italianrestaurant.rating, 4) + def test_mixin_init(self): m = MixinModel() self.assertEqual(m.other_attr, 1) diff -Nru python-django-1.6.1/tests/model_inheritance_regress/tests.py python-django-1.6.11/tests/model_inheritance_regress/tests.py --- python-django-1.6.1/tests/model_inheritance_regress/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/model_inheritance_regress/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -441,3 +441,9 @@ # used in the qs and top contains direct pointer to the bottom model. qs = ItalianRestaurant.objects.values_list('serves_gnocchi').filter(name='foo') self.assertEqual(str(qs.query).count('JOIN'), 1) + + def test_inheritance_resolve_columns(self): + Restaurant.objects.create(name='Bobs Cafe', address="Somewhere", + serves_pizza=True, serves_hot_dogs=True) + p = Place.objects.all().select_related('restaurant')[0] + self.assertIsInstance(p.restaurant.serves_pizza, bool) diff -Nru python-django-1.6.1/tests/prefetch_related/tests.py python-django-1.6.11/tests/prefetch_related/tests.py --- python-django-1.6.1/tests/prefetch_related/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/prefetch_related/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,9 +2,11 @@ from django.contrib.contenttypes.models import ContentType from django.db import connection +from django.db.models.query import get_prefetcher from django.test import TestCase from django.test.utils import override_settings from django.utils import six +from django.utils.encoding import force_text from .models import (Author, Book, Reader, Qualification, Teacher, Department, TaggedItem, Bookmark, AuthorAddress, FavoriteAuthors, AuthorWithAge, @@ -677,3 +679,18 @@ def test_bug(self): list(Author2.objects.prefetch_related('first_book', 'favorite_books')) + + +class Ticket21760Tests(TestCase): + + def setUp(self): + self.rooms = [] + for _ in range(3): + house = House.objects.create() + for _ in range(3): + self.rooms.append(Room.objects.create(house = house)) + + def test_bug(self): + prefetcher = get_prefetcher(self.rooms[0], 'house')[0] + queryset = prefetcher.get_prefetch_queryset(list(Room.objects.all()))[0] + self.assertNotIn(' JOIN ', force_text(queryset.query)) diff -Nru python-django-1.6.1/tests/queries/models.py python-django-1.6.11/tests/queries/models.py --- python-django-1.6.1/tests/queries/models.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/queries/models.py 2015-03-18 23:50:43.000000000 +0000 @@ -536,3 +536,18 @@ class Ticket21203Child(models.Model): childid = models.AutoField(primary_key=True) parent = models.ForeignKey(Ticket21203Parent) + + +# Bug #22429 + +class School(models.Model): + pass + + +class Student(models.Model): + school = models.ForeignKey(School) + + +class Classroom(models.Model): + school = models.ForeignKey(School) + students = models.ManyToManyField(Student, related_name='classroom') diff -Nru python-django-1.6.1/tests/queries/tests.py python-django-1.6.11/tests/queries/tests.py --- python-django-1.6.1/tests/queries/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/queries/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -27,7 +27,8 @@ ModelA, ModelB, ModelC, ModelD, Responsibility, Job, JobResponsibilities, BaseA, FK1, Identifier, Program, Channel, Page, Paragraph, Chapter, Book, MyObject, Order, OrderItem, Task, Staff, StaffUser, Ticket21203Parent, - Ticket21203Child) + Ticket21203Child, Classroom, School, Student) + class BaseQuerysetTest(TestCase): def assertValueQuerysetEqual(self, qs, values): @@ -2662,6 +2663,85 @@ self.assertEqual(str(qs.query).count('INNER JOIN'), 1) self.assertEqual(list(qs), [self.a2]) + def test_ticket_21748(self): + i1 = Identifier.objects.create(name='i1') + i2 = Identifier.objects.create(name='i2') + i3 = Identifier.objects.create(name='i3') + Program.objects.create(identifier=i1) + Channel.objects.create(identifier=i1) + Program.objects.create(identifier=i2) + self.assertQuerysetEqual( + Identifier.objects.filter(program=None, channel=None), + [i3], lambda x: x) + self.assertQuerysetEqual( + Identifier.objects.exclude(program=None, channel=None).order_by('name'), + [i1, i2], lambda x: x) + + def test_ticket_21748_double_negated_and(self): + i1 = Identifier.objects.create(name='i1') + i2 = Identifier.objects.create(name='i2') + Identifier.objects.create(name='i3') + p1 = Program.objects.create(identifier=i1) + c1 = Channel.objects.create(identifier=i1) + Program.objects.create(identifier=i2) + # Check the ~~Q() (or equivalently .exclude(~Q)) works like Q() for + # join promotion. + qs1_doubleneg = Identifier.objects.exclude(~Q(program__id=p1.id, channel__id=c1.id)).order_by('pk') + qs1_filter = Identifier.objects.filter(program__id=p1.id, channel__id=c1.id).order_by('pk') + self.assertQuerysetEqual(qs1_doubleneg, qs1_filter, lambda x: x) + self.assertEqual(str(qs1_filter.query).count('JOIN'), + str(qs1_doubleneg.query).count('JOIN')) + self.assertEqual(2, str(qs1_doubleneg.query).count('INNER JOIN')) + self.assertEqual(str(qs1_filter.query).count('INNER JOIN'), + str(qs1_doubleneg.query).count('INNER JOIN')) + + def test_ticket_21748_double_negated_or(self): + i1 = Identifier.objects.create(name='i1') + i2 = Identifier.objects.create(name='i2') + Identifier.objects.create(name='i3') + p1 = Program.objects.create(identifier=i1) + c1 = Channel.objects.create(identifier=i1) + p2 = Program.objects.create(identifier=i2) + # Test OR + doubleneq. The expected result is that channel is LOUTER + # joined, program INNER joined + qs1_filter = Identifier.objects.filter( + Q(program__id=p2.id, channel__id=c1.id) + | Q(program__id=p1.id) + ).order_by('pk') + qs1_doubleneg = Identifier.objects.exclude( + ~Q(Q(program__id=p2.id, channel__id=c1.id) + | Q(program__id=p1.id)) + ).order_by('pk') + self.assertQuerysetEqual(qs1_doubleneg, qs1_filter, lambda x: x) + self.assertEqual(str(qs1_filter.query).count('JOIN'), + str(qs1_doubleneg.query).count('JOIN')) + self.assertEqual(1, str(qs1_doubleneg.query).count('INNER JOIN')) + self.assertEqual(str(qs1_filter.query).count('INNER JOIN'), + str(qs1_doubleneg.query).count('INNER JOIN')) + + def test_ticket_21748_complex_filter(self): + i1 = Identifier.objects.create(name='i1') + i2 = Identifier.objects.create(name='i2') + Identifier.objects.create(name='i3') + p1 = Program.objects.create(identifier=i1) + c1 = Channel.objects.create(identifier=i1) + p2 = Program.objects.create(identifier=i2) + # Finally, a more complex case, one time in a way where each + # NOT is pushed to lowest level in the boolean tree, and + # another query where this isn't done. + qs1 = Identifier.objects.filter( + ~Q(~Q(program__id=p2.id, channel__id=c1.id) + & Q(program__id=p1.id))).order_by('pk') + qs2 = Identifier.objects.filter( + Q(Q(program__id=p2.id, channel__id=c1.id) + | ~Q(program__id=p1.id))).order_by('pk') + self.assertQuerysetEqual(qs1, qs2, lambda x: x) + self.assertEqual(str(qs1.query).count('JOIN'), + str(qs2.query).count('JOIN')) + self.assertEqual(0, str(qs1.query).count('INNER JOIN')) + self.assertEqual(str(qs1.query).count('INNER JOIN'), + str(qs2.query).count('INNER JOIN')) + class ReverseJoinTrimmingTest(TestCase): def test_reverse_trimming(self): @@ -3008,3 +3088,36 @@ qs = Ticket21203Child.objects.select_related('parent').defer('parent__created') self.assertQuerysetEqual(qs, [c], lambda x: x) self.assertIs(qs[0].parent.parent_bool, True) + + +class ForeignKeyToBaseExcludeTests(TestCase): + def test_ticket_21787(self): + sc1 = SpecialCategory.objects.create(special_name='sc1', name='sc1') + sc2 = SpecialCategory.objects.create(special_name='sc2', name='sc2') + sc3 = SpecialCategory.objects.create(special_name='sc3', name='sc3') + c1 = CategoryItem.objects.create(category=sc1) + CategoryItem.objects.create(category=sc2) + self.assertQuerysetEqual( + SpecialCategory.objects.exclude( + categoryitem__id=c1.pk).order_by('name'), + [sc2, sc3], lambda x: x + ) + self.assertQuerysetEqual( + SpecialCategory.objects.filter(categoryitem__id=c1.pk), + [sc1], lambda x: x + ) + + +class Ticket22429Tests(TestCase): + def test_ticket_22429(self): + sc1 = School.objects.create() + st1 = Student.objects.create(school=sc1) + + sc2 = School.objects.create() + st2 = Student.objects.create(school=sc2) + + cr = Classroom.objects.create(school=sc1) + cr.students.add(st1) + + queryset = Student.objects.filter(~Q(classroom__school=F('school'))) + self.assertQuerysetEqual(queryset, [st2], lambda x: x) diff -Nru python-django-1.6.1/tests/requirements/base.txt python-django-1.6.11/tests/requirements/base.txt --- python-django-1.6.1/tests/requirements/base.txt 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/requirements/base.txt 2015-03-18 23:50:43.000000000 +0000 @@ -1,6 +1,6 @@ bcrypt docutils -numpy +numpy < 1.9 Pillow PyYAML pytz diff -Nru python-django-1.6.1/tests/select_for_update/tests.py python-django-1.6.11/tests/select_for_update/tests.py --- python-django-1.6.1/tests/select_for_update/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/select_for_update/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -151,6 +151,34 @@ Person.objects.all().select_for_update(nowait=True) ) + @skipUnlessDBFeature('has_select_for_update') + def test_for_update_requires_transaction(self): + """ + Test that a TransactionManagementError is raised + when a select_for_update query is executed outside of a transaction. + """ + transaction.set_autocommit(True) + try: + with self.assertRaises(transaction.TransactionManagementError): + list(Person.objects.all().select_for_update()) + finally: + transaction.set_autocommit(True) + + @skipUnlessDBFeature('has_select_for_update') + def test_for_update_requires_transaction_only_in_execution(self): + """ + Test that no TransactionManagementError is raised + when select_for_update is invoked outside of a transaction - + only when the query is executed. + """ + transaction.set_autocommit(True) + try: + people = Person.objects.all().select_for_update() + with self.assertRaises(transaction.TransactionManagementError): + list(people) + finally: + transaction.set_autocommit(True) + def run_select_for_update(self, status, nowait=False): """ Utility method that runs a SELECT FOR UPDATE against all diff -Nru python-django-1.6.1/tests/select_related_regress/tests.py python-django-1.6.11/tests/select_related_regress/tests.py --- python-django-1.6.1/tests/select_related_regress/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/select_related_regress/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -173,3 +173,13 @@ self.assertEqual(Chick.objects.all()[0].mother.name, 'Hen') self.assertEqual(Chick.objects.select_related()[0].mother.name, 'Hen') + + def test_regression_22508(self): + building = Building.objects.create(name='101') + device = Device.objects.create(name="router", building=building) + Port.objects.create(port_number='1', device=device) + + device = Device.objects.get() + port = device.port_set.select_related('device__building').get() + with self.assertNumQueries(0): + port.device.building diff -Nru python-django-1.6.1/tests/servers/test_basehttp.py python-django-1.6.11/tests/servers/test_basehttp.py --- python-django-1.6.1/tests/servers/test_basehttp.py 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/tests/servers/test_basehttp.py 2015-03-18 23:40:08.000000000 +0000 @@ -0,0 +1,67 @@ +import sys + +from django.core.servers.basehttp import WSGIRequestHandler +from django.test import TestCase +from django.utils.six import BytesIO, StringIO + + +class Stub(object): + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + +class WSGIRequestHandlerTestCase(TestCase): + + def test_strips_underscore_headers(self): + """WSGIRequestHandler ignores headers containing underscores. + + This follows the lead of nginx and Apache 2.4, and is to avoid + ambiguity between dashes and underscores in mapping to WSGI environ, + which can have security implications. + """ + def test_app(environ, start_response): + """A WSGI app that just reflects its HTTP environ.""" + start_response('200 OK', []) + http_environ_items = sorted( + '%s:%s' % (k, v) for k, v in environ.items() + if k.startswith('HTTP_') + ) + yield (','.join(http_environ_items)).encode('utf-8') + + rfile = BytesIO() + rfile.write(b"GET / HTTP/1.0\r\n") + rfile.write(b"Some-Header: good\r\n") + rfile.write(b"Some_Header: bad\r\n") + rfile.write(b"Other_Header: bad\r\n") + rfile.seek(0) + + # WSGIRequestHandler closes the output file; we need to make this a + # no-op so we can still read its contents. + class UnclosableBytesIO(BytesIO): + def close(self): + pass + + wfile = UnclosableBytesIO() + + def makefile(mode, *a, **kw): + if mode == 'rb': + return rfile + elif mode == 'wb': + return wfile + + request = Stub(makefile=makefile) + server = Stub(base_environ={}, get_app=lambda: test_app) + + # We don't need to check stderr, but we don't want it in test output + old_stderr = sys.stderr + sys.stderr = StringIO() + try: + # instantiating a handler runs the request as side effect + WSGIRequestHandler(request, '192.168.0.2', server) + finally: + sys.stderr = old_stderr + + wfile.seek(0) + body = list(wfile.readlines())[-1] + + self.assertEqual(body, b'HTTP_SOME_HEADER:good') diff -Nru python-django-1.6.1/tests/staticfiles_tests/tests.py python-django-1.6.11/tests/staticfiles_tests/tests.py --- python-django-1.6.1/tests/staticfiles_tests/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/staticfiles_tests/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -21,6 +21,10 @@ from django.utils import six from django.contrib.staticfiles import finders, storage +from django.contrib.staticfiles.management.commands import collectstatic + +from .storage import DummyStorage + TEST_ROOT = os.path.dirname(upath(__file__)) TEST_SETTINGS = { @@ -223,6 +227,40 @@ self.assertIn('apps', force_text(lines[1])) +class TestConfiguration(StaticFilesTestCase): + def test_location_empty(self): + err = six.StringIO() + for root in ['', None]: + with override_settings(STATIC_ROOT=root): + with six.assertRaisesRegex( + self, ImproperlyConfigured, + 'without having set the STATIC_ROOT setting to a filesystem path'): + call_command('collectstatic', interactive=False, verbosity=0, stderr=err) + + def test_local_storage_detection_helper(self): + staticfiles_storage = storage.staticfiles_storage + try: + storage.staticfiles_storage._wrapped = empty + with override_settings(STATICFILES_STORAGE='django.contrib.staticfiles.storage.StaticFilesStorage'): + command = collectstatic.Command() + self.assertTrue(command.is_local_storage()) + + storage.staticfiles_storage._wrapped = empty + with override_settings(STATICFILES_STORAGE='staticfiles_tests.storage.DummyStorage'): + command = collectstatic.Command() + self.assertFalse(command.is_local_storage()) + + storage.staticfiles_storage = storage.FileSystemStorage() + command = collectstatic.Command() + self.assertTrue(command.is_local_storage()) + + storage.staticfiles_storage = DummyStorage() + command = collectstatic.Command() + self.assertFalse(command.is_local_storage()) + finally: + storage.staticfiles_storage = staticfiles_storage + + class TestCollection(CollectionTestCase, TestDefaults): """ Test ``collectstatic`` management command. diff -Nru python-django-1.6.1/tests/transactions/tests.py python-django-1.6.11/tests/transactions/tests.py --- python-django-1.6.1/tests/transactions/tests.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/transactions/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,8 +1,14 @@ from __future__ import absolute_import import sys +try: + import threading +except ImportError: + threading = None +import time -from django.db import connection, transaction, DatabaseError, IntegrityError +from django.db import (connection, transaction, + DatabaseError, Error, IntegrityError, OperationalError) from django.test import TransactionTestCase, skipIfDBFeature, skipUnlessDBFeature from django.test.utils import IgnorePendingDeprecationWarningsMixin from django.utils import six @@ -353,6 +359,59 @@ r2.save(force_update=True) self.assertEqual(Reporter.objects.get(pk=r1.pk).last_name, "Calculus") + @skipUnlessDBFeature('test_db_allows_multiple_connections') + def test_atomic_prevents_queries_in_broken_transaction_after_client_close(self): + with transaction.atomic(): + Reporter.objects.create(first_name="Archibald", last_name="Haddock") + connection.close() + # The connection is closed and the transaction is marked as + # needing rollback. This will raise an InterfaceError on databases + # that refuse to create cursors on closed connections (PostgreSQL) + # and a TransactionManagementError on other databases. + with self.assertRaises(Error): + Reporter.objects.create(first_name="Cuthbert", last_name="Calculus") + # The connection is usable again . + self.assertEqual(Reporter.objects.count(), 0) + + +@skipUnless(connection.vendor == 'mysql', "MySQL-specific behaviors") +class AtomicMySQLTests(TransactionTestCase): + + available_apps = ['transactions'] + + @skipIf(threading is None, "Test requires threading") + def test_implicit_savepoint_rollback(self): + """MySQL implicitly rolls back savepoints when it deadlocks (#22291).""" + + other_thread_ready = threading.Event() + + def other_thread(): + try: + with transaction.atomic(): + Reporter.objects.create(id=1, first_name="Tintin") + other_thread_ready.set() + # We cannot synchronize the two threads with an event here + # because the main thread locks. Sleep for a little while. + time.sleep(1) + # 2) ... and this line deadlocks. (see below for 1) + Reporter.objects.exclude(id=1).update(id=2) + finally: + # This is the thread-local connection, not the main connection. + connection.close() + + other_thread = threading.Thread(target=other_thread) + other_thread.start() + other_thread_ready.wait() + + with six.assertRaisesRegex(self, OperationalError, 'Deadlock found'): + # Double atomic to enter a transaction and create a savepoint. + with transaction.atomic(): + with transaction.atomic(): + # 1) This line locks... (see above for 2) + Reporter.objects.create(id=1, first_name="Tintin") + + other_thread.join() + class AtomicMiscTests(TransactionTestCase): diff -Nru python-django-1.6.1/tests/urlpatterns_reverse/nonimported_module.py python-django-1.6.11/tests/urlpatterns_reverse/nonimported_module.py --- python-django-1.6.1/tests/urlpatterns_reverse/nonimported_module.py 1970-01-01 00:00:00.000000000 +0000 +++ python-django-1.6.11/tests/urlpatterns_reverse/nonimported_module.py 2015-03-09 15:58:12.000000000 +0000 @@ -0,0 +1,3 @@ +def view(request): + """Stub view""" + pass diff -Nru python-django-1.6.1/tests/urlpatterns_reverse/tests.py python-django-1.6.11/tests/urlpatterns_reverse/tests.py --- python-django-1.6.1/tests/urlpatterns_reverse/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/urlpatterns_reverse/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,8 +1,11 @@ +# -*- coding: utf-8 -*- """ Unit tests for reverse URL lookups. """ from __future__ import absolute_import, unicode_literals +import sys + from django.conf import settings from django.contrib.auth.models import User from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist @@ -144,6 +147,9 @@ ('defaults', '/defaults_view2/3/', [], {'arg1': 3, 'arg2': 2}), ('defaults', NoReverseMatch, [], {'arg1': 3, 'arg2': 3}), ('defaults', NoReverseMatch, [], {'arg2': 1}), + + # Security tests + ('security', '/%2Fexample.com/security/', ['/example.com'], {}), ) class NoURLPatternsTests(TestCase): @@ -313,6 +319,25 @@ self.assertEqual(res.url, '/foo/') res = redirect('http://example.com/') self.assertEqual(res.url, 'http://example.com/') + # Assert that we can redirect using UTF-8 strings + res = redirect('/æøå/abc/') + self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5/abc/') + # Assert that no imports are attempted when dealing with a relative path + # (previously, the below would resolve in a UnicodeEncodeError from __import__ ) + res = redirect('/æøå.abc/') + self.assertEqual(res.url, '/%C3%A6%C3%B8%C3%A5.abc/') + res = redirect('os.path') + self.assertEqual(res.url, 'os.path') + + def test_no_illegal_imports(self): + # modules that are not listed in urlpatterns should not be importable + redirect("urlpatterns_reverse.nonimported_module.view") + self.assertNotIn("urlpatterns_reverse.nonimported_module", sys.modules) + + def test_reverse_by_path_nested(self): + # Views that are added to urlpatterns using include() should be + # reversable by doted path. + self.assertEqual(reverse('urlpatterns_reverse.views.nested_view'), '/includes/nested_path/') def test_redirect_view_object(self): from .views import absolute_kwargs_view @@ -641,4 +666,3 @@ # swallow it. self.assertRaises(AttributeError, get_callable, 'urlpatterns_reverse.views_broken.i_am_broken') - diff -Nru python-django-1.6.1/tests/urlpatterns_reverse/urls.py python-django-1.6.11/tests/urlpatterns_reverse/urls.py --- python-django-1.6.1/tests/urlpatterns_reverse/urls.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/urlpatterns_reverse/urls.py 2015-03-18 23:50:43.000000000 +0000 @@ -2,11 +2,12 @@ from django.conf.urls import patterns, url, include -from .views import empty_view, absolute_kwargs_view +from .views import empty_view, empty_view_partial, empty_view_wrapped, absolute_kwargs_view other_patterns = patterns('', url(r'non_path_include/$', empty_view, name='non_path_include'), + url(r'nested_path/$', 'urlpatterns_reverse.views.nested_view'), ) urlpatterns = patterns('', @@ -55,6 +56,10 @@ # This is non-reversible, but we shouldn't blow up when parsing it. url(r'^(?:foo|bar)(\w+)/$', empty_view, name="disjunction"), + # Partials should be fine. + url(r'^partial/', empty_view_partial, name="partial"), + url(r'^partial_wrapped/', empty_view_wrapped, name="partial_wrapped"), + # Regression views for #9038. See tests for more details url(r'arg_view/$', 'kwargs_view'), url(r'arg_view/(?P\d+)/$', 'kwargs_view'), @@ -66,4 +71,7 @@ (r'defaults_view2/(?P\d+)/', 'defaults_view', {'arg2': 2}, 'defaults'), url('^includes/', include(other_patterns)), + + # Security tests + url('(.+)/security/$', empty_view, name='security'), ) diff -Nru python-django-1.6.1/tests/urlpatterns_reverse/views.py python-django-1.6.11/tests/urlpatterns_reverse/views.py --- python-django-1.6.1/tests/urlpatterns_reverse/views.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/urlpatterns_reverse/views.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,3 +1,5 @@ +from functools import partial, update_wrapper + from django.http import HttpResponse from django.views.generic import RedirectView from django.core.urlresolvers import reverse_lazy @@ -16,6 +18,10 @@ def defaults_view(request, arg1, arg2): pass +def nested_view(request): + pass + + def erroneous_view(request): import non_existent @@ -41,3 +47,11 @@ def bad_view(request, *args, **kwargs): raise ValueError("I don't think I'm getting good value for this view") + + +empty_view_partial = partial(empty_view, template_name="template.html") + + +empty_view_wrapped = update_wrapper( + partial(empty_view, template_name="template.html"), empty_view, +) diff -Nru python-django-1.6.1/tests/user_commands/tests.py python-django-1.6.11/tests/user_commands/tests.py --- python-django-1.6.1/tests/user_commands/tests.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/user_commands/tests.py 2015-03-18 23:50:43.000000000 +0000 @@ -1,8 +1,9 @@ +import os import sys from django.core import management from django.core.management import CommandError -from django.core.management.utils import popen_wrapper +from django.core.management.utils import find_command, popen_wrapper from django.test import SimpleTestCase from django.utils import translation from django.utils.six import StringIO @@ -60,6 +61,19 @@ management.call_command('leave_locale_alone_true', stdout=out) self.assertEqual(out.getvalue(), "pl\n") + def test_find_command_without_PATH(self): + """ + find_command should still work when the PATH environment variable + doesn't exist (#22256). + """ + current_path = os.environ.pop('PATH', None) + + try: + self.assertIsNone(find_command('_missing_')) + finally: + if current_path is not None: + os.environ['PATH'] = current_path + class UtilsTests(SimpleTestCase): diff -Nru python-django-1.6.1/tests/utils_tests/test_datastructures.py python-django-1.6.11/tests/utils_tests/test_datastructures.py --- python-django-1.6.1/tests/utils_tests/test_datastructures.py 2013-12-12 19:37:59.000000000 +0000 +++ python-django-1.6.11/tests/utils_tests/test_datastructures.py 2015-03-18 23:50:43.000000000 +0000 @@ -280,7 +280,7 @@ 'pm': ['Rory'], }) d = mvd.dict() - self.assertEqual(list(six.iterkeys(d)), list(six.iterkeys(mvd))) + self.assertEqual(sorted(six.iterkeys(d)), sorted(six.iterkeys(mvd))) for key in six.iterkeys(mvd): self.assertEqual(d[key], mvd[key]) diff -Nru python-django-1.6.1/tests/utils_tests/test_html.py python-django-1.6.11/tests/utils_tests/test_html.py --- python-django-1.6.1/tests/utils_tests/test_html.py 2013-08-13 17:17:36.000000000 +0000 +++ python-django-1.6.11/tests/utils_tests/test_html.py 2015-03-18 23:50:46.000000000 +0000 @@ -80,10 +80,21 @@ ('a

b

c', 'abc'), ('de

f', 'def'), ('foobar', 'foobar'), + # caused infinite loop on Pythons not patched with + # http://bugs.python.org/issue20288 + ('&gotcha&#;<>', '&gotcha&#;<>'), ) for value, output in items: self.check_output(f, value, output) + # Some convoluted syntax for which parsing may differ between python versions + output = html.strip_tags('ript>test</script>') + self.assertNotIn('&h') + self.assertNotIn('