From fa7ecb6c810e6c15871c1a686786407f2f4d1c89 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 6 May 2017 09:29:37 -0400 Subject: [PATCH 001/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 2e9c36e7758d..05ec33500e5d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 1, 'final', 0) +VERSION = (1, 11, 2, 'alpha', 0) __version__ = get_version(VERSION) From 97f0e32f8e5d8c8d98e63468531602842945ddd2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 6 May 2017 09:42:25 -0400 Subject: [PATCH 002/389] [1.11.x] Added stub release notes for 1.11.2. Backport of b57dc9268bc635525cf143ca0b36276e04542332 from master --- docs/releases/1.11.2.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.2.txt diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt new file mode 100644 index 000000000000..084edc5e86fd --- /dev/null +++ b/docs/releases/1.11.2.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.2 release notes +=========================== + +*Under development* + +Django 1.11.2 fixes several bugs in 1.11.1. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 64fa9f70ea1e..18158a86ecf0 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.2 1.11.1 1.11 From 4bdc832a7598c1811c5a50fb39c912c02ce87a39 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 6 May 2017 14:45:10 -0400 Subject: [PATCH 003/389] [1.11.x] Fixed #28095 -- Doc'd Widget.build_attrs() signature change in Django 1.11. Backport of e86f4786a7f39b0ed833c0699addf0c27811d864 from master --- docs/releases/1.11.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 94998d509532..03a0a28dea09 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -739,6 +739,9 @@ Miscellaneous * Support for IPython < 1.0 is removed from the ``shell`` command. +* The signature of private API ``Widget.build_attrs()`` changed from + ``extra_attrs=None, **kwargs`` to ``base_attrs, extra_attrs=None``. + .. _deprecated-features-1.11: Features deprecated in 1.11 From 7fdbd253365f934d2910de3bfd756616c306bc14 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 8 May 2017 10:59:16 -0400 Subject: [PATCH 004/389] [1.11.x] Fixed #28181 -- Added detection for GDAL 2.1 and 2.0. Follow up to: ffdf507ec0821f0520e315c2e8a6cf231ea3fd5a (adding GDAL 2.0 support) ebaa2fef27644430e2b9dfa912f02e39582bfc05 (confirming GDAL 2.1 support) 8c7778884bced3e756ce139da781b0c8c8b2395c (removing GDAL 1.8, 1.7 support) Backport of a404f75f92971634c76330f3742261d33ccecca1 from master --- django/contrib/gis/gdal/libgdal.py | 4 ++-- docs/releases/1.11.2.txt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index a987b387b20a..4ce0c42a06d7 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -23,10 +23,10 @@ lib_names = None elif os.name == 'nt': # Windows NT shared libraries - lib_names = [str('gdal111'), str('gdal110'), str('gdal19'), str('gdal18'), str('gdal17')] + lib_names = [str('gdal21'), str('gdal20'), str('gdal111'), str('gdal110'), str('gdal19')] elif os.name == 'posix': # *NIX library names. - lib_names = ['gdal', 'GDAL', 'gdal1.11.0', 'gdal1.10.0', 'gdal1.9.0', 'gdal1.8.0', 'gdal1.7.0'] + lib_names = ['gdal', 'GDAL', 'gdal2.1.0', 'gdal2.0.0', 'gdal1.11.0', 'gdal1.10.0', 'gdal1.9.0'] else: raise GDALException('Unsupported OS "%s"' % os.name) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 084edc5e86fd..12c03558ccd9 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -9,4 +9,5 @@ Django 1.11.2 fixes several bugs in 1.11.1. Bugfixes ======== -* ... +* Added detection for GDAL 2.1 and 2.0, and removed detection for unsupported + versions 1.7 and 1.8 (:ticket:`28181`). From b3e56da05061c75755475651fd57a10314e952a7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 8 May 2017 09:42:06 -0400 Subject: [PATCH 005/389] [1.11.x] Fixed #28178 -- Changed contrib.gis to raise ImproperlyConfigured if gdal isn't installed. Backport of c2975910a5bc2729c2de01eb5b84777fa59551e1 from master --- django/contrib/gis/gdal/libgdal.py | 9 +++++---- docs/releases/1.11.2.txt | 4 ++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 4ce0c42a06d7..232865f4931a 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -28,7 +28,7 @@ # *NIX library names. lib_names = ['gdal', 'GDAL', 'gdal2.1.0', 'gdal2.0.0', 'gdal1.11.0', 'gdal1.10.0', 'gdal1.9.0'] else: - raise GDALException('Unsupported OS "%s"' % os.name) + raise ImproperlyConfigured('GDAL is unsupported on OS "%s".' % os.name) # Using the ctypes `find_library` utility to find the # path to the GDAL library from the list of library names. @@ -39,9 +39,10 @@ break if lib_path is None: - raise GDALException( - 'Could not find the GDAL library (tried "%s"). Try setting ' - 'GDAL_LIBRARY_PATH in your settings.' % '", "'.join(lib_names) + raise ImproperlyConfigured( + 'Could not find the GDAL library (tried "%s"). Is GDAL installed? ' + 'If it is, try setting GDAL_LIBRARY_PATH in your settings.' + % '", "'.join(lib_names) ) # This loads the GDAL/OGR C library diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 12c03558ccd9..f4d13980945a 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -11,3 +11,7 @@ Bugfixes * Added detection for GDAL 2.1 and 2.0, and removed detection for unsupported versions 1.7 and 1.8 (:ticket:`28181`). + +* Changed ``contrib.gis`` to raise ``ImproperlyConfigured`` rather than + ``GDALException`` if ``gdal`` isn't installed, to allow third-party apps to + catch that exception (:ticket:`28178`). From ab0df82b408c8c92c694a1b0e1ec2eccc989f6dd Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Tue, 9 May 2017 12:47:35 +0200 Subject: [PATCH 006/389] [1.11.x] Pass type to sql_alter_column_* where it was missing. This is a followup to 2b3a9414570af623853ca0f819c7d77d0511f22c Backport of 837259a63ff03fbc0ca2bc2999a6fbc8c6c40bcc from master. --- django/db/backends/base/schema.py | 1 + 1 file changed, 1 insertion(+) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index ecea348cbf25..e36c46e5930a 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -434,6 +434,7 @@ def add_field(self, model, field): "table": self.quote_name(model._meta.db_table), "changes": self.sql_alter_column_no_default % { "column": self.quote_name(field.column), + "type": db_params['type'], } } self.execute(sql) From 38e116906703540379fd7e72d5404e7a9b123b1d Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Tue, 9 May 2017 00:55:54 +0200 Subject: [PATCH 007/389] [1.11.x] Refs #22397 -- Removed model in test cleanup The test was failing when using --keepdb due to a pre-existing PonyStables model. Thanks Florian Apolloner for the report Backport of 8e352876c337332b45a72da8bbccad2830c7b1e0 from master. --- tests/migrations/test_operations.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index dd8aff4ca870..3b2bd8275c13 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1107,7 +1107,7 @@ def test_remove_field_m2m_with_through(self): ]) self.assertTableExists("test_rmflmmwt_ponystables") - operations = [migrations.RemoveField("Pony", "stables")] + operations = [migrations.RemoveField("Pony", "stables"), migrations.DeleteModel("PonyStables")] self.apply_operations("test_rmflmmwt", project_state, operations=operations) def test_remove_field(self): From 4841fafb4421a215e854f68daebde2d4cfccfc3f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 9 May 2017 10:04:58 -0400 Subject: [PATCH 008/389] [1.11.x] Refs #28160 -- Skipped a GeoManager test if not using a GIS database backend. The test errors if GDAL isn't installed. --- tests/managers_regress/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index 1ff34dede345..c9e9f07188f6 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -5,7 +5,7 @@ from django.db import models from django.db.utils import DatabaseError from django.template import Context, Template -from django.test import TestCase, override_settings +from django.test import TestCase, override_settings, skipUnlessDBFeature from django.test.utils import isolate_apps from django.utils.deprecation import RemovedInDjango20Warning from django.utils.encoding import force_text @@ -305,6 +305,7 @@ class TestModel(AbstractModel): @isolate_apps('managers_regress') class TestManagerDeprecations(TestCase): + @skipUnlessDBFeature('gis_enabled') def test_use_for_related_fields_on_geomanager(self): from django.contrib.gis.db.models import GeoManager From 03d0c05fdfd3de5f36bf54470ed03018295497c7 Mon Sep 17 00:00:00 2001 From: UmanShahzad Date: Sat, 29 Apr 2017 19:10:43 -0400 Subject: [PATCH 009/389] [1.11.x] Fixed #28142 -- Fixed is_safe_url() crash on invalid IPv6 URLs. Backport of 856072dd4a3e479aa09b0ab6b498ff599ca2a809 from master --- django/utils/http.py | 5 ++++- docs/releases/1.11.2.txt | 3 +++ tests/utils_tests/test_http.py | 2 ++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/django/utils/http.py b/django/utils/http.py index f47a09a2dcac..1fbc11b6fbac 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -387,7 +387,10 @@ def _is_safe_url(url, allowed_hosts, require_https=False): # urlparse is not so flexible. Treat any url with three slashes as unsafe. if url.startswith('///'): return False - url_info = _urlparse(url) + try: + url_info = _urlparse(url) + except ValueError: # e.g. invalid IPv6 addresses + return False # Forbid URLs like http:///example.com - with a scheme, but without a hostname. # In that URL, example.com is not the hostname but, a path component. However, # Chrome will still consider example.com to be the hostname, so we must not diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index f4d13980945a..fd6b7083e9ef 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -15,3 +15,6 @@ Bugfixes * Changed ``contrib.gis`` to raise ``ImproperlyConfigured`` rather than ``GDALException`` if ``gdal`` isn't installed, to allow third-party apps to catch that exception (:ticket:`28178`). + +* Fixed ``django.utils.http.is_safe_url()`` crash on invalid IPv6 URLs + (:ticket:`28142`). diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index f6f711c72f4b..b435e33e44d1 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -109,6 +109,8 @@ def test_is_safe_url(self): 'http:999999999', 'ftp:9999999999', '\n', + 'http://[2001:cdba:0000:0000:0000:0000:3257:9652/', + 'http://2001:cdba:0000:0000:0000:0000:3257:9652]/', ) for bad_url in bad_urls: with ignore_warnings(category=RemovedInDjango21Warning): From cf81add99745f230f858652b7546da779fc39cd2 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 10 May 2017 15:47:20 +0200 Subject: [PATCH 010/389] [1.11.x] Fixed db backend discovery in admin_scripts tests. Not all backends have dots in them. Backport of 60f51290766dae16abfb60459940de51fa5c5b2f from master --- tests/admin_scripts/tests.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/admin_scripts/tests.py b/tests/admin_scripts/tests.py index af3ada5bcc4c..d647c821aae0 100644 --- a/tests/admin_scripts/tests.py +++ b/tests/admin_scripts/tests.py @@ -120,11 +120,10 @@ def _ext_backend_paths(self): Returns the paths for any external backend packages. """ paths = [] - first_package_re = re.compile(r'(^[^\.]+)\.') for backend in settings.DATABASES.values(): - result = first_package_re.findall(backend['ENGINE']) - if result and result != ['django']: - backend_pkg = __import__(result[0]) + package = backend['ENGINE'].split('.')[0] + if package != 'django': + backend_pkg = __import__(package) backend_dir = os.path.dirname(backend_pkg.__file__) paths.append(os.path.dirname(backend_dir)) return paths From 643413f654d52ca4d7bf6d9402ff9ca24d2ea635 Mon Sep 17 00:00:00 2001 From: Riccardo Magliocchetti Date: Sat, 6 May 2017 19:14:15 +0200 Subject: [PATCH 011/389] [1.11.x] Fixed #28148 -- Doc'd ImageField name validation concerns with the test client. Backport of bdf192c59357a0d8117f6f34c94fb32a51e7a774 from master --- docs/releases/1.11.txt | 6 ++++++ docs/topics/testing/tools.txt | 10 +++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 03a0a28dea09..e43d53fdf823 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -742,6 +742,12 @@ Miscellaneous * The signature of private API ``Widget.build_attrs()`` changed from ``extra_attrs=None, **kwargs`` to ``base_attrs, extra_attrs=None``. +* File-like objects (e.g., :class:`~io.StringIO` and :class:`~io.BytesIO`) + uploaded to an :class:`~django.db.models.ImageField` using the test client + now require a ``name`` attribute with a value that passes the + :data:`~django.core.validators.validate_image_file_extension` validator. + See the note in :meth:`.Client.post`. + .. _deprecated-features-1.11: Features deprecated in 1.11 diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 9b2e76cadc27..1a2e9a847a2d 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -235,7 +235,15 @@ Use the ``django.test.Client`` class to make requests. file-processing code expects.) You may also provide any file-like object (e.g., :class:`~io.StringIO` or - :class:`~io.BytesIO`) as a file handle. + :class:`~io.BytesIO`) as a file handle. If you're uploading to an + :class:`~django.db.models.ImageField`, the object needs a ``name`` + attribute that passes the + :data:`~django.core.validators.validate_image_file_extension` validator. + For example:: + + >>> from io import BytesIO + >>> img = BytesIO(b'mybinarydata') + >>> img.name = 'myimage.jpg' Note that if you wish to use the same file handle for multiple ``post()`` calls then you will need to manually reset the file From 74b0837bef6270a78f55878c488561f81a6339f7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 11 May 2017 21:04:52 -0400 Subject: [PATCH 012/389] [1.11.x] Fixed #28188 -- Fixed crash when pickling model fields. Regression in d2a26c1a90e837777dabdf3d67ceec4d2a70fb86. Thanks Adam Alton for the report and test, and Adam Johnson for suggesting the fix. Backport of a9874d48b1b9d91988b9f299726ec4f559fb2f75 from master --- django/db/models/fields/__init__.py | 6 +++++- docs/releases/1.11.2.txt | 2 ++ tests/model_fields/tests.py | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 0af100efd9dd..8d40c77345a7 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -514,7 +514,11 @@ def __reduce__(self): # instance. The code below will create a new empty instance of # class self.__class__, then update its dict with self.__dict__ # values - so, this is very close to normal pickle. - return _empty, (self.__class__,), self.__dict__ + state = self.__dict__.copy() + # The _get_default cached_property can't be pickled due to lambda + # usage. + state.pop('_get_default', None) + return _empty, (self.__class__,), state return _load_field, (self.model._meta.app_label, self.model._meta.object_name, self.name) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index fd6b7083e9ef..558b3e40c377 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -18,3 +18,5 @@ Bugfixes * Fixed ``django.utils.http.is_safe_url()`` crash on invalid IPv6 URLs (:ticket:`28142`). + +* Fixed regression causing pickling of model fields to crash (:ticket:`28188`). diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index a9c9794a3cdc..00f83381281a 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -1,3 +1,5 @@ +import pickle + from django import forms from django.db import models from django.test import SimpleTestCase, TestCase @@ -76,6 +78,13 @@ def test_field_ordering(self): self.assertIsNotNone(f1) self.assertNotIn(f2, (None, 1, '')) + def test_field_instance_is_picklable(self): + """Field instances can be pickled.""" + field = models.Field(max_length=100, default='a string') + # Must be picklable with this cached property populated (#28188). + field._get_default + pickle.dumps(field) + class ChoicesTests(SimpleTestCase): From 28fe20b5aff84553b70e495e211253ab25394280 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 12 May 2017 09:22:59 -0400 Subject: [PATCH 013/389] [1.11.x] Fixed widgets module path in docs/ref/contrib/gis/forms-api.txt. Backport of b0d0dbe2806567c6cf1b9f3f41976bf1abe95065 from master --- docs/ref/contrib/gis/forms-api.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt index 30279fe44d20..51dfa4ad1829 100644 --- a/docs/ref/contrib/gis/forms-api.txt +++ b/docs/ref/contrib/gis/forms-api.txt @@ -82,7 +82,7 @@ Form field classes Form widgets ============ -.. module:: django.contrib.gis.widgets +.. module:: django.contrib.gis.forms.widgets :synopsis: GeoDjango widgets API. GeoDjango form widgets allow you to display and edit geographic data on a From 84f6098aaf8d9cca521f7776134564b7f5ed704a Mon Sep 17 00:00:00 2001 From: Danilo Bargen Date: Fri, 12 May 2017 17:24:53 +0200 Subject: [PATCH 014/389] [1.11.x] Documented OSMWidget.default_lat/lon. Backport of 680968b9e42d63f9a3ee929ebd6afdef06999d24 from master --- docs/ref/contrib/gis/forms-api.txt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt index 51dfa4ad1829..32cdd51dade9 100644 --- a/docs/ref/contrib/gis/forms-api.txt +++ b/docs/ref/contrib/gis/forms-api.txt @@ -173,7 +173,17 @@ Widget classes .. class:: OSMWidget This widget uses an OpenStreetMap base layer to display geographic objects - on. ``template_name`` is ``gis/openlayers-osm.html``. + on. Attributes are: + + .. attribute:: template_name + + ``gis/openlayers-osm.html`` + + .. attribute:: default_lat + .. attribute:: default_lon + + The default center latitude and longitude are ``47`` and ``5``, + respectively, which is a location in eastern France. The :class:`OpenLayersWidget` note about JavaScript file hosting above also applies here. See also this `FAQ answer`_ about ``https`` access to map From d945b7e42a8d45cb13b1bd0420b420cf563482f1 Mon Sep 17 00:00:00 2001 From: Tamas Szabo Date: Mon, 15 May 2017 06:22:58 +0800 Subject: [PATCH 015/389] [1.11.x] Fixed #28207 -- Fixed contrib.auth.authenticate() if multiple auth backends don't accept a request. Backport of 3008f30f194af386c354416be4c483f0f6b15f33 from master --- django/contrib/auth/__init__.py | 65 ++++++++++--------- docs/releases/1.11.2.txt | 3 + .../test_auth_backends_deprecation.py | 18 +++++ 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 426bd6982bef..94153d243db3 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -66,38 +66,8 @@ def authenticate(request=None, **credentials): If the given credentials are valid, return a User object. """ for backend, backend_path in _get_backends(return_tuples=True): - args = (request,) - # Does the backend accept a request argument? try: - inspect.getcallargs(backend.authenticate, request, **credentials) - except TypeError: - args = () - # Does the backend accept a request keyword argument? - try: - inspect.getcallargs(backend.authenticate, request=request, **credentials) - except TypeError: - # Does the backend accept credentials without request? - try: - inspect.getcallargs(backend.authenticate, **credentials) - except TypeError: - # This backend doesn't accept these credentials as arguments. Try the next one. - continue - else: - warnings.warn( - "Update %s.authenticate() to accept a positional " - "`request` argument." % backend_path, - RemovedInDjango21Warning - ) - else: - credentials['request'] = request - warnings.warn( - "In %s.authenticate(), move the `request` keyword argument " - "to the first positional argument." % backend_path, - RemovedInDjango21Warning - ) - - try: - user = backend.authenticate(*args, **credentials) + user = _authenticate_with_backend(backend, backend_path, request, **credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. break @@ -111,6 +81,39 @@ def authenticate(request=None, **credentials): user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request) +def _authenticate_with_backend(backend, backend_path, request, **credentials): + args = (request,) + # Does the backend accept a request argument? + try: + inspect.getcallargs(backend.authenticate, request, **credentials) + except TypeError: + args = () + # Does the backend accept a request keyword argument? + try: + inspect.getcallargs(backend.authenticate, request=request, **credentials) + except TypeError: + # Does the backend accept credentials without request? + try: + inspect.getcallargs(backend.authenticate, **credentials) + except TypeError: + # This backend doesn't accept these credentials as arguments. Try the next one. + return None + else: + warnings.warn( + "Update %s.authenticate() to accept a positional " + "`request` argument." % backend_path, + RemovedInDjango21Warning + ) + else: + credentials['request'] = request + warnings.warn( + "In %s.authenticate(), move the `request` keyword argument " + "to the first positional argument." % backend_path, + RemovedInDjango21Warning + ) + return backend.authenticate(*args, **credentials) + + def login(request, user, backend=None): """ Persist a user id and a backend in the request. This way a user doesn't diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 558b3e40c377..6831f60cefb8 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -20,3 +20,6 @@ Bugfixes (:ticket:`28142`). * Fixed regression causing pickling of model fields to crash (:ticket:`28188`). + +* Fixed ``django.contrib.auth.authenticate()`` when multiple authentication + backends don't accept a positional ``request`` argument (:ticket:`28207`). diff --git a/tests/auth_tests/test_auth_backends_deprecation.py b/tests/auth_tests/test_auth_backends_deprecation.py index 0d45fee7a42a..7ee53a0bc6c6 100644 --- a/tests/auth_tests/test_auth_backends_deprecation.py +++ b/tests/auth_tests/test_auth_backends_deprecation.py @@ -50,3 +50,21 @@ def test_request_keyword_arg_deprecation_warning(self): "In %s.authenticate(), move the `request` keyword argument to the " "first positional argument." % self.request_not_positional_backend ) + + @override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_backend, no_request_backend]) + def test_both_types_of_deprecation_warning(self): + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + authenticate(mock_request, username='username', password='pass') + + self.assertEqual(len(warns), 2) + self.assertEqual( + str(warns[0].message), + "In %s.authenticate(), move the `request` keyword argument to the " + "first positional argument." % self.request_not_positional_backend + ) + self.assertEqual( + str(warns[1].message), + "Update %s.authenticate() to accept a positional `request` " + "argument." % self.no_request_backend + ) From 566726ff964b004f2a6adecc1e51197bc34d78d2 Mon Sep 17 00:00:00 2001 From: Tim Schneider Date: Fri, 12 May 2017 18:01:30 +0200 Subject: [PATCH 016/389] [1.11.x] Fixed #28197 -- Fixed introspection of index field ordering on PostgreSQL. Backport of 3a5299c19cd5a38f7fa0f45ed2df7b10f0c9cf5d from master --- django/db/backends/postgresql/introspection.py | 10 +++++++--- docs/releases/1.11.2.txt | 2 ++ tests/introspection/models.py | 1 + tests/introspection/tests.py | 7 ++++++- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py index c75f06f873a1..032a777aa535 100644 --- a/django/db/backends/postgresql/introspection.py +++ b/django/db/backends/postgresql/introspection.py @@ -196,13 +196,17 @@ def get_constraints(self, cursor, table_name): "options": options, } # Now get indexes + # The row_number() function for ordering the index fields can be + # replaced by WITH ORDINALITY in the unnest() functions when support + # for PostgreSQL 9.3 is dropped. cursor.execute(""" SELECT - indexname, array_agg(attname), indisunique, indisprimary, - array_agg(ordering), amname, exprdef, s2.attoptions + indexname, array_agg(attname ORDER BY rnum), indisunique, indisprimary, + array_agg(ordering ORDER BY rnum), amname, exprdef, s2.attoptions FROM ( SELECT - c2.relname as indexname, idx.*, attr.attname, am.amname, + row_number() OVER () as rnum, c2.relname as indexname, + idx.*, attr.attname, am.amname, CASE WHEN idx.indexprs IS NOT NULL THEN pg_get_indexdef(idx.indexrelid) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 6831f60cefb8..8d82581966ae 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -23,3 +23,5 @@ Bugfixes * Fixed ``django.contrib.auth.authenticate()`` when multiple authentication backends don't accept a positional ``request`` argument (:ticket:`28207`). + +* Fixed introspection of index field ordering on PostgreSQL (:ticket:`28197`). diff --git a/tests/introspection/models.py b/tests/introspection/models.py index b8c7a83b9f90..022895df5232 100644 --- a/tests/introspection/models.py +++ b/tests/introspection/models.py @@ -54,6 +54,7 @@ class Meta: ordering = ('headline',) index_together = [ ["headline", "pub_date"], + ['headline', 'response_to', 'pub_date', 'reporter'], ] diff --git a/tests/introspection/tests.py b/tests/introspection/tests.py index 18283f6fbb2d..c4a2ef56fe51 100644 --- a/tests/introspection/tests.py +++ b/tests/introspection/tests.py @@ -191,10 +191,14 @@ def test_get_constraints_index_types(self): with connection.cursor() as cursor: constraints = connection.introspection.get_constraints(cursor, Article._meta.db_table) index = {} + index2 = {} for key, val in constraints.items(): if val['columns'] == ['headline', 'pub_date']: index = val + if val['columns'] == ['headline', 'response_to_id', 'pub_date', 'reporter_id']: + index2 = val self.assertEqual(index['type'], Index.suffix) + self.assertEqual(index2['type'], Index.suffix) @skipUnlessDBFeature('supports_index_column_ordering') def test_get_constraints_indexes_orders(self): @@ -208,13 +212,14 @@ def test_get_constraints_indexes_orders(self): ['reporter_id'], ['headline', 'pub_date'], ['response_to_id'], + ['headline', 'response_to_id', 'pub_date', 'reporter_id'], ] for key, val in constraints.items(): if val['index'] and not (val['primary_key'] or val['unique']): self.assertIn(val['columns'], expected_columns) self.assertEqual(val['orders'], ['ASC'] * len(val['columns'])) indexes_verified += 1 - self.assertEqual(indexes_verified, 3) + self.assertEqual(indexes_verified, 4) def datatype(dbtype, description): From f2b8fa1763116a9b8fbe7791a4b009c95cf8c0b1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 19 May 2017 12:47:10 -0400 Subject: [PATCH 017/389] [1.11.x] Fixed #28210 -- Fixed Model._state.adding on MTI parent model after saving child model. Regression in 38575b007a722d6af510ea46d46393a4cda9ca29. Backport of 59ab1b2683b6c090dc409d9eb8303aadbd590c04 from master --- django/db/models/fields/related_descriptors.py | 1 + docs/releases/1.11.2.txt | 4 ++++ tests/model_inheritance_regress/tests.py | 3 ++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index 7cb6448bd7c6..d51f73d85500 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -289,6 +289,7 @@ def get_object(self, instance): if not any(field in fields for field in deferred): kwargs = {field: getattr(instance, field) for field in fields} obj = rel_model(**kwargs) + obj._state.adding = instance._state.adding obj._state.db = instance._state.db return obj return super(ForwardOneToOneDescriptor, self).get_object(instance) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 8d82581966ae..a15d4272ec21 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -25,3 +25,7 @@ Bugfixes backends don't accept a positional ``request`` argument (:ticket:`28207`). * Fixed introspection of index field ordering on PostgreSQL (:ticket:`28197`). + +* Fixed a regression where ``Model._state.adding`` wasn't set correctly on + multi-table inheritance parent models after saving a child model + (:ticket:`28210`). diff --git a/tests/model_inheritance_regress/tests.py b/tests/model_inheritance_regress/tests.py index c91d7cb85350..ca583e7d20de 100644 --- a/tests/model_inheritance_regress/tests.py +++ b/tests/model_inheritance_regress/tests.py @@ -480,8 +480,9 @@ def test_filter_with_parent_fk(self): # The mismatch between Restaurant and Place is intentional (#28175). self.assertSequenceEqual(Supplier.objects.filter(restaurant__in=Place.objects.all()), [s]) - def test_ptr_accessor_assigns_db(self): + def test_ptr_accessor_assigns_state(self): r = Restaurant.objects.create() + self.assertIs(r.place_ptr._state.adding, False) self.assertEqual(r.place_ptr._state.db, 'default') def test_related_filtering_query_efficiency_ticket_15844(self): From ec24108008b05b582c651e339cb55def6a655b8b Mon Sep 17 00:00:00 2001 From: Michiel Beijen Date: Sat, 20 May 2017 15:32:35 +0200 Subject: [PATCH 018/389] [1.11.x] Pointed Dive into Python links to python3 site Backport of 48028c6f9a22742ff05da79822f5e3385d1b4c4c from master --- AUTHORS | 3 +-- docs/intro/contributing.txt | 2 +- docs/ref/django-admin.txt | 2 +- docs/topics/http/urls.txt | 2 +- docs/topics/settings.txt | 2 +- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 658c84e809a6..ac8837505a20 100644 --- a/AUTHORS +++ b/AUTHORS @@ -831,7 +831,6 @@ A big THANK YOU goes to: Ian Bicking for convincing Adrian to ditch code generation. - Mark Pilgrim for "Dive Into Python" (http://www.diveintopython.net, - http://www.diveintopython3.net). + Mark Pilgrim for "Dive Into Python" (http://www.diveintopython3.net). Guido van Rossum for creating Python. diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 703847eebbb6..2526faf9981d 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -396,7 +396,7 @@ that passing a ``prefix`` parameter when creating an instance still works too. * After reading those, if you want something a little meatier to sink your teeth into, there's always the Python :mod:`unittest` documentation. -__ http://www.diveintopython.net/unit_testing/index.html +__ http://www.diveintopython3.net/unit-testing.html Running your new test --------------------- diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 7209025263ed..800b80ee6938 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1591,7 +1591,7 @@ Example usage:: django-admin migrate --pythonpath='/home/djangoprojects/myproject' -.. _import search path: http://www.diveintopython.net/getting_to_know_python/everything_is_an_object.html +.. _import search path: http://www.diveintopython3.net/your-first-python-program.html#importsearchpath .. django-admin-option:: --settings SETTINGS diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 6e891e72f305..d5bae7f07270 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -115,7 +115,7 @@ Example requests: * ``/articles/2003/03/03/`` would match the final pattern. Django would call the function ``views.article_detail(request, '2003', '03', '03')``. -.. _Dive Into Python's explanation: http://www.diveintopython.net/regular_expressions/street_addresses.html#re.matching.2.3 +.. _Dive Into Python's explanation: http://www.diveintopython3.net/regular-expressions.html#streetaddresses Named groups ============ diff --git a/docs/topics/settings.txt b/docs/topics/settings.txt index 25b3f94fe387..668f90afe2cc 100644 --- a/docs/topics/settings.txt +++ b/docs/topics/settings.txt @@ -46,7 +46,7 @@ The value of ``DJANGO_SETTINGS_MODULE`` should be in Python path syntax, e.g. ``mysite.settings``. Note that the settings module should be on the Python `import search path`_. -.. _import search path: http://www.diveintopython.net/getting_to_know_python/everything_is_an_object.html +.. _import search path: http://www.diveintopython3.net/your-first-python-program.html#importsearchpath The ``django-admin`` utility ---------------------------- From bd6a3546d4c0552ca18853f4843f799acfe3e15b Mon Sep 17 00:00:00 2001 From: Markus Amalthea Magnuson Date: Mon, 22 May 2017 18:06:49 +0200 Subject: [PATCH 019/389] [1.11.x] Made docs/topics/migrations.txt use single quotes consistently. Backport of 266b24316841f878c129e6dbb026f6c3edcdb54f from master --- docs/topics/migrations.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index db510010e2f6..6d34b9061e8e 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -205,11 +205,11 @@ A basic migration file looks like this:: class Migration(migrations.Migration): - dependencies = [("migrations", "0001_initial")] + dependencies = [('migrations', '0001_initial')] operations = [ - migrations.DeleteModel("Tribble"), - migrations.AddField("Author", "rating", models.IntegerField(default=0)), + migrations.DeleteModel('Tribble'), + migrations.AddField('Author', 'rating', models.IntegerField(default=0)), ] What Django looks for when it loads a migration file (as a Python module) is @@ -497,9 +497,9 @@ need to do is use the historical model and iterate over the rows:: def combine_names(apps, schema_editor): # We can't import the Person model directly as it may be a newer # version than this migration expects. We use the historical version. - Person = apps.get_model("yourappname", "Person") + Person = apps.get_model('yourappname', 'Person') for person in Person.objects.all(): - person.name = "%s %s" % (person.first_name, person.last_name) + person.name = '%s %s' % (person.first_name, person.last_name) person.save() class Migration(migrations.Migration): From a810f4aa04d205afba39903a141422cdbc945667 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 22 May 2017 17:03:18 +0200 Subject: [PATCH 020/389] [1.11.x] Refs #28207 -- Fixed contrib.auth.authenticate() if 'backend' is in the credentials. Regression in 3008f30f194af386c354416be4c483f0f6b15f33. Backport of a3ba2662cdaa36183fdfb8a26dfa157e26fca76a from master --- django/contrib/auth/__init__.py | 5 +++-- .../test_auth_backends_deprecation.py | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 94153d243db3..a0a2cb7aff67 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -67,7 +67,7 @@ def authenticate(request=None, **credentials): """ for backend, backend_path in _get_backends(return_tuples=True): try: - user = _authenticate_with_backend(backend, backend_path, request, **credentials) + user = _authenticate_with_backend(backend, backend_path, request, credentials) except PermissionDenied: # This backend says to stop in our tracks - this user should not be allowed in at all. break @@ -81,13 +81,14 @@ def authenticate(request=None, **credentials): user_login_failed.send(sender=__name__, credentials=_clean_credentials(credentials), request=request) -def _authenticate_with_backend(backend, backend_path, request, **credentials): +def _authenticate_with_backend(backend, backend_path, request, credentials): args = (request,) # Does the backend accept a request argument? try: inspect.getcallargs(backend.authenticate, request, **credentials) except TypeError: args = () + credentials.pop('request', None) # Does the backend accept a request keyword argument? try: inspect.getcallargs(backend.authenticate, request=request, **credentials) diff --git a/tests/auth_tests/test_auth_backends_deprecation.py b/tests/auth_tests/test_auth_backends_deprecation.py index 7ee53a0bc6c6..675e185e9f0d 100644 --- a/tests/auth_tests/test_auth_backends_deprecation.py +++ b/tests/auth_tests/test_auth_backends_deprecation.py @@ -4,6 +4,7 @@ from django.test import SimpleTestCase, override_settings mock_request = object() +mock_backend = object() class NoRequestBackend(object): @@ -19,6 +20,14 @@ def authenticate(self, username=None, password=None, request=None): assert request is mock_request +class RequestNotPositionArgWithUsedKwargBackend: + def authenticate(self, username=None, password=None, request=None, backend=None): + assert username == 'username' + assert password == 'pass' + assert request is mock_request + assert backend is mock_backend + + class AcceptsRequestBackendTest(SimpleTestCase): """ A deprecation warning is shown for backends that have an authenticate() @@ -26,6 +35,7 @@ class AcceptsRequestBackendTest(SimpleTestCase): """ no_request_backend = '%s.NoRequestBackend' % __name__ request_not_positional_backend = '%s.RequestNotPositionArgBackend' % __name__ + request_not_positional_with_used_kwarg_backend = '%s.RequestNotPositionArgWithUsedKwargBackend' % __name__ @override_settings(AUTHENTICATION_BACKENDS=[no_request_backend]) def test_no_request_deprecation_warning(self): @@ -68,3 +78,15 @@ def test_both_types_of_deprecation_warning(self): "Update %s.authenticate() to accept a positional `request` " "argument." % self.no_request_backend ) + + @override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_with_used_kwarg_backend]) + def test_handles_backend_in_kwargs(self): + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + authenticate(username='username', password='pass', request=mock_request, backend=mock_backend) + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "In %s.authenticate(), move the `request` keyword argument to the " + "first positional argument." % self.request_not_positional_with_used_kwarg_backend + ) From 65dfe579d08361e04b7b3c1895b10f84b69d5c59 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 20 May 2017 17:51:21 +0200 Subject: [PATCH 021/389] [1.11.x] Updated various links in docs to avoid redirects Thanks Tim Graham and Mariusz Felisiak for review and completion. --- docs/conf.py | 2 +- docs/faq/install.txt | 2 +- docs/howto/deployment/checklist.txt | 2 +- docs/howto/deployment/wsgi/modwsgi.txt | 2 +- docs/howto/deployment/wsgi/uwsgi.txt | 8 ++------ docs/howto/jython.txt | 2 +- docs/howto/static-files/deployment.txt | 6 +++--- docs/internals/contributing/localizing.txt | 2 +- .../contributing/writing-code/unit-tests.txt | 4 ++-- .../contributing/writing-code/working-with-git.txt | 4 ++-- .../contributing/writing-documentation.txt | 2 +- docs/internals/git.txt | 4 ++-- docs/internals/howto-release-django.txt | 2 +- docs/intro/contributing.txt | 6 +++--- docs/intro/install.txt | 2 +- docs/intro/reusable-apps.txt | 13 ++++++------- docs/misc/design-philosophies.txt | 6 +++--- docs/ref/clickjacking.txt | 4 ++-- docs/ref/contrib/gis/forms-api.txt | 4 ++-- docs/ref/contrib/gis/geos.txt | 2 +- docs/ref/contrib/gis/install/index.txt | 6 +++--- docs/ref/contrib/gis/install/spatialite.txt | 2 +- docs/ref/contrib/postgres/fields.txt | 2 +- docs/ref/contrib/sitemaps.txt | 8 ++++---- docs/ref/csrf.txt | 4 ++-- docs/ref/databases.txt | 4 ++-- docs/ref/django-admin.txt | 2 +- docs/ref/middleware.txt | 11 ++++------- docs/ref/utils.txt | 2 +- docs/ref/validators.txt | 2 +- docs/topics/db/optimization.txt | 2 +- docs/topics/db/queries.txt | 2 +- docs/topics/http/sessions.txt | 2 +- docs/topics/install.txt | 8 ++++---- docs/topics/performance.txt | 8 ++++---- docs/topics/templates.txt | 2 +- docs/topics/testing/advanced.txt | 2 +- 37 files changed, 70 insertions(+), 78 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index f1e1457a799b..a7acfe6b69ff 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -375,5 +375,5 @@ def django_release(): # epub_use_index = True # -- custom extension options -------------------------------------------------- -cve_url = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId=%s' +cve_url = 'https://nvd.nist.gov/view/vuln/detail?vulnId=%s' ticket_url = 'https://code.djangoproject.com/ticket/%s' diff --git a/docs/faq/install.txt b/docs/faq/install.txt index ea27b11db802..980f0182ca63 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -36,7 +36,7 @@ PostgreSQL fans, and MySQL_, `SQLite 3`_, and Oracle_ are also supported. .. _PostgreSQL: https://www.postgresql.org/ .. _MySQL: https://www.mysql.com/ .. _`SQLite 3`: https://www.sqlite.org/ -.. _Oracle: http://www.oracle.com/ +.. _Oracle: https://www.oracle.com/ .. _faq-python-version-support: diff --git a/docs/howto/deployment/checklist.txt b/docs/howto/deployment/checklist.txt index 8d7c27b04f8f..d1a8fda70d38 100644 --- a/docs/howto/deployment/checklist.txt +++ b/docs/howto/deployment/checklist.txt @@ -234,7 +234,7 @@ See :doc:`/howto/error-reporting` for details on error reporting by email. Consider using an error monitoring system such as Sentry_ before your inbox is flooded by reports. Sentry can also aggregate logs. - .. _Sentry: https://docs.getsentry.com/ + .. _Sentry: https://docs.sentry.io/ Customize the default error views --------------------------------- diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index 6647e624d1ea..b6e0fd154b26 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -179,7 +179,7 @@ If you are using a version of Apache older than 2.4, replace ``Require all granted`` with ``Allow from all`` and also add the line ``Order deny,allow`` above it. -.. _Nginx: http://wiki.nginx.org/Main +.. _Nginx: https://nginx.org/en/ .. _Apache: https://httpd.apache.org/ .. More details on configuring a mod_wsgi site to serve static files can be found diff --git a/docs/howto/deployment/wsgi/uwsgi.txt b/docs/howto/deployment/wsgi/uwsgi.txt index e21adbb9902a..a02c12f26c34 100644 --- a/docs/howto/deployment/wsgi/uwsgi.txt +++ b/docs/howto/deployment/wsgi/uwsgi.txt @@ -7,7 +7,7 @@ How to use Django with uWSGI uWSGI_ is a fast, self-healing and developer/sysadmin-friendly application container server coded in pure C. -.. _uWSGI: https://projects.unbit.it/uwsgi/ +.. _uWSGI: https://uwsgi-docs.readthedocs.io/ .. seealso:: @@ -39,18 +39,14 @@ uWSGI model uWSGI operates on a client-server model. Your Web server (e.g., nginx, Apache) communicates with a `django-uwsgi` "worker" process to serve dynamic content. -See uWSGI's `background documentation`_ for more detail. - -.. _background documentation: https://projects.unbit.it/uwsgi/wiki/Background Configuring and starting the uWSGI server for Django ---------------------------------------------------- uWSGI supports multiple ways to configure the process. See uWSGI's -`configuration documentation`_ and `examples`_. +`configuration documentation`_. .. _configuration documentation: https://uwsgi.readthedocs.io/en/latest/Configuration.html -.. _examples: https://projects.unbit.it/uwsgi/wiki/Example Here's an example command to start a uWSGI server:: diff --git a/docs/howto/jython.txt b/docs/howto/jython.txt index 607bbbdd1b5e..cdb9b06caf30 100644 --- a/docs/howto/jython.txt +++ b/docs/howto/jython.txt @@ -27,7 +27,7 @@ such as `Apache Tomcat`_. Full JavaEE applications servers such as `GlassFish`_ or `JBoss`_ are also OK, if you need the extra features they include. .. _`Apache Tomcat`: https://tomcat.apache.org/ -.. _GlassFish: https://glassfish.java.net/ +.. _GlassFish: https://javaee.github.io/glassfish/ .. _JBoss: https://www.jboss.org/ Installing Django diff --git a/docs/howto/static-files/deployment.txt b/docs/howto/static-files/deployment.txt index 6c116f816f99..94b7157a90d9 100644 --- a/docs/howto/static-files/deployment.txt +++ b/docs/howto/static-files/deployment.txt @@ -73,7 +73,7 @@ type of web server -- faster but less full-featured. Some common choices are: * Nginx_ * A stripped-down version of Apache_ -.. _Nginx: http://wiki.nginx.org/Main +.. _Nginx: https://nginx.org/en/ .. _Apache: https://httpd.apache.org/ Configuring these servers is out of scope of this document; check each @@ -142,8 +142,8 @@ as changing your :setting:`STATICFILES_STORAGE` setting. For details on how you'd write one of these backends, see :doc:`/howto/custom-file-storage`. There are 3rd party apps available that provide storage backends for many common file storage APIs. A good starting -point is the `overview at djangopackages.com -`_. +point is the `overview at djangopackages.org +`_. Learn more ========== diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt index e3df902549e6..f0d89f900194 100644 --- a/docs/internals/contributing/localizing.txt +++ b/docs/internals/contributing/localizing.txt @@ -70,7 +70,7 @@ Django source tree, as for any code change: .. _Transifex: https://www.transifex.com/ .. _Django project page: https://www.transifex.com/django/django/ -.. _Transifex User Guide: http://docs.transifex.com/ +.. _Transifex User Guide: https://docs.transifex.com/ .. _translating-documentation: diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 1bed50389827..f296d94b66a0 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -61,7 +61,7 @@ Having problems? See :ref:`troubleshooting-unit-tests` for some common issues. Running tests using ``tox`` --------------------------- -`Tox `_ is a tool for running tests in different +`Tox `_ is a tool for running tests in different virtual environments. Django includes a basic ``tox.ini`` that automates some checks that our build server performs on pull requests. To run the unit tests and other checks (such as :ref:`import sorting `, the @@ -278,7 +278,7 @@ associated tests will be skipped. .. _gettext: https://www.gnu.org/software/gettext/manual/gettext.html .. _selenium: https://pypi.python.org/pypi/selenium .. _sqlparse: https://pypi.python.org/pypi/sqlparse -.. _pip requirements files: https://pip.pypa.io/en/latest/user_guide.html#requirements-files +.. _pip requirements files: https://pip.pypa.io/en/latest/user_guide/#requirements-files Code coverage ------------- diff --git a/docs/internals/contributing/writing-code/working-with-git.txt b/docs/internals/contributing/writing-code/working-with-git.txt index 85ca6dfc8526..1ace4491890d 100644 --- a/docs/internals/contributing/writing-code/working-with-git.txt +++ b/docs/internals/contributing/writing-code/working-with-git.txt @@ -18,7 +18,7 @@ Installing Git ============== Django uses `Git`_ for its source control. You can `download -`_ Git, but it's often easier to install with +`_ Git, but it's often easier to install with your operating system's package manager. Django's `Git repository`_ is hosted on `GitHub`_, and it is recommended @@ -34,7 +34,7 @@ Note that ``user.name`` should be your real name, not your GitHub nick. GitHub should know the email you use in the ``user.email`` field, as this will be used to associate your commits with your GitHub account. -.. _Git: http://git-scm.com/ +.. _Git: https://git-scm.com/ .. _Git repository: https://github.com/django/django/ .. _GitHub: https://github.com/ diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index e19b5854fdf6..d58c318408f8 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -427,7 +427,7 @@ Before you commit your docs, it's a good idea to run the spelling checker. You'll need to install a couple packages first: * `pyenchant `_ (which requires - `enchant `_) + `enchant `_) * `sphinxcontrib-spelling `_ diff --git a/docs/internals/git.txt b/docs/internals/git.txt index 386a2a05a754..f71c1e921dbe 100644 --- a/docs/internals/git.txt +++ b/docs/internals/git.txt @@ -52,8 +52,8 @@ The source code for the `Djangoproject.com `_ website can be found at `github.com/django/djangoproject.com `_. -.. _Git: http://git-scm.com/ -.. _documentation: http://git-scm.com/documentation +.. _Git: https://git-scm.com/ +.. _documentation: https://git-scm.com/documentation .. _branches: https://github.com/django/django/branches .. _tags: https://github.com/django/django/tags diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 6a91069abce3..8670ab03c10e 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -155,7 +155,7 @@ OK, this is the fun part, where we actually push out a release! #. Check `Jenkins`__ is green for the version(s) you're putting out. You probably shouldn't issue a release until it's green. - __ http://djangoci.com + __ https://djangoci.com #. A release always begins from a release branch, so you should make sure you're on a stable branch and up-to-date. For example:: diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 2526faf9981d..9eb857e9aef1 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -71,7 +71,7 @@ probably got the answers. .. admonition:: Python 3 required! This tutorial assumes you are using Python 3. Get the latest version at - `Python's download page `_ or with your + `Python's download page `_ or with your operating system's package manager. .. admonition:: For Windows users @@ -105,7 +105,7 @@ have to download and install it, see `Git's download page`__. If you're not that familiar with Git, you can always find out more about its commands (once it's installed) by typing ``git help`` into the command line. -__ http://git-scm.com/download +__ https://git-scm.com/download Getting a copy of Django's development version ============================================== @@ -299,7 +299,7 @@ present in Django's official builds. If you click to view a particular build, you can view the "Configuration Matrix" which shows failures broken down by Python version and database backend. -__ http://djangoci.com +__ https://djangoci.com .. note:: diff --git a/docs/intro/install.txt b/docs/intro/install.txt index 7a338ee858a8..9861923b3700 100644 --- a/docs/intro/install.txt +++ b/docs/intro/install.txt @@ -16,7 +16,7 @@ database called SQLite_ so you won't need to set up a database just yet. .. _sqlite: https://sqlite.org/ -Get the latest version of Python at https://www.python.org/download/ or with +Get the latest version of Python at https://www.python.org/downloads/ or with your operating system's package manager. .. admonition:: Django on Jython diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 69017660477c..bace43cb3144 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -19,11 +19,10 @@ could save some of this repeated work? Reusability is the way of life in Python. `The Python Package Index (PyPI) `_ has a vast range of packages you can use in your own Python programs. Check out `Django Packages -`_ for existing reusable apps you could -incorporate in your project. Django itself is also just a Python package. This -means that you can take existing Python packages or Django apps and compose -them into your own web project. You only need to write the parts that make -your project unique. +`_ for existing reusable apps you could incorporate +in your project. Django itself is also just a Python package. This means that +you can take existing Python packages or Django apps and compose them into your +own web project. You only need to write the parts that make your project unique. Let's say you were starting a new project that needed a polls app like the one we've been working on. How do you make this app reusable? Luckily, you're well @@ -259,7 +258,7 @@ this. For a small app like polls, this process isn't too difficult. new package, ``django-polls-0.1.tar.gz``. For more information on packaging, see Python's `Tutorial on Packaging and -Distributing Projects `_. +Distributing Projects `_. Using your own package ====================== @@ -305,7 +304,7 @@ the world! If this wasn't just an example, you could now: * Post the package on a public repository, such as `the Python Package Index (PyPI)`_. `packaging.python.org `_ has `a good - tutorial `_ + tutorial `_ for doing this. Installing Python packages with virtualenv diff --git a/docs/misc/design-philosophies.txt b/docs/misc/design-philosophies.txt index 60e375718e60..8c0cac554ef0 100644 --- a/docs/misc/design-philosophies.txt +++ b/docs/misc/design-philosophies.txt @@ -27,7 +27,7 @@ template system a programmer uses. Although Django comes with a full stack for convenience, the pieces of the stack are independent of another wherever possible. -.. _`loose coupling and tight cohesion`: http://c2.com/cgi/wiki?CouplingAndCohesion +.. _`loose coupling and tight cohesion`: http://wiki.c2.com/?CouplingAndCohesion .. _less-code: @@ -66,7 +66,7 @@ as possible. The `discussion of DRY on the Portland Pattern Repository`__ - __ http://c2.com/cgi/wiki?DontRepeatYourself + __ http://wiki.c2.com/?DontRepeatYourself .. _explicit-is-better-than-implicit: @@ -110,7 +110,7 @@ it (its human-readable name, options like default ordering, etc.) are defined in the model class; all the information needed to understand a given model should be stored *in* the model. -.. _`Active Record`: http://www.martinfowler.com/eaaCatalog/activeRecord.html +.. _`Active Record`: https://www.martinfowler.com/eaaCatalog/activeRecord.html Database API ============ diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index 2f72e18d2f15..ade7069db585 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -35,7 +35,7 @@ load the resource in a frame if the request originated from the same site. If the header is set to ``DENY`` then the browser will block the resource from loading in a frame no matter which site made the request. -.. _X-Frame-Options: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options +.. _X-Frame-Options: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options Django provides a few simple ways to include this header in responses from your site: @@ -127,5 +127,5 @@ See also A `complete list`_ of browsers supporting ``X-Frame-Options``. -.. _complete list: https://developer.mozilla.org/en-US/docs/Web/HTTP/X-Frame-Options#Browser_compatibility +.. _complete list: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#Browser_compatibility .. _other clickjacking prevention techniques: https://en.wikipedia.org/wiki/Clickjacking#Prevention diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt index 32cdd51dade9..ccb4776f8c4f 100644 --- a/docs/ref/contrib/gis/forms-api.txt +++ b/docs/ref/contrib/gis/forms-api.txt @@ -7,10 +7,10 @@ GeoDjango Forms API GeoDjango provides some specialized form fields and widgets in order to visually display and edit geolocalized data on a map. By default, they use -`OpenLayers`_-powered maps, with a base WMS layer provided by `Metacarta`_. +`OpenLayers`_-powered maps, with a base WMS layer provided by `NASA`_. .. _OpenLayers: http://openlayers.org/ -.. _Metacarta: http://www.metacarta.com/ +.. _NASA: https://earthdata.nasa.gov/ Field arguments =============== diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 59a199492a77..63aa69729c09 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -18,7 +18,7 @@ and spatial operators. GEOS, now an OSGeo project, was initially developed and maintained by `Refractions Research`__ of Victoria, Canada. __ https://trac.osgeo.org/geos/ -__ http://sourceforge.net/projects/jts-topo-suite/ +__ https://sourceforge.net/projects/jts-topo-suite/ __ http://www.opengeospatial.org/standards/sfs __ http://www.refractions.net/ diff --git a/docs/ref/contrib/gis/install/index.txt b/docs/ref/contrib/gis/install/index.txt index e9f66623277f..dda1b5bc3489 100644 --- a/docs/ref/contrib/gis/install/index.txt +++ b/docs/ref/contrib/gis/install/index.txt @@ -257,7 +257,7 @@ Summary:: $ brew install gdal $ brew install libgeoip -__ http://brew.sh/ +__ https://brew.sh/ .. _Xcode: https://developer.apple.com/xcode/ .. _kyngchaos: @@ -403,8 +403,8 @@ install :ref:`postgisasb`. and contains shortcuts for the ASB as well as the 'SQL Shell', which will launch a ``psql`` command window. -__ http://www.enterprisedb.com/products-services-training/pgdownload -__ http://www.enterprisedb.com +__ https://www.enterprisedb.com/downloads/postgres-postgresql-downloads +__ https://www.enterprisedb.com .. _postgisasb: diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt index 5871233ee1b6..21208ebfb4cb 100644 --- a/docs/ref/contrib/gis/install/spatialite.txt +++ b/docs/ref/contrib/gis/install/spatialite.txt @@ -133,4 +133,4 @@ following to your ``settings.py``:: SPATIALITE_LIBRARY_PATH='/usr/local/lib/mod_spatialite.dylib' -.. _Homebrew: http://brew.sh/ +.. _Homebrew: https://brew.sh/ diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index f384f5829cfa..33e48e2910a3 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -278,7 +278,7 @@ A more useful index is a ``GIN`` index, which you should create using a similar to PostgreSQL's ``text`` type. .. _citext: https://www.postgresql.org/docs/current/static/citext.html - .. _the performance considerations: https://www.postgresql.org/docs/current/static/citext.html#AEN169274 + .. _the performance considerations: https://www.postgresql.org/docs/current/static/citext.html#AEN178177 ``HStoreField`` =============== diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index cbebfb7fbfb8..1b9c62e5d54c 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -8,7 +8,7 @@ The sitemap framework Django comes with a high-level sitemap-generating framework that makes creating sitemap_ XML files easy. -.. _sitemap: http://www.sitemaps.org/ +.. _sitemap: https://www.sitemaps.org/ Overview ======== @@ -226,7 +226,7 @@ Note: default priority of a page is ``0.5``. See the `sitemaps.org documentation`_ for more. - .. _sitemaps.org documentation: http://www.sitemaps.org/protocol.html#prioritydef + .. _sitemaps.org documentation: https://www.sitemaps.org/protocol.html#prioritydef .. attribute:: Sitemap.protocol @@ -244,7 +244,7 @@ Note: This attribute defines the maximum number of URLs included on each page of the sitemap. Its value should not exceed the default value of ``50000``, which is the upper limit allowed in the `Sitemaps protocol - `_. + `_. .. attribute:: Sitemap.i18n @@ -455,7 +455,7 @@ generate a Google News compatible sitemap: {% spaceless %} {% for url in urlset %} diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index a95bc2af60a2..ba91850d853a 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -145,8 +145,8 @@ Setting the token on the AJAX request Finally, you'll have to actually set the header on your AJAX request, while protecting the CSRF token from being sent to other domains using -`settings.crossDomain `_ in jQuery 1.5.1 and -newer: +`settings.crossDomain `_ in jQuery 1.5.1 +and newer: .. code-block:: javascript diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 1a9f37afedd1..add34acb6ccc 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -773,8 +773,8 @@ Oracle notes Django supports `Oracle Database Server`_ versions 11.2 and higher. Version 5.2 or higher of the `cx_Oracle`_ Python driver is required. -.. _`Oracle Database Server`: http://www.oracle.com/ -.. _`cx_Oracle`: http://cx-oracle.sourceforge.net/ +.. _`Oracle Database Server`: https://www.oracle.com/ +.. _`cx_Oracle`: https://oracle.github.io/python-cx_Oracle/ In order for the ``python manage.py migrate`` command to work, your Oracle database user must have privileges to run the following commands: diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 800b80ee6938..9e3f6177e482 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -966,7 +966,7 @@ Python interpreter, use ``python`` as the interface name, like so:: is deprecated and will be removed in Django 2.0. .. _IPython: https://ipython.org/ -.. _bpython: http://bpython-interpreter.org/ +.. _bpython: https://bpython-interpreter.org/ .. django-admin-option:: --nostartup diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index dd0bae3bb3a2..f4ecc8a96ec4 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -295,7 +295,7 @@ If you wish to submit your site to the `browser preload list`_, set the you may need to set the :setting:`SECURE_PROXY_SSL_HEADER` setting. .. _"Strict-Transport-Security" header: https://en.wikipedia.org/wiki/Strict_Transport_Security -.. _browser preload list: https://hstspreload.appspot.com/ +.. _browser preload list: https://hstspreload.org/ .. _x-content-type-options: @@ -311,12 +311,9 @@ If your site serves user-uploaded files, a malicious user could upload a specially-crafted file that would be interpreted as HTML or JavaScript by the browser when you expected it to be something harmless. -To learn more about this header and how the browser treats it, you can -read about it on the `IE Security Blog`_. - To prevent the browser from guessing the content type and force it to always use the type provided in the ``Content-Type`` header, you can pass -the ``X-Content-Type-Options: nosniff`` header. ``SecurityMiddleware`` will +the `X-Content-Type-Options: nosniff`__ header. ``SecurityMiddleware`` will do this for all responses if the :setting:`SECURE_CONTENT_TYPE_NOSNIFF` setting is ``True``. @@ -328,7 +325,7 @@ you are using Django to do something like require authorization in order to download files and you cannot set the header using your Web server, this setting will be useful. -.. _IE Security Blog: http://blogs.msdn.com/b/ie/archive/2008/09/02/ie8-security-part-vi-beta-2-update.aspx +__ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options .. _x-xss-protection: @@ -355,7 +352,7 @@ header. ``SecurityMiddleware`` will do this for all responses if the sanitizing ` all input to prevent XSS attacks. .. _XSS attack: https://en.wikipedia.org/wiki/Cross-site_scripting -.. _X-XSS-Protection header: http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx +.. _X-XSS-Protection header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection .. _ssl-redirect: diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 6ab00cc9c744..4ad645f95864 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -425,7 +425,7 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 .. class:: Rss201rev2Feed(RssFeed) - Spec: https://cyber.law.harvard.edu/rss/rss.html + Spec: https://cyber.harvard.edu/rss/rss.html ``RssUserland091Feed`` ---------------------- diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index eeac982bdc68..66803ee5349f 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -305,4 +305,4 @@ to, or in lieu of custom ``field.clean()`` methods. .. versionadded:: 1.11 Uses Pillow to ensure that the ``value`` is `a valid image extension - `_. + `_. diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 66326f2fb157..ea29108c0cf4 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -29,7 +29,7 @@ readability of your code. **All** of the suggestions below come with the caveat that in your circumstances the general principle might not apply, or might even be reversed. -.. _django-debug-toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar/ +.. _django-debug-toolbar: https://github.com/jazzband/django-debug-toolbar/ Use standard DB optimization techniques ======================================= diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 21ae388fa884..b24f7beaadba 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -1088,7 +1088,7 @@ For example, a ``Blog`` object ``b`` has access to a list of all related All examples in this section use the sample ``Blog``, ``Author`` and ``Entry`` models defined at the top of this page. -.. _descriptors: http://users.rcn.com/python/download/Descriptor.htm +.. _descriptors: https://docs.python.org/3/howto/descriptor.html One-to-many relationships ------------------------- diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 39c64f525da5..31cdd2ac82ce 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -167,7 +167,7 @@ and the :setting:`SECRET_KEY` setting. .. _`common limit of 4096 bytes`: https://tools.ietf.org/html/rfc2965#section-5.3 .. _`replay attacks`: https://en.wikipedia.org/wiki/Replay_attack -.. _`speed of your site`: http://yuiblog.com/blog/2007/03/01/performance-research-part-3/ +.. _`speed of your site`: https://yuiblog.com/blog/2007/03/01/performance-research-part-3/ Using sessions in views ======================= diff --git a/docs/topics/install.txt b/docs/topics/install.txt index 5bdd98596d4b..06f61c349905 100644 --- a/docs/topics/install.txt +++ b/docs/topics/install.txt @@ -10,7 +10,7 @@ Install Python Being a Python Web framework, Django requires Python. See :ref:`faq-python-version-support` for details. -Get the latest version of Python at https://www.python.org/download/ or with +Get the latest version of Python at https://www.python.org/downloads/ or with your operating system's package manager. .. admonition:: Django on Jython @@ -119,8 +119,8 @@ database queries, Django will need permission to create a test database. .. _MySQL: https://www.mysql.com/ .. _psycopg2: http://initd.org/psycopg/ .. _SQLite: https://www.sqlite.org/ -.. _cx_Oracle: http://cx-oracle.sourceforge.net/ -.. _Oracle: http://www.oracle.com/ +.. _cx_Oracle: https://oracle.github.io/python-cx_Oracle/ +.. _Oracle: https://www.oracle.com/ .. _removing-old-versions-of-django: @@ -243,4 +243,4 @@ When you want to update your copy of the Django source code, just run the command ``git pull`` from within the ``django`` directory. When you do this, Git will automatically download any changes. -.. _Git: http://git-scm.com/ +.. _Git: https://git-scm.com/ diff --git a/docs/topics/performance.txt b/docs/topics/performance.txt index 8f2516a4eb3d..1306a28c8e26 100644 --- a/docs/topics/performance.txt +++ b/docs/topics/performance.txt @@ -56,10 +56,10 @@ Django tools ~~~~~~~~~~~~ `django-debug-toolbar -`_ is a very -handy tool that provides insights into what your code is doing and how much -time it spends doing it. In particular it can show you all the SQL queries your -page is generating, and how long each one has taken. +`_ is a very handy tool that +provides insights into what your code is doing and how much time it spends +doing it. In particular it can show you all the SQL queries your page is +generating, and how long each one has taken. Third-party panels are also available for the toolbar, that can (for example) report on cache performance and template rendering times. diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index 854596bc37a1..f751d40ac1cf 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -837,4 +837,4 @@ Implementing a custom context processor is as simple as defining a function. .. _Jinja2: http://jinja.pocoo.org/ .. _DEP 182: https://github.com/django/deps/blob/master/final/0182-multiple-template-engines.rst -.. _Django Debug Toolbar: https://github.com/django-debug-toolbar/django-debug-toolbar +.. _Django Debug Toolbar: https://github.com/jazzband/django-debug-toolbar diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 192a62c516ba..50307269b855 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -740,5 +740,5 @@ listed here because of the ``source`` flag passed to the previous command. For more options like annotated HTML listings detailing missed lines, see the `coverage.py`_ docs. -.. _coverage.py: http://nedbatchelder.com/code/coverage/ +.. _coverage.py: http://coverage.readthedocs.io/ .. _install coverage.py: https://pypi.python.org/pypi/coverage From d1d08d86baef05db5b37c266ed9142f1c57e6322 Mon Sep 17 00:00:00 2001 From: vinay karanam Date: Tue, 23 May 2017 07:28:38 +0530 Subject: [PATCH 022/389] [1.11.x] Fixed #28230 -- Allowed DjangoJsonEncoder to serialize CallableBool. --- django/core/serializers/json.py | 3 +++ docs/releases/1.11.2.txt | 3 +++ tests/serializers/test_json.py | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/django/core/serializers/json.py b/django/core/serializers/json.py index 9da8993ee649..a475858205d1 100644 --- a/django/core/serializers/json.py +++ b/django/core/serializers/json.py @@ -16,6 +16,7 @@ Deserializer as PythonDeserializer, Serializer as PythonSerializer, ) from django.utils import six +from django.utils.deprecation import CallableBool from django.utils.duration import duration_iso_string from django.utils.functional import Promise from django.utils.timezone import is_aware @@ -117,5 +118,7 @@ def default(self, o): return str(o) elif isinstance(o, Promise): return six.text_type(o) + elif isinstance(o, CallableBool): + return bool(o) else: return super(DjangoJSONEncoder, self).default(o) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index a15d4272ec21..e1cd939fa0a9 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -29,3 +29,6 @@ Bugfixes * Fixed a regression where ``Model._state.adding`` wasn't set correctly on multi-table inheritance parent models after saving a child model (:ticket:`28210`). + +* Allowed ``DjangoJSONEncoder`` to serialize + ``django.utils.deprecation.CallableBool`` (:ticket:`28230`). diff --git a/tests/serializers/test_json.py b/tests/serializers/test_json.py index b191ce664408..391523ae336c 100644 --- a/tests/serializers/test_json.py +++ b/tests/serializers/test_json.py @@ -12,6 +12,7 @@ from django.db import models from django.test import SimpleTestCase, TestCase, TransactionTestCase from django.test.utils import isolate_apps +from django.utils.deprecation import CallableFalse, CallableTrue from django.utils.translation import override, ugettext_lazy from .models import Score @@ -315,3 +316,7 @@ def test_timedelta(self): json.dumps({'duration': duration}, cls=DjangoJSONEncoder), '{"duration": "P0DT00H00M00S"}' ) + + def test_callable_bool(self): + self.assertEqual(json.dumps(CallableTrue, cls=DjangoJSONEncoder), 'true') + self.assertEqual(json.dumps(CallableFalse, cls=DjangoJSONEncoder), 'false') From b78e5dec18c79ab8a1910478ab61a65cd691078a Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Wed, 17 May 2017 13:09:12 +0300 Subject: [PATCH 023/389] [1.11.x] Fixed docs build with Sphinx 1.6. Backport of f370bfb10878918eae8db9985e0856949fa65d3a from master --- docs/Makefile | 6 +++++- docs/_ext/djangodocs.py | 9 ++++++--- docs/conf.py | 6 ++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index f57db99699f6..39f84ec0e3ff 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,10 +9,14 @@ PAPER ?= BUILDDIR ?= _build LANGUAGE ?= +# Convert something like "en_US" to "en", because Sphinx does not recognize +# underscores. Country codes should be passed using a dash, e.g. "pt-BR". +LANGUAGEOPT = $(firstword $(subst _, ,$(LANGUAGE))) + # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGE) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +ALLSPHINXOPTS = -n -d $(BUILDDIR)/doctrees -D language=$(LANGUAGEOPT) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index a5d545f46b67..a196d63dd1f8 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -13,7 +13,10 @@ from sphinx.util.compat import Directive from sphinx.util.console import bold from sphinx.util.nodes import set_source_info -from sphinx.writers.html import SmartyPantsHTMLTranslator +try: + from sphinx.writers.html import SmartyPantsHTMLTranslator as HTMLTranslator +except ImportError: # Sphinx 1.6+ + from sphinx.writers.html import HTMLTranslator # RE for option descriptions without a '--' prefix simple_option_desc_re = re.compile( @@ -226,7 +229,7 @@ def run(self): return ret -class DjangoHTMLTranslator(SmartyPantsHTMLTranslator): +class DjangoHTMLTranslator(HTMLTranslator): """ Django-specific reST to HTML tweaks. """ @@ -287,7 +290,7 @@ def visit_section(self, node): old_ids = node.get('ids', []) node['ids'] = ['s-' + i for i in old_ids] node['ids'].extend(old_ids) - SmartyPantsHTMLTranslator.visit_section(self, node) + HTMLTranslator.visit_section(self, node) node['ids'] = old_ids diff --git a/docs/conf.py b/docs/conf.py index a7acfe6b69ff..62e337dcebad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -52,6 +52,8 @@ # Add it only if spelling check is requested so docs can be generated without it. if 'spelling' in sys.argv: extensions.append("sphinxcontrib.spelling") + # Workaround for https://bitbucket.org/dhellmann/sphinxcontrib-spelling/issues/13 + html_use_smartypants = False # Spelling language. spelling_lang = 'en_US' @@ -188,10 +190,6 @@ def django_release(): # using the given strftime format. html_last_updated_fmt = '%b %d, %Y' -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -html_use_smartypants = True - # Content template for the index page. # html_index = '' From 64ed10e67281d13ff857155db76769ca1a4bb8b6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 24 May 2017 10:39:51 -0400 Subject: [PATCH 024/389] [1.11.x] Removed usage of deprecated sphinx.util.compat.Directive. Backport of cb16458c4f91fe43b898b55b04fb177e914ac3af from master --- docs/_ext/djangodocs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index a196d63dd1f8..c3ca88a61048 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -6,13 +6,13 @@ import re from docutils import nodes -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.domains.std import Cmdoption -from sphinx.util.compat import Directive from sphinx.util.console import bold from sphinx.util.nodes import set_source_info + try: from sphinx.writers.html import SmartyPantsHTMLTranslator as HTMLTranslator except ImportError: # Sphinx 1.6+ From 945b3d1da1df81cd7ca94771025b8019c7de1340 Mon Sep 17 00:00:00 2001 From: Dima Veselov Date: Thu, 25 May 2017 12:44:55 +0300 Subject: [PATCH 025/389] [1.11.x] Fixed typo in docs/topics/testing/advanced.txt. Backport of 01d7ff9a17af2024956f6c81106a22bffcd9a703 from master --- docs/topics/testing/advanced.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 50307269b855..996bcf1a0607 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -203,7 +203,7 @@ example database configuration:: }, }, 'diamonds': { - ... db settings + # ... db settings 'TEST': { 'DEPENDENCIES': [], }, From d6bff52fdfb5ab48c28d8c58cb28dcc04e217734 Mon Sep 17 00:00:00 2001 From: Yuichi Fujikawa Date: Thu, 25 May 2017 18:54:51 +0900 Subject: [PATCH 026/389] [1.11.x] Fixed docstring typo in django/contrib/admin/actions.py. Backport of f8bce94997739e4e61919cddff84fc3e3302d8f7 from master --- django/contrib/admin/actions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/actions.py b/django/contrib/admin/actions.py index f07f3549cbda..4e6ea18a5273 100644 --- a/django/contrib/admin/actions.py +++ b/django/contrib/admin/actions.py @@ -17,7 +17,7 @@ def delete_selected(modeladmin, request, queryset): Default action which deletes the selected objects. This action first displays a confirmation page which shows all the - deleteable objects, or, if the user has no permission one of the related + deletable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all selected objects and redirects back to the change list. From 61f61a2b0c3ecddd568e9aee914644bd8cb18167 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 25 May 2017 13:15:12 -0400 Subject: [PATCH 027/389] [1.11.x] Fixed #28239 -- Removed docs for a removed arg of template.Context. Backport of 0ea321193a1d7f857544760e12058303edd51362 from master --- docs/ref/templates/api.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index 25563c19b7b6..df3123cf1666 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -210,14 +210,8 @@ different contexts. .. class:: Context(dict_=None) - This class lives at ``django.template.Context``. The constructor takes - two optional arguments: - - * A dictionary mapping variable names to variable values. - - * The name of the current application. This application name is used - to help :ref:`resolve namespaced URLs`. - If you're not using namespaced URLs, you can ignore this argument. + The constructor of ``django.template.Context`` takes an optional argument — + a dictionary mapping variable names to variable values. For details, see :ref:`playing-with-context` below. From f804b46294c58d80452c91c485717fe8a31b2f47 Mon Sep 17 00:00:00 2001 From: Eric Theise Date: Sun, 21 May 2017 16:54:26 -0700 Subject: [PATCH 028/389] [1.11.x] Added some shell output in tutorial 2. Backport of 1b5b3710f16450eca49fb11b41a242816857a454 from master --- docs/intro/tutorial05.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 45a11a68c06a..f32fccc33d4b 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -361,6 +361,7 @@ With that ready, we can ask the client to do some work for us:: >>> # get a response from '/' >>> response = client.get('/') + Not Found: / >>> # we should expect a 404 from that address; if you instead see an >>> # "Invalid HTTP_HOST header" error and a 400 response, you probably >>> # omitted the setup_test_environment() call described earlier. From b9abdd92ab21a796943e8047e778eebc671c8c00 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 19 May 2017 06:40:43 -0400 Subject: [PATCH 029/389] [1.11.x] Fixed #28222 -- Allowed settable properties in QuerySet.update_or_create()/get_or_create() defaults. Backport of 37ab3c3f9d707d6a1896db79c631e920dcb1fb78 from master --- django/db/models/options.py | 5 +---- django/db/models/query.py | 4 +++- docs/releases/1.11.2.txt | 4 ++++ tests/get_or_create/models.py | 12 ++++++++++++ tests/get_or_create/tests.py | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 1f1fb4bd7579..56a7e87b70e0 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -881,10 +881,7 @@ def has_auto_field(self, value): @cached_property def _property_names(self): - """ - Return a set of the names of the properties defined on the model. - Internal helper for model initialization. - """ + """Return a set of the names of the properties defined on the model.""" return frozenset({ attr for attr in dir(self.model) if isinstance(getattr(self.model, attr), property) diff --git a/django/db/models/query.py b/django/db/models/query.py index deeb0705fe5b..c9ff437232cd 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -518,12 +518,14 @@ def _extract_model_params(self, defaults, **kwargs): lookup[f.name] = lookup.pop(f.attname) params = {k: v for k, v in kwargs.items() if LOOKUP_SEP not in k} params.update(defaults) + property_names = self.model._meta._property_names invalid_params = [] for param in params: try: self.model._meta.get_field(param) except exceptions.FieldDoesNotExist: - if param != 'pk': # It's okay to use a model's pk property. + # It's okay to use a model's property if it has a setter. + if not (param in property_names and getattr(self.model, param).fset): invalid_params.append(param) if invalid_params: raise exceptions.FieldError( diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index e1cd939fa0a9..ef235fa9ed57 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -32,3 +32,7 @@ Bugfixes * Allowed ``DjangoJSONEncoder`` to serialize ``django.utils.deprecation.CallableBool`` (:ticket:`28230`). + +* Relaxed the validation added in Django 1.11 of the fields in the ``defaults`` + argument of ``QuerySet.get_or_create()`` and ``update_or_create()`` to + reallow settable model properties (:ticket:`28222`). diff --git a/tests/get_or_create/models.py b/tests/get_or_create/models.py index 03944344081a..b5fe534e3a32 100644 --- a/tests/get_or_create/models.py +++ b/tests/get_or_create/models.py @@ -36,6 +36,18 @@ class Thing(models.Model): name = models.CharField(max_length=256) tags = models.ManyToManyField(Tag) + @property + def capitalized_name_property(self): + return self.name + + @capitalized_name_property.setter + def capitalized_name_property(self, val): + self.name = val.capitalize() + + @property + def name_in_all_caps(self): + return self.name.upper() + class Publisher(models.Model): name = models.CharField(max_length=100) diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index d727bdee5d48..0c8efb6f4e2c 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -77,6 +77,11 @@ def test_get_or_create_with_pk_property(self): """ Thing.objects.get_or_create(pk=1) + def test_get_or_create_with_model_property_defaults(self): + """Using a property with a setter implemented is allowed.""" + t, _ = Thing.objects.get_or_create(defaults={'capitalized_name_property': 'annie'}, pk=1) + self.assertEqual(t.name, 'Annie') + def test_get_or_create_on_related_manager(self): p = Publisher.objects.create(name="Acme Publishing") # Create a book through the publisher. @@ -336,6 +341,11 @@ def test_with_pk_property(self): """ Thing.objects.update_or_create(pk=1) + def test_update_or_create_with_model_property_defaults(self): + """Using a property with a setter implemented is allowed.""" + t, _ = Thing.objects.get_or_create(defaults={'capitalized_name_property': 'annie'}, pk=1) + self.assertEqual(t.name, 'Annie') + def test_error_contains_full_traceback(self): """ update_or_create should raise IntegrityErrors with the full traceback. @@ -522,3 +532,11 @@ def test_update_or_create_with_invalid_kwargs(self): def test_multiple_invalid_fields(self): with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'invalid', 'nonexistent'"): Thing.objects.update_or_create(name='a', nonexistent='b', defaults={'invalid': 'c'}) + + def test_property_attribute_without_setter_defaults(self): + with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'name_in_all_caps'"): + Thing.objects.update_or_create(name='a', defaults={'name_in_all_caps': 'FRANK'}) + + def test_property_attribute_without_setter_kwargs(self): + with self.assertRaisesMessage(FieldError, "Invalid field name(s) for model Thing: 'name_in_all_caps'"): + Thing.objects.update_or_create(name_in_all_caps='FRANK', defaults={'name': 'Frank'}) From 2c03e145866337a0dded66e0c87ce235cd29581c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Sat, 27 May 2017 16:28:37 -0700 Subject: [PATCH 030/389] [1.11.x] Corrected REPL example in forms docs for Python 3. Backport of c3e638112d2a96c8d678524903f37b13c6692188 from master --- docs/ref/forms/api.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index c74cfaed4815..38d1348da142 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -1208,8 +1208,8 @@ by setting the name of the field to ``None`` on the subclass. For example:: >>> class ChildForm(ParentForm): ... name = None - >>> ChildForm().fields.keys() - ... ['age'] + >>> list(ChildForm().fields) + ['age'] .. _form-prefix: From c341803315c17f79a14acd88aaf1d44a23604c8b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 29 May 2017 09:45:41 -0400 Subject: [PATCH 031/389] [1.11.x] Fixed #28204 -- Fixed MultipleObjectMixin.paginate_queryset() crash on Python 2 if InvalidPage message contains non-ASCII. --- django/views/generic/list.py | 3 ++- docs/releases/1.11.2.txt | 3 +++ tests/generic_views/test_list.py | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/django/views/generic/list.py b/django/views/generic/list.py index 2277ffd4d3ec..481929a98755 100644 --- a/django/views/generic/list.py +++ b/django/views/generic/list.py @@ -5,6 +5,7 @@ from django.db.models.query import QuerySet from django.http import Http404 from django.utils import six +from django.utils.encoding import force_text from django.utils.translation import ugettext as _ from django.views.generic.base import ContextMixin, TemplateResponseMixin, View @@ -80,7 +81,7 @@ def paginate_queryset(self, queryset, page_size): except InvalidPage as e: raise Http404(_('Invalid page (%(page_number)s): %(message)s') % { 'page_number': page_number, - 'message': str(e) + 'message': force_text(e), }) def get_paginate_by(self, queryset): diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index ef235fa9ed57..4dcfda7cc79b 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -36,3 +36,6 @@ Bugfixes * Relaxed the validation added in Django 1.11 of the fields in the ``defaults`` argument of ``QuerySet.get_or_create()`` and ``update_or_create()`` to reallow settable model properties (:ticket:`28222`). + +* Fixed ``MultipleObjectMixin.paginate_queryset()`` crash on Python 2 if the + ``InvalidPage`` message contains non-ASCII (:ticket:`28204`). diff --git a/tests/generic_views/test_list.py b/tests/generic_views/test_list.py index 5a22abd33b33..37408e866255 100644 --- a/tests/generic_views/test_list.py +++ b/tests/generic_views/test_list.py @@ -5,6 +5,7 @@ from django.core.exceptions import ImproperlyConfigured from django.test import TestCase, override_settings +from django.utils import translation from django.utils.encoding import force_str from django.views.generic.base import View @@ -103,6 +104,13 @@ def test_paginated_page_out_of_range(self): res = self.client.get('/list/authors/paginated/42/') self.assertEqual(res.status_code, 404) + def test_paginated_page_out_of_range_non_ascii_message(self): + msg = 'Ung\xfcltige Seite (42): Diese Seite enth\xe4lt keine Ergebnisse' + with translation.override('de'): + res = self.client.get('/list/authors/paginated/42/') + self.assertEqual(res.status_code, 404) + self.assertEqual(res.context['exception'], msg) + def test_paginated_invalid_page(self): self._make_authors(100) res = self.client.get('/list/authors/paginated/?page=frog') From 3e17b0222c5b8d3961973e4d8a9fde5ffa4f53e4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 29 May 2017 18:00:47 -0400 Subject: [PATCH 032/389] [1.11.x] Removed incorrect "required" attribute in docs/ref/forms/fields.txt. Backport of 17ed0d36bc3c380b7d8a0a30ceda09d98ab74fd4 from master --- docs/ref/forms/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 175aa77f46be..e2cfb59df287 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -125,7 +125,7 @@ We've specified ``auto_id=False`` to simplify the output:: >>> f = CommentForm(auto_id=False) >>> print(f) Your name: - Your website: + Your website: Comment: ``label_suffix`` From 9b9a81024a4955d6d3fb9f440b89567bb1634090 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 May 2017 06:40:41 -0400 Subject: [PATCH 033/389] [1.11.x] Fixed #28199 -- Fixed Subquery generating unnecessary/invalid CAST. Thanks Simon Charette for the fix. Backport of f04495521ade8a2befc1aca70dd0a2c7aad4c987 from master --- django/db/models/expressions.py | 1 - docs/releases/1.11.2.txt | 3 +++ tests/expressions/models.py | 8 +++++++- tests/expressions/tests.py | 9 ++++++++- 4 files changed, 18 insertions(+), 3 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index b1d67e6653b8..e8bd921083ce 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -977,7 +977,6 @@ def as_sql(self, compiler, connection, template=None, **extra_context): template = template or template_params.get('template', self.template) sql = template % template_params - sql = connection.ops.unification_cast_sql(self.output_field) % sql return sql, sql_params def _prepare(self, output_field): diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 4dcfda7cc79b..718dc386bb50 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -39,3 +39,6 @@ Bugfixes * Fixed ``MultipleObjectMixin.paginate_queryset()`` crash on Python 2 if the ``InvalidPage`` message contains non-ASCII (:ticket:`28204`). + +* Prevented ``Subquery`` from adding an unnecessary ``CAST`` which resulted in + invalid SQL (:ticket:`28199`). diff --git a/tests/expressions/models.py b/tests/expressions/models.py index 6dc956c8fe22..b1a737d0b9c5 100644 --- a/tests/expressions/models.py +++ b/tests/expressions/models.py @@ -1,9 +1,10 @@ """ Tests for F() query expression syntax. """ - from __future__ import unicode_literals +import uuid + from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -88,9 +89,14 @@ def __str__(self): return "%s (%s to %s)" % (self.midpoint, self.start, self.end) +class UUIDPK(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4) + + @python_2_unicode_compatible class UUID(models.Model): uuid = models.UUIDField(null=True) + uuid_fk = models.ForeignKey(UUIDPK, models.CASCADE, null=True) def __str__(self): return "%s" % self.uuid diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 6e5cf5829567..253a9c04291f 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -25,7 +25,8 @@ from django.utils import six from .models import ( - UUID, Company, Employee, Experiment, Number, Result, SimulationRun, Time, + UUID, UUIDPK, Company, Employee, Experiment, Number, Result, SimulationRun, + Time, ) @@ -480,6 +481,12 @@ def test_in_subquery(self): subquery_test2 = Company.objects.filter(pk=Subquery(small_companies.filter(num_employees=3))) self.assertCountEqual(subquery_test2, [self.foobar_ltd]) + def test_uuid_pk_subquery(self): + u = UUIDPK.objects.create() + UUID.objects.create(uuid_fk=u) + qs = UUIDPK.objects.filter(id__in=Subquery(UUID.objects.values('uuid_fk__id'))) + self.assertCountEqual(qs, [u]) + def test_nested_subquery(self): inner = Company.objects.filter(point_of_contact=OuterRef('pk')) outer = Employee.objects.annotate(is_point_of_contact=Exists(inner)) From 5688562f66b2074f7906e34326f54b1bba3f52a8 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 30 May 2017 14:22:40 +0200 Subject: [PATCH 034/389] [1.11.x] Fixed gis_tests.geoapp test with incorrect geodetic coordinates. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The latitude coordinates exceed -90, 90 bounds and caused a test failure on Oracle 12.2. Thanks Michał Wierzbowski for help preparing the patch. Backport of 037d6540ecb7a60dca99162f6adedb2d879fa3ff from master --- tests/gis_tests/geoapp/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index b7deacf989ac..b6a5c2d0c851 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -68,7 +68,7 @@ def test_proxy(self): nullcity.delete() # Testing on a Polygon - shell = LinearRing((0, 0), (0, 100), (100, 100), (100, 0), (0, 0)) + shell = LinearRing((0, 0), (0, 90), (100, 90), (100, 0), (0, 0)) inner = LinearRing((40, 40), (40, 60), (60, 60), (60, 40), (40, 40)) # Creating a State object using a built Polygon From 4112bce442c96747f588b9f99f63e45634950972 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 May 2017 09:39:59 -0400 Subject: [PATCH 035/389] [1.11.x] Refs #28181 -- Corrected detection of GDAL 2.1 on Windows. Follow up to a404f75f92971634c76330f3742261d33ccecca1. Backport of c32476e5ba2f1c18758cfcffc857fa4eab37e816 from master --- django/contrib/gis/gdal/libgdal.py | 2 +- docs/releases/1.11.2.txt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/gdal/libgdal.py b/django/contrib/gis/gdal/libgdal.py index 232865f4931a..022aa945d759 100644 --- a/django/contrib/gis/gdal/libgdal.py +++ b/django/contrib/gis/gdal/libgdal.py @@ -23,7 +23,7 @@ lib_names = None elif os.name == 'nt': # Windows NT shared libraries - lib_names = [str('gdal21'), str('gdal20'), str('gdal111'), str('gdal110'), str('gdal19')] + lib_names = [str('gdal201'), str('gdal20'), str('gdal111'), str('gdal110'), str('gdal19')] elif os.name == 'posix': # *NIX library names. lib_names = ['gdal', 'GDAL', 'gdal2.1.0', 'gdal2.0.0', 'gdal1.11.0', 'gdal1.10.0', 'gdal1.9.0'] diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 718dc386bb50..99e82f75a012 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -42,3 +42,5 @@ Bugfixes * Prevented ``Subquery`` from adding an unnecessary ``CAST`` which resulted in invalid SQL (:ticket:`28199`). + +* Corrected detection of GDAL 2.1 on Windows (:ticket:`28181`). From 4e675999d530692f96745f5a54ba0376acd39e22 Mon Sep 17 00:00:00 2001 From: Adit Biswas Date: Tue, 16 May 2017 00:26:30 +0530 Subject: [PATCH 036/389] [1.11.x] Fixed #28209 -- Made date-based generic views return a 404 rather than crash when given an out of range date. Backport of c2eea61dff44b16caab04928d582b856c2b8b615 from master --- django/views/generic/dates.py | 15 ++++++++++++--- docs/releases/1.11.2.txt | 3 +++ tests/generic_views/test_dates.py | 11 +++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/django/views/generic/dates.py b/django/views/generic/dates.py index 938c53853588..c678b58bae27 100644 --- a/django/views/generic/dates.py +++ b/django/views/generic/dates.py @@ -66,7 +66,10 @@ def _get_next_year(self, date): The interval is defined by start date <= item date < next start date. """ - return date.replace(year=date.year + 1, month=1, day=1) + try: + return date.replace(year=date.year + 1, month=1, day=1) + except ValueError: + raise Http404(_("Date out of range")) def _get_current_year(self, date): """ @@ -123,7 +126,10 @@ def _get_next_month(self, date): The interval is defined by start date <= item date < next start date. """ if date.month == 12: - return date.replace(year=date.year + 1, month=1, day=1) + try: + return date.replace(year=date.year + 1, month=1, day=1) + except ValueError: + raise Http404(_("Date out of range")) else: return date.replace(month=date.month + 1, day=1) @@ -237,7 +243,10 @@ def _get_next_week(self, date): The interval is defined by start date <= item date < next start date. """ - return date + datetime.timedelta(days=7 - self._get_weekday(date)) + try: + return date + datetime.timedelta(days=7 - self._get_weekday(date)) + except OverflowError: + raise Http404(_("Date out of range")) def _get_current_week(self, date): """ diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index 99e82f75a012..ab7d04959f84 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -44,3 +44,6 @@ Bugfixes invalid SQL (:ticket:`28199`). * Corrected detection of GDAL 2.1 on Windows (:ticket:`28181`). + +* Made date-based generic views return a 404 rather than crash when given an + out of range date (:ticket:`28209`). diff --git a/tests/generic_views/test_dates.py b/tests/generic_views/test_dates.py index 9b7c20ec7386..5e52bb48e292 100644 --- a/tests/generic_views/test_dates.py +++ b/tests/generic_views/test_dates.py @@ -664,6 +664,17 @@ def test_date_detail_allow_future(self): self.assertEqual(res.context['book'], b) self.assertTemplateUsed(res, 'generic_views/book_detail.html') + def test_year_out_of_range(self): + urls = [ + '/dates/books/9999/', + '/dates/books/9999/12/', + '/dates/books/9999/week/52/', + ] + for url in urls: + res = self.client.get(url) + self.assertEqual(res.status_code, 404) + self.assertEqual(res.context['exception'], 'Date out of range') + def test_invalid_url(self): with self.assertRaises(AttributeError): self.client.get("/dates/books/2008/oct/01/nopk/") From 48274c15893a6d4dd0700e24c3636cf56333c593 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 May 2017 15:14:32 -0400 Subject: [PATCH 037/389] [1.11.x] Fixed AppRegistryNotReady error when running gis_tests in isolation on PostGIS. Regression in 890537253cf235091816d27a5c2fb64943c8e34a. Backport of 4a251f8bce2e65a445fb2c46c2f71f763311c0ac from master --- tests/runtests.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/runtests.py b/tests/runtests.py index e89ac2a2581d..b8e98691bd68 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -113,10 +113,6 @@ def setup(verbosity, test_labels, parallel): bits = label.split('.')[:1] test_labels_set.add('.'.join(bits)) - if 'gis_tests' in test_labels_set and not connection.features.gis_enabled: - print('Aborting: A GIS database backend is required to run gis_tests.') - sys.exit(1) - if verbosity >= 1: msg = "Testing against Django installed in '%s'" % os.path.dirname(django.__file__) max_parallel = default_test_processes() if parallel == 0 else parallel @@ -193,6 +189,14 @@ def no_available_apps(self): # Load all the ALWAYS_INSTALLED_APPS. django.setup() + # It would be nice to put this validation earlier but it must come after + # django.setup() so that connection.features.gis_enabled can be accessed + # without raising AppRegistryNotReady when running gis_tests in isolation + # on some backends (e.g. PostGIS). + if 'gis_tests' in test_labels_set and not connection.features.gis_enabled: + print('Aborting: A GIS database backend is required to run gis_tests.') + sys.exit(1) + # Load all the test model apps. test_modules = get_test_modules() From ceb6a64f2f5d8e4a5309d9558f89a6d1f0b6308d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fleschenberg?= Date: Tue, 30 May 2017 23:15:20 +0200 Subject: [PATCH 038/389] [1.11.x] Refs #26294 -- Fixed typo in docs/ref/django-admin.txt. Backport of a30482a4b6e5d13e7325238fdc902fbca7e714f4 from master --- docs/ref/django-admin.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 9e3f6177e482..852ebf424aa2 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1778,7 +1778,7 @@ To call a management command from code use ``call_command``. ``*args`` a list of arguments accepted by the command. Arguments are passed to the argument parser, so you can use the same style as you would on the command - line. For example, ``call_command('flush', 'verbosity=0')``. + line. For example, ``call_command('flush', '--verbosity=0')``. ``**options`` named options accepted on the command-line. Options are passed to the command From 877d7b71ae952b3bc946e5187d6c23039a71614d Mon Sep 17 00:00:00 2001 From: Robert Rollins Date: Mon, 22 May 2017 10:16:56 -0700 Subject: [PATCH 039/389] [1.11.x] Fixed #28212 -- Allowed customizing the port that LiveServerTestCase uses. --- django/test/testcases.py | 16 ++++++++++------ docs/releases/1.11.2.txt | 9 ++++++++- docs/releases/1.11.txt | 5 +++++ docs/spelling_wordlist | 1 + tests/servers/tests.py | 18 ++++++++++++++++++ 5 files changed, 42 insertions(+), 7 deletions(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index 8203a3837da3..d70f57588fc8 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1257,9 +1257,9 @@ class LiveServerThread(threading.Thread): Thread for running a live http server while the tests are running. """ - def __init__(self, host, static_handler, connections_override=None): + def __init__(self, host, static_handler, connections_override=None, port=0): self.host = host - self.port = None + self.port = port self.is_ready = threading.Event() self.error = None self.static_handler = static_handler @@ -1279,8 +1279,10 @@ def run(self): try: # Create the handler for serving static and media files handler = self.static_handler(_MediaFilesHandler(WSGIHandler())) - self.httpd = self._create_server(0) - self.port = self.httpd.server_address[1] + self.httpd = self._create_server() + # If binding to port zero, assign the port allocated by the OS. + if self.port == 0: + self.port = self.httpd.server_address[1] self.httpd.set_app(handler) self.is_ready.set() self.httpd.serve_forever() @@ -1290,8 +1292,8 @@ def run(self): finally: connections.close_all() - def _create_server(self, port): - return WSGIServer((self.host, port), QuietWSGIRequestHandler, allow_reuse_address=False) + def _create_server(self): + return WSGIServer((self.host, self.port), QuietWSGIRequestHandler, allow_reuse_address=False) def terminate(self): if hasattr(self, 'httpd'): @@ -1313,6 +1315,7 @@ class LiveServerTestCase(TransactionTestCase): other thread can see the changes. """ host = 'localhost' + port = 0 server_thread_class = LiveServerThread static_handler = _StaticFilesHandler @@ -1354,6 +1357,7 @@ def _create_server_thread(cls, connections_override): cls.host, cls.static_handler, connections_override=connections_override, + port=cls.port, ) @classmethod diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index ab7d04959f84..a01ecc334234 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -4,7 +4,14 @@ Django 1.11.2 release notes *Under development* -Django 1.11.2 fixes several bugs in 1.11.1. +Django 1.11.2 adds a minor feature and fixes several bugs in 1.11.1. + +Minor feature +============= + +The new ``LiveServerTestCase.port`` attribute reallows the use case of binding +to a specific port following the :ref:`bind to port zero +` change in Django 1.11. Bugfixes ======== diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index e43d53fdf823..05c925259f13 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -533,6 +533,8 @@ to support it. Also, the minimum supported version of psycopg2 is increased from 2.4.5 to 2.5.4. +.. _liveservertestcase-port-zero-change: + ``LiveServerTestCase`` binds to port zero ----------------------------------------- @@ -542,6 +544,9 @@ to assign a free port. The ``DJANGO_LIVE_TEST_SERVER_ADDRESS`` environment variable is no longer used, and as it's also no longer used, the ``manage.py test --liveserver`` option is removed. +If you need to bind ``LiveServerTestCase`` to a specific port, use the ``port`` +attribute added in Django 1.11.2. + Protection against insecure redirects in :mod:`django.contrib.auth` and ``i18n`` views -------------------------------------------------------------------------------------- diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 2cc51435fff4..f23091f4fbd5 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -644,6 +644,7 @@ rc readded reallow reallowed +reallows rebase rebased rebasing diff --git a/tests/servers/tests.py b/tests/servers/tests.py index 03e05d0957e8..1224dddc22b2 100644 --- a/tests/servers/tests.py +++ b/tests/servers/tests.py @@ -136,3 +136,21 @@ def test_port_bind(self): finally: if hasattr(TestCase, 'server_thread'): TestCase.server_thread.terminate() + + def test_specified_port_bind(self): + """LiveServerTestCase.port customizes the server's port.""" + TestCase = type(str('TestCase'), (LiveServerBase,), {}) + # Find an open port and tell TestCase to use it. + s = socket.socket() + s.bind(('', 0)) + TestCase.port = s.getsockname()[1] + s.close() + TestCase.setUpClass() + try: + self.assertEqual( + TestCase.port, TestCase.server_thread.port, + 'Did not use specified port for LiveServerTestCase thread: %s' % TestCase.port + ) + finally: + if hasattr(TestCase, 'server_thread'): + TestCase.server_thread.terminate() From 7250393f31cf8000833312e381501b4575fdb1f1 Mon Sep 17 00:00:00 2001 From: Derrick Jackson Date: Wed, 10 May 2017 11:29:12 -0400 Subject: [PATCH 040/389] [1.11.x] Fixed #28170 -- Fixed file_move_safe() crash when moving files to a CIFS mount. Backport of 789c290150a0a5e7312e152df281dbcaf4ec174e from master --- django/core/files/move.py | 11 ++++++++++- docs/releases/1.11.2.txt | 3 +++ tests/files/tests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/django/core/files/move.py b/django/core/files/move.py index f3972c5f984d..8be62ff6d808 100644 --- a/django/core/files/move.py +++ b/django/core/files/move.py @@ -5,6 +5,7 @@ >>> file_move_safe("/tmp/old_file", "/tmp/new_file") """ +import errno import os from shutil import copystat @@ -67,7 +68,15 @@ def file_move_safe(old_file_name, new_file_name, chunk_size=1024 * 64, allow_ove finally: locks.unlock(fd) os.close(fd) - copystat(old_file_name, new_file_name) + + try: + copystat(old_file_name, new_file_name) + except OSError as e: + # Certain filesystems (e.g. CIFS) fail to copy the file's metadata if + # the type of the destination filesystem isn't the same as the source + # filesystem; ignore that. + if getattr(e, 'errno', 0) != errno.EPERM: + raise try: os.remove(old_file_name) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index a01ecc334234..fa23f1ddf022 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -54,3 +54,6 @@ Bugfixes * Made date-based generic views return a 404 rather than crash when given an out of range date (:ticket:`28209`). + +* Fixed a regression where ``file_move_safe()`` crashed when moving files to a + CIFS mount (:ticket:`28170`). diff --git a/tests/files/tests.py b/tests/files/tests.py index 72a121fcfecb..02d6d43b3ce2 100644 --- a/tests/files/tests.py +++ b/tests/files/tests.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals +import errno import gzip import os import struct @@ -317,6 +318,31 @@ def test_file_move_overwrite(self): os.close(handle_a) os.close(handle_b) + def test_file_move_copystat_cifs(self): + """ + file_move_safe() ignores a copystat() EPERM PermissionError. This + happens when the destination filesystem is CIFS, for example. + """ + copystat_EACCES_error = OSError(errno.EACCES, 'msg') + copystat_EPERM_error = OSError(errno.EPERM, 'msg') + handle_a, self.file_a = tempfile.mkstemp() + handle_b, self.file_b = tempfile.mkstemp() + try: + # This exception is required to reach the copystat() call in + # file_safe_move(). + with mock.patch('django.core.files.move.os.rename', side_effect=OSError()): + # An error besides EPERM isn't ignored. + with mock.patch('django.core.files.move.copystat', side_effect=copystat_EACCES_error): + with self.assertRaises(OSError) as e: + file_move_safe(self.file_a, self.file_b, allow_overwrite=True) + self.assertEqual(e.exception.errno, errno.EACCES) + # EPERM is ignored. + with mock.patch('django.core.files.move.copystat', side_effect=copystat_EPERM_error): + self.assertIsNone(file_move_safe(self.file_a, self.file_b, allow_overwrite=True)) + finally: + os.close(handle_a) + os.close(handle_b) + class SpooledTempTests(unittest.TestCase): def test_in_memory_spooled_temp(self): From 02158a785eff923110cb4c7c7e635d3ce4a928e3 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 31 May 2017 20:37:17 +0200 Subject: [PATCH 041/389] [1.11.x] Updated translations from Transifex --- django/conf/locale/is/LC_MESSAGES/django.mo | Bin 24826 -> 24824 bytes django/conf/locale/is/LC_MESSAGES/django.po | 6 +- django/conf/locale/mk/LC_MESSAGES/django.mo | Bin 29995 -> 30907 bytes django/conf/locale/mk/LC_MESSAGES/django.po | 16 +- django/conf/locale/mn/LC_MESSAGES/django.mo | Bin 27966 -> 28942 bytes django/conf/locale/mn/LC_MESSAGES/django.po | 16 +- django/conf/locale/nl/LC_MESSAGES/django.mo | Bin 23876 -> 24558 bytes django/conf/locale/nl/LC_MESSAGES/django.po | 202 +++++++++--------- django/conf/locale/ro/LC_MESSAGES/django.mo | Bin 24444 -> 24441 bytes django/conf/locale/ro/LC_MESSAGES/django.po | 7 +- .../admin/locale/eu/LC_MESSAGES/django.mo | Bin 13641 -> 13689 bytes .../admin/locale/eu/LC_MESSAGES/django.po | 4 +- .../admin/locale/is/LC_MESSAGES/djangojs.mo | Bin 4589 -> 4587 bytes .../admin/locale/is/LC_MESSAGES/djangojs.po | 6 +- .../admin/locale/mk/LC_MESSAGES/django.mo | Bin 19453 -> 20661 bytes .../admin/locale/mk/LC_MESSAGES/django.po | 22 +- .../admin/locale/mn/LC_MESSAGES/django.mo | Bin 18903 -> 20092 bytes .../admin/locale/mn/LC_MESSAGES/django.po | 17 +- .../admin/locale/nl/LC_MESSAGES/django.mo | Bin 16034 -> 16306 bytes .../admin/locale/nl/LC_MESSAGES/django.po | 137 ++++++------ .../admin/locale/pl/LC_MESSAGES/django.mo | Bin 16547 -> 16768 bytes .../admin/locale/pl/LC_MESSAGES/django.po | 79 +++---- .../admin/locale/pl/LC_MESSAGES/djangojs.mo | Bin 5114 -> 5125 bytes .../admin/locale/pl/LC_MESSAGES/djangojs.po | 12 +- .../admin/locale/pt_BR/LC_MESSAGES/django.mo | Bin 16391 -> 16400 bytes .../admin/locale/pt_BR/LC_MESSAGES/django.po | 7 +- .../admindocs/locale/mk/LC_MESSAGES/django.mo | Bin 8257 -> 8290 bytes .../admindocs/locale/mk/LC_MESSAGES/django.po | 19 +- .../admindocs/locale/pl/LC_MESSAGES/django.mo | Bin 6604 -> 6653 bytes .../admindocs/locale/pl/LC_MESSAGES/django.po | 34 +-- .../auth/locale/ko/LC_MESSAGES/django.mo | Bin 7656 -> 7635 bytes .../auth/locale/ko/LC_MESSAGES/django.po | 9 +- .../auth/locale/mk/LC_MESSAGES/django.mo | Bin 9528 -> 9741 bytes .../auth/locale/mk/LC_MESSAGES/django.po | 5 +- .../flatpages/locale/eo/LC_MESSAGES/django.mo | Bin 2162 -> 2159 bytes .../flatpages/locale/eo/LC_MESSAGES/django.po | 18 +- .../gis/locale/pl/LC_MESSAGES/django.mo | Bin 2146 -> 2139 bytes .../gis/locale/pl/LC_MESSAGES/django.po | 14 +- .../postgres/locale/mk/LC_MESSAGES/django.mo | Bin 3835 -> 3717 bytes .../postgres/locale/mk/LC_MESSAGES/django.po | 16 +- .../postgres/locale/nl/LC_MESSAGES/django.mo | Bin 3022 -> 3243 bytes .../postgres/locale/nl/LC_MESSAGES/django.po | 49 +++-- .../postgres/locale/ru/LC_MESSAGES/django.mo | Bin 5112 -> 5123 bytes .../postgres/locale/ru/LC_MESSAGES/django.po | 10 +- docs/releases/1.11.2.txt | 3 +- 45 files changed, 371 insertions(+), 337 deletions(-) diff --git a/django/conf/locale/is/LC_MESSAGES/django.mo b/django/conf/locale/is/LC_MESSAGES/django.mo index 5bbc5ec60863d6fd29c7ad5e9c6b09aa572bf425..5661b2bf6786d88eacb75931b0f7ca78a10e0b1b 100644 GIT binary patch delta 1147 zcmXZaOGwmF6vy#1Rx_H_4C!PxA!84wl1MAd#&)GtT7lz~Y3LL^78$h3MFjt7mC(XP z6g`M&;Y36X4J`^JbyJI^f(VNiCPj%PM1>LT}P1opaCs|7R#LGZe^mhrM%V z){rtQ!9kpZZ*c>TVgd_W&6Z*{MsYXp!7f~m_pub8J3nHW_y-nYAnjX>g~UJmg!25#m}b!$wpAov3?H zVH|rr25QwcRBN(WfjNxgD^vkrPzNVaiGHD8_SeP1c7HvBDl~@bYy!1^J+8s6sE1~7 z6?zvL)G^575*)*oIF0%TVjcd0CR{;$8dXpqD$x~GqC2Sb{Wuo~P@i%bRlsXh!5>f$ z9Y@Z4Ht8m&@i+^1*l+O(3=*D2{bc8{6tANaKSU*Zic0j%t&gD2jiU;k!0iFE?^urO zI?Zk}zq3pG`2J)62WDSU7fv~UI%k}-sEZ3SzD2105oZ+DiDfR1yLdI~TotMV8&Mrd zN{_(~23lxFZESURpb{K)o8+AdCi#;qL?nS-$GOCb17hiSWaNb7Udl%ChWEtq- z15}(t{cMj>g$<(SU!eAlxcCjK_3u#gpD~Qz@=r=;6QSC8^`>Oami$2drLufu>PRSm Iue~Ms55wG+FaQ7m delta 1151 zcmXZaPe@cz6vy%VCUY`1&3}_H)ifa^Va8MwgX4BAL=DmzO)VWrD3GF+LC`bWL_rG| zAyg0%r37KUMFd4mw1^;#BB;pCAVO9YfssPk_t(3t&pG$sIrqNzC-1D}ote=J_pC7{ z-EB-Y=5Y}53I*P zpX(P4_ZhQ|fgPxV9jJz~s29m$DURR{ip_pQ0Ifego(hQ|HQ|!n0yTNlbypFyoM?~gDUg{RVZ)gXHom+QH_4ZR)=qn_1JWj zGZ=3h(mK9>;eTMv7u112txMJw>niHtqG8`s)cPuG2=x+Sn@4RPL+#s%dV#&D7ig6( zL7G4lU8sdw>k(9eW7ZR>r#y)|;Ec`BV}SfJ>c&@4jZD~l(t6!`6Ls!w%qW;9(8l|y z{2}URdxUE2IcoeBYTc~O-(flV2h{ii>Stc`9#{X31@}baiIzm%dz!i!aw1;3I~R1C Ky*vGfivIzyXO~g{ diff --git a/django/conf/locale/is/LC_MESSAGES/django.po b/django/conf/locale/is/LC_MESSAGES/django.po index fe2be6b97c72..8fc6d23a3ab9 100644 --- a/django/conf/locale/is/LC_MESSAGES/django.po +++ b/django/conf/locale/is/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-04-03 15:51+0000\n" +"PO-Revision-Date: 2017-04-05 03:16+0000\n" "Last-Translator: Thordur Sigurdsson \n" "Language-Team: Icelandic (http://www.transifex.com/django/django/language/" "is/)\n" @@ -299,7 +299,7 @@ msgid "Syndication" msgstr "" msgid "That page number is not an integer" -msgstr "Þetta síðunúmer er ekki heiltala." +msgstr "Þetta síðunúmer er ekki heiltala" msgid "That page number is less than 1" msgstr "Þetta síðunúmer er minna en 1" @@ -1153,7 +1153,7 @@ msgid "" "Next, start your first app by running python manage.py startapp " "[app_label]." msgstr "" -" Næst skaltu búa til fyrsta appið þitt með því að nota skipuninapython " +"Næst skaltu búa til fyrsta appið þitt með því að nota skipuninapython " "manage.py startapp [app_heiti]." msgid "" diff --git a/django/conf/locale/mk/LC_MESSAGES/django.mo b/django/conf/locale/mk/LC_MESSAGES/django.mo index e331254c404b85c1a185e1198766e0f7b1fd468d..23efbe6ad6369ad1f9fbe0b29128273092bedc46 100644 GIT binary patch delta 7765 zcmbW+d3=<`9mnyBV1O8|5G9;>2m}a(B;5BU3K6+f1W}GGVIk3E6OsT@wF_8~dZK^@ z6>TY6M2e9RNI4=@K~yZ8dKRn)6|ZW6DkzBB&v&0+)E<9zUi|hwGtbOC^UO2Pvzr%B zeHydtR807tgu1I8X-JH7tua2%xu?mWZ>C!3s#-eN0{?(n_$n4--B!+J<2+QqYHWi? zaUh<>e%PCX{V{}Uw~>!VxF4H17k00aNu=U9Cg3^$fNRKTG;nS4Ld-OKn*+_^<|y=N zHx|cX2(QLH7{DfNotuIs<{>QP{H|lNa}zkQ3cKQQOu?p}bE()1d*k((i7T)j?!^o6 z7&gG;7=tIUE}lXiKZ81c7VBdjI_rYsF_!bY1TtFf7N`c@bO8IHR%9?f}I`S=8`z&5GQZNM$K9+#o!IL$GHj)?d4|gtIosE#?8# z61{`h;y-WT`@fIBC9#sFmuB8h<#lzugqP1m}mzsKY&| zd-xl?7`G#nbce73eu4GzTQipX#Kv}wu`YJQ9PEK=zY^==I@EZNTm3G~pu8V-YrUaz4)-18|ax345n!sv|!^bclH(C7-)cN+|lY0L5 zlUYhdQMy0iY1GnugBl>An?Ip8coF4P)Gh0dbajQOiLAxw2~iiY$=r&%fSst7dd@r; zJ8gOU5>i) zdr=d502}N1f7lK@g__9Ir~$Vl|J+`yKY@CKok4B33A~Liz+7ap+-zjyyOpTNtqQf0 zFQQiLJygHeS^i_3iD5m@!>!^PGl-hu0*qb(YWFTjUCFPl{t46swqPH8#_B)Dk(5s& z|6Jeh++NJXF8CH+igkK$|A&(q$y;P7E=R4vLDbS7#wK_KbtQjCP3VH2{^J>knrKT@ zeS6drceVOVY)rWyUXIx~3hza&@Ebi@e|3D13f%wCqB@*J?b@%*^QaTV@ET0U zWK6-Ks4JX_dckZ&O>{rdJ9fByvgP!m6ZI>8b1bJGp*>zkP!qjLZIlTpVps1xT{gA#M89bbdm zjGIyI_E`O4^KYmVe}&p~acltD64kE@Ho}3Z^Nq#?&hMs^NyNFRl~`upY2Ir-U_NL* zWIk%HH=i&!p>FLK)GgRy^}Efz7}oRqyft{i4je-5<_JC;+)TM4 z-=Q;cCwkassQ&|G2#%yY4OtAg!OHJqD&>yD{4b?3!+8E%P%)E=WV{u%yC1RgPV)$A zFPt;=(oi|pj5ibTI_i^fHO|F#SdX{OdVCzim_5SZI|oNl(t&B0Id?Pa8M+!4;sQK` zx{}NI$i=&GGM+%aaz~BwfAf{0?(KXlFGVf+9jKMO8?^`4nwwBBxLsi~>UaQC@paTD z`UcgpQMUh!s1>%PJOVY)bX2<{a|xzVUXA_mSsaUBquP(W!aqJ4)o(Gj$M7mLYEWed zUc~Duzm3|IeMkEf8H1YW6jaAN9D{e`ZTJ_|IFqmR`{iLA<=apfvK+OCR%2V-inI^A z8Zxb@IEFgGcX$mpWOQ{5pk^GxPPiDkjqXX*2|vRZ@mstg#<>@`>^+oU8ppVlmyc&} z;f4vm&tJvu<@g6zv){BErgJvk`=gkN6DRt=YJY)Aly~3?jNq>@a4kPR@GK6(u9N(4 zxjgJec`fP!{)AmIg$=_4zcS7I{d+mPGm)}dD5L(~eLtvG^M5o*hLE_#qbKSEzyV zZt&~pq53UDwO?!X8?0Pu<-MpkU=8Yx`97-O7pU{aPG$Y4aBmt;Wd(4~G=IRA)BP2A z6fdD+HEN*Ot$ZAFD4)k6IBkaC?-A6O&hJqDDp7mt5bA=CVtxF~JUt`qU)gt5Byr%P z9KS&aREKWn5Y)`aV@I5cYX3`2#Pz7-J1~eZA>XtvE8vem8+GDR)Ro_A^|yz~=(%5E z2ezXI+=CkM&o~;7qb8I!lSdVYV>>*DdeOAz?KBbFp*GiiY=Ijw1OJGN@D#Sel39NL z@CwUpMICqzlkuOZrAy#)bmDf{4SQHQ2X%a|l^5Y1l<&84i`nEUcflOojeo#4>^v6R zy@@OI{P)e{7b_J9@jjfI?=RVBs3ks&+RX_!`fsq_s1wh?bPS;;_%O!dX4DezKyBJ; zY=ti)b8sIa+s%!;Nh{3#e~L`BA!-F)#!mPNYS%U`@K4Ylb!&QIKfD69$(Eodv=((s zHsS<)2`|Q0h5m%PV?5=4sP>mj&hI9Y(LlKvhd)DI(Na{02d)0Ms0nOAP2f4~gXgh7 z_6#{!j)m9@+ZOrluE7D6@5Xd|0kiN+3~Q-Ui~SR(qb4vI(=Z!#yaYAlg{TQFM{Tav zR(=|_S$CUQKDjwSwy2BIc#DXQaG)CtS5E-uEC zF}!$iEiNeIhb8q*=lkvIE%5s%Vm<1U%}!>f*%$RV4qd?dE0b*%SD~)>dTSUk^RWT- zb5Rq#1vRl{*aTOa8&E4%i5mBL^A+<9#|4G#X)v&eM-t1y_ zH~X1G&5`CpM4`UML^~g#`lb(i?gf>?*Dl734)UBFA ztRcQ3l$ulD+B(jrd?EQeaR>1s5l4BSp8wk@D6tvcpNVW0ER8Vv{&6ko(3yCV{OiO3 z^8HYc*b{{IhmyYQYm;6$`d;}x_9e#E9xy-A^WX3%m3r>?5-W(SiS0xt13Zt%h;76q zViTdHr+~fZmJzz&nM57h{|@&MtB6?2kD<~bpXiU&ugI6``L9jw$^3)RkBY;@38E#@ zl~8I-=mk}q-nPsos5hF@6+Y2>rXL}bi3UudHWicUPyCZOK!oq+qYn)R;z*(~`4sEW z6Bkj(`^B9hl%Dg6e*T)zSmG(-k3<%+oVc9$fKa;2$DPF`#4m_pgi=}z>z_epLbS-& zFDLwz{1_sMe1=VAA?1eT_4DZ&;#b5LLMfNXvUan`w}NrAU|G);_tvi@(7 z>1zY*B;SjCx9CBBDBxJ44#AHW*N9{FiHFJ0Bn}cv&k}k+^s{l^B;SqvI*btKh%X4G z5YdRZRt4Sv-pW{7fO$k);ws`oqBgxuW|tKsfzJQ^PbzkkKVsz^+)BJk=y$!+OT=j6X`+PqEunOpc$BCnx>(yQ$(Iv7 zh_8vw#6v_X@h3v*W?lbqKmWt$&n(y82HHqDhWsN|{~Pn~=u!V9CJ;457eYz@n=%UP zVG6!RyhGHc`^Y>_tk(Juq><8l#9|_e_>Kq=t%*S#-;Z;MHso)`4)`UGCoUq|Ql5@V z9m&7tXQQ9}DJv}{<`I_>KPQ?}AAW(%Ux_jzolxpP+)gyHjx$tgDS^6Ql7EHx*y=oN zX8BVXXZZ?DCUlYU#Ck%h1!L6p$JQbKLLt)aWIb?hwATM7yodZP#Bt(MqB$|x+LDc? z&-qLwnh-~$H9vmnZ$PCgeWavzE)JCCrIn_5vx(7bpo1P(N!kq z=?#3$C(*yn?b)H!-jFXlP#H z%wUPGs32Hc>XqdMLSFa(c|3Z>Aum5v7Mv3-so3B1L~LBIZdtv(to{Rf_Fq{&pn2M) zKxuw~Hz^RB6D%m2UwTu%H)#I+A4|jL6bAAOGIEOwE6xn4Y8_r(^H|N2NM&SCWJjc` zW=*7=Qu%)^His)c{o59)rr}mTcSSpuXLylS|4~;(%gD}}#XpW4sq$(TM=Jlv&8W|< zEsfrd&697cpHg14h`!r6U3I$0XL;mVYRWlbrAG5=)~vkgiWXtjGl8nePUgGJpSR9X z?NMJ%YmU_1j^#BEY1Rzn(Y89Wo1S|Z-@nTLlUx=2+;QqF<6@Os87=d3kLqEn2X zL(8OjM7B}4U6-xv*}+_PXd*gU~5xd|mRKNRbI9CfF#nw0lTi`})iC0nW zV)#=YJ7GoVe6A0fFe)-I1gClj+${4QEJ6KRbECP{++ps<2-+RMhw&=*!nVvR9dmIY z-Z0Z@J2#H=yZ6cT;6PX%=i)H~BXKT9;RZ~>@30Y8jBu_lw#Q;P3|TanfdQC>#qkN$ z@rkJ8Q?L}ifV!ZW7~~`KIvK6@Tc`#F7=$0AR%9EN!NaKflNgLYU`f1+TEW}MwYW0O zu0F=0j(0~L?~m#~7As&j`ZTi_$f)5gjKc+1{v6d|4{FATuo51{YIqTwpq5J$PezT` z2Gu?l)jtin*RD6}LPwz5PmW~$wHcnXhPl{)@ z2F}5W7|rG9;u2hr4Wpb}ir-)xoEXi9#lmRTU%T}N6;-hzXP2p{B^r!Vz}Q zxu|xFP`BhgE9YBz4VI>U0|w)EEQ@=s{ut_nXYm8PfYbH-&*quafME&Vl7wS0<;JK9 zwL$Ivj;LGJ1NE5BMD^Q*YQG)Tey@21bpa<(D|FtxXveQ$ke>gWWHiuU{tBMqL~o!{ zsDUe@HeU@h*6N$1uDAoXz%{-8=h|Vn#;m`t zd=?d2>iJj^^H5i^4mF|OSOE{ACVIx|FQJz9n$_P#wfh@8VL9GTov}A+W#2}Pw*t#y zK@-+Lgv?fJa1b@2<5oU{dcH5CI+RHE1_;Hnlxv_K!x+?(r=lkGJZhYmP$!&aEgP%HHgYP@_5 z#npEFbJWCqMV9%>4t$IL6+kW789RO%_4&MxT8XZ_hc)m(jK;wjg40pA@=er4mZ8SU z$4dAS>dL?Hj{DqRGB>F>fCq3VJ8M4XHup{t#j~ZE$DvMepV`wKVNNn%HJ70JtwWu- z$m)-om;J}N|98k}(^ceWiW=5MbxbxpqfY!NYI9|p&!GCv#`2hl>bC~fekX?EQPfKO zY+f~QNIn0zt>RDfAG261uVauIY?epe+Yr<(sA2UHW_|R(C+v8<)hD4gZBzUtfJYI1 z+EjnAb$ekIzKyGKG!9d_t@o2^3hIZ&B22~2NMHXqROQxuOQZ04tc)wMI&Q~0cpPit z?^dqT&Z}?Mj^|&Sp&u1;kU7j8WoF_4j_2S4Jb+7Zczf@=9>810M|nAF@6_jQvlKVu zWE}K>cY#N766KhV-i5r|kzT{8xJX5R?ApnDwJt+e$Q?%A+pn#B4z=VzpqBCyY7g8p zgIEsj{;H^ckr;)IQG234s^1uFfRlV=!pY>J2HJvZxZgaDv6QbP`_$Fw%xe?-pxQ4* z9WO-nJB9V|njH`8;?+mtBhCXUczaZ@}PI3LR7~h)D@mU z4R8UqhpuC7EdP+#J`wfiybm@0BiI{9qxx+_ChT+j$TXnh6mq*>*yYJ_l)SlPi%_qdph?tZb9Aq=Dob1?dz}!<+E4~Lzzvkc1;4A zg;Z?A#@M%y_apITOrl(bP4O1$3gY^DKR}*Az4JfBWc&%$KfIs!`A)-l%1@!{^RW(| zKo-~ifg#$p9oTugmwnBVSfBDFD=)@K$_1#UJB&KeBffvmp{bRnaT15q8uqUtAEd4`qeq27SYu@Y`T_1lFy;RzhbEjf$v*y>Sl zype;vD}Ms3&~7ejoRx!Ee^qRvVkjO$-VrY8UtY&7EJt}Ns@)9KX39fNa1EBi&&)lj zD?5VK@U+$chHCEydsaeCJaRDWA5A8O3UwHSVfdsqcoj1!FToTH9O9jzCF;bfs4MSo z_5D!K`*5p&1vOqS>VzwheeO1)CKTu!>V3DvP%oBd$UbweQJW?gtK&{=h$rz`ER*iN zQfFda${(6vS^ZV4Lw(s{-ijrlPS_d~G0n=psn*~%t5}3D(_odAlX*SPrQ8vR;xXKe z%||%LMY!vji#=JUd3YY@;KY&MN(HeqwX&fYi3u2s-H}`Ab5qD9P%#5F!;Pp-xC^zk zM^N|lIO>J+BQmxN7{%?vQK+T;68-J4mfB$h3>@uk)_BzTZBe)6VQiu2e>fR!uEnU$ zRft;Bo!A2}U}a1h<4ve5>O{Rz?T46|sBxxYFwRGvFb~y!oz-tcO<)hM|xf z(~itK?1j-7nCb2AIBZWj8MPugsF}Zkn#d~D1lFVaZ9|>#0%|i~$7=!nz`?ih;&|So z)IT@DYd0yI_16F|W_!Oz=a>u4JTo6l(Qd7|$=rdO$UZEIN6Zsgn({f+_`jegb{n;F zfjORGIjnzqDx#>+37VR1%~aG0x})0lLk*CDTDgf>3|~RDn`zE77n$#wE6ug$$3Dw! zGmB6&-ESVZ`X9|J=C7!U{AuMfPkN782(r@t?*q7K6)f?jx|2kEf;Y4O_kiy+GCvZ3 z5)%ofFoIXO+fDpJ{6QozP)Ag1M0`n{A}$eHnRf^t6#pj}{4bf$aTcK$+1=EPjNSu! zCy;!uh`+xQW$&4(j?^ZSsMCFo!~fySgr1BfLLZO@L=Qs$dC4o@jUg5gm5Ffb)*^e) z|2yGDe3wva#<6KwC4l=MOW`pJ^NCRMr3rn#?xy8rp1)hb6$GCHe|n3*4B?vJ)Ba8G zklTU-h=&R8ia%+Ob+&v-`n;yj704u8WmOEL{DhU;n$vL|ahOOawCT~xtStT}^o;LOfpn7?N;Ib3 z{rEKT9ijAZ5C7j3%fE0>-RtB(A&%($x5#`!3?c5OiVT`X;iy$qyn6uSiBGLO8CzIh zG_-simS>Dogi=*g`TK|jWk}EJ`rDBS_ZJz!8t$aL(CPxHTW|T;)<117(*Yg)mH9~&nU0;e~|czs6-=P`K~AN6>*zbO8iXJ`|eWEVq zNjQ&C>aRc4eE=tjdgLF%xlN zoqR0ONtKog%~e>Ac$Fwk%ptxdl!86nRlM%yeeNGJJBV&X0dasBPP8P(6EU1Lk*G~< zpnQYaOSBJ#CGB@B8s>-1=25}~m%y#3G2X!Hh^mx=f7;6|)W{U+2u zIP|{1Ky)RP@`z!?J|dFibMbC!LZ%k6gcwBx5=!|*b0S*>CEfoBIxE#TyO95yXhM0M z4&0OWlOJj22s}$XMWhi*C5Q&}JC4;cil|2P(LquwF@rcyOdx#4`IAh8yXhL4V?O2jV0MMlMpPtO?@n;jW3A}b?1A~UOS zfB4u6g>S|UDPGvK(fSgFBT}veb!eQ}Bq<`PWvis*_#{$rT6%U)VxI}=nb~90bFwD1 zig+kJGjnuyL|Vq^5gB76+KllR+mH1Z8;;H#nN_%`&8C`#-wzyJs_^e&yMqfeGrI-< EAGXC1lK=n! diff --git a/django/conf/locale/mk/LC_MESSAGES/django.po b/django/conf/locale/mk/LC_MESSAGES/django.po index faa82d20006c..5d2b04202afb 100644 --- a/django/conf/locale/mk/LC_MESSAGES/django.po +++ b/django/conf/locale/mk/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # dekomote , 2015 # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2015 # Vasil Vangelovski , 2011-2013 msgid "" @@ -11,8 +11,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:03+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-04-05 09:39+0000\n" +"Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" "MIME-Version: 1.0\n" @@ -298,13 +298,13 @@ msgid "Syndication" msgstr "Синдикација" msgid "That page number is not an integer" -msgstr "" +msgstr "Тој број на страна не е цел број" msgid "That page number is less than 1" -msgstr "" +msgstr "Тој број на страна е помал од 1" msgid "That page contains no results" -msgstr "" +msgstr "Таа страна не содржи резултати" msgid "Enter a valid value." msgstr "Внесете правилна вредност." @@ -414,6 +414,8 @@ msgid "" "File extension '%(extension)s' is not allowed. Allowed extensions are: " "'%(allowed_extensions)s'." msgstr "" +"Еџтензијата '%(extension)s' не е дозволена. Дозволени екстензии се: " +"'%(allowed_extensions)s'." msgid "and" msgstr "и" @@ -1158,6 +1160,8 @@ msgid "" "Next, start your first app by running python manage.py startapp " "[app_label]." msgstr "" +"Наредно, направете ја вашата прва апликација со повикување на командата " +"python manage.py startapp [app_label]." msgid "" "You're seeing this message because you have DEBUG = True in " diff --git a/django/conf/locale/mn/LC_MESSAGES/django.mo b/django/conf/locale/mn/LC_MESSAGES/django.mo index 001a59191abce769fad27df23a6969043511b1ac..ad9abb75b2e0786282baf8348a7fc06a886db4db 100644 GIT binary patch delta 7811 zcma*s33yf2xyJDoh9rbQAVh(X$Yw|&ND?4Gm`72NQPEpuP9TtIauSmOrnEhv7fXwv z2o9)K>Och*0?B0%f}jSe1wDakfmWp`Ee?QIMNwM&f6xA+T%Y^gd-ub4e&1Sq?X}ik zdpHN!dm>`hsfgga@r@pEY(pZPOTyR~=eCevYNcA|b|yO47I$F}Jc=dQsGW18aRKVM zy_k%D#({Vd`(Yn;_QwFK-Lrf&$69RRT+scIB!P<47?0ohJ6tn*qk+2$8)K%~#~f%5 zH?KpFcH?mZ2Jl9#!5nOHm2=3o>CP%F3)IVraSJL4wX|EBH#6RO`&Fb+S*pk~&P(^W^!up3@s{EY3{W0ax349n!p1XgHK>AZnF9vsPXpW zMm_(vB+IBM?(TQ^C2DEDMRgFL?oTKgn^I0eU9z6Yp{@uuk#!gz5Oo5Z%x$O>coDTy zub2nJ`?>yyNpwbUp*s4Dl~08mIQJo{w}TZ4Ms zcA{4D_ox*+i8?N+hyNI7Vo=ZXaI2VX=AmY|2*YQ9x_g(S&SbsSZ$M38Gxo);R(}Ss zrTi)K$7T2A@?t)A!(%uC8}{P*4=1^nx5!Xjj#`0(sHJ@qTi^-QnS6|zP=hT0@r*%D zG!a$b3AMymTYV{{!_{&Ot5tQq+WMP$#w@)&8Iv3XTJl3+7qWL~D_6Z1-2xCA@^Z_FV$kOA`zBCD9TNKn*k!Tj6MHFda4H z*=D}gm!KwCj#`<;wtpGwCS8eIp}(X0{Ss608`KJS$PQoPpzBSdnG8jBG!k`Fjz_J? zEZd)lUr;WsaHSq(e0Zy1_O*g==Z)K*2<@)z0(ShSo1JAYwrRFl* z|1j!id>+-V#_HcR-$xDn73!vo;RcY2sN=d}a~y~o?|O`9d^eLM0dGgG#NFnv%~j_8 z=7Z)V=Hup*<_2>U>e6mTU4k7}zsKB%K|Q~(T7%bY$05|+9Ky{J&b@;vlFnF-!PtkEyXb^+Tkatax^am z-VrVdRi1#l7jn&fGhmjRKgBHCEx`a*<2~4E1aDca#5Fi>BwzV>a%9l|p)mOxe%(;< z0jh)EJWHIU%g0{00Vm^eq)(TAt#fHuj16!dYQ-MI2;7KT^5<-SCF+G%g}Mj!q8|Ih zK@we~MlNln&|tem53U{TMx&e`dN=^_X0-ZUW~?C z)PQf8XOQ+m*Wh}79bxGPzOM0hgmDvI$$^E-b{u7=r`G@{Nh3@O}IeZ^z@~ zxB+qOczywKP2Rz$D1SbIRlrIecj7RTg ze}%G8_r@^H!f~i2UV_{3SD1iji<2w=SgCx@cRrep*q^Z z^mI4BjGFNoY>5|99X4fIim)|i<1$poHJFI6qxw0GYX2!}g)UjS@if1_~x>NBWwo?P_fbXFu5HZ`gIVMqVhq@X2q0W3PHo&Pk4`-kz^eR4qhmp(eigNgU zh_yHz7v(y4ub%&1Bs@`W%pCvA;!)I@ev7&Uk#qflTchrY-l&16q9$B`YQGFM@O>Cw zNer(f>V&qVR^S-&fVd$Pn(FyqOQH_eqYiiuyW)OqjUS;_q5-ET<1hu2FcC*s{S4HE zZbe;!yHWjZ#YlV&brZjVI)RfI&G_!TReX(_NYs4ac+`N&R_=y6un+dbYj6<$9C^99 z7x7Mv&iCI7E3iN1mvAUv#39(f!2g@m9T<$H;%SobnWM`4u{|EN@)^`hoX2R4y4C-o z(gHQX1hWHPNjU?5hoeyao<$wkkVhjEV^No6&~2>0&S)kT8n_5GP&w+Ac@M_mOQ;TB z$EzdwhXibg-xczkf%0;$R<_L3QG3&20m~IsX=0engcbTiL z{t5GW)P#1SCit?I52H@#Z7ZL){pV08c+u29q3O7&pe6CB2Fch6J7ZH!Lrt)cIl}5E zn$yhrW|^seb^Hp{P4^J$gf^o3+hGP@CDEDGp$0yII`BR0h#w;>9p2Q@r$ld}6_x*m zS7IviW_Aw~-xAtdQ=eo9&ZFFz{MWdH_!|*JdB2|jpHtAr%D5xMXccT5Y4X~3i5$?C z_&xbs!~k+V1-ijD5SqNU_SDyJyGV4uynxxng!&!khkE|CEcII#l6}N|#EryuB9jhY z#ZyEz!Iz}lL}+VEEF|tGw2GNTL)z=^uOZeFk(8f6ZHIiquk^3T%NYOrtrOWfLeKP@ z#Cbw5n5zkGaYS-B^FRJ#xgNRcmX9%Y&G>y0e)TpXuivL&r zapWECfGk`>T@i7C(DsT?_;Wpl zCWVXKcntUv`8c8td4^4-f^su*{o2_|JVb0JwDC3JdRV);N^Dz%`NUPk4Mbz2e)|K-ZYw;EeTiQZeHiHPxQ@{F9`V<3=6~qdZ%@)o zLp^6QB$L@DtUq3uiJabhpg&DxG7uOxaA zUlY$0j}ZFmeuL09`zeBo~R?b5!&>>Dc4~X zcEmpt?-KRfy(G^N4`}@d(n#A$Vkyyv_!p5wBoTwyUyJjJWb&V3XZ!*u5>1J#D9=P~ zspQA}H2m40vbJT!0^%Ct7eq_ygRhaiLzENU32mK;yNDKc;4D?z7Ej%;$PW`|tj@z$ zmVbsZmRDhWLMItZJV|J4OCOE=zO{?Lco5xfuwHn3xYqw+xsrS_ahe!Gv?c~yThj1$ zmd^yD1@Y%_%?}^?ouF-}K02m!Ey*d*Pb=%_%`MI=^8!`ZCl@NnnwHmS3U%GR zvhtkLa<8IzL8&*dptOu$OG>=l3a@lQAW#sP?+uz$JU4GhNkw@+6Di6Gxd zQ`2dD%qq;u%`2QSC^OtPqbebzy7A1Ma<7EO-kjn-Pp0 z6y@fX>Wm8W%F4X*{G5Q-^MBkQKI4E_5Gc=^pI2H{n{_@is&{(NEU!oQn%x5uvqP&x zm38;lEe%zLDnq-88ZWe=Ze?g&Xm{Ppx|QCb+?vYeb**?qHn4Xen>FD}lQQ(RPa zVZgaI!5^>i(T#OWLREa;sn5`EFSG-;)h$1|QM<^iLe6FZZYNXUuCBI|?q`c#V02wX zChhf1*Tv&tTVd+!Q>OQS930xpkjuCSc6(e%ty!iw#=n2a=y7LV?DEUHtID1m)#Tsp C$Fi6J delta 6897 zcmYk=3w+P@9>?+TX2zJ?HpS-h-`L&Q1w#|LRm^4XDl}OUwlXPo>MtUdjY4i|m0aqO z%iKajPN+K!$(V;R_%g=g25g1Lum#rkITwu`F%U-}ljia;01Ge( zA4RoKM72-CsyG#OLo+ehPv#Xen(c+C16E@&ZbHq-2Us0HN7Wz4YIq8(;04qSUPbQ3 zRcCZfF$vY)8`VAtb^chag@x$X$flCffkl{v^Q^oRb;3T>h!0{NJdE}545p)|O9Rh9 z^_PV@z8mWNY-FunKh%wmL>>P`4D+we@T?s;8{;X@w*x*zb+iv_U>RyDj-v(~*2K99 z7>TdqES!jO-2QA_f^TE-Y#ZoNdst=OEa%WkMC8ioTfAG2`} zvXNb#1n0PxOF@-$P)juqb%&G9xu}_1iRyP3vM=3HyaliN$>@X{iQXDTV_nLvkukY! z48cOIicg!Jyw4Vn^QiGT9Rcm*Q2UAcN zoMFCdA=GccYPbVKalh3cL0#}WdtJag(WqM0`(kywp# z8frjUsNLTMwN!mjkLgU*d0SA&??4^D-~0k~14mIabkaOy?dLIA&;MmI>galT1XdP;AZB@OV|4+zhYRga^ z9!9Oz39GM3L7S^C24Vs-R+o%ya5otBcs-5URK=*7T829Bd(>ll*$i&smFuBj4M}7) zvMkidyP|e$Pt+acS^XH)01B}+PPF>f*p2c=OvOL308=Qi%x(#G!hLuLR!?)T1NKZ~ z{&nX?RA{Q_Vr?uz-N{aw!Q1Ih?1!4!#i;(4 zVGUfJ&isdw*=7fnp$7D|l~16a@3W{ADra~dgkvb>DAZ$^fSU4dr~yqu^)nrH!6I`W zYBMiE&FnjV%dA12uo1Oex0}0Ack(gn#Sz-l8&D$h<#k-2CxVbzpCi6yA>-a{R~8zb;A zY9`K^7tBji&;M1cxMtok1KW8g2AkE)ny9r6LoGp+)%(n*SpJ@{_GGJ1MQz%axFCQ> z5&f}L9AoSDMSUB0LO-025$(Mn6pOJb<*lg4=>!hPc)p<{@Of;4=TMumW(RL3;!sad zJ1Y-Fo=o?!m0#P!ZWBTJBQ4e`wfHe4^;aN)NT*x z>g|D0)bkyMTB3N=%%r0F>2K}1sF@k(C!@{s0_udVyp#i@Q)GK7(uz7un6b zaDP<$aMb3VhV^ki>il)6es`dL@$5zQAIv&x;QqQ~w2OVHk)|QD=my|GoR8}00_wQy z7>uEJc^!wNF4(|qjXJ&$Cg4yjPe)e6&B16qf(*p(ekT)7MU}hxWr|U#kE*q!_G1F&CTxU`n2m8*gj2Y_+fbfy zF0+sKwc3iB;vcNsILG_Z*d2Y;Kac8QC2C0yqGl|xulL#Rf?AS7jKPK26t`m$`k1nzCn6o2M8va1mC;GSrLYE7aPZM-AXQYDr@Hdv}_Fnz5-mVIjwPsm zHscuFiMn310nEP|+LBR4CsesNs-wZ?17<#IfRj+)fN7}n=AbV4242J!xE1HKfz(gm zLEelEMGbf=*1@@hn16M+oQiR{9y8H5*z0&OMpDj0bu<}u{7a~T&bRU#<|@>2Tg;El zGSr2STKSxLWiaz!mj->>>cK6jDQ@nHj;MuU)cc!}(H(a{z0rE%gP4OF&=xGgUC8RV++n;_@O{*- zA2XbPgy3rAL2&Kw_r49YPi7~&z>Rn-ev6ukt7fGKG!Q?dBNItOYt#Wbr~wT_ zEy0th4ws<@v<}zbpV`C6tiZ=m z9XEW)J23-WP|iY4{aDnuMz4F6U|hs&onz(eNPPHxc*ojhhQk?nd7bAZ@y&CHrxl4hPrSqjKL^mrpx~^0xnJkOKdB5oajjK`Y8Vh`9CD{196R*NGRz| z#*57DC4M6QAezxp7gW-o_!sdX;zvR=_&UMzx=Dmm3bB(YBGQP8)S8S2s<$o4@AmNb zS0ePLk*cK;kxE?^Ov1nMd7_=_iCRQF(TC7~Uh;l&>?=2q&?b(gZVl!WjmbZUZxBkY zDL;+31hD>z6ds~5mk1{xLTJ}kq_@dTsVLwwVwjZ|B3-!5n~q;#ZCg?A`d-9iR;Q)A z)ACg~=Ovw7i;SLsB|T0NlpifGdGB^*rx9z3&xu?jkT^j!A|)PlbAa1p^Lp?vV}T{22vdboxh6imEnW$_mgN9lrkokxhPE@4YbXq{+uvOHqXuxD*yOp27HkKF7EuVxn>7y#4 zbgQYn-l)x#Ax+l(cOVm4UZev%a5v@oRu@3sddnB%pTzgXQmY$iev2zDzYf14&Re+^ zedwFA%F4a*8RfaY;|H$$FHwhs#$XQd8F7_(i#SI#psfw@KKV05QzDx3<2Z*<8l*qe zy#q&yCgi(gG!aH#X(pi;o6;#l?;rQ1N~Fca|A@+V@N48R5(f#T>Q?_d`9z|tDlL_o z|HK-^i$n-9i#S9mRr7Ec@E0%dcQ?rFB6>#cavBb@(66ejP;)RW5UJ6COncmVN_yajBjK?UZF3)p!Czov9(GUBn=NL z&1tc|a_Puc=Yu*xU&P, 2011 # Zorig , 2013-2014,2016 # Анхбаяр Анхаа , 2013-2016 -# Баясгалан Цэвлээ , 2011,2015 +# Баясгалан Цэвлээ , 2011,2015,2017 # Ганзориг БП , 2011 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:03+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-04-13 07:03+0000\n" +"Last-Translator: Баясгалан Цэвлээ \n" "Language-Team: Mongolian (http://www.transifex.com/django/django/language/" "mn/)\n" "MIME-Version: 1.0\n" @@ -303,13 +303,13 @@ msgid "Syndication" msgstr "Нэгтгэл" msgid "That page number is not an integer" -msgstr "" +msgstr "Хуудасны дугаар бүхэл тоо / Integer / биш байна" msgid "That page number is less than 1" -msgstr "" +msgstr "Хуудасны дугаар 1-ээс байга байна" msgid "That page contains no results" -msgstr "" +msgstr "Хуудас үр дүн агуулаагүй байна" msgid "Enter a valid value." msgstr "Зөв утга оруулна уу." @@ -413,6 +413,8 @@ msgid "" "File extension '%(extension)s' is not allowed. Allowed extensions are: " "'%(allowed_extensions)s'." msgstr "" +"Файлын '%(extension)s' өргөтгөл зөвшөөрөгдөөгүй байна. Дараах өргөтгөлүүд " +"зөвшөөрөгдсөн: '%(allowed_extensions)s'." msgid "and" msgstr "ба" @@ -1146,6 +1148,8 @@ msgid "" "Next, start your first app by running python manage.py startapp " "[app_label]." msgstr "" +"Одоо Та анхныхаа програмыг дараах командыг хийж ажиллуулна уу python " +"manage.py startapp [app_label]." msgid "" "You're seeing this message because you have DEBUG = True in " diff --git a/django/conf/locale/nl/LC_MESSAGES/django.mo b/django/conf/locale/nl/LC_MESSAGES/django.mo index 3e95fe340970627a6ba0a76cfd5108d0fb133155..cbf223d13241128f6a4033230fcf1da4e3e6fb48 100644 GIT binary patch delta 9588 zcmb8!33wD`p2zWO?mG~O$PrQyLc)=R8xqbyfCwZ(?o;R_m840hJ9c-1;5h9qqaz^Y zHQ+H`tTKvXTu~Ik1I1kxQO9w-##_|!S`|fco$s&f1z8uI$9nwd^S*Dr_11e;RjW_z z^nJG57k@Lk*;N+Dc%NnU#CB~g>u%CVI;+&O9_Vga-S9yij4xsxHtS(oMOcrz?h#DI z*KiCT#?d%}lX)0I)w`WP9dIvpw5+)G5{WJpypPHFy?4TDPis`LdSP?SHI6WjF%}r7 zp-sJFEWr@Y#$D*gj=d~v5k`#9U@iBz`lVUcEKXdB1Mz+AgPm;4%D~|`0vBU0ZouBS z2bNxmzp!UR-9 zMc5uoP)o5C)!~g8!%cWKCNb;=+=3f0HN&!Q#cg;q&go}aWAPA<$6=YwzjkYcyLQEG z#wSrz^d`>3&u}IdW?2>++qwd|r*)UfA3!bDr1Q4 z1(&1N@Fq;b`;kss&tPl(3R~fi#uls-8{104W|)mDa44$2i!JebRC~9X@`o^o{9e@3 z#J?udl*A9F;3w3DEqD|bV+YiY1I7sI!n09JbH2&1HTm_Z4qSz8@J4KhTTS^6)cqdC zJM{eTC2>0qjK0 z)Z@l|iSw-gvm`X4zoHs?-Q?d%RIsd%Pz`^H?eU1Q4fCk-Q&A&MN4@c~QE$vjR7cK5 zwYwHI@^z>VZNwx!|C`N;zo0sDH>$z=k$WA z$L#^sOg@d8vA0my^&IRy#<>{R^ITvG<`^AR4_9Gg1gPD+7B!MToANEF4s64bxZRW= z#Hr-JK>o2t4Pkk)8VBGjI0>5$W&I0COyw;y0oS5tU>|B~_hUzV6*ZDiP#tPA%zHfB zpgP(eRi2KT;(?|-7n8`3#wl2Y({LSXhF>1W{OiKEDA3v-#AG~VDzqN%b*MAyg6^p2 zzb~r(MAY>q*be8T9?Npnl%I#{&@R-#9!1sPXM8?RLc97E)YQIb{1|n?7pPtPt?>xz z20mVcX_$t6Z~|(CWvCa-?Wm6KMZVb9+o&Zxg1q*vF07Y279UAMQ<#ss(PZq5MW(`1 zRF78}t4(8MP^k zQ8TjKoOkeR@&SAn&)`|R5(njZH+U4)@h4F?c-8o+(aQJAI~)5YWc~9<=)#$(8?P`G zBF2l%`D;;|@lI5|U8a1$@mD|0H4_&b zFEy?+ZZuwP++@7Lc(ZYfaVu(Rx1p9`hbe#9xCi5UexEQEo-!w%LG9+}ah=by-oUQp zgX7pIcp+YZyKy>B=3A=s>rh|2Ey%1}yKw>@M)s|hM@E}7gqoSlCh+`glWe9yGjIoL zY92vN*$bxPyQsbJmGQ9gCu8%8-ubqu-Q5Wn;V@i=mtzn=!3tc+OUJ=o1##~OLprY` zRosN?>367+W>5Bh;Z!3}j6mw=tFckLG-jDcuH1%=yQ#i2QTd0=rJLtaGs!{tj=%rPz~7 z_u*>Xi8-fP)*k!_=iyd%?r83BSu?!dnunUQDE7iDP$Rtu!}vH>;jo$B`OTO~ey7R5 zgWCPRV((JKyw~^?E`%!E7k;ykN@jjnvsF|3B&2TX?b=GoJ zhpVwAUW@@;hi&mYRQ+b9-qY0@bzLuRqQ5`sB)W0t2 zd>hmfbVN1W)s)*N-_MwfTEfvLKN*{npE-;9e}=>?3f5uj>0ZSV-fVBA0o08`s1C124d?MsbNi4<05@2EpP#ri6 zbMQQqzX$cAdJsEf*STJU1924jT+G5MOvVkUO?L~j9jwQ&3wGiW(Dm_tCXtUCS&4Be zssk%g9g3nFJlmA7Lyh1XRL5^ZR>Qj0oIixTB(0xt5Kf-2f8AzpVLyBm`7Fe(<_i)} zpjC*xmaPq_sd@o*gI6#G-$8vmzC(2=Wuf=9q@p^SXPkuU&-qnHgc|q)Rq-(DhOPO6s65S>WgLRNDbGjE$Wqki3Ztg90rhRT6xGf)REM5G)q4sx zfajFg^ZzmlZKgL+BR^;=e5MNI51H~GQM@L~s6}m(2&(6opn7~Ms)HL*yZBnvb=RQ=umv?^+fjRA zCu&c%<9)1lyJIG%p=NH{Qs!S5E~Y>?a!?IaV<$Wlb;AqH`9I(ng6zB<;@*w&b2>}>36?6ExVU65u9(v6wMfyP|pFjR*|p*lLwIMrB+nyH1T5w9>h zsOwguI#`FgJ{BjT3(i4J(Rj6Slko=Q&BiUpt;TJ}dr>c_`%!!8F;o7u zG5#zG-S7aa$1j-^e@DHT-asGf(2{8ShZCJC{7*au`yqQXu@pa`j;@sVG#6HqZ%+DB z+(CRmv?2efp8s>m=m--p5Jk$EW3rJAY<1^?EaGX>e74{`g4Gzjo-+L%VYiv4NOP+)w1vz!UfmaUa2p%i2ol=teXU7ZX}feT;ICVlj%Bauh^5Ah@s|091!Qeg~EC6Y+@F&7NOb1Bn%`w*eyagW5` zKl8T*@fYGTVlc6mm_ocq=(x5?OXCH~g6NOyX41Ii@44 z$+ssx47U^45ZeeH<-}l9uYz=MLQmB3N7iwf8k3%zkokX^#3<9iPSV3kXPZj@#A2c; z!OP6*z`0h$X3}NEK0?QCLXYog)6U;WXOq4jpC`U2z9Mvlhz`U&WwibylrYCCtS0n+ z)+BQYcgt&#!@eOeU@dz=%)SW^49%3l*9dReIiO3+HBXpdl z@fUdMV}I9}RJv*CcJe;b*O~I0jGv%Q`5l-{93Tb|I`n^2reRC$gD(+p5=W2qByJ1O8R%$7r(|*qAk&j{28dDAL&=TWa4igc^wxK z^+X}@d!iHN@ux_w18nVSv8GZUrWhv{*YPB>=! zgTe4Bry|EL@Dld1N>SS%aq@MYsk8j&Q#6p%xV^vAY$0WX>}bp%iP@{e^%1)=5Q)-i zU7cOF+K$wRLV-|~J+?et;f$|a9jm4zwf>O5%E_r)?Omp-OZc-q=r41E%f{v=>gF_d z$+@rj8UC1EM`gP_9E$k^A$8e~IMMoGENacIK6*A(Ut8uxG@_srjoPtlf5;wk!t;p{ zhwMNo=2SV6#=XNnZ_#E%_TZuRkiq#QM!1jUckMAZ911aiV`B+(YRe{8)%pX$obqt3 zdno^{ROZ8*=uErJVc>Kk6mVje%4^(n$9HjCj;+fbW7meOoM2#Ojk;Cgta0p$`Z5N% zCcuvE(^!AE?1&Te#{!O(kyRN9*Y=NQXJo~~;Px2zMU(o7+hKfB6Mw|r z)-%VwWPImS7Wn;<3de3>wA{SGAFOv+7uPqjv%7nI@(6oneIOchw90B#{aD12U0j@9 zSeRq|%GLcRv`_y3SGxyNyYPPme*1OXC#3(|>ymzPKX>p%JAUF@+0~9y8Dma=afzK( zRFq#_yd*ywU6NCuIF>EzKZyP2k8n*)6W}-@I~-zRDgx}PSYTzv$p*v~DJQ&*`&_RK z-=K{J=~I4d_kN!J<9g-#3%Z?bC3h91oqm#=)P@<5MpF?9tZZ;X6;5_dxVDyOCFT#> zYXU1nCmWdiSwZT_ZkRgBKG~_#N$2})_k~GATRVY@H7ou8P}FTRc~7tG1}74z43sCf zSZyF0tgngjGVzBRB7T3B`{87}?Seow>V&Mx;b7QJDO}@=y6X%5eFF6PEC!&y+SLJ` zUp+>4EX z^YV)#?E-i7^gh09cgge&_u%wyNypmr_K17?^hK^cql-IsMz1Erv)$n{hPu;cB*h(j z-t3vbyo0x?=FK~;@Wf{-{IUAlUpbYl{dnT@Iuos^=lz-(LdwxO@p@@H*?L8}<7RY^ zS2;W$LHnfR^>+PU__ZT$gIdEGQk8~##_jb#QC$}7A zhijR9*FPZD-B(=MJlkIpaoiCl>CLL0vY5M|q{Q7`GN1$R^-mge2t zIAm5kAG>V)=~J49gHd3PfMB)*xUdb@rV+nbN3llW>@s0Y9Odui7Pcy{<0urugU z!Zo#z-xjn=Xf*1!pVPhV6epA&uFGcevfYt$3I~Mra@11(pLJdp=EI^g zzPmi%G5S~0xOvX0zARNueD*>W`i3Hg+ zxt3<^%5bE%KFH&)dHXjN+$-i4_b3gOIjfvV4TCESg#3YsL%*sV-g!~?^LZ(a&F7Ev zHJ|I$I3aiXf>G}I3-~`Dg?#S#-9U9Z+rSsPO8xeJIQaQNWEFZp9jL%>hg$EqLru^L zgh*B-em$J1MxvGDC(0^x?5I;-AF4?7D;jj_V@}Y0b;0Y6I~F$gxw{sP?rb)n_Zz@$ zt(J=`Q^U1(gA)h_s;YURMk5XrP9K?Vhp)D~esNw0t%AB<0 zM(VtzZ?iSL_1!T``uzKr+8y7LyL(9r?Yy)kcUm@24*d3C8H;B9mgdCV~D#vh>1e634ZsgE6Uhm~4ay3m%%HBKBI9_V&iB_{aih!ZGrrIB=TlX0RlW6A z)mt4swKw75o`mQ-DM`0mTq6=J>pVQs(z5O${ZL1hTGsF$men0c;6R*#OYugWjDJJ5 z8`IOWdf|Nh1zv^2@lhOsr%?4W`KKM`W3pvMt(hb`QQ%?<*2QmFtBsqmDdjtij~bsa z?l&Grn|d$eR6K>#aWtcHa4pWm)5gN{EvuU6TlbMD=0>O9men6!Ov76-9e3e{_!bVp zWZSY*aU3RMDKcr6iwPLSB)kH3zZP}>Dr|u_pk8PdHj9$Djf7@`sBAkT*EX0GzMz%U+S{Bc;a!~ma)KXQW-tb!EI@C^}t^mZ$;JHfLfBfOn$4$Z^xFD@4{C2G`7L#O!*tA z2Oh@<@C2^V_rHd3P91i-AU-8Muod}1r~!>a?fyK}QWc{<(^aT;yHWL@M%91L_y^Pr z96`;{yT+5|{zuqM-~SmB>gbzT0pD;=yrUMVj+0TFuctA~ln+C_@dO->g{YHq1!^Fh zQT=X3z4c} zs7a;! ze-sDeSqx$h8J5}Fh!gNx9FMIBS=LxA8pQnT%~w;Psa}W4xEb{(52FTj7~A8^sDU0c zTe6S#chL`{}d8Wm)7ZoG!E89+_h zF?0U|)X(QT0zhV3!cAxb>OG2A2nLkrh zF%{Kth;b6?!Shj@D_~rXYPSa4;bv63?Wp?CU?+SXH4`5iPZ>{3eg9vWg0GF=852jw z8#XhxGPXmlZ3=1$dYW?E*cW5xgt_0}ln+F0+M&2Eflm>m+B|QtbxB*Na5-l28+i%d zkGlUkcEi@ATSSkOkxyQV+b$DPjC?ykK>TS9XK2NaOw=iI^%DV!_Yc9KFSXb ziPjuX`cWB;eb9%R!quqVyA~6018U0e#6-Lcb^l)DcGQdPMlI3Ps6BPq+&^N<-$U(@ zkD?@WAbf9bw4NAWqfV#}Qc(v`wy^+vkuNp(uf@*fSE9b>&8RirYVPkcK7~1yA3_cI zj46+PX9}9<#YfZuZ7TFdo$>jo23MmVaFfZeLA~i6s5RY$TH_t4fgeEaffrCO_%>>W zPonzkm>)YQqE;^ws*s73aVY8lx(2Vs8&PZAw7{|!;Pto*kKrC%H_5X0;WQf0!sLtN zKgZ?Bhh%L=&DaqO8i8#QTa)z-CvH~F^WBKJ@&?3 zsCuuV9`rfd*r709e=w?jp~<_b%^1a&Jl}elL=x^nP2FBpN6(_B{IA#=Poe64jhfo; zQT5vuS=Ko0icN7Ys@@`0`^Bcb3Tw$`HQsScREH@h-_4kc zZ79z+`C-_E{5X6C^Kb)po)#a#!>CQX8}%Zuq1qpx#{8?`V+vSi>nj|9V~gW&5X4sG zBd8HaQE#*oHPBm8Z+eGuqq%>#@j=uJ>_pAfZdCpK=Ki5#=D#fkFHxWe9Yqc3BU)Ecixz4^W7{)6WJPE-E4 zaW7t`@BeosdSm+B_-B%jnj#0g;BwU3-;Nrc8?auU_< zV^q7(Py_u2qp_)-$GTI{9`!XisE&h}ff3XU-HB?r1J&UkRQsni$qe?irI-}sU7Q{x%qm&UV3E4nb= zFv-{gbpW+NZL)5rytlCr>VX-k0cV@?0jPs#24^Rv@!!Pzgbtidgpat2(3L~%BUTfGh=%JT z5@|%YctO-UNaAy%O{0CVeCRC!y;i^4DOO1lB)`Oa+;BL`TvsiT*^xbuWqQ8#1_sSY-0+kuI!Zjq2ZF z?(IRou-I{3W6HFLCYf||+WdzawB?dYQgp{uJ==`nGUCVwG)^d$X|$xp-OO7nc{V&bnv2P#!z3Go8)1#vf_qw_rO4JRHa zeUj))q>^8T9-(WF{-NvvI70LxeF>%#DWr9+B6P0kdXI?u`KL}fuHO(J6HQI!J4t^+ zyiDk7ZOXqSokirU&|LM#-=cm)ZYEk1w-B!px?07tPT^>_`fc#arFoJT~6iNw>yH$*zo_-aDCwG^yuR5-(=Z;J1KMnol^8~ZmC>d3w`r`a zuW>TzKN5q-VI~P2R@i#J>@Rgs!GU2JQZY-7%f$M$FVrt^(qh#Jfa@h$ivR z5GpiWpOSck7)n`pQ-TrmT(e??$nmc#7yi93rf=^vtDBWJy*y z%`OYNVLK44|9uaC`}#YwFH5Q~8L+cyec6Q{HJdOfXYfFK;E0g}hx8xF)vC}5M{;I{ zoIu#`M1rA__9adr;0xP@uCL7X+oLLD*>V0@HrE#@4|=Oc9k&}jt<()i94BH2d~PH* ziYmt7tr~q@cCJ0gaYEt6OMI09zpt{&rr$ER&b6KDQeQ=l9W1v^V{gs!X zY+rzf*z>q@ii&db@|cN;6Y;rraA}2G;nunVds&c%fn4jHz1eMl)RR|oey5zD-AB70 z^9o$bcnP~RY%lS-q1e}AM}llfeLaEthYAiS zT{elei!DTL(C>F+ix=PI?A=`Jhr4`B;Rr=`HM=5SA>8P8h0C~NEoRsF8|{QL?~hZ` zyak2*5`*R5d4=8D{p9Y1!g)!0hPSz}YhrMzcf7E3!cgz!!okrWp4_lKW=${r@sNJF zG3(;IbKfa*A~n_j&#l;|Irsgj_EWg5rY01-|Kqo^-D=0@cgjMXc|KO z!=(JAf@`byb3Zsfx4DBFcY3bg99E0ZXR)uc+zo~4%e!!@ld{w)vkH7{k34s=kI&3+ zd)=m8l;C<(r==$PD!t&eiC+D*mCbFZOr!V47x(de#YL?he>hT8>i6*-dzY8=Z(SP< zhV02RXBN+}y;H?~T4;xAGkV=i(i2&(u_b9gX_GZXXKSooM%bx#bAlmXg)flN&{Dg% zn5`SBbggL(Thn{8WKx3dHJP5;+#Eag{inA{plJAv{HA^%=eT$6j1l!+XO2opcI^ql zs_Fw<{MJ-P7j`{oR*&`vRvvgDkP}>*bKn6Iws+I4y#5vL;$VPBL|iviW)5CEpfg7^ z<_km^iM|c>9-N)+WzIfvfs{S*lV6knUF3hMuJ_8EqKxS^%UrwE7jS$b*Jckp*7TrD$1Lmgns8X- zw(6WnebL;}2`#2LA*U+hhP+$m4NC}khvtn>2zyH}J)ID!FP+~sfu*llFg(Q$R?|`Z zm(VQh&IRRZfncz#&Z%-^zm&mh-qYvzRV?9$Hym>Lx#HI_b68^+|# zvg`QWjdxb913ysV-MTQ7Rok<$ZxS0R, 2011,2013 # Blue , 2011-2012 -# Bouke Haarsma , 2013 +# Bouke Haarsma , 2013 # Claude Paroz , 2014 # Erik Romijn , 2013 # Evelijn Saaltink , 2016 @@ -12,15 +12,16 @@ # Jannis Leidel , 2011 # Jeffrey Gelens , 2011-2012,2014 # Michiel Overtoom , 2014 -# Sander Steffann , 2014-2015 +# Sander Steffann , 2014-2015 # Tino de Bruijn , 2013 +# Tonnes , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:03+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-05-02 10:55+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -38,7 +39,7 @@ msgid "Asturian" msgstr "Asturisch" msgid "Azerbaijani" -msgstr "Azerbaijani" +msgstr "Azerbeidzjaans" msgid "Bulgarian" msgstr "Bulgaars" @@ -59,7 +60,7 @@ msgid "Catalan" msgstr "Catalaans" msgid "Czech" -msgstr "Tjechisch" +msgstr "Tsjechisch" msgid "Welsh" msgstr "Welsh" @@ -92,10 +93,10 @@ msgid "Spanish" msgstr "Spaans" msgid "Argentinian Spanish" -msgstr "Argentijns-Spaans" +msgstr "Argentijns Spaans" msgid "Colombian Spanish" -msgstr "Columbiaans Spaans" +msgstr "Colombiaans Spaans" msgid "Mexican Spanish" msgstr "Mexicaans Spaans" @@ -197,7 +198,7 @@ msgid "Malayalam" msgstr "Malayalam" msgid "Mongolian" -msgstr "Mongolisch" +msgstr "Mongools" msgid "Marathi" msgstr "Marathi" @@ -206,7 +207,7 @@ msgid "Burmese" msgstr "Birmaans" msgid "Norwegian Bokmål" -msgstr "Noorse Bokmål" +msgstr "Noors Bokmål" msgid "Nepali" msgstr "Nepalees" @@ -215,7 +216,7 @@ msgid "Dutch" msgstr "Nederlands" msgid "Norwegian Nynorsk" -msgstr "Noorse Nynorsk" +msgstr "Noors Nynorsk" msgid "Ossetic" msgstr "Ossetisch" @@ -266,7 +267,7 @@ msgid "Telugu" msgstr "Telegu" msgid "Thai" -msgstr "Thais" +msgstr "Thai" msgid "Turkish" msgstr "Turks" @@ -305,50 +306,50 @@ msgid "Syndication" msgstr "Syndicatie" msgid "That page number is not an integer" -msgstr "" +msgstr "Dat paginanummer is geen geheel getal" msgid "That page number is less than 1" -msgstr "" +msgstr "Dat paginanummer is kleiner dan 1" msgid "That page contains no results" -msgstr "" +msgstr "Die pagina bevat geen resultaten" msgid "Enter a valid value." -msgstr "Geef een geldige waarde." +msgstr "Voer een geldige waarde in." msgid "Enter a valid URL." -msgstr "Geef een geldige URL op." +msgstr "Voer een geldige URL in." msgid "Enter a valid integer." -msgstr "Geef een geldig geheel getal op." +msgstr "Voer een geldig geheel getal in." msgid "Enter a valid email address." -msgstr "Vul een geldig emailadres in." +msgstr "Voer een geldig e-mailadres in." msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." msgstr "" -"Vul een geldigde 'slug' in, bestaande uit letters, cijfers, liggende " +"Voer een geldige 'slug' in, bestaande uit letters, cijfers, liggende " "streepjes en verbindingsstreepjes." msgid "" "Enter a valid 'slug' consisting of Unicode letters, numbers, underscores, or " "hyphens." msgstr "" -"Vul een geldigde 'slug' in, bestaande uit Unicode letters, cijfers, liggende " -"streepjes of verbindingsstreepjes." +"Voer een geldige 'slug' in, bestaande uit Unicode-letters, cijfers, liggende " +"streepjes en verbindingsstreepjes." msgid "Enter a valid IPv4 address." -msgstr "Geef een geldig IPv4-adres op." +msgstr "Voer een geldig IPv4-adres in." msgid "Enter a valid IPv6 address." msgstr "Voer een geldig IPv6-adres in." msgid "Enter a valid IPv4 or IPv6 address." -msgstr "Voer een geldig IPv4 of IPv6-adres in." +msgstr "Voer een geldig IPv4- of IPv6-adres in." msgid "Enter only digits separated by commas." -msgstr "Geef alleen cijfers op, gescheiden door komma's." +msgstr "Voer alleen cijfers in, gescheiden door komma's." #, python-format msgid "Ensure this value is %(limit_value)s (it is %(show_value)s)." @@ -417,6 +418,8 @@ msgid "" "File extension '%(extension)s' is not allowed. Allowed extensions are: " "'%(allowed_extensions)s'." msgstr "" +"Bestandsextensie '%(extension)s' is niet toegestaan. Toegestane extensies " +"zijn: '%(allowed_extensions)s'." msgid "and" msgstr "en" @@ -456,21 +459,21 @@ msgstr "Geheel getal" #, python-format msgid "'%(value)s' value must be an integer." -msgstr "'%(value)s' waarde moet een geheel getal zijn." +msgstr "Waarde van '%(value)s' moet een geheel getal zijn." msgid "Big (8 byte) integer" msgstr "Groot (8 byte) geheel getal" #, python-format msgid "'%(value)s' value must be either True or False." -msgstr "'%(value)s' waarde moet True of False zijn." +msgstr "Waarde van '%(value)s' moet True of False zijn." msgid "Boolean (Either True or False)" msgstr "Boolean (True danwel False)" #, python-format msgid "String (up to %(max_length)s)" -msgstr "Karakterreeks (hooguit %(max_length)s)" +msgstr "Tekenreeks (hooguit %(max_length)s)" msgid "Comma-separated integers" msgstr "Komma-gescheiden gehele getallen" @@ -480,15 +483,15 @@ msgid "" "'%(value)s' value has an invalid date format. It must be in YYYY-MM-DD " "format." msgstr "" -"'%(value)s' waarde heeft een ongeldige datumnotatie. Deze moet in de YYYY-MM-" -"DD notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige datumnotatie. De juiste notatie " +"is YYYY-MM-DD." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD) but it is an invalid " "date." msgstr "" -"'%(value)s' waarde heeft een geldige notatie (YYYY-MM-DD) maar is een " +"Waarde van '%(value)s' heeft de juiste notatie (YYYY-MM-DD), maar het is een " "ongeldige datum." msgid "Date (without time)" @@ -499,23 +502,23 @@ msgid "" "'%(value)s' value has an invalid format. It must be in YYYY-MM-DD HH:MM[:ss[." "uuuuuu]][TZ] format." msgstr "" -"'%(value)s' waarde heeft een ongeldige notatie. Deze moet in de YYYY-MM-DD " -"HH:MM[:ss[.uuuuuu]][TZ] notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is " +"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]." #, python-format msgid "" "'%(value)s' value has the correct format (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" "[TZ]) but it is an invalid date/time." msgstr "" -"'%(value)s' waarde heeft een geldige notatie (YYYY-MM-DD HH:MM[:ss[.uuuuuu]]" -"[TZ]) maar is een ongeldige datum/tijd." +"Waarde van '%(value)s' heeft de juiste notatie (YYYY-MM-DD HH:MM[:ss[." +"uuuuuu]][TZ]), maar is een ongeldige datum/tijd." msgid "Date (with time)" msgstr "Datum (met tijd)" #, python-format msgid "'%(value)s' value must be a decimal number." -msgstr "'%(value)s' waarde moet een decimaal getal zijn." +msgstr "Waarde van '%(value)s' moet een decimaal getal zijn." msgid "Decimal number" msgstr "Decimaal getal" @@ -525,8 +528,8 @@ msgid "" "'%(value)s' value has an invalid format. It must be in [DD] [HH:[MM:]]ss[." "uuuuuu] format." msgstr "" -"'%(value)s' waarde heeft een ongeldig formaat. Het juiste formaat is [DD] " -"[HH:[MM:]]ss[.uuuuuu]." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is " +"[DD] [HH:[MM:]]ss[.uuuuuu]." msgid "Duration" msgstr "Tijdsduur" @@ -539,20 +542,20 @@ msgstr "Bestandspad" #, python-format msgid "'%(value)s' value must be a float." -msgstr "'%(value)s' waarde moet een decimaal getal zijn." +msgstr "Waarde van '%(value)s' moet een drijvende-kommagetal zijn." msgid "Floating point number" -msgstr "Decimaal getal" +msgstr "Drijvende-kommagetal" msgid "IPv4 address" -msgstr "IPv4 address" +msgstr "IPv4-adres" msgid "IP address" msgstr "IP-adres" #, python-format msgid "'%(value)s' value must be either None, True or False." -msgstr "'%(value)s' waarde moet None, True of False zijn." +msgstr "Waarde van '%(value)s' moet None, True of False zijn." msgid "Boolean (Either True, False or None)" msgstr "Boolean (True, False of None)" @@ -578,16 +581,16 @@ msgid "" "'%(value)s' value has an invalid format. It must be in HH:MM[:ss[.uuuuuu]] " "format." msgstr "" -"'%(value)s' waarde heeft een ongeldige notatie. Deze moet in de HH:MM[:ss[." -"uuuuuu]] notatie opgegeven worden." +"Waarde van '%(value)s' heeft een ongeldige notatie. De juiste notatie is HH:" +"MM[:ss[.uuuuuu]]." #, python-format msgid "" "'%(value)s' value has the correct format (HH:MM[:ss[.uuuuuu]]) but it is an " "invalid time." msgstr "" -"'%(value)s' waarde heeft een geldige notatie (HH:MM[:ss[.uuuuuu]]) maar is " -"een ongeldige tijd." +"Waarde van '%(value)s' heeft de juiste notatie (HH:MM[:ss[.uuuuuu]]), maar " +"het is een ongeldige tijd." msgid "Time" msgstr "Tijd" @@ -596,7 +599,7 @@ msgid "URL" msgstr "URL" msgid "Raw binary data" -msgstr "Ruwe binaire data" +msgstr "Onbewerkte binaire gegevens" #, python-format msgid "'%(value)s' is not a valid UUID." @@ -606,28 +609,28 @@ msgid "File" msgstr "Bestand" msgid "Image" -msgstr "Plaatje" +msgstr "Afbeelding" #, python-format msgid "%(model)s instance with %(field)s %(value)r does not exist." msgstr "%(model)s-instantie met %(field)s %(value)r bestaat niet." msgid "Foreign Key (type determined by related field)" -msgstr "Refererende sleutel (type wordt bepaalde door gerelateerde veld)" +msgstr "Refererende sleutel (type wordt bepaald door gerelateerde veld)" msgid "One-to-one relationship" -msgstr "Één-op-één relatie" +msgstr "Een-op-een-relatie" #, python-format msgid "%(from)s-%(to)s relationship" -msgstr "%(from)s-%(to)s relatie" +msgstr "%(from)s-%(to)s-relatie" #, python-format msgid "%(from)s-%(to)s relationships" -msgstr "%(from)s-%(to)s relaties" +msgstr "%(from)s-%(to)s-relaties" msgid "Many-to-many relationship" -msgstr "Veel-op-veel relatie" +msgstr "Veel-op-veel-relatie" #. Translators: If found as last label character, these punctuation #. characters will prevent the default label_suffix to be appended to the @@ -639,22 +642,22 @@ msgid "This field is required." msgstr "Dit veld is verplicht." msgid "Enter a whole number." -msgstr "Geef een geheel getal op." +msgstr "Voer een geheel getal in." msgid "Enter a number." -msgstr "Geef een getal op." +msgstr "Voer een getal in." msgid "Enter a valid date." -msgstr "Geef een geldige datum op." +msgstr "Voer een geldige datum in." msgid "Enter a valid time." -msgstr "Geef een geldige tijd op." +msgstr "Voer een geldige tijd in." msgid "Enter a valid date/time." -msgstr "Geef een geldige datum/tijd op." +msgstr "Voer een geldige datum/tijd in." msgid "Enter a valid duration." -msgstr "Geef een geldige tijdsduur op." +msgstr "Voer een geldige tijdsduur in." msgid "No file was submitted. Check the encoding type on the form." msgstr "" @@ -679,7 +682,7 @@ msgstr[1] "" "nu %(length)d)." msgid "Please either submit a file or check the clear checkbox, not both." -msgstr "Upload a.u.b. een bestand of vink de verwijder vink, niet allebei." +msgstr "Upload een bestand of vink het vakje Wissen aan, niet allebei." msgid "" "Upload a valid image. The file you uploaded was either not an image or a " @@ -693,13 +696,13 @@ msgid "Select a valid choice. %(value)s is not one of the available choices." msgstr "Selecteer een geldige keuze. %(value)s is geen beschikbare keuze." msgid "Enter a list of values." -msgstr "Geef een lijst op met waardes." +msgstr "Voer een lijst met waarden in." msgid "Enter a complete value." -msgstr "Geef een volledige waarde op." +msgstr "Voer een volledige waarde in." msgid "Enter a valid UUID." -msgstr "Geef een geldige UUID op." +msgstr "Voer een geldige UUID in." #. Translators: This is the default suffix added to form field labels msgid ":" @@ -721,8 +724,8 @@ msgstr[1] "Verstuur niet meer dan %d formulieren." #, python-format msgid "Please submit %d or more forms." msgid_plural "Please submit %d or more forms." -msgstr[0] "Geef alstublieft %d of meer formulieren op." -msgstr[1] "Geef alstublieft %d of meer formulieren op." +msgstr[0] "Verstuur %d of meer formulieren." +msgstr[1] "Verstuur %d of meer formulieren." msgid "Order" msgstr "Volgorde" @@ -759,7 +762,7 @@ msgstr "Selecteer een geldige keuze. Deze keuze is niet beschikbaar." #, python-format msgid "\"%(pk)s\" is not a valid value for a primary key." -msgstr "\"%(pk)s\" is geen geldige waarde voor een primaire sleutel." +msgstr "'%(pk)s' is geen geldige waarde voor een primaire sleutel." #, python-format msgid "" @@ -767,10 +770,10 @@ msgid "" "may be ambiguous or it may not exist." msgstr "" "%(datetime)s kon niet worden geïnterpreteerd in tijdzone " -"%(current_timezone)s. Waarschijnlijk is deze ambigu of bestaat niet." +"%(current_timezone)s; mogelijk is deze dubbelzinnig of bestaat deze niet." msgid "Clear" -msgstr "Verwijder" +msgstr "Wissen" msgid "Currently" msgstr "Huidige" @@ -862,19 +865,19 @@ msgid "Tue" msgstr "di" msgid "Wed" -msgstr "woe" +msgstr "wo" msgid "Thu" -msgstr "don" +msgstr "do" msgid "Fri" -msgstr "vrij" +msgstr "vr" msgid "Sat" -msgstr "zat" +msgstr "za" msgid "Sun" -msgstr "zon" +msgstr "zo" msgid "January" msgstr "januari" @@ -1063,7 +1066,7 @@ msgstr ", " msgid "%d year" msgid_plural "%d years" msgstr[0] "%d jaar" -msgstr[1] "%d jaren" +msgstr[1] "%d jaar" #, python-format msgid "%d month" @@ -1087,7 +1090,7 @@ msgstr[1] "%d dagen" msgid "%d hour" msgid_plural "%d hours" msgstr[0] "%d uur" -msgstr[1] "%d uren" +msgstr[1] "%d uur" #, python-format msgid "%d minute" @@ -1102,7 +1105,7 @@ msgid "Forbidden" msgstr "Verboden" msgid "CSRF verification failed. Request aborted." -msgstr "CSRF verificatie mislukt. Verzoek afgebroken." +msgstr "CSRF-verificatie mislukt. Aanvraag afgebroken." msgid "" "You are seeing this message because this HTTPS site requires a 'Referer " @@ -1110,37 +1113,36 @@ msgid "" "required for security reasons, to ensure that your browser is not being " "hijacked by third parties." msgstr "" -"U ziet deze melding omdat deze HTTPS website vereist dat uw webbrowser een " -"'Referer header' meestuurt, maar deze ontbreekt. Deze header is noodzakelijk " -"om veiligheidsredenen om er zeker van te zijn dat uw browser niet door " -"derden gekaapt wordt." +"U ziet deze melding, omdat deze HTTPS-website vereist dat uw webbrowser een " +"'Referer header' meestuurt, maar deze ontbreekt. Deze header is om " +"veiligheidsredenen vereist om er zeker van te zijn dat uw browser niet door " +"derden wordt gekaapt." msgid "" "If you have configured your browser to disable 'Referer' headers, please re-" "enable them, at least for this site, or for HTTPS connections, or for 'same-" "origin' requests." msgstr "" -"Als u uw webbrowser ingesteld heeft om geen 'Referer headers' mee te sturen, " -"schakelt u deze dan alstublieft weer in, op zijn minst voor deze website, " -"voor HTTPS verbindingen, of voor 'same-origin' verzoeken." +"Als u uw webbrowser hebt ingesteld heeft om geen 'Referer headers' mee te " +"sturen, schakelt u deze dan weer in, op zijn minst voor deze website, of " +"voor HTTPS-verbindingen, of voor 'same-origin'-aanvragen." msgid "" "You are seeing this message because this site requires a CSRF cookie when " "submitting forms. This cookie is required for security reasons, to ensure " "that your browser is not being hijacked by third parties." msgstr "" -"U ziet deze melding omdat deze website vereist dat een CSRF cookie wordt " -"meegestuurd bij het versturen van formulieren. Dit cookie is vereist om " -"veiligheidsredenen om er zeker van te zijn dat uw browser niet door derden " -"gekaapt wordt." +"U ziet deze melding, omdat deze website vereist dat een CSRF-cookie wordt " +"meegestuurd bij het verzenden van formulieren. Dit cookie is om " +"veiligheidsredenen vereist om er zeker van te zijn dat uw browser niet door " +"derden wordt gekaapt." msgid "" "If you have configured your browser to disable cookies, please re-enable " "them, at least for this site, or for 'same-origin' requests." msgstr "" -"Als u cookies in uw webbrowser heeft uitgeschakeld, schakel deze dan " -"alstublieft weer in, op zijn minst voor deze website of voor 'same-origin' " -"verzoeken." +"Als u cookies in uw webbrowser hebt uitgeschakeld, schakel deze dan weer in, " +"op zijn minst voor deze website, of voor 'same-origin'-aanvragen." msgid "More information is available with DEBUG=True." msgstr "Meer informatie is beschikbaar met DEBUG=True." @@ -1158,13 +1160,16 @@ msgid "" "Next, start your first app by running python manage.py startapp " "[app_label]." msgstr "" +"Start nu uw eerste app door python manage.py startapp [app_label] uit te voeren." msgid "" "You're seeing this message because you have DEBUG = True in " "your Django settings file and you haven't configured any URLs. Get to work!" msgstr "" -"U ziet dit bericht omdat u DEBUG = True in uw Django settings " -"bestand heeft staan en u nog geen URLs geconfigureerd heeft. Aan het werk!" +"U ziet dit bericht, omdat u DEBUG = True in uw Django-" +"instellingenbestand hebt staan en u nog geen URL's hebt geconfigureerd. Aan " +"het werk!" msgid "No year specified" msgstr "Geen jaar opgegeven" @@ -1187,12 +1192,12 @@ msgid "" "Future %(verbose_name_plural)s not available because %(class_name)s." "allow_future is False." msgstr "" -"Geen toekomstige %(verbose_name_plural)s beschikbaar omdat %(class_name)s." +"Geen toekomstige %(verbose_name_plural)s beschikbaar, omdat %(class_name)s." "allow_future de waarde False (Onwaar) heeft." #, python-format msgid "Invalid date string '%(datestr)s' given format '%(format)s'" -msgstr "Ongeldige datum tekst '%(datestr)s' op basis van formaat '%(format)s'" +msgstr "Ongeldige datumtekst '%(datestr)s' op basis van notatie '%(format)s'" #, python-format msgid "No %(verbose_name)s found matching the query" @@ -1200,7 +1205,8 @@ msgstr "Geen %(verbose_name)s gevonden die voldoet aan de query" msgid "Page is not 'last', nor can it be converted to an int." msgstr "" -"Pagina is niet 'last' en kan ook niet geconverteerd worden naar een int." +"Pagina is niet 'last' en kan ook niet naar een geheel getal worden " +"geconverteerd." #, python-format msgid "Invalid page (%(page_number)s): %(message)s" @@ -1212,12 +1218,12 @@ msgstr "" "Lege lijst en %(class_name)s.allow_empty heeft de waarde False (Onwaar)." msgid "Directory indexes are not allowed here." -msgstr "Directory overzicht is hier niet toegestaan" +msgstr "Directoryindexen zijn hier niet toegestaan." #, python-format msgid "\"%(path)s\" does not exist" -msgstr "\"%(path)s\" bestaat niet" +msgstr "'%(path)s' bestaat niet" #, python-format msgid "Index of %(directory)s" -msgstr "Overzicht van %(directory)s" +msgstr "Index van %(directory)s" diff --git a/django/conf/locale/ro/LC_MESSAGES/django.mo b/django/conf/locale/ro/LC_MESSAGES/django.mo index 4a20b784f187e235dc1067dc098a852d8641e215..41a490d9041b7a7c01879c2891b1f09e0731519c 100644 GIT binary patch delta 2574 zcmXZdeN2{B7{~DoBnwPT@dY##5kzGW)O7kJ3Y1cai7%P>f;<8`Hv|KuhMTveHL~kVk!f>Q48sF=g%^$q~C@saNumSQe1=8*p9DY zQodQX;`lu7#0)%xxp*r-i0oi&f!TNh#Tbn>_!QP*6uyly*o=A}U@W${@8gs7_uxR> zi=*%W#$kuY&!H-H(fc}r(-3OFS{#J6s55K!_*NW3zZHk!$EXB{F&@7` zy?+KZU#Iu`P~+_7{&9iD(cu1B5^5)l@jR}?2RLz#*$!OiHAhX0Q3rleVZo$#mhGVb;lkpEs z#fLZ!N6$B#gdx-|Tl1rFe#Wh@THA*ck&ZaV6{EbmW4C7y-xSiFe(Ye!`SlxQvLGHyg&mP4os zPoqkD0kz{x-tR_zc2_-q7h~w(M_pw( zUd+cBe*W}(Fb8$1%5gYu!35le+Q8@TG1NTYp%#7#RoUNBiLYZZ1~+MFrMa(j;V^_* z*n&-X6enZGQj+6B)aUb&dk>X(!5fj%mf;Zk)u@GSLRD}(4#l0QeEX223+xCDmFz3j z3XkJ3JdIS(x^X#9S{8Z!4r;;=P>J^7NIZx-(+*UEPPZFXu^!afKSnJ$u1sGS=Rcgr zR08QZ0#~5U_)XNnO{jzcYT|9E%eDh`clLVxu=l@4ZRiv#;aTLvurAc^Ln^z`XI_Ab zk7K5C(Ts0BoEUaIV1)bBtdYQAO+ zvS|b~H1U_%zhcw^x=<5c_UG47m3WA{ME{}wJw~tOV$mG1iu1**s06j;k?{?v{$_Wp z`))b)*Ms*6=sW(<-HqDWC#Z$AdHf)1{1NXTLoMhyYWzvmk)3saMQz{;YX0l)pQ!P7 z%BjB+JRqPK|8`?mM@kltdY651x_PtAHEHzj9!C?_W~hrhUza6@C(lKOCMLru7`u0E7fu%@~uWl^{) mT$wU6OuJsq{B;|`>ua(r>o&CKh0+ps{Z%rwyS!WV@cis delta 2574 zcmXxkeN5F=9LMo<3GoS)hYTi~S3p4tx|%gn2vAHVltfGk1aE+fWB33wa@q|_XJl<@ zYv>=XoM^fkHFTD1$Q-z^sk5|j)=ZZ=w`LD69Y|a65BHlJzRvgiJHPYrJ?D4(Iy?g% zo-e)`9_aKqw=3PbvCg?gbDW!oVt?rGeP&*3pl!QW8tM`Q+_f=?1> zU@VrP59=`+TX6_}o#_IDjnkIsHor43VLTmwLM^111e;I`eFs(AJr*A_ z+fWtw3iaL{%m0O1aG;-tRunSNxdelX5 zqTWA?8t;O|{iuE+xr6-zE}RDU&&8p3vK)J`3?E?feCIlG;tS3_!e24TrrQ2XZhDr zFE(O0?zVUzYQlqf0YAf|_!{4$#*61%RKaPe1koWq84`F;sMkG!#HRej-xONHG$VmM!n~=I2|={4vxmfh16d=DkY(bs!^A*4s}_M zp$6 ze+-Rhd60}_u?%&_^{AaSpeAfW4cv^nYXy7k#aK)$vTtN+V)t=u(RiYnti9$;G&%-%lFU_!391C7YO|Ypf*nbNu ze#3mr+*L;X_24}c`i}RQEvTJ+j9N&m<&U8Hw^@7~wV+d|{%24})?@yF+Q2WU@o$^I zqx%0{M*THG9|^tquQ{|lSh7ge^YNzFOf-|tr%azY)0|~ynAxaHnu}Wavj1Nwu@2>C z1!|{jEv`Ww&BpfP=mS&cPEVcT^ZL?f`<_Yh@oz-Fzpg&DXtTezuEt-#Ve@Qnp1-!X ny3U(lRb5$C, 2017 # mihneasim , 2011 # Daniel Ursache-Dogariu , 2011 # Denis Darii , 2011,2014 @@ -13,8 +14,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:03+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-05-31 08:00+0000\n" +"Last-Translator: Abel Radac \n" "Language-Team: Romanian (http://www.transifex.com/django/django/language/" "ro/)\n" "MIME-Version: 1.0\n" @@ -319,7 +320,7 @@ msgid "Enter a valid integer." msgstr "Introduceți un întreg valid." msgid "Enter a valid email address." -msgstr "Introduceți o adresă de email validaă." +msgstr "Introduceți o adresă de email validă." msgid "" "Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens." diff --git a/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo b/django/contrib/admin/locale/eu/LC_MESSAGES/django.mo index 47276f63e91db24a73cd92edc9b5e3a6c7995066..9c254d9070d518ee0564a30ef1cb93cd033010b8 100644 GIT binary patch delta 3287 zcmYk;X;76_9LMn^iwh#k6>$rJRFq8t!39M{6BV)uK@r+qt|TBJT}p9zv_vs?#22P5 zgJvd8mR94~jN8Yv}uXp2JLM{NK+x&%O7Y|M@@XxpK3z zyVAMq3pr|NdU=BQI>eY8SUrFjT64HD!*CCd#`mxczrqBJ9^_rm!7RorP@g}DeenbA zhaY1wevW+AT=d>|%r|tj@GTsQ4{-$k>wUmPM;OCD6T^!pNI~V6hW#-cx8i)c{S$1*wOB@)C-EBU3x3L@1U92~xF40k zah!{1aSZ;7N+2wXWH1uPVFgy>J`BQcbX3BB=qR&LR^j%WLAV)xxEv3lCj13;?>bSR z{~IZa38Kz=--oK)NK}Ppp%Pt)TCfDk&6J|PUma~6_g)0JpoMp%cC^oa;J6*1L@jjQ zj<2H*Q^pE2E2CP-$-=pEY+;33VaG zF^_R14&g-$CZQ&nin{kXsCgD*5|*P9+=Z%G8|u)VLsk4b>hs?@bk5PahvV_!C}R@w z3si~j;cVxrY((v(FDFiqE%v{z4@dG}hZlKWh~3 z|d#=l$}{L@iv5ny4JLkRN&C%obEaEvSS~B2$=l)FJ*Jv+x1xkPYWx zX`W=%I16<~7NZkNrcyx z@=-f0#a66Aay7we-Y;Gn4q#l4^U#mnHFE}aIDbM_vI}*FhNbiKtmpr2I^kSMWVoO}ODG{V9m*}lCPL+YlhA$h5!FO*+fJvRs3X=9D)B60I?+fhBGis}xUX;VT1Jf1 zavSKB5Qhjgs$%vND&<NZ;V@h4QCR1ob&{&B6MzEBGwRb1W%*;I4{LF zh^L4a!u|c9PiGggp6G45?`k`U)o#zbcPr@^5?k%HS{!2gtyb|gv5Y9zz_w>_x$Wym zYb;Tqa?c}jh~D-hopL*vjfaVSL?tnmI7-A5YMfd3FPsK<zP7i06nDqJqdLULi7wp+ta)B%UYK^tV8>+w=Y)v0d{r9Y2vuEF_AE z-WEY8uXlj_RF83gyuUomk921i{s%K!E`9(2 delta 3240 zcmXxmdrX&A9LMo52BIjafQETPZYm@f@n?l%cuh&dQZkV)f>{xW7zk$iq#zcgCEyyQ zn^XTto8_i|)bcMkvvTE_vp>2pv6`!8mYb&LtoP@64rhL!*E!Gc_dDl%F3*FvE5EAr zdSk=)8l{sMOPmTfJA-9|xlq;*H5-YwI0koODIUT!yyxzxMw^9b&p~~^0SDqPjKog7 z5BDKIu>-;9o*kp2flp!__Fw}38hpX-VGQjEF6tm2m0JQ1!X(^+8Q6?Rke^uQFtbrO z3zf(+)bC0$7T5TCWJ-m9*2+bhc4HDAbob9-4(*FL0waf;Md4^68=I(nO#M;-)`V$yn`!nBcrRsZ&7=95%vAc$fDTq z?(;jSl?!9twL&SVM6*x><|DaS0qXb5N15mEMHx3Va1Clkb?ysIuHA+jsN1!Vq7LI{ zI0P@^1nk2)3}fAw;zrzwXE6_pIauny6*Zx*1lC^}9pHwR_9XIM`vR3vFR}>sGmgev zr~zYXsDtsSy-!8;lZELx50&6L)QYvD4&D2x6+epl{t1uDCsfX2DmFY|HU;;imgp?b z#`DNOi=o$SoPtc&O0gbmaR**OEqyhQ)HB$KO0WlYIDc^Vp;p4XLPZ1JL@n_>)XZbX z1)0a8I!MHaFdf^l95s_aQIF+6sJ;FdGjK2`u@Xa=j|Wf_=*7u+8(B%ulGr#cX(p

epr9o5gk)Sx{a{l^j` zxSxfdIto$Isn5p;u>>`v?e2aXHq$m&9DbG z&=u5s;}){r7R|Eiem3$7u{pRIOFb$JsCAKSs2To^>R=F`Mq?^!X1Umji;-Nd z7g=2!lI;#F&ZS*|+S?}7;XH;~$sW`h`U`hseU86^o`o~rXSp!~wUjlej2lsV@1YWC zMxB*z9EOKc37*8Ecp78yJTeFC#r+ta8+`r|>izO5>U*a!ne%7os654uYgmADrw17~ zA?MK!qJD7B`8}$m-%tZzb>6`tv?D`7f^n#)DjAjNObo-P33ksfd3yfWxW+mxCUgMx z?ye(LH0$RHE#qq9HKK^nVA=wew}|(MQbMcQU-ZhJM^qDU6MG1ig#qTxc9pfLw_7Zs zZKx-dHA(v?wgh!R#}WOdoeJCNmvXLUuD%Ob5ib$is`5P@@Rl@H2l)Sbp){} zXxb|psJ|?5m6@nVY$Kt+X#GWxsR{?%3W#_@2QZ&dc_YBTzRXpJI*!mEsVYK`wH{5q zDK`+|gxAKUl~8${s3i0rc$g?8GKd!l6}@s*wh?puT5uT-X|DYkE+d+VBtmbwM538k sLPQY#WpS`0G*9dpdMUPJ!-T_8xl^Z2OUw!7\n" "Language-Team: Basque (http://www.transifex.com/django/django/language/eu/)\n" "MIME-Version: 1.0\n" @@ -96,7 +96,7 @@ msgid "user" msgstr "erabiltzailea" msgid "content type" -msgstr "" +msgstr "eduki mota" msgid "object id" msgstr "objetuaren id-a" diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.mo index ce294e7f2fad94ec3bd9793dcfb804e152b5a150..be6b79cb2411d6e4d12ac4d1363064d4623d7495 100644 GIT binary patch delta 201 zcmaE>{91X#9H#na1_p-fybKJSKsuU_fq@lBrvhmXAe{}Qxqx&Dkmdo>^*|b=t_Mhi z)J+4@tAX?!AiV%cd+;+bumS0pKpLd(1CVwF(!YQ-JCJq|U|{G6(jGv17LdLnz`)0l z0b~>lGB7X#={u7ZnU#5&fUIXwk(ZkznG@N0&2fq@lBrvqsYAe{@Oxqx&Tkmdo>jX)Ztt`A6q z)Xf0WtAX@9AiV%cd+{?cumS1UKpLd(6OeWV(tm(7JCJq~U|{G6(q2G%7LdLr092j< zWRyq^owtRA% JCHO8g0{|se9@78- diff --git a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po index 1c4b95da8325..0caf06854ad4 100644 --- a/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/is/LC_MESSAGES/djangojs.po @@ -4,13 +4,13 @@ # gudbergur , 2012 # Hafsteinn Einarsson , 2011-2012 # Jannis Leidel , 2011 -# Thordur Sigurdsson , 2016 +# Thordur Sigurdsson , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-10-07 16:16+0000\n" +"PO-Revision-Date: 2017-04-05 03:24+0000\n" "Last-Translator: Thordur Sigurdsson \n" "Language-Team: Icelandic (http://www.transifex.com/django/django/language/" "is/)\n" @@ -108,7 +108,7 @@ msgstr[1] "Athugaðu að þú ert %s klukkustundum á undan tíma vefþjóns." #, javascript-format msgid "Note: You are %s hour behind server time." msgid_plural "Note: You are %s hours behind server time." -msgstr[0] "Athugaðu að þú ert %s klukkustundum á eftir tíma vefþjóns." +msgstr[0] "Athugaðu að þú ert %s klukkustund á eftir tíma vefþjóns." msgstr[1] "Athugaðu að þú ert %s klukkustundum á eftir tíma vefþjóns." msgid "Now" diff --git a/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo b/django/contrib/admin/locale/mk/LC_MESSAGES/django.mo index 46a67468ac4b6dc6f3a845b8339fb4ebe4590d36..8734eee9ec3823923dc72ed1388fa8c87ef5d464 100644 GIT binary patch delta 4947 zcmbW)4|I+99mnw>4GAKYR*{ycAt9B-4T408KT(x+M2h-byK*lHk(8StGQ9+&Wz~pQ zsXx_#Ww=YCG#`ch*zu>DF_Yy+8MPu2H7uIsM+(?|JU? z{C?lx_xF2l^vbptjeA=JKk5|qlHoW&JV?xHWz0p~*_IEEzH!FfkIC2tvoRj0;tX7Z zNqE-1{w0p$ykiGrGB6+Y{03}|yD=JT@g8G><}DhrT&Q<%XhuD78kNv_?2P}yt{B_V z=_nBy%cLM3nPJ!lvoQw8<4VlOwP<5|oN})-kpgVZ_+|wSJ-8Y>;YQcp$e5-cyW!7J znV-i5yoi0UH4o7=X{h^0Vmjuz*Q-#)+=@zg4{E%_*oyH@6AhK786UvYr~yAj4e(FY zz&$C0D$@tM<8WM#({M3{@Gy?K&zO053E#l!Jf!EY@S#LIvyQ4nA_kSwKpG=)H1@zX zsDxg}DR>Zj<5eui-elPlt5K!iib`-7=3p&G;ip)MpW{=wko8l)@1d6b7hS2p9{e2_ zRJsf94WFY*{1vKn-6)4L?u{CF05VyVfqH&CQY148S!UxyB~Xr<;0n}z&$;K-sPSIw zM*Vd|9T&8#PhvYfk3I3PXkj$P%{mo5=I;)vrHx)8OwN33FM+Wn2Opgvr!$*$49Ud>BH45n$;YVsKf@z< z6%+BuL%hoPTU14YS80r(5zWFg8S@x=(2Fd$*@ESG5H)a5%JKx};yB!fDR==VV64S* z;7nYHwWuZR_b?k32cssm8TqLRn(a=*>_L^T7M0mC)EXT}mFkzMnTAjue1t>r6WoXv z8%8rfg!(;bMD3ZMU=^Ohewf?Km<6~LhwJ_S6AjJ0XK&UQvrtP=;9836uo9JE5S7Ra z?)R^v5_;3M-o5?~MsfWNo@~JckRq8)NzM`+!g$6v@6)KpOE?VI_HlOqyQtUiBIaNw z*|A#YSyTc$Q8PT~p1+N};^yb5%6)(u@Ke;%wM}+j?+hdtGZ};0l@&DDA7&%Q-~m)e z^{C36!Fv1{|BPGdO?#t)diKW`Q3<_^8u%8*_#o8Y%10%>CWZQ^(x~P_ zI-W!g_yt-R%a=Y(MfRWBg5kt)5a(x56S$1+FfPqmqVC9~O)`cD#1WjYK_z$!E&O#F z^{2Y#A6z(y@l0nIp2q&HSpiSia$ZArmoamIbN}Fh&R@5uQ3-EFy>3Tv4!(!Ou@_m! zq8ERJB{&niWl&8l57KB&W7=S6CUdYO=ZkSAR-u+eTm2JggZuFks^jfLol@^dou5Tj z>I00!uaUW!m`tbtq1cu4T#UkC0S%SD(7j;?>VZRO;UBOqUO`?I6O-j6n1U>?8IBef zp-TQdj>ZPm=KBgC!!GP>IU6!>LkYFUr-fkk?jo7 z38}6bhDy8?mEc}ff`^cz85^~!qenX5_eL#Ie^mctFiY=$5e;>;5BZHS@1th&HL8Pz zQO@g=jgv@p0n)cQ&NQ{gmrs*Dd+ z$qlH9ZpWZD%W)dZ@jP}Q!^z)qO7DBZ*(9q_OR*0Bh~OETfP{X|Qmgm{k7F@l&D zZaE*DU?#DL=tHa^W)YF2uiF@cZQS-=97^mYQiynh7byJy3Xk9cViXbnM}->sL;|6A z`B5U9c!l^Lv53&I+`;+BDecOd#`a$hT~<_9XpPUY5)%6^^j9PY5-eY-KTy)A%OXlCv6oM4wmUmwp_fBTL%r4P;;l-bXd?(wwEo0yvKUs@C>DlJLP@s|0stO1^M?i-Zq$*??`S?TEw zxxIF@oSN8IiMt$_@88S4qM?8p&3(4`)PeZjnjk zwp&dqc-vW7_MZRtY}v;-p{z~@8uKX4X6oveq|OSxLblXu6=gX_xtT$lx!w3RPmaE~ x#*^4Sc$f9Np)}!@xV!QNZ&SZ-{EYmsJ#lT?&W`y1YS%?|ySaD`hsR8e`44&0`Ac5VGgcvO#5(@$iXn_b6BxKRVkU+BhNi{r( zB-kSVN-0V~{%Fx^#6OuRL;{s@EVaY*Z95J)qkSx`u**DINr(G&wX!q z-#z!-dzYc>aKg8H6WkBdlJ*$w2r-<$p)Ge4rflg;}VCs_-Z1Vmj_dJ*WeD zwsj&8vNPBZ-@?A=VGF*Cn{mPQX5?(|p(gSlOlEvb803GkFQ)NET3`ujyK>e)PU8f0XCop zK8mW$F}xAq#PxU)S7Rk*I)-oK3Tz&1_7q-1eQz@_N@yRd5*_F&qi#A=@UJ)mQ>c#; z8jbTXgxT1Rb@)6cVmjHX)ETG*vvC^cV-l{$hj1+}!2hA1dl&1dC7+v4{q@Bq+)(LO zpgyn`RpJ&@=?A#MeTuZY{j!U7H4L06mcE?6#s>q zh{FcT!&^}mtqxp;dfvl0Q0IRm9hGoL@PYlPgq{t2CAfbQmHAmboxlu`Ty0ynU(y#) zOL87};s5bgY@_ac)ZV}fyo}SZXp~td<6AQwC9oGY!>5DmSCA8Fr%|PQ2Q}coQG1|w zu76xZ$XHf^+GJ5=xvd?w7oI{rrvp`)Gx##TkM3nUJE)V^Iy%~HJZ?oL^g3$bv&fis zA$b2gWP8|nvd~_cjY@n27T{wT#%|PrpQ3|(Sg&f#L$;A^8%zD;nQ^0t8)r~6_zL@D zzkGia4MDACHpU0UNnCG0CD?-wo+FTemgRNyGPN}D` z_3OBveVhNo(~UYEmrxbCiag)70o0YMSqUohT2zMnQ5imiOxpg2!!ePC*ZU(;OEd;K zQ8oz~+p1B|Ify!TG1Nq^puV3u**`sHI5)vHi_qcEUYp{t@mHwQ7vIjW8cxO8_)naR zS(GCan{Wg(-Gvvqe(4TBp`FuMMy{X6A$Uu;|6v;3C@a;ICs3HD9XwOU~vI(^( zd|Z3P@5EnOV%tIN3+^df4K$Xh4DJc-2d%wY4xve?mV zHoiSZ93ifaude-u&>7ZJ+)HdA8i;FcLC|Ro+#6VclL>9w6~Vo2*qewFYl)%6R^m}Y zZ41#(xNCUfNXL)ha$H4d&DFG~x$&O=FI-yCUl+*sh;K|?-c3}iL;F3kpSX{>pWqak(c|>7w zEsBGF{*UgZ{V`;@Q{NDEBI|3KqT@Q}rp8h`pLObzic1PY z#RcJsPT_>IaN*ccVJMU`r=sBQ$l98wnuhv, 2015 # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2015 # Vasil Vangelovski , 2011-2013 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-06-15 11:14+0000\n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-04-05 09:11+0000\n" "Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" @@ -172,31 +172,39 @@ msgstr "" msgid "" "The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" +"Ставката {name} \"{obj}\" беше успешно додадена. Подолу можете повторно да " +"ја уредите." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"Ставката {name} \"{obj}\" беше успешно додадена. Можете да додадете нов " +"{name} подолу." #, python-brace-format msgid "The {name} \"{obj}\" was added successfully." -msgstr "" +msgstr "Ставката {name} \"{obj}\" беше успешно додадена." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" +"Ставката {name} \"{obj}\" беше успешно уредена. Подолу можете повторно да ја " +"уредите." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"Ставката {name} \"{obj}\" беше успешно додадена. Можете да додадете нов " +"{name} подолу." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." -msgstr "" +msgstr " {name} \"{obj}\" беше успешно изменета." msgid "" "Items must be selected in order to perform actions on them. No items have " @@ -213,8 +221,8 @@ msgid "The %(name)s \"%(obj)s\" was deleted successfully." msgstr "Ставаката %(name)s \"%(obj)s\" беше успешно избришана." #, python-format -msgid "%(name)s object with primary key %(key)r does not exist." -msgstr "Објект %(name)s со примарен клуч %(key)r не постои." +msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" +msgstr "%(name)s со клуч \"%(key)s\" не постои. Можеби е избришан?" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo b/django/contrib/admin/locale/mn/LC_MESSAGES/django.mo index b3865c21c395f89e007ff0e14623330ae4161f42..e3ea2e9fc84e6a7ac3d7d040f4d17ff992b2e768 100644 GIT binary patch delta 4377 zcmbu>2~btn9mnybK~V%^+>oenMF`;X3b;^L#9d>IOWmS2KHw=J@?=p#Dle$iDlXNh zO^jRAs9`2m5YuX1(wHG;(qwvHZO2Y&l1XfvPLd{_j5VFMsr~-my`-IX(&1W5#@xXpQG8I+VvXsI>DUKzupchMC$JnJ z!MEJ=cQKda?hhI>66d4t{|V;G62@d0B3=KEBlInnH1a2<6+D{4S(*c1PZeKESb z(@+Z1ml=vQWX57w%)u@=6F1|0+=e#B;KGNTiR5Ev`Zt@X=*Bwifeo(5kUmW_#^Wof zk+)$I-oaGt%uQ5{2X*~K%*46w`7Nkq9zYHFII6#M*opqlB`QkIb&SJSREKY)I`|yb zaUy9@W>WEC9FJAF1j{gl=Wuc_V^-m3cnX(tlkWS14-K>@>!?hmU_c`pPGura!UWuk z8qi5xh>e(x_pt<%8D&S@hf4JU)Bum-6g-U)_#xKdr??o`uzqUyRn(Hd(U<(|#@n1w z>fUuP_!O1md#KdKlManI8P#zXGFdYcb^lBxNoF3h%%%V}fc2;eZbDtR!#&=I>hHOD z@~;b;IH6trGIql@OvDe+!btL6iD@_&kK%Ux1jpb8ZdS+5sP=B4W*R~b^p~jAe~cu< z{2evGJ^{8k%Vhc?eVGi@0A`^YScKXvD^Lxs#=%&Fv|-MmQh5WFfuEw5-Sby4g7Y`=Tn8qATH=~Voc<1CKl(RKRO;|e{2I(#0)Btv&W_Spd;CRrr#(-{EK}DOV47=hs)Nb8_ z;l~8u;J67}v1YI{kX-V~*i1euGY!ZSYo0}tYFd!(Wo{$;&-@9sw|cN))bHe>DKoF4;70_Hfp3xP&X8zHrp{&N`H*K@h#MV z@1TYEP@5}mjB`8=Cv&_4HINfX(#-2P9{-B3;^?uwE%p3=L8U9HJwML*vAN7u@m%oW z1m|%~N6l~LTN$!%N)^;iKXHM|JoXl^q7Y-yXpW!If zz|*JjVxoVul1c&ILAHaLF_nLpuojCklAUx9%P|Iv=tYm=M%3|mMiGgRqV~iBOvOCx zgF7(=PoS3WCMv_f!hkAwsVu|Tnf&N5!(y~JE}P}dY(FZchw%p9z=c@fi?8)(P9Ej6VBQHY67cNKn z+~U?xp$5P>!X3uqH;DF9P33g^0WKi41iiHWPg1EM*qCM>F`7_1RlEdQ4;Q@&GYAcA zBT-AJ1c?&jfDR}JiRTDjmZp$U@jIArx(-4u$+xxstB7NS%5LIWLTS=t^Aw@NikR<) zYtDbQ8{9e>4?k8h_yAGqo>3kk0l;+`tHqJ0YWgD@N z&>omdw3n?^&bS9fxQ94PoFtwg>IoI?6}DKoB=9+fSVxS|^WR=pQ_)%%xF;UQ=iRzk z>eeU!%Q@cC;U`6>))N--Ekc`5WwAr}voD`M;&Eak;S~DsLakf-G3xcWonVuPe_S-g zc!IrW_7eIfdzx55sMHZV!!_q)2z-wychBji!_SA=Pvp7hqLDpsqSSbAq9ajC>>yMo z5KF=}=VKQvBaRcP#3o`n(O%NrN;YnD)cy*WY*CG>>NCCZ4w#0x|&5q|5bGM`8y ziivSV4)F}Jl2}WqR5>{RoKmj|z7+F^oKo*vUwVbLv8b}pnmNTvN=aMet4^;-vI_jZ ziqh0d%U4xYQR%Vf`pOHv>ng0GN^7IH!Yc5U_$qw`IW5OyTRX;CtNi6wjhfnQCDr)z zihcQ&o0B{(3mzH~6I`CqbD*`q;@`gNLiGO3TG$CGB_W$xq zUjbe5=UweBDz(}#$_iE_r2kLNdH!otEr%11L`01kmO0$Y$R3lK5zHMFmu>H}n?id- zb#{y0WM3h!S@xOGZu^3LCA2%V+ZvbWt@c)Uy-#M3%Fdct?Je~CJ^B8%E!BfMcTD+b z=Io21t>Gq`LQjRZ(9C5kCC$lmaNo26o>QT^&=z_Lx8JPq-Gk5``>JI(+t=y*x_!Y4 zZ4GVBX{k-AjmY|s2&1e*?S{1M|v=D?!7f%bQHdL{P*7uRPicjXcN$4b6+B zX=uiD44cTAvQir>&6zZ_amJbs$4VO~M{P!mzQ5hI@g1N2yzlzo-DjWoeODWIhJ05Q z;#`ah+iJAEL|39;bF&{XHG%`Jp^aGw{17|hY3zcRa0-S-n)Sq~sPn6E5Z0isJBKas z3f_a)u^HaR)@F`1`9Emg*1I4Yl}I0q#vBab6x5B%kV|YC>PBm@C01fMR^v*n!ByC; zof)}WE$RghqV977Tk?E6O(%*I-}_!cuC*rYgl*ebIj6x+e88fg5oo;mAq2rbW$1^^S37AN|N-!5g@dH$e52F%1 zio@|M48wM@X3MbyPQ*>9`~HT^wOvD9_YbOa%>&e5Kj^>&tF&EFrAtR;oQrz!B-C2S zL)~C5^76I>nI&6`N?-@-4Qo)pJK&E$Kt1qte|!$LSbq&r|5kKDsDF2CgF#Hfg*X-; z$D>$*G0ej-+=+Vdcc=$mM!m=_RHFZ)N*~2jcv(w8C76oLj%6aZv9*DG6uG==bgM048E0ev5tZHmV|tab{_lgyd*W37up* z705JN11`eTs0U}WK>Fc4T#dU>Qxs3RCSiY6!WF31y~(#4RheC=2k%2o!68&7jv+7N z*cWtkgL60pf5ded2zqb!7HXfri&_&0@nt-M_hD8KvsqY*599Z!H;ZLK^~M3H3g`LG zMBT3pBenmR&{0WW@qbW-N@$PoJO254ROUx;TL^D}n!~9H-UFAQrf4(1g!}P+OlBS^ zn&o32I!KOo0bBEY`-hI+ETX4Z+5l=U<57!c2J2NAORWy|`>#=J;wRJ# z+(0eDCRFL$_cHqud*E@rf{rqIk2=w}v#2?1M0SX^>O&HkfGh_aiL4_lM6H=})PoP< z19$?1*oe9=js=v9naHf#deratq1MzVeW|}Pyvhm9Q4?w^S~0OCZ2?qb={O8$qTX;X zs-*9u9&{Fe$G`Ci{>Fo|3mAVci z@Ke;ApG4j0I&zIQqSiplVDE!62KDzdFo>n71U8_4UxS+3Z&4M!g&i=AEvAG!V-TGr zI$Bg?{S#&Q2;(iNM9w2GX-%l@HDD-TL!5^Pao~e2HcER5$1}dhJZV?tJmhV^eAJ7r zK~2>bWaC%^l9OW>=qU53RPTcmk9zP})S_F2x1Y80K-l7+dfL z$1%wGWVV<1RaC`p;%69_<4whnc#830-fB9AjpTEUMX1VEqtl(v89MShhGF!hUWNhe z!Z^eCY1ATGiqZHQ_Q1WU#E&8QSw8U^F^-r?>>$Y5GKhFWZM%nKq1Kj2bbH=uPQAG40mJ@FhYHK{~$*RQ2tM%jBeimAv78T#7>Sp&&^>xAO zu&k7%0m;GSp~F%J-j_^^7+p}dFlpR^g3_{*f`xMz3=8HKl$I8k1xL>)er`rdFm;wY n%qVe({fkQr=hjyzwg|0{?7cFq{&>HSW2H5Yv$ diff --git a/django/contrib/admin/locale/mn/LC_MESSAGES/django.po b/django/contrib/admin/locale/mn/LC_MESSAGES/django.po index 382511679719..a9a8d6626c64 100644 --- a/django/contrib/admin/locale/mn/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/mn/LC_MESSAGES/django.po @@ -6,14 +6,14 @@ # jargalan , 2011 # Zorig , 2016 # Анхбаяр Анхаа , 2013-2016 -# Баясгалан Цэвлээ , 2011 +# Баясгалан Цэвлээ , 2011,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 08:14+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-04-13 07:10+0000\n" +"Last-Translator: Баясгалан Цэвлээ \n" "Language-Team: Mongolian (http://www.transifex.com/django/django/language/" "mn/)\n" "MIME-Version: 1.0\n" @@ -147,7 +147,7 @@ msgstr "ба" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "" +msgstr "{name} \"{object}\"-ны {fields} өөрчилөгдсөн." #, python-brace-format msgid "Changed {fields}." @@ -172,13 +172,15 @@ msgstr "" #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may edit it again below." -msgstr "" +msgstr "{name} \"{obj}\" амжилттай нэмэгдлээ. Та дахин засах боломжтой." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" амжилттай нэмэгдлээ. Доорх хэсгээс {name} өөрийн нэмэх " +"боломжтой." #, python-brace-format msgid "The {name} \"{obj}\" was added successfully." @@ -187,13 +189,15 @@ msgstr " {name} \"{obj}\" амжилттай нэмэгдлээ." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." -msgstr "" +msgstr "{name} \"{obj}\" амжилттай өөрчилөгдлөө. Та дахин засах боломжтой." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" +"{name} \"{obj}\" амжилттай өөрчилөгдлөө. Доорх хэсгээс {name} өөрийн нэмэх " +"боломжтой." #, python-brace-format msgid "The {name} \"{obj}\" was changed successfully." @@ -215,6 +219,7 @@ msgstr " %(name)s \"%(obj)s\" амжилттай устгагдлаа." #, python-format msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" msgstr "" +"\"%(key)s\" дугаартай %(name)s байхгүй байна. Устсан байсан юм болов уу?" #, python-format msgid "Add %s" diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/nl/LC_MESSAGES/django.mo index f273bf4fdd4c331a63e2d8c1b2d375f03a2ffe0a..65b3a96029c4a9242b2e5821f12aa31c4842b82f 100644 GIT binary patch delta 5455 zcma*p32>C<9mnw}$RRg^0Rl)~5|Ry&gm5HCBnhVoa>Roht()DK%_f_D!@j$T1nma# zrW)nNS}Pu4QS4AHVyAVA+S)R-wzhRT+FD2Pn2y?dwe{#YRr>wyz6oKfQ{UmgpXa@v zWU<8du+!~_=ObN=;@ zZ~^Bdh8t6f7o*<45&Pr4H~Ao8+Is~1(Z2Z!1*PUG9D~PD4ZebE;8Rq? z`J_Ra3E()a!7Fh+ZpG*E5nOb(FR6n}zBT^{LBk0+xVo{0?BRHEKrh9t>cgiNyuqdJg64X^|C+)e)Z?Wp$d z&m;eO;D=n$s(u;=u57{Fhf3W6RL>qq&CyX*s(yGP-xUd1{1 zHts@;1*4H4LVXW@gjzFC;5B$0r{VHKW16rHYqbAgr=XGNPiB6x8Z`wWUk6p;c2oz` zsE*v~-@hBxp@)1A``3SpeYt)dZ_QxKtP8u$#=!J&*sYh)afBvXZKD6<|_&sI!pq+JwL(cP#X z@5juDP;++}JMk#qhBZ^NtNC}xpv-G{CiY{2sJ>ySjE+M!SczJUtI)y*RQ)@rl7Cfj z3m5oj9^*$3zJi+jf8boqVdAt)&cgv%j~d~69E3J%0IfI}+fgIjiOjCqhtu&G>b;Lq z9qUs{{`KH^@_q}>#dbW7hp?_pOO$ECj zKfeuG_~tHD{YUXE{1s|Zr~g7hsdyFD(5I+<-)B~KmFA(=LQvuy?+pu+INwSV$Q6~HaN$30ruhgg{Tg$ zsw4kO@fBPcg)#p|C#t8rQTO-y=La!|a~<~(`mXDEfCv#668VJIjWXJOY@=|X`vjF$ zW}7Uejrj%zmmtgLB7#}Sj2<$3_IwpMw5=)#b+n!MCZWS4V#FSuaNI%MPbfpI@XX=( zUhZ3nMa1>ApZUMqz5WgD@$VAaa_mIYN$6Nc+?FY2|5w}QmscROky)T;VA8*Sm#^66 zmo+6?Cv2R|SGO6{3;EHpl(?H1Nz5X&V#^2}*Am*ITJ=ka?qer~2mKSRgIkG*i2cMy z;yOad8sdUXDf|D+#gto#+1mfzM}&g*b=bc!4!`G@#Rk8;sModWlyCIUQ)m%a5p0ai zvCdbFC2V3fF`vjDw686qMb>?cp>PASoX{#&7xRc!L>DoExRbby(4if1bEcI2nFik` z68<&quo=W|qQSqW@5M@DkSfn5`VevACPD{0DD&T?%n3i=A~q5C5doru*i3XEC4ONJ z4)n|WkZ+CIOOz7Yn)Sq1LLaDu#05m=FW>^am?$8chX^Ky#Edx2rUsf!0&>?y9*9!)k{%NAP&MJ3Jlwkd9b6?SYl9!OgDl~FfYZq?g~ z#$c;!MUz&0(6z#L%ud?jx+5P9dp~zxWm&~cYeq$N#ms3H9D`N_-DKINiD2A~1(Qyq z+Pc(<$8Fb|m&{n+(l9^L5{$;mLr#k~vFP)hGH*ih+(9jN(sEOwknMVx7fiyQjs~m-+f4?8Nh=<;>F3I*>xLSmcHD}pmmRj%W+&RCO<_9`uJc|izOkgp zwUUnA=GYNCZdN%F%jV&tk`{+Ck%^vrURR)S49z$VO?D_b#SQ3IB=cg}`*onz`(o5+ z?`I|XUZgbFYb*KdP%5UOj;QKg=bfm|O;=k*u2~svij%WYW3t_G65(FYe95(?wo7J1 zNt^E4y?S-3V!wrIb6VFTcJ_&|RmNMP#$Y@`7C)M@aQG#e`l2@Nx~?7f7ERq*w=z?G z!nU$wWBffyqVHeThn{D=6I0LeCQd6Hds-KIZimZ{Y?yXa&d@bhqusy)V&rz>$cLrZ z_lctn9o#u@5DvvCK+T_t%%)_ zNJX2yleq=nyXAk3G2sa(&Kz63VxOwcipEPVhSzReZZeh7s))PEL@JbwvK%wDHL{*` z(T>HGhhY3m8!tC&qDj^aRd_QhCXTfNi=22e;lu)^ma|1?EiJ)#IN&u_6!wcI?H1R& zx#FFbOA=Oxraz)JmbpyPpQ&=QAQ*44V_{Ohx-~o6V9%8WTl`xqoX8Sd+E(UxPt4de zkW{835uQ7;X6BAQY@8Qo&z()>DK@6-B)A=oNB(EcIez^;12v1iy2`x%{)pCBZtlat z9+A;CtFL<4`>g8w)0j1vu}m_HwL<-EnOzFnCG5MN zjqDwtlTRPtn)7U7U^|QL>eFv$cALMY%B?lt!MU@SevRj>=2SeH-Q1@pKJM^I3C3K$ z2kmyE*;_UD+=4H!>(mTnn>_#jKlZg+oK#&>!7MQTwrJ2_g;qnfNekA`S$6rePF>c4 zCfM6i{c(C7TY@Ywi%x6zlSU~g%M7PE+SJS%xjTpuoIqc7l3^u!7cwW~z9nnyu)w2sS z+0)ixYq9mbfi=&bt+lT$-TO)Jjha|rT5`SBwPOkz9J`s1kUH*80UF~sU~jrlF^>BkRSpMk~#K#C6z=W%zV_ z{4Jb8`y$htHsU$B6u)Ln!K{ccSdY4)4K-5n_Z{R%19}dXiI-3VdL0W3*!dkh7vMPZ zqZBW|HQ0m~;vr1oi`WCFFj}R$7B#>*xCk4uC*F?RaWA%DU)E3kI>?%vO{n|w#pGY9 z+ZkW57nS1sP^tSqYQ!(19{e*@O}vTf;5{S><}b+dnjWN20~msuVKM5u^0++(^}L3- zy?iA3S2bVFf&SQyC3p*3Jd8Pf1y|x6@_!rd!Fn9Z&3f<()B`u5X6mB`+KEd2ZX_Az zVblP>i>!|ML4h6mHa|rT;1sHZzoUxfU#O1G<&~I-BauU95h|4%P#H+0mgEN1^>^Si zcnBxpWn(FD%%U=K2ph0)m>m_>yEqm9gDkU|N z|ADt)qm7pE$9M(pw@?$BJT9vKS&;=Zj~%715jC=A)EccrrK$rpQy`5bpH`B8 zU06y>Yc>;S<091SaXsqB05!vd*bl#nn!$0@jDCoED_%j({P#Eod$RDlZ#pUy3sIR^ zhkJ4BB=X@#<;8|2=zZai>5m_DcHtLHzV5Ty_Vwba{nbx8n+!&tVn~wWwufuZu zD{75L@%2&1i;&eZm!r<_Lh8;O#o_pKyc<7^kMF1>tF({cD*PNPRcy_a{2JVW&G-iH z#|ib(8lAumv|q)rc;-akgIZK3>QHOE0Cm0@!==N%v{R@7ccLbE6OvqWHx`bu^D;Z? zsEY;Zfdw3fyKoyGK+Svz-_0b>Lw%r*;As2&O!CJ7`3!ZPy=W|6>VDs z`PYdo2Xy1E_<}ug`v4YkT(43-jg#=&iaC?Mb{l{~A)C_+ax4DmaG(JY{ zh5v19nOeeQ%0C-55q?UwuP@YC;z2dyjim~=)%=bi!v7brDLfjz5h?a}$L$O*Bd(0w zPv8y2DB>`oO-s_O=U+zLOlZ?Gu>58Lq2kikO|%o#pE*cq+dwGg7Za>r_(~nYFK#Nn zo`ja@3SuYW5vMm5V>tio;v)~oRw0=W*UF8L-HYcESt3uUKK2p%NNL+c+)3mJ3f|Nb zt%OhLZP2DQ*Vj;61wqk9`R@!5MSnna#rv0ITfDyuzd>9}Y$O_qmBa^j6AF z2XPxQg;+|gAodg59wJJJG!g#&KZczI(TmU*@ESs|kG9H)@VA`bnlH5V3Tc}ZVID&j z^G(E!#N&iEOI$^4ChjM+O(y0OTQvWRi0g^vgtiI9twax^AE9k~gjs@D6NlnNsJ?Q5Kj`??urP%RK{M^9b8j3D!6#yYsXg) z`e4|?>652Uw^QrtXH1_om95`0C!3qRI^%fRl#}x_^|mqPlO3Mza@scKx_m#=Zd%Ul+OF_=*E8{@ z2{-Ge=$i>J(KF+SPuH6!H@YKufAHAg*u?n*mbr{Ox+m%0JL#r*YuyZ&w7VIb4m-)X zHX1GuIeYGZJSTX0QbX~XtG+bX>eAz*s_rNnxR`0sdES!bjk)8`PTp0tGS2v-4#!LQ zHk-`3r;qq;`LyfhoLtg1i_=ar<+Nwqtmffv3{&kT-F(*=X3W0C=94+gf@b{;XOdpR zEO9g0oSU*=t&MQhT>0e%a@t6sItgaXTpt88C-z@;x=gGSJU?@M@ZrqHqE*4PSx*(& z;McQ`j@KfLGf}NlsJ7GbY_5BYYRYrcE~T9eo~tY2>euT|22J(D22$#AiC5c8-QfAz z({;G!%ZKgeyq9C?i8Bilzctyon1k8%b=+BU&e>i{%Gm#ZZXGbEqRzD&{d~?Qv$iwo zL?u8rlxgcQ33AirwrYCTX-_A;WHy)4*C89Yb3QJt_I-~f3Tx0@xI)$yw?tS8ZBcrrO#{VdPnF9+cNQgkJ^L zCzsD>+O40qXA8K%>&=xczt!qehgH@|Wi6Gc@^aeReC1D1p_q9?qg=LA^E|@elF$-x z;n~8kwrlJ*KhIzpe{0h7IJ;R@WOKabHtoAPj!@bxaKa6|d4=K0xLm{T!^f(QgMJMs v2ib&D$PLjo!KsGSG)9w2@=}|mx5>}9XH_MJg3q{#D7Vb`EA8N(`Q!cz$8lIN diff --git a/django/contrib/admin/locale/nl/LC_MESSAGES/django.po b/django/contrib/admin/locale/nl/LC_MESSAGES/django.po index 5c4efe94885f..673284ffc26d 100644 --- a/django/contrib/admin/locale/nl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/nl/LC_MESSAGES/django.po @@ -9,15 +9,16 @@ # Jannis Leidel , 2011 # Jeffrey Gelens , 2011-2012 # dokterbob , 2015 -# Sander Steffann , 2014-2015 +# Sander Steffann , 2014-2015 # Tino de Bruijn , 2011 +# Tonnes , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-04-04 08:54+0000\n" -"Last-Translator: Claude Paroz \n" +"PO-Revision-Date: 2017-05-02 10:02+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -115,28 +116,28 @@ msgid "action flag" msgstr "actievlag" msgid "change message" -msgstr "wijzig bericht" +msgstr "wijzigingsbericht" msgid "log entry" -msgstr "logregistratie" +msgstr "logboekvermelding" msgid "log entries" -msgstr "logregistraties" +msgstr "logboekvermeldingen" #, python-format msgid "Added \"%(object)s\"." -msgstr "Toegevoegd \"%(object)s\"." +msgstr "'%(object)s' toegevoegd." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Gewijzigd \"%(object)s\" - %(changes)s" +msgstr "'%(object)s' gewijzigd - %(changes)s" #, python-format msgid "Deleted \"%(object)s.\"" -msgstr "Verwijderd \"%(object)s.\"" +msgstr "'%(object)s' verwijderd." msgid "LogEntry Object" -msgstr "LogEntry Object" +msgstr "LogEntry-object" #, python-brace-format msgid "Added {name} \"{object}\"." @@ -169,23 +170,23 @@ msgstr "Geen" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." msgstr "" -"Houdt \"Control\", of \"Command\" op een Mac, ingedrukt om meerdere te " +"Houd 'Control', of 'Command' op een Mac, ingedrukt om meerdere items te " "selecteren." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may edit it again below." msgstr "" -"De {name} \"{obj}\" is succesvol toegevoegd. Je kunt het hieronder nog eens " -"wijzigen." +"De {name} '{obj}' is met succes toegevoegd. U kunt deze hieronder nogmaals " +"bewerken." #, python-brace-format msgid "" "The {name} \"{obj}\" was added successfully. You may add another {name} " "below." msgstr "" -"De {name} \"{obj}\" is succesvol toegevoegd. Je kunt hieronder nog een " -"{name} toevoegen." +"De {name} '{obj}' is met succes toegevoegd. U kunt hieronder nog een {name} " +"toevoegen." #, python-brace-format msgid "The {name} \"{obj}\" was added successfully." @@ -195,15 +196,15 @@ msgstr "De {name} \"{obj}\" is succesvol toegevoegd." msgid "" "The {name} \"{obj}\" was changed successfully. You may edit it again below." msgstr "" -"De {name} \"{obj}\" is succesvol gewijzigd. Je kunt het hieronder nog eens " -"wijzigen." +"De {name} '{obj}' is met succes gewijzigd. U kunt deze hieronder nogmaals " +"bewerken." #, python-brace-format msgid "" "The {name} \"{obj}\" was changed successfully. You may add another {name} " "below." msgstr "" -"De {name} \"{obj}\" is succesvol gewijzigd. Je kunt hieronder nog een {name} " +"De {name} '{obj}' is met succes gewijzigd. U kunt hieronder nog een {name} " "toevoegen." #, python-brace-format @@ -214,27 +215,27 @@ msgid "" "Items must be selected in order to perform actions on them. No items have " "been changed." msgstr "" -"Er moeten items worden geselecteerd om acties op uit te voeren. Geen items " -"zijn veranderd." +"Er moeten items worden geselecteerd om acties op uit te voeren. Er zijn geen " +"items gewijzigd." msgid "No action selected." msgstr "Geen actie geselecteerd." #, python-format msgid "The %(name)s \"%(obj)s\" was deleted successfully." -msgstr "\"%(obj)s\" van type %(name)s is verwijderd." +msgstr "De %(name)s '%(obj)s' is met succes verwijderd." #, python-format msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" -msgstr "" +msgstr "%(name)s met ID '%(key)s' bestaat niet. Misschien is deze verwijderd?" #, python-format msgid "Add %s" -msgstr "Toevoegen %s" +msgstr "%s toevoegen" #, python-format msgid "Change %s" -msgstr "Wijzig %s" +msgstr "%s wijzigen" msgid "Database error" msgstr "Databasefout" @@ -242,8 +243,8 @@ msgstr "Databasefout" #, python-format msgid "%(count)s %(name)s was changed successfully." msgid_plural "%(count)s %(name)s were changed successfully." -msgstr[0] "%(count)s %(name)s is succesvol gewijzigd." -msgstr[1] "%(count)s %(name)s zijn succesvol gewijzigd." +msgstr[0] "%(count)s %(name)s is met succes gewijzigd." +msgstr[1] "%(count)s %(name)s zijn met succes gewijzigd." #, python-format msgid "%(total_count)s selected" @@ -274,26 +275,26 @@ msgstr "" "de volgende beschermde gerelateerde objecten: %(related_objects)s" msgid "Django site admin" -msgstr "Django sitebeheer" +msgstr "Django-websitebeheer" msgid "Django administration" -msgstr "Djangobeheer" +msgstr "Django-beheer" msgid "Site administration" -msgstr "Sitebeheer" +msgstr "Websitebeheer" msgid "Log in" -msgstr "Inloggen" +msgstr "Aanmelden" #, python-format msgid "%(app)s administration" -msgstr "%(app)s beheer" +msgstr "%(app)s-beheer" msgid "Page not found" msgstr "Pagina niet gevonden" msgid "We're sorry, but the requested page could not be found." -msgstr "Onze excuses, maar de gevraagde pagina bestaat niet." +msgstr "Het spijt ons, maar de opgevraagde pagina kon niet worden gevonden." msgid "Home" msgstr "Voorpagina" @@ -311,25 +312,25 @@ msgid "" "There's been an error. It's been reported to the site administrators via " "email and should be fixed shortly. Thanks for your patience." msgstr "" -"Er heeft zich een fout voorgedaan. De fout is via email gemeld aan de " -"website administrators en zou snel verholpen moeten zijn. Bedankt voor uw " +"Er heeft zich een fout voorgedaan. Dit is via e-mail bij de " +"websitebeheerders gemeld en zou snel verholpen moeten zijn. Bedankt voor uw " "geduld." msgid "Run the selected action" -msgstr "Voer de geselecteerde actie uit" +msgstr "De geselecteerde actie uitvoeren" msgid "Go" -msgstr "Voer Uit" +msgstr "Uitvoeren" msgid "Click here to select the objects across all pages" msgstr "Klik hier om alle objecten op alle pagina's te selecteren" #, python-format msgid "Select all %(total_count)s %(module_name)s" -msgstr "Selecteer alle %(total_count)s %(module_name)s" +msgstr "Alle %(total_count)s %(module_name)s selecteren" msgid "Clear selection" -msgstr "Leeg selectie" +msgstr "Selectie wissen" msgid "" "First, enter a username and password. Then, you'll be able to edit more user " @@ -353,13 +354,13 @@ msgstr "Herstel de fouten hieronder." #, python-format msgid "Enter a new password for the user %(username)s." msgstr "" -"Geef een nieuw wachtwoord voor gebruiker %(username)s." +"Voer een nieuw wachtwoord in voor de gebruiker %(username)s." msgid "Welcome," msgstr "Welkom," msgid "View site" -msgstr "Bekijk site" +msgstr "Website bekijken" msgid "Documentation" msgstr "Documentatie" @@ -375,17 +376,17 @@ msgid "History" msgstr "Geschiedenis" msgid "View on site" -msgstr "Toon op site" +msgstr "Weergeven op website" msgid "Filter" msgstr "Filter" msgid "Remove from sorting" -msgstr "Verwijder uit de sortering" +msgstr "Verwijderen uit sortering" #, python-format msgid "Sorting priority: %(priority_number)s" -msgstr "Sorteer prioriteit: %(priority_number)s" +msgstr "Sorteerprioriteit: %(priority_number)s" msgid "Toggle sorting" msgstr "Sortering aan/uit" @@ -400,8 +401,8 @@ msgid "" "following types of objects:" msgstr "" "Het verwijderen van %(object_name)s '%(escaped_object)s' zal ook " -"gerelateerde objecten verwijderen. Echter u heeft geen rechten om de " -"volgende typen objecten te verwijderen:" +"gerelateerde objecten verwijderen. U hebt echter geen rechten om de volgende " +"typen objecten te verwijderen:" #, python-format msgid "" @@ -416,8 +417,8 @@ msgid "" "Are you sure you want to delete the %(object_name)s \"%(escaped_object)s\"? " "All of the following related items will be deleted:" msgstr "" -"Weet u zeker dat u %(object_name)s \"%(escaped_object)s\" wilt verwijderen? " -"Alle volgende objecten worden verwijderd:" +"Weet u zeker dat u %(object_name)s '%(escaped_object)s' wilt verwijderen? " +"Alle volgende gerelateerde objecten worden verwijderd:" msgid "Objects" msgstr "Objecten" @@ -426,10 +427,10 @@ msgid "Yes, I'm sure" msgstr "Ja, ik weet het zeker" msgid "No, take me back" -msgstr "Nee, ga terug" +msgstr "Nee, teruggaan" msgid "Delete multiple objects" -msgstr "Verwijder meerdere objecten" +msgstr "Meerdere objecten verwijderen" #, python-format msgid "" @@ -506,7 +507,7 @@ msgid "" "page. Would you like to login to a different account?" msgstr "" "U bent geverifieerd als %(username)s, maar niet bevoegd om deze pagina te " -"bekijken. Wilt u inloggen met een ander account?" +"bekijken. Wilt u zich aanmelden bij een andere account?" msgid "Forgotten your password or username?" msgstr "Wachtwoord of gebruikersnaam vergeten?" @@ -534,22 +535,22 @@ msgid "Save" msgstr "Opslaan" msgid "Popup closing..." -msgstr "Popup wordt gesloten..." +msgstr "Pop-up wordt gesloten..." #, python-format msgid "Change selected %(model)s" -msgstr "Wijzig geselecteerde %(model)s" +msgstr "Geselecteerde %(model)s wijzigen" #, python-format msgid "Add another %(model)s" -msgstr "Voeg nog een %(model)s toe" +msgstr "Nog een %(model)s toevoegen" #, python-format msgid "Delete selected %(model)s" -msgstr "Verwijder geselecteerde %(model)s" +msgstr "Geselecteerde %(model)s verwijderen" msgid "Search" -msgstr "Zoek" +msgstr "Zoeken" #, python-format msgid "%(counter)s result" @@ -574,7 +575,7 @@ msgid "Thanks for spending some quality time with the Web site today." msgstr "Bedankt voor de aanwezigheid op de site vandaag." msgid "Log in again" -msgstr "Log opnieuw in" +msgstr "Opnieuw aanmelden" msgid "Password change" msgstr "Wachtwoordwijziging" @@ -590,13 +591,13 @@ msgstr "" "invoeren, zodat we kunnen controleren of er geen typefouten zijn gemaakt." msgid "Change my password" -msgstr "Wijzig mijn wachtwoord" +msgstr "Mijn wachtwoord wijzigen" msgid "Password reset" msgstr "Wachtwoord hersteld" msgid "Your password has been set. You may go ahead and log in now." -msgstr "Uw wachtwoord is ingesteld. U kunt nu verder gaan en inloggen." +msgstr "Uw wachtwoord is ingesteld. U kunt nu verdergaan en zich aanmelden." msgid "Password reset confirmation" msgstr "Bevestiging wachtwoord herstellen" @@ -625,9 +626,9 @@ msgid "" "We've emailed you instructions for setting your password, if an account " "exists with the email you entered. You should receive them shortly." msgstr "" -"We hebben u instructies toegestuurd om uw wachtwoord in te stellen, als er " -"een account bestond met het door u opgegeven emailadres. U zou deze binnen " -"korte tijd moeten ontvangen." +"We hebben u instructies gestuurd voor het instellen van uw wachtwoord, als " +"er een account bestaat met het door u ingevoerde e-mailadres. U zou deze " +"straks moeten ontvangen." msgid "" "If you don't receive an email, please make sure you've entered the address " @@ -651,24 +652,24 @@ msgid "Your username, in case you've forgotten:" msgstr "Uw gebruikersnaam, mocht u deze vergeten zijn:" msgid "Thanks for using our site!" -msgstr "Bedankt voor het gebruik van onze site!" +msgstr "Bedankt voor het gebruik van onze website!" #, python-format msgid "The %(site_name)s team" -msgstr "Het %(site_name)s team" +msgstr "Het %(site_name)s-team" msgid "" "Forgotten your password? Enter your email address below, and we'll email " "instructions for setting a new one." msgstr "" -"Wachtwoord vergeten? Vul uw emailadres hieronder in, en we zullen " -"instructies voor het opnieuw instellen van uw wachtwoord mailen." +"Wachtwoord vergeten? Vul hieronder uw e-mailadres in, en we sturen " +"instructies voor het instellen van een nieuw wachtwoord." msgid "Email address:" -msgstr "Emailadres:" +msgstr "E-mailadres:" msgid "Reset my password" -msgstr "Herstel mijn wachtwoord" +msgstr "Mijn wachtwoord opnieuw instellen" msgid "All dates" msgstr "Alle data" @@ -694,4 +695,4 @@ msgid "Currently:" msgstr "Huidig:" msgid "Change:" -msgstr "Wijzig:" +msgstr "Wijzigen:" diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pl/LC_MESSAGES/django.mo index 064a44ac70038821f8611f2b7ac3b8229622b85e..13180e92044b67174e51a9a857c361d6ef9ce2de 100644 GIT binary patch delta 2325 zcmZA0U2IfE6u|MhC{$`GNcm8(IwFOZZd)j&K&6x-5F#odL=qtCbbD=YclXY+d+WMa z!fJ&ynxN4eF$fPJ;l-FJtchANM%nd2G4kYh;@5*;5#Q7oqyNLM8f_@QnYp)T&YU^- z)FaNrk2){!om^<0q|}9lN;N8_eqW^2Y@D-LsreYg_i!`bi4Dz4-G~FY9>*|_Wt6{b zX;Er2_Td!VSe@UF^~~?WtI*(F9BEMnbpwMj7OusUD2Ys94Ss^t@GF!*yo3_yH>|@c zOO(oD1LkoYr=!!V)b)4`N+Rtz6<6R)>_drrS8Jg%xQ_)%z(?759N9xXi5>VlO2$9n z40K{jEyI~80XLv*u&X*Z)p>wYkpoo^qU`fHO1x7A22Bj!Knd_QO6h+>jWul~fz5ar zU&D8CQ@c|8a2naYfCZd|OO{qn-iLL}_g3|=pZNoLGroqBSm6r>K7(&?1#ah9&)_&p zB^o;`6>37cg5_v&4Nk`w@hqOht!S55Hhv4GCoWXy-=Mtz3(A%Ji5#?`rgT*vEI`S) z6R*HNoQb!hROW7!%YQy0h*EOt6r3^!J=e*xcdA!%8Ab4Y9Kroaf2oFBl-h~?RgWO;p?<<$c>S7+0Ul?50c9hvmtTAU z=loEHNoOm;mwAzIcK7~@!roKv##8B>d zDL#d3QBHOd_u(JNKV{cdzNVLuz12mO{p$NSS4uyRZ^L1s3FR&qqm;M@rO9qXY09lA zSG5D>B=?{M3i^xB)t;WWv8%1KQ!nf6S=!mUl%Lw$O*UxTnK6DgX#$(+(K~I*WJA5h zj0845tk(oK7y8yLTINRMVmeXn#hC>EA1 za^|%WGCpklAvfES)oORj4(9Lo5;kX|H(M7^{byGEp>>b5AZ5$vTwRG-8rNCkymKP_ zJ7nB*Q_ZijBfN7$Lg@cQ(3RxR7jvxpUh3aN z1f(#Bek_sLbW5B=xr<>>pJNaVmjpT z;ZU-4bWqY8aKp6c#zVJ!FdO6yx#{%zVtIng7}2@3#|01OMiUtuu%dV7>rs1-=s4p=Mk!1|8HOVCX-HjIa@wPr@XReL%pP3J-fDkRM*>EyNzusy&i4t-8F-0 zp2`w7b-5>sFZJ$su8rw|ypG$1P3HIjv^$s&=wyPf@B4XSOwOLmr_G?7i0Prhyi}j| SHEEmjJ=&q!v_u{2g1-R7j}T-4 delta 2014 zcmY+@Yitx%6u|L2r928%3TTjI%wnGgWy?eX~lbN~P3a)k;-h(Q2iZ;u`!K@5e4&RHM{#+>hHZgMD}j zOR+kl)NI_0b8uU(zZ37IzX#{xlURoR5tW%)FvCDO1Fxe5@(Iqx@9_@&8Rf<6C=1=h zLY%cmse@RAK91r-oW^BXSQ`cs!2aU{4X&k}II;D={ z8+Z|`>%)`(iWBtrH7Herd5z)8%dn7sbIwleq~C>iY+MGl%#ztfOAo7Eh? zSd6piYg~-0P&R78CD@6Qx*p6&FQ<Smq%GYonCE_1*^S_~-=r+np7i?7OOQunqgwHFWO{1Pb-wR97OhP!jVjpyl4qil3;b66W+pj^fJ z_Ao=6Q2L#?2zR1<&fV?gUrrolU;+BM2gXrOd*e@>A#dr|}k6U|C1FUsK19F!k*W7)x%y2+Twz|*!h zHI^Nz=`jpH_$4yBGHni8Q=d7Oc6(Xg^L5%ydZsfuu9Gn{>84C4JGQ3UD3^7sYWfbC zPQR6mB(-`xZd;D+29vd`XV0{=JvXO@S@=Dg~-pTRk)CKLB>6EWyuH%{6Lru*_!NYD}dUD*eV%bmEZ8Gvb ze`q-Pd;Q@c-Mq4(H(|!qHvR=4HkW3SVUE?Q&kG z^S@Mb3oK7kT_=~ML0h}pvE!N}+h)|ANeG>xMA}MgOYVlq*reA?S#cKSN*vpq8uj&1 z!tEVoOy1-HC(QcfF`aPJDSL7}Z90Cv-kvW1UrZ^V>EXC65%jk2EJ+R7N$v5r9AxI+ zm^k0wK1c3mYV5?8eFa^%mGmsnUz_dUa@Z)-y}q`hzQ?_?S)Uto2T4UNxV`m8P}u4H E3y{@`H2?qr diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/django.po b/django/contrib/admin/locale/pl/LC_MESSAGES/django.po index 662b7be41a95..48de4ef05179 100644 --- a/django/contrib/admin/locale/pl/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pl/LC_MESSAGES/django.po @@ -13,13 +13,14 @@ # Ola Sitarska , 2013 # Ola Sitarska , 2013 # Roman Barczyński , 2014 +# Tomasz Kajtoch , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 10:59+0000\n" -"Last-Translator: m_aciek \n" +"PO-Revision-Date: 2017-04-22 12:02+0000\n" +"Last-Translator: Tomasz Kajtoch \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -31,7 +32,7 @@ msgstr "" #, python-format msgid "Successfully deleted %(count)d %(items)s." -msgstr "Usunięto %(count)d %(items)s." +msgstr "Pomyślnie usunięto %(count)d %(items)s." #, python-format msgid "Cannot delete %(name)s" @@ -133,42 +134,42 @@ msgstr "Dodano „%(object)s”." #, python-format msgid "Changed \"%(object)s\" - %(changes)s" -msgstr "Zmieniono „%(object)s” – %(changes)s " +msgstr "Zmieniono „%(object)s” - %(changes)s " #, python-format msgid "Deleted \"%(object)s.\"" msgstr "Usunięto „%(object)s”." msgid "LogEntry Object" -msgstr "Obiekt typu LogEntry" +msgstr "Obiekt LogEntry" #, python-brace-format msgid "Added {name} \"{object}\"." msgstr "Dodano {name} „{object}”." msgid "Added." -msgstr "Dodany." +msgstr "Dodano." msgid "and" msgstr "i" #, python-brace-format msgid "Changed {fields} for {name} \"{object}\"." -msgstr "Zmieniono {fields} w {name} „{object}”." +msgstr "Zmodyfikowano {fields} w {name} „{object}”." #, python-brace-format msgid "Changed {fields}." -msgstr "Zmieniono {fields}." +msgstr "Zmodyfikowano {fields}." #, python-brace-format msgid "Deleted {name} \"{object}\"." msgstr "Usunięto {name} „{object}”." msgid "No fields changed." -msgstr "Żadne pole nie zmienione." +msgstr "Żadne pole nie zostało zmienione." msgid "None" -msgstr "brak" +msgstr "Brak" msgid "" "Hold down \"Control\", or \"Command\" on a Mac, to select more than one." @@ -275,8 +276,8 @@ msgid "" "Deleting %(class_name)s %(instance)s would require deleting the following " "protected related objects: %(related_objects)s" msgstr "" -"Usunięcie %(class_name)s %(instance)s spowoduje usunięcia następujących " -"chronionych obiektów pokrewnych: %(related_objects)s" +"Usunięcie %(class_name)s %(instance)s może wiązać się z usunięciem " +"następujących chronionych obiektów pokrewnych: %(related_objects)s" msgid "Django site admin" msgstr "Administracja stroną Django" @@ -292,16 +293,16 @@ msgstr "Zaloguj się" #, python-format msgid "%(app)s administration" -msgstr "%(app)s – administracja" +msgstr "administracja %(app)s" msgid "Page not found" -msgstr "Strona nie znaleziona" +msgstr "Strona nie została znaleziona" msgid "We're sorry, but the requested page could not be found." -msgstr "Niestety, żądana strona nie została znaleziona." +msgstr "Przykro nam, ale żądana strona nie została znaleziona." msgid "Home" -msgstr "Początek" +msgstr "Strona główna" msgid "Server error" msgstr "Błąd serwera" @@ -316,9 +317,8 @@ msgid "" "There's been an error. It's been reported to the site administrators via " "email and should be fixed shortly. Thanks for your patience." msgstr "" -"Niestety wystąpił błąd. Administratorzy strony zostali o nim powiadomieni " -"poprzez email i niebawem zaistniały problem powinien zostać rozwiązany. " -"Dziękujemy za wyrozumiałość." +"Niestety wystąpił błąd. Zostało to zgłoszone administratorom strony poprzez " +"email i niebawem powinno zostać naprawione. Dziękujemy za cierpliwość." msgid "Run the selected action" msgstr "Wykonaj wybraną akcję" @@ -392,7 +392,7 @@ msgid "Sorting priority: %(priority_number)s" msgstr "Priorytet sortowania: %(priority_number)s " msgid "Toggle sorting" -msgstr "Zmień sortowanie" +msgstr "Przełącz sortowanie" msgid "Delete" msgstr "Usuń" @@ -403,16 +403,16 @@ msgid "" "related objects, but your account doesn't have permission to delete the " "following types of objects:" msgstr "" -"Usunięcie %(object_name)s '%(escaped_object)s' spowoduje skasowanie " -"obiektów, które są z nim powiązane. Niestety nie posiadasz uprawnień do " -"usunięcia następujących typów obiektów:" +"Usunięcie %(object_name)s '%(escaped_object)s' może wiązać się z usunięciem " +"obiektów z nim powiązanych, ale niestety nie posiadasz uprawnień do " +"usunięcia obiektów następujących typów:" #, python-format msgid "" "Deleting the %(object_name)s '%(escaped_object)s' would require deleting the " "following protected related objects:" msgstr "" -"Usunięcie %(object_name)s '%(escaped_object)s' wymaga skasowania " +"Usunięcie %(object_name)s '%(escaped_object)s' może wymagać skasowania " "następujących chronionych obiektów, które są z nim powiązane:" #, python-format @@ -421,7 +421,7 @@ msgid "" "All of the following related items will be deleted:" msgstr "" "Czy chcesz skasować %(object_name)s „%(escaped_object)s”? Następujące " -"zależne obiekty zostaną skasowane:" +"obiekty powiązane zostaną usunięte:" msgid "Objects" msgstr "Obiekty" @@ -482,7 +482,7 @@ msgid "Add" msgstr "Dodaj" msgid "You don't have permission to edit anything." -msgstr "Nie masz uprawnień by edytować cokolwiek." +msgstr "Nie masz uprawnień, by cokolwiek edytować." msgid "Recent actions" msgstr "Ostatnie działania" @@ -491,7 +491,7 @@ msgid "My actions" msgstr "Moje działania" msgid "None available" -msgstr "Brak" +msgstr "Brak dostępnych" msgid "Unknown content" msgstr "Zawartość nieznana" @@ -514,7 +514,7 @@ msgstr "" "dostępu do tej strony. Czy chciałbyś zalogować się na inne konto?" msgid "Forgotten your password or username?" -msgstr "Nie pamiętasz swojego hasła, bądź nazwy konta użytkownika?" +msgstr "Nie pamiętasz swojego hasła lub nazwy użytkownika?" msgid "Date/time" msgstr "Data/czas" @@ -529,8 +529,8 @@ msgid "" "This object doesn't have a change history. It probably wasn't added via this " "admin site." msgstr "" -"Ten obiekt nie ma historii zmian. Najprawdopodobniej wpis ten nie został " -"dodany poprzez panel administracyjny." +"Ten obiekt nie ma historii zmian. Najprawdopodobniej nie został on dodany " +"poprzez panel administracyjny." msgid "Show all" msgstr "Pokaż wszystko" @@ -539,7 +539,7 @@ msgid "Save" msgstr "Zapisz" msgid "Popup closing..." -msgstr "Zamykanie okienka..." +msgstr "Zamykanie okna..." #, python-format msgid "Change selected %(model)s" @@ -566,19 +566,19 @@ msgstr[3] "%(counter)s wyników" #, python-format msgid "%(full_result_count)s total" -msgstr "%(full_result_count)s trafień" +msgstr "%(full_result_count)s łącznie" msgid "Save as new" -msgstr "Zapisz jako nowe" +msgstr "Zapisz jako nowy" msgid "Save and add another" -msgstr "Zapisz i dodaj nowe" +msgstr "Zapisz i dodaj nowy" msgid "Save and continue editing" msgstr "Zapisz i kontynuuj edycję" msgid "Thanks for spending some quality time with the Web site today." -msgstr "Dziękujemy za odwiedzenie serwisu." +msgstr "Dziękujemy za spędzenie cennego czasu na stronie." msgid "Log in again" msgstr "Zaloguj się ponownie" @@ -592,7 +592,10 @@ msgstr "Twoje hasło zostało zmienione." msgid "" "Please enter your old password, for security's sake, and then enter your new " "password twice so we can verify you typed it in correctly." -msgstr "Podaj swoje stare hasło i dwa razy nowe." +msgstr "" +"Podaj swoje stare hasło, ze względów bezpieczeństwa, a później wpisz " +"dwukrotnie Twoje nowe hasło, abyśmy mogli zweryfikować, że zostało wpisane " +"poprawnie." msgid "Change my password" msgstr "Zmień hasło" @@ -655,10 +658,10 @@ msgstr "" "poniżej:" msgid "Your username, in case you've forgotten:" -msgstr "Twoja nazwa użytkownika:" +msgstr "Twoja nazwa użytkownika, na wypadek, gdybyś zapomniał(a):" msgid "Thanks for using our site!" -msgstr "Dziękujemy za skorzystanie naszej strony." +msgstr "Dziękujemy za korzystanie naszej strony." #, python-format msgid "The %(site_name)s team" diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.mo index 9f984591bbd54ccc04b531e1916322c2a6283aab..e75d6721cbf7f46cbbb55fb7c1665a3d85ed3c91 100644 GIT binary patch delta 162 zcmeyR-m0;IhuN==fq|h`h=E}Wkp2UtQ-JhDVFm_9ApH$W|A5lJp!6Ro{SQhrh%hiP z0o5~!Y>r}1VCOa0H89aNGEy)!v@$l@e4OhUJEuaqLP35`VsgghwL%7>Itqyjd6}sS hj~eDhB^G2BS3PQ&t2gN4R_P4PjjXK#C)b diff --git a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po index b02eb1a93fcd..8d0b27212e88 100644 --- a/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po +++ b/django/contrib/admin/locale/pl/LC_MESSAGES/djangojs.po @@ -7,13 +7,13 @@ # konryd , 2011 # m_aciek , 2016 # Roman Barczyński , 2012 -# Tomasz Kajtoch , 2016 +# Tomasz Kajtoch , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-11-12 12:15+0000\n" +"PO-Revision-Date: 2017-04-22 11:32+0000\n" "Last-Translator: Tomasz Kajtoch \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" @@ -86,7 +86,7 @@ msgid "" "You have unsaved changes on individual editable fields. If you run an " "action, your unsaved changes will be lost." msgstr "" -"Zmiany w niektórych polach nie zostały zachowane. Po wykonaniu akcji zmiany " +"Zmiany w niektórych polach nie zostały zachowane. Po wykonaniu akcji, zmiany " "te zostaną utracone." msgid "" @@ -94,7 +94,7 @@ msgid "" "individual fields yet. Please click OK to save. You'll need to re-run the " "action." msgstr "" -"Wybrano akcję, lecz część zmian w polach nie została zachowana. Kliknij OK " +"Wybrano akcję, lecz część zmian w polach nie została zachowana. Kliknij OK, " "aby zapisać. Aby wykonać akcję, należy ją ponownie uruchomić." msgid "" @@ -102,8 +102,8 @@ msgid "" "fields. You're probably looking for the Go button rather than the Save " "button." msgstr "" -"Wybrano akcję, lecz nie dokonano żadnych zmian. Prawdopodobnie szukasz " -"przycisku „Wykonaj” (a nie „Zapisz”)" +"Wybrano akcję, lecz nie dokonano żadnych zmian w polach. Prawdopodobnie " +"szukasz przycisku „Wykonaj”, a nie „Zapisz”." #, javascript-format msgid "Note: You are %s hour ahead of server time." diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.mo index 60b7a8f165d595cf17b29e0825a3e4ce4ee2cef7..b90e83a21f552416d01674d8afd8d122d54b0470 100644 GIT binary patch delta 1462 zcmXxkOGuPa6u|MTY3BIWQq!bQ%rsv)shN|eq^6QYgxNwP#vF6ZM}8_s79)kt!bM|< zfwjm&R4NmtjgV1MVA>PGO)Z8tQ4tgvp%wiP-{j_Z&*i)K+ znJC{EBrAtXU~r6yXzal_9K-;e#t@uCUwnnZxP(FY5qt4FI#J(YBv?h+*Du95Kq^Y& z9Mo_>M&QX5hp|yN6OyK)1req$(xq$;%+H4MXAlmoY7B%VXL`2fnn zucK6E1Y>XwXo0elK)}mDA7RrGf&lxl`c!@lpvSe}#tV3^ni&Bv#G~oxV!A~d$E-~|5Scy&O zMwzeNWmKjLLx~$O1iMl8bK+L=D;EQK(3~vvfwL9kF20nC^*ZwK_Z3| zr=cV+L}}tOlpZ;OlDGxsf)_mTD8`dtJzyXuS;i~4hIZ`FR%#emP`cceW8CO9%8lG8 z34fp*XceWa!*&}7PemzlnI}Hvu?c1UW%MV%y2c<7M?4=)BGpwdkP@jilm)T5Mxr#7 z?~74xdKjfA8j7(jCvC2}MridENZ%j-`SLlx!~dM$IluFp{k=in-k^JjgFWLx zO1;ZaYPC|TbE8s8=)<)*f(`f-^Dr?>o3~&UaTAu~EnI`2F$Di%DC)A63P(N0VFE^C z7RvWU*~+8R8SG^u8QXC^`Y-|~Fb1bE7++#E&S4aO#9sW4UbJu05-g(ZyNq&xv>a_d z4-Ld67>@^YJlaN1CM0n;hT$M);s~;bdW&*{-x!M_xk{B{60X8Jlmpu^5!+A__n{p8 z5=vzTFa?KE@=bdfNNN2jB?~5y4Wdw@fLtKfoC>3~(vfwL9k1U{6D0qu@gLN23 zoQ{&X7^R8JQF^2SCGioI3%UdG5T=q}-DMypna7K`gmyezL?7S+N|$@LYBzd?aw9)V z!XGFHT14sUuww1tX(%Nw55(00ttjiyU^w~J1qP8g5cpsesjhm4lt?Y1EQs5tB}zy6 z-i&h7T_`=#ggn1W*{(J19+U%g1mZ4~rn`;wv6@9s0)w9n`cYS+HREM$AfCZ`OfqW= z94PTM+>Zc=P(Sip7*~EH)TTW>ZP7kw1N{wZolX z-)?nwIIM10yV+3NY40*rxvcFrx7%eXb2JBrmB(7Gc1J;rt97z3GfEd~bJz?=UG~Z0 HthVI;DRQ}7 diff --git a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po index df0b25edfc7b..7067e885ccb3 100644 --- a/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po +++ b/django/contrib/admin/locale/pt_BR/LC_MESSAGES/django.po @@ -9,6 +9,7 @@ # Fábio C. Barrionuevo da Luz , 2015 # Francisco Petry Rauber , 2016 # Gladson , 2013 +# Guilherme Ferreira , 2017 # semente, 2012-2013 # Jannis Leidel , 2011 # Lucas Infante , 2015 @@ -21,8 +22,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-02-06 02:29+0000\n" -"Last-Translator: Luiz Boaretto \n" +"PO-Revision-Date: 2017-04-19 17:11+0000\n" +"Last-Translator: Guilherme Ferreira \n" "Language-Team: Portuguese (Brazil) (http://www.transifex.com/django/django/" "language/pt_BR/)\n" "MIME-Version: 1.0\n" @@ -232,7 +233,7 @@ msgstr "%(name)s \"%(obj)s\": excluído com sucesso." #, python-format msgid "%(name)s with ID \"%(key)s\" doesn't exist. Perhaps it was deleted?" -msgstr "%(name)s com o ID \"%(key)s\" não existe. Talvez ele foi excluído?" +msgstr "%(name)s com o ID \"%(key)s\" não existe. Talvez tenha sido excluído?" #, python-format msgid "Add %s" diff --git a/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.mo b/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.mo index 3282a663105e799d6aef9043709641ab295e587a..8af54326e0915e8f39e93a78616699c43c3df2a4 100644 GIT binary patch delta 1423 zcmZwFTWm{l9Ki8km$ha$S~aCMCoXZBZtYx_j?HWl!X)mOMMP~yty`0|hPd9S+XGFL4w4Y7rb~7A@ShyEW-EKHu2&l=X}opfBxtH`~QEx|I@~gjlJ8F zUJ>bYh)lvkEXENWiyyEAzheRZ#v*i#%J#cZ|1(hMS%lYdGmgPuXvZRc=2vkdUE;wl zXcS0N@_9K(hZB$DdhEp-{Dk`BgwY~pSb_6!0gl56>Vl7;20V>Az7JEF!j;%u$Q*bM zr{hi3xGxKnBIUeTbm)%1q3-Ap>dq&zT{q4`7uF+l${J*dwBl9_;{$w>Ywu%Kx{=pd zkJFe;1BOuN*@dNeFv*Li=oEVJ9BL|Wq3+}vYRX>Z+HX*I{2r_EGwQ@;%;rZg>U=Hu z#3r&1kI{CqJI&lh)XcrWASOTX(#(s8{OiiL;T(*iuDlN$@EvLiCNM}dR+X~_`6PRh zYRO^L44%M+cnvksTb#jyoJ808Ye_5Pld_W+-BCx*1DK|LB0E5WTLwP$ClRlBV3Z zO_A_^quo5~@TQ+TYHjJs1;-s@YkmHJFAy{W3+v_wEByhpztppu&iOw70>dAy3)EEl zxe8lC@q}+fcc?2K4JBgTb;hPpJQC&EwL2V*^~5_O#^Royf33#douNpysx8);E*Wzs z&w1JEw;pm27}kLGn1{L4Q<{lh5X5F{?M^0Kp P=`YS@dFi$8A4Puw6B^NK delta 1449 zcmZY8UuaWT9Ki7tX=+T@KXz3+ZSCo7wW&svlvWe}q_jF_5p>=BSDYHwTaAsm(nLW7 zGunY5Qd@4-(d}VH#E1D0VzF$jV-FjXJ?!>D-NVE`LlG50I`Gev-(Pyg2QP%r`JJA7 z&iVa*xA&WWYaZB<`9LW(oUhb;JdY*#2NvQj^xy+5z-P|u3?l=LD>Cf89)W;^jeC>h_u zW%v|j;|g{QU^U8q?Rd>anX!+!gzThpLnxK|6T_JKmq9Co+0lwgT9Hw;3?!pA$E}zp?nGHw zK5Ht|YCK2$8Ft_cloqseOEywp;!f#*0zTiK2%KZNLG}SDUGO1Almk zpW@t)rWSsMQQ`wwg;y|)wiA0vS6Wt&oTXZE0d}B#j7RYd_Tyd-Tu5K95})K&mvAlR z9N~O5$v}F2ivK~m`S&OrUPsPScX18A#t4SF4d0ncBkfmTJ8?hKO*M>B{1N5bdE&&A zDC--vQrsuE*41il&a3iEXFujAPYXXOoD}Wt)5L%;Dff7-k-SAd$&mZxJ~o+d=0`_z zj~p(&mZ#Kup0~&?UrL!(>0VYSEs!dP_|34kyO+BPtU>oYSD|&${jAx)Vk^ zrCLlgVZ@TUZe2XR*XYq-^c*y_*`q&+B~pejdpkeuD%WP0P9HvCM09OkSKLT+?vlut z%9ePJ=QlR_1C9RRavf@j1Vf7hp+La9X`{d0I22FC&7^;AEX|9dKrpv16x4xmq%mmy zT{Nel%nl(~7E3EAA>~TA5k4+r2f3mOW xI(y9i*&fpqz4ka$zu4p2nPEKoZq}Fem#&*uIjVX4s;nNfe|;~FePu5T-T<;!-;n?S diff --git a/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.po index a6ec5cc6f4b6..c2352405048d 100644 --- a/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.po +++ b/django/contrib/admindocs/locale/mk/LC_MESSAGES/django.po @@ -2,16 +2,16 @@ # # Translators: # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2014 # Vasil Vangelovski , 2012 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-05-21 09:51+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-04-05 09:12+0000\n" +"Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" "MIME-Version: 1.0\n" @@ -142,7 +142,7 @@ msgid "Arguments" msgstr "Аргументи" msgid "Back to Model documentation" -msgstr "" +msgstr "Назад до документација за модели." msgid "Model documentation" msgstr "Документација за модели" @@ -217,7 +217,7 @@ msgid "Templates:" msgstr "Шаблони:" msgid "Back to View documentation" -msgstr "" +msgstr "Назад до документација за погледи" msgid "View documentation" msgstr "Документација за поглед" @@ -245,13 +245,6 @@ msgstr "" " Погледска функција (вју функција): %(full_name)s. Име: " "%(url_name)s.\n" -msgid "Boolean (Either True or False)" -msgstr "Логичка (или точно или неточно)" - -#, python-format -msgid "Field of type: %(field_type)s" -msgstr "Поле од тип: %(field_type)s" - msgid "tag:" msgstr "таг:" diff --git a/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.mo b/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.mo index 0b2d66b7b436a6ae6caecba9fcb8f4378918bca9..b2ce886f979ad2cedae3d21f8b0981ed0575d74e 100644 GIT binary patch delta 895 zcmZY5KWGzC90%}UW3U=()3m8+s?;Z_RJ75i$xlGZ3><@fuoPL3_8&r&&=V-nc@^c~ z!8q1mU>p3Fq8jZ)u#Chm_zS*(y`8j~{{&^hO5_i?73(#q!*!U1qAP6FgOgY~!|Xf)`I6W02z&$O1bzmm%)WEB$)|lb#x>V2U1!H#+Z4LawUIwT&M|`4WFl59&1e>LPz7sk0@_+c4;9pki#;ev2$Sr@Om?%oBwIt) zVo)g_yeNwo5xsfvR9NufO|*!p=%EzsMex#t1qBgA5d9|!Z1T-}voG(>i z6pY|hd*C=!HWYs+wLjl+0jkD}P~A{6i-kJLJ*fWU2T&EP1!sDeqtPRWrl;7{ z%xpU2f9y-_Kg%sk*4ToOx-i*XxfPx?Tj8X%igpm~kA_aTc9qj9$CL&ylsOxp;Et0s zd0v5`S9wLa)rNmFHWK7xOIr&I{Mpd7B3F znA2(JAB?|==$;h#SpKj0@!mS~DpFWhqfiRwhGs)y&1P<+609VibtP0sDd>{aZ9_7r LJ%SW|F8TOBGI^x} diff --git a/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.po b/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.po index d210fbdce893..343e10f971da 100644 --- a/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.po +++ b/django/contrib/admindocs/locale/pl/LC_MESSAGES/django.po @@ -9,13 +9,14 @@ # m_aciek , 2014 # p , 2014 # Mattia Procopio , 2014 +# Tomasz Kajtoch , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:34+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-04-22 12:15+0000\n" +"Last-Translator: Tomasz Kajtoch \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -26,10 +27,10 @@ msgstr "" "%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n" msgid "Administrative Documentation" -msgstr "Dokumentacja administratora" +msgstr "Dokumentacja administracyjna" msgid "Home" -msgstr "Początek" +msgstr "Strona główna" msgid "Documentation" msgstr "Dokumentacja" @@ -46,8 +47,9 @@ msgid "" "bookmarklet from any page in the site." msgstr "" "Aby zainstalować skryptozakładkę, przeciągnij jej link do paska zakładek lub " -"kliknij prawym przyciskiem na link i wybierz opcję zapisania do zakładek. Po " -"tym możesz użyć tak zapisanej zakładki na dowolnej podstronie tego serwisu." +"kliknij prawym przyciskiem na link i wybierz opcję zapisania do zakładek. Od " +"tego momentu możesz użyć tak zapisanej zakładki na dowolnej podstronie tego " +"serwisu." msgid "Documentation for this page" msgstr "Dokumentacja dla tej strony" @@ -63,7 +65,7 @@ msgid "Tags" msgstr "Tagi" msgid "List of all the template tags and their functions." -msgstr "Lista wszystkich tagów i ich funkcji w szablonie." +msgstr "Lista wszystkich templatetagów i ich funkcji." msgid "Filters" msgstr "Filtry" @@ -83,8 +85,8 @@ msgid "" "associated fields. Each model has a list of fields which can be accessed as " "template variables" msgstr "" -"Modele to opisy wszystkich obiektów w systemie i związanych z nimi dziedzin. " -"Każdy model ma listę pól, które mogą być dostępne w zmiennych szablonu" +"Modele to opisy wszystkich obiektów w systemie i związanych z nimi pól. " +"Każdy model ma listę pól, które mogą być dostępne jako zmienne szablonu" msgid "Views" msgstr "Widoki" @@ -138,13 +140,13 @@ msgid "Description" msgstr "Opis" msgid "Methods with arguments" -msgstr "Metody z parametrami" +msgstr "Metody z argumentami" msgid "Method" msgstr "Metoda" msgid "Arguments" -msgstr "Parametry" +msgstr "Argumenty" msgid "Back to Model documentation" msgstr "Powrót do dokumentacji modelu" @@ -228,17 +230,17 @@ msgid "View documentation" msgstr "Zobacz dokumentację" msgid "Jump to namespace" -msgstr "Przeskocz do nazwy" +msgstr "Przejdź do przestrzeni nazw" msgid "Empty namespace" -msgstr "Pusta nazwa" +msgstr "Pusta przestrzeń nazw" #, python-format msgid "Views by namespace %(name)s" -msgstr "Widoki po nazwie %(name)s" +msgstr "Widoki według przestrzeni nazw %(name)s" msgid "Views by empty namespace" -msgstr "Widoki po pustej nazwie" +msgstr "Widoki po pustej przestrzeni nazw" #, python-format msgid "" @@ -261,7 +263,7 @@ msgstr "widok:" #, python-format msgid "App %(app_label)r not found" -msgstr "Aplikacja %(app_label)r nie została znaleziona" +msgstr "Nie znaleziono aplikacji %(app_label)r" #, python-format msgid "Model %(model_name)r not found in app %(app_label)r" diff --git a/django/contrib/auth/locale/ko/LC_MESSAGES/django.mo b/django/contrib/auth/locale/ko/LC_MESSAGES/django.mo index 48494066a77e0be0eb1b87b927f5eea020196f19..eb840a20530c446cd49f316e095a77266f3db76e 100644 GIT binary patch delta 784 zcmXZYPe>F|9Ki8k8wTkX>87?@(d$r(wL02vifa*VM9>x;_UL63q%F4vM-NIgi}uI# zq9LVac1bFdP_{ppCFl@c(o1wJh@uE*mM&4Z==+*C4D)%v-<$V)zxRG5-Y1sk0?zn4 zk>^`PPKd~n8j&`TdR)a0{Enw^S4iYC=5Ql=*oVI{i#=QYbt`zB_#3i??5`C` z;~@;<&02B(Z*-qTgv2s7;X7oF{4qZU>qHnz7}-Nwa4#l}=S_YJd1Zl*!V)&%C)9N- zIE{rCZoo{v<9Bk7gg((Ay7&u^;pc7sMti~{y~HU@U=ek}pV){Ix{qTLV>pD}_!Q4z z)pn87*o_DA38wIi!yw6^WryF9jrwYaQD5zK;}q&Eyldh`6Td)xlDEcH;|JqM<5$!- zw}$KSFY0V-+x2`Xu%!5>Q0Qg6AzkI@pY)7@*ls`e~thE delta 745 zcmXZYPe>GD7{~EPvq0M-9d-SaFkM_JcAbe@WH%&ntq6+>4}0)3hmuv}K| zk1&KES|m6LTBFcG;TvAYpU5{7YOOY2LP`=tG9`uGm^KEcUO;a7#KXpIjNl>ax*hDw z!67_@Ps2eqV1~jA3Uj!N^=*XUAJm{kyT}vtFo_$e3)Zpg9_+>In8s`P7DsU%AE4bS zav!HKj$d#Xk8Uy;WRSX8-O+2*qj`sVw9Cc<>JgUA{HvL7E>6{1f&NQ{ih5u?Mm?*4 zcnYnq>Iz}hz8+&QYGT~X2aR`)4^flT$lpi?wf`0B4i~y4cGB?PG#2qZFFvCNZ5em) z9P=OL_S2zIe?00sQP;JdzJ%jmakv_Wy<9#zGVNt^lV0ARPT0A}{)GL^_s28YG5dxY u#I#x-ZEULTd|BLETHRZjuasu(NNU{6j`?, 2017 # Jiyoon, Ha , 2016 # DaHae Sung , 2016 # Ian Y. Choi , 2015 @@ -16,8 +17,8 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-10-11 08:01+0000\n" -"Last-Translator: seok woojing \n" +"PO-Revision-Date: 2017-04-08 13:12+0000\n" +"Last-Translator: Bumsoo Kim \n" "Language-Team: Korean (http://www.transifex.com/django/django/language/ko/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -215,8 +216,8 @@ msgid "" "Designates whether this user should be treated as active. Unselect this " "instead of deleting accounts." msgstr "" -"사용자가 'Django 관리'에 로그인이 가능한지를 나타냅니다.계정을 삭제하는 대신 " -"이것을 선택 해제하세요." +"이 사용자가 활성화되어 있는지를 나타냅니다. 계정을 삭제하는 대신 이것을 선택 " +"해제하세요." msgid "date joined" msgstr "등록일" diff --git a/django/contrib/auth/locale/mk/LC_MESSAGES/django.mo b/django/contrib/auth/locale/mk/LC_MESSAGES/django.mo index 6ffb802c3aa1b46ee92c05dad313b35889c5faf6..53ab2c391fa9b8fa299d900e636b78b90588853b 100644 GIT binary patch delta 1854 zcmYk*TWnNC9LMp$*sX;YDXndR7CDMyYw7M5c6$YFxs_roSc8%#NSf|SHweUS8-&s|Z9~Vk0*Q^2G$11#x4pv-aR*V~Q7v6=9cnYKV z4Qh%_d1m`@JMO~?EW*NtW+AhLt){Vrjy)L0K3s$+u>fDk<#+}Q(e?W0k-6-W*M9{y zPaY4iz-4$nuE!GW!fUY)%kT)U;{A4Mnuf_lL?K8(L3QMZ1!$0qF< zYUhu6jw0`}6R3s1gv@Q@$eL{ibrc_==J^bB@GDGcz}Gb7Mbry^K~3}*YG5vL*L^P1iE>{Vtj;7;!M zk}>ivtiyls1`Kg-FXKHpjDO>4Ok9`UahQXj;=UQ*z$>^0PnBkq{NqyMuVe^w8N^N` z+BS#k|AI?3{)kmrx-5I9EvRwbsD;E(XZ$4A;@hZ&Uhw+MNCX{i6K=s?+<>noXzZeK z5u34&)MfD&$5r?^Y6q`k8xiRxLx;G3lT6x-RV&Rt#Dlm4*OBs_ID~8Q6KupkF!Mi? zE@7S_RKyaa4(C8)DcjIOp289Q7Qe&@+tN-ONOgU;ZOExuH!j5ks7MW=LOX^UH;qcR z&r!K@&U4=D{~0-jg#Ai`#oMBrvM;K{7Vhg&6Fh_rw1oHn3Gebb6`>gkS5Pxi9WYu}CwytdMN3$>M6n>^|(@AP~B6rdKWl0#K~Xam}$BB;gdDE}*W z&_jnB_@Xjp zD_0!@0n23kZKzMOol3y$PU?1Qvstn~cX`Pg+F>erYqwBUDkopeZS`&Kiat0Pi$^1V zr#2jPdi#3fJ-q|bc)xQv?%W?e5{>(vj_81T);f{c{@6gj({m`|v;_Qt>Of6kpL6)o z14kz>A delta 1668 zcmXxkUrfzm9LMo*>KsR%NJ(;(bo>b&j#AFapY%s1Mdn{O7)Ca3G-kt`i^gUno5pm3 zw#?$j>@aO?W!%_onC_ax{8{Ugi`!E}A zirGrDnB~x!#&r``U^}K`H~O&`^YIR5;c#;NEi#uSlH+5jd4A$-^f5^x1~C_FaVoZB z9(Led-fuBF+Q}^pU>q~>HD=-{?!j^F#U^eB@e}StFRNrHb{H4n4b+04V==x*Wmc2j zgaWL=rx@n__J>XhE@ycS*p6rL7Lsk7!?xIz1yMT=C9Oo}wrbRZTac-&9a*a#LLI>| z)I29qr+PMdzYDc_>!za@^r9x}M?H8ydHoRe;$hVDFO%aVsOLVS7V;H!MBh>KrE;PB znW*t>el$-IwXo7G@=u*u83TH86%M741>C~5mr9X4u@dj#JbaJ0@h^7aZO-ozmdtP` zPT&)+zu-OWqW~7-rkQSWccN+_F_ZkCpfkaMisLAwx^bGHrPzauaR_y$ zMxF6WT!Fh#3%!sWA4VPRXRJp*ldQq5*oNIPI;-h?Ko)BON_Q@XQ9EeEMl#~3GV8eB zP9?p->)4FD3(Q(^084QKqZp+0o%=YAzaKzlYLLlK;hG|M0kOw)&NJX6ukY|OYKI5d z7TL8<aHikJr_KI z){rh~`eL+DwKPIyu!PVil{wW-g!tc@=`15!h+2Y*vAUjbo}AcndfJkP=rpE>f5UzHV|q`d$rSZ{}ZZPPv{uPvh&HU#mz($K?a?oYs4x(k}hw4E=hF$ t3sSF6U4Si$Kk(Lg;!*!dT4rS^yebrq6osQz<w{sDEwnA`vW diff --git a/django/contrib/auth/locale/mk/LC_MESSAGES/django.po b/django/contrib/auth/locale/mk/LC_MESSAGES/django.po index bb4082e4a3cd..9bdf5040480a 100644 --- a/django/contrib/auth/locale/mk/LC_MESSAGES/django.po +++ b/django/contrib/auth/locale/mk/LC_MESSAGES/django.po @@ -3,7 +3,7 @@ # Translators: # dekomote , 2015 # Jannis Leidel , 2011 -# Vasil Vangelovski , 2016 +# Vasil Vangelovski , 2016-2017 # Vasil Vangelovski , 2013-2015 # Vasil Vangelovski , 2012 msgid "" @@ -11,7 +11,7 @@ msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2016-05-17 23:12+0200\n" -"PO-Revision-Date: 2016-07-08 09:39+0000\n" +"PO-Revision-Date: 2017-04-05 09:16+0000\n" "Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" @@ -189,6 +189,7 @@ msgstr "корисничко име" msgid "Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only." msgstr "" +"Задолжително. 150 или помалку знаци. Единствено букви, бројки и @/./+/-/_ ." msgid "A user with that username already exists." msgstr "Веќе постои корисник со тоа корисничко име." diff --git a/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.mo b/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.mo index 90baa0d4f28012fba2a02853e53a1a326bad1b0b..cccbd1fe2569fc7bbaedc2c3e3df2d9f5310614c 100644 GIT binary patch delta 249 zcmXBMPYc0t9LMqR&@A~M%0=}$ zD38F0onC$F{r!H@@AToZGl{4o5+>PT(18@C9wyh!TaN3kUE7RrrKRC?*l5UM@goDf5e16&(LAE`;pnUjzaD2k%O?{LRys7F#oU&1^Z7bD!>T#;Q@}|FZAL23sDB{VHd`*4X+R< zlsAZ8VG-tF6{i0humt-Mr!;{koWiu{8I+8)L8j2IJ5IgTv>dnRc9Ly&r@e(zs8D>z zylQwHDB&socVfDqq%Y8lx#z*0cQBqN85bmdQn*YG&FL&ydnN Ya*bL!V4tn|!aB!dCOKeIUy3EuAJ6(mAOHXW diff --git a/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.po b/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.po index 02d473ac45a4..293a8f2ca156 100644 --- a/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.po +++ b/django/contrib/flatpages/locale/eo/LC_MESSAGES/django.po @@ -2,13 +2,13 @@ # # Translators: # Baptiste Darthenay , 2011-2012 -# Baptiste Darthenay , 2014-2015 +# Baptiste Darthenay , 2014-2015,2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-10-09 17:42+0200\n" -"PO-Revision-Date: 2015-11-20 13:36+0000\n" +"PO-Revision-Date: 2017-05-22 21:47+0000\n" "Last-Translator: Baptiste Darthenay \n" "Language-Team: Esperanto (http://www.transifex.com/django/django/language/" "eo/)\n" @@ -22,7 +22,7 @@ msgid "Advanced options" msgstr "Altnivelaj elektoj" msgid "Flat Pages" -msgstr "Plataj paĝoj" +msgstr "Simplaj paĝoj" msgid "URL" msgstr "URL" @@ -47,7 +47,7 @@ msgstr "La streka karaktero \"/\" ne ĉeestas en fino de ĉeno." #, python-format msgid "Flatpage with url %(url)s already exists for site %(site)s" -msgstr "Platpaĝo kun URL %(url)s jam ekzistas for la retejo %(site)s" +msgstr "Simpla paĝo kun URL %(url)s jam ekzistas por la retejo %(site)s" msgid "title" msgstr "titolo" @@ -59,14 +59,14 @@ msgid "enable comments" msgstr "ebligu rimarkoj" msgid "template name" -msgstr "nomo de ŝablono" +msgstr "ŝablono nomo" msgid "" "Example: 'flatpages/contact_page.html'. If this isn't provided, the system " "will use 'flatpages/default.html'." msgstr "" -"Ekzemplo: 'platpaĝoj/kontakto_paĝo.html'. Se ĉi tiu ne provizas, la sistemo " -"uzos 'platpaĝoj/defaŭlto.html'." +"Ekzemplo: 'flatpages/contact_page.html'. Se ĉi tiu ne provizas, la sistemo " +"uzos 'flatpages/default.html'." msgid "registration required" msgstr "registrado postulita" @@ -79,7 +79,7 @@ msgid "sites" msgstr "retejoj" msgid "flat page" -msgstr "plata paĝo" +msgstr "simpla paĝo" msgid "flat pages" -msgstr "plataj paĝoj" +msgstr "simplaj paĝoj" diff --git a/django/contrib/gis/locale/pl/LC_MESSAGES/django.mo b/django/contrib/gis/locale/pl/LC_MESSAGES/django.mo index 66fd6ae2c0358458a3a5d99b4740bb10c9a1ddcd..d63367b2e1887d0659394ef6c1539ec07c52fc10 100644 GIT binary patch delta 407 zcmXxdKT88K7zXgPY7Z5$AfgUJhB`>;X^VptK`j(P9Rw?aTR399zmeq1afhIY;NT+M zBz^&xPB%M5EkGV)L}YTgQIX8&OrlC!59v}wnp^d#RQEJG;Se< z4=@dQhimW~j>CEqWLi}$duzPx+1l08Qg>;z_ VTIoX^Yse!Ta-`v&%>eg1N^m?ruiA=)RR`7BWxhOhu{;4FNE zh|mDq@B`-I7o3ARi)aikz$xg!B)17C;T~Lq0?xooI12A9st!LsU@?Kk6D-14m=yfL zEtts>O~XbvOns*}mMb-fIV<&=Q+Dvi8{7nyUCov8c;M?g+vZA1!x}=igvVCh;kfP% z$Es8Ze)m4}o+?B}XvsYp`5mq}>u??T{p;Roe4AzYmd|)wi#Bh%l11Fe-c|d#+lk+@ wXVo3V`zI`E(ucemx+-K!3Ksb$NDdL#!WAaaNpTooSZj8vs7wDd@xVI%0}ZxeqyPW_ diff --git a/django/contrib/gis/locale/pl/LC_MESSAGES/django.po b/django/contrib/gis/locale/pl/LC_MESSAGES/django.po index ebce3e96e9d2..6622101a1be1 100644 --- a/django/contrib/gis/locale/pl/LC_MESSAGES/django.po +++ b/django/contrib/gis/locale/pl/LC_MESSAGES/django.po @@ -7,14 +7,14 @@ # Piotr Jakimiak , 2015 # m_aciek , 2015 # Mattia Procopio , 2014 -# Tomasz Kajtoch , 2016 +# Tomasz Kajtoch , 2016-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 08:20+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-04-22 11:28+0000\n" +"Last-Translator: Tomasz Kajtoch \n" "Language-Team: Polish (http://www.transifex.com/django/django/language/pl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -57,7 +57,7 @@ msgid "Geometry collection" msgstr "Zbiór geometrii" msgid "Extent Aggregate Field" -msgstr "Pole do agregacji zasięgów" +msgstr "Pole Zasięgu Agregacji" msgid "Raster Field" msgstr "Pole Rastrowe" @@ -85,11 +85,11 @@ msgid "WKT debugging window:" msgstr "Okno debugowania WKT:" msgid "Debugging window (serialized value)" -msgstr "Okno debugowania (zserializowana wartość)" +msgstr "Okno debugowania (wartość zserializowana)" msgid "No feeds are registered." -msgstr "Kanał informacyjny nie został zarejestrowany." +msgstr "Brak zarejestrowanych kanałów informacyjnych." #, python-format msgid "Slug %r isn't registered." -msgstr "Slug '%r' nie został zarejestrowany." +msgstr "Slug %r nie jest zarejestrowany." diff --git a/django/contrib/postgres/locale/mk/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/mk/LC_MESSAGES/django.mo index e57ff1178ab6c4b8ee6b728ba0af8aad96649f3c..9c81baf15bda89fad57d8b2bd4590a56e064c596 100644 GIT binary patch delta 627 zcmYMx&r4KM6u|NGoFA5^>6A`GOZX;Op3tIAQHv;r7Oq6X=zGUS2hRK4d*6?H?s)I&1DPaUgA4Hu@8Earz}6O#1oq%#9KuJqj4?dGoA?EL@eJeG6%x6D{df;^xGl0G z9)m3w;$d#$9_nI#qQY}b;q8dX1mdaKs4EP$iOisdAvRdWbLKDGMdq+}<)UMsP``JA(>OqzD!7DC z@CYaIVW$Y65zgBT>X5Mi#)#*xGU_5ZK{NF6bx??0Bd?RRQP@Bzq8U-Hy5IVrbk~0v zM=k!Z@b7k2ey+SF*Bz9C|JO+PCz1Tfm}%Ljol*8^Hl4X^*_PEdJ7dn5s!qizd**}U zi&9n%TQ<+7O>0D1nXEnTd(n8RP^>tvDipnil3T7;o;hlwTKzwpT39SP?%>n%Vtp%m j8mY%RwvB`e8o^G`P{D4{IDCCr3-(mMt|;Zd@67!J>ndk( delta 775 zcma*j&ubGw6u|LGlT;gPwee@rz<4N8>V|D-wT99km_vyDp;Ejrx($(ZQ?jdg5Mv|u zrieihksL)&Mq_ELCjJ46&K^YY;7LIb7VA|6znda>bl}Zr-tNAcw{t!ED_Z%`up z-Nb(44G|?S`!#EG>%{qWB3@ohCbj5O|D~_{Lv z{wikhIocTL5#l&r!clyQzRJ8WHnE+|Yn9I1!@IX_1PzFtgm(>Gr{EyL;qB4Myn|qK z5w5@Yed~x!84I#7CvB&gv$D2y3V)ZCW?;=97iTJKzDVU=FkL#L=|{DJA*mlr4i5I~ z30)7JoY7937jt&5U}-7CF_SW(5B&4pP@-RFgeDE!(PoQ=W#Qt=b{fxFbA?fAtn39NYihHmF`txE^=jvrVCj~-s9NrlYPfe*%`K~jda9bL zCYc&)Wn)p3yjWJN?xI_Euc?}PLDrP&m1?PH?W&eqmEK1G^I=@5+p5m~%j{pj(Ovab IQauxY0E_9sKL7v# diff --git a/django/contrib/postgres/locale/mk/LC_MESSAGES/django.po b/django/contrib/postgres/locale/mk/LC_MESSAGES/django.po index 8cd03978a0cd..f07c1a596c9b 100644 --- a/django/contrib/postgres/locale/mk/LC_MESSAGES/django.po +++ b/django/contrib/postgres/locale/mk/LC_MESSAGES/django.po @@ -2,15 +2,15 @@ # # Translators: # dekomote , 2015 -# Vasil Vangelovski , 2015-2016 +# Vasil Vangelovski , 2015-2017 # Vasil Vangelovski , 2015 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-07-15 09:44+0200\n" -"PO-Revision-Date: 2016-07-15 09:52+0000\n" -"Last-Translator: Jannis Leidel \n" +"POT-Creation-Date: 2017-01-19 16:49+0100\n" +"PO-Revision-Date: 2017-04-05 09:18+0000\n" +"Last-Translator: Vasil Vangelovski \n" "Language-Team: Macedonian (http://www.transifex.com/django/django/language/" "mk/)\n" "MIME-Version: 1.0\n" @@ -29,12 +29,12 @@ msgstr "Членот %(nth)s на низата не помина валидац msgid "Nested arrays must have the same length." msgstr "Вгнездени низи мораат да имаат иста должина." -msgid "Map of strings to strings" -msgstr "Асоцијативна низа на текстуален податок со текстуален податок." +msgid "Map of strings to strings/nulls" +msgstr "" #, python-format -msgid "The value of \"%(key)s\" is not a string." -msgstr "Вредноста за \"%(key)s\" не е низа од знаци." +msgid "The value of \"%(key)s\" is not a string or null." +msgstr "Вредноста за \"%(key)s\" не е низа од знаци или ништо (null)." msgid "A JSON object" msgstr "JSON објект" diff --git a/django/contrib/postgres/locale/nl/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/nl/LC_MESSAGES/django.mo index 6a8cecb9652fa51eece6553e6799fe4bc331da19..0eb938222f83bc67fcfc597f58e86d680de98542 100644 GIT binary patch delta 1130 zcmY+C&rcIU6vtor)orVyC?F}?5rtBP@*|Qo1c)&Z6A3>I1dJYR3y)>#cE{{)LGjR| z2{B$ap7czN9>jR?;6W1)c=BNMDDiH>fxp1-3>4%gGoN|0^X7f$z5U+&wxzt&Q1gOd z%^0m1KQR&*&qA1BMio&z7y(a$Jzxts3{Hac;5fJmHh_Cz9IUM-Y635T>h2799-IaR zXn|csW!k{vH8wtin_#wPUqHB)s1tq?Yy_u40WN_l@F_S1z6X22T~GycV~H`Ee*sr8}* z-M#&yuXpfzCK#@3j?YTRVNVSCn(1QxhFQ#6c6vb;gHqj}h5200U1X7L@6_p1UQ!e# z`@&><(K1=wo|&2y>(+{srZIN3+b#1eyx`NgJn&66d6gY#vk+I&XPveoQp!mE#F^*ofBmAq!HtfLhuxFdWnu;VfpJVfk;S4am}hgzXWJ9f@$;^_ZBXd`c`1*^TwP1SPC==ZPF@{A{VP$yL~JU= h!K+yOno@(d=*}lq=S_yBBYo=6fSfdqa*z!t{s9qc^%?*G delta 920 zcmY+>&rcIU6bJCJ(02PnK@^dII6x{?1S(#%X)4zEBS7GQ#FLR-`zTANvu1bOMoH|| zgNDOs;$J{ah!;(~nP_5SqKPN3D#n8+^=3l6_?<1FzU1v^-u{?*GrQ6KzN_)6qxB&} zbRv6^uaQTPVh=8eO_+jT;9eLFG1d(`;Vc}7)9?=LfNx_576Ap?dJYYE@_BlL;`U4z?zu_x55@YNI{0<9nBi`KE57>`7 zn!qMt8csnAa`*(IPlkO4f$3Q5VsjjHtcPeWh<04}BeAI<^hxNzn(QDliNwhUzXa+8dberTDJMT!}bp(+*ZscQsE7!>PTq0WHew~Py$Tx++?9R&8iBqRn;F~43X5RukoC^9v{$U za~M|F5|Bd>JQW5#}$eyUD>n;zhcy diff --git a/django/contrib/postgres/locale/nl/LC_MESSAGES/django.po b/django/contrib/postgres/locale/nl/LC_MESSAGES/django.po index 05d782db0163..670fd2618104 100644 --- a/django/contrib/postgres/locale/nl/LC_MESSAGES/django.po +++ b/django/contrib/postgres/locale/nl/LC_MESSAGES/django.po @@ -3,14 +3,15 @@ # Translators: # Evelijn Saaltink , 2016 # Ilja Maas , 2015 -# Sander Steffann , 2015 +# Sander Steffann , 2015 +# Tonnes , 2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-01-20 09:32+0000\n" -"Last-Translator: Jannis Leidel \n" +"PO-Revision-Date: 2017-05-02 10:48+0000\n" +"Last-Translator: Tonnes \n" "Language-Team: Dutch (http://www.transifex.com/django/django/language/nl/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -19,7 +20,7 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n != 1);\n" msgid "PostgreSQL extensions" -msgstr "PostgreSQL uitbreidingen" +msgstr "PostgreSQL-extensies" #, python-format msgid "Item %(nth)s in the array did not validate: " @@ -29,46 +30,46 @@ msgid "Nested arrays must have the same length." msgstr "Lijsten met meerdere lagen moeten allemaal dezelfde lengte hebben." msgid "Map of strings to strings/nulls" -msgstr "" +msgstr "Toewijzing van tekenreeksen naar tekenreeksen/nulwaarden" #, python-format msgid "The value of \"%(key)s\" is not a string or null." -msgstr "" +msgstr "De waarde van '%(key)s' is geen tekenreeks of nul." msgid "A JSON object" -msgstr "Een JSON object" +msgstr "Een JSON-object" msgid "Value must be valid JSON." msgstr "De waarde moet valide JSON zijn." msgid "Could not load JSON data." -msgstr "Kon de JSON gegevens niet inlezen." +msgstr "Kon JSON-gegevens niet laden." msgid "Input must be a JSON dictionary." msgstr "Invoer moet een JSON-bibliotheek zijn." #, python-format msgid "'%(value)s' value must be valid JSON." -msgstr "'%(value)s' waarde moet valide JSON zijn." +msgstr "Waarde '%(value)s' moet geldige JSON zijn." msgid "Enter two valid values." -msgstr "Geef twee geldige waarden op." +msgstr "Voer twee geldige waarden in." msgid "The start of the range must not exceed the end of the range." msgstr "" -"Het begin van het bereik mag niet groter zijn dan het eind van het bereik." +"Het begin van het bereik mag niet groter zijn dan het einde van het bereik." msgid "Enter two whole numbers." -msgstr "Geef twee gehele getallen op." +msgstr "Voer twee gehele getallen in." msgid "Enter two numbers." -msgstr "Geef twee getallen op." +msgstr "Voer twee getallen in." msgid "Enter two valid date/times." -msgstr "Geef twee geldige datums/tijden op." +msgstr "Voer twee geldige datums/tijden in." msgid "Enter two valid dates." -msgstr "Geef twee geldige datums op." +msgstr "Voer twee geldige datums in." #, python-format msgid "" @@ -78,10 +79,10 @@ msgid_plural "" "List contains %(show_value)d items, it should contain no more than " "%(limit_value)d." msgstr[0] "" -"Lijst bevat %(show_value)d element maar mag niet meer dan %(limit_value)d " +"Lijst bevat %(show_value)d element, maar mag niet meer dan %(limit_value)d " "elementen bevatten." msgstr[1] "" -"Lijst bevat %(show_value)d elementen maar mag niet meer dan %(limit_value)d " +"Lijst bevat %(show_value)d elementen, maar mag niet meer dan %(limit_value)d " "elementen bevatten." #, python-format @@ -92,27 +93,29 @@ msgid_plural "" "List contains %(show_value)d items, it should contain no fewer than " "%(limit_value)d." msgstr[0] "" -"Lijst bevat %(show_value)d element maar mag niet minder dan %(limit_value)d " +"Lijst bevat %(show_value)d element, maar mag niet minder dan %(limit_value)d " "elementen bevatten." msgstr[1] "" -"Lijst bevat %(show_value)d elementen maar mag niet minder dan " +"Lijst bevat %(show_value)d elementen, maar mag niet minder dan " "%(limit_value)d elementen bevatten." #, python-format msgid "Some keys were missing: %(keys)s" -msgstr "Sommige sleutels missen: %(keys)s" +msgstr "Sommige sleutels ontbreken: %(keys)s" #, python-format msgid "Some unknown keys were provided: %(keys)s" -msgstr "Een aantal onbekende sleutels werden opgegeven: %(keys)s" +msgstr "Er zijn enkele onbekende sleutels opgegeven: %(keys)s" #, python-format msgid "" "Ensure that this range is completely less than or equal to %(limit_value)s." -msgstr "Controleer dat dit bereik minder dan of gelijk is aan %(limit_value)s." +msgstr "" +"Zorg ervoor dat dit bereik minder dan of gelijk is aan %(limit_value)s." #, python-format msgid "" "Ensure that this range is completely greater than or equal to " "%(limit_value)s." -msgstr "Controleer dat dit bereik groter dan of gelijk is aan %(limit_value)s." +msgstr "" +"Zorg ervoor dat dit bereik groter dan of gelijk is aan %(limit_value)s." diff --git a/django/contrib/postgres/locale/ru/LC_MESSAGES/django.mo b/django/contrib/postgres/locale/ru/LC_MESSAGES/django.mo index 0cf5f2479ad5b59ed771903506d23bb9a49af221..fba68fbc7569fb75f17e59f4128cf9fbfd980ffb 100644 GIT binary patch delta 283 zcmWN~u`dI07{>8Or41)(a=KakFpy9=mqT4@H8h=s5E98H3_@DzwH5e!Gn~S# zBbCs@057nJueg9exQ-KJQWmHqW2KF1by*YJ`X>B@bnHA`@?y6FD9MErMjetRVj%xJjYLb#y@<=r!^^q zmMvZ3E*3DrGEUINFRb8tLaO35ez5a~Q><<9%YCt_Q{7X>tZw5kdYIml23W^C{KhtV zDd`etQQz8GspPXKMJHGCj`N2(VpmPr%HA|h5H?KfwdpyxCiEMA$GHgn7e8>yX6aGe WgdG$8#7nm2MsBhYv)s{RYVr@SC^CKk diff --git a/django/contrib/postgres/locale/ru/LC_MESSAGES/django.po b/django/contrib/postgres/locale/ru/LC_MESSAGES/django.po index 605cee1cb6c6..8f9624d70013 100644 --- a/django/contrib/postgres/locale/ru/LC_MESSAGES/django.po +++ b/django/contrib/postgres/locale/ru/LC_MESSAGES/django.po @@ -5,14 +5,14 @@ # eXtractor , 2015 # Kirill Gagarski , 2015 # Vasiliy Anikin , 2017 -# Алексей Борискин , 2015-2016 +# Алексей Борискин , 2015-2017 msgid "" msgstr "" "Project-Id-Version: django\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-01-19 16:49+0100\n" -"PO-Revision-Date: 2017-03-25 07:13+0000\n" -"Last-Translator: Vasiliy Anikin \n" +"PO-Revision-Date: 2017-04-08 15:22+0000\n" +"Last-Translator: Алексей Борискин \n" "Language-Team: Russian (http://www.transifex.com/django/django/language/" "ru/)\n" "MIME-Version: 1.0\n" @@ -35,12 +35,12 @@ msgstr "Вложенные массивы должны иметь одинако msgid "Map of strings to strings/nulls" msgstr "" -"Ассоциативный массив, со строковыми ключами и строковыми или отсутствующими " +"Ассоциативный массив со строковыми ключами и строковыми или отсутствующими " "значениями." #, python-format msgid "The value of \"%(key)s\" is not a string or null." -msgstr "Значение \"%(key)s\", не является строкой или отсутствующим значением." +msgstr "Значение \"%(key)s\" не является строкой или отсутствующим значением." msgid "A JSON object" msgstr "JSON-объект" diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index fa23f1ddf022..e595d796ee49 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -4,7 +4,8 @@ Django 1.11.2 release notes *Under development* -Django 1.11.2 adds a minor feature and fixes several bugs in 1.11.1. +Django 1.11.2 adds a minor feature and fixes several bugs in 1.11.1. Also, the +latest string translations from Transifex are incorporated. Minor feature ============= From af9a81aa7f14ae1a9fd1f25676f526a43f0c65f3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 31 May 2017 19:29:34 -0400 Subject: [PATCH 042/389] [1.11.x] Fixed a forms test after updated translations. Backport of 84fb50df670c8126983c9f0fd4a5c30dbe57b684 from master --- tests/forms_tests/field_tests/test_datefield.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/forms_tests/field_tests/test_datefield.py b/tests/forms_tests/field_tests/test_datefield.py index b23b5a42ce34..e3a02f953355 100644 --- a/tests/forms_tests/field_tests/test_datefield.py +++ b/tests/forms_tests/field_tests/test_datefield.py @@ -104,7 +104,7 @@ def test_l10n_invalid_date_in(self): a = GetDate({'mydate_month': '2', 'mydate_day': '31', 'mydate_year': '2010'}) self.assertFalse(a.is_valid()) # 'Geef een geldige datum op.' = 'Enter a valid date.' - self.assertEqual(a.errors, {'mydate': ['Geef een geldige datum op.']}) + self.assertEqual(a.errors, {'mydate': ['Voer een geldige datum in.']}) @override_settings(USE_L10N=True) @translation.override('nl') From 110bd820380e06fc8572f94b36bba6fc9d057a6b Mon Sep 17 00:00:00 2001 From: Manatsawin Hanmongkolchai Date: Sun, 28 May 2017 14:05:21 +0700 Subject: [PATCH 043/389] [1.11.x] Fixed #28242 -- Moved ImageField file extension validation to the form field. Backport of a0c07d77fc313388c72a17cd59411265069f037f from master --- django/db/models/fields/files.py | 2 -- django/forms/fields.py | 1 + docs/releases/1.11.2.txt | 4 ++++ docs/releases/1.11.txt | 1 + .../field_tests/test_imagefield.py | 11 +++++++++- tests/model_fields/test_imagefield.py | 8 +------- tests/model_forms/models.py | 11 ++++++++++ tests/model_forms/tests.py | 20 ++++++++++++++++++- 8 files changed, 47 insertions(+), 11 deletions(-) diff --git a/django/db/models/fields/files.py b/django/db/models/fields/files.py index e877fc7ef4c1..6906b8f485cc 100644 --- a/django/db/models/fields/files.py +++ b/django/db/models/fields/files.py @@ -8,7 +8,6 @@ from django.core.files.base import File from django.core.files.images import ImageFile from django.core.files.storage import default_storage -from django.core.validators import validate_image_file_extension from django.db.models import signals from django.db.models.fields import Field from django.utils import six @@ -387,7 +386,6 @@ def delete(self, save=True): class ImageField(FileField): - default_validators = [validate_image_file_extension] attr_class = ImageFieldFile descriptor_class = ImageFileDescriptor description = _("Image") diff --git a/django/forms/fields.py b/django/forms/fields.py index 168ea9673c9d..33ed2882a317 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -610,6 +610,7 @@ def has_changed(self, initial, data): class ImageField(FileField): + default_validators = [validators.validate_image_file_extension] default_error_messages = { 'invalid_image': _( "Upload a valid image. The file you uploaded was either not an " diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index e595d796ee49..ed7753a74fb1 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -58,3 +58,7 @@ Bugfixes * Fixed a regression where ``file_move_safe()`` crashed when moving files to a CIFS mount (:ticket:`28170`). + +* Moved the ``ImageField`` file extension validation added in Django 1.11 from + the model field to the form field to reallow the use case of storing images + without an extension (:ticket:`28242`). diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 05c925259f13..a6955d14604c 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -327,6 +327,7 @@ Models * :class:`~django.db.models.ImageField` now has a default :data:`~django.core.validators.validate_image_file_extension` validator. + (This validator moved to the form field in :doc:`Django 1.11.2 <1.11.2>`.) * Added support for time truncation to :class:`~django.db.models.functions.datetime.Trunc` functions. diff --git a/tests/forms_tests/field_tests/test_imagefield.py b/tests/forms_tests/field_tests/test_imagefield.py index ee0e1e3b7397..c73d9b70d3dd 100644 --- a/tests/forms_tests/field_tests/test_imagefield.py +++ b/tests/forms_tests/field_tests/test_imagefield.py @@ -4,7 +4,7 @@ import unittest from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import ImageField +from django.forms import ImageField, ValidationError from django.test import SimpleTestCase from django.utils._os import upath @@ -58,3 +58,12 @@ def test_imagefield_annotate_with_bitmap_image_after_clean(self): self.assertIsNone(uploaded_file.content_type) finally: Image.register_mime(BmpImageFile.format, 'image/bmp') + + def test_file_extension_validation(self): + f = ImageField() + img_path = get_img_path('filepath_test_files/1x1.png') + with open(img_path, 'rb') as img_file: + img_data = img_file.read() + img_file = SimpleUploadedFile('1x1.txt', img_data) + with self.assertRaisesMessage(ValidationError, "File extension 'txt' is not allowed."): + f.clean(img_file) diff --git a/tests/model_fields/test_imagefield.py b/tests/model_fields/test_imagefield.py index e178760d2fc9..2d636f9610e5 100644 --- a/tests/model_fields/test_imagefield.py +++ b/tests/model_fields/test_imagefield.py @@ -4,7 +4,7 @@ import shutil from unittest import skipIf -from django.core.exceptions import ImproperlyConfigured, ValidationError +from django.core.exceptions import ImproperlyConfigured from django.core.files import File from django.core.files.images import ImageFile from django.test import TestCase @@ -133,12 +133,6 @@ def test_equal_notequal_hash(self): self.assertEqual(hash(p1_db.mugshot), hash(p1.mugshot)) self.assertIs(p1_db.mugshot != p1.mugshot, False) - def test_validation(self): - p = self.PersonModel(name="Joan") - p.mugshot.save("shot.txt", self.file1) - with self.assertRaisesMessage(ValidationError, "File extension 'txt' is not allowed."): - p.full_clean() - def test_instantiate_missing(self): """ If the underlying file is unavailable, still create instantiate the diff --git a/tests/model_forms/models.py b/tests/model_forms/models.py index f873cfea972c..42224ac63e5f 100644 --- a/tests/model_forms/models.py +++ b/tests/model_forms/models.py @@ -214,6 +214,17 @@ def custom_upload_path(self, filename): def __str__(self): return self.description + + class NoExtensionImageFile(models.Model): + def upload_to(self, filename): + return 'tests/no_extension' + + description = models.CharField(max_length=20) + image = models.ImageField(storage=temp_storage, upload_to=upload_to) + + def __str__(self): + return self.description + except ImportError: test_images = False diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index 159d0634ea33..fe9a153a90b8 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -36,7 +36,7 @@ ) if test_images: - from .models import ImageFile, OptionalImageFile + from .models import ImageFile, OptionalImageFile, NoExtensionImageFile class ImageFileForm(forms.ModelForm): class Meta: @@ -48,6 +48,11 @@ class Meta: model = OptionalImageFile fields = '__all__' + class NoExtensionImageFileForm(forms.ModelForm): + class Meta: + model = NoExtensionImageFile + fields = '__all__' + class ProductForm(forms.ModelForm): class Meta: @@ -2469,6 +2474,19 @@ def test_image_field(self): self.assertEqual(instance.image.name, 'foo/test4.png') instance.delete() + # Editing an instance that has an image without an extension shouldn't + # fail validation. First create: + f = NoExtensionImageFileForm( + data={'description': 'An image'}, + files={'image': SimpleUploadedFile('test.png', image_data)}, + ) + self.assertTrue(f.is_valid()) + instance = f.save() + self.assertEqual(instance.image.name, 'tests/no_extension') + # Then edit: + f = NoExtensionImageFileForm(data={'description': 'Edited image'}, instance=instance) + self.assertTrue(f.is_valid()) + class ModelOtherFieldTests(SimpleTestCase): def test_big_integer_field(self): From 78975c453b896b0107a4bd3a463c06ee70aa8087 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Jun 2017 11:09:51 -0400 Subject: [PATCH 044/389] [1.11.x] Added release date for 1.11.2. Backport of 162778b8e2fecfe6171eba09a7f0606b61ad3403 from master --- docs/releases/1.11.2.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.2.txt b/docs/releases/1.11.2.txt index ed7753a74fb1..43ee7ac3ec2a 100644 --- a/docs/releases/1.11.2.txt +++ b/docs/releases/1.11.2.txt @@ -2,7 +2,7 @@ Django 1.11.2 release notes =========================== -*Under development* +*June 1, 2017* Django 1.11.2 adds a minor feature and fixes several bugs in 1.11.1. Also, the latest string translations from Transifex are incorporated. From ce4edd260bfa790418eea7de0112ce7c16feb304 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Jun 2017 11:22:36 -0400 Subject: [PATCH 045/389] [1.11.x] Bumped version for 1.11.2 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 05ec33500e5d..3bdf36cf5d8c 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 2, 'alpha', 0) +VERSION = (1, 11, 2, 'final', 0) __version__ = get_version(VERSION) From 80a62ac64f3109f2760372d862fa86b0396a6f84 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Jun 2017 12:54:38 -0400 Subject: [PATCH 046/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 3bdf36cf5d8c..873a88c57c1a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 2, 'final', 0) +VERSION = (1, 11, 3, 'alpha', 0) __version__ = get_version(VERSION) From 2134090e798bbc2d11c57c11f7e1c05dbcf1e39f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Jun 2017 11:19:43 -0400 Subject: [PATCH 047/389] [1.11.x] Added stub release notes for 1.11.3. Backport of 4ef093b0b485ff425590ffb49bee62c21e5264e9 from master --- docs/releases/1.11.3.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.3.txt diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt new file mode 100644 index 000000000000..7eacbd8a36fc --- /dev/null +++ b/docs/releases/1.11.3.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.3 release notes +=========================== + +*Under development* + +Django 1.11.3 fixes several bugs in 1.11.2. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 18158a86ecf0..8fc020372908 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.3 1.11.2 1.11.1 1.11 From 3cd9a4b1ea47614693619f56b4ee0d37244132a5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Jun 2017 13:26:37 -0400 Subject: [PATCH 048/389] [1.11.x] Sorted imports per isort 4.2.9. Backport of cde31daf8815e05b4b86b857b49fb0e31e1f0a38 from master --- django/contrib/gis/db/backends/mysql/base.py | 5 +++-- django/contrib/gis/db/backends/mysql/features.py | 5 +++-- django/contrib/gis/db/backends/mysql/operations.py | 5 +++-- django/contrib/gis/db/backends/oracle/base.py | 5 +++-- django/contrib/gis/db/backends/oracle/features.py | 5 +++-- django/contrib/gis/db/backends/oracle/operations.py | 5 +++-- django/contrib/gis/db/backends/postgis/base.py | 5 +++-- django/contrib/gis/db/backends/postgis/features.py | 5 +++-- django/contrib/gis/db/backends/postgis/operations.py | 5 +++-- django/contrib/gis/db/backends/spatialite/features.py | 5 +++-- .../contrib/gis/db/backends/spatialite/operations.py | 5 +++-- django/contrib/gis/geoip/prototypes.py | 1 - django/contrib/gis/management/commands/inspectdb.py | 5 +++-- .../staticfiles/management/commands/runserver.py | 5 +++-- django/core/mail/backends/filebased.py | 5 +++-- tests/base/models.py | 1 - tests/check_framework/tests_1_10_compatibility.py | 5 +++-- tests/check_framework/tests_1_8_compatibility.py | 5 +++-- tests/foreign_object/tests.py | 1 - tests/i18n/test_compilation.py | 5 +++-- tests/i18n/test_extraction.py | 5 +++-- tests/model_inheritance/models.py | 1 - tests/model_options/models/tablespaces.py | 1 - tests/prefetch_related/models.py | 2 -- tests/proxy_models/models.py | 1 - tests/select_related/models.py | 1 - tests/serializers/test_data.py | 1 - tests/sessions_tests/tests.py | 10 ++++++---- tests/staticfiles_tests/test_storage.py | 5 +++-- tests/unmanaged_models/models.py | 1 - 30 files changed, 63 insertions(+), 53 deletions(-) diff --git a/django/contrib/gis/db/backends/mysql/base.py b/django/contrib/gis/db/backends/mysql/base.py index 9cf61e40d2fc..fccea5919df5 100644 --- a/django/contrib/gis/db/backends/mysql/base.py +++ b/django/contrib/gis/db/backends/mysql/base.py @@ -1,5 +1,6 @@ -from django.db.backends.mysql.base import \ - DatabaseWrapper as MySQLDatabaseWrapper +from django.db.backends.mysql.base import ( + DatabaseWrapper as MySQLDatabaseWrapper, +) from .features import DatabaseFeatures from .introspection import MySQLIntrospection diff --git a/django/contrib/gis/db/backends/mysql/features.py b/django/contrib/gis/db/backends/mysql/features.py index 05affeecb784..9d4f8982d55a 100644 --- a/django/contrib/gis/db/backends/mysql/features.py +++ b/django/contrib/gis/db/backends/mysql/features.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures -from django.db.backends.mysql.features import \ - DatabaseFeatures as MySQLDatabaseFeatures +from django.db.backends.mysql.features import ( + DatabaseFeatures as MySQLDatabaseFeatures, +) class DatabaseFeatures(BaseSpatialFeatures, MySQLDatabaseFeatures): diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index 7d8adbf1585e..a86370121dfa 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.backends.base.adapter import WKTAdapter -from django.contrib.gis.db.backends.base.operations import \ - BaseSpatialOperations +from django.contrib.gis.db.backends.base.operations import ( + BaseSpatialOperations, +) from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.db.models import GeometryField, aggregates from django.db.backends.mysql.operations import DatabaseOperations diff --git a/django/contrib/gis/db/backends/oracle/base.py b/django/contrib/gis/db/backends/oracle/base.py index a4f6684f6df0..0093ef83bba8 100644 --- a/django/contrib/gis/db/backends/oracle/base.py +++ b/django/contrib/gis/db/backends/oracle/base.py @@ -1,5 +1,6 @@ -from django.db.backends.oracle.base import \ - DatabaseWrapper as OracleDatabaseWrapper +from django.db.backends.oracle.base import ( + DatabaseWrapper as OracleDatabaseWrapper, +) from .features import DatabaseFeatures from .introspection import OracleIntrospection diff --git a/django/contrib/gis/db/backends/oracle/features.py b/django/contrib/gis/db/backends/oracle/features.py index 996fe8e54b50..ece45b262318 100644 --- a/django/contrib/gis/db/backends/oracle/features.py +++ b/django/contrib/gis/db/backends/oracle/features.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures -from django.db.backends.oracle.features import \ - DatabaseFeatures as OracleDatabaseFeatures +from django.db.backends.oracle.features import ( + DatabaseFeatures as OracleDatabaseFeatures, +) class DatabaseFeatures(BaseSpatialFeatures, OracleDatabaseFeatures): diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 013ffa74f697..758581a1aad1 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -9,8 +9,9 @@ """ import re -from django.contrib.gis.db.backends.base.operations import \ - BaseSpatialOperations +from django.contrib.gis.db.backends.base.operations import ( + BaseSpatialOperations, +) from django.contrib.gis.db.backends.oracle.adapter import OracleSpatialAdapter from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.db.models import aggregates diff --git a/django/contrib/gis/db/backends/postgis/base.py b/django/contrib/gis/db/backends/postgis/base.py index 203e3ba075eb..afcf8646800e 100644 --- a/django/contrib/gis/db/backends/postgis/base.py +++ b/django/contrib/gis/db/backends/postgis/base.py @@ -1,6 +1,7 @@ from django.db.backends.base.base import NO_DB_ALIAS -from django.db.backends.postgresql.base import \ - DatabaseWrapper as Psycopg2DatabaseWrapper +from django.db.backends.postgresql.base import ( + DatabaseWrapper as Psycopg2DatabaseWrapper, +) from .features import DatabaseFeatures from .introspection import PostGISIntrospection diff --git a/django/contrib/gis/db/backends/postgis/features.py b/django/contrib/gis/db/backends/postgis/features.py index 2d613efe6e65..60158ca5c30b 100644 --- a/django/contrib/gis/db/backends/postgis/features.py +++ b/django/contrib/gis/db/backends/postgis/features.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures -from django.db.backends.postgresql.features import \ - DatabaseFeatures as Psycopg2DatabaseFeatures +from django.db.backends.postgresql.features import ( + DatabaseFeatures as Psycopg2DatabaseFeatures, +) class DatabaseFeatures(BaseSpatialFeatures, Psycopg2DatabaseFeatures): diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index ae0821694a68..5cc90397685d 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -1,8 +1,9 @@ import re from django.conf import settings -from django.contrib.gis.db.backends.base.operations import \ - BaseSpatialOperations +from django.contrib.gis.db.backends.base.operations import ( + BaseSpatialOperations, +) from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.gdal import GDALRaster from django.contrib.gis.measure import Distance diff --git a/django/contrib/gis/db/backends/spatialite/features.py b/django/contrib/gis/db/backends/spatialite/features.py index 79517e8190f4..8a08b7fef141 100644 --- a/django/contrib/gis/db/backends/spatialite/features.py +++ b/django/contrib/gis/db/backends/spatialite/features.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.backends.base.features import BaseSpatialFeatures -from django.db.backends.sqlite3.features import \ - DatabaseFeatures as SQLiteDatabaseFeatures +from django.db.backends.sqlite3.features import ( + DatabaseFeatures as SQLiteDatabaseFeatures, +) from django.utils.functional import cached_property diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 909355b03157..31cf6242872c 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -6,8 +6,9 @@ import re import sys -from django.contrib.gis.db.backends.base.operations import \ - BaseSpatialOperations +from django.contrib.gis.db.backends.base.operations import ( + BaseSpatialOperations, +) from django.contrib.gis.db.backends.spatialite.adapter import SpatiaLiteAdapter from django.contrib.gis.db.backends.utils import SpatialOperator from django.contrib.gis.db.models import aggregates diff --git a/django/contrib/gis/geoip/prototypes.py b/django/contrib/gis/geoip/prototypes.py index 74b9b2142f0a..ed46aebcb9b1 100644 --- a/django/contrib/gis/geoip/prototypes.py +++ b/django/contrib/gis/geoip/prototypes.py @@ -4,7 +4,6 @@ # #### GeoIP C Structure definitions #### - class GeoIPRecord(Structure): _fields_ = [('country_code', c_char_p), ('country_code3', c_char_p), diff --git a/django/contrib/gis/management/commands/inspectdb.py b/django/contrib/gis/management/commands/inspectdb.py index 27345c59d464..5275175d66ac 100644 --- a/django/contrib/gis/management/commands/inspectdb.py +++ b/django/contrib/gis/management/commands/inspectdb.py @@ -1,5 +1,6 @@ -from django.core.management.commands.inspectdb import \ - Command as InspectDBCommand +from django.core.management.commands.inspectdb import ( + Command as InspectDBCommand, +) class Command(InspectDBCommand): diff --git a/django/contrib/staticfiles/management/commands/runserver.py b/django/contrib/staticfiles/management/commands/runserver.py index c25ac1f36952..455d926b3ebd 100644 --- a/django/contrib/staticfiles/management/commands/runserver.py +++ b/django/contrib/staticfiles/management/commands/runserver.py @@ -1,7 +1,8 @@ from django.conf import settings from django.contrib.staticfiles.handlers import StaticFilesHandler -from django.core.management.commands.runserver import \ - Command as RunserverCommand +from django.core.management.commands.runserver import ( + Command as RunserverCommand, +) class Command(RunserverCommand): diff --git a/django/core/mail/backends/filebased.py b/django/core/mail/backends/filebased.py index cfe033fb4873..17e89b75a442 100644 --- a/django/core/mail/backends/filebased.py +++ b/django/core/mail/backends/filebased.py @@ -5,8 +5,9 @@ from django.conf import settings from django.core.exceptions import ImproperlyConfigured -from django.core.mail.backends.console import \ - EmailBackend as ConsoleEmailBackend +from django.core.mail.backends.console import ( + EmailBackend as ConsoleEmailBackend, +) from django.utils import six diff --git a/tests/base/models.py b/tests/base/models.py index 4a8a2ffd8175..7a6b1145162f 100644 --- a/tests/base/models.py +++ b/tests/base/models.py @@ -3,7 +3,6 @@ from django.db import models from django.utils import six - # The models definitions below used to crash. Generating models dynamically # at runtime is a bad idea because it pollutes the app registry. This doesn't # integrate well with the test suite but at least it prevents regressions. diff --git a/tests/check_framework/tests_1_10_compatibility.py b/tests/check_framework/tests_1_10_compatibility.py index 388ac1b02431..e085f68881d1 100644 --- a/tests/check_framework/tests_1_10_compatibility.py +++ b/tests/check_framework/tests_1_10_compatibility.py @@ -1,5 +1,6 @@ -from django.core.checks.compatibility.django_1_10 import \ - check_duplicate_middleware_settings +from django.core.checks.compatibility.django_1_10 import ( + check_duplicate_middleware_settings, +) from django.test import SimpleTestCase from django.test.utils import override_settings diff --git a/tests/check_framework/tests_1_8_compatibility.py b/tests/check_framework/tests_1_8_compatibility.py index d8601b106480..c3865643b27e 100644 --- a/tests/check_framework/tests_1_8_compatibility.py +++ b/tests/check_framework/tests_1_8_compatibility.py @@ -1,5 +1,6 @@ -from django.core.checks.compatibility.django_1_8_0 import \ - check_duplicate_template_settings +from django.core.checks.compatibility.django_1_8_0 import ( + check_duplicate_template_settings, +) from django.test import SimpleTestCase from django.test.utils import override_settings diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index 3c1f5bdfb4e2..e74732ab7260 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -13,7 +13,6 @@ Group, Membership, NewsArticle, Person, ) - # Note that these tests are testing internal implementation details. # ForeignObject is not part of public API. diff --git a/tests/i18n/test_compilation.py b/tests/i18n/test_compilation.py index b33338800a28..85d3b7939abe 100644 --- a/tests/i18n/test_compilation.py +++ b/tests/i18n/test_compilation.py @@ -10,8 +10,9 @@ from django.core.management import ( CommandError, call_command, execute_from_command_line, ) -from django.core.management.commands.makemessages import \ - Command as MakeMessagesCommand +from django.core.management.commands.makemessages import ( + Command as MakeMessagesCommand, +) from django.core.management.utils import find_command from django.test import SimpleTestCase, mock, override_settings from django.test.utils import captured_stderr, captured_stdout diff --git a/tests/i18n/test_extraction.py b/tests/i18n/test_extraction.py index 9311a1e7f075..3befcb7c8a1e 100644 --- a/tests/i18n/test_extraction.py +++ b/tests/i18n/test_extraction.py @@ -14,8 +14,9 @@ from django.core import management from django.core.management import execute_from_command_line from django.core.management.base import CommandError -from django.core.management.commands.makemessages import \ - Command as MakeMessagesCommand +from django.core.management.commands.makemessages import ( + Command as MakeMessagesCommand, +) from django.core.management.utils import find_command from django.test import SimpleTestCase, mock, override_settings from django.test.utils import captured_stderr, captured_stdout diff --git a/tests/model_inheritance/models.py b/tests/model_inheritance/models.py index 45f22df0bcce..659f0c5b2272 100644 --- a/tests/model_inheritance/models.py +++ b/tests/model_inheritance/models.py @@ -16,7 +16,6 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible - # # Abstract base classes # diff --git a/tests/model_options/models/tablespaces.py b/tests/model_options/models/tablespaces.py index ec705b7b2d18..0ee0e20ef4d3 100644 --- a/tests/model_options/models/tablespaces.py +++ b/tests/model_options/models/tablespaces.py @@ -1,6 +1,5 @@ from django.db import models - # Since the test database doesn't have tablespaces, it's impossible for Django # to create the tables for models where db_tablespace is set. To avoid this # problem, we mark the models as unmanaged, and temporarily revert them to diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py index 6600418b8fbc..c5f895fe96a6 100644 --- a/tests/prefetch_related/models.py +++ b/tests/prefetch_related/models.py @@ -10,8 +10,6 @@ from django.utils.functional import cached_property -# Basic tests - @python_2_unicode_compatible class Author(models.Model): name = models.CharField(max_length=50, unique=True) diff --git a/tests/proxy_models/models.py b/tests/proxy_models/models.py index 6960042d78df..8b081f529093 100644 --- a/tests/proxy_models/models.py +++ b/tests/proxy_models/models.py @@ -7,7 +7,6 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible - # A couple of managers for testing managing overriding in proxy model cases. diff --git a/tests/select_related/models.py b/tests/select_related/models.py index bef287373105..26bf34ebd177 100644 --- a/tests/select_related/models.py +++ b/tests/select_related/models.py @@ -14,7 +14,6 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible - # Who remembers high school biology? diff --git a/tests/serializers/test_data.py b/tests/serializers/test_data.py index f9cb9582fe30..cc42f2869a28 100644 --- a/tests/serializers/test_data.py +++ b/tests/serializers/test_data.py @@ -32,7 +32,6 @@ ) from .tests import register_tests - # A set of functions that can be used to recreate # test data objects of various kinds. # The save method is a raw base model save, to make diff --git a/tests/sessions_tests/tests.py b/tests/sessions_tests/tests.py index 5ccccf699bf8..c15c39224b8b 100644 --- a/tests/sessions_tests/tests.py +++ b/tests/sessions_tests/tests.py @@ -10,12 +10,14 @@ from django.conf import settings from django.contrib.sessions.backends.base import UpdateError from django.contrib.sessions.backends.cache import SessionStore as CacheSession -from django.contrib.sessions.backends.cached_db import \ - SessionStore as CacheDBSession +from django.contrib.sessions.backends.cached_db import ( + SessionStore as CacheDBSession, +) from django.contrib.sessions.backends.db import SessionStore as DatabaseSession from django.contrib.sessions.backends.file import SessionStore as FileSession -from django.contrib.sessions.backends.signed_cookies import \ - SessionStore as CookieSession +from django.contrib.sessions.backends.signed_cookies import ( + SessionStore as CookieSession, +) from django.contrib.sessions.exceptions import InvalidSessionKey from django.contrib.sessions.middleware import SessionMiddleware from django.contrib.sessions.models import Session diff --git a/tests/staticfiles_tests/test_storage.py b/tests/staticfiles_tests/test_storage.py index e06e54487e9a..d0dcafc123c9 100644 --- a/tests/staticfiles_tests/test_storage.py +++ b/tests/staticfiles_tests/test_storage.py @@ -8,8 +8,9 @@ from django.conf import settings from django.contrib.staticfiles import finders, storage -from django.contrib.staticfiles.management.commands.collectstatic import \ - Command as CollectstaticCommand +from django.contrib.staticfiles.management.commands.collectstatic import ( + Command as CollectstaticCommand, +) from django.core.cache.backends.base import BaseCache from django.core.management import call_command from django.test import override_settings diff --git a/tests/unmanaged_models/models.py b/tests/unmanaged_models/models.py index e925752a0679..657d3d5be0ac 100644 --- a/tests/unmanaged_models/models.py +++ b/tests/unmanaged_models/models.py @@ -6,7 +6,6 @@ from django.db import models from django.utils.encoding import python_2_unicode_compatible - # All of these models are created in the database by Django. From 8245255ae5dffa13705f8f21c86071362a484420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sat, 27 May 2017 18:12:18 -0700 Subject: [PATCH 049/389] [1.11.x] Clarified QuerySet.iterator()'s docs on server-side cursors. Backport of bf50ae821021c4f7cd608d2bd1f2dfff98f3ceb9 from master --- docs/_theme/djangodocs/static/djangodocs.css | 3 ++- docs/ref/models/querysets.txt | 27 ++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/_theme/djangodocs/static/djangodocs.css b/docs/_theme/djangodocs/static/djangodocs.css index 22fce2a15807..143bcdb6c96c 100644 --- a/docs/_theme/djangodocs/static/djangodocs.css +++ b/docs/_theme/djangodocs/static/djangodocs.css @@ -43,11 +43,12 @@ div.nav { margin: 0; font-size: 11px; text-align: right; color: #487858;} /*** basic styles ***/ dd { margin-left:15px; } -h1,h2,h3,h4 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } +h1,h2,h3,h4,h5 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; } h1 { font-size:218%; margin-top:0.6em; margin-bottom:.4em; line-height:1.1em; } h2 { font-size:175%; margin-bottom:.6em; line-height:1.2em; color:#092e20; } h3 { font-size:150%; font-weight:bold; margin-bottom:.2em; color:#487858; } h4 { font-size:125%; font-weight:bold; margin-top:1.5em; margin-bottom:3px; } +h5 { font-size:110%; font-weight:bold; margin-top:1em; margin-bottom:3px; } div.figure { text-align: center; } div.figure p.caption { font-size:1em; margin-top:0; margin-bottom:1.5em; color: #555;} hr { color:#ccc; background-color:#ccc; height:1px; border:0; } diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index d49a2d27eb73..f86157784d59 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2036,15 +2036,32 @@ evaluated will force it to evaluate again, repeating the query. Also, use of ``iterator()`` causes previous ``prefetch_related()`` calls to be ignored since these two optimizations do not make sense together. -Some Python database drivers still load the entire result set into memory, but -won't cache results after iterating over them. Oracle and :ref:`PostgreSQL -` use server-side cursors to stream results -from the database without loading the entire result set into memory. +Depending on the database backend, query results will either be loaded all at +once or streamed from the database using server-side cursors. + +With server-side cursors +^^^^^^^^^^^^^^^^^^^^^^^^ + +Oracle and :ref:`PostgreSQL ` use server-side +cursors to stream results from the database without loading the entire result +set into memory. + +The Oracle database driver always uses server-side cursors. On PostgreSQL, server-side cursors will only be used when the :setting:`DISABLE_SERVER_SIDE_CURSORS ` setting is ``False``. Read :ref:`transaction-pooling-server-side-cursors` if -you're using a connection pooler configured in transaction pooling mode. +you're using a connection pooler configured in transaction pooling mode. When +server-side cursors are disabled, the behavior is the same as databases that +don't support server-side cursors. + +Without server-side cursors +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +MySQL and SQLite don't support streaming results, hence the Python database +drivers load the entire result set into memory. The result set is then +transformed into Python row objects by the database adapter using the +``fetchmany()`` method defined in :pep:`249`. .. versionchanged:: 1.11 From 84dac491d4cf1901fe22c4035c8609a36f5c2c9a Mon Sep 17 00:00:00 2001 From: Lachlan Musicman Date: Fri, 2 Jun 2017 23:17:15 +1000 Subject: [PATCH 050/389] [1.11.x] Fixed #28266 -- Fixed typo in docs/ref/models/instances.txt. Backport of 00093daec9cc61d8af7fcebdbe58e263bea935a3 from master --- docs/ref/models/instances.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 3c1f2e051be0..2ec7a9bd064e 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -99,7 +99,7 @@ are loaded from the database:: values.pop() if f.attname in field_names else DEFERRED for f in cls._meta.concrete_fields ] - new = cls(*values) + instance = cls(*values) instance._state.adding = False instance._state.db = db # customization to store the original field values on the instance From 1940e3daef8192d289f82fe97a9535fbca6a2c8c Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 2 Jun 2017 06:46:43 -0700 Subject: [PATCH 051/389] [1.11.x] Fixed #28265 -- Prevented renderer warning on Widget.render() with **kwargs. Backport of 29a518006f7f96186483fa50e249e1c3f21728d5 from master --- django/forms/boundfield.py | 4 +-- docs/releases/1.11.3.txt | 4 ++- .../widget_tests/test_render_deprecation.py | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/forms_tests/widget_tests/test_render_deprecation.py diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py index a8e81afe9b51..c2c598ca6bbb 100644 --- a/django/forms/boundfield.py +++ b/django/forms/boundfield.py @@ -10,7 +10,7 @@ from django.utils.encoding import force_text, python_2_unicode_compatible from django.utils.functional import cached_property from django.utils.html import conditional_escape, format_html, html_safe -from django.utils.inspect import func_supports_parameter +from django.utils.inspect import func_accepts_kwargs, func_supports_parameter from django.utils.safestring import mark_safe from django.utils.translation import ugettext_lazy as _ @@ -112,7 +112,7 @@ def as_widget(self, widget=None, attrs=None, only_initial=False): name = self.html_initial_name kwargs = {} - if func_supports_parameter(widget.render, 'renderer'): + if func_supports_parameter(widget.render, 'renderer') or func_accepts_kwargs(widget.render): kwargs['renderer'] = self.form.renderer else: warnings.warn( diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 7eacbd8a36fc..15200fafd5df 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -9,4 +9,6 @@ Django 1.11.3 fixes several bugs in 1.11.2. Bugfixes ======== -* ... +* Removed an incorrect deprecation warning about a missing ``renderer`` + argument if a ``Widget.render()`` method accepts ``**kwargs`` + (:ticket:`28265`). diff --git a/tests/forms_tests/widget_tests/test_render_deprecation.py b/tests/forms_tests/widget_tests/test_render_deprecation.py new file mode 100644 index 000000000000..fc979957fbdd --- /dev/null +++ b/tests/forms_tests/widget_tests/test_render_deprecation.py @@ -0,0 +1,30 @@ +from django import forms +from django.test import SimpleTestCase +from django.utils.deprecation import RemovedInDjango21Warning + + +class RenderDeprecationTests(SimpleTestCase): + def test_custom_widget_renderer_warning(self): + class CustomWidget1(forms.TextInput): + def render(self, name, value, attrs=None, renderer=None): + return super(CustomWidget1, self).render(name, value, attrs, renderer) + + class CustomWidget2(forms.TextInput): + def render(self, *args, **kwargs): + return super(CustomWidget2, self).render(*args, **kwargs) + + class CustomWidget3(forms.TextInput): + def render(self, name, value, attrs=None): + return super(CustomWidget3, self).render(name, value, attrs) + + class MyForm(forms.Form): + foo = forms.CharField(widget=CustomWidget1) + bar = forms.CharField(widget=CustomWidget2) + baz = forms.CharField(widget=CustomWidget3) + + form = MyForm() + str(form['foo']) # No warning. + str(form['bar']) # No warning. + msg = "Add the `renderer` argument to the render() method of Date: Wed, 31 May 2017 15:25:09 +0100 Subject: [PATCH 052/389] [1.11.x] Fixed #26755 -- Fixed test_middleware_classes_headers if Django source isn't writable. Backport of 2ec56bb78237ebf58494d7a7f3262482399f0be6 from master --- tests/project_template/test_settings.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/project_template/test_settings.py b/tests/project_template/test_settings.py index a0047dd836dc..791c42e03aef 100644 --- a/tests/project_template/test_settings.py +++ b/tests/project_template/test_settings.py @@ -1,11 +1,12 @@ import os import shutil +import tempfile import unittest from django import conf from django.test import TestCase +from django.test.utils import extend_sys_path from django.utils import six -from django.utils._os import upath @unittest.skipIf( @@ -15,16 +16,16 @@ ) class TestStartProjectSettings(TestCase): def setUp(self): - # Ensure settings.py exists - project_dir = os.path.join( - os.path.dirname(upath(conf.__file__)), + self.temp_dir = tempfile.TemporaryDirectory() + self.addCleanup(self.temp_dir.cleanup) + template_settings_py = os.path.join( + os.path.dirname(conf.__file__), 'project_template', 'project_name', + 'settings.py-tpl', ) - template_settings_py = os.path.join(project_dir, 'settings.py-tpl') - test_settings_py = os.path.join(project_dir, 'settings.py') + test_settings_py = os.path.join(self.temp_dir.name, 'test_settings.py') shutil.copyfile(template_settings_py, test_settings_py) - self.addCleanup(os.remove, test_settings_py) def test_middleware_headers(self): """ @@ -32,7 +33,8 @@ def test_middleware_headers(self): change. For example, we never want "Vary: Cookie" to appear in the list since it prevents the caching of responses. """ - from django.conf.project_template.project_name.settings import MIDDLEWARE + with extend_sys_path(self.temp_dir.name): + from test_settings import MIDDLEWARE with self.settings( MIDDLEWARE=MIDDLEWARE, From 34ea3d61af2361c1ed2f4f0709d59c9a64bb866c Mon Sep 17 00:00:00 2001 From: Philip James Date: Sat, 4 Jun 2016 11:50:45 -0700 Subject: [PATCH 053/389] [1.11.x] Fixed #26028 -- Added overriding templates howto. Backport of 7c9a83330169df1118f6bd690aed131e7c59638d from master --- docs/howto/index.txt | 1 + docs/howto/overriding-templates.txt | 94 +++++++++++++++++++++++++++++ docs/ref/templates/index.txt | 3 + 3 files changed, 98 insertions(+) create mode 100644 docs/howto/overriding-templates.txt diff --git a/docs/howto/index.txt b/docs/howto/index.txt index 89e319281015..1bd3e757923c 100644 --- a/docs/howto/index.txt +++ b/docs/howto/index.txt @@ -24,6 +24,7 @@ you quickly accomplish common tasks. legacy-databases outputting-csv outputting-pdf + overriding-templates static-files/index static-files/deployment windows diff --git a/docs/howto/overriding-templates.txt b/docs/howto/overriding-templates.txt new file mode 100644 index 000000000000..f46dd1d85fd7 --- /dev/null +++ b/docs/howto/overriding-templates.txt @@ -0,0 +1,94 @@ +==================== +Overriding templates +==================== + +In your project, you might want to override a template in another Django +application, whether it be a third-party application or a contrib application +such as ``django.contrib.admin``. You can either put template overrides in your +project's templates directory or in an application's templates directory. + +If you have app and project templates directories that both contain overrides, +the default Django template loader will try to load the template from the +project-level directory first. In other words, :setting:`DIRS ` +is searched before :setting:`APP_DIRS `. + +Overriding from the project's templates directory +================================================= + +First, we'll explore overriding templates by creating replacement templates in +your project's templates directory. + +Let's say you're trying to override the templates for a third-party application +called ``blog``, which provides the templates ``blog/post.html`` and +``blog/list.html``. The relevant settings for your project would look like:: + + import os + + BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + INSTALLED_APPS = [ + ..., + 'blog', + ..., + ] + + TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [os.path.join(BASE_DIR, 'templates')], + 'APP_DIRS': True, + ... + }, + ] + +The :setting:`TEMPLATES` setting and ``BASE_DIR`` will already exist if you +created your project using the default project template. The setting that needs +to be modified is :setting:`DIRS`. + +These settings assume you have a ``templates`` directory in the root of your +project. To override the templates for the ``blog`` app, create a folder +in the ``templates`` directory, and add the template files to that folder: + +.. code-block:: none + + templates/ + blog/ + list.html + post.html + +The template loader first looks for templates in the ``DIRS`` directory. When +the views in the ``blog`` app ask for the ``blog/post.html`` and +``blog/list.html`` templates, the loader will return the files you just created. + +Overriding from an app's template directory +=========================================== + +Since you're overriding templates located outside of one of your project's +apps, it's more common to use the first method and put template overrides in a +project's templates folder. If you prefer, however, it's also possible to put +the overrides in an app's template directory. + +First, make sure your template settings are checking inside app directories:: + + TEMPLATES = [ + { + ..., + 'APP_DIRS': True, + ... + }, + ] + +If you want to put the template overrides in an app called ``myapp`` and the +templates to override are named ``blog/list.html`` and ``blog/post.html``, +then your directory structure will look like: + +.. code-block:: none + + myapp/ + templates/ + blog/ + list.html + post.html + +With :setting:`APP_DIRS` set to ``True``, the template +loader will look in the app's templates directory and find the templates. diff --git a/docs/ref/templates/index.txt b/docs/ref/templates/index.txt index e5730488c1a7..5d7edd865caa 100644 --- a/docs/ref/templates/index.txt +++ b/docs/ref/templates/index.txt @@ -20,3 +20,6 @@ material, see :doc:`/topics/templates` topic guide. For information on writing your own custom tags and filters, see :doc:`/howto/custom-template-tags`. + + To learn how to override templates in other Django applications, see + :doc:`/howto/overriding-templates`. From 9a3bcaf46aba223789f42df8bc08348694d9f16f Mon Sep 17 00:00:00 2001 From: Anupam Date: Sat, 3 Jun 2017 17:41:04 +0530 Subject: [PATCH 054/389] [1.11.x] Fixed #28190 -- Clarifed how include/extends treat template names. Backport of 1f2e4f9cfe26dd9ad1fc85375c1dce38c65bbe6b from master --- docs/ref/templates/builtins.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 80ba36b93a68..e9903223841f 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -215,8 +215,9 @@ This tag can be used in two ways: See :ref:`template-inheritance` for more information. -A string argument may be a relative path starting with ``./`` or ``../``. For -example, assume the following directory structure:: +Normally the template name is relative to the template loader's root directory. +A string argument may also be a relative path starting with ``./`` or ``../``. +For example, assume the following directory structure:: dir1/ template.html @@ -682,8 +683,9 @@ This example includes the contents of the template ``"foo/bar.html"``:: {% include "foo/bar.html" %} -A string argument may be a relative path starting with ``./`` or ``../`` as -described in the :ttag:`extends` tag. +Normally the template name is relative to the template loader's root directory. +A string argument may also be a relative path starting with ``./`` or ``../`` +as described in the :ttag:`extends` tag. .. versionadded:: 1.10 From 81c3967e554716dbb5c86ebc390fd07389c39c2e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 3 Jun 2017 09:50:14 +0200 Subject: [PATCH 055/389] [1.11.x] Refs #28192 -- Fixed documentation of ChoiceField choices requirement Thanks Tim Graham for noticing the issue. Backport of 54caca2d34c7cb6807da0a82bcec7b3a679ac104 from master. --- docs/ref/forms/fields.txt | 3 ++- tests/forms_tests/field_tests/test_choicefield.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index e2cfb59df287..8e419c7b5ba6 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -409,7 +409,7 @@ For each field, we describe the default widget used if you don't specify The ``invalid_choice`` error message may contain ``%(value)s``, which will be replaced with the selected choice. - Takes one extra required argument: + Takes one extra argument: .. attribute:: choices @@ -419,6 +419,7 @@ For each field, we describe the default widget used if you don't specify model field. See the :ref:`model field reference documentation on choices ` for more details. If the argument is a callable, it is evaluated each time the field's form is initialized. + Defaults to an emtpy list. ``TypedChoiceField`` -------------------- diff --git a/tests/forms_tests/field_tests/test_choicefield.py b/tests/forms_tests/field_tests/test_choicefield.py index 1d8fe5a3cfb0..ad773615ae0b 100644 --- a/tests/forms_tests/field_tests/test_choicefield.py +++ b/tests/forms_tests/field_tests/test_choicefield.py @@ -55,6 +55,10 @@ def test_choicefield_4(self): with self.assertRaisesMessage(ValidationError, msg): f.clean('6') + def test_choicefield_choices_default(self): + f = ChoiceField() + self.assertEqual(f.choices, []) + def test_choicefield_callable(self): def choices(): return [('J', 'John'), ('P', 'Paul')] From d6100b715d7804a74abaf47a30fa1d28e50dca4e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 3 Jun 2017 10:37:20 -0400 Subject: [PATCH 056/389] [1.11.x] Fixed typo in docs/ref/forms/fields.txt. Backport of ecae9c7aec3012788e08dea60bede63405c860fa from master --- docs/ref/forms/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 8e419c7b5ba6..4b00b4c68ecd 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -419,7 +419,7 @@ For each field, we describe the default widget used if you don't specify model field. See the :ref:`model field reference documentation on choices ` for more details. If the argument is a callable, it is evaluated each time the field's form is initialized. - Defaults to an emtpy list. + Defaults to an empty list. ``TypedChoiceField`` -------------------- From fa8346b9a9d9e16c4b6e928648538fccf9c82a2e Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 5 Jun 2017 08:27:55 -0400 Subject: [PATCH 057/389] [1.11.x] Added a test for Model._meta._property_names. Backport of 36f09c8a29eaad6a7e903ddc3ea1e8b5954ee67a from master --- tests/model_meta/models.py | 4 ++++ tests/model_meta/tests.py | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/tests/model_meta/models.py b/tests/model_meta/models.py index 074db093f9f2..882ac2c9fd8c 100644 --- a/tests/model_meta/models.py +++ b/tests/model_meta/models.py @@ -39,6 +39,10 @@ class AbstractPerson(models.Model): class Meta: abstract = True + @property + def test_property(self): + return 1 + class BasePerson(AbstractPerson): # DATA fields diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index 9a692ffdd26a..265e18033142 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -272,3 +272,8 @@ def test_get_parent_list(self): self.assertEqual(FirstParent._meta.get_parent_list(), [CommonAncestor]) self.assertEqual(SecondParent._meta.get_parent_list(), [CommonAncestor]) self.assertEqual(Child._meta.get_parent_list(), [FirstParent, SecondParent, CommonAncestor]) + + +class PropertyNamesTests(SimpleTestCase): + def test_person(self): + self.assertEqual(AbstractPerson._meta._property_names, frozenset(['pk', 'test_property'])) From b7d6077517c6cb2daa5e5faf2ae9f94698c06ca9 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sun, 4 Jun 2017 22:58:24 +0100 Subject: [PATCH 058/389] [1.11.x] Fixed #28269 -- Fixed Model.__init__() crash on models with a field that has an instance only descriptor. Regression in d2a26c1a90e837777dabdf3d67ceec4d2a70fb86. Backport of ed244199c72f5bbf33ab4547e06e69873d7271d0 from master --- django/db/models/options.py | 14 ++++++++++---- docs/releases/1.11.3.txt | 3 +++ tests/model_meta/models.py | 9 +++++++++ tests/model_meta/tests.py | 2 ++ 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/django/db/models/options.py b/django/db/models/options.py index 56a7e87b70e0..71b80b8abb71 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -882,7 +882,13 @@ def has_auto_field(self, value): @cached_property def _property_names(self): """Return a set of the names of the properties defined on the model.""" - return frozenset({ - attr for attr in - dir(self.model) if isinstance(getattr(self.model, attr), property) - }) + names = [] + for name in dir(self.model): + try: + attr = getattr(self.model, name) + except AttributeError: + pass + else: + if isinstance(attr, property): + names.append(name) + return frozenset(names) diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 15200fafd5df..ed2bf31cf5fa 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -12,3 +12,6 @@ Bugfixes * Removed an incorrect deprecation warning about a missing ``renderer`` argument if a ``Widget.render()`` method accepts ``**kwargs`` (:ticket:`28265`). + +* Fixed a regression causing ``Model.__init__()`` to crash if a field has an + instance only descriptor (:ticket:`28269`). diff --git a/tests/model_meta/models.py b/tests/model_meta/models.py index 882ac2c9fd8c..bd7e7f188910 100644 --- a/tests/model_meta/models.py +++ b/tests/model_meta/models.py @@ -9,6 +9,13 @@ class Relation(models.Model): pass +class InstanceOnlyDescriptor(object): + def __get__(self, instance, cls=None): + if instance is None: + raise AttributeError('Instance only') + return 1 + + class AbstractPerson(models.Model): # DATA fields data_abstract = models.CharField(max_length=10) @@ -43,6 +50,8 @@ class Meta: def test_property(self): return 1 + test_instance_only_descriptor = InstanceOnlyDescriptor() + class BasePerson(AbstractPerson): # DATA fields diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py index 265e18033142..1b71c95b3e52 100644 --- a/tests/model_meta/tests.py +++ b/tests/model_meta/tests.py @@ -276,4 +276,6 @@ def test_get_parent_list(self): class PropertyNamesTests(SimpleTestCase): def test_person(self): + # Instance only descriptors don't appear in _property_names. + self.assertEqual(AbstractPerson().test_instance_only_descriptor, 1) self.assertEqual(AbstractPerson._meta._property_names, frozenset(['pk', 'test_property'])) From 834d57b4de80e525195128c88592e0e076708a23 Mon Sep 17 00:00:00 2001 From: Paulo Date: Sun, 4 Jun 2017 14:10:48 -0400 Subject: [PATCH 059/389] [1.11.x] Fixed #28262 -- Fixed incorrect DisallowedModelAdminLookup when a nested reverse relation is in list_filter. Backport of b7f99f84bcc4a06114ac31174840efab0aef7602 from master --- django/contrib/admin/options.py | 2 +- docs/releases/1.11.3.txt | 3 +++ tests/admin_views/admin.py | 15 +++++++++------ tests/admin_views/models.py | 2 ++ tests/admin_views/tests.py | 5 +++++ 5 files changed, 20 insertions(+), 7 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index aef1e2c24e8a..0a2c53b3c6f2 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -360,7 +360,7 @@ def lookup_allowed(self, lookup, value): # It is allowed to filter on values that would be found from local # model anyways. For example, if you filter on employee__department__id, # then the id value would be found already from employee__department_id. - if not prev_field or (prev_field.concrete and + if not prev_field or (prev_field.is_relation and field not in prev_field.get_path_info()[-1].target_fields): relation_parts.append(part) if not getattr(field, 'get_path_info', None): diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index ed2bf31cf5fa..f273a36e9f83 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -15,3 +15,6 @@ Bugfixes * Fixed a regression causing ``Model.__init__()`` to crash if a field has an instance only descriptor (:ticket:`28269`). + +* Fixed an incorrect ``DisallowedModelAdminLookup`` exception when using + a nested reverse relation in ``list_filter`` (:ticket:`28262`). diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index e8a1cf3bff0e..c70bebec8715 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -82,12 +82,15 @@ class ChapterInline(admin.TabularInline): class ChapterXtra1Admin(admin.ModelAdmin): - list_filter = ('chap', - 'chap__title', - 'chap__book', - 'chap__book__name', - 'chap__book__promo', - 'chap__book__promo__name',) + list_filter = ( + 'chap', + 'chap__title', + 'chap__book', + 'chap__book__name', + 'chap__book__promo', + 'chap__book__promo__name', + 'guest_author__promo__book', + ) class ArticleAdmin(admin.ModelAdmin): diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 86ab055f3027..9c7eee754759 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -77,6 +77,7 @@ def __str__(self): class Promo(models.Model): name = models.CharField(max_length=100, verbose_name='¿Name?') book = models.ForeignKey(Book, models.CASCADE) + author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True) def __str__(self): return self.name @@ -100,6 +101,7 @@ class Meta: class ChapterXtra1(models.Model): chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name='¿Chap?') xtra = models.CharField(max_length=100, verbose_name='¿Xtra?') + guest_author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True) def __str__(self): return '¿Xtra1: %s' % self.xtra diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 4f281bd01aad..febb20914c5d 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -612,6 +612,11 @@ def test_relation_spanning_filters(self): 'values': [p.name for p in Promo.objects.all()], 'test': lambda obj, value: obj.chap.book.promo_set.filter(name=value).exists(), }, + # A forward relation (book) after a reverse relation (promo). + 'guest_author__promo__book__id__exact': { + 'values': [p.id for p in Book.objects.all()], + 'test': lambda obj, value: obj.guest_author.promo_set.filter(book=value).exists(), + }, } for filter_path, params in filters.items(): for value in params['values']: From b373812b0bb4654e049ccf6a60e92a7e9f603a99 Mon Sep 17 00:00:00 2001 From: Windson yang Date: Tue, 6 Jun 2017 05:26:49 +0800 Subject: [PATCH 060/389] [1.11.x] Fixed #28102 -- Doc'd how to compute path to built-in widget template directories. Backport of 7f238097c0614707d6ee3fffbaf76f111b2fd38d from master --- docs/ref/forms/renderers.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ref/forms/renderers.txt b/docs/ref/forms/renderers.txt index c94b5ef2264b..fcf025a9c8a6 100644 --- a/docs/ref/forms/renderers.txt +++ b/docs/ref/forms/renderers.txt @@ -93,9 +93,11 @@ Using this renderer along with the built-in widget templates requires either: #. ``'django.forms'`` in :setting:`INSTALLED_APPS` and at least one engine with :setting:`APP_DIRS=True `. -#. Adding the built-in widgets templates directory (``django/forms/templates`` - or ``django/forms/jinja2``) in :setting:`DIRS ` of one of - your template engines. +#. Adding the built-in widgets templates directory in :setting:`DIRS + ` of one of your template engines. To generate that path:: + + import django + django.__path__[0] + '/forms/templates' # or '/forms/jinja2' Using this renderer requires you to make sure the form templates your project needs can be located. From a0707947e4aacd461a3dbb653ddbf800ec2a6dea Mon Sep 17 00:00:00 2001 From: Paulo Date: Sat, 3 Jun 2017 18:13:38 -0400 Subject: [PATCH 061/389] [1.11.x] Fixed #28202 -- Fixed FieldListFilter.get_queryset() crash on invalid input. Backport of 4ad2f862844d35404e4798b3227517625210a72e from master --- django/contrib/admin/filters.py | 4 +++- docs/releases/1.11.3.txt | 3 +++ tests/admin_filters/tests.py | 8 ++++++++ tests/admin_views/admin.py | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/filters.py b/django/contrib/admin/filters.py index 87839c313064..8980370fbf75 100644 --- a/django/contrib/admin/filters.py +++ b/django/contrib/admin/filters.py @@ -135,7 +135,9 @@ def has_output(self): def queryset(self, request, queryset): try: return queryset.filter(**self.used_parameters) - except ValidationError as e: + except (ValueError, ValidationError) as e: + # Fields may raise a ValueError or ValidationError when converting + # the parameters to the correct type. raise IncorrectLookupParameters(e) @classmethod diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index f273a36e9f83..5b3b1066a93b 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -18,3 +18,6 @@ Bugfixes * Fixed an incorrect ``DisallowedModelAdminLookup`` exception when using a nested reverse relation in ``list_filter`` (:ticket:`28262`). + +* Fixed admin's ``FieldListFilter.get_queryset()`` crash on invalid input + (:ticket:`28202`). diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index e2da7ec59e59..509e25eeceb6 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -8,6 +8,7 @@ AllValuesFieldListFilter, BooleanFieldListFilter, ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, site, ) +from django.contrib.admin.options import IncorrectLookupParameters from django.contrib.admin.views.main import ChangeList from django.contrib.auth.admin import UserAdmin from django.contrib.auth.models import User @@ -766,6 +767,13 @@ def test_fieldlistfilter_underscorelookup_tuple(self): queryset = changelist.get_queryset(request) self.assertEqual(list(queryset), [self.bio_book, self.djangonaut_book]) + def test_fieldlistfilter_invalid_lookup_parameters(self): + """Filtering by an invalid value.""" + modeladmin = BookAdmin(Book, site) + request = self.request_factory.get('/', {'author__id__exact': 'StringNotInteger!'}) + with self.assertRaises(IncorrectLookupParameters): + self.get_changelist(request, Book, modeladmin) + def test_simplelistfilter(self): modeladmin = DecadeFilterBookAdmin(Book, site) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index c70bebec8715..b5e0ff481436 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -176,7 +176,7 @@ def changelist_view(self, request): class ThingAdmin(admin.ModelAdmin): - list_filter = ('color__warm', 'color__value', 'pub_date',) + list_filter = ('color', 'color__warm', 'color__value', 'pub_date') class InquisitionAdmin(admin.ModelAdmin): From 8f7e6b55e55f102d5e8ea7b9eca46b82bf8eaf61 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Jun 2017 11:24:44 -0400 Subject: [PATCH 062/389] [1.11.x] Fixed typo in docs/ref/class-based-views/mixins-single-object.txt. Backport of fc13a697b41568993ba02b7c52bb863456af6c84 from master --- docs/ref/class-based-views/mixins-single-object.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/class-based-views/mixins-single-object.txt b/docs/ref/class-based-views/mixins-single-object.txt index 9100e4a104a0..2801b9964bf1 100644 --- a/docs/ref/class-based-views/mixins-single-object.txt +++ b/docs/ref/class-based-views/mixins-single-object.txt @@ -100,7 +100,7 @@ Single object mixins .. method:: get_context_data(**kwargs) - Returns context data for displaying the list of objects. + Returns context data for displaying the object. The base implementation of this method requires that the ``self.object`` attribute be set by the view (even if ``None``). Be sure to do this if From 6ae60295d71b29e55d0498551c0dd6ee07cee1c4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Jun 2017 12:12:42 -0400 Subject: [PATCH 063/389] [1.11.x] Updated was_published_recently() tutorial test to check boundary condition. Backport of 268a646353c6fa9e5fc3730e13b386ddabb018ef from master --- docs/intro/tutorial05.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index f32fccc33d4b..7b1287809a92 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -285,7 +285,7 @@ more comprehensively: was_published_recently() should return False for questions whose pub_date is older than 1 day. """ - time = timezone.now() - datetime.timedelta(days=30) + time = timezone.now() - datetime.timedelta(days=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) @@ -294,7 +294,7 @@ more comprehensively: was_published_recently() should return True for questions whose pub_date is within the last day. """ - time = timezone.now() - datetime.timedelta(hours=1) + time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) self.assertIs(recent_question.was_published_recently(), True) From 992f143bad4143da7e43e21c41dfdab79ddd5070 Mon Sep 17 00:00:00 2001 From: kakulukia Date: Mon, 5 Jun 2017 22:17:10 +0200 Subject: [PATCH 064/389] [1.11.x] Fixed #28278 -- Fixed invalid HTML for a required AdminFileWidget. Backport of 525dc283a68c0d47f5eb2192cc4a20111d561ae0 from master --- .../admin/widgets/clearable_file_input.html | 2 +- docs/releases/1.11.3.txt | 2 ++ tests/admin_widgets/tests.py | 12 ++++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/django/contrib/admin/templates/admin/widgets/clearable_file_input.html b/django/contrib/admin/templates/admin/widgets/clearable_file_input.html index 327b8ad16a9d..9fee235819b6 100644 --- a/django/contrib/admin/templates/admin/widgets/clearable_file_input.html +++ b/django/contrib/admin/templates/admin/widgets/clearable_file_input.html @@ -1,6 +1,6 @@ {% if is_initial %}

{{ initial_text }}: {{ widget.value }}{% if not widget.required %} -{% endif %}
+{% endif %}
{{ input_text }}:{% endif %} {% if is_initial %}

{% endif %} diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 5b3b1066a93b..7085b026692b 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -21,3 +21,5 @@ Bugfixes * Fixed admin's ``FieldListFilter.get_queryset()`` crash on invalid input (:ticket:`28202`). + +* Fixed invalid HTML for a required ``AdminFileWidget`` (:ticket:`28278`). diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index f9c6d6a6140b..2e2bdb0b52df 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -434,6 +434,18 @@ def test_render(self): '', ) + def test_render_required(self): + widget = widgets.AdminFileWidget() + widget.is_required = True + self.assertHTMLEqual( + widget.render('test', self.album.cover_art), + '

Currently: albums\hybrid_theory.jpg
' + 'Change:

' % { + 'STORAGE_URL': default_storage.url(''), + }, + ) + def test_readonly_fields(self): """ File widgets should render as a link when they're marked "read only." From bc9c6fe7cb11fb0a59b39ca6f7661cb6013f513c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Jun 2017 16:11:48 -0400 Subject: [PATCH 065/389] [1.11.x] Fixed #28233 -- Used a simpler example in the aggregation "cheat sheet" docs. Backport of 49b9c89d4094574117c9d5b7a696ce152e02553a from master --- docs/topics/db/aggregation.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index aa56a8b4eb66..6f7d3918affe 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -67,11 +67,11 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above >>> Book.objects.all().aggregate(Max('price')) {'price__max': Decimal('81.20')} - # Cost per page - >>> from django.db.models import F, FloatField, Sum - >>> Book.objects.all().aggregate( - ... price_per_page=Sum(F('price')/F('pages'), output_field=FloatField())) - {'price_per_page': 0.4470664529184653} + # Difference between the highest priced book and the average price of all books. + >>> from django.db.models import FloatField + >>> Book.objects.aggregate( + ... price_diff=Max('price', output_field=FloatField()) - Avg('price'))) + {'price_diff': 46.85} # All the following queries involve traversing the Book<->Publisher # foreign key relationship backwards. From 56e4a01b505089929e3018ea53bdeef16ab7c7c6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 7 Jun 2017 09:47:15 -0400 Subject: [PATCH 066/389] [1.11.x] Simplified tutorial's test names and docstrings. Backport of 23825b2494a1edb1e02d57dbdc7eca0614cefcc8 from master --- docs/intro/tutorial05.txt | 71 +++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 7b1287809a92..aad693c0dc44 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -171,12 +171,12 @@ Put the following in the ``tests.py`` file in the ``polls`` application: from .models import Question - class QuestionMethodTests(TestCase): + class QuestionModelTests(TestCase): def test_was_published_recently_with_future_question(self): """ - was_published_recently() should return False for questions whose - pub_date is in the future. + was_published_recently() returns False for questions whose pub_date + is in the future. """ time = timezone.now() + datetime.timedelta(days=30) future_question = Question(pub_date=time) @@ -200,7 +200,7 @@ and you'll see something like:: System check identified no issues (0 silenced). F ====================================================================== - FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionMethodTests) + FAIL: test_was_published_recently_with_future_question (polls.tests.QuestionModelTests) ---------------------------------------------------------------------- Traceback (most recent call last): File "/path/to/mysite/polls/tests.py", line 16, in test_was_published_recently_with_future_question @@ -282,8 +282,8 @@ more comprehensively: def test_was_published_recently_with_old_question(self): """ - was_published_recently() should return False for questions whose - pub_date is older than 1 day. + was_published_recently() returns False for questions whose pub_date + is older than 1 day. """ time = timezone.now() - datetime.timedelta(days=1) old_question = Question(pub_date=time) @@ -291,8 +291,8 @@ more comprehensively: def test_was_published_recently_with_recent_question(self): """ - was_published_recently() should return True for questions whose - pub_date is within the last day. + was_published_recently() returns True for questions whose pub_date + is within the last day. """ time = timezone.now() - datetime.timedelta(hours=23, minutes=59, seconds=59) recent_question = Question(pub_date=time) @@ -450,7 +450,7 @@ class: def create_question(question_text, days): """ - Creates a question with the given `question_text` and published the + Create a question with the given `question_text` and published the given number of `days` offset to now (negative for questions published in the past, positive for questions that have yet to be published). """ @@ -458,19 +458,19 @@ class: return Question.objects.create(question_text=question_text, pub_date=time) - class QuestionViewTests(TestCase): - def test_index_view_with_no_questions(self): + class QuestionIndexViewTests(TestCase): + def test_no_questions(self): """ - If no questions exist, an appropriate message should be displayed. + If no questions exist, an appropriate message is displayed. """ response = self.client.get(reverse('polls:index')) self.assertEqual(response.status_code, 200) self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) - def test_index_view_with_a_past_question(self): + def test_past_question(self): """ - Questions with a pub_date in the past should be displayed on the + Questions with a pub_date in the past are displayed on the index page. """ create_question(question_text="Past question.", days=-30) @@ -480,9 +480,9 @@ class: [''] ) - def test_index_view_with_a_future_question(self): + def test_future_question(self): """ - Questions with a pub_date in the future should not be displayed on + Questions with a pub_date in the future aren't displayed on the index page. """ create_question(question_text="Future question.", days=30) @@ -490,10 +490,10 @@ class: self.assertContains(response, "No polls are available.") self.assertQuerysetEqual(response.context['latest_question_list'], []) - def test_index_view_with_future_question_and_past_question(self): + def test_future_question_and_past_question(self): """ Even if both past and future questions exist, only past questions - should be displayed. + are displayed. """ create_question(question_text="Past question.", days=-30) create_question(question_text="Future question.", days=30) @@ -503,7 +503,7 @@ class: [''] ) - def test_index_view_with_two_past_questions(self): + def test_two_past_questions(self): """ The questions index page may display multiple questions. """ @@ -521,20 +521,19 @@ Let's look at some of these more closely. First is a question shortcut function, ``create_question``, to take some repetition out of the process of creating questions. -``test_index_view_with_no_questions`` doesn't create any questions, but checks -the message: "No polls are available." and verifies the ``latest_question_list`` -is empty. Note that the :class:`django.test.TestCase` class provides some -additional assertion methods. In these examples, we use +``test_no_questions`` doesn't create any questions, but checks the message: +"No polls are available." and verifies the ``latest_question_list`` is empty. +Note that the :class:`django.test.TestCase` class provides some additional +assertion methods. In these examples, we use :meth:`~django.test.SimpleTestCase.assertContains()` and :meth:`~django.test.TransactionTestCase.assertQuerysetEqual()`. -In ``test_index_view_with_a_past_question``, we create a question and verify that it -appears in the list. +In ``test_past_question``, we create a question and verify that it appears in +the list. -In ``test_index_view_with_a_future_question``, we create a question with a -``pub_date`` in the future. The database is reset for each test method, so the -first question is no longer there, and so again the index shouldn't have any -questions in it. +In ``test_future_question``, we create a question with a ``pub_date`` in the +future. The database is reset for each test method, so the first question is no +longer there, and so again the index shouldn't have any questions in it. And so on. In effect, we are using the tests to tell a story of admin input and user experience on the site, and checking that at every state and for every @@ -565,21 +564,21 @@ in the future is not: .. snippet:: :filename: polls/tests.py - class QuestionIndexDetailTests(TestCase): - def test_detail_view_with_a_future_question(self): + class QuestionDetailViewTests(TestCase): + def test_future_question(self): """ - The detail view of a question with a pub_date in the future should - return a 404 not found. + The detail view of a question with a pub_date in the future + returns a 404 not found. """ future_question = create_question(question_text='Future question.', days=5) url = reverse('polls:detail', args=(future_question.id,)) response = self.client.get(url) self.assertEqual(response.status_code, 404) - def test_detail_view_with_a_past_question(self): + def test_past_question(self): """ - The detail view of a question with a pub_date in the past should - display the question's text. + The detail view of a question with a pub_date in the past + displays the question's text. """ past_question = create_question(question_text='Past Question.', days=-5) url = reverse('polls:detail', args=(past_question.id,)) From 288fd9b9e0a727a6e2374cc181d2c4572ca0cb3d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 7 Jun 2017 16:46:52 -0400 Subject: [PATCH 067/389] [1.11.x] Corrected FileExtensionValidator doc regarding the value being validated. Backport of c01409c7899789206b40769ed308e6a7297f9697 from master --- docs/ref/validators.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 66803ee5349f..98152c78a186 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -288,8 +288,8 @@ to, or in lieu of custom ``field.clean()`` methods. .. versionadded:: 1.11 Raises a :exc:`~django.core.exceptions.ValidationError` with a code of - ``'invalid_extension'`` if the ``value`` cannot be found in - ``allowed_extensions``. + ``'invalid_extension'`` if the extension of ``value.name`` (``value`` is + a :class:`~django.core.files.File`) isn't found in ``allowed_extensions``. .. warning:: @@ -304,5 +304,6 @@ to, or in lieu of custom ``field.clean()`` methods. .. versionadded:: 1.11 - Uses Pillow to ensure that the ``value`` is `a valid image extension + Uses Pillow to ensure that ``value.name`` (``value`` is a + :class:`~django.core.files.File`) has `a valid image extension `_. From 319839d7805efe48da82d7565b638c3b872461ae Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Thu, 8 Jun 2017 10:34:28 +0200 Subject: [PATCH 068/389] [1.11.x] Refs #25240 -- Added ExtractWeek examples. Backport of 085c2f94ec0155417601a9750ad60bb93536e166 from master --- docs/ref/models/database-functions.txt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index 231d023e11d2..a25206951954 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -407,7 +407,7 @@ that deal with date-parts can be used with ``DateField``:: >>> from datetime import datetime >>> from django.utils import timezone >>> from django.db.models.functions import ( - ... ExtractYear, ExtractMonth, ExtractDay, ExtractWeekDay + ... ExtractDay, ExtractMonth, ExtractWeek, ExtractWeekDay, ExtractYear, ... ) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) @@ -417,12 +417,13 @@ that deal with date-parts can be used with ``DateField``:: >>> Experiment.objects.annotate( ... year=ExtractYear('start_date'), ... month=ExtractMonth('start_date'), + ... week=ExtractWeek('start_date'), ... day=ExtractDay('start_date'), ... weekday=ExtractWeekDay('start_date'), - ... ).values('year', 'month', 'day', 'weekday').get( + ... ).values('year', 'month', 'week', 'day', 'weekday').get( ... end_date__year=ExtractYear('start_date'), ... ) - {'year': 2015, 'month': 6, 'day': 15, 'weekday': 2} + {'year': 2015, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2} ``DateTimeField`` extracts ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -451,8 +452,8 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as >>> from datetime import datetime >>> from django.utils import timezone >>> from django.db.models.functions import ( - ... ExtractYear, ExtractMonth, ExtractDay, ExtractWeekDay, - ... ExtractHour, ExtractMinute, ExtractSecond, + ... ExtractDay, ExtractHour, ExtractMinute, ExtractMonth, ExtractSecond, + ... ExtractWeek, ExtractWeekDay, ExtractYear, ... ) >>> start_2015 = datetime(2015, 6, 15, 23, 30, 1, tzinfo=timezone.utc) >>> end_2015 = datetime(2015, 6, 16, 13, 11, 27, tzinfo=timezone.utc) @@ -462,15 +463,17 @@ Each class is also a ``Transform`` registered on ``DateTimeField`` as >>> Experiment.objects.annotate( ... year=ExtractYear('start_datetime'), ... month=ExtractMonth('start_datetime'), + ... week=ExtractWeek('start_datetime'), ... day=ExtractDay('start_datetime'), ... weekday=ExtractWeekDay('start_datetime'), ... hour=ExtractHour('start_datetime'), ... minute=ExtractMinute('start_datetime'), ... second=ExtractSecond('start_datetime'), ... ).values( - ... 'year', 'month', 'day', 'weekday', 'hour', 'minute', 'second', + ... 'year', 'month', 'week', 'day', 'weekday', 'hour', 'minute', 'second', ... ).get(end_datetime__year=ExtractYear('start_datetime')) - {'year': 2015, 'month': 6, 'day': 15, 'weekday': 2, 'hour': 23, 'minute': 30, 'second': 1} + {'year': 2015, 'month': 6, 'week': 25, 'day': 15, 'weekday': 2, 'hour': 23, + 'minute': 30, 'second': 1} When :setting:`USE_TZ` is ``True`` then datetimes are stored in the database in UTC. If a different timezone is active in Django, the datetime is converted From ccb8297eeec1c2bcbb193d2ae37f081b2c418757 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Wed, 7 Jun 2017 07:13:12 -0700 Subject: [PATCH 069/389] [1.11.x] Fixed #28282 -- Fixed class-based indexes name for models that only inherit Model. Backport of 0c3c37a376bac149fe7e7e4b2696f8fb7990e2ab from master --- django/db/models/base.py | 17 +++++++++-------- docs/releases/1.11.3.txt | 3 +++ tests/model_indexes/models.py | 3 +++ tests/model_indexes/tests.py | 4 ++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 217bde1e2a96..5067fb9e4f12 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -303,14 +303,15 @@ def __new__(cls, name, bases, attrs): else: new_class.add_to_class(field.name, copy.deepcopy(field)) - if base_meta and base_meta.abstract and not abstract: - new_class._meta.indexes = [copy.deepcopy(idx) for idx in new_class._meta.indexes] - # Set the name of _meta.indexes. This can't be done in - # Options.contribute_to_class() because fields haven't been added - # to the model at that point. - for index in new_class._meta.indexes: - if not index.name: - index.set_name_with_model(new_class) + # Copy indexes so that index names are unique when models extend an + # abstract model. + new_class._meta.indexes = [copy.deepcopy(idx) for idx in new_class._meta.indexes] + # Set the name of _meta.indexes. This can't be done in + # Options.contribute_to_class() because fields haven't been added to + # the model at that point. + for index in new_class._meta.indexes: + if not index.name: + index.set_name_with_model(new_class) if abstract: # Abstract base models can't be instantiated and don't appear in diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 7085b026692b..de6def5aa15b 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -23,3 +23,6 @@ Bugfixes (:ticket:`28202`). * Fixed invalid HTML for a required ``AdminFileWidget`` (:ticket:`28278`). + +* Fixed model initialization to set the name of class-based model indexes + for models that only inherit ``models.Model`` (:ticket:`28282`). diff --git a/tests/model_indexes/models.py b/tests/model_indexes/models.py index 34b3f3246cfb..6d74ad8fa67f 100644 --- a/tests/model_indexes/models.py +++ b/tests/model_indexes/models.py @@ -6,6 +6,9 @@ class Book(models.Model): author = models.CharField(max_length=50) pages = models.IntegerField(db_column='page_count') + class Meta: + indexes = [models.indexes.Index(fields=['title'])] + class AbstractModel(models.Model): name = models.CharField(max_length=50) diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index 791233daf091..c0f5a84fdb56 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -83,6 +83,10 @@ def test_clone(self): self.assertIsNot(index, new_index) self.assertEqual(index.fields, new_index.fields) + def test_name_set(self): + index_names = [index.name for index in Book._meta.indexes] + self.assertEqual(index_names, ['model_index_title_196f42_idx']) + def test_abstract_children(self): index_names = [index.name for index in ChildModel1._meta.indexes] self.assertEqual(index_names, ['model_index_name_440998_idx']) From 109fd94c965ad8b13ec27e17f9ba8f5fbd286975 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 9 Jun 2017 12:42:53 -0400 Subject: [PATCH 070/389] [1.11.x] Fixed typo in docs/ref/contrib/postgres/fields.txt. Backport of 108ff788cbcd0e1f492d1494dc95e7b2165340fd from master --- docs/ref/contrib/postgres/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 33e48e2910a3..2048bb064efe 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -559,7 +559,7 @@ name:: Multiple keys can be chained together to form a path lookup:: >>> Dog.objects.filter(data__owner__name='Bob') - ]> + ]> If the key is an integer, it will be interpreted as an index lookup in an array:: From 48ce204488c6cca8ab453a756fa655f0c886533c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 9 Jun 2017 12:48:24 -0400 Subject: [PATCH 071/389] [1.11.x] Fixed typo in docs/ref/models/querysets.txt. Backport of 0877989c94130079930419e867ff55fa1fb4f5a0 from master --- docs/ref/models/querysets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index f86157784d59..53e98f9ba6b6 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -3221,7 +3221,7 @@ as the string based lookups passed to # This will only execute two queries regardless of the number of Question # and Choice objects. >>> Question.objects.prefetch_related(Prefetch('choice_set')).all() - ]> + ]> The ``queryset`` argument supplies a base ``QuerySet`` for the given lookup. This is useful to further filter down the prefetch operation, or to call From 7fb148a638044e96fea9996115deb334992ec072 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 12 Jun 2017 15:39:09 -0400 Subject: [PATCH 072/389] [1.11.x] Fixed #27655 -- Added some guidelines to the coding style docs. Backport of c68f5d83c0a4ea4ccf87c7d1d5dd1e9f7b2907a3 from master --- .../writing-code/coding-style.txt | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt index 189e7c742610..0d3ba8da1f41 100644 --- a/docs/internals/contributing/writing-code/coding-style.txt +++ b/docs/internals/contributing/writing-code/coding-style.txt @@ -33,6 +33,27 @@ Python style * Use four spaces for indentation. +* Use four space hanging indentation rather than vertical alignment:: + + raise AttributeError( + 'Here is a multine error message ' + 'shortened for clarity.' + ) + + Instead of:: + + raise AttributeError('Here is a multine error message ' + 'shortened for clarity.') + + This makes better use of space and avoids having to realign strings if the + length of the first line changes. + +* Use single quotes for strings, or a double quote if the the string contains a + single quote. Don't waste time doing unrelated refactoring of existing code + to conform to this style. + +* Avoid use of "we" in comments, e.g. "Loop over" rather than "We loop over". + * Use underscores, not camelCase, for variable, function and method names (i.e. ``poll.get_unique_voters()``, not ``poll.getUniqueVoters()``). From 927d9b51fee2442280ae975b21b98b5a705c4b17 Mon Sep 17 00:00:00 2001 From: Paulo Date: Fri, 9 Jun 2017 20:54:10 -0400 Subject: [PATCH 073/389] [1.11.x] Fixed #27967 -- Fixed KeyError in admin's inline form with inherited non-editable pk. Thanks Robin Anupol for the initial report and workaround. Backport of 9dc83c356d363c090f3351c908cad6f823aeb7bf from master --- django/contrib/admin/helpers.py | 2 +- docs/releases/1.11.3.txt | 3 +++ tests/admin_inlines/admin.py | 14 ++++++++++---- tests/admin_inlines/models.py | 4 ++++ tests/admin_inlines/tests.py | 17 ++++++++++++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index cbe03031a97c..b0726cee1207 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -355,7 +355,7 @@ def needs_explicit_pk_field(self): # Also search any parents for an auto field. (The pk info is propagated to child # models so that does not need to be checked in parents.) for parent in self.form._meta.model._meta.get_parent_list(): - if parent._meta.auto_field: + if parent._meta.auto_field or not parent._meta.model._meta.pk.editable: return True return False diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index de6def5aa15b..e742f3e770a6 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -26,3 +26,6 @@ Bugfixes * Fixed model initialization to set the name of class-based model indexes for models that only inherit ``models.Model`` (:ticket:`28282`). + +* Fixed crash in admin's inlines when a model has an inherited non-editable + primary key (:ticket:`27967`). diff --git a/tests/admin_inlines/admin.py b/tests/admin_inlines/admin.py index c3bc8d769840..1a4c8a1f1f7a 100644 --- a/tests/admin_inlines/admin.py +++ b/tests/admin_inlines/admin.py @@ -5,10 +5,10 @@ Author, BinaryTree, CapoFamiglia, Chapter, ChildModel1, ChildModel2, Consigliere, EditablePKBook, ExtraTerrestrial, Fashionista, Holder, Holder2, Holder3, Holder4, Inner, Inner2, Inner3, Inner4Stacked, - Inner4Tabular, NonAutoPKBook, Novel, ParentModelWithCustomPk, Poll, - Profile, ProfileCollection, Question, ReadOnlyInline, ShoppingWeakness, - Sighting, SomeChildModel, SomeParentModel, SottoCapo, Title, - TitleCollection, + Inner4Tabular, NonAutoPKBook, NonAutoPKBookChild, Novel, + ParentModelWithCustomPk, Poll, Profile, ProfileCollection, Question, + ReadOnlyInline, ShoppingWeakness, Sighting, SomeChildModel, + SomeParentModel, SottoCapo, Title, TitleCollection, ) site = admin.AdminSite(name="admin") @@ -23,6 +23,11 @@ class NonAutoPKBookTabularInline(admin.TabularInline): classes = ('collapse',) +class NonAutoPKBookChildTabularInline(admin.TabularInline): + model = NonAutoPKBookChild + classes = ('collapse',) + + class NonAutoPKBookStackedInline(admin.StackedInline): model = NonAutoPKBook classes = ('collapse',) @@ -40,6 +45,7 @@ class AuthorAdmin(admin.ModelAdmin): inlines = [ BookInline, NonAutoPKBookTabularInline, NonAutoPKBookStackedInline, EditablePKBookTabularInline, EditablePKBookStackedInline, + NonAutoPKBookChildTabularInline, ] diff --git a/tests/admin_inlines/models.py b/tests/admin_inlines/models.py index 15297c521ff3..30ae6b631489 100644 --- a/tests/admin_inlines/models.py +++ b/tests/admin_inlines/models.py @@ -63,6 +63,10 @@ def save(self, *args, **kwargs): super(NonAutoPKBook, self).save(*args, **kwargs) +class NonAutoPKBookChild(NonAutoPKBook): + pass + + class EditablePKBook(models.Model): manual_pk = models.IntegerField(primary_key=True) author = models.ForeignKey(Author, models.CASCADE) diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 6922a9f82fd5..501d092ec19a 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -357,6 +357,21 @@ def test_inline_nonauto_noneditable_pk(self): html=True ) + def test_inline_nonauto_noneditable_inherited_pk(self): + response = self.client.get(reverse('admin:admin_inlines_author_add')) + self.assertContains( + response, + '', + html=True + ) + self.assertContains( + response, + '', + html=True + ) + def test_inline_editable_pk(self): response = self.client.get(reverse('admin:admin_inlines_author_add')) self.assertContains( @@ -888,7 +903,7 @@ def test_collapsed_inlines(self): # One field is in a stacked inline, other in a tabular one. test_fields = ['#id_nonautopkbook_set-0-title', '#id_nonautopkbook_set-2-0-title'] show_links = self.selenium.find_elements_by_link_text('SHOW') - self.assertEqual(len(show_links), 2) + self.assertEqual(len(show_links), 3) for show_index, field_name in enumerate(test_fields, 0): self.wait_until_invisible(field_name) show_links[show_index].click() From 44e29ea1e906859e85bb2a46ae5ea9d82bd96f5f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 13 Jun 2017 08:16:16 +0200 Subject: [PATCH 074/389] [1.11.x] Fixed #28293 -- Fixed union(), intersection(), and difference() when combining with an EmptyQuerySet. Thanks Jon Dufresne for the report and Tim Graham for the review. Backport of 82175ead723f8fa3f9271fbd4b24275097029aab from master --- django/db/models/query.py | 13 +++++++++++++ django/db/models/sql/compiler.py | 2 +- docs/releases/1.11.3.txt | 3 +++ tests/queries/test_qs_combinators.py | 25 +++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index c9ff437232cd..8a97e45b8801 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -838,12 +838,25 @@ def union(self, *other_qs, **kwargs): "union() received an unexpected keyword argument '%s'" % (unexpected_kwarg,) ) + # If the query is an EmptyQuerySet, combine all nonempty querysets. + if isinstance(self, EmptyQuerySet): + qs = [q for q in other_qs if not isinstance(q, EmptyQuerySet)] + return qs[0]._combinator_query('union', *qs[1:], **kwargs) if qs else self return self._combinator_query('union', *other_qs, **kwargs) def intersection(self, *other_qs): + # If any query is an EmptyQuerySet, return it. + if isinstance(self, EmptyQuerySet): + return self + for other in other_qs: + if isinstance(other, EmptyQuerySet): + return other return self._combinator_query('intersection', *other_qs) def difference(self, *other_qs): + # If the query is an EmptyQuerySet, return it. + if isinstance(self, EmptyQuerySet): + return self return self._combinator_query('difference', *other_qs) def select_for_update(self, nowait=False, skip_locked=False): diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 9acb56aa8441..d1373fcf9552 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -379,7 +379,7 @@ def get_combinator_sql(self, combinator, all): features = self.connection.features compilers = [ query.get_compiler(self.using, self.connection) - for query in self.query.combined_queries + for query in self.query.combined_queries if not query.is_empty() ] if not features.supports_slicing_ordering_in_compound: for query, compiler in zip(self.query.combined_queries, compilers): diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index e742f3e770a6..33645d05b366 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -29,3 +29,6 @@ Bugfixes * Fixed crash in admin's inlines when a model has an inherited non-editable primary key (:ticket:`27967`). + +* Fixed ``QuerySet.union()``, ``intersection()``, and ``difference()`` when + combining with an ``EmptyQuerySet`` (:ticket:`28293`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index a0faab2eb79a..ec341952eac5 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -45,6 +45,31 @@ def test_union_distinct(self): self.assertEqual(len(list(qs1.union(qs2, all=True))), 20) self.assertEqual(len(list(qs1.union(qs2))), 10) + @skipUnlessDBFeature('supports_select_intersection') + def test_intersection_with_empty_qs(self): + qs1 = Number.objects.all() + qs2 = Number.objects.none() + self.assertEqual(len(qs1.intersection(qs2)), 0) + self.assertEqual(len(qs2.intersection(qs1)), 0) + self.assertEqual(len(qs2.intersection(qs2)), 0) + + @skipUnlessDBFeature('supports_select_difference') + def test_difference_with_empty_qs(self): + qs1 = Number.objects.all() + qs2 = Number.objects.none() + self.assertEqual(len(qs1.difference(qs2)), 10) + self.assertEqual(len(qs2.difference(qs1)), 0) + self.assertEqual(len(qs2.difference(qs2)), 0) + + def test_union_with_empty_qs(self): + qs1 = Number.objects.all() + qs2 = Number.objects.none() + self.assertEqual(len(qs1.union(qs2)), 10) + self.assertEqual(len(qs2.union(qs1)), 10) + self.assertEqual(len(qs2.union(qs1, qs1, qs1)), 10) + self.assertEqual(len(qs2.union(qs1, qs1, all=True)), 20) + self.assertEqual(len(qs2.union(qs2)), 0) + def test_union_bad_kwarg(self): qs1 = Number.objects.all() msg = "union() received an unexpected keyword argument 'bad'" From f908e9d015b5ac56b834f362339b03f6647e35ee Mon Sep 17 00:00:00 2001 From: orf Date: Thu, 8 Jun 2017 17:29:13 +0100 Subject: [PATCH 075/389] [1.11.x] Fixed #28284 -- Prevented Paginator's unordered object list warning from evaluating a QuerySet. Backport of a118287bca65f2e5da110c89509941c677ebc2e1 from master --- django/core/paginator.py | 10 ++++++++-- docs/releases/1.11.3.txt | 3 +++ tests/pagination/tests.py | 22 ++++++++++++++++++---- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/django/core/paginator.py b/django/core/paginator.py index bcd43c2033a1..f149598203f0 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -105,10 +105,16 @@ def _check_object_list_is_ordered(self): """ Warn if self.object_list is unordered (typically a QuerySet). """ - if hasattr(self.object_list, 'ordered') and not self.object_list.ordered: + ordered = getattr(self.object_list, 'ordered', None) + if ordered is not None and not ordered: + obj_list_repr = ( + '{} {}'.format(self.object_list.model, self.object_list.__class__.__name__) + if hasattr(self.object_list, 'model') + else '{!r}'.format(self.object_list) + ) warnings.warn( 'Pagination may yield inconsistent results with an unordered ' - 'object_list: {!r}'.format(self.object_list), + 'object_list: {}.'.format(obj_list_repr), UnorderedObjectListWarning, stacklevel=3 ) diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 33645d05b366..6afe77e8bbb8 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -32,3 +32,6 @@ Bugfixes * Fixed ``QuerySet.union()``, ``intersection()``, and ``difference()`` when combining with an ``EmptyQuerySet`` (:ticket:`28293`). + +* Prevented ``Paginator``’s unordered object list warning from evaluating a + ``QuerySet`` (:ticket:`28284`). diff --git a/tests/pagination/tests.py b/tests/pagination/tests.py index beab0ae0c54e..cc1c81a2115b 100644 --- a/tests/pagination/tests.py +++ b/tests/pagination/tests.py @@ -331,11 +331,25 @@ def test_paginating_unordered_queryset_raises_warning(self): warning = warns[0] self.assertEqual(str(warning.message), ( "Pagination may yield inconsistent results with an unordered " - "object_list: , " - ", , , " - ", , , " - ", ]>" + "object_list: QuerySet." )) # The warning points at the Paginator caller (i.e. the stacklevel # is appropriate). self.assertEqual(warning.filename, __file__) + + def test_paginating_unordered_object_list_raises_warning(self): + """ + Unordered object list warning with an object that has an orderd + attribute but not a model attribute. + """ + class ObjectList(): + ordered = False + object_list = ObjectList() + with warnings.catch_warnings(record=True) as warns: + warnings.filterwarnings('always', category=UnorderedObjectListWarning) + Paginator(object_list, 5) + self.assertEqual(len(warns), 1) + self.assertEqual(str(warns[0].message), ( + "Pagination may yield inconsistent results with an unordered " + "object_list: {!r}.".format(object_list) + )) From 16431b03f801788d791bbb24d6fb266c3591ab07 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Mon, 22 May 2017 14:52:56 -0700 Subject: [PATCH 076/389] [1.11.x] Fixed #28229 -- Fixed the value of LoginView's "next" template variable. Backport of e7dc39fb65e51d7613c941f7e5768b621dea4e76 from master --- django/contrib/auth/views.py | 12 +++++++----- docs/releases/1.11.3.txt | 5 +++++ tests/auth_tests/test_views.py | 1 + 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 8f841ab571e5..4c5128143681 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -90,7 +90,11 @@ def dispatch(self, request, *args, **kwargs): return super(LoginView, self).dispatch(request, *args, **kwargs) def get_success_url(self): - """Ensure the user-originating redirection URL is safe.""" + url = self.get_redirect_url() + return url or resolve_url(settings.LOGIN_REDIRECT_URL) + + def get_redirect_url(self): + """Return the user-originating redirect URL if it's safe.""" redirect_to = self.request.POST.get( self.redirect_field_name, self.request.GET.get(self.redirect_field_name, '') @@ -100,9 +104,7 @@ def get_success_url(self): allowed_hosts=self.get_success_url_allowed_hosts(), require_https=self.request.is_secure(), ) - if not url_is_safe: - return resolve_url(settings.LOGIN_REDIRECT_URL) - return redirect_to + return redirect_to if url_is_safe else '' def get_form_class(self): return self.authentication_form or self.form_class @@ -121,7 +123,7 @@ def get_context_data(self, **kwargs): context = super(LoginView, self).get_context_data(**kwargs) current_site = get_current_site(self.request) context.update({ - self.redirect_field_name: self.get_success_url(), + self.redirect_field_name: self.get_redirect_url(), 'site': current_site, 'site_name': current_site.name, }) diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 6afe77e8bbb8..5ff33e42e6ec 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -35,3 +35,8 @@ Bugfixes * Prevented ``Paginator``’s unordered object list warning from evaluating a ``QuerySet`` (:ticket:`28284`). + +* Fixed the value of ``redirect_field_name`` in ``LoginView``’s template + context. It's now an empty string (as it is for the original function-based + ``login()`` view) if the corresponding parameter isn't sent in a request (in + particular, when the login page is accessed directly) (:ticket:`28229`). diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index a66d1e5809a9..25b779f709be 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -832,6 +832,7 @@ def test_default(self): self.login() response = self.client.get(self.dont_redirect_url) self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['next'], '') def test_guest(self): """If not logged in, stay on the same page.""" From 31d7fc85410c81932a42c4d5bb84759fd9f4a295 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 13 Jun 2017 14:16:52 -0400 Subject: [PATCH 077/389] [1.11.x] Refs #23853 -- Updated sql.query.Query.join() docstring. Follow up to ab89414f40db1598364a7fe4cfac1766cacd2668. Backport of 7acbe89cc2a72f1b85d415c1cdb622c0dbbbe37c from master --- django/db/models/sql/query.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 6b3dc4a3e941..e2d273994e1c 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -902,27 +902,16 @@ def count_active_tables(self): def join(self, join, reuse=None): """ - Returns an alias for the join in 'connection', either reusing an - existing alias for that join or creating a new one. 'connection' is a - tuple (lhs, table, join_cols) where 'lhs' is either an existing - table alias or a table name. 'join_cols' is a tuple of tuples containing - columns to join on ((l_id1, r_id1), (l_id2, r_id2)). The join corresponds - to the SQL equivalent of:: + Return an alias for the 'join', either reusing an existing alias for + that join or creating a new one. 'join' is either a + sql.datastructures.BaseTable or Join. - lhs.l_id1 = table.r_id1 AND lhs.l_id2 = table.r_id2 - - The 'reuse' parameter can be either None which means all joins - (matching the connection) are reusable, or it can be a set containing - the aliases that can be reused. + The 'reuse' parameter can be either None which means all joins are + reusable, or it can be a set containing the aliases that can be reused. A join is always created as LOUTER if the lhs alias is LOUTER to make - sure we do not generate chains like t1 LOUTER t2 INNER t3. All new - joins are created as LOUTER if nullable is True. - - If 'nullable' is True, the join can potentially involve NULL values and - is a candidate for promotion (to "left outer") when combining querysets. - - The 'join_field' is the field we are joining along (if any). + sure chains like t1 LOUTER t2 INNER t3 aren't generated. All new + joins are created as LOUTER if the join is nullable. """ reuse = [a for a, j in self.alias_map.items() if (reuse is None or a in reuse) and j == join] From a1c6c220e2ac86a74869d0017cd658115cc4ad7b Mon Sep 17 00:00:00 2001 From: Matthias Kestenholz Date: Sun, 23 Apr 2017 12:55:07 +0200 Subject: [PATCH 078/389] [1.11.x] Fixed #27434 -- Doc'd how to raise a model validation error for a field not in a model form. Backport of e8c056c31a5b353e7b50a405c00db12c28f4a756 from master --- docs/ref/models/instances.txt | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 2ec7a9bd064e..696e09ba57e6 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -322,6 +322,35 @@ pass a dictionary mapping field names to errors:: Finally, ``full_clean()`` will check any unique constraints on your model. +.. admonition:: How to raise field-specific validation errors if those fields don't appear in a ``ModelForm`` + + You can't raise validation errors in ``Model.clean()`` for fields that + don't appear in a model form (a form may limit its fields using + ``Meta.fields`` or ``Meta.exclude``). Doing so will raise a ``ValueError`` + because the validation error won't be able to be associated with the + excluded field. + + To work around this dilemma, instead override :meth:`Model.clean_fields() + ` as it receives the list of fields + that are excluded from validation. For example:: + + class Article(models.Model): + ... + def clean_fields(self, exclude=None): + super(Article, self).clean_fields(exclude=exclude) + if self.status == 'draft' and self.pub_date is not None: + if exclude and 'status' in exclude: + raise ValidationError( + _('Draft entries may not have a publication date.') + ) + else: + raise ValidationError({ + 'status': _( + 'Set status to draft if there is not a ' + 'publication date.' + ), + }) + .. method:: Model.validate_unique(exclude=None) This method is similar to :meth:`~Model.clean_fields`, but validates all From f20168e873d9c55b66aa49aee85d9e4804697afb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 14 Jun 2017 06:11:17 -0400 Subject: [PATCH 079/389] [1.11.x] Fixed #28308 -- Doc'd removal of Select.render_option() (refs #15667). Backport of f2b698631719c6df082a627b6f7ddf2d7f9fa751 from master --- docs/releases/1.11.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index a6955d14604c..953c2325faf9 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -606,6 +606,8 @@ Some undocumented classes in ``django.forms.widgets`` are removed: ``CheckboxFieldRenderer`` * ``ChoiceInput``, ``RadioChoiceInput``, ``CheckboxChoiceInput`` +The undocumented ``Select.render_option()`` method is removed. + The ``Widget.format_output()`` method is removed. Use a custom widget template instead. From 49de4f15413a4f281e835e383a7b1aa202a3dd7e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Wed, 14 Jun 2017 22:58:25 +0200 Subject: [PATCH 080/389] [1.11.x] Refs #24423 -- Readded inadvertently deleted i18n tests. Mistake in 97c1931c4f610e80053430d0297d51e1bed1e7ae. Backport of 357a6428980961b2c5311eb75d16229c7fc0d982 from master --- tests/i18n/tests.py | 65 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 6e2269da4c67..90141ca5f4ea 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -16,23 +16,25 @@ from django.conf.urls.i18n import i18n_patterns from django.template import Context, Template from django.test import ( - RequestFactory, SimpleTestCase, TestCase, override_settings, + RequestFactory, SimpleTestCase, TestCase, ignore_warnings, + override_settings, ) from django.utils import six, translation from django.utils._os import upath +from django.utils.deprecation import RemovedInDjango21Warning from django.utils.formats import ( date_format, get_format, get_format_modules, iter_format_modules, localize, localize_input, reset_format_cache, sanitize_separators, time_format, ) from django.utils.numberformat import format as nformat -from django.utils.safestring import SafeBytes, SafeText +from django.utils.safestring import SafeBytes, SafeString, SafeText, mark_safe from django.utils.six import PY3 from django.utils.translation import ( LANGUAGE_SESSION_KEY, activate, check_for_language, deactivate, - get_language, get_language_from_request, get_language_info, gettext, - gettext_lazy, ngettext_lazy, npgettext, npgettext_lazy, pgettext, - pgettext_lazy, trans_real, ugettext, ugettext_lazy, ungettext, - ungettext_lazy, + get_language, get_language_bidi, get_language_from_request, + get_language_info, gettext, gettext_lazy, ngettext_lazy, npgettext, + npgettext_lazy, pgettext, pgettext_lazy, string_concat, to_locale, + trans_real, ugettext, ugettext_lazy, ungettext, ungettext_lazy, ) from .forms import CompanyForm, I18nForm, SelectDateForm @@ -262,6 +264,57 @@ def test_pgettext(self): self.assertEqual(pgettext("verb", "May"), "Kann") self.assertEqual(npgettext("search", "%d result", "%d results", 4) % 4, "4 Resultate") + @ignore_warnings(category=RemovedInDjango21Warning) + def test_string_concat(self): + self.assertEqual(str(string_concat('dja', 'ngo')), 'django') + + def test_empty_value(self): + """Empty value must stay empty after being translated (#23196).""" + with translation.override('de'): + self.assertEqual('', gettext('')) + s = mark_safe('') + self.assertEqual(s, gettext(s)) + + def test_safe_status(self): + """ + Translating a string requiring no auto-escaping shouldn't change the + "safe" status. + """ + s = mark_safe(str('Password')) + self.assertIs(type(s), SafeString) + with translation.override('de', deactivate=True): + self.assertIs(type(ugettext(s)), SafeText) + self.assertEqual('aPassword', SafeText('a') + s) + self.assertEqual('Passworda', s + SafeText('a')) + self.assertEqual('Passworda', s + mark_safe('a')) + self.assertEqual('aPassword', mark_safe('a') + s) + self.assertEqual('as', mark_safe('a') + mark_safe('s')) + + def test_maclines(self): + """ + Translations on files with Mac or DOS end of lines will be converted + to unix EOF in .po catalogs. + """ + ca_translation = trans_real.translation('ca') + ca_translation._catalog['Mac\nEOF\n'] = 'Catalan Mac\nEOF\n' + ca_translation._catalog['Win\nEOF\n'] = 'Catalan Win\nEOF\n' + with translation.override('ca', deactivate=True): + self.assertEqual('Catalan Mac\nEOF\n', gettext('Mac\rEOF\r')) + self.assertEqual('Catalan Win\nEOF\n', gettext('Win\r\nEOF\r\n')) + + def test_to_locale(self): + self.assertEqual(to_locale('en-us'), 'en_US') + self.assertEqual(to_locale('sr-lat'), 'sr_Lat') + + def test_to_language(self): + self.assertEqual(trans_real.to_language('en_US'), 'en-us') + self.assertEqual(trans_real.to_language('sr_Lat'), 'sr-lat') + + def test_language_bidi(self): + self.assertIs(get_language_bidi(), False) + with translation.override(None): + self.assertIs(get_language_bidi(), False) + class TranslationThreadSafetyTests(SimpleTestCase): From f0ec88fb63f39db38ab027477167f9d7a7ea151c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 17 Jun 2017 08:12:05 -0400 Subject: [PATCH 081/389] [1.11.x] Fixed #28303 -- Prevented localization of attribute values in the DTL attrs.html widget template. Backport of 3b050fd0d0b8dbf499bdb44ce12fa926298c0bd0 from master --- .../templates/django/forms/widgets/attrs.html | 2 +- docs/releases/1.11.3.txt | 4 ++++ .../forms_tests/widget_tests/test_numberinput.py | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/forms_tests/widget_tests/test_numberinput.py diff --git a/django/forms/templates/django/forms/widgets/attrs.html b/django/forms/templates/django/forms/widgets/attrs.html index c8bba9f35c56..7a5592afcb22 100644 --- a/django/forms/templates/django/forms/widgets/attrs.html +++ b/django/forms/templates/django/forms/widgets/attrs.html @@ -1 +1 @@ -{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value }}"{% endif %}{% endif %}{% endfor %} \ No newline at end of file +{% for name, value in widget.attrs.items %}{% if value is not False %} {{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}{% endfor %} \ No newline at end of file diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 5ff33e42e6ec..1e2ddddb66dd 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -40,3 +40,7 @@ Bugfixes context. It's now an empty string (as it is for the original function-based ``login()`` view) if the corresponding parameter isn't sent in a request (in particular, when the login page is accessed directly) (:ticket:`28229`). + +* Prevented attribute values in the ``django/forms/widgets/attrs.html`` + template from being localized so that numeric attributes (e.g. ``max`` and + ``min``) of ``NumberInput`` work correctly (:ticket:`28303`). diff --git a/tests/forms_tests/widget_tests/test_numberinput.py b/tests/forms_tests/widget_tests/test_numberinput.py new file mode 100644 index 000000000000..95a0a9250ae2 --- /dev/null +++ b/tests/forms_tests/widget_tests/test_numberinput.py @@ -0,0 +1,15 @@ +from django.forms.widgets import NumberInput +from django.test import override_settings + +from .base import WidgetTest + + +class NumberInputTests(WidgetTest): + + @override_settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True) + def test_attrs_not_localized(self): + widget = NumberInput(attrs={'max': 12345, 'min': 1234, 'step': 9999}) + self.check_html( + widget, 'name', 'value', + '' + ) From a3b1319d5863600981e71fbaa452d7104715a9e7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 15 Jun 2017 11:05:21 -0400 Subject: [PATCH 082/389] [1.11.x] Fixed #28176 -- Restored the uncasted option value in ChoiceWidget template context. Backport of 221e6e18177516ac4ac95e40c344b93d14dd607b from master --- django/forms/templates/django/forms/widgets/input.html | 2 +- .../templates/django/forms/widgets/select_option.html | 2 +- django/forms/widgets.py | 2 +- docs/releases/1.11.3.txt | 7 +++++++ tests/forms_tests/widget_tests/test_select.py | 6 ++++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/django/forms/templates/django/forms/widgets/input.html b/django/forms/templates/django/forms/widgets/input.html index abbdf6bd26d8..5feef43c553b 100644 --- a/django/forms/templates/django/forms/widgets/input.html +++ b/django/forms/templates/django/forms/widgets/input.html @@ -1 +1 @@ - + diff --git a/django/forms/templates/django/forms/widgets/select_option.html b/django/forms/templates/django/forms/widgets/select_option.html index c6355f69dd5c..8d31961dd336 100644 --- a/django/forms/templates/django/forms/widgets/select_option.html +++ b/django/forms/templates/django/forms/widgets/select_option.html @@ -1 +1 @@ - + diff --git a/django/forms/widgets.py b/django/forms/widgets.py index e84db8d7c07d..c2bc534e5ac0 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -611,7 +611,7 @@ def create_option(self, name, value, label, selected, index, subindex=None, attr option_attrs['id'] = self.id_for_label(option_attrs['id'], index) return { 'name': name, - 'value': force_text(value), + 'value': value, 'label': label, 'selected': selected, 'index': index, diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt index 1e2ddddb66dd..9aab4fbfb761 100644 --- a/docs/releases/1.11.3.txt +++ b/docs/releases/1.11.3.txt @@ -44,3 +44,10 @@ Bugfixes * Prevented attribute values in the ``django/forms/widgets/attrs.html`` template from being localized so that numeric attributes (e.g. ``max`` and ``min``) of ``NumberInput`` work correctly (:ticket:`28303`). + +* Removed casting of the option value to a string in the template context of + the ``CheckboxSelectMultiple``, ``NullBooleanSelect``, ``RadioSelect``, + ``SelectMultiple``, and ``Select`` widgets (:ticket:`28176`). In Django + 1.11.1, casting was added in Python to avoid localization of numeric values + in Django templates, but this made some use cases more difficult. Casting is + now done in the template using the ``|stringformat:'s'`` filter. diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py index d366d29ff3b8..13dc5ea66967 100644 --- a/tests/forms_tests/widget_tests/test_select.py +++ b/tests/forms_tests/widget_tests/test_select.py @@ -351,6 +351,12 @@ def test_optgroups(self): ) self.assertEqual(index, 2) + def test_optgroups_integer_choices(self): + """The option 'value' is the same type as what's in `choices`.""" + groups = list(self.widget(choices=[[0, 'choice text']]).optgroups('name', ['vhs'])) + label, options, index = groups[0] + self.assertEqual(options[0]['value'], 0) + def test_deepcopy(self): """ __deepcopy__() should copy all attributes properly (#25085). From 712ce47e1a55f7ebd96ac64201a4817ae4743216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Sat, 17 Jun 2017 20:17:15 -0700 Subject: [PATCH 083/389] [1.11.x] Fixed #18485 -- Doc'd behavior of PostgreSQL when manually setting AutoField. Backport of 4f1eb64ad0bcebfae4075486855eb6b63355cc5a from master --- docs/ref/databases.txt | 27 +++++++++++++++++++++++++++ docs/ref/models/instances.txt | 3 +++ 2 files changed, 30 insertions(+) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index add34acb6ccc..76ead1024e69 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -224,6 +224,33 @@ live for the duration of the transaction. .. _pgBouncer: https://pgbouncer.github.io/ +.. _manually-specified-autoincrement-pk: + +Manually-specifying values of auto-incrementing primary keys +------------------------------------------------------------ + +Django uses PostgreSQL's `SERIAL data type`_ to store auto-incrementing primary +keys. A ``SERIAL`` column is populated with values from a `sequence`_ that +keeps track of the next available value. Manually assigning a value to an +auto-incrementing field doesn't update the field's sequence, which might later +cause a conflict. For example:: + + >>> from django.contrib.auth.models import User + >>> User.objects.create(username='alice', pk=1) + + >>> # The sequence hasn't been updated; its next value is 1. + >>> User.objects.create(username='bob') + ... + IntegrityError: duplicate key value violates unique constraint + "auth_user_pkey" DETAIL: Key (id)=(1) already exists. + +If you need to specify such values, reset the sequence afterwards to avoid +reusing a value that's already in the table. The :djadmin:`sqlsequencereset` +management command generates the SQL statements to do that. + +.. _SERIAL data type: https://www.postgresql.org/docs/current/static/datatype-numeric.html#DATATYPE-SERIAL +.. _sequence: https://www.postgresql.org/docs/current/static/sql-createsequence.html + Test database templates ----------------------- diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 696e09ba57e6..4a63b016aadc 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -437,6 +437,9 @@ happens. Explicitly specifying auto-primary-key values is mostly useful for bulk-saving objects, when you're confident you won't have primary-key collision. +If you're using PostgreSQL, the sequence associated with the primary key might +need to be updated; see :ref:`manually-specified-autoincrement-pk`. + What happens when you save? --------------------------- From a4c9eada2b4f2365cde99ad7444d8c549f4b3849 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 20 Jun 2017 07:35:55 -0400 Subject: [PATCH 084/389] [1.11.x] Fixed #28327 -- Removed contradictory description of mod_wsgi docs. Backport of 2503ad51549ffa60468b80b9c99d3e54c744be4f from master --- docs/howto/deployment/wsgi/modwsgi.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/howto/deployment/wsgi/modwsgi.txt b/docs/howto/deployment/wsgi/modwsgi.txt index b6e0fd154b26..217a3c770947 100644 --- a/docs/howto/deployment/wsgi/modwsgi.txt +++ b/docs/howto/deployment/wsgi/modwsgi.txt @@ -14,9 +14,9 @@ mod_wsgi. .. _WSGI: http://www.wsgi.org -The `official mod_wsgi documentation`_ is fantastic; it's your source for all -the details about how to use mod_wsgi. You'll probably want to start with the -`installation and configuration documentation`_. +The `official mod_wsgi documentation`_ is your source for all the details about +how to use mod_wsgi. You'll probably want to start with the `installation and +configuration documentation`_. .. _official mod_wsgi documentation: https://modwsgi.readthedocs.io/ .. _installation and configuration documentation: https://modwsgi.readthedocs.io/en/develop/installation.html From 1de0da961e5cc0dcb9b939ccce26fa471f3f1821 Mon Sep 17 00:00:00 2001 From: aruseni Date: Tue, 20 Jun 2017 18:22:26 +0300 Subject: [PATCH 085/389] [1.11.x] Fixed typo in docs/ref/request-response.txt. Backport of ad524980ac9644d5d40c2c79af3c183f4351841e from master --- docs/ref/request-response.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 347f9c15bbf3..9f41243c6fce 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -389,7 +389,7 @@ Methods .. class:: QueryDict In an :class:`HttpRequest` object, the :attr:`~HttpRequest.GET` and -`attr:`~HttpRequest.POST` attributes are instances of ``django.http.QueryDict``, +:attr:`~HttpRequest.POST` attributes are instances of ``django.http.QueryDict``, a dictionary-like class customized to deal with multiple values for the same key. This is necessary because some HTML form elements, notably ``') + # Test fails on Windows due to http://bugs.python.org/issue8304#msg222667 + @skipIf(sys.platform.startswith('win'), 'Fails with UnicodeEncodeError error on Windows.') + def test_non_ascii_format(self): + widget = TimeInput(format='τ-%H:%M') + self.check_html(widget, 'time', time(10, 10), '') + @override_settings(USE_L10N=True) @translation.override('de-at') def test_l10n(self): From 5b450b84e14f42302f58ec1f15a67a368e64d85c Mon Sep 17 00:00:00 2001 From: marton bognar Date: Fri, 7 Jul 2017 00:23:49 +0200 Subject: [PATCH 100/389] [1.11.x] Fixed #28361 -- Fixed possible time-related failure in was_published_recently() tutorial test. Regression in 268a646353c6fa9e5fc3730e13b386ddabb018ef. Backport of 82741605206382d0afd879c4bce174ef3d6e8aea from master --- docs/intro/tutorial05.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index aad693c0dc44..88901cd5c14f 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -285,7 +285,7 @@ more comprehensively: was_published_recently() returns False for questions whose pub_date is older than 1 day. """ - time = timezone.now() - datetime.timedelta(days=1) + time = timezone.now() - datetime.timedelta(days=1, seconds=1) old_question = Question(pub_date=time) self.assertIs(old_question.was_published_recently(), False) From b72298de75952a5f8871f973c3e62f6634588443 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 10 Jul 2017 19:42:58 +0200 Subject: [PATCH 101/389] [1.11.x] Added test for intersection() when combining with a queryset raising EmptyResultSet. Backport of 9bca0d0b38d941fe7f3842cb2259d018823ed25e from master --- tests/queries/test_qs_combinators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index ec341952eac5..235eb4f15ddd 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -49,9 +49,13 @@ def test_union_distinct(self): def test_intersection_with_empty_qs(self): qs1 = Number.objects.all() qs2 = Number.objects.none() + qs3 = Number.objects.filter(pk__in=[]) self.assertEqual(len(qs1.intersection(qs2)), 0) + self.assertEqual(len(qs1.intersection(qs3)), 0) self.assertEqual(len(qs2.intersection(qs1)), 0) + self.assertEqual(len(qs3.intersection(qs1)), 0) self.assertEqual(len(qs2.intersection(qs2)), 0) + self.assertEqual(len(qs3.intersection(qs3)), 0) @skipUnlessDBFeature('supports_select_difference') def test_difference_with_empty_qs(self): From 346b46a274aa8f945aec7d63ea962b41c2c32e40 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 10 Jul 2017 19:45:09 +0200 Subject: [PATCH 102/389] [1.11.x] Fixed #28378 -- Fixed union() and difference() when combining with a queryset raising EmptyResultSet. Thanks Jon Dufresne for the report. Thanks Tim Graham and Simon Charette for the reviews. Backport of ca74e563500e291480f1976b58fcd34aac768dca from master --- django/db/models/sql/compiler.py | 28 ++++++++++++++++++---------- docs/releases/1.11.4.txt | 3 +++ tests/queries/test_qs_combinators.py | 8 ++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index d1373fcf9552..e96cbee71fe9 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -387,7 +387,18 @@ def get_combinator_sql(self, combinator, all): raise DatabaseError('LIMIT/OFFSET not allowed in subqueries of compound statements.') if compiler.get_order_by(): raise DatabaseError('ORDER BY not allowed in subqueries of compound statements.') - parts = (compiler.as_sql() for compiler in compilers) + parts = () + for compiler in compilers: + try: + parts += (compiler.as_sql(),) + except EmptyResultSet: + # Omit the empty queryset with UNION and with DIFFERENCE if the + # first queryset is nonempty. + if combinator == 'union' or (combinator == 'difference' and parts): + continue + raise + if not parts: + return [], [] combinator_sql = self.connection.ops.set_operators[combinator] if all and combinator == 'union': combinator_sql += ' ALL' @@ -410,16 +421,7 @@ def as_sql(self, with_limits=True, with_col_aliases=False): refcounts_before = self.query.alias_refcount.copy() try: extra_select, order_by, group_by = self.pre_sql_setup() - distinct_fields = self.get_distinct() - - # This must come after 'select', 'ordering', and 'distinct' -- see - # docstring of get_from_clause() for details. - from_, f_params = self.get_from_clause() - for_update_part = None - where, w_params = self.compile(self.where) if self.where is not None else ("", []) - having, h_params = self.compile(self.having) if self.having is not None else ("", []) - combinator = self.query.combinator features = self.connection.features if combinator: @@ -427,6 +429,12 @@ def as_sql(self, with_limits=True, with_col_aliases=False): raise DatabaseError('{} not supported on this database backend.'.format(combinator)) result, params = self.get_combinator_sql(combinator, self.query.combinator_all) else: + distinct_fields = self.get_distinct() + # This must come after 'select', 'ordering', and 'distinct' + # (see docstring of get_from_clause() for details). + from_, f_params = self.get_from_clause() + where, w_params = self.compile(self.where) if self.where is not None else ("", []) + having, h_params = self.compile(self.having) if self.having is not None else ("", []) result = ['SELECT'] params = [] diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 28f06deb063f..2383ad6a1769 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -12,3 +12,6 @@ Bugfixes * Fixed a regression in 1.11.3 on Python 2 where non-ASCII ``format`` values for date/time widgets results in an empty ``value`` in the widget's HTML (:ticket:`28355`). + +* Fixed ``QuerySet.union()`` and ``difference()`` when combining with + a queryset raising ``EmptyResultSet`` (:ticket:`28378`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 235eb4f15ddd..2374bb2868ed 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -61,18 +61,26 @@ def test_intersection_with_empty_qs(self): def test_difference_with_empty_qs(self): qs1 = Number.objects.all() qs2 = Number.objects.none() + qs3 = Number.objects.filter(pk__in=[]) self.assertEqual(len(qs1.difference(qs2)), 10) + self.assertEqual(len(qs1.difference(qs3)), 10) self.assertEqual(len(qs2.difference(qs1)), 0) + self.assertEqual(len(qs3.difference(qs1)), 0) self.assertEqual(len(qs2.difference(qs2)), 0) + self.assertEqual(len(qs3.difference(qs3)), 0) def test_union_with_empty_qs(self): qs1 = Number.objects.all() qs2 = Number.objects.none() + qs3 = Number.objects.filter(pk__in=[]) self.assertEqual(len(qs1.union(qs2)), 10) self.assertEqual(len(qs2.union(qs1)), 10) + self.assertEqual(len(qs1.union(qs3)), 10) + self.assertEqual(len(qs3.union(qs1)), 10) self.assertEqual(len(qs2.union(qs1, qs1, qs1)), 10) self.assertEqual(len(qs2.union(qs1, qs1, all=True)), 20) self.assertEqual(len(qs2.union(qs2)), 0) + self.assertEqual(len(qs3.union(qs3)), 0) def test_union_bad_kwarg(self): qs1 = Number.objects.all() From b2ebc47a22412ca8091c341b0435abc5fcc5b76e Mon Sep 17 00:00:00 2001 From: Tom Date: Tue, 11 Jul 2017 08:15:13 -0400 Subject: [PATCH 103/389] [1.11.x] Updated name of topics/db/queries link on index. Backport of 719b370b34ee0b9b4c115da1ce73d1748cd48dd7 from master --- docs/index.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.txt b/docs/index.txt index ef9add53a75a..4df5f4bef9f2 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -87,7 +87,7 @@ manipulating the data of your Web application. Learn more about it below: :doc:`Model class ` * **QuerySets:** - :doc:`Executing queries ` | + :doc:`Making queries ` | :doc:`QuerySet method reference ` | :doc:`Lookup expressions ` From fe7b4568255ae1f77c180404631d81ba6b2d996d Mon Sep 17 00:00:00 2001 From: Irindu Indeera Date: Tue, 11 Jul 2017 23:45:17 +0530 Subject: [PATCH 104/389] [1.11.x] Fixed #28352 -- Corrected QuerySet.values_list() return type in docs examples. Backport of babe9e64a6172b09e7f70e8d8f01e67f2cb4176d and 2457c1866ef3586c56bd19aeaa554e78c1ed1875 from master --- docs/ref/models/conditional-expressions.txt | 8 ++++---- docs/ref/models/querysets.txt | 14 +++++++------- docs/topics/db/models.txt | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/docs/ref/models/conditional-expressions.txt b/docs/ref/models/conditional-expressions.txt index 4331d1185ec8..af134d456124 100644 --- a/docs/ref/models/conditional-expressions.txt +++ b/docs/ref/models/conditional-expressions.txt @@ -110,7 +110,7 @@ A simple example:: ... output_field=CharField(), ... ), ... ).values_list('name', 'discount') - [('Jane Doe', '0%'), ('James Smith', '5%'), ('Jack Black', '10%')] + ``Case()`` accepts any number of ``When()`` objects as individual arguments. Other options are provided using keyword arguments. If none of the conditions @@ -132,7 +132,7 @@ the ``Client`` has been with us, we could do so using lookups:: ... output_field=CharField(), ... ) ... ).values_list('name', 'discount') - [('Jane Doe', '5%'), ('James Smith', '0%'), ('Jack Black', '10%')] + .. note:: @@ -153,7 +153,7 @@ registered more than a year ago:: ... When(account_type=Client.PLATINUM, then=a_year_ago), ... ), ... ).values_list('name', 'account_type') - [('Jack Black', 'P')] + Advanced queries ================ @@ -182,7 +182,7 @@ their registration dates. We can do this using a conditional expression and the ... ), ... ) >>> Client.objects.values_list('name', 'account_type') - [('Jane Doe', 'G'), ('James Smith', 'R'), ('Jack Black', 'P')] + Conditional aggregation ----------------------- diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 53e98f9ba6b6..951d91098c48 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -635,20 +635,20 @@ respective field or expression passed into the ``values_list()`` call — so the first item is the first field, etc. For example:: >>> Entry.objects.values_list('id', 'headline') - [(1, 'First entry'), ...] + >>> from django.db.models.functions import Lower >>> Entry.objects.values_list('id', Lower('headline')) - [(1, 'first entry'), ...] + If you only pass in a single field, you can also pass in the ``flat`` parameter. If ``True``, this will mean the returned results are single values, rather than one-tuples. An example should make the difference clearer:: >>> Entry.objects.values_list('id').order_by('id') - [(1,), (2,), (3,), ...] + >>> Entry.objects.values_list('id', flat=True).order_by('id') - [1, 2, 3, ...] + It is an error to pass in ``flat`` when there is more than one field. @@ -671,10 +671,10 @@ For example, notice the behavior when querying across a :class:`~django.db.models.ManyToManyField`:: >>> Author.objects.values_list('name', 'entry__headline') - [('Noam Chomsky', 'Impressions of Gaza'), + Authors with multiple entries appear multiple times and authors without any entries have ``None`` for the entry headline. @@ -683,7 +683,7 @@ Similarly, when querying a reverse foreign key, ``None`` appears for entries not having any author:: >>> Entry.objects.values_list('authors') - [('Noam Chomsky',), ('George Orwell',), (None,)] + .. versionchanged:: 1.11 diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index ec725b77c238..6ee07f712a5c 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -229,7 +229,7 @@ ones: >>> 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. From 30f334cc58e939c7d9bd8455c80bd066fbde9f2b Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Wed, 12 Jul 2017 15:18:49 +0500 Subject: [PATCH 105/389] [1.11.x] Fixed #28389 -- Fixed pickling of LazyObject on Python 2 when wrapped object doesn't have __reduce__(). Partial revert of 35355a4ffedb2aeed52d5fe3034380ffc6a438db. --- django/utils/functional.py | 13 +++++++------ docs/releases/1.11.4.txt | 3 +++ tests/utils_tests/test_lazyobject.py | 2 ++ 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/django/utils/functional.py b/django/utils/functional.py index 7d5b7feea550..4ea5fe57455e 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -300,13 +300,14 @@ def __reduce__(self): self._setup() return (unpickle_lazyobject, (self._wrapped,)) + # Overriding __class__ stops __reduce__ from being called on Python 2. + # So, define __getstate__ in a way that cooperates with the way that + # pickle interprets this class. This fails when the wrapped class is a + # builtin, but it's better than nothing. def __getstate__(self): - """ - Prevent older versions of pickle from trying to pickle the __dict__ - (which in the case of a SimpleLazyObject may contain a lambda). The - value will be ignored by __reduce__() and the custom unpickler. - """ - return {} + if self._wrapped is empty: + self._setup() + return self._wrapped.__dict__ def __copy__(self): if self._wrapped is empty: diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 2383ad6a1769..2cd093335e84 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -15,3 +15,6 @@ Bugfixes * Fixed ``QuerySet.union()`` and ``difference()`` when combining with a queryset raising ``EmptyResultSet`` (:ticket:`28378`). + +* Fixed a regression in pickling of ``LazyObject`` on Python 2 when the wrapped + object doesn't have ``__reduce__()`` (:ticket:`28389`). diff --git a/tests/utils_tests/test_lazyobject.py b/tests/utils_tests/test_lazyobject.py index 9dc225b55f8a..c2a9855abf7c 100644 --- a/tests/utils_tests/test_lazyobject.py +++ b/tests/utils_tests/test_lazyobject.py @@ -187,11 +187,13 @@ def __iter__(self): def test_pickle(self): # See ticket #16563 obj = self.lazy_wrap(Foo()) + obj.bar = 'baz' pickled = pickle.dumps(obj) unpickled = pickle.loads(pickled) self.assertIsInstance(unpickled, Foo) self.assertEqual(unpickled, obj) self.assertEqual(unpickled.foo, obj.foo) + self.assertEqual(unpickled.bar, obj.bar) # Test copying lazy objects wrapping both builtin types and user-defined # classes since a lot of the relevant code does __dict__ manipulation and From fc6b90bdb7a9531e988245942f79518308616b7b Mon Sep 17 00:00:00 2001 From: Mark Rogaski Date: Wed, 12 Jul 2017 00:59:46 -0400 Subject: [PATCH 106/389] [1.11.x] Fixed #28174 -- Fixed crash in runserver's autoreload with Python 2 on Windows with non-str environment variables. --- django/utils/autoreload.py | 9 +++++++++ docs/releases/1.11.4.txt | 3 +++ tests/utils_tests/test_autoreload.py | 17 +++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index e7c9acbaeae1..cf2cefeea9c6 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -40,6 +40,7 @@ from django.core.signals import request_finished from django.utils import six from django.utils._os import npath +from django.utils.encoding import force_bytes, get_system_encoding from django.utils.six.moves import _thread as thread # This import does nothing, but it's necessary to avoid some race conditions @@ -285,6 +286,14 @@ def restart_with_reloader(): while True: args = [sys.executable] + ['-W%s' % o for o in sys.warnoptions] + sys.argv new_environ = os.environ.copy() + if _win and six.PY2: + # Environment variables on Python 2 + Windows must be str. + encoding = get_system_encoding() + for key in new_environ.keys(): + str_key = force_bytes(key, encoding=encoding) + str_value = force_bytes(new_environ[key], encoding=encoding) + del new_environ[key] + new_environ[str_key] = str_value new_environ["RUN_MAIN"] = 'true' exit_code = subprocess.call(args, env=new_environ) if exit_code != 3: diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 2cd093335e84..9448611eccdd 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -18,3 +18,6 @@ Bugfixes * Fixed a regression in pickling of ``LazyObject`` on Python 2 when the wrapped object doesn't have ``__reduce__()`` (:ticket:`28389`). + +* Fixed crash in ``runserver``'s ``autoreload`` with Python 2 on Windows with + non-``str`` environment variables (:ticket:`28174`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 5d42af62c8f4..3fae2e4da2de 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -1,6 +1,9 @@ +from __future__ import unicode_literals + import gettext import os import shutil +import sys import tempfile from importlib import import_module @@ -251,3 +254,17 @@ def test_resets_trans_real(self): self.assertEqual(trans_real._translations, {}) self.assertIsNone(trans_real._default) self.assertIsInstance(trans_real._active, _thread._local) + + +class TestRestartWithReloader(SimpleTestCase): + + def test_environment(self): + """" + With Python 2 on Windows, restart_with_reloader() coerces environment + variables to str to avoid "TypeError: environment can only contain + strings" in Python's subprocess.py. + """ + # With unicode_literals, these values are unicode. + os.environ['SPAM'] = 'spam' + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() From a3b5df8ed503ea559d2ffaca7ec0c735d98f1a38 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Thu, 13 Jul 2017 20:25:32 +0530 Subject: [PATCH 107/389] [1.11.x] Fixed #28387 -- Fixed has_changed() for disabled form fields that subclass it. Backport of 5debbdfcc84266703191e084914998e38f5f52eb from master --- django/forms/fields.py | 8 ++++++++ django/forms/models.py | 4 ++++ docs/releases/1.11.4.txt | 4 ++++ tests/forms_tests/field_tests/test_booleanfield.py | 4 ++++ tests/forms_tests/field_tests/test_filefield.py | 4 ++++ tests/forms_tests/field_tests/test_multiplechoicefield.py | 4 ++++ tests/forms_tests/field_tests/test_multivaluefield.py | 4 ++++ tests/model_forms/tests.py | 8 ++++++++ 8 files changed, 40 insertions(+) diff --git a/django/forms/fields.py b/django/forms/fields.py index 33ed2882a317..f2b5b77185dd 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -604,6 +604,8 @@ def bound_data(self, data, initial): return data def has_changed(self, initial, data): + if self.disabled: + return False if data is None: return False return True @@ -724,6 +726,8 @@ def validate(self, value): raise ValidationError(self.error_messages['required'], code='required') def has_changed(self, initial, data): + if self.disabled: + return False # Sometimes data or initial may be a string equivalent of a boolean # so we should run it through to_python first to get a boolean value return self.to_python(initial) != self.to_python(data) @@ -891,6 +895,8 @@ def validate(self, value): ) def has_changed(self, initial, data): + if self.disabled: + return False if initial is None: initial = [] if data is None: @@ -1069,6 +1075,8 @@ def compress(self, data_list): raise NotImplementedError('Subclasses must implement this method.') def has_changed(self, initial, data): + if self.disabled: + return False if initial is None: initial = ['' for x in range(0, len(data))] else: diff --git a/django/forms/models.py b/django/forms/models.py index 132e4255bb51..db710a2ed287 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -1244,6 +1244,8 @@ def validate(self, value): return Field.validate(self, value) def has_changed(self, initial, data): + if self.disabled: + return False initial_value = initial if initial is not None else '' data_value = data if data is not None else '' return force_text(self.prepare_value(initial_value)) != force_text(data_value) @@ -1331,6 +1333,8 @@ def prepare_value(self, value): return super(ModelMultipleChoiceField, self).prepare_value(value) def has_changed(self, initial, data): + if self.disabled: + return False if initial is None: initial = [] if data is None: diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 9448611eccdd..c64f5c9c6f23 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -21,3 +21,7 @@ Bugfixes * Fixed crash in ``runserver``'s ``autoreload`` with Python 2 on Windows with non-``str`` environment variables (:ticket:`28174`). + +* Corrected ``Field.has_changed()`` to return ``False`` for disabled form + fields: ``BooleanField``, ``MultipleChoiceField``, ``MultiValueField``, + ``FileField``, ``ModelChoiceField``, and ``ModelMultipleChoiceField``. diff --git a/tests/forms_tests/field_tests/test_booleanfield.py b/tests/forms_tests/field_tests/test_booleanfield.py index e267777b9438..d15967c85c3c 100644 --- a/tests/forms_tests/field_tests/test_booleanfield.py +++ b/tests/forms_tests/field_tests/test_booleanfield.py @@ -59,3 +59,7 @@ def test_booleanfield_changed(self): self.assertFalse(f.has_changed(True, 'True')) self.assertTrue(f.has_changed(False, 'True')) self.assertTrue(f.has_changed(True, 'False')) + + def test_disabled_has_changed(self): + f = BooleanField(disabled=True) + self.assertIs(f.has_changed('True', 'False'), False) diff --git a/tests/forms_tests/field_tests/test_filefield.py b/tests/forms_tests/field_tests/test_filefield.py index 2c08075f3f30..59c91005d63e 100644 --- a/tests/forms_tests/field_tests/test_filefield.py +++ b/tests/forms_tests/field_tests/test_filefield.py @@ -77,5 +77,9 @@ def test_filefield_changed(self): # with here) self.assertTrue(f.has_changed('resume.txt', {'filename': 'resume.txt', 'content': 'My resume'})) + def test_disabled_has_changed(self): + f = FileField(disabled=True) + self.assertIs(f.has_changed('x', 'y'), False) + def test_file_picklable(self): self.assertIsInstance(pickle.loads(pickle.dumps(FileField())), FileField) diff --git a/tests/forms_tests/field_tests/test_multiplechoicefield.py b/tests/forms_tests/field_tests/test_multiplechoicefield.py index 85b70498546d..21866b89d144 100644 --- a/tests/forms_tests/field_tests/test_multiplechoicefield.py +++ b/tests/forms_tests/field_tests/test_multiplechoicefield.py @@ -70,3 +70,7 @@ def test_multiplechoicefield_changed(self): self.assertFalse(f.has_changed([2, 1], ['1', '2'])) self.assertTrue(f.has_changed([1, 2], ['1'])) self.assertTrue(f.has_changed([1, 2], ['1', '3'])) + + def test_disabled_has_changed(self): + f = MultipleChoiceField(choices=[('1', 'One'), ('2', 'Two')], disabled=True) + self.assertIs(f.has_changed('x', 'y'), False) diff --git a/tests/forms_tests/field_tests/test_multivaluefield.py b/tests/forms_tests/field_tests/test_multivaluefield.py index 3d1716861897..d5669a358a69 100644 --- a/tests/forms_tests/field_tests/test_multivaluefield.py +++ b/tests/forms_tests/field_tests/test_multivaluefield.py @@ -103,6 +103,10 @@ def test_has_changed_last_widget(self): ['some text', ['J', 'P'], ['2009-04-25', '11:44:00']], )) + def test_disabled_has_changed(self): + f = MultiValueField(fields=(CharField(), CharField()), disabled=True) + self.assertIs(f.has_changed(['x', 'x'], ['y', 'y']), False) + def test_form_as_table(self): form = ComplexFieldForm() self.assertHTMLEqual( diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index f33278f88a32..f3317f59de7a 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -1708,6 +1708,10 @@ class Meta: ['Select a valid choice. That choice is not one of the available choices.'] ) + def test_disabled_modelchoicefield_has_changed(self): + field = forms.ModelChoiceField(Author.objects.all(), disabled=True) + self.assertIs(field.has_changed('x', 'y'), False) + def test_disabled_multiplemodelchoicefield(self): class ArticleForm(forms.ModelForm): categories = forms.ModelMultipleChoiceField(Category.objects.all(), required=False) @@ -1733,6 +1737,10 @@ class Meta: self.assertEqual(form.errors, {}) self.assertEqual([x.pk for x in form.cleaned_data['categories']], [category1.pk]) + def test_disabled_modelmultiplechoicefield_has_changed(self): + field = forms.ModelMultipleChoiceField(Author.objects.all(), disabled=True) + self.assertIs(field.has_changed('x', 'y'), False) + def test_modelchoicefield_iterator(self): """ Iterator defaults to ModelChoiceIterator and can be overridden with From d9ef8ffb5854c9a5fd81f18b3e383accafd6d7ff Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 15 Jul 2017 08:34:16 -0400 Subject: [PATCH 108/389] [1.11.x] Refs #28174 -- Fixed autoreload test crash on Python 2/non-ASCII path. --- tests/utils_tests/test_autoreload.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 3fae2e4da2de..78206135faaf 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -12,11 +12,11 @@ from django.test import SimpleTestCase, mock, override_settings from django.test.utils import extend_sys_path from django.utils import autoreload -from django.utils._os import npath +from django.utils._os import npath, upath from django.utils.six.moves import _thread from django.utils.translation import trans_real -LOCALE_PATH = os.path.join(os.path.dirname(__file__), 'locale') +LOCALE_PATH = os.path.join(os.path.dirname(upath(__file__)), 'locale') class TestFilenameGenerator(SimpleTestCase): @@ -50,7 +50,7 @@ def test_django_locales(self): """ gen_filenames() yields the built-in Django locale files. """ - django_dir = os.path.join(os.path.dirname(conf.__file__), 'locale') + django_dir = os.path.join(os.path.dirname(upath(conf.__file__)), 'locale') django_mo = os.path.join(django_dir, 'nl', 'LC_MESSAGES', 'django.mo') self.assertFileFound(django_mo) @@ -69,7 +69,7 @@ def test_project_root_locale(self): """ old_cwd = os.getcwd() os.chdir(os.path.dirname(__file__)) - current_dir = os.path.join(os.path.dirname(__file__), 'locale') + current_dir = os.path.join(os.path.dirname(upath(__file__)), 'locale') current_dir_mo = os.path.join(current_dir, 'nl', 'LC_MESSAGES', 'django.mo') try: self.assertFileFound(current_dir_mo) @@ -81,7 +81,7 @@ def test_app_locales(self): """ gen_filenames() also yields from locale dirs in installed apps. """ - admin_dir = os.path.join(os.path.dirname(admin.__file__), 'locale') + admin_dir = os.path.join(os.path.dirname(upath(admin.__file__)), 'locale') admin_mo = os.path.join(admin_dir, 'nl', 'LC_MESSAGES', 'django.mo') self.assertFileFound(admin_mo) @@ -91,7 +91,7 @@ def test_no_i18n(self): If i18n machinery is disabled, there is no need for watching the locale files. """ - django_dir = os.path.join(os.path.dirname(conf.__file__), 'locale') + django_dir = os.path.join(os.path.dirname(upath(conf.__file__)), 'locale') django_mo = os.path.join(django_dir, 'nl', 'LC_MESSAGES', 'django.mo') self.assertFileNotFound(django_mo) From 9350f77c69a5cef3d7a9b8078ab33ff43335a112 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 14 Jul 2017 18:11:29 +0200 Subject: [PATCH 109/389] [1.11.x] Fixed #28399 -- Fixed QuerySet.count() for union(), difference(), and intersection() queries. Backport of adab280cefb15659c39558ac26ea392b0a1e456c from master --- django/db/models/sql/compiler.py | 2 +- django/db/models/sql/query.py | 10 +++++----- docs/ref/models/querysets.txt | 14 +++++++++----- docs/releases/1.11.4.txt | 3 +++ tests/queries/test_qs_combinators.py | 21 +++++++++++++++++++++ 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index e96cbee71fe9..6ec2284a91d0 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -398,7 +398,7 @@ def get_combinator_sql(self, combinator, all): continue raise if not parts: - return [], [] + raise EmptyResultSet combinator_sql = self.connection.ops.set_operators[combinator] if all and combinator == 'union': combinator_sql += ' ALL' diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index e2d273994e1c..d1c4e5446ba3 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -416,12 +416,12 @@ def get_aggregation(self, using, added_aggregate_names): # aren't smart enough to remove the existing annotations from the # query, so those would force us to use GROUP BY. # - # If the query has limit or distinct, then those operations must be - # done in a subquery so that we are aggregating on the limit and/or - # distinct results instead of applying the distinct and limit after the - # aggregation. + # If the query has limit or distinct, or uses set operations, then + # those operations must be done in a subquery so that the query + # aggregates on the limit and/or distinct results instead of applying + # the distinct and limit after the aggregation. if (isinstance(self.group_by, list) or has_limit or has_existing_annotations or - self.distinct): + self.distinct or self.combinator): from django.db.models.sql.subqueries import AggregateQuery outer_query = AggregateQuery(self.model) inner_query = self.clone() diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 951d91098c48..082861f61b7d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -822,11 +822,15 @@ of other models. Passing different models works as long as the ``SELECT`` list is the same in all ``QuerySet``\s (at least the types, the names don't matter as long as the types in the same order). -In addition, only ``LIMIT``, ``OFFSET``, and ``ORDER BY`` (i.e. slicing and -:meth:`order_by`) are allowed on the resulting ``QuerySet``. Further, databases -place restrictions on what operations are allowed in the combined queries. For -example, most databases don't allow ``LIMIT`` or ``OFFSET`` in the combined -queries. +In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, and ``ORDER BY`` (i.e. +slicing, :meth:`count`, and :meth:`order_by`) are allowed on the resulting +``QuerySet``. Further, databases place restrictions on what operations are +allowed in the combined queries. For example, most databases don't allow +``LIMIT`` or ``OFFSET`` in the combined queries. + +.. versionchanged:: 1.11.4 + + ``COUNT(*)`` support was added. ``intersection()`` ~~~~~~~~~~~~~~~~~~ diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index c64f5c9c6f23..8952ce1c4bd5 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -25,3 +25,6 @@ Bugfixes * Corrected ``Field.has_changed()`` to return ``False`` for disabled form fields: ``BooleanField``, ``MultipleChoiceField``, ``MultiValueField``, ``FileField``, ``ModelChoiceField``, and ``ModelMultipleChoiceField``. + +* Fixed ``QuerySet.count()`` for ``union()``, ``difference()``, and + ``intersection()`` queries. (:ticket:`28399`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 2374bb2868ed..ef8c188c0596 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -98,6 +98,27 @@ def test_ordering(self): qs2 = Number.objects.filter(num__gte=2, num__lte=3) self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0]) + def test_count_union(self): + qs1 = Number.objects.filter(num__lte=1).values('num') + qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') + self.assertEqual(qs1.union(qs2).count(), 4) + + def test_count_union_empty_result(self): + qs = Number.objects.filter(pk__in=[]) + self.assertEqual(qs.union(qs).count(), 0) + + @skipUnlessDBFeature('supports_select_difference') + def test_count_difference(self): + qs1 = Number.objects.filter(num__lt=10) + qs2 = Number.objects.filter(num__lt=9) + self.assertEqual(qs1.difference(qs2).count(), 1) + + @skipUnlessDBFeature('supports_select_intersection') + def test_count_intersection(self): + qs1 = Number.objects.filter(num__gte=5) + qs2 = Number.objects.filter(num__lte=5) + self.assertEqual(qs1.intersection(qs2).count(), 1) + @skipUnlessDBFeature('supports_slicing_ordering_in_compound') def test_ordering_subqueries(self): qs1 = Number.objects.order_by('num')[:2] From 308945957cf0ae50f4ea33e42ab63a709afcc81c Mon Sep 17 00:00:00 2001 From: jmk Date: Mon, 17 Jul 2017 17:26:11 +0100 Subject: [PATCH 110/389] [1.11.x] Fixed 403 link in docs/ref/contrib/gis/install/spatialite.txt. Backport of 841b4648839ce803b7cd5ca8d689fd488293efbd from master --- docs/ref/contrib/gis/install/spatialite.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt index 21208ebfb4cb..306c2444833f 100644 --- a/docs/ref/contrib/gis/install/spatialite.txt +++ b/docs/ref/contrib/gis/install/spatialite.txt @@ -86,7 +86,7 @@ Get the latest SpatiaLite library source bundle from the $ ./configure --target=macosx -__ https://www.gaia-gis.it/gaia-sins/libspatialite-sources/ +__ http://www.gaia-gis.it/gaia-sins/libspatialite-sources/ .. _spatialite_macos: From a5e91ab1fbf6badab51e04dd445d234bc11ddd0a Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Tue, 20 Jun 2017 19:02:43 +0100 Subject: [PATCH 111/389] [1.11.x] Doc'd the need to remove default ordering on Subquery aggregates. Backport of 62917cee5ac75693aa5d9a3de5d8935da2f011df from master --- docs/ref/models/expressions.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index 731a28093925..dd38902479c6 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -608,15 +608,16 @@ Assuming both models have a ``length`` field, to find posts where the post length is greater than the total length of all combined comments:: >>> from django.db.models import OuterRef, Subquery, Sum - >>> comments = Comment.objects.filter(post=OuterRef('pk')).values('post') + >>> comments = Comment.objects.filter(post=OuterRef('pk')).order_by().values('post') >>> total_comments = comments.annotate(total=Sum('length')).values('total') >>> Post.objects.filter(length__gt=Subquery(total_comments)) The initial ``filter(...)`` limits the subquery to the relevant parameters. -``values('post')`` aggregates comments by ``Post``. Finally, ``annotate(...)`` -performs the aggregation. The order in which these queryset methods are applied -is important. In this case, since the subquery must be limited to a single -column, ``values('total')`` is required. +``order_by()`` removes the default :attr:`~django.db.models.Options.ordering` +(if any) on the ``Comment`` model. ``values('post')`` aggregates comments by +``Post``. Finally, ``annotate(...)`` performs the aggregation. The order in +which these queryset methods are applied is important. In this case, since the +subquery must be limited to a single column, ``values('total')`` is required. This is the only way to perform an aggregation within a ``Subquery``, as using :meth:`~.QuerySet.aggregate` attempts to evaluate the queryset (and if From 99d5059d766a18f80bb24a44023f9e653a08c803 Mon Sep 17 00:00:00 2001 From: Roman Selivanov Date: Wed, 19 Jul 2017 18:24:27 +0300 Subject: [PATCH 112/389] [1.11.x] Fixed #28414 -- Fixed ClearableFileInput rendering as a subwidget of MultiWidget. Backport of d4da39685b5974849c73e4c4dc6e07dfdf21c67a from master --- .../admin/widgets/clearable_file_input.html | 10 +++++----- .../django/forms/widgets/clearable_file_input.html | 8 ++++---- .../django/forms/widgets/clearable_file_input.html | 8 ++++---- django/forms/widgets.py | 2 +- docs/releases/1.11.4.txt | 7 +++++++ docs/spelling_wordlist | 1 + .../widget_tests/test_clearablefileinput.py | 14 +++++++++++++- 7 files changed, 35 insertions(+), 15 deletions(-) diff --git a/django/contrib/admin/templates/admin/widgets/clearable_file_input.html b/django/contrib/admin/templates/admin/widgets/clearable_file_input.html index 9fee235819b6..71491fca451d 100644 --- a/django/contrib/admin/templates/admin/widgets/clearable_file_input.html +++ b/django/contrib/admin/templates/admin/widgets/clearable_file_input.html @@ -1,6 +1,6 @@ -{% if is_initial %}

{{ initial_text }}: {{ widget.value }}{% if not widget.required %} +{% if widget.is_initial %}

{{ widget.initial_text }}: {{ widget.value }}{% if not widget.required %} - -{% endif %}
-{{ input_text }}:{% endif %} -{% if is_initial %}

{% endif %} + +{% endif %}
+{{ widget.input_text }}:{% endif %} +{% if widget.is_initial %}

{% endif %} diff --git a/django/forms/jinja2/django/forms/widgets/clearable_file_input.html b/django/forms/jinja2/django/forms/widgets/clearable_file_input.html index 05f2c2dbe5d6..7248f32d2a67 100644 --- a/django/forms/jinja2/django/forms/widgets/clearable_file_input.html +++ b/django/forms/jinja2/django/forms/widgets/clearable_file_input.html @@ -1,5 +1,5 @@ -{% if is_initial %}{{ initial_text }}: {{ widget.value }}{% if not widget.required %} - -{% endif %}
-{{ input_text }}:{% endif %} +{% if widget.is_initial %}{{ widget.initial_text }}: {{ widget.value }}{% if not widget.required %} + +{% endif %}
+{{ widget.input_text }}:{% endif %} diff --git a/django/forms/templates/django/forms/widgets/clearable_file_input.html b/django/forms/templates/django/forms/widgets/clearable_file_input.html index 05f2c2dbe5d6..7248f32d2a67 100644 --- a/django/forms/templates/django/forms/widgets/clearable_file_input.html +++ b/django/forms/templates/django/forms/widgets/clearable_file_input.html @@ -1,5 +1,5 @@ -{% if is_initial %}{{ initial_text }}: {{ widget.value }}{% if not widget.required %} - -{% endif %}
-{{ input_text }}:{% endif %} +{% if widget.is_initial %}{{ widget.initial_text }}: {{ widget.value }}{% if not widget.required %} + +{% endif %}
+{{ widget.input_text }}:{% endif %} diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 67108e450b0c..d93fc1dbdc80 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -409,7 +409,7 @@ def get_context(self, name, value, attrs): context = super(ClearableFileInput, self).get_context(name, value, attrs) checkbox_name = self.clear_checkbox_name(name) checkbox_id = self.clear_checkbox_id(checkbox_name) - context.update({ + context['widget'].update({ 'checkbox_name': checkbox_name, 'checkbox_id': checkbox_id, 'is_initial': self.is_initial(value), diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 8952ce1c4bd5..180d758246be 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -28,3 +28,10 @@ Bugfixes * Fixed ``QuerySet.count()`` for ``union()``, ``difference()``, and ``intersection()`` queries. (:ticket:`28399`). + +* Fixed ``ClearableFileInput`` rendering as a subwidget of ``MultiWidget`` + (:ticket:`28414`). Custom ``clearable_file_input.html`` widget templates + will need to adapt for the fact that context values + ``checkbox_name``, ``checkbox_id``, ``is_initial``, ``input_text``, + ``initial_text``, and ``clear_checkbox_label`` are now attributes of + ``widget`` rather than appearing in the top-level context. diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index f23091f4fbd5..61ef218f8850 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -804,6 +804,7 @@ subtransactions subtree subtype subviews +subwidget subwidgets superclass superset diff --git a/tests/forms_tests/widget_tests/test_clearablefileinput.py b/tests/forms_tests/widget_tests/test_clearablefileinput.py index 1e52f0f62e0c..8a075569c036 100644 --- a/tests/forms_tests/widget_tests/test_clearablefileinput.py +++ b/tests/forms_tests/widget_tests/test_clearablefileinput.py @@ -1,5 +1,5 @@ from django.core.files.uploadedfile import SimpleUploadedFile -from django.forms import ClearableFileInput +from django.forms import ClearableFileInput, MultiWidget from django.utils.encoding import python_2_unicode_compatible from .base import WidgetTest @@ -77,6 +77,18 @@ def test_clear_input_renders_only_if_initial(self): """ self.check_html(self.widget, 'myfile', None, html='') + def test_render_as_subwidget(self): + """A ClearableFileInput as a subwidget of MultiWidget.""" + widget = MultiWidget(widgets=(self.widget,)) + self.check_html(widget, 'myfile', [FakeFieldFile()], html=( + """ + Currently: something + +
+ Change: + """ + )) + def test_clear_input_checked_returns_false(self): """ ClearableFileInput.value_from_datadict returns False if the clear From b0304428d682716db8b18e4ba452e2521f462e4a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 21 Jul 2017 18:09:48 -0400 Subject: [PATCH 113/389] [1.11.x] Refs #17453 -- Fixed broken link to #django IRC logs. Backport of c6986a4ebff8db923f906ce84cf118efc82ec79a from master --- README.rst | 2 +- docs/index.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 3afba227fbe8..20913f4e132e 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ ticket here: https://code.djangoproject.com/newticket To get more help: * Join the ``#django`` channel on irc.freenode.net. Lots of helpful people hang out - there. Read the archives at http://django-irc-logs.com/. + there. Read the archives at https://botbot.me/freenode/django/. * Join the django-users mailing list, or read the archives, at https://groups.google.com/group/django-users. diff --git a/docs/index.txt b/docs/index.txt index 4df5f4bef9f2..76b17a62f982 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -25,7 +25,7 @@ Having trouble? We'd like to help! .. _archives: https://groups.google.com/group/django-users/ .. _post a question: https://groups.google.com/d/forum/django-users .. _#django IRC channel: irc://irc.freenode.net/django -.. _IRC logs: http://django-irc-logs.com/ +.. _IRC logs: https://botbot.me/freenode/django/ .. _ticket tracker: https://code.djangoproject.com/ How the documentation is organized From 801b6fb32e36a9b61ae469e29e17650dd8afd9fe Mon Sep 17 00:00:00 2001 From: Rachel Tobin Date: Fri, 21 Jul 2017 15:21:13 -0700 Subject: [PATCH 114/389] [1.11.x] Fixed #28418 -- Fixed queryset crash when using a GenericRelation to a proxy model. Backport of f9e5f9ae9f83c7ddf5e5d3c369b6bf54a9b80ab5 from master --- AUTHORS | 1 + django/contrib/contenttypes/fields.py | 2 +- docs/releases/1.11.4.txt | 3 +++ tests/generic_relations_regress/models.py | 6 ++++++ tests/generic_relations_regress/tests.py | 5 +++++ 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index ac8837505a20..cf4a5594278e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -633,6 +633,7 @@ answer newbie questions, and generally made Django that much better: pradeep.gowda@gmail.com Preston Holmes Preston Timmons + Rachel Tobin Rachel Willmer Radek Švarz Rajesh Dhawan diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index a273cf034798..6c4eb43ac1b7 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -368,7 +368,7 @@ def _get_path_info_with_parent(self): # generating a join to the parent model, then generating joins to the # child models. path = [] - opts = self.remote_field.model._meta + opts = self.remote_field.model._meta.concrete_model._meta parent_opts = opts.get_field(self.object_id_field_name).model._meta target = parent_opts.pk path.append(PathInfo(self.model._meta, parent_opts, (target,), self.remote_field, True, False)) diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 180d758246be..1f0081c4b609 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -35,3 +35,6 @@ Bugfixes ``checkbox_name``, ``checkbox_id``, ``is_initial``, ``input_text``, ``initial_text``, and ``clear_checkbox_label`` are now attributes of ``widget`` rather than appearing in the top-level context. + +* Fixed queryset crash when using a ``GenericRelation`` to a proxy model + (:ticket:`28418`). diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index eb4f645d3450..669e7b7186db 100644 --- a/tests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -21,10 +21,16 @@ def __str__(self): return "Link to %s id=%s" % (self.content_type, self.object_id) +class LinkProxy(Link): + class Meta: + proxy = True + + @python_2_unicode_compatible class Place(models.Model): name = models.CharField(max_length=100) links = GenericRelation(Link) + link_proxy = GenericRelation(LinkProxy) def __str__(self): return "Place: %s" % self.name diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index d3986b6916df..b2d5b0869234 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -244,3 +244,8 @@ def test_ticket_22998(self): def test_ticket_22982(self): place = Place.objects.create(name='My Place') self.assertIn('GenericRelatedObjectManager', str(place.links)) + + def test_filter_on_related_proxy_model(self): + place = Place.objects.create() + Link.objects.create(content_object=place) + self.assertEqual(Place.objects.get(link_proxy__object_id=place.id), place) From aef117eb2e7805c5965adbfbfe1b5374cb58bbbe Mon Sep 17 00:00:00 2001 From: Tobias Schulmann Date: Sun, 23 Jul 2017 08:43:23 +1200 Subject: [PATCH 115/389] [1.11.x] Fixed #28420 -- Doc'd 'is' comparison restriction for User.is_authenticated/anonymous. --- docs/ref/contrib/auth.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index b50bc966e595..71c6bd2ce15b 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -145,6 +145,15 @@ Attributes In older versions, this was a method. Backwards-compatibility support for using it as a method will be removed in Django 2.0. + .. admonition:: Don't use the ``is`` operator for comparisons! + + To allow the ``is_authenticated`` and ``is_anonymous`` attributes + to also work as methods, the attributes are ``CallableBool`` + objects. Thus, until the deprecation period ends in Django 2.0, you + can't compare these properties using the ``is`` operator. That is, + ``request.user.is_authenticated is True`` always evaluate to + ``False``. + .. attribute:: is_anonymous Read-only attribute which is always ``False``. This is a way of From b6620dee72dc44da45403ff663853bfd8421c63e Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Mon, 24 Jul 2017 12:33:30 -0500 Subject: [PATCH 116/389] [1.11.x] Fixed #28349 -- Doc'd how to upgrade Django from LTS to LTS. Backport of 27ef04bb5c3118e00d3eda4b3aa3201d18ea1785 from master --- docs/howto/upgrade-version.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/howto/upgrade-version.txt b/docs/howto/upgrade-version.txt index 9f433094b151..c495bd79e524 100644 --- a/docs/howto/upgrade-version.txt +++ b/docs/howto/upgrade-version.txt @@ -33,6 +33,14 @@ the new Django version(s): Pay particular attention to backwards incompatible changes to get a clear idea of what will be needed for a successful upgrade. +If you're upgrading through more than one feature version (e.g. A.B to A.B+2), +it's usually easier to upgrade through each feature release incrementally +(A.B to A.B+1 to A.B+2) rather than to make all the changes for each feature +release at once. For each feature release, use the latest patch release (A.B.C). + +The same incremental upgrade approach is recommended when upgrading from one +LTS to the next. + Dependencies ============ From 7386029249e68b87ed0b50235174bcff4ec6cea0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 25 Jul 2017 15:12:50 -0400 Subject: [PATCH 117/389] [1.11.x] Fixed #28435 -- Removed inaccurate warning about SECURE_HSTS_PRELOAD. Backport of c7d58c6f43b2d1db5d01f32451bb9ce46d69c5eb from master --- docs/ref/settings.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index bead7539e50b..0e856a12f934 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2157,11 +2157,6 @@ the ``preload`` directive to the :ref:`http-strict-transport-security` header. It has no effect unless :setting:`SECURE_HSTS_SECONDS` is set to a non-zero value. -.. warning:: - Setting this incorrectly can irreversibly (for at least several months, - depending on browser releases) break your site. Read the - :ref:`http-strict-transport-security` documentation first. - .. setting:: SECURE_HSTS_SECONDS ``SECURE_HSTS_SECONDS`` From 6e6aa77b3b0ea191300478f1c2569b1238d23648 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Wed, 26 Jul 2017 16:34:10 +0300 Subject: [PATCH 118/389] [1.11.x] Replaced "not A== B" with "A != B" in docs/howto/writing-migrations.txt. Backport of c362228556bedb4a8a3cdaf91b9e456e459ecda1 from master --- docs/howto/writing-migrations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/writing-migrations.txt b/docs/howto/writing-migrations.txt index 9465290c743c..d10097df2880 100644 --- a/docs/howto/writing-migrations.txt +++ b/docs/howto/writing-migrations.txt @@ -22,7 +22,7 @@ attribute:: from django.db import migrations def forwards(apps, schema_editor): - if not schema_editor.connection.alias == 'default': + if schema_editor.connection.alias != 'default': return # Your migration code goes here From 020c1c4cc8e334fbfa4c9d6d11e8953c9a5c9f3a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 27 Jul 2017 08:42:01 -0400 Subject: [PATCH 119/389] [1.11.x] Fixed #28415 -- Clarified what characters ASCII/UnicodeUsernameValidator accept. Backport of 14172cf4426de6c867028f1c194011c0a26e662d from master --- docs/ref/contrib/auth.txt | 20 ++++++++++---------- docs/releases/1.10.txt | 8 ++++---- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 71c6bd2ce15b..b503b02455a8 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -34,12 +34,12 @@ Fields .. admonition:: Usernames and Unicode - Django originally accepted only ASCII letters in usernames. - Although it wasn't a deliberate choice, Unicode characters have - always been accepted when using Python 3. Django 1.10 officially - added Unicode support in usernames, keeping the ASCII-only behavior - on Python 2, with the option to customize the behavior using - :attr:`.User.username_validator`. + Django originally accepted only ASCII letters and numbers in + usernames. Although it wasn't a deliberate choice, Unicode + characters have always been accepted when using Python 3. Django + 1.10 officially added Unicode support in usernames, keeping the + ASCII-only behavior on Python 2, with the option to customize the + behavior using :attr:`.User.username_validator`. .. versionchanged:: 1.10 @@ -426,15 +426,15 @@ Validators .. versionadded:: 1.10 - A field validator allowing only ASCII letters, in addition to ``@``, ``.``, - ``+``, ``-``, and ``_``. The default validator for ``User.username`` on - Python 2. + A field validator allowing only ASCII letters and numbers, in addition to + ``@``, ``.``, ``+``, ``-``, and ``_``. The default validator for + ``User.username`` on Python 2. .. class:: validators.UnicodeUsernameValidator .. versionadded:: 1.10 - A field validator allowing Unicode letters, in addition to ``@``, ``.``, + A field validator allowing Unicode characters, in addition to ``@``, ``.``, ``+``, ``-``, and ``_``. The default validator for ``User.username`` on Python 3. diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index d25c7bc1ab18..daf244e45735 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -55,11 +55,11 @@ Official support for Unicode usernames -------------------------------------- The :class:`~django.contrib.auth.models.User` model in ``django.contrib.auth`` -originally only accepted ASCII letters in usernames. Although it wasn't a -deliberate choice, Unicode characters have always been accepted when using -Python 3. +originally only accepted ASCII letters and numbers in usernames. Although it +wasn't a deliberate choice, Unicode characters have always been accepted when +using Python 3. -The username validator now explicitly accepts Unicode letters by +The username validator now explicitly accepts Unicode characters by default on Python 3 only. This default behavior can be overridden by changing the :attr:`~django.contrib.auth.models.User.username_validator` attribute of the ``User`` model, or to any proxy of that model, using either From 318414ca70f26e1949788796518cb629e1e7c7aa Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Aug 2017 08:08:18 -0400 Subject: [PATCH 120/389] [1.11.x] Added release date for 1.11.4. Backport of 4c68ef9136c9c32e58ce0273a4e026f17140e2fc from master --- docs/releases/1.11.4.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.4.txt b/docs/releases/1.11.4.txt index 1f0081c4b609..fe2f6d88a467 100644 --- a/docs/releases/1.11.4.txt +++ b/docs/releases/1.11.4.txt @@ -2,7 +2,7 @@ Django 1.11.4 release notes =========================== -*Under development* +*August 1, 2017* Django 1.11.4 fixes several bugs in 1.11.3. From 1a34dfcf797640d5d580d261694cb54e6f97c552 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Aug 2017 08:12:43 -0400 Subject: [PATCH 121/389] [1.11.x] Bumped version for 1.11.4 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 1c02ac72c223..d5d903c846f8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 4, 'alpha', 0) +VERSION = (1, 11, 4, 'final', 0) __version__ = get_version(VERSION) From 55bbfd08b7fd4e341cec4ef55631a296e5925dbe Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Aug 2017 08:31:30 -0400 Subject: [PATCH 122/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index d5d903c846f8..64db32eabdd1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 4, 'final', 0) +VERSION = (1, 11, 5, 'alpha', 0) __version__ = get_version(VERSION) From 2ec74bfcaccd533ed9684c88f734ba3765866a95 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Aug 2017 08:46:23 -0400 Subject: [PATCH 123/389] [1.11.x] Added stub release notes for 1.11.5. Backport of 53d2534b3813f46f373bbf751ac3baf21c50d405 from master --- docs/releases/1.11.5.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.5.txt diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt new file mode 100644 index 000000000000..bbdd640e4b55 --- /dev/null +++ b/docs/releases/1.11.5.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.5 release notes +=========================== + +*Under development* + +Django 1.11.5 fixes several bugs in 1.11.4. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6eb5aae55eba..6a6af01960b4 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.5 1.11.4 1.11.3 1.11.2 From a49764dd9daa11c4e24bad84423f71711b3e0de0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Aug 2017 15:06:31 -0400 Subject: [PATCH 124/389] [1.11.x] Fixed #28441 -- Fixed GEOS version parsing with a commit hash at the end. A less invasive backport of 78c155cf2e5a27fd2db18c2d46953b1b0fdba829 from master --- django/contrib/gis/geos/libgeos.py | 2 +- docs/releases/1.11.5.txt | 3 ++- tests/gis_tests/geos_tests/test_geos.py | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/geos/libgeos.py b/django/contrib/gis/geos/libgeos.py index 48532d5c53ad..2257061ed7bd 100644 --- a/django/contrib/gis/geos/libgeos.py +++ b/django/contrib/gis/geos/libgeos.py @@ -179,7 +179,7 @@ def get_func(self, *args, **kwargs): # '3.0.0rc4-CAPI-1.3.3', '3.0.0-CAPI-1.4.1', '3.4.0dev-CAPI-1.8.0' or '3.4.0dev-CAPI-1.8.0 r0' version_regex = re.compile( r'^(?P(?P\d+)\.(?P\d+)\.(?P\d+))' - r'((rc(?P\d+))|dev)?-CAPI-(?P\d+\.\d+\.\d+)( r\d+)?$' + r'((rc(?P\d+))|dev)?-CAPI-(?P\d+\.\d+\.\d+)( r\d+)?( \w+)?$' ) diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index bbdd640e4b55..55fa0eda7e67 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -9,4 +9,5 @@ Django 1.11.5 fixes several bugs in 1.11.4. Bugfixes ======== -* ... +* Fixed GEOS version parsing if the version has a commit hash at the end (new + in GEOS 3.6.2) (:ticket:`28441`). diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index 554f1e811729..60ceeffdcc10 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -1278,7 +1278,8 @@ def test_geos_version(self): versions = [('3.0.0rc4-CAPI-1.3.3', '3.0.0', '1.3.3'), ('3.0.0-CAPI-1.4.1', '3.0.0', '1.4.1'), ('3.4.0dev-CAPI-1.8.0', '3.4.0', '1.8.0'), - ('3.4.0dev-CAPI-1.8.0 r0', '3.4.0', '1.8.0')] + ('3.4.0dev-CAPI-1.8.0 r0', '3.4.0', '1.8.0'), + ('3.6.2-CAPI-1.10.2 4d2925d6', '3.6.2', '1.10.2')] for v_init, v_geos, v_capi in versions: m = version_regex.match(v_init) self.assertTrue(m, msg="Unable to parse the version string '%s'" % v_init) From 05a828a1aea83f499da15dbaaa98a4ce21f7381b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 7 Aug 2017 15:51:46 -0400 Subject: [PATCH 125/389] [1.11.x] Fixed #28466 -- Clarified the definition of a lazy relationship. Backport of 50a97edc1a01f3d5325f0be1b91e7c77c3ba7dc0 from master --- docs/ref/models/fields.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index cdc4371d374d..487de45641bc 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1223,8 +1223,8 @@ need to use:: on_delete=models.CASCADE, ) -This sort of reference can be useful when resolving circular import -dependencies between two applications. +This sort of reference, called a lazy relationship, can be useful when +resolving circular import dependencies between two applications. A database index is automatically created on the ``ForeignKey``. You can disable this by setting :attr:`~Field.db_index` to ``False``. You may want to From 479554f569abe5dbca980ac930fa1f474ef17c6e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 8 Aug 2017 08:55:14 -0400 Subject: [PATCH 126/389] [1.11.x] Fixed #28471 -- Clarified that Meta.indexes is preferred to index_together. Backport of d18227e341ed044980d02a1f65f3874166552ded from master --- docs/ref/models/options.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 0e885dfae62d..63a4fc1b79e4 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -445,6 +445,12 @@ Django quotes column and table names behind the scenes. .. attribute:: Options.index_together + .. admonition:: Use the :attr:`~Options.indexes` option instead. + + The newer :attr:`~Options.indexes` option provides more functionality + than ``index_together``. ``index_together`` may be deprecated in the + future. + Sets of field names that, taken together, are indexed:: index_together = [ From 7e7edba64bb432dfec36123e03a38074e5fb6fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Pedro=20Fernandes=20Santos?= Date: Wed, 9 Aug 2017 23:05:05 +0100 Subject: [PATCH 127/389] [1.11.x] Fixed argument name in call_command() docstring. Follow up to 8f6a1a15516629b30e2fa2c48d5e682f7955868c. Backport of 7104e051c1c53c1348c1ebb630e5074ea49943b7 from master --- django/core/management/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 6466130f5fed..ad66735c82f6 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -82,8 +82,9 @@ def call_command(command_name, *args, **options): This is the primary API you should use for calling specific commands. - `name` may be a string or a command object. Using a string is preferred - unless the command object is required for further processing or testing. + `command_name` may be a string or a command object. Using a string is + preferred unless the command object is required for further processing or + testing. Some examples: call_command('migrate') From fe51017efdc3f26c94c83fe5930b14d71e1bb380 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 10 Aug 2017 22:21:11 +0200 Subject: [PATCH 128/389] [1.11.x] Fixed #23766 -- Doc'd CursorWrapper.callproc(). Thanks Tim Graham for the review. Backport of 660d50805b6788d592b4f1fae706b725baf0195c from master --- docs/topics/db/sql.txt | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/topics/db/sql.txt b/docs/topics/db/sql.txt index b5c45844cd39..94e08b8bef00 100644 --- a/docs/topics/db/sql.txt +++ b/docs/topics/db/sql.txt @@ -346,3 +346,29 @@ is equivalent to:: c.execute(...) finally: c.close() + +Calling stored procedures +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. method:: CursorWrapper.callproc(procname, params=None) + + Calls a database stored procedure with the given name and optional sequence + of input parameters. + + For example, given this stored procedure in an Oracle database: + + .. code-block:: sql + + CREATE PROCEDURE "TEST_PROCEDURE"(v_i INTEGER, v_text NVARCHAR2(10)) AS + p_i INTEGER; + p_text NVARCHAR2(10); + BEGIN + p_i := v_i; + p_text := v_text; + ... + END; + + This will call it:: + + with connection.cursor() as cursor: + cursor.callproc('test_procedure', [1, 'test']) From 93b53fb942c2b16e2f7079b86d18bcf800a3f368 Mon Sep 17 00:00:00 2001 From: Bryan Helmig Date: Thu, 10 Aug 2017 11:54:19 -0400 Subject: [PATCH 129/389] [1.11.x] Made the @cached_property example more consistent. Backport of 68f0bcb012fefffcf94b25dedd02c061a7544041 from master --- docs/ref/utils.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index 4ad645f95864..9910c6111637 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -477,16 +477,16 @@ https://web.archive.org/web/20110718035220/http://diveintomark.org/archives/2004 {% for friend in person.friends %} Here, ``friends()`` will be called twice. Since the instance ``person`` in - the view and the template are the same, ``@cached_property`` can avoid - that:: + the view and the template are the same, decorating the ``friends()`` method + with ``@cached_property`` can avoid that:: from django.utils.functional import cached_property - @cached_property - def friends(self): - # expensive computation - ... - return friends + class Person(models.Model): + + @cached_property + def friends(self): + ... Note that as the method is now a property, in Python code it will need to be invoked appropriately:: From bca1ffc87afaff83072030890894a73d3e2478e8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 11 Aug 2017 11:17:08 -0400 Subject: [PATCH 130/389] [1.11.x] Fixed #27855 -- Updated docs for Python 3.4 support in Django 2.0. Backport of abd723c6a010be1bc06687d21e8841e07af6fde3 from master --- docs/faq/install.txt | 3 ++- docs/releases/1.11.txt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index 980f0182ca63..12482362d8a4 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -49,7 +49,8 @@ Django version Python versions 1.8 2.7, 3.2 (until the end of 2016), 3.3, 3.4, 3.5 1.9, 1.10 2.7, 3.4, 3.5 1.11 2.7, 3.4, 3.5, 3.6 -2.0 3.5+ +2.0 3.4, 3.5, 3.6 +2.1 3.5, 3.6, 3.7 ============== =============== For each version of Python, only the latest micro release (A.B.C) is officially diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 953c2325faf9..3da71a0dd793 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -27,7 +27,7 @@ release to support Python 3.6. We **highly recommend** and only officially support the latest release of each series. The Django 1.11.x series is the last to support Python 2. The next major -release, Django 2.0, will only support Python 3.5+. +release, Django 2.0, will only support Python 3.4+. Deprecating warnings are no longer loud by default ================================================== From b0ed14644a10dc8263e02b7f075e31169b450ea7 Mon Sep 17 00:00:00 2001 From: Jonatas CD Date: Fri, 11 Aug 2017 21:22:10 +0200 Subject: [PATCH 131/389] [1.11.x] Fixed #28252 -- Corrected docs for default file extensions of makemessages. Backport of 31f133ea08d41907a67f9e3d667f2a09c167a97c from master --- docs/topics/i18n/translation.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index aa85aff90711..36d00a41360b 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1668,9 +1668,9 @@ message file under the directory listed first in :setting:`LOCALE_PATHS` or will generate an error if :setting:`LOCALE_PATHS` is empty. By default :djadmin:`django-admin makemessages ` examines every -file that has the ``.html`` or ``.txt`` file extension. In case you want to -override that default, use the ``--extension`` or ``-e`` option to specify the -file extensions to examine:: +file that has the ``.html``, ``.txt`` or ``.py`` file extension. If you want to +override that default, use the :option:`--extension ` +or ``-e`` option to specify the file extensions to examine:: django-admin makemessages -l de -e txt From 1214e7c1b1248a7e51dfbdaaaed5bca56956f218 Mon Sep 17 00:00:00 2001 From: Mathieu Hinderyckx Date: Sun, 13 Aug 2017 15:24:14 +0200 Subject: [PATCH 132/389] [1.11.x] Clarified Concat example in docs. Backport of cf5740fbc8414ab722b938f92b4363ff00d8db88 from master --- docs/ref/models/database-functions.txt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index a25206951954..0b0b0050fffc 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -90,8 +90,9 @@ Usage examples:: Accepts a list of at least two text fields or expressions and returns the concatenated text. Each argument must be of a text or char type. If you want to concatenate a ``TextField()`` with a ``CharField()``, then be sure to tell -Django that the ``output_field`` should be a ``TextField()``. This is also -required when concatenating a ``Value`` as in the example below. +Django that the ``output_field`` should be a ``TextField()``. Specifying an +``output_field`` is also required when concatenating a ``Value`` as in the +example below. This function will never have a null result. On backends where a null argument results in the entire expression being null, Django will ensure that each null @@ -104,8 +105,11 @@ Usage example:: >>> from django.db.models.functions import Concat >>> Author.objects.create(name='Margaret Smith', goes_by='Maggie') >>> author = Author.objects.annotate( - ... screen_name=Concat('name', V(' ('), 'goes_by', V(')'), - ... output_field=CharField())).get() + ... screen_name=Concat( + ... 'name', V(' ('), 'goes_by', V(')'), + ... output_field=CharField() + ... ) + ... ).get() >>> print(author.screen_name) Margaret Smith (Maggie) From 07e34f8bca83704e4c3d50830574a839354d9bcc Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 16 Aug 2017 18:39:58 +0200 Subject: [PATCH 133/389] [1.11.x] Fixed #28498 -- Fixed test database creation with cx_Oracle 6. Backport of 6784383e93d582f43f8cb5f7647a05645cbb339b from master --- django/db/backends/oracle/creation.py | 4 ++++ docs/releases/1.11.5.txt | 2 ++ tests/select_for_update/tests.py | 6 +++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py index d34f2a2d5079..911f19b50ee1 100644 --- a/django/db/backends/oracle/creation.py +++ b/django/db/backends/oracle/creation.py @@ -96,6 +96,8 @@ def _create_test_db(self, verbosity=1, autoclobber=False, keepdb=False): print("Tests cancelled.") sys.exit(1) + # Cursor must be closed before closing connection. + cursor.close() self._maindb_connection.close() # done with main user -- test user and tablespaces created self._switch_to_test_user(parameters) return self.connection.settings_dict['NAME'] @@ -180,6 +182,8 @@ def _destroy_test_db(self, test_database_name, verbosity=1): if verbosity >= 1: print('Destroying test database tables...') self._execute_test_db_destruction(cursor, parameters, verbosity) + # Cursor must be closed before closing connection. + cursor.close() self._maindb_connection.close() def _execute_test_db_creation(self, cursor, parameters, verbosity, keepdb=False): diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 55fa0eda7e67..556cc7379327 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -11,3 +11,5 @@ Bugfixes * Fixed GEOS version parsing if the version has a commit hash at the end (new in GEOS 3.6.2) (:ticket:`28441`). + +* Fixed test database creation with ``cx_Oracle`` 6 (:ticket:`28498`). diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index 9e5ee598b0b8..3344e3dcf357 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -51,6 +51,7 @@ def start_blocking_transaction(self): def end_blocking_transaction(self): # Roll back the blocking transaction. + self.cursor.close() self.new_connection.rollback() self.new_connection.set_autocommit(True) @@ -274,7 +275,10 @@ def raw(status): finally: # This method is run in a separate thread. It uses its own # database connection. Close it without waiting for the GC. - connection.close() + # Connection cannot be closed on Oracle because cursor is still + # open. + if connection.vendor != 'oracle': + connection.close() status = [] thread = threading.Thread(target=raw, kwargs={'status': status}) From be24b5eaa543bedfc233bff5092b8ca3762e3c70 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 21 Aug 2017 10:16:22 +0300 Subject: [PATCH 134/389] [1.11.x] Removed redundant backticks in docs/releases/1.8.txt Backport of 8d095c6378666e6d5f6cabc9e485c9db2618ff88 from master. --- docs/releases/1.8.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 5cb91427c78b..54120609f8f1 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -1442,11 +1442,11 @@ Old :tfilter:`unordered_list` syntax An older (pre-1.0), more restrictive and verbose input format for the :tfilter:`unordered_list` template filter has been deprecated:: - ``['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]]`` + ['States', [['Kansas', [['Lawrence', []], ['Topeka', []]]], ['Illinois', []]]] Using the new syntax, this becomes:: - ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]`` + ['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']] ``django.forms.Field._has_changed()`` ------------------------------------- From dd82f1df556561233eab6a4a2032c2bb306dec87 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 22 Aug 2017 14:45:08 +0200 Subject: [PATCH 135/389] [1.11.x] Fixed #28502 -- Made stringformat template filter accept tuples. Backport of 4ead705cb3cf04bb7551ac037d1e11f682b62bcf and ed77bea58274e11e5a9e4c8b9650f50deb8a2b26 from master --- django/template/defaultfilters.py | 2 ++ docs/releases/1.11.5.txt | 2 ++ tests/template_tests/filter_tests/test_stringformat.py | 3 +++ 3 files changed, 7 insertions(+) diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index a1f96f5e2e4a..bfa6cff668be 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -249,6 +249,8 @@ def stringformat(value, arg): See https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting for documentation of Python string formatting. """ + if isinstance(value, tuple): + value = six.text_type(value) try: return ("%" + six.text_type(arg)) % value except (ValueError, TypeError): diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 556cc7379327..e7c6c91a5d40 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -13,3 +13,5 @@ Bugfixes in GEOS 3.6.2) (:ticket:`28441`). * Fixed test database creation with ``cx_Oracle`` 6 (:ticket:`28498`). + +* Fixed select widget rendering when option values are tuples (:ticket:`28502`). diff --git a/tests/template_tests/filter_tests/test_stringformat.py b/tests/template_tests/filter_tests/test_stringformat.py index 9501878ebd6e..19e2d9eb17c3 100644 --- a/tests/template_tests/filter_tests/test_stringformat.py +++ b/tests/template_tests/filter_tests/test_stringformat.py @@ -29,6 +29,9 @@ class FunctionTests(SimpleTestCase): def test_format(self): self.assertEqual(stringformat(1, '03d'), '001') + self.assertEqual(stringformat((1, 2, 3), 's'), '(1, 2, 3)') + self.assertEqual(stringformat((1,), 's'), '(1,)') def test_invalid(self): self.assertEqual(stringformat(1, 'z'), '') + self.assertEqual(stringformat((1, 2, 3), 'd'), '') From d72f953e5a785feef67eef1b1434fbbe392ccb13 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 22 Aug 2017 14:10:42 +0100 Subject: [PATCH 136/389] [1.11.x] Fixed incorrect indentation in remove_stale_contenttypes. It's unnecessary for content_type_display to be constructed from ct_info in every loop iteration. Backport of 796fde5b793b6a36b7fc5481994d37ef71da8f58 from master --- .../management/commands/remove_stale_contenttypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py b/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py index 2a3b23b0d05f..e1e00bb2e23e 100644 --- a/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py +++ b/django/contrib/contenttypes/management/commands/remove_stale_contenttypes.py @@ -52,7 +52,7 @@ def handle(self, **options): len(objs), obj_type._meta.label, )) - content_type_display = '\n'.join(ct_info) + content_type_display = '\n'.join(ct_info) self.stdout.write("""Some content types in your database are stale and can be deleted. Any objects that depend on these content types will also be deleted. The content types and dependent objects that would be deleted are: From 60f81118f412268f317abbcc7509e315a714315d Mon Sep 17 00:00:00 2001 From: Harry Moreno Date: Tue, 22 Aug 2017 10:26:00 -0400 Subject: [PATCH 137/389] [1.11.x] Added "test --keepdb" to testing speedup docs. Backport of 254fb8d1a4e0525d890a5363a5c08f00bc873f03 from master --- docs/topics/testing/overview.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 245ee0707902..a3036192cdd1 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -343,3 +343,10 @@ the :setting:`PASSWORD_HASHERS` setting to a faster hashing algorithm:: Don't forget to also include in :setting:`PASSWORD_HASHERS` any hashing algorithm used in fixtures, if any. + +Preserving the test database +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The :option:`test --keepdb` option preserves the test database between test +runs. It skips the create and destroy actions which can greatly decrease the +time to run tests. From 90be8cf2a4d81005c5c35074ba763f5fd3a56bd1 Mon Sep 17 00:00:00 2001 From: Kevin Grinberg Date: Tue, 22 Aug 2017 17:41:25 -0400 Subject: [PATCH 138/389] [1.11.x] Fixed #28451 -- Restored pre-Django 1.11 Oracle sequence/trigger naming. Regression in 69b7d4b116e3b70b250c77829e11038d5d55c2a8. Backport of c6a3546093bebae8225a2c5b7e0836a2b0617ee5 from master --- django/db/backends/oracle/operations.py | 6 ++---- docs/releases/1.11.5.txt | 8 ++++++++ tests/backends/tests.py | 8 ++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/django/db/backends/oracle/operations.py b/django/db/backends/oracle/operations.py index 9c382895b713..1af985bfcbae 100644 --- a/django/db/backends/oracle/operations.py +++ b/django/db/backends/oracle/operations.py @@ -522,13 +522,11 @@ def combine_expression(self, connector, sub_expressions): def _get_sequence_name(self, table): name_length = self.max_name_length() - 3 - sequence_name = '%s_SQ' % strip_quotes(table) - return truncate_name(sequence_name, name_length).upper() + return '%s_SQ' % truncate_name(strip_quotes(table), name_length).upper() def _get_trigger_name(self, table): name_length = self.max_name_length() - 3 - trigger_name = '%s_TR' % strip_quotes(table) - return truncate_name(trigger_name, name_length).upper() + return '%s_TR' % truncate_name(strip_quotes(table), name_length).upper() def bulk_insert_sql(self, fields, placeholder_rows): return " UNION ALL ".join( diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index e7c6c91a5d40..5cd38e3b986a 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -15,3 +15,11 @@ Bugfixes * Fixed test database creation with ``cx_Oracle`` 6 (:ticket:`28498`). * Fixed select widget rendering when option values are tuples (:ticket:`28502`). + +* Django 1.11 inadvertently changed the sequence and trigger naming scheme on + Oracle. This causes errors on INSERTs for some tables if + ``'use_returning_into': False`` is in the ``OPTIONS`` part of ``DATABASES``. + The pre-11.1 naming scheme is now restored. Unfortunately, it necessarily + requires an update to Oracle tables created with Django 1.11.[1-4]. Use the + upgrade script in :ticket:`28451` comment 8 to update sequence and trigger + names to use the pre-1.11 naming scheme diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 5a49d9921766..83d6b05b5e02 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -128,6 +128,14 @@ def test_order_of_nls_parameters(self): cursor.execute(query) self.assertEqual(cursor.fetchone()[0], 1) + def test_sequence_name_truncation(self): + seq_name = connection.ops._get_sequence_name('schema_authorwithevenlongee869') + self.assertEqual(seq_name, 'SCHEMA_AUTHORWITHEVENLOB0B8_SQ') + + def test_trigger_name_truncation(self): + trigger_name = connection.ops._get_trigger_name('schema_authorwithevenlongee869') + self.assertEqual(trigger_name, 'SCHEMA_AUTHORWITHEVENLOB0B8_TR') + @unittest.skipUnless(connection.vendor == 'sqlite', "Test only for SQLite") class SQLiteTests(TestCase): From 1b0e45e4ea44da95b19aa62713a1e52d5c6af867 Mon Sep 17 00:00:00 2001 From: Kim DoHyeon Date: Wed, 23 Aug 2017 05:24:18 +0900 Subject: [PATCH 139/389] [1.11.x] Fixed #27931 -- Clarified the meaning of "django catch-all logger." Backport of f21915bb3ad73001b7d987332b2b4a0ae4ec288d from master --- docs/topics/logging.txt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/topics/logging.txt b/docs/topics/logging.txt index 15a49f0d4d92..066e772c7c8d 100644 --- a/docs/topics/logging.txt +++ b/docs/topics/logging.txt @@ -456,8 +456,8 @@ Django provides several built-in loggers. ``django`` ~~~~~~~~~~ -``django`` is the catch-all logger. No messages are posted directly to -this logger. +The catch-all logger for messages in the ``django`` hierarchy. No messages are +posted using this name but instead using one of the loggers below. .. _django-request-logger: @@ -740,18 +740,19 @@ By default, Django configures the following logging: When :setting:`DEBUG` is ``True``: -* The ``django`` catch-all logger sends all messages at the ``INFO`` level or - higher to the console. +* The ``django`` logger sends messages in the ``django`` hierarchy (except + ``django.server``) at the ``INFO`` level or higher to the console. When :setting:`DEBUG` is ``False``: -* The ``django`` logger send messages with ``ERROR`` or ``CRITICAL`` level to +* The ``django`` logger sends messages in the ``django`` hierarchy (except + ``django.server``) with ``ERROR`` or ``CRITICAL`` level to :class:`AdminEmailHandler`. Independent of the value of :setting:`DEBUG`: -* The :ref:`django-server-logger` logger sends all messages at the ``INFO`` - level or higher to the console. +* The :ref:`django-server-logger` logger sends messages at the ``INFO`` level + or higher to the console. See also :ref:`Configuring logging ` to learn how you can complement or replace this default logging configuration. From e6dd785bb7dd7e02ee338786f73f7fdcdc6f60ec Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 23 Aug 2017 08:55:25 +0200 Subject: [PATCH 140/389] [1.11.x] Fixed typo in docs/releases/1.11.5.txt. Backport of 330e965cd8d70eb3c169d655aaa88f7f915adb1a from master --- docs/releases/1.11.5.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 5cd38e3b986a..78279d5aa3b7 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -19,7 +19,7 @@ Bugfixes * Django 1.11 inadvertently changed the sequence and trigger naming scheme on Oracle. This causes errors on INSERTs for some tables if ``'use_returning_into': False`` is in the ``OPTIONS`` part of ``DATABASES``. - The pre-11.1 naming scheme is now restored. Unfortunately, it necessarily + The pre-1.11 naming scheme is now restored. Unfortunately, it necessarily requires an update to Oracle tables created with Django 1.11.[1-4]. Use the upgrade script in :ticket:`28451` comment 8 to update sequence and trigger - names to use the pre-1.11 naming scheme + names to use the pre-1.11 naming scheme. From 0d21bdd3805f5d3a37a02062a170cabe377aa8fe Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 23 Aug 2017 10:01:49 +0200 Subject: [PATCH 141/389] [1.11.x] Fixed #28498 -- Added support for cx_Oracle 6. - Fixed implicit Decimal to float conversion when input_size is not specified for Decimal parameters. - Used encoding, nencoding parameters of cx_Oracle.connect() to support unicode in DSN. Thanks Tim Graham for the review. --- django/db/backends/oracle/base.py | 11 ++++++++--- docs/releases/1.11.5.txt | 2 +- tests/backends/tests.py | 8 ++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index 65b5e1090980..2de74da5f81e 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -200,9 +200,12 @@ def _connect_string(self): settings_dict['PASSWORD'], dsn) def get_connection_params(self): - conn_params = self.settings_dict['OPTIONS'].copy() - if 'use_returning_into' in conn_params: - del conn_params['use_returning_into'] + # Specify encoding to support unicode in DSN. + conn_params = {'encoding': 'UTF-8', 'nencoding': 'UTF-8'} + user_params = self.settings_dict['OPTIONS'].copy() + if 'use_returning_into' in user_params: + del user_params['use_returning_into'] + conn_params.update(user_params) return conn_params def get_new_connection(self, conn_params): @@ -359,6 +362,8 @@ def __init__(self, param, cursor, strings_only=False): elif string_size > 4000: # Mark any string param greater than 4000 characters as a CLOB. self.input_size = Database.CLOB + elif isinstance(param, decimal.Decimal): + self.input_size = Database.NUMBER else: self.input_size = None diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 78279d5aa3b7..f650fe4b5a99 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -12,7 +12,7 @@ Bugfixes * Fixed GEOS version parsing if the version has a commit hash at the end (new in GEOS 3.6.2) (:ticket:`28441`). -* Fixed test database creation with ``cx_Oracle`` 6 (:ticket:`28498`). +* Added compatibility for ``cx_Oracle`` 6 (:ticket:`28498`). * Fixed select widget rendering when option values are tuples (:ticket:`28502`). diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 83d6b05b5e02..16a20de40d66 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -117,6 +117,14 @@ def test_client_encoding(self): connection.ensure_connection() self.assertEqual(connection.connection.encoding, "UTF-8") self.assertEqual(connection.connection.nencoding, "UTF-8") + # Client encoding may be changed in OPTIONS. + new_connection = connection.copy() + new_connection.settings_dict['OPTIONS']['encoding'] = 'ISO-8859-2' + new_connection.settings_dict['OPTIONS']['nencoding'] = 'ASCII' + new_connection.ensure_connection() + self.assertEqual(new_connection.connection.encoding, 'ISO-8859-2') + self.assertEqual(new_connection.connection.nencoding, 'ASCII') + new_connection.close() def test_order_of_nls_parameters(self): # an 'almost right' datetime should work with configured From 58aaf13e759cf60f8eaab42f650921923fde6502 Mon Sep 17 00:00:00 2001 From: hui shang Date: Thu, 24 Aug 2017 21:11:16 +0800 Subject: [PATCH 142/389] [1.11.x] Fixed #28513 -- Added POST request support to LogoutView. Backport of c0f4c60edd429f5ef57241cfabd159d13e26e5ac from master --- django/contrib/auth/views.py | 4 ++++ docs/releases/1.11.5.txt | 3 +++ tests/auth_tests/test_views.py | 6 ++++++ 3 files changed, 13 insertions(+) diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 4c5128143681..f8934e9edb26 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -159,6 +159,10 @@ def dispatch(self, request, *args, **kwargs): return HttpResponseRedirect(next_page) return super(LogoutView, self).dispatch(request, *args, **kwargs) + def post(self, request, *args, **kwargs): + """Logout may be done via POST.""" + return self.get(request, *args, **kwargs) + def get_next_page(self): if self.next_page is not None: next_page = resolve_url(self.next_page) diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index f650fe4b5a99..baa327bc0234 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -23,3 +23,6 @@ Bugfixes requires an update to Oracle tables created with Django 1.11.[1-4]. Use the upgrade script in :ticket:`28451` comment 8 to update sequence and trigger names to use the pre-1.11 naming scheme. + +* Added POST request support to ``LogoutView``, for equivalence with the + function-based ``logout()`` view (:ticket:`28513`). diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index 25b779f709be..e2424a2b7194 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -919,6 +919,12 @@ def test_logout_default(self): self.assertContains(response, 'Logged out') self.confirm_logged_out() + def test_logout_with_post(self): + self.login() + response = self.client.post('/logout/') + self.assertContains(response, 'Logged out') + self.confirm_logged_out() + def test_14377(self): # Bug 14377 self.login() From 503b9ab7ad8369ce1e1566de987872ccebc11f08 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 26 Aug 2017 10:20:01 +0200 Subject: [PATCH 143/389] [1.11.x] Fixed #28532 -- Fixed typo in PostgreSQL field docs Thanks Andreas Poisel for the report. Backport of 3c0b2b80edbe744f45b59fa29219db4997d2a108 from master. --- docs/ref/contrib/postgres/fields.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 2048bb064efe..d2e01f6d7e3d 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -658,7 +658,7 @@ excluded; that is, ``[)``. .. class:: DateTimeRangeField(**options) Stores a range of timestamps. Based on a - :class:`~django.db.models.DateTimeField`. Represented by a ``tztsrange`` in + :class:`~django.db.models.DateTimeField`. Represented by a ``tstzrange`` in the database and a :class:`~psycopg2:psycopg2.extras.DateTimeTZRange` in Python. From c685b8f838f7f9411a2a65fba7e3893f15439e18 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Fri, 25 Aug 2017 23:54:36 +0200 Subject: [PATCH 144/389] [1.11.x] Refs #25809 -- Omitted pages_per_range from BrinIndex.deconstruct() if it's None. Backport of fb42d0247136249ea81962474e9a6a2faf1755f1 from master --- django/contrib/postgres/indexes.py | 3 ++- docs/releases/1.11.5.txt | 3 +++ tests/postgres_tests/test_indexes.py | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/django/contrib/postgres/indexes.py b/django/contrib/postgres/indexes.py index 750adc26d461..138dde26de8f 100644 --- a/django/contrib/postgres/indexes.py +++ b/django/contrib/postgres/indexes.py @@ -31,7 +31,8 @@ def __repr__(self): def deconstruct(self): path, args, kwargs = super(BrinIndex, self).deconstruct() - kwargs['pages_per_range'] = self.pages_per_range + if self.pages_per_range is not None: + kwargs['pages_per_range'] = self.pages_per_range return path, args, kwargs def get_sql_create_template_values(self, model, schema_editor, using): diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index baa327bc0234..5716ad63c1cb 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -26,3 +26,6 @@ Bugfixes * Added POST request support to ``LogoutView``, for equivalence with the function-based ``logout()`` view (:ticket:`28513`). + +* Omitted ``pages_per_range`` from ``BrinIndex.deconstruct()`` if it's ``None`` + (:ticket:`25809`). diff --git a/tests/postgres_tests/test_indexes.py b/tests/postgres_tests/test_indexes.py index e26d96f00345..63a187ac3586 100644 --- a/tests/postgres_tests/test_indexes.py +++ b/tests/postgres_tests/test_indexes.py @@ -39,7 +39,7 @@ def test_deconstruction(self): path, args, kwargs = index.deconstruct() self.assertEqual(path, 'django.contrib.postgres.indexes.BrinIndex') self.assertEqual(args, ()) - self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_brin', 'pages_per_range': None}) + self.assertEqual(kwargs, {'fields': ['title'], 'name': 'test_title_brin'}) def test_deconstruction_with_pages_per_range(self): index = BrinIndex(fields=['title'], name='test_title_brin', pages_per_range=16) From 3bb03df0fc642c48ff70cdd74572c31f135f9c08 Mon Sep 17 00:00:00 2001 From: caleb logan Date: Mon, 28 Aug 2017 18:42:03 -0700 Subject: [PATCH 145/389] [1.11.x] Fixed #28530 -- Prevented SelectDateWidget from localizing years in output. Backport of 9e2bf65d6a5dc3b53db84f4839652f0d154349ee from master --- django/forms/widgets.py | 2 +- docs/releases/1.11.5.txt | 3 + .../widget_tests/test_selectdatewidget.py | 61 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index d93fc1dbdc80..d046d3f001fe 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -943,7 +943,7 @@ def __init__(self, attrs=None, years=None, months=None, empty_label=None): def get_context(self, name, value, attrs): context = super(SelectDateWidget, self).get_context(name, value, attrs) date_context = {} - year_choices = [(i, i) for i in self.years] + year_choices = [(i, force_text(i)) for i in self.years] if self.is_required is False: year_choices.insert(0, self.year_none_value) year_attrs = context['widget']['attrs'].copy() diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 5716ad63c1cb..92fa8820a0e8 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -29,3 +29,6 @@ Bugfixes * Omitted ``pages_per_range`` from ``BrinIndex.deconstruct()`` if it's ``None`` (:ticket:`25809`). + +* Fixed a regression where ``SelectDateWidget`` localized the years in the + select box (:ticket:`28530`). diff --git a/tests/forms_tests/widget_tests/test_selectdatewidget.py b/tests/forms_tests/widget_tests/test_selectdatewidget.py index 24294200c0c4..38540f051096 100644 --- a/tests/forms_tests/widget_tests/test_selectdatewidget.py +++ b/tests/forms_tests/widget_tests/test_selectdatewidget.py @@ -485,3 +485,64 @@ def test_value_omitted_from_data(self): self.assertIs(self.widget.value_omitted_from_data({'field_day': '1'}, {}, 'field'), False) data = {'field_day': '1', 'field_month': '12', 'field_year': '2000'} self.assertIs(self.widget.value_omitted_from_data(data, {}, 'field'), False) + + @override_settings(USE_THOUSAND_SEPARATOR=True, USE_L10N=True) + def test_years_rendered_without_separator(self): + widget = SelectDateWidget(years=(2007,)) + self.check_html(widget, 'mydate', '', html=( + """ + + + + """ + )) From 10b54c8782e41efbad805e1f982ca2116c78191a Mon Sep 17 00:00:00 2001 From: Jkrzy Date: Wed, 30 Aug 2017 06:25:51 -0400 Subject: [PATCH 146/389] [1.11.x] Fixed #28548 -- Replaced 'middlewares' with 'middleware' in docs. Backport of da3a5cee4f06ed801c6fb42bd8995428ff0b28bf from master --- docs/ref/contrib/flatpages.txt | 2 +- docs/ref/request-response.txt | 2 +- docs/spelling_wordlist | 1 - docs/topics/i18n/translation.txt | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index 33d408a2b89a..0b66cbf3593b 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -147,7 +147,7 @@ can do all of the work. Note that the order of :setting:`MIDDLEWARE` matters. Generally, you can put :class:`~django.contrib.flatpages.middleware.FlatpageFallbackMiddleware` at the end of the list. This means it will run first when processing the response, and -ensures that any other response-processing middlewares see the real flatpage +ensures that any other response-processing middleware see the real flatpage response rather than the 404. For more on middleware, read the :doc:`middleware docs diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 9f41243c6fce..8e9f0fcf026e 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -1039,7 +1039,7 @@ with the following notable differences: :class:`StreamingHttpResponse` should only be used in situations where it is absolutely required that the whole content isn't iterated before transferring the data to the client. Because the content can't be accessed, many -middlewares can't function normally. For example the ``ETag`` and +middleware can't function normally. For example the ``ETag`` and ``Content-Length`` headers can't be generated for streaming responses. Attributes diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 61ef218f8850..670d1de2d745 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -476,7 +476,6 @@ metre MiB micrometre middleware -middlewares migrationname millimetre Minification diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 36d00a41360b..e1dc6deb2bc1 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -2117,7 +2117,7 @@ To use ``LocaleMiddleware``, add ``'django.middleware.locale.LocaleMiddleware'`` to your :setting:`MIDDLEWARE` setting. Because middleware order matters, follow these guidelines: -* Make sure it's one of the first middlewares installed. +* Make sure it's one of the first middleware installed. * It should come after ``SessionMiddleware``, because ``LocaleMiddleware`` makes use of session data. And it should come before ``CommonMiddleware`` because ``CommonMiddleware`` needs an activated language in order From 046b8c80ce76ed1d410910aa92f67c405b8a15ba Mon Sep 17 00:00:00 2001 From: jkrzy Date: Fri, 18 Aug 2017 15:02:47 -0700 Subject: [PATCH 147/389] [1.11.x] Fixed #27701 -- Doc'd staticfiles runserver bypasses middleware when serving static files. Backport of 20a761697fd28c08ab82dec777b4056a5bfaf6a2 from master --- docs/ref/contrib/staticfiles.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index b8d91fddb87f..74f8a127fb61 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -201,7 +201,9 @@ the directories which were searched:: Overrides the core :djadmin:`runserver` command if the ``staticfiles`` app is :setting:`installed` and adds automatic serving of static -files and the following new options. +files. File serving doesn't run through :setting:`MIDDLEWARE`. + +The command adds these options: .. django-admin-option:: --nostatic From 80a0016c49331bf0a14ef76e714acbff6c6640bd Mon Sep 17 00:00:00 2001 From: Mark Rogaski Date: Mon, 28 Aug 2017 12:40:27 -0400 Subject: [PATCH 148/389] [1.11.x] Fixed #28487 -- Fixed runserver crash with non-Unicode system encodings on Python 2 + Windows. --- django/utils/autoreload.py | 6 +++--- docs/releases/1.11.5.txt | 3 +++ tests/utils_tests/test_autoreload.py | 31 +++++++++++++++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index cf2cefeea9c6..7a9702aebb0a 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -40,7 +40,7 @@ from django.core.signals import request_finished from django.utils import six from django.utils._os import npath -from django.utils.encoding import force_bytes, get_system_encoding +from django.utils.encoding import get_system_encoding from django.utils.six.moves import _thread as thread # This import does nothing, but it's necessary to avoid some race conditions @@ -290,8 +290,8 @@ def restart_with_reloader(): # Environment variables on Python 2 + Windows must be str. encoding = get_system_encoding() for key in new_environ.keys(): - str_key = force_bytes(key, encoding=encoding) - str_value = force_bytes(new_environ[key], encoding=encoding) + str_key = key.decode(encoding).encode('utf-8') + str_value = new_environ[key].decode(encoding).encode('utf-8') del new_environ[key] new_environ[str_key] = str_value new_environ["RUN_MAIN"] = 'true' diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 92fa8820a0e8..91620eb74068 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -32,3 +32,6 @@ Bugfixes * Fixed a regression where ``SelectDateWidget`` localized the years in the select box (:ticket:`28530`). + +* Fixed a regression in 1.11.4 where ``runserver`` crashed with non-Unicode + system encodings on Python 2 + Windows (:ticket:`28487`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 78206135faaf..cab48309fb88 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -5,13 +5,14 @@ import shutil import sys import tempfile +import unittest from importlib import import_module from django import conf from django.contrib import admin from django.test import SimpleTestCase, mock, override_settings from django.test.utils import extend_sys_path -from django.utils import autoreload +from django.utils import autoreload, six from django.utils._os import npath, upath from django.utils.six.moves import _thread from django.utils.translation import trans_real @@ -258,6 +259,13 @@ def test_resets_trans_real(self): class TestRestartWithReloader(SimpleTestCase): + def setUp(self): + self._orig_environ = os.environ.copy() + + def tearDown(self): + os.environ.clear() + os.environ.update(self._orig_environ) + def test_environment(self): """" With Python 2 on Windows, restart_with_reloader() coerces environment @@ -268,3 +276,24 @@ def test_environment(self): os.environ['SPAM'] = 'spam' with mock.patch.object(sys, 'argv', ['-c', 'pass']): autoreload.restart_with_reloader() + + @unittest.skipUnless(six.PY2 and sys.platform == 'win32', 'This is a Python 2 + Windows-specific issue.') + def test_environment_decoding(self): + """The system encoding is used for decoding.""" + os.environ['SPAM'] = 'spam' + os.environ['EGGS'] = b'\xc6u vi komprenas?' + with mock.patch('locale.getdefaultlocale') as default_locale: + # Latin-3 is the correct mapping. + default_locale.return_value = ('eo', 'latin3') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() + # CP1252 interprets latin3's C circumflex as AE ligature. + # It's incorrect but doesn't raise an error. + default_locale.return_value = ('en_US', 'cp1252') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() + # Interpreting the string as UTF-8 is fatal. + with self.assertRaises(UnicodeDecodeError): + default_locale.return_value = ('en_US', 'utf-8') + with mock.patch.object(sys, 'argv', ['-c', 'pass']): + autoreload.restart_with_reloader() From 20c03399d8fd03484f3ed33d93691c29c2ff5aaf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 30 Aug 2017 10:06:10 -0400 Subject: [PATCH 149/389] [1.11.x] Fixed #27998, #28543 -- Restored logging of ManyToManyField changes in admin's object history. And prevented ManyToManyField initial data in model forms from being affected by subsequent model changes. Regression in 56a55566a791a11420fe96f745b7489e756fc931. Partial backport of e5bd585c6eb1e13e2f8aac030b33c077b0b70c05 and 15b465c584f49a1d43b6c18796f83521ee4ffc22 from master --- AUTHORS | 1 + django/forms/models.py | 5 +++++ docs/releases/1.11.5.txt | 5 +++++ tests/admin_views/admin.py | 17 +++++++++-------- tests/admin_views/models.py | 7 +++++++ tests/admin_views/tests.py | 24 ++++++++++++++++++------ tests/model_forms/tests.py | 15 +++++++++++++++ 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/AUTHORS b/AUTHORS index cf4a5594278e..d1280eb6c914 100644 --- a/AUTHORS +++ b/AUTHORS @@ -459,6 +459,7 @@ answer newbie questions, and generally made Django that much better: Lex Berezhny Liang Feng limodou + Lincoln Smith Loek van Gent Loïc Bistuer Lowe Thiderman diff --git a/django/forms/models.py b/django/forms/models.py index db710a2ed287..0e80e19042c3 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -84,6 +84,7 @@ def model_to_dict(instance, fields=None, exclude=None): fields will be excluded from the returned dict, even if they are listed in the ``fields`` argument. """ + from django.db import models opts = instance._meta data = {} for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many): @@ -94,6 +95,10 @@ def model_to_dict(instance, fields=None, exclude=None): if exclude and f.name in exclude: continue data[f.name] = f.value_from_object(instance) + # Evaluate ManyToManyField QuerySets to prevent subsequent model + # alteration of that field from being reflected in the data. + if isinstance(f, models.ManyToManyField): + data[f.name] = list(data[f.name]) return data diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 91620eb74068..cb9e4662484a 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -35,3 +35,8 @@ Bugfixes * Fixed a regression in 1.11.4 where ``runserver`` crashed with non-Unicode system encodings on Python 2 + Windows (:ticket:`28487`). + +* Fixed a regression in Django 1.10 where changes to a ``ManyToManyField`` + weren't logged in the admin change history (:ticket:`27998`) and prevented + ``ManyToManyField`` initial data in model forms from being affected by + subsequent model changes (:ticket:`28543`). diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index b5e0ff481436..5d02f0f37d1d 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -38,14 +38,14 @@ OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture, Pizza, Plot, PlotDetails, PlotProxy, PluggableSearchPerson, Podcast, Post, PrePopulatedPost, - PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, Recipe, - Recommendation, Recommender, ReferencedByGenRel, ReferencedByInline, - ReferencedByParent, RelatedPrepopulated, RelatedWithUUIDPKModel, Report, - Reservation, Restaurant, RowLevelChangePermissionModel, Section, - ShortMessage, Simple, Sketch, State, Story, StumpJoke, Subscriber, - SuperVillain, Telegram, Thing, Topping, UnchangeableObject, - UndeletableObject, UnorderedObject, UserMessenger, Villain, Vodcast, - Whatsit, Widget, Worker, WorkHour, + PrePopulatedPostLargeSlug, PrePopulatedSubPost, Promo, Question, + ReadablePizza, Recipe, Recommendation, Recommender, ReferencedByGenRel, + ReferencedByInline, ReferencedByParent, RelatedPrepopulated, + RelatedWithUUIDPKModel, Report, Reservation, Restaurant, + RowLevelChangePermissionModel, Section, ShortMessage, Simple, Sketch, + State, Story, StumpJoke, Subscriber, SuperVillain, Telegram, Thing, + Topping, UnchangeableObject, UndeletableObject, UnorderedObject, + UserMessenger, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour, ) @@ -988,6 +988,7 @@ def get_formsets_with_inlines(self, request, obj=None): site.register(Promo) site.register(ChapterXtra1, ChapterXtra1Admin) site.register(Pizza, PizzaAdmin) +site.register(ReadablePizza) site.register(Topping, ToppingAdmin) site.register(Album, AlbumAdmin) site.register(Question) diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py index 9c7eee754759..29d96474c69b 100644 --- a/tests/admin_views/models.py +++ b/tests/admin_views/models.py @@ -611,6 +611,13 @@ class Pizza(models.Model): toppings = models.ManyToManyField('Topping', related_name='pizzas') +# Pizza's ModelAdmin has readonly_fields = ['toppings']. +# toppings is editable for this model's admin. +class ReadablePizza(Pizza): + class Meta: + proxy = True + + class Album(models.Model): owner = models.ForeignKey(User, models.SET_NULL, null=True, blank=True) title = models.CharField(max_length=30) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index febb20914c5d..efddc49de7e7 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -57,12 +57,13 @@ MainPrepopulated, Media, ModelWithStringPrimaryKey, OtherStory, Paper, Parent, ParentWithDependentChildren, ParentWithUUIDPK, Person, Persona, Picture, Pizza, Plot, PlotDetails, PluggableSearchPerson, Podcast, Post, - PrePopulatedPost, Promo, Question, Recommendation, Recommender, - RelatedPrepopulated, RelatedWithUUIDPKModel, Report, Restaurant, - RowLevelChangePermissionModel, SecretHideout, Section, ShortMessage, - Simple, State, Story, Subscriber, SuperSecretHideout, SuperVillain, - Telegram, TitleTranslation, Topping, UnchangeableObject, UndeletableObject, - UnorderedObject, Villain, Vodcast, Whatsit, Widget, Worker, WorkHour, + PrePopulatedPost, Promo, Question, ReadablePizza, Recommendation, + Recommender, RelatedPrepopulated, RelatedWithUUIDPKModel, Report, + Restaurant, RowLevelChangePermissionModel, SecretHideout, Section, + ShortMessage, Simple, State, Story, Subscriber, SuperSecretHideout, + SuperVillain, Telegram, TitleTranslation, Topping, UnchangeableObject, + UndeletableObject, UnorderedObject, Villain, Vodcast, Whatsit, Widget, + Worker, WorkHour, ) @@ -879,6 +880,17 @@ def test_change_view_with_show_delete_extra_context(self): response = self.client.get(reverse('admin:admin_views_undeletableobject_change', args=(instance.pk,))) self.assertNotContains(response, 'deletelink') + def test_change_view_logs_m2m_field_changes(self): + """Changes to ManyToManyFields are included in the object's history.""" + pizza = ReadablePizza.objects.create(name='Cheese') + cheese = Topping.objects.create(name='cheese') + post_data = {'name': pizza.name, 'toppings': [cheese.pk]} + response = self.client.post(reverse('admin:admin_views_readablepizza_change', args=(pizza.pk,)), post_data) + self.assertRedirects(response, reverse('admin:admin_views_readablepizza_changelist')) + pizza_ctype = ContentType.objects.get_for_model(ReadablePizza, for_concrete_model=False) + log = LogEntry.objects.filter(content_type=pizza_ctype, object_id=pizza.pk).first() + self.assertEqual(log.get_change_message(), 'Changed toppings.') + def test_allows_attributeerror_to_bubble_up(self): """ AttributeErrors are allowed to bubble when raised inside a change list diff --git a/tests/model_forms/tests.py b/tests/model_forms/tests.py index f3317f59de7a..b14e0e238904 100644 --- a/tests/model_forms/tests.py +++ b/tests/model_forms/tests.py @@ -3142,3 +3142,18 @@ def test_setattr_raises_validation_error_non_field(self): '__all__': ['Cannot set attribute'], 'title': ['This field cannot be blank.'] }) + + +class ModelToDictTests(TestCase): + def test_many_to_many(self): + """Data for a ManyToManyField is a list rather than a lazy QuerySet.""" + blue = Colour.objects.create(name='blue') + red = Colour.objects.create(name='red') + item = ColourfulItem.objects.create() + item.colours.set([blue]) + data = model_to_dict(item)['colours'] + self.assertEqual(data, [blue]) + item.colours.set([red]) + # If data were a QuerySet, it would be reevaluated here and give "red" + # instead of the original value. + self.assertEqual(data, [blue]) From c51fdda7762083fc3b97b56baa4f6b65398cec1b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 31 Aug 2017 11:57:46 -0400 Subject: [PATCH 150/389] [1.11.x] Refs #23276 -- Fixed explanation of how calling views works. "Importing the view" is no longer applicable after a9fd740d22bc4fed5fdb280c036618000ee13df1. Backport of 907580557053085578d8de0e1b7309e0e0ed8755 from master --- docs/intro/overview.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index 011d1695a06b..608faf4a8753 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -209,9 +209,9 @@ matches the requested URL. (If none of them matches, Django calls a special-case 404 view.) This is blazingly fast, because the regular expressions are compiled at load time. -Once one of the regexes matches, Django imports and calls the given view, which -is a simple Python function. Each view gets passed a request object -- -which contains request metadata -- and the values captured in the regex. +Once one of the regexes matches, Django calls the given view, which is a Python +function. Each view gets passed a request object -- which contains request +metadata -- and the values captured in the regex. For example, if a user requested the URL "/articles/2005/05/39323/", Django would call the function ``news.views.article_detail(request, From 6346d64873493f0a56879eabc566d0f6e501a0cb Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Wed, 23 Aug 2017 11:11:49 +0100 Subject: [PATCH 151/389] [1.11.x] Made GeoIP docs headers consistent with other GIS docs. Backport of 49017dc13a20ad88c8df2375617287ee445c5d03 from master --- docs/ref/contrib/gis/geoip2.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/gis/geoip2.txt b/docs/ref/contrib/gis/geoip2.txt index 3870556260e3..a7bd773e6fbe 100644 --- a/docs/ref/contrib/gis/geoip2.txt +++ b/docs/ref/contrib/gis/geoip2.txt @@ -47,8 +47,8 @@ Here is an example of its usage:: >>> g.geos('24.124.1.80').wkt 'POINT (-97 38)' -``GeoIP`` Settings -================== +Settings +======== .. setting:: GEOIP_PATH @@ -75,7 +75,7 @@ The basename to use for the GeoIP country data file. Defaults to The basename to use for the GeoIP city data file. Defaults to ``'GeoLite2-City.mmdb'``. -``GeoIP`` API +API Reference ============= .. class:: GeoIP2(path=None, cache=0, country=None, city=None) @@ -110,8 +110,8 @@ Keyword Arguments Description the :setting:`GEOIP_CITY` setting. =================== ======================================================= -``GeoIP`` Methods -================= +Methods +======= Instantiating ------------- From 511dfb336fc271e538f714b7a9bdb0b375924b53 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Wed, 23 Aug 2017 11:25:42 +0100 Subject: [PATCH 152/389] [1.11.x] Reordered GeoIP docs be consistent with GDAL/GEOS ordering. Backport of cbb27d603b33192a4bb4bd506747c33084620d1a from master --- docs/ref/contrib/gis/geoip2.txt | 56 ++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/docs/ref/contrib/gis/geoip2.txt b/docs/ref/contrib/gis/geoip2.txt index a7bd773e6fbe..2c6ec4f03d4d 100644 --- a/docs/ref/contrib/gis/geoip2.txt +++ b/docs/ref/contrib/gis/geoip2.txt @@ -47,34 +47,6 @@ Here is an example of its usage:: >>> g.geos('24.124.1.80').wkt 'POINT (-97 38)' -Settings -======== - -.. setting:: GEOIP_PATH - -``GEOIP_PATH`` --------------- - -A string specifying the directory where the GeoIP data files are -located. This setting is *required* unless manually specified -with ``path`` keyword when initializing the :class:`GeoIP2` object. - -.. setting:: GEOIP_COUNTRY - -``GEOIP_COUNTRY`` ------------------ - -The basename to use for the GeoIP country data file. Defaults to -``'GeoLite2-Country.mmdb'``. - -.. setting:: GEOIP_CITY - -``GEOIP_CITY`` --------------- - -The basename to use for the GeoIP city data file. Defaults to -``'GeoLite2-City.mmdb'``. - API Reference ============= @@ -167,5 +139,33 @@ Returns a coordinate tuple of (latitude, longitude), Returns a :class:`~django.contrib.gis.geos.Point` object corresponding to the query. +Settings +======== + +.. setting:: GEOIP_PATH + +``GEOIP_PATH`` +-------------- + +A string specifying the directory where the GeoIP data files are +located. This setting is *required* unless manually specified +with ``path`` keyword when initializing the :class:`GeoIP2` object. + +.. setting:: GEOIP_COUNTRY + +``GEOIP_COUNTRY`` +----------------- + +The basename to use for the GeoIP country data file. Defaults to +``'GeoLite2-Country.mmdb'``. + +.. setting:: GEOIP_CITY + +``GEOIP_CITY`` +-------------- + +The basename to use for the GeoIP city data file. Defaults to +``'GeoLite2-City.mmdb'``. + .. rubric:: Footnotes .. [#] GeoIP(R) is a registered trademark of MaxMind, Inc. From 8d66bffbae8e5a230da51c7638d24fdbd327a96b Mon Sep 17 00:00:00 2001 From: Bo Marchman Date: Thu, 2 Mar 2017 09:36:25 -0500 Subject: [PATCH 153/389] [1.11.x] Fixed #26522 -- Fixed a nondeterministic AssertionError in QuerySet combining. Thanks Andrew Brown for the test case. Backport of 9bbb6e2d2536c4ac20dc13a94c1f80494e51f8d9 from master --- AUTHORS | 1 + django/db/models/sql/query.py | 2 +- docs/releases/1.11.5.txt | 3 +++ tests/queries/models.py | 5 +++++ tests/queries/tests.py | 18 +++++++++++++++--- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index d1280eb6c914..1479bb878654 100644 --- a/AUTHORS +++ b/AUTHORS @@ -110,6 +110,7 @@ answer newbie questions, and generally made Django that much better: berto Bill Fenner Bjørn Stabell + Bo Marchman Bojan Mihelac Bouke Haarsma Božidar Benko diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index d1c4e5446ba3..e51b1037ca30 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -133,7 +133,7 @@ def __init__(self, model, where=WhereNode): # types they are. The key is the alias of the joined table (possibly # the table name) and the value is a Join-like object (see # sql.datastructures.Join for more information). - self.alias_map = {} + self.alias_map = OrderedDict() # Sometimes the query contains references to aliases in outer queries (as # a result of split_exclude). Correct alias quoting needs to know these # aliases too. diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index cb9e4662484a..139371d90e7a 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -40,3 +40,6 @@ Bugfixes weren't logged in the admin change history (:ticket:`27998`) and prevented ``ManyToManyField`` initial data in model forms from being affected by subsequent model changes (:ticket:`28543`). + +* Fixed non-deterministic results or an ``AssertionError`` crash in some + queries with multiple joins (:ticket:`26522`). diff --git a/tests/queries/models.py b/tests/queries/models.py index ab03b9c24816..fd76623c330d 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -709,6 +709,11 @@ class Classroom(models.Model): students = models.ManyToManyField(Student, related_name='classroom') +class Teacher(models.Model): + schools = models.ManyToManyField(School) + friends = models.ManyToManyField('self') + + class Ticket23605AParent(models.Model): pass diff --git a/tests/queries/tests.py b/tests/queries/tests.py index 6633985f8174..877cf8091eb8 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -28,9 +28,9 @@ ProxyCategory, ProxyObjectA, ProxyObjectB, Ranking, Related, RelatedIndividual, RelatedObject, Report, ReportComment, ReservedName, Responsibility, School, SharedConnection, SimpleCategory, SingleObject, - SpecialCategory, Staff, StaffUser, Student, Tag, Task, Ticket21203Child, - Ticket21203Parent, Ticket23605A, Ticket23605B, Ticket23605C, TvChef, Valid, - X, + SpecialCategory, Staff, StaffUser, Student, Tag, Task, Teacher, + Ticket21203Child, Ticket21203Parent, Ticket23605A, Ticket23605B, + Ticket23605C, TvChef, Valid, X, ) @@ -1391,6 +1391,18 @@ def test_combine_join_reuse(self): self.assertEqual(len(combined), 1) self.assertEqual(combined[0].name, 'a1') + def test_join_reuse_order(self): + # Join aliases are reused in order. This shouldn't raise AssertionError + # because change_map contains a circular reference (#26522). + s1 = School.objects.create() + s2 = School.objects.create() + s3 = School.objects.create() + t1 = Teacher.objects.create() + otherteachers = Teacher.objects.exclude(pk=t1.pk).exclude(friends=t1) + qs1 = otherteachers.filter(schools=s1).filter(schools=s2) + qs2 = otherteachers.filter(schools=s1).filter(schools=s3) + self.assertQuerysetEqual(qs1 | qs2, []) + def test_ticket7095(self): # Updates that are filtered on the model being updated are somewhat # tricky in MySQL. From d236f30237357d43530266d067d227225e0f5e77 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 1 Sep 2017 13:46:51 -0400 Subject: [PATCH 154/389] [1.11.x] Fixed #28557 -- Fixed ForeignKey/OneToOneField/ManyToManyField argument name in docs. Backport of 6e4a34580d05ca8036c2bc1f7a53558cdc0cc77f from master --- docs/ref/models/fields.txt | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 487de45641bc..f20dfcaefc2c 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1154,10 +1154,12 @@ Django also defines a set of fields that represent relations. ``ForeignKey`` -------------- -.. class:: ForeignKey(othermodel, on_delete, **options) +.. class:: ForeignKey(to, on_delete, **options) -A many-to-one relationship. Requires a positional argument: the class to which -the model is related. +A many-to-one relationship. Requires two positional arguments: the class to +which the model is related and the :attr:`~ForeignKey.on_delete` option. +(``on_delete`` isn't actually required, but not providing it gives a +deprecation warning. It will be required in Django 2.0.) .. _recursive-relationships: @@ -1452,7 +1454,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in ``ManyToManyField`` ------------------- -.. class:: ManyToManyField(othermodel, **options) +.. class:: ManyToManyField(to, **options) A many-to-many relationship. Requires a positional argument: the class to which the model is related, which works exactly the same as it does for @@ -1655,7 +1657,7 @@ relationship at the database level. ``OneToOneField`` ----------------- -.. class:: OneToOneField(othermodel, on_delete, parent_link=False, **options) +.. class:: OneToOneField(to, on_delete, parent_link=False, **options) A one-to-one relationship. Conceptually, this is similar to a :class:`ForeignKey` with :attr:`unique=True `, but the From b9436d1ba8a0398efcc37b0cca481e7d129c68b1 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 2 Sep 2017 16:09:07 +0300 Subject: [PATCH 155/389] [1.11.x] Fixed typos in docs/releases/1.10.txt. Backport of 90fcf0fce7a9029ae0993eec7020ac23834fc328 from master --- docs/releases/1.10.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index daf244e45735..9673bc9a4790 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -225,7 +225,7 @@ Minor features not worry about whether or not the ``staticfiles`` app is installed. * You can :ref:`more easily customize ` - the ``collectstatic --ignore_patterns`` option with a custom ``AppConfig``. + the ``collectstatic --ignore`` option with a custom ``AppConfig``. Cache ~~~~~ @@ -450,7 +450,7 @@ Requests and Responses :meth:`~django.http.HttpResponse.seekable()` to make an instance a stream-like object and allow wrapping it with :py:class:`io.TextIOWrapper`. -* Added the :attr:`HttpResponse.content_type +* Added the :attr:`HttpRequest.content_type ` and :attr:`~django.http.HttpRequest.content_params` attributes which are parsed from the ``CONTENT_TYPE`` header. From 1739ef78553361e7d13e19ab78004bff993fe9c2 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Thu, 18 May 2017 14:12:28 +0100 Subject: [PATCH 156/389] [1.11.x] Fixed #28525 -- Documented GDAL and GeoIP exceptions. Backport of 11f4c52ec74ea5244bc5988f37cbfdce2586b642 from master --- docs/ref/contrib/gis/gdal.txt | 12 ++++++++++++ docs/ref/contrib/gis/geoip2.txt | 8 ++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index 1047d5851702..f847b46fcb84 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -1683,3 +1683,15 @@ Settings A string specifying the location of the GDAL library. Typically, this setting is only used if the GDAL library is in a non-standard location (e.g., ``/home/john/lib/libgdal.so``). + +Exceptions +========== + +.. exception:: GDALException + + The base GDAL exception, indicating a GDAL-related error. + +.. exception:: SRSException + + An exception raised when an error occurs when constructing or using a + spatial reference system object. diff --git a/docs/ref/contrib/gis/geoip2.txt b/docs/ref/contrib/gis/geoip2.txt index 2c6ec4f03d4d..e059dfabebd3 100644 --- a/docs/ref/contrib/gis/geoip2.txt +++ b/docs/ref/contrib/gis/geoip2.txt @@ -167,5 +167,13 @@ The basename to use for the GeoIP country data file. Defaults to The basename to use for the GeoIP city data file. Defaults to ``'GeoLite2-City.mmdb'``. +Exceptions +========== + +.. exception:: GeoIP2Exception + + The exception raised when an error occurs in a call to the underlying + ``geoip2`` library. + .. rubric:: Footnotes .. [#] GeoIP(R) is a registered trademark of MaxMind, Inc. From ff0b81f3a6d7454a614491a1a39cb5360264fcec Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 22 Aug 2017 12:58:57 +0100 Subject: [PATCH 157/389] [1.11.x] Fixed typo in ModelAdmin action logging test. --- tests/modeladmin/tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/modeladmin/tests.py b/tests/modeladmin/tests.py index 1f5d8a32364f..455a52215482 100644 --- a/tests/modeladmin/tests.py +++ b/tests/modeladmin/tests.py @@ -586,7 +586,7 @@ def test_log_actions(self): mock_request.user = User.objects.create(username='bill') self.assertEqual(ma.log_addition(mock_request, self.band, 'added'), LogEntry.objects.latest('id')) self.assertEqual(ma.log_change(mock_request, self.band, 'changed'), LogEntry.objects.latest('id')) - self.assertEqual(ma.log_change(mock_request, self.band, 'deleted'), LogEntry.objects.latest('id')) + self.assertEqual(ma.log_deletion(mock_request, self.band, 'deleted'), LogEntry.objects.latest('id')) class ModelAdminPermissionTests(SimpleTestCase): From 07f73daf6ba2994e572776edbbf3266f2f09d7a5 Mon Sep 17 00:00:00 2001 From: Simon Meers Date: Tue, 15 Aug 2017 10:48:51 +1000 Subject: [PATCH 158/389] [1.11.x] Fixed #17985 -- Documented ModelAdmin.lookup_allowed(). Backport of 60443e84b38ea3a143b0ef9c05b1e1f39d91ddb5 from master --- docs/ref/contrib/admin/index.txt | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 40acb7dd2966..49777b2fcae6 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1756,6 +1756,31 @@ templates used by the :class:`ModelAdmin` views: kwargs['formset'] = MyAdminFormSet return super(MyModelAdmin, self).get_changelist_formset(request, **kwargs) +.. method:: ModelAdmin.lookup_allowed(lookup, value) + + The objects in the changelist page can be filtered with lookups from the + URL's query string. This is how :attr:`list_filter` works, for example. The + lookups are similar to what's used in :meth:`.QuerySet.filter` (e.g. + ``user__email=user@example.com``). Since the lookups in the query string + can be manipulated by the user, they must be sanitized to prevent + unauthorized data exposure. + + The ``lookup_allowed()`` method is given a lookup path from the query string + (e.g. ``'user__email'``) and the corresponding value + (e.g. ``'user@example.com'``), and returns a boolean indicating whether + filtering the changelist's ``QuerySet`` using the parameters is permitted. + If ``lookup_allowed()`` returns ``False``, ``DisallowedModelAdminLookup`` + (subclass of :exc:`~django.core.exceptions.SuspiciousOperation`) is raised. + + By default, ``lookup_allowed()`` allows access to a model's local fields, + field paths used in :attr:`~ModelAdmin.list_filter` (but not paths from + :meth:`~ModelAdmin.get_list_filter`), and lookups required for + :attr:`~django.db.models.ForeignKey.limit_choices_to` to function + correctly in :attr:`~django.contrib.admin.ModelAdmin.raw_id_fields`. + + Override this method to customize the lookups permitted for your + :class:`~django.contrib.admin.ModelAdmin` subclass. + .. method:: ModelAdmin.has_add_permission(request) Should return ``True`` if adding an object is permitted, ``False`` From e921e983876b62f5b8f405e0b8afcdcc1f53d8bb Mon Sep 17 00:00:00 2001 From: Jeremy Satterfield Date: Thu, 17 Aug 2017 13:06:29 -0500 Subject: [PATCH 159/389] [1.11.x] Fixed #28332 -- Fixed diamond inheritence example in docs. Backport of 473ab4610ef90be05f09127aa37cd20bcda5875e from master --- docs/topics/db/models.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 6ee07f712a5c..f4034f6ba762 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -1359,15 +1359,20 @@ use an explicit :class:`~django.db.models.AutoField` in the base models:: class BookReview(Book, Article): pass -Or use a common ancestor to hold the :class:`~django.db.models.AutoField`:: +Or use a common ancestor to hold the :class:`~django.db.models.AutoField`. This +requires using an explicit :class:`~django.db.models.OneToOneField` from each +parent model to the common ancestor to avoid a clash between the fields that +are automatically generated and inherited by the child:: class Piece(models.Model): pass class Article(Piece): + article_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True) ... class Book(Piece): + book_piece = models.OneToOneField(Piece, on_delete=models.CASCADE, parent_link=True) ... class BookReview(Book, Article): From f9db06cf0812ee017b34bc786e1bf96e40f2327b Mon Sep 17 00:00:00 2001 From: jkrzy Date: Fri, 18 Aug 2017 13:52:06 -0700 Subject: [PATCH 160/389] [1.11.x] Fixed #28367 -- Doc'd how to override management commands. Backport of 48d92fea672928b4571ddaab03667e74671391c0 from master --- docs/howto/custom-management-commands.txt | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index e6482a0da823..5a091f835eaa 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -186,6 +186,24 @@ Testing Information on how to test custom management commands can be found in the :ref:`testing docs `. +Overriding commands +=================== + +Django registers the built-in commands and then searches for commands in +:setting:`INSTALLED_APPS` in reverse. During the search, if a command name +duplicates an already registered command, the newly discovered command +overrides the first. + +In other words, to override a command, the new command must have the same name +and its app must be before the overridden command's app in +:setting:`INSTALLED_APPS`. + +Management commands from third-party apps that have been unintentionally +overridden can be made available under a new name by creating a new command in +one of your project's apps (ordered before the third-party app in +:setting:`INSTALLED_APPS`) which imports the ``Command`` of the overridden +command. + Command objects =============== From f8e0557b01ebbb11478cfb012c4cafc67f1213c1 Mon Sep 17 00:00:00 2001 From: Zach Liu Date: Sun, 3 Sep 2017 12:05:18 -0400 Subject: [PATCH 161/389] [1.11.x] Fixed #28550 -- Restored contrib.auth's login() and logout() views' respect of positional arguments. Regression in 78963495d0caadb77eb97ccf319ef0ba3b204fb5. --- AUTHORS | 1 + django/contrib/auth/views.py | 25 ++++++++++-- docs/releases/1.11.5.txt | 3 ++ tests/auth_tests/test_deprecated_views.py | 48 ++++++++++++++++++++++- 4 files changed, 72 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index 1479bb878654..c2a55095f239 100644 --- a/AUTHORS +++ b/AUTHORS @@ -816,6 +816,7 @@ answer newbie questions, and generally made Django that much better: Yoong Kang Lim Yusuke Miyazaki Zachary Voase + Zach Liu Zach Thompson Zain Memon Zak Johnson diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index f8934e9edb26..aa3ca10dfb2f 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -133,12 +133,21 @@ def get_context_data(self, **kwargs): @deprecate_current_app -def login(request, *args, **kwargs): +def login(request, template_name='registration/login.html', + redirect_field_name=REDIRECT_FIELD_NAME, + authentication_form=AuthenticationForm, + extra_context=None, redirect_authenticated_user=False): warnings.warn( 'The login() view is superseded by the class-based LoginView().', RemovedInDjango21Warning, stacklevel=2 ) - return LoginView.as_view(**kwargs)(request, *args, **kwargs) + return LoginView.as_view( + template_name=template_name, + redirect_field_name=redirect_field_name, + form_class=authentication_form, + extra_context=extra_context, + redirect_authenticated_user=redirect_authenticated_user, + )(request) class LogoutView(SuccessURLAllowedHostsMixin, TemplateView): @@ -202,12 +211,20 @@ def get_context_data(self, **kwargs): @deprecate_current_app -def logout(request, *args, **kwargs): +def logout(request, next_page=None, + template_name='registration/logged_out.html', + redirect_field_name=REDIRECT_FIELD_NAME, + extra_context=None): warnings.warn( 'The logout() view is superseded by the class-based LogoutView().', RemovedInDjango21Warning, stacklevel=2 ) - return LogoutView.as_view(**kwargs)(request, *args, **kwargs) + return LogoutView.as_view( + next_page=next_page, + template_name=template_name, + redirect_field_name=redirect_field_name, + extra_context=extra_context, + )(request) _sentinel = object() diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 139371d90e7a..5fc3afe9b4f4 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -43,3 +43,6 @@ Bugfixes * Fixed non-deterministic results or an ``AssertionError`` crash in some queries with multiple joins (:ticket:`26522`). + +* Fixed a regression in ``contrib.auth``'s ``login()`` and ``logout()`` views + where they ignored positional arguments (:ticket:`28550`). diff --git a/tests/auth_tests/test_deprecated_views.py b/tests/auth_tests/test_deprecated_views.py index 542833686a85..f8d5ede5ca0d 100644 --- a/tests/auth_tests/test_deprecated_views.py +++ b/tests/auth_tests/test_deprecated_views.py @@ -11,9 +11,10 @@ AuthenticationForm, PasswordChangeForm, SetPasswordForm, ) from django.contrib.auth.models import User +from django.contrib.auth.views import login, logout from django.core import mail from django.http import QueryDict -from django.test import TestCase, override_settings +from django.test import RequestFactory, TestCase, override_settings from django.test.utils import ignore_warnings, patch_logger from django.utils.deprecation import RemovedInDjango21Warning from django.utils.encoding import force_text @@ -444,3 +445,48 @@ def test_user_password_change_updates_session(self): }) # if the hash isn't updated, retrieving the redirection page will fail. self.assertRedirects(response, '/password_change/done/') + + +@ignore_warnings(category=RemovedInDjango21Warning) +class TestLogin(TestCase): + def setUp(self): + self.factory = RequestFactory() + self.request = self.factory.get('/') + + def test_template_name(self): + response = login(self.request, 'template.html') + self.assertEqual(response.template_name, ['template.html']) + + def test_form_class(self): + class NewForm(AuthenticationForm): + def confirm_login_allowed(self, user): + pass + response = login(self.request, 'template.html', None, NewForm) + self.assertEqual(response.context_data['form'].__class__.__name__, 'NewForm') + + def test_extra_context(self): + extra_context = {'fake_context': 'fake_context'} + response = login(self.request, 'template.html', None, AuthenticationForm, extra_context) + self.assertEqual(response.resolve_context('fake_context'), 'fake_context') + + +@ignore_warnings(category=RemovedInDjango21Warning) +class TestLogout(AuthViewsTestCase): + def setUp(self): + self.login() + self.factory = RequestFactory() + self.request = self.factory.post('/') + self.request.session = self.client.session + + def test_template_name(self): + response = logout(self.request, None, 'template.html') + self.assertEqual(response.template_name, ['template.html']) + + def test_next_page(self): + response = logout(self.request, 'www.next_page.com') + self.assertEqual(response.url, 'www.next_page.com') + + def test_extra_context(self): + extra_context = {'fake_context': 'fake_context'} + response = logout(self.request, None, 'template.html', None, extra_context) + self.assertEqual(response.resolve_context('fake_context'), 'fake_context') From 1db5e687a7df6921ec4ee4ac7490008a922fe94d Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Mon, 4 Sep 2017 15:40:07 +0300 Subject: [PATCH 162/389] [1.11.x] Fixed typo in docs/releases/1.10.txt. Backport of d81c86d32c6b716e49288c22c80ceb6ee924531f from master --- docs/releases/1.10.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 9673bc9a4790..8ce6581a8507 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -908,7 +908,7 @@ Miscellaneous a new :meth:`.AbstractBaseUser.clean` method. * Private API ``django.forms.models.model_to_dict()`` returns a queryset rather - than a list of primary keys for ``ManyToManyField``\s . + than a list of primary keys for ``ManyToManyField``\s. * If ``django.contrib.staticfiles`` is installed, the :ttag:`static` template tag uses the ``staticfiles`` storage From ddea2166f1db776dadbc26751b8ceaba3cace6b5 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 4 Sep 2017 18:40:56 +0000 Subject: [PATCH 163/389] [1.11.x] Fixed #28568 -- Fixed typo in docs/ref/models/database-functions.txt. And made an example use naming consistent with the rest of the doc. Backport of 3f2b1d926bb3a72b4c3d2cd958455ebb9b4ca458 from master --- docs/ref/models/database-functions.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index 0b0b0050fffc..72a208157b1c 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -486,8 +486,8 @@ the Melbourne timezone (UTC +10:00), which changes the day, weekday, and hour values that are returned:: >>> import pytz - >>> tzinfo = pytz.timezone('Australia/Melbourne') # UTC+10:00 - >>> with timezone.override(tzinfo): + >>> melb = pytz.timezone('Australia/Melbourne') # UTC+10:00 + >>> with timezone.override(melb): ... Experiment.objects.annotate( ... day=ExtractDay('start_datetime'), ... weekday=ExtractWeekDay('start_datetime'), @@ -501,7 +501,7 @@ Explicitly passing the timezone to the ``Extract`` function behaves in the same way, and takes priority over an active timezone:: >>> import pytz - >>> tzinfo = pytz.timezone('Australia/Melbourne') + >>> melb = pytz.timezone('Australia/Melbourne') >>> Experiment.objects.annotate( ... day=ExtractDay('start_datetime', tzinfo=melb), ... weekday=ExtractWeekDay('start_datetime', tzinfo=melb), From 02210393097b0183ad2a462dbdfb900e450327eb Mon Sep 17 00:00:00 2001 From: Jonatas CD Date: Mon, 4 Sep 2017 21:43:29 +0200 Subject: [PATCH 164/389] [1.11.x] Fixed #28479 -- Doc'd that transaction rollback doesn't revert model state. Backport of c9b22707b0703db6c6ddaebdd00e2cd33d182e40 from master --- docs/topics/db/transactions.txt | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/topics/db/transactions.txt b/docs/topics/db/transactions.txt index 0f028dd343b1..36391c92f49a 100644 --- a/docs/topics/db/transactions.txt +++ b/docs/topics/db/transactions.txt @@ -176,6 +176,29 @@ Django provides a single API to control database transactions. If you catch exceptions raised by raw SQL queries, Django's behavior is unspecified and database-dependent. + .. admonition:: You may need to manually revert model state when rolling back a transaction. + + The values of a model's fields won't be reverted when a transaction + rollback happens. This could lead to an inconsistent model state unless + you manually restore the original field values. + + For example, given ``MyModel`` with an ``active`` field, this snippet + ensures that the ``if obj.active`` check at the end uses the correct + value if updating ``active`` to ``True`` fails in the transaction:: + + from django.db import DatabaseError, transaction + + obj = MyModel(active=False) + obj.active = True + try: + with transaction.atomic(): + obj.save() + except DatabaseError: + obj.active = False + + if obj.active: + ... + In order to guarantee atomicity, ``atomic`` disables some APIs. Attempting to commit, roll back, or change the autocommit state of the database connection within an ``atomic`` block will raise an exception. From 56c445295d17cd7942e892c56524fdd0694bbefc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 9 Aug 2017 19:49:32 -0400 Subject: [PATCH 165/389] [1.11.x] Added stub release notes for security releases. --- docs/releases/1.10.8.txt | 7 +++++++ docs/releases/1.11.5.txt | 4 ++-- docs/releases/index.txt | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.10.8.txt diff --git a/docs/releases/1.10.8.txt b/docs/releases/1.10.8.txt new file mode 100644 index 000000000000..160e555fefd8 --- /dev/null +++ b/docs/releases/1.10.8.txt @@ -0,0 +1,7 @@ +=========================== +Django 1.10.8 release notes +=========================== + +*September 5, 2017* + +Django 1.10.8 fixes a security issue in 1.10.7. diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index 5fc3afe9b4f4..c0af25fb43d5 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -2,9 +2,9 @@ Django 1.11.5 release notes =========================== -*Under development* +*September 5, 2017* -Django 1.11.5 fixes several bugs in 1.11.4. +Django 1.11.5 fixes a security issue and several bugs in 1.11.4. Bugfixes ======== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6a6af01960b4..cac6bb938f69 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -38,6 +38,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.10.8 1.10.7 1.10.6 1.10.5 From e35a0c56086924f331e9422daa266e907a4784cc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 2 Aug 2017 16:22:35 -0400 Subject: [PATCH 166/389] [1.11.x] Fixed CVE-2017-12794 -- Fixed XSS possibility in traceback section of technical 500 debug page. This is a security fix. --- django/views/debug.py | 20 +++++++++----------- docs/releases/1.10.8.txt | 9 +++++++++ docs/releases/1.11.5.txt | 9 +++++++++ tests/view_tests/tests/py3_test_debug.py | 13 +++++++------ 4 files changed, 34 insertions(+), 17 deletions(-) diff --git a/django/views/debug.py b/django/views/debug.py index 57dbff225957..6db3cf52d0f2 100644 --- a/django/views/debug.py +++ b/django/views/debug.py @@ -774,38 +774,37 @@ def default_urlconf(request):

Traceback {% if not is_email %} Switch to copy-and-paste view{% endif %}

- {% autoescape off %}
    {% for frame in frames %} {% ifchanged frame.exc_cause %}{% if frame.exc_cause %}
  • {% if frame.exc_cause_explicit %} - The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception: + The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause of the following exception: {% else %} - During handling of the above exception ({{ frame.exc_cause }}), another exception occurred: + During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred: {% endif %}

  • {% endif %}{% endifchanged %}
  • - {{ frame.filename|escape }} in {{ frame.function|escape }} + {{ frame.filename }} in {{ frame.function }} {% if frame.context_line %}
    {% if frame.pre_context and not is_email %}
      {% for line in frame.pre_context %} -
    1. {{ line|escape }}
    2. +
    3. {{ line }}
    4. {% endfor %}
    {% endif %}
    1. -"""            """{{ frame.context_line|escape }}
      {% if not is_email %} ...{% endif %}
    +""" """{{ frame.context_line }}{% if not is_email %} ...{% endif %}
  • {% if frame.post_context and not is_email %}
      {% for line in frame.post_context %} -
    1. {{ line|escape }}
    2. +
    3. {{ line }}
    4. {% endfor %}
    {% endif %} @@ -830,7 +829,7 @@ def default_urlconf(request): {% for var in frame.vars|dictsort:0 %} - {{ var.0|force_escape }} + {{ var.0 }}
    {{ var.1 }}
    {% endfor %} @@ -841,7 +840,6 @@ def default_urlconf(request): {% endfor %}
- {% endautoescape %}
{% if not is_email %}
@@ -887,9 +885,9 @@ def default_urlconf(request): Traceback:{% for frame in frames %} {% ifchanged frame.exc_cause %}{% if frame.exc_cause %}{% if frame.exc_cause_explicit %} -The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception: +The above exception ({{ frame.exc_cause|force_escape }}) was the direct cause of the following exception: {% else %} -During handling of the above exception ({{ frame.exc_cause }}), another exception occurred: +During handling of the above exception ({{ frame.exc_cause|force_escape }}), another exception occurred: {% endif %}{% endif %}{% endifchanged %} File "{{ frame.filename|escape }}" in {{ frame.function|escape }} {% if frame.context_line %} {{ frame.lineno }}. {{ frame.context_line|escape }}{% endif %}{% endfor %} diff --git a/docs/releases/1.10.8.txt b/docs/releases/1.10.8.txt index 160e555fefd8..3785e6535aa7 100644 --- a/docs/releases/1.10.8.txt +++ b/docs/releases/1.10.8.txt @@ -5,3 +5,12 @@ Django 1.10.8 release notes *September 5, 2017* Django 1.10.8 fixes a security issue in 1.10.7. + +CVE-2017-12794: Possible XSS in traceback section of technical 500 debug page +============================================================================= + +In older versions, HTML autoescaping was disabled in a portion of the template +for the technical 500 debug page. Given the right circumstances, this allowed +a cross-site scripting attack. This vulnerability shouldn't affect most +production sites since you shouldn't run with ``DEBUG = True`` (which makes +this page accessible) in your production settings. diff --git a/docs/releases/1.11.5.txt b/docs/releases/1.11.5.txt index c0af25fb43d5..dda3489b93c0 100644 --- a/docs/releases/1.11.5.txt +++ b/docs/releases/1.11.5.txt @@ -6,6 +6,15 @@ Django 1.11.5 release notes Django 1.11.5 fixes a security issue and several bugs in 1.11.4. +CVE-2017-12794: Possible XSS in traceback section of technical 500 debug page +============================================================================= + +In older versions, HTML autoescaping was disabled in a portion of the template +for the technical 500 debug page. Given the right circumstances, this allowed +a cross-site scripting attack. This vulnerability shouldn't affect most +production sites since you shouldn't run with ``DEBUG = True`` (which makes +this page accessible) in your production settings. + Bugfixes ======== diff --git a/tests/view_tests/tests/py3_test_debug.py b/tests/view_tests/tests/py3_test_debug.py index 30201bae53f8..316179ae3e5a 100644 --- a/tests/view_tests/tests/py3_test_debug.py +++ b/tests/view_tests/tests/py3_test_debug.py @@ -9,6 +9,7 @@ import sys from django.test import RequestFactory, TestCase +from django.utils.safestring import mark_safe from django.views.debug import ExceptionReporter @@ -20,10 +21,10 @@ def test_reporting_of_nested_exceptions(self): request = self.rf.get('/test_view/') try: try: - raise AttributeError('Top level') + raise AttributeError(mark_safe('

Top level

')) except AttributeError as explicit: try: - raise ValueError('Second exception') from explicit + raise ValueError('

Second exception

') from explicit except ValueError: raise IndexError('Final exception') except Exception: @@ -37,9 +38,9 @@ def test_reporting_of_nested_exceptions(self): html = reporter.get_traceback_html() # Both messages are twice on page -- one rendered as html, # one as plain text (for pastebin) - self.assertEqual(2, html.count(explicit_exc.format("Top level"))) - self.assertEqual(2, html.count(implicit_exc.format("Second exception"))) + self.assertEqual(2, html.count(explicit_exc.format('<p>Top level</p>'))) + self.assertEqual(2, html.count(implicit_exc.format('<p>Second exception</p>'))) text = reporter.get_traceback_text() - self.assertIn(explicit_exc.format("Top level"), text) - self.assertIn(implicit_exc.format("Second exception"), text) + self.assertIn(explicit_exc.format('

Top level

'), text) + self.assertIn(implicit_exc.format('

Second exception

'), text) From 7e3f8e05eb56cbd01f50b811c551a7a79b250ef7 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 5 Sep 2017 11:12:15 -0400 Subject: [PATCH 167/389] [1.11.x] Bumped version for 1.11.5 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 64db32eabdd1..cb9b974f154c 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 5, 'alpha', 0) +VERSION = (1, 11, 5, 'final', 0) __version__ = get_version(VERSION) From 2edb94e1fc95d317818f878e9d76ac3b993a97b5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 5 Sep 2017 11:38:31 -0400 Subject: [PATCH 168/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index cb9b974f154c..cc27e92e6b85 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 5, 'final', 0) +VERSION = (1, 11, 6, 'alpha', 0) __version__ = get_version(VERSION) From c69051894cf31462bbc0f55ee0f2ee49e86c4a94 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 5 Sep 2017 12:09:44 -0400 Subject: [PATCH 169/389] [1.11.x] Added 2017-12794 to the security release archive. Backport of 79ae5811c7b06b6462f9411b6665241a4e98bedb from master --- docs/releases/security.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 0e92d6a185d5..910e6914c702 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -833,3 +833,15 @@ Versions affected * Django 1.10 `(patch) `__ * Django 1.9 `(patch) `__ * Django 1.8 `(patch) `__ + +September 5, 2017 - :cve:`2017-12794` +------------------------------------- + +Possible XSS in traceback section of technical 500 debug page. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 1.11 `(patch) `__ +* Django 1.10 `(patch) `__ From 312050df82919cb87180705c826302b7b9b5e367 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 5 Sep 2017 16:12:55 -0400 Subject: [PATCH 170/389] [1.11.x] Added stub release notes for 1.11.6. Backport of 1b86088f6f92d9c937b24f063141d0b8bfb39969 from master --- docs/releases/1.11.6.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.6.txt diff --git a/docs/releases/1.11.6.txt b/docs/releases/1.11.6.txt new file mode 100644 index 000000000000..4069c8297e42 --- /dev/null +++ b/docs/releases/1.11.6.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.6 release notes +=========================== + +*Expected October 2, 2017* + +Django 1.11.6 fixes several bugs in 1.11.5. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index cac6bb938f69..ad1970b715ec 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.6 1.11.5 1.11.4 1.11.3 From 1d1a56c59983ae40675aff2c737bdde8f988e5e9 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Tue, 5 Sep 2017 12:41:38 -0400 Subject: [PATCH 171/389] [1.11.x] Fixed #28555 -- Made CharField convert whitespace-only values to the empty_value when strip is enabled. Backport of 48c394a6fc2594891f766293afec8f86d63e1015 from master --- django/forms/fields.py | 7 +++--- docs/releases/1.11.6.txt | 3 ++- .../forms_tests/field_tests/test_charfield.py | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/django/forms/fields.py b/django/forms/fields.py index f2b5b77185dd..734907644a0f 100644 --- a/django/forms/fields.py +++ b/django/forms/fields.py @@ -233,11 +233,12 @@ def __init__(self, max_length=None, min_length=None, strip=True, empty_value='', def to_python(self, value): "Returns a Unicode object." + if value not in self.empty_values: + value = force_text(value) + if self.strip: + value = value.strip() if value in self.empty_values: return self.empty_value - value = force_text(value) - if self.strip: - value = value.strip() return value def widget_attrs(self, widget): diff --git a/docs/releases/1.11.6.txt b/docs/releases/1.11.6.txt index 4069c8297e42..cc8e1bfa59be 100644 --- a/docs/releases/1.11.6.txt +++ b/docs/releases/1.11.6.txt @@ -9,4 +9,5 @@ Django 1.11.6 fixes several bugs in 1.11.5. Bugfixes ======== -* ... +* Made the ``CharField`` form field convert whitespace-only values to the + ``empty_value`` when ``strip`` is enabled (:ticket:`28555`). diff --git a/tests/forms_tests/field_tests/test_charfield.py b/tests/forms_tests/field_tests/test_charfield.py index d8fa41b0739c..aea892ea8910 100644 --- a/tests/forms_tests/field_tests/test_charfield.py +++ b/tests/forms_tests/field_tests/test_charfield.py @@ -121,6 +121,29 @@ def test_charfield_strip(self): self.assertEqual(f.clean(' 1'), ' 1') self.assertEqual(f.clean('1 '), '1 ') + def test_strip_before_checking_empty(self): + """ + A whitespace-only value, ' ', is stripped to an empty string and then + converted to the empty value, None. + """ + f = CharField(required=False, empty_value=None) + self.assertIsNone(f.clean(' ')) + + def test_clean_non_string(self): + """CharField.clean() calls str(value) before stripping it.""" + class StringWrapper: + def __init__(self, v): + self.v = v + + def __str__(self): + return self.v + + value = StringWrapper(' ') + f1 = CharField(required=False, empty_value=None) + self.assertIsNone(f1.clean(value)) + f2 = CharField(strip=False) + self.assertEqual(f2.clean(value), ' ') + def test_charfield_disabled(self): f = CharField(disabled=True) self.assertWidgetRendersTo(f, '') From a86d95726bdf84efaba7f30c44edddf6c34d2c2e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 6 Sep 2017 19:21:38 -0400 Subject: [PATCH 172/389] [1.11.x] Fixed #28561 -- Removed inaccurate docs about QuerySet.order_by() and joins. As of ccbba98131ace3beb43790c65e8f4eee94e9631c, both examples don't use a join. Backport of 44a6c27fd461e1d2f37388c26c629f8f170e8722 from master --- docs/ref/models/querysets.txt | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 082861f61b7d..c9f3556f106b 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -312,15 +312,6 @@ identical to:: Entry.objects.order_by('blog__name') -It is also possible to order a queryset by a related field, without incurring -the cost of a JOIN, by referring to the ``_id`` of the related field:: - - # No Join - Entry.objects.order_by('blog_id') - - # Join - Entry.objects.order_by('blog__id') - You can also order by :doc:`query expressions ` by calling ``asc()`` or ``desc()`` on the expression:: From cee07ba0880e9d1396795e4823a7371da90cc5cc Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 17 Sep 2017 08:26:18 +0200 Subject: [PATCH 173/389] [1.11.x] Fixed #28597 -- Fixed crash with the name of a model's autogenerated primary key in an Index's fields. Backport of fb02ebe889eee292144f9157ed4ddcdcc139eba9 from master --- django/db/models/base.py | 13 +++++++------ docs/releases/1.11.6.txt | 3 +++ tests/model_indexes/models.py | 6 +++++- tests/model_indexes/tests.py | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/django/db/models/base.py b/django/db/models/base.py index 5067fb9e4f12..cf8f44919c22 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -306,12 +306,6 @@ def __new__(cls, name, bases, attrs): # Copy indexes so that index names are unique when models extend an # abstract model. new_class._meta.indexes = [copy.deepcopy(idx) for idx in new_class._meta.indexes] - # Set the name of _meta.indexes. This can't be done in - # Options.contribute_to_class() because fields haven't been added to - # the model at that point. - for index in new_class._meta.indexes: - if not index.name: - index.set_name_with_model(new_class) if abstract: # Abstract base models can't be instantiated and don't appear in @@ -371,6 +365,13 @@ def _prepare(cls): manager.auto_created = True cls.add_to_class('objects', manager) + # Set the name of _meta.indexes. This can't be done in + # Options.contribute_to_class() because fields haven't been added to + # the model at that point. + for index in cls._meta.indexes: + if not index.name: + index.set_name_with_model(cls) + class_prepared.send(sender=cls) def _requires_legacy_default_manager(cls): # RemovedInDjango20Warning diff --git a/docs/releases/1.11.6.txt b/docs/releases/1.11.6.txt index cc8e1bfa59be..ff9d4385fea1 100644 --- a/docs/releases/1.11.6.txt +++ b/docs/releases/1.11.6.txt @@ -11,3 +11,6 @@ Bugfixes * Made the ``CharField`` form field convert whitespace-only values to the ``empty_value`` when ``strip`` is enabled (:ticket:`28555`). + +* Fixed crash when using the name of a model's autogenerated primary key + (``id``) in an ``Index``'s ``fields`` (:ticket:`28597`). diff --git a/tests/model_indexes/models.py b/tests/model_indexes/models.py index 6d74ad8fa67f..0fa998843e11 100644 --- a/tests/model_indexes/models.py +++ b/tests/model_indexes/models.py @@ -5,9 +5,13 @@ class Book(models.Model): title = models.CharField(max_length=50) author = models.CharField(max_length=50) pages = models.IntegerField(db_column='page_count') + isbn = models.CharField(max_length=50, db_tablespace='idx_tbls') class Meta: - indexes = [models.indexes.Index(fields=['title'])] + indexes = [ + models.indexes.Index(fields=['title']), + models.indexes.Index(fields=['isbn', 'id']), + ] class AbstractModel(models.Model): diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index c0f5a84fdb56..74c7cf45f429 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -85,7 +85,7 @@ def test_clone(self): def test_name_set(self): index_names = [index.name for index in Book._meta.indexes] - self.assertEqual(index_names, ['model_index_title_196f42_idx']) + self.assertCountEqual(index_names, ['model_index_title_196f42_idx', 'model_index_isbn_34f975_idx']) def test_abstract_children(self): index_names = [index.name for index in ChildModel1._meta.indexes] From 979d2ebea613e763f409e280f0430e31f800b3f5 Mon Sep 17 00:00:00 2001 From: tk Date: Tue, 19 Sep 2017 05:52:01 -0500 Subject: [PATCH 174/389] [1.11.x] Fixed typo in docs/topics/cache.txt. Backport of e7adad27f30396823f3609fcb8699cefb25278bb from master --- docs/topics/cache.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index fc10025ce514..168b873b8358 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -895,7 +895,7 @@ from the cache, not just the keys set by your application. :: You can also increment or decrement a key that already exists using the ``incr()`` or ``decr()`` methods, respectively. By default, the existing cache -value will incremented or decremented by 1. Other increment/decrement values +value will be incremented or decremented by 1. Other increment/decrement values can be specified by providing an argument to the increment/decrement call. A ValueError will be raised if you attempt to increment or decrement a nonexistent cache key.:: From 19ea298aaff3ebecb26466f469226953d451e347 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Wed, 20 Sep 2017 15:52:14 -0400 Subject: [PATCH 175/389] [1.11.x] Initialized CsrfViewMiddleware once in csrf_tests. Backport of 77f82c4bf1565b074d12b1531caa4bc4f4b89506 from master --- tests/csrf_tests/tests.py | 107 +++++++++++++++++++------------------- 1 file changed, 54 insertions(+), 53 deletions(-) diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index 4480f5348e3f..e71fb4019322 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -45,6 +45,7 @@ class CsrfViewMiddlewareTestMixin(object): """ _csrf_id = _csrf_id_cookie = '1bcdefghij2bcdefghij3bcdefghij4bcdefghij5bcdefghij6bcdefghijABCD' + mw = CsrfViewMiddleware() def _get_GET_no_csrf_cookie_request(self): return TestingHttpRequest() @@ -89,9 +90,9 @@ def test_process_response_get_token_not_used(self): # does use the csrf request processor. By using this, we are testing # that the view processor is properly lazy and doesn't call get_token() # until needed. - CsrfViewMiddleware().process_view(req, non_token_view_using_request_processor, (), {}) + self.mw.process_view(req, non_token_view_using_request_processor, (), {}) resp = non_token_view_using_request_processor(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) self.assertIs(csrf_cookie, False) @@ -104,7 +105,7 @@ def test_process_request_no_csrf_cookie(self): """ with patch_logger('django.security.csrf', 'warning') as logger_calls: req = self._get_POST_no_csrf_cookie_request() - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_NO_CSRF_COOKIE) @@ -115,7 +116,7 @@ def test_process_request_csrf_cookie_no_token(self): """ with patch_logger('django.security.csrf', 'warning') as logger_calls: req = self._get_POST_csrf_cookie_request() - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN) @@ -124,7 +125,7 @@ def test_process_request_csrf_cookie_and_token(self): If both a cookie and a token is present, the middleware lets it through. """ req = self._get_POST_request_with_token() - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) def test_process_request_csrf_cookie_no_token_exempt_view(self): @@ -133,7 +134,7 @@ def test_process_request_csrf_cookie_no_token_exempt_view(self): has been applied to the view, the middleware lets it through """ req = self._get_POST_csrf_cookie_request() - req2 = CsrfViewMiddleware().process_view(req, csrf_exempt(post_form_view), (), {}) + req2 = self.mw.process_view(req, csrf_exempt(post_form_view), (), {}) self.assertIsNone(req2) def test_csrf_token_in_header(self): @@ -142,7 +143,7 @@ def test_csrf_token_in_header(self): """ req = self._get_POST_csrf_cookie_request() req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @override_settings(CSRF_HEADER_NAME='HTTP_X_CSRFTOKEN_CUSTOMIZED') @@ -152,7 +153,7 @@ def test_csrf_token_in_header_with_customized_name(self): """ req = self._get_POST_csrf_cookie_request() req.META['HTTP_X_CSRFTOKEN_CUSTOMIZED'] = self._csrf_id - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) def test_put_and_delete_rejected(self): @@ -162,14 +163,14 @@ def test_put_and_delete_rejected(self): req = TestingHttpRequest() req.method = 'PUT' with patch_logger('django.security.csrf', 'warning') as logger_calls: - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_NO_CSRF_COOKIE) req = TestingHttpRequest() req.method = 'DELETE' with patch_logger('django.security.csrf', 'warning') as logger_calls: - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_NO_CSRF_COOKIE) @@ -180,13 +181,13 @@ def test_put_and_delete_allowed(self): req = self._get_GET_csrf_cookie_request() req.method = 'PUT' req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) req = self._get_GET_csrf_cookie_request() req.method = 'DELETE' req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) # Tests for the template tag method @@ -207,7 +208,7 @@ def test_token_node_empty_csrf_cookie(self): """ req = self._get_GET_no_csrf_cookie_request() req.COOKIES[settings.CSRF_COOKIE_NAME] = b"" - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) token = get_token(req) @@ -219,7 +220,7 @@ def test_token_node_with_csrf_cookie(self): CsrfTokenNode works when a CSRF cookie is set. """ req = self._get_GET_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) self._check_token_present(resp) @@ -228,7 +229,7 @@ def test_get_token_for_exempt_view(self): get_token still works for a view decorated with 'csrf_exempt'. """ req = self._get_GET_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, csrf_exempt(token_view), (), {}) + self.mw.process_view(req, csrf_exempt(token_view), (), {}) resp = token_view(req) self._check_token_present(resp) @@ -246,9 +247,9 @@ def test_token_node_with_new_csrf_cookie(self): the middleware (when one was not already present) """ req = self._get_GET_no_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME] self._check_token_present(resp, csrf_id=csrf_cookie.value) @@ -259,9 +260,9 @@ def test_cookie_not_reset_on_accepted_request(self): requests. If it appears in the response, it should keep its value. """ req = self._get_POST_request_with_token() - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp = CsrfViewMiddleware().process_response(req, resp) + resp = self.mw.process_response(req, resp) csrf_cookie = resp.cookies.get(settings.CSRF_COOKIE_NAME, None) if csrf_cookie: self.assertEqual( @@ -279,7 +280,7 @@ def test_https_bad_referer(self): req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.evil.org/somepage' req.META['SERVER_PORT'] = '443' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains( response, 'Referer checking failed - https://www.evil.org/somepage does not ' @@ -296,7 +297,7 @@ def test_https_malformed_referer(self): req = self._get_POST_request_with_token() req._is_secure_override = True req.META['HTTP_REFERER'] = 'http://http://www.example.com/' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains( response, 'Referer checking failed - Referer is insecure while host is secure.', @@ -304,23 +305,23 @@ def test_https_malformed_referer(self): ) # Empty req.META['HTTP_REFERER'] = '' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains(response, malformed_referer_msg, status_code=403) # Non-ASCII req.META['HTTP_REFERER'] = b'\xd8B\xf6I\xdf' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains(response, malformed_referer_msg, status_code=403) # missing scheme # >>> urlparse('//example.com/') # ParseResult(scheme='', netloc='example.com', path='/', params='', query='', fragment='') req.META['HTTP_REFERER'] = '//example.com/' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains(response, malformed_referer_msg, status_code=403) # missing netloc # >>> urlparse('https://') # ParseResult(scheme='https', netloc='', path='', params='', query='', fragment='') req.META['HTTP_REFERER'] = 'https://' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains(response, malformed_referer_msg, status_code=403) @override_settings(ALLOWED_HOSTS=['www.example.com']) @@ -332,7 +333,7 @@ def test_https_good_referer(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.example.com/somepage' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @override_settings(ALLOWED_HOSTS=['www.example.com']) @@ -346,7 +347,7 @@ def test_https_good_referer_2(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.example.com' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) def _test_https_good_referer_behind_proxy(self): @@ -359,7 +360,7 @@ def _test_https_good_referer_behind_proxy(self): 'HTTP_X_FORWARDED_HOST': 'www.example.com', 'HTTP_X_FORWARDED_PORT': '443', }) - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['dashboard.example.com']) @@ -372,7 +373,7 @@ def test_https_csrf_trusted_origin_allowed(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://dashboard.example.com' - req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @override_settings(ALLOWED_HOSTS=['www.example.com'], CSRF_TRUSTED_ORIGINS=['.example.com']) @@ -385,7 +386,7 @@ def test_https_csrf_wildcard_trusted_origin_allowed(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://dashboard.example.com' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) def _test_https_good_referer_matches_cookie_domain(self): @@ -393,7 +394,7 @@ def _test_https_good_referer_matches_cookie_domain(self): req._is_secure_override = True req.META['HTTP_REFERER'] = 'https://foo.example.com/' req.META['SERVER_PORT'] = '443' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) def _test_https_good_referer_matches_cookie_domain_with_different_port(self): @@ -402,7 +403,7 @@ def _test_https_good_referer_matches_cookie_domain_with_different_port(self): req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://foo.example.com:4443/' req.META['SERVER_PORT'] = '4443' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) def test_ensures_csrf_cookie_no_logging(self): @@ -466,12 +467,12 @@ def _set_post(self, post): token = ('ABC' + self._csrf_id)[:CSRF_TOKEN_LENGTH] req = CsrfPostRequest(token, raise_error=False) - resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + resp = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(resp) req = CsrfPostRequest(token, raise_error=True) with patch_logger('django.security.csrf', 'warning') as logger_calls: - resp = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + resp = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(resp.status_code, 403) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN) @@ -508,9 +509,9 @@ def test_ensures_csrf_cookie_with_middleware(self): enabled. """ req = self._get_GET_no_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, ensure_csrf_cookie_view, (), {}) + self.mw.process_view(req, ensure_csrf_cookie_view, (), {}) resp = ensure_csrf_cookie_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) self.assertTrue(resp2.cookies.get(settings.CSRF_COOKIE_NAME, False)) self.assertIn('Cookie', resp2.get('Vary', '')) @@ -528,10 +529,10 @@ def test_csrf_cookie_age(self): CSRF_COOKIE_SECURE=True, CSRF_COOKIE_HTTPONLY=True): # token_view calls get_token() indirectly - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) max_age = resp2.cookies.get('csrfcookie').get('max-age') self.assertEqual(max_age, MAX_AGE) @@ -550,10 +551,10 @@ def test_csrf_cookie_age_none(self): CSRF_COOKIE_SECURE=True, CSRF_COOKIE_HTTPONLY=True): # token_view calls get_token() indirectly - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) max_age = resp2.cookies.get('csrfcookie').get('max-age') self.assertEqual(max_age, '') @@ -564,9 +565,9 @@ def test_process_view_token_too_long(self): """ req = self._get_GET_no_csrf_cookie_request() req.COOKIES[settings.CSRF_COOKIE_NAME] = 'x' * 100000 - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH) @@ -596,9 +597,9 @@ def test_process_view_token_invalid_chars(self): token = ('!@#' + self._csrf_id)[:CSRF_TOKEN_LENGTH] req = self._get_GET_no_csrf_cookie_request() req.COOKIES[settings.CSRF_COOKIE_NAME] = token - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - resp2 = CsrfViewMiddleware().process_response(req, resp) + resp2 = self.mw.process_response(req, resp) csrf_cookie = resp2.cookies.get(settings.CSRF_COOKIE_NAME, False) self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH) self.assertNotEqual(csrf_cookie.value, token) @@ -608,10 +609,10 @@ def test_bare_secret_accepted_and_replaced(self): The csrf token is reset from a bare secret. """ req = self._get_POST_bare_secret_csrf_cookie_request_with_token() - req2 = CsrfViewMiddleware().process_view(req, token_view, (), {}) + req2 = self.mw.process_view(req, token_view, (), {}) self.assertIsNone(req2) resp = token_view(req) - resp = CsrfViewMiddleware().process_response(req, resp) + resp = self.mw.process_response(req, resp) self.assertIn(settings.CSRF_COOKIE_NAME, resp.cookies, "Cookie was not reset from bare secret") csrf_cookie = resp.cookies[settings.CSRF_COOKIE_NAME] self.assertEqual(len(csrf_cookie.value), CSRF_TOKEN_LENGTH) @@ -649,7 +650,7 @@ def test_https_reject_insecure_referer(self): req._is_secure_override = True req.META['HTTP_REFERER'] = 'http://example.com/' req.META['SERVER_PORT'] = '443' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains( response, 'Referer checking failed - Referer is insecure while host is secure.', @@ -679,7 +680,7 @@ def test_no_session_on_request(self): 'SessionMiddleware must appear before CsrfViewMiddleware in MIDDLEWARE.' ) with self.assertRaisesMessage(ImproperlyConfigured, msg): - CsrfViewMiddleware().process_view(HttpRequest(), None, (), {}) + self.mw.process_view(HttpRequest(), None, (), {}) def test_process_response_get_token_used(self): """The ensure_csrf_cookie() decorator works without middleware.""" @@ -693,9 +694,9 @@ def test_ensures_csrf_cookie_with_middleware(self): enabled. """ req = self._get_GET_no_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, ensure_csrf_cookie_view, (), {}) + self.mw.process_view(req, ensure_csrf_cookie_view, (), {}) resp = ensure_csrf_cookie_view(req) - CsrfViewMiddleware().process_response(req, resp) + self.mw.process_response(req, resp) self.assertTrue(req.session.get(CSRF_SESSION_KEY, False)) def test_token_node_with_new_csrf_cookie(self): @@ -704,9 +705,9 @@ def test_token_node_with_new_csrf_cookie(self): (when one was not already present). """ req = self._get_GET_no_csrf_cookie_request() - CsrfViewMiddleware().process_view(req, token_view, (), {}) + self.mw.process_view(req, token_view, (), {}) resp = token_view(req) - CsrfViewMiddleware().process_response(req, resp) + self.mw.process_response(req, resp) csrf_cookie = req.session[CSRF_SESSION_KEY] self._check_token_present(resp, csrf_id=csrf_cookie) @@ -747,7 +748,7 @@ def test_https_reject_insecure_referer(self): req._is_secure_override = True req.META['HTTP_REFERER'] = 'http://example.com/' req.META['SERVER_PORT'] = '443' - response = CsrfViewMiddleware().process_view(req, post_form_view, (), {}) + response = self.mw.process_view(req, post_form_view, (), {}) self.assertContains( response, 'Referer checking failed - Referer is insecure while host is secure.', From 42847327d1277451ee7a61716f7b9f62f50ecbdc Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Sun, 17 Sep 2017 22:24:05 +0200 Subject: [PATCH 176/389] [1.11.x] Fixed #28488 -- Reallowed error handlers to access CSRF tokens. Regression in eef95ea96faef0b7dbbe0c8092202b74f68a899b. Backport of c4c128d67c7dc2830631c6859a204c9d259f1fb1 from master --- django/middleware/csrf.py | 10 +++-- docs/releases/1.11.6.txt | 4 ++ .../csrf_token_error_handler_urls.py | 3 ++ tests/csrf_tests/tests.py | 37 ++++++++++++++++++- tests/csrf_tests/views.py | 9 ++++- 5 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 tests/csrf_tests/csrf_token_error_handler_urls.py diff --git a/django/middleware/csrf.py b/django/middleware/csrf.py index d7359e491219..13134309a55d 100644 --- a/django/middleware/csrf.py +++ b/django/middleware/csrf.py @@ -201,15 +201,16 @@ def _set_token(self, request, response): # Set the Vary header since content varies with the CSRF cookie. patch_vary_headers(response, ('Cookie',)) - def process_view(self, request, callback, callback_args, callback_kwargs): - if getattr(request, 'csrf_processing_done', False): - return None - + def process_request(self, request): csrf_token = self._get_token(request) if csrf_token is not None: # Use same token next time. request.META['CSRF_COOKIE'] = csrf_token + def process_view(self, request, callback, callback_args, callback_kwargs): + if getattr(request, 'csrf_processing_done', False): + return None + # Wait until request.META["CSRF_COOKIE"] has been manipulated before # bailing out, so that get_token still works if getattr(callback, 'csrf_exempt', False): @@ -285,6 +286,7 @@ def process_view(self, request, callback, callback_args, callback_kwargs): reason = REASON_BAD_REFERER % referer.geturl() return self._reject(request, reason) + csrf_token = request.META.get('CSRF_COOKIE') if csrf_token is None: # No CSRF cookie. For POST requests, we insist on a CSRF cookie, # and in this way we can avoid all CSRF attacks, including login diff --git a/docs/releases/1.11.6.txt b/docs/releases/1.11.6.txt index ff9d4385fea1..222a9c9125fc 100644 --- a/docs/releases/1.11.6.txt +++ b/docs/releases/1.11.6.txt @@ -14,3 +14,7 @@ Bugfixes * Fixed crash when using the name of a model's autogenerated primary key (``id``) in an ``Index``'s ``fields`` (:ticket:`28597`). + +* Fixed a regression in Django 1.9 where a custom view error handler such as + ``handler404`` that accesses ``csrf_token`` could cause CSRF verification + failures on other pages (:ticket:`28488`). diff --git a/tests/csrf_tests/csrf_token_error_handler_urls.py b/tests/csrf_tests/csrf_token_error_handler_urls.py new file mode 100644 index 000000000000..3c02f613ec2c --- /dev/null +++ b/tests/csrf_tests/csrf_token_error_handler_urls.py @@ -0,0 +1,3 @@ +urlpatterns = [] + +handler404 = 'csrf_tests.views.csrf_token_error_handler' diff --git a/tests/csrf_tests/tests.py b/tests/csrf_tests/tests.py index e71fb4019322..306a4fff3329 100644 --- a/tests/csrf_tests/tests.py +++ b/tests/csrf_tests/tests.py @@ -90,6 +90,7 @@ def test_process_response_get_token_not_used(self): # does use the csrf request processor. By using this, we are testing # that the view processor is properly lazy and doesn't call get_token() # until needed. + self.mw.process_request(req) self.mw.process_view(req, non_token_view_using_request_processor, (), {}) resp = non_token_view_using_request_processor(req) resp2 = self.mw.process_response(req, resp) @@ -105,6 +106,7 @@ def test_process_request_no_csrf_cookie(self): """ with patch_logger('django.security.csrf', 'warning') as logger_calls: req = self._get_POST_no_csrf_cookie_request() + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_NO_CSRF_COOKIE) @@ -116,6 +118,7 @@ def test_process_request_csrf_cookie_no_token(self): """ with patch_logger('django.security.csrf', 'warning') as logger_calls: req = self._get_POST_csrf_cookie_request() + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(403, req2.status_code) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN) @@ -125,6 +128,7 @@ def test_process_request_csrf_cookie_and_token(self): If both a cookie and a token is present, the middleware lets it through. """ req = self._get_POST_request_with_token() + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -134,6 +138,7 @@ def test_process_request_csrf_cookie_no_token_exempt_view(self): has been applied to the view, the middleware lets it through """ req = self._get_POST_csrf_cookie_request() + self.mw.process_request(req) req2 = self.mw.process_view(req, csrf_exempt(post_form_view), (), {}) self.assertIsNone(req2) @@ -143,6 +148,7 @@ def test_csrf_token_in_header(self): """ req = self._get_POST_csrf_cookie_request() req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -153,6 +159,7 @@ def test_csrf_token_in_header_with_customized_name(self): """ req = self._get_POST_csrf_cookie_request() req.META['HTTP_X_CSRFTOKEN_CUSTOMIZED'] = self._csrf_id + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -181,12 +188,14 @@ def test_put_and_delete_allowed(self): req = self._get_GET_csrf_cookie_request() req.method = 'PUT' req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) req = self._get_GET_csrf_cookie_request() req.method = 'DELETE' req.META['HTTP_X_CSRFTOKEN'] = self._csrf_id + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -220,6 +229,7 @@ def test_token_node_with_csrf_cookie(self): CsrfTokenNode works when a CSRF cookie is set. """ req = self._get_GET_csrf_cookie_request() + self.mw.process_request(req) self.mw.process_view(req, token_view, (), {}) resp = token_view(req) self._check_token_present(resp) @@ -229,6 +239,7 @@ def test_get_token_for_exempt_view(self): get_token still works for a view decorated with 'csrf_exempt'. """ req = self._get_GET_csrf_cookie_request() + self.mw.process_request(req) self.mw.process_view(req, csrf_exempt(token_view), (), {}) resp = token_view(req) self._check_token_present(resp) @@ -260,6 +271,7 @@ def test_cookie_not_reset_on_accepted_request(self): requests. If it appears in the response, it should keep its value. """ req = self._get_POST_request_with_token() + self.mw.process_request(req) self.mw.process_view(req, token_view, (), {}) resp = token_view(req) resp = self.mw.process_response(req, resp) @@ -333,6 +345,7 @@ def test_https_good_referer(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.example.com/somepage' + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -347,6 +360,7 @@ def test_https_good_referer_2(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://www.example.com' + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -360,6 +374,7 @@ def _test_https_good_referer_behind_proxy(self): 'HTTP_X_FORWARDED_HOST': 'www.example.com', 'HTTP_X_FORWARDED_PORT': '443', }) + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -373,6 +388,7 @@ def test_https_csrf_trusted_origin_allowed(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://dashboard.example.com' + self.mw.process_request(req) req2 = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(req2) @@ -386,6 +402,7 @@ def test_https_csrf_wildcard_trusted_origin_allowed(self): req._is_secure_override = True req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://dashboard.example.com' + self.mw.process_request(req) response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) @@ -394,6 +411,7 @@ def _test_https_good_referer_matches_cookie_domain(self): req._is_secure_override = True req.META['HTTP_REFERER'] = 'https://foo.example.com/' req.META['SERVER_PORT'] = '443' + self.mw.process_request(req) response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) @@ -403,6 +421,7 @@ def _test_https_good_referer_matches_cookie_domain_with_different_port(self): req.META['HTTP_HOST'] = 'www.example.com' req.META['HTTP_REFERER'] = 'https://foo.example.com:4443/' req.META['SERVER_PORT'] = '4443' + self.mw.process_request(req) response = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(response) @@ -467,11 +486,13 @@ def _set_post(self, post): token = ('ABC' + self._csrf_id)[:CSRF_TOKEN_LENGTH] req = CsrfPostRequest(token, raise_error=False) + self.mw.process_request(req) resp = self.mw.process_view(req, post_form_view, (), {}) self.assertIsNone(resp) req = CsrfPostRequest(token, raise_error=True) with patch_logger('django.security.csrf', 'warning') as logger_calls: + self.mw.process_request(req) resp = self.mw.process_view(req, post_form_view, (), {}) self.assertEqual(resp.status_code, 403) self.assertEqual(logger_calls[0], 'Forbidden (%s): ' % REASON_BAD_TOKEN) @@ -609,6 +630,7 @@ def test_bare_secret_accepted_and_replaced(self): The csrf token is reset from a bare secret. """ req = self._get_POST_bare_secret_csrf_cookie_request_with_token() + self.mw.process_request(req) req2 = self.mw.process_view(req, token_view, (), {}) self.assertIsNone(req2) resp = token_view(req) @@ -680,7 +702,7 @@ def test_no_session_on_request(self): 'SessionMiddleware must appear before CsrfViewMiddleware in MIDDLEWARE.' ) with self.assertRaisesMessage(ImproperlyConfigured, msg): - self.mw.process_view(HttpRequest(), None, (), {}) + self.mw.process_request(HttpRequest()) def test_process_response_get_token_used(self): """The ensure_csrf_cookie() decorator works without middleware.""" @@ -754,3 +776,16 @@ def test_https_reject_insecure_referer(self): 'Referer checking failed - Referer is insecure while host is secure.', status_code=403, ) + + +@override_settings(ROOT_URLCONF='csrf_tests.csrf_token_error_handler_urls', DEBUG=False) +class CsrfInErrorHandlingViewsTests(SimpleTestCase): + def test_csrf_token_on_404_stays_constant(self): + response = self.client.get('/does not exist/') + # The error handler returns status code 599. + self.assertEqual(response.status_code, 599) + token1 = response.content + response = self.client.get('/does not exist/') + self.assertEqual(response.status_code, 599) + token2 = response.content + self.assertTrue(equivalent_tokens(token1.decode('ascii'), token2.decode('ascii'))) diff --git a/tests/csrf_tests/views.py b/tests/csrf_tests/views.py index e41f2d080511..cf1b67fc2702 100644 --- a/tests/csrf_tests/views.py +++ b/tests/csrf_tests/views.py @@ -1,7 +1,8 @@ from __future__ import unicode_literals from django.http import HttpResponse -from django.template import RequestContext, Template +from django.middleware.csrf import get_token +from django.template import Context, RequestContext, Template from django.template.context_processors import csrf from django.views.decorators.csrf import ensure_csrf_cookie @@ -30,3 +31,9 @@ def non_token_view_using_request_processor(request): context = RequestContext(request, processors=[csrf]) template = Template('') return HttpResponse(template.render(context)) + + +def csrf_token_error_handler(request, **kwargs): + """This error handler accesses the CSRF token.""" + template = Template(get_token(request)) + return HttpResponse(template.render(Context()), status=599) From e8a82e82c148b135a8511ca4ca0f79dea4b7bc78 Mon Sep 17 00:00:00 2001 From: Stefan Schneider Date: Fri, 29 Sep 2017 16:31:49 +0200 Subject: [PATCH 177/389] [1.11.x] Fixed #28648 -- Corrected typo in docs/topics/db/queries.txt. Backport of 293df73fb67a56c0417af8c39f808f64bc03cbeb from master --- docs/topics/db/queries.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 9f7f3584cd0d..4b9210f0209d 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -228,7 +228,7 @@ refinements together. For example:: ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( - ... pub_date__gte=datetime(2005, 1, 30) + ... pub_date__gte=datetime.date(2005, 1, 30) ... ) This takes the initial :class:`~django.db.models.query.QuerySet` of all entries From 251190cc5956c835a1d7dd3bd084370f0ec50cb5 Mon Sep 17 00:00:00 2001 From: Stefan Schneider Date: Fri, 29 Sep 2017 17:38:28 +0200 Subject: [PATCH 178/389] [1.11.x] Fixed #28653 -- Added missing ForeignKey.on_delete argument in docs. Backport of 08c8c3ead97893ec0e1dece699525ad7ed27c2d7 from master --- docs/ref/models/querysets.txt | 2 +- docs/topics/conditional-view-processing.txt | 2 +- docs/topics/db/aggregation.txt | 2 +- docs/topics/db/queries.txt | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index c9f3556f106b..4b22def9469d 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1074,7 +1074,7 @@ fields. Suppose we have an additional model to the example above:: class Restaurant(models.Model): pizzas = models.ManyToManyField(Pizza, related_name='restaurants') - best_pizza = models.ForeignKey(Pizza, related_name='championed_by') + best_pizza = models.ForeignKey(Pizza, related_name='championed_by', on_delete=models.CASCADE) The following are all legal: diff --git a/docs/topics/conditional-view-processing.txt b/docs/topics/conditional-view-processing.txt index 02bf4635a82f..0e111d4fec5e 100644 --- a/docs/topics/conditional-view-processing.txt +++ b/docs/topics/conditional-view-processing.txt @@ -84,7 +84,7 @@ Suppose you have this pair of models, representing a simple blog system:: ... class Entry(models.Model): - blog = models.ForeignKey(Blog) + blog = models.ForeignKey(Blog, on_delete=models.CASCADE) published = models.DateTimeField(default=datetime.datetime.now) ... diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 6f7d3918affe..f47e9ac2305d 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -34,7 +34,7 @@ used to track the inventory for a series of online bookstores: price = models.DecimalField(max_digits=10, decimal_places=2) rating = models.FloatField() authors = models.ManyToManyField(Author) - publisher = models.ForeignKey(Publisher) + publisher = models.ForeignKey(Publisher, on_delete=models.CASCADE) pubdate = models.DateField() class Store(models.Model): diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index 4b9210f0209d..d7dc263a92cd 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -34,7 +34,7 @@ models, which comprise a Weblog application: return self.name class Entry(models.Model): - blog = models.ForeignKey(Blog) + blog = models.ForeignKey(Blog, on_delete=models.CASCADE) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() From c3ea1e4145e0c0a40ea04a0aa46e8a9a2d4e52e4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Oct 2017 14:13:32 -0400 Subject: [PATCH 179/389] [1.11.x] Added release date for 1.11.6. Backport of a8bcb8b509629bc843c4da231955d8c5ef786edd from master --- docs/releases/1.11.6.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.6.txt b/docs/releases/1.11.6.txt index 222a9c9125fc..69f94709b687 100644 --- a/docs/releases/1.11.6.txt +++ b/docs/releases/1.11.6.txt @@ -2,7 +2,7 @@ Django 1.11.6 release notes =========================== -*Expected October 2, 2017* +*October 5, 2017* Django 1.11.6 fixes several bugs in 1.11.5. From 5d5ae260f83a530a03d08235fda9e0d575fd61d1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Oct 2017 14:16:31 -0400 Subject: [PATCH 180/389] [1.11.x] Bumped version for 1.11.6 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index cc27e92e6b85..aea04ac54db6 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 6, 'alpha', 0) +VERSION = (1, 11, 6, 'final', 0) __version__ = get_version(VERSION) From e75c5bd872463bcad675a443c08b98c39c6750ca Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Oct 2017 14:44:22 -0400 Subject: [PATCH 181/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index aea04ac54db6..367b34dd1a05 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 6, 'final', 0) +VERSION = (1, 11, 7, 'alpha', 0) __version__ = get_version(VERSION) From 9b8e76f96dcb755a2cbf4846888058eae2510cf8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 5 Oct 2017 14:44:50 -0400 Subject: [PATCH 182/389] [1.11.x] Added stub release notes for 1.11.7. Backport of fd4698fe3f2a1cfe9deef83a95db725e5cb66f21 from master --- docs/releases/1.11.7.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.7.txt diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt new file mode 100644 index 000000000000..41d144e74287 --- /dev/null +++ b/docs/releases/1.11.7.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.7 release notes +=========================== + +*Expected November 1, 2017* + +Django 1.11.7 fixes several bugs in 1.11.6. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index ad1970b715ec..a7534009014f 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.7 1.11.6 1.11.5 1.11.4 From 45b0ec87d3d7c60c60b0f1c69cd89789bafaa6a8 Mon Sep 17 00:00:00 2001 From: Daniel Tao Date: Fri, 15 Sep 2017 16:16:44 -0500 Subject: [PATCH 183/389] [1.11.x] Fixed #28601 -- Prevented cache.get_or_set() from caching None if default is a callable that returns None. Backport of 4d60261b2a77460b4c127c3d832518b95e11a0ac from master --- django/core/cache/backends/base.py | 12 +++++++----- docs/releases/1.11.7.txt | 3 ++- tests/cache/tests.py | 5 +++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index 9cbfe0be1d07..1235f7e098ae 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -157,13 +157,15 @@ def get_or_set(self, key, default, timeout=DEFAULT_TIMEOUT, version=None): Return the value of the key stored or retrieved. """ val = self.get(key, version=version) - if val is None and default is not None: + if val is None: if callable(default): default = default() - self.add(key, default, timeout=timeout, version=version) - # Fetch the value again to avoid a race condition if another caller - # added a value between the first get() and the add() above. - return self.get(key, default, version=version) + if default is not None: + self.add(key, default, timeout=timeout, version=version) + # Fetch the value again to avoid a race condition if another + # caller added a value between the first get() and the add() + # above. + return self.get(key, default, version=version) return val def has_key(self, key, version=None): diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt index 41d144e74287..61f0d6d012b3 100644 --- a/docs/releases/1.11.7.txt +++ b/docs/releases/1.11.7.txt @@ -9,4 +9,5 @@ Django 1.11.7 fixes several bugs in 1.11.6. Bugfixes ======== -* ... +* Prevented ``cache.get_or_set()`` from caching ``None`` if the ``default`` + argument is a callable that returns ``None`` (:ticket:`28601`). diff --git a/tests/cache/tests.py b/tests/cache/tests.py index edf25cfc65df..0305020840ad 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -931,6 +931,11 @@ def my_callable(): self.assertEqual(cache.get_or_set('mykey', my_callable), 'value') self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value') + def test_get_or_set_callable_returning_none(self): + self.assertIsNone(cache.get_or_set('mykey', lambda: None)) + # Previous get_or_set() doesn't store None in the cache. + self.assertEqual(cache.get('mykey', 'default'), 'default') + def test_get_or_set_version(self): msg = ( "get_or_set() missing 1 required positional argument: 'default'" From 50bbc93ccb9c90a527849cbd18c2f6f02ca83cd1 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 13 Oct 2017 18:37:31 +0200 Subject: [PATCH 184/389] [1.11.x] Fixed #28710 -- Fixed the Basque DATE_FORMAT string Thanks Eneko Illarramendi for the report and initial patch. Backport of 8c538871bda3832bca2dddefe317bf4a9230dd45 from master. --- django/conf/locale/eu/formats.py | 2 +- docs/releases/1.11.7.txt | 2 ++ tests/i18n/tests.py | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/django/conf/locale/eu/formats.py b/django/conf/locale/eu/formats.py index 4ddf04ef5ab8..767491719baf 100644 --- a/django/conf/locale/eu/formats.py +++ b/django/conf/locale/eu/formats.py @@ -5,7 +5,7 @@ # The *_FORMAT strings use the Django date format syntax, # see http://docs.djangoproject.com/en/dev/ref/templates/builtins/#date -DATE_FORMAT = r'Yeko M\re\n d\a' +DATE_FORMAT = r'Y\k\o N j\a' TIME_FORMAT = 'H:i' # DATETIME_FORMAT = # YEAR_MONTH_FORMAT = diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt index 61f0d6d012b3..717174c62518 100644 --- a/docs/releases/1.11.7.txt +++ b/docs/releases/1.11.7.txt @@ -11,3 +11,5 @@ Bugfixes * Prevented ``cache.get_or_set()`` from caching ``None`` if the ``default`` argument is a callable that returns ``None`` (:ticket:`28601`). + +* Fixed the Basque ``DATE_FORMAT`` string (:ticket:`28710`). diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 90141ca5f4ea..d9224d531982 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -13,6 +13,7 @@ from django import forms from django.conf import settings +from django.conf.locale import LANG_INFO from django.conf.urls.i18n import i18n_patterns from django.template import Context, Template from django.test import ( @@ -364,6 +365,23 @@ def setUp(self): 'l': self.long, }) + def test_all_format_strings(self): + all_locales = LANG_INFO.keys() + today = datetime.date.today() + now = datetime.datetime.now() + current_year = str(today.year) + current_day = str(today.day) + current_minute = str(now.minute) + for locale in all_locales: + with translation.override(locale): + self.assertIn(current_year, date_format(today)) # Uses DATE_FORMAT by default + self.assertIn(current_minute, time_format(now)) # Uses TIME_FORMAT by default + self.assertIn(current_year, date_format(now, format=get_format('DATETIME_FORMAT'))) + self.assertIn(current_year, date_format(today, format=get_format('YEAR_MONTH_FORMAT'))) + self.assertIn(current_day, date_format(today, format=get_format('MONTH_DAY_FORMAT'))) + self.assertIn(current_year, date_format(today, format=get_format('SHORT_DATE_FORMAT'))) + self.assertIn(current_year, date_format(now, format=get_format('SHORT_DATETIME_FORMAT'))) + def test_locale_independent(self): """ Localization of numbers From 398a79ceb67be33d9ad63fd8221d3523525d70db Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 14 Oct 2017 20:46:57 +0200 Subject: [PATCH 185/389] [1.11.x] Refs #28710 -- Simplified l10n format test Backport of c1fa6672dd995e5ab4e06d5132db40ed0f41a47e from master. --- tests/i18n/tests.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index d9224d531982..25d74a35dc98 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -367,20 +367,17 @@ def setUp(self): def test_all_format_strings(self): all_locales = LANG_INFO.keys() - today = datetime.date.today() - now = datetime.datetime.now() - current_year = str(today.year) - current_day = str(today.day) - current_minute = str(now.minute) + some_date = datetime.date(2017, 10, 14) + some_datetime = datetime.datetime(2017, 10, 14, 10, 23) for locale in all_locales: with translation.override(locale): - self.assertIn(current_year, date_format(today)) # Uses DATE_FORMAT by default - self.assertIn(current_minute, time_format(now)) # Uses TIME_FORMAT by default - self.assertIn(current_year, date_format(now, format=get_format('DATETIME_FORMAT'))) - self.assertIn(current_year, date_format(today, format=get_format('YEAR_MONTH_FORMAT'))) - self.assertIn(current_day, date_format(today, format=get_format('MONTH_DAY_FORMAT'))) - self.assertIn(current_year, date_format(today, format=get_format('SHORT_DATE_FORMAT'))) - self.assertIn(current_year, date_format(now, format=get_format('SHORT_DATETIME_FORMAT'))) + self.assertIn('2017', date_format(some_date)) # Uses DATE_FORMAT by default + self.assertIn('23', time_format(some_datetime)) # Uses TIME_FORMAT by default + self.assertIn('2017', date_format(some_datetime, format=get_format('DATETIME_FORMAT'))) + self.assertIn('2017', date_format(some_date, format=get_format('YEAR_MONTH_FORMAT'))) + self.assertIn('14', date_format(some_date, format=get_format('MONTH_DAY_FORMAT'))) + self.assertIn('2017', date_format(some_date, format=get_format('SHORT_DATE_FORMAT'))) + self.assertIn('2017', date_format(some_datetime, format=get_format('SHORT_DATETIME_FORMAT'))) def test_locale_independent(self): """ From 0f9de3976c0671200a927839dd7f5b300bf0044a Mon Sep 17 00:00:00 2001 From: Jozef Date: Tue, 17 Oct 2017 16:07:20 +0200 Subject: [PATCH 186/389] [1.11.x] Fixed typo in docs/ref/models/querysets.txt. Backport of 3bd69b126115102a4630354c876e6c7cc2d68f8f from master --- docs/ref/models/querysets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 4b22def9469d..179696d764a2 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1088,7 +1088,7 @@ one for the restaurants, one for the pizzas, and one for the toppings. This will fetch the best pizza and all the toppings for the best pizza for each restaurant. This will be done in 3 database queries - one for the restaurants, -one for the 'best pizzas', and one for one for the toppings. +one for the 'best pizzas', and one for the toppings. Of course, the ``best_pizza`` relationship could also be fetched using ``select_related`` to reduce the query count to 2: From b0566e5bb3216b8ca6e9c8473ed550d5d8825f56 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 20 Oct 2017 14:00:51 -0400 Subject: [PATCH 187/389] [1.11.x] Fixed #28729 -- Replaced a numbered list with unordered list in TemplatesSetting docs. Backport of eb9b56c5b60215a683c80e68f08ae6fca0ec24ef from master --- docs/ref/forms/renderers.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/forms/renderers.txt b/docs/ref/forms/renderers.txt index fcf025a9c8a6..1f2f48d4b943 100644 --- a/docs/ref/forms/renderers.txt +++ b/docs/ref/forms/renderers.txt @@ -90,11 +90,11 @@ templates based on what's configured in the :setting:`TEMPLATES` setting. Using this renderer along with the built-in widget templates requires either: -#. ``'django.forms'`` in :setting:`INSTALLED_APPS` and at least one engine - with :setting:`APP_DIRS=True `. +* ``'django.forms'`` in :setting:`INSTALLED_APPS` and at least one engine + with :setting:`APP_DIRS=True `. -#. Adding the built-in widgets templates directory in :setting:`DIRS - ` of one of your template engines. To generate that path:: +* Adding the built-in widgets templates directory in :setting:`DIRS + ` of one of your template engines. To generate that path:: import django django.__path__[0] + '/forms/templates' # or '/forms/jinja2' From e98ae4fe6bc1baa402b10bc379d2e96b79bbb3b0 Mon Sep 17 00:00:00 2001 From: Tomer Chachamu Date: Wed, 18 Oct 2017 14:09:45 +0100 Subject: [PATCH 188/389] [1.11.x] Fixed #28722 -- Made QuerySet.reverse() affect nulls_first/nulls_last. Backport of 21a3a29dc9d138c248fd7922923b3ec710735c6c from master --- AUTHORS | 1 + django/db/models/expressions.py | 3 +++ docs/releases/1.11.7.txt | 3 +++ tests/ordering/tests.py | 36 ++++++++++++++++++--------------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/AUTHORS b/AUTHORS index c2a55095f239..9d27cd039bd5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -765,6 +765,7 @@ answer newbie questions, and generally made Django that much better: Tome Cvitan Tomek Paczkowski Tom Insam + Tomer Chachamu Tommy Beadle Tom Tobin Tore Lundqvist diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index e8bd921083ce..adafd26e894e 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1089,6 +1089,9 @@ def get_group_by_cols(self): def reverse_ordering(self): self.descending = not self.descending + if self.nulls_first or self.nulls_last: + self.nulls_first = not self.nulls_first + self.nulls_last = not self.nulls_last return self def asc(self): diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt index 717174c62518..fe2cf2e300f4 100644 --- a/docs/releases/1.11.7.txt +++ b/docs/releases/1.11.7.txt @@ -13,3 +13,6 @@ Bugfixes argument is a callable that returns ``None`` (:ticket:`28601`). * Fixed the Basque ``DATE_FORMAT`` string (:ticket:`28710`). + +* Made ``QuerySet.reverse()`` affect ``nulls_first`` and ``nulls_last`` + (:ticket:`28722`). diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index 399b8d70351a..62ce558a93ba 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -94,24 +94,28 @@ def test_order_by_nulls_first_and_last(self): with self.assertRaisesMessage(ValueError, msg): Article.objects.order_by(F("author").desc(nulls_last=True, nulls_first=True)) + def assertQuerysetEqualReversible(self, queryset, sequence): + self.assertSequenceEqual(queryset, sequence) + self.assertSequenceEqual(queryset.reverse(), list(reversed(sequence))) + def test_order_by_nulls_last(self): Article.objects.filter(headline="Article 3").update(author=self.author_1) Article.objects.filter(headline="Article 4").update(author=self.author_2) # asc and desc are chainable with nulls_last. - self.assertSequenceEqual( - Article.objects.order_by(F("author").desc(nulls_last=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(F("author").desc(nulls_last=True), 'headline'), [self.a4, self.a3, self.a1, self.a2], ) - self.assertSequenceEqual( - Article.objects.order_by(F("author").asc(nulls_last=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(F("author").asc(nulls_last=True), 'headline'), [self.a3, self.a4, self.a1, self.a2], ) - self.assertSequenceEqual( - Article.objects.order_by(Upper("author__name").desc(nulls_last=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(Upper("author__name").desc(nulls_last=True), 'headline'), [self.a4, self.a3, self.a1, self.a2], ) - self.assertSequenceEqual( - Article.objects.order_by(Upper("author__name").asc(nulls_last=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(Upper("author__name").asc(nulls_last=True), 'headline'), [self.a3, self.a4, self.a1, self.a2], ) @@ -119,20 +123,20 @@ def test_order_by_nulls_first(self): Article.objects.filter(headline="Article 3").update(author=self.author_1) Article.objects.filter(headline="Article 4").update(author=self.author_2) # asc and desc are chainable with nulls_first. - self.assertSequenceEqual( - Article.objects.order_by(F("author").asc(nulls_first=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(F("author").asc(nulls_first=True), 'headline'), [self.a1, self.a2, self.a3, self.a4], ) - self.assertSequenceEqual( - Article.objects.order_by(F("author").desc(nulls_first=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(F("author").desc(nulls_first=True), 'headline'), [self.a1, self.a2, self.a4, self.a3], ) - self.assertSequenceEqual( - Article.objects.order_by(Upper("author__name").asc(nulls_first=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(Upper("author__name").asc(nulls_first=True), 'headline'), [self.a1, self.a2, self.a3, self.a4], ) - self.assertSequenceEqual( - Article.objects.order_by(Upper("author__name").desc(nulls_first=True)), + self.assertQuerysetEqualReversible( + Article.objects.order_by(Upper("author__name").desc(nulls_first=True), 'headline'), [self.a1, self.a2, self.a4, self.a3], ) From 11f6d435667dd28db0e861db55e51f53c28862d2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 25 Oct 2017 21:52:38 +0200 Subject: [PATCH 189/389] [1.11.x] Fixed #28689 -- Fixed unquoted table names in Subquery SQL when using OuterRef. Regression in f48bc7c3dbd204eefb3c19016b1e4906ac26bee3. Backport of 81e357a7e19f35235cc998459a10213532727d4e from master --- django/db/models/expressions.py | 2 +- docs/releases/1.11.7.txt | 3 +++ tests/expressions/models.py | 1 + tests/expressions/tests.py | 5 +++++ 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index adafd26e894e..2db832e1ebcb 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -939,7 +939,7 @@ def resolve(child): ) # Add table alias to the parent query's aliases to prevent # quoting. - if hasattr(resolved, 'alias'): + if hasattr(resolved, 'alias') and resolved.alias != resolved.target.model._meta.db_table: clone.queryset.query.external_aliases.add(resolved.alias) return resolved return child diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt index fe2cf2e300f4..81334533e923 100644 --- a/docs/releases/1.11.7.txt +++ b/docs/releases/1.11.7.txt @@ -16,3 +16,6 @@ Bugfixes * Made ``QuerySet.reverse()`` affect ``nulls_first`` and ``nulls_last`` (:ticket:`28722`). + +* Fixed unquoted table names in ``Subquery`` SQL when using ``OuterRef`` + (:ticket:`28689`). diff --git a/tests/expressions/models.py b/tests/expressions/models.py index b1a737d0b9c5..678af731f8d8 100644 --- a/tests/expressions/models.py +++ b/tests/expressions/models.py @@ -56,6 +56,7 @@ class Experiment(models.Model): end = models.DateTimeField() class Meta: + db_table = 'expressions_ExPeRiMeNt' ordering = ('name',) def duration(self): diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index 253a9c04291f..17d4ec4e7bba 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -533,6 +533,11 @@ def test_subquery_references_joined_table_twice(self): outer = Company.objects.filter(pk__in=Subquery(inner.values('pk'))) self.assertFalse(outer.exists()) + def test_outerref_mixed_case_table_name(self): + inner = Result.objects.filter(result_time__gte=OuterRef('experiment__assigned')) + outer = Result.objects.filter(pk__in=Subquery(inner.values('pk'))) + self.assertFalse(outer.exists()) + class IterableLookupInnerExpressionsTests(TestCase): @classmethod From 8038b5c10e7bef8b62dbfaec796ef8f3cd52f88a Mon Sep 17 00:00:00 2001 From: Duarte Fernandes Date: Thu, 26 Oct 2017 22:48:31 -0300 Subject: [PATCH 190/389] [1.11.x] Fixed #28747 -- Fixed typos in django/conf/global_settings.py comments. Backport of 019c2600a6771d2cd0574062dee468ce96d7e69d from master --- django/conf/global_settings.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index f732682b1cfe..148c7a0203e6 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -244,7 +244,7 @@ def gettext_noop(s): # re.compile(r'^NaverBot.*'), # re.compile(r'^EmailSiphon.*'), # re.compile(r'^SiteSucker.*'), -# re.compile(r'^sohu-search') +# re.compile(r'^sohu-search'), # ] DISALLOWED_USER_AGENTS = [] @@ -255,9 +255,9 @@ def gettext_noop(s): # import re # IGNORABLE_404_URLS = [ # re.compile(r'^/apple-touch-icon.*\.png$'), -# re.compile(r'^/favicon.ico$), -# re.compile(r'^/robots.txt$), -# re.compile(r'^/phpmyadmin/), +# re.compile(r'^/favicon.ico$'), +# re.compile(r'^/robots.txt$'), +# re.compile(r'^/phpmyadmin/'), # re.compile(r'\.(cgi|php|pl)$'), # ] IGNORABLE_404_URLS = [] From 124929999daae06a0f29301cc5aa75e21d343d1f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Nov 2017 21:11:38 -0400 Subject: [PATCH 191/389] [1.11.x] Added release date for 1.11.7. Backport of 5f5425f74e57aa3caff056082a8899c0fdedb07e from master --- docs/releases/1.11.7.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.7.txt b/docs/releases/1.11.7.txt index 81334533e923..6d2273f33284 100644 --- a/docs/releases/1.11.7.txt +++ b/docs/releases/1.11.7.txt @@ -2,7 +2,7 @@ Django 1.11.7 release notes =========================== -*Expected November 1, 2017* +*November 1, 2017* Django 1.11.7 fixes several bugs in 1.11.6. From 33cb52857843ea4b5289d4b55fe4808c2c4c9e56 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Nov 2017 21:17:57 -0400 Subject: [PATCH 192/389] [1.11.x] Bumped version for 1.11.7 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 367b34dd1a05..1d0e241c505a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 7, 'alpha', 0) +VERSION = (1, 11, 7, 'final', 0) __version__ = get_version(VERSION) From cf939c642081e542067abf2f2d9c41775c0e1139 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Nov 2017 21:28:40 -0400 Subject: [PATCH 193/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 1d0e241c505a..7ec14fd518f5 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 7, 'final', 0) +VERSION = (1, 11, 8, 'alpha', 0) __version__ = get_version(VERSION) From d464f6b7621774f8e9ddf97fdf2c4638b0d64ce4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Nov 2017 21:33:51 -0400 Subject: [PATCH 194/389] [1.11.x] Added stub release notes for 1.11.8. Backport of ef718a72b3db81d35a6c1273b1565b48dd867e90 from master --- docs/releases/1.11.8.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.8.txt diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt new file mode 100644 index 000000000000..dd9d19ae19e2 --- /dev/null +++ b/docs/releases/1.11.8.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.8 release notes +=========================== + +*Expected December 1, 2017* + +Django 1.11.8 fixes several bugs in 1.11.7. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index a7534009014f..c3378cc23c76 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.8 1.11.7 1.11.6 1.11.5 From d9b457d906a55a7473f5727fe24cd4b0141f8b5f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 7 Nov 2017 09:03:01 -0500 Subject: [PATCH 195/389] [1.11.x] Fixed typo in docs/topics/db/aggregation.txt. Backport of 00b93c2b1ecdda978f067309c6feafda633a7264 from master --- docs/topics/db/aggregation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index f47e9ac2305d..86a2a4541734 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -70,7 +70,7 @@ In a hurry? Here's how to do common aggregate queries, assuming the models above # Difference between the highest priced book and the average price of all books. >>> from django.db.models import FloatField >>> Book.objects.aggregate( - ... price_diff=Max('price', output_field=FloatField()) - Avg('price'))) + ... price_diff=Max('price', output_field=FloatField()) - Avg('price')) {'price_diff': 46.85} # All the following queries involve traversing the Book<->Publisher From 308f64462421b09b21ef0dcd9cc3654cc25bceba Mon Sep 17 00:00:00 2001 From: shanghui Date: Wed, 8 Nov 2017 16:32:49 +0800 Subject: [PATCH 196/389] [1.11.x] Fixed #28645 -- Reallowed AuthenticationForm to raise the inactive user error when using ModelBackend. Regression in e0a3d937309a82b8beea8f41b17d8b6298da2a86. Thanks Guilherme Junqueira for the report and Tim Graham for the review. Backport of 359370a8b8ca0efe99b1d4630b291ec060b69225 from master --- django/contrib/auth/forms.py | 9 +++++++++ docs/releases/1.11.8.txt | 3 ++- tests/auth_tests/test_forms.py | 5 ++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 02250d83da6f..abfd96aeec30 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -186,6 +186,15 @@ def clean(self): if username is not None and password: self.user_cache = authenticate(self.request, username=username, password=password) if self.user_cache is None: + # An authentication backend may reject inactive users. Check + # if the user exists and is inactive, and raise the 'inactive' + # error if so. + try: + self.user_cache = UserModel._default_manager.get_by_natural_key(username) + except UserModel.DoesNotExist: + pass + else: + self.confirm_login_allowed(self.user_cache) raise forms.ValidationError( self.error_messages['invalid_login'], code='invalid_login', diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index dd9d19ae19e2..dd9fb0c3cef8 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -9,4 +9,5 @@ Django 1.11.8 fixes several bugs in 1.11.7. Bugfixes ======== -* ... +* Reallowed, following a regression in Django 1.10, ``AuthenticationForm`` to + raise the inactive user error when using ``ModelBackend`` (:ticket:`28645`). diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py index 4bfc3d11e730..b82bbc58f4c6 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -249,9 +249,6 @@ def test_password_help_text(self): ) -# To verify that the login form rejects inactive users, use an authentication -# backend that allows them. -@override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend']) class AuthenticationFormTest(TestDataMixin, TestCase): def test_invalid_username(self): @@ -310,6 +307,8 @@ def test_inactive_user_i18n(self): self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), [force_text(form.error_messages['inactive'])]) + # Use an authentication backend that allows inactive users. + @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend']) def test_custom_login_allowed_policy(self): # The user is inactive, but our custom form policy allows them to log in. data = { From 67316720603821ebb64dfe8fa592ba6edcef5f3e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 12 Nov 2017 14:28:11 +0100 Subject: [PATCH 197/389] [1.11.x] Fixed #28781 -- Added QuerySet.values()/values_list() support for union(), difference(), and intersection(). Thanks Tim Graham for the review. Backport of 2d3cc94284674638c334670903d49565039d77ae from master --- django/db/models/sql/compiler.py | 5 +++++ docs/ref/models/querysets.txt | 13 +++++++++--- docs/releases/1.11.8.txt | 4 ++++ tests/queries/test_qs_combinators.py | 30 ++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 6ec2284a91d0..9888816c8d8e 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -390,6 +390,11 @@ def get_combinator_sql(self, combinator, all): parts = () for compiler in compilers: try: + # If the columns list is limited, then all combined queries + # must have the same columns list. Set the selects defined on + # the query on all combined queries, if not already set. + if not compiler.query.values_select and self.query.values_select: + compiler.query.set_values(self.query.values_select) parts += (compiler.as_sql(),) except EmptyResultSet: # Omit the empty queryset with UNION and with DIFFERENCE if the diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 179696d764a2..c1c2a249f8fa 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -811,10 +811,17 @@ duplicate values, use the ``all=True`` argument. of the type of the first ``QuerySet`` even if the arguments are ``QuerySet``\s of other models. Passing different models works as long as the ``SELECT`` list is the same in all ``QuerySet``\s (at least the types, the names don't matter -as long as the types in the same order). +as long as the types in the same order). In such cases, you must use the column +names from the first ``QuerySet`` in ``QuerySet`` methods applied to the +resulting ``QuerySet``. For example:: -In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, and ``ORDER BY`` (i.e. -slicing, :meth:`count`, and :meth:`order_by`) are allowed on the resulting + >>> qs1 = Author.objects.values_list('name') + >>> qs2 = Entry.objects.values_list('headline') + >>> qs1.union(qs2).order_by('name') + +In addition, only ``LIMIT``, ``OFFSET``, ``COUNT(*)``, ``ORDER BY``, and +specifying columns (i.e. slicing, :meth:`count`, :meth:`order_by`, and +:meth:`values()`/:meth:`values_list()`) are allowed on the resulting ``QuerySet``. Further, databases place restrictions on what operations are allowed in the combined queries. For example, most databases don't allow ``LIMIT`` or ``OFFSET`` in the combined queries. diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index dd9fb0c3cef8..7e4963f7132a 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -11,3 +11,7 @@ Bugfixes * Reallowed, following a regression in Django 1.10, ``AuthenticationForm`` to raise the inactive user error when using ``ModelBackend`` (:ticket:`28645`). + +* Added support for ``QuerySet.values()`` and ``values_list()`` for + ``union()``, ``difference()``, and ``intersection()`` queries + (:ticket:`28781`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index ef8c188c0596..3450014d94a8 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -33,6 +33,16 @@ def test_simple_intersection(self): qs3 = Number.objects.filter(num__gte=4, num__lte=6) self.assertNumbersEqual(qs1.intersection(qs2, qs3), [5], ordered=False) + @skipUnlessDBFeature('supports_select_intersection') + def test_intersection_with_values(self): + ReservedName.objects.create(name='a', order=2) + qs1 = ReservedName.objects.all() + reserved_name = qs1.intersection(qs1).values('name', 'order', 'id').get() + self.assertEqual(reserved_name['name'], 'a') + self.assertEqual(reserved_name['order'], 2) + reserved_name = qs1.intersection(qs1).values_list('name', 'order', 'id').get() + self.assertEqual(reserved_name[:2], ('a', 2)) + @skipUnlessDBFeature('supports_select_difference') def test_simple_difference(self): qs1 = Number.objects.filter(num__lte=5) @@ -69,6 +79,17 @@ def test_difference_with_empty_qs(self): self.assertEqual(len(qs2.difference(qs2)), 0) self.assertEqual(len(qs3.difference(qs3)), 0) + @skipUnlessDBFeature('supports_select_difference') + def test_difference_with_values(self): + ReservedName.objects.create(name='a', order=2) + qs1 = ReservedName.objects.all() + qs2 = ReservedName.objects.none() + reserved_name = qs1.difference(qs2).values('name', 'order', 'id').get() + self.assertEqual(reserved_name['name'], 'a') + self.assertEqual(reserved_name['order'], 2) + reserved_name = qs1.difference(qs2).values_list('name', 'order', 'id').get() + self.assertEqual(reserved_name[:2], ('a', 2)) + def test_union_with_empty_qs(self): qs1 = Number.objects.all() qs2 = Number.objects.none() @@ -98,6 +119,15 @@ def test_ordering(self): qs2 = Number.objects.filter(num__gte=2, num__lte=3) self.assertNumbersEqual(qs1.union(qs2).order_by('-num'), [3, 2, 1, 0]) + def test_union_with_values(self): + ReservedName.objects.create(name='a', order=2) + qs1 = ReservedName.objects.all() + reserved_name = qs1.union(qs1).values('name', 'order', 'id').get() + self.assertEqual(reserved_name['name'], 'a') + self.assertEqual(reserved_name['order'], 2) + reserved_name = qs1.union(qs1).values_list('name', 'order', 'id').get() + self.assertEqual(reserved_name[:2], ('a', 2)) + def test_count_union(self): qs1 = Number.objects.filter(num__lte=1).values('num') qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') From afcde50497983ec48020600345367e161f6ff9c3 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 8 Nov 2017 10:02:30 -0500 Subject: [PATCH 198/389] [1.11.x] Fixed #28786 -- Doc'd middleware ordering considerations due to CommonMiddleware setting Content-Length. Backport of bc95314ca6af0b5e993ae07fdc7d8e6166d3b8ca from master --- docs/releases/1.11.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 3da71a0dd793..5b6b17a01295 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -727,6 +727,12 @@ Miscellaneous ``Content-Length`` header as this is now done by :class:`~django.middleware.common.CommonMiddleware`. + If you have a middleware that modifies a response's content and appears + before ``CommonMiddleware`` in the ``MIDDLEWARE`` or ``MIDDLEWARE_CLASSES`` + settings, you must reorder your middleware so that responses aren't modified + after ``Content-Length`` is set, or have the response modifying middleware + reset the ``Content-Length`` header. + * :meth:`~django.apps.AppConfig.get_model` and :meth:`~django.apps.AppConfig.get_models` now raise :exc:`~django.core.exceptions.AppRegistryNotReady` if they're called before From a35ab95ed4eec5c62fa19bdc69ecfe0eff3e1fca Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sat, 11 Nov 2017 19:17:20 -0500 Subject: [PATCH 199/389] [1.11.x] Fixed #28792 -- Fixed index name truncation of namespaced tables. Refs #27458, #27843. Thanks Tim and Mariusz for the review. Backport of ee85ef8315db839e5723dea19d8b971420a2ebb4 from master --- django/db/backends/base/schema.py | 4 ++-- django/db/backends/utils.py | 34 ++++++++++++++++++++++--------- docs/releases/1.11.8.txt | 3 +++ tests/backends/test_utils.py | 8 +++++++- tests/schema/tests.py | 25 +++++++++++++++++++++++ 5 files changed, 61 insertions(+), 13 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index f4b8b02049dc..56102ff27b65 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -2,7 +2,7 @@ import logging from datetime import datetime -from django.db.backends.utils import strip_quotes +from django.db.backends.utils import split_identifier from django.db.models import Index from django.db.transaction import TransactionManagementError, atomic from django.utils import six, timezone @@ -852,7 +852,7 @@ def _create_index_name(self, model, column_names, suffix=""): The name is divided into 3 parts: the table name, the column names, and a unique digest and suffix. """ - table_name = strip_quotes(model._meta.db_table) + _, table_name = split_identifier(model._meta.db_table) hash_data = [table_name] + list(column_names) hash_suffix_part = '%s%s' % (self._digest(*hash_data), suffix) max_length = self.connection.ops.max_name_length() or 200 diff --git a/django/db/backends/utils.py b/django/db/backends/utils.py index ad87eb82e7a5..e7f55d29728f 100644 --- a/django/db/backends/utils.py +++ b/django/db/backends/utils.py @@ -4,7 +4,6 @@ import decimal import hashlib import logging -import re from time import time from django.conf import settings @@ -180,20 +179,35 @@ def rev_typecast_decimal(d): return str(d) -def truncate_name(name, length=None, hash_len=4): +def split_identifier(identifier): """ - Shorten a string to a repeatable mangled version with the given length. - If a quote stripped name contains a username, e.g. USERNAME"."TABLE, + Split a SQL identifier into a two element tuple of (namespace, name). + + The identifier could be a table, column, or sequence name might be prefixed + by a namespace. + """ + try: + namespace, name = identifier.split('"."') + except ValueError: + namespace, name = '', identifier + return namespace.strip('"'), name.strip('"') + + +def truncate_name(identifier, length=None, hash_len=4): + """ + Shorten a SQL identifier to a repeatable mangled version with the given + length. + + If a quote stripped name contains a namespace, e.g. USERNAME"."TABLE, truncate the table portion only. """ - match = re.match(r'([^"]+)"\."([^"]+)', name) - table_name = match.group(2) if match else name + namespace, name = split_identifier(identifier) - if length is None or len(table_name) <= length: - return name + if length is None or len(name) <= length: + return identifier - hsh = hashlib.md5(force_bytes(table_name)).hexdigest()[:hash_len] - return '%s%s%s' % (match.group(1) + '"."' if match else '', table_name[:length - hash_len], hsh) + digest = hashlib.md5(force_bytes(name)).hexdigest()[:hash_len] + return '%s%s%s' % ('%s"."' % namespace if namespace else '', name[:length - hash_len], digest) def format_number(value, max_digits, decimal_places): diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 7e4963f7132a..426b6d92b285 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -15,3 +15,6 @@ Bugfixes * Added support for ``QuerySet.values()`` and ``values_list()`` for ``union()``, ``difference()``, and ``intersection()`` queries (:ticket:`28781`). + +* Fixed incorrect index name truncation when using a namespaced ``db_table`` + (:ticket:`28792`). diff --git a/tests/backends/test_utils.py b/tests/backends/test_utils.py index 47720f7c924d..6fac407a6e24 100644 --- a/tests/backends/test_utils.py +++ b/tests/backends/test_utils.py @@ -1,5 +1,5 @@ from django.core.exceptions import ImproperlyConfigured -from django.db.backends.utils import truncate_name +from django.db.backends.utils import split_identifier, truncate_name from django.db.utils import load_backend from django.test import SimpleTestCase from django.utils import six @@ -25,3 +25,9 @@ def test_truncate_name(self): self.assertEqual(truncate_name('username"."some_table', 10), 'username"."some_table') self.assertEqual(truncate_name('username"."some_long_table', 10), 'username"."some_la38a') self.assertEqual(truncate_name('username"."some_long_table', 10, 3), 'username"."some_loa38') + + def test_split_identifier(self): + self.assertEqual(split_identifier('some_table'), ('', 'some_table')) + self.assertEqual(split_identifier('"some_table"'), ('', 'some_table')) + self.assertEqual(split_identifier('namespace"."some_table'), ('namespace', 'some_table')) + self.assertEqual(split_identifier('"namespace"."some_table"'), ('namespace', 'some_table')) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 671e1234ffe1..8e868dfb0e84 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -2267,6 +2267,31 @@ def test_add_datefield_and_datetimefield_use_effective_default(self, mocked_date cast_function=lambda x: x.time(), ) + @isolate_apps('schema') + def test_namespaced_db_table_create_index_name(self): + """ + Table names are stripped of their namespace/schema before being used to + generate index names. + """ + with connection.schema_editor() as editor: + max_name_length = connection.ops.max_name_length() or 200 + namespace = 'n' * max_name_length + table_name = 't' * max_name_length + + class TableName(Model): + class Meta: + app_label = 'schema' + db_table = table_name + + class NameSpacedTableName(Model): + class Meta: + app_label = 'schema' + db_table = '"%s"."%s"' % (namespace, table_name) + self.assertEqual( + editor._create_index_name(TableName, []), + editor._create_index_name(NameSpacedTableName, []), + ) + @unittest.skipUnless(connection.vendor == 'oracle', 'Oracle specific db_table syntax') def test_creation_with_db_table_double_quotes(self): oracle_user = connection.creation._test_database_user() From dc629097af783e305e1bddbbd39745846fea6cdf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 15 Nov 2017 09:37:18 -0500 Subject: [PATCH 200/389] [1.11.x] Fixed test failures due to ordering differences on PostgreSQL 10. Backport of 9bea555d06e0e585645053ae6ca9ac3dc8b899bd from master --- tests/postgres_tests/test_search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/postgres_tests/test_search.py b/tests/postgres_tests/test_search.py index 0bf2df50f1d6..b93077f9111a 100644 --- a/tests/postgres_tests/test_search.py +++ b/tests/postgres_tests/test_search.py @@ -125,7 +125,7 @@ def test_simple_on_scene(self): searched = Line.objects.annotate( search=SearchVector('scene__setting', 'dialogue'), ).filter(search='Forest') - self.assertSequenceEqual(searched, self.verses) + self.assertCountEqual(searched, self.verses) def test_non_exact_match(self): searched = Line.objects.annotate( @@ -143,7 +143,7 @@ def test_terms_adjacent(self): searched = Line.objects.annotate( search=SearchVector('character__name', 'dialogue'), ).filter(search='minstrel') - self.assertSequenceEqual(searched, self.verses) + self.assertCountEqual(searched, self.verses) searched = Line.objects.annotate( search=SearchVector('scene__setting', 'dialogue'), ).filter(search='minstrelbravely') From 18324f2e657c68947e110f96a1a529e1e6c3f55e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 16 Nov 2017 10:37:50 -0500 Subject: [PATCH 201/389] [1.11.x] Fixed #28802 -- Fixed typo in docs/topics/auth/default.txt. Backport of d392fc293c9439c19451e152f9560f24d1659563 from master --- docs/releases/1.8.txt | 2 +- docs/topics/auth/default.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 54120609f8f1..43cf453da839 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -200,7 +200,7 @@ Minor features and :meth:`~django.contrib.auth.models.User.has_module_perms` to short-circuit permission checking. * :class:`~django.contrib.auth.forms.PasswordResetForm` now - has a method :meth:`~django.contrib.auth.forms.PasswordResetForm.send_email` + has a method :meth:`~django.contrib.auth.forms.PasswordResetForm.send_mail` that can be overridden to customize the mail to be sent. * The ``max_length`` of :attr:`Permission.name diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index b00da04c25cf..727336a3617d 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -1673,7 +1673,7 @@ provides several built-in forms located in :mod:`django.contrib.auth.forms`: A form for generating and emailing a one-time use link to reset a user's password. - .. method:: send_email(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None) + .. method:: send_mail(subject_template_name, email_template_name, context, from_email, to_email, html_email_template_name=None) Uses the arguments to send an ``EmailMultiAlternatives``. Can be overridden to customize how the email is sent to the user. From 90c120c2b73534bd84166f5e67a01cddfeb4cede Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 21 Nov 2017 08:11:50 -0500 Subject: [PATCH 202/389] [1.11.x] Added assertion helpers for PostgreSQL's server-side cursor tests. Backport of 6cb6382639cbd29c8348b42c4d43b02c950eff3a from master --- tests/backends/test_postgresql.py | 40 +++++++++++++------------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/tests/backends/test_postgresql.py b/tests/backends/test_postgresql.py index 969e3dfaef6a..8ea86383060e 100644 --- a/tests/backends/test_postgresql.py +++ b/tests/backends/test_postgresql.py @@ -37,29 +37,28 @@ def override_db_setting(self, **kwargs): connection.settings_dict[setting] = kwargs[setting] yield - def test_server_side_cursor(self): - persons = Person.objects.iterator() - next(persons) # Open a server-side cursor - cursors = self.inspect_cursors() - self.assertEqual(len(cursors), 1) - self.assertIn('_django_curs_', cursors[0].name) - self.assertFalse(cursors[0].is_scrollable) - self.assertFalse(cursors[0].is_holdable) - self.assertFalse(cursors[0].is_binary) - - def test_server_side_cursor_many_cursors(self): - persons = Person.objects.iterator() - persons2 = Person.objects.iterator() - next(persons) # Open a server-side cursor - next(persons2) # Open a second server-side cursor + def assertUsesCursor(self, queryset, num_expected=1): + next(queryset) # Open a server-side cursor cursors = self.inspect_cursors() - self.assertEqual(len(cursors), 2) + self.assertEqual(len(cursors), num_expected) for cursor in cursors: self.assertIn('_django_curs_', cursor.name) self.assertFalse(cursor.is_scrollable) self.assertFalse(cursor.is_holdable) self.assertFalse(cursor.is_binary) + def asserNotUsesCursor(self, queryset): + self.assertUsesCursor(queryset, num_expected=0) + + def test_server_side_cursor(self): + self.assertUsesCursor(Person.objects.iterator()) + + def test_server_side_cursor_many_cursors(self): + persons = Person.objects.iterator() + persons2 = Person.objects.iterator() + next(persons) # Open a server-side cursor + self.assertUsesCursor(persons2, num_expected=2) + def test_closed_server_side_cursor(self): persons = Person.objects.iterator() next(persons) # Open a server-side cursor @@ -70,13 +69,8 @@ def test_closed_server_side_cursor(self): def test_server_side_cursors_setting(self): with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=False): persons = Person.objects.iterator() - next(persons) # Open a server-side cursor - cursors = self.inspect_cursors() - self.assertEqual(len(cursors), 1) + self.assertUsesCursor(persons) del persons # Close server-side cursor with self.override_db_setting(DISABLE_SERVER_SIDE_CURSORS=True): - persons = Person.objects.iterator() - next(persons) # Should not open a server-side cursor - cursors = self.inspect_cursors() - self.assertEqual(len(cursors), 0) + self.asserNotUsesCursor(Person.objects.iterator()) From 7f4e17451135a8aee597e24aac4670a6d8860047 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Odoba=C5=A1i=C4=87?= Date: Sun, 19 Nov 2017 10:13:10 -0500 Subject: [PATCH 203/389] [1.11.x] Fixed #28817 -- Made QuerySet.iterator() use server-side cursors after values() and values_list(). Backport of d97f026a7ab5212192426e45121f7a52751a2044 from master --- django/db/models/query.py | 8 ++++---- django/db/models/sql/compiler.py | 4 ++-- docs/releases/1.11.8.txt | 3 +++ tests/backends/test_postgresql.py | 15 ++++++++++++++- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 8a97e45b8801..379edf38826c 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -103,7 +103,7 @@ def __iter__(self): # extra(select=...) cols are always at the start of the row. names = extra_names + field_names + annotation_names - for row in compiler.results_iter(): + for row in compiler.results_iter(chunked_fetch=self.chunked_fetch): yield dict(zip(names, row)) @@ -119,7 +119,7 @@ def __iter__(self): compiler = query.get_compiler(queryset.db) if not query.extra_select and not query.annotation_select: - for row in compiler.results_iter(): + for row in compiler.results_iter(chunked_fetch=self.chunked_fetch): yield tuple(row) else: field_names = list(query.values_select) @@ -135,7 +135,7 @@ def __iter__(self): else: fields = names - for row in compiler.results_iter(): + for row in compiler.results_iter(chunked_fetch=self.chunked_fetch): data = dict(zip(names, row)) yield tuple(data[f] for f in fields) @@ -149,7 +149,7 @@ class FlatValuesListIterable(BaseIterable): def __iter__(self): queryset = self.queryset compiler = queryset.query.get_compiler(queryset.db) - for row in compiler.results_iter(): + for row in compiler.results_iter(chunked_fetch=self.chunked_fetch): yield row[0] diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 9888816c8d8e..e40770c1511e 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -833,12 +833,12 @@ def apply_converters(self, row, converters): row[pos] = value return tuple(row) - def results_iter(self, results=None): + def results_iter(self, results=None, chunked_fetch=False): """ Returns an iterator over the results from executing this query. """ if results is None: - results = self.execute_sql(MULTI) + results = self.execute_sql(MULTI, chunked_fetch=chunked_fetch) fields = [s[0] for s in self.select[0:self.col_count]] converters = self.get_converters(fields) for rows in results: diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 426b6d92b285..89b6e5ed524c 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -18,3 +18,6 @@ Bugfixes * Fixed incorrect index name truncation when using a namespaced ``db_table`` (:ticket:`28792`). + +* Made ``QuerySet.iterator()`` use server-side cursors on PostgreSQL after + ``values()`` and ``values_list()`` (:ticket:`28817`). diff --git a/tests/backends/test_postgresql.py b/tests/backends/test_postgresql.py index 8ea86383060e..2efec5eef269 100644 --- a/tests/backends/test_postgresql.py +++ b/tests/backends/test_postgresql.py @@ -3,7 +3,7 @@ from collections import namedtuple from contextlib import contextmanager -from django.db import connection +from django.db import connection, models from django.test import TestCase from .models import Person @@ -53,6 +53,19 @@ def asserNotUsesCursor(self, queryset): def test_server_side_cursor(self): self.assertUsesCursor(Person.objects.iterator()) + def test_values(self): + self.assertUsesCursor(Person.objects.values('first_name').iterator()) + + def test_values_list(self): + self.assertUsesCursor(Person.objects.values_list('first_name').iterator()) + + def test_values_list_flat(self): + self.assertUsesCursor(Person.objects.values_list('first_name', flat=True).iterator()) + + def test_values_list_fields_not_equal_to_names(self): + expr = models.Count('id') + self.assertUsesCursor(Person.objects.annotate(id__count1=expr).values_list(expr, 'id__count1').iterator()) + def test_server_side_cursor_many_cursors(self): persons = Person.objects.iterator() persons2 = Person.objects.iterator() From be45c90ce30fcf3262b8c739c04163f9ad61f46a Mon Sep 17 00:00:00 2001 From: Hyunwoo Park Date: Wed, 22 Nov 2017 15:43:47 +0900 Subject: [PATCH 204/389] [1.11.x] Fixed typo in docs/topics/forms/media.txt. Backport of 3f237c1a5b936a9b85304cffbf3343f491e395d6 from master --- docs/topics/forms/media.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/forms/media.txt b/docs/topics/forms/media.txt index adbd2c377ed5..42f4f3b58fef 100644 --- a/docs/topics/forms/media.txt +++ b/docs/topics/forms/media.txt @@ -241,7 +241,7 @@ But if :setting:`STATIC_URL` is ``'http://static.example.com/'``:: Or if :mod:`~django.contrib.staticfiles` is configured using the -`~django.contib.staticfiles.ManifestStaticFilesStorage`:: +:class:`~django.contrib.staticfiles.storage.ManifestStaticFilesStorage`:: >>> w = CalendarWidget() >>> print(w.media) From a09e974688e283019df32c35c6d7c217d1d16a8e Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Thu, 23 Nov 2017 21:26:39 +0300 Subject: [PATCH 205/389] [1.11.x] Linked to prefetch_related_objects func in DB optimization docs. Backport of e283c1a from master --- docs/topics/db/optimization.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index ea29108c0cf4..96ff65fd7e81 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -195,12 +195,13 @@ Understand :meth:`~django.db.models.query.QuerySet.select_related` and :meth:`~django.db.models.query.QuerySet.prefetch_related` thoroughly, and use them: -* in view code, - -* and in :doc:`managers and default managers ` where +* in :doc:`managers and default managers ` where appropriate. Be aware when your manager is and is not used; sometimes this is tricky so don't make assumptions. +* in view code or other layers, possibly making use of + :func:`~django.db.models.prefetch_related_objects` where needed. + Don't retrieve things you don't need ==================================== From 899999db4293d40613626833860de28e8ccdd413 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 26 Nov 2017 17:32:17 +0100 Subject: [PATCH 206/389] [1.11.x] Fixed #28848 -- Fixed SQLite/MySQL crash when ordering by a filtered subquery that uses nulls_first/nulls_last. Backport of 616f468760e4984915bb2ccca6b9eb3d80ddadb0 from master --- django/db/models/expressions.py | 1 + docs/releases/1.11.8.txt | 3 +++ tests/ordering/tests.py | 23 ++++++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index 2db832e1ebcb..e7084ebfc68a 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -1063,6 +1063,7 @@ def as_sql(self, compiler, connection, template=None, **extra_context): } placeholders.update(extra_context) template = template or self.template + params *= template.count('%(expression)s') return (template % placeholders).rstrip(), params def as_sqlite(self, compiler, connection): diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 89b6e5ed524c..6f744a688664 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -21,3 +21,6 @@ Bugfixes * Made ``QuerySet.iterator()`` use server-side cursors on PostgreSQL after ``values()`` and ``values_list()`` (:ticket:`28817`). + +* Fixed crash on SQLite and MySQL when ordering by a filtered subquery that + uses ``nulls_first`` or ``nulls_last`` (:ticket:`28848`). diff --git a/tests/ordering/tests.py b/tests/ordering/tests.py index 62ce558a93ba..d93c511e6154 100644 --- a/tests/ordering/tests.py +++ b/tests/ordering/tests.py @@ -3,7 +3,7 @@ from datetime import datetime from operator import attrgetter -from django.db.models import F +from django.db.models import DateTimeField, F, Max, OuterRef, Subquery from django.db.models.functions import Upper from django.test import TestCase @@ -140,6 +140,27 @@ def test_order_by_nulls_first(self): [self.a1, self.a2, self.a4, self.a3], ) + def test_orders_nulls_first_on_filtered_subquery(self): + Article.objects.filter(headline='Article 1').update(author=self.author_1) + Article.objects.filter(headline='Article 2').update(author=self.author_1) + Article.objects.filter(headline='Article 4').update(author=self.author_2) + Author.objects.filter(name__isnull=True).delete() + author_3 = Author.objects.create(name='Name 3') + article_subquery = Article.objects.filter( + author=OuterRef('pk'), + headline__icontains='Article', + ).order_by().values('author').annotate( + last_date=Max('pub_date'), + ).values('last_date') + self.assertQuerysetEqualReversible( + Author.objects.annotate( + last_date=Subquery(article_subquery, output_field=DateTimeField()) + ).order_by( + F('last_date').asc(nulls_first=True) + ).distinct(), + [author_3, self.author_1, self.author_2], + ) + def test_stop_slicing(self): """ Use the 'stop' part of slicing notation to limit the results. From 3545e844885608932a692d952c12cd863e2320b5 Mon Sep 17 00:00:00 2001 From: Mads Jensen Date: Tue, 14 Nov 2017 22:51:51 +0100 Subject: [PATCH 207/389] [1.11.x] Fixed #28702 -- Made query lookups for CIText fields use citext. Backport of f0a68c25118786d47041d0a435b2afa953be3c86 from master --- django/contrib/postgres/fields/citext.py | 6 +++++- django/db/backends/postgresql/operations.py | 2 ++ docs/releases/1.11.8.txt | 3 +++ tests/backends/tests.py | 10 ++++++++-- tests/postgres_tests/test_citext.py | 16 ++++++++++++++++ 5 files changed, 34 insertions(+), 3 deletions(-) diff --git a/django/contrib/postgres/fields/citext.py b/django/contrib/postgres/fields/citext.py index 42660001ae61..f5ef86c586c6 100644 --- a/django/contrib/postgres/fields/citext.py +++ b/django/contrib/postgres/fields/citext.py @@ -3,7 +3,11 @@ __all__ = ['CICharField', 'CIEmailField', 'CIText', 'CITextField'] -class CIText: +class CIText(object): + + def get_internal_type(self): + return 'CI' + super(CIText, self).get_internal_type() + def db_type(self, connection): return 'citext' diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index a66cb0c563ed..f2206397250c 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -83,6 +83,8 @@ def lookup_cast(self, lookup_type, internal_type=None): 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'): if internal_type in ('IPAddressField', 'GenericIPAddressField'): lookup = "HOST(%s)" + elif internal_type in ('CICharField', 'CIEmailField', 'CITextField'): + lookup = '%s::citext' else: lookup = "%s::text" diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 6f744a688664..959731bbd5c4 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -24,3 +24,6 @@ Bugfixes * Fixed crash on SQLite and MySQL when ordering by a filtered subquery that uses ``nulls_first`` or ``nulls_last`` (:ticket:`28848`). + +* Made query lookups for ``CICharField``, ``CIEmailField``, and ``CITextField`` + use a ``citext`` cast (:ticket:`28702`). diff --git a/tests/backends/tests.py b/tests/backends/tests.py index 16a20de40d66..22176693549f 100644 --- a/tests/backends/tests.py +++ b/tests/backends/tests.py @@ -377,9 +377,15 @@ def test_lookup_cast(self): from django.db.backends.postgresql.operations import DatabaseOperations do = DatabaseOperations(connection=None) - for lookup in ('iexact', 'contains', 'icontains', 'startswith', - 'istartswith', 'endswith', 'iendswith', 'regex', 'iregex'): + lookups = ( + 'iexact', 'contains', 'icontains', 'startswith', 'istartswith', + 'endswith', 'iendswith', 'regex', 'iregex', + ) + for lookup in lookups: self.assertIn('::text', do.lookup_cast(lookup)) + for lookup in lookups: + for field_type in ('CICharField', 'CIEmailField', 'CITextField'): + self.assertIn('::citext', do.lookup_cast(lookup, internal_type=field_type)) def test_correct_extraction_psycopg2_version(self): from django.db.backends.postgresql.base import psycopg2_version diff --git a/tests/postgres_tests/test_citext.py b/tests/postgres_tests/test_citext.py index 0a7012b07204..22dc9e743150 100644 --- a/tests/postgres_tests/test_citext.py +++ b/tests/postgres_tests/test_citext.py @@ -12,6 +12,7 @@ @modify_settings(INSTALLED_APPS={'append': 'django.contrib.postgres'}) class CITextTestCase(PostgreSQLTestCase): + case_sensitive_lookups = ('contains', 'startswith', 'endswith', 'regex') @classmethod def setUpTestData(cls): @@ -42,3 +43,18 @@ def test_array_field(self): instance = CITestModel.objects.get() self.assertEqual(instance.array_field, self.john.array_field) self.assertTrue(CITestModel.objects.filter(array_field__contains=['joe']).exists()) + + def test_lookups_name_char(self): + for lookup in self.case_sensitive_lookups: + query = {'name__{}'.format(lookup): 'john'} + self.assertSequenceEqual(CITestModel.objects.filter(**query), [self.john]) + + def test_lookups_description_text(self): + for lookup, string in zip(self.case_sensitive_lookups, ('average', 'average joe', 'john', 'Joe.named')): + query = {'description__{}'.format(lookup): string} + self.assertSequenceEqual(CITestModel.objects.filter(**query), [self.john]) + + def test_lookups_email(self): + for lookup, string in zip(self.case_sensitive_lookups, ('john', 'john', 'john.com', 'john.com')): + query = {'email__{}'.format(lookup): string} + self.assertSequenceEqual(CITestModel.objects.filter(**query), [self.john]) From f319e7abad145ddcb1017293b5cdb7e09a92ee85 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Wed, 29 Nov 2017 01:06:45 -0500 Subject: [PATCH 208/389] [1.11.x] Fixed #28856 -- Fixed a regression in caching of a GenericForeignKey pointing to a MTI model. Regression in b9f8635f58ad743995cad2081b3dc395e55761e5. Backport of d31424fec1a3de9d281535c0503644a9d7b93c63 from stable/2.0.x --- django/contrib/contenttypes/fields.py | 13 ++++++++++--- docs/releases/1.11.8.txt | 3 +++ tests/generic_relations_regress/tests.py | 6 ++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index 6c4eb43ac1b7..84aac508142f 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -230,9 +230,16 @@ def __get__(self, instance, cls=None): except AttributeError: rel_obj = None else: - if rel_obj and (ct_id != self.get_content_type(obj=rel_obj, using=instance._state.db).id or - rel_obj._meta.pk.to_python(pk_val) != rel_obj._get_pk_val()): - rel_obj = None + if rel_obj: + if ct_id != self.get_content_type(obj=rel_obj, using=instance._state.db).id: + rel_obj = None + else: + pk = rel_obj._meta.pk + # If the primary key is a remote field, use the referenced + # field's to_python(). + pk_to_python = pk.target_field.to_python if pk.remote_field else pk.to_python + if pk_to_python(pk_val) != rel_obj._get_pk_val(): + rel_obj = None if rel_obj is not None: return rel_obj diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 959731bbd5c4..596ea434ecec 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -27,3 +27,6 @@ Bugfixes * Made query lookups for ``CICharField``, ``CIEmailField``, and ``CITextField`` use a ``citext`` cast (:ticket:`28702`). + +* Fixed a regression in caching of a ``GenericForeignKey`` when the referenced + model instance uses multi-table inheritance (:ticket:`28856`). diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index b2d5b0869234..e6d350aa5b1d 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -48,6 +48,12 @@ def test_textlink_delete(self): TextLink.objects.create(content_object=oddrel) oddrel.delete() + def test_coerce_object_id_remote_field_cache_persistence(self): + restaurant = Restaurant.objects.create() + CharLink.objects.create(content_object=restaurant) + charlink = CharLink.objects.latest('pk') + self.assertIs(charlink.content_object, charlink.content_object) + def test_q_object_or(self): """ SQL query parameters for generic relations are properly From b8a2f3c2d66aa15af4be745a576609b958a853c0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 30 Jun 2017 14:51:08 -0400 Subject: [PATCH 209/389] [1.11.x] Fixed #28305 -- Fixed "Cannot change column 'x': used in a foreign key constraint" crash on MySQL with a sequence of AlterField or RenameField operations. Regression in 45ded053b1f4320284aa5dac63052f6d1baefea9. Backport of c3e0adcad8d8ba94b33cabd137056166ed36dae0 from master --- django/db/backends/base/schema.py | 14 +++-- django/db/migrations/operations/fields.py | 17 +++-- django/db/migrations/operations/utils.py | 9 +++ docs/releases/1.11.8.txt | 4 ++ tests/migrations/test_operations.py | 77 +++++++++++++++++++++++ 5 files changed, 113 insertions(+), 8 deletions(-) create mode 100644 django/db/migrations/operations/utils.py diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 56102ff27b65..2c9e016d1109 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -543,9 +543,15 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, )) for constraint_name in constraint_names: self.execute(self._delete_constraint_sql(self.sql_delete_unique, model, constraint_name)) - # Drop incoming FK constraints if we're a primary key and things are going - # to change. - if old_field.primary_key and new_field.primary_key and old_type != new_type: + # Drop incoming FK constraints if the field is a primary key or unique, + # which might be a to_field target, and things are going to change. + drop_foreign_keys = ( + ( + (old_field.primary_key and new_field.primary_key) or + (old_field.unique and new_field.unique) + ) and old_type != new_type + ) + if drop_foreign_keys: # '_meta.related_field' also contains M2M reverse fields, these # will be filtered out for _old_rel, new_rel in _related_non_m2m_objects(old_field, new_field): @@ -772,7 +778,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, new_field.db_constraint): self.execute(self._create_fk_sql(model, new_field, "_fk_%(to_table)s_%(to_column)s")) # Rebuild FKs that pointed to us if we previously had to drop them - if old_field.primary_key and new_field.primary_key and old_type != new_type: + if drop_foreign_keys: for rel in new_field.model._meta.related_objects: if not rel.many_to_many and rel.field.db_constraint: self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk")) diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py index a458397fd40f..0d5e69047229 100644 --- a/django/db/migrations/operations/fields.py +++ b/django/db/migrations/operations/fields.py @@ -5,6 +5,7 @@ from django.utils.functional import cached_property from .base import Operation +from .utils import is_referenced_by_foreign_key class FieldOperation(Operation): @@ -201,8 +202,12 @@ def state_forwards(self, app_label, state): ] # TODO: investigate if old relational fields must be reloaded or if it's # sufficient if the new field is (#27737). - # Delay rendering of relationships if it's not a relational field - delay = not field.is_relation + # Delay rendering of relationships if it's not a relational field and + # not referenced by a foreign key. + delay = ( + not field.is_relation and + not is_referenced_by_foreign_key(state, self.model_name_lower, self.field, self.name) + ) state.reload_model(app_label, self.model_name_lower, delay=delay) def database_forwards(self, app_label, schema_editor, from_state, to_state): @@ -275,8 +280,12 @@ def state_forwards(self, app_label, state): for index, (name, field) in enumerate(fields): if name == self.old_name: fields[index] = (self.new_name, field) - # Delay rendering of relationships if it's not a relational field. - delay = not field.is_relation + # Delay rendering of relationships if it's not a relational + # field and not referenced by a foreign key. + delay = ( + not field.is_relation and + not is_referenced_by_foreign_key(state, self.model_name_lower, field, self.name) + ) break else: raise FieldDoesNotExist( diff --git a/django/db/migrations/operations/utils.py b/django/db/migrations/operations/utils.py new file mode 100644 index 000000000000..af23ea956346 --- /dev/null +++ b/django/db/migrations/operations/utils.py @@ -0,0 +1,9 @@ +def is_referenced_by_foreign_key(state, model_name_lower, field, field_name): + for state_app_label, state_model in state.models: + for _, f in state.models[state_app_label, state_model].fields: + if (f.related_model and + '%s.%s' % (state_app_label, model_name_lower) == f.related_model.lower() and + hasattr(f, 'to_fields')): + if (f.to_fields[0] is None and field.primary_key) or field_name in f.to_fields: + return True + return False diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 596ea434ecec..9ef7d73abfef 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -30,3 +30,7 @@ Bugfixes * Fixed a regression in caching of a ``GenericForeignKey`` when the referenced model instance uses multi-table inheritance (:ticket:`28856`). + +* Fixed "Cannot change column 'x': used in a foreign key constraint" crash on + MySQL with a sequence of ``AlterField`` and/or ``RenameField`` operations in + a migration (:ticket:`28305`). diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index 64ae395d94f8..0899e92a3db5 100644 --- a/tests/migrations/test_operations.py +++ b/tests/migrations/test_operations.py @@ -1324,6 +1324,83 @@ def assertIdTypeEqualsFkType(): operation.database_backwards("test_alflpkfk", editor, new_state, project_state) assertIdTypeEqualsFkType() + def test_alter_field_reloads_state_on_fk_target_changes(self): + """ + If AlterField doesn't reload state appropriately, the second AlterField + crashes on MySQL due to not dropping the PonyRider.pony foreign key + constraint before modifying the column. + """ + app_label = 'alter_alter_field_reloads_state_on_fk_target_changes' + project_state = self.apply_operations(app_label, ProjectState(), operations=[ + migrations.CreateModel('Rider', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ]), + migrations.CreateModel('Pony', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ('rider', models.ForeignKey('%s.Rider' % app_label, models.CASCADE)), + ]), + migrations.CreateModel('PonyRider', fields=[ + ('id', models.AutoField(primary_key=True)), + ('pony', models.ForeignKey('%s.Pony' % app_label, models.CASCADE)), + ]), + ]) + project_state = self.apply_operations(app_label, project_state, operations=[ + migrations.AlterField('Rider', 'id', models.CharField(primary_key=True, max_length=99)), + migrations.AlterField('Pony', 'id', models.CharField(primary_key=True, max_length=99)), + ]) + + def test_alter_field_reloads_state_on_fk_with_to_field_target_changes(self): + """ + If AlterField doesn't reload state appropriately, the second AlterField + crashes on MySQL due to not dropping the PonyRider.pony foreign key + constraint before modifying the column. + """ + app_label = 'alter_alter_field_reloads_state_on_fk_with_to_field_target_changes' + project_state = self.apply_operations(app_label, ProjectState(), operations=[ + migrations.CreateModel('Rider', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ('slug', models.CharField(unique=True, max_length=100)), + ]), + migrations.CreateModel('Pony', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ('rider', models.ForeignKey('%s.Rider' % app_label, models.CASCADE, to_field='slug')), + ('slug', models.CharField(unique=True, max_length=100)), + ]), + migrations.CreateModel('PonyRider', fields=[ + ('id', models.AutoField(primary_key=True)), + ('pony', models.ForeignKey('%s.Pony' % app_label, models.CASCADE, to_field='slug')), + ]), + ]) + project_state = self.apply_operations(app_label, project_state, operations=[ + migrations.AlterField('Rider', 'slug', models.CharField(unique=True, max_length=99)), + migrations.AlterField('Pony', 'slug', models.CharField(unique=True, max_length=99)), + ]) + + def test_rename_field_reloads_state_on_fk_target_changes(self): + """ + If RenameField doesn't reload state appropriately, the AlterField + crashes on MySQL due to not dropping the PonyRider.pony foreign key + constraint before modifying the column. + """ + app_label = 'alter_rename_field_reloads_state_on_fk_target_changes' + project_state = self.apply_operations(app_label, ProjectState(), operations=[ + migrations.CreateModel('Rider', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ]), + migrations.CreateModel('Pony', fields=[ + ('id', models.CharField(primary_key=True, max_length=100)), + ('rider', models.ForeignKey('%s.Rider' % app_label, models.CASCADE)), + ]), + migrations.CreateModel('PonyRider', fields=[ + ('id', models.AutoField(primary_key=True)), + ('pony', models.ForeignKey('%s.Pony' % app_label, models.CASCADE)), + ]), + ]) + project_state = self.apply_operations(app_label, project_state, operations=[ + migrations.RenameField('Rider', 'id', 'id2'), + migrations.AlterField('Pony', 'id', models.CharField(primary_key=True, max_length=99)), + ]) + def test_rename_field(self): """ Tests the RenameField operation. From cd91f4fe6201292ac9fb7c58137bc96160b32938 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 2 Dec 2017 08:55:33 -0500 Subject: [PATCH 210/389] [1.11.x] Added release date for 1.11.8. Backport of 335aad5d9170b3e3807ebd35a7dd74800df03588 from master --- docs/releases/1.11.8.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.8.txt b/docs/releases/1.11.8.txt index 9ef7d73abfef..b328f2ccb48c 100644 --- a/docs/releases/1.11.8.txt +++ b/docs/releases/1.11.8.txt @@ -2,7 +2,7 @@ Django 1.11.8 release notes =========================== -*Expected December 1, 2017* +*December 2, 2017* Django 1.11.8 fixes several bugs in 1.11.7. From dfe7b85ed7e0c46cb59f545a4eefa9c3fd629f7d Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 2 Dec 2017 09:04:17 -0500 Subject: [PATCH 211/389] [1.11.x] Bumped version for 1.11.8 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 7ec14fd518f5..305809791ac1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 8, 'alpha', 0) +VERSION = (1, 11, 8, 'final', 0) __version__ = get_version(VERSION) From fceefb4d25ab09e4884c7ead5e613bc2b63ee55f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 2 Dec 2017 09:35:54 -0500 Subject: [PATCH 212/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 305809791ac1..81c249733ba7 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 8, 'final', 0) +VERSION = (1, 11, 9, 'alpha', 0) __version__ = get_version(VERSION) From 9a64da939927af492a08a023a344904688c579b5 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Mon, 4 Dec 2017 14:23:16 +0500 Subject: [PATCH 213/389] [1.11.x] Fixed typo in docs/topics/testing/advanced.txt. Backport of 3922f02dc6b10a3268a710a2837027d3999957a3 from master --- docs/topics/testing/advanced.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 996bcf1a0607..865e4fca9bc1 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -25,7 +25,7 @@ restricted subset of the test client API: :meth:`~Client.options()`, and :meth:`~Client.trace()`. * These methods accept all the same arguments *except* for - ``follows``. Since this is just a factory for producing + ``follow``. Since this is just a factory for producing requests, it's up to you to handle the response. * It does not support middleware. Session and authentication From 9ca5ff7996e7539b83dfd9e799ac28014ea422a6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 5 Dec 2017 10:54:33 -0500 Subject: [PATCH 214/389] [1.11.x] Added stub release notes for 1.11.9. Backport of dfeb19121b40cadd22b81c4b9d0373d617a695ed from master --- docs/releases/1.11.9.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.9.txt diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt new file mode 100644 index 000000000000..851f1f64c1ee --- /dev/null +++ b/docs/releases/1.11.9.txt @@ -0,0 +1,12 @@ +=========================== +Django 1.11.9 release notes +=========================== + +*Expected January 1, 2018* + +Django 1.11.9 fixes several bugs in 1.11.8. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c3378cc23c76..6c534b08e509 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.9 1.11.8 1.11.7 1.11.6 From 47681af34447e5d45f3fdb316497cdf9fbd0b7ce Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Tue, 5 Dec 2017 14:42:10 +0000 Subject: [PATCH 215/389] [1.11.x] Fixed #28890 -- Removed newlines between MultiWidget's subwidgets. Regression in b52c73008a9d67e9ddbb841872dc15cdd3d6ee01. Backport of e014f91a70aa3ccdddb363a733c76e35597424fa from master --- django/forms/jinja2/django/forms/widgets/multiwidget.html | 2 +- .../forms/templates/django/forms/widgets/multiwidget.html | 2 +- docs/releases/1.11.9.txt | 3 ++- tests/forms_tests/widget_tests/test_multiwidget.py | 7 +++++++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/django/forms/jinja2/django/forms/widgets/multiwidget.html b/django/forms/jinja2/django/forms/widgets/multiwidget.html index 003071118257..ae120e91f558 100644 --- a/django/forms/jinja2/django/forms/widgets/multiwidget.html +++ b/django/forms/jinja2/django/forms/widgets/multiwidget.html @@ -1 +1 @@ -{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %} +{% for widget in widget.subwidgets -%}{% include widget.template_name %}{%- endfor %} diff --git a/django/forms/templates/django/forms/widgets/multiwidget.html b/django/forms/templates/django/forms/widgets/multiwidget.html index 003071118257..7e687a136bd9 100644 --- a/django/forms/templates/django/forms/widgets/multiwidget.html +++ b/django/forms/templates/django/forms/widgets/multiwidget.html @@ -1 +1 @@ -{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %} +{% spaceless %}{% for widget in widget.subwidgets %}{% include widget.template_name %}{% endfor %}{% endspaceless %} diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt index 851f1f64c1ee..20d587248fd3 100644 --- a/docs/releases/1.11.9.txt +++ b/docs/releases/1.11.9.txt @@ -9,4 +9,5 @@ Django 1.11.9 fixes several bugs in 1.11.8. Bugfixes ======== -* ... +* Fixed a regression in Django 1.11 that added newlines between ``MultiWidget``'s + subwidgets (:ticket:`28890`). diff --git a/tests/forms_tests/widget_tests/test_multiwidget.py b/tests/forms_tests/widget_tests/test_multiwidget.py index 02cbe10c7cad..e1f3eedd6843 100644 --- a/tests/forms_tests/widget_tests/test_multiwidget.py +++ b/tests/forms_tests/widget_tests/test_multiwidget.py @@ -168,6 +168,13 @@ def test_nested_multiwidget(self): """ )) + def test_no_whitespace_between_widgets(self): + widget = MyMultiWidget(widgets=(TextInput, TextInput())) + self.check_html(widget, 'code', None, html=( + '' + '' + ), strict=True) + def test_deepcopy(self): """ MultiWidget should define __deepcopy__() (#12048). From 3e52fd7595f80ec162fc44798c29399b7e899b9b Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 5 Dec 2017 21:05:10 +0100 Subject: [PATCH 216/389] [1.11.x] Refs #28876 -- Fixed incorrect class-based model index name generation for models with quoted db_table. Thanks Simon Charette and Tim Graham for the review and Carlos E. C. Leite for the report. Backport of f79d9a322c6008e5fada1453aebfb56afc316cc8 from master --- django/db/models/indexes.py | 3 ++- docs/releases/1.11.9.txt | 3 +++ tests/model_indexes/tests.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index 0ee2bf3610c5..7cda26508e43 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -2,6 +2,7 @@ import hashlib +from django.db.backends.utils import split_identifier from django.utils.encoding import force_bytes __all__ = [str('Index')] @@ -101,7 +102,7 @@ def set_name_with_model(self, model): (8 chars) and unique hash + suffix (10 chars). Each part is made to fit its size by truncating the excess length. """ - table_name = model._meta.db_table + _, table_name = split_identifier(model._meta.db_table) column_names = [model._meta.get_field(field_name).column for field_name, order in self.fields_orders] column_names_with_order = [ (('-%s' if order else '%s') % column_name) diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt index 20d587248fd3..c7cca5181810 100644 --- a/docs/releases/1.11.9.txt +++ b/docs/releases/1.11.9.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed a regression in Django 1.11 that added newlines between ``MultiWidget``'s subwidgets (:ticket:`28890`). + +* Fixed incorrect class-based model index name generation for models with + quoted ``db_table`` (:ticket:`28876`). diff --git a/tests/model_indexes/tests.py b/tests/model_indexes/tests.py index 74c7cf45f429..d3815eee0d18 100644 --- a/tests/model_indexes/tests.py +++ b/tests/model_indexes/tests.py @@ -1,5 +1,6 @@ from django.db import models from django.test import SimpleTestCase +from django.test.utils import isolate_apps from .models import Book, ChildModel1, ChildModel2 @@ -69,6 +70,18 @@ def test_name_auto_generation(self): with self.assertRaisesMessage(AssertionError, msg): long_field_index.set_name_with_model(Book) + @isolate_apps('model_indexes') + def test_name_auto_generation_with_quoted_db_table(self): + class QuotedDbTable(models.Model): + name = models.CharField(max_length=50) + + class Meta: + db_table = '"t_quoted"' + + index = models.Index(fields=['name']) + index.set_name_with_model(QuotedDbTable) + self.assertEqual(index.name, 't_quoted_name_e4ed1b_idx') + def test_deconstruction(self): index = models.Index(fields=['title']) index.set_name_with_model(Book) From 1decd0197d241b27d54bb12eca04b7e89a9ccba6 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 5 Dec 2017 21:11:20 +0100 Subject: [PATCH 217/389] [1.11.x] Refs #28876 -- Fixed incorrect foreign key constraint name for models with quoted db_table. Thanks Simon Charette and Tim Graham for the review and Carlos E. C. Leite for the report. Backport of fc48047586a8f92262f55d9d2bfb976325844b23 from master --- django/db/backends/base/schema.py | 4 ++-- docs/releases/1.11.9.txt | 3 +++ tests/schema/tests.py | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 2c9e016d1109..8802476c077f 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -951,7 +951,7 @@ def _rename_field_sql(self, table, old_field, new_field, new_type): def _create_fk_sql(self, model, field, suffix): from_table = model._meta.db_table from_column = field.column - to_table = field.target_field.model._meta.db_table + _, to_table = split_identifier(field.target_field.model._meta.db_table) to_column = field.target_field.column suffix = suffix % { "to_table": to_table, @@ -962,7 +962,7 @@ def _create_fk_sql(self, model, field, suffix): "table": self.quote_name(from_table), "name": self.quote_name(self._create_index_name(model, [from_column], suffix=suffix)), "column": self.quote_name(from_column), - "to_table": self.quote_name(to_table), + "to_table": self.quote_name(field.target_field.model._meta.db_table), "to_column": self.quote_name(to_column), "deferrable": self.connection.ops.deferrable_sql(), } diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt index c7cca5181810..fa480d44bca5 100644 --- a/docs/releases/1.11.9.txt +++ b/docs/releases/1.11.9.txt @@ -14,3 +14,6 @@ Bugfixes * Fixed incorrect class-based model index name generation for models with quoted ``db_table`` (:ticket:`28876`). + +* Fixed incorrect foreign key constraint name for models with quoted + ``db_table`` (:ticket:`28876`). diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 8e868dfb0e84..f3ad54b3bf1e 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1784,6 +1784,28 @@ def test_add_foreign_key_long_names(self): with connection.schema_editor() as editor: editor.add_field(BookWithLongName, new_field) + @isolate_apps('schema') + @skipUnlessDBFeature('supports_foreign_keys') + def test_add_foreign_key_quoted_db_table(self): + class Author(Model): + class Meta: + db_table = '"table_author_double_quoted"' + app_label = 'schema' + + class Book(Model): + author = ForeignKey(Author, CASCADE) + + class Meta: + app_label = 'schema' + + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + if connection.vendor == 'mysql': + self.assertForeignKeyExists(Book, 'author_id', '"table_author_double_quoted"') + else: + self.assertForeignKeyExists(Book, 'author_id', 'table_author_double_quoted') + def test_add_foreign_object(self): with connection.schema_editor() as editor: editor.create_model(BookForeignObj) From 35222035029863f95769e2e59beeeb953d125689 Mon Sep 17 00:00:00 2001 From: Morgan Wahl Date: Tue, 5 Dec 2017 16:08:50 -0500 Subject: [PATCH 218/389] [1.11.x] Refs #28856 -- Fixed caching of a GenericForeignKey pointing to a model that uses more than one level of MTI. --- django/contrib/contenttypes/fields.py | 10 +++++++++- docs/releases/1.11.9.txt | 4 ++++ tests/generic_relations_regress/models.py | 6 ++++++ tests/generic_relations_regress/tests.py | 12 +++++++++--- 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/django/contrib/contenttypes/fields.py b/django/contrib/contenttypes/fields.py index 84aac508142f..11afe4df3389 100644 --- a/django/contrib/contenttypes/fields.py +++ b/django/contrib/contenttypes/fields.py @@ -237,7 +237,15 @@ def __get__(self, instance, cls=None): pk = rel_obj._meta.pk # If the primary key is a remote field, use the referenced # field's to_python(). - pk_to_python = pk.target_field.to_python if pk.remote_field else pk.to_python + to_python_field = pk + # Out of an abundance of caution, avoid infinite loops. + seen = {to_python_field} + while to_python_field.remote_field: + to_python_field = to_python_field.target_field + if to_python_field in seen: + break + seen.add(to_python_field) + pk_to_python = to_python_field.to_python if pk_to_python(pk_val) != rel_obj._get_pk_val(): rel_obj = None diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt index fa480d44bca5..10fe3d9f96b4 100644 --- a/docs/releases/1.11.9.txt +++ b/docs/releases/1.11.9.txt @@ -17,3 +17,7 @@ Bugfixes * Fixed incorrect foreign key constraint name for models with quoted ``db_table`` (:ticket:`28876`). + +* Fixed a regression in caching of a ``GenericForeignKey`` when the referenced + model instance uses more than one level of multi-table inheritance + (:ticket:`28856`). diff --git a/tests/generic_relations_regress/models.py b/tests/generic_relations_regress/models.py index 669e7b7186db..5f3b2f849c47 100644 --- a/tests/generic_relations_regress/models.py +++ b/tests/generic_relations_regress/models.py @@ -42,6 +42,12 @@ def __str__(self): return "Restaurant: %s" % self.name +@python_2_unicode_compatible +class Cafe(Restaurant): + def __str__(self): + return "Cafe: %s" % self.name + + @python_2_unicode_compatible class Address(models.Model): street = models.CharField(max_length=80) diff --git a/tests/generic_relations_regress/tests.py b/tests/generic_relations_regress/tests.py index e6d350aa5b1d..adb26f68839f 100644 --- a/tests/generic_relations_regress/tests.py +++ b/tests/generic_relations_regress/tests.py @@ -5,9 +5,10 @@ from django.test import TestCase, skipIfDBFeature from .models import ( - A, Address, B, Board, C, CharLink, Company, Contact, Content, D, Developer, - Guild, HasLinkThing, Link, Node, Note, OddRelation1, OddRelation2, - Organization, Person, Place, Related, Restaurant, Tag, Team, TextLink, + A, Address, B, Board, C, Cafe, CharLink, Company, Contact, Content, D, + Developer, Guild, HasLinkThing, Link, Node, Note, OddRelation1, + OddRelation2, Organization, Person, Place, Related, Restaurant, Tag, Team, + TextLink, ) @@ -53,6 +54,11 @@ def test_coerce_object_id_remote_field_cache_persistence(self): CharLink.objects.create(content_object=restaurant) charlink = CharLink.objects.latest('pk') self.assertIs(charlink.content_object, charlink.content_object) + # If the model (Cafe) uses more than one level of multi-table inheritance. + cafe = Cafe.objects.create() + CharLink.objects.create(content_object=cafe) + charlink = CharLink.objects.latest('pk') + self.assertIs(charlink.content_object, charlink.content_object) def test_q_object_or(self): """ From 06e4e80382b2987e47e9fb1f9083dc117e9be40c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 18 Dec 2017 10:37:43 -1000 Subject: [PATCH 219/389] [1.11.x] Fixed #25277 -- Restored test dependency to the original python-memcached. Backport of 770b9ea77fb5e39d616e62b54c06755e6d4f4d36 from master --- tests/requirements/base.txt | 1 + tests/requirements/py2.txt | 2 -- tests/requirements/py3.txt | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt index ee72121d5200..6ee885e13c99 100644 --- a/tests/requirements/base.txt +++ b/tests/requirements/base.txt @@ -8,6 +8,7 @@ Pillow PyYAML # pylibmc/libmemcached can't be built on Windows. pylibmc; sys.platform != 'win32' +python-memcached >= 1.59 pytz selenium sqlparse diff --git a/tests/requirements/py2.txt b/tests/requirements/py2.txt index 0ed845bb7e1e..43719a354f47 100644 --- a/tests/requirements/py2.txt +++ b/tests/requirements/py2.txt @@ -1,5 +1,3 @@ -r base.txt enum34 -# Due to https://github.com/linsomniac/python-memcached/issues/79 in newer versions. -python-memcached <= 1.53 mock diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt index ced3eed1017c..a3e81b8dcf5c 100644 --- a/tests/requirements/py3.txt +++ b/tests/requirements/py3.txt @@ -1,2 +1 @@ -r base.txt -python3-memcached From 2e5be68b9c13899bb8afd3349f9a7eadb42e00a8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Jan 2018 19:34:34 -0500 Subject: [PATCH 220/389] [1.11.x] Added release date for 1.11.9. Backport of 3ae2bcc7689bb912399c08329d2baa4b5d8bd6b2 from master --- docs/releases/1.11.9.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.9.txt b/docs/releases/1.11.9.txt index 10fe3d9f96b4..a7939a602852 100644 --- a/docs/releases/1.11.9.txt +++ b/docs/releases/1.11.9.txt @@ -2,7 +2,7 @@ Django 1.11.9 release notes =========================== -*Expected January 1, 2018* +*January 1, 2018* Django 1.11.9 fixes several bugs in 1.11.8. From c3eafed987ee7356e270eb2826e096692528f816 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Jan 2018 19:53:50 -0500 Subject: [PATCH 221/389] [1.11.x] Bumped version for 1.11.9 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 81c249733ba7..56c8814caf47 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 9, 'alpha', 0) +VERSION = (1, 11, 9, 'final', 0) __version__ = get_version(VERSION) From dba6075b077a70d7c0d56a6d3d063e59b34b29fc Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 1 Jan 2018 20:12:32 -0500 Subject: [PATCH 222/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 56c8814caf47..af973efba2bc 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 9, 'final', 0) +VERSION = (1, 11, 10, 'alpha', 0) __version__ = get_version(VERSION) From 1bf0e5c43c5d63569446233b4fb8610218b6b467 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 8 Jan 2018 09:57:49 -0500 Subject: [PATCH 223/389] [1.11.x] Fixed GeoIP test failure with the latest data. --- tests/gis_tests/test_geoip.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index fa0df950b68c..36382bfb5d83 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -145,7 +145,7 @@ def test05_unicode_response(self): fqdn = "messe-duesseldorf.com" if self._is_dns_available(fqdn): d = g.city(fqdn) - self.assertEqual('Düsseldorf', d['city']) + self.assertEqual('Essen', d['city']) d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) From 967d824288ddd0b9bc5e713ae8f7ba052ac88967 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 10 Jan 2018 09:39:41 -0500 Subject: [PATCH 224/389] [1.11.x] Fixed a GeoIP2 test failure with the latest GeoIP2 database. Backport of 66d74676e23c32bc676fb0706af8580b391953b6 from master --- tests/gis_tests/test_geoip2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index 7db1648cca55..a2a59a3de640 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -137,10 +137,10 @@ def test04_city(self, gethostbyname): @mock.patch('socket.gethostbyname') def test05_unicode_response(self, gethostbyname): "GeoIP strings should be properly encoded (#16553)." - gethostbyname.return_value = '194.27.42.76' + gethostbyname.return_value = '191.252.51.69' g = GeoIP2() - d = g.city("nigde.edu.tr") - self.assertEqual('Niğde', d['city']) + d = g.city('www.fasano.com.br') + self.assertEqual(d['city'], 'São José dos Campos') d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) From bb39e4b57e036765ea86ebb36913a54ec78b2b04 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 11 Jan 2018 06:48:29 -0500 Subject: [PATCH 225/389] [1.11.x] Fixed #29002 -- Corrected cached template loader docs about when it's automatically enabled. Thanks oTree-org for the suggestion. Backport of 7c00f9fb1cc47e1c993f7728e2b592a1be29dd40 from master --- docs/ref/templates/api.txt | 6 ++++-- docs/releases/1.11.txt | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/ref/templates/api.txt b/docs/ref/templates/api.txt index df3123cf1666..7513b11930dc 100644 --- a/docs/ref/templates/api.txt +++ b/docs/ref/templates/api.txt @@ -932,8 +932,10 @@ loaders that come with Django: ``Template`` in memory. The cached ``Template`` instance is returned for subsequent requests to load the same template. - This loader is automatically enabled if :setting:`DEBUG` is ``False`` and - :setting:`OPTIONS['loaders'] ` isn't specified. + This loader is automatically enabled if :setting:`OPTIONS['loaders'] + ` isn't specified and :setting:`OPTIONS['debug'] + ` is ``False`` (the latter option defaults to the value + of :setting:`DEBUG`). You can also enable template caching with some custom template loaders using settings like this:: diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 5b6b17a01295..86a0fa53b518 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -695,8 +695,9 @@ Miscellaneous 1.0) is removed. * The :class:`cached template loader ` - is now enabled if :setting:`DEBUG` is ``False`` and - :setting:`OPTIONS['loaders'] ` isn't specified. This could + is now enabled if :setting:`OPTIONS['loaders'] ` isn't + specified and :setting:`OPTIONS['debug'] ` is ``False`` + (the latter option defaults to the value of :setting:`DEBUG`). This could be backwards-incompatible if you have some :ref:`template tags that aren't thread safe `. From ced0b1edef6743012c8e17d447b701006758c52e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 12 Jan 2018 14:17:53 -0500 Subject: [PATCH 226/389] [1.11.x] Fixed #29017 -- Updated BaseCommand.leave_locale_alone doc per refs #24073. Backport of b9cec9fa1fc50207ab68a59853c851a945c5db5e from master --- docs/howto/custom-management-commands.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt index 5a091f835eaa..c0f8c343a0a6 100644 --- a/docs/howto/custom-management-commands.txt +++ b/docs/howto/custom-management-commands.txt @@ -259,7 +259,8 @@ All attributes can be set in your derived class and can be used in .. attribute:: BaseCommand.leave_locale_alone A boolean indicating whether the locale set in settings should be preserved - during the execution of the command instead of being forcibly set to 'en-us'. + during the execution of the command instead of translations being + deactivated. Default value is ``False``. @@ -267,9 +268,8 @@ All attributes can be set in your derived class and can be used in this option in your custom command if it creates database content that is locale-sensitive and such content shouldn't contain any translations (like it happens e.g. with :mod:`django.contrib.auth` permissions) as - making the locale differ from the de facto default 'en-us' might cause - unintended effects. See the `Management commands and locales`_ section - above for further details. + activating any locale might cause unintended effects. See the `Management + commands and locales`_ section above for further details. .. attribute:: BaseCommand.style From fb56038723950d9230a7e5fce4d3ee96954b80e8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 13 Jan 2018 09:18:13 -0500 Subject: [PATCH 227/389] [1.11.x] Added stub release notes for 1.11.10. Backport of cea5fe94c6bb1b61e791f1375c246566c950b3e3 from master --- docs/releases/1.11.10.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.10.txt diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt new file mode 100644 index 000000000000..d078959357c8 --- /dev/null +++ b/docs/releases/1.11.10.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.10 release notes +============================ + +*Expected February 1, 2018* + +Django 1.11.10 fixes several bugs in 1.11.9. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6c534b08e509..51d9a9a7789b 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.10 1.11.9 1.11.8 1.11.7 From 419705bbe84e27c3d5be85f198a0352a6724927e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Loks?= Date: Sat, 13 Jan 2018 12:23:48 +0100 Subject: [PATCH 228/389] [1.11.x] Fixed #29016 -- Fixed incorrect foreign key nullification on related instance deletion. Backport of 9a621edf624a4eb1f1645fca628a9e432f0de776 from master --- django/db/models/deletion.py | 2 +- docs/releases/1.11.10.txt | 3 ++- tests/delete_regress/models.py | 2 ++ tests/delete_regress/tests.py | 28 ++++++++++++++++++++++++++-- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/django/db/models/deletion.py b/django/db/models/deletion.py index 26073be3ba71..590d5a229816 100644 --- a/django/db/models/deletion.py +++ b/django/db/models/deletion.py @@ -286,8 +286,8 @@ def delete(self): # update fields for model, instances_for_fieldvalues in six.iteritems(self.field_updates): - query = sql.UpdateQuery(model) for (field, value), instances in six.iteritems(instances_for_fieldvalues): + query = sql.UpdateQuery(model) query.update_batch([obj.pk for obj in instances], {field.name: value}, self.using) diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt index d078959357c8..49e19614a519 100644 --- a/docs/releases/1.11.10.txt +++ b/docs/releases/1.11.10.txt @@ -9,4 +9,5 @@ Django 1.11.10 fixes several bugs in 1.11.9. Bugfixes ======== -* ... +* Fixed incorrect foreign key nullification if a model has two foreign keys to + the same model and a target model is deleted (:ticket:`29016`). diff --git a/tests/delete_regress/models.py b/tests/delete_regress/models.py index f0145de65b55..90eae1ba1c2b 100644 --- a/tests/delete_regress/models.py +++ b/tests/delete_regress/models.py @@ -56,6 +56,8 @@ class Email(Contact): class Researcher(models.Model): contacts = models.ManyToManyField(Contact, related_name="research_contacts") + primary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='primary_contacts') + secondary_contact = models.ForeignKey(Contact, models.SET_NULL, null=True, related_name='secondary_contacts') class Food(models.Model): diff --git a/tests/delete_regress/tests.py b/tests/delete_regress/tests.py index 2128733798d6..dac518243649 100644 --- a/tests/delete_regress/tests.py +++ b/tests/delete_regress/tests.py @@ -6,7 +6,7 @@ from django.test import TestCase, TransactionTestCase, skipUnlessDBFeature from .models import ( - Award, AwardNote, Book, Child, Eaten, Email, File, Food, FooFile, + Award, AwardNote, Book, Child, Contact, Eaten, Email, File, Food, FooFile, FooFileProxy, FooImage, FooPhoto, House, Image, Item, Location, Login, OrderedPerson, OrgUnit, Person, Photo, PlayedWith, PlayedWithNote, Policy, Researcher, Toy, Version, @@ -335,7 +335,7 @@ def test_ticket_19102_defer(self): self.assertTrue(Login.objects.filter(pk=self.l2.pk).exists()) -class OrderedDeleteTests(TestCase): +class DeleteTests(TestCase): def test_meta_ordered_delete(self): # When a subquery is performed by deletion code, the subquery must be # cleared of all ordering. There was a but that caused _meta ordering @@ -345,3 +345,27 @@ def test_meta_ordered_delete(self): OrderedPerson.objects.create(name='Bob', lives_in=h) OrderedPerson.objects.filter(lives_in__address='Foo').delete() self.assertEqual(OrderedPerson.objects.count(), 0) + + def test_foreign_key_delete_nullifies_correct_columns(self): + """ + With a model (Researcher) that has two foreign keys pointing to the + same model (Contact), deleting an instance of the target model + (contact1) nullifies the correct fields of Researcher. + """ + contact1 = Contact.objects.create(label='Contact 1') + contact2 = Contact.objects.create(label='Contact 2') + researcher1 = Researcher.objects.create( + primary_contact=contact1, + secondary_contact=contact2, + ) + researcher2 = Researcher.objects.create( + primary_contact=contact2, + secondary_contact=contact1, + ) + contact1.delete() + researcher1.refresh_from_db() + researcher2.refresh_from_db() + self.assertIsNone(researcher1.primary_contact) + self.assertEqual(researcher1.secondary_contact, contact2) + self.assertEqual(researcher2.primary_contact, contact2) + self.assertIsNone(researcher2.secondary_contact) From de1520ec2003b32e1cc93a11fbef1e711f4c8518 Mon Sep 17 00:00:00 2001 From: Peter Wischer Date: Wed, 17 Jan 2018 08:47:37 +0100 Subject: [PATCH 229/389] [1.11.x] Fixed typo in docs/topics/i18n/translation.txt. Backport of 196c257a230bba8f2f1b2021c383eb2744e8df41 from master --- docs/topics/i18n/translation.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index e1dc6deb2bc1..158bc6fb58c5 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1822,13 +1822,13 @@ To workaround this, you can escape percent signs by adding a second percent sign:: from django.utils.translation import ugettext as _ - output = _("10%% interest) + output = _("10%% interest") Or you can use ``no-python-format`` so that all percent signs are treated as literals:: # xgettext:no-python-format - output = _("10% interest) + output = _("10% interest") .. _creating-message-files-from-js-code: From 649d5eaa8b7b224fc0eb8a3339a589288d5b10e8 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 19 Jan 2018 08:55:29 +0100 Subject: [PATCH 230/389] [1.11.x] Fixed #29032 -- Fixed an example of using expressions in QuerySet.values(). Backport of 7fbb1bd00d8a3e9a834de83d36ebcbff15c18938 from master --- docs/ref/models/querysets.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index c1c2a249f8fa..1a7141b3adb1 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -541,10 +541,10 @@ within the same ``values()`` clause. If you need to group by another value, add it to an earlier ``values()`` clause instead. For example:: >>> from django.db.models import Count - >>> Blog.objects.values('author', entries=Count('entry')) - - >>> Blog.objects.values('author').annotate(entries=Count('entry')) - + >>> Blog.objects.values('entry__authors', entries=Count('entry')) + + >>> Blog.objects.values('entry__authors').annotate(entries=Count('entry')) + A few subtleties that are worth mentioning: From 4430b83e4bcefbd7da1831df543f711d9da6bc26 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 30 Jan 2018 19:31:25 -0500 Subject: [PATCH 231/389] [1.11.x] Fixed #29071 -- Fixed contrib.auth.authenticate() crash if a backend doesn't accept a request but a later one does. Regression in a3ba2662cdaa36183fdfb8a26dfa157e26fca76a. Backport of 55e16f25e9d2050e95e448f9ab2e4b9fc845a9e5 from stable/2.0.x --- django/contrib/auth/__init__.py | 1 + docs/releases/1.11.10.txt | 4 +++ .../test_auth_backends_deprecation.py | 29 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index a0a2cb7aff67..c617f1c2ca73 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -82,6 +82,7 @@ def authenticate(request=None, **credentials): def _authenticate_with_backend(backend, backend_path, request, credentials): + credentials = credentials.copy() # Prevent a mutation from propagating. args = (request,) # Does the backend accept a request argument? try: diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt index 49e19614a519..88d2d007bf4d 100644 --- a/docs/releases/1.11.10.txt +++ b/docs/releases/1.11.10.txt @@ -11,3 +11,7 @@ Bugfixes * Fixed incorrect foreign key nullification if a model has two foreign keys to the same model and a target model is deleted (:ticket:`29016`). + +* Fixed a regression where ``contrib.auth.authenticate()`` crashes if an + authentication backend doesn't accept ``request`` and a later one does + (:ticket:`29071`). diff --git a/tests/auth_tests/test_auth_backends_deprecation.py b/tests/auth_tests/test_auth_backends_deprecation.py index 675e185e9f0d..00b7e19d8903 100644 --- a/tests/auth_tests/test_auth_backends_deprecation.py +++ b/tests/auth_tests/test_auth_backends_deprecation.py @@ -13,6 +13,18 @@ def authenticate(self, username=None, password=None): pass +class NoRequestWithKwargs: + def authenticate(self, username=None, password=None, **kwargs): + pass + + +class RequestPositionalArg: + def authenticate(self, request, username=None, password=None, **kwargs): + assert username == 'username' + assert password == 'pass' + assert request is mock_request + + class RequestNotPositionArgBackend: def authenticate(self, username=None, password=None, request=None): assert username == 'username' @@ -34,6 +46,8 @@ class AcceptsRequestBackendTest(SimpleTestCase): method without a request parameter. """ no_request_backend = '%s.NoRequestBackend' % __name__ + no_request_with_kwargs_backend = '%s.NoRequestWithKwargs' % __name__ + request_positional_arg_backend = '%s.RequestPositionalArg' % __name__ request_not_positional_backend = '%s.RequestNotPositionArgBackend' % __name__ request_not_positional_with_used_kwarg_backend = '%s.RequestNotPositionArgWithUsedKwargBackend' % __name__ @@ -79,6 +93,21 @@ def test_both_types_of_deprecation_warning(self): "argument." % self.no_request_backend ) + @override_settings(AUTHENTICATION_BACKENDS=[no_request_with_kwargs_backend, request_positional_arg_backend]) + def test_credentials_not_mutated(self): + """ + No problem if a backend doesn't accept `request` and a later one does. + """ + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + authenticate(mock_request, username='username', password='pass') + self.assertEqual(len(warns), 1) + self.assertEqual( + str(warns[0].message), + "In %s.authenticate(), move the `request` keyword argument to the " + "first positional argument." % self.no_request_with_kwargs_backend + ) + @override_settings(AUTHENTICATION_BACKENDS=[request_not_positional_with_used_kwarg_backend]) def test_handles_backend_in_kwargs(self): with warnings.catch_warnings(record=True) as warns: From a0c2e3fde75f984ab2ead30e7004cc7ab38e30a8 Mon Sep 17 00:00:00 2001 From: Rodrigo Date: Wed, 31 Jan 2018 11:14:41 -0500 Subject: [PATCH 232/389] [1.11.x] Fixed location of spatialite_source label. Backport of 7c5cf331278407ab87737d10a1b2e722c0e666b0 from master --- docs/ref/contrib/gis/install/spatialite.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/contrib/gis/install/spatialite.txt b/docs/ref/contrib/gis/install/spatialite.txt index 306c2444833f..cf0c8ae055b5 100644 --- a/docs/ref/contrib/gis/install/spatialite.txt +++ b/docs/ref/contrib/gis/install/spatialite.txt @@ -21,14 +21,14 @@ In any case, you should always be able to :ref:`install from source __ https://www.gaia-gis.it/fossil/libspatialite __ https://www.gaia-gis.it/gaia-sins/ -.. _spatialite_source: - .. admonition:: ``SPATIALITE_LIBRARY_PATH`` setting required for SpatiaLite 4.2+ If you're using SpatiaLite 4.2+, you must put this in your settings:: SPATIALITE_LIBRARY_PATH = 'mod_spatialite' +.. _spatialite_source: + Installing from source ====================== From 1c9233b1b9f903e4e2cb20a724e8c22aee4aacb2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 31 Jan 2018 13:43:05 -0500 Subject: [PATCH 233/389] [1.11.x] Fixed #29094 -- Fixed crash when entering an invalid uuid in ModelAdmin.raw_id_fields. Regression in 2f9861d823620da7ecb291a8f005f53da12b1e89. Thanks Carel Burger for the report and fix. Backport of docs552abffab16cbdff571486b683e7e7ef12e46066 from master --- django/contrib/admin/widgets.py | 3 ++- docs/releases/1.11.10.txt | 3 +++ tests/admin_widgets/models.py | 3 +++ tests/admin_widgets/tests.py | 8 +++++++- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 669c7151233c..89e8adde3029 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -6,6 +6,7 @@ import copy from django import forms +from django.core.exceptions import ValidationError from django.db.models.deletion import CASCADE from django.urls import reverse from django.urls.exceptions import NoReverseMatch @@ -174,7 +175,7 @@ def label_and_url_for_value(self, value): key = self.rel.get_related_field().name try: obj = self.rel.model._default_manager.using(self.db).get(**{key: value}) - except (ValueError, self.rel.model.DoesNotExist): + except (ValueError, self.rel.model.DoesNotExist, ValidationError): return '', '' try: diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt index 88d2d007bf4d..cfa8fc2070a9 100644 --- a/docs/releases/1.11.10.txt +++ b/docs/releases/1.11.10.txt @@ -15,3 +15,6 @@ Bugfixes * Fixed a regression where ``contrib.auth.authenticate()`` crashes if an authentication backend doesn't accept ``request`` and a later one does (:ticket:`29071`). + +* Fixed crash when entering an invalid uuid in ``ModelAdmin.raw_id_fields`` + (:ticket:`29094`). diff --git a/tests/admin_widgets/models.py b/tests/admin_widgets/models.py index 274c36e15ee3..4c6c10000a57 100644 --- a/tests/admin_widgets/models.py +++ b/tests/admin_widgets/models.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import uuid + from django.contrib.auth.models import User from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -99,6 +101,7 @@ class CarTire(models.Model): class Honeycomb(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) location = models.CharField(max_length=20) diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 2e2bdb0b52df..4d78d5fd5bd4 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -17,7 +17,7 @@ from django.contrib.auth.models import User from django.core.files.storage import default_storage from django.core.files.uploadedfile import SimpleUploadedFile -from django.db.models import CharField, DateField, DateTimeField +from django.db.models import CharField, DateField, DateTimeField, UUIDField from django.test import SimpleTestCase, TestCase, override_settings from django.urls import reverse from django.utils import six, translation @@ -251,6 +251,12 @@ def my_callable(): lookup2 = widgets.url_params_from_lookup_dict({'myfield': my_callable()}) self.assertEqual(lookup1, lookup2) + def test_label_and_url_for_value_invalid_uuid(self): + field = Bee._meta.get_field('honeycomb') + self.assertIsInstance(field.target_field, UUIDField) + widget = widgets.ForeignKeyRawIdWidget(field.remote_field, admin.site) + self.assertEqual(widget.label_and_url_for_value('invalid-uuid'), ('', '')) + class FilteredSelectMultipleWidgetTest(SimpleTestCase): def test_render(self): From 57b95fedad5e0b83fc9c81466b7d1751c6427aae Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 23 Jan 2018 13:20:18 -0500 Subject: [PATCH 234/389] [1.11.x] Fixed CVE-2018-6188 -- Fixed information leakage in AuthenticationForm. Reverted 359370a8b8ca0efe99b1d4630b291ec060b69225 (refs #28645). This is a security fix. --- django/contrib/auth/forms.py | 9 --------- docs/releases/1.11.10.txt | 23 +++++++++++++++++++++-- tests/auth_tests/test_forms.py | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 11 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index abfd96aeec30..02250d83da6f 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -186,15 +186,6 @@ def clean(self): if username is not None and password: self.user_cache = authenticate(self.request, username=username, password=password) if self.user_cache is None: - # An authentication backend may reject inactive users. Check - # if the user exists and is inactive, and raise the 'inactive' - # error if so. - try: - self.user_cache = UserModel._default_manager.get_by_natural_key(username) - except UserModel.DoesNotExist: - pass - else: - self.confirm_login_allowed(self.user_cache) raise forms.ValidationError( self.error_messages['invalid_login'], code='invalid_login', diff --git a/docs/releases/1.11.10.txt b/docs/releases/1.11.10.txt index cfa8fc2070a9..96f07920a361 100644 --- a/docs/releases/1.11.10.txt +++ b/docs/releases/1.11.10.txt @@ -2,9 +2,28 @@ Django 1.11.10 release notes ============================ -*Expected February 1, 2018* +*February 1, 2018* -Django 1.11.10 fixes several bugs in 1.11.9. +Django 1.11.10 fixes a security issue and several bugs in 1.11.9. + +CVE-2018-6188: Information leakage in ``AuthenticationForm`` +============================================================ + +A regression in Django 1.11.8 made +:class:`~django.contrib.auth.forms.AuthenticationForm` run its +``confirm_login_allowed()`` method even if an incorrect password is entered. +This can leak information about a user, depending on what messages +``confirm_login_allowed()`` raises. If ``confirm_login_allowed()`` isn't +overridden, an attacker enter an arbitrary username and see if that user has +been set to ``is_active=False``. If ``confirm_login_allowed()`` is overridden, +more sensitive details could be leaked. + +This issue is fixed with the caveat that ``AuthenticationForm`` can no longer +raise the "This account is inactive." error if the authentication backend +rejects inactive users (the default authentication backend, ``ModelBackend``, +has done that since Django 1.10). This issue will be revisited for Django 2.1 +as a fix to address the caveat will likely be too invasive for inclusion in +older versions. Bugfixes ======== diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py index b82bbc58f4c6..e09285277f40 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -249,6 +249,9 @@ def test_password_help_text(self): ) +# To verify that the login form rejects inactive users, use an authentication +# backend that allows them. +@override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend']) class AuthenticationFormTest(TestDataMixin, TestCase): def test_invalid_username(self): @@ -278,6 +281,24 @@ def test_inactive_user(self): self.assertFalse(form.is_valid()) self.assertEqual(form.non_field_errors(), [force_text(form.error_messages['inactive'])]) + # Use an authentication backend that rejects inactive users. + @override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.ModelBackend']) + def test_inactive_user_incorrect_password(self): + """An invalid login doesn't leak the inactive status of a user.""" + data = { + 'username': 'inactive', + 'password': 'incorrect', + } + form = AuthenticationForm(None, data) + self.assertFalse(form.is_valid()) + self.assertEqual( + form.non_field_errors(), [ + form.error_messages['invalid_login'] % { + 'username': User._meta.get_field('username').verbose_name + } + ] + ) + def test_login_failed(self): signal_calls = [] From 5d4368cad39b79ae4ce2224c64facc72366d0fb8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Feb 2018 09:35:13 -0500 Subject: [PATCH 235/389] [1.11.x] Bumped version for 1.11.10 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index af973efba2bc..c49cc5aa8dea 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 10, 'alpha', 0) +VERSION = (1, 11, 10, 'final', 0) __version__ = get_version(VERSION) From 1a4085abcbe3f1f4a1ebbf4eac1fd7bb802eb58e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Feb 2018 09:46:12 -0500 Subject: [PATCH 236/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index c49cc5aa8dea..e8fbabc44a82 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 10, 'final', 0) +VERSION = (1, 11, 11, 'alpha', 0) __version__ = get_version(VERSION) From 01448a97e075df08ed0f89c443ee35e649dfb630 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 1 Feb 2018 10:14:17 -0500 Subject: [PATCH 237/389] [1.11.x] Added CVE-2018-6188 to the security release archive. Backport of 66119ed64233c3abe586606a9e81a75edc2a6a92 from master --- docs/releases/security.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 910e6914c702..357ab39a2f5d 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -845,3 +845,15 @@ Versions affected * Django 1.11 `(patch) `__ * Django 1.10 `(patch) `__ + +February 1, 2018 - :cve:`2018-6188` +----------------------------------- + +Information leakage in ``AuthenticationForm``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ From 4b65fc807ae5b64c5cd1029f11f7c3f0ec67f03b Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 12 Feb 2018 09:09:46 -0500 Subject: [PATCH 238/389] [1.11.x] Corrected doc'd type of some parameters from string to str. Backport of d63c00a4283ce85622ec00c6f668630078c75817 from master --- docs/ref/contrib/gis/geos.txt | 2 +- docs/ref/urls.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/gis/geos.txt b/docs/ref/contrib/gis/geos.txt index 63aa69729c09..1c58628c5c74 100644 --- a/docs/ref/contrib/gis/geos.txt +++ b/docs/ref/contrib/gis/geos.txt @@ -932,7 +932,7 @@ Geometry Factories .. function:: fromstr(string, srid=None) :param string: string that contains spatial data - :type string: string + :type string: str :param srid: spatial reference identifier :type srid: int :rtype: a :class:`GEOSGeometry` corresponding to the spatial data in the string diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index d1464da66d26..cae83b92269e 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -74,14 +74,14 @@ parameter is useful. :arg module: URLconf module (or module name) :arg namespace: Instance namespace for the URL entries being included - :type namespace: string + :type namespace: str :arg app_name: Application namespace for the URL entries being included - :type app_name: string + :type app_name: str :arg pattern_list: Iterable of :func:`django.conf.urls.url` instances :arg app_namespace: Application namespace for the URL entries being included - :type app_namespace: string + :type app_namespace: str :arg instance_namespace: Instance namespace for the URL entries being included - :type instance_namespace: string + :type instance_namespace: str See :ref:`including-other-urlconfs` and :ref:`namespaces-and-include`. From d5da552d92c2d5165a23bceb229dec023b8bb5c5 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 20 Feb 2018 08:58:29 -0500 Subject: [PATCH 239/389] [1.11.x] Removed blank lines per isort 4.3.0. --- django/contrib/messages/__init__.py | 1 - django/core/management/commands/runserver.py | 1 - django/db/models/signals.py | 1 - tests/admin_views/tests.py | 1 - tests/db_functions/tests.py | 1 - tests/messages_tests/urls.py | 1 - tests/template_tests/syntax_tests/test_multiline.py | 1 - 7 files changed, 7 deletions(-) diff --git a/django/contrib/messages/__init__.py b/django/contrib/messages/__init__.py index a0cb24b2d9c1..25da09f3bf7e 100644 --- a/django/contrib/messages/__init__.py +++ b/django/contrib/messages/__init__.py @@ -1,5 +1,4 @@ from django.contrib.messages.api import * # NOQA from django.contrib.messages.constants import * # NOQA - default_app_config = 'django.contrib.messages.apps.MessagesConfig' diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py index 0bfbd5b68e15..43cb6c40fdda 100644 --- a/django/core/management/commands/runserver.py +++ b/django/core/management/commands/runserver.py @@ -15,7 +15,6 @@ from django.utils import autoreload, six from django.utils.encoding import force_text, get_system_encoding - naiveip_re = re.compile(r"""^(?: (?P (?P\d{1,3}(?:\.\d{1,3}){3}) | # IPv4 address diff --git a/django/db/models/signals.py b/django/db/models/signals.py index 5047f11743db..064428b4a661 100644 --- a/django/db/models/signals.py +++ b/django/db/models/signals.py @@ -6,7 +6,6 @@ from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning - class_prepared = Signal(providing_args=["class"]) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index efddc49de7e7..c588c1a02743 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -66,7 +66,6 @@ Worker, WorkHour, ) - ERROR_MESSAGE = "Please enter the correct username and password \ for a staff account. Note that both fields may be case-sensitive." diff --git a/tests/db_functions/tests.py b/tests/db_functions/tests.py index 3041f2957065..a014f97370f4 100644 --- a/tests/db_functions/tests.py +++ b/tests/db_functions/tests.py @@ -16,7 +16,6 @@ from .models import Article, Author, DecimalModel, Fan - lorem_ipsum = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.""" diff --git a/tests/messages_tests/urls.py b/tests/messages_tests/urls.py index 4005ffac6b1f..d9a8a59b91c1 100644 --- a/tests/messages_tests/urls.py +++ b/tests/messages_tests/urls.py @@ -9,7 +9,6 @@ from django.views.decorators.cache import never_cache from django.views.generic.edit import FormView - TEMPLATE = """{% if messages %}
    {% for message in messages %} diff --git a/tests/template_tests/syntax_tests/test_multiline.py b/tests/template_tests/syntax_tests/test_multiline.py index b2371f7fd103..a95e12986a98 100644 --- a/tests/template_tests/syntax_tests/test_multiline.py +++ b/tests/template_tests/syntax_tests/test_multiline.py @@ -2,7 +2,6 @@ from ..utils import setup - multiline_string = """ Hello, boys. From e8afd6bf814dac5002b994c631907539d5215abb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 8 Feb 2018 19:15:36 -0500 Subject: [PATCH 240/389] [1.11.x] Switched test requirement to new psycopg2-binary package. Backport of d4373b6da4b420fe9211438addeedb396a3821be from master --- tests/requirements/postgres.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements/postgres.txt b/tests/requirements/postgres.txt index 59041b90ef06..820d85bb44df 100644 --- a/tests/requirements/postgres.txt +++ b/tests/requirements/postgres.txt @@ -1 +1 @@ -psycopg2>=2.5.4 +psycopg2-binary>=2.5.4 From 7d7ab26bc07ea0fd96b0dcdad53c234b2b484210 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 24 Feb 2018 19:39:17 -0500 Subject: [PATCH 241/389] [1.11.x] Added stub release notes for security releases. --- docs/releases/1.11.11.txt | 7 +++++++ docs/releases/1.8.19.txt | 7 +++++++ docs/releases/index.txt | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 docs/releases/1.11.11.txt create mode 100644 docs/releases/1.8.19.txt diff --git a/docs/releases/1.11.11.txt b/docs/releases/1.11.11.txt new file mode 100644 index 000000000000..c344f3e7b540 --- /dev/null +++ b/docs/releases/1.11.11.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.11 release notes +============================ + +*March 6, 2018* + +Django 1.11.11 fixes two security issues in 1.11.10. diff --git a/docs/releases/1.8.19.txt b/docs/releases/1.8.19.txt new file mode 100644 index 000000000000..9709f2622dd7 --- /dev/null +++ b/docs/releases/1.8.19.txt @@ -0,0 +1,7 @@ +=========================== +Django 1.8.19 release notes +=========================== + +*March 6, 2018* + +Django 1.8.19 fixes two security issues in 1.18.18. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 51d9a9a7789b..9ec56c3f9ecb 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.11 1.11.10 1.11.9 1.11.8 @@ -78,6 +79,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.8.19 1.8.18 1.8.17 1.8.16 From abf89d729f210c692a50e0ad3f75fb6bec6fae16 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 24 Feb 2018 11:30:11 -0500 Subject: [PATCH 242/389] [1.11.x] Fixed CVE-2018-7536 -- Fixed catastrophic backtracking in urlize and urlizetrunc template filters. Thanks Florian Apolloner for assisting with the patch. --- django/utils/html.py | 33 +++++++++++++++++++++------------ docs/releases/1.11.11.txt | 11 +++++++++++ docs/releases/1.8.19.txt | 11 +++++++++++ tests/utils_tests/test_html.py | 8 ++++++++ 4 files changed, 51 insertions(+), 12 deletions(-) diff --git a/django/utils/html.py b/django/utils/html.py index 9a968a5a41e5..9c38cde55d65 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -17,12 +17,7 @@ from .html_parser import HTMLParseError, HTMLParser # Configuration for urlize() function. -TRAILING_PUNCTUATION_RE = re.compile( - '^' # Beginning of word - '(.*?)' # The URL in word - '([.,:;!]+)' # Allowed non-wrapping, trailing punctuation - '$' # End of word -) +TRAILING_PUNCTUATION_CHARS = '.,:;!' WRAPPING_PUNCTUATION = [('(', ')'), ('<', '>'), ('[', ']'), ('<', '>'), ('"', '"'), ('\'', '\'')] # List of possible strings used for bullets in bulleted lists. @@ -32,7 +27,6 @@ word_split_re = re.compile(r'''([\s<>"']+)''') simple_url_re = re.compile(r'^https?://\[?\w', re.IGNORECASE) simple_url_2_re = re.compile(r'^www\.|^(?!http)\w[^@]+\.(com|edu|gov|int|mil|net|org)($|/.*)$', re.IGNORECASE) -simple_email_re = re.compile(r'^\S+@\S+\.\S+$') @keep_lazy(six.text_type, SafeText) @@ -280,10 +274,10 @@ def trim_punctuation(lead, middle, trail): trimmed_something = False # Trim trailing punctuation. - match = TRAILING_PUNCTUATION_RE.match(middle) - if match: - middle = match.group(1) - trail = match.group(2) + trail + stripped = middle.rstrip(TRAILING_PUNCTUATION_CHARS) + if middle != stripped: + trail = middle[len(stripped):] + trail + middle = stripped trimmed_something = True # Trim wrapping punctuation. @@ -300,6 +294,21 @@ def trim_punctuation(lead, middle, trail): trimmed_something = True return lead, middle, trail + def is_email_simple(value): + """Return True if value looks like an email address.""" + # An @ must be in the middle of the value. + if '@' not in value or value.startswith('@') or value.endswith('@'): + return False + try: + p1, p2 = value.split('@') + except ValueError: + # value contains more than one @. + return False + # Dot must be in p2 (e.g. example.com) + if '.' not in p2 or p2.startswith('.'): + return False + return True + words = word_split_re.split(force_text(text)) for i, word in enumerate(words): if '.' in word or '@' in word or ':' in word: @@ -319,7 +328,7 @@ def trim_punctuation(lead, middle, trail): elif simple_url_2_re.match(middle): middle, middle_unescaped, trail = unescape(middle, trail) url = smart_urlquote('http://%s' % middle_unescaped) - elif ':' not in middle and simple_email_re.match(middle): + elif ':' not in middle and is_email_simple(middle): local, domain = middle.rsplit('@', 1) try: domain = domain.encode('idna').decode('ascii') diff --git a/docs/releases/1.11.11.txt b/docs/releases/1.11.11.txt index c344f3e7b540..696465fd47fa 100644 --- a/docs/releases/1.11.11.txt +++ b/docs/releases/1.11.11.txt @@ -5,3 +5,14 @@ Django 1.11.11 release notes *March 6, 2018* Django 1.11.11 fixes two security issues in 1.11.10. + +CVE-2018-7536: Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters +=============================================================================================== + +The ``django.utils.html.urlize()`` function was extremely slow to evaluate +certain inputs due to catastrophic backtracking vulnerabilities in two regular +expressions. The ``urlize()`` function is used to implement the ``urlize`` and +``urlizetrunc`` template filters, which were thus vulnerable. + +The problematic regular expressions are replaced with parsing logic that +behaves similarly. diff --git a/docs/releases/1.8.19.txt b/docs/releases/1.8.19.txt index 9709f2622dd7..ae509f11c46f 100644 --- a/docs/releases/1.8.19.txt +++ b/docs/releases/1.8.19.txt @@ -5,3 +5,14 @@ Django 1.8.19 release notes *March 6, 2018* Django 1.8.19 fixes two security issues in 1.18.18. + +CVE-2018-7536: Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters +=============================================================================================== + +The ``django.utils.html.urlize()`` function was extremely slow to evaluate +certain inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``urlize()`` function is used to implement the ``urlize`` and +``urlizetrunc`` template filters, which were thus vulnerable. + +The problematic regular expression is replaced with parsing logic that behaves +similarly. diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 7982f4fe4205..1bebe9452197 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -232,3 +232,11 @@ def test_html_safe_doesnt_define_str(self): @html.html_safe class HtmlClass(object): pass + + def test_urlize_unchanged_inputs(self): + tests = ( + ('a' + '@a' * 50000) + 'a', # simple_email_re catastrophic test + ('a' + '.' * 1000000) + 'a', # trailing_punctuation catastrophic test + ) + for value in tests: + self.assertEqual(html.urlize(value), value) From a91436360b79a6ff995c3e5018bcc666dfaf1539 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 24 Feb 2018 16:22:43 -0500 Subject: [PATCH 243/389] [1.11.x] Fixed CVE-2018-7537 -- Fixed catastrophic backtracking in django.utils.text.Truncator. Thanks James Davis for suggesting the fix. --- django/utils/text.py | 2 +- docs/releases/1.11.11.txt | 12 ++++++++++++ docs/releases/1.8.19.txt | 12 ++++++++++++ tests/utils_tests/test_text.py | 4 ++++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/django/utils/text.py b/django/utils/text.py index b0f139e03497..a6172c41b0fa 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -29,7 +29,7 @@ def capfirst(x): # Set up regular expressions re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S) re_chars = re.compile(r'<.*?>|(.)', re.U | re.S) -re_tag = re.compile(r'<(/)?([^ ]+?)(?:(\s*/)| .*?)?>', re.S) +re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S) re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') diff --git a/docs/releases/1.11.11.txt b/docs/releases/1.11.11.txt index 696465fd47fa..314338a54147 100644 --- a/docs/releases/1.11.11.txt +++ b/docs/releases/1.11.11.txt @@ -16,3 +16,15 @@ expressions. The ``urlize()`` function is used to implement the ``urlize`` and The problematic regular expressions are replaced with parsing logic that behaves similarly. + +CVE-2018-7537: Denial-of-service possibility in ``truncatechars_html`` and ``truncatewords_html`` template filters +================================================================================================================== + +If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods were +passed the ``html=True`` argument, they were extremely slow to evaluate certain +inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``chars()`` and ``words()`` methods are used to implement the +``truncatechars_html`` and ``truncatewords_html`` template filters, which were +thus vulnerable. + +The backtracking problem in the regular expression is fixed. diff --git a/docs/releases/1.8.19.txt b/docs/releases/1.8.19.txt index ae509f11c46f..96410a331c7c 100644 --- a/docs/releases/1.8.19.txt +++ b/docs/releases/1.8.19.txt @@ -16,3 +16,15 @@ expression. The ``urlize()`` function is used to implement the ``urlize`` and The problematic regular expression is replaced with parsing logic that behaves similarly. + +CVE-2018-7537: Denial-of-service possibility in ``truncatechars_html`` and ``truncatewords_html`` template filters +================================================================================================================== + +If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods were +passed the ``html=True`` argument, they were extremely slow to evaluate certain +inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``chars()`` and ``words()`` methods are used to implement the +``truncatechars_html`` and ``truncatewords_html`` template filters, which were +thus vulnerable. + +The backtracking problem in the regular expression is fixed. diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index d190d852320e..50d1805e8672 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -139,6 +139,10 @@ def test_truncate_html_words(self): truncator = text.Truncator('

    I <3 python, what about you?

    ') self.assertEqual('

    I <3 python...

    ', truncator.words(3, '...', html=True)) + re_tag_catastrophic_test = ('' + truncator = text.Truncator(re_tag_catastrophic_test) + self.assertEqual(re_tag_catastrophic_test, truncator.words(500, html=True)) + def test_wrap(self): digits = '1234 67 9' self.assertEqual(text.wrap(digits, 100), '1234 67 9') From 1cc5aceac0a73468a6d1a671b9c86423e5bcf011 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Mar 2018 09:08:15 -0500 Subject: [PATCH 244/389] [1.11.x] Bumped version for 1.11.11 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index e8fbabc44a82..6b5eef4d204b 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 11, 'alpha', 0) +VERSION = (1, 11, 11, 'final', 0) __version__ = get_version(VERSION) From cd8496b3b49e4b58dc15e6695847d696d7d4dc52 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Mar 2018 09:38:46 -0500 Subject: [PATCH 245/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 6b5eef4d204b..5f4bdd0c5cf7 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 11, 'final', 0) +VERSION = (1, 11, 12, 'alpha', 0) __version__ = get_version(VERSION) From c8e5c1f4205078d8b58420cc585917384e51dab2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 6 Mar 2018 12:59:36 -0500 Subject: [PATCH 246/389] [1.11.x] Added CVE-2018-7536,7 to the security release archive. Backport of 5bbbdd26d1ea4f3bb164ad64b0d0d458d8bfdd02 from master --- docs/releases/security.txt | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 357ab39a2f5d..47aef2bb240e 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -857,3 +857,31 @@ Versions affected * Django 2.0 `(patch) `__ * Django 1.11 `(patch) `__ + +March 6, 2018 - :cve:`2018-7536` +-------------------------------- + +Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template +filters. `Full description +`_ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ +* Django 1.8 `(patch) `__ + +March 6, 2018 - :cve:`2018-7537` +-------------------------------- + +Denial-of-service possibility in ``truncatechars_html`` and +``truncatewords_html`` template filters. `Full description +`_ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ +* Django 1.8 `(patch) `__ From fbae9c0e023aea2666feb1603ece172548342ec1 Mon Sep 17 00:00:00 2001 From: ovalseven8 Date: Tue, 6 Mar 2018 17:18:49 +0100 Subject: [PATCH 247/389] [1.11.x] Fixed #29192 -- Removed inaccurate statement regarding overriding fields from abstract base classes. Partial backport of 22bcd3a1d88add6e4cf2c4451ede8d1ae142dedd from master --- docs/topics/db/models.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index f4034f6ba762..944b944e0ea1 100644 --- a/docs/topics/db/models.txt +++ b/docs/topics/db/models.txt @@ -899,9 +899,7 @@ information into a number of other models. You write your base class and put ``abstract=True`` in the :ref:`Meta ` class. This model will then not be used to create any database table. Instead, when it is used as a base class for other models, its -fields will be added to those of the child class. It is an error to -have fields in the abstract base class with the same name as those in -the child (and Django will raise an exception). +fields will be added to those of the child class. An example:: From cd4275f8d70a9bab43c4b9f5a2c8007b86765373 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 19 Mar 2018 09:49:16 -0400 Subject: [PATCH 248/389] [1.11.x] Added stub release notes for 1.11.12. Backport of 8d67c7cffdcd5fd0c5cb0b87cd699a05b461e58d from master --- docs/releases/1.11.12.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.12.txt diff --git a/docs/releases/1.11.12.txt b/docs/releases/1.11.12.txt new file mode 100644 index 000000000000..aa0035a3c32b --- /dev/null +++ b/docs/releases/1.11.12.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.12 release notes +============================ + +*Expected April 2, 2018* + +Django 1.11.12 fixes a bug in 1.11.11. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 9ec56c3f9ecb..c2e1a0c99a41 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.12 1.11.11 1.11.10 1.11.9 From c5bb472095f02df364f9a0ce64f0b97bfd4a8c63 Mon Sep 17 00:00:00 2001 From: Amr Anwar Date: Mon, 19 Mar 2018 09:58:50 -0400 Subject: [PATCH 249/389] [1.11.x] Fixed #29229 -- Fixed column mismatch crash when combining two annotated values_list() querysets with union(), difference(), or intersection(). Regression in 7316720603821ebb64dfe8fa592ba6edcef5f3e. Backport of a0c03c62a8ac586e5be5b21393c925afa581efaf from master --- django/db/models/sql/compiler.py | 3 ++- docs/releases/1.11.12.txt | 4 +++- tests/queries/test_qs_combinators.py | 11 +++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index e40770c1511e..033ea984bd3e 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -393,7 +393,8 @@ def get_combinator_sql(self, combinator, all): # If the columns list is limited, then all combined queries # must have the same columns list. Set the selects defined on # the query on all combined queries, if not already set. - if not compiler.query.values_select and self.query.values_select: + if (not compiler.query.values_select and not compiler.query.annotations and + self.query.values_select): compiler.query.set_values(self.query.values_select) parts += (compiler.as_sql(),) except EmptyResultSet: diff --git a/docs/releases/1.11.12.txt b/docs/releases/1.11.12.txt index aa0035a3c32b..64130c065e49 100644 --- a/docs/releases/1.11.12.txt +++ b/docs/releases/1.11.12.txt @@ -9,4 +9,6 @@ Django 1.11.12 fixes a bug in 1.11.11. Bugfixes ======== -* ... +* Fixed a regression in Django 1.11.8 where combining two annotated + ``values_list()`` querysets with ``union()``, ``difference()``, or + ``intersection()`` crashed due to mismatching columns (:ticket:`29229`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 3450014d94a8..9083520c24d1 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -128,6 +128,17 @@ def test_union_with_values(self): reserved_name = qs1.union(qs1).values_list('name', 'order', 'id').get() self.assertEqual(reserved_name[:2], ('a', 2)) + def test_union_with_two_annotated_values_list(self): + qs1 = Number.objects.filter(num=1).annotate( + count=Value(0, IntegerField()), + ).values_list('num', 'count') + qs2 = Number.objects.filter(num=2).values('pk').annotate( + count=F('num'), + ).annotate( + num=Value(1, IntegerField()), + ).values_list('num', 'count') + self.assertCountEqual(qs1.union(qs2), [(1, 0), (2, 1)]) + def test_count_union(self): qs1 = Number.objects.filter(num__lte=1).values('num') qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') From a73a9cf60a4ae4cbf0183a23bf815fd5be3a40ea Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 29 Mar 2018 13:37:31 -0400 Subject: [PATCH 250/389] [1.11.x] Fixed links to Sphinx docs. Backport of 73cb62a33197652a3c8261dbf052d7eb75e26139 from master --- docs/internals/contributing/writing-documentation.txt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index d58c318408f8..8a3bc4b16ace 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -52,9 +52,7 @@ Then, building the HTML is easy; just ``make html`` (or ``make.bat html`` on Windows) from the ``docs`` directory. To get started contributing, you'll want to read the :ref:`reStructuredText -Primer `. After that, you'll want to read about the -:ref:`Sphinx-specific markup ` that's used to manage -metadata, indexing, and cross-references. +reference `. Your locally-built documentation will be themed differently than the documentation at `docs.djangoproject.com `_. @@ -225,8 +223,8 @@ documentation: Django-specific markup ====================== -Besides the :ref:`Sphinx built-in markup `, Django's -docs defines some extra description units: +Besides :ref:`Sphinx's built-in markup `, Django's docs +define some extra description units: * Settings:: From b25433a225d85c6dace5d5e97f4553c01f035fb1 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 30 Mar 2018 11:55:33 +0200 Subject: [PATCH 251/389] [1.11.x] Fixed #29273 -- Prevented initial selection of empty choice in multiple choice widgets. Regression in b52c73008a9d67e9ddbb841872dc15cdd3d6ee01. Backport of f3b69f9757ec03057441ebbd52b7cdbfed31fb32 from master. --- django/forms/widgets.py | 2 ++ docs/releases/1.11.12.txt | 4 ++++ .../widget_tests/test_checkboxselectmultiple.py | 6 ++++-- tests/forms_tests/widget_tests/test_selectmultiple.py | 10 ++++++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index d046d3f001fe..2b7e07bea3e2 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -648,6 +648,8 @@ def value_from_datadict(self, data, files, name): def format_value(self, value): """Return selected values as a list.""" + if value is None and self.allow_multiple_selected: + return [] if not isinstance(value, (tuple, list)): value = [value] return [force_text(v) if v is not None else '' for v in value] diff --git a/docs/releases/1.11.12.txt b/docs/releases/1.11.12.txt index 64130c065e49..186c18be58d6 100644 --- a/docs/releases/1.11.12.txt +++ b/docs/releases/1.11.12.txt @@ -12,3 +12,7 @@ Bugfixes * Fixed a regression in Django 1.11.8 where combining two annotated ``values_list()`` querysets with ``union()``, ``difference()``, or ``intersection()`` crashed due to mismatching columns (:ticket:`29229`). + +* Fixed a regression in Django 1.11 where an empty choice could be initially + selected for the ``SelectMultiple`` and ``CheckboxSelectMultiple`` widgets + (:ticket:`29273`) diff --git a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py index 239f80da4753..1942d3297042 100644 --- a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py +++ b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py @@ -32,10 +32,12 @@ def test_render_value_multiple(self): def test_render_none(self): """ - If the value is None, none of the options are selected. + If the value is None, none of the options are selected, even if the + choices have an empty option. """ - self.check_html(self.widget(choices=self.beatles), 'beatles', None, html=( + self.check_html(self.widget(choices=(('', 'Unknown'),) + self.beatles), 'beatles', None, html=( """
      +
    • diff --git a/tests/forms_tests/widget_tests/test_selectmultiple.py b/tests/forms_tests/widget_tests/test_selectmultiple.py index 149fae6d4b26..3897e3688813 100644 --- a/tests/forms_tests/widget_tests/test_selectmultiple.py +++ b/tests/forms_tests/widget_tests/test_selectmultiple.py @@ -9,7 +9,7 @@ class SelectMultipleTest(WidgetTest): def test_format_value(self): widget = self.widget(choices=self.numeric_choices) - self.assertEqual(widget.format_value(None), ['']) + self.assertEqual(widget.format_value(None), []) self.assertEqual(widget.format_value(''), ['']) self.assertEqual(widget.format_value([3, 0, 1]), ['3', '0', '1']) @@ -35,10 +35,12 @@ def test_render_multiple_selected(self): def test_render_none(self): """ - If the value is None, none of the options are selected. + If the value is None, none of the options are selected, even if the + choices have an empty option. """ - self.check_html(self.widget(choices=self.beatles), 'beatles', None, html=( - """ + From 4e89bbe4a9ff43a18e643ccbae46ff6246d9ee35 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Apr 2018 09:37:38 -0400 Subject: [PATCH 252/389] [1.11.x] Fixed typo in docs/releases/1.11.12.txt. Backport of 09c6d0146178d83b6bd6cc8bb4162a5ae7c1c5f5 from master --- docs/releases/1.11.12.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.12.txt b/docs/releases/1.11.12.txt index 186c18be58d6..347087123d76 100644 --- a/docs/releases/1.11.12.txt +++ b/docs/releases/1.11.12.txt @@ -15,4 +15,4 @@ Bugfixes * Fixed a regression in Django 1.11 where an empty choice could be initially selected for the ``SelectMultiple`` and ``CheckboxSelectMultiple`` widgets - (:ticket:`29273`) + (:ticket:`29273`). From c659625a696aba76fc344e03746441809b0e7bc4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Apr 2018 21:36:23 -0400 Subject: [PATCH 253/389] [1.11.x] Added release date for 1.11.12. Backport of 597aba6d6762f1507aaad1a8caa59def0e1f1871 from master --- docs/releases/1.11.12.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.11.12.txt b/docs/releases/1.11.12.txt index 347087123d76..41eec09c1054 100644 --- a/docs/releases/1.11.12.txt +++ b/docs/releases/1.11.12.txt @@ -2,9 +2,9 @@ Django 1.11.12 release notes ============================ -*Expected April 2, 2018* +*April 2, 2018* -Django 1.11.12 fixes a bug in 1.11.11. +Django 1.11.12 fixes two bugs in 1.11.11. Bugfixes ======== From 8458d4620e26f3d90b51f2080fe4638ce47a9b48 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Apr 2018 22:41:40 -0400 Subject: [PATCH 254/389] [1.11.x] Bumped version for 1.11.12 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 5f4bdd0c5cf7..46f1dcc7c2b5 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 12, 'alpha', 0) +VERSION = (1, 11, 12, 'final', 0) __version__ = get_version(VERSION) From 668f55fab329e5568e5dcd35fea6b1a2f265e468 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 2 Apr 2018 22:46:12 -0400 Subject: [PATCH 255/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 46f1dcc7c2b5..239e0bd18ee8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 12, 'final', 0) +VERSION = (1, 11, 13, 'alpha', 0) __version__ = get_version(VERSION) From 2e9d9dd6b1e319a51f3738cf77f741729d0efb6d Mon Sep 17 00:00:00 2001 From: Daniel Roseman Date: Wed, 4 Apr 2018 14:43:36 +0100 Subject: [PATCH 256/389] [1.11.x] Fixed reference to nonexistent __between lookup. Backport of e6c21217d3c79a507ebab290ba41116407dd7f2a from master --- docs/ref/models/querysets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 1a7141b3adb1..f7b617aa1e1a 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2844,7 +2844,7 @@ lookups. Takes a :class:`datetime.time` value. Example:: Entry.objects.filter(pub_date__time=datetime.time(14, 30)) - Entry.objects.filter(pub_date__time__between=(datetime.time(8), datetime.time(17))) + Entry.objects.filter(pub_date__time__range=(datetime.time(8), datetime.time(17))) (No equivalent SQL code fragment is included for this lookup because implementation of the relevant query varies among different database engines.) From d2906d7c1739d2a15179a80bd35f880af46cd337 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 3 Apr 2018 15:02:58 -0400 Subject: [PATCH 257/389] [1.11.x] Added stub release notes for 1.11.13. Backport of b2678468aee3526ec2d092a2f20b1d12b15ba12f from master --- docs/releases/1.11.13.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.13.txt diff --git a/docs/releases/1.11.13.txt b/docs/releases/1.11.13.txt new file mode 100644 index 000000000000..8dbd6516528b --- /dev/null +++ b/docs/releases/1.11.13.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.13 release notes +============================ + +*Expected May 1, 2018* + +Django 1.11.13 fixes several bugs in 1.11.12. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c2e1a0c99a41..72043d5007f4 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.13 1.11.12 1.11.11 1.11.10 From 4aa097e94e72e0246c882de27d59e37988eabe6f Mon Sep 17 00:00:00 2001 From: Luoxzhg Date: Mon, 9 Apr 2018 21:12:47 +0800 Subject: [PATCH 258/389] [1.11.x] Fixed mistakes in docs/topics/db/examples/many_to_one.txt. Backport of 9d7e2c7b447b2bbabe746770ebd26465cc564f05 from master --- docs/topics/db/examples/many_to_one.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt index c57d9964606d..245c809a9e29 100644 --- a/docs/topics/db/examples/many_to_one.txt +++ b/docs/topics/db/examples/many_to_one.txt @@ -83,7 +83,7 @@ Create an Article via the Reporter object:: Create a new article, and add it to the article set:: - >>> new_article2 = Article(headline="Paul's story", pub_date=date(2006, 1, 17)) + >>> new_article2 = Article.objects.create(headline="Paul's story", pub_date=date(2006, 1, 17)) >>> r.article_set.add(new_article2) >>> new_article2.reporter @@ -105,7 +105,7 @@ Adding an object of the wrong type raises TypeError:: >>> r.article_set.add(r2) Traceback (most recent call last): ... - TypeError: 'Article' instance expected + TypeError: 'Article' instance expected, got >>> r.article_set.all() , ]> From 0037cd1fa0f45c9a8545dcc16bdee7f71f94c773 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 10 Apr 2018 13:26:50 -0400 Subject: [PATCH 259/389] [1.11.x] Refs #28062 -- Doc'd PostgreSQL server-side cursors as a backwards incompatible change. Backport of 2919a08c20d5ae48e381d6bd251d3b0d400d47d9 from master --- docs/releases/1.11.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 86a0fa53b518..7a01cba20576 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -637,6 +637,17 @@ you must render model states using the ``clear_delayed_apps_cache()`` method as described in :ref:`writing your own migration operation `. +Server-side cursors on PostgreSQL +--------------------------------- + +The change to make :meth:`.QuerySet.iterator()` use server-side cursors on +PostgreSQL prevents running Django with `pgBouncer` in transaction pooling +mode. To reallow that, use the :setting:`DISABLE_SERVER_SIDE_CURSORS +` setting (added in Django 1.11.1) in +:setting:`DATABASES`. + +See :ref:`transaction-pooling-server-side-cursors` for more discussion. + Miscellaneous ------------- From a1f4e14f99dfaa63b181c2fb1f211d9f91644ad0 Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Wed, 11 Apr 2018 10:19:09 -0400 Subject: [PATCH 260/389] [1.11.x] Tested altering a unique field when a reverse M2M relation exists. Backport of 003334f8af29e2023cf7ad7d080aa9ab26a7c528 from master --- tests/schema/tests.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index f3ad54b3bf1e..36daccb19bfa 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -21,7 +21,7 @@ from django.test import ( TransactionTestCase, mock, skipIfDBFeature, skipUnlessDBFeature, ) -from django.test.utils import CaptureQueriesContext, isolate_apps +from django.test.utils import CaptureQueriesContext, isolate_apps, patch_logger from django.utils import timezone from .fields import ( @@ -1392,6 +1392,42 @@ def test_unique(self): TagUniqueRename.objects.create(title="bar", slug2="foo") Tag.objects.all().delete() + @isolate_apps('schema') + @unittest.skipIf(connection.vendor == 'sqlite', 'SQLite remakes the table on field alteration.') + def test_unique_and_reverse_m2m(self): + """ + AlterField can modify a unique field when there's a reverse M2M + relation on the model. + """ + class Tag(Model): + title = CharField(max_length=255) + slug = SlugField(unique=True) + + class Meta: + app_label = 'schema' + + class Book(Model): + tags = ManyToManyField(Tag, related_name='books') + + class Meta: + app_label = 'schema' + + with connection.schema_editor() as editor: + editor.create_model(Tag) + editor.create_model(Book) + new_field = SlugField(max_length=75, unique=True) + new_field.model = Tag + new_field.set_attributes_from_name('slug') + with patch_logger('django.db.backends.schema', 'debug') as logger_calls: + with connection.schema_editor() as editor: + editor.alter_field(Tag, Tag._meta.get_field('slug'), new_field) + # One SQL statement is executed to alter the field. + self.assertEqual(len(logger_calls), 1) + # Ensure that the field is still unique. + Tag.objects.create(title='foo', slug='foo') + with self.assertRaises(IntegrityError): + Tag.objects.create(title='bar', slug='foo') + def test_unique_together(self): """ Tests removing and adding unique_together constraints on a model. From 8f76939f54924b01b41d4242e71ca98eb35964f2 Mon Sep 17 00:00:00 2001 From: Jeremy Bowman Date: Mon, 9 Apr 2018 13:35:06 -0400 Subject: [PATCH 261/389] [1.11.x] Fixed #29193 -- Prevented unnecessary foreign key drops when altering a unique field. Stopped dropping and recreating foreign key constraints on other fields in the same table as the one which is actually being altered in an AlterField operation. Regression in c3e0adcad8d8ba94b33cabd137056166ed36dae0. Backport of ee17bb8a67a9e7e688da6e6f4b3be1b3a69c09b0 from master --- AUTHORS | 1 + django/db/backends/base/schema.py | 22 +++++++++++++++++--- docs/releases/1.11.13.txt | 4 +++- tests/schema/tests.py | 34 +++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9d27cd039bd5..4f92fd3ef72b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -365,6 +365,7 @@ answer newbie questions, and generally made Django that much better: Jensen Cochran Jeong-Min Lee Jérémie Blaser + Jeremy Bowman Jeremy Carbaugh Jeremy Dunck Jeremy Lainé diff --git a/django/db/backends/base/schema.py b/django/db/backends/base/schema.py index 8802476c077f..efef3e0f3cba 100644 --- a/django/db/backends/base/schema.py +++ b/django/db/backends/base/schema.py @@ -11,12 +11,28 @@ logger = logging.getLogger('django.db.backends.schema') +def _is_relevant_relation(relation, altered_field): + """ + When altering the given field, must constraints on its model from the given + relation be temporarily dropped? + """ + field = relation.field + if field.many_to_many: + # M2M reverse field + return False + if altered_field.primary_key and field.to_fields == [None]: + # Foreign key constraint on the primary key, which is being altered. + return True + # Is the constraint targeting the field being altered? + return altered_field.name in field.to_fields + + def _related_non_m2m_objects(old_field, new_field): # Filters out m2m objects from reverse relations. # Returns (old_relation, new_relation) tuples. return zip( - (obj for obj in old_field.model._meta.related_objects if not obj.field.many_to_many), - (obj for obj in new_field.model._meta.related_objects if not obj.field.many_to_many) + (obj for obj in old_field.model._meta.related_objects if _is_relevant_relation(obj, old_field)), + (obj for obj in new_field.model._meta.related_objects if _is_relevant_relation(obj, new_field)) ) @@ -780,7 +796,7 @@ def _alter_field(self, model, old_field, new_field, old_type, new_type, # Rebuild FKs that pointed to us if we previously had to drop them if drop_foreign_keys: for rel in new_field.model._meta.related_objects: - if not rel.many_to_many and rel.field.db_constraint: + if _is_relevant_relation(rel, new_field) and rel.field.db_constraint: self.execute(self._create_fk_sql(rel.related_model, rel.field, "_fk")) # Does it have check constraints we need to add? if old_db_params['check'] != new_db_params['check'] and new_db_params['check']: diff --git a/docs/releases/1.11.13.txt b/docs/releases/1.11.13.txt index 8dbd6516528b..b9fd3329ef34 100644 --- a/docs/releases/1.11.13.txt +++ b/docs/releases/1.11.13.txt @@ -9,4 +9,6 @@ Django 1.11.13 fixes several bugs in 1.11.12. Bugfixes ======== -* ... +* Fixed a regression in Django 1.11.8 where altering a field with a unique + constraint may drop and rebuild more foreign keys than necessary + (:ticket:`29193`). diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 36daccb19bfa..1f8f97188ccf 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1392,6 +1392,40 @@ def test_unique(self): TagUniqueRename.objects.create(title="bar", slug2="foo") Tag.objects.all().delete() + @isolate_apps('schema') + @unittest.skipIf(connection.vendor == 'sqlite', 'SQLite naively remakes the table on field alteration.') + @skipUnlessDBFeature('supports_foreign_keys') + def test_unique_no_unnecessary_fk_drops(self): + """ + If AlterField isn't selective about dropping foreign key constraints + when modifying a field with a unique constraint, the AlterField + incorrectly drops and recreates the Book.author foreign key even though + it doesn't restrict the field being changed (#29193). + """ + class Author(Model): + name = CharField(max_length=254, unique=True) + + class Meta: + app_label = 'schema' + + class Book(Model): + author = ForeignKey(Author, CASCADE) + + class Meta: + app_label = 'schema' + + with connection.schema_editor() as editor: + editor.create_model(Author) + editor.create_model(Book) + new_field = CharField(max_length=255, unique=True) + new_field.model = Author + new_field.set_attributes_from_name('name') + with patch_logger('django.db.backends.schema', 'debug') as logger_calls: + with connection.schema_editor() as editor: + editor.alter_field(Author, Author._meta.get_field('name'), new_field) + # One SQL statement is executed to alter the field. + self.assertEqual(len(logger_calls), 1) + @isolate_apps('schema') @unittest.skipIf(connection.vendor == 'sqlite', 'SQLite remakes the table on field alteration.') def test_unique_and_reverse_m2m(self): From 979253fce9c0bb6ee484a5a786d477a47545d972 Mon Sep 17 00:00:00 2001 From: Paul Donohue Date: Sun, 8 Apr 2018 13:35:24 -0400 Subject: [PATCH 262/389] [1.11.x] Fixed #29296 -- Fixed crashes in admindocs when a view is a callable object. Backport of 33a0b7ac815588ed92dca215e153390af8bdbdda from master --- AUTHORS | 1 + django/contrib/admindocs/middleware.py | 4 +++- django/contrib/admindocs/utils.py | 11 +++++++++++ django/contrib/admindocs/views.py | 15 +++------------ docs/releases/1.11.13.txt | 3 +++ tests/admin_docs/test_middleware.py | 5 +++++ tests/admin_docs/test_views.py | 6 ++++++ tests/admin_docs/urls.py | 2 ++ tests/admin_docs/views.py | 5 +++++ 9 files changed, 39 insertions(+), 13 deletions(-) diff --git a/AUTHORS b/AUTHORS index 4f92fd3ef72b..e29c17953fd0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -607,6 +607,7 @@ answer newbie questions, and generally made Django that much better: Paul Bissex Paul Collier Paul Collins + Paul Donohue Paul Lanier Paul McLanahan Paul McMillan diff --git a/django/contrib/admindocs/middleware.py b/django/contrib/admindocs/middleware.py index 4bdd4f45b5bb..63dcb5f076ec 100644 --- a/django/contrib/admindocs/middleware.py +++ b/django/contrib/admindocs/middleware.py @@ -2,6 +2,8 @@ from django.conf import settings from django.utils.deprecation import MiddlewareMixin +from .utils import get_view_name + class XViewMiddleware(MiddlewareMixin): """ @@ -24,5 +26,5 @@ def process_view(self, request, view_func, view_args, view_kwargs): if request.method == 'HEAD' and (request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS or (request.user.is_active and request.user.is_staff)): response = http.HttpResponse() - response['X-View'] = "%s.%s" % (view_func.__module__, view_func.__name__) + response['X-View'] = get_view_name(view_func) return response diff --git a/django/contrib/admindocs/utils.py b/django/contrib/admindocs/utils.py index b6a23c884949..7275e15707ce 100644 --- a/django/contrib/admindocs/utils.py +++ b/django/contrib/admindocs/utils.py @@ -5,6 +5,7 @@ from email.parser import HeaderParser from django.urls import reverse +from django.utils import six from django.utils.encoding import force_bytes from django.utils.safestring import mark_safe @@ -18,6 +19,16 @@ docutils_is_available = True +def get_view_name(view_func): + mod_name = view_func.__module__ + if six.PY3: + view_name = getattr(view_func, '__qualname__', view_func.__class__.__name__) + else: + # PY2 does not support __qualname__ + view_name = getattr(view_func, '__name__', view_func.__class__.__name__) + return mod_name + '.' + view_name + + def trim_docstring(docstring): """ Uniformly trim leading/trailing whitespace from docstrings. diff --git a/django/contrib/admindocs/views.py b/django/contrib/admindocs/views.py index 5c6701aef2d7..12f5863228e8 100644 --- a/django/contrib/admindocs/views.py +++ b/django/contrib/admindocs/views.py @@ -15,7 +15,6 @@ from django.http import Http404 from django.template.engine import Engine from django.urls import get_mod_func, get_resolver, get_urlconf, reverse -from django.utils import six from django.utils.decorators import method_decorator from django.utils.inspect import ( func_accepts_kwargs, func_accepts_var_args, func_has_no_args, @@ -24,6 +23,8 @@ from django.utils.translation import ugettext as _ from django.views.generic import TemplateView +from .utils import get_view_name + # Exclude methods starting with these strings from documentation MODEL_METHODS_EXCLUDE = ('_', 'add_', 'delete', 'save', 'set_') @@ -129,23 +130,13 @@ def get_context_data(self, **kwargs): class ViewIndexView(BaseAdminDocsView): template_name = 'admin_doc/view_index.html' - @staticmethod - def _get_full_name(func): - mod_name = func.__module__ - if six.PY3: - return '%s.%s' % (mod_name, func.__qualname__) - else: - # PY2 does not support __qualname__ - func_name = getattr(func, '__name__', func.__class__.__name__) - return '%s.%s' % (mod_name, func_name) - def get_context_data(self, **kwargs): views = [] urlconf = import_module(settings.ROOT_URLCONF) view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns) for (func, regex, namespace, name) in view_functions: views.append({ - 'full_name': self._get_full_name(func), + 'full_name': get_view_name(func), 'url': simplify_regex(regex), 'url_name': ':'.join((namespace or []) + (name and [name] or [])), 'namespace': ':'.join((namespace or [])), diff --git a/docs/releases/1.11.13.txt b/docs/releases/1.11.13.txt index b9fd3329ef34..f72ccd8fd260 100644 --- a/docs/releases/1.11.13.txt +++ b/docs/releases/1.11.13.txt @@ -12,3 +12,6 @@ Bugfixes * Fixed a regression in Django 1.11.8 where altering a field with a unique constraint may drop and rebuild more foreign keys than necessary (:ticket:`29193`). + +* Fixed crashes in ``django.contrib.admindocs`` when a view is a callable + object, such as ``django.contrib.syndication.views.Feed`` (:ticket:`29296`). diff --git a/tests/admin_docs/test_middleware.py b/tests/admin_docs/test_middleware.py index 426c78d58fb0..e5dbd9dfb305 100644 --- a/tests/admin_docs/test_middleware.py +++ b/tests/admin_docs/test_middleware.py @@ -42,3 +42,8 @@ def test_xview_class(self): user.save() response = self.client.head('/xview/class/') self.assertNotIn('X-View', response) + + def test_callable_object_view(self): + self.client.force_login(self.superuser) + response = self.client.head('/xview/callable_object/') + self.assertEqual(response['X-View'], 'admin_docs.views.XViewCallableObject') diff --git a/tests/admin_docs/test_views.py b/tests/admin_docs/test_views.py index bd483007c755..cf4f9359c7df 100644 --- a/tests/admin_docs/test_views.py +++ b/tests/admin_docs/test_views.py @@ -54,6 +54,12 @@ def test_view_index(self): ) self.assertContains(response, 'Views by namespace test') self.assertContains(response, 'Name: test:func.') + self.assertContains( + response, + '

      ' + '/xview/callable_object_without_xview/

      ', + html=True, + ) @unittest.skipIf(six.PY2, "Python 2 doesn't support __qualname__.") def test_view_index_with_method(self): diff --git a/tests/admin_docs/urls.py b/tests/admin_docs/urls.py index 0bcdee4b4bd7..67c72b249c41 100644 --- a/tests/admin_docs/urls.py +++ b/tests/admin_docs/urls.py @@ -13,4 +13,6 @@ url(r'^', include(ns_patterns, namespace='test')), url(r'^xview/func/$', views.xview_dec(views.xview)), url(r'^xview/class/$', views.xview_dec(views.XViewClass.as_view())), + url(r'^xview/callable_object/$', views.xview_dec(views.XViewCallableObject())), + url(r'^xview/callable_object_without_xview/$', views.XViewCallableObject()), ] diff --git a/tests/admin_docs/views.py b/tests/admin_docs/views.py index 31d253f7e285..21fe382bba7b 100644 --- a/tests/admin_docs/views.py +++ b/tests/admin_docs/views.py @@ -13,3 +13,8 @@ def xview(request): class XViewClass(View): def get(self, request): return HttpResponse() + + +class XViewCallableObject(View): + def __call__(self, request): + return HttpResponse() From 46496a542c2ff9f273e090073e9c8071acb1a4a4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 12 Apr 2018 13:59:02 -0400 Subject: [PATCH 263/389] [1.11.x] Fixed schema test failure when running tests in reverse. Follow up to 003334f8af29e2023cf7ad7d080aa9ab26a7c528. Backport of 78f8b80f9b215e50618375adce4c97795dabbb84 from master --- tests/schema/tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 1f8f97188ccf..7108977c2b2d 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -1446,6 +1446,7 @@ class Book(Model): class Meta: app_label = 'schema' + self.isolated_local_models = [Book._meta.get_field('tags').remote_field.through] with connection.schema_editor() as editor: editor.create_model(Tag) editor.create_model(Book) From f89b11b879b83aa505dc8231da5f06ca4b1b062e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 13 Apr 2018 12:15:52 +0200 Subject: [PATCH 264/389] [1.11.x] Fixed #29286 -- Fixed column mismatch crash with QuerySet.values() or values_list() after combining an annotated and unannotated queryset with union(), difference(), or intersection(). Regression in a0c03c62a8ac586e5be5b21393c925afa581efaf. Thanks Tim Graham and Carlton Gibson for reviews. Backport of 0b66c3b442875627fa6daef4ac1e90900d74290b from master. --- django/db/models/sql/compiler.py | 5 ++--- docs/releases/1.11.13.txt | 5 +++++ tests/queries/test_qs_combinators.py | 10 +++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 033ea984bd3e..edfe22e36685 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -393,9 +393,8 @@ def get_combinator_sql(self, combinator, all): # If the columns list is limited, then all combined queries # must have the same columns list. Set the selects defined on # the query on all combined queries, if not already set. - if (not compiler.query.values_select and not compiler.query.annotations and - self.query.values_select): - compiler.query.set_values(self.query.values_select) + if not compiler.query.values_select and self.query.values_select: + compiler.query.set_values(tuple(self.query.values_select) + tuple(self.query.annotation_select)) parts += (compiler.as_sql(),) except EmptyResultSet: # Omit the empty queryset with UNION and with DIFFERENCE if the diff --git a/docs/releases/1.11.13.txt b/docs/releases/1.11.13.txt index f72ccd8fd260..4c55454bcd17 100644 --- a/docs/releases/1.11.13.txt +++ b/docs/releases/1.11.13.txt @@ -15,3 +15,8 @@ Bugfixes * Fixed crashes in ``django.contrib.admindocs`` when a view is a callable object, such as ``django.contrib.syndication.views.Feed`` (:ticket:`29296`). + +* Fixed a regression in Django 1.11.12 where ``QuerySet.values()`` or + ``values_list()`` after combining an annotated and unannotated queryset with + ``union()``, ``difference()``, or ``intersection()`` crashed due to mismatching + columns (:ticket:`29286`). diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 9083520c24d1..94af81a49ba6 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from django.db.models import F, IntegerField, Value +from django.db.models import Exists, F, IntegerField, OuterRef, Value from django.db.utils import DatabaseError from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature from django.utils.six.moves import range @@ -139,6 +139,14 @@ def test_union_with_two_annotated_values_list(self): ).values_list('num', 'count') self.assertCountEqual(qs1.union(qs2), [(1, 0), (2, 1)]) + def test_union_with_values_list_on_annotated_and_unannotated(self): + ReservedName.objects.create(name='rn1', order=1) + qs1 = Number.objects.annotate( + has_reserved_name=Exists(ReservedName.objects.filter(order=OuterRef('num'))) + ).filter(has_reserved_name=True) + qs2 = Number.objects.filter(num=9) + self.assertCountEqual(qs1.union(qs2).values_list('num', flat=True), [1, 9]) + def test_count_union(self): qs1 = Number.objects.filter(num__lte=1).values('num') qs2 = Number.objects.filter(num__gte=2, num__lte=3).values('num') From 4a20aae4687df13bf4d81c120c57a88dfb468600 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 14 Apr 2018 07:18:33 -0400 Subject: [PATCH 265/389] [1.11.x] Added isolated_local_models support to schema tests. Follow up to 46496a542c2ff9f273e090073e9c8071acb1a4a4, which otherwise has no effect. Partial backport of 9f7772e098439f9edea3d25ab127539fc514eeb2 from master --- tests/schema/tests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/schema/tests.py b/tests/schema/tests.py index 7108977c2b2d..96198bad8877 100644 --- a/tests/schema/tests.py +++ b/tests/schema/tests.py @@ -59,6 +59,9 @@ def setUp(self): # local_models should contain test dependent model classes that will be # automatically removed from the app cache on test tear down. self.local_models = [] + # isolated_local_models contains models that are in test methods + # decorated with @isolate_apps. + self.isolated_local_models = [] def tearDown(self): # Delete any tables made for our models @@ -73,6 +76,10 @@ def tearDown(self): if through and through._meta.auto_created: del new_apps.all_models['schema'][through._meta.model_name] del new_apps.all_models['schema'][model._meta.model_name] + if self.isolated_local_models: + with connection.schema_editor() as editor: + for model in self.isolated_local_models: + editor.delete_model(model) def delete_tables(self): "Deletes all model tables for our models for a clean test environment" From 800778f7ad64a04401034f6b1005ec8071efc3bf Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 17 Apr 2018 21:30:05 -0400 Subject: [PATCH 266/389] [1.11.x] Fixed a test failure with the latest GeoIP databases. Backport of 7a22d9f75125e3cfbea0979a876efe4634f6fe05 from master --- tests/gis_tests/test_geoip.py | 2 +- tests/gis_tests/test_geoip2.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/gis_tests/test_geoip.py b/tests/gis_tests/test_geoip.py index 36382bfb5d83..fa0df950b68c 100644 --- a/tests/gis_tests/test_geoip.py +++ b/tests/gis_tests/test_geoip.py @@ -145,7 +145,7 @@ def test05_unicode_response(self): fqdn = "messe-duesseldorf.com" if self._is_dns_available(fqdn): d = g.city(fqdn) - self.assertEqual('Essen', d['city']) + self.assertEqual('Düsseldorf', d['city']) d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index a2a59a3de640..414d9e492b94 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -137,10 +137,10 @@ def test04_city(self, gethostbyname): @mock.patch('socket.gethostbyname') def test05_unicode_response(self, gethostbyname): "GeoIP strings should be properly encoded (#16553)." - gethostbyname.return_value = '191.252.51.69' + gethostbyname.return_value = '194.27.42.76' g = GeoIP2() - d = g.city('www.fasano.com.br') - self.assertEqual(d['city'], 'São José dos Campos') + d = g.city('nigde.edu.tr') + self.assertEqual('Niğde', d['city']) d = g.country('200.26.205.1') # Some databases have only unaccented countries self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) From 8b4798c8d31b3cd9faab4caf11fca000b07f0181 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 18 Apr 2018 08:29:21 -0400 Subject: [PATCH 267/389] [1.11.x] Fixed #29174, #29175 -- Doc'd that f-strings and JavaScript template strings can't be translated. Backport of c3437f734d03d93f798151f712064394652cabed from master --- docs/topics/i18n/translation.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 158bc6fb58c5..a9fc247d0fc8 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -137,6 +137,13 @@ instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you have more than a single parameter. If you used positional interpolation, translations wouldn't be able to reorder placeholder text. +Since string extraction is done by the ``xgettext`` command, only syntaxes +supported by ``gettext`` are supported by Django. Python f-strings_ and +`JavaScript template strings`_ are not yet supported by ``xgettext``. + +.. _f-strings: https://docs.python.org/3/reference/lexical_analysis.html#f-strings +.. _JavaScript template strings: https://savannah.gnu.org/bugs/?50920 + .. _translator-comments: Comments for translators From a5d1fe59c59fa716918b6cec5e8ab747720b74fc Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 18 Apr 2018 15:03:56 +0200 Subject: [PATCH 268/389] Revert "[1.11.x] Fixed #29174, #29175 -- Doc'd that f-strings and JavaScript template strings can't be translated." This reverts commit 8b4798c8d31b3cd9faab4caf11fca000b07f0181. --- docs/topics/i18n/translation.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index a9fc247d0fc8..158bc6fb58c5 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -137,13 +137,6 @@ instead of positional interpolation (e.g., ``%s`` or ``%d``) whenever you have more than a single parameter. If you used positional interpolation, translations wouldn't be able to reorder placeholder text. -Since string extraction is done by the ``xgettext`` command, only syntaxes -supported by ``gettext`` are supported by Django. Python f-strings_ and -`JavaScript template strings`_ are not yet supported by ``xgettext``. - -.. _f-strings: https://docs.python.org/3/reference/lexical_analysis.html#f-strings -.. _JavaScript template strings: https://savannah.gnu.org/bugs/?50920 - .. _translator-comments: Comments for translators From 2d1c914910fb33b5797d94f57277907945da28a4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 May 2018 21:18:44 -0400 Subject: [PATCH 269/389] [1.11.x] Added release date for 1.11.13. --- docs/releases/1.11.13.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.13.txt b/docs/releases/1.11.13.txt index 4c55454bcd17..d5a72bb52918 100644 --- a/docs/releases/1.11.13.txt +++ b/docs/releases/1.11.13.txt @@ -2,7 +2,7 @@ Django 1.11.13 release notes ============================ -*Expected May 1, 2018* +*May 1, 2018* Django 1.11.13 fixes several bugs in 1.11.12. From 2b882a4bd954c8a6b1447f8fc0841a3352514c26 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 May 2018 21:41:00 -0400 Subject: [PATCH 270/389] [1.11.x] Bumped version for 1.11.13 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 239e0bd18ee8..95d426a6e81a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 13, 'alpha', 0) +VERSION = (1, 11, 13, 'final', 0) __version__ = get_version(VERSION) From 999e82313c3e36ef54b4ab0acb4917505038d5c9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 May 2018 21:56:43 -0400 Subject: [PATCH 271/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 95d426a6e81a..abf90175ec26 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 13, 'final', 0) +VERSION = (1, 11, 14, 'alpha', 0) __version__ = get_version(VERSION) From 1824656b10a7759281d94c70e383257774470e85 Mon Sep 17 00:00:00 2001 From: Daniel Hepper Date: Sun, 27 May 2018 16:08:50 +0200 Subject: [PATCH 272/389] [1.11.x] Removed docs for obsolete ExceptionMiddleware. Backport of a6fb5b1fe022c5279aa275c70b5193f2a2fac5fe from master --- docs/ref/middleware.txt | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index f4ecc8a96ec4..ca869366a2d7 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -89,36 +89,6 @@ issued by the middleware. * Sends broken link notification emails to :setting:`MANAGERS` (see :doc:`/howto/error-reporting`). -Exception middleware --------------------- - -.. module:: django.middleware.exception - :synopsis: Middleware to return responses for exceptions. - -.. class:: ExceptionMiddleware - -.. versionadded:: 1.10 - -Catches exceptions raised during the request/response cycle and returns the -appropriate response. - -* :class:`~django.http.Http404` is processed by - :data:`~django.conf.urls.handler404` (or a more friendly debug page if - :setting:`DEBUG=True `). -* :class:`~django.core.exceptions.PermissionDenied` is processed - by :data:`~django.conf.urls.handler403`. -* ``MultiPartParserError`` is processed by :data:`~django.conf.urls.handler400`. -* :class:`~django.core.exceptions.SuspiciousOperation` is processed by - :data:`~django.conf.urls.handler400` (or a more friendly debug page if - :setting:`DEBUG=True `). -* Any other exception is processed by :data:`~django.conf.urls.handler500` - (or a more friendly debug page if :setting:`DEBUG=True `). - -Django uses this middleware regardless of whether or not you include it in -:setting:`MIDDLEWARE`, however, you may want to subclass if your own middleware -needs to transform any of these exceptions into the appropriate responses. -:class:`~django.middleware.locale.LocaleMiddleware` does this, for example. - GZip middleware --------------- From 10e6dd718d0aa276aa0d8c9c1400c933538999a3 Mon Sep 17 00:00:00 2001 From: Osaetin Daniel Date: Sun, 27 May 2018 21:50:30 +0100 Subject: [PATCH 273/389] [1.11.x] Fixed docs typo in HttpResponse.set_signed_cookie() signature. Backport of cd242d185bda9269913d4d101a7f704204ec907d from master --- docs/ref/request-response.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 8e9f0fcf026e..791b8a5343f6 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -807,7 +807,7 @@ Methods to store a cookie of more than 4096 bytes, but many browsers will not set the cookie correctly. -.. method:: HttpResponse.set_signed_cookie(key, value, salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=True) +.. method:: HttpResponse.set_signed_cookie(key, value, salt='', max_age=None, expires=None, path='/', domain=None, secure=None, httponly=False) Like :meth:`~HttpResponse.set_cookie()`, but :doc:`cryptographic signing ` the cookie before setting From 212804faed77955bdd580887eb25e6b24923942f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 31 May 2018 10:15:39 -0400 Subject: [PATCH 274/389] [1.11.x] Added stub release notes for 1.11.14. Backport of 8a6fcfdc77d84bd5cebf1e6a6dd65c64f9cb40b8 from master --- docs/releases/1.11.14.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.14.txt diff --git a/docs/releases/1.11.14.txt b/docs/releases/1.11.14.txt new file mode 100644 index 000000000000..23f16e0651c1 --- /dev/null +++ b/docs/releases/1.11.14.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.14 release notes +============================ + +*Expected July 2, 2018* + +Django 1.11.14 fixes several bugs in 1.11.13. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 72043d5007f4..6347af1a5727 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.14 1.11.13 1.11.12 1.11.11 From 5bb00c01d68ad2b099396b4b4f367f0c9d0fddb1 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 31 May 2018 11:35:59 -0400 Subject: [PATCH 275/389] [1.11.x] Fixed #29460 -- Added support for GEOS 3.6. Backport of f185d929fa1c0caad8c03fccde899b647d7248c6 from master --- django/contrib/gis/geos/prototypes/io.py | 8 +++++--- docs/ref/contrib/gis/install/geolibs.txt | 3 ++- docs/releases/1.11.14.txt | 3 ++- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index c932a1a15f6f..911aa490161b 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -2,7 +2,9 @@ from ctypes import POINTER, Structure, byref, c_char, c_char_p, c_int, c_size_t from django.contrib.gis.geos.base import GEOSBase -from django.contrib.gis.geos.libgeos import GEOM_PTR, GEOSFuncFactory +from django.contrib.gis.geos.libgeos import ( + GEOM_PTR, GEOSFuncFactory, geos_version_info, +) from django.contrib.gis.geos.prototypes.errcheck import ( check_geom, check_sized_string, check_string, ) @@ -236,7 +238,7 @@ def write(self, geom): from django.contrib.gis.geos import Polygon geom = self._handle_empty_point(geom) wkb = wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())) - if isinstance(geom, Polygon) and geom.empty: + if geos_version_info()['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: # Fix GEOS output for empty polygon. # See https://trac.osgeo.org/geos/ticket/680. wkb = wkb[:-8] + b'\0' * 4 @@ -247,7 +249,7 @@ def write_hex(self, geom): from django.contrib.gis.geos.polygon import Polygon geom = self._handle_empty_point(geom) wkb = wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) - if isinstance(geom, Polygon) and geom.empty: + if geos_version_info()['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: wkb = wkb[:-16] + b'0' * 8 return wkb diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 8d6281539b05..f4f74d558762 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -8,7 +8,7 @@ geospatial libraries: ======================== ==================================== ================================ =================================== Program Description Required Supported Versions ======================== ==================================== ================================ =================================== -:doc:`GEOS <../geos>` Geometry Engine Open Source Yes 3.5, 3.4, 3.3 +:doc:`GEOS <../geos>` Geometry Engine Open Source Yes 3.6, 3.5, 3.4, 3.3 `PROJ.4`_ Cartographic Projections library Yes (PostgreSQL and SQLite only) 4.9, 4.8, 4.7, 4.6, 4.5, 4.4 :doc:`GDAL <../gdal>` Geospatial Data Abstraction Library Yes 2.1, 2.0, 1.11, 1.10, 1.9 :doc:`GeoIP <../geoip2>` IP-based geolocation library No 2 @@ -29,6 +29,7 @@ totally fine with GeoDjango. Your mileage may vary. GEOS 3.3.0 2011-05-30 GEOS 3.4.0 2013-08-11 GEOS 3.5.0 2015-08-15 + GEOS 3.6.0 2016-10-25 GDAL 1.9.0 2012-01-03 GDAL 1.10.0 2013-04-29 GDAL 1.11.0 2014-04-25 diff --git a/docs/releases/1.11.14.txt b/docs/releases/1.11.14.txt index 23f16e0651c1..06fe74464a52 100644 --- a/docs/releases/1.11.14.txt +++ b/docs/releases/1.11.14.txt @@ -9,4 +9,5 @@ Django 1.11.14 fixes several bugs in 1.11.13. Bugfixes ======== -* ... +* Fixed ``WKBWriter.write()`` and ``write_hex()`` for empty polygons on + GEOS 3.6.1+ (:ticket:`29460`). From 6f171c285ee5fc5af211597cae41dc724d4bc2eb Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 2 Jun 2017 20:47:17 +0200 Subject: [PATCH 276/389] [1.11.x] Refs #28257 -- Updated a test for GDAL 2.2 Partial backport of 28627608945ddc3f59fb6a011a4eb363d8020e83 from master --- tests/gis_tests/gdal_tests/test_ds.py | 32 ++++++++++++++------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tests/gis_tests/gdal_tests/test_ds.py b/tests/gis_tests/gdal_tests/test_ds.py index 5b0903248260..45cb2557b3dc 100644 --- a/tests/gis_tests/gdal_tests/test_ds.py +++ b/tests/gis_tests/gdal_tests/test_ds.py @@ -1,4 +1,5 @@ import os +import re import unittest from django.contrib.gis.gdal import ( @@ -9,17 +10,26 @@ from ..test_data import TEST_DATA, TestDS, get_ds_file +wgs_84_wkt = ( + 'GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",' + '6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",' + '0.017453292519943295]]' +) +# Using a regex because of small differences depending on GDAL versions. +# AUTHORITY part has been added in GDAL 2.2. +wgs_84_wkt_regex = ( + r'^GEOGCS\["GCS_WGS_1984",DATUM\["WGS_1984",SPHEROID\["WGS_(19)?84",' + r'6378137,298.257223563\]\],PRIMEM\["Greenwich",0\],UNIT\["Degree",' + r'0.017453292519943295\](,AUTHORITY\["EPSG","4326"\])?\]$' +) + # List of acceptable data sources. ds_list = ( TestDS( 'test_point', nfeat=5, nfld=3, geom='POINT', gtype=1, driver='ESRI Shapefile', fields={'dbl': OFTReal, 'int': OFTInteger, 'str': OFTString}, extent=(-1.35011, 0.166623, -0.524093, 0.824508), # Got extent from QGIS - srs_wkt=( - 'GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",' - '6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",' - '0.017453292519943295]]' - ), + srs_wkt=wgs_84_wkt, field_values={ 'dbl': [float(i) for i in range(1, 6)], 'int': list(range(1, 6)), @@ -48,11 +58,7 @@ driver='ESRI Shapefile', fields={'float': OFTReal, 'int': OFTInteger, 'str': OFTString}, extent=(-1.01513, -0.558245, 0.161876, 0.839637), # Got extent from QGIS - srs_wkt=( - 'GEOGCS["GCS_WGS_1984",DATUM["WGS_1984",SPHEROID["WGS_1984",' - '6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",' - '0.017453292519943295]]' - ), + srs_wkt=wgs_84_wkt, ) ) @@ -212,11 +218,7 @@ def test05_geometries(self): # Making sure the SpatialReference is as expected. if hasattr(source, 'srs_wkt'): - self.assertEqual( - source.srs_wkt, - # Depending on lib versions, WGS_84 might be WGS_1984 - g.srs.wkt.replace('SPHEROID["WGS_84"', 'SPHEROID["WGS_1984"') - ) + self.assertIsNotNone(re.match(wgs_84_wkt_regex, g.srs.wkt)) def test06_spatial_filter(self): "Testing the Layer.spatial_filter property." From d60d7d6d71018b17f01bbf0c390c7d929ff28310 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 1 Jun 2018 22:31:46 -0400 Subject: [PATCH 277/389] [1.11.x] Fixed #29462 -- Fixed ogrinspect test failures with GDAL 2.2. Backport of 55f4eee75d41499995bfdb611ac89e80c87404eb from master --- tests/gis_tests/inspectapp/tests.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/gis_tests/inspectapp/tests.py b/tests/gis_tests/inspectapp/tests.py index f971c85548f8..1e153fe77247 100644 --- a/tests/gis_tests/inspectapp/tests.py +++ b/tests/gis_tests/inspectapp/tests.py @@ -62,6 +62,7 @@ def test_3d_columns(self): INSTALLED_APPS={'append': 'django.contrib.gis'}, ) class OGRInspectTest(TestCase): + expected_srid = 'srid=-1' if GDAL_VERSION < (2, 2) else '' maxDiff = 1024 def test_poly(self): @@ -76,7 +77,7 @@ def test_poly(self): ' float = models.FloatField()', ' int = models.{}()'.format('BigIntegerField' if GDAL_VERSION >= (2, 0) else 'FloatField'), ' str = models.CharField(max_length=80)', - ' geom = models.PolygonField(srid=-1)', + ' geom = models.PolygonField(%s)' % self.expected_srid, ] self.assertEqual(model_def, '\n'.join(expected)) @@ -84,7 +85,7 @@ def test_poly(self): def test_poly_multi(self): shp_file = os.path.join(TEST_DATA, 'test_poly', 'test_poly.shp') model_def = ogrinspect(shp_file, 'MyModel', multi_geom=True) - self.assertIn('geom = models.MultiPolygonField(srid=-1)', model_def) + self.assertIn('geom = models.MultiPolygonField(%s)' % self.expected_srid, model_def) # Same test with a 25D-type geometry field shp_file = os.path.join(TEST_DATA, 'gas_lines', 'gas_leitung.shp') model_def = ogrinspect(shp_file, 'MyModel', multi_geom=True) @@ -103,7 +104,7 @@ def test_date_field(self): ' population = models.{}()'.format('BigIntegerField' if GDAL_VERSION >= (2, 0) else 'FloatField'), ' density = models.FloatField()', ' created = models.DateField()', - ' geom = models.PointField(srid=-1)', + ' geom = models.PointField(%s)' % self.expected_srid, ] self.assertEqual(model_def, '\n'.join(expected)) From b548180605e760fcc284bf20dde310e9afb49a5e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 1 Jun 2018 22:54:26 -0400 Subject: [PATCH 278/389] [1.11.x] Fixed #29461 -- Fixed ogrinspect test_time_field failure on SpatiaLite. Backport of 666be7b9942611d5c0f5e485c448f219cd5a1ad5 from master --- tests/gis_tests/inspectapp/tests.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/gis_tests/inspectapp/tests.py b/tests/gis_tests/inspectapp/tests.py index 1e153fe77247..23980f860c31 100644 --- a/tests/gis_tests/inspectapp/tests.py +++ b/tests/gis_tests/inspectapp/tests.py @@ -133,12 +133,20 @@ def test_time_field(self): )) # The ordering of model fields might vary depending on several factors (version of GDAL, etc.) - self.assertIn(' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)', model_def) + if connection.vendor == 'sqlite': + # SpatiaLite introspection is somewhat lacking (#29461). + self.assertIn(' f_decimal = models.CharField(max_length=0)', model_def) + else: + self.assertIn(' f_decimal = models.DecimalField(max_digits=0, decimal_places=0)', model_def) self.assertIn(' f_int = models.IntegerField()', model_def) self.assertIn(' f_datetime = models.DateTimeField()', model_def) self.assertIn(' f_time = models.TimeField()', model_def) - self.assertIn(' f_float = models.FloatField()', model_def) - self.assertIn(' f_char = models.CharField(max_length=10)', model_def) + if connection.vendor == 'sqlite': + self.assertIn(' f_float = models.CharField(max_length=0)', model_def) + else: + self.assertIn(' f_float = models.FloatField()', model_def) + max_length = 0 if connection.vendor == 'sqlite' else 10 + self.assertIn(' f_char = models.CharField(max_length=%s)' % max_length, model_def) self.assertIn(' f_date = models.DateField()', model_def) # Some backends may have srid=-1 From 56c5c1599a884f6d985c68c54d106db50381e02e Mon Sep 17 00:00:00 2001 From: Adam Donaghy Date: Thu, 3 May 2018 23:41:04 +1000 Subject: [PATCH 279/389] [1.11.x] Fixed #28462 -- Decreased memory usage with ModelAdmin.list_editable. Regression in 917cc288a38f3c114a5440f0749b7e5e1086eb36. Backport of b18650a2634890aa758abae2f33875daa13a9ba3 from master --- AUTHORS | 1 + django/contrib/admin/options.py | 25 ++++++++- docs/releases/1.11.14.txt | 3 ++ tests/admin_changelist/models.py | 3 ++ tests/admin_changelist/tests.py | 87 ++++++++++++++++++++++++++++++-- 5 files changed, 115 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index e29c17953fd0..82341c3201c7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,6 +8,7 @@ answer newbie questions, and generally made Django that much better: Aaron Cannon Aaron Swartz Aaron T. Myers + Adam Donaghy Adam Johnson Adam Malinowski Adam Vandenberg diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 0a2c53b3c6f2..a479cb200860 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -3,6 +3,7 @@ import copy import json import operator +import re from collections import OrderedDict from functools import partial, reduce, update_wrapper @@ -1510,6 +1511,27 @@ def add_view(self, request, form_url='', extra_context=None): def change_view(self, request, object_id, form_url='', extra_context=None): return self.changeform_view(request, object_id, form_url, extra_context) + def _get_edited_object_pks(self, request, prefix): + """Return POST data values of list_editable primary keys.""" + pk_pattern = re.compile(r'{}-\d+-{}$'.format(prefix, self.model._meta.pk.name)) + return [value for key, value in request.POST.items() if pk_pattern.match(key)] + + def _get_list_editable_queryset(self, request, prefix): + """ + Based on POST data, return a queryset of the objects that were edited + via list_editable. + """ + object_pks = self._get_edited_object_pks(request, prefix) + queryset = self.get_queryset(request) + validate = queryset.model._meta.pk.to_python + try: + for pk in object_pks: + validate(pk) + except ValidationError: + # Disable the optimization if the POST data was tampered with. + return queryset + return queryset.filter(pk__in=object_pks) + @csrf_protect_m def changelist_view(self, request, extra_context=None): """ @@ -1601,7 +1623,8 @@ def changelist_view(self, request, extra_context=None): # Handle POSTed bulk-edit data. if request.method == 'POST' and cl.list_editable and '_save' in request.POST: FormSet = self.get_changelist_formset(request) - formset = cl.formset = FormSet(request.POST, request.FILES, queryset=self.get_queryset(request)) + modified_objects = self._get_list_editable_queryset(request, FormSet.get_default_prefix()) + formset = cl.formset = FormSet(request.POST, request.FILES, queryset=modified_objects) if formset.is_valid(): changecount = 0 for form in formset.forms: diff --git a/docs/releases/1.11.14.txt b/docs/releases/1.11.14.txt index 06fe74464a52..c433673111f7 100644 --- a/docs/releases/1.11.14.txt +++ b/docs/releases/1.11.14.txt @@ -11,3 +11,6 @@ Bugfixes * Fixed ``WKBWriter.write()`` and ``write_hex()`` for empty polygons on GEOS 3.6.1+ (:ticket:`29460`). + +* Fixed a regression in Django 1.10 that could result in large memory usage + when making edits using ``ModelAdmin.list_editable`` (:ticket:`28462`). diff --git a/tests/admin_changelist/models.py b/tests/admin_changelist/models.py index fb5f03cbd611..c64b27219984 100644 --- a/tests/admin_changelist/models.py +++ b/tests/admin_changelist/models.py @@ -1,3 +1,5 @@ +import uuid + from django.db import models from django.utils.encoding import python_2_unicode_compatible @@ -75,6 +77,7 @@ class Invitation(models.Model): class Swallow(models.Model): + uuid = models.UUIDField(primary_key=True, default=uuid.uuid4) origin = models.CharField(max_length=255) load = models.FloatField() speed = models.FloatField() diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index 651bcdf475b5..16570125c349 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -10,9 +10,11 @@ from django.contrib.admin.views.main import ALL_VAR, SEARCH_VAR, ChangeList from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType +from django.db import connection from django.template import Context, Template from django.test import TestCase, ignore_warnings, override_settings from django.test.client import RequestFactory +from django.test.utils import CaptureQueriesContext from django.urls import reverse from django.utils import formats, six from django.utils.deprecation import RemovedInDjango20Warning @@ -669,9 +671,9 @@ def test_multiuser_edit(self): 'form-INITIAL_FORMS': '3', 'form-MIN_NUM_FORMS': '0', 'form-MAX_NUM_FORMS': '1000', - 'form-0-id': str(d.pk), - 'form-1-id': str(c.pk), - 'form-2-id': str(a.pk), + 'form-0-uuid': str(d.pk), + 'form-1-uuid': str(c.pk), + 'form-2-uuid': str(a.pk), 'form-0-load': '9.0', 'form-0-speed': '9.0', 'form-1-load': '5.0', @@ -701,6 +703,85 @@ def test_multiuser_edit(self): # No new swallows were created. self.assertEqual(len(Swallow.objects.all()), 4) + def test_get_edited_object_ids(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + b = Swallow.objects.create(origin='Swallow B', load=2, speed=2) + c = Swallow.objects.create(origin='Swallow C', load=5, speed=5) + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + data = { + 'form-TOTAL_FORMS': '3', + 'form-INITIAL_FORMS': '3', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-1-uuid': str(b.pk), + 'form-2-uuid': str(c.pk), + 'form-0-load': '9.0', + 'form-0-speed': '9.0', + 'form-1-load': '5.0', + 'form-1-speed': '5.0', + 'form-2-load': '5.0', + 'form-2-speed': '4.0', + '_save': 'Save', + } + request = self.factory.post(changelist_url, data=data) + pks = m._get_edited_object_pks(request, prefix='form') + self.assertEqual(sorted(pks), sorted([str(a.pk), str(b.pk), str(c.pk)])) + + def test_get_list_editable_queryset(self): + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '2', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-0-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + m = SwallowAdmin(Swallow, custom_site) + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form') + self.assertEqual(queryset.count(), 1) + data['form-0-uuid'] = 'INVALD_PRIMARY_KEY' + # The unfiltered queryset is returned if there's invalid data. + request = self.factory.post(changelist_url, data=data) + queryset = m._get_list_editable_queryset(request, prefix='form') + self.assertEqual(queryset.count(), 2) + + def test_changelist_view_list_editable_changed_objects_uses_filter(self): + """list_editable edits use a filtered queryset to limit memory usage.""" + a = Swallow.objects.create(origin='Swallow A', load=4, speed=1) + b = Swallow.objects.create(origin='Swallow B', load=2, speed=2) + data = { + 'form-TOTAL_FORMS': '2', + 'form-INITIAL_FORMS': '2', + 'form-MIN_NUM_FORMS': '0', + 'form-MAX_NUM_FORMS': '1000', + 'form-0-uuid': str(a.pk), + 'form-0-load': '10', + 'form-1-uuid': str(b.pk), + 'form-1-load': '10', + '_save': 'Save', + } + superuser = self._create_superuser('superuser') + self.client.force_login(superuser) + changelist_url = reverse('admin:admin_changelist_swallow_changelist') + with CaptureQueriesContext(connection) as context: + response = self.client.post(changelist_url, data=data) + self.assertEqual(response.status_code, 200) + self.assertIn('WHERE', context.captured_queries[4]['sql']) + self.assertIn('IN', context.captured_queries[4]['sql']) + # Check only the first few characters since the UUID may have dashes. + self.assertIn(str(a.pk)[:8], context.captured_queries[4]['sql']) + def test_deterministic_order_for_unordered_model(self): """ The primary key is used in the ordering of the changelist's results to From d46fb4edee9c44a0804037858cb37ba794cc936c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 27 Jun 2018 13:55:09 -0400 Subject: [PATCH 280/389] [1.11.x] Fixed location of a few doc labels. Backport of 1229687a0a261d05a72e6f189c1a9b0069b302e5 from master --- docs/topics/http/file-uploads.txt | 4 ++-- docs/topics/testing/tools.txt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/topics/http/file-uploads.txt b/docs/topics/http/file-uploads.txt index 75ac1de45292..46ebf2e52ad9 100644 --- a/docs/topics/http/file-uploads.txt +++ b/docs/topics/http/file-uploads.txt @@ -190,8 +190,6 @@ data on the fly, render progress bars, and even send data to another storage location directly without storing it locally. See :ref:`custom_upload_handlers` for details on how you can customize or completely replace upload behavior. -.. _modifying_upload_handlers_on_the_fly: - Where uploaded data is stored ----------------------------- @@ -216,6 +214,8 @@ Changing upload handler behavior There are a few settings which control Django's file upload behavior. See :ref:`File Upload Settings ` for details. +.. _modifying_upload_handlers_on_the_fly: + Modifying upload handlers on the fly ------------------------------------ diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 1a2e9a847a2d..859a5f42061f 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1102,8 +1102,6 @@ tests can't rely upon the fact that your views will be available at a particular URL. Decorate your test class or test method with ``@override_settings(ROOT_URLCONF=...)`` for URLconf configuration. -.. _emptying-test-outbox: - Multi-database support ---------------------- @@ -1318,6 +1316,8 @@ LOCALE_PATHS, LANGUAGE_CODE Default translation and loaded translations MEDIA_ROOT, DEFAULT_FILE_STORAGE Default file storage ================================ ======================== +.. _emptying-test-outbox: + Emptying the test outbox ------------------------ From 63a6aa2039ae8caefa3fdb860de3ac1fce4c6432 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Jul 2018 10:12:20 +0200 Subject: [PATCH 281/389] [1.11.x] Added release date for 1.11.14. Backport of 65df375c40dfe591b258f36709123abc6957fbd7 from master --- docs/releases/1.11.14.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.14.txt b/docs/releases/1.11.14.txt index c433673111f7..fee0e9ff69f6 100644 --- a/docs/releases/1.11.14.txt +++ b/docs/releases/1.11.14.txt @@ -2,7 +2,7 @@ Django 1.11.14 release notes ============================ -*Expected July 2, 2018* +*July 2, 2018* Django 1.11.14 fixes several bugs in 1.11.13. From 32009eecc2e2591c97e37be586fa37c836673221 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Jul 2018 10:17:44 +0200 Subject: [PATCH 282/389] [1.11.x] Bumped version for 1.11.14 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index abf90175ec26..9f26a4583f5e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 14, 'alpha', 0) +VERSION = (1, 11, 14, 'final', 0) __version__ = get_version(VERSION) From bce29f10a2ee991e70133b71e0de2a6cf4260f0f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Jul 2018 11:11:33 +0200 Subject: [PATCH 283/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 9f26a4583f5e..eb45a75d03e8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 14, 'final', 0) +VERSION = (1, 11, 15, 'alpha', 0) __version__ = get_version(VERSION) From 4fd1f6702aaa9ca9b9918dbdb79546557a00d331 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 23 Jul 2018 10:42:40 -0400 Subject: [PATCH 284/389] [1.11.x] Added stub release notes for security release. --- docs/releases/1.11.15.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/1.11.15.txt diff --git a/docs/releases/1.11.15.txt b/docs/releases/1.11.15.txt new file mode 100644 index 000000000000..397681d52e42 --- /dev/null +++ b/docs/releases/1.11.15.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.15 release notes +============================ + +*August 1, 2018* + +Django 1.11.15 fixes a security issue in 1.11.14. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6347af1a5727..19201a632d31 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.15 1.11.14 1.11.13 1.11.12 From d6eaee092709aad477a9894598496c6deec532ff Mon Sep 17 00:00:00 2001 From: Andreas Hug Date: Tue, 24 Jul 2018 16:18:17 -0400 Subject: [PATCH 285/389] [1.11.x] Fixed CVE-2018-14574 -- Fixed open redirect possibility in CommonMiddleware. --- django/middleware/common.py | 3 +++ django/urls/resolvers.py | 8 ++++---- django/utils/http.py | 11 +++++++++++ docs/releases/1.11.15.txt | 13 +++++++++++++ tests/middleware/tests.py | 19 +++++++++++++++++++ tests/middleware/urls.py | 2 ++ tests/utils_tests/test_http.py | 10 ++++++++++ 7 files changed, 62 insertions(+), 4 deletions(-) diff --git a/django/middleware/common.py b/django/middleware/common.py index d18d23fa4353..fff46ba552eb 100644 --- a/django/middleware/common.py +++ b/django/middleware/common.py @@ -11,6 +11,7 @@ ) from django.utils.deprecation import MiddlewareMixin, RemovedInDjango21Warning from django.utils.encoding import force_text +from django.utils.http import escape_leading_slashes from django.utils.six.moves.urllib.parse import urlparse @@ -90,6 +91,8 @@ def get_full_path_with_slash(self, request): POST, PUT, or PATCH. """ new_path = request.get_full_path(force_append_slash=True) + # Prevent construction of scheme relative urls. + new_path = escape_leading_slashes(new_path) if settings.DEBUG and request.method in ('POST', 'PUT', 'PATCH'): raise RuntimeError( "You called this URL via %(method)s, but the URL doesn't end " diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 1de59a8763cc..25e9ae82766f 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -20,7 +20,9 @@ from django.utils.datastructures import MultiValueDict from django.utils.encoding import force_str, force_text from django.utils.functional import cached_property -from django.utils.http import RFC3986_SUBDELIMS, urlquote +from django.utils.http import ( + RFC3986_SUBDELIMS, escape_leading_slashes, urlquote, +) from django.utils.regex_helper import normalize from django.utils.translation import get_language @@ -465,9 +467,7 @@ def _reverse_with_prefix(self, lookup_view, _prefix, *args, **kwargs): # safe characters from `pchar` definition of RFC 3986 url = urlquote(candidate_pat % candidate_subs, safe=RFC3986_SUBDELIMS + str('/~:@')) # Don't allow construction of scheme relative urls. - if url.startswith('//'): - url = '/%%2F%s' % url[2:] - return url + return escape_leading_slashes(url) # lookup_view can be URL name or callable, but callables are not # friendly in error messages. m = getattr(lookup_view, '__module__', None) diff --git a/django/utils/http.py b/django/utils/http.py index 1fbc11b6fbac..644d4d09fd18 100644 --- a/django/utils/http.py +++ b/django/utils/http.py @@ -466,3 +466,14 @@ def limited_parse_qsl(qs, keep_blank_values=False, encoding='utf-8', value = unquote(nv[1].replace(b'+', b' ')) r.append((name, value)) return r + + +def escape_leading_slashes(url): + """ + If redirecting to an absolute path (two leading slashes), a slash must be + escaped to prevent browsers from handling the path as schemaless and + redirecting to another host. + """ + if url.startswith('//'): + url = '/%2F{}'.format(url[2:]) + return url diff --git a/docs/releases/1.11.15.txt b/docs/releases/1.11.15.txt index 397681d52e42..fca551e77294 100644 --- a/docs/releases/1.11.15.txt +++ b/docs/releases/1.11.15.txt @@ -5,3 +5,16 @@ Django 1.11.15 release notes *August 1, 2018* Django 1.11.15 fixes a security issue in 1.11.14. + +CVE-2018-14574: Open redirect possibility in ``CommonMiddleware`` +================================================================= + +If the :class:`~django.middleware.common.CommonMiddleware` and the +:setting:`APPEND_SLASH` setting are both enabled, and if the project has a +URL pattern that accepts any path ending in a slash (many content management +systems have such a pattern), then a request to a maliciously crafted URL of +that site could lead to a redirect to another site, enabling phishing and other +attacks. + +``CommonMiddleware`` now escapes leading slashes to prevent redirects to other +domains. diff --git a/tests/middleware/tests.py b/tests/middleware/tests.py index 1d473ef558cb..d9d701f22ac1 100644 --- a/tests/middleware/tests.py +++ b/tests/middleware/tests.py @@ -137,6 +137,25 @@ def test_append_slash_quoted(self): self.assertEqual(r.status_code, 301) self.assertEqual(r.url, '/needsquoting%23/') + @override_settings(APPEND_SLASH=True) + def test_append_slash_leading_slashes(self): + """ + Paths starting with two slashes are escaped to prevent open redirects. + If there's a URL pattern that allows paths to start with two slashes, a + request with path //evil.com must not redirect to //evil.com/ (appended + slash) which is a schemaless absolute URL. The browser would navigate + to evil.com/. + """ + # Use 4 slashes because of RequestFactory behavior. + request = self.rf.get('////evil.com/security') + response = HttpResponseNotFound() + r = CommonMiddleware().process_request(request) + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, '/%2Fevil.com/security/') + r = CommonMiddleware().process_response(request, response) + self.assertEqual(r.status_code, 301) + self.assertEqual(r.url, '/%2Fevil.com/security/') + @override_settings(APPEND_SLASH=False, PREPEND_WWW=True) def test_prepend_www(self): request = self.rf.get('/path/') diff --git a/tests/middleware/urls.py b/tests/middleware/urls.py index 8c6621d059ca..d623e7d6af8e 100644 --- a/tests/middleware/urls.py +++ b/tests/middleware/urls.py @@ -6,4 +6,6 @@ url(r'^noslash$', views.empty_view), url(r'^slash/$', views.empty_view), url(r'^needsquoting#/$', views.empty_view), + # Accepts paths with two leading slashes. + url(r'^(.+)/security/$', views.empty_view), ] diff --git a/tests/utils_tests/test_http.py b/tests/utils_tests/test_http.py index b435e33e44d1..d339e8a79cbd 100644 --- a/tests/utils_tests/test_http.py +++ b/tests/utils_tests/test_http.py @@ -248,3 +248,13 @@ def test_parsing_rfc850(self): def test_parsing_asctime(self): parsed = http.parse_http_date('Sun Nov 6 08:49:37 1994') self.assertEqual(datetime.utcfromtimestamp(parsed), datetime(1994, 11, 6, 8, 49, 37)) + + +class EscapeLeadingSlashesTests(unittest.TestCase): + def test(self): + tests = ( + ('//example.com', '/%2Fexample.com'), + ('//', '/%2F'), + ) + for url, expected in tests: + self.assertEqual(http.escape_leading_slashes(url), expected) From 6010da2fbda5eee76b6ec586112561dd26b650e8 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Aug 2018 09:40:26 -0400 Subject: [PATCH 286/389] [1.11.x] Bumped version for 1.11.15 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index eb45a75d03e8..1cb2ca67b820 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 15, 'alpha', 0) +VERSION = (1, 11, 15, 'final', 0) __version__ = get_version(VERSION) From 08cbca3aebf50fb4bd30159c8001e1f918d1deda Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Aug 2018 10:53:21 -0400 Subject: [PATCH 287/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 1cb2ca67b820..d346e1cc7de6 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 15, 'final', 0) +VERSION = (1, 11, 16, 'alpha', 0) __version__ = get_version(VERSION) From 98c77c5a700eed4bd314a40ab70904db0c0adac6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 1 Aug 2018 10:51:24 -0400 Subject: [PATCH 288/389] [1.11.x] Added CVE-2018-14574 to the security release archive. Backport of 0006538e53bf11d1de26801b13b78807354de2c8 from master --- docs/releases/security.txt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 47aef2bb240e..f74ec87c7e3c 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -863,7 +863,7 @@ March 6, 2018 - :cve:`2018-7536` Denial-of-service possibility in ``urlize`` and ``urlizetrunc`` template filters. `Full description -`_ +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -877,7 +877,7 @@ March 6, 2018 - :cve:`2018-7537` Denial-of-service possibility in ``truncatechars_html`` and ``truncatewords_html`` template filters. `Full description -`_ +`__ Versions affected ~~~~~~~~~~~~~~~~~ @@ -885,3 +885,16 @@ Versions affected * Django 2.0 `(patch) `__ * Django 1.11 `(patch) `__ * Django 1.8 `(patch) `__ + +August 1, 2018 - :cve:`2018-14574` +---------------------------------- + +Open redirect possibility in ``CommonMiddleware``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.1 `(patch) `__ +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ From 2668418d99b42599536d353705456cf5db718d48 Mon Sep 17 00:00:00 2001 From: Michael Sanders Date: Wed, 1 Aug 2018 10:52:28 +0100 Subject: [PATCH 289/389] [1.11.x] Fixed #29499 -- Fixed race condition in QuerySet.update_or_create(). A race condition happened when the object didn't already exist and another process/thread created the object before update_or_create() did and then attempted to update the object, also before update_or_create() saved the object. The update by the other process/thread could be lost. Backport of 271542dad1686c438f658aa6220982495db09797 from master --- AUTHORS | 1 + django/db/models/query.py | 9 ++++-- docs/releases/1.11.16.txt | 13 ++++++++ docs/releases/index.txt | 1 + tests/get_or_create/models.py | 2 +- tests/get_or_create/tests.py | 58 +++++++++++++++++++++++++++++++++++ 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 docs/releases/1.11.16.txt diff --git a/AUTHORS b/AUTHORS index 82341c3201c7..4109686bfb02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -543,6 +543,7 @@ answer newbie questions, and generally made Django that much better: michael.mcewan@gmail.com Michael Placentra II Michael Radziej + Michael Sanders Michael Schwarz Michael Thornhill Michal Chruszcz diff --git a/django/db/models/query.py b/django/db/models/query.py index 379edf38826c..cbf35610db18 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -479,7 +479,9 @@ def update_or_create(self, defaults=None, **kwargs): try: obj = self.select_for_update().get(**lookup) except self.model.DoesNotExist: - obj, created = self._create_object_from_params(lookup, params) + # Lock the row so that a concurrent update is blocked until + # after update_or_create() has performed its save. + obj, created = self._create_object_from_params(lookup, params, lock=True) if created: return obj, created for k, v in six.iteritems(defaults): @@ -487,7 +489,7 @@ def update_or_create(self, defaults=None, **kwargs): obj.save(using=self.db) return obj, False - def _create_object_from_params(self, lookup, params): + def _create_object_from_params(self, lookup, params, lock=False): """ Tries to create an object using passed params. Used by get_or_create and update_or_create @@ -500,7 +502,8 @@ def _create_object_from_params(self, lookup, params): except IntegrityError: exc_info = sys.exc_info() try: - return self.get(**lookup), False + qs = self.select_for_update() if lock else self + return qs.get(**lookup), False except self.model.DoesNotExist: pass six.reraise(*exc_info) diff --git a/docs/releases/1.11.16.txt b/docs/releases/1.11.16.txt new file mode 100644 index 000000000000..04335f943944 --- /dev/null +++ b/docs/releases/1.11.16.txt @@ -0,0 +1,13 @@ +============================ +Django 1.11.16 release notes +============================ + +*Expected September 1, 2018* + +Django 1.11.16 fixes a data loss bug in 1.11.15. + +Bugfixes +======== + +* Fixed a race condition in ``QuerySet.update_or_create()`` that could result + in data loss (:ticket:`29499`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 19201a632d31..344420a01511 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.16 1.11.15 1.11.14 1.11.13 diff --git a/tests/get_or_create/models.py b/tests/get_or_create/models.py index b5fe534e3a32..8103a451c4b3 100644 --- a/tests/get_or_create/models.py +++ b/tests/get_or_create/models.py @@ -6,7 +6,7 @@ @python_2_unicode_compatible class Person(models.Model): - first_name = models.CharField(max_length=100) + first_name = models.CharField(max_length=100, unique=True) last_name = models.CharField(max_length=100) birthday = models.DateField() defaults = models.TextField() diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index 0c8efb6f4e2c..ad6030dda218 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -509,6 +509,64 @@ def lock_wait(): self.assertGreater(after_update - before_start, timedelta(seconds=0.5)) self.assertEqual(updated_person.last_name, 'NotLennon') + @skipUnlessDBFeature('has_select_for_update') + @skipUnlessDBFeature('supports_transactions') + def test_creation_in_transaction(self): + """ + Objects are selected and updated in a transaction to avoid race + conditions. This test checks the behavior of update_or_create() when + the object doesn't already exist, but another thread creates the + object before update_or_create() does and then attempts to update the + object, also before update_or_create(). It forces update_or_create() to + hold the lock in another thread for a relatively long time so that it + can update while it holds the lock. The updated field isn't a field in + 'defaults', so update_or_create() shouldn't have an effect on it. + """ + lock_status = {'lock_count': 0} + + def birthday_sleep(): + lock_status['lock_count'] += 1 + time.sleep(0.5) + return date(1940, 10, 10) + + def update_birthday_slowly(): + try: + Person.objects.update_or_create(first_name='John', defaults={'birthday': birthday_sleep}) + finally: + # Avoid leaking connection for Oracle + connection.close() + + def lock_wait(expected_lock_count): + # timeout after ~0.5 seconds + for i in range(20): + time.sleep(0.025) + if lock_status['lock_count'] == expected_lock_count: + return True + self.skipTest('Database took too long to lock the row') + + # update_or_create in a separate thread. + t = Thread(target=update_birthday_slowly) + before_start = datetime.now() + t.start() + lock_wait(1) + # Create object *after* initial attempt by update_or_create to get obj + # but before creation attempt. + Person.objects.create(first_name='John', last_name='Lennon', birthday=date(1940, 10, 9)) + lock_wait(2) + # At this point, the thread is pausing for 0.5 seconds, so now attempt + # to modify object before update_or_create() calls save(). This should + # be blocked until after the save(). + Person.objects.filter(first_name='John').update(last_name='NotLennon') + after_update = datetime.now() + # Wait for thread to finish + t.join() + # Check call to update_or_create() succeeded and the subsequent + # (blocked) call to update(). + updated_person = Person.objects.get(first_name='John') + self.assertEqual(updated_person.birthday, date(1940, 10, 10)) # set by update_or_create() + self.assertEqual(updated_person.last_name, 'NotLennon') # set by update() + self.assertGreater(after_update - before_start, timedelta(seconds=1)) + class InvalidCreateArgumentsTests(SimpleTestCase): msg = "Invalid field name(s) for model Thing: 'nonexistent'." From 8a0b9051878d1f604ea40e9441be1bc86ca97cf2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 3 Aug 2018 12:13:06 -0400 Subject: [PATCH 290/389] [1.11.x] Refs #29499 -- Skipped QuerySet.update_or_create() test that fails on MySQL. --- tests/get_or_create/tests.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/get_or_create/tests.py b/tests/get_or_create/tests.py index ad6030dda218..76db80fe90c0 100644 --- a/tests/get_or_create/tests.py +++ b/tests/get_or_create/tests.py @@ -4,6 +4,7 @@ import traceback from datetime import date, datetime, timedelta from threading import Thread +from unittest import skipIf from django.core.exceptions import FieldError from django.db import DatabaseError, IntegrityError, connection @@ -509,6 +510,7 @@ def lock_wait(): self.assertGreater(after_update - before_start, timedelta(seconds=0.5)) self.assertEqual(updated_person.last_name, 'NotLennon') + @skipIf(connection.vendor == 'mysql', "MySQL's default isolation level is repeatable read.") @skipUnlessDBFeature('has_select_for_update') @skipUnlessDBFeature('supports_transactions') def test_creation_in_transaction(self): From ceae3069ec2f0fd9f53ae901a55b4f9c985a4e78 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 4 Aug 2018 10:24:22 -0400 Subject: [PATCH 291/389] [1.11.x] Fixed #28540 -- Doc'd a change to file upload permissions in Django 1.11. Behavior changed in f734e2d4b2fc4391a4d097b80357724815c1d414 (refs #27334). Backport of 89d4d412404d31ef34ae3170c0c056eff55b2a17 from master --- docs/releases/1.11.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 7a01cba20576..4debdb484215 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -774,6 +774,13 @@ Miscellaneous :data:`~django.core.validators.validate_image_file_extension` validator. See the note in :meth:`.Client.post`. +* :class:`~django.db.models.FileField` now moves rather than copies the file + it receives. With the default file upload settings, files larger than + :setting:`FILE_UPLOAD_MAX_MEMORY_SIZE` now have the same permissions as + temporary files (often ``0o600``) rather than the system's standard umask + (often ``0o6644``). Set the :setting:`FILE_UPLOAD_PERMISSIONS` if you need + the same permission regardless of file size. + .. _deprecated-features-1.11: Features deprecated in 1.11 From 006ca978b9c754b29024b39a11df2e7728f7f317 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 18 Sep 2018 10:30:45 +0200 Subject: [PATCH 292/389] [1.11.x] Refs #29759 -- Doc'd that cx_Oracle < 7 is required. Backport of 7085247e2fd1ad8b08103173a23ca730784765a3 from stable/2.0.x --- docs/ref/databases.txt | 2 +- tests/requirements/oracle.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 76ead1024e69..f9372d030cb3 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -798,7 +798,7 @@ Oracle notes ============ Django supports `Oracle Database Server`_ versions 11.2 and higher. Version -5.2 or higher of the `cx_Oracle`_ Python driver is required. +5.2 through 6.4.1 of the `cx_Oracle`_ Python driver are supported. .. _`Oracle Database Server`: https://www.oracle.com/ .. _`cx_Oracle`: https://oracle.github.io/python-cx_Oracle/ diff --git a/tests/requirements/oracle.txt b/tests/requirements/oracle.txt index ae5b7349cde3..9a279612fde5 100644 --- a/tests/requirements/oracle.txt +++ b/tests/requirements/oracle.txt @@ -1 +1 @@ -cx_oracle +cx_oracle < 7 From c1e9e2a525718721da914b7740e67bc6fa2d0c8d Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Oct 2018 09:33:28 +0200 Subject: [PATCH 293/389] [1.11.x] Added release date for 1.11.16. Backport of d37ed40048b749c75f7f54ef8b96d8e738f10719 from master --- docs/releases/1.11.16.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.16.txt b/docs/releases/1.11.16.txt index 04335f943944..161d0b2c669e 100644 --- a/docs/releases/1.11.16.txt +++ b/docs/releases/1.11.16.txt @@ -2,7 +2,7 @@ Django 1.11.16 release notes ============================ -*Expected September 1, 2018* +*October 1, 2018* Django 1.11.16 fixes a data loss bug in 1.11.15. From 3d0344dc402e11c57b65ecf57481e681f790db2e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Oct 2018 09:38:51 +0200 Subject: [PATCH 294/389] [1.11.x] Bumped version for 1.11.16 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index d346e1cc7de6..d558b15d0c63 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 16, 'alpha', 0) +VERSION = (1, 11, 16, 'final', 0) __version__ = get_version(VERSION) From abd0bafb49d11c1d18ad641bab7c123d2fa2f052 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Oct 2018 11:38:36 +0200 Subject: [PATCH 295/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index d558b15d0c63..2b7a0052d83e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 16, 'final', 0) +VERSION = (1, 11, 17, 'alpha', 0) __version__ = get_version(VERSION) From 21ea15d1206425c8735460c9225d5f5855eea4ba Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Oct 2018 11:44:36 +0200 Subject: [PATCH 296/389] [1.11.x] Added stub release notes for 1.11.17 release. Backport of 7040e638b960c122cd71eccac2b1bf2fe8d0f5da from master --- docs/releases/1.11.17.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.17.txt diff --git a/docs/releases/1.11.17.txt b/docs/releases/1.11.17.txt new file mode 100644 index 000000000000..8950f6adb888 --- /dev/null +++ b/docs/releases/1.11.17.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.17 release notes +============================ + +*Expected November 1, 2018* + +Django 1.11.17 fixes several bugs in 1.11.16. + +Bugfixes +======== + +* ... \ No newline at end of file diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 344420a01511..5bd5ba8b8105 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.17 1.11.16 1.11.15 1.11.14 From bd197d3f927f6d17fc4738366126e06c6a95f366 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 1 Oct 2018 11:54:31 +0200 Subject: [PATCH 297/389] [1.11.x] Added CVE-2018-16984 to the security release archive. Backport of 0b3b7c4b0ab2567cfe5df3ac19563d4a59276cb1 and 92ccc3917058b1025b2d657ffdf3c21eb8009f7b from master --- docs/releases/security.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index f74ec87c7e3c..9ddef5054792 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -898,3 +898,14 @@ Versions affected * Django 2.1 `(patch) `__ * Django 2.0 `(patch) `__ * Django 1.11 `(patch) `__ + +October 1, 2018 - :cve:`2018-16984` +----------------------------------- + +Password hash disclosure to "view only" admin users. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.1 `(patch) `__ From b5702dac7fcd903ba41b320b2ba7cd2266428e7a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 24 Oct 2018 09:13:13 +0200 Subject: [PATCH 298/389] [1.11.x] Ignored flake8 W504 warnings. W504 is mutually exclusive with W503 that we follow. Backport of 58d1e9aa8ab505912389e7cd019a6f21785ad4bf from master. --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 9d336981dda5..e34124c4c284 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,7 +4,7 @@ install-script = scripts/rpm-install.sh [flake8] exclude = build,.git,.tox,./django/utils/lru_cache.py,./django/utils/six.py,./django/conf/app_template/*,./django/dispatch/weakref_backports.py,./tests/.env,./xmlrunner,tests/view_tests/tests/py3_test_debug.py,tests/template_tests/annotated_tag_function.py -ignore = W601 +ignore = W504,W601 max-line-length = 119 [isort] From 0ecc4f8d497679979f48ff18754494e8387a494d Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Wed, 24 Oct 2018 01:46:49 +0100 Subject: [PATCH 299/389] [1.11.x] Removed obsolete and flaky GeoIP tests. Backport of 8f90593e6f8197148c8f86e598bfef6792f3f4bf from master. --- tests/gis_tests/test_geoip2.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index 414d9e492b94..b15d0fc6eeb7 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -127,23 +127,10 @@ def test04_city(self, gethostbyname): geom = g.geos(query) self.assertIsInstance(geom, GEOSGeometry) - lon, lat = (-95.4010, 29.7079) - lat_lon = g.lat_lon(query) - lat_lon = (lat_lon[1], lat_lon[0]) - for tup in (geom.tuple, g.coords(query), g.lon_lat(query), lat_lon): - self.assertAlmostEqual(lon, tup[0], 4) - self.assertAlmostEqual(lat, tup[1], 4) - @mock.patch('socket.gethostbyname') - def test05_unicode_response(self, gethostbyname): - "GeoIP strings should be properly encoded (#16553)." - gethostbyname.return_value = '194.27.42.76' - g = GeoIP2() - d = g.city('nigde.edu.tr') - self.assertEqual('Niğde', d['city']) - d = g.country('200.26.205.1') - # Some databases have only unaccented countries - self.assertIn(d['country_name'], ('Curaçao', 'Curacao')) + for e1, e2 in (geom.tuple, g.coords(query), g.lon_lat(query), g.lat_lon(query)): + self.assertIsInstance(e1, float) + self.assertIsInstance(e2, float) def test06_ipv6_query(self): "GeoIP can lookup IPv6 addresses." From e75120c5a68cf61f2a6e384ef2d0fc03738059c6 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 16 Nov 2018 09:34:10 -0500 Subject: [PATCH 300/389] [1.11.x] Removed release date for 1.11.17. Backport of 97cec6f75d9d9b86892829f784e5e9dabfd1242a from master. --- docs/releases/1.11.17.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/1.11.17.txt b/docs/releases/1.11.17.txt index 8950f6adb888..52d86601a642 100644 --- a/docs/releases/1.11.17.txt +++ b/docs/releases/1.11.17.txt @@ -2,11 +2,11 @@ Django 1.11.17 release notes ============================ -*Expected November 1, 2018* +*Release date TBD* Django 1.11.17 fixes several bugs in 1.11.16. Bugfixes ======== -* ... \ No newline at end of file +* ... From 216398d1b12bc63a82580705ad9f9ed00b28ac4a Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 16 Nov 2018 15:10:33 +0100 Subject: [PATCH 301/389] [1.11.x] Fixed #29959 -- Cached GEOS version in WKBWriter class. Regression in f185d929fa1c0caad8c03fccde899b647d7248c6. Backport of e7e55059027ae2f644c852e0ba60dc9307b425e1 from master. --- django/contrib/gis/geos/prototypes/io.py | 5 +++-- docs/releases/1.11.17.txt | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/django/contrib/gis/geos/prototypes/io.py b/django/contrib/gis/geos/prototypes/io.py index 911aa490161b..befe929d8d05 100644 --- a/django/contrib/gis/geos/prototypes/io.py +++ b/django/contrib/gis/geos/prototypes/io.py @@ -216,6 +216,7 @@ class WKBWriter(IOBase): _constructor = wkb_writer_create ptr_type = WKB_WRITE_PTR destructor = wkb_writer_destroy + geos_version = geos_version_info() def __init__(self, dim=2): super(WKBWriter, self).__init__() @@ -238,7 +239,7 @@ def write(self, geom): from django.contrib.gis.geos import Polygon geom = self._handle_empty_point(geom) wkb = wkb_writer_write(self.ptr, geom.ptr, byref(c_size_t())) - if geos_version_info()['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: + if self.geos_version['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: # Fix GEOS output for empty polygon. # See https://trac.osgeo.org/geos/ticket/680. wkb = wkb[:-8] + b'\0' * 4 @@ -249,7 +250,7 @@ def write_hex(self, geom): from django.contrib.gis.geos.polygon import Polygon geom = self._handle_empty_point(geom) wkb = wkb_writer_write_hex(self.ptr, geom.ptr, byref(c_size_t())) - if geos_version_info()['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: + if self.geos_version['version'] < '3.6.1' and isinstance(geom, Polygon) and geom.empty: wkb = wkb[:-16] + b'0' * 8 return wkb diff --git a/docs/releases/1.11.17.txt b/docs/releases/1.11.17.txt index 52d86601a642..db201d0b9a18 100644 --- a/docs/releases/1.11.17.txt +++ b/docs/releases/1.11.17.txt @@ -9,4 +9,6 @@ Django 1.11.17 fixes several bugs in 1.11.16. Bugfixes ======== -* ... +* Prevented repetitive calls to ``geos_version_tuple()`` in the ``WKBWriter`` + class in an attempt to fix a random crash involving ``LooseVersion`` since + Django 1.11.14 (:ticket:`29959`). From 568c2f45ef531257a600c0e6ff8fe02525d7ab4e Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 17 Nov 2017 15:38:29 -0500 Subject: [PATCH 302/389] [1.11.x] Refs #28814 -- Fixed "SyntaxError: Generator expression must be parenthesized" on Python 3.7. Due to https://bugs.python.org/issue32012. Backport of 931c60c5216bd71bc11f489e00e063331cf21f40 from master --- django/contrib/admin/widgets.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 89e8adde3029..62da6265b6d0 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -148,9 +148,7 @@ def get_context(self, name, value, attrs): params = self.url_parameters() if params: - related_url += '?' + '&'.join( - '%s=%s' % (k, v) for k, v in params.items(), - ) + related_url += '?' + '&'.join('%s=%s' % (k, v) for k, v in params.items()) context['related_url'] = mark_safe(related_url) context['link_title'] = _('Lookup') # The JavaScript code looks for this class. From b9e248975f3190fee245b30313c2744bf836bb1c Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 17 Nov 2017 16:12:48 -0500 Subject: [PATCH 303/389] [1.11.x] Refs #28814 -- Fixed test_runner failure on Python 3.7. Due to https://bugs.python.org/issue30399. Backport of 9d1d3b2d2fe0bef995b024368088eeabbdf73629 from master --- tests/test_runner/test_parallel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_runner/test_parallel.py b/tests/test_runner/test_parallel.py index b888dc62afb6..7dca22e43ee4 100644 --- a/tests/test_runner/test_parallel.py +++ b/tests/test_runner/test_parallel.py @@ -1,3 +1,4 @@ +import sys import unittest from django.test import SimpleTestCase @@ -83,7 +84,8 @@ def test_add_failing_subtests(self): event = events[1] self.assertEqual(event[0], 'addSubTest') self.assertEqual(str(event[2]), 'dummy_test (test_runner.test_parallel.SampleFailingSubtest) (index=0)') - self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1',)") + trailing_comma = '' if sys.version_info >= (3, 7) else ',' + self.assertEqual(repr(event[3][1]), "AssertionError('0 != 1'%s)" % trailing_comma) event = events[2] - self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1',)") + self.assertEqual(repr(event[3][1]), "AssertionError('2 != 1'%s)" % trailing_comma) From 8deb0a8efd948855310c45064b109090596510e4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 5 Feb 2018 13:16:57 -0500 Subject: [PATCH 304/389] [1.11.x] Refs #28814 -- Fixed migrations crash with namespace packages on Python 3.7. Due to https://bugs.python.org/issue32303. Backport of 0f0a07ac278dc2be6da81e519188f77e2a2a00cf from master --- django/db/migrations/loader.py | 5 +++-- django/db/migrations/questioner.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/django/db/migrations/loader.py b/django/db/migrations/loader.py index bbbde07a9843..be39b09e26a2 100644 --- a/django/db/migrations/loader.py +++ b/django/db/migrations/loader.py @@ -89,8 +89,9 @@ def load_disk(self): continue raise else: - # PY3 will happily import empty dirs as namespaces. - if not hasattr(module, '__file__'): + # Empty directories are namespaces. + # getattr() needed on PY36 and older (replace w/attribute access). + if getattr(module, '__file__', None) is None: self.unmigrated_apps.add(app_config.label) continue # Module is not a package (e.g. migrations.py). diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index df08508a109f..6668e33fda60 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -46,7 +46,8 @@ def ask_initial(self, app_label): except ImportError: return self.defaults.get("ask_initial", False) else: - if hasattr(migrations_module, "__file__"): + # getattr() needed on PY36 and older (replace with attribute access). + if getattr(migrations_module, "__file__", None): filenames = os.listdir(os.path.dirname(migrations_module.__file__)) elif hasattr(migrations_module, "__path__"): if len(migrations_module.__path__) > 1: From c11a7b4907e58447daab4632a50f5e72d838715e Mon Sep 17 00:00:00 2001 From: Asif Saifuddin Auvi Date: Wed, 14 Nov 2018 18:44:52 +0100 Subject: [PATCH 305/389] [1.11.x] Refs #28814 -- Documented Python 3.7 compatibility. Backport of 2f7cd7f8ecb01d30c1dfdaefa1c1714db76d2553 from master --- docs/faq/install.txt | 4 ++-- docs/releases/1.11.17.txt | 3 ++- docs/releases/1.11.txt | 6 +++--- setup.py | 1 + tox.ini | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index 12482362d8a4..e556d3550c27 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -48,8 +48,8 @@ Django version Python versions ============== =============== 1.8 2.7, 3.2 (until the end of 2016), 3.3, 3.4, 3.5 1.9, 1.10 2.7, 3.4, 3.5 -1.11 2.7, 3.4, 3.5, 3.6 -2.0 3.4, 3.5, 3.6 +1.11 2.7, 3.4, 3.5, 3.6, 3.7 (added in 1.11.17) +2.0 3.4, 3.5, 3.6, 3.7 2.1 3.5, 3.6, 3.7 ============== =============== diff --git a/docs/releases/1.11.17.txt b/docs/releases/1.11.17.txt index db201d0b9a18..26bdfb0de01f 100644 --- a/docs/releases/1.11.17.txt +++ b/docs/releases/1.11.17.txt @@ -4,7 +4,8 @@ Django 1.11.17 release notes *Release date TBD* -Django 1.11.17 fixes several bugs in 1.11.16. +Django 1.11.17 fixes several bugs in 1.11.16 and adds compatibility with +Python 3.7. Bugfixes ======== diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 4debdb484215..377c402eb79a 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -22,9 +22,9 @@ for the previous LTS, Django 1.8, will end in April 2018. Python compatibility ==================== -Django 1.11 requires Python 2.7, 3.4, 3.5, or 3.6. Django 1.11 is the first -release to support Python 3.6. We **highly recommend** and only officially -support the latest release of each series. +Django 1.11 requires Python 2.7, 3.4, 3.5, 3.6, or 3.7 (as of 1.11.17). We +**highly recommend** and only officially support the latest release of each +series. The Django 1.11.x series is the last to support Python 2. The next major release, Django 2.0, will only support Python 3.4+. diff --git a/setup.py b/setup.py index db7d079549a9..e3cf07ee39d7 100644 --- a/setup.py +++ b/setup.py @@ -67,6 +67,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 'Topic :: Internet :: WWW/HTTP :: WSGI', diff --git a/tox.ini b/tox.ini index e9892a75a42b..af43e94382c5 100644 --- a/tox.ini +++ b/tox.ini @@ -25,7 +25,7 @@ setenv = PYTHONDONTWRITEBYTECODE=1 deps = py{2,27}: -rtests/requirements/py2.txt - py{3,34,35,36}: -rtests/requirements/py3.txt + py{3,34,35,36,37}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt oracle: -rtests/requirements/oracle.txt From 882935ef8f10dc6905e4455183908d283bf2ad01 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 10 Sep 2018 16:08:21 -0400 Subject: [PATCH 306/389] [1.11.x] Removed usage of deprecated sphinx APIs. Backport of cc4bb110d31f18d2931fd79d792d3ac09cce19e5 from master. --- docs/_ext/djangodocs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index c3ca88a61048..883c8ab9b9d6 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -10,6 +10,7 @@ from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.domains.std import Cmdoption +from sphinx.util import logging from sphinx.util.console import bold from sphinx.util.nodes import set_source_info @@ -18,6 +19,7 @@ except ImportError: # Sphinx 1.6+ from sphinx.writers.html import HTMLTranslator +logger = logging.getLogger(__name__) # RE for option descriptions without a '--' prefix simple_option_desc_re = re.compile( r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') @@ -44,7 +46,7 @@ def setup(app): rolename="lookup", indextemplate="pair: %s; field lookup type", ) - app.add_description_unit( + app.add_object_type( directivename="django-admin", rolename="djadmin", indextemplate="pair: %s; django-admin command", @@ -311,7 +313,7 @@ class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder): def finish(self): super(DjangoStandaloneHTMLBuilder, self).finish() - self.info(bold("writing templatebuiltins.js...")) + logger.info(bold("writing templatebuiltins.js...")) xrefs = self.env.domaindata["std"]["objects"] templatebuiltins = { "ttags": [ From b69c27ad8c83c02d0fb578e296c174fc626f8ef1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Dec 2018 15:14:58 +0100 Subject: [PATCH 307/389] [1.11.x] Added release date for 1.11.17. Backport of 950112548e61098f442d37a8ded4ef9f83ff8fda from master --- docs/releases/1.11.17.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.17.txt b/docs/releases/1.11.17.txt index 26bdfb0de01f..6035eca2e5a3 100644 --- a/docs/releases/1.11.17.txt +++ b/docs/releases/1.11.17.txt @@ -2,7 +2,7 @@ Django 1.11.17 release notes ============================ -*Release date TBD* +*December 3, 2018* Django 1.11.17 fixes several bugs in 1.11.16 and adds compatibility with Python 3.7. From 4f5f6f3e8c4edaaa2dc6a09a2a755cadf066d95c Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Dec 2018 15:36:33 +0100 Subject: [PATCH 308/389] [1.11.x] Bumped version for 1.11.17 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 2b7a0052d83e..a70527998f2a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 17, 'alpha', 0) +VERSION = (1, 11, 17, 'final', 0) __version__ = get_version(VERSION) From a5338b125288b087de8419d236464f902db187af Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Dec 2018 18:13:45 +0100 Subject: [PATCH 309/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index a70527998f2a..fea66ed04bbd 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 17, 'final', 0) +VERSION = (1, 11, 18, 'alpha', 0) __version__ = get_version(VERSION) From 2ea1e0e58d3e0afffda4308a45e6c96693f3a7c9 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 5 Dec 2018 15:48:24 -0500 Subject: [PATCH 310/389] [1.11.x] Refs #30013 -- Doc'd that mysqlclient 1.3.14 and later isn't supported. --- docs/ref/databases.txt | 3 +-- tests/requirements/mysql.txt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index f9372d030cb3..614570812858 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -381,8 +381,7 @@ Python 3. In order to use MySQLdb under Python 3, you'll have to install mysqlclient ~~~~~~~~~~~ -Django requires `mysqlclient`_ 1.3.3 or later. mysqlclient should mostly behave -the same as MySQLdb. +Django supports `mysqlclient`_ 1.3.3 through 1.3.13. MySQL Connector/Python ~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/requirements/mysql.txt b/tests/requirements/mysql.txt index cec08055cfe7..8abfd961fcb5 100644 --- a/tests/requirements/mysql.txt +++ b/tests/requirements/mysql.txt @@ -1,2 +1 @@ -# Due to a bug that will be fixed in mysqlclient 1.3.7. -mysqlclient >= 1.3.7 +mysqlclient >= 1.3.7, < 1.3.14 From 190aa594477a62a43ca78c41d2d0af47dd6ccbba Mon Sep 17 00:00:00 2001 From: CHI Cheng Date: Thu, 27 Dec 2018 20:48:37 +1100 Subject: [PATCH 311/389] [1.11.x] Fixed broken links to PyYAML page. Backport of b7dbd5ff68bb9d2235ca081c0bd0b8baa65f8c77 from master. --- docs/howto/initial-data.txt | 2 +- docs/topics/serialization.txt | 2 +- tests/timezones/tests.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/howto/initial-data.txt b/docs/howto/initial-data.txt index 5945073889fe..923157452b69 100644 --- a/docs/howto/initial-data.txt +++ b/docs/howto/initial-data.txt @@ -19,7 +19,7 @@ Or, you can write fixtures by hand; fixtures can be written as JSON, XML or YAML ` has more details about each of these supported :ref:`serialization formats `. -.. _PyYAML: http://www.pyyaml.org/ +.. _PyYAML: https://pyyaml.org/ As an example, though, here's what a fixture for a simple ``Person`` model might look like in JSON: diff --git a/docs/topics/serialization.txt b/docs/topics/serialization.txt index 5db5e656ecb0..f6d78b40f084 100644 --- a/docs/topics/serialization.txt +++ b/docs/topics/serialization.txt @@ -165,7 +165,7 @@ Identifier Information ========== ============================================================== .. _json: http://json.org/ -.. _PyYAML: http://www.pyyaml.org/ +.. _PyYAML: https://pyyaml.org/ XML --- diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index 8f9bd23241de..49247294f854 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -664,7 +664,7 @@ class SerializationTests(SimpleTestCase): # - JSON supports only milliseconds, microseconds will be truncated. # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes, # but when it loads this representation, it subtracts the offset and - # returns a naive datetime object in UTC (http://pyyaml.org/ticket/202). + # returns a naive datetime object in UTC. See ticket #18867. # Tests are adapted to take these quirks into account. def assert_python_contains_datetime(self, objects, dt): From b683bb0c9fec43706e4117ef0d690ab4758d8af0 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Tue, 1 Jan 2019 19:05:28 -0500 Subject: [PATCH 312/389] [1.11.x] Pinned Pillow != 5.4.0 in test requirements. There's a bug that causes a test failure in forms_tests: https://github.com/python-pillow/Pillow/pull/3501/files#r244651761. Backport of e4a714b259125423059b9f65f5e0ab70d78521ba from master. --- tests/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt index 6ee885e13c99..08a6b3a526b3 100644 --- a/tests/requirements/base.txt +++ b/tests/requirements/base.txt @@ -4,7 +4,7 @@ docutils geoip2 jinja2 >= 2.9.2 numpy -Pillow +Pillow != 5.4.0 PyYAML # pylibmc/libmemcached can't be built on Windows. pylibmc; sys.platform != 'win32' From 1cd00fcf52d089ef0fe03beabd05d59df8ea052a Mon Sep 17 00:00:00 2001 From: Tom Hacohen Date: Fri, 4 Jan 2019 02:21:55 +0000 Subject: [PATCH 313/389] [1.11.x] Fixed #30070, CVE-2019-3498 -- Fixed content spoofing possiblity in the default 404 page. Co-Authored-By: Tim Graham Backport of 1ecc0a395be721e987e8e9fdfadde952b6dee1c7 from master. --- django/views/defaults.py | 8 +++++--- docs/releases/1.11.18.txt | 18 ++++++++++++++++++ docs/releases/index.txt | 1 + tests/handlers/tests.py | 12 ++++++++---- 4 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 docs/releases/1.11.18.txt diff --git a/django/views/defaults.py b/django/views/defaults.py index 348837ed99d7..5ec9ac8e166c 100644 --- a/django/views/defaults.py +++ b/django/views/defaults.py @@ -2,6 +2,7 @@ from django.template import Context, Engine, TemplateDoesNotExist, loader from django.utils import six from django.utils.encoding import force_text +from django.utils.http import urlquote from django.views.decorators.csrf import requires_csrf_token ERROR_404_TEMPLATE_NAME = '404.html' @@ -21,7 +22,8 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): Templates: :template:`404.html` Context: request_path - The path of the requested URL (e.g., '/app/pages/bad_page/') + The path of the requested URL (e.g., '/app/pages/bad_page/'). It's + quoted to prevent a content injection attack. exception The message from the exception which triggered the 404 (if one was supplied), or the exception class name @@ -37,7 +39,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): if isinstance(message, six.text_type): exception_repr = message context = { - 'request_path': request.path, + 'request_path': urlquote(request.path), 'exception': exception_repr, } try: @@ -50,7 +52,7 @@ def page_not_found(request, exception, template_name=ERROR_404_TEMPLATE_NAME): raise template = Engine().from_string( '

      Not Found

      ' - '

      The requested URL {{ request_path }} was not found on this server.

      ') + '

      The requested resource was not found on this server.

      ') body = template.render(Context(context)) content_type = 'text/html' return http.HttpResponseNotFound(body, content_type=content_type) diff --git a/docs/releases/1.11.18.txt b/docs/releases/1.11.18.txt new file mode 100644 index 000000000000..82a229e6dd7b --- /dev/null +++ b/docs/releases/1.11.18.txt @@ -0,0 +1,18 @@ +============================ +Django 1.11.18 release notes +============================ + +*January 4, 2019* + +Django 1.11.18 fixes a security issue in 1.11.17. + +CVE-2019-3498: Content spoofing possibility in the default 404 page +------------------------------------------------------------------- + +An attacker could craft a malicious URL that could make spoofed content appear +on the default page generated by the ``django.views.defaults.page_not_found()`` +view. + +The URL path is no longer displayed in the default 404 template and the +``request_path`` context variable is now quoted to fix the issue for custom +templates that use the path. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 5bd5ba8b8105..719e5f416e18 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.18 1.11.17 1.11.16 1.11.15 diff --git a/tests/handlers/tests.py b/tests/handlers/tests.py index 29083150a7d2..b34a7918d4e2 100644 --- a/tests/handlers/tests.py +++ b/tests/handlers/tests.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals +import sys import unittest from django.core.exceptions import ImproperlyConfigured @@ -19,6 +20,8 @@ except ImportError: # Python < 3.5 HTTPStatus = None +PY37 = sys.version_info >= (3, 7, 0) + class HandlerTests(SimpleTestCase): @@ -184,16 +187,17 @@ def test_suspiciousop_in_view_returns_400(self): def test_invalid_urls(self): response = self.client.get('~%A9helloworld') - self.assertContains(response, '~%A9helloworld', status_code=404) + self.assertEqual(response.status_code, 404) + self.assertEqual(response.context['request_path'], '/~%25A9helloworld' if PY37 else '/%7E%25A9helloworld') response = self.client.get('d%aao%aaw%aan%aal%aao%aaa%aad%aa/') - self.assertContains(response, 'd%AAo%AAw%AAn%AAl%AAo%AAa%AAd%AA', status_code=404) + self.assertEqual(response.context['request_path'], '/d%25AAo%25AAw%25AAn%25AAl%25AAo%25AAa%25AAd%25AA') response = self.client.get('/%E2%99%E2%99%A5/') - self.assertContains(response, '%E2%99\u2665', status_code=404) + self.assertEqual(response.context['request_path'], '/%25E2%2599%E2%99%A5/') response = self.client.get('/%E2%98%8E%E2%A9%E2%99%A5/') - self.assertContains(response, '\u260e%E2%A9\u2665', status_code=404) + self.assertEqual(response.context['request_path'], '/%E2%98%8E%25E2%25A9%E2%99%A5/') def test_environ_path_info_type(self): environ = RequestFactory().get('/%E2%A8%87%87%A5%E2%A8%A0').environ From 2c9dbe9226478e3c04cb2ec3bbbf18462ae87efb Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 4 Jan 2019 09:06:59 -0500 Subject: [PATCH 314/389] [1.11.x] Bumped version for 1.11.18 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index fea66ed04bbd..0293960f574f 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 18, 'alpha', 0) +VERSION = (1, 11, 18, 'final', 0) __version__ = get_version(VERSION) From b4937b70f7edc1dd951cd3d9e75cee04f70665c4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 4 Jan 2019 09:11:09 -0500 Subject: [PATCH 315/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 0293960f574f..dd4b3608d6d9 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 18, 'final', 0) +VERSION = (1, 11, 19, 'alpha', 0) __version__ = get_version(VERSION) From 71e8cdb3a4bfd9edd6b2e098591e042ecd875a9a Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 4 Jan 2019 09:24:47 -0500 Subject: [PATCH 316/389] [1.11.x] Added CVE-2019-3498 to the security release archive. Backport of 162ae9c9143aa85eb27ea69b446a28973eea4854 from master. --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 9ddef5054792..6c34c2f1dda7 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -909,3 +909,16 @@ Versions affected ~~~~~~~~~~~~~~~~~ * Django 2.1 `(patch) `__ + +January 4, 2019 - :cve:`2019-3498` +---------------------------------- + +Content spoofing possibility in the default 404 page. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.1 `(patch) `__ +* Django 2.0 `(patch) `__ +* Django 1.11 `(patch) `__ From cea425e6eb7db9ae56e835577415267e95a56e26 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 30 Jan 2019 13:06:09 +0100 Subject: [PATCH 317/389] [1.11.x] Fixed E117 and F405 flake8 warnings. Backport of 5a5c77d55dc85c7e6cf910243257e408887f412a from master --- django/contrib/gis/gdal/envelope.py | 2 +- django/db/models/sql/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/django/contrib/gis/gdal/envelope.py b/django/contrib/gis/gdal/envelope.py index 64cac5baa072..0de78d8c5877 100644 --- a/django/contrib/gis/gdal/envelope.py +++ b/django/contrib/gis/gdal/envelope.py @@ -126,7 +126,7 @@ def expand_to_include(self, *args): raise TypeError('Incorrect type of argument: %s' % str(type(args[0]))) elif len(args) == 2: # An x and an y parameter were passed in - return self.expand_to_include((args[0], args[1], args[0], args[1])) + return self.expand_to_include((args[0], args[1], args[0], args[1])) elif len(args) == 4: # Individual parameters passed in. return self.expand_to_include(args) diff --git a/django/db/models/sql/__init__.py b/django/db/models/sql/__init__.py index 31f45eb90dc9..762f8a5d624b 100644 --- a/django/db/models/sql/__init__.py +++ b/django/db/models/sql/__init__.py @@ -1,5 +1,6 @@ from django.core.exceptions import EmptyResultSet from django.db.models.sql.query import * # NOQA +from django.db.models.sql.query import Query from django.db.models.sql.subqueries import * # NOQA from django.db.models.sql.where import AND, OR From 951ee0b118eb640e6484189117be3308417d87bd Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 1 Feb 2019 08:32:42 -0500 Subject: [PATCH 318/389] [1.11.x] Refs #30150 -- Doc'd that MySQL 8 isn't supported. --- docs/ref/databases.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 614570812858..50886d3e845e 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -282,7 +282,7 @@ MySQL notes Version support --------------- -Django supports MySQL 5.5 and higher. +Django supports MySQL 5.5.x - 5.7.x. MySQL 8 and later aren't supported. Django's ``inspectdb`` feature uses the ``information_schema`` database, which contains detailed data on all database schemas. From 5a50ef90852be2723e97b0bd4537fc8faa5f263f Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 27 Aug 2018 10:26:29 -0400 Subject: [PATCH 319/389] [1.11.x] Replaced CVE/ticket roles with extlinks. Backport of 44f98f78804627839d5f0a8b3a32bfbb4546ff52 from master. --- docs/_ext/cve_role.py | 27 --------------------------- docs/_ext/ticket_role.py | 39 --------------------------------------- docs/conf.py | 12 ++++++------ 3 files changed, 6 insertions(+), 72 deletions(-) delete mode 100644 docs/_ext/cve_role.py delete mode 100644 docs/_ext/ticket_role.py diff --git a/docs/_ext/cve_role.py b/docs/_ext/cve_role.py deleted file mode 100644 index 254d3e679fed..000000000000 --- a/docs/_ext/cve_role.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -An interpreted text role to link docs to CVE issues. To use: :cve:`XXXXX` -""" -from docutils import nodes, utils -from docutils.parsers.rst import roles - - -def cve_role(name, rawtext, text, lineno, inliner, options=None, content=None): - if options is None: - options = {} - - url_pattern = inliner.document.settings.env.app.config.cve_url - if url_pattern is None: - msg = inliner.reporter.warning("cve not configured: please configure cve_url in conf.py") - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url = url_pattern % text - roles.set_classes(options) - node = nodes.reference(rawtext, utils.unescape('CVE-%s' % text), refuri=url, **options) - return [node], [] - - -def setup(app): - app.add_config_value('cve_url', None, 'env') - app.add_role('cve', cve_role) - return {'parallel_read_safe': True} diff --git a/docs/_ext/ticket_role.py b/docs/_ext/ticket_role.py deleted file mode 100644 index 809b4239b2a7..000000000000 --- a/docs/_ext/ticket_role.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -An interpreted text role to link docs to Trac tickets. - -To use: :ticket:`XXXXX` - -Based on code from psycopg2 by Daniele Varrazzo. -""" -from docutils import nodes, utils -from docutils.parsers.rst import roles - - -def ticket_role(name, rawtext, text, lineno, inliner, options=None, content=None): - if options is None: - options = {} - try: - num = int(text.replace('#', '')) - except ValueError: - msg = inliner.reporter.error( - "ticket number must be... a number, got '%s'" % text) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url_pattern = inliner.document.settings.env.app.config.ticket_url - if url_pattern is None: - msg = inliner.reporter.warning( - "ticket not configured: please configure ticket_url in conf.py") - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - - url = url_pattern % num - roles.set_classes(options) - node = nodes.reference(rawtext, '#' + utils.unescape(text), refuri=url, **options) - return [node], [] - - -def setup(app): - app.add_config_value('ticket_url', None, 'env') - app.add_role('ticket', ticket_role) - return {'parallel_read_safe': True} diff --git a/docs/conf.py b/docs/conf.py index 62e337dcebad..495eda08580f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -42,12 +42,16 @@ # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ "djangodocs", + 'sphinx.ext.extlinks', "sphinx.ext.intersphinx", "sphinx.ext.viewcode", - "ticket_role", - "cve_role", ] +extlinks = { + 'cve': ('https://nvd.nist.gov/view/vuln/detail?vulnId=%s', 'CVE-'), + 'ticket': ('https://code.djangoproject.com/ticket/%s', '#'), +} + # Spelling check needs an additional module that is not installed by default. # Add it only if spelling check is requested so docs can be generated without it. if 'spelling' in sys.argv: @@ -371,7 +375,3 @@ def django_release(): # If false, no index is generated. # epub_use_index = True - -# -- custom extension options -------------------------------------------------- -cve_url = 'https://nvd.nist.gov/view/vuln/detail?vulnId=%s' -ticket_url = 'https://code.djangoproject.com/ticket/%s' From f245cecc6f887d181bfa2400f60283ad5037c485 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 1 Feb 2019 15:42:48 -0500 Subject: [PATCH 320/389] [1.11.x] Used extlinks for GitHub commits. Backport of c34c6d0a2fc6d9bc55fb2db94b9ed40141babb15 from master. --- docs/conf.py | 1 + docs/internals/howto-release-django.txt | 4 +- docs/releases/security.txt | 340 ++++++++++++------------ 3 files changed, 172 insertions(+), 173 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 495eda08580f..038ca2541eac 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,6 +48,7 @@ ] extlinks = { + 'commit': ('https://github.com/django/django/commit/%s', ''), 'cve': ('https://nvd.nist.gov/view/vuln/detail?vulnId=%s', 'CVE-'), 'ticket': ('https://code.djangoproject.com/ticket/%s', '#'), } diff --git a/docs/internals/howto-release-django.txt b/docs/internals/howto-release-django.txt index 8670ab03c10e..c27f5a8a1cda 100644 --- a/docs/internals/howto-release-django.txt +++ b/docs/internals/howto-release-django.txt @@ -180,9 +180,7 @@ OK, this is the fun part, where we actually push out a release! checkout security/1.5.x; git rebase stable/1.5.x``) and then switch back and do the merge. Make sure the commit message for each security fix explains that the commit is a security fix and that an announcement will follow - (`example security commit`__). - - __ https://github.com/django/django/commit/3ef4bbf495cc6c061789132e3d50a8231a89406b + (:commit:`example security commit `). #. For a feature release, remove the ``UNDER DEVELOPMENT`` header at the top of the release notes and add the release date on the next line. For a diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 6c34c2f1dda7..d62ebd96dfcc 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -46,9 +46,9 @@ Filename validation issue in translation framework. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.90 `(patch) `__ -* Django 0.91 `(patch) `__ -* Django 0.95 `(patch) `__ (released January 21 2007) +* Django 0.90 :commit:`(patch) <518d406e53>` +* Django 0.91 :commit:`(patch) <518d406e53>` +* Django 0.95 :commit:`(patch) ` (released January 21 2007) January 21, 2007 - :cve:`2007-0405` ----------------------------------- @@ -59,7 +59,7 @@ Apparent "caching" of authenticated user. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.95 `(patch) `__ +* Django 0.95 :commit:`(patch) ` Issues under Django's security process ====================================== @@ -76,9 +76,9 @@ description `__ Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.91 `(patch) `__ -* Django 0.95 `(patch) `__ -* Django 0.96 `(patch) `__ +* Django 0.91 :commit:`(patch) <8bc36e726c9e8c75c681d3ad232df8e882aaac81>` +* Django 0.95 :commit:`(patch) <412ed22502e11c50dbfee854627594f0e7e2c234>` +* Django 0.96 :commit:`(patch) <7dd2dd08a79e388732ce00e2b5514f15bd6d0f6f>` May 14, 2008 - :cve:`2008-2302` ------------------------------- @@ -89,9 +89,9 @@ XSS via admin login redirect. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.91 `(patch) `__ -* Django 0.95 `(patch) `__ -* Django 0.96 `(patch) `__ +* Django 0.91 :commit:`(patch) <50ce7fb57d>` +* Django 0.95 :commit:`(patch) <50ce7fb57d>` +* Django 0.96 :commit:`(patch) <7791e5c050>` September 2, 2008 - :cve:`2008-3909` ------------------------------------ @@ -102,9 +102,9 @@ CSRF via preservation of POST data during admin login. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.91 `(patch) `__ -* Django 0.95 `(patch) `__ -* Django 0.96 `(patch) `__ +* Django 0.91 :commit:`(patch) <44debfeaa4473bd28872c735dd3d9afde6886752>` +* Django 0.95 :commit:`(patch) ` +* Django 0.96 :commit:`(patch) <7e0972bded362bc4b851c109df2c8a6548481a8e>` July 28, 2009 - :cve:`2009-2659` -------------------------------- @@ -115,8 +115,8 @@ Directory-traversal in development server media handler. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 0.96 `(patch) `__ -* Django 1.0 `(patch) `__ +* Django 0.96 :commit:`(patch) ` +* Django 1.0 :commit:`(patch) ` October 9, 2009 - :cve:`2009-3965` ---------------------------------- @@ -127,8 +127,8 @@ description `__ Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.0 `(patch) `__ -* Django 1.1 `(patch) `__ +* Django 1.0 :commit:`(patch) <594a28a904>` +* Django 1.1 :commit:`(patch) ` September 8, 2010 - :cve:`2010-3082` ------------------------------------ @@ -139,7 +139,7 @@ XSS via trusting unsafe cookie value. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.2 `(patch) `__ +* Django 1.2 :commit:`(patch) <7f84657b6b>` December 22, 2010 - :cve:`2010-4534` ------------------------------------ @@ -150,8 +150,8 @@ Information leakage in administrative interface. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.1 `(patch) `__ -* Django 1.2 `(patch) `__ +* Django 1.1 :commit:`(patch) <17084839fd>` +* Django 1.2 :commit:`(patch) <85207a245b>` December 22, 2010 - :cve:`2010-4535` ------------------------------------ @@ -162,8 +162,8 @@ Denial-of-service in password-reset mechanism. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.1 `(patch) `__ -* Django 1.2 `(patch) `__ +* Django 1.1 :commit:`(patch) <7f8dd9cbac>` +* Django 1.2 :commit:`(patch) ` February 8, 2011 - :cve:`2011-0696` ----------------------------------- @@ -174,8 +174,8 @@ CSRF via forged HTTP headers. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.1 `(patch) `__ -* Django 1.2 `(patch) `__ +* Django 1.1 :commit:`(patch) <408c5c873c>` +* Django 1.2 :commit:`(patch) <818e70344e>` February 8, 2011 - :cve:`2011-0697` ----------------------------------- @@ -186,8 +186,8 @@ XSS via unsanitized names of uploaded files. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.1 `(patch) `__ -* Django 1.2 `(patch) `__ +* Django 1.1 :commit:`(patch) <1966786d2d>` +* Django 1.2 :commit:`(patch) <1f814a9547>` February 8, 2011 - :cve:`2011-0698` ----------------------------------- @@ -198,8 +198,8 @@ description `__ Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.1 `(patch) `__ -* Django 1.2 `(patch) `__ +* Django 1.1 :commit:`(patch) <570a32a047>` +* Django 1.2 :commit:`(patch) <194566480b>` September 9, 2011 - :cve:`2011-4136` ------------------------------------ @@ -210,8 +210,8 @@ Session manipulation when using memory-cache-backed session. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.2 `(patch) `__ -* Django 1.3 `(patch) `__ +* Django 1.2 :commit:`(patch) ` +* Django 1.3 :commit:`(patch) ` September 9, 2011 - :cve:`2011-4137` ------------------------------------ @@ -222,8 +222,8 @@ Denial-of-service via ``URLField.verify_exists``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.2 `(patch) `__ -* Django 1.3 `(patch) `__ +* Django 1.2 :commit:`(patch) <7268f8af86>` +* Django 1.3 :commit:`(patch) <1a76dbefdf>` September 9, 2011 - :cve:`2011-4138` ------------------------------------ @@ -235,8 +235,8 @@ Information leakage/arbitrary request issuance via ``URLField.verify_exists``. Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.2: `(patch) `__ -* Django 1.3: `(patch) `__ +* Django 1.2: :commit:`(patch) <7268f8af86>` +* Django 1.3: :commit:`(patch) <1a76dbefdf>` September 9, 2011 - :cve:`2011-4139` ------------------------------------ @@ -247,8 +247,8 @@ September 9, 2011 - :cve:`2011-4139` Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.2 `(patch) `__ -* Django 1.3 `(patch) `__ +* Django 1.2 :commit:`(patch) ` +* Django 1.3 :commit:`(patch) <2f7fadc38e>` September 9, 2011 - :cve:`2011-4140` ------------------------------------ @@ -273,8 +273,8 @@ XSS via failure to validate redirect scheme. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3: `(patch) `__ -* Django 1.4: `(patch) `__ +* Django 1.3: :commit:`(patch) <4dea4883e6c50d75f215a6b9bcbd95273f57c72d>` +* Django 1.4: :commit:`(patch) ` July 30, 2012 - :cve:`2012-3443` -------------------------------- @@ -285,8 +285,8 @@ Denial-of-service via compressed image files. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3: `(patch) `__ -* Django 1.4: `(patch) `__ +* Django 1.3: :commit:`(patch) ` +* Django 1.4: :commit:`(patch) ` July 30, 2012 - :cve:`2012-3444` -------------------------------- @@ -297,8 +297,8 @@ Denial-of-service via large image files. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) <9ca0ff6268eeff92d0d0ac2c315d4b6a8e229155>` +* Django 1.4 :commit:`(patch) ` October 17, 2012 - :cve:`2012-4520` ----------------------------------- @@ -309,8 +309,8 @@ October 17, 2012 - :cve:`2012-4520` Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) ` +* Django 1.4 :commit:`(patch) <92d3430f12171f16f566c9050c40feefb830a4a3>` December 10, 2012 - No CVE 1 ---------------------------- @@ -321,8 +321,8 @@ Additional hardening of ``Host`` header handling. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) <2da4ace0bc1bc1d79bf43b368cb857f6f0cd6b1b>` +* Django 1.4 :commit:`(patch) <319627c184e71ae267d6b7f000e293168c7b6e09>` December 10, 2012 - No CVE 2 ---------------------------- @@ -333,8 +333,8 @@ Additional hardening of redirect validation. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3: `(patch) `__ -* Django 1.4: `(patch) `__ +* Django 1.3: :commit:`(patch) <1515eb46daa0897ba5ad5f0a2db8969255f1b343>` +* Django 1.4: :commit:`(patch) ` February 19, 2013 - No CVE -------------------------- @@ -345,8 +345,8 @@ Additional hardening of ``Host`` header handling. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) <27cd872e6e36a81d0bb6f5b8765a1705fecfc253>` +* Django 1.4 :commit:`(patch) <9936fdb11d0bbf0bd242f259bfb97bbf849d16f8>` February 19, 2013 - :cve:`2013-1664` / :cve:`2013-1665` ------------------------------------------------------- @@ -357,8 +357,8 @@ Entity-based attacks against Python XML libraries. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) ` +* Django 1.4 :commit:`(patch) <1c60d07ba23e0350351c278ad28d0bd5aa410b40>` February 19, 2013 - :cve:`2013-0305` ------------------------------------ @@ -369,8 +369,8 @@ Information leakage via admin history log. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) ` +* Django 1.4 :commit:`(patch) <0e7861aec73702f7933ce2a93056f7983939f0d6>` February 19, 2013 - :cve:`2013-0306` ------------------------------------ @@ -381,8 +381,8 @@ Denial-of-service via formset ``max_num`` bypass. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.3 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.3 :commit:`(patch) ` +* Django 1.4 :commit:`(patch) <0cc350a896f70ace18280410eb616a9197d862b0>` August 13, 2013 - :cve:`2013-4249` ---------------------------------- @@ -393,7 +393,7 @@ XSS via admin trusting ``URLField`` values. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.5 `(patch) `__ +* Django 1.5 :commit:`(patch) <90363e388c61874add3f3557ee654a996ec75d78>` August 13, 2013 - :cve:`2013-6044` ---------------------------------- @@ -404,8 +404,8 @@ Possible XSS via unvalidated URL redirect schemes. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.5 `(patch) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.5 :commit:`(patch) <1a274ccd6bc1afbdac80344c9b6e5810c1162b5f>` September 10, 2013 - :cve:`2013-4315` ------------------------------------- @@ -416,8 +416,8 @@ Directory-traversal via ``ssi`` template tag. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.5 `(patch) `__ +* Django 1.4 :commit:`(patch) <87d2750b39f6f2d54b7047225521a44dcd37e896>` +* Django 1.5 :commit:`(patch) <988b61c550d798f9a66d17ee0511fb7a9a7f33ca>` September 14, 2013 - :cve:`2013-1443` ------------------------------------- @@ -428,8 +428,8 @@ Denial-of-service via large passwords. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch `__ and `Python compatibility fix) `__ -* Django 1.5 `(patch) `__ +* Django 1.4 :commit:`(patch <3f3d887a6844ec2db743fee64c9e53e04d39a368>` and :commit:`Python compatibility fix) <6903d1690a92aa040adfb0c8eb37cf62e4206714>` +* Django 1.5 :commit:`(patch) <22b74fa09d7ccbc8c52270d648a0da7f3f0fa2bc>` April 21, 2014 - :cve:`2014-0472` --------------------------------- @@ -440,10 +440,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.5 :commit:`(patch) <2a5bcb69f42b84464b24b5c835dca6467b6aa7f1>` +* Django 1.6 :commit:`(patch) <4352a50871e239ebcdf64eee6f0b88e714015c1b>` +* Django 1.7 :commit:`(patch) <546740544d7f69254a67b06a3fc7fa0c43512958>` April 21, 2014 - :cve:`2014-0473` --------------------------------- @@ -454,10 +454,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) <1170f285ddd6a94a65f911a27788ba49ca08c0b0>` +* Django 1.5 :commit:`(patch) <6872f42757d7ef6a97e0b6ec5db4d2615d8a2bd8>` +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) <380545bf85cbf17fc698d136815b7691f8d023ca>` April 21, 2014 - :cve:`2014-0474` --------------------------------- @@ -468,10 +468,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.5 :commit:`(patch) <985434fb1d6bf2335bf96c6ebf91c3674f1f399f>` +* Django 1.6 :commit:`(patch) <5f0829a27e85d89ad8c433f5c6a7a7d17c9e9292>` +* Django 1.7 :commit:`(patch) <34526c2f56b863c2103655a0893ac801667e86ea>` May 18, 2014 - :cve:`2014-1418` ------------------------------- @@ -482,10 +482,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) <28e23306aa53bbbb8fb87db85f99d970b051026c>` +* Django 1.5 :commit:`(patch) <4001ec8698f577b973c5a540801d8a0bbea1205b>` +* Django 1.6 :commit:`(patch) <1abcf3a808b35abae5d425ed4d44cb6e886dc769>` +* Django 1.7 :commit:`(patch) <7fef18ba9e5a8b47bc24b5bb259c8bf3d3879f2a>` May 18, 2014 - :cve:`2014-3730` ------------------------------- @@ -496,10 +496,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) <7feb54bbae3f637ab3c4dd4831d4385964f574df>` +* Django 1.5 :commit:`(patch) ` +* Django 1.6 :commit:`(patch) <601107524523bca02376a0ddc1a06c6fdb8f22f3>` +* Django 1.7 :commit:`(patch) ` August 20, 2014 - :cve:`2014-0480` ---------------------------------- @@ -510,10 +510,10 @@ August 20, 2014 - :cve:`2014-0480` Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.5 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.5 :commit:`(patch) <45ac9d4fb087d21902469fc22643f5201d41a0cd>` +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) ` August 20, 2014 - :cve:`2014-0481` ---------------------------------- @@ -524,10 +524,10 @@ 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) `__ +* Django 1.4 :commit:`(patch) <30042d475bf084c6723c6217a21598d9247a9c41>` +* Django 1.5 :commit:`(patch) <26cd48e166ac4d84317c8ee6d63ac52a87e8da99>` +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) <3123f8452cf49071be9110e277eea60ba0032216>` August 20, 2014 - :cve:`2014-0482` ---------------------------------- @@ -538,10 +538,10 @@ August 20, 2014 - :cve:`2014-0482` Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.5 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.5 :commit:`(patch) ` +* Django 1.6 :commit:`(patch) <0268b855f9eab3377f2821164ef3e66037789e09>` +* Django 1.7 :commit:`(patch) <1a45d059c70385fcd6f4a3955f3b4e4cc96d0150>` August 20, 2014 - :cve:`2014-0483` ---------------------------------- @@ -552,10 +552,10 @@ Data leakage via querystring manipulation in admin. Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.5 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) <027bd348642007617518379f8b02546abacaa6e0>` +* Django 1.5 :commit:`(patch) <2a446c896e7c814661fb9c4f212b071b2a7fa446>` +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) <2b31342cdf14fc20e07c43d258f1e7334ad664a6>` January 13, 2015 - :cve:`2015-0219` ----------------------------------- @@ -566,9 +566,9 @@ WSGI header spoofing via underscore/dash conflation. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) <4f6fffc1dc429f1ad428ecf8e6620739e8837450>` +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) <41b4bc73ee0da7b2e09f4af47fc1fd21144c710f>` January 13, 2015 - :cve:`2015-0220` ----------------------------------- @@ -579,9 +579,9 @@ description `__ Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) <4c241f1b710da6419d9dca160e80b23b82db7758>` +* Django 1.6 :commit:`(patch) <72e0b033662faa11bb7f516f18a132728aa0ae28>` +* Django 1.7 :commit:`(patch) ` January 13, 2015 - :cve:`2015-0221` ----------------------------------- @@ -592,9 +592,9 @@ description `__ Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.4 `(patch) `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.4 :commit:`(patch) ` +* Django 1.6 :commit:`(patch) <553779c4055e8742cc832ed525b9ee34b174934f>` +* Django 1.7 :commit:`(patch) <818e59a3f0fbadf6c447754d202d88df025f8f2a>` January 13, 2015 - :cve:`2015-0222` ----------------------------------- @@ -605,8 +605,8 @@ Database denial-of-service with ``ModelMultipleChoiceField``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) ` March 9, 2015 - :cve:`2015-2241` -------------------------------- @@ -617,8 +617,8 @@ XSS attack via properties in ``ModelAdmin.readonly_fields``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.7 `(patch) `__ -* Django 1.8 `(patch) `_ +* Django 1.7 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) <2654e1b93923bac55f12b4e66c5e39b16695ace5>` March 18, 2015 - :cve:`2015-2316` --------------------------------- @@ -629,9 +629,9 @@ Denial-of-service possibility with ``strip_tags()``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.6 :commit:`(patch) ` +* Django 1.7 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) <5447709a571cd5d95971f1d5d21d4a7edcf85bbd>` March 18, 2015 - :cve:`2015-2317` --------------------------------- @@ -642,10 +642,10 @@ description `__ -* Django 1.6 `(patch) `__ -* Django 1.7 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.4 :commit:`(patch) <2342693b31f740a422abf7267c53b4e7bc487c1b>` +* Django 1.6 :commit:`(patch) <5510f070711540aaa8d3707776cd77494e688ef9>` +* Django 1.7 :commit:`(patch) <2a4113dbd532ce952308992633d802dc169a75f1>` +* Django 1.8 :commit:`(patch) <770427c2896a078925abfca2317486b284d22f04>` May 20, 2015 - :cve:`2015-3982` ------------------------------- @@ -656,7 +656,7 @@ Fixed session flushing in the cached_db backend. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.8 `(patch) `__ +* Django 1.8 :commit:`(patch) <31cb25adecba930bdeee4556709f5a1c42d88fd6>` July 8, 2015 - :cve:`2015-5143` ------------------------------- @@ -667,9 +667,9 @@ description `__ -* Django 1.7 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.8 :commit:`(patch) <66d12d1ababa8f062857ee5eb43276493720bf16>` +* Django 1.7 :commit:`(patch) <1828f4341ec53a8684112d24031b767eba557663>` +* Django 1.4 :commit:`(patch) <2e47f3e401c29bc2ba5ab794d483cb0820855fb9>` July 8, 2015 - :cve:`2015-5144` ------------------------------- @@ -680,9 +680,9 @@ description `__ -* Django 1.7 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.8 :commit:`(patch) <574dd5e0b0fbb877ae5827b1603d298edc9bb2a0>` +* Django 1.7 :commit:`(patch) ` +* Django 1.4 :commit:`(patch) <1ba1cdce7d58e6740fe51955d945b56ae51d072a>` July 8, 2015 - :cve:`2015-5145` ------------------------------- @@ -693,7 +693,7 @@ Denial-of-service possibility in URL validation. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.8 `(patch) `__ +* Django 1.8 :commit:`(patch) <8f9a4d3a2bc42f14bb437defd30c7315adbff22c>` August 18, 2015 - :cve:`2015-5963` / :cve:`2015-5964` ----------------------------------------------------- @@ -704,9 +704,9 @@ Denial-of-service possibility in ``logout()`` view by filling session store. Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.8 `(patch) `__ -* Django 1.7 `(patch) `__ -* Django 1.4 `(patch) `__ +* Django 1.8 :commit:`(patch) <2eb86b01d7b59be06076f6179a454d0fd0afaff6>` +* Django 1.7 :commit:`(patch) <2f5485346ee6f84b4e52068c04e043092daf55f7>` +* Django 1.4 :commit:`(patch) <575f59f9bc7c59a5e41a081d1f5f55fc859c5012>` November 24, 2015 - :cve:`2015-8213` ------------------------------------ @@ -717,8 +717,8 @@ Settings leak possibility in ``date`` template filter. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.8 `(patch) `__ -* Django 1.7 `(patch) `__ +* Django 1.8 :commit:`(patch) <9f83fc2f66f5a0bac7c291aec55df66050bb6991>` +* Django 1.7 :commit:`(patch) <8a01c6b53169ee079cb21ac5919fdafcc8c5e172>` February 1, 2016 - :cve:`2016-2048` ----------------------------------- @@ -730,7 +730,7 @@ User with "change" but not "add" permission can create objects for Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.9 `(patch) `__ +* Django 1.9 :commit:`(patch) ` March 1, 2016 - :cve:`2016-2512` -------------------------------- @@ -742,8 +742,8 @@ containing basic auth. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.9 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) <382ab137312961ad62feb8109d70a5a581fe8350>` March 1, 2016 - :cve:`2016-2513` -------------------------------- @@ -755,8 +755,8 @@ upgrade. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.9 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) ` July 18, 2016 - :cve:`2016-6186` -------------------------------- @@ -767,8 +767,8 @@ XSS in admin's add/change related popup. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.9 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) ` September 26, 2016 - :cve:`2016-7401` ------------------------------------- @@ -779,8 +779,8 @@ CSRF protection bypass on a site with Google Analytics. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.9 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) <6118ab7d0676f0d622278e5be215f14fb5410b6a>` November 1, 2016 - :cve:`2016-9013` ----------------------------------- @@ -791,9 +791,9 @@ description `__ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.10 :commit:`(patch) <34e10720d81b8d407aa14d763b6a7fe8f13b4f2e>` +* Django 1.9 :commit:`(patch) <4844d86c7728c1a5a3bbce4ad336a8d32304072b>` +* Django 1.8 :commit:`(patch) <70f99952965a430daf69eeb9947079aae535d2d0>` November 1, 2016 - :cve:`2016-9014` ----------------------------------- @@ -804,9 +804,9 @@ DNS rebinding vulnerability when ``DEBUG=True``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.10 `(patch) `__ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.10 :commit:`(patch) <884e113838e5a72b4b0ec9e5e87aa480f6aa4472>` +* Django 1.9 :commit:`(patch) <45acd6d836895a4c36575f48b3fb36a3dae98d19>` +* Django 1.8 :commit:`(patch) ` April 4, 2017 - :cve:`2017-7233` -------------------------------- @@ -817,9 +817,9 @@ Open redirect and possible XSS attack via user-supplied numeric redirect URLs. Versions affected ~~~~~~~~~~~~~~~~~ -* Django 1.10 `(patch) `__ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.10 :commit:`(patch) ` +* Django 1.9 :commit:`(patch) <254326cb3682389f55f886804d2c43f7b9f23e4f>` +* Django 1.8 :commit:`(patch) <8339277518c7d8ec280070a780915304654e3b66>` April 4, 2017 - :cve:`2017-7234` -------------------------------- @@ -830,9 +830,9 @@ description `__ -* Django 1.9 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 1.10 :commit:`(patch) <2a9f6ef71b8e23fd267ee2be1be26dde8ab67037>` +* Django 1.9 :commit:`(patch) <5f1ffb07afc1e59729ce2b283124116d6c0659e4>` +* Django 1.8 :commit:`(patch) <4a6b945dffe8d10e7cec107d93e6efaebfbded29>` September 5, 2017 - :cve:`2017-12794` ------------------------------------- @@ -843,8 +843,8 @@ description `__ -* Django 1.10 `(patch) `__ +* Django 1.11 :commit:`(patch) ` +* Django 1.10 :commit:`(patch) <58e08e80e362db79eb0fd775dc81faad90dca47a>` February 1, 2018 - :cve:`2018-6188` ----------------------------------- @@ -855,8 +855,8 @@ Information leakage in ``AuthenticationForm``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.0 `(patch) `__ -* Django 1.11 `(patch) `__ +* Django 2.0 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) <57b95fedad5e0b83fc9c81466b7d1751c6427aae>` March 6, 2018 - :cve:`2018-7536` -------------------------------- @@ -868,9 +868,9 @@ filters. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.0 `(patch) `__ -* Django 1.11 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 2.0 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) <1ca63a66ef3163149ad822701273e8a1844192c2>` March 6, 2018 - :cve:`2018-7537` -------------------------------- @@ -882,9 +882,9 @@ Denial-of-service possibility in ``truncatechars_html`` and Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.0 `(patch) `__ -* Django 1.11 `(patch) `__ -* Django 1.8 `(patch) `__ +* Django 2.0 :commit:`(patch) <94c5da1d17a6b0d378866c66b605102c19f7988c>` +* Django 1.11 :commit:`(patch) ` +* Django 1.8 :commit:`(patch) ` August 1, 2018 - :cve:`2018-14574` ---------------------------------- @@ -895,9 +895,9 @@ Open redirect possibility in ``CommonMiddleware``. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.1 `(patch) `__ -* Django 2.0 `(patch) `__ -* Django 1.11 `(patch) `__ +* Django 2.1 :commit:`(patch) ` +* Django 2.0 :commit:`(patch) <6fffc3c6d420e44f4029d5643f38d00a39b08525>` +* Django 1.11 :commit:`(patch) ` October 1, 2018 - :cve:`2018-16984` ----------------------------------- @@ -908,7 +908,7 @@ Password hash disclosure to "view only" admin users. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.1 `(patch) `__ +* Django 2.1 :commit:`(patch) ` January 4, 2019 - :cve:`2019-3498` ---------------------------------- @@ -919,6 +919,6 @@ Content spoofing possibility in the default 404 page. `Full description Versions affected ~~~~~~~~~~~~~~~~~ -* Django 2.1 `(patch) `__ -* Django 2.0 `(patch) `__ -* Django 1.11 `(patch) `__ +* Django 2.1 :commit:`(patch) <64d2396e83aedba3fcc84ca40f23fbd22f0b9b5b>` +* Django 2.0 :commit:`(patch) <9f4ed7c94c62e21644ef5115e393ac426b886f2e>` +* Django 1.11 :commit:`(patch) <1cd00fcf52d089ef0fe03beabd05d59df8ea052a>` From fc858abe51675843407debbd613c2be627a1209f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 7 Feb 2019 15:46:53 +0100 Subject: [PATCH 321/389] Added stub release notes for security releases. # Conflicts: # docs/releases/2.1.6.txt --- docs/releases/1.11.19.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/1.11.19.txt diff --git a/docs/releases/1.11.19.txt b/docs/releases/1.11.19.txt new file mode 100644 index 000000000000..cae22c441597 --- /dev/null +++ b/docs/releases/1.11.19.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.19 release notes +============================ + +*February 11, 2019* + +Django 1.11.19 fixes a security issue in 1.11.18. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 719e5f416e18..c5c4f21af7ba 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.19 1.11.18 1.11.17 1.11.16 From 11cb39514dcb6de197ce22f8fa0cf011d53162e7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 8 Feb 2019 21:38:30 +0100 Subject: [PATCH 322/389] [1.11.x] Removed extra characters in docs header underlines. Backport of 25829197bb94585e94695360065ac614aa9e6a56 from master --- docs/howto/custom-model-fields.txt | 4 ++-- docs/intro/tutorial03.txt | 2 +- docs/ref/databases.txt | 2 +- docs/releases/1.0-porting-guide.txt | 4 ++-- docs/releases/1.1.txt | 2 +- docs/topics/auth/passwords.txt | 2 +- docs/topics/pagination.txt | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/howto/custom-model-fields.txt b/docs/howto/custom-model-fields.txt index 3c71f8f004ed..f0b64ae1cbc2 100644 --- a/docs/howto/custom-model-fields.txt +++ b/docs/howto/custom-model-fields.txt @@ -696,7 +696,7 @@ existing conversion code:: return self.get_prep_value(value) Some general advice --------------------- +------------------- Writing a custom field can be a tricky process, particularly if you're doing complex conversions between your Python types and your database and @@ -737,7 +737,7 @@ told to use it. To do so, simply assign the new ``File`` subclass to the special ``attr_class`` attribute of the ``FileField`` subclass. A few suggestions ------------------- +----------------- In addition to the above details, there are a few guidelines which can greatly improve the efficiency and readability of the field's code. diff --git a/docs/intro/tutorial03.txt b/docs/intro/tutorial03.txt index 2d1104d3d756..6569f0533dc2 100644 --- a/docs/intro/tutorial03.txt +++ b/docs/intro/tutorial03.txt @@ -415,7 +415,7 @@ template (or templates) you would change it in ``polls/urls.py``:: ... Namespacing URL names -====================== +===================== The tutorial project has just one app, ``polls``. In real Django projects, there might be five, ten, twenty apps or more. How does Django differentiate diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 50886d3e845e..67307a9bc881 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -886,7 +886,7 @@ both as empty strings. Django will use a different connect descriptor depending on that choice. Threaded option ----------------- +--------------- If you plan to run Django in a multithreaded environment (e.g. Apache using the default MPM module on any modern operating system), then you **must** set diff --git a/docs/releases/1.0-porting-guide.txt b/docs/releases/1.0-porting-guide.txt index 72f01495a59d..8dded73236dc 100644 --- a/docs/releases/1.0-porting-guide.txt +++ b/docs/releases/1.0-porting-guide.txt @@ -298,7 +298,7 @@ Old (0.96) New (1.0) ===================== ===================== Work with file fields using the new API -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The internal implementation of :class:`django.db.models.FileField` have changed. A visible result of this is that the way you access special attributes (URL, @@ -644,7 +644,7 @@ Testing ------- :meth:`django.test.Client.login` has changed -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Old (0.96):: diff --git a/docs/releases/1.1.txt b/docs/releases/1.1.txt index 1ef566733605..61da881ddf83 100644 --- a/docs/releases/1.1.txt +++ b/docs/releases/1.1.txt @@ -267,7 +267,7 @@ A few notable improvements have been made to the :doc:`testing framework `. Test performance improvements -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. currentmodule:: django.test diff --git a/docs/topics/auth/passwords.txt b/docs/topics/auth/passwords.txt index 7015e654046b..d39aebb89a51 100644 --- a/docs/topics/auth/passwords.txt +++ b/docs/topics/auth/passwords.txt @@ -565,7 +565,7 @@ Django includes four validators: Validates whether the password is not entirely numeric. Integrating validation ------------------------ +---------------------- There are a few functions in ``django.contrib.auth.password_validation`` that you can call from your own forms or other code to integrate password diff --git a/docs/topics/pagination.txt b/docs/topics/pagination.txt index 0f436e63536b..b89cdb2408f5 100644 --- a/docs/topics/pagination.txt +++ b/docs/topics/pagination.txt @@ -76,7 +76,7 @@ page:: Using ``Paginator`` in a view -============================== +============================= Here's a slightly more complex example using :class:`Paginator` in a view to paginate a queryset. We give both the view and the accompanying template to From 0bbb560183fabf0533289700845dafa94951f227 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 11 Feb 2019 11:15:45 +0100 Subject: [PATCH 323/389] [1.11.x] Fixed CVE-2019-6975 -- Fixed memory exhaustion in utils.numberformat.format(). Thanks Sjoerd Job Postmus for the report and initial patch. Thanks Michael Manfre, Tim Graham, and Florian Apolloner for review. Backport of 402c0caa851e265410fbcaa55318f22d2bf22ee2 from master. --- django/utils/numberformat.py | 15 ++++++++++++++- docs/releases/1.11.19.txt | 12 ++++++++++++ tests/utils_tests/test_numberformat.py | 18 ++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/django/utils/numberformat.py b/django/utils/numberformat.py index ae5a3b547410..97d112aad2d8 100644 --- a/django/utils/numberformat.py +++ b/django/utils/numberformat.py @@ -30,7 +30,20 @@ def format(number, decimal_sep, decimal_pos=None, grouping=0, thousand_sep='', # sign sign = '' if isinstance(number, Decimal): - str_number = '{:f}'.format(number) + # Format values with more than 200 digits (an arbitrary cutoff) using + # scientific notation to avoid high memory usage in {:f}'.format(). + _, digits, exponent = number.as_tuple() + if abs(exponent) + len(digits) > 200: + number = '{:e}'.format(number) + coefficient, exponent = number.split('e') + # Format the coefficient. + coefficient = format( + coefficient, decimal_sep, decimal_pos, grouping, + thousand_sep, force_grouping, + ) + return '{}e{}'.format(coefficient, exponent) + else: + str_number = '{:f}'.format(number) else: str_number = six.text_type(number) if str_number[0] == '-': diff --git a/docs/releases/1.11.19.txt b/docs/releases/1.11.19.txt index cae22c441597..9ce48f26b240 100644 --- a/docs/releases/1.11.19.txt +++ b/docs/releases/1.11.19.txt @@ -5,3 +5,15 @@ Django 1.11.19 release notes *February 11, 2019* Django 1.11.19 fixes a security issue in 1.11.18. + +CVE-2019-6975: Memory exhaustion in ``django.utils.numberformat.format()`` +-------------------------------------------------------------------------- + +If ``django.utils.numberformat.format()`` -- used by ``contrib.admin`` as well +as the the ``floatformat``, ``filesizeformat``, and ``intcomma`` templates +filters -- received a ``Decimal`` with a large number of digits or a large +exponent, it could lead to significant memory usage due to a call to +``'{:f}'.format()``. + +To avoid this, decimals with more than 200 digits are now formatted using +scientific notation. diff --git a/tests/utils_tests/test_numberformat.py b/tests/utils_tests/test_numberformat.py index 3dd1b0644ff2..769406c0d896 100644 --- a/tests/utils_tests/test_numberformat.py +++ b/tests/utils_tests/test_numberformat.py @@ -60,6 +60,24 @@ def test_decimal_numbers(self): self.assertEqual(nformat(Decimal('1234'), '.', grouping=2, thousand_sep=',', force_grouping=True), '12,34') self.assertEqual(nformat(Decimal('-1234.33'), '.', decimal_pos=1), '-1234.3') self.assertEqual(nformat(Decimal('0.00000001'), '.', decimal_pos=8), '0.00000001') + # Very large & small numbers. + tests = [ + ('9e9999', None, '9e+9999'), + ('9e9999', 3, '9.000e+9999'), + ('9e201', None, '9e+201'), + ('9e200', None, '9e+200'), + ('1.2345e999', 2, '1.23e+999'), + ('9e-999', None, '9e-999'), + ('1e-7', 8, '0.00000010'), + ('1e-8', 8, '0.00000001'), + ('1e-9', 8, '0.00000000'), + ('1e-10', 8, '0.00000000'), + ('1e-11', 8, '0.00000000'), + ('1' + ('0' * 300), 3, '1.000e+300'), + ('0.{}1234'.format('0' * 299), 3, '1.234e-300'), + ] + for value, decimal_pos, expected_value in tests: + self.assertEqual(nformat(Decimal(value), '.', decimal_pos), expected_value) def test_decimal_subclass(self): class EuroDecimal(Decimal): From 1cdba624d55d5c2fe3c74fff1fb5fb17b126821d Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 11 Feb 2019 11:31:04 +0100 Subject: [PATCH 324/389] [1.11.x] Bumped version for 1.11.19 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index dd4b3608d6d9..37a850960c66 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 19, 'alpha', 0) +VERSION = (1, 11, 19, 'final', 0) __version__ = get_version(VERSION) From f2c5f66c7c7212721ce2de6a44dfd828c7268c16 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 11 Feb 2019 15:46:33 +0100 Subject: [PATCH 325/389] [1.11.x] Refs #30175 -- Added release notes for 1.11.20 release. Backport of b39bd0aa6d5667d6bbcf7d349a1035c676e3f972 from master --- docs/releases/1.11.20.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.20.txt diff --git a/docs/releases/1.11.20.txt b/docs/releases/1.11.20.txt new file mode 100644 index 000000000000..225370af63a9 --- /dev/null +++ b/docs/releases/1.11.20.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.20 release notes +============================ + +*February 11, 2019* + +Django 1.11.20 fixes a packaging error in 1.11.19. + +Bugfixes +======== + +* Corrected packaging error from 1.11.19 (:ticket:`30175`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c5c4f21af7ba..e2da7c3de6a5 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.20 1.11.19 1.11.18 1.11.17 From 1c9cb948d7b0c264d244763b6682ab790a6b90a0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 11 Feb 2019 15:54:05 +0100 Subject: [PATCH 326/389] [1.11.x] Bumped version for 1.11.20 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 37a850960c66..8f8e78f2343b 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 19, 'final', 0) +VERSION = (1, 11, 20, 'final', 0) __version__ = get_version(VERSION) From 013b923876e54024e15d2364f54f038560968278 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 11 Feb 2019 15:54:39 -0500 Subject: [PATCH 327/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 8f8e78f2343b..ca628f5df2b2 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 20, 'final', 0) +VERSION = (1, 11, 21, 'alpha', 0) __version__ = get_version(VERSION) From d718f5203e75dc7478399916211e478c1cfb77d4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Mon, 11 Feb 2019 16:08:50 -0500 Subject: [PATCH 328/389] [1.11.x] Added CVE-2019-6975 to the security release archive. Backport of d6e5aad5c7eba3d8061c09902de16cd2b22619af from master. --- docs/releases/security.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index d62ebd96dfcc..cce666ce99ae 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -922,3 +922,17 @@ Versions affected * Django 2.1 :commit:`(patch) <64d2396e83aedba3fcc84ca40f23fbd22f0b9b5b>` * Django 2.0 :commit:`(patch) <9f4ed7c94c62e21644ef5115e393ac426b886f2e>` * Django 1.11 :commit:`(patch) <1cd00fcf52d089ef0fe03beabd05d59df8ea052a>` + +February 11, 2019 - :cve:`2019-6975` +------------------------------------ + +Memory exhaustion in ``django.utils.numberformat.format()``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.1 :commit:`(patch) <40cd19055773705301c3428ed5e08a036d2091f3>` +* Django 2.0 :commit:`(patch <1f42f82566c9d2d73aff1c42790d6b1b243f7676>` and + :commit:`correction) <392e040647403fc8007708d52ce01d915b014849>` +* Django 1.11 :commit:`(patch) <0bbb560183fabf0533289700845dafa94951f227>` From 1b8a26efa2e45ac471c969791e8c977e6d633091 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 14 Feb 2019 09:35:54 -0500 Subject: [PATCH 329/389] [1.11.x] Fixed E117 flake8 warnings. --- django/test/testcases.py | 18 +++++++++--------- tests/managers_regress/tests.py | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index d70f57588fc8..facf5fb126dd 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1029,15 +1029,15 @@ def setUpClass(cls): if cls.fixtures: for db_name in cls._databases_names(include_mirrors=False): - try: - call_command('loaddata', *cls.fixtures, **{ - 'verbosity': 0, - 'commit': False, - 'database': db_name, - }) - except Exception: - cls._rollback_atomics(cls.cls_atomics) - raise + try: + call_command('loaddata', *cls.fixtures, **{ + 'verbosity': 0, + 'commit': False, + 'database': db_name, + }) + except Exception: + cls._rollback_atomics(cls.cls_atomics) + raise try: cls.setUpTestData() except Exception: diff --git a/tests/managers_regress/tests.py b/tests/managers_regress/tests.py index c9e9f07188f6..4cc4ecf22230 100644 --- a/tests/managers_regress/tests.py +++ b/tests/managers_regress/tests.py @@ -567,7 +567,7 @@ class Meta: warnings.simplefilter('always', RemovedInDjango20Warning) class MyModel(ConcreteParentWithoutManager): - pass + pass self.assertEqual(len(warns), 0) # Should create 'objects' (set as default) and warn that From b9beb6a52e84e565533f029555eea731d92fe4f3 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 25 Feb 2019 11:03:30 +0100 Subject: [PATCH 330/389] [1.11.x] Fixed relative paths imports per isort 4.3.5. Backport of 463fe11bc8b2d068e447c5df677e7a31c2af7e03 from master. --- django/contrib/postgres/fields/array.py | 2 +- tests/gis_tests/distapp/tests.py | 2 +- tests/gis_tests/geoapp/test_expressions.py | 2 +- tests/gis_tests/geoapp/test_functions.py | 2 +- tests/gis_tests/geoapp/test_regress.py | 2 +- tests/gis_tests/geoapp/tests.py | 2 +- tests/gis_tests/geogapp/tests.py | 2 +- tests/gis_tests/inspectapp/tests.py | 2 +- tests/gis_tests/rasterapp/test_rasterfield.py | 2 +- tests/gis_tests/relatedapp/tests.py | 2 +- tests/template_tests/filter_tests/test_date.py | 2 +- tests/template_tests/filter_tests/test_time.py | 2 +- tests/template_tests/filter_tests/test_timesince.py | 2 +- tests/template_tests/filter_tests/test_timeuntil.py | 2 +- tests/template_tests/syntax_tests/i18n/test_blocktrans.py | 2 +- tests/template_tests/syntax_tests/i18n/test_trans.py | 2 +- .../template_tests/syntax_tests/i18n/test_underscore_syntax.py | 2 +- tests/template_tests/syntax_tests/test_exceptions.py | 2 +- tests/template_tests/syntax_tests/test_include.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index b70ae37a3af4..d6848c3f32ac 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -9,8 +9,8 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ -from ..utils import prefix_validation_error from .utils import AttributeSetter +from ..utils import prefix_validation_error __all__ = ['ArrayField'] diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index 4609083f3b41..136b1f957f72 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -12,11 +12,11 @@ from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from django.utils.deprecation import RemovedInDjango20Warning -from ..utils import no_oracle, oracle, postgis, spatialite from .models import ( AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt, SouthTexasInterstate, SouthTexasZipcode, ) +from ..utils import no_oracle, oracle, postgis, spatialite class DistanceTest(TestCase): diff --git a/tests/gis_tests/geoapp/test_expressions.py b/tests/gis_tests/geoapp/test_expressions.py index 503b706f0e54..f7568585830d 100644 --- a/tests/gis_tests/geoapp/test_expressions.py +++ b/tests/gis_tests/geoapp/test_expressions.py @@ -4,8 +4,8 @@ from django.contrib.gis.geos import Point, Polygon from django.test import TestCase, skipUnlessDBFeature -from ..utils import postgis from .models import City +from ..utils import postgis class GeoExpressionsTests(TestCase): diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index a3ffc40ecd87..2428e248a45d 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -11,8 +11,8 @@ from django.test import TestCase, skipUnlessDBFeature from django.utils import six -from ..utils import mysql, oracle, postgis, spatialite from .models import City, Country, CountryWebMercator, State, Track +from ..utils import mysql, oracle, postgis, spatialite class GISFunctionsTests(TestCase): diff --git a/tests/gis_tests/geoapp/test_regress.py b/tests/gis_tests/geoapp/test_regress.py index a9d160c53422..3b050db54782 100644 --- a/tests/gis_tests/geoapp/test_regress.py +++ b/tests/gis_tests/geoapp/test_regress.py @@ -8,8 +8,8 @@ from django.db.models import Count, Min from django.test import TestCase, skipUnlessDBFeature -from ..utils import no_oracle from .models import City, PennsylvaniaCity, State, Truth +from ..utils import no_oracle class GeoRegressionTests(TestCase): diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index b6a5c2d0c851..183fcd5047d6 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -15,11 +15,11 @@ from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning -from ..utils import oracle, postgis, skipUnlessGISLookup, spatialite from .models import ( City, Country, Feature, MinusOneSRID, NonConcreteModel, PennsylvaniaCity, State, Track, ) +from ..utils import oracle, postgis, skipUnlessGISLookup, spatialite class GeoModelTest(TestCase): diff --git a/tests/gis_tests/geogapp/tests.py b/tests/gis_tests/geogapp/tests.py index a23dad3a5390..72041e805ba1 100644 --- a/tests/gis_tests/geogapp/tests.py +++ b/tests/gis_tests/geogapp/tests.py @@ -17,8 +17,8 @@ from django.utils._os import upath from django.utils.deprecation import RemovedInDjango20Warning -from ..utils import oracle, postgis, spatialite from .models import City, County, Zipcode +from ..utils import oracle, postgis, spatialite class GeographyTest(TestCase): diff --git a/tests/gis_tests/inspectapp/tests.py b/tests/gis_tests/inspectapp/tests.py index 23980f860c31..afd39b6f5c32 100644 --- a/tests/gis_tests/inspectapp/tests.py +++ b/tests/gis_tests/inspectapp/tests.py @@ -11,9 +11,9 @@ from django.test.utils import modify_settings from django.utils.six import StringIO +from .models import AllOGRFields from ..test_data import TEST_DATA from ..utils import postgis -from .models import AllOGRFields class InspectDbTests(TestCase): diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py index 9628fc0170fc..7f2f56996a1c 100644 --- a/tests/gis_tests/rasterapp/test_rasterfield.py +++ b/tests/gis_tests/rasterapp/test_rasterfield.py @@ -11,8 +11,8 @@ from django.db.models import Q from django.test import TransactionTestCase, skipUnlessDBFeature -from ..data.rasters.textrasters import JSON_RASTER from .models import RasterModel, RasterRelatedModel +from ..data.rasters.textrasters import JSON_RASTER @skipUnlessDBFeature('supports_raster') diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py index 024c1b644a1d..35b40e176cf1 100644 --- a/tests/gis_tests/relatedapp/tests.py +++ b/tests/gis_tests/relatedapp/tests.py @@ -8,10 +8,10 @@ from django.test.utils import override_settings from django.utils import timezone -from ..utils import no_oracle from .models import ( Article, Author, Book, City, DirectoryEntry, Event, Location, Parcel, ) +from ..utils import no_oracle class RelatedGeoModelTest(TestCase): diff --git a/tests/template_tests/filter_tests/test_date.py b/tests/template_tests/filter_tests/test_date.py index 4c83068eb8bf..eabb513fcb2b 100644 --- a/tests/template_tests/filter_tests/test_date.py +++ b/tests/template_tests/filter_tests/test_date.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase, override_settings from django.utils import timezone, translation -from ..utils import setup from .timezone_utils import TimezoneTestCase +from ..utils import setup class DateTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_time.py b/tests/template_tests/filter_tests/test_time.py index a05624a0e1f9..a43012d229b9 100644 --- a/tests/template_tests/filter_tests/test_time.py +++ b/tests/template_tests/filter_tests/test_time.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase, override_settings from django.utils import timezone, translation -from ..utils import setup from .timezone_utils import TimezoneTestCase +from ..utils import setup class TimeTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_timesince.py b/tests/template_tests/filter_tests/test_timesince.py index 0cb975a90ed0..44dac55b7ba3 100644 --- a/tests/template_tests/filter_tests/test_timesince.py +++ b/tests/template_tests/filter_tests/test_timesince.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase from django.test.utils import requires_tz_support -from ..utils import setup from .timezone_utils import TimezoneTestCase +from ..utils import setup class TimesinceTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_timeuntil.py b/tests/template_tests/filter_tests/test_timeuntil.py index 1b06a21c937a..c26bcd0f4a49 100644 --- a/tests/template_tests/filter_tests/test_timeuntil.py +++ b/tests/template_tests/filter_tests/test_timeuntil.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase from django.test.utils import requires_tz_support -from ..utils import setup from .timezone_utils import TimezoneTestCase +from ..utils import setup class TimeuntilTests(TimezoneTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py index 552c08b0381c..2f37a9597032 100644 --- a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py +++ b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py @@ -10,8 +10,8 @@ from django.utils.safestring import mark_safe from django.utils.translation import trans_real -from ...utils import setup from .base import MultipleLocaleActivationTestCase, extended_locale_paths, here +from ...utils import setup class I18nBlockTransTagTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_trans.py b/tests/template_tests/syntax_tests/i18n/test_trans.py index dd3860817556..17c4e94f0c3c 100644 --- a/tests/template_tests/syntax_tests/i18n/test_trans.py +++ b/tests/template_tests/syntax_tests/i18n/test_trans.py @@ -8,8 +8,8 @@ from django.utils.safestring import mark_safe from django.utils.translation import trans_real -from ...utils import setup from .base import MultipleLocaleActivationTestCase, extended_locale_paths +from ...utils import setup class I18nTransTagTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py index aed204d63b17..930508c3ac28 100644 --- a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py +++ b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase from django.utils import translation -from ...utils import setup from .base import MultipleLocaleActivationTestCase +from ...utils import setup class MultipleLocaleActivationTests(MultipleLocaleActivationTestCase): diff --git a/tests/template_tests/syntax_tests/test_exceptions.py b/tests/template_tests/syntax_tests/test_exceptions.py index db1e0f9f5f7b..e3645cb13fcb 100644 --- a/tests/template_tests/syntax_tests/test_exceptions.py +++ b/tests/template_tests/syntax_tests/test_exceptions.py @@ -1,8 +1,8 @@ from django.template import TemplateDoesNotExist, TemplateSyntaxError from django.test import SimpleTestCase -from ..utils import setup from .test_extends import inheritance_templates +from ..utils import setup class ExceptionsTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py index 3368522065ab..6f65c85ac1f5 100644 --- a/tests/template_tests/syntax_tests/test_include.py +++ b/tests/template_tests/syntax_tests/test_include.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase, ignore_warnings from django.utils.deprecation import RemovedInDjango21Warning -from ..utils import setup from .test_basic import basic_templates +from ..utils import setup include_fail_templates = { 'include-fail1': '{% load bad_tag %}{% badtag %}', From f13bfdeb559c533ce5ace19a250821e6f8abb13d Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Sun, 3 Mar 2019 19:33:48 +0100 Subject: [PATCH 331/389] [1.11.x] Reverted "Fixed relative paths imports per isort 4.3.5." This reverts commit 463fe11bc8b2d068e447c5df677e7a31c2af7e03 due to restore of relative paths sorting from isort < 4.3.5 in isort 4.3.10. Backport of b435f82939edf70674856e0e1cd63973c2e0a1d1 from master. --- django/contrib/postgres/fields/array.py | 2 +- tests/gis_tests/distapp/tests.py | 2 +- tests/gis_tests/geoapp/test_expressions.py | 2 +- tests/gis_tests/geoapp/test_functions.py | 2 +- tests/gis_tests/geoapp/test_regress.py | 2 +- tests/gis_tests/geoapp/tests.py | 2 +- tests/gis_tests/geogapp/tests.py | 2 +- tests/gis_tests/inspectapp/tests.py | 2 +- tests/gis_tests/rasterapp/test_rasterfield.py | 2 +- tests/gis_tests/relatedapp/tests.py | 2 +- tests/template_tests/filter_tests/test_date.py | 2 +- tests/template_tests/filter_tests/test_time.py | 2 +- tests/template_tests/filter_tests/test_timesince.py | 2 +- tests/template_tests/filter_tests/test_timeuntil.py | 2 +- tests/template_tests/syntax_tests/i18n/test_blocktrans.py | 2 +- tests/template_tests/syntax_tests/i18n/test_trans.py | 2 +- .../template_tests/syntax_tests/i18n/test_underscore_syntax.py | 2 +- tests/template_tests/syntax_tests/test_exceptions.py | 2 +- tests/template_tests/syntax_tests/test_include.py | 2 +- 19 files changed, 19 insertions(+), 19 deletions(-) diff --git a/django/contrib/postgres/fields/array.py b/django/contrib/postgres/fields/array.py index d6848c3f32ac..b70ae37a3af4 100644 --- a/django/contrib/postgres/fields/array.py +++ b/django/contrib/postgres/fields/array.py @@ -9,8 +9,8 @@ from django.utils import six from django.utils.translation import ugettext_lazy as _ -from .utils import AttributeSetter from ..utils import prefix_validation_error +from .utils import AttributeSetter __all__ = ['ArrayField'] diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index 136b1f957f72..4609083f3b41 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -12,11 +12,11 @@ from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from django.utils.deprecation import RemovedInDjango20Warning +from ..utils import no_oracle, oracle, postgis, spatialite from .models import ( AustraliaCity, CensusZipcode, Interstate, SouthTexasCity, SouthTexasCityFt, SouthTexasInterstate, SouthTexasZipcode, ) -from ..utils import no_oracle, oracle, postgis, spatialite class DistanceTest(TestCase): diff --git a/tests/gis_tests/geoapp/test_expressions.py b/tests/gis_tests/geoapp/test_expressions.py index f7568585830d..503b706f0e54 100644 --- a/tests/gis_tests/geoapp/test_expressions.py +++ b/tests/gis_tests/geoapp/test_expressions.py @@ -4,8 +4,8 @@ from django.contrib.gis.geos import Point, Polygon from django.test import TestCase, skipUnlessDBFeature -from .models import City from ..utils import postgis +from .models import City class GeoExpressionsTests(TestCase): diff --git a/tests/gis_tests/geoapp/test_functions.py b/tests/gis_tests/geoapp/test_functions.py index 2428e248a45d..a3ffc40ecd87 100644 --- a/tests/gis_tests/geoapp/test_functions.py +++ b/tests/gis_tests/geoapp/test_functions.py @@ -11,8 +11,8 @@ from django.test import TestCase, skipUnlessDBFeature from django.utils import six -from .models import City, Country, CountryWebMercator, State, Track from ..utils import mysql, oracle, postgis, spatialite +from .models import City, Country, CountryWebMercator, State, Track class GISFunctionsTests(TestCase): diff --git a/tests/gis_tests/geoapp/test_regress.py b/tests/gis_tests/geoapp/test_regress.py index 3b050db54782..a9d160c53422 100644 --- a/tests/gis_tests/geoapp/test_regress.py +++ b/tests/gis_tests/geoapp/test_regress.py @@ -8,8 +8,8 @@ from django.db.models import Count, Min from django.test import TestCase, skipUnlessDBFeature -from .models import City, PennsylvaniaCity, State, Truth from ..utils import no_oracle +from .models import City, PennsylvaniaCity, State, Truth class GeoRegressionTests(TestCase): diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index 183fcd5047d6..b6a5c2d0c851 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -15,11 +15,11 @@ from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning +from ..utils import oracle, postgis, skipUnlessGISLookup, spatialite from .models import ( City, Country, Feature, MinusOneSRID, NonConcreteModel, PennsylvaniaCity, State, Track, ) -from ..utils import oracle, postgis, skipUnlessGISLookup, spatialite class GeoModelTest(TestCase): diff --git a/tests/gis_tests/geogapp/tests.py b/tests/gis_tests/geogapp/tests.py index 72041e805ba1..a23dad3a5390 100644 --- a/tests/gis_tests/geogapp/tests.py +++ b/tests/gis_tests/geogapp/tests.py @@ -17,8 +17,8 @@ from django.utils._os import upath from django.utils.deprecation import RemovedInDjango20Warning -from .models import City, County, Zipcode from ..utils import oracle, postgis, spatialite +from .models import City, County, Zipcode class GeographyTest(TestCase): diff --git a/tests/gis_tests/inspectapp/tests.py b/tests/gis_tests/inspectapp/tests.py index afd39b6f5c32..23980f860c31 100644 --- a/tests/gis_tests/inspectapp/tests.py +++ b/tests/gis_tests/inspectapp/tests.py @@ -11,9 +11,9 @@ from django.test.utils import modify_settings from django.utils.six import StringIO -from .models import AllOGRFields from ..test_data import TEST_DATA from ..utils import postgis +from .models import AllOGRFields class InspectDbTests(TestCase): diff --git a/tests/gis_tests/rasterapp/test_rasterfield.py b/tests/gis_tests/rasterapp/test_rasterfield.py index 7f2f56996a1c..9628fc0170fc 100644 --- a/tests/gis_tests/rasterapp/test_rasterfield.py +++ b/tests/gis_tests/rasterapp/test_rasterfield.py @@ -11,8 +11,8 @@ from django.db.models import Q from django.test import TransactionTestCase, skipUnlessDBFeature -from .models import RasterModel, RasterRelatedModel from ..data.rasters.textrasters import JSON_RASTER +from .models import RasterModel, RasterRelatedModel @skipUnlessDBFeature('supports_raster') diff --git a/tests/gis_tests/relatedapp/tests.py b/tests/gis_tests/relatedapp/tests.py index 35b40e176cf1..024c1b644a1d 100644 --- a/tests/gis_tests/relatedapp/tests.py +++ b/tests/gis_tests/relatedapp/tests.py @@ -8,10 +8,10 @@ from django.test.utils import override_settings from django.utils import timezone +from ..utils import no_oracle from .models import ( Article, Author, Book, City, DirectoryEntry, Event, Location, Parcel, ) -from ..utils import no_oracle class RelatedGeoModelTest(TestCase): diff --git a/tests/template_tests/filter_tests/test_date.py b/tests/template_tests/filter_tests/test_date.py index eabb513fcb2b..4c83068eb8bf 100644 --- a/tests/template_tests/filter_tests/test_date.py +++ b/tests/template_tests/filter_tests/test_date.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase, override_settings from django.utils import timezone, translation -from .timezone_utils import TimezoneTestCase from ..utils import setup +from .timezone_utils import TimezoneTestCase class DateTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_time.py b/tests/template_tests/filter_tests/test_time.py index a43012d229b9..a05624a0e1f9 100644 --- a/tests/template_tests/filter_tests/test_time.py +++ b/tests/template_tests/filter_tests/test_time.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase, override_settings from django.utils import timezone, translation -from .timezone_utils import TimezoneTestCase from ..utils import setup +from .timezone_utils import TimezoneTestCase class TimeTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_timesince.py b/tests/template_tests/filter_tests/test_timesince.py index 44dac55b7ba3..0cb975a90ed0 100644 --- a/tests/template_tests/filter_tests/test_timesince.py +++ b/tests/template_tests/filter_tests/test_timesince.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase from django.test.utils import requires_tz_support -from .timezone_utils import TimezoneTestCase from ..utils import setup +from .timezone_utils import TimezoneTestCase class TimesinceTests(TimezoneTestCase): diff --git a/tests/template_tests/filter_tests/test_timeuntil.py b/tests/template_tests/filter_tests/test_timeuntil.py index c26bcd0f4a49..1b06a21c937a 100644 --- a/tests/template_tests/filter_tests/test_timeuntil.py +++ b/tests/template_tests/filter_tests/test_timeuntil.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase from django.test.utils import requires_tz_support -from .timezone_utils import TimezoneTestCase from ..utils import setup +from .timezone_utils import TimezoneTestCase class TimeuntilTests(TimezoneTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py index 2f37a9597032..552c08b0381c 100644 --- a/tests/template_tests/syntax_tests/i18n/test_blocktrans.py +++ b/tests/template_tests/syntax_tests/i18n/test_blocktrans.py @@ -10,8 +10,8 @@ from django.utils.safestring import mark_safe from django.utils.translation import trans_real -from .base import MultipleLocaleActivationTestCase, extended_locale_paths, here from ...utils import setup +from .base import MultipleLocaleActivationTestCase, extended_locale_paths, here class I18nBlockTransTagTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_trans.py b/tests/template_tests/syntax_tests/i18n/test_trans.py index 17c4e94f0c3c..dd3860817556 100644 --- a/tests/template_tests/syntax_tests/i18n/test_trans.py +++ b/tests/template_tests/syntax_tests/i18n/test_trans.py @@ -8,8 +8,8 @@ from django.utils.safestring import mark_safe from django.utils.translation import trans_real -from .base import MultipleLocaleActivationTestCase, extended_locale_paths from ...utils import setup +from .base import MultipleLocaleActivationTestCase, extended_locale_paths class I18nTransTagTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py index 930508c3ac28..aed204d63b17 100644 --- a/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py +++ b/tests/template_tests/syntax_tests/i18n/test_underscore_syntax.py @@ -4,8 +4,8 @@ from django.test import SimpleTestCase from django.utils import translation -from .base import MultipleLocaleActivationTestCase from ...utils import setup +from .base import MultipleLocaleActivationTestCase class MultipleLocaleActivationTests(MultipleLocaleActivationTestCase): diff --git a/tests/template_tests/syntax_tests/test_exceptions.py b/tests/template_tests/syntax_tests/test_exceptions.py index e3645cb13fcb..db1e0f9f5f7b 100644 --- a/tests/template_tests/syntax_tests/test_exceptions.py +++ b/tests/template_tests/syntax_tests/test_exceptions.py @@ -1,8 +1,8 @@ from django.template import TemplateDoesNotExist, TemplateSyntaxError from django.test import SimpleTestCase -from .test_extends import inheritance_templates from ..utils import setup +from .test_extends import inheritance_templates class ExceptionsTests(SimpleTestCase): diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py index 6f65c85ac1f5..3368522065ab 100644 --- a/tests/template_tests/syntax_tests/test_include.py +++ b/tests/template_tests/syntax_tests/test_include.py @@ -6,8 +6,8 @@ from django.test import SimpleTestCase, ignore_warnings from django.utils.deprecation import RemovedInDjango21Warning -from .test_basic import basic_templates from ..utils import setup +from .test_basic import basic_templates include_fail_templates = { 'include-fail1': '{% load bad_tag %}{% badtag %}', From f8ce3cd1622e974aeb88691cd5761db096513d7d Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 13 Mar 2019 20:06:47 +0100 Subject: [PATCH 332/389] [1.11.x] Fixed serializers tests for PyYAML 5.1+. Backport of a57c783dd4e6dc73847081221827a1902eede88b from master --- tests/serializers/test_yaml.py | 4 +++- tests/timezones/tests.py | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py index 0b4b9b00d1b3..aa38ab31d9da 100644 --- a/tests/serializers/test_yaml.py +++ b/tests/serializers/test_yaml.py @@ -119,7 +119,9 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase): author: %(author_pk)s headline: Poker has no place on ESPN pub_date: 2006-06-16 11:00:00 - categories: [%(first_category_pk)s, %(second_category_pk)s] + categories:""" + ( + ' [%(first_category_pk)s, %(second_category_pk)s]' if yaml.__version__ < '5.1' + else '\n - %(first_category_pk)s\n - %(second_category_pk)s') + """ meta_data: [] """ diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index 49247294f854..d1a63c67ec2c 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -700,7 +700,7 @@ def test_naive_datetime(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt, dt) @@ -724,7 +724,7 @@ def test_naive_datetime_with_microsecond(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30.405060") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt, dt) @@ -748,7 +748,7 @@ def test_aware_datetime_with_microsecond(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) @@ -772,7 +772,7 @@ def test_aware_datetime_in_utc(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 10:20:30+00:00") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) @@ -796,7 +796,7 @@ def test_aware_datetime_in_local_timezone(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) @@ -820,7 +820,7 @@ def test_aware_datetime_in_other_timezone(self): self.assertEqual(obj.dt, dt) if not isinstance(serializers.get_serializer('yaml'), serializers.BadSerializer): - data = serializers.serialize('yaml', [Event(dt=dt)]) + data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00") obj = next(serializers.deserialize('yaml', data)).object self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) From 9530fac978073af8b3c6b374b588873d5c18d2a4 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Wed, 20 Mar 2019 09:07:49 -0400 Subject: [PATCH 333/389] [1.11.x] Fixed serializers test crash if PyYAML isn't installed. Follow up to a57c783dd4e6dc73847081221827a1902eede88b. Backport of 55490ac7469a3647ce163bee323f7fe4a06fcaa6 from master --- tests/serializers/test_yaml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/serializers/test_yaml.py b/tests/serializers/test_yaml.py index aa38ab31d9da..e88505dbb6ba 100644 --- a/tests/serializers/test_yaml.py +++ b/tests/serializers/test_yaml.py @@ -120,7 +120,7 @@ class YamlSerializerTestCase(SerializersTestBase, TestCase): headline: Poker has no place on ESPN pub_date: 2006-06-16 11:00:00 categories:""" + ( - ' [%(first_category_pk)s, %(second_category_pk)s]' if yaml.__version__ < '5.1' + ' [%(first_category_pk)s, %(second_category_pk)s]' if HAS_YAML and yaml.__version__ < '5.1' else '\n - %(first_category_pk)s\n - %(second_category_pk)s') + """ meta_data: [] """ From 22c0564193dd8dbd2e6cbfba1cd2b11d334bf172 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Thu, 21 Mar 2019 10:04:15 -0400 Subject: [PATCH 334/389] [1.11.x] Fixed #30277 -- Fixed broken links to packaging.python.org. Backport of 8f1cc7e9e61758475ddd6586e0fede4af1ca0e8d from master. --- docs/intro/reusable-apps.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index bace43cb3144..a501efed2312 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -258,7 +258,8 @@ this. For a small app like polls, this process isn't too difficult. new package, ``django-polls-0.1.tar.gz``. For more information on packaging, see Python's `Tutorial on Packaging and -Distributing Projects `_. +Distributing Projects +`_. Using your own package ====================== @@ -304,7 +305,7 @@ the world! If this wasn't just an example, you could now: * Post the package on a public repository, such as `the Python Package Index (PyPI)`_. `packaging.python.org `_ has `a good - tutorial `_ + tutorial `_ for doing this. Installing Python packages with virtualenv From d13490c18a075799aec1951ee7e260876b5d1ba6 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 5 Apr 2019 12:06:04 +0200 Subject: [PATCH 335/389] [1.11.x] Refs #30331 -- Doc'd that psycopg2 < 2.8 is required. Backport of 0a8617a5b1cac7063f30e4d8ff4ea4c30748f7b8 from stable/2.1.x. --- docs/ref/databases.txt | 4 ++-- tests/requirements/postgres.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 67307a9bc881..02eb86d474b4 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -92,8 +92,8 @@ below for information on how to set up your database correctly. PostgreSQL notes ================ -Django supports PostgreSQL 9.3 and higher. `psycopg2`_ 2.5.4 or higher is -required, though the latest release is recommended. +Django supports PostgreSQL 9.3 and higher. `psycopg2`_ 2.5.4 through 2.7.7 is +required, though the 2.7.7 is recommended. .. _psycopg2: http://initd.org/psycopg/ diff --git a/tests/requirements/postgres.txt b/tests/requirements/postgres.txt index 820d85bb44df..86cf59f3393f 100644 --- a/tests/requirements/postgres.txt +++ b/tests/requirements/postgres.txt @@ -1 +1 @@ -psycopg2-binary>=2.5.4 +psycopg2-binary>=2.5.4,<2.8 From 331d76528154407927f88759c9e520494e29b914 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Sat, 11 Feb 2017 13:39:35 -0500 Subject: [PATCH 336/389] [1.11.x] Refs #27807 -- Removed docs for User.username_validator. The new override functionality claimed in refs #21379 doesn't work. Forwardport of 714fdbaa7048c2321f6238d9421137c33d9af7cc from stable/1.10.x. --- docs/ref/contrib/auth.txt | 24 +----------------------- docs/releases/1.10.txt | 9 ++++----- 2 files changed, 5 insertions(+), 28 deletions(-) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index b503b02455a8..c557e8fe7a0b 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -38,8 +38,7 @@ Fields usernames. Although it wasn't a deliberate choice, Unicode characters have always been accepted when using Python 3. Django 1.10 officially added Unicode support in usernames, keeping the - ASCII-only behavior on Python 2, with the option to customize the - behavior using :attr:`.User.username_validator`. + ASCII-only behavior on Python 2. .. versionchanged:: 1.10 @@ -167,27 +166,6 @@ Attributes In older versions, this was a method. Backwards-compatibility support for using it as a method will be removed in Django 2.0. - .. attribute:: username_validator - - .. versionadded:: 1.10 - - Points to a validator instance used to validate usernames. Defaults to - :class:`validators.UnicodeUsernameValidator` on Python 3 and - :class:`validators.ASCIIUsernameValidator` on Python 2. - - To change the default username validator, you can subclass the ``User`` - model and set this attribute to a different validator instance. For - example, to use ASCII usernames on Python 3:: - - from django.contrib.auth.models import User - from django.contrib.auth.validators import ASCIIUsernameValidator - - class CustomUser(User): - username_validator = ASCIIUsernameValidator() - - class Meta: - proxy = True # If no new field is added. - Methods ------- diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 8ce6581a8507..3b7ca01468ec 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -60,12 +60,11 @@ wasn't a deliberate choice, Unicode characters have always been accepted when using Python 3. The username validator now explicitly accepts Unicode characters by -default on Python 3 only. This default behavior can be overridden by changing -the :attr:`~django.contrib.auth.models.User.username_validator` attribute of -the ``User`` model, or to any proxy of that model, using either +default on Python 3 only. + +Custom user models may use the new :class:`~django.contrib.auth.validators.ASCIIUsernameValidator` or -:class:`~django.contrib.auth.validators.UnicodeUsernameValidator`. Custom user -models may also use those validators. +:class:`~django.contrib.auth.validators.UnicodeUsernameValidator`. Minor features -------------- From 4b3716e6540e06874b9053b59e39cd76fb48b677 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 27 May 2019 09:37:10 +0200 Subject: [PATCH 337/389] [1.11.x] Added stub release notes for security releases. Backport of 98c0fe19ee2cba9726708ac9336e1dc0d43cca69 from master --- docs/releases/1.11.21.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/1.11.21.txt diff --git a/docs/releases/1.11.21.txt b/docs/releases/1.11.21.txt new file mode 100644 index 000000000000..75d3599c2ac0 --- /dev/null +++ b/docs/releases/1.11.21.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.21 release notes +============================ + +*June 3, 2019* + +Django 1.11.21 fixes security issues in 1.11.20. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index e2da7c3de6a5..c2e8503102ff 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.21 1.11.20 1.11.19 1.11.18 From c238701859a52d584f349cce15d56c8e8137c52b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 27 May 2019 12:10:02 +0200 Subject: [PATCH 338/389] [1.11.x] Fixed CVE-2019-12308 -- Made AdminURLFieldWidget validate URL before rendering clickable link. Backport of deeba6d92006999fee9adfbd8be79bf0a59e8008 from master. --- .../admin/templates/admin/widgets/url.html | 2 +- django/contrib/admin/widgets.py | 10 ++++++++- docs/releases/1.11.21.txt | 16 +++++++++++++- tests/admin_widgets/tests.py | 22 ++++++++++++------- 4 files changed, 39 insertions(+), 11 deletions(-) diff --git a/django/contrib/admin/templates/admin/widgets/url.html b/django/contrib/admin/templates/admin/widgets/url.html index 554a9343feaf..629a740664c9 100644 --- a/django/contrib/admin/templates/admin/widgets/url.html +++ b/django/contrib/admin/templates/admin/widgets/url.html @@ -1 +1 @@ -{% if widget.value %}

      {{ current_label }} {{ widget.value }}
      {{ change_label }} {% endif %}{% include "django/forms/widgets/input.html" %}{% if widget.value %}

      {% endif %} +{% if url_valid %}

      {{ current_label }} {{ widget.value }}
      {{ change_label }} {% endif %}{% include "django/forms/widgets/input.html" %}{% if url_valid %}

      {% endif %} diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py index 62da6265b6d0..209e0289e3cb 100644 --- a/django/contrib/admin/widgets.py +++ b/django/contrib/admin/widgets.py @@ -7,6 +7,7 @@ from django import forms from django.core.exceptions import ValidationError +from django.core.validators import URLValidator from django.db.models.deletion import CASCADE from django.urls import reverse from django.urls.exceptions import NoReverseMatch @@ -339,17 +340,24 @@ def __init__(self, attrs=None): class AdminURLFieldWidget(forms.URLInput): template_name = 'admin/widgets/url.html' - def __init__(self, attrs=None): + def __init__(self, attrs=None, validator_class=URLValidator): final_attrs = {'class': 'vURLField'} if attrs is not None: final_attrs.update(attrs) super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) + self.validator = validator_class() def get_context(self, name, value, attrs): + try: + self.validator(value if value else '') + url_valid = True + except ValidationError: + url_valid = False context = super(AdminURLFieldWidget, self).get_context(name, value, attrs) context['current_label'] = _('Currently:') context['change_label'] = _('Change:') context['widget']['href'] = smart_urlquote(context['widget']['value']) if value else '' + context['url_valid'] = url_valid return context diff --git a/docs/releases/1.11.21.txt b/docs/releases/1.11.21.txt index 75d3599c2ac0..3da7a78612e9 100644 --- a/docs/releases/1.11.21.txt +++ b/docs/releases/1.11.21.txt @@ -4,4 +4,18 @@ Django 1.11.21 release notes *June 3, 2019* -Django 1.11.21 fixes security issues in 1.11.20. +Django 1.11.21 fixes a security issue in 1.11.20. + +CVE-2019-12308: AdminURLFieldWidget XSS +--------------------------------------- + +The clickable "Current URL" link generated by ``AdminURLFieldWidget`` displayed +the provided value without validating it as a safe URL. Thus, an unvalidated +value stored in the database, or a value provided as a URL query parameter +payload, could result in an clickable JavaScript link. + +``AdminURLFieldWidget`` now validates the provided value using +:class:`~django.core.validators.URLValidator` before displaying the clickable +link. You may customise the validator by passing a ``validator_class`` kwarg to +``AdminURLFieldWidget.__init__()``, e.g. when using +:attr:`~django.contrib.admin.ModelAdmin.formfield_overrides`. diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py index 4d78d5fd5bd4..96fbfc12523a 100644 --- a/tests/admin_widgets/tests.py +++ b/tests/admin_widgets/tests.py @@ -336,6 +336,12 @@ def test_localization(self): class AdminURLWidgetTest(SimpleTestCase): + def test_get_context_validates_url(self): + w = widgets.AdminURLFieldWidget() + for invalid in ['', '/not/a/full/url/', 'javascript:alert("Danger XSS!")']: + self.assertFalse(w.get_context('name', invalid, {})['url_valid']) + self.assertTrue(w.get_context('name', 'http://example.com', {})['url_valid']) + def test_render(self): w = widgets.AdminURLFieldWidget() self.assertHTMLEqual( @@ -369,31 +375,31 @@ def test_render_quoting(self): VALUE_RE = re.compile('value="([^"]+)"') TEXT_RE = re.compile(']+>([^>]+)') w = widgets.AdminURLFieldWidget() - output = w.render('test', 'http://example.com/some text') + output = w.render('test', 'http://example.com/some-text') self.assertEqual( HREF_RE.search(output).groups()[0], - 'http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E', + 'http://example.com/%3Csometag%3Esome-text%3C/sometag%3E', ) self.assertEqual( TEXT_RE.search(output).groups()[0], - 'http://example.com/<sometag>some text</sometag>', + 'http://example.com/<sometag>some-text</sometag>', ) self.assertEqual( VALUE_RE.search(output).groups()[0], - 'http://example.com/<sometag>some text</sometag>', + 'http://example.com/<sometag>some-text</sometag>', ) - output = w.render('test', 'http://example-äüö.com/some text') + output = w.render('test', 'http://example-äüö.com/some-text') self.assertEqual( HREF_RE.search(output).groups()[0], - 'http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E', + 'http://xn--example--7za4pnc.com/%3Csometag%3Esome-text%3C/sometag%3E', ) self.assertEqual( TEXT_RE.search(output).groups()[0], - 'http://example-äüö.com/<sometag>some text</sometag>', + 'http://example-äüö.com/<sometag>some-text</sometag>', ) self.assertEqual( VALUE_RE.search(output).groups()[0], - 'http://example-äüö.com/<sometag>some text</sometag>', + 'http://example-äüö.com/<sometag>some-text</sometag>', ) output = w.render('test', 'http://www.example.com/%C3%A4">"') self.assertEqual( From bc1f79d0a01d085500aa82ff29800403291a91a4 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Jun 2019 11:48:10 +0200 Subject: [PATCH 339/389] [1.11.x] Bumped version for 1.11.21 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index ca628f5df2b2..cf2707d2ed84 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 21, 'alpha', 0) +VERSION = (1, 11, 21, 'final', 0) __version__ = get_version(VERSION) From 2f67c8e70b54f268f551a621c703c2161cdf7702 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Jun 2019 11:52:57 +0200 Subject: [PATCH 340/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index cf2707d2ed84..3dbec73181e4 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 21, 'final', 0) +VERSION = (1, 11, 22, 'alpha', 0) __version__ = get_version(VERSION) From a07ce0e25e204e3966a2b704c70dd5d87d63dca5 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 3 Jun 2019 13:16:29 +0200 Subject: [PATCH 341/389] [1.11.x] Fixed typo in 1.11.21 release notes. Backport of 100ec901aebebe56b61f101af38a228414098dd5 from master. --- docs/releases/1.11.21.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.21.txt b/docs/releases/1.11.21.txt index 3da7a78612e9..f670be285b39 100644 --- a/docs/releases/1.11.21.txt +++ b/docs/releases/1.11.21.txt @@ -16,6 +16,6 @@ payload, could result in an clickable JavaScript link. ``AdminURLFieldWidget`` now validates the provided value using :class:`~django.core.validators.URLValidator` before displaying the clickable -link. You may customise the validator by passing a ``validator_class`` kwarg to +link. You may customize the validator by passing a ``validator_class`` kwarg to ``AdminURLFieldWidget.__init__()``, e.g. when using :attr:`~django.contrib.admin.ModelAdmin.formfield_overrides`. From 9f8bed5bdf5b5ea85d663105e1637ac2bffd87a1 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Mon, 3 Jun 2019 20:15:59 +0100 Subject: [PATCH 342/389] [1.11.x] Added CVE-2019-11358 to the security release archive. Backport of 8fb0ea55830321852a4a051a478f78e24d4f6889 from master --- docs/releases/security.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index cce666ce99ae..e318c62bf79e 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -936,3 +936,15 @@ Versions affected * Django 2.0 :commit:`(patch <1f42f82566c9d2d73aff1c42790d6b1b243f7676>` and :commit:`correction) <392e040647403fc8007708d52ce01d915b014849>` * Django 1.11 :commit:`(patch) <0bbb560183fabf0533289700845dafa94951f227>` + +June 3, 2019 - :cve:`2019-11358` +-------------------------------- + +Prototype pollution in bundled jQuery. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) ` +* Django 2.1 :commit:`(patch) <95649bc08547a878cebfa1d019edec8cb1b80829>` From 341f44448c899719f123699fb014725802a650af Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Mon, 3 Jun 2019 20:17:39 +0100 Subject: [PATCH 343/389] [1.11.x] Added CVE-2019-12308 to the security release archive. Backport of 21b1d239125f1228e579b1ce8d94d4d5feadd2a6 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index e318c62bf79e..d26669d63aed 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -948,3 +948,16 @@ Versions affected * Django 2.2 :commit:`(patch) ` * Django 2.1 :commit:`(patch) <95649bc08547a878cebfa1d019edec8cb1b80829>` + +June 3, 2019 - :cve:`2019-12308` +-------------------------------- + +XSS via "Current URL" link generated by ``AdminURLFieldWidget``. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) ` +* Django 2.1 :commit:`(patch) <09186a13d975de6d049f8b3e05484f66b01ece62>` +* Django 1.11 :commit:`(patch) ` From 790696836fed1302c67fd091e0d23466b9bb182b Mon Sep 17 00:00:00 2001 From: Markus Holtermann Date: Fri, 21 Jun 2019 17:57:36 +0200 Subject: [PATCH 344/389] [1.11.x] Bumped minimum ESLint version to 4.18.2. Backport of ad7b438002f1ab2a0ccb321012182991737ea84e from master. --- .../admin/static/admin/js/SelectBox.js | 4 +- .../contrib/admin/static/admin/js/actions.js | 98 +++++++++---------- .../admin/js/admin/DateTimeShortcuts.js | 8 +- django/contrib/admin/static/admin/js/core.js | 30 +++--- .../contrib/admin/static/admin/js/inlines.js | 28 +++--- .../contrib/admin/static/admin/js/urlify.js | 12 +-- .../contrib/gis/static/gis/js/OLMapWidget.js | 18 ++-- js_tests/admin/inlines.test.js | 2 +- package.json | 2 +- 9 files changed, 101 insertions(+), 101 deletions(-) diff --git a/django/contrib/admin/static/admin/js/SelectBox.js b/django/contrib/admin/static/admin/js/SelectBox.js index 1a14959bcada..2073f03dd819 100644 --- a/django/contrib/admin/static/admin/js/SelectBox.js +++ b/django/contrib/admin/static/admin/js/SelectBox.js @@ -19,7 +19,7 @@ var box = document.getElementById(id); var node; $(box).empty(); // clear all options - var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag + var new_options = box.outerHTML.slice(0, -9); // grab just the opening tag var cache = SelectBox.cache[id]; for (var i = 0, j = cache.length; i < j; i++) { node = cache[i]; @@ -48,7 +48,7 @@ token = tokens[k]; if (node_text.indexOf(token) === -1) { node.displayed = 0; - break; // Once the first token isn't found we're done + break; // Once the first token isn't found we're done } } } diff --git a/django/contrib/admin/static/admin/js/actions.js b/django/contrib/admin/static/admin/js/actions.js index 7041701f271b..0901bb54bbd3 100644 --- a/django/contrib/admin/static/admin/js/actions.js +++ b/django/contrib/admin/static/admin/js/actions.js @@ -8,59 +8,59 @@ var actionCheckboxes = $(this); var list_editable_changed = false; var showQuestion = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).show(); - $(options.allContainer).hide(); - }, - showClear = function() { - $(options.acrossClears).show(); - $(options.acrossQuestions).hide(); - $(options.actionContainer).toggleClass(options.selectedClass); - $(options.allContainer).show(); - $(options.counterContainer).hide(); - }, - reset = function() { - $(options.acrossClears).hide(); - $(options.acrossQuestions).hide(); - $(options.allContainer).hide(); - $(options.counterContainer).show(); - }, - clearAcross = function() { - reset(); - $(options.acrossInput).val(0); - $(options.actionContainer).removeClass(options.selectedClass); - }, - checker = function(checked) { - if (checked) { - showQuestion(); - } else { + $(options.acrossClears).hide(); + $(options.acrossQuestions).show(); + $(options.allContainer).hide(); + }, + showClear = function() { + $(options.acrossClears).show(); + $(options.acrossQuestions).hide(); + $(options.actionContainer).toggleClass(options.selectedClass); + $(options.allContainer).show(); + $(options.counterContainer).hide(); + }, + reset = function() { + $(options.acrossClears).hide(); + $(options.acrossQuestions).hide(); + $(options.allContainer).hide(); + $(options.counterContainer).show(); + }, + clearAcross = function() { reset(); - } - $(actionCheckboxes).prop("checked", checked) - .parent().parent().toggleClass(options.selectedClass, checked); - }, - updateCounter = function() { - var sel = $(actionCheckboxes).filter(":checked").length; - // data-actions-icnt is defined in the generated HTML - // and contains the total amount of objects in the queryset - var actions_icnt = $('.action-counter').data('actionsIcnt'); - $(options.counterContainer).html(interpolate( - ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { - sel: sel, - cnt: actions_icnt - }, true)); - $(options.allToggle).prop("checked", function() { - var value; - if (sel === actionCheckboxes.length) { - value = true; + $(options.acrossInput).val(0); + $(options.actionContainer).removeClass(options.selectedClass); + }, + checker = function(checked) { + if (checked) { showQuestion(); } else { - value = false; - clearAcross(); + reset(); } - return value; - }); - }; + $(actionCheckboxes).prop("checked", checked) + .parent().parent().toggleClass(options.selectedClass, checked); + }, + updateCounter = function() { + var sel = $(actionCheckboxes).filter(":checked").length; + // data-actions-icnt is defined in the generated HTML + // and contains the total amount of objects in the queryset + var actions_icnt = $('.action-counter').data('actionsIcnt'); + $(options.counterContainer).html(interpolate( + ngettext('%(sel)s of %(cnt)s selected', '%(sel)s of %(cnt)s selected', sel), { + sel: sel, + cnt: actions_icnt + }, true)); + $(options.allToggle).prop("checked", function() { + var value; + if (sel === actionCheckboxes.length) { + value = true; + showQuestion(); + } else { + value = false; + clearAcross(); + } + return value; + }); + }; // Show counter by default $(options.counterContainer).show(); // Check state of checkboxes and reinit state if needed diff --git a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js index ce865936543c..38d578b8ec7a 100644 --- a/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js +++ b/django/contrib/admin/static/admin/js/admin/DateTimeShortcuts.js @@ -11,10 +11,10 @@ dismissClockFunc: [], dismissCalendarFunc: [], calendarDivName1: 'calendarbox', // name of calendar
      that gets toggled - calendarDivName2: 'calendarin', // name of
      that contains calendar - calendarLinkName: 'calendarlink',// name of the link that is used to toggle - clockDivName: 'clockbox', // name of clock
      that gets toggled - clockLinkName: 'clocklink', // name of the link that is used to toggle + calendarDivName2: 'calendarin', // name of
      that contains calendar + calendarLinkName: 'calendarlink', // name of the link that is used to toggle + clockDivName: 'clockbox', // name of clock
      that gets toggled + clockLinkName: 'clocklink', // name of the link that is used to toggle shortCutsClass: 'datetimeshortcuts', // class of the clock and cal shortcuts timezoneWarningClass: 'timezonewarning', // class of the warning for timezone mismatch timezoneOffset: 0, diff --git a/django/contrib/admin/static/admin/js/core.js b/django/contrib/admin/static/admin/js/core.js index edccdc0217dc..2e0ad6b84cae 100644 --- a/django/contrib/admin/static/admin/js/core.js +++ b/django/contrib/admin/static/admin/js/core.js @@ -191,9 +191,9 @@ function findPosY(obj) { return result; }; -// ---------------------------------------------------------------------------- -// String object extensions -// ---------------------------------------------------------------------------- + // ---------------------------------------------------------------------------- + // String object extensions + // ---------------------------------------------------------------------------- String.prototype.pad_left = function(pad_length, pad_string) { var new_string = this; for (var i = 0; new_string.length < pad_length; i++) { @@ -209,18 +209,18 @@ function findPosY(obj) { var day, month, year; while (i < split_format.length) { switch (split_format[i]) { - case "%d": - day = date[i]; - break; - case "%m": - month = date[i] - 1; - break; - case "%Y": - year = date[i]; - break; - case "%y": - year = date[i]; - break; + case "%d": + day = date[i]; + break; + case "%m": + month = date[i] - 1; + break; + case "%Y": + year = date[i]; + break; + case "%y": + year = date[i]; + break; } ++i; } diff --git a/django/contrib/admin/static/admin/js/inlines.js b/django/contrib/admin/static/admin/js/inlines.js index 4e9bb77e9c45..669fb02423ce 100644 --- a/django/contrib/admin/static/admin/js/inlines.js +++ b/django/contrib/admin/static/admin/js/inlines.js @@ -63,8 +63,8 @@ var template = $("#" + options.prefix + "-empty"); var row = template.clone(true); row.removeClass(options.emptyCssClass) - .addClass(options.formCssClass) - .attr("id", options.prefix + "-" + nextIndex); + .addClass(options.formCssClass) + .attr("id", options.prefix + "-" + nextIndex); if (row.is("tr")) { // If the forms are laid out in table rows, insert // the remove button into the last table cell: @@ -131,16 +131,16 @@ /* Setup plugin defaults */ $.fn.formset.defaults = { - prefix: "form", // The form prefix for your django formset - addText: "add another", // Text for the add link - deleteText: "remove", // Text for the delete link - addCssClass: "add-row", // CSS class applied to the add link - deleteCssClass: "delete-row", // CSS class applied to the delete link - emptyCssClass: "empty-row", // CSS class applied to the empty row - formCssClass: "dynamic-form", // CSS class applied to each form in a formset - added: null, // Function called each time a new form is added - removed: null, // Function called each time a form is deleted - addButton: null // Existing add button to use + prefix: "form", // The form prefix for your django formset + addText: "add another", // Text for the add link + deleteText: "remove", // Text for the delete link + addCssClass: "add-row", // CSS class applied to the add link + deleteCssClass: "delete-row", // CSS class applied to the delete link + emptyCssClass: "empty-row", // CSS class applied to the empty row + formCssClass: "dynamic-form", // CSS class applied to each form in a formset + added: null, // Function called each time a new form is added + removed: null, // Function called each time a form is deleted + addButton: null // Existing add button to use }; @@ -149,8 +149,8 @@ var $rows = $(this); var alternatingRows = function(row) { $($rows.selector).not(".add-row").removeClass("row1 row2") - .filter(":even").addClass("row1").end() - .filter(":odd").addClass("row2"); + .filter(":even").addClass("row1").end() + .filter(":odd").addClass("row2"); }; var reinitDateTimeShortCuts = function() { diff --git a/django/contrib/admin/static/admin/js/urlify.js b/django/contrib/admin/static/admin/js/urlify.js index 9dcbc82d13a7..7003565b0fb2 100644 --- a/django/contrib/admin/static/admin/js/urlify.js +++ b/django/contrib/admin/static/admin/js/urlify.js @@ -119,7 +119,7 @@ var Downcoder = { 'Initialize': function() { - if (Downcoder.map) { // already made + if (Downcoder.map) { // already made return; } Downcoder.map = {}; @@ -168,12 +168,12 @@ // characters, whitespace, and dash; remove other characters. s = XRegExp.replace(s, XRegExp('[^-_\\p{L}\\p{N}\\s]', 'g'), ''); } else { - s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars + s = s.replace(/[^-\w\s]/g, ''); // remove unneeded chars } - s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces - s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens - s = s.toLowerCase(); // convert to lowercase - return s.substring(0, num_chars); // trim to first num_chars chars + s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces + s = s.replace(/[-\s]+/g, '-'); // convert spaces to hyphens + s = s.toLowerCase(); // convert to lowercase + return s.substring(0, num_chars); // trim to first num_chars chars } window.URLify = URLify; })(); diff --git a/django/contrib/gis/static/gis/js/OLMapWidget.js b/django/contrib/gis/static/gis/js/OLMapWidget.js index 4cd98e2f1aae..dae97e1f8753 100644 --- a/django/contrib/gis/static/gis/js/OLMapWidget.js +++ b/django/contrib/gis/static/gis/js/OLMapWidget.js @@ -207,15 +207,15 @@ ol.inherits(GeometryTypeControl, ol.control.Control); } else { geometry = features[0].getGeometry().clone(); for (var j = 1; j < features.length; j++) { - switch(geometry.getType()) { - case "MultiPoint": - geometry.appendPoint(features[j].getGeometry().getPoint(0)); - break; - case "MultiLineString": - geometry.appendLineString(features[j].getGeometry().getLineString(0)); - break; - case "MultiPolygon": - geometry.appendPolygon(features[j].getGeometry().getPolygon(0)); + switch (geometry.getType()) { + case "MultiPoint": + geometry.appendPoint(features[j].getGeometry().getPoint(0)); + break; + case "MultiLineString": + geometry.appendLineString(features[j].getGeometry().getLineString(0)); + break; + case "MultiPolygon": + geometry.appendPolygon(features[j].getGeometry().getPolygon(0)); } } } diff --git a/js_tests/admin/inlines.test.js b/js_tests/admin/inlines.test.js index 2ff6a1457793..794edc638ca1 100644 --- a/js_tests/admin/inlines.test.js +++ b/js_tests/admin/inlines.test.js @@ -54,7 +54,7 @@ QUnit.test('add/remove form events', function(assert) { QUnit.test('existing add button', function(assert) { var $ = django.jQuery; - $('#qunit-fixture').empty(); // Clear the table added in beforeEach + $('#qunit-fixture').empty(); // Clear the table added in beforeEach $('#qunit-fixture').append($('#tabular-formset').text()); this.table = $('table.inline'); this.inlineRow = this.table.find('tr'); diff --git a/package.json b/package.json index 77a47a874721..8a3853ff9fab 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "npm": ">=1.3.0 <3.0.0" }, "devDependencies": { - "eslint": "^0.22.1", + "eslint": "^4.18.2", "grunt": "^1.0.1", "grunt-cli": "^1.2.0", "grunt-contrib-qunit": "^1.2.0" From bc5febec4ed3d3008c31dc0f4eb72e9f02f00781 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 27 Jun 2019 15:09:28 +0200 Subject: [PATCH 345/389] [1.11.x] Fixed GeoIPTest.test04_city() failure with the latest GeoIP2 database. Backport of 4305fbe8b11f44ab5d6759346488026c1e9677b2 from master. --- tests/gis_tests/test_geoip2.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/gis_tests/test_geoip2.py b/tests/gis_tests/test_geoip2.py index b15d0fc6eeb7..38f80952d2fa 100644 --- a/tests/gis_tests/test_geoip2.py +++ b/tests/gis_tests/test_geoip2.py @@ -24,7 +24,7 @@ "GeoIP is required along with the GEOIP_PATH setting." ) class GeoIPTest(unittest.TestCase): - addr = '128.249.1.1' + addr = '75.41.39.1' fqdn = 'tmc.edu' def test01_init(self): @@ -99,7 +99,7 @@ def test03_country(self, gethostbyname): @mock.patch('socket.gethostbyname') def test04_city(self, gethostbyname): "GeoIP city querying methods." - gethostbyname.return_value = '128.249.1.1' + gethostbyname.return_value = '75.41.39.1' g = GeoIP2(country='') for query in (self.fqdn, self.addr): @@ -122,7 +122,7 @@ def test04_city(self, gethostbyname): # City information dictionary. d = g.city(query) self.assertEqual('US', d['country_code']) - self.assertEqual('Houston', d['city']) + self.assertEqual('Dallas', d['city']) self.assertEqual('TX', d['region']) geom = g.geos(query) From 58553bb297aaef83009f6e36d889e17c4160d397 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 20 Jun 2019 10:45:38 +0200 Subject: [PATCH 346/389] [1.11.x] Added stub release notes for security releases. Backport of 30b3ee9d0b33bb440f9c73d1ce9e0e7303887a9f from master --- docs/releases/1.11.22.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/1.11.22.txt diff --git a/docs/releases/1.11.22.txt b/docs/releases/1.11.22.txt new file mode 100644 index 000000000000..91d81890dfa4 --- /dev/null +++ b/docs/releases/1.11.22.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.22 release notes +============================ + +*July 1, 2019* + +Django 1.11.22 fixes a security issue in 1.11.21. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c2e8503102ff..e00804d651ed 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.22 1.11.21 1.11.20 1.11.19 From 32124fc41e75074141b05f10fc55a4f01ff7f050 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 13 Jun 2019 10:57:29 +0200 Subject: [PATCH 347/389] [1.11.x] Fixed CVE-2019-12781 -- Made HttpRequest always trust SECURE_PROXY_SSL_HEADER if set. An HTTP request would not be redirected to HTTPS when the SECURE_PROXY_SSL_HEADER and SECURE_SSL_REDIRECT settings were used if the proxy connected to Django via HTTPS. HttpRequest.scheme will now always trust the SECURE_PROXY_SSL_HEADER if set, rather than falling back to the request scheme when the SECURE_PROXY_SSL_HEADER did not have the secure value. Thanks to Gavin Wahl for the report and initial patch suggestion, and Shai Berger for review. Backport of 54d0f5e62f54c29a12dd96f44bacd810cbe03ac8 from master. --- django/http/request.py | 7 ++++--- docs/ref/settings.txt | 11 +++++++---- docs/releases/1.11.22.txt | 20 ++++++++++++++++++++ tests/settings_tests/tests.py | 12 ++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/django/http/request.py b/django/http/request.py index 9ffcd23fbd0a..b573cdb180b7 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -199,13 +199,14 @@ def _get_scheme(self): def scheme(self): if settings.SECURE_PROXY_SSL_HEADER: try: - header, value = settings.SECURE_PROXY_SSL_HEADER + header, secure_value = settings.SECURE_PROXY_SSL_HEADER except ValueError: raise ImproperlyConfigured( 'The SECURE_PROXY_SSL_HEADER setting must be a tuple containing two values.' ) - if self.META.get(header) == value: - return 'https' + header_value = self.META.get(header) + if header_value is not None: + return 'https' if header_value == secure_value else 'http' return self._get_scheme() def is_secure(self): diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 0e856a12f934..6b84d34f7704 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2189,10 +2189,13 @@ whether a request is secure by looking at whether the requested URL uses "https://". This is important for Django's CSRF protection, and may be used by your own code or third-party apps. -If your Django app is behind a proxy, though, the proxy may be "swallowing" the -fact that a request is HTTPS, using a non-HTTPS connection between the proxy -and Django. In this case, ``is_secure()`` would always return ``False`` -- even -for requests that were made via HTTPS by the end user. +If your Django app is behind a proxy, though, the proxy may be "swallowing" +whether the original request uses HTTPS or not. If there is a non-HTTPS +connection between the proxy and Django then ``is_secure()`` would always +return ``False`` -- even for requests that were made via HTTPS by the end user. +In contrast, if there is an HTTPS connection between the proxy and Django then +``is_secure()`` would always return ``True`` -- even for requests that were +made originally via HTTP. In this situation, you'll want to configure your proxy to set a custom HTTP header that tells Django whether the request came in via HTTPS, and you'll want diff --git a/docs/releases/1.11.22.txt b/docs/releases/1.11.22.txt index 91d81890dfa4..58ea68146e56 100644 --- a/docs/releases/1.11.22.txt +++ b/docs/releases/1.11.22.txt @@ -5,3 +5,23 @@ Django 1.11.22 release notes *July 1, 2019* Django 1.11.22 fixes a security issue in 1.11.21. + +CVE-2019-12781: Incorrect HTTP detection with reverse-proxy connecting via HTTPS +-------------------------------------------------------------------------------- + +When deployed behind a reverse-proxy connecting to Django via HTTPS, +:attr:`django.http.HttpRequest.scheme` would incorrectly detect client +requests made via HTTP as using HTTPS. This entails incorrect results for +:meth:`~django.http.HttpRequest.is_secure`, and +:meth:`~django.http.HttpRequest.build_absolute_uri`, and that HTTP +requests would not be redirected to HTTPS in accordance with +:setting:`SECURE_SSL_REDIRECT`. + +``HttpRequest.scheme`` now respects :setting:`SECURE_PROXY_SSL_HEADER`, if it +is configured, and the appropriate header is set on the request, for both HTTP +and HTTPS requests. + +If you deploy Django behind a reverse-proxy that forwards HTTP requests, and +that connects to Django via HTTPS, be sure to verify that your application +correctly handles code paths relying on ``scheme``, ``is_secure()``, +``build_absolute_uri()``, and ``SECURE_SSL_REDIRECT``. diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py index bf015affc2d3..012264dc3471 100644 --- a/tests/settings_tests/tests.py +++ b/tests/settings_tests/tests.py @@ -334,6 +334,18 @@ def test_set_with_xheader_right(self): req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'https' self.assertIs(req.is_secure(), True) + @override_settings(SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTOCOL', 'https')) + def test_xheader_preferred_to_underlying_request(self): + class ProxyRequest(HttpRequest): + def _get_scheme(self): + """Proxy always connecting via HTTPS""" + return 'https' + + # Client connects via HTTP. + req = ProxyRequest() + req.META['HTTP_X_FORWARDED_PROTOCOL'] = 'http' + self.assertIs(req.is_secure(), False) + class IsOverriddenTest(SimpleTestCase): def test_configure(self): From 480380c9935a2e920a41828b3a07bee66a686a67 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 1 Jul 2019 08:43:35 +0200 Subject: [PATCH 348/389] [1.11.x] Bumped version for 1.11.22 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 3dbec73181e4..90ca62a2834f 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 22, 'alpha', 0) +VERSION = (1, 11, 22, 'final', 0) __version__ = get_version(VERSION) From 7c849b9e3babdecfc441161847e5316c63b1ecac Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 1 Jul 2019 08:47:34 +0200 Subject: [PATCH 349/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 90ca62a2834f..cfaeb7a7f12f 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 22, 'final', 0) +VERSION = (1, 11, 23, 'alpha', 0) __version__ = get_version(VERSION) From 6d054b5a8f8812169b74e4304291d94874c2b012 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 1 Jul 2019 10:14:36 +0200 Subject: [PATCH 350/389] [1.11.x] Added CVE-2019-12781 to the security release archive. Backport of 868cd56f058ca203419ad0886353173b74c3bcf1 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index d26669d63aed..2e1e94198875 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -961,3 +961,16 @@ Versions affected * Django 2.2 :commit:`(patch) ` * Django 2.1 :commit:`(patch) <09186a13d975de6d049f8b3e05484f66b01ece62>` * Django 1.11 :commit:`(patch) ` + +July 1, 2019 - :cve:`2019-12781` +-------------------------------- + +Incorrect HTTP detection with reverse-proxy connecting via HTTPS. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) <77706a3e4766da5d5fb75c4db22a0a59a28e6cd6>` +* Django 2.1 :commit:`(patch) <1e40f427bb8d0fb37cc9f830096a97c36c97af6f>` +* Django 1.11 :commit:`(patch) <32124fc41e75074141b05f10fc55a4f01ff7f050>` From 693046e54b9f207dece1907a2515ce555cec83be Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 25 Jul 2019 10:58:17 +0200 Subject: [PATCH 351/389] [1.11.x] Added stub release notes for security releases. Backport of f13147c8de725eed7038941758469aeb9bd66503 from master. --- docs/releases/1.11.23.txt | 7 +++++++ docs/releases/index.txt | 1 + 2 files changed, 8 insertions(+) create mode 100644 docs/releases/1.11.23.txt diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt new file mode 100644 index 000000000000..9a3ab7cbc962 --- /dev/null +++ b/docs/releases/1.11.23.txt @@ -0,0 +1,7 @@ +============================ +Django 1.11.23 release notes +============================ + +*August 1, 2019* + +Django 1.11.23 fixes security issues in 1.11.22. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index e00804d651ed..f6b0dccb7d67 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.23 1.11.22 1.11.21 1.11.20 From 42a66e969023c00536256469f0e8b8a099ef109d Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 15 Jul 2019 11:46:09 +0200 Subject: [PATCH 352/389] [1.11.X] Fixed CVE-2019-14232 -- Adjusted regex to avoid backtracking issues when truncating HTML. Thanks to Guido Vranken for initial report. --- django/utils/text.py | 4 ++-- docs/releases/1.11.23.txt | 14 +++++++++++ .../filter_tests/test_truncatewords_html.py | 4 ++-- tests/utils_tests/test_text.py | 23 +++++++++++++++---- 4 files changed, 37 insertions(+), 8 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index a6172c41b0fa..f221747f6f36 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -27,8 +27,8 @@ def capfirst(x): # Set up regular expressions -re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.U | re.S) -re_chars = re.compile(r'<.*?>|(.)', re.U | re.S) +re_words = re.compile(r'<[^>]+?>|([^<>\s]+)', re.S) +re_chars = re.compile(r'<[^>]+?>|(.)', re.S) re_tag = re.compile(r'<(/)?(\S+?)(?:(\s*/)|\s.*?)?>', re.S) re_newlines = re.compile(r'\r\n|\r') # Used in normalize_newlines re_camel_case = re.compile(r'(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))') diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index 9a3ab7cbc962..6058bb8a818c 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -5,3 +5,17 @@ Django 1.11.23 release notes *August 1, 2019* Django 1.11.23 fixes security issues in 1.11.22. + +CVE-2019-14232: Denial-of-service possibility in ``django.utils.text.Truncator`` +================================================================================ + +If ``django.utils.text.Truncator``'s ``chars()`` and ``words()`` methods +were passed the ``html=True`` argument, they were extremely slow to evaluate +certain inputs due to a catastrophic backtracking vulnerability in a regular +expression. The ``chars()`` and ``words()`` methods are used to implement the +:tfilter:`truncatechars_html` and :tfilter:`truncatewords_html` template +filters, which were thus vulnerable. + +The regular expressions used by ``Truncator`` have been simplified in order to +avoid potential backtracking issues. As a consequence, trailing punctuation may +now at times be included in the truncated output. diff --git a/tests/template_tests/filter_tests/test_truncatewords_html.py b/tests/template_tests/filter_tests/test_truncatewords_html.py index aec2abf2d4d0..3c73442fbe84 100644 --- a/tests/template_tests/filter_tests/test_truncatewords_html.py +++ b/tests/template_tests/filter_tests/test_truncatewords_html.py @@ -19,13 +19,13 @@ def test_truncate(self): def test_truncate2(self): self.assertEqual( truncatewords_html('

      one two - three
      four
      five

      ', 4), - '

      one two - three
      four ...

      ', + '

      one two - three ...

      ', ) def test_truncate3(self): self.assertEqual( truncatewords_html('

      one two - three
      four
      five

      ', 5), - '

      one two - three
      four
      five

      ', + '

      one two - three
      four ...

      ', ) def test_truncate4(self): diff --git a/tests/utils_tests/test_text.py b/tests/utils_tests/test_text.py index 50d1805e8672..bfc1b4efc44b 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -88,6 +88,16 @@ def test_truncate_chars(self): # lazy strings are handled correctly self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(12), 'The quick...') + def test_truncate_chars_html(self): + perf_test_values = [ + (('', None), + ('&' * 50000, '&' * 7 + '...'), + ('_X<<<<<<<<<<<>', None), + ] + for value, expected in perf_test_values: + truncator = text.Truncator(value) + self.assertEqual(expected if expected else value, truncator.chars(10, html=True)) + def test_truncate_words(self): truncator = text.Truncator('The quick brown fox jumped over the lazy dog.') self.assertEqual('The quick brown fox jumped over the lazy dog.', truncator.words(10)) @@ -137,11 +147,16 @@ def test_truncate_html_words(self): truncator = text.Truncator('Buenos días! ¿Cómo está?') self.assertEqual('Buenos días! ¿Cómo...', truncator.words(3, '...', html=True)) truncator = text.Truncator('

      I <3 python, what about you?

      ') - self.assertEqual('

      I <3 python...

      ', truncator.words(3, '...', html=True)) + self.assertEqual('

      I <3 python,...

      ', truncator.words(3, '...', html=True)) - re_tag_catastrophic_test = ('' - truncator = text.Truncator(re_tag_catastrophic_test) - self.assertEqual(re_tag_catastrophic_test, truncator.words(500, html=True)) + perf_test_values = [ + ('', + '&' * 50000, + '_X<<<<<<<<<<<>', + ] + for value in perf_test_values: + truncator = text.Truncator(value) + self.assertEqual(value, truncator.words(50, html=True)) def test_wrap(self): digits = '1234 67 9' From 52479acce792ad80bb0f915f20b835f919993c72 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 15 Jul 2019 12:00:06 +0200 Subject: [PATCH 353/389] [1.11.x] Fixed CVE-2019-14233 -- Prevented excessive HTMLParser recursion in strip_tags() when handling incomplete HTML entities. Thanks to Guido Vranken for initial report. --- django/utils/html.py | 4 ++-- docs/releases/1.11.23.txt | 17 +++++++++++++++++ tests/utils_tests/test_html.py | 2 ++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/django/utils/html.py b/django/utils/html.py index 9c38cde55d65..30a6a2f0c820 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -169,8 +169,8 @@ def strip_tags(value): value = force_text(value) while '<' in value and '>' in value: new_value = _strip_once(value) - if len(new_value) >= len(value): - # _strip_once was not able to detect more tags or length increased + if len(new_value) >= len(value) or value.count('<') == new_value.count('<'): + # _strip_once wasn't able to detect more tags, or line length increased. # due to http://bugs.python.org/issue20288 # (affects Python 2 < 2.7.7 and Python 3 < 3.3.5) break diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index 6058bb8a818c..c95ffd9a5033 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -19,3 +19,20 @@ filters, which were thus vulnerable. The regular expressions used by ``Truncator`` have been simplified in order to avoid potential backtracking issues. As a consequence, trailing punctuation may now at times be included in the truncated output. + +CVE-2019-14233: Denial-of-service possibility in ``strip_tags()`` +================================================================= + +Due to the behavior of the underlying ``HTMLParser``, +:func:`django.utils.html.strip_tags` would be extremely slow to evaluate +certain inputs containing large sequences of nested incomplete HTML entities. +The ``strip_tags()`` method is used to implement the corresponding +:tfilter:`striptags` template filter, which was thus also vulnerable. + +``strip_tags()`` now avoids recursive calls to ``HTMLParser`` when progress +removing tags, but necessarily incomplete HTML entities, stops being made. + +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`. diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 1bebe9452197..6122b695f3b5 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -86,6 +86,8 @@ def test_strip_tags(self): # caused infinite loop on Pythons not patched with # http://bugs.python.org/issue20288 ('&gotcha&#;<>', '&gotcha&#;<>'), + ('>br>br>br>X', 'XX'), ) for value, output in items: self.check_output(f, value, output) From ed682a24fca774818542757651bfba576c3fc3ef Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 22 Jul 2019 10:45:26 +0200 Subject: [PATCH 354/389] [1.11.x] Fixed CVE-2019-14234 -- Protected JSONField/HStoreField key and index lookups against SQL injection. Thanks to Sage M. Abdullah for the report and initial patch. Thanks Florian Apolloner for reviews. --- django/contrib/postgres/fields/hstore.py | 2 +- django/contrib/postgres/fields/jsonb.py | 8 +++----- docs/releases/1.11.23.txt | 9 +++++++++ tests/postgres_tests/test_hstore.py | 15 ++++++++++++++- tests/postgres_tests/test_json.py | 14 ++++++++++++++ 5 files changed, 41 insertions(+), 7 deletions(-) diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index 605deaf62c53..b77d1b1958cd 100644 --- a/django/contrib/postgres/fields/hstore.py +++ b/django/contrib/postgres/fields/hstore.py @@ -86,7 +86,7 @@ def __init__(self, key_name, *args, **kwargs): def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) - return "(%s -> '%s')" % (lhs, self.key_name), params + return '(%s -> %%s)' % lhs, [self.key_name] + params class KeyTransformFactory(object): diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index 0722a05a69c2..d7a22591e20c 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -104,12 +104,10 @@ def as_sql(self, compiler, connection): if len(key_transforms) > 1: return "(%s %s %%s)" % (lhs, self.nested_operator), [key_transforms] + params try: - int(self.key_name) + lookup = int(self.key_name) except ValueError: - lookup = "'%s'" % self.key_name - else: - lookup = "%s" % self.key_name - return "(%s %s %s)" % (lhs, self.operator, lookup), params + lookup = self.key_name + return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params class KeyTextTransform(KeyTransform): diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index c95ffd9a5033..03b33ebf630b 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -36,3 +36,12 @@ 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`. + +CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONField``/``HStoreField`` +==================================================================================================== + +:lookup:`Key and index lookups ` for +:class:`~django.contrib.postgres.fields.JSONField` and :lookup:`key lookups +` for :class:`~django.contrib.postgres.fields.HStoreField` +were subject to SQL injection, using a suitably crafted dictionary, with +dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``. diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index 0fc427f67c06..dd8e642ed0c9 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -4,8 +4,9 @@ import json from django.core import exceptions, serializers +from django.db import connection from django.forms import Form -from django.test.utils import modify_settings +from django.test.utils import CaptureQueriesContext, modify_settings from . import PostgreSQLTestCase from .models import HStoreModel @@ -167,6 +168,18 @@ def test_usage_in_subquery(self): self.objs[:2] ) + def test_key_sql_injection(self): + with CaptureQueriesContext(connection) as queries: + self.assertFalse( + HStoreModel.objects.filter(**{ + "field__test' = 'a') OR 1 = 1 OR ('d": 'x', + }).exists() + ) + self.assertIn( + """."field" -> 'test'' = ''a'') OR 1 = 1 OR (''d') = 'x' """, + queries[0]['sql'], + ) + class TestSerialization(HStoreTestCase): test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, ' diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index 4e8851d4853b..925e800131c0 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -6,8 +6,10 @@ from django.core import exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder +from django.db import connection from django.forms import CharField, Form, widgets from django.test import skipUnlessDBFeature +from django.test.utils import CaptureQueriesContext from django.utils.html import escape from . import PostgreSQLTestCase @@ -263,6 +265,18 @@ def test_regex(self): def test_iregex(self): self.assertTrue(JSONModel.objects.filter(field__foo__iregex=r'^bAr$').exists()) + def test_key_sql_injection(self): + with CaptureQueriesContext(connection) as queries: + self.assertFalse( + JSONModel.objects.filter(**{ + """field__test' = '"a"') OR 1 = 1 OR ('d""": 'x', + }).exists() + ) + self.assertIn( + """."field" -> 'test'' = ''"a"'') OR 1 = 1 OR (''d') = '"x"' """, + queries[0]['sql'], + ) + @skipUnlessDBFeature('has_jsonb_datatype') class TestSerialization(PostgreSQLTestCase): From 869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 19 Jul 2019 17:04:53 +0200 Subject: [PATCH 355/389] [1.11.x] Fixed CVE-2019-14235 -- Fixed potential memory exhaustion in django.utils.encoding.uri_to_iri(). Thanks to Guido Vranken for initial report. --- django/utils/encoding.py | 17 ++++++++++------- docs/releases/1.11.23.txt | 10 ++++++++++ tests/utils_tests/test_encoding.py | 12 +++++++++++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 999ffae19a02..a29ef2be58fe 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -237,13 +237,16 @@ def repercent_broken_unicode(path): we need to re-percent-encode any octet produced that is not part of a strictly legal UTF-8 octet sequence. """ - try: - path.decode('utf-8') - except UnicodeDecodeError as e: - repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~") - path = repercent_broken_unicode( - path[:e.start] + force_bytes(repercent) + path[e.end:]) - return path + while True: + try: + path.decode('utf-8') + except UnicodeDecodeError as e: + # CVE-2019-14235: A recursion shouldn't be used since the exception + # handling uses massive amounts of memory + repercent = quote(path[e.start:e.end], safe=b"/#%[]=:;$&()+,!?*@'~") + path = path[:e.start] + force_bytes(repercent) + path[e.end:] + else: + return path def filepath_to_uri(path): diff --git a/docs/releases/1.11.23.txt b/docs/releases/1.11.23.txt index 03b33ebf630b..04acca90f181 100644 --- a/docs/releases/1.11.23.txt +++ b/docs/releases/1.11.23.txt @@ -45,3 +45,13 @@ CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONFie ` for :class:`~django.contrib.postgres.fields.HStoreField` were subject to SQL injection, using a suitably crafted dictionary, with dictionary expansion, as the ``**kwargs`` passed to ``QuerySet.filter()``. + +CVE-2019-14235: Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()`` +===================================================================================== + +If passed certain inputs, :func:`django.utils.encoding.uri_to_iri` could lead +to significant memory usage due to excessive recursion when re-percent-encoding +invalid UTF-8 octet sequences. + +``uri_to_iri()`` now avoids recursion when re-percent-encoding invalid UTF-8 +octet sequences. diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py index 688b46194d67..2b4bcff870d7 100644 --- a/tests/utils_tests/test_encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -2,12 +2,13 @@ from __future__ import unicode_literals import datetime +import sys import unittest from django.utils import six from django.utils.encoding import ( escape_uri_path, filepath_to_uri, force_bytes, force_text, iri_to_uri, - smart_text, uri_to_iri, + repercent_broken_unicode, smart_text, uri_to_iri, ) from django.utils.functional import SimpleLazyObject from django.utils.http import urlquote_plus @@ -76,6 +77,15 @@ def __unicode__(self): self.assertEqual(smart_text(1), '1') self.assertEqual(smart_text('foo'), 'foo') + def test_repercent_broken_unicode_recursion_error(self): + # Prepare a string long enough to force a recursion error if the tested + # function uses recursion. + data = b'\xfc' * sys.getrecursionlimit() + try: + self.assertEqual(repercent_broken_unicode(data), b'%FC' * sys.getrecursionlimit()) + except RecursionError: + self.fail('Unexpected RecursionError raised.') + class TestRFC3987IEncodingUtils(unittest.TestCase): From 974897759e9afc4cc56fb87e12319fa9697e93c9 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 10:43:51 +0200 Subject: [PATCH 356/389] [1.11.x] Bumped version for 1.11.23 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index cfaeb7a7f12f..c622e303cff0 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 23, 'alpha', 0) +VERSION = (1, 11, 23, 'final', 0) __version__ = get_version(VERSION) From 1e6a5b000189878ef3832d104b960acd4a8c6fc8 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 10:46:21 +0200 Subject: [PATCH 357/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index c622e303cff0..1593f649f8f1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 23, 'final', 0) +VERSION = (1, 11, 24, 'alpha', 0) __version__ = get_version(VERSION) From ba791617e0ec879bfc764d644246665ead59965f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:54:24 +0200 Subject: [PATCH 358/389] [1.11.x] Added CVE-2019-14232 to the security release archive. Backport of 87750787d1e464b7143f366d9485ba20fefc9c94 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 2e1e94198875..cbbccb94950e 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -974,3 +974,16 @@ Versions affected * Django 2.2 :commit:`(patch) <77706a3e4766da5d5fb75c4db22a0a59a28e6cd6>` * Django 2.1 :commit:`(patch) <1e40f427bb8d0fb37cc9f830096a97c36c97af6f>` * Django 1.11 :commit:`(patch) <32124fc41e75074141b05f10fc55a4f01ff7f050>` + +August 1, 2019 - :cve:`2019-14232` +---------------------------------- + +Denial-of-service possibility in ``django.utils.text.Truncator``. `Full +description `__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) ` +* Django 2.1 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) <42a66e969023c00536256469f0e8b8a099ef109d>` From 7482d25f1e174a6f85563efa584815ce955ae2c4 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:57:24 +0200 Subject: [PATCH 359/389] [1.11.x] Added CVE-2019-14233 to security release archive. Backport of 9600f63885d2d240f85d59bff6acbe200f890298 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index cbbccb94950e..2da3b86f8652 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -987,3 +987,16 @@ Versions affected * Django 2.2 :commit:`(patch) ` * Django 2.1 :commit:`(patch) ` * Django 1.11 :commit:`(patch) <42a66e969023c00536256469f0e8b8a099ef109d>` + +August 1, 2019 - :cve:`2019-14233` +---------------------------------- + +Denial-of-service possibility in ``strip_tags()``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) ` +* Django 2.1 :commit:`(patch) <5ff8e791148bd451180124d76a55cb2b2b9556eb>` +* Django 1.11 :commit:`(patch) <52479acce792ad80bb0f915f20b835f919993c72>` From 738b45dd3b3cd17ec52744b1181597de073f5e4d Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:59:45 +0200 Subject: [PATCH 360/389] [1.11.x] Added CVE-2019-14234 to security release archive. Backport of 3a6a2f5eaf74200a9591a6311fdb0ea78ee305ee from master --- docs/releases/security.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 2da3b86f8652..450e36a87a96 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1000,3 +1000,18 @@ Versions affected * Django 2.2 :commit:`(patch) ` * Django 2.1 :commit:`(patch) <5ff8e791148bd451180124d76a55cb2b2b9556eb>` * Django 1.11 :commit:`(patch) <52479acce792ad80bb0f915f20b835f919993c72>` + + +August 1, 2019 - :cve:`2019-14234` +---------------------------------- + +SQL injection possibility in key and index lookups for +``JSONField``/``HStoreField``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) <4f5b58f5cd3c57fee9972ab074f8dc6895d8f387>` +* Django 2.1 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) ` From 3deda1f680ef34a753f6d872813737f363cb4886 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 12:01:27 +0200 Subject: [PATCH 361/389] [1.11.x] Added CVE-2019-14235 to security release archive. Backport of a5652eb795e896df0c0f2515201f35f9cd86b99b from master --- docs/releases/security.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index 450e36a87a96..ef70cac0d9fa 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1015,3 +1015,17 @@ Versions affected * Django 2.2 :commit:`(patch) <4f5b58f5cd3c57fee9972ab074f8dc6895d8f387>` * Django 2.1 :commit:`(patch) ` * Django 1.11 :commit:`(patch) ` + +August 1, 2019 - :cve:`2019-14235` +---------------------------------- + +Potential memory exhaustion in ``django.utils.encoding.uri_to_iri()``. `Full +description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 2.2 :commit:`(patch) ` +* Django 2.1 :commit:`(patch) <5d50a2e5fa36ad23ab532fc54cf4073de84b3306>` +* Django 1.11 :commit:`(patch) <869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79>` From 473c526b1b014e73e139665db2ddbbcee23bb826 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 14 Aug 2019 15:25:35 +0200 Subject: [PATCH 362/389] [1.11.x] Fixed #30672 -- Fixed crash of JSONField/HStoreField key transforms on expressions with params. Regression in 4f5b58f5cd3c57fee9972ab074f8dc6895d8f387. Thanks Florian Apolloner for the report and helping with tests. Backport of 1f8382d34d54061eddc41df6994e20ee38c60907 from master. --- django/contrib/postgres/fields/hstore.py | 2 +- django/contrib/postgres/fields/jsonb.py | 2 +- docs/releases/1.11.24.txt | 15 +++++++++++++++ docs/releases/index.txt | 1 + tests/postgres_tests/test_hstore.py | 9 +++++++++ tests/postgres_tests/test_json.py | 22 ++++++++++++++++++++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.11.24.txt diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index b77d1b1958cd..e2c4a2b33853 100644 --- a/django/contrib/postgres/fields/hstore.py +++ b/django/contrib/postgres/fields/hstore.py @@ -86,7 +86,7 @@ def __init__(self, key_name, *args, **kwargs): def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) - return '(%s -> %%s)' % lhs, [self.key_name] + params + return '(%s -> %%s)' % lhs, params + [self.key_name] class KeyTransformFactory(object): diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index d7a22591e20c..bf1b4ee1a7e7 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -107,7 +107,7 @@ def as_sql(self, compiler, connection): lookup = int(self.key_name) except ValueError: lookup = self.key_name - return '(%s %s %%s)' % (lhs, self.operator), [lookup] + params + return '(%s %s %%s)' % (lhs, self.operator), params + [lookup] class KeyTextTransform(KeyTransform): diff --git a/docs/releases/1.11.24.txt b/docs/releases/1.11.24.txt new file mode 100644 index 000000000000..3013c866d040 --- /dev/null +++ b/docs/releases/1.11.24.txt @@ -0,0 +1,15 @@ +============================ +Django 1.11.24 release notes +============================ + +*Expected September 2, 2019* + +Django 1.11.24 fixes a regression in 1.11.23. + +Bugfixes +======== + +* Fixed crash of ``KeyTransform()`` for + :class:`~django.contrib.postgres.fields.JSONField` and + :class:`~django.contrib.postgres.fields.HStoreField` when using on + expressions with params (:ticket:`30672`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index f6b0dccb7d67..de3883409c26 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.24 1.11.23 1.11.22 1.11.21 diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index dd8e642ed0c9..3ac68e1e690a 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -5,6 +5,7 @@ from django.core import exceptions, serializers from django.db import connection +from django.db.models.expressions import RawSQL from django.forms import Form from django.test.utils import CaptureQueriesContext, modify_settings @@ -14,6 +15,7 @@ try: from django.contrib.postgres import forms from django.contrib.postgres.fields import HStoreField + from django.contrib.postgres.fields.hstore import KeyTransform from django.contrib.postgres.validators import KeysValidator except ImportError: pass @@ -121,6 +123,13 @@ def test_key_transform(self): self.objs[:2] ) + def test_key_transform_raw_expression(self): + expr = RawSQL('%s::hstore', ['x => b, y => c']) + self.assertSequenceEqual( + HStoreModel.objects.filter(field__a=KeyTransform('x', expr)), + self.objs[:2] + ) + def test_keys(self): self.assertSequenceEqual( HStoreModel.objects.filter(field__keys=['a']), diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index 925e800131c0..ead2c68df73b 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -7,6 +7,9 @@ from django.core import exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder from django.db import connection +from django.db.models import F +from django.db.models.expressions import RawSQL +from django.db.models.functions import Cast from django.forms import CharField, Form, widgets from django.test import skipUnlessDBFeature from django.test.utils import CaptureQueriesContext @@ -18,6 +21,7 @@ try: from django.contrib.postgres import forms from django.contrib.postgres.fields import JSONField + from django.contrib.postgres.fields.jsonb import KeyTransform except ImportError: pass @@ -147,6 +151,24 @@ def test_isnull(self): [self.objs[0]] ) + def test_key_transform_raw_expression(self): + expr = RawSQL('%s::jsonb', ['{"x": "bar"}']) + self.assertSequenceEqual( + JSONModel.objects.filter(field__foo=KeyTransform('x', expr)), + [self.objs[-1]], + ) + + def test_key_transform_expression(self): + self.assertSequenceEqual( + JSONModel.objects.filter(field__d__0__isnull=False).annotate( + key=KeyTransform('d', 'field'), + ).annotate( + chain=KeyTransform('0', 'key'), + expr=KeyTransform('0', Cast('key', JSONField())), + ).filter(chain=F('expr')), + [self.objs[8]], + ) + def test_isnull_key(self): # key__isnull works the same as has_key='key'. self.assertSequenceEqual( From 835b62a5883e8f8b9c1e5d570115160f057ed5ce Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 07:43:51 +0200 Subject: [PATCH 363/389] [1.11.x] Added release date for 1.11.24. Backport of 47f49adc11c0d39be3f41f92becc1f606c49d8ce from master. --- docs/releases/1.11.24.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.24.txt b/docs/releases/1.11.24.txt index 3013c866d040..578854f6e755 100644 --- a/docs/releases/1.11.24.txt +++ b/docs/releases/1.11.24.txt @@ -2,7 +2,7 @@ Django 1.11.24 release notes ============================ -*Expected September 2, 2019* +*September 2, 2019* Django 1.11.24 fixes a regression in 1.11.23. From 4c049c805ab6db302032debea7caa119dec5cc9f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 08:45:34 +0200 Subject: [PATCH 364/389] [1.11.x] Bumped version for 1.11.24 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 1593f649f8f1..5ac7ff35c6fd 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 24, 'alpha', 0) +VERSION = (1, 11, 24, 'final', 0) __version__ = get_version(VERSION) From f213c4c406bf97d825b2c0af80f2e01a5668cb3b Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 09:02:39 +0200 Subject: [PATCH 365/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 5ac7ff35c6fd..24a24876deaf 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 24, 'final', 0) +VERSION = (1, 11, 25, 'alpha', 0) __version__ = get_version(VERSION) From 30c3d5fd731450705104dee1f3c2f67b8aad8495 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 16 Sep 2019 07:37:47 +0200 Subject: [PATCH 366/389] [1.11.x] Added stub release notes for 1.11.25. Backport of bd7e0f81f8590eadcb820c976ba03c9b75bbcad6 from master --- docs/releases/1.11.25.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.25.txt diff --git a/docs/releases/1.11.25.txt b/docs/releases/1.11.25.txt new file mode 100644 index 000000000000..4195e8cbe0b2 --- /dev/null +++ b/docs/releases/1.11.25.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.25 release notes +============================ + +*Expected October 1, 2019* + +Django 1.11.25 fixes a regression in 1.11.23. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index de3883409c26..647a92278ca9 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.25 1.11.24 1.11.23 1.11.22 From fd393907c91e855acbd6f7a287bdc8e951c328e8 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 15 Sep 2019 23:25:50 -0400 Subject: [PATCH 367/389] [1.11.x] Fixed #30769 -- Fixed a crash when filtering against a subquery JSON/HStoreField annotation. This was a regression introduced by 7deeabc7c7526786df6894429ce89a9c4b614086 to address CVE-2019-14234. Thanks Tim Kleinschmidt for the report and Mariusz for the tests. Backport of 6c3dfba89215fc56fc27ef61829a6fff88be4abb from master. --- django/contrib/postgres/fields/hstore.py | 2 +- django/contrib/postgres/fields/jsonb.py | 2 +- docs/releases/1.11.25.txt | 4 +++- tests/postgres_tests/test_hstore.py | 8 +++++++- tests/postgres_tests/test_json.py | 8 +++++++- 5 files changed, 19 insertions(+), 5 deletions(-) diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index e2c4a2b33853..5ddb10e5840c 100644 --- a/django/contrib/postgres/fields/hstore.py +++ b/django/contrib/postgres/fields/hstore.py @@ -86,7 +86,7 @@ def __init__(self, key_name, *args, **kwargs): def as_sql(self, compiler, connection): lhs, params = compiler.compile(self.lhs) - return '(%s -> %%s)' % lhs, params + [self.key_name] + return '(%s -> %%s)' % lhs, tuple(params) + (self.key_name,) class KeyTransformFactory(object): diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index bf1b4ee1a7e7..559fc5ba4e0b 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -107,7 +107,7 @@ def as_sql(self, compiler, connection): lookup = int(self.key_name) except ValueError: lookup = self.key_name - return '(%s %s %%s)' % (lhs, self.operator), params + [lookup] + return '(%s %s %%s)' % (lhs, self.operator), tuple(params) + (lookup,) class KeyTextTransform(KeyTransform): diff --git a/docs/releases/1.11.25.txt b/docs/releases/1.11.25.txt index 4195e8cbe0b2..0e9e2d7ee59f 100644 --- a/docs/releases/1.11.25.txt +++ b/docs/releases/1.11.25.txt @@ -9,4 +9,6 @@ Django 1.11.25 fixes a regression in 1.11.23. Bugfixes ======== -* ... +* Fixed a crash when filtering with a ``Subquery()`` annotation of a queryset + containing :class:`~django.contrib.postgres.fields.JSONField` or + :class:`~django.contrib.postgres.fields.HStoreField` (:ticket:`30769`). diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index 3ac68e1e690a..acad950fbbe9 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -5,7 +5,7 @@ from django.core import exceptions, serializers from django.db import connection -from django.db.models.expressions import RawSQL +from django.db.models.expressions import OuterRef, RawSQL, Subquery from django.forms import Form from django.test.utils import CaptureQueriesContext, modify_settings @@ -189,6 +189,12 @@ def test_key_sql_injection(self): queries[0]['sql'], ) + def test_obj_subquery_lookup(self): + qs = HStoreModel.objects.annotate( + value=Subquery(HStoreModel.objects.filter(pk=OuterRef('pk')).values('field')), + ).filter(value__a='b') + self.assertSequenceEqual(qs, self.objs[:2]) + class TestSerialization(HStoreTestCase): test_data = ('[{"fields": {"field": "{\\"a\\": \\"b\\"}"}, ' diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index ead2c68df73b..b8b5dd481f17 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -7,7 +7,7 @@ from django.core import exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder from django.db import connection -from django.db.models import F +from django.db.models import F, OuterRef, Subquery from django.db.models.expressions import RawSQL from django.db.models.functions import Cast from django.forms import CharField, Form, widgets @@ -222,6 +222,12 @@ def test_shallow_obj_lookup(self): [self.objs[7], self.objs[8]] ) + def test_obj_subquery_lookup(self): + qs = JSONModel.objects.annotate( + value=Subquery(JSONModel.objects.filter(pk=OuterRef('pk')).values('field')), + ).filter(value__a='b') + self.assertSequenceEqual(qs, [self.objs[7], self.objs[8]]) + def test_deep_lookup_objs(self): self.assertSequenceEqual( JSONModel.objects.filter(field__k__l='m'), From 9d2916faf510311663da13a0bd4f539cca23afa9 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 08:49:15 +0200 Subject: [PATCH 368/389] [1.11.x] Added release date for 1.11.25. Backport of 3826aed46d7d4310c2ab6777a4f92165ca4d8d4f from master. --- docs/releases/1.11.25.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.25.txt b/docs/releases/1.11.25.txt index 0e9e2d7ee59f..7b63b92d6474 100644 --- a/docs/releases/1.11.25.txt +++ b/docs/releases/1.11.25.txt @@ -2,7 +2,7 @@ Django 1.11.25 release notes ============================ -*Expected October 1, 2019* +*October 1, 2019* Django 1.11.25 fixes a regression in 1.11.23. From 81f0da91fb30237eadd532c597f84d47afc56449 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 09:54:07 +0200 Subject: [PATCH 369/389] [1.11.x] Bumped version for 1.11.25 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 24a24876deaf..61439cd4d35d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 25, 'alpha', 0) +VERSION = (1, 11, 25, 'final', 0) __version__ = get_version(VERSION) From b73bb46d424f6da2599b003a082dc448de9b5997 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 10:06:53 +0200 Subject: [PATCH 370/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 61439cd4d35d..4c302f1fa85e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 25, 'final', 0) +VERSION = (1, 11, 26, 'alpha', 0) __version__ = get_version(VERSION) From cf2b475aaba8e854cafa99575c8082d844e5cb09 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 2 Oct 2019 07:49:47 +0200 Subject: [PATCH 371/389] [1.11.x] Added stub release notes for 1.11.26. Backport of 84322a29ce9b0940335f8ab3d60e55192bef1e50 from master --- docs/releases/1.11.26.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/1.11.26.txt diff --git a/docs/releases/1.11.26.txt b/docs/releases/1.11.26.txt new file mode 100644 index 000000000000..a0c39b416871 --- /dev/null +++ b/docs/releases/1.11.26.txt @@ -0,0 +1,12 @@ +============================ +Django 1.11.26 release notes +============================ + +*Expected November 1, 2019* + +Django 1.11.26 fixes a regression in 1.11.25. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 647a92278ca9..0d832e3c1df5 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.26 1.11.25 1.11.24 1.11.23 From a843a9ba8da40ffae49c9ec13e7b5c41d5e40ca5 Mon Sep 17 00:00:00 2001 From: Louise Grandjonc Date: Tue, 1 Oct 2019 16:25:40 -0700 Subject: [PATCH 372/389] [1.11.x] Fixed #30826 -- Fixed crash of many JSONField lookups when one hand side is key transform. Regression in 6c3dfba89215fc56fc27ef61829a6fff88be4abb. Backport of 7d1bf29977bb368d7c28e7c6eb146db3b3009ae7 from master. --- django/contrib/postgres/lookups.py | 2 +- docs/releases/1.11.26.txt | 5 ++++- tests/postgres_tests/test_json.py | 30 ++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/django/contrib/postgres/lookups.py b/django/contrib/postgres/lookups.py index 53a62eacd16f..466b8cdf5651 100644 --- a/django/contrib/postgres/lookups.py +++ b/django/contrib/postgres/lookups.py @@ -8,7 +8,7 @@ class PostgresSimpleLookup(Lookup): def as_sql(self, qn, connection): lhs, lhs_params = self.process_lhs(qn, connection) rhs, rhs_params = self.process_rhs(qn, connection) - params = lhs_params + rhs_params + params = tuple(lhs_params) + tuple(rhs_params) return '%s %s %s' % (lhs, self.operator, rhs), params diff --git a/docs/releases/1.11.26.txt b/docs/releases/1.11.26.txt index a0c39b416871..1a54b47e1b7b 100644 --- a/docs/releases/1.11.26.txt +++ b/docs/releases/1.11.26.txt @@ -9,4 +9,7 @@ Django 1.11.26 fixes a regression in 1.11.25. Bugfixes ======== -* ... +* Fixed a crash when using a ``contains``, ``contained_by``, ``has_key``, + ``has_keys``, or ``has_any_keys`` lookup on + :class:`~django.contrib.postgres.fields.JSONField`, if the right or left hand + side of an expression is a key transform (:ticket:`30826`). diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index b8b5dd481f17..faefcd18a343 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -21,7 +21,9 @@ try: from django.contrib.postgres import forms from django.contrib.postgres.fields import JSONField - from django.contrib.postgres.fields.jsonb import KeyTransform + from django.contrib.postgres.fields.jsonb import ( + KeyTextTransform, KeyTransform + ) except ImportError: pass @@ -130,7 +132,12 @@ def setUpTestData(cls): 'k': True, 'l': False, }), - JSONModel.objects.create(field={'foo': 'bar'}), + JSONModel.objects.create(field={ + 'foo': 'bar', + 'baz': {'a': 'b', 'c': 'd'}, + 'bar': ['foo', 'bar'], + 'bax': {'foo': 'bar'}, + }), ] def test_exact(self): @@ -305,6 +312,25 @@ def test_key_sql_injection(self): queries[0]['sql'], ) + def test_lookups_with_key_transform(self): + tests = ( + ('field__d__contains', 'e'), + ('field__baz__contained_by', {'a': 'b', 'c': 'd', 'e': 'f'}), + ('field__baz__has_key', 'c'), + ('field__baz__has_keys', ['a', 'c']), + ('field__baz__has_any_keys', ['a', 'x']), + ('field__contains', KeyTransform('bax', 'field')), + ( + 'field__contained_by', + KeyTransform('x', RawSQL('%s::jsonb', ['{"x": {"a": "b", "c": 1, "d": "e"}}'])), + ), + ('field__has_key', KeyTextTransform('foo', 'field')), + ) + for lookup, value in tests: + self.assertTrue(JSONModel.objects.filter( + **{lookup: value} + ).exists()) + @skipUnlessDBFeature('has_jsonb_datatype') class TestSerialization(PostgreSQLTestCase): From 4017507660e7098a11544a82298842ab228abbed Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 08:20:22 +0100 Subject: [PATCH 373/389] [1.11.x] Added release date for 1.11.26. Backport of 126cfefce2b59900138f2bf1ef6ad966cddc55d4 from master --- docs/releases/1.11.26.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.26.txt b/docs/releases/1.11.26.txt index 1a54b47e1b7b..8db2cb45b875 100644 --- a/docs/releases/1.11.26.txt +++ b/docs/releases/1.11.26.txt @@ -2,7 +2,7 @@ Django 1.11.26 release notes ============================ -*Expected November 1, 2019* +*November 4, 2019* Django 1.11.26 fixes a regression in 1.11.25. From f24d305761fbdfa583117cb4df3ab8a3afb04949 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 09:21:03 +0100 Subject: [PATCH 374/389] [1.11.x] Bumped version for 1.11.26 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 4c302f1fa85e..32376236758f 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 26, 'alpha', 0) +VERSION = (1, 11, 26, 'final', 0) __version__ = get_version(VERSION) From 4f1501660b869c8358ce32b71464d8016c15208c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 09:31:11 +0100 Subject: [PATCH 375/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 32376236758f..1a1d58309dc1 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 26, 'final', 0) +VERSION = (1, 11, 27, 'alpha', 0) __version__ = get_version(VERSION) From e8fdf00cc2acdcc992fd9621230e927594dc5d4f Mon Sep 17 00:00:00 2001 From: Peter Andersen Date: Mon, 9 Dec 2019 08:54:40 -0800 Subject: [PATCH 376/389] [1.11.x] Fixed #31073 -- Prevented CheckboxInput.get_context() from mutating attrs. Backport of 02eff7ef60466da108b1a33f1e4dc01eec45c99d from master. --- django/forms/widgets.py | 2 ++ tests/forms_tests/widget_tests/test_checkboxinput.py | 5 +++++ tests/postgres_tests/test_array.py | 11 +++++++++++ 3 files changed, 18 insertions(+) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 2b7e07bea3e2..5e82c06213ee 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -508,6 +508,8 @@ def get_context(self, name, value, attrs): if self.check_test(value): if attrs is None: attrs = {} + else: + attrs = attrs.copy() attrs['checked'] = True return super(CheckboxInput, self).get_context(name, value, attrs) diff --git a/tests/forms_tests/widget_tests/test_checkboxinput.py b/tests/forms_tests/widget_tests/test_checkboxinput.py index 1ae6ab73b654..37bb2df55299 100644 --- a/tests/forms_tests/widget_tests/test_checkboxinput.py +++ b/tests/forms_tests/widget_tests/test_checkboxinput.py @@ -89,3 +89,8 @@ def test_value_from_datadict_string_int(self): def test_value_omitted_from_data(self): self.assertIs(self.widget.value_omitted_from_data({'field': 'value'}, {}, 'field'), False) self.assertIs(self.widget.value_omitted_from_data({}, {}, 'field'), False) + + def test_get_context_does_not_mutate_attrs(self): + attrs = {'checked': False} + self.widget.get_context('name', True, attrs) + self.assertIs(attrs['checked'], False) diff --git a/tests/postgres_tests/test_array.py b/tests/postgres_tests/test_array.py index a66c92a016ff..0b706e85ad44 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -826,6 +826,17 @@ def test_get_context(self): } ) + def test_checkbox_get_context_attrs(self): + context = SplitArrayWidget( + forms.CheckboxInput(), + size=2, + ).get_context('name', [True, False]) + self.assertEqual(context['widget']['value'], '[True, False]') + self.assertEqual( + [subwidget['attrs'] for subwidget in context['widget']['subwidgets']], + [{'checked': True}, {}] + ) + def test_render(self): self.check_html( SplitArrayWidget(forms.TextInput(), size=2), 'array', None, From a2355740ed76ca9461b34d65beb450c0156f3224 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 11 Dec 2019 10:07:41 +0100 Subject: [PATCH 377/389] [1.11.x] Refs #31073 -- Added release notes for 02eff7ef60466da108b1a33f1e4dc01eec45c99d. Backport of ec12c37384798093e359971c8980fe0c68d555bc from master. --- docs/releases/1.11.27.txt | 15 +++++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 16 insertions(+) create mode 100644 docs/releases/1.11.27.txt diff --git a/docs/releases/1.11.27.txt b/docs/releases/1.11.27.txt new file mode 100644 index 000000000000..cb4329afdb26 --- /dev/null +++ b/docs/releases/1.11.27.txt @@ -0,0 +1,15 @@ +============================ +Django 1.11.27 release notes +============================ + +*Expected January 2, 2020* + +Django 1.11.27 fixes a data loss bug in 1.11.26. + +Bugfixes +======== + +* Fixed a data loss possibility in + :class:`~django.contrib.postgres.forms.SplitArrayField`. When using with + ``ArrayField(BooleanField())``, all values after the first ``True`` value + were marked as checked instead of preserving passed values (:ticket:`31073`). diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 0d832e3c1df5..cd13f8695ed4 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.27 1.11.26 1.11.25 1.11.24 From f4cff43bf921fcea6a29b726eb66767f67753fa2 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 16 Dec 2019 21:51:57 -0500 Subject: [PATCH 378/389] [1.11.x] Fixed CVE-2019-19844 -- Used verified user email for password reset requests. Backport of 5b1fbcef7a8bec991ebe7b2a18b5d5a95d72cb70 from master. Co-Authored-By: Florian Apolloner --- django/contrib/auth/forms.py | 30 ++++++++++++++++++++---- docs/releases/1.11.27.txt | 20 ++++++++++++++-- tests/auth_tests/test_forms.py | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index 02250d83da6f..3741fdea7c34 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -16,12 +16,27 @@ from django.template import loader from django.utils.encoding import force_bytes from django.utils.http import urlsafe_base64_encode +from django.utils.six import PY3 from django.utils.text import capfirst from django.utils.translation import ugettext, ugettext_lazy as _ UserModel = get_user_model() +def _unicode_ci_compare(s1, s2): + """ + Perform case-insensitive comparison of two identifiers, using the + recommended algorithm from Unicode Technical Report 36, section + 2.11.2(B)(2). + """ + normalized1 = unicodedata.normalize('NFKC', s1) + normalized2 = unicodedata.normalize('NFKC', s2) + if PY3: + return normalized1.casefold() == normalized2.casefold() + # lower() is the best alternative available on Python 2. + return normalized1.lower() == normalized2.lower() + + class ReadOnlyPasswordHashWidget(forms.Widget): template_name = 'auth/widgets/read_only_password_hash.html' @@ -249,11 +264,16 @@ def get_users(self, email): that prevent inactive users and users with unusable passwords from resetting their password. """ + email_field_name = UserModel.get_email_field_name() active_users = UserModel._default_manager.filter(**{ - '%s__iexact' % UserModel.get_email_field_name(): email, + '%s__iexact' % email_field_name: email, 'is_active': True, }) - return (u for u in active_users if u.has_usable_password()) + return ( + u for u in active_users + if u.has_usable_password() and + _unicode_ci_compare(email, getattr(u, email_field_name)) + ) def save(self, domain_override=None, subject_template_name='registration/password_reset_subject.txt', @@ -266,6 +286,7 @@ def save(self, domain_override=None, user. """ email = self.cleaned_data["email"] + email_field_name = UserModel.get_email_field_name() for user in self.get_users(email): if not domain_override: current_site = get_current_site(request) @@ -273,8 +294,9 @@ def save(self, domain_override=None, domain = current_site.domain else: site_name = domain = domain_override + user_email = getattr(user, email_field_name) context = { - 'email': email, + 'email': user_email, 'domain': domain, 'site_name': site_name, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), @@ -286,7 +308,7 @@ def save(self, domain_override=None, context.update(extra_email_context) self.send_mail( subject_template_name, email_template_name, context, from_email, - email, html_email_template_name=html_email_template_name, + user_email, html_email_template_name=html_email_template_name, ) diff --git a/docs/releases/1.11.27.txt b/docs/releases/1.11.27.txt index cb4329afdb26..6197dee1f60b 100644 --- a/docs/releases/1.11.27.txt +++ b/docs/releases/1.11.27.txt @@ -2,9 +2,25 @@ Django 1.11.27 release notes ============================ -*Expected January 2, 2020* +*December 18, 2019* -Django 1.11.27 fixes a data loss bug in 1.11.26. +Django 1.11.27 fixes a security issue and a data loss bug in 1.11.26. + +CVE-2019-19844: Potential account hijack via password reset form +================================================================ + +By submitting a suitably crafted email address making use of Unicode +characters, that compared equal to an existing user email when lower-cased for +comparison, an attacker could be sent a password reset token for the matched +account. + +In order to avoid this vulnerability, password reset requests now compare the +submitted email using the stricter, recommended algorithm for case-insensitive +comparison of two identifiers from `Unicode Technical Report 36, section +2.11.2(B)(2)`__. Upon a match, the email containing the reset token will be +sent to the email address on record rather than the submitted address. + +.. __: https://www.unicode.org/reports/tr36/#Recommendations_General Bugfixes ======== diff --git a/tests/auth_tests/test_forms.py b/tests/auth_tests/test_forms.py index e09285277f40..213d58b77343 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -694,6 +694,48 @@ def test_invalid_email(self): self.assertFalse(form.is_valid()) self.assertEqual(form['email'].errors, [_('Enter a valid email address.')]) + def test_user_email_unicode_collision(self): + User.objects.create_user('mike123', 'mike@example.org', 'test123') + User.objects.create_user('mike456', 'mıke@example.org', 'test123') + data = {'email': 'mıke@example.org'} + form = PasswordResetForm(data) + if six.PY2: + self.assertFalse(form.is_valid()) + else: + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['mıke@example.org']) + + def test_user_email_domain_unicode_collision(self): + User.objects.create_user('mike123', 'mike@ixample.org', 'test123') + User.objects.create_user('mike456', 'mike@ıxample.org', 'test123') + data = {'email': 'mike@ıxample.org'} + form = PasswordResetForm(data) + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].to, ['mike@ıxample.org']) + + def test_user_email_unicode_collision_nonexistent(self): + User.objects.create_user('mike123', 'mike@example.org', 'test123') + data = {'email': 'mıke@example.org'} + form = PasswordResetForm(data) + if six.PY2: + self.assertFalse(form.is_valid()) + else: + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual(len(mail.outbox), 0) + + def test_user_email_domain_unicode_collision_nonexistent(self): + User.objects.create_user('mike123', 'mike@ixample.org', 'test123') + data = {'email': 'mike@ıxample.org'} + form = PasswordResetForm(data) + self.assertTrue(form.is_valid()) + form.save() + self.assertEqual(len(mail.outbox), 0) + def test_nonexistent_email(self): """ Test nonexistent email address. This should not fail because it would From 358973a12eb3105ba084a2d594428a19223b8582 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 09:32:29 +0100 Subject: [PATCH 379/389] [1.11.x] Bumped version for 1.11.27 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 1a1d58309dc1..429a5e90fd8d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 27, 'alpha', 0) +VERSION = (1, 11, 27, 'final', 0) __version__ = get_version(VERSION) From 2c4fb9a35db575ec56207446d782b8f450a2b4e7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 09:35:18 +0100 Subject: [PATCH 380/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 429a5e90fd8d..460f3baf7072 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 27, 'final', 0) +VERSION = (1, 11, 28, 'alpha', 0) __version__ = get_version(VERSION) From 121115d2c291b3969ac00ca62253f23513481739 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 10:36:22 +0100 Subject: [PATCH 381/389] [1.11.x] Added CVE-2019-19844 to the security archive. Backport of 5a2b9f0b546222e928df91310acb9cf363a6c920 from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index ef70cac0d9fa..d461ce3d86d4 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1029,3 +1029,16 @@ Versions affected * Django 2.2 :commit:`(patch) ` * Django 2.1 :commit:`(patch) <5d50a2e5fa36ad23ab532fc54cf4073de84b3306>` * Django 1.11 :commit:`(patch) <869b34e9b3be3a4cfcb3a145f218ffd3f5e3fd79>` + +December 18, 2019 - :cve:`2019-19844` +------------------------------------- + +Potential account hijack via password reset form. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 3.0 :commit:`(patch) <302a4ff1e8b1c798aab97673909c7a3dfda42c26>` +* Django 2.2 :commit:`(patch) <4d334bea06cac63dc1272abcec545b85136cca0e>` +* Django 1.11 :commit:`(patch) ` From 7fd1ca3ef63e5e834205a8208f4dc17d80f9a417 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 7 Jan 2020 09:54:22 +0100 Subject: [PATCH 382/389] [1.11.x] Fixed timezones tests for PyYAML 5.3+. Backport of 8be477be5c1a4afc9ad00bb58a324f637e018c0f from master --- tests/timezones/tests.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/timezones/tests.py b/tests/timezones/tests.py index d1a63c67ec2c..f13ca54e9565 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -36,6 +36,12 @@ AllDayEvent, Event, MaybeEvent, Session, SessionEvent, Timestamp, ) +try: + import yaml + HAS_YAML = True +except ImportError: + HAS_YAML = False + # These tests use the EAT (Eastern Africa Time) and ICT (Indochina Time) # who don't have Daylight Saving Time, so we can represent them easily # with FixedOffset, and use them directly as tzinfo in the constructors. @@ -662,9 +668,10 @@ class SerializationTests(SimpleTestCase): # Backend-specific notes: # - JSON supports only milliseconds, microseconds will be truncated. - # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes, - # but when it loads this representation, it subtracts the offset and - # returns a naive datetime object in UTC. See ticket #18867. + # - PyYAML dumps the UTC offset correctly for timezone-aware datetimes. + # When PyYAML < 5.3 loads this representation, it subtracts the offset + # and returns a naive datetime object in UTC. PyYAML 5.3+ loads timezones + # correctly. # Tests are adapted to take these quirks into account. def assert_python_contains_datetime(self, objects, dt): @@ -751,7 +758,10 @@ def test_aware_datetime_with_microsecond(self): data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30.405060+07:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) def test_aware_datetime_in_utc(self): dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC) @@ -799,7 +809,10 @@ def test_aware_datetime_in_local_timezone(self): data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 13:20:30+03:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) def test_aware_datetime_in_other_timezone(self): dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=ICT) @@ -823,7 +836,10 @@ def test_aware_datetime_in_other_timezone(self): data = serializers.serialize('yaml', [Event(dt=dt)], default_flow_style=None) self.assert_yaml_contains_datetime(data, "2011-09-01 17:20:30+07:00") obj = next(serializers.deserialize('yaml', data)).object - self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + if HAS_YAML and yaml.__version__ < '5.3': + self.assertEqual(obj.dt.replace(tzinfo=UTC), dt) + else: + self.assertEqual(obj.dt, dt) @override_settings(DATETIME_FORMAT='c', TIME_ZONE='Africa/Nairobi', USE_L10N=False, USE_TZ=True) From 001b0634cd309e372edb6d7d95d083d02b8e37bd Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 22 Jan 2020 09:03:27 +0100 Subject: [PATCH 383/389] [1.11.x] Fixed CVE-2020-7471 -- Properly escaped StringAgg(delimiter) parameter. --- django/contrib/postgres/aggregates/general.py | 6 ++++-- docs/releases/1.11.28.txt | 13 +++++++++++++ docs/releases/index.txt | 1 + tests/postgres_tests/test_aggregates.py | 4 ++++ 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.11.28.txt diff --git a/django/contrib/postgres/aggregates/general.py b/django/contrib/postgres/aggregates/general.py index 5b3d22bf9817..cd9a4bc34988 100644 --- a/django/contrib/postgres/aggregates/general.py +++ b/django/contrib/postgres/aggregates/general.py @@ -1,4 +1,5 @@ from django.contrib.postgres.fields import JSONField +from django.db.models import Value from django.db.models.aggregates import Aggregate __all__ = [ @@ -43,11 +44,12 @@ def convert_value(self, value, expression, connection, context): class StringAgg(Aggregate): function = 'STRING_AGG' - template = "%(function)s(%(distinct)s%(expressions)s, '%(delimiter)s')" + template = '%(function)s(%(distinct)s%(expressions)s)' def __init__(self, expression, delimiter, distinct=False, **extra): distinct = 'DISTINCT ' if distinct else '' - super(StringAgg, self).__init__(expression, delimiter=delimiter, distinct=distinct, **extra) + delimiter_expr = Value(str(delimiter)) + super(StringAgg, self).__init__(expression, delimiter_expr, distinct=distinct, **extra) def convert_value(self, value, expression, connection, context): if not value: diff --git a/docs/releases/1.11.28.txt b/docs/releases/1.11.28.txt new file mode 100644 index 000000000000..81ccb0ce06f8 --- /dev/null +++ b/docs/releases/1.11.28.txt @@ -0,0 +1,13 @@ +============================ +Django 1.11.28 release notes +============================ + +*February 3, 2020* + +Django 1.11.28 fixes a security issue in 1.11.27. + +CVE-2020-7471: Potential SQL injection via ``StringAgg(delimiter)`` +=================================================================== + +:class:`~django.contrib.postgres.aggregates.StringAgg` aggregation function was +subject to SQL injection, using a suitably crafted ``delimiter``. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index cd13f8695ed4..c2e913cafc4d 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.28 1.11.27 1.11.26 1.11.25 diff --git a/tests/postgres_tests/test_aggregates.py b/tests/postgres_tests/test_aggregates.py index 9aa0e0659577..ddfd7fce267f 100644 --- a/tests/postgres_tests/test_aggregates.py +++ b/tests/postgres_tests/test_aggregates.py @@ -108,6 +108,10 @@ def test_string_agg_requires_delimiter(self): with self.assertRaises(TypeError): AggregateTestModel.objects.aggregate(stringagg=StringAgg('char_field')) + def test_string_agg_delimiter_escaping(self): + values = AggregateTestModel.objects.aggregate(stringagg=StringAgg('char_field', delimiter="'")) + self.assertEqual(values, {'stringagg': "Foo1'Foo2'Foo3'Foo4"}) + def test_string_agg_charfield(self): values = AggregateTestModel.objects.aggregate(stringagg=StringAgg('char_field', delimiter=';')) self.assertEqual(values, {'stringagg': 'Foo1;Foo2;Foo3;Foo4'}) From e09f09b965ef47ffd99abd2c26ba7416751cffa6 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 09:16:55 +0100 Subject: [PATCH 384/389] [1.11.x] Bumped version for 1.11.28 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 460f3baf7072..a5c5ca149eba 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 28, 'alpha', 0) +VERSION = (1, 11, 28, 'final', 0) __version__ = get_version(VERSION) From 9a62ed5d5f827b44e8c95559c60fb28fb444e3ad Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 09:27:14 +0100 Subject: [PATCH 385/389] [1.11.x] Post-release version bump. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index a5c5ca149eba..a4da03ad7d03 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 28, 'final', 0) +VERSION = (1, 11, 29, 'alpha', 0) __version__ = get_version(VERSION) From d0e3eb8e827355462a2d90660079234796d94ab0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 10:11:34 +0100 Subject: [PATCH 386/389] [1.11.x] Added CVE-2020-7471 to security archive. Backport of d8b2ccbbb846328a0938347dc70cb2e603164d9a from master --- docs/releases/security.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/releases/security.txt b/docs/releases/security.txt index d461ce3d86d4..8b705e90d139 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1042,3 +1042,16 @@ Versions affected * Django 3.0 :commit:`(patch) <302a4ff1e8b1c798aab97673909c7a3dfda42c26>` * Django 2.2 :commit:`(patch) <4d334bea06cac63dc1272abcec545b85136cca0e>` * Django 1.11 :commit:`(patch) ` + +February 3, 2020 - :cve:`2020-7471` +----------------------------------- + +Potential SQL injection via ``StringAgg(delimiter)``. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 3.0 :commit:`(patch) <505826b469b16ab36693360da9e11fd13213421b>` +* Django 2.2 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) <001b0634cd309e372edb6d7d95d083d02b8e37bd>` From e6438335626cb7e662dd015485a23cfc5721ad72 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 4 Feb 2020 09:48:29 +0100 Subject: [PATCH 387/389] [1.11.x] Pinned PyYAML < 5.3 in test requirements. PyYAML 5.3+ doesn't support Python 3.4. --- tests/requirements/base.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/requirements/base.txt b/tests/requirements/base.txt index 08a6b3a526b3..d323dbbf5686 100644 --- a/tests/requirements/base.txt +++ b/tests/requirements/base.txt @@ -5,7 +5,7 @@ geoip2 jinja2 >= 2.9.2 numpy Pillow != 5.4.0 -PyYAML +PyYAML < 5.3 # pylibmc/libmemcached can't be built on Windows. pylibmc; sys.platform != 'win32' python-memcached >= 1.59 From 02d97f3c9a88adc890047996e5606180bd1c6166 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 24 Feb 2020 14:46:28 +0100 Subject: [PATCH 388/389] [1.11.x] Fixed CVE-2020-9402 -- Properly escaped tolerance parameter in GIS functions and aggregates on Oracle. Thanks to Norbert Szetei for the report. --- django/contrib/gis/db/models/aggregates.py | 12 ++++--- django/contrib/gis/db/models/functions.py | 6 +++- docs/releases/1.11.29.txt | 13 ++++++++ docs/releases/index.txt | 1 + tests/gis_tests/distapp/tests.py | 33 +++++++++++++++++- tests/gis_tests/geoapp/tests.py | 39 +++++++++++++++++++++- 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 docs/releases/1.11.29.txt diff --git a/django/contrib/gis/db/models/aggregates.py b/django/contrib/gis/db/models/aggregates.py index 416481f9cac9..0e077550a2e1 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -1,4 +1,5 @@ from django.contrib.gis.db.models.fields import ExtentField +from django.db.models import Value from django.db.models.aggregates import Aggregate __all__ = ['Collect', 'Extent', 'Extent3D', 'MakeLine', 'Union'] @@ -16,11 +17,14 @@ def as_sql(self, compiler, connection): return super(GeoAggregate, self).as_sql(compiler, connection) def as_oracle(self, compiler, connection): - if not hasattr(self, 'tolerance'): - self.tolerance = 0.05 - self.extra['tolerance'] = self.tolerance if not self.is_extent: - self.template = '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))' + tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05) + clone = self.copy() + expressions = clone.get_source_expressions() + expressions.append(Value(tolerance)) + clone.set_source_expressions(expressions) + clone.template = '%(function)s(SDOAGGRTYPE(%(expressions)s))' + return clone.as_sql(compiler, connection) return self.as_sql(compiler, connection) def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 95eda246ade1..ef9e11818944 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -117,7 +117,11 @@ class OracleToleranceMixin(object): tolerance = 0.05 def as_oracle(self, compiler, connection): - tol = self.extra.get('tolerance', self.tolerance) + tol = self._handle_param( + self.extra.get('tolerance', self.tolerance), + 'tolerance', + NUMERIC_TYPES, + ) self.template = "%%(function)s(%%(expressions)s, %s)" % tol return super(OracleToleranceMixin, self).as_sql(compiler, connection) diff --git a/docs/releases/1.11.29.txt b/docs/releases/1.11.29.txt new file mode 100644 index 000000000000..d37f3ffc0dac --- /dev/null +++ b/docs/releases/1.11.29.txt @@ -0,0 +1,13 @@ +============================ +Django 1.11.29 release notes +============================ + +*March 4, 2020* + +Django 1.11.29 fixes a security issue in 1.11.29. + +CVE-2020-9402: Potential SQL injection via ``tolerance`` parameter in GIS functions and aggregates on Oracle +============================================================================================================ + +GIS functions and aggregates on Oracle were subject to SQL injection, +using a suitably crafted ``tolerance``. diff --git a/docs/releases/index.txt b/docs/releases/index.txt index c2e913cafc4d..be5fb3e54e9a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -26,6 +26,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 1.11.29 1.11.28 1.11.27 1.11.26 diff --git a/tests/gis_tests/distapp/tests.py b/tests/gis_tests/distapp/tests.py index 4609083f3b41..dd7f9efe69cc 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -1,6 +1,6 @@ from __future__ import unicode_literals -from unittest import skipIf +from unittest import skipIf, skipUnless from django.contrib.gis.db.models.functions import ( Area, Distance, Length, Perimeter, Transform, @@ -588,6 +588,37 @@ def test_distance_geodetic_spheroid(self): for i, c in enumerate(qs): self.assertAlmostEqual(sphere_distances[i], c.distance.m, tol) + @skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_distance_function_tolerance_escaping(self): + qs = AustraliaCity.objects.annotate( + d=Distance( + 'point', + Point(0, 0, srid=3857), + tolerance='0.05) = 1 OR 1=1 OR (1+1', + ), + ).filter(d=1).values('pk') + msg = 'The tolerance parameter has the wrong type' + with self.assertRaisesMessage(TypeError, msg): + qs.exists() + + @skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_distance_function_tolerance(self): + # Tolerance is greater than distance. + qs = AustraliaCity.objects.annotate( + d=Distance( + 'point', + Point(151.23, -33.95, srid=4326), + tolerance=340.7, + ), + ).filter(d=0).values('pk') + self.assertIs(qs.exists(), True) + @no_oracle # Oracle already handles geographic distance calculation. @skipUnlessDBFeature("has_Distance_function", 'has_Transform_function') def test_distance_transform(self): diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index b6a5c2d0c851..7eff87693d4a 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -2,6 +2,7 @@ import re import tempfile +import unittest from django.contrib.gis import gdal from django.contrib.gis.db.models import Extent, MakeLine, Union @@ -10,7 +11,7 @@ MultiPoint, MultiPolygon, Point, Polygon, fromstr, ) from django.core.management import call_command -from django.db import connection +from django.db import DatabaseError, connection from django.test import TestCase, ignore_warnings, skipUnlessDBFeature from django.utils import six from django.utils.deprecation import RemovedInDjango20Warning @@ -881,6 +882,42 @@ def test_unionagg(self): qs = City.objects.filter(name='NotACity') self.assertIsNone(qs.aggregate(Union('point'))['point__union']) + @unittest.skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_unionagg_tolerance(self): + City.objects.create( + point=fromstr('POINT(-96.467222 32.751389)', srid=4326), + name='Forney', + ) + tx = Country.objects.get(name='Texas').mpoly + # Tolerance is greater than distance between Forney and Dallas, that's + # why Dallas is ignored. + forney_houston = GEOSGeometry( + 'MULTIPOINT(-95.363151 29.763374, -96.467222 32.751389)', + srid=4326, + ) + self.assertIs( + forney_houston.equals( + City.objects.filter(point__within=tx).aggregate( + Union('point', tolerance=32000), + )['point__union'], + ), + True, + ) + + @unittest.skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_unionagg_tolerance_escaping(self): + tx = Country.objects.get(name='Texas').mpoly + with self.assertRaises(DatabaseError): + City.objects.filter(point__within=tx).aggregate( + Union('point', tolerance='0.05))), (((1'), + ) + def test_within_subquery(self): """ Using a queryset inside a geo lookup is working (using a subquery) From f1e3017aeaeddc590dcf2cf88511f3a726da73ca Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Mar 2020 09:49:38 +0100 Subject: [PATCH 389/389] [1.11.x] Bumped version for 1.11.29 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index a4da03ad7d03..b683b5e0e295 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -2,7 +2,7 @@ from django.utils.version import get_version -VERSION = (1, 11, 29, 'alpha', 0) +VERSION = (1, 11, 29, 'final', 0) __version__ = get_version(VERSION)