From 5d39f62f7e5879fe425d016f09ec70045451b431 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 1 Jul 2019 08:21:52 +0200 Subject: [PATCH 001/287] [2.2.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 bcad1c6e7f2a..d0e1bd999a28 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 3, 'final', 0) +VERSION = (2, 2, 4, 'alpha', 0) __version__ = get_version(VERSION) From 2b533ae60e37f68fc6e9328334cd813cf0b4aa09 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 1 Jul 2019 10:14:36 +0200 Subject: [PATCH 002/287] [2.2.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 b9d1bb6955d45d9da165ed11292a197d0728fe3f Mon Sep 17 00:00:00 2001 From: aitoehigie Date: Sat, 29 Jun 2019 02:41:36 +0100 Subject: [PATCH 003/287] [2.2.x] Fixed #30589 -- Clarified that urlize should be applied only to email addresses without single quotes. Backport of c2f381ef17058e5cfea58ae507983d2e459a2888 from master --- docs/ref/templates/builtins.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index daa4a48ee649..8a8514914e71 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -2434,8 +2434,9 @@ Django's built-in :tfilter:`escape` filter. The default value for .. note:: - If ``urlize`` is applied to text that already contains HTML markup, - things won't work as expected. Apply this filter only to plain text. + If ``urlize`` is applied to text that already contains HTML markup, or to + email addresses that contain single quotes (``'``), things won't work as + expected. Apply this filter only to plain text. .. templatefilter:: urlizetrunc From b6d8957356fd7f23e806c65657a72f7316d66b61 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 2 Jul 2019 11:20:53 +0200 Subject: [PATCH 004/287] [2.2.x] Fixed #28588 -- Doc'd User.has_perm() & co. behavior for active superusers. Equivalent note for PermissionsMixin was added in d33864ed138f65070049a3ac20ee98e03a1442b9. Backport of 4b32d039dbb59b3c3e76587df5c58150e752d9ac from master --- docs/ref/contrib/auth.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 375831be0e10..7be5b55ee3e4 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -220,7 +220,8 @@ Methods Returns ``True`` if the user has the specified permission, where perm is in the format ``"."``. (see documentation on :ref:`permissions `). If the user is - inactive, this method will always return ``False``. + inactive, this method will always return ``False``. For an active + superuser, this method will always return ``True``. If ``obj`` is passed in, this method won't check for a permission for the model, but for this specific object. @@ -230,7 +231,8 @@ Methods Returns ``True`` if the user has each of the specified permissions, where each perm is in the format ``"."``. If the user is inactive, - this method will always return ``False``. + this method will always return ``False``. For an active superuser, this + method will always return ``True``. If ``obj`` is passed in, this method won't check for permissions for the model, but for the specific object. @@ -239,7 +241,8 @@ Methods Returns ``True`` if the user has any permissions in the given package (the Django app label). If the user is inactive, this method will - always return ``False``. + always return ``False``. For an active superuser, this method will + always return ``True``. .. method:: email_user(subject, message, from_email=None, **kwargs) From 7d52d056e370734f3938d7cf3ab30e06205f1f85 Mon Sep 17 00:00:00 2001 From: swatantra Date: Wed, 26 Jun 2019 11:44:47 +0530 Subject: [PATCH 005/287] [2.2.x] Fixed #28667 -- Clarified how to override list of forms fields for custom UserAdmin with a custom user model. Backport of c13e3715f5f46f2ee4ddba357e2589a45e831813 from master --- docs/topics/auth/customizing.txt | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index fd723cbb9442..ce4c1c9edf80 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -877,6 +877,28 @@ override any of the definitions that refer to fields on ``django.contrib.auth.models.AbstractUser`` that aren't on your custom user class. +.. note:: + + If you are using a custom ``ModelAdmin`` which is a subclass of + ``django.contrib.auth.admin.UserAdmin``, then you need to add your custom + fields to ``fieldsets`` (for fields to be used in editing users) and to + ``add_fieldsets`` (for fields to be used when creating a user). For + example:: + + from django.contrib.auth.admin import UserAdmin + + class CustomUserAdmin(UserAdmin): + ... + fieldsets = UserAdmin.fieldsets + ( + (None, {'fields': ('custom_field',)}), + ) + add_fieldsets = UserAdmin.add_fieldsets + ( + (None, {'fields': ('custom_field',)}), + ) + + See :ref:`a full example ` for more + details. + Custom users and permissions ---------------------------- @@ -962,6 +984,8 @@ If your project uses proxy models, you must either modify the proxy to extend the user model that's in use in your project, or merge your proxy's behavior into your :class:`~django.contrib.auth.models.User` subclass. +.. _custom-users-admin-full-example: + A full example -------------- From 0ea952e3d65f3e9e2d63fc2153a8cc20f791b550 Mon Sep 17 00:00:00 2001 From: sp1rs Date: Wed, 3 Jul 2019 23:30:18 +0530 Subject: [PATCH 006/287] [2.2.x] Fixed #30600 -- Clarified that ValueError raised by converter.to_python() means no match. Backport of f197c3dd9130b18397022605c27ffe5755f329d7 from master --- docs/topics/http/urls.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/topics/http/urls.txt b/docs/topics/http/urls.txt index 3a17a4532285..ed7257d84736 100644 --- a/docs/topics/http/urls.txt +++ b/docs/topics/http/urls.txt @@ -146,7 +146,9 @@ A converter is a class that includes the following: * A ``to_python(self, value)`` method, which handles converting the matched string into the type that should be passed to the view function. It should - raise ``ValueError`` if it can't convert the given value. + raise ``ValueError`` if it can't convert the given value. A ``ValueError`` is + interpreted as no match and as a consequence a 404 response is sent to the + user. * A ``to_url(self, value)`` method, which handles converting the Python type into a string to be used in the URL. From b593c39d7fb7d92d779eed3508f5234f4e784b40 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 9 Jul 2019 07:39:00 +0200 Subject: [PATCH 007/287] [2.2.x] Added stub release notes for 2.2.4. Backport of 08e69cad9ccb18738b66388b0d0ee4660470710e from master --- docs/releases/2.2.4.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.4.txt diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt new file mode 100644 index 000000000000..0bc4dce2959b --- /dev/null +++ b/docs/releases/2.2.4.txt @@ -0,0 +1,12 @@ +========================== +Django 2.2.4 release notes +========================== + +*Expected August 1, 2019* + +Django 2.2.4 fixes several bugs in 2.2.3. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 920bbf98fad1..e2ed3b4852c8 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.4 2.2.3 2.2.2 2.2.1 From 8f0b9e7f9a3bf4db1d70186b96da89823d293608 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 9 Jul 2019 13:38:11 +0200 Subject: [PATCH 008/287] [2.2.x] Fixed typos in docs/ref/django-admin.txt. Backport of 24e8f7f7d3063a3bbfe14774080bc89035b4a3e2 from master --- docs/ref/django-admin.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 13399f59f7dc..eabc3be460ce 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1244,7 +1244,7 @@ files is: - ``app_directory`` -- the full path of the newly created app - ``camel_case_app_name`` -- the app name in camel case format - ``docs_version`` -- the version of the documentation: ``'dev'`` or ``'1.x'`` -- ``django_version`` -- the version of Django, e.g.``'2.0.3'`` +- ``django_version`` -- the version of Django, e.g. ``'2.0.3'`` .. _render_warning: @@ -1312,7 +1312,7 @@ The :class:`template context ` used is: - ``project_directory`` -- the full path of the newly created project - ``secret_key`` -- a random key for the :setting:`SECRET_KEY` setting - ``docs_version`` -- the version of the documentation: ``'dev'`` or ``'1.x'`` -- ``django_version`` -- the version of Django, e.g.``'2.0.3'`` +- ``django_version`` -- the version of Django, e.g. ``'2.0.3'`` Please also see the :ref:`rendering warning ` as mentioned for :djadmin:`startapp`. From 9dee8515d6f2876fa039aaebdfe8e2bc9de63085 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 9 Jul 2019 17:26:37 -0400 Subject: [PATCH 009/287] [2.2.x] Fixed #30628 -- Adjusted expression identity to differentiate bound fields. Expressions referring to different bound fields should not be considered equal. Thanks Julien Enselme for the detailed report. Regression in bc7e288ca9554ac1a0a19941302dea19df1acd21. Backport of ee6e93ec8727d0f5ed33190a3c354867669ed72f from master --- django/db/models/expressions.py | 5 ++++- docs/releases/2.2.4.txt | 4 +++- tests/expressions/tests.py | 21 ++++++++++++++++++++- tests/queries/models.py | 1 + tests/queries/test_qs_combinators.py | 9 ++++++++- tests/queries/tests.py | 2 +- 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py index ccb9636503f8..a67de51cdc80 100644 --- a/django/db/models/expressions.py +++ b/django/db/models/expressions.py @@ -372,7 +372,10 @@ def identity(self): identity = [self.__class__] for arg, value in arguments: if isinstance(value, fields.Field): - value = type(value) + if value.name and value.model: + value = (value.model._meta.label, value.name) + else: + value = type(value) else: value = make_hashable(value) identity.append((arg, value)) diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 0bc4dce2959b..a1a849680d95 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -9,4 +9,6 @@ Django 2.2.4 fixes several bugs in 2.2.3. Bugfixes ======== -* ... +* Fixed a regression in Django 2.2 when ordering a ``QuerySet.union()``, + ``intersection()``, or ``difference()`` by a field type present more than + once results in the wrong ordering being used (:ticket:`30628`). diff --git a/tests/expressions/tests.py b/tests/expressions/tests.py index e66dcd629725..f0819992d26c 100644 --- a/tests/expressions/tests.py +++ b/tests/expressions/tests.py @@ -21,7 +21,7 @@ from django.db.models.sql import constants from django.db.models.sql.datastructures import Join from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature -from django.test.utils import Approximate +from django.test.utils import Approximate, isolate_apps from .models import ( UUID, UUIDPK, Company, Employee, Experiment, Number, RemoteEmployee, @@ -839,6 +839,7 @@ def test_insensitive_patterns_escape(self): ) +@isolate_apps('expressions') class SimpleExpressionTests(SimpleTestCase): def test_equal(self): @@ -852,6 +853,15 @@ def test_equal(self): Expression(models.CharField()) ) + class TestModel(models.Model): + field = models.IntegerField() + other_field = models.IntegerField() + + self.assertNotEqual( + Expression(TestModel._meta.get_field('field')), + Expression(TestModel._meta.get_field('other_field')), + ) + def test_hash(self): self.assertEqual(hash(Expression()), hash(Expression())) self.assertEqual( @@ -863,6 +873,15 @@ def test_hash(self): hash(Expression(models.CharField())), ) + class TestModel(models.Model): + field = models.IntegerField() + other_field = models.IntegerField() + + self.assertNotEqual( + hash(Expression(TestModel._meta.get_field('field'))), + hash(Expression(TestModel._meta.get_field('other_field'))), + ) + class ExpressionsNumericTests(TestCase): diff --git a/tests/queries/models.py b/tests/queries/models.py index af0af1d10c54..5751738c9592 100644 --- a/tests/queries/models.py +++ b/tests/queries/models.py @@ -143,6 +143,7 @@ def __str__(self): class Number(models.Model): num = models.IntegerField() + other_num = models.IntegerField(null=True) def __str__(self): return str(self.num) diff --git a/tests/queries/test_qs_combinators.py b/tests/queries/test_qs_combinators.py index 3902db59e21e..0c1c614999fa 100644 --- a/tests/queries/test_qs_combinators.py +++ b/tests/queries/test_qs_combinators.py @@ -9,7 +9,7 @@ class QuerySetSetOperationTests(TestCase): @classmethod def setUpTestData(cls): - Number.objects.bulk_create(Number(num=i) for i in range(10)) + Number.objects.bulk_create(Number(num=i, other_num=10 - i) for i in range(10)) def number_transform(self, value): return value.num @@ -225,3 +225,10 @@ def test_qs_with_subcompound_qs(self): qs1 = Number.objects.all() qs2 = Number.objects.intersection(Number.objects.filter(num__gt=1)) self.assertEqual(qs1.difference(qs2).count(), 2) + + def test_order_by_same_type(self): + qs = Number.objects.all() + union = qs.union(qs) + numbers = list(range(10)) + self.assertNumbersEqual(union.order_by('num'), numbers) + self.assertNumbersEqual(union.order_by('other_num'), reversed(numbers)) diff --git a/tests/queries/tests.py b/tests/queries/tests.py index c655fe52cb9f..e72ecaa654c8 100644 --- a/tests/queries/tests.py +++ b/tests/queries/tests.py @@ -2345,7 +2345,7 @@ def test_named_values_list_without_fields(self): qs = Number.objects.extra(select={'num2': 'num+1'}).annotate(Count('id')) values = qs.values_list(named=True).first() self.assertEqual(type(values).__name__, 'Row') - self.assertEqual(values._fields, ('num2', 'id', 'num', 'id__count')) + self.assertEqual(values._fields, ('num2', 'id', 'num', 'other_num', 'id__count')) self.assertEqual(values.num, 72) self.assertEqual(values.num2, 73) self.assertEqual(values.id__count, 1) From 1088a9777da86dbf398106761c776edab29b163b Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 10 Jul 2019 10:33:36 +0200 Subject: [PATCH 010/287] [2.2.x] Fixed #30621 -- Fixed crash of __contains lookup for Date/DateTimeRangeField when the right hand side is the same type. Thanks Tilman Koschnick for the report and initial patch. Thanks Carlton Gibson for the review. Regression in 6b048b364ca1e0e56a0d3815bf2be33ac9998355. Backport of 7991111af12056ec9a856f35935d273526338c1f from master --- django/contrib/postgres/fields/ranges.py | 7 ++- docs/releases/2.2.4.txt | 6 +++ .../migrations/0002_create_test_models.py | 2 + tests/postgres_tests/models.py | 2 + tests/postgres_tests/test_constraints.py | 52 ++++++++++++++++++- tests/postgres_tests/test_ranges.py | 10 +++- 6 files changed, 76 insertions(+), 3 deletions(-) diff --git a/django/contrib/postgres/fields/ranges.py b/django/contrib/postgres/fields/ranges.py index 74ba4eb23015..0e8a347d5fe4 100644 --- a/django/contrib/postgres/fields/ranges.py +++ b/django/contrib/postgres/fields/ranges.py @@ -170,7 +170,12 @@ def as_sql(self, compiler, connection): params = lhs_params + rhs_params # Cast the rhs if needed. cast_sql = '' - if isinstance(self.rhs, models.Expression) and self.rhs._output_field_or_none: + if ( + isinstance(self.rhs, models.Expression) and + self.rhs._output_field_or_none and + # Skip cast if rhs has a matching range type. + not isinstance(self.rhs._output_field_or_none, self.lhs.output_field.__class__) + ): cast_internal_type = self.lhs.output_field.base_field.get_internal_type() cast_sql = '::{}'.format(connection.data_types.get(cast_internal_type)) return '%s @> %s%s' % (lhs, rhs, cast_sql), params diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index a1a849680d95..0ad92f4ab1ca 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -12,3 +12,9 @@ Bugfixes * Fixed a regression in Django 2.2 when ordering a ``QuerySet.union()``, ``intersection()``, or ``difference()`` by a field type present more than once results in the wrong ordering being used (:ticket:`30628`). + +* Fixed a migration crash on PostgreSQL when adding a check constraint + with a ``contains`` lookup on + :class:`~django.contrib.postgres.fields.DateRangeField` or + :class:`~django.contrib.postgres.fields.DateTimeRangeField`, if the right + hand side of an expression is the same type (:ticket:`30621`). diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 5db8a7138530..9f70f3ce7547 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -211,7 +211,9 @@ class Migration(migrations.Migration): ('bigints', BigIntegerRangeField(null=True, blank=True)), ('decimals', DecimalRangeField(null=True, blank=True)), ('timestamps', DateTimeRangeField(null=True, blank=True)), + ('timestamps_inner', DateTimeRangeField(null=True, blank=True)), ('dates', DateRangeField(null=True, blank=True)), + ('dates_inner', DateRangeField(null=True, blank=True)), ], options={ 'required_db_vendor': 'postgresql' diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index cbe477e40263..385b80f00161 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -131,7 +131,9 @@ class RangesModel(PostgreSQLModel): bigints = BigIntegerRangeField(blank=True, null=True) decimals = DecimalRangeField(blank=True, null=True) timestamps = DateTimeRangeField(blank=True, null=True) + timestamps_inner = DateTimeRangeField(blank=True, null=True) dates = DateRangeField(blank=True, null=True) + dates_inner = DateRangeField(blank=True, null=True) class RangeLookupsModel(PostgreSQLModel): diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py index 0e09a1c54606..2fc6ee532219 100644 --- a/tests/postgres_tests/test_constraints.py +++ b/tests/postgres_tests/test_constraints.py @@ -1,5 +1,7 @@ +import datetime + from django.db import connection, transaction -from django.db.models import Q +from django.db.models import F, Q from django.db.models.constraints import CheckConstraint from django.db.utils import IntegrityError @@ -33,3 +35,51 @@ def test_check_constraint_range_value(self): with self.assertRaises(IntegrityError), transaction.atomic(): RangesModel.objects.create(ints=(20, 50)) RangesModel.objects.create(ints=(10, 30)) + + def test_check_constraint_daterange_contains(self): + constraint_name = 'dates_contains' + self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) + constraint = CheckConstraint( + check=Q(dates__contains=F('dates_inner')), + name=constraint_name, + ) + with connection.schema_editor() as editor: + editor.add_constraint(RangesModel, constraint) + with connection.cursor() as cursor: + constraints = connection.introspection.get_constraints(cursor, RangesModel._meta.db_table) + self.assertIn(constraint_name, constraints) + date_1 = datetime.date(2016, 1, 1) + date_2 = datetime.date(2016, 1, 4) + with self.assertRaises(IntegrityError), transaction.atomic(): + RangesModel.objects.create( + dates=(date_1, date_2), + dates_inner=(date_1, date_2.replace(day=5)), + ) + RangesModel.objects.create( + dates=(date_1, date_2), + dates_inner=(date_1, date_2), + ) + + def test_check_constraint_datetimerange_contains(self): + constraint_name = 'timestamps_contains' + self.assertNotIn(constraint_name, self.get_constraints(RangesModel._meta.db_table)) + constraint = CheckConstraint( + check=Q(timestamps__contains=F('timestamps_inner')), + name=constraint_name, + ) + with connection.schema_editor() as editor: + editor.add_constraint(RangesModel, constraint) + with connection.cursor() as cursor: + constraints = connection.introspection.get_constraints(cursor, RangesModel._meta.db_table) + self.assertIn(constraint_name, constraints) + datetime_1 = datetime.datetime(2016, 1, 1) + datetime_2 = datetime.datetime(2016, 1, 2, 12) + with self.assertRaises(IntegrityError), transaction.atomic(): + RangesModel.objects.create( + timestamps=(datetime_1, datetime_2), + timestamps_inner=(datetime_1, datetime_2.replace(hour=13)), + ) + RangesModel.objects.create( + timestamps=(datetime_1, datetime_2), + timestamps_inner=(datetime_1, datetime_2), + ) diff --git a/tests/postgres_tests/test_ranges.py b/tests/postgres_tests/test_ranges.py index ae834b6ff09b..89f32ee77c11 100644 --- a/tests/postgres_tests/test_ranges.py +++ b/tests/postgres_tests/test_ranges.py @@ -115,11 +115,15 @@ def setUpTestData(cls): ] cls.obj = RangesModel.objects.create( dates=(cls.dates[0], cls.dates[3]), + dates_inner=(cls.dates[1], cls.dates[2]), timestamps=(cls.timestamps[0], cls.timestamps[3]), + timestamps_inner=(cls.timestamps[1], cls.timestamps[2]), ) cls.aware_obj = RangesModel.objects.create( dates=(cls.dates[0], cls.dates[3]), + dates_inner=(cls.dates[1], cls.dates[2]), timestamps=(cls.aware_timestamps[0], cls.aware_timestamps[3]), + timestamps_inner=(cls.timestamps[1], cls.timestamps[2]), ) # Objects that don't match any queries. for i in range(3, 4): @@ -140,6 +144,7 @@ def test_datetime_range_contains(self): (self.aware_timestamps[1], self.aware_timestamps[2]), Value(self.dates[0], output_field=DateTimeField()), Func(F('dates'), function='lower', output_field=DateTimeField()), + F('timestamps_inner'), ) for filter_arg in filter_args: with self.subTest(filter_arg=filter_arg): @@ -154,6 +159,7 @@ def test_date_range_contains(self): (self.dates[1], self.dates[2]), Value(self.dates[0], output_field=DateField()), Func(F('timestamps'), function='lower', output_field=DateField()), + F('dates_inner'), ) for filter_arg in filter_args: with self.subTest(filter_arg=filter_arg): @@ -361,7 +367,9 @@ class TestSerialization(PostgreSQLSimpleTestCase): '\\"bounds\\": \\"[)\\"}", "decimals": "{\\"empty\\": true}", ' '"bigints": null, "timestamps": "{\\"upper\\": \\"2014-02-02T12:12:12+00:00\\", ' '\\"lower\\": \\"2014-01-01T00:00:00+00:00\\", \\"bounds\\": \\"[)\\"}", ' - '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}" }, ' + '"timestamps_inner": null, ' + '"dates": "{\\"upper\\": \\"2014-02-02\\", \\"lower\\": \\"2014-01-01\\", \\"bounds\\": \\"[)\\"}", ' + '"dates_inner": null }, ' '"model": "postgres_tests.rangesmodel", "pk": null}]' ) From a39365c48e5d322ff111fc698234b20c9bd5fc65 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Thu, 11 Jul 2019 10:25:40 +0200 Subject: [PATCH 011/287] [2.2.x] Doc'd --no-input option for createsuperuser. Backport of 8dd5877f58f84f2b11126afbd0813e24545919ed from master --- docs/ref/django-admin.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index eabc3be460ce..c2094e962b0a 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1537,6 +1537,11 @@ the new superuser account. When run non-interactively, no password will be set, and the superuser account will not be able to log in until a password has been manually set for it. +.. django-admin-option:: --noinput, --no-input + +Suppresses all user prompts. If a suppressed prompt cannot be resolved +automatically, the command will exit with error code 1. + .. django-admin-option:: --username USERNAME .. django-admin-option:: --email EMAIL From de19a600f0d99980fe5be4bd98625b27eec8e830 Mon Sep 17 00:00:00 2001 From: Frank Wiles Date: Sat, 13 Jul 2019 14:01:07 -0500 Subject: [PATCH 012/287] [2.2.x] Fixed explanation of how to automatically create tables in database. Backport of c1b94e32fb3df25d72b5e9973da7928dddbc3a2e from master --- docs/intro/overview.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/intro/overview.txt b/docs/intro/overview.txt index bd2eb589892d..47af40533c76 100644 --- a/docs/intro/overview.txt +++ b/docs/intro/overview.txt @@ -48,16 +48,18 @@ database-schema problems. Here's a quick example: Install it ========== -Next, run the Django command-line utility to create the database tables +Next, run the Django command-line utilities to create the database tables automatically: .. console:: + $ python manage.py makemigrations $ python manage.py migrate -The :djadmin:`migrate` command looks at all your available models and creates -tables in your database for whichever tables don't already exist, as well as -optionally providing :doc:`much richer schema control `. +The :djadmin:`makemigrations` command looks at all your available models and +creates migrations for whichever tables don't already exist. :djadmin:`migrate` +runs the migrations and creates tables in your database, as well as optionally +providing :doc:`much richer schema control `. Enjoy the free API ================== From d58cde74446551b5f5242b4656022710e9b85230 Mon Sep 17 00:00:00 2001 From: Frank Wiles Date: Tue, 16 Jul 2019 07:44:00 -0500 Subject: [PATCH 013/287] [2.2.x] Updated WSGI servers ordering according to the more commonly used. Backport of fa65b90a96f27dced8cfa89126d28186b4c80fbf from master --- docs/howto/deployment/wsgi/index.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/howto/deployment/wsgi/index.txt b/docs/howto/deployment/wsgi/index.txt index b49de1ec9fa4..ffc0fbe09ac0 100644 --- a/docs/howto/deployment/wsgi/index.txt +++ b/docs/howto/deployment/wsgi/index.txt @@ -16,10 +16,10 @@ Django includes getting-started documentation for the following WSGI servers: .. toctree:: :maxdepth: 1 - modwsgi - apache-auth gunicorn uwsgi + modwsgi + apache-auth The ``application`` object ========================== From 4814db40c16028277abe854527824db8bb089066 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 16 Jul 2019 15:08:14 +0200 Subject: [PATCH 014/287] [2.2.x] Fixed heading level typo in docs/ref/contrib/postgres/fields.txt. Backport of ad4e83a6d1c0a212fae751a3125dff6e28b2390a 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 eb46074e1e40..97f4913fec31 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -887,7 +887,7 @@ Returned objects are empty ranges. Can be chained to valid lookups for a Defining your own range types -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +----------------------------- PostgreSQL allows the definition of custom range types. Django's model and form field implementations use base classes below, and psycopg2 provides a From 0088e592922e2a711815cdb90a2610a1f1704a9a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 18 Jul 2019 12:56:25 +0200 Subject: [PATCH 015/287] [2.2.x] Refs #30547 -- Clarified that partial UniqueConstraints don't affect model validation. Backport of 230d75f59c43b9731465c4ec92ad714e301637b8 from master --- docs/ref/models/constraints.txt | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/ref/models/constraints.txt b/docs/ref/models/constraints.txt index d17235332697..1d75a9ede4da 100644 --- a/docs/ref/models/constraints.txt +++ b/docs/ref/models/constraints.txt @@ -32,11 +32,12 @@ option. In general constraints are **not** checked during ``full_clean()``, and do not raise ``ValidationError``\s. Rather you'll get a database integrity - error on ``save()``. ``UniqueConstraint``\s are different in this regard, - in that they leverage the existing ``validate_unique()`` logic, and thus - enable two-stage validation. In addition to ``IntegrityError`` on - ``save()``, ``ValidationError`` is also raised during model validation when - the ``UniqueConstraint`` is violated. + error on ``save()``. ``UniqueConstraint``\s without a + :attr:`~UniqueConstraint.condition` (i.e. non-partial unique constraints) + are different in this regard, in that they leverage the existing + ``validate_unique()`` logic, and thus enable two-stage validation. In + addition to ``IntegrityError`` on ``save()``, ``ValidationError`` is also + raised during model validation when the ``UniqueConstraint`` is violated. ``CheckConstraint`` =================== From de2635fb4ea103a539d8dc080d9dee4ae0feb6d4 Mon Sep 17 00:00:00 2001 From: Davit Gachechiladze <50764979+gachdavit@users.noreply.github.com> Date: Thu, 18 Jul 2019 17:57:25 +0400 Subject: [PATCH 016/287] [2.2.x] Fixed #30648 -- Removed unnecessary overriding get_context_data() from mixins with CBVs docs. Backport of 7f612eda80db1c1c8e502aced54c2062080eae46 from master --- docs/topics/class-based-views/mixins.txt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt index ac0648df98b3..ad9fb7547bfa 100644 --- a/docs/topics/class-based-views/mixins.txt +++ b/docs/topics/class-based-views/mixins.txt @@ -461,11 +461,6 @@ Our new ``AuthorDetail`` looks like this:: def get_success_url(self): return reverse('author-detail', kwargs={'pk': self.object.pk}) - def get_context_data(self, **kwargs): - context = super().get_context_data(**kwargs) - context['form'] = self.get_form() - return context - def post(self, request, *args, **kwargs): if not request.user.is_authenticated: return HttpResponseForbidden() @@ -483,9 +478,7 @@ Our new ``AuthorDetail`` looks like this:: ``get_success_url()`` is just providing somewhere to redirect to, which gets used in the default implementation of -``form_valid()``. We have to provide our own ``post()`` as -noted earlier, and override ``get_context_data()`` to make the -:class:`~django.forms.Form` available in the context data. +``form_valid()``. We have to provide our own ``post()`` as noted earlier. A better solution ----------------- From fa3ae446d938455b47fc978cdfecb956b1238812 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 19 Jul 2019 13:02:57 +0200 Subject: [PATCH 017/287] [2.2.x] Refs #30083 -- Clarified database state of instances in signals.pre_init docs. Backport of a2e1c17f193f5017e1f6fac7d860f1f9e34d7892 from master --- docs/ref/signals.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index f2f0ab4257b8..a89560cc1e60 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -99,6 +99,13 @@ Arguments sent with this signal: ``instance`` The actual instance of the model that's just been created. + .. note:: + + ``instance._state`` isn't set before sending the ``post_init`` signal, + so ``_state`` attributes always have their default values. For example, + ``_state.db`` is ``None`` and cannot be used to check an ``instance`` + database. + ``pre_save`` ------------ From 506f800eadea2ab8c9d90f00278b4b03f3a9b771 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 19 Jul 2019 13:05:31 +0200 Subject: [PATCH 018/287] [2.2.x] Refs #30083 -- Added a warning about performing queries in pre/post_init receivers. Thanks Carlton Gibson the review. Backport of fc1182af01c391ce33d7fcf51c756829c6a11d5b from master --- docs/ref/signals.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index a89560cc1e60..19a92a875c4e 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -106,6 +106,12 @@ Arguments sent with this signal: ``_state.db`` is ``None`` and cannot be used to check an ``instance`` database. +.. warning:: + + For performance reasons, you shouldn't perform queries in receivers of + ``pre_init`` or ``post_init`` signals because they would be executed for + each instance returned during queryset iteration. + ``pre_save`` ------------ From 2d2859bec27c9e3994cff6be56dd5fe0f694a24c Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Sun, 21 Jul 2019 21:55:25 +0100 Subject: [PATCH 019/287] [2.2.x] Fixed #30506 -- Fixed crash of autoreloader when path contains null characters. Backport of 2ff517ccb6116c1be6338e6bdcf08a313defc5c7 from master. --- django/utils/autoreload.py | 14 +++++++++----- docs/releases/2.2.4.txt | 3 +++ tests/utils_tests/test_autoreload.py | 11 +++++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 2fdc2f3f83bd..3153f3f14d91 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -135,11 +135,15 @@ def iter_modules_and_files(modules, extra_files): if not filename: continue path = pathlib.Path(filename) - if not path.exists(): - # The module could have been removed, don't fail loudly if this - # is the case. - continue - results.add(path.resolve().absolute()) + try: + if not path.exists(): + # The module could have been removed, don't fail loudly if this + # is the case. + continue + results.add(path.resolve().absolute()) + except ValueError as e: + # Network filesystems may return null bytes in file paths. + logger.debug('"%s" raised when resolving path: "%s"' % (str(e), path)) return frozenset(results) diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 0ad92f4ab1ca..af0950d70058 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -18,3 +18,6 @@ Bugfixes :class:`~django.contrib.postgres.fields.DateRangeField` or :class:`~django.contrib.postgres.fields.DateTimeRangeField`, if the right hand side of an expression is the same type (:ticket:`30621`). + +* Fixed a regression in Django 2.2 where auto-reloader crashes if a file path + contains nulls characters (``'\x00'``) (:ticket:`30506`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index c7308ca53ad6..59eb32ed70d3 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -138,6 +138,17 @@ def test_main_module_without_file_is_not_resolved(self): fake_main = types.ModuleType('__main__') self.assertEqual(autoreload.iter_modules_and_files((fake_main,), frozenset()), frozenset()) + def test_path_with_embedded_null_bytes(self): + for path in ( + 'embedded_null_byte\x00.py', + 'di\x00rectory/embedded_null_byte.py', + ): + with self.subTest(path=path): + self.assertEqual( + autoreload.iter_modules_and_files((), frozenset([path])), + frozenset(), + ) + class TestCommonRoots(SimpleTestCase): def test_common_roots(self): From 61d4a159899358e3570dfc5db039651325b30992 Mon Sep 17 00:00:00 2001 From: terminator14 Date: Tue, 23 Jul 2019 07:10:58 -0600 Subject: [PATCH 020/287] [2.2.x] Fixed typo in docs/topics/http/sessions.txt. Backport of 8323691de0ba120dbdc8055063574df2b0c0afa4 from master --- docs/topics/http/sessions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/http/sessions.txt b/docs/topics/http/sessions.txt index 745c735e4608..f0311f6fa177 100644 --- a/docs/topics/http/sessions.txt +++ b/docs/topics/http/sessions.txt @@ -651,7 +651,7 @@ session for their account. If the attacker has control over ``bad.example.com``, they can use it to send their session key to you since a subdomain is permitted to set cookies on ``*.example.com``. When you visit ``good.example.com``, you'll be logged in as the attacker and might inadvertently enter your -sensitive personal data (e.g. credit card info) into the attackers account. +sensitive personal data (e.g. credit card info) into the attacker's account. Another possible attack would be if ``good.example.com`` sets its :setting:`SESSION_COOKIE_DOMAIN` to ``"example.com"`` which would cause From 4d6449e1258c88b6e4e1ccbb5e84b210371598d2 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Sun, 21 Jul 2019 22:28:39 +0100 Subject: [PATCH 021/287] [2.2.x] Fixed #30647 -- Fixed crash of autoreloader when extra directory cannot be resolved. Backport of fc75694257b5bceab82713f84fe5a1b23d641c3f from master. --- django/utils/autoreload.py | 11 +++++++++-- django/utils/translation/reloader.py | 3 +-- docs/releases/2.2.4.txt | 3 +++ tests/utils_tests/test_autoreload.py | 6 ++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/django/utils/autoreload.py b/django/utils/autoreload.py index 3153f3f14d91..aa152343d6b1 100644 --- a/django/utils/autoreload.py +++ b/django/utils/autoreload.py @@ -235,8 +235,15 @@ def __init__(self): def watch_dir(self, path, glob): path = Path(path) - if not path.is_absolute(): - raise ValueError('%s must be absolute.' % path) + try: + path = path.absolute() + except FileNotFoundError: + logger.debug( + 'Unable to watch directory %s as it cannot be resolved.', + path, + exc_info=True, + ) + return logger.debug('Watching dir %s with glob %s.', path, glob) self.directory_globs[path].add(glob) diff --git a/django/utils/translation/reloader.py b/django/utils/translation/reloader.py index 8e2d320208fd..4b363f512997 100644 --- a/django/utils/translation/reloader.py +++ b/django/utils/translation/reloader.py @@ -13,8 +13,7 @@ def watch_for_translation_changes(sender, **kwargs): directories.extend(Path(config.path) / 'locale' for config in apps.get_app_configs()) directories.extend(Path(p) for p in settings.LOCALE_PATHS) for path in directories: - absolute_path = path.absolute() - sender.watch_dir(absolute_path, '**/*.mo') + sender.watch_dir(path, '**/*.mo') def translation_file_changed(sender, file_path, **kwargs): diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index af0950d70058..16e3bf07beb1 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -21,3 +21,6 @@ Bugfixes * Fixed a regression in Django 2.2 where auto-reloader crashes if a file path contains nulls characters (``'\x00'``) (:ticket:`30506`). + +* Fixed a regression in Django 2.2 where auto-reloader crashes if a translation + directory cannot be resolved (:ticket:`30647`). diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 59eb32ed70d3..64c71bfe3f61 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -515,6 +515,12 @@ def test_watch_with_single_file(self): watched_files = list(self.reloader.watched_files()) self.assertIn(self.existing_file, watched_files) + def test_watch_dir_with_unresolvable_path(self): + path = Path('unresolvable_directory') + with mock.patch.object(Path, 'absolute', side_effect=FileNotFoundError): + self.reloader.watch_dir(path, '**/*.mo') + self.assertEqual(list(self.reloader.directory_globs), []) + def test_watch_with_glob(self): self.reloader.watch_dir(self.tempdir, '*.py') watched_files = list(self.reloader.watched_files()) From ea57c8a345c73d17d5ac3a504d8f1c1bd47a7572 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 25 Jul 2019 10:49:30 +0200 Subject: [PATCH 022/287] [2.2.x] Added stub release notes for security releases. Backport of f13147c8de725eed7038941758469aeb9bd66503 from master --- docs/releases/1.11.23.txt | 7 +++++++ docs/releases/2.1.11.txt | 7 +++++++ docs/releases/2.2.4.txt | 4 ++-- docs/releases/index.txt | 2 ++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 docs/releases/1.11.23.txt create mode 100644 docs/releases/2.1.11.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/2.1.11.txt b/docs/releases/2.1.11.txt new file mode 100644 index 000000000000..b8098334e133 --- /dev/null +++ b/docs/releases/2.1.11.txt @@ -0,0 +1,7 @@ +=========================== +Django 2.1.11 release notes +=========================== + +*August 1, 2019* + +Django 2.1.11 fixes security issues in 2.1.10. diff --git a/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 16e3bf07beb1..59c05bf0e2a1 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -2,9 +2,9 @@ Django 2.2.4 release notes ========================== -*Expected August 1, 2019* +*August 1, 2019* -Django 2.2.4 fixes several bugs in 2.2.3. +Django 2.2.4 fixes security issues and several bugs in 2.2.3. Bugfixes ======== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index e2ed3b4852c8..b2d14bb05397 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -36,6 +36,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.1.11 2.1.10 2.1.9 2.1.8 @@ -73,6 +74,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 b4139ed6eaa430874360a3a98e85bd2c91e19bc7 Mon Sep 17 00:00:00 2001 From: daniel a rios Date: Fri, 26 Jul 2019 15:11:48 +0200 Subject: [PATCH 023/287] [2.2.x] Refs #30656 -- Reorganized bulk methods in the database optimization docs. Backport of fe33fdc049df75f9dd8e2eecc8c94aefc0132cb8 from master --- docs/topics/db/optimization.txt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 671f57a8ae6b..0f39fae103b1 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -341,8 +341,13 @@ it on a ``QuerySet`` by calling Adding an index to your database may help to improve ordering performance. -Insert in bulk -============== +Use bulk methods +================ + +Use bulk methods to reduce the number of SQL statements. + +Create in bulk +-------------- When creating objects, where possible, use the :meth:`~django.db.models.query.QuerySet.bulk_create()` method to reduce the @@ -362,8 +367,13 @@ Note that there are a number of :meth:`caveats to this method `, so make sure it's appropriate for your use case. -This also applies to :class:`ManyToManyFields -`, so doing:: +Insert in bulk +-------------- + +When inserting objects into :class:`ManyToManyFields +`, use +:meth:`~django.db.models.fields.related.RelatedManager.add` with multiple +objects to reduce the number of SQL queries. For example:: my_band.members.add(me, my_friend) From f9462f4c82c503fca21fc2af5cb5d6362c36fa83 Mon Sep 17 00:00:00 2001 From: daniel a rios Date: Fri, 26 Jul 2019 15:12:29 +0200 Subject: [PATCH 024/287] [2.2.x] Fixed #30656 -- Added QuerySet.bulk_update() to the database optimization docs. Backport of 68aeb9016084290aac4f82860e17a9f4e941676e from master --- docs/topics/db/optimization.txt | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 0f39fae103b1..9d22b980bd29 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -367,6 +367,37 @@ Note that there are a number of :meth:`caveats to this method `, so make sure it's appropriate for your use case. +Update in bulk +-------------- + +.. versionadded:: 2.2 + +When updating objects, where possible, use the +:meth:`~django.db.models.query.QuerySet.bulk_update()` method to reduce the +number of SQL queries. Given a list or queryset of objects:: + + entries = Entry.objects.bulk_create([ + Entry(headline='This is a test'), + Entry(headline='This is only a test'), + ]) + +The following example:: + + entries[0].headline = 'This is not a test' + entries[1].headline = 'This is no longer a test' + Entry.objects.bulk_update(entries, ['headline']) + +...is preferable to:: + + entries[0].headline = 'This is not a test' + entries.save() + entries[1].headline = 'This is no longer a test' + entries.save() + +Note that there are a number of :meth:`caveats to this method +`, so make sure it's appropriate +for your use case. + Insert in bulk -------------- From c3289717c6f21a8cf23daff1c78c0c014b94041f Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 15 Jul 2019 11:46:09 +0200 Subject: [PATCH 025/287] [2.2.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 +++++++++++ docs/releases/2.1.11.txt | 14 +++++++++++ docs/releases/2.2.4.txt | 14 +++++++++++ .../filter_tests/test_truncatewords_html.py | 4 +-- tests/utils_tests/test_text.py | 25 ++++++++++++++++--- 6 files changed, 67 insertions(+), 8 deletions(-) diff --git a/django/utils/text.py b/django/utils/text.py index 44007beb0f1f..853436a38f3f 100644 --- a/django/utils/text.py +++ b/django/utils/text.py @@ -15,8 +15,8 @@ def capfirst(x): # Set up regular expressions -re_words = re.compile(r'<.*?>|((?:\w[-\w]*|&.*?;)+)', re.S) -re_chars = re.compile(r'<.*?>|(.)', 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/docs/releases/2.1.11.txt b/docs/releases/2.1.11.txt index b8098334e133..f4ee3dbd301e 100644 --- a/docs/releases/2.1.11.txt +++ b/docs/releases/2.1.11.txt @@ -5,3 +5,17 @@ Django 2.1.11 release notes *August 1, 2019* Django 2.1.11 fixes security issues in 2.1.10. + +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/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 59c05bf0e2a1..b22aa42482ea 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -6,6 +6,20 @@ Django 2.2.4 release notes Django 2.2.4 fixes security issues and several bugs in 2.2.3. +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. + Bugfixes ======== diff --git a/tests/template_tests/filter_tests/test_truncatewords_html.py b/tests/template_tests/filter_tests/test_truncatewords_html.py index 5daeef6cf3f7..6177fc875daa 100644 --- a/tests/template_tests/filter_tests/test_truncatewords_html.py +++ b/tests/template_tests/filter_tests/test_truncatewords_html.py @@ -16,13 +16,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 daa028a0f70b..cab324d64edb 100644 --- a/tests/utils_tests/test_text.py +++ b/tests/utils_tests/test_text.py @@ -86,6 +86,17 @@ def test_truncate_chars(self): # lazy strings are handled correctly self.assertEqual(text.Truncator(lazystr('The quick brown fox')).chars(10), 'The quick…') + def test_truncate_chars_html(self): + perf_test_values = [ + (('', None), + ('&' * 50000, '&' * 9 + '…'), + ('_X<<<<<<<<<<<>', None), + ] + for value, expected in perf_test_values: + with self.subTest(value=value): + 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)) @@ -135,11 +146,17 @@ 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: + with self.subTest(value=value): + truncator = text.Truncator(value) + self.assertEqual(value, truncator.words(50, html=True)) def test_wrap(self): digits = '1234 67 9' From e34f3c0e9ee5fc9022428fe91640638bafd4cda7 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Mon, 15 Jul 2019 12:00:06 +0200 Subject: [PATCH 026/287] [2.2.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 +++++++++++++++++ docs/releases/2.1.11.txt | 17 +++++++++++++++++ docs/releases/2.2.4.txt | 17 +++++++++++++++++ tests/utils_tests/test_html.py | 2 ++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/django/utils/html.py b/django/utils/html.py index 44a3f16459e2..7a33d5f68d5b 100644 --- a/django/utils/html.py +++ b/django/utils/html.py @@ -187,8 +187,8 @@ def strip_tags(value): value = str(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 + if value.count('<') == new_value.count('<'): + # _strip_once wasn't able to detect more tags. break value = new_value return value 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/docs/releases/2.1.11.txt b/docs/releases/2.1.11.txt index f4ee3dbd301e..9cae1e6f2efc 100644 --- a/docs/releases/2.1.11.txt +++ b/docs/releases/2.1.11.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/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index b22aa42482ea..c96537367753 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -20,6 +20,23 @@ 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`. + Bugfixes ======== diff --git a/tests/utils_tests/test_html.py b/tests/utils_tests/test_html.py index 8057fdc05104..5cc2d9b95d63 100644 --- a/tests/utils_tests/test_html.py +++ b/tests/utils_tests/test_html.py @@ -88,6 +88,8 @@ def test_strip_tags(self): ('&gotcha&#;<>', '&gotcha&#;<>'), ('ript>test</script>', 'ript>test'), ('&h', 'alert()h'), + ('>br>br>br>X', 'XX'), ) for value, output in items: with self.subTest(value=value, output=output): From 4f5b58f5cd3c57fee9972ab074f8dc6895d8f387 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 22 Jul 2019 10:45:26 +0200 Subject: [PATCH 027/287] [2.2.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 +++++++++ docs/releases/2.1.11.txt | 9 +++++++++ docs/releases/2.2.4.txt | 9 +++++++++ tests/postgres_tests/test_hstore.py | 15 ++++++++++++++- tests/postgres_tests/test_json.py | 15 ++++++++++++++- 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index 39f074b76345..8d6cd6c812cd 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: diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index 966e8f114186..be98ff2d48dd 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -109,12 +109,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/docs/releases/2.1.11.txt b/docs/releases/2.1.11.txt index 9cae1e6f2efc..0de4175b5f4f 100644 --- a/docs/releases/2.1.11.txt +++ b/docs/releases/2.1.11.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/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index c96537367753..3aac51869ca2 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -37,6 +37,15 @@ Remember that absolutely NO guarantee is provided about the results of ``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()``. + Bugfixes ======== diff --git a/tests/postgres_tests/test_hstore.py b/tests/postgres_tests/test_hstore.py index 1d7403fb203e..29936e297ecb 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -1,8 +1,9 @@ import json from django.core import checks, exceptions, serializers +from django.db import connection from django.forms import Form -from django.test.utils import isolate_apps +from django.test.utils import CaptureQueriesContext, isolate_apps from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase from .models import HStoreModel, PostgreSQLModel @@ -185,6 +186,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'], + ) + @isolate_apps('postgres_tests') class TestChecks(PostgreSQLSimpleTestCase): diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index 9acd7c8bf844..c208df1b9ff9 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -5,9 +5,10 @@ from django.core import checks, exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder +from django.db import connection from django.db.models import Count, Q from django.forms import CharField, Form, widgets -from django.test.utils import isolate_apps +from django.test.utils import CaptureQueriesContext, isolate_apps from django.utils.html import escape from . import PostgreSQLSimpleTestCase, PostgreSQLTestCase @@ -322,6 +323,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'], + ) + @isolate_apps('postgres_tests') class TestChecks(PostgreSQLSimpleTestCase): From cf694e6852b0da7799f8b53f1fb2f7d20cf17534 Mon Sep 17 00:00:00 2001 From: Florian Apolloner Date: Fri, 19 Jul 2019 17:04:53 +0200 Subject: [PATCH 028/287] [2.2.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 ++++++++++ docs/releases/2.1.11.txt | 10 ++++++++++ docs/releases/2.2.4.txt | 10 ++++++++++ tests/utils_tests/test_encoding.py | 14 ++++++++++++-- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/django/utils/encoding.py b/django/utils/encoding.py index 1a1a6d06b138..98da64730df1 100644 --- a/django/utils/encoding.py +++ b/django/utils/encoding.py @@ -225,13 +225,16 @@ def repercent_broken_unicode(path): repercent-encode any octet produced that is not part of a strictly legal UTF-8 octet sequence. """ - try: - path.decode() - 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() + 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/docs/releases/2.1.11.txt b/docs/releases/2.1.11.txt index 0de4175b5f4f..ae344f35b38c 100644 --- a/docs/releases/2.1.11.txt +++ b/docs/releases/2.1.11.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/docs/releases/2.2.4.txt b/docs/releases/2.2.4.txt index 3aac51869ca2..8a71fec7830e 100644 --- a/docs/releases/2.2.4.txt +++ b/docs/releases/2.2.4.txt @@ -46,6 +46,16 @@ CVE-2019-14234: SQL injection possibility in key and index lookups for ``JSONFie 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. + Bugfixes ======== diff --git a/tests/utils_tests/test_encoding.py b/tests/utils_tests/test_encoding.py index c461df71eed8..ea7ba5f335a4 100644 --- a/tests/utils_tests/test_encoding.py +++ b/tests/utils_tests/test_encoding.py @@ -1,4 +1,5 @@ import datetime +import sys import unittest from unittest import mock from urllib.parse import quote_plus @@ -6,8 +7,8 @@ from django.test import SimpleTestCase from django.utils.encoding import ( DjangoUnicodeDecodeError, escape_uri_path, filepath_to_uri, force_bytes, - force_text, get_system_encoding, iri_to_uri, smart_bytes, smart_text, - uri_to_iri, + force_text, get_system_encoding, iri_to_uri, repercent_broken_unicode, + smart_bytes, smart_text, uri_to_iri, ) from django.utils.functional import SimpleLazyObject from django.utils.translation import gettext_lazy @@ -90,6 +91,15 @@ def test_get_default_encoding(self): with mock.patch('locale.getdefaultlocale', side_effect=Exception): self.assertEqual(get_system_encoding(), 'ascii') + 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 8687fbe034ac5eec20e0948b98eb8a2f0b1431a1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 10:56:47 +0200 Subject: [PATCH 029/287] [2.2.x] Bumped version for 2.2.4 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index d0e1bd999a28..db9801464aef 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 4, 'alpha', 0) +VERSION = (2, 2, 4, 'final', 0) __version__ = get_version(VERSION) From 7c65adf0f80bea07b30820cd9b1b2752cd517e9a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 10:59:41 +0200 Subject: [PATCH 030/287] [2.2.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 db9801464aef..c3135d44bfba 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 4, 'final', 0) +VERSION = (2, 2, 5, 'alpha', 0) __version__ = get_version(VERSION) From 928d596886a9c7cc8b9847631c3592e88b6b7e6f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:54:24 +0200 Subject: [PATCH 031/287] [2.2.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 859d1d1cb0f5784a5573447dbca8785326849abe Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:57:24 +0200 Subject: [PATCH 032/287] [2.2.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 3bfa2f99d805ed1ce84a4e1c275d948ffeabe725 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 11:59:45 +0200 Subject: [PATCH 033/287] [2.2.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 68cc954ffba7fff0b672adf75a04f1d488efa652 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 1 Aug 2019 12:01:27 +0200 Subject: [PATCH 034/287] [2.2.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 5b294d393135babefe137ba4b87b995fed321e66 Mon Sep 17 00:00:00 2001 From: niauah <50414882+niauah@users.noreply.github.com> Date: Tue, 30 Jul 2019 11:30:41 +0800 Subject: [PATCH 035/287] [2.2.x] Corrected StreamingHttpResponse.streaming_content description in docs. Backport of 75f8264083c2c938da757fcef3678faee66b1d45 from master --- docs/ref/request-response.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 10d18a8d9d39..22cc01605735 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -1069,7 +1069,8 @@ Attributes .. attribute:: StreamingHttpResponse.streaming_content - An iterator of strings representing the content. + An iterator of the response content, bytestring encoded according to + :attr:`HttpResponse.charset`. .. attribute:: StreamingHttpResponse.status_code From 2c66f340bb50ed6790d839157dff64456b497a43 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 2 Aug 2019 20:32:21 +0200 Subject: [PATCH 036/287] [2.2.x] Added stub release notes for 2.2.5. Backport of 1af469e67fd3928a4b01722d4706c066000014e9 from master --- docs/releases/2.2.5.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.5.txt diff --git a/docs/releases/2.2.5.txt b/docs/releases/2.2.5.txt new file mode 100644 index 000000000000..178588327ee6 --- /dev/null +++ b/docs/releases/2.2.5.txt @@ -0,0 +1,12 @@ +========================== +Django 2.2.5 release notes +========================== + +*Expected September 2, 2019* + +Django 2.2.5 fixes several bugs in 2.2.4. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index b2d14bb05397..654b5d026682 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.5 2.2.4 2.2.3 2.2.2 From 1265a26b2fa3cbd73a2bccd91b700268bc28bc07 Mon Sep 17 00:00:00 2001 From: Adnan Umer Date: Sat, 3 Aug 2019 16:22:27 +0500 Subject: [PATCH 037/287] [2.2.x] Fixed #30673 -- Relaxed system check for db_table collision when database routers are installed by turning the error into a warning. Backport of 8d3519071ec001f763b70a3a1f98ae2e980bd552 from master. --- AUTHORS | 1 + django/core/checks/model_checks.py | 20 ++++++-- docs/ref/checks.txt | 2 + docs/releases/2.2.5.txt | 4 +- tests/check_framework/test_model_checks.py | 60 +++++++++++++++++++++- 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/AUTHORS b/AUTHORS index 90b49ced9026..77947dcf452a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -18,6 +18,7 @@ answer newbie questions, and generally made Django that much better: Adam Malinowski Adam Vandenberg Adiyat Mubarak + Adnan Umer Adrian Holovaty Adrien Lemaire Afonso Fernández Nogueira diff --git a/django/core/checks/model_checks.py b/django/core/checks/model_checks.py index 8514fb4d12c2..eeb95dd6bc0b 100644 --- a/django/core/checks/model_checks.py +++ b/django/core/checks/model_checks.py @@ -4,7 +4,8 @@ from itertools import chain from django.apps import apps -from django.core.checks import Error, Tags, register +from django.conf import settings +from django.core.checks import Error, Tags, Warning, register @register(Tags.models) @@ -29,14 +30,25 @@ def check_all_models(app_configs=None, **kwargs): ) else: errors.extend(model.check(**kwargs)) + if settings.DATABASE_ROUTERS: + error_class, error_id = Warning, 'models.W035' + error_hint = ( + 'You have configured settings.DATABASE_ROUTERS. Verify that %s ' + 'are correctly routed to separate databases.' + ) + else: + error_class, error_id = Error, 'models.E028' + error_hint = None for db_table, model_labels in db_table_models.items(): if len(model_labels) != 1: + model_labels_str = ', '.join(model_labels) errors.append( - Error( + error_class( "db_table '%s' is used by multiple models: %s." - % (db_table, ', '.join(db_table_models[db_table])), + % (db_table, model_labels_str), obj=db_table, - id='models.E028', + hint=(error_hint % model_labels_str) if error_hint else None, + id=error_id, ) ) return errors diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index a4267131ed47..b1e48b2c1b9e 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -304,6 +304,8 @@ Models * **models.W027**: ```` does not support check constraints. * **models.E028**: ``db_table`` ```` is used by multiple models: ````. +* **models.W035**: ``db_table`` ```` is used by multiple models: + ````. Security -------- diff --git a/docs/releases/2.2.5.txt b/docs/releases/2.2.5.txt index 178588327ee6..718ec8d8888c 100644 --- a/docs/releases/2.2.5.txt +++ b/docs/releases/2.2.5.txt @@ -9,4 +9,6 @@ Django 2.2.5 fixes several bugs in 2.2.4. Bugfixes ======== -* ... +* Relaxed the system check added in Django 2.2 for models to reallow use of the + same ``db_table`` by multiple models when database routers are installed + (:ticket:`30673`). diff --git a/tests/check_framework/test_model_checks.py b/tests/check_framework/test_model_checks.py index 2e55ad637d5a..320a8fdd6e10 100644 --- a/tests/check_framework/test_model_checks.py +++ b/tests/check_framework/test_model_checks.py @@ -1,12 +1,16 @@ from django.core import checks -from django.core.checks import Error +from django.core.checks import Error, Warning from django.db import models from django.test import SimpleTestCase from django.test.utils import ( - isolate_apps, modify_settings, override_system_checks, + isolate_apps, modify_settings, override_settings, override_system_checks, ) +class EmptyRouter: + pass + + @isolate_apps('check_framework', attr_name='apps') @override_system_checks([checks.model_checks.check_all_models]) class DuplicateDBTableTests(SimpleTestCase): @@ -28,6 +32,30 @@ class Meta: ) ]) + @override_settings(DATABASE_ROUTERS=['check_framework.test_model_checks.EmptyRouter']) + def test_collision_in_same_app_database_routers_installed(self): + class Model1(models.Model): + class Meta: + db_table = 'test_table' + + class Model2(models.Model): + class Meta: + db_table = 'test_table' + + self.assertEqual(checks.run_checks(app_configs=self.apps.get_app_configs()), [ + Warning( + "db_table 'test_table' is used by multiple models: " + "check_framework.Model1, check_framework.Model2.", + hint=( + 'You have configured settings.DATABASE_ROUTERS. Verify ' + 'that check_framework.Model1, check_framework.Model2 are ' + 'correctly routed to separate databases.' + ), + obj='test_table', + id='models.W035', + ) + ]) + @modify_settings(INSTALLED_APPS={'append': 'basic'}) @isolate_apps('basic', 'check_framework', kwarg_name='apps') def test_collision_across_apps(self, apps): @@ -50,6 +78,34 @@ class Meta: ) ]) + @modify_settings(INSTALLED_APPS={'append': 'basic'}) + @override_settings(DATABASE_ROUTERS=['check_framework.test_model_checks.EmptyRouter']) + @isolate_apps('basic', 'check_framework', kwarg_name='apps') + def test_collision_across_apps_database_routers_installed(self, apps): + class Model1(models.Model): + class Meta: + app_label = 'basic' + db_table = 'test_table' + + class Model2(models.Model): + class Meta: + app_label = 'check_framework' + db_table = 'test_table' + + self.assertEqual(checks.run_checks(app_configs=apps.get_app_configs()), [ + Warning( + "db_table 'test_table' is used by multiple models: " + "basic.Model1, check_framework.Model2.", + hint=( + 'You have configured settings.DATABASE_ROUTERS. Verify ' + 'that basic.Model1, check_framework.Model2 are correctly ' + 'routed to separate databases.' + ), + obj='test_table', + id='models.W035', + ) + ]) + def test_no_collision_for_unmanaged_models(self): class Unmanaged(models.Model): class Meta: From c5cbb797d7b90aa5d262c46520e631c0ae7f5204 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Fri, 9 Aug 2019 09:56:30 +0200 Subject: [PATCH 038/287] [2.2.x] Fixed #30688 -- Clarified base_manager_name docs. Backport of 514efa3129792ec2abb2444f3e7aeb3f21a38386 from master --- docs/ref/models/options.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index ffc8d51808d8..e70a4b03a0ba 100644 --- a/docs/ref/models/options.txt +++ b/docs/ref/models/options.txt @@ -38,8 +38,8 @@ Available ``Meta`` options .. attribute:: Options.base_manager_name - The name of the manager to use for the model's - :attr:`~django.db.models.Model._base_manager`. + The attribute name of the manager, for example, ``'objects'``, to use for + the model's :attr:`~django.db.models.Model._base_manager`. ``db_table`` ------------ From 8a3a3280209118265845003b7fe3554aa3cd6da4 Mon Sep 17 00:00:00 2001 From: Akash Agrawal Date: Sun, 11 Aug 2019 06:56:44 +0530 Subject: [PATCH 039/287] [2.2.x] Fixed #30670 -- Doc'd SchemaEditor.add/remove_constraint(). Backport of af08a5441585393b287535b9417436b896bd8052 from master --- AUTHORS | 1 + docs/ref/schema-editor.txt | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/AUTHORS b/AUTHORS index 77947dcf452a..ab06700192e3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -27,6 +27,7 @@ answer newbie questions, and generally made Django that much better: Ahmad Al-Ibrahim Ahmed Eltawela ajs + Akash Agrawal Akis Kesoglou Aksel Ethem Akshesh Doshi diff --git a/docs/ref/schema-editor.txt b/docs/ref/schema-editor.txt index 1edbeb93afd0..20cd59ad6a52 100644 --- a/docs/ref/schema-editor.txt +++ b/docs/ref/schema-editor.txt @@ -81,6 +81,24 @@ Adds ``index`` to ``model``’s table. Removes ``index`` from ``model``’s table. +``add_constraint()`` +-------------------- + +.. method:: BaseDatabaseSchemaEditor.add_constraint(model, constraint) + +.. versionadded:: 2.2 + +Adds ``constraint`` to ``model``'s table. + +``remove_constraint()`` +----------------------- + +.. method:: BaseDatabaseSchemaEditor.remove_constraint(model, constraint) + +.. versionadded:: 2.2 + +Removes ``constraint`` from ``model``'s table. + ``alter_unique_together()`` --------------------------- From be4a2a0358fefeae33b22e55e9875cc757bf5101 Mon Sep 17 00:00:00 2001 From: Akash Agrawal Date: Sat, 10 Aug 2019 22:38:00 +0530 Subject: [PATCH 040/287] [2.2.x] Fixed #30696 -- Removed obsolete mention of CheckConstraint in the AddConstraint docs. Follow up to 8eae094638acf802c8047b341d126d94bc9b45a3. Backport of c1b26c77a94c13b350d80e9a2f8d0393dee737cd from master --- docs/ref/migration-operations.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index d84fee2ade5c..f7778c297986 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -214,8 +214,8 @@ Removes the index named ``name`` from the model with ``model_name``. .. versionadded:: 2.2 -Creates a constraint in the database table for the model with ``model_name``. -``constraint`` is an instance of :class:`~django.db.models.CheckConstraint`. +Creates a :doc:`constraint ` in the database table for +the model with ``model_name``. ``RemoveConstraint`` -------------------- From 52a7759a4916484953c8e5a7d2f260dd1c1e3c12 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 14 Aug 2019 15:25:35 +0200 Subject: [PATCH 041/287] [2.2.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/2.1.12.txt | 15 +++++++++++++++ docs/releases/2.2.5.txt | 5 +++++ docs/releases/index.txt | 2 ++ tests/postgres_tests/test_hstore.py | 9 +++++++++ tests/postgres_tests/test_json.py | 21 ++++++++++++++++++++- 8 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 docs/releases/1.11.24.txt create mode 100644 docs/releases/2.1.12.txt diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index 8d6cd6c812cd..33a741b0c8dc 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: diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index be98ff2d48dd..be93b11d9fa2 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -112,7 +112,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/2.1.12.txt b/docs/releases/2.1.12.txt new file mode 100644 index 000000000000..696a6c4890f0 --- /dev/null +++ b/docs/releases/2.1.12.txt @@ -0,0 +1,15 @@ +=========================== +Django 2.1.12 release notes +=========================== + +*Expected September 2, 2019* + +Django 2.1.12 fixes a regression in 2.1.11. + +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/2.2.5.txt b/docs/releases/2.2.5.txt index 718ec8d8888c..0b9310a34471 100644 --- a/docs/releases/2.2.5.txt +++ b/docs/releases/2.2.5.txt @@ -12,3 +12,8 @@ Bugfixes * Relaxed the system check added in Django 2.2 for models to reallow use of the same ``db_table`` by multiple models when database routers are installed (:ticket:`30673`). + +* 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 654b5d026682..b64e8dcfd001 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -37,6 +37,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.1.12 2.1.11 2.1.10 2.1.9 @@ -75,6 +76,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 29936e297ecb..45a5e99dd115 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -2,6 +2,7 @@ from django.core import checks, 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, isolate_apps @@ -11,6 +12,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 @@ -127,6 +129,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 c208df1b9ff9..d6d42965efc0 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -6,7 +6,9 @@ from django.core import checks, exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder from django.db import connection -from django.db.models import Count, Q +from django.db.models import Count, F, Q +from django.db.models.expressions import RawSQL +from django.db.models.functions import Cast from django.forms import CharField, Form, widgets from django.test.utils import CaptureQueriesContext, isolate_apps from django.utils.html import escape @@ -177,6 +179,23 @@ def test_ordering_grouping_by_key_transform(self): operator.itemgetter('key', 'count'), ) + 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'), + chain=KeyTransform('0', 'key'), + expr=KeyTransform('0', Cast('key', JSONField())), + ).filter(chain=F('expr')), + [self.objs[8]], + ) + def test_deep_values(self): query = JSONModel.objects.values_list('field__k__l') self.assertSequenceEqual( From c2732e6839fffd96b136607f10af95fe58e0de17 Mon Sep 17 00:00:00 2001 From: zeyneloz Date: Thu, 15 Aug 2019 06:54:41 +0200 Subject: [PATCH 042/287] [2.2.x] Fixed #30449 -- Fixed RelatedFieldListFilter/RelatedOnlyFieldListFilter to respect model's Meta.ordering. Regression in 6d4e5feb79f7eabe8a0c7c4b87f25b1a7f87ca0b. Co-Authored-By: Mariusz Felisiak Backport of 00035672a460b6eb5442d2837bc783f8af28c6f3 from master --- django/db/models/fields/__init__.py | 6 ++- django/db/models/fields/reverse_related.py | 5 +- docs/releases/2.2.5.txt | 5 ++ tests/admin_filters/tests.py | 57 ++++++++++++++++++++++ tests/model_fields/tests.py | 20 +++++++- 5 files changed, 88 insertions(+), 5 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 324bb0bf75bc..2210b56dbe0c 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -826,9 +826,11 @@ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, limit_ if hasattr(self.remote_field, 'get_related_field') else 'pk' ) + qs = rel_model._default_manager.complex_filter(limit_choices_to) + if ordering: + qs = qs.order_by(*ordering) return (blank_choice if include_blank else []) + [ - (choice_func(x), str(x)) - for x in rel_model._default_manager.complex_filter(limit_choices_to).order_by(*ordering) + (choice_func(x), str(x)) for x in qs ] def value_to_string(self, obj): diff --git a/django/db/models/fields/reverse_related.py b/django/db/models/fields/reverse_related.py index eb6b9342594b..700410a086bb 100644 --- a/django/db/models/fields/reverse_related.py +++ b/django/db/models/fields/reverse_related.py @@ -122,8 +122,11 @@ def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH, orderi Analog of django.db.models.fields.Field.get_choices(), provided initially for utilization by RelatedFieldListFilter. """ + qs = self.related_model._default_manager.all() + if ordering: + qs = qs.order_by(*ordering) return (blank_choice if include_blank else []) + [ - (x.pk, str(x)) for x in self.related_model._default_manager.order_by(*ordering) + (x.pk, str(x)) for x in qs ] def is_hidden(self): diff --git a/docs/releases/2.2.5.txt b/docs/releases/2.2.5.txt index 0b9310a34471..9f143c8d96d4 100644 --- a/docs/releases/2.2.5.txt +++ b/docs/releases/2.2.5.txt @@ -17,3 +17,8 @@ Bugfixes :class:`~django.contrib.postgres.fields.JSONField` and :class:`~django.contrib.postgres.fields.HStoreField` when using on expressions with params (:ticket:`30672`). + +* Fixed a regression in Django 2.2 where + :attr:`ModelAdmin.list_filter ` + choices to foreign objects don't respect a model's ``Meta.ordering`` + (:ticket:`30449`). diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index 4ff7d012e553..75563bbaaf79 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -591,6 +591,22 @@ class BookAdmin(ModelAdmin): expected = [(self.john.pk, 'John Blue'), (self.jack.pk, 'Jack Red')] self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedfieldlistfilter_foreignkey_default_ordering(self): + """RelatedFieldListFilter ordering respects Model.ordering.""" + class BookAdmin(ModelAdmin): + list_filter = ('employee',) + + self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering) + Employee._meta.ordering = ('name',) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(self.jack.pk, 'Jack Red'), (self.john.pk, 'John Blue')] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedfieldlistfilter_manytomany(self): modeladmin = BookAdmin(Book, site) @@ -696,6 +712,23 @@ def test_relatedfieldlistfilter_reverse_relationships(self): filterspec = changelist.get_filters(request)[0] self.assertEqual(len(filterspec), 0) + def test_relatedfieldlistfilter_reverse_relationships_default_ordering(self): + self.addCleanup(setattr, Book._meta, 'ordering', Book._meta.ordering) + Book._meta.ordering = ('title',) + modeladmin = CustomUserAdmin(User, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [ + (self.bio_book.pk, 'Django: a biography'), + (self.djangonaut_book.pk, 'Djangonaut: an art of living'), + (self.guitar_book.pk, 'Guitar for dummies'), + (self.django_book.pk, 'The Django Book') + ] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedonlyfieldlistfilter_foreignkey(self): modeladmin = BookAdminRelatedOnlyFilter(Book, site) @@ -708,6 +741,30 @@ def test_relatedonlyfieldlistfilter_foreignkey(self): expected = [(self.alfred.pk, 'alfred'), (self.bob.pk, 'bob')] self.assertEqual(sorted(filterspec.lookup_choices), sorted(expected)) + def test_relatedonlyfieldlistfilter_foreignkey_default_ordering(self): + """RelatedOnlyFieldListFilter ordering respects Meta.ordering.""" + class BookAdmin(ModelAdmin): + list_filter = ( + ('employee', RelatedOnlyFieldListFilter), + ) + + albert = Employee.objects.create(name='Albert Green', department=self.dev) + self.djangonaut_book.employee = albert + self.djangonaut_book.save() + self.bio_book.employee = self.jack + self.bio_book.save() + + self.addCleanup(setattr, Employee._meta, 'ordering', Employee._meta.ordering) + Employee._meta.ordering = ('name',) + modeladmin = BookAdmin(Book, site) + + request = self.request_factory.get('/') + request.user = self.alfred + changelist = modeladmin.get_changelist_instance(request) + filterspec = changelist.get_filters(request)[0][0] + expected = [(albert.pk, 'Albert Green'), (self.jack.pk, 'Jack Red')] + self.assertEqual(filterspec.lookup_choices, expected) + def test_relatedonlyfieldlistfilter_underscorelookup_foreignkey(self): Department.objects.create(code='TEST', description='Testing') self.djangonaut_book.employee = self.john diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 42e784db5430..9e38f0fcf006 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -163,9 +163,9 @@ class GetChoicesOrderingTests(TestCase): @classmethod def setUpTestData(cls): - cls.foo1 = Foo.objects.create(a='a', d='12.34') + cls.foo1 = Foo.objects.create(a='a', d='12.35') cls.foo2 = Foo.objects.create(a='b', d='12.34') - cls.bar1 = Bar.objects.create(a=cls.foo1, b='a') + cls.bar1 = Bar.objects.create(a=cls.foo1, b='b') cls.bar2 = Bar.objects.create(a=cls.foo2, b='a') cls.field = Bar._meta.get_field('a') @@ -182,6 +182,14 @@ def test_get_choices(self): [self.foo2, self.foo1] ) + def test_get_choices_default_ordering(self): + self.addCleanup(setattr, Foo._meta, 'ordering', Foo._meta.ordering) + Foo._meta.ordering = ('d',) + self.assertChoicesEqual( + self.field.get_choices(include_blank=False), + [self.foo2, self.foo1] + ) + def test_get_choices_reverse_related_field(self): self.assertChoicesEqual( self.field.remote_field.get_choices(include_blank=False, ordering=('a',)), @@ -191,3 +199,11 @@ def test_get_choices_reverse_related_field(self): self.field.remote_field.get_choices(include_blank=False, ordering=('-a',)), [self.bar2, self.bar1] ) + + def test_get_choices_reverse_related_field_default_ordering(self): + self.addCleanup(setattr, Bar._meta, 'ordering', Bar._meta.ordering) + Bar._meta.ordering = ('b',) + self.assertChoicesEqual( + self.field.remote_field.get_choices(include_blank=False), + [self.bar2, self.bar1] + ) From 6624a3de286ccebf2dafba5a3e9b5ee91ae43cf9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 15 Aug 2019 19:38:12 +0200 Subject: [PATCH 043/287] [2.2.x] Fixed test_json.TestQuerying.test_key_transform_expression() on Python 3.5. --- tests/postgres_tests/test_json.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index d6d42965efc0..3a364ec8fb12 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -190,6 +190,7 @@ 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')), From 556cef1ba8facbd3231f6351956a6581c6367632 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sat, 17 Aug 2019 09:20:36 +0200 Subject: [PATCH 044/287] [2.2.x] Fixed #30694 -- Documented FileResponse does not seek its file source. Backport of 7203efb799969b4662ecb58f4cefd2a5f2e0062b from master --- docs/howto/outputting-pdf.txt | 1 + docs/ref/request-response.txt | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docs/howto/outputting-pdf.txt b/docs/howto/outputting-pdf.txt index 9950c4316ccf..986a5b252c57 100644 --- a/docs/howto/outputting-pdf.txt +++ b/docs/howto/outputting-pdf.txt @@ -67,6 +67,7 @@ Here's a "Hello World" example:: # FileResponse sets the Content-Disposition header so that browsers # present the option to save the file. + buffer.seek(0) return FileResponse(buffer, as_attachment=True, filename='hello.pdf') The code and comments should be self-explanatory, but a few things deserve a diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 22cc01605735..30ad0ccf4259 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -1106,6 +1106,8 @@ Attributes If ``open_file`` doesn't have a name or if the name of ``open_file`` isn't appropriate, provide a custom file name using the ``filename`` parameter. + Note that if you pass a file-like object like ``io.BytesIO``, it's your + task to ``seek()`` it before passing it to ``FileResponse``. The ``Content-Length``, ``Content-Type``, and ``Content-Disposition`` headers are automatically set when they can be guessed from contents of From 9ecff1272064c4d517948080e7731023eea448ce Mon Sep 17 00:00:00 2001 From: bcye Date: Mon, 19 Aug 2019 01:28:09 +0200 Subject: [PATCH 045/287] [2.2.x] Fixed broken links to Dive Into Python 3 book. Backport of ac34fccdb900788c2e024ea61bbaf4be7a46cce1 from master --- docs/intro/contributing.txt | 2 +- docs/intro/index.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index e234612ef559..39cbebb95e68 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -43,7 +43,7 @@ so that it can be of use to the widest audience. to |django-developers| or drop by `#django-dev on irc.freenode.net`__ to chat with other Django users who might be able to help. -__ https://www.diveinto.org/python3/ +__ https://diveinto.org/python3/table-of-contents.html __ irc://irc.freenode.net/django-dev What does this tutorial cover? diff --git a/docs/intro/index.txt b/docs/intro/index.txt index 23d7588af8c1..1be0facb2234 100644 --- a/docs/intro/index.txt +++ b/docs/intro/index.txt @@ -36,5 +36,5 @@ place: read this material to quickly get up and running. .. _python: https://python.org/ .. _list of Python resources for non-programmers: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers - .. _Dive Into Python: https://www.diveinto.org/python3/ + .. _Dive Into Python: https://diveinto.org/python3/table-of-contents.html .. _books about Python: https://wiki.python.org/moin/PythonBooks From 98464dde1259d865c7a211e68b64c642005ce386 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 19 Aug 2019 11:43:06 +0200 Subject: [PATCH 046/287] [2.2.x] Fixed broken links and redirects to OGR library in docs. Backport of 49aeabbf27637dbdb7a96d43b164a20c8c8ea70b from master --- docs/ref/contrib/gis/gdal.txt | 12 ++++++------ docs/ref/contrib/gis/install/geolibs.txt | 3 ++- docs/ref/contrib/gis/tutorial.txt | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/ref/contrib/gis/gdal.txt b/docs/ref/contrib/gis/gdal.txt index c1860a204db1..4043a6baa14e 100644 --- a/docs/ref/contrib/gis/gdal.txt +++ b/docs/ref/contrib/gis/gdal.txt @@ -22,7 +22,7 @@ to raster (image) data. capabilities of OGR and GDAL's raster features at this time. __ https://www.gdal.org/ -__ https://www.gdal.org/ogr_arch.html +__ https://gdal.org/user/vector_data_model.html Overview ======== @@ -92,7 +92,7 @@ each feature in that layer. Returns the name of the data source. -__ https://www.gdal.org/ogr_formats.html +__ https://gdal.org/drivers/vector/ ``Layer`` --------- @@ -446,7 +446,7 @@ coordinate transformation:: :class:`Feature.geom` attribute, when reading vector data from :class:`Layer` (which is in turn a part of a :class:`DataSource`). - __ https://www.gdal.org/classOGRGeometry.html + __ https://gdal.org/api/ogrgeometry_cpp.html#ogrgeometry-class .. classmethod:: from_gml(gml_string) @@ -1176,7 +1176,7 @@ blue. needed. For instance, use ``GTiff`` for a ``GeoTiff`` file. For a list of file types, see also the `GDAL Raster Formats`__ list. - __ https://www.gdal.org/formats_list.html + __ https://gdal.org/drivers/raster/ An in-memory raster is created through the following example: @@ -1403,7 +1403,7 @@ blue. Returns a string with a summary of the raster. This is equivalent to the `gdalinfo`__ command line utility. - __ https://www.gdal.org/gdalinfo.html + __ https://gdal.org/programs/gdalinfo.html .. attribute:: metadata @@ -1732,7 +1732,7 @@ Key Default Usage ... } ... }) -__ https://www.gdal.org/frmt_gtiff.html +__ https://gdal.org/drivers/raster/gtiff.html The ``band_input`` dictionary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/ref/contrib/gis/install/geolibs.txt b/docs/ref/contrib/gis/install/geolibs.txt index 877c894bee52..1c44e83db7bf 100644 --- a/docs/ref/contrib/gis/install/geolibs.txt +++ b/docs/ref/contrib/gis/install/geolibs.txt @@ -240,4 +240,5 @@ the GDAL library. For example:: It is easier to install the shifting files now, then to have debug a problem caused by their absence later. .. [#] Specifically, GeoDjango provides support for the `OGR - `_ library, a component of GDAL. + `_ library, a component of + GDAL. diff --git a/docs/ref/contrib/gis/tutorial.txt b/docs/ref/contrib/gis/tutorial.txt index 072e8a3311bd..299fb360824d 100644 --- a/docs/ref/contrib/gis/tutorial.txt +++ b/docs/ref/contrib/gis/tutorial.txt @@ -302,7 +302,7 @@ besides the tools included within GeoDjango, you may also use the following: * `shp2pgsql`_: This utility included with PostGIS imports ESRI shapefiles into PostGIS. -.. _ogr2ogr: https://www.gdal.org/ogr2ogr.html +.. _ogr2ogr: https://gdal.org/programs/ogr2ogr.html .. _shp2pgsql: https://postgis.net/docs/using_postgis_dbmanagement.html#shp2pgsql_usage .. _gdalinterface: From 4a24fe940e5f5d519ffff80fce541595919eb754 Mon Sep 17 00:00:00 2001 From: Garry Polley Date: Mon, 19 Aug 2019 05:27:10 -0500 Subject: [PATCH 047/287] [2.2.x] Removed outdated examples of flatpages served sites. Removing the lawrence journal flat page examples because one is no longer a web page (404) and the other is no longer served by Django: https://www.reddit.com/r/django/comments/8v0fpb/the_lawrence_journalworld_where_django_was/ Backport of 10528a81edaca4275e4762b82336e129b0cfe17d from master --- docs/ref/contrib/flatpages.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/ref/contrib/flatpages.txt b/docs/ref/contrib/flatpages.txt index f9126cc9e2ee..61ff6dceb176 100644 --- a/docs/ref/contrib/flatpages.txt +++ b/docs/ref/contrib/flatpages.txt @@ -20,11 +20,6 @@ template. It can be associated with one, or multiple, sites. The content field may optionally be left blank if you prefer to put your content in a custom template. -Here are some examples of flatpages on Django-powered sites: - -* http://www.lawrence.com/about/contact/ -* http://www2.ljworld.com/site/rules/ - Installation ============ From 0dc3ad163a7343347af710f4e057fbdb3d0eacf4 Mon Sep 17 00:00:00 2001 From: Harrison88 Date: Tue, 20 Aug 2019 05:01:10 -0500 Subject: [PATCH 048/287] [2.2.x] Fixed #30695 -- Used relative path in default_storage docs example. Backport of 1e429df748867097451bf0b45d1080ae6828d921 from master --- docs/topics/files.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 6a2ff44f0bc6..6318f207e2f9 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -143,14 +143,14 @@ useful -- you can use the global default storage system:: >>> from django.core.files.base import ContentFile >>> from django.core.files.storage import default_storage - >>> path = default_storage.save('/path/to/file', ContentFile('new content')) + >>> path = default_storage.save('path/to/file', ContentFile(b'new content')) >>> path - '/path/to/file' + 'path/to/file' >>> default_storage.size(path) 11 >>> default_storage.open(path).read() - 'new content' + b'new content' >>> default_storage.delete(path) >>> default_storage.exists(path) From 9b7c5a92ec5446fc4295038990acc1e94ef1f545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Freitag?= Date: Wed, 21 Aug 2019 10:57:24 +0200 Subject: [PATCH 049/287] [2.2.x] Fixed broken links and redirects in documentation. Removed reference to custom builds, feature removed in https://github.com/openlayers/openlayers/commit/8e6b3bba994f5908e51c492ac314240863178789. Backport of 9a88e43aeba6cc85ffb2a48a06c296ccf68e0d71 from master --- docs/ref/contrib/gis/forms-api.txt | 6 ++---- docs/ref/contrib/postgres/fields.txt | 2 +- docs/releases/1.7.2.txt | 2 +- docs/topics/testing/tools.txt | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/ref/contrib/gis/forms-api.txt b/docs/ref/contrib/gis/forms-api.txt index 5fbbdb2f9d56..1320815ad694 100644 --- a/docs/ref/contrib/gis/forms-api.txt +++ b/docs/ref/contrib/gis/forms-api.txt @@ -155,10 +155,8 @@ Widget classes ``OpenLayersWidget`` and :class:`OSMWidget` use the ``openlayers.js`` file hosted on the ``cdnjs.cloudflare.com`` content-delivery network. You can subclass these widgets in order to specify your own version of the - ``OpenLayers.js`` file `tailored to your needs`_ in the ``js`` property of - the inner ``Media`` class (see :ref:`assets-as-a-static-definition`). - - .. _tailored to your needs: https://openlayers.org/en/latest/doc/tutorials/custom-builds.html + ``OpenLayers.js`` file in the ``js`` property of the inner ``Media`` class + (see :ref:`assets-as-a-static-definition`). ``OSMWidget`` diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 97f4913fec31..44186c766085 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -284,7 +284,7 @@ transform do not change. For example:: similar to PostgreSQL's ``text`` type. .. _citext: https://www.postgresql.org/docs/current/citext.html - .. _the performance considerations: https://www.postgresql.org/docs/current/citext.html#AEN178177 + .. _the performance considerations: https://www.postgresql.org/docs/current/citext.html#id-1.11.7.17.7 ``HStoreField`` =============== diff --git a/docs/releases/1.7.2.txt b/docs/releases/1.7.2.txt index 2b6725265e7a..171d938861ce 100644 --- a/docs/releases/1.7.2.txt +++ b/docs/releases/1.7.2.txt @@ -157,7 +157,7 @@ Bugfixes could crash with an ``AppRegistryNotReady`` exception (:ticket:`24007`). * Added quoting to field indexes in the SQL generated by migrations to prevent - a crash when the index name requires it (:ticket:`#24015`). + a crash when the index name requires it (:ticket:`24015`). * Added ``datetime.time`` support to migrations questioner (:ticket:`23998`). diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 62b37bb63b9f..2e615cd4fda9 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -987,7 +987,7 @@ out the `full reference`_ for more details. `Selenium documentation`_ for more information. .. _Selenium FAQ: https://web.archive.org/web/20160129132110/http://code.google.com/p/selenium/wiki/FrequentlyAskedQuestions#Q:_WebDriver_fails_to_find_elements_/_Does_not_block_on_page_loa - .. _Selenium documentation: http://seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits + .. _Selenium documentation: https://www.seleniumhq.org/docs/04_webdriver_advanced.html#explicit-waits Test cases features =================== From 56f7a62d28a3b534ec68aed2610425ce28a2870d Mon Sep 17 00:00:00 2001 From: Ali Mirlou Date: Fri, 23 Aug 2019 11:18:27 +0430 Subject: [PATCH 050/287] [2.2.x] Fixed typo in docs/ref/applications.txt. Backport of 33b9b23bbb6c373843ef184d24438c1bdff37c82 from master --- docs/ref/applications.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index 0875b5080890..e735fce951e2 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -13,7 +13,7 @@ This registry is simply called :attr:`~django.apps.apps` and it's available in >>> from django.apps import apps >>> apps.get_app_config('admin').verbose_name - 'Admin' + 'Administration' Projects and applications ========================= From 6c17b865905c203b21c1bcb6079261bd1cbf5e6a Mon Sep 17 00:00:00 2001 From: Matthew Schinckel Date: Tue, 27 Aug 2019 16:49:49 +0200 Subject: [PATCH 051/287] [2.2.x] Doc'd for_save argument of Expression.resolve_expression(). Backport of dba749917f73ce517448a42fed38dd61382e92a7 from master --- docs/ref/models/expressions.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/ref/models/expressions.txt b/docs/ref/models/expressions.txt index a26e918e70e6..4729cada585a 100644 --- a/docs/ref/models/expressions.txt +++ b/docs/ref/models/expressions.txt @@ -946,6 +946,9 @@ calling the appropriate methods on the wrapped expression. ``summarize`` is a boolean that, when ``True``, signals that the query being computed is a terminal aggregate query. + ``for_save`` is a boolean that, when ``True``, signals that the query + being executed is performing a create or update. + .. method:: get_source_expressions() Returns an ordered list of inner expressions. For example:: From 6402855098f773f15adc2139286a552ded84f3d7 Mon Sep 17 00:00:00 2001 From: Tom Forbes Date: Sun, 25 Aug 2019 18:13:15 +0100 Subject: [PATCH 052/287] [2.2.x] Fixed #30500 -- Fixed race condition in loading URLconf module. --- django/urls/resolvers.py | 13 +++++++++---- docs/releases/2.2.5.txt | 3 +++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 9d3379a8217c..5b722474c9ec 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -381,6 +381,7 @@ def __init__(self, pattern, urlconf_name, default_kwargs=None, app_name=None, na self._callback_strs = set() self._populated = False self._local = threading.local() + self._urlconf_lock = threading.Lock() def __repr__(self): if isinstance(self.urlconf_name, list) and self.urlconf_name: @@ -568,10 +569,14 @@ def resolve(self, path): @cached_property def urlconf_module(self): - if isinstance(self.urlconf_name, str): - return import_module(self.urlconf_name) - else: - return self.urlconf_name + # import_module is not thread safe if the module throws an exception + # during import, and can return an empty module object in Python < 3.6 + # (see https://bugs.python.org/issue36284). + with self._urlconf_lock: + if isinstance(self.urlconf_name, str): + return import_module(self.urlconf_name) + else: + return self.urlconf_name @cached_property def url_patterns(self): diff --git a/docs/releases/2.2.5.txt b/docs/releases/2.2.5.txt index 9f143c8d96d4..592b8256a180 100644 --- a/docs/releases/2.2.5.txt +++ b/docs/releases/2.2.5.txt @@ -22,3 +22,6 @@ Bugfixes :attr:`ModelAdmin.list_filter ` choices to foreign objects don't respect a model's ``Meta.ordering`` (:ticket:`30449`). + +* Fixed a race condition in loading URLconf module that could cause a crash of + auto-reloader on Python 3.5 and below (:ticket:`30500`). From 16e5e8ff5a8c8eb5bfd5de999b22803819c6881e Mon Sep 17 00:00:00 2001 From: Andrew <36489577+recvfrom@users.noreply.github.com> Date: Tue, 27 Aug 2019 17:37:24 -0400 Subject: [PATCH 053/287] [2.2.x] Fixed #30733 -- Doc'd that datetime lookups require time zone definitions in the database. Note was missing for date, year, iso_year, week, time, hour, minute, and second lookups. Backport of 29adcd215f80383f00d9f837311e857142319722 from master --- docs/ref/models/querysets.txt | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 9379359ec628..c76df17d7f10 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -2948,7 +2948,8 @@ Example:: implementation of the relevant query varies among different database engines.) When :setting:`USE_TZ` is ``True``, fields are converted to the current time -zone before filtering. +zone before filtering. This requires :ref:`time zone definitions in the +database `. .. fieldlookup:: year @@ -2971,7 +2972,8 @@ SQL equivalent:: (The exact SQL syntax varies for each database engine.) When :setting:`USE_TZ` is ``True``, datetime fields are converted to the -current time zone before filtering. +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: iso_year @@ -2991,7 +2993,8 @@ Example:: (The exact SQL syntax varies for each database engine.) When :setting:`USE_TZ` is ``True``, datetime fields are converted to the -current time zone before filtering. +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: month @@ -3061,8 +3064,9 @@ Example:: (No equivalent SQL code fragment is included for this lookup because implementation of the relevant query varies among different database engines.) -When :setting:`USE_TZ` is ``True``, fields are converted to the current time -zone before filtering. +When :setting:`USE_TZ` is ``True``, datetime fields are converted to the +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: week_day @@ -3128,7 +3132,8 @@ Example:: implementation of the relevant query varies among different database engines.) When :setting:`USE_TZ` is ``True``, fields are converted to the current time -zone before filtering. +zone before filtering. This requires :ref:`time zone definitions in the +database `. .. fieldlookup:: hour @@ -3152,8 +3157,9 @@ SQL equivalent:: (The exact SQL syntax varies for each database engine.) -For datetime fields, when :setting:`USE_TZ` is ``True``, values are converted -to the current time zone before filtering. +When :setting:`USE_TZ` is ``True``, datetime fields are converted to the +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: minute @@ -3177,8 +3183,9 @@ SQL equivalent:: (The exact SQL syntax varies for each database engine.) -For datetime fields, When :setting:`USE_TZ` is ``True``, values are converted -to the current time zone before filtering. +When :setting:`USE_TZ` is ``True``, datetime fields are converted to the +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: second @@ -3202,8 +3209,9 @@ SQL equivalent:: (The exact SQL syntax varies for each database engine.) -For datetime fields, when :setting:`USE_TZ` is ``True``, values are converted -to the current time zone before filtering. +When :setting:`USE_TZ` is ``True``, datetime fields are converted to the +current time zone before filtering. This requires :ref:`time zone definitions +in the database `. .. fieldlookup:: isnull From 11cdfb340830abe57142e1fbc8bfae835b0f1183 Mon Sep 17 00:00:00 2001 From: Daria Kolodzey Date: Sat, 31 Aug 2019 11:58:41 +0300 Subject: [PATCH 054/287] [2.2.x] Fixed #30738 -- Fixed typo in docs/ref/forms/widgets.txt. Thanks Emmanuel Cazenave for the report. Backport of 3f8ee58ccc5c55e62625ad797ddde05778fe1bec from master --- docs/ref/forms/widgets.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index 0e701babebb1..d649ece6ba12 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -401,7 +401,7 @@ foundation for custom widgets. .. code-block:: html+django {% for subwidget in widget.subwidgets %} - {% include widget.template_name with widget=subwidget %} + {% include subwidget.template_name with widget=subwidget %} {% endfor %} Here's an example widget which subclasses :class:`MultiWidget` to display From 4ed59db199425c610b134f3f198a0af461ad26e8 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 07:43:51 +0200 Subject: [PATCH 055/287] [2.2.x] Added release dates for 2.2.5, 2.1.12, and 1.11.24. Backport of 47f49adc11c0d39be3f41f92becc1f606c49d8ce from master --- docs/releases/1.11.24.txt | 2 +- docs/releases/2.1.12.txt | 2 +- docs/releases/2.2.5.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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. diff --git a/docs/releases/2.1.12.txt b/docs/releases/2.1.12.txt index 696a6c4890f0..087ad5f59d86 100644 --- a/docs/releases/2.1.12.txt +++ b/docs/releases/2.1.12.txt @@ -2,7 +2,7 @@ Django 2.1.12 release notes =========================== -*Expected September 2, 2019* +*September 2, 2019* Django 2.1.12 fixes a regression in 2.1.11. diff --git a/docs/releases/2.2.5.txt b/docs/releases/2.2.5.txt index 592b8256a180..87a502080491 100644 --- a/docs/releases/2.2.5.txt +++ b/docs/releases/2.2.5.txt @@ -2,7 +2,7 @@ Django 2.2.5 release notes ========================== -*Expected September 2, 2019* +*September 2, 2019* Django 2.2.5 fixes several bugs in 2.2.4. From 8514c6f329d6977d06aea4f3084a1f9b19a26470 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 08:00:01 +0200 Subject: [PATCH 056/287] [2.2.x] Updated man page for Django 2.2. --- docs/man/django-admin.1 | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index 72c1da24466b..1ab6f57e593d 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "DJANGO-ADMIN" "1" "July 01, 2019" "2.2" "Django" +.TH "DJANGO-ADMIN" "1" "September 02, 2019" "2.2" "Django" .SH NAME django-admin \- Utility script for the Django Web framework . @@ -1735,7 +1735,7 @@ options) .IP \(bu 2 \fBdocs_version\fP \-\- the version of the documentation: \fB\(aqdev\(aq\fP or \fB\(aq1.x\(aq\fP .IP \(bu 2 -\fBdjango_version\fP \-\- the version of Django, e.g.\(ga\(ga\(aq2.0.3\(aq\(ga\(ga +\fBdjango_version\fP \-\- the version of Django, e.g. \fB\(aq2.0.3\(aq\fP .UNINDENT .sp \fBWARNING:\fP @@ -1825,7 +1825,7 @@ supported options) .IP \(bu 2 \fBdocs_version\fP \-\- the version of the documentation: \fB\(aqdev\(aq\fP or \fB\(aq1.x\(aq\fP .IP \(bu 2 -\fBdjango_version\fP \-\- the version of Django, e.g.\(ga\(ga\(aq2.0.3\(aq\(ga\(ga +\fBdjango_version\fP \-\- the version of Django, e.g. \fB\(aq2.0.3\(aq\fP .UNINDENT .sp Please also see the \fI\%rendering warning\fP as mentioned @@ -2117,6 +2117,13 @@ will be set, and the superuser account will not be able to log in until a password has been manually set for it. .INDENT 0.0 .TP +.B \-\-noinput, \-\-no\-input +.UNINDENT +.sp +Suppresses all user prompts. If a suppressed prompt cannot be resolved +automatically, the command will exit with error code 1. +.INDENT 0.0 +.TP .B \-\-username USERNAME .UNINDENT .INDENT 0.0 From ce97960e34baf7db865b5424d3c61274a9f88919 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 08:21:38 +0200 Subject: [PATCH 057/287] [2.2.x] Bumped version for 2.2.5 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index c3135d44bfba..dd98b239dfad 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 5, 'alpha', 0) +VERSION = (2, 2, 5, 'final', 0) __version__ = get_version(VERSION) From 96f1d8240152e67c4b3e2a4bc525ac965a74e747 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Sep 2019 09:04:02 +0200 Subject: [PATCH 058/287] [2.2.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 dd98b239dfad..503edc4acd61 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 5, 'final', 0) +VERSION = (2, 2, 6, 'alpha', 0) __version__ = get_version(VERSION) From b71cabe45970222d5c593bef44f01aea96854c89 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Sep 2019 08:02:32 +0200 Subject: [PATCH 059/287] [2.2.x] Added stub release notes for 2.2.6. Backport of 0d4529d314f5b804c4e856146f6641a73027a2c4 from master --- docs/releases/2.2.6.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.6.txt diff --git a/docs/releases/2.2.6.txt b/docs/releases/2.2.6.txt new file mode 100644 index 000000000000..e75160f8f36b --- /dev/null +++ b/docs/releases/2.2.6.txt @@ -0,0 +1,12 @@ +========================== +Django 2.2.6 release notes +========================== + +*Expected October 1, 2019* + +Django 2.2.6 fixes several bugs in 2.2.5. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index b64e8dcfd001..aac32328f131 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.6 2.2.5 2.2.4 2.2.3 From 23946bdb06c459086851e7dfa6117bdc9d301466 Mon Sep 17 00:00:00 2001 From: Min ho Kim Date: Wed, 4 Sep 2019 16:11:22 +1000 Subject: [PATCH 060/287] [2.2.x] Changed example git clone URLs to use HTTPS. Backport of 3c6a4fdb6d828a03e368632d88f8261cc30104da from master --- docs/internals/contributing/writing-code/unit-tests.txt | 2 +- docs/internals/contributing/writing-code/working-with-git.txt | 2 +- docs/intro/contributing.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 3480e3f2cac6..fa4df3c42991 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -28,7 +28,7 @@ how to do that, read our :doc:`contributing tutorial `. Next, clone your fork, install some requirements, and run the tests:: - $ git clone git@github.com:YourGitHubName/django.git django-repo + $ git clone https://github.com/YourGitHubName/django.git django-repo $ cd django-repo/tests $ pip install -e .. $ pip install -r requirements/py3.txt diff --git a/docs/internals/contributing/writing-code/working-with-git.txt b/docs/internals/contributing/writing-code/working-with-git.txt index 1ace4491890d..60ba1345a480 100644 --- a/docs/internals/contributing/writing-code/working-with-git.txt +++ b/docs/internals/contributing/writing-code/working-with-git.txt @@ -45,7 +45,7 @@ When you have created your GitHub account, with the nick "GitHub_nick", and `forked Django's repository `__, create a local copy of your fork:: - git clone git@github.com:GitHub_nick/django.git + git clone https://github.com/GitHub_nick/django.git This will create a new directory "django", containing a clone of your GitHub repository. The rest of the git commands on this page need to be run within the diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 39cbebb95e68..5a3350370e81 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -113,7 +113,7 @@ Download the Django source code repository using the following command: .. console:: - $ git clone git@github.com:YourGitHubName/django.git + $ git clone https://github.com/YourGitHubName/django.git .. admonition:: Low bandwidth connection? From dee22a024bcaa20d0f0c2592827b031a4f6b1fe7 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 5 Sep 2019 09:54:01 +0200 Subject: [PATCH 061/287] [2.2.x] Refs #30573 -- Noted to avoid "simple" & co. in Writing Style guide. Co-authored-by: Tobias Kunze Backport of ed2d411aa84efc76baba3adf0d0f99df0e44ba57 from master --- docs/internals/contributing/writing-documentation.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index 47ea4c589c62..187ea6978357 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -129,6 +129,12 @@ Instead of: * his or hers... use theirs. * himself or herself... use themselves. +Try to avoid using words that minimise the difficulty involved in a task or +operation, such as "easily", "simply", "just", "merely", "straightforward", and +so on. People's experience may not match your expectations, and they may become +frustrated when they do not find a step as "straightforward" or "simple" as it +is implied to be. + Commonly used terms =================== From fe33dd67fbdac80812177674d194f9279e572eb0 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 5 Sep 2019 14:06:46 +0200 Subject: [PATCH 062/287] [2.2.x] Fixed typo in docs/internals/contributing/writing-documentation.txt. Backport of 053ff7e9e65ce82bb7ee72bff3dcdb797e16dbb9 from master --- docs/internals/contributing/writing-documentation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index 187ea6978357..b6732746fa61 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -129,7 +129,7 @@ Instead of: * his or hers... use theirs. * himself or herself... use themselves. -Try to avoid using words that minimise the difficulty involved in a task or +Try to avoid using words that minimize the difficulty involved in a task or operation, such as "easily", "simply", "just", "merely", "straightforward", and so on. People's experience may not match your expectations, and they may become frustrated when they do not find a step as "straightforward" or "simple" as it From 1f304ebdc6ccdf42c8be96e972867981c7a0cb48 Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Mon, 9 Sep 2019 18:02:33 +1000 Subject: [PATCH 063/287] [2.2.x] Fixed #30767 -- Improved references to deployment documentation. * Increased tocdepth to expose more complexity of topics. * Ensured deployment checklist is linked on main doc page. Backport of fee75d2aed4e58ada6567c464cfd22e89dc65f4a from master. --- docs/howto/deployment/index.txt | 4 +++- docs/index.txt | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/howto/deployment/index.txt b/docs/howto/deployment/index.txt index 8ffda2cf63bf..96eb8200ec78 100644 --- a/docs/howto/deployment/index.txt +++ b/docs/howto/deployment/index.txt @@ -7,9 +7,11 @@ those tools are of no use if you can't easily deploy your sites. Since Django's inception, ease of deployment has been a major goal. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 wsgi/index + ../static-files/deployment + ../error-reporting checklist If you're new to deploying Django and/or Python, we'd recommend you try diff --git a/docs/index.txt b/docs/index.txt index 31a641e16876..6139c3e9b889 100644 --- a/docs/index.txt +++ b/docs/index.txt @@ -227,7 +227,8 @@ testing of Django applications: :doc:`Overview ` | :doc:`WSGI servers ` | :doc:`Deploying static files ` | - :doc:`Tracking code errors by email ` + :doc:`Tracking code errors by email ` | + :doc:`Deployment checklist ` The admin ========= From be053c0b2a5160246e3ca76ff4802e9181ae6f7d Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Sun, 24 Mar 2019 21:26:04 +0000 Subject: [PATCH 064/287] [2.2.x] Standardized links for headers in security middleware documentation. Backport of fc62e16291136513ac0d67d6a2c71e8674740435 from master --- docs/ref/middleware.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 62df23c7dc3e..db70a7c14d04 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -197,7 +197,7 @@ HTTP Strict Transport Security For sites that should only be accessed over HTTPS, you can instruct modern browsers to refuse to connect to your domain name via an insecure connection (for a given period of time) by setting the `"Strict-Transport-Security" -header`_. This reduces your exposure to some SSL-stripping man-in-the-middle +header`__. This reduces your exposure to some SSL-stripping man-in-the-middle (MITM) attacks. ``SecurityMiddleware`` will set this header for you on all HTTPS responses if @@ -238,7 +238,7 @@ If you wish to submit your site to the `browser preload list`_, set the it may be because Django doesn't realize that it's on a secure connection; you may need to set the :setting:`SECURE_PROXY_SSL_HEADER` setting. -.. _"Strict-Transport-Security" header: https://en.wikipedia.org/wiki/Strict_Transport_Security +__ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security .. _browser preload list: https://hstspreload.org/ .. _x-content-type-options: @@ -281,7 +281,7 @@ attack`_. They work by looking for JavaScript content in the GET or POST parameters of a page. If the JavaScript is replayed in the server's response, the page is blocked from rendering and an error page is shown instead. -The `X-XSS-Protection header`_ is used to control the operation of the +The `X-XSS-Protection header`__ is used to control the operation of the XSS filter. To enable the XSS filter in the browser, and force it to always block @@ -296,7 +296,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: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection +__ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection .. _ssl-redirect: From 964dd4f4f208722d8993a35c1ff047d353cea1ea Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 8 Sep 2019 20:31:43 -0400 Subject: [PATCH 065/287] [2.2.x] Fixed #30754 -- Prevented inclusion of aliases in partial index conditions. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SQLite doesn't repoint table aliases in partial index conditions on table rename which breaks the documented table alteration procedure. Thanks Pēteris Caune for the report. Backport of 34decdebf157b6f05836009cc1967f74ee541fdf from master --- django/db/models/indexes.py | 9 +++------ docs/releases/2.2.6.txt | 3 ++- tests/indexes/tests.py | 12 ++++++------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/django/db/models/indexes.py b/django/db/models/indexes.py index a8bf089ba1f7..5571be3f46ee 100644 --- a/django/db/models/indexes.py +++ b/django/db/models/indexes.py @@ -59,13 +59,10 @@ def _get_condition_sql(self, model, schema_editor): if self.condition is None: return None query = Query(model=model) - query.add_q(self.condition) + where = query.build_where(self.condition) compiler = query.get_compiler(connection=schema_editor.connection) - # Only the WhereNode is of interest for the partial index. - sql, params = query.where.as_sql(compiler=compiler, connection=schema_editor.connection) - # BaseDatabaseSchemaEditor does the same map on the params, but since - # it's handled outside of that class, the work is done here. - return sql % tuple(map(schema_editor.quote_value, params)) + sql, params = where.as_sql(compiler, schema_editor.connection) + return sql % tuple(schema_editor.quote_value(p) for p in params) def create_sql(self, model, schema_editor, using=''): fields = [model._meta.get_field(field_name) for field_name, _ in self.fields_orders] diff --git a/docs/releases/2.2.6.txt b/docs/releases/2.2.6.txt index e75160f8f36b..59c29ef0a678 100644 --- a/docs/releases/2.2.6.txt +++ b/docs/releases/2.2.6.txt @@ -9,4 +9,5 @@ Django 2.2.6 fixes several bugs in 2.2.5. Bugfixes ======== -* ... +* Fixed migrations crash on SQLite when altering a model containing partial + indexes (:ticket:`30754`). diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 7eb5dd89a9bb..8f3c03264142 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -108,7 +108,7 @@ def test_condition_ignored(self): editor.add_index(Article, index) self.assertNotIn( - 'WHERE %s.%s' % (editor.quote_name(Article._meta.db_table), 'published'), + 'WHERE %s' % editor.quote_name('published'), str(index.create_sql(Article, editor)) ) @@ -260,7 +260,7 @@ def test_partial_index(self): ) ) self.assertIn( - 'WHERE %s.%s' % (editor.quote_name(Article._meta.db_table), editor.quote_name("pub_date")), + 'WHERE %s' % editor.quote_name('pub_date'), str(index.create_sql(Article, schema_editor=editor)) ) editor.add_index(index=index, model=Article) @@ -277,7 +277,7 @@ def test_integer_restriction_partial(self): condition=Q(pk__gt=1), ) self.assertIn( - 'WHERE %s.%s' % (editor.quote_name(Article._meta.db_table), editor.quote_name('id')), + 'WHERE %s' % editor.quote_name('id'), str(index.create_sql(Article, schema_editor=editor)) ) editor.add_index(index=index, model=Article) @@ -294,7 +294,7 @@ def test_boolean_restriction_partial(self): condition=Q(published=True), ) self.assertIn( - 'WHERE %s.%s' % (editor.quote_name(Article._meta.db_table), editor.quote_name('published')), + 'WHERE %s' % editor.quote_name('published'), str(index.create_sql(Article, schema_editor=editor)) ) editor.add_index(index=index, model=Article) @@ -321,7 +321,7 @@ def test_multiple_conditions(self): sql = str(index.create_sql(Article, schema_editor=editor)) where = sql.find('WHERE') self.assertIn( - 'WHERE (%s.%s' % (editor.quote_name(Article._meta.db_table), editor.quote_name("pub_date")), + 'WHERE (%s' % editor.quote_name('pub_date'), sql ) # Because each backend has different syntax for the operators, @@ -341,7 +341,7 @@ def test_is_null_condition(self): condition=Q(pub_date__isnull=False), ) self.assertIn( - 'WHERE %s.%s IS NOT NULL' % (editor.quote_name(Article._meta.db_table), editor.quote_name("pub_date")), + 'WHERE %s IS NOT NULL' % editor.quote_name('pub_date'), str(index.create_sql(Article, schema_editor=editor)) ) editor.add_index(index=index, model=Article) From 1864d61d6c20e275e9196deff6c8f3de78fcee3d Mon Sep 17 00:00:00 2001 From: Ben Falk Date: Fri, 13 Sep 2019 14:36:35 -0400 Subject: [PATCH 066/287] [2.2.x] Fixed typos in docs/ref/settings.txt. Backport of 4056558a1c9cf650ab6e7cb1a319206d2e8c770f from master. --- docs/ref/settings.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 9618f1f039ec..7937995442e3 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -323,7 +323,7 @@ protection is safe from cross-subdomain attacks by default - please see the Default: ``False`` Whether to use ``HttpOnly`` flag on the CSRF cookie. If this is set to -``True``, client-side JavaScript will not to be able to access the CSRF cookie. +``True``, client-side JavaScript will not be able to access the CSRF cookie. Designating the CSRF cookie as ``HttpOnly`` doesn't offer any practical protection because CSRF is only to protect against cross-domain attacks. If an @@ -3012,7 +3012,7 @@ This setting also affects cookies set by :mod:`django.contrib.messages`. Default: ``True`` Whether to use ``HttpOnly`` flag on the session cookie. If this is set to -``True``, client-side JavaScript will not to be able to access the session +``True``, client-side JavaScript will not be able to access the session cookie. HttpOnly_ is a flag included in a Set-Cookie HTTP response header. It's part of From f452d4232e1c14269bf2f1bd234d6e4ef462dc55 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Sun, 8 Sep 2019 23:25:53 +0200 Subject: [PATCH 067/287] [2.2.x] Fixed #29823 -- Doc'd limitation of DecimalField on SQLite. Backport of b8dff52f440adfb78b40e19ee8bff45373ca2501 from master --- docs/ref/databases.txt | 12 ++++++++++++ docs/ref/models/fields.txt | 4 +++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 84dce9fa7c62..2520e479c33c 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -665,6 +665,18 @@ substring filtering. .. _documented at sqlite.org: https://www.sqlite.org/faq.html#q18 +.. _sqlite-decimal-handling: + +Decimal handling +---------------- + +SQLite has no real decimal internal type. Decimal values are internally +converted to the ``REAL`` data type (8-byte IEEE floating point number), as +explained in the `SQLite datatypes documentation`__, so they don't support +correctly-rounded decimal floating point arithmetic. + +__ https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes + "Database is locked" errors --------------------------- diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 1090d4f66a52..5ce3abae7220 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -594,7 +594,9 @@ when :attr:`~django.forms.Field.localize` is ``False`` or For more information about the differences between the :class:`FloatField` and :class:`DecimalField` classes, please - see :ref:`FloatField vs. DecimalField `. + see :ref:`FloatField vs. DecimalField `. You + should also be aware of :ref:`SQLite limitations ` + of decimal fields. ``DurationField`` ----------------- From e4fb132f43af0da7e0688779ea251f66f57b5464 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 16 Sep 2019 07:37:47 +0200 Subject: [PATCH 068/287] [2.2.x] Added stub release notes for 1.11.25 and 2.1.13. Backport of bd7e0f81f8590eadcb820c976ba03c9b75bbcad6 from master --- docs/releases/1.11.25.txt | 12 ++++++++++++ docs/releases/2.1.13.txt | 12 ++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 docs/releases/1.11.25.txt create mode 100644 docs/releases/2.1.13.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/2.1.13.txt b/docs/releases/2.1.13.txt new file mode 100644 index 000000000000..bd6fdad2b3eb --- /dev/null +++ b/docs/releases/2.1.13.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.1.13 release notes +=========================== + +*Expected October 1, 2019* + +Django 2.1.13 fixes a regression in 2.1.11. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index aac32328f131..ba7128e15a14 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 + 2.1.13 2.1.12 2.1.11 2.1.10 @@ -77,6 +78,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 7806e4545452ae36c71d41c2b9b35f6e15df7b58 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Sun, 15 Sep 2019 23:25:50 -0400 Subject: [PATCH 069/287] [2.2.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 +++- docs/releases/2.1.13.txt | 4 +++- docs/releases/2.2.6.txt | 5 +++++ tests/postgres_tests/test_hstore.py | 8 +++++++- tests/postgres_tests/test_json.py | 8 +++++++- 7 files changed, 27 insertions(+), 6 deletions(-) diff --git a/django/contrib/postgres/fields/hstore.py b/django/contrib/postgres/fields/hstore.py index 33a741b0c8dc..91d58fd30d97 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: diff --git a/django/contrib/postgres/fields/jsonb.py b/django/contrib/postgres/fields/jsonb.py index be93b11d9fa2..370accbfc8c3 100644 --- a/django/contrib/postgres/fields/jsonb.py +++ b/django/contrib/postgres/fields/jsonb.py @@ -112,7 +112,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/docs/releases/2.1.13.txt b/docs/releases/2.1.13.txt index bd6fdad2b3eb..33b2ea21b003 100644 --- a/docs/releases/2.1.13.txt +++ b/docs/releases/2.1.13.txt @@ -9,4 +9,6 @@ Django 2.1.13 fixes a regression in 2.1.11. 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/docs/releases/2.2.6.txt b/docs/releases/2.2.6.txt index 59c29ef0a678..49d758abda2d 100644 --- a/docs/releases/2.2.6.txt +++ b/docs/releases/2.2.6.txt @@ -11,3 +11,8 @@ Bugfixes * Fixed migrations crash on SQLite when altering a model containing partial indexes (:ticket:`30754`). + +* Fixed a regression in Django 2.2.4 that caused 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 45a5e99dd115..e7ce2b28c5f2 100644 --- a/tests/postgres_tests/test_hstore.py +++ b/tests/postgres_tests/test_hstore.py @@ -2,7 +2,7 @@ from django.core import checks, 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, isolate_apps @@ -207,6 +207,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]) + @isolate_apps('postgres_tests') class TestChecks(PostgreSQLSimpleTestCase): diff --git a/tests/postgres_tests/test_json.py b/tests/postgres_tests/test_json.py index 3a364ec8fb12..a74a5f8dde78 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -6,7 +6,7 @@ from django.core import checks, exceptions, serializers from django.core.serializers.json import DjangoJSONEncoder from django.db import connection -from django.db.models import Count, F, Q +from django.db.models import Count, F, OuterRef, Q, Subquery from django.db.models.expressions import RawSQL from django.db.models.functions import Cast from django.forms import CharField, Form, widgets @@ -278,6 +278,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 242805c82be5ae9109b4b14335bcad3a3870571d Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Sat, 14 Sep 2019 12:47:15 +1000 Subject: [PATCH 070/287] [2.2.x] Fixed #30775 -- Added admonition about missing imports to "Running tests" section in tutorial 5. Backport of 44077985f58be02214a11ffde35776fed3c960e1 from master --- docs/intro/tutorial02.txt | 5 +++-- docs/intro/tutorial05.txt | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/intro/tutorial02.txt b/docs/intro/tutorial02.txt index 9046d167b091..01bee32a6ca3 100644 --- a/docs/intro/tutorial02.txt +++ b/docs/intro/tutorial02.txt @@ -443,8 +443,9 @@ models, not only for your own convenience when dealing with the interactive prompt, but also because objects' representations are used throughout Django's automatically-generated admin. -Note these are normal Python methods. Let's add a custom method, just for -demonstration: +.. _tutorial02-import-timezone: + +Let's also add a custom method to this model: .. code-block:: python :caption: polls/models.py diff --git a/docs/intro/tutorial05.txt b/docs/intro/tutorial05.txt index 42891a6ecd99..b488fabba52a 100644 --- a/docs/intro/tutorial05.txt +++ b/docs/intro/tutorial05.txt @@ -220,6 +220,13 @@ and you'll see something like:: FAILED (failures=1) Destroying test database for alias 'default'... +.. admonition:: Different error? + + If instead you're getting a ``NameError`` here, you may have missed a step + in :ref:`Part 2 ` where we added imports of + ``datetime`` and ``timezone`` to ``polls/models.py``. Copy the imports from + that section, and try running your tests again. + What happened is this: * ``manage.py test polls`` looked for tests in the ``polls`` application From 268e7c7747ecb9ef9b33b66fe6acd526359835b1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 18 Sep 2019 16:08:56 +0200 Subject: [PATCH 071/287] [2.2.x] Fixed #27462 -- Clarifed pk_set difference in m2m_changed signal receivers for add() and remove(). Thank you to Mariusz Felisiak for review. Backport of bae05bcf68710cb6dafa51325c3ec83ddda83c39 from master --- docs/ref/signals.txt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 19a92a875c4e..48915cdc452d 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -270,9 +270,16 @@ Arguments sent with this signal: from the relation. ``pk_set`` - For the ``pre_add``, ``post_add``, ``pre_remove`` and ``post_remove`` - actions, this is a set of primary key values that have been added to - or removed from the relation. + For the ``pre_add`` and ``post_add`` actions, this is a set of primary key + values that will be, or have been, added to the relation. This may be a + subset of the values submitted to be added, since inserts must filter + existing values in order to avoid a database ``IntegrityError``. + + For the ``pre_remove`` and ``post_remove`` actions, this is a set of + primary key values that was submitted to be removed from the relation. This + is not dependent on whether the values actually will be, or have been, + removed. In particular, non-existent values may be submitted, and will + appear in ``pk_set``, even though they have no effect on the database. For the ``pre_clear`` and ``post_clear`` actions, this is ``None``. From ae2dee6ecf5090768c31be6695cc8a3a4201d177 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 19 Sep 2019 13:03:25 +0200 Subject: [PATCH 072/287] [2.2.x] Refs #29915 -- Doc'd limitation of using pattern lookups with UUIDField on PostgreSQL. Backport of 343afa788080fb874bcd10eab1a1a2fede98c526 from master --- docs/ref/models/fields.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index 5ce3abae7220..d870ccff7112 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1150,6 +1150,13 @@ it is recommended to use :attr:`~Field.default`:: Note that a callable (with the parentheses omitted) is passed to ``default``, not an instance of ``UUID``. +.. admonition:: Lookups on PostgreSQL + + Using :lookup:`iexact`, :lookup:`contains`, :lookup:`icontains`, + :lookup:`startswith`, :lookup:`istartswith`, :lookup:`endswith`, or + :lookup:`iendswith` lookups on PostgreSQL don't work for values without + hyphens, because PostgreSQL stores them in a hyphenated uuid datatype type. + Relationship fields =================== From 62a4a5062fb12bebe87505f9ecd1484212d85795 Mon Sep 17 00:00:00 2001 From: Jezeniel Zapanta Date: Mon, 16 Sep 2019 18:13:06 +0800 Subject: [PATCH 073/287] [2.2.x] Fixed #30732 -- Doc'd that SameSite cookies flags can affect xframe_options_exempt. Backport of e8ad265ac882f8f118d2c4a7618bd3e3916fc13e from master --- docs/ref/clickjacking.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/clickjacking.txt b/docs/ref/clickjacking.txt index 6fba3eaea86e..f00f3c3628d5 100644 --- a/docs/ref/clickjacking.txt +++ b/docs/ref/clickjacking.txt @@ -84,6 +84,11 @@ that tells the middleware not to set the header:: def ok_to_load_in_a_frame(request): return HttpResponse("This page is safe to load in a frame on any site.") +.. note:: + + If you want to submit a form or access a session cookie within a frame or + iframe, you may need to modify the :setting:`CSRF_COOKIE_SAMESITE` or + :setting:`SESSION_COOKIE_SAMESITE` settings. Setting ``X-Frame-Options`` per view ------------------------------------ From 29d2f5a66212af91f93edb47df07982f92b4ac5f Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 24 Sep 2019 09:58:17 +0200 Subject: [PATCH 074/287] [2.2.x] Refs #26601 -- Used new-style middlewares in documentation. Backport of d71497bb249a2c3ffec41e99089f5ae8e575f2d3 from master --- docs/ref/request-response.txt | 10 ++++++---- docs/topics/i18n/timezones.txt | 9 ++++++--- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 30ad0ccf4259..8d834621cc46 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -279,16 +279,17 @@ Methods behind multiple proxies. One solution is to use middleware to rewrite the proxy headers, as in the following example:: - from django.utils.deprecation import MiddlewareMixin - - class MultipleProxyMiddleware(MiddlewareMixin): + class MultipleProxyMiddleware: FORWARDED_FOR_FIELDS = [ 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED_HOST', 'HTTP_X_FORWARDED_SERVER', ] - def process_request(self, request): + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): """ Rewrites the proxy headers so that only the most recent proxy is used. @@ -298,6 +299,7 @@ Methods if ',' in request.META[field]: parts = request.META[field].split(',') request.META[field] = parts[-1].strip() + return self.get_response(request) This middleware should be positioned before any other middleware that relies on the value of :meth:`~HttpRequest.get_host()` -- for instance, diff --git a/docs/topics/i18n/timezones.txt b/docs/topics/i18n/timezones.txt index 86fd8b50621e..1c3c2bac2882 100644 --- a/docs/topics/i18n/timezones.txt +++ b/docs/topics/i18n/timezones.txt @@ -169,15 +169,18 @@ Add the following middleware to :setting:`MIDDLEWARE`:: import pytz from django.utils import timezone - from django.utils.deprecation import MiddlewareMixin - class TimezoneMiddleware(MiddlewareMixin): - def process_request(self, request): + class TimezoneMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): tzname = request.session.get('django_timezone') if tzname: timezone.activate(pytz.timezone(tzname)) else: timezone.deactivate() + return self.get_response(request) Create a view that can set the current timezone:: From f314cebe82902f2afcf4b2ae6f3ea069e6bfd19a Mon Sep 17 00:00:00 2001 From: Ryan Nowakowski Date: Tue, 24 Sep 2019 05:42:43 -0500 Subject: [PATCH 075/287] [2.2.x] Updated the default cache key transformation in documentation. Follow up to 6c69de80bdcd2744bc64cb933c2d863dd5e74a33. Backport of 3f141719a198983a41840e3c2ea542f87c92e4c1 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 6827b3f40e76..33e0f72e9513 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -1038,7 +1038,7 @@ key version to provide a final cache key. By default, the three parts are joined using colons to produce a final string:: def make_key(key, key_prefix, version): - return ':'.join([key_prefix, str(version), key]) + return '%s:%s:%s' % (key_prefix, version, key) If you want to combine the parts in different ways, or apply other processing to the final key (e.g., taking a hash digest of the key From acc0d99e6c1388199ca2275b179051e5a89cdc25 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 24 Sep 2019 12:58:04 +0200 Subject: [PATCH 076/287] [2.2.x] Refs #30350 -- Doc'd support for range serialization in migrations. Backport of bc46e386c7aa496642d3ffc9e4f56ae5a46417a7 from master --- docs/topics/migrations.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index f518341310cf..d71816d9a399 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -672,7 +672,7 @@ for basic values, and doesn't specify import paths). Django can serialize the following: - ``int``, ``float``, ``bool``, ``str``, ``bytes``, ``None``, ``NoneType`` -- ``list``, ``set``, ``tuple``, ``dict`` +- ``list``, ``set``, ``tuple``, ``dict``, ``range``. - ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances (include those that are timezone-aware) - ``decimal.Decimal`` instances From 80d78fd651b331a4a478c22d8b0f5c0f52120fa5 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Wed, 25 Sep 2019 10:28:08 +0100 Subject: [PATCH 077/287] [2.2.x] Documented admonition on when to use custom signals Thanks Cesar Canassa for the suggestion and Aymeric Augustin for review. Backport of 566fca14b3e1406f918898de5bb2b2382a44edaf from master --- docs/topics/signals.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index 80a92d7a9c41..fc7c641af16f 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -206,6 +206,12 @@ Defining and sending signals Your applications can take advantage of the signal infrastructure and provide its own signals. +.. admonition:: When to use custom signals + + Signals are implicit function calls which make debugging harder. If the + sender and receiver of your custom signal are both within your project, + you're better off using an explicit function call. + Defining signals ---------------- From 38af2579881ed476ecd6bbde5b2329fa22d54210 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pablo=20Garc=C3=ADa?= Date: Thu, 26 Sep 2019 14:55:25 -0700 Subject: [PATCH 078/287] [2.2.x] Fixed #30810 -- Fixed WatchmanReloaderTests.test_setting_timeout_from_environment_variable test. client_timeout is an instance attribute. Backport of 2fd610eb301dc01e100befed891e8007fd2e981f from master --- tests/utils_tests/test_autoreload.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/utils_tests/test_autoreload.py b/tests/utils_tests/test_autoreload.py index 64c71bfe3f61..ec913593280d 100644 --- a/tests/utils_tests/test_autoreload.py +++ b/tests/utils_tests/test_autoreload.py @@ -666,7 +666,7 @@ class TestException(Exception): @mock.patch.dict(os.environ, {'DJANGO_WATCHMAN_TIMEOUT': '10'}) def test_setting_timeout_from_environment_variable(self): - self.assertEqual(self.RELOADER_CLS.client_timeout, 10) + self.assertEqual(self.RELOADER_CLS().client_timeout, 10) class StatReloaderTests(ReloaderTests, IntegrationTests): From b1eea8a73a4fcf13844559e24d147287aafae994 Mon Sep 17 00:00:00 2001 From: Glenn Date: Thu, 26 Sep 2019 10:46:20 -0700 Subject: [PATCH 079/287] [2.2.x] Fixed #27921 -- Clarified usage of make_aware() with is_dst argument. Backport of c2678e49759e5c4c329bff0eeca2886267005d21 from master --- docs/ref/utils.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index b4f96b8d2fb8..cb39d0d9d585 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -999,11 +999,14 @@ appropriate entities. post-transition respectively. The ``pytz.NonExistentTimeError`` exception is raised if you try to make - ``value`` aware during a DST transition such that the time never occurred - (when entering into DST). Setting ``is_dst`` to ``True`` or ``False`` will - avoid the exception by moving the hour backwards or forwards by 1 - respectively. For example, ``is_dst=True`` would change a nonexistent - time of 2:30 to 1:30 and ``is_dst=False`` would change the time to 3:30. + ``value`` aware during a DST transition such that the time never occurred. + For example, if the 2:00 hour is skipped during a DST transition, trying to + make 2:30 aware in that time zone will raise an exception. To avoid that + you can use ``is_dst`` to specify how ``make_aware()`` should interpret + such a nonexistent time. If ``is_dst=True`` then the above time would be + interpreted as 2:30 DST time (equivalent to 1:30 local time). Conversely, + if ``is_dst=False`` the time would be interpreted as 2:30 standard time + (equivalent to 3:30 local time). .. function:: make_naive(value, timezone=None) From a6972e88547ad5a51592f2b6d5046754c4b59394 Mon Sep 17 00:00:00 2001 From: David Vaz Date: Fri, 27 Sep 2019 04:00:32 -0700 Subject: [PATCH 080/287] [2.2.x] Fixed #30216 -- Doc'd that BooleanField is no longer blank=True in Django 2.1. --- docs/ref/models/fields.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index d870ccff7112..a4ec2db358db 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -462,6 +462,10 @@ isn't defined. use :class:`NullBooleanField` instead. Using the latter is now discouraged as it's likely to be deprecated in a future version of Django. + In older versions, this field has :attr:`blank=True ` + implicitly. You can restore the previous behavior by setting + ``blank=True``. + ``CharField`` ------------- From 1ac2f25ec091847a2db313210eb9743ed3b0dabf Mon Sep 17 00:00:00 2001 From: David Vaz Date: Fri, 27 Sep 2019 09:41:49 -0700 Subject: [PATCH 081/287] [2.2.x] Refs #30597 -- Added a warning about dependent apps when unapplying migrations. Backport of abba563c8635a52e8b657dee7708202e1f69433c from master --- docs/ref/django-admin.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index c2094e962b0a..8590739fed6a 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -804,6 +804,12 @@ The behavior of this command changes depending on the arguments provided: migrated past the named migration. Use the name ``zero`` to unapply all migrations for an app. +.. warning:: + + When unapplying migrations, all dependent migrations will also be + unapplied, regardless of ````. You can use ``--plan`` to check + which migrations will be unapplied. + .. django-admin-option:: --database DATABASE Specifies the database to migrate. Defaults to ``default``. From 4116b369b191f1830bbe761fa7fb29bf1becc8ef Mon Sep 17 00:00:00 2001 From: David Vaz Date: Thu, 26 Sep 2019 10:39:36 -0700 Subject: [PATCH 082/287] [2.2.x] Fixed #30597 -- Clarified how to unapply migrations. Backport of 45554fd5c5c6a536db3ce70d78646d3dc398cf08 from master --- docs/ref/django-admin.txt | 4 ++-- docs/topics/migrations.txt | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 8590739fed6a..2e076095d636 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -801,8 +801,8 @@ The behavior of this command changes depending on the arguments provided: * `` ``: Brings the database schema to a state where the named migration is applied, but no later migrations in the same app are applied. This may involve unapplying migrations if you have previously - migrated past the named migration. Use the name ``zero`` to unapply all - migrations for an app. + migrated past the named migration. Use the name ``zero`` to migrate all the + way back i.e. to revert all applied migrations for an app. .. warning:: diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index d71816d9a399..871f61a7b6cd 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -349,6 +349,30 @@ Note that this only works given two things: that your database doesn't match your models, you'll just get errors when migrations try to modify those tables. +Reverting migrations +==================== + +Any migration can be reverted with :djadmin:`migrate` by using the number of +previous migrations:: + + $ python manage.py migrate books 0002 + Operations to perform: + Target specific migration: 0002_auto, from books + Running migrations: + Rendering model states... DONE + Unapplying books.0003_auto... OK + +If you want to revert all migrations applied for an app, use the name +``zero``:: + + $ python manage.py migrate books zero + Operations to perform: + Unapply all migrations: books + Running migrations: + Rendering model states... DONE + Unapplying books.0002_auto... OK + Unapplying books.0001_initial... OK + .. _historical-models: Historical models From 43c894ffe773efec5c95d65c3b5edcddafa8e78f Mon Sep 17 00:00:00 2001 From: David Vaz Date: Fri, 27 Sep 2019 14:31:58 -0700 Subject: [PATCH 083/287] [2.2.x] Doc'd that migrate commmand accepts a unique migration name prefix. Backport of e02f67ef2d03d48128e7a118bf75f0418e24e8ac from master --- docs/ref/django-admin.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 2e076095d636..66e92bae6db4 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -801,8 +801,10 @@ The behavior of this command changes depending on the arguments provided: * `` ``: Brings the database schema to a state where the named migration is applied, but no later migrations in the same app are applied. This may involve unapplying migrations if you have previously - migrated past the named migration. Use the name ``zero`` to migrate all the - way back i.e. to revert all applied migrations for an app. + migrated past the named migration. You can use a prefix of the migration + name, e.g. ``0001``, as long as it's unique for the given app name. Use the + name ``zero`` to migrate all the way back i.e. to revert all applied + migrations for an app. .. warning:: From e1a75db7d1471711d5e284c8d91bc9efc375904e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 08:49:15 +0200 Subject: [PATCH 084/287] [2.2.x] Added release dates for 2.2.6, 2.1.13, and 1.11.25. Backport of 3826aed46d7d4310c2ab6777a4f92165ca4d8d4f from master --- docs/releases/1.11.25.txt | 2 +- docs/releases/2.1.13.txt | 2 +- docs/releases/2.2.6.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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. diff --git a/docs/releases/2.1.13.txt b/docs/releases/2.1.13.txt index 33b2ea21b003..502b73c8c9b1 100644 --- a/docs/releases/2.1.13.txt +++ b/docs/releases/2.1.13.txt @@ -2,7 +2,7 @@ Django 2.1.13 release notes =========================== -*Expected October 1, 2019* +*October 1, 2019* Django 2.1.13 fixes a regression in 2.1.11. diff --git a/docs/releases/2.2.6.txt b/docs/releases/2.2.6.txt index 49d758abda2d..512b3601e059 100644 --- a/docs/releases/2.2.6.txt +++ b/docs/releases/2.2.6.txt @@ -2,7 +2,7 @@ Django 2.2.6 release notes ========================== -*Expected October 1, 2019* +*October 1, 2019* Django 2.2.6 fixes several bugs in 2.2.5. From b0654fd6fafc28c3b0476cf2fa0d4eefe4162425 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 10:27:56 +0200 Subject: [PATCH 085/287] [2.2.x] Bumped version for 2.2.6 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 503edc4acd61..877ad809cd51 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 6, 'alpha', 0) +VERSION = (2, 2, 6, 'final', 0) __version__ = get_version(VERSION) From a00d823951d9a5e765d75f652a46e620566d03a1 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 10:36:02 +0200 Subject: [PATCH 086/287] [2.2.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 877ad809cd51..233c18b5b1d4 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 6, 'final', 0) +VERSION = (2, 2, 7, 'alpha', 0) __version__ = get_version(VERSION) From c3302c7976e3331fe48c89fc208f4a3b2056d454 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 1 Oct 2019 10:42:43 +0200 Subject: [PATCH 087/287] [2.2.x] Added stub release notes for 2.2.7. Backport of e1c1eaf0c6f4d3d2f60513d20aa9b84b17d096ec from master --- docs/releases/2.2.7.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.7.txt diff --git a/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt new file mode 100644 index 000000000000..001af881760c --- /dev/null +++ b/docs/releases/2.2.7.txt @@ -0,0 +1,12 @@ +========================== +Django 2.2.7 release notes +========================== + +*Expected November 1, 2019* + +Django 2.2.7 fixes several bugs in 2.2.6. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index ba7128e15a14..6aec59f79083 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.7 2.2.6 2.2.5 2.2.4 From 18df8484bdb2b56e7728bc9d10014fad2ec2edd2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 2 Oct 2019 07:49:47 +0200 Subject: [PATCH 088/287] [2.2.x] Added stub release notes for 1.11.26 and 2.1.14. Backport of 84322a29ce9b0940335f8ab3d60e55192bef1e50 from master --- docs/releases/1.11.26.txt | 12 ++++++++++++ docs/releases/2.1.14.txt | 12 ++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 26 insertions(+) create mode 100644 docs/releases/1.11.26.txt create mode 100644 docs/releases/2.1.14.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/2.1.14.txt b/docs/releases/2.1.14.txt new file mode 100644 index 000000000000..17b001e4e5e2 --- /dev/null +++ b/docs/releases/2.1.14.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.1.14 release notes +=========================== + +*Expected November 1, 2019* + +Django 2.1.14 fixes a regression in 2.1.13. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 6aec59f79083..673764b4f77f 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -39,6 +39,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.1.14 2.1.13 2.1.12 2.1.11 @@ -79,6 +80,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 da31472abfdc6364fcd00d6c902db8bdc04965c8 Mon Sep 17 00:00:00 2001 From: Sjbrgsn Date: Mon, 30 Sep 2019 14:52:17 +0200 Subject: [PATCH 089/287] [2.2.x] Fixed #30817 -- Clarified return value of Sitemap.items(). Backport of 7b3c06cd72e691ffd932ccce338701c37297a415 from master --- docs/ref/contrib/sitemaps.txt | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/ref/contrib/sitemaps.txt b/docs/ref/contrib/sitemaps.txt index 166013659da9..42c07e88319a 100644 --- a/docs/ref/contrib/sitemaps.txt +++ b/docs/ref/contrib/sitemaps.txt @@ -116,11 +116,11 @@ Note: attributes corresponding to ```` and ```` elements, respectively. They can be made callable as functions, as :attr:`~Sitemap.lastmod` was in the example. -* :attr:`~Sitemap.items()` is simply a method that returns a list of - objects. The objects returned will get passed to any callable methods - corresponding to a sitemap property (:attr:`~Sitemap.location`, - :attr:`~Sitemap.lastmod`, :attr:`~Sitemap.changefreq`, and - :attr:`~Sitemap.priority`). +* :attr:`~Sitemap.items()` is simply a method that returns a :term:`sequence` + or ``QuerySet`` of objects. The objects returned will get passed to any + callable methods corresponding to a sitemap property + (:attr:`~Sitemap.location`, :attr:`~Sitemap.lastmod`, + :attr:`~Sitemap.changefreq`, and :attr:`~Sitemap.priority`). * :attr:`~Sitemap.lastmod` should return a :class:`~datetime.datetime`. * There is no :attr:`~Sitemap.location` method in this example, but you can provide it in order to specify the URL for your object. By default, @@ -136,11 +136,11 @@ Note: .. attribute:: Sitemap.items - **Required.** A method that returns a list of objects. The framework - doesn't care what *type* of objects they are; all that matters is that - these objects get passed to the :attr:`~Sitemap.location()`, - :attr:`~Sitemap.lastmod()`, :attr:`~Sitemap.changefreq()` and - :attr:`~Sitemap.priority()` methods. + **Required.** A method that returns a :term:`sequence` or ``QuerySet`` + of objects. The framework doesn't care what *type* of objects they are; + all that matters is that these objects get passed to the + :attr:`~Sitemap.location()`, :attr:`~Sitemap.lastmod()`, + :attr:`~Sitemap.changefreq()` and :attr:`~Sitemap.priority()` methods. .. attribute:: Sitemap.location From 09013aae13b008359c054d5e4252844ebdb5aa57 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 2 Oct 2019 13:11:03 +0200 Subject: [PATCH 090/287] [2.2.x] Refs #28699 -- Clarified CSRF middleware ordering in relation to RemoteUserMiddleware. Backport of 94469504706b494877b6bb45a979bcb81c7fd7be from master --- docs/ref/middleware.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index db70a7c14d04..99ff001954c8 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -458,6 +458,10 @@ Here are some hints about the ordering of various Django middleware classes: Before any view middleware that assumes that CSRF attacks have been dealt with. + Before :class:`~django.contrib.auth.middleware.RemoteUserMiddleware`, or any + other authentication middleware that may perform a login, and hence rotate + the CSRF token, before calling down the middleware chain. + After ``SessionMiddleware`` if you're using :setting:`CSRF_USE_SESSIONS`. #. :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` From 8ac918a91349df26d83264b37da9dbd0140aeede Mon Sep 17 00:00:00 2001 From: Katherine Michel Date: Thu, 26 Sep 2019 16:51:38 -0700 Subject: [PATCH 091/287] [2.2.x] Refs #30010 -- Doc'd running tests with django-docker-box. Backport of f7eb9fb676d7afba16fc577283610b359718e644 from master --- AUTHORS | 1 + docs/internals/contributing/writing-code/unit-tests.txt | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/AUTHORS b/AUTHORS index ab06700192e3..bdba0741b1d8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -471,6 +471,7 @@ answer newbie questions, and generally made Django that much better: Karderio Karen Tracey Karol Sikora + Katherine “Kati” Michel Katie Miller Keith Bussell Kenneth Love diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index fa4df3c42991..b453b73c17b1 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -123,6 +123,15 @@ necessary for the majority of patches. To run the JavaScript tests using This command runs ``npm install`` to ensure test requirements are up to date and then runs ``npm test``. +Running tests using ``django-docker-box`` +----------------------------------------- + +`django-docker-box`_ allows you to run the Django's test suite across all +supported databases and python versions. See the `django-docker-box`_ project +page for installation and usage instructions. + +.. _django-docker-box: https://github.com/django/django-docker-box + .. _running-unit-tests-settings: Using another ``settings`` module From f5f4c28bfcf5b1303689ed8d854d5e0d9cfb14ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=20S=C3=A1nchez?= Date: Thu, 26 Sep 2019 12:32:56 -0700 Subject: [PATCH 092/287] [2.2.x] Refs #15396 -- Mentioned full path to GZipMiddleware in documentation. Backport of f1d4a540b29c6766b988852bc302ac036a2d82e2 from master --- docs/ref/middleware.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/middleware.txt b/docs/ref/middleware.txt index 99ff001954c8..80abbb33a1b8 100644 --- a/docs/ref/middleware.txt +++ b/docs/ref/middleware.txt @@ -95,8 +95,8 @@ GZip middleware .. _the BREACH paper (PDF): http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf .. _breachattack.com: http://breachattack.com -Compresses content for browsers that understand GZip compression (all modern -browsers). +The ``django.middleware.gzip.GZipMiddleware`` compresses content for browsers +that understand GZip compression (all modern browsers). This middleware should be placed before any other middleware that need to read or write the response body so that compression happens afterward. From 146086f219d01dbb1cd8c089b5a5667e396e1cc4 Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Thu, 3 Oct 2019 11:52:50 -0400 Subject: [PATCH 093/287] [2.2.x] Fixed #30834 -- Added explicit list of supported databases to the databases docs. Backport of 9dca904a5bf37ee9ad015212b17a9929ab8952cd from master --- docs/ref/databases.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 2520e479c33c..f712926bca9b 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -2,6 +2,16 @@ Databases ========= +Django officially supports the following databases: + +* :ref:`PostgreSQL ` +* :ref:`MySQL ` +* :ref:`Oracle ` +* :ref:`SQLite ` + +There are also a number of :ref:`database backends provided by third parties +`. + Django attempts to support as many features as possible on all database backends. However, not all database backends are alike, and we've had to make design decisions on which features to support and which assumptions we can make From 98573cfe48b6c52dd0655f649e4180df3c6cc635 Mon Sep 17 00:00:00 2001 From: Tamer Sherif Date: Sun, 29 Sep 2019 10:40:16 +0200 Subject: [PATCH 094/287] [2.2.x] Refs #10348 -- Doc'd that ModelAdmin ignores list_select_related when QuerySet.select_related() was already called. Backport of e3f647f4d5fc414f8cb7ab58df5e61a19908c63f from master --- docs/ref/contrib/admin/index.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index b0daeaadaf4c..3ce62158d7fb 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1031,6 +1031,12 @@ subclass:: If you need to specify a dynamic value based on the request, you can implement a :meth:`~ModelAdmin.get_list_select_related` method. + .. note:: + + ``ModelAdmin`` ignores this attribute when + :meth:`~django.db.models.query.QuerySet.select_related` was already + called on the changelist's ``QuerySet``. + .. attribute:: ModelAdmin.ordering Set ``ordering`` to specify how lists of objects should be ordered in the From 46a05e10a420deae3158c502ce58cf25ab59ccb1 Mon Sep 17 00:00:00 2001 From: Caio Ariede Date: Fri, 4 Oct 2019 10:55:41 -0300 Subject: [PATCH 095/287] [2.2.x] Fixed #28273 -- Doc'd fast nullable column creation with defaults. Backport of 06909fe084f87a65459a83bd69d7cdbe4fce9a7c from master --- docs/ref/migration-operations.txt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/ref/migration-operations.txt b/docs/ref/migration-operations.txt index f7778c297986..804a6cbe52f9 100644 --- a/docs/ref/migration-operations.txt +++ b/docs/ref/migration-operations.txt @@ -150,6 +150,21 @@ a default value to put into existing rows. It does not affect the behavior of setting defaults in the database directly - Django never sets database defaults and always applies them in the Django ORM code. +.. warning:: + + On older databases, adding a field with a default value may cause a full + rewrite of the table. This happens even for nullable fields and may have a + negative performance impact. To avoid that, the following steps should be + taken. + + * Add the nullable field without the default value and run the + :djadmin:`makemigrations` command. This should generate a migration with + an ``AddField`` operation. + + * Add the default value to your field and run the :djadmin:`makemigrations` + command. This should generate a migration with an ``AlterField`` + operation. + ``RemoveField`` --------------- From 5e91adc79d2fd79821be43007b99ba9982a0358c Mon Sep 17 00:00:00 2001 From: Caio Ariede Date: Wed, 5 Jun 2019 11:35:36 -0300 Subject: [PATCH 096/287] [2.2.x] Fixed #28790 -- Doc'd how to avoid running certain test classes in parallel. Backport of dafdfd6a60638c4edcca7c4e65d11c0af654d759 from master --- docs/ref/django-admin.txt | 6 ++++++ docs/topics/testing/advanced.txt | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 66e92bae6db4..f2029e9b19ee 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -1397,6 +1397,12 @@ Each process gets its own database. You must ensure that different test cases don't access the same resources. For instance, test cases that touch the filesystem should create a temporary directory for their own use. +.. note:: + + If you have test classes that cannot be run in parallel, you can use + ``SerializeMixin`` to run them sequentially. See :ref:`Enforce running test + classes sequentially `. + This option requires the third-party ``tblib`` package to display tracebacks correctly: diff --git a/docs/topics/testing/advanced.txt b/docs/topics/testing/advanced.txt index 0a228fc4efa7..e9f64f1941c2 100644 --- a/docs/topics/testing/advanced.txt +++ b/docs/topics/testing/advanced.txt @@ -300,6 +300,40 @@ Advanced features of ``TransactionTestCase`` Using ``reset_sequences = True`` will slow down the test, since the primary key reset is a relatively expensive database operation. +.. _topics-testing-enforce-run-sequentially: + +Enforce running test classes sequentially +========================================= + +If you have test classes that cannot be run in parallel (e.g. because they +share a common resource), you can use ``django.test.testcases.SerializeMixin`` +to run them sequentially. This mixin uses a filesystem ``lockfile``. + +For example, you can use ``__file__`` to determine that all test classes in the +same file that inherit from ``SerializeMixin`` will run sequentially:: + + import os + + from django.test import TestCase + from django.test.testcases import SerializeMixin + + class ImageTestCaseMixin(SerializeMixin): + lockfile = __file__ + + def setUp(self): + self.filename = os.path.join(temp_storage_dir, 'my_file.png') + self.file = create_file(self.filename) + + class RemoveImageTests(ImageTestCaseMixin, TestCase): + def test_remove_image(self): + os.remove(self.filename) + self.assertFalse(os.path.exists(self.filename)) + + class ResizeImageTests(ImageTestCaseMixin, TestCase): + def test_resize_image(self): + resize_image(self.file, (48, 48)) + self.assertEqual(get_image_size(self.file), (48, 48)) + .. _testing-reusable-applications: Using the Django test runner to test reusable applications From 9763ed7a668c865100fca214a34923faeeb5da93 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 9 Oct 2019 09:29:46 +0200 Subject: [PATCH 097/287] [2.2.x] Fixed #30858 -- Clarified that AdminEmailHandler processes all 5xx responses. Backport of 94eae4e5633fbf21f5dcae6e0472ce6a51dd3411 from master --- docs/howto/error-reporting.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 90918dffe4df..0edd9f929047 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -20,10 +20,11 @@ Server errors When :setting:`DEBUG` is ``False``, Django will email the users listed in the :setting:`ADMINS` setting whenever your code raises an unhandled exception and -results in an internal server error (HTTP status code 500). This gives the -administrators immediate notification of any errors. The :setting:`ADMINS` will -get a description of the error, a complete Python traceback, and details about -the HTTP request that caused the error. +results in an internal server error (strictly speaking, for any response with +an HTTP status code of 500 or greater). This gives the administrators immediate +notification of any errors. The :setting:`ADMINS` will get a description of the +error, a complete Python traceback, and details about the HTTP request that +caused the error. .. note:: From 8c1b492e5fde8d1b8c08e5775eee12f5d671a553 Mon Sep 17 00:00:00 2001 From: Mac Chapman Date: Tue, 8 Oct 2019 14:07:17 +0100 Subject: [PATCH 098/287] [2.2.x] Fixed #11097 -- Added note about parent link fields in formsets for multi-table inheritance models. Backport of 0b7378db1fdd7bfc7b78089811b3abb722c4ba95 from master --- docs/topics/forms/modelforms.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 2a13c2d1dfce..12828cfab5e1 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -787,6 +787,12 @@ with the ``Author`` model. It works just like a regular formset:: means that a model formset is just an extension of a basic formset that knows how to interact with a particular model. +.. note:: + + When using :ref:`multi-table inheritance `, forms + generated by a formset factory will contain a parent link field (by default + ``_ptr``) instead of an ``id`` field. + Changing the queryset --------------------- From 67bfedd2ae822272d6679c07ac70aa290f1faa89 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Fri, 27 Sep 2019 11:58:14 -0700 Subject: [PATCH 099/287] [2.2.x] Corrected section indentation in unit tests contributor docs. Backport of b39c39e82ae12ca92686ee97a7be2958ee667e5c from master --- docs/internals/contributing/writing-code/unit-tests.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index b453b73c17b1..1c0af171f3e9 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -411,12 +411,12 @@ You can also use the ``DJANGO_TEST_PROCESSES`` environment variable for this purpose. Tips for writing tests ----------------------- +====================== .. highlight:: python Isolating model registration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +---------------------------- To avoid polluting the global :attr:`~django.apps.apps` registry and prevent unnecessary table creation, models defined in a test method should be bound to From c22945f4c738f770f4842c6c939690684329a783 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 9 Oct 2019 12:06:07 +0200 Subject: [PATCH 100/287] [2.2.x] Added note to check Python version in unit tests troubleshooting section. At DjangoCon US 2019 sprints, a number of contributors hit problems with an outdated Python. Backport of 06e087c3ded703b334a4d7c74a882212774253bc from master --- docs/internals/contributing/writing-code/unit-tests.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 1c0af171f3e9..185c6e4494fc 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -324,6 +324,13 @@ in ``tests/auth_tests``. Troubleshooting =============== +Test suite hangs or shows failures on ``master`` branch +------------------------------------------------------- + +Ensure you have the latest point release of a :ref:`supported Python version +`, since there are often bugs in earlier versions +that may cause the test suite to fail or hang. + Many test failures with ``UnicodeEncodeError`` ---------------------------------------------- From 4f7ba25e67681efbb67cc4810e919aa7369cbe34 Mon Sep 17 00:00:00 2001 From: Kees Hink Date: Wed, 9 Oct 2019 12:08:31 +0200 Subject: [PATCH 101/287] [2.2.x] Fixed #27086 -- Doc'd fix for test suite hang on macOS. Backport of 5d9cf79baf07fc4aed7ad1b06990532a65378155 from master --- .../contributing/writing-code/unit-tests.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index 185c6e4494fc..a26993141afe 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -331,6 +331,20 @@ Ensure you have the latest point release of a :ref:`supported Python version `, since there are often bugs in earlier versions that may cause the test suite to fail or hang. +On **macOS** (High Sierra and newer versions), you might see this message +logged, after which the tests hang:: + + objc[42074]: +[__NSPlaceholderDate initialize] may have been in progress in + another thread when fork() was called. + +To avoid this set a ``OBJC_DISABLE_INITIALIZE_FORK_SAFETY`` environment +variable, for example:: + + $ OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES ./runtests.py + +Or add ``export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES`` to your shell's +startup file (e.g. ``~/.profile``). + Many test failures with ``UnicodeEncodeError`` ---------------------------------------------- From 323467e286787684de18d2731776c71667a296be Mon Sep 17 00:00:00 2001 From: Louise Grandjonc Date: Tue, 1 Oct 2019 16:25:40 -0700 Subject: [PATCH 102/287] [2.2.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 ++++- docs/releases/2.1.14.txt | 5 ++++- docs/releases/2.2.7.txt | 5 ++++- tests/postgres_tests/test_json.py | 27 ++++++++++++++++++++++++++- 5 files changed, 39 insertions(+), 5 deletions(-) diff --git a/django/contrib/postgres/lookups.py b/django/contrib/postgres/lookups.py index c2b3d2b569b4..ac8d3bc5225b 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/docs/releases/2.1.14.txt b/docs/releases/2.1.14.txt index 17b001e4e5e2..92354462a06d 100644 --- a/docs/releases/2.1.14.txt +++ b/docs/releases/2.1.14.txt @@ -9,4 +9,7 @@ Django 2.1.14 fixes a regression in 2.1.13. 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/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt index 001af881760c..f39587e43eb3 100644 --- a/docs/releases/2.2.7.txt +++ b/docs/releases/2.2.7.txt @@ -9,4 +9,7 @@ Django 2.2.7 fixes several bugs in 2.2.6. 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 a74a5f8dde78..8a4584eda6a0 100644 --- a/tests/postgres_tests/test_json.py +++ b/tests/postgres_tests/test_json.py @@ -126,7 +126,12 @@ def setUpTestData(cls): 'k': True, 'l': False, }), - JSONModel(field={'foo': 'bar'}), + JSONModel(field={ + 'foo': 'bar', + 'baz': {'a': 'b', 'c': 'd'}, + 'bar': ['foo', 'bar'], + 'bax': {'foo': 'bar'}, + }), ]) def test_exact(self): @@ -361,6 +366,26 @@ 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: + with self.subTest(lookup=lookup): + self.assertTrue(JSONModel.objects.filter( + **{lookup: value}, + ).exists()) + @isolate_apps('postgres_tests') class TestChecks(PostgreSQLSimpleTestCase): From d4d37d09008f920c8cd4faa9ec075de51da0a36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fleschenberg?= Date: Tue, 10 Sep 2019 17:35:36 +0200 Subject: [PATCH 103/287] [2.2.x] Clarified that SECURE_REDIRECT_EXEMPT patterns should not include leading slashes. Backport of d232fd76a85870daf345fd8f8d617fe7802ae194 from master --- docs/ref/settings.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 7937995442e3..dc247d0bb5ed 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2279,8 +2279,11 @@ available in ``request.META``.) Default: ``[]`` (Empty list) If a URL path matches a regular expression in this list, the request will not be -redirected to HTTPS. If :setting:`SECURE_SSL_REDIRECT` is ``False``, this -setting has no effect. +redirected to HTTPS. The +:class:`~django.middleware.security.SecurityMiddleware` strips leading slashes +from URL paths, so patterns shouldn't include them, e.g. +``SECURE_REDIRECT_EXEMPT = [r'^no-ssl/$', …]``. If +:setting:`SECURE_SSL_REDIRECT` is ``False``, this setting has no effect. .. setting:: SECURE_SSL_HOST From d2f02aa56b57f8b3de1b624f8b1a909c67b6823a Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 14 Oct 2019 09:37:45 +0200 Subject: [PATCH 104/287] [2.2.x] Fixed #30870 -- Fixed showing that RunPython operations are irreversible by migrate --plan. Thanks Hasan Ramezani for the initial patch and Kyle Dickerson for the report. Backport of 06d34aab7cfb1632a1538a243db81f24498525ff from master. --- django/core/management/commands/migrate.py | 10 ++++----- docs/releases/2.2.7.txt | 4 ++++ tests/migrations/test_commands.py | 22 +++++++++++++++++++ .../test_migrations_plan/0005_fifth.py | 22 +++++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 tests/migrations/test_migrations_plan/0005_fifth.py diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py index 4914da6803ac..b89e13f2d61f 100644 --- a/django/core/management/commands/migrate.py +++ b/django/core/management/commands/migrate.py @@ -344,21 +344,21 @@ def model_installed(model): def describe_operation(operation, backwards): """Return a string that describes a migration operation for --plan.""" prefix = '' + is_error = False if hasattr(operation, 'code'): code = operation.reverse_code if backwards else operation.code - action = code.__doc__ if code else '' + action = (code.__doc__ or '') if code else None elif hasattr(operation, 'sql'): action = operation.reverse_sql if backwards else operation.sql else: action = '' if backwards: prefix = 'Undo ' - if action is None: + if action is not None: + action = str(action).replace('\n', '') + elif backwards: action = 'IRREVERSIBLE' is_error = True - else: - action = str(action).replace('\n', '') - is_error = False if action: action = ' -> ' + action truncated = Truncator(action) diff --git a/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt index f39587e43eb3..3232b5c5e843 100644 --- a/docs/releases/2.2.7.txt +++ b/docs/releases/2.2.7.txt @@ -13,3 +13,7 @@ Bugfixes ``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`). + +* Prevented :option:`migrate --plan` from showing that ``RunPython`` operations + are irreversible when ``reverse_code`` callables don't have docstrings or + when showing a forward migration plan (:ticket:`30870`). diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py index 36e8f2f5ea40..06cc77287ed0 100644 --- a/tests/migrations/test_commands.py +++ b/tests/migrations/test_commands.py @@ -360,6 +360,28 @@ def test_migrate_plan(self): ' Raw SQL operation -> IRREVERSIBLE\n', out.getvalue() ) + out = io.StringIO() + call_command('migrate', 'migrations', '0005', plan=True, stdout=out, no_color=True) + # Operation is marked as irreversible only in the revert plan. + self.assertEqual( + 'Planned operations:\n' + 'migrations.0005_fifth\n' + ' Raw Python operation\n' + ' Raw Python operation\n' + ' Raw Python operation -> Feed salamander.\n', + out.getvalue() + ) + call_command('migrate', 'migrations', '0005', verbosity=0) + out = io.StringIO() + call_command('migrate', 'migrations', '0004', plan=True, stdout=out, no_color=True) + self.assertEqual( + 'Planned operations:\n' + 'migrations.0005_fifth\n' + ' Raw Python operation -> IRREVERSIBLE\n' + ' Raw Python operation -> IRREVERSIBLE\n' + ' Raw Python operation\n', + out.getvalue() + ) # Cleanup by unmigrating everything: fake the irreversible, then # migrate all to zero. call_command('migrate', 'migrations', '0003', fake=True, verbosity=0) diff --git a/tests/migrations/test_migrations_plan/0005_fifth.py b/tests/migrations/test_migrations_plan/0005_fifth.py new file mode 100644 index 000000000000..3c569ffded8b --- /dev/null +++ b/tests/migrations/test_migrations_plan/0005_fifth.py @@ -0,0 +1,22 @@ +from django.db import migrations + + +def grow_tail(x, y): + pass + + +def feed(x, y): + """Feed salamander.""" + pass + + +class Migration(migrations.Migration): + dependencies = [ + ('migrations', '0004_fourth'), + ] + + operations = [ + migrations.RunPython(migrations.RunPython.noop), + migrations.RunPython(grow_tail), + migrations.RunPython(feed, migrations.RunPython.noop), + ] From f037ae11ec97f90ff94fbff2cdae336619ae63c5 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 14 Oct 2019 06:51:07 -0400 Subject: [PATCH 105/287] [2.2.x] Refs #26207 -- Removed obsolete note about slow constructing a model with deferred fields. This is not true since 7f51876 removed the necessity of creating proxy model classes at runtime for each deferred field sets. Backport of 35396a7f243eceec42cc90725ab573a7d9ac3b4c from master --- docs/topics/db/optimization.txt | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 9d22b980bd29..6e5ccd029057 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -233,14 +233,12 @@ you know that you won't need (or won't need in most cases) to avoid loading them. Note that if you *do* use them, the ORM will have to go and get them in a separate query, making this a pessimization if you use it inappropriately. -Also, be aware that there is some (small extra) overhead incurred inside -Django when constructing a model with deferred fields. Don't be too aggressive -in deferring fields without profiling as the database has to read most of the -non-text, non-VARCHAR data from the disk for a single row in the results, even -if it ends up only using a few columns. The ``defer()`` and ``only()`` methods -are most useful when you can avoid loading a lot of text data or for fields -that might take a lot of processing to convert back to Python. As always, -profile first, then optimize. +Don't be too aggressive in deferring fields without profiling as the database +has to read most of the non-text, non-VARCHAR data from the disk for a single +row in the results, even if it ends up only using a few columns. The +``defer()`` and ``only()`` methods are most useful when you can avoid loading a +lot of text data or for fields that might take a lot of processing to convert +back to Python. As always, profile first, then optimize. Use ``QuerySet.count()`` ------------------------ From 4dc3192a36d7f99721d9dac100db53fbedc4d57c Mon Sep 17 00:00:00 2001 From: Graham Healy Date: Fri, 4 Oct 2019 23:41:31 +0100 Subject: [PATCH 106/287] [2.2.x] Fixed #30808 -- Added the Django Forum to contributing index. Backport of 90abdba08657db397f1887b94ec1ebdc32783379 from master --- docs/internals/contributing/index.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/internals/contributing/index.txt b/docs/internals/contributing/index.txt index c7f2d3726b53..9a1e5d64d7f5 100644 --- a/docs/internals/contributing/index.txt +++ b/docs/internals/contributing/index.txt @@ -6,6 +6,11 @@ Django is a community that lives on its volunteers. As it keeps growing, we always need more people to help others. As soon as you learn Django, you can contribute in many ways: +* Join the `Django forum`_. This forum is a place for discussing the Django + framework and applications and projects that use it. This is also a good + place to ask and answer any questions related to installing, using, or + contributing to Django. + * Join the |django-users| mailing list and answer questions. This mailing list has a huge audience, and we really want to maintain a friendly and helpful atmosphere. If you're new to the Django community, @@ -66,6 +71,7 @@ Browse the following sections to find out how: .. _#django IRC channel: irc://irc.freenode.net/django .. _#django-dev IRC channel: irc://irc.freenode.net/django-dev .. _community page: https://www.djangoproject.com/community/ +.. _Django forum: https://forum.djangoproject.com/ .. _register it here: https://www.djangoproject.com/community/add/blogs/ .. _ticket tracker: https://code.djangoproject.com/ .. _easy pickings: https://code.djangoproject.com/query?status=!closed&easy=1 From 573350475274d23d7bc20ae35e52edf50a09fd6d Mon Sep 17 00:00:00 2001 From: Ad Timmering Date: Sat, 12 Oct 2019 11:44:37 +0900 Subject: [PATCH 107/287] [2.2.x] Fixed #30816 -- Doc'd how to create projects with a local copy of Django. Backport of dee687e93a2d45e9fac404be2098cc4707d31c1f from master --- docs/intro/contributing.txt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt index 5a3350370e81..eb00190ad764 100644 --- a/docs/intro/contributing.txt +++ b/docs/intro/contributing.txt @@ -169,15 +169,27 @@ command line to help you keep track of which one you are using. Anything you install through ``pip`` while this name is displayed will be installed in that virtual environment, isolated from other environments and system-wide packages. +.. _intro-contributing-install-local-copy: + Go ahead and install the previously cloned copy of Django: .. console:: $ pip install -e /path/to/your/local/clone/django/ -The installed version of Django is now pointing at your local copy. You will -immediately see any changes you make to it, which is of great help when writing -your first patch. +The installed version of Django is now pointing at your local copy by installing +in editable mode. You will immediately see any changes you make to it, which is +of great help when writing your first patch. + +Creating projects with a local copy of Django +--------------------------------------------- + +It may be helpful to test your local changes with a Django project. First you +have to create a new virtual environment, :ref:`install the previously cloned +local copy of Django in editable mode `, +and create a new Django project outside of your local copy of Django. You will +immediately see any changes you make to Django in your new project, which is +of great help when writing your first patch. Running Django's test suite for the first time ============================================== From 26ac7ae080ad5ae1c8ddce29fc3b6757b7b3f28a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 15 Oct 2019 14:10:35 +0200 Subject: [PATCH 108/287] [2.2.x] Moved "Sign the CLA" to the bottom of New Contributor First Steps. As discussed on the mailing list, step 1 of First Steps shouldn't be "Find a printer". https://groups.google.com/d/topic/django-developers/owOqFmxufgs/discussion Backport of adfd920cc5f932b9cabc8061563088016a8dfe13 from master --- docs/internals/contributing/new-contributors.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/internals/contributing/new-contributors.txt b/docs/internals/contributing/new-contributors.txt index 90fa97cf287c..de63137286d4 100644 --- a/docs/internals/contributing/new-contributors.txt +++ b/docs/internals/contributing/new-contributors.txt @@ -15,13 +15,6 @@ First steps Start with these easy tasks to discover Django's development process. -* **Sign the Contributor License Agreement** - - The code that you write belongs to you or your employer. If your - contribution is more than one or two lines of code, you need to sign the - `CLA`_. See the `Contributor License Agreement FAQ`_ for a more thorough - explanation. - * **Triage tickets** If an `unreviewed ticket`_ reports a bug, try and reproduce it. If you @@ -63,6 +56,13 @@ Start with these easy tasks to discover Django's development process. .. _reports page: https://code.djangoproject.com/wiki/Reports +* **Sign the Contributor License Agreement** + + The code that you write belongs to you or your employer. If your + contribution is more than one or two lines of code, you need to sign the + `CLA`_. See the `Contributor License Agreement FAQ`_ for a more thorough + explanation. + .. _CLA: https://www.djangoproject.com/foundation/cla/ .. _Contributor License Agreement FAQ: https://www.djangoproject.com/foundation/cla/faq/ .. _unreviewed ticket: https://code.djangoproject.com/query?status=!closed&stage=Unreviewed From aa28213eb51ba26b349c2e51f2defcc7cc87780e Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 16 Oct 2019 14:39:13 +0200 Subject: [PATCH 109/287] [2.2.x] Refs #28436 -- Corrected docs regarding MySQL support of distance lookups. Backport of 440603a4bc0dc4f0b489489e80dcbab0ca736d23 from master --- docs/ref/contrib/gis/db-api.txt | 12 ++++++------ docs/ref/contrib/gis/geoquerysets.txt | 4 ++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 7497e6037748..329ea78dfb5a 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -225,7 +225,7 @@ in the :doc:`model-api` documentation for more details. Distance Lookups ---------------- -*Availability*: PostGIS, Oracle, SpatiaLite, PGRaster (Native) +*Availability*: PostGIS, MySQL, Oracle, SpatiaLite, PGRaster (Native) The following distance lookups are available: @@ -233,7 +233,7 @@ The following distance lookups are available: * :lookup:`distance_lte` * :lookup:`distance_gt` * :lookup:`distance_gte` -* :lookup:`dwithin` +* :lookup:`dwithin` (except MySQL) .. note:: @@ -328,10 +328,10 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite :lookup:`covers` X X X B :lookup:`crosses` X X C :lookup:`disjoint` X X X X B -:lookup:`distance_gt` X X X N -:lookup:`distance_gte` X X X N -:lookup:`distance_lt` X X X N -:lookup:`distance_lte` X X X N +:lookup:`distance_gt` X X X X N +:lookup:`distance_gte` X X X X N +:lookup:`distance_lt` X X X X N +:lookup:`distance_lte` X X X X N :lookup:`dwithin` X X X B :lookup:`equals` X X X X C :lookup:`exact` X X X X B diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 154d8736f806..38e9fec13342 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -696,6 +696,7 @@ Example:: Backend SQL Equivalent ========== ================================================== PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) > 5`` +MySQL ``ST_Distance(poly, geom) > 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) > 5`` SpatiaLite ``Distance(poly, geom) > 5`` ========== ================================================== @@ -716,6 +717,7 @@ Example:: Backend SQL Equivalent ========== =================================================== PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) >= 5`` +MySQL ``ST_Distance(poly, geom) >= 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) >= 5`` SpatiaLite ``Distance(poly, geom) >= 5`` ========== =================================================== @@ -736,6 +738,7 @@ Example:: Backend SQL Equivalent ========== ================================================== PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) < 5`` +MySQL ``ST_Distance(poly, geom) < 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) < 5`` SpatiaLite ``Distance(poly, geom) < 5`` ========== ================================================== @@ -756,6 +759,7 @@ Example:: Backend SQL Equivalent ========== =================================================== PostGIS ``ST_Distance/ST_Distance_Sphere(poly, geom) <= 5`` +MySQL ``ST_Distance(poly, geom) <= 5`` Oracle ``SDO_GEOM.SDO_DISTANCE(poly, geom, 0.05) <= 5`` SpatiaLite ``Distance(poly, geom) <= 5`` ========== =================================================== From 43bc59ceb624d6a1ac58aa7a282254e60c9c4025 Mon Sep 17 00:00:00 2001 From: Antonio Dudarev <8568870+Rusox89@users.noreply.github.com> Date: Tue, 22 Oct 2019 13:23:21 +0100 Subject: [PATCH 110/287] [2.2.x] Refs #13724 -- Corrected QuerySet signature in docs. Backport of 6c23b43655f3710cfb1ecc57236405d11a544247 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 c76df17d7f10..7880d0eed793 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -126,7 +126,7 @@ described here. Here's the formal declaration of a ``QuerySet``: -.. class:: QuerySet(model=None, query=None, using=None) +.. class:: QuerySet(model=None, query=None, using=None, hints=None) Usually when you'll interact with a ``QuerySet`` you'll use it by :ref:`chaining filters `. To make this work, most From c7f4a7da0da35feaa3c8605bfc53420ffefded4c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 23 Oct 2019 14:50:04 +0200 Subject: [PATCH 111/287] [2.2.x] Added tblib to the list of test suite dependencies in documentation. Backport of 2a54ce72f95977fefe796e99c7bbf6cd366927f9 from master --- docs/internals/contributing/writing-code/unit-tests.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index a26993141afe..e0d4ab52b6f5 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -244,6 +244,7 @@ dependencies: * gettext_ (:ref:`gettext_on_windows`) * selenium_ * sqlparse_ (required) +* tblib_ You can find these dependencies in `pip requirements files`_ inside the ``tests/requirements`` directory of the Django source tree and install them @@ -287,6 +288,7 @@ service. .. _selenium: https://pypi.org/project/selenium/ .. _sqlparse: https://pypi.org/project/sqlparse/ .. _pip requirements files: https://pip.pypa.io/en/latest/user_guide/#requirements-files +.. _tblib: https://pypi.org/project/tblib/ .. _Watchman: https://facebook.github.io/watchman/ Code coverage From 7fe09e6d41b35d5a9a03783650249723313d3793 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Wed, 23 Oct 2019 22:16:55 +0200 Subject: [PATCH 112/287] [2.2.x] Fixed #30903 -- Fixed migrations crash on PostgreSQL when adding Index with opclasses and ordering. Backport of fa5f3291e7f2611d53e64ab481ebe951b0161791 from master --- AUTHORS | 1 + django/db/backends/ddl_references.py | 11 ++++++----- docs/releases/2.2.7.txt | 4 ++++ tests/indexes/tests.py | 27 +++++++++++++++++++++++++++ 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/AUTHORS b/AUTHORS index bdba0741b1d8..7eb38f72f706 100644 --- a/AUTHORS +++ b/AUTHORS @@ -329,6 +329,7 @@ answer newbie questions, and generally made Django that much better: Guillaume Pannatier Gustavo Picon hambaloney + Hannes Ljungberg Hannes Struß Hasan Ramezani Hawkeye diff --git a/django/db/backends/ddl_references.py b/django/db/backends/ddl_references.py index d71f6169eaa0..44e49806f815 100644 --- a/django/db/backends/ddl_references.py +++ b/django/db/backends/ddl_references.py @@ -110,13 +110,14 @@ def __init__(self, table, columns, quote_name, col_suffixes=(), opclasses=()): def __str__(self): def col_str(column, idx): - try: - col = self.quote_name(column) + self.col_suffixes[idx] - except IndexError: - col = self.quote_name(column) # Index.__init__() guarantees that self.opclasses is the same # length as self.columns. - return '{} {}'.format(col, self.opclasses[idx]) + col = '{} {}'.format(self.quote_name(column), self.opclasses[idx]) + try: + col = '{} {}'.format(col, self.col_suffixes[idx]) + except IndexError: + pass + return col return ', '.join(col_str(column, idx) for idx, column in enumerate(self.columns)) diff --git a/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt index 3232b5c5e843..cf1f52a685e7 100644 --- a/docs/releases/2.2.7.txt +++ b/docs/releases/2.2.7.txt @@ -17,3 +17,7 @@ Bugfixes * Prevented :option:`migrate --plan` from showing that ``RunPython`` operations are irreversible when ``reverse_code`` callables don't have docstrings or when showing a forward migration plan (:ticket:`30870`). + +* Fixed migrations crash on PostgreSQL when adding an + :class:`~django.db.models.Index` with fields ordering and + :attr:`~.Index.opclasses` (:ticket:`30903`). diff --git a/tests/indexes/tests.py b/tests/indexes/tests.py index 8f3c03264142..4f534f06933a 100644 --- a/tests/indexes/tests.py +++ b/tests/indexes/tests.py @@ -196,6 +196,33 @@ def test_ops_class_partial_tablespace(self): cursor.execute(self.get_opclass_query % indexname) self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', indexname)]) + def test_ops_class_descending(self): + indexname = 'test_ops_class_ordered' + index = Index( + name=indexname, + fields=['-body'], + opclasses=['text_pattern_ops'], + ) + with connection.schema_editor() as editor: + editor.add_index(IndexedArticle2, index) + with editor.connection.cursor() as cursor: + cursor.execute(self.get_opclass_query % indexname) + self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', indexname)]) + + def test_ops_class_descending_partial(self): + indexname = 'test_ops_class_ordered_partial' + index = Index( + name=indexname, + fields=['-body'], + opclasses=['text_pattern_ops'], + condition=Q(headline__contains='China'), + ) + with connection.schema_editor() as editor: + editor.add_index(IndexedArticle2, index) + with editor.connection.cursor() as cursor: + cursor.execute(self.get_opclass_query % indexname) + self.assertCountEqual(cursor.fetchall(), [('text_pattern_ops', indexname)]) + @skipUnless(connection.vendor == 'mysql', 'MySQL tests') class SchemaIndexesMySQLTests(TransactionTestCase): From 9d627bf9b222758d8cccaecce53115cdd5bc1fa0 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Fri, 25 Oct 2019 08:11:11 +0200 Subject: [PATCH 113/287] [2.2.x] Fixed #30906 -- Fixed an example of using the template system to generate CSV. Backport of 05c3ef26a203de1bc227e31b88999ff2e3b11abf from master --- docs/howto/outputting-csv.txt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/howto/outputting-csv.txt b/docs/howto/outputting-csv.txt index 4d614b6ab825..c29e9bc2a6f2 100644 --- a/docs/howto/outputting-csv.txt +++ b/docs/howto/outputting-csv.txt @@ -105,7 +105,7 @@ template output the commas in a :ttag:`for` loop. Here's an example, which generates the same CSV file as above:: from django.http import HttpResponse - from django.template import Context, loader + from django.template import loader def some_view(request): # Create the HttpResponse object with the appropriate CSV header. @@ -120,9 +120,7 @@ Here's an example, which generates the same CSV file as above:: ) t = loader.get_template('my_template_name.txt') - c = Context({ - 'data': csv_data, - }) + c = {'data': csv_data} response.write(t.render(c)) return response From fb77d53ffefc5c203e6cb93be71fd1154c2a884f Mon Sep 17 00:00:00 2001 From: Saad Date: Fri, 25 Oct 2019 00:39:12 +0300 Subject: [PATCH 114/287] [2.2.x] Fixed #30904 -- Doc'd caveat about using filtered queryset with select_for_update() on MySQL. Backport of fc2b1cc926e34041953738e58fa6ad3053059b22 from master --- docs/ref/databases.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index f712926bca9b..7a766bcf2167 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -617,6 +617,11 @@ the ``SELECT ... FOR UPDATE`` statement. If ``select_for_update()`` is used with ``nowait=True``, ``skip_locked=True``, or ``of`` then a :exc:`~django.db.NotSupportedError` is raised. +When using ``select_for_update()`` on MySQL, make sure you filter a queryset +against at least set of fields contained in unique constraints or only against +fields covered by indexes. Otherwise, an exclusive write lock will be acquired +over the full table for the duration of the transaction. + Automatic typecasting can cause unexpected results -------------------------------------------------- From 48df40262bb13ff923dcbafc2bc70af12fe9db47 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 25 Oct 2019 11:22:03 +0200 Subject: [PATCH 115/287] [2.2.x] Refs #30908 -- Fixed the empty value of forms.FilePathField in docs. Backport of daa9415f78279855808134e1f40766f6c5f4bdbc 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 aa13d4f0219f..10bcc0eb3a39 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -627,7 +627,7 @@ For each field, we describe the default widget used if you don't specify .. class:: FilePathField(**kwargs) * Default widget: :class:`Select` - * Empty value: ``None`` + * Empty value: ``''`` (an empty string) * Normalizes to: A string. * Validates that the selected choice exists in the list of choices. * Error message keys: ``required``, ``invalid_choice`` From 4c0979bba4f5ec31f5b5c523c26ae53b38b212f9 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Mon, 28 Oct 2019 12:31:23 +0000 Subject: [PATCH 116/287] [2.2.x] Documented the order in which signal receivers are called. Backport of ab0fd3f58f751859cc7c5260decf0acca26513bf from master --- docs/topics/signals.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/topics/signals.txt b/docs/topics/signals.txt index fc7c641af16f..df23875d3f85 100644 --- a/docs/topics/signals.txt +++ b/docs/topics/signals.txt @@ -49,7 +49,8 @@ Listening to signals To receive a signal, register a *receiver* function using the :meth:`Signal.connect` method. The receiver function is called when the signal -is sent. +is sent. All of the signal's receiver functions are called one at a time, in +the order they were registered. .. method:: Signal.connect(receiver, sender=None, weak=True, dispatch_uid=None) From 4d992bc7d1548663be871aa3b835e7ac91659a22 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 29 Oct 2019 14:13:47 +0100 Subject: [PATCH 117/287] [2.2.x] Fixed typo in docs/topics/files.txt. Backport of bf7b3e2750e3392d112b34e076ee1a77c8adbe77 from master --- docs/topics/files.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 6318f207e2f9..67ce56adcdbd 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -39,7 +39,7 @@ the details of the attached photo:: >>> car = Car.objects.get(name="57 Chevy") >>> car.photo - + >>> car.photo.name 'cars/chevy.jpg' >>> car.photo.path From 4cc1549b6ebfe8983ee2fd4b9be973448f2d0e0b Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Fri, 25 Oct 2019 14:57:37 +0200 Subject: [PATCH 118/287] [2.2.x] Fixed #13750 -- Clarified need to reopen models.ImageField.image file to access raw image data. Backport of f57e174fa61e4c31213f6d0033fb9d647b463626 from master --- docs/topics/files.txt | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 67ce56adcdbd..58bf9b77154e 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -73,6 +73,27 @@ location (:setting:`MEDIA_ROOT` if you are using the default >>> car.photo.path == new_path True +.. note:: + + Whilst :class:`~django.db.models.ImageField` non-image data attributes, + such as ``height``, ``width``, and ``size`` are available on the instance, + the underlying image data cannot be used without reopening the image. For + example:: + + >>> from PIL import Image + >>> car = Car.objects.get(name='57 Chevy') + >>> car.photo.width + 191 + >>> car.photo.height + 287 + >>> image = Image.open(car.photo) + # Raises ValueError: seek of closed file. + >>> car.photo.open() + + >>> image = Image.open(car.photo) + >>> image + + The ``File`` object =================== From 9d15f1ead9c4bc42abcc39ba7aed5c34ade16b1f Mon Sep 17 00:00:00 2001 From: Ken Whitesell Date: Wed, 30 Oct 2019 08:39:23 +0100 Subject: [PATCH 119/287] [2.2.x] Fixed #30917 -- Clarified formsets topic documentation. Backport of 4c762588ff6748a9a9bad111894fd418c43b74a9 from master --- docs/topics/forms/formsets.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index a790ec16fe66..8c2539934ead 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -21,9 +21,9 @@ a formset out of an ``ArticleForm`` you would do:: >>> from django.forms import formset_factory >>> ArticleFormSet = formset_factory(ArticleForm) -You now have created a formset named ``ArticleFormSet``. The formset gives you -the ability to iterate over the forms in the formset and display them as you -would with a regular form:: +You now have created a formset class named ``ArticleFormSet``. +Instantiating the formset gives you the ability to iterate over the forms +in the formset and display them as you would with a regular form:: >>> formset = ArticleFormSet() >>> for form in formset: @@ -34,11 +34,11 @@ would with a regular form:: As you can see it only displayed one empty form. The number of empty forms that is displayed is controlled by the ``extra`` parameter. By default, :func:`~django.forms.formsets.formset_factory` defines one extra form; the -following example will display two blank forms:: +following example will create a formset class to display two blank forms:: >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) -Iterating over the ``formset`` will render the forms in the order they were +Iterating over a formset will render the forms in the order they were created. You can change this order by providing an alternate implementation for the ``__iter__()`` method. From 972eef6b9060aee4a092bedee38a7775fbbe5d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Ericson?= Date: Tue, 29 Oct 2019 20:27:22 +0100 Subject: [PATCH 120/287] [2.2.x] Fixed #30927 -- Simplified an example of test for the deprecation warning with assertWarnsMessage(). Backport of 54a7b021125d23a248e70ba17bf8b10bc8619234 from master --- .../writing-code/submitting-patches.txt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index 5bfff59b52ca..af0000c5d8fb 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -201,20 +201,15 @@ level: class MyDeprecatedTests(unittest.TestCase): ... -You can also add a test for the deprecation warning. You'll have to disable the -"warning as error" behavior in your test by doing:: +You can also add a test for the deprecation warning:: - import warnings + from django.utils.deprecation import RemovedInDjangoXXWarning def test_foo_deprecation_warning(self): - with warnings.catch_warnings(record=True) as warns: - warnings.simplefilter('always') # prevent warnings from appearing as errors + msg = 'Expected deprecation message' + with self.assertWarnsMessage(RemovedInDjangoXXWarning, msg): # invoke deprecated behavior - self.assertEqual(len(warns), 1) - msg = str(warns[0].message) - self.assertEqual(msg, 'Expected deprecation message') - Finally, there are a couple of updates to Django's documentation to make: #) If the existing feature is documented, mark it deprecated in documentation From 3ca4457af3dce203848783c4e1c12fdcefae42ab Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 30 Oct 2019 16:17:20 +0100 Subject: [PATCH 121/287] [2.2.x] Corrected error message in Many-to-many relationships docs. Backport of a370dbd9543703677697e80701b5dbec8c8e7da7 from master --- docs/topics/db/examples/many_to_many.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/db/examples/many_to_many.txt b/docs/topics/db/examples/many_to_many.txt index 746fb2f550a9..922dc977a4e2 100644 --- a/docs/topics/db/examples/many_to_many.txt +++ b/docs/topics/db/examples/many_to_many.txt @@ -54,7 +54,7 @@ You can't associate it with a ``Publication`` until it's been saved:: >>> a1.publications.add(p1) Traceback (most recent call last): ... - ValueError: 'Article' instance needs to have a primary key value before a many-to-many relationship can be used. + ValueError: "" needs to have a value for field "id" before this many-to-many relationship can be used. Save it! :: From eb8a53c7c1c445cc8245bf0b2136d3062e6c7d17 Mon Sep 17 00:00:00 2001 From: Gil Forcada Codinachs Date: Wed, 30 Oct 2019 16:52:40 +0100 Subject: [PATCH 122/287] [2.2.x] Fixed typo in docs/ref/signals.txt. Backport of 632d7bbd3dff1614cf7936dae733278149930f5b from master --- docs/ref/signals.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index 48915cdc452d..dc8c53ac2d20 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -29,7 +29,7 @@ model system. override in your own code. If you override these methods on your model, you must call the parent class' - methods for this signals to be sent. + methods for these signals to be sent. Note also that Django stores signal handlers as weak references by default, so if your handler is a local function, it may be garbage collected. To From 785d1706c411168d5acfa709157f60399ea690d0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 31 Oct 2019 11:34:56 +0100 Subject: [PATCH 123/287] [2.2.x] Fixed #30931 -- Restored ability to override Model.get_FIELD_display(). Thanks Sergey Fedoseev for the implementation idea. Regression in a68ea231012434b522ce45c513d84add516afa60. Backport of 2d38eb0ab9f78d68c083a5b78b1eca39027b279a from master --- django/db/models/fields/__init__.py | 8 ++++++-- docs/releases/2.2.7.txt | 3 +++ tests/model_fields/tests.py | 10 ++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 2210b56dbe0c..e2d1846ad625 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -743,8 +743,12 @@ def contribute_to_class(self, cls, name, private_only=False): if not getattr(cls, self.attname, None): setattr(cls, self.attname, DeferredAttribute(self.attname)) if self.choices: - setattr(cls, 'get_%s_display' % self.name, - partialmethod(cls._get_FIELD_display, field=self)) + if not hasattr(cls, 'get_%s_display' % self.name): + setattr( + cls, + 'get_%s_display' % self.name, + partialmethod(cls._get_FIELD_display, field=self), + ) def get_filter_kwargs_for_object(self, obj): """ diff --git a/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt index cf1f52a685e7..537508225b32 100644 --- a/docs/releases/2.2.7.txt +++ b/docs/releases/2.2.7.txt @@ -21,3 +21,6 @@ Bugfixes * Fixed migrations crash on PostgreSQL when adding an :class:`~django.db.models.Index` with fields ordering and :attr:`~.Index.opclasses` (:ticket:`30903`). + +* Restored the ability to override + :meth:`~django.db.models.Model.get_FOO_display` (:ticket:`30931`). diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index 9e38f0fcf006..7a26fe01e52a 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -114,6 +114,16 @@ def test_choices_and_field_display(self): self.assertIsNone(Whiz(c=None).get_c_display()) # Blank value self.assertEqual(Whiz(c='').get_c_display(), '') # Empty value + def test_overriding_FIELD_display(self): + class FooBar(models.Model): + foo_bar = models.IntegerField(choices=[(1, 'foo'), (2, 'bar')]) + + def get_foo_bar_display(self): + return 'something' + + f = FooBar(foo_bar=1) + self.assertEqual(f.get_foo_bar_display(), 'something') + def test_iterator_choices(self): """ get_choices() works with Iterators. From 6f26693cc87621d5a35bffb5fc3f61b5cc153ce9 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 08:20:22 +0100 Subject: [PATCH 124/287] [2.2.x] Added release dates for 2.2.7, 2.1.14, and 1.11.26. Backport of 126cfefce2b59900138f2bf1ef6ad966cddc55d4 from master --- docs/releases/1.11.26.txt | 2 +- docs/releases/2.1.14.txt | 2 +- docs/releases/2.2.7.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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. diff --git a/docs/releases/2.1.14.txt b/docs/releases/2.1.14.txt index 92354462a06d..310ec56012e2 100644 --- a/docs/releases/2.1.14.txt +++ b/docs/releases/2.1.14.txt @@ -2,7 +2,7 @@ Django 2.1.14 release notes =========================== -*Expected November 1, 2019* +*November 4, 2019* Django 2.1.14 fixes a regression in 2.1.13. diff --git a/docs/releases/2.2.7.txt b/docs/releases/2.2.7.txt index 537508225b32..75b2816c4dcc 100644 --- a/docs/releases/2.2.7.txt +++ b/docs/releases/2.2.7.txt @@ -2,7 +2,7 @@ Django 2.2.7 release notes ========================== -*Expected November 1, 2019* +*November 4, 2019* Django 2.2.7 fixes several bugs in 2.2.6. From 1cbf607009758ef4d495d5617dd113babb637924 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 08:44:55 +0100 Subject: [PATCH 125/287] [2.2.x] Updated man page for Django 2.2. --- docs/man/django-admin.1 | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/man/django-admin.1 b/docs/man/django-admin.1 index 1ab6f57e593d..5b7e4d06bc59 100644 --- a/docs/man/django-admin.1 +++ b/docs/man/django-admin.1 @@ -1,6 +1,6 @@ .\" Man page generated from reStructuredText. . -.TH "DJANGO-ADMIN" "1" "September 02, 2019" "2.2" "Django" +.TH "DJANGO-ADMIN" "1" "November 04, 2019" "2.2" "Django" .SH NAME django-admin \- Utility script for the Django Web framework . @@ -1095,9 +1095,20 @@ to dependencies. \fB \fP: Brings the database schema to a state where the named migration is applied, but no later migrations in the same app are applied. This may involve unapplying migrations if you have previously -migrated past the named migration. Use the name \fBzero\fP to unapply all +migrated past the named migration. You can use a prefix of the migration +name, e.g. \fB0001\fP, as long as it\(aqs unique for the given app name. Use the +name \fBzero\fP to migrate all the way back i.e. to revert all applied migrations for an app. .UNINDENT +.sp +\fBWARNING:\fP +.INDENT 0.0 +.INDENT 3.5 +When unapplying migrations, all dependent migrations will also be +unapplied, regardless of \fB\fP\&. You can use \fB\-\-plan\fP to check +which migrations will be unapplied. +.UNINDENT +.UNINDENT .INDENT 0.0 .TP .B \-\-database DATABASE @@ -1917,6 +1928,15 @@ Each process gets its own database. You must ensure that different test cases don\(aqt access the same resources. For instance, test cases that touch the filesystem should create a temporary directory for their own use. .sp +\fBNOTE:\fP +.INDENT 0.0 +.INDENT 3.5 +If you have test classes that cannot be run in parallel, you can use +\fBSerializeMixin\fP to run them sequentially. See Enforce running test +classes sequentially\&. +.UNINDENT +.UNINDENT +.sp This option requires the third\-party \fBtblib\fP package to display tracebacks correctly: .INDENT 0.0 From 27f2b4475d92af9877c715074b4399a9c72ec557 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 08:49:44 +0100 Subject: [PATCH 126/287] [2.2.x] Bumped version for 2.2.7 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 233c18b5b1d4..650255a6452c 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 7, 'alpha', 0) +VERSION = (2, 2, 7, 'final', 0) __version__ = get_version(VERSION) From a43f71e55fb956d076d0669c03bbfada714c1512 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 09:02:41 +0100 Subject: [PATCH 127/287] [2.2.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 650255a6452c..84db598181a2 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 7, 'final', 0) +VERSION = (2, 2, 8, 'alpha', 0) __version__ = get_version(VERSION) From 24a0ce187a1a337fb7e4686d0c8902dcffa9cec4 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Fri, 1 Nov 2019 19:44:42 +0000 Subject: [PATCH 128/287] [2.2.x] Updated list of field types conversions in ModelForm docs. Backport of 5a856669bf0574f612eea89e72a6b0a3d2cb80d9 from master --- docs/topics/forms/modelforms.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 12828cfab5e1..51f11b729973 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -80,6 +80,8 @@ Model field Form field :class:`DecimalField` :class:`~django.forms.DecimalField` +:class:`DurationField` :class:`~django.forms.DurationField` + :class:`EmailField` :class:`~django.forms.EmailField` :class:`FileField` :class:`~django.forms.FileField` @@ -91,7 +93,7 @@ Model field Form field :class:`ForeignKey` :class:`~django.forms.ModelChoiceField` (see below) -``ImageField`` :class:`~django.forms.ImageField` +:class:`ImageField` :class:`~django.forms.ImageField` :class:`IntegerField` :class:`~django.forms.IntegerField` @@ -118,6 +120,8 @@ Model field Form field :class:`TimeField` :class:`~django.forms.TimeField` :class:`URLField` :class:`~django.forms.URLField` + +:class:`UUIDField` :class:`~django.forms.UUIDField` =================================== ================================================== .. currentmodule:: django.forms From a7f90d9c02f5ed6f6dc1621ef10b9db1f042600b Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 4 Nov 2019 11:57:53 +0100 Subject: [PATCH 129/287] [2.2.x] Refs #30947 -- Changed tuples to lists in model Meta options examples in docs. Follow up to 97d3321e89c8d4434927bdbc308db1ccffa99d3b. Backport of e5cacb1f47cb3a2943bbc7685a630c84503b0d1b from master --- docs/topics/db/examples/many_to_many.txt | 4 ++-- docs/topics/db/examples/many_to_one.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/topics/db/examples/many_to_many.txt b/docs/topics/db/examples/many_to_many.txt index 922dc977a4e2..752abe9f721a 100644 --- a/docs/topics/db/examples/many_to_many.txt +++ b/docs/topics/db/examples/many_to_many.txt @@ -18,7 +18,7 @@ objects, and a ``Publication`` has multiple ``Article`` objects: title = models.CharField(max_length=30) class Meta: - ordering = ('title',) + ordering = ['title'] def __str__(self): return self.title @@ -28,7 +28,7 @@ objects, and a ``Publication`` has multiple ``Article`` objects: publications = models.ManyToManyField(Publication) class Meta: - ordering = ('headline',) + ordering = ['headline'] def __str__(self): return self.headline diff --git a/docs/topics/db/examples/many_to_one.txt b/docs/topics/db/examples/many_to_one.txt index 8a77d89d6df3..6c07b5a45d60 100644 --- a/docs/topics/db/examples/many_to_one.txt +++ b/docs/topics/db/examples/many_to_one.txt @@ -23,7 +23,7 @@ To define a many-to-one relationship, use :class:`~django.db.models.ForeignKey`: return self.headline class Meta: - ordering = ('headline',) + ordering = ['headline'] What follows are examples of operations that can be performed using the Python API facilities. From 406fb336b75663a0131251f9414d8cf5cc43e09b Mon Sep 17 00:00:00 2001 From: Alasdair Nicol Date: Tue, 5 Nov 2019 11:41:14 +0000 Subject: [PATCH 130/287] [2.2.x] Refs #30923 -- Fixed an example of rendering templates in contrib.sites docs. Backport of b991eefd3a9fa4da4eb46fddcb36cf1e8b5862e9 from master --- docs/ref/contrib/sites.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/sites.txt b/docs/ref/contrib/sites.txt index 0a808f23a95b..9a6379943e89 100644 --- a/docs/ref/contrib/sites.txt +++ b/docs/ref/contrib/sites.txt @@ -218,14 +218,14 @@ different template directories (:setting:`DIRS `), you could simply farm out to the template system like so:: from django.core.mail import send_mail - from django.template import Context, loader + from django.template import loader def register_for_newsletter(request): # Check form values, etc., and subscribe the user. # ... - subject = loader.get_template('alerts/subject.txt').render(Context({})) - message = loader.get_template('alerts/message.txt').render(Context({})) + subject = loader.get_template('alerts/subject.txt').render({}) + message = loader.get_template('alerts/message.txt').render({}) send_mail(subject, message, 'editor@ljworld.com', [user.email]) # ... From 22466f91dccf440fa8b0aff3610ed51dbb25403b Mon Sep 17 00:00:00 2001 From: kola-er Date: Mon, 4 Nov 2019 22:44:10 +0100 Subject: [PATCH 131/287] [2.2.x] Fixed #30928 -- Clarified MySQL/MariaDB support of QuerySet.select_for_update() options. Thanks Par Andersson for reporting the issue. Backport of d94d7b113c21d7a0fd802fadb1adbbec8155e880 from master --- docs/ref/databases.txt | 15 +++++++++++---- docs/ref/models/querysets.txt | 3 ++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/ref/databases.txt b/docs/ref/databases.txt index 7a766bcf2167..d525224dd64b 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -612,10 +612,17 @@ both MySQL and Django will attempt to convert the values from UTC to local time. Row locking with ``QuerySet.select_for_update()`` ------------------------------------------------- -MySQL does not support the ``NOWAIT``, ``SKIP LOCKED``, and ``OF`` options to -the ``SELECT ... FOR UPDATE`` statement. If ``select_for_update()`` is used -with ``nowait=True``, ``skip_locked=True``, or ``of`` then a -:exc:`~django.db.NotSupportedError` is raised. +MySQL does not support some options to the ``SELECT ... FOR UPDATE`` statement. +If ``select_for_update()`` is used with an unsupported option, then +a :exc:`~django.db.NotSupportedError` is raised. + +=============== ========== +Option MySQL +=============== ========== +``SKIP LOCKED`` X (≥8.0.1) +``NOWAIT`` X (≥8.0.1) +``OF`` +=============== ========== When using ``select_for_update()`` on MySQL, make sure you filter a queryset against at least set of fields contained in unique constraints or only against diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 7880d0eed793..3ccd5958fd03 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1711,7 +1711,8 @@ them:: Currently, the ``postgresql``, ``oracle``, and ``mysql`` database backends support ``select_for_update()``. However, MySQL doesn't support the -``nowait``, ``skip_locked``, and ``of`` arguments. +``of`` argument and the ``nowait`` and ``skip_locked`` arguments are supported +only on MySQL 8.0.1+. Passing ``nowait=True``, ``skip_locked=True``, or ``of`` to ``select_for_update()`` using database backends that do not support these From 4325239e2df70054716e8211dd0749dfdc1f2550 Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Fri, 1 Nov 2019 17:37:12 -0700 Subject: [PATCH 132/287] [2.2.x] Fixed #30944 -- Changed reusable apps docs to use a declarative config. Backport of 89368ab6e358ebe29a0417d65209182238daa245 from master. --- docs/intro/reusable-apps.txt | 81 ++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 40 deletions(-) diff --git a/docs/intro/reusable-apps.txt b/docs/intro/reusable-apps.txt index 56e8bb162560..dda315b9ad79 100644 --- a/docs/intro/reusable-apps.txt +++ b/docs/intro/reusable-apps.txt @@ -181,50 +181,51 @@ this. For a small app like polls, this process isn't too difficult. license. Just be aware that your licensing choice will affect who is able to use your code. -#. Next we'll create a ``setup.py`` file which provides details about how to - build and install the app. A full explanation of this file is beyond the - scope of this tutorial, but the `setuptools docs - `_ have a good - explanation. Create a file ``django-polls/setup.py`` with the following - contents: +#. Next we'll create ``setup.cfg`` and ``setup.py`` files which detail how to + build and install the app. A full explanation of these files is beyond the + scope of this tutorial, but the `setuptools documentation + `_ has a good explanation. + Create the files ``django-polls/setup.cfg`` and ``django-polls/setup.py`` + with the following contents: + + .. code-block:: ini + :caption: django-polls/setup.cfg + + [metadata] + name = django-polls + version = 0.1 + description = A Django app to conduct Web-based polls. + long_description = file: README.rst + url = https://www.example.com/ + author = Your Name + author_email = yourname@example.com + license = BSD-3-Clause # Example license + classifiers = + Environment :: Web Environment + Framework :: Django + Framework :: Django :: X.Y # Replace "X.Y" as appropriate + Intended Audience :: Developers + License :: OSI Approved :: BSD License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Topic :: Internet :: WWW/HTTP + Topic :: Internet :: WWW/HTTP :: Dynamic Content + + [options] + include_package_data = true + packages = find: .. code-block:: python :caption: django-polls/setup.py - import os - from setuptools import find_packages, setup - - with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as readme: - README = readme.read() - - # allow setup.py to be run from any path - os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir))) - - setup( - name='django-polls', - version='0.1', - packages=find_packages(), - include_package_data=True, - license='BSD License', # example license - description='A simple Django app to conduct Web-based polls.', - long_description=README, - url='https://www.example.com/', - author='Your Name', - author_email='yourname@example.com', - classifiers=[ - 'Environment :: Web Environment', - 'Framework :: Django', - 'Framework :: Django :: X.Y', # replace "X.Y" as appropriate - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', # example license - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Topic :: Internet :: WWW/HTTP', - 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', - ], - ) + from setuptools import setup + + setup() #. Only Python modules and packages are included in the package by default. To include additional files, we'll need to create a ``MANIFEST.in`` file. The From 9f81af4be49cd691cf872c0ea34c5c91e79ea96a Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Tue, 5 Nov 2019 23:24:49 -0800 Subject: [PATCH 133/287] [2.2.x] Added a link to the file email backend from EMAIL_FILE_PATH setting. Backport of c8debd50617142937191986aec77a5eee472c28e from master --- docs/ref/settings.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index dc247d0bb5ed..2f7839fe36a6 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1301,7 +1301,8 @@ The backend to use for sending emails. For the list of available backends see Default: Not defined -The directory used by the ``file`` email backend to store output files. +The directory used by the :ref:`file email backend ` +to store output files. .. setting:: EMAIL_HOST From 8b14545d3610fabb41b386b61985a40afc4f9520 Mon Sep 17 00:00:00 2001 From: Dan Swain Date: Tue, 5 Nov 2019 15:27:09 -0500 Subject: [PATCH 134/287] [2.2.x] Documented arguments for custom 500 error view. Backport of 422b875c6586a6548c6bf34a0851ae82198a8818 from master --- docs/ref/urls.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ref/urls.txt b/docs/ref/urls.txt index 88720189d22d..1527a347202e 100644 --- a/docs/ref/urls.txt +++ b/docs/ref/urls.txt @@ -192,5 +192,5 @@ that should be called in case of server errors. Server errors happen when you have runtime errors in view code. By default, this is :func:`django.views.defaults.server_error`. If you -implement a custom view, be sure it returns an -:class:`~django.http.HttpResponseServerError`. +implement a custom view, be sure it accepts a ``request`` argument and returns +an :class:`~django.http.HttpResponseServerError`. From 7a13f06f3615d05e39e2b495a80e3cb1fc08e30f Mon Sep 17 00:00:00 2001 From: Daniel Fairhead Date: Mon, 4 Nov 2019 11:38:31 +0000 Subject: [PATCH 135/287] [2.2.x] Fixed #15742 -- Fixed an example of collecting selected objects in ModelAdmin.actions docs. The queryset argument is already filtered, and request.POST doesn't contain all selected objects when "Select All" is used. Backport of e651b3095c950627b1eed1527f2bb011ddad03de from master --- docs/ref/contrib/admin/actions.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 91b9ff14dcd9..edf196360f42 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -238,14 +238,16 @@ you'd want to let the user choose a format, and possibly a list of fields to include in the export. The best thing to do would be to write a small action that simply redirects to your custom export view:: - from django.contrib import admin from django.contrib.contenttypes.models import ContentType from django.http import HttpResponseRedirect def export_selected_objects(modeladmin, request, queryset): - selected = request.POST.getlist(admin.ACTION_CHECKBOX_NAME) + selected = queryset.values_list('pk', flat=True) ct = ContentType.objects.get_for_model(queryset.model) - return HttpResponseRedirect("/export/?ct=%s&ids=%s" % (ct.pk, ",".join(selected))) + return HttpResponseRedirect('/export/?ct=%s&ids=%s' % ( + ct.pk, + ','.join(str(pk) for pk in selected), + )) As you can see, the action is the simple part; all the complex logic would belong in your export view. This would need to deal with objects of any type, From 398a9f42097ce2cbfc05de04d50c1ca18b68d542 Mon Sep 17 00:00:00 2001 From: Farhaan Bukhsh Date: Thu, 31 Oct 2019 09:27:31 +0530 Subject: [PATCH 136/287] [2.2.x] Fixed #30865 -- Doc'd that not all DATABASES['OPTIONS'] are passed to command-line client. Backport of cc5622ec8c260535c63279cf6eb293f2b5a742f0 from master --- docs/ref/django-admin.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index f2029e9b19ee..81672b46f4c3 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -211,6 +211,12 @@ program manually. Specifies the database onto which to open a shell. Defaults to ``default``. +.. note:: + + Be aware that not all options set it in the :setting:`OPTIONS` part of your + database configuration in :setting:`DATABASES` are passed to the + command-line client, e.g. ``'isolation_level'``. + ``diffsettings`` ---------------- From a39db53e0c53fb5ed9b166e8d1e69576fe2527eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Fleschenberg?= Date: Wed, 6 Nov 2019 15:09:14 +0100 Subject: [PATCH 137/287] [2.2.x] Replaced 'n_' prefix with 'number_of_' in docs/topics/db/queries.txt. Backport of 367634f976ab43db93321bf4eb898449b670e291 from master --- docs/topics/db/queries.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt index bcdaaea359b1..4fdd20db12bb 100644 --- a/docs/topics/db/queries.txt +++ b/docs/topics/db/queries.txt @@ -40,8 +40,8 @@ models, which comprise a Weblog application: pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) - n_comments = models.IntegerField() - n_pingbacks = models.IntegerField() + number_of_comments = models.IntegerField() + number_of_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): @@ -625,20 +625,20 @@ than pingbacks, we construct an ``F()`` object to reference the pingback count, and use that ``F()`` object in the query:: >>> from django.db.models import F - >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks')) + >>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks')) Django supports the use of addition, subtraction, multiplication, division, modulo, and power arithmetic with ``F()`` objects, both with constants and with other ``F()`` objects. To find all the blog entries with more than *twice* as many comments as pingbacks, we modify the query:: - >>> Entry.objects.filter(n_comments__gt=F('n_pingbacks') * 2) + >>> Entry.objects.filter(number_of_comments__gt=F('number_of_pingbacks') * 2) To find all the entries where the rating of the entry is less than the sum of the pingback count and comment count, we would issue the query:: - >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks')) + >>> Entry.objects.filter(rating__lt=F('number_of_comments') + F('number_of_pingbacks')) You can also use the double underscore notation to span relationships in an ``F()`` object. An ``F()`` object with a double underscore will introduce @@ -1049,7 +1049,7 @@ update one field based on the value of another field in the model. This is especially useful for incrementing counters based upon their current value. For example, to increment the pingback count for every entry in the blog:: - >>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1) + >>> Entry.objects.all().update(number_of_pingbacks=F('number_of_pingbacks') + 1) However, unlike ``F()`` objects in filter and exclude clauses, you can't introduce joins when you use ``F()`` objects in an update -- you can only From 5ad97e53fdf84b5f47bef859c5f358ee6fb6deb2 Mon Sep 17 00:00:00 2001 From: Shipeng Feng Date: Fri, 8 Nov 2019 16:41:36 +0800 Subject: [PATCH 138/287] [2.2.x] Fixed #30955 -- Doc'd that only concrete base models are stored in historical models bases. Abstract models are removed from bases when generating historical model state since 6436f1fad9ce51f18735106ac75aeea3d6d1f310. Backport of 8058d9d7adb189fec75a4b57565f225996c7b22c 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 871f61a7b6cd..04f2fe5695d6 100644 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -414,11 +414,11 @@ classes will need to be kept around for as long as there is a migration referencing them. Any :doc:`custom model fields ` will also need to be kept, since these are imported directly by migrations. -In addition, the base classes of the model are just stored as pointers, so you -must always keep base classes around for as long as there is a migration that -contains a reference to them. On the plus side, methods and managers from these -base classes inherit normally, so if you absolutely need access to these you -can opt to move them into a superclass. +In addition, the concrete base classes of the model are stored as pointers, so +you must always keep base classes around for as long as there is a migration +that contains a reference to them. On the plus side, methods and managers from +these base classes inherit normally, so if you absolutely need access to these +you can opt to move them into a superclass. To remove old references, you can :ref:`squash migrations ` or, if there aren't many references, copy them into the migration files. From a2305a0c0a879ec2efad87a6cce7b99505e1c9f3 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Fri, 8 Nov 2019 12:09:59 +0100 Subject: [PATCH 139/287] [2.2.x] Doc'd archiving historical branches as git tags. Backport of a32f983be8f5711963f20d976f13b3354e36f5c7 from master --- docs/internals/git.txt | 98 +++++++++++++++--------------------------- 1 file changed, 35 insertions(+), 63 deletions(-) diff --git a/docs/internals/git.txt b/docs/internals/git.txt index 90ff3ff3c5bb..f71fcf251ae6 100644 --- a/docs/internals/git.txt +++ b/docs/internals/git.txt @@ -39,15 +39,12 @@ The Git repository includes several `branches`_: They are also used for bugfix and security releases which occur as necessary after the initial release of a feature version. -* ``soc20XX/`` branches were used by students who worked on Django - during the 2009 and 2010 Google Summer of Code programs. - -* ``attic/`` branches were used to develop major or experimental new - features without affecting the rest of Django's code. - The Git repository also contains `tags`_. These are the exact revisions from which packaged Django releases were produced, since version 1.0. +A number of tags also exist under the ``archive/`` prefix for :ref:`archived +work`. + The source code for the `Djangoproject.com `_ website can be found at `github.com/django/djangoproject.com `_. @@ -81,18 +78,11 @@ over to :doc:`the documentation for contributing to Django `, which covers things like the preferred coding style and how to generate and submit a patch. -Other branches -============== - -Django uses branches to prepare for releases of Django. - -In the past when Django was hosted on Subversion, branches were also used for -feature development. Now Django is hosted on Git and feature development is -done on contributor's forks, but the Subversion feature branches remain in Git -for historical reference. - Stable branches ---------------- +=============== + +Django uses branches to prepare for releases of Django. Each major release +series has its own stable branch. These branches can be found in the repository as ``stable/A.B.x`` branches and will be created right after the first alpha is tagged. @@ -127,8 +117,19 @@ updates. interested community members have occasionally used them to provide unofficial support for old Django releases. -Feature-development branches ----------------------------- +Tags +==== + +Each Django release is tagged and signed by the releaser. + +The tags can be found on GitHub's `tags`_ page. + +.. _tags: https://github.com/django/django/tags + +.. _archived-feature-development-work: + +Archived feature-development work +--------------------------------- .. admonition:: Historical information @@ -146,13 +147,15 @@ become part of an official release, but others do not; in either case, there comes a time when the branch is no longer being actively worked on by any developer. At this point the branch is considered closed. -Unfortunately, Django used to be maintained with the Subversion revision -control system, that has no standard way of indicating this. As a workaround, -branches of Django which are closed and no longer maintained were moved into -``attic``. +Django used to be maintained with the Subversion revision control system, that +has no standard way of indicating this. As a workaround, branches of Django +which are closed and no longer maintained were moved into ``attic``. + +A number of tags exist under the ``archive/`` prefix to maintain a reference to +this and other work of historical interest. -For reference, the following are branches whose code eventually became -part of Django itself, and so are no longer separately maintained: +The following tags under the ``archive/attic/`` prefix reference the tip of +branches whose code eventually became part of Django itself: * ``boulder-oracle-sprint``: Added support for Oracle databases to Django's object-relational mapper. This has been part of Django @@ -192,31 +195,9 @@ part of Django itself, and so are no longer separately maintained: Unicode-based strings in most places within Django and Django applications. This became part of Django as of the 1.0 release. -When Django moved from Subversion to Git, the information about branch merges -wasn't preserved in the source code repository. This means that the ``master`` -branch of Django doesn't contain merge commits for the above branches. - -However, this information is `available as a grafts file`_. You can restore it -by putting the following lines in ``.git/info/grafts`` in your local clone:: - - ac64e91a0cadc57f4bc5cd5d66955832320ca7a1 553a20075e6991e7a60baee51ea68c8adc520d9a 0cb8e31823b2e9f05c4ae868c19f5f38e78a5f2e - 79e68c225b926302ebb29c808dda8afa49856f5c d0f57e7c7385a112cb9e19d314352fc5ed5b0747 aa239e3e5405933af6a29dac3cf587b59a099927 - 5cf8f684237ab5addaf3549b2347c3adf107c0a7 cb45fd0ae20597306cd1f877efc99d9bd7cbee98 e27211a0deae2f1d402537f0ebb64ad4ccf6a4da - f69cf70ed813a8cd7e1f963a14ae39103e8d5265 d5dbeaa9be359a4c794885c2e9f1b5a7e5e51fb8 d2fcbcf9d76d5bb8a661ee73dae976c74183098b - aab3a418ac9293bb4abd7670f65d930cb0426d58 4ea7a11659b8a0ab07b0d2e847975f7324664f10 adf4b9311d5d64a2bdd58da50271c121ea22e397 - ff60c5f9de3e8690d1e86f3e9e3f7248a15397c8 7ef212af149540aa2da577a960d0d87029fd1514 45b4288bb66a3cda401b45901e85b645674c3988 - 9dda4abee1225db7a7b195b84c915fdd141a7260 4fe5c9b7ee09dc25921918a6dbb7605edb374bc9 3a7c14b583621272d4ef53061287b619ce3c290d - a19ed8aea395e8e07164ff7d85bd7dff2f24edca dc375fb0f3b7fbae740e8cfcd791b8bccb8a4e66 42ea7a5ce8aece67d16c6610a49560c1493d4653 - 9c52d56f6f8a9cdafb231adf9f4110473099c9b5 c91a30f00fd182faf8ca5c03cd7dbcf8b735b458 4a5c5c78f2ecd4ed8859cd5ac773ff3a01bccf96 - 953badbea5a04159adbfa970f5805c0232b6a401 4c958b15b250866b70ded7d82aa532f1e57f96ae 5664a678b29ab04cad425c15b2792f4519f43928 - 471596fc1afcb9c6258d317c619eaf5fd394e797 4e89105d64bb9e04c409139a41e9c7aac263df4c 3e9035a9625c8a8a5e88361133e87ce455c4fc13 - 9233d0426537615e06b78d28010d17d5a66adf44 6632739e94c6c38b4c5a86cf5c80c48ae50ac49f 18e151bc3f8a85f2766d64262902a9fcad44d937 - -.. _available as a grafts file: https://github.com/ramiro/django-git-grafts - -Additionally, the following branches are closed, but their code was -never merged into Django and the features they aimed to implement -were never finished: +Additionally, the following tags under the ``archive/attic/`` prefix reference +the tips of branches that were closed, but whose code was never merged into +Django, and the features they aimed to implement were never finished: * ``full-history`` @@ -234,16 +215,7 @@ were never finished: * ``sqlalchemy`` -All of the above-mentioned branches now reside in ``attic``. - -Finally, the repository contains ``soc2009/xxx`` and ``soc2010/xxx`` feature -branches, used for the 2009 and 2010 Google Summer of Code projects. - -Tags -==== - -Each Django release is tagged and signed by the releaser. - -The tags can be found on GitHub's `tags`_ page. - -.. _tags: https://github.com/django/django/tags +Finally, under the ``archive/`` prefix, the repository contains +``soc20XX/`` tags referencing the tip of branches that were used by +students who worked on Django during the 2009 and 2010 Google Summer of Code +programs. From 3627fc5ee6e0ff8ff0c9bd145a642c523889547f Mon Sep 17 00:00:00 2001 From: Jon Dufresne Date: Thu, 7 Nov 2019 15:51:02 +0100 Subject: [PATCH 140/287] [2.2.x] Refs #30948 -- Updated install instructions to use pip instead of setup.py. Co-authored-by: Nick Pope Backport of eee4da3b5748370a5c562bc63d2cae61ea85b16c from master --- INSTALL | 4 +--- docs/faq/troubleshooting.txt | 6 +++--- docs/ref/django-admin.txt | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/INSTALL b/INSTALL index dda9b4c4e596..25ff0ec52503 100644 --- a/INSTALL +++ b/INSTALL @@ -3,8 +3,6 @@ Thanks for downloading Django. To install it, make sure you have Python 3.5 or greater installed. Then run this command from the command prompt: - python setup.py install - -If you're upgrading from a previous version, you need to remove it first. + python -m pip install . For more detailed instructions, see docs/intro/install.txt. diff --git a/docs/faq/troubleshooting.txt b/docs/faq/troubleshooting.txt index ba44aa83ef0d..f90d0e8e6ee2 100644 --- a/docs/faq/troubleshooting.txt +++ b/docs/faq/troubleshooting.txt @@ -14,9 +14,9 @@ Problems running ``django-admin`` ----------------------------------- :doc:`django-admin ` should be on your system path if you -installed Django via ``python setup.py``. If it's not on your path, you can -find it in ``site-packages/django/bin``, where ``site-packages`` is a directory -within your Python installation. Consider symlinking to :doc:`django-admin +installed Django via ``pip``. If it's not on your path, you can find it in +``site-packages/django/bin``, where ``site-packages`` is a directory within +your Python installation. Consider symlinking to :doc:`django-admin ` from some place on your path, such as :file:`/usr/local/bin`. diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 81672b46f4c3..4b67880f348c 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -11,8 +11,8 @@ does the same thing as ``django-admin`` but also sets the project's ``settings.py`` file. The ``django-admin`` script should be on your system path if you installed -Django via its ``setup.py`` utility. If it's not on your path, you can find it -in ``site-packages/django/bin`` within your Python installation. Consider +Django via ``pip``. If it's not on your path, you can find it in +``site-packages/django/bin`` within your Python installation. Consider symlinking it from some place on your path, such as ``/usr/local/bin``. For Windows users, who do not have symlinking functionality available, you can From f55f89f9084541e260d35d830205ff6a7db86772 Mon Sep 17 00:00:00 2001 From: Farhaan Bukhsh Date: Sun, 10 Nov 2019 19:22:21 +0530 Subject: [PATCH 141/287] [2.2.x] Fixed #30958 -- Used a clearer example in the Cast() docs. Backport of 4cb15450adac4003ed98f4adcb1710c95fd2b919 from master --- docs/ref/models/database-functions.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index c82a7cd7b7bb..deb559362d73 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -39,10 +39,12 @@ Usage example:: >>> from django.db.models import FloatField >>> from django.db.models.functions import Cast - >>> Value.objects.create(integer=4) - >>> value = Value.objects.annotate(as_float=Cast('integer', FloatField())).get() - >>> print(value.as_float) - 4.0 + >>> Author.objects.create(age=25, name='Margaret Smith') + >>> author = Author.objects.annotate( + ... age_as_float=Cast('age', output_field=FloatField()), + ... ).get() + >>> print(author.age_as_float) + 25.0 ``Coalesce`` ------------ From 6429d71ecbfd778d56ede1a51224286c81eab7f6 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Tue, 22 Oct 2019 23:18:21 +0200 Subject: [PATCH 142/287] [2.2.x] Fixed #30252 -- Clarified need to reopen forms.fields.ImageField.image file to access raw image data. Backport of 2282d9f2e5d08fc782087ebe97ab195303a6e79b from master --- docs/ref/forms/fields.txt | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 10bcc0eb3a39..6f76d0d6ed89 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -710,9 +710,41 @@ For each field, we describe the default widget used if you don't specify After the field has been cleaned and validated, the ``UploadedFile`` object will have an additional ``image`` attribute containing the Pillow - `Image`_ instance used to check if the file was a valid image. Also, - ``UploadedFile.content_type`` will be updated with the image's content type - if Pillow can determine it, otherwise it will be set to ``None``. + `Image`_ instance used to check if the file was a valid image. Pillow + closes the underlying file descriptor after verifying an image, so whilst + non-image data attributes, such as ``format``, ``height``, and ``width``, + are available, methods that access the underlying image data, such as + ``getdata()`` or ``getpixel()``, cannot be used without reopening the file. + For example:: + + >>> from PIL import Image + >>> from django import forms + >>> from django.core.files.uploadedfile import SimpleUploadedFile + >>> class ImageForm(forms.Form): + ... img = forms.ImageField() + >>> file_data = {'img': SimpleUploadedFile('test.png', )} + >>> form = ImageForm({}, file_data) + # Pillow closes the underlying file descriptor. + >>> form.is_valid() + True + >>> image_field = form.cleaned_data['img'] + >>> image_field.image + + >>> image_field.image.width + 191 + >>> image_field.image.height + 287 + >>> image_field.image.format + 'PNG' + >>> image_field.image.getdata() + # Raises AttributeError: 'NoneType' object has no attribute 'seek'. + >>> image = Image.open(image_field) + >>> image.getdata() + + + Additionally, ``UploadedFile.content_type`` will be updated with the + image's content type if Pillow can determine it, otherwise it will be set + to ``None``. .. _Pillow: https://pillow.readthedocs.io/en/latest/ .. _Image: https://pillow.readthedocs.io/en/latest/reference/Image.html From 612c2d166c9048c59ee241ce4ba89858aa65665d Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 11 Nov 2019 11:31:57 +0100 Subject: [PATCH 143/287] [2.2.x] Fixed typo in docs/topics/files.txt. Backport of 08c0d8b16bb7e79074bc9c8ab4f0db97a94d336a from master --- docs/topics/files.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/files.txt b/docs/topics/files.txt index 58bf9b77154e..f8fd623f0b49 100644 --- a/docs/topics/files.txt +++ b/docs/topics/files.txt @@ -92,7 +92,7 @@ location (:setting:`MEDIA_ROOT` if you are using the default >>> image = Image.open(car.photo) >>> image - + The ``File`` object =================== From afde973061a3e6477f3c454f4471842d37e73494 Mon Sep 17 00:00:00 2001 From: David Foster Date: Sun, 20 Oct 2019 11:47:38 -0700 Subject: [PATCH 144/287] [2.2.x] Fixed #30828 -- Added how to remove/insert many-to-many relations in bulk to the database optimization docs. Backport of 6a04e69e686cf417b469d7676f93c2e3a9c8d6a3 from master --- AUTHORS | 1 + docs/topics/db/optimization.txt | 62 +++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/AUTHORS b/AUTHORS index 7eb38f72f706..9b889cbd1067 100644 --- a/AUTHORS +++ b/AUTHORS @@ -225,6 +225,7 @@ answer newbie questions, and generally made Django that much better: David Cramer David Danier David Eklund + David Foster David Gouldin david@kazserve.org David Krauth diff --git a/docs/topics/db/optimization.txt b/docs/topics/db/optimization.txt index 6e5ccd029057..ca2ab35a3eda 100644 --- a/docs/topics/db/optimization.txt +++ b/docs/topics/db/optimization.txt @@ -412,3 +412,65 @@ objects to reduce the number of SQL queries. For example:: my_band.members.add(my_friend) ...where ``Bands`` and ``Artists`` have a many-to-many relationship. + +When inserting different pairs of objects into +:class:`~django.db.models.ManyToManyField` or when the custom +:attr:`~django.db.models.ManyToManyField.through` table is defined, use +:meth:`~django.db.models.query.QuerySet.bulk_create()` method to reduce the +number of SQL queries. For example:: + + PizzaToppingRelationship = Pizza.toppings.through + PizzaToppingRelationship.objects.bulk_create([ + PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni), + PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni), + PizzaToppingRelationship(pizza=your_pizza, topping=mushroom), + ], ignore_conflicts=True) + +...is preferable to:: + + my_pizza.toppings.add(pepperoni) + your_pizza.toppings.add(pepperoni, mushroom) + +...where ``Pizza`` and ``Topping`` have a many-to-many relationship. Note that +there are a number of :meth:`caveats to this method +`, so make sure it's appropriate +for your use case. + +Remove in bulk +-------------- + +When removing objects from :class:`ManyToManyFields +`, use +:meth:`~django.db.models.fields.related.RelatedManager.remove` with multiple +objects to reduce the number of SQL queries. For example:: + + my_band.members.remove(me, my_friend) + +...is preferable to:: + + my_band.members.remove(me) + my_band.members.remove(my_friend) + +...where ``Bands`` and ``Artists`` have a many-to-many relationship. + +When removing different pairs of objects from :class:`ManyToManyFields +`, use +:meth:`~django.db.models.query.QuerySet.delete` on a +:class:`~django.db.models.Q` expression with multiple +:attr:`~django.db.models.ManyToManyField.through` model instances to reduce +the number of SQL queries. For example:: + + from django.db.models import Q + PizzaToppingRelationship = Pizza.toppings.through + PizzaToppingRelationship.objects.filter( + Q(pizza=my_pizza, topping=pepperoni) | + Q(pizza=your_pizza, topping=pepperoni) | + Q(pizza=your_pizza, topping=mushroom) + ).delete() + +...is preferable to:: + + my_pizza.toppings.remove(pepperoni) + your_pizza.toppings.remove(pepperoni, mushroom) + +...where ``Pizza`` and ``Topping`` have a many-to-many relationship. From 90c730d96360d6047fdcf645ef453c4230b219bf Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 12 Nov 2019 14:37:34 +0100 Subject: [PATCH 145/287] [2.2.x] Added stub release notes for 2.2.8 release. Backport of 30359496a3f3d9af0b02afc334710f7e24c74f5b from master --- docs/releases/2.2.8.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.8.txt diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt new file mode 100644 index 000000000000..a2772f51df78 --- /dev/null +++ b/docs/releases/2.2.8.txt @@ -0,0 +1,12 @@ +========================== +Django 2.2.8 release notes +========================== + +*Expected December 2, 2019* + +Django 2.2.8 fixes several bugs in 2.2.7. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 673764b4f77f..83890ceb2b29 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.8 2.2.7 2.2.6 2.2.5 From 8eda248dc9be2da93ff470567ec3291ac51179e3 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 12 Nov 2019 14:06:03 +0100 Subject: [PATCH 146/287] [2.2.x] Refs #29926 -- Bumped minimum tblib version to 1.5.0 in test requirements. Backport of 25903e41fb45ce9cc80dc93bf4b51ea431dcb2b6 from master. --- docs/internals/contributing/writing-code/unit-tests.txt | 2 +- tests/requirements/py3.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/internals/contributing/writing-code/unit-tests.txt b/docs/internals/contributing/writing-code/unit-tests.txt index e0d4ab52b6f5..25a9a15ab854 100644 --- a/docs/internals/contributing/writing-code/unit-tests.txt +++ b/docs/internals/contributing/writing-code/unit-tests.txt @@ -244,7 +244,7 @@ dependencies: * gettext_ (:ref:`gettext_on_windows`) * selenium_ * sqlparse_ (required) -* tblib_ +* tblib_ 1.5.0+ You can find these dependencies in `pip requirements files`_ inside the ``tests/requirements`` directory of the Django source tree and install them diff --git a/tests/requirements/py3.txt b/tests/requirements/py3.txt index 3f0a01e16427..bb335791f445 100644 --- a/tests/requirements/py3.txt +++ b/tests/requirements/py3.txt @@ -13,4 +13,4 @@ pywatchman; sys.platform != 'win32' PyYAML selenium sqlparse -tblib +tblib >= 1.5.0 From cc3516dc4fe0731ceada2f1b1ae41d57e31817a7 Mon Sep 17 00:00:00 2001 From: Bastien Vallet Date: Thu, 24 Oct 2019 13:08:52 +0200 Subject: [PATCH 147/287] [2.2.x] Refs #29926 -- Added Python 3.8 to classifiers and tox.ini. Backport of 37f02c47f8eb4d6e7b3c22401dba57b00780dc5d from master. --- setup.py | 1 + tox.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 20f21ed867ec..0dcfa4724333 100644 --- a/setup.py +++ b/setup.py @@ -101,6 +101,7 @@ def read(fname): 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3 :: Only', 'Topic :: Internet :: WWW/HTTP', 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', diff --git a/tox.ini b/tox.ini index 2f03d810c876..fa0957d541d2 100644 --- a/tox.ini +++ b/tox.ini @@ -21,7 +21,7 @@ passenv = DJANGO_SETTINGS_MODULE PYTHONPATH HOME DISPLAY setenv = PYTHONDONTWRITEBYTECODE=1 deps = - py{3,35,36,37}: -rtests/requirements/py3.txt + py{3,35,36,37,38}: -rtests/requirements/py3.txt postgres: -rtests/requirements/postgres.txt mysql: -rtests/requirements/mysql.txt oracle: -rtests/requirements/oracle.txt From 622158b371ade0e76e84d93ce18fc8caef911d22 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 12 Nov 2019 22:11:44 +0100 Subject: [PATCH 148/287] [2.2.x] Refs #29926 -- Doc'd Python 3.8 compatibility in Django 2.2.x. Backport of b93a0e34d9b9b99d41103782b7e7aeabf47517e3 from master. --- docs/faq/install.txt | 3 ++- docs/releases/2.2.8.txt | 3 ++- docs/releases/2.2.txt | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/faq/install.txt b/docs/faq/install.txt index 1aaaa56b29ed..5f322bc7e639 100644 --- a/docs/faq/install.txt +++ b/docs/faq/install.txt @@ -48,7 +48,8 @@ Django version Python versions ============== =============== 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, 2.2 3.5, 3.6, 3.7 +2.1 3.5, 3.6, 3.7 +2.2 3.5, 3.6, 3.7, 3.8 (added in 2.2.8) ============== =============== For each version of Python, only the latest micro release (A.B.C) is officially diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index a2772f51df78..0c2b3eabdf39 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -4,7 +4,8 @@ Django 2.2.8 release notes *Expected December 2, 2019* -Django 2.2.8 fixes several bugs in 2.2.7. +Django 2.2.8 fixes several bugs in 2.2.7 and adds compatibility with Python +3.8. Bugfixes ======== diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 2639aa886a55..98795433c002 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -22,8 +22,9 @@ for the previous LTS, Django 1.11, will end in April 2020. Python compatibility ==================== -Django 2.2 supports Python 3.5, 3.6, and 3.7. We **highly recommend** and only -officially support the latest release of each series. +Django 2.2 supports Python 3.5, 3.6, 3.7, and 3.8 (as of 2.2.8). We +**highly recommend** and only officially support the latest release of each +series. .. _whats-new-2.2: From d5e8ad004913bc5aa9925b997154d5ef8bcb81cd Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Fri, 15 Nov 2019 17:28:48 +0100 Subject: [PATCH 149/287] [2.2.x] Fixed #30990 -- Fixed example output in 'z' date format docs. Backport of 1185c6172b4dd5482b7bc76b12d7a0588320e027 from master --- django/utils/dateformat.py | 2 +- docs/ref/templates/builtins.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index d3f586aacf02..29893fe6b96e 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -348,7 +348,7 @@ def Y(self): return self.data.year def z(self): - "Day of the year; i.e. '0' to '365'" + """Day of the year, i.e. 1 to 366.""" doy = self.year_days[self.data.month] + self.data.day if self.L() and self.data.month > 2: doy += 1 diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 8a8514914e71..f33b52e5d44c 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1352,7 +1352,7 @@ Format character Description Example output month, 2 characters. ``w`` Day of the week, digits without ``'0'`` (Sunday) to ``'6'`` (Saturday) leading zeros. -``z`` Day of the year. ``0`` to ``365`` +``z`` Day of the year. ``1`` to ``366`` **Week** ``W`` ISO-8601 week number of year, with ``1``, ``53`` weeks starting on Monday. From da186625a017175e9caccc304474511620d4c6ef Mon Sep 17 00:00:00 2001 From: Luke Plant Date: Fri, 15 Nov 2019 22:06:33 +0300 Subject: [PATCH 150/287] [2.2.x] Expanded API stability docs to include our policy of continual improvement. Backport of 5cef2cd4a10e51035e2728e3e5e59265bc0347e0 from master --- docs/misc/api-stability.txt | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/misc/api-stability.txt b/docs/misc/api-stability.txt index e18b3bb3135f..d59e557cc13d 100644 --- a/docs/misc/api-stability.txt +++ b/docs/misc/api-stability.txt @@ -2,12 +2,21 @@ API stability ============= -Django promises API stability and forwards-compatibility since version 1.0. In -a nutshell, this means that code you develop against a version of Django will -continue to work with future releases. You may need to make minor changes when -upgrading the version of Django your project uses: see the "Backwards -incompatible changes" section of the :doc:`release note ` for -the version or versions to which you are upgrading. +Django is committed to API stability and forwards-compatibility. In a nutshell, +this means that code you develop against a version of Django will continue to +work with future releases. You may need to make minor changes when upgrading +the version of Django your project uses: see the "Backwards incompatible +changes" section of the :doc:`release note ` for the version +or versions to which you are upgrading. + +At the same time as making API stability a very high priority, Django is also +committed to continual improvement, along with aiming for "one way to do it" +(eventually) in the APIs we provide. This means that when we discover clearly +superior ways to do things, we will deprecate and eventually remove the old +ways. Our aim is to provide a modern, dependable web framework of the highest +quality that encourages best practices in all projects that use it. By using +incremental improvements, we try to avoid both stagnation and large breaking +upgrades. What "stable" means =================== @@ -29,8 +38,8 @@ In this context, stable means: See :ref:`official-releases` for more details on how Django's version numbering scheme works, and how features will be deprecated. -- We'll only break backwards compatibility of these APIs if a bug or - security hole makes it completely unavoidable. +- We'll only break backwards compatibility of these APIs without a deprecation + process if a bug or security hole makes it completely unavoidable. Stable APIs =========== From c31252fc75fd29291e4592c9a08e8583e421f28d Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Tue, 19 Nov 2019 10:08:32 +0100 Subject: [PATCH 151/287] [2.2.x] Fixed #30999 -- Fixed typo in docs/howto/custom-template-tags.txt. Backport of 33eecfa7405ea997669f8edb61c596c3f295c793 from master --- docs/howto/custom-template-tags.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/howto/custom-template-tags.txt b/docs/howto/custom-template-tags.txt index c311607af7fc..24a2e59f1c7f 100644 --- a/docs/howto/custom-template-tags.txt +++ b/docs/howto/custom-template-tags.txt @@ -892,7 +892,7 @@ Registering the tag ------------------- Finally, register the tag with your module's ``Library`` instance, as explained -in :ref:`writing custom template filters` +in :ref:`writing custom template tags` above. Example:: register.tag('current_time', do_current_time) From 4082f078bce6ef937f604cd6270676700607b20f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 19 Nov 2019 12:33:39 +0100 Subject: [PATCH 152/287] [2.2.x] Added stub release notes for 2.1.15. Backport of e9def97d1095efed15a109d82fe0498ebd56fa04 from master --- docs/releases/2.1.15.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.1.15.txt diff --git a/docs/releases/2.1.15.txt b/docs/releases/2.1.15.txt new file mode 100644 index 000000000000..9cd6457985dd --- /dev/null +++ b/docs/releases/2.1.15.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.1.15 release notes +=========================== + +*Expected December 2, 2019* + +Django 2.1.15 fixes a data loss bug in 2.1.14. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 83890ceb2b29..067b17791247 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -40,6 +40,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.1.15 2.1.14 2.1.13 2.1.12 From 11e42001d9b4d91ad1f7161b7242ddff5457f56a Mon Sep 17 00:00:00 2001 From: Caio Ariede Date: Mon, 18 Nov 2019 09:35:31 -0300 Subject: [PATCH 153/287] [2.2.x] Fixed #27164 -- Fixed an example of using routers in multiple databases docs. Make sure that AuthRouter includes ContentType in the same database. Backport of 608e06023e6eaf75f744134a0fd203853260e616 from master --- docs/topics/db/multi-db.txt | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/docs/topics/db/multi-db.txt b/docs/topics/db/multi-db.txt index 095e228949de..513d11b0dd08 100644 --- a/docs/topics/db/multi-db.txt +++ b/docs/topics/db/multi-db.txt @@ -301,44 +301,51 @@ databases:: } Now we'll need to handle routing. First we want a router that knows to -send queries for the ``auth`` app to ``auth_db``:: +send queries for the ``auth`` and ``contenttypes`` apps to ``auth_db`` +(``auth`` models are linked to ``ContentType``, so they must be stored in the +same database):: class AuthRouter: """ A router to control all database operations on models in the - auth application. + auth and contenttypes applications. """ + route_app_labels = {'auth', 'contenttypes'} + def db_for_read(self, model, **hints): """ - Attempts to read auth models go to auth_db. + Attempts to read auth and contenttypes models go to auth_db. """ - if model._meta.app_label == 'auth': + if model._meta.app_label in self.route_app_labels: return 'auth_db' return None def db_for_write(self, model, **hints): """ - Attempts to write auth models go to auth_db. + Attempts to write auth and contenttypes models go to auth_db. """ - if model._meta.app_label == 'auth': + if model._meta.app_label in self.route_app_labels: return 'auth_db' return None def allow_relation(self, obj1, obj2, **hints): """ - Allow relations if a model in the auth app is involved. + Allow relations if a model in the auth or contenttypes apps is + involved. """ - if obj1._meta.app_label == 'auth' or \ - obj2._meta.app_label == 'auth': + if ( + obj1._meta.app_label in self.route_app_labels or + obj2._meta.app_label in self.route_app_labels + ): return True return None def allow_migrate(self, db, app_label, model_name=None, **hints): """ - Make sure the auth app only appears in the 'auth_db' - database. + Make sure the auth and contenttypes apps only appear in the + 'auth_db' database. """ - if app_label == 'auth': + if app_label in self.route_app_labels: return db == 'auth_db' return None From 9c0fed53ede759ac6adb5a49428dd4704250c3a9 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Thu, 21 Nov 2019 13:03:18 +0500 Subject: [PATCH 154/287] [2.2.x] Made versionadded/versionchanged annotations without a content end with ".". Regression in d2afa5eb2308e672b6313876856e32e2561b90f3. Backport of 5032556483f16b0b5f182e393eb5c6548fc505be from master --- docs/_ext/djangodocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 6f8c112539d7..d33d48a1ebea 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -153,7 +153,7 @@ def visit_versionmodified(self, node): if version_text: title = "%s%s" % ( version_text % node['version'], - ":" if node else "." + ":" if len(node) else "." ) self.body.append('%s ' % title) From 8a8ea93b98f927d198d713b7367925f82b4d70e8 Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Thu, 21 Nov 2019 10:54:05 +0100 Subject: [PATCH 155/287] [2.2.x] Fixed #30625 -- Doc'd cache.get()/delete() behavior change in Django 2.2. Backport of f69b32782e21642c6184162d888fcc17dd1dd85e from master --- docs/releases/2.2.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 98795433c002..86e1f70dc844 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -471,6 +471,9 @@ Miscellaneous with several third-party apps that had models in tests without migrations. You must add migrations for such models. +* Providing an integer in the ``key`` argument of the :meth:`.cache.delete` or + :meth:`.cache.get` now raises :exc:`ValueError`. + .. _deprecated-features-2.2: Features deprecated in 2.2 From ad56858e83c39f50612f635500a1a5df37b245d3 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Fri, 22 Nov 2019 21:50:11 +0100 Subject: [PATCH 156/287] [2.2.x] Fixed #28469 -- Doc'd how to create a custom HttpResponse subclass. Backport of 9f1ec9efc35bbb375c9cebb3e0d8c1b7be838338 from master --- docs/ref/request-response.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 8d834621cc46..288fdae4f7ca 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -756,6 +756,8 @@ Methods :setting:`DEFAULT_CHARSET` settings, by default: "`text/html; charset=utf-8`". ``status`` is the :rfc:`HTTP status code <7231#section-6>` for the response. + You can use Python's :py:class:`http.HTTPStatus` for meaningful aliases, + such as ``HTTPStatus.NO_CONTENT``. ``reason`` is the HTTP response phrase. If not provided, a default phrase will be used. @@ -958,6 +960,18 @@ types of HTTP responses. Like ``HttpResponse``, these subclasses live in :class:`~django.template.response.SimpleTemplateResponse`, and the ``render`` method must itself return a valid response object. +Custom response classes +~~~~~~~~~~~~~~~~~~~~~~~ + +If you find yourself needing a response class that Django doesn't provide, you +can create it with the help of :py:class:`http.HTTPStatus`. For example:: + + from http import HTTPStatus + from django.http import HttpResponse + + class HttpResponseNoContent(HttpResponse): + status_code = HTTPStatus.NO_CONTENT + ``JsonResponse`` objects ======================== From f2f8e84939a2987aa82c6ddd9351b2575cde184b Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Sat, 23 Nov 2019 13:59:31 +0500 Subject: [PATCH 157/287] [2.2.x] Doc'd SpatiaLite support of 3D geometry fields. Support was added in 65129aac07022f23afa1df7ec7fad2216634cb38. Backport of 85c598900772a00aee2ad4a20e3f3854f1c87fbd from master --- docs/ref/contrib/gis/model-api.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/gis/model-api.txt b/docs/ref/contrib/gis/model-api.txt index 7ea6e9a0805d..0f9c31192270 100644 --- a/docs/ref/contrib/gis/model-api.txt +++ b/docs/ref/contrib/gis/model-api.txt @@ -205,7 +205,7 @@ three-dimensional support. .. note:: - At this time 3D support is limited to the PostGIS spatial backend. + At this time 3D support is limited to the PostGIS and SpatiaLite backends. ``geography`` ------------- From 28dd5362ae17a975c4d273d6019aeb8e890e3576 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Mon, 25 Nov 2019 10:33:34 +0100 Subject: [PATCH 158/287] [2.2.x] Fixed #31025 -- Fixed highlightlinenothreshold deprecation warning on Sphinx 1.8+. Backport of 89d41cba392b759732ba9f1db4ff29ed47da6a56 from master --- docs/_ext/djangodocs.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index d33d48a1ebea..95ae53068024 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -254,10 +254,7 @@ def visit_console_html(self, node): self.body.append('
\n' % {'id': uid}) win_text = node['win_console_text'] highlight_args = {'force': True} - if 'linenos' in node: - linenos = node['linenos'] - else: - linenos = win_text.count('\n') >= self.highlightlinenothreshold - 1 + linenos = node.get('linenos', False) def warner(msg): self.builder.warn(msg, (self.builder.current_docname, node.line)) From 2af606003a97559cd2c91b2ca0e41b326752303b Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Tue, 26 Nov 2019 00:59:42 +0500 Subject: [PATCH 159/287] [2.2.x] Updated link to IBM DB2 backend. See https://github.com/ibmdb/python-ibmdb/pull/375 Backport of 5573a54d409bb98b5c5acdb308310bed02d392c2 from master --- 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 d525224dd64b..a511b5828e81 100644 --- a/docs/ref/databases.txt +++ b/docs/ref/databases.txt @@ -984,7 +984,7 @@ vary considerably. Queries regarding the specific capabilities of these unofficial backends, along with any support queries, should be directed to the support channels provided by each 3rd party project. -.. _IBM DB2: https://pypi.org/project/ibm_db/ +.. _IBM DB2: https://pypi.org/project/ibm_db_django/ .. _Microsoft SQL Server: https://pypi.org/project/django-pyodbc-azure/ .. _Firebird: https://github.com/maxirobaina/django-firebird .. _ODBC: https://github.com/lionheart/django-pyodbc/ From 7873d3757d3075d36cf68771bf4f584ba3386e37 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sun, 24 Nov 2019 23:09:07 +0100 Subject: [PATCH 160/287] [2.2.x] Fixed #31031 -- Fixed data loss in admin changelist view when formset's prefix contains regex special chars. Regression in b18650a2634890aa758abae2f33875daa13a9ba3. Backport of 52936efacec4df05df2d9872c09c3332335bf21b from master --- django/contrib/admin/options.py | 4 +++- docs/releases/2.2.8.txt | 4 +++- tests/admin_changelist/tests.py | 20 ++++++++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 52ad319ef2bb..520e3d023e28 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1638,7 +1638,9 @@ def change_view(self, request, object_id, form_url='', extra_context=None): 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)) + pk_pattern = re.compile( + r'{}-\d+-{}$'.format(re.escape(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): diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 0c2b3eabdf39..e9bd1ed1cb68 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -10,4 +10,6 @@ Django 2.2.8 fixes several bugs in 2.2.7 and adds compatibility with Python Bugfixes ======== -* ... +* Fixed a data loss possibility in the admin changelist view when a custom + :ref:`formset's prefix ` contains regular expression special + characters, e.g. `'$'` (:ticket:`31031`). diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index dfd8e914514e..464e6904d82b 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -827,6 +827,26 @@ def test_get_list_editable_queryset(self): queryset = m._get_list_editable_queryset(request, prefix='form') self.assertEqual(queryset.count(), 2) + def test_get_list_editable_queryset_with_regex_chars_in_prefix(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) + 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) From c2a8a69b729749ede9b366a5791ea60b51a6dfa0 Mon Sep 17 00:00:00 2001 From: Sergey Fedoseev Date: Mon, 25 Nov 2019 20:28:47 +0500 Subject: [PATCH 161/287] [2.2.x] Used :ticket: role in all tickets links. Backport of abc51d44afcb8482c64c0bb8c1022f11e7eb2bbb from master. --- docs/releases/1.11.txt | 4 ++-- docs/releases/1.2.1.txt | 8 +++----- docs/releases/1.2.4.txt | 4 +--- docs/releases/1.5.1.txt | 4 ++-- docs/releases/1.5.txt | 5 ++--- docs/topics/db/aggregation.txt | 5 ++--- docs/topics/forms/modelforms.txt | 6 +++--- docs/topics/testing/overview.txt | 4 +--- 8 files changed, 16 insertions(+), 24 deletions(-) diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 7d10d80d5ce8..bb7c06fe9f17 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -449,8 +449,8 @@ Backwards incompatible changes in 1.11 dependency for GeoDjango. In older versions, it's only required for SQLite. * ``contrib.gis.maps`` is removed as it interfaces with a retired version of - the Google Maps API and seems to be unmaintained. If you're using it, `let - us know `_. + the Google Maps API and seems to be unmaintained. If you're using it, + :ticket:`let us know <14284>`. * The ``GEOSGeometry`` equality operator now also compares SRID. diff --git a/docs/releases/1.2.1.txt b/docs/releases/1.2.1.txt index 0e193bfe3446..4ccefd82ec62 100644 --- a/docs/releases/1.2.1.txt +++ b/docs/releases/1.2.1.txt @@ -3,8 +3,6 @@ Django 1.2.1 release notes ========================== Django 1.2.1 was released almost immediately after 1.2.0 to correct two small -bugs: one was in the documentation packaging script, the other was a bug_ that -affected datetime form field widgets when localization was enabled. - -.. _bug: https://code.djangoproject.com/ticket/13560 - +bugs: one was in the documentation packaging script, the other was a +:ticket:`bug <13560>` that affected datetime form field widgets when +localization was enabled. diff --git a/docs/releases/1.2.4.txt b/docs/releases/1.2.4.txt index 661dcc925b66..ea0b9a5abcbf 100644 --- a/docs/releases/1.2.4.txt +++ b/docs/releases/1.2.4.txt @@ -66,12 +66,10 @@ configuration. Most users -- even users with multiple-database configurations -- need not be concerned about the data loss bug, or the manual configuration of -:setting:`TEST_DEPENDENCIES`. See the `original problem report`_ +:setting:`TEST_DEPENDENCIES`. See the :ticket:`original problem report <14415>` documentation on :ref:`controlling the creation order of test databases ` for details. -.. _original problem report: https://code.djangoproject.com/ticket/14415 - GeoDjango ========= diff --git a/docs/releases/1.5.1.txt b/docs/releases/1.5.1.txt index e9d7e75b2386..cc961ac304bf 100644 --- a/docs/releases/1.5.1.txt +++ b/docs/releases/1.5.1.txt @@ -10,9 +10,9 @@ compatible with Django 1.5, but includes a handful of fixes. The biggest fix is for a memory leak introduced in Django 1.5. Under certain circumstances, repeated iteration over querysets could leak memory - sometimes quite a bit of it. If you'd like more information, the details are in -`our ticket tracker`__ (and in `a related issue`__ in Python itself). +:ticket:`our ticket tracker <19895>` (and in `a related issue`__ in Python +itself). -__ https://code.djangoproject.com/ticket/19895 __ https://bugs.python.org/issue17468 If you've noticed memory problems under Django 1.5, upgrading to 1.5.1 should diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt index 62d821e6b59e..833e5b6f653a 100644 --- a/docs/releases/1.5.txt +++ b/docs/releases/1.5.txt @@ -505,7 +505,8 @@ between other versions of ``simplejson``: - ``simplejson.JSONEncoder`` gained a ``namedtuple_as_object`` keyword argument in version 2.2. -More information on these incompatibilities is available in `ticket #18023`_. +More information on these incompatibilities is available in +:ticket:`ticket #18023 <18023#comment:10>`. The net result is that, if you have installed ``simplejson`` and your code uses Django's serialization internals directly -- for instance @@ -517,8 +518,6 @@ At this point, the maintainers of Django believe that using :mod:`json` from the standard library offers the strongest guarantee of backwards-compatibility. They recommend to use it from now on. -.. _ticket #18023: https://code.djangoproject.com/ticket/18023#comment:10 - String types of hasher method parameters ---------------------------------------- diff --git a/docs/topics/db/aggregation.txt b/docs/topics/db/aggregation.txt index 505b3adf91ea..4bb3b00b008f 100644 --- a/docs/topics/db/aggregation.txt +++ b/docs/topics/db/aggregation.txt @@ -199,9 +199,8 @@ modified using any other ``QuerySet`` operation, including ``filter()``, Combining multiple aggregations ------------------------------- -Combining multiple aggregations with ``annotate()`` will `yield the wrong -results `_ because joins are used -instead of subqueries: +Combining multiple aggregations with ``annotate()`` will :ticket:`yield the +wrong results <10060>` because joins are used instead of subqueries: >>> book = Book.objects.first() >>> book.authors.count() diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 51f11b729973..b9ecc14026f8 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -960,9 +960,9 @@ extra forms displayed. Also, ``extra=0`` doesn't prevent creation of new model instances as you can :ref:`add additional forms with JavaScript ` -or just send additional POST data. Formsets `don't yet provide functionality -`_ for an "edit only" view that -prevents creation of new instances. +or just send additional POST data. Formsets :ticket:`don't yet provide +functionality <26142>` for an "edit only" view that prevents creation of new +instances. If the value of ``max_num`` is greater than the number of existing related objects, up to ``extra`` additional blank forms will be added to the formset, diff --git a/docs/topics/testing/overview.txt b/docs/topics/testing/overview.txt index 1eea7ead226c..3b07ed42c69c 100644 --- a/docs/topics/testing/overview.txt +++ b/docs/topics/testing/overview.txt @@ -275,9 +275,7 @@ setting. Caches are not cleared after each test, and running "manage.py test fooapp" can insert data from the tests into the cache of a live system if you run your tests in production because, unlike databases, a separate "test cache" is not -used. This behavior `may change`_ in the future. - -.. _may change: https://code.djangoproject.com/ticket/11505 +used. This behavior :ticket:`may change <11505>` in the future. Understanding the test output ----------------------------- From e82a1bcf622e5c9cf37f28bde21af8176b6dd118 Mon Sep 17 00:00:00 2001 From: Adam Johnson Date: Sat, 23 Nov 2019 10:21:53 +0000 Subject: [PATCH 162/287] [2.2.x] Improved custom MultiWidget example in docs. Backport of 7742cc0c8f620f7b65b02b5018a5f7078325d467 from master --- docs/ref/forms/widgets.txt | 74 ++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt index d649ece6ba12..08ec8c80143f 100644 --- a/docs/ref/forms/widgets.txt +++ b/docs/ref/forms/widgets.txt @@ -411,57 +411,51 @@ foundation for custom widgets. :meth:`~Widget.value_from_datadict`:: from datetime import date - from django.forms import widgets + from django import forms - class DateSelectorWidget(widgets.MultiWidget): + class DateSelectorWidget(forms.MultiWidget): def __init__(self, attrs=None): - # create choices for days, months, years - # example below, the rest snipped for brevity. - years = [(year, year) for year in (2011, 2012, 2013)] - _widgets = ( - widgets.Select(attrs=attrs, choices=days), - widgets.Select(attrs=attrs, choices=months), - widgets.Select(attrs=attrs, choices=years), - ) - super().__init__(_widgets, attrs) + days = [(day, day) for day in range(1, 32)] + months = [(month, month) for month in range(1, 13)] + years = [(year, year) for year in [2018, 2019, 2020]] + widgets = [ + forms.Select(attrs=attrs, choices=days), + forms.Select(attrs=attrs, choices=months), + forms.Select(attrs=attrs, choices=years), + ] + super().__init__(widgets, attrs) def decompress(self, value): - if value: + if isinstance(value, date): return [value.day, value.month, value.year] + elif isinstance(value, str): + year, month, day = value.split('-') + return [day, month, year] return [None, None, None] def value_from_datadict(self, data, files, name): - datelist = [ - widget.value_from_datadict(data, files, name + '_%s' % i) - for i, widget in enumerate(self.widgets)] - try: - D = date( - day=int(datelist[0]), - month=int(datelist[1]), - year=int(datelist[2]), - ) - except ValueError: - return '' - else: - return str(D) - - The constructor creates several :class:`Select` widgets in a tuple. The - ``super`` class uses this tuple to setup the widget. + day, month, year = super().value_from_datadict(data, files, name) + # DateField expects a single string that it can parse into a date. + return '{}-{}-{}'.format(year, month, day) + + The constructor creates several :class:`Select` widgets in a list. The + ``super()`` method uses this list to setup the widget. The required method :meth:`~MultiWidget.decompress` breaks up a ``datetime.date`` value into the day, month, and year values corresponding - to each widget. Note how the method handles the case where ``value`` is - ``None``. - - The default implementation of :meth:`~Widget.value_from_datadict` returns - a list of values corresponding to each ``Widget``. This is appropriate - when using a ``MultiWidget`` with a :class:`~django.forms.MultiValueField`, - but since we want to use this widget with a :class:`~django.forms.DateField` - which takes a single value, we have overridden this method to combine the - data of all the subwidgets into a ``datetime.date``. The method extracts - data from the ``POST`` dictionary and constructs and validates the date. - If it is valid, we return the string, otherwise, we return an empty string - which will cause ``form.is_valid`` to return ``False``. + to each widget. If an invalid date was selected, such as the non-existent + 30th February, the :class:`~django.forms.DateField` passes this method a + string instead, so that needs parsing. The final ``return`` handles when + ``value`` is ``None``, meaning we don't have any defaults for our + subwidgets. + + The default implementation of :meth:`~Widget.value_from_datadict` returns a + list of values corresponding to each ``Widget``. This is appropriate when + using a ``MultiWidget`` with a :class:`~django.forms.MultiValueField`. But + since we want to use this widget with a :class:`~django.forms.DateField`, + which takes a single value, we have overridden this method. The + implementation here combines the data from the subwidgets into a string in + the format that :class:`~django.forms.DateField` expects. .. _built-in widgets: From 3cf70df468d4453fb315cb74db0232ac9a1e89be Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 27 Nov 2019 07:36:20 +0100 Subject: [PATCH 163/287] [2.2.x] Fixed #31018 -- Removed django-nonrel in NoSQL databases FAQ. Backport of a36f7ed6ac5b2529db7e13fd3754290484928b18 from master --- docs/faq/models.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/faq/models.txt b/docs/faq/models.txt index c6a7f28e239d..2aa211e17734 100644 --- a/docs/faq/models.txt +++ b/docs/faq/models.txt @@ -68,13 +68,12 @@ Does Django support NoSQL databases? ==================================== NoSQL databases are not officially supported by Django itself. There are, -however, a number of side project and forks which allow NoSQL functionality in -Django, like `Django non-rel`_. +however, a number of side projects and forks which allow NoSQL functionality in +Django. -You can also take a look on `the wiki page`_ which discusses some alternatives. +You can take a look on `the wiki page`_ which discusses some projects. -.. _`Django non-rel`: http://django-nonrel.org/ -.. _`the wiki page`: https://code.djangoproject.com/wiki/NoSqlSupport +.. _the wiki page: https://code.djangoproject.com/wiki/NoSqlSupport How do I add database-specific options to my CREATE TABLE statements, such as specifying MyISAM as the table type? ================================================================================================================== From 57f5a7e36b514e078fc92f9014771f94b945acb4 Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Tue, 26 Nov 2019 13:07:55 +0100 Subject: [PATCH 164/287] [2.2.x] Refs #31029 -- Added note about :rfc: role in writing documentation docs. Backport of 42df1b178b22d499986516b5d3d6bb70c044901a from master --- docs/internals/contributing/writing-documentation.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/internals/contributing/writing-documentation.txt b/docs/internals/contributing/writing-documentation.txt index b6732746fa61..76a5b31c2120 100644 --- a/docs/internals/contributing/writing-documentation.txt +++ b/docs/internals/contributing/writing-documentation.txt @@ -232,6 +232,10 @@ documentation: Five ^^^^ +* Use :rst:role:`:rfc:` to reference RFC and and try to link to the + relevant section if possible. For example, use ``:rfc:`2324#section-2.3.2``` + or ``:rfc:`Custom link text <2324#section-2.3.2>```. + Django-specific markup ====================== From 019a1b9274c645db82593bc5898a279d134b464b Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sat, 23 Nov 2019 13:42:57 +0100 Subject: [PATCH 165/287] [2.2.x] Fixed #31029 -- Used more specific links to RFCs. Backport of ff1b19da6761217ed1b14cc7e94c6438903565d8 from master --- docs/ref/csrf.txt | 9 +++++---- docs/ref/models/instances.txt | 4 ++-- docs/ref/request-response.txt | 16 ++++++++-------- docs/ref/settings.txt | 6 +++--- docs/ref/templates/builtins.txt | 3 ++- docs/ref/utils.txt | 4 ++-- docs/ref/validators.txt | 3 ++- 7 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/ref/csrf.txt b/docs/ref/csrf.txt index e961d76a0299..1362f8f20682 100644 --- a/docs/ref/csrf.txt +++ b/docs/ref/csrf.txt @@ -298,10 +298,11 @@ This ensures that only forms that have originated from trusted domains can be used to POST data back. It deliberately ignores GET requests (and other requests that are defined as -'safe' by :rfc:`7231`). These requests ought never to have any potentially -dangerous side effects , and so a CSRF attack with a GET request ought to be -harmless. :rfc:`7231` defines POST, PUT, and DELETE as 'unsafe', and all other -methods are also assumed to be unsafe, for maximum protection. +'safe' by :rfc:`7231#section-4.2.1`). These requests ought never to have any +potentially dangerous side effects, and so a CSRF attack with a GET request +ought to be harmless. :rfc:`7231#section-4.2.1` defines POST, PUT, and DELETE +as 'unsafe', and all other methods are also assumed to be unsafe, for maximum +protection. The CSRF protection cannot protect against man-in-the-middle attacks, so use :ref:`HTTPS ` with diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index edc4459f1c98..1b518ebf3026 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -755,8 +755,8 @@ in ``get_absolute_url()`` and have all your other code call that one place. .. note:: The string you return from ``get_absolute_url()`` **must** contain only - ASCII characters (required by the URI specification, :rfc:`2396`) and be - URL-encoded, if necessary. + ASCII characters (required by the URI specification, :rfc:`2396#section-2`) + and be URL-encoded, if necessary. Code and templates calling ``get_absolute_url()`` should be able to use the result directly without any further processing. You may wish to use the diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index 288fdae4f7ca..08eaf62ca4ca 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -809,9 +809,9 @@ Methods JavaScript from having access to the cookie. HttpOnly_ is a flag included in a Set-Cookie HTTP response header. It's - part of the :rfc:`6265` standard for cookies and can be a useful way to - mitigate the risk of a client-side script accessing the protected cookie - data. + part of the :rfc:`RFC 6265 <6265#section-4.1.2.6>` standard for cookies + and can be a useful way to mitigate the risk of a client-side script + accessing the protected cookie data. * Use ``samesite='Strict'`` or ``samesite='Lax'`` to tell the browser not to send this cookie when performing a cross-origin request. `SameSite`_ isn't supported by all browsers, so it's not a replacement for Django's @@ -826,11 +826,11 @@ Methods .. warning:: - :rfc:`6265` states that user agents should support cookies of at least - 4096 bytes. For many browsers this is also the maximum size. Django - will not raise an exception if there's an attempt to store a cookie of - more than 4096 bytes, but many browsers will not set the cookie - correctly. + :rfc:`RFC 6265 <6265#section-6.1>` states that user agents should + support cookies of at least 4096 bytes. For many browsers this is also + the maximum size. Django will not raise an exception if there's an + attempt 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=False, samesite=None) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 2f7839fe36a6..daca1bea5690 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -2701,7 +2701,7 @@ preference to the ``Host`` header. This should only be enabled if a proxy which sets this header is in use. This setting takes priority over :setting:`USE_X_FORWARDED_PORT`. Per -:rfc:`7239#page-7`, the ``X-Forwarded-Host`` header can include the port +:rfc:`7239#section-5.3`, the ``X-Forwarded-Host`` header can include the port number, in which case you shouldn't use :setting:`USE_X_FORWARDED_PORT`. .. setting:: USE_X_FORWARDED_PORT @@ -3020,8 +3020,8 @@ Whether to use ``HttpOnly`` flag on the session cookie. If this is set to cookie. HttpOnly_ is a flag included in a Set-Cookie HTTP response header. It's part of -the :rfc:`6265` standard for cookies and can be a useful way to mitigate the -risk of a client-side script accessing the protected cookie data. +the :rfc:`6265#section-4.1.2.6` standard for cookies and can be a useful way to +mitigate the risk of a client-side script accessing the protected cookie data. This makes it less trivial for an attacker to escalate a cross-site scripting vulnerability into full hijacking of a user's session. There aren't many good diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index f33b52e5d44c..8d3f353468d7 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1417,7 +1417,8 @@ Format character Description Example output the "c" formatter will not add timezone offset if value is a naive datetime (see :class:`datetime.tzinfo`). -``r`` :rfc:`5322` formatted date. ``'Thu, 21 Dec 2000 16:01:07 +0200'`` +``r`` :rfc:`RFC 5322 <5322#section-3.3>` ``'Thu, 21 Dec 2000 16:01:07 +0200'`` + formatted date. ``U`` Seconds since the Unix Epoch (January 1 1970 00:00:00 UTC). ================ ======================================== ===================== diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt index cb39d0d9d585..390f167ce2a6 100644 --- a/docs/ref/utils.txt +++ b/docs/ref/utils.txt @@ -720,8 +720,8 @@ escaping HTML. .. function:: http_date(epoch_seconds=None) - Formats the time to match the :rfc:`1123` date format as specified by HTTP - :rfc:`7231#section-7.1.1.1`. + Formats the time to match the :rfc:`1123#section-5.2.14` date format as + specified by HTTP :rfc:`7231#section-7.1.1.1`. Accepts a floating point number expressed in seconds since the epoch in UTC--such as that outputted by ``time.time()``. If set to ``None``, diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index b6a233014d54..75d1394f0d77 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -154,7 +154,8 @@ to, or in lieu of custom ``field.clean()`` methods. an error code of ``'invalid'`` if it doesn't. Loopback addresses and reserved IP spaces are considered valid. Literal - IPv6 addresses (:rfc:`2732`) and unicode domains are both supported. + IPv6 addresses (:rfc:`3986#section-3.2.2`) and unicode domains are both + supported. In addition to the optional arguments of its parent :class:`RegexValidator` class, ``URLValidator`` accepts an extra optional attribute: From 9a17ae50c61a3a0ea6c552ce4e3eab27f796d094 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 28 Nov 2019 11:46:08 +0100 Subject: [PATCH 166/287] [2.2.x] Fixed #31021 -- Fixed proxy model permissions data migration crash with a multiple databases setup. Regression in 98296f86b340c8c9c968375d59f1d3a3479e60c2. Backport of e8fcdaad5c428878d0a5d6ba820d957013f75595 from master --- .../0011_update_proxy_permissions.py | 10 +-- docs/releases/2.2.8.txt | 4 ++ tests/auth_tests/test_migrations.py | 72 ++++++++++++++----- 3 files changed, 66 insertions(+), 20 deletions(-) diff --git a/django/contrib/auth/migrations/0011_update_proxy_permissions.py b/django/contrib/auth/migrations/0011_update_proxy_permissions.py index c3f617f43886..7409ea12545e 100644 --- a/django/contrib/auth/migrations/0011_update_proxy_permissions.py +++ b/django/contrib/auth/migrations/0011_update_proxy_permissions.py @@ -23,6 +23,7 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False): style = color_style() Permission = apps.get_model('auth', 'Permission') ContentType = apps.get_model('contenttypes', 'ContentType') + alias = schema_editor.connection.alias for Model in apps.get_models(): opts = Model._meta if not opts.proxy: @@ -34,13 +35,14 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False): permissions_query = Q(codename__in=proxy_default_permissions_codenames) for codename, name in opts.permissions: permissions_query = permissions_query | Q(codename=codename, name=name) - concrete_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=True) - proxy_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=False) + content_type_manager = ContentType.objects.db_manager(alias) + concrete_content_type = content_type_manager.get_for_model(Model, for_concrete_model=True) + proxy_content_type = content_type_manager.get_for_model(Model, for_concrete_model=False) old_content_type = proxy_content_type if reverse else concrete_content_type new_content_type = concrete_content_type if reverse else proxy_content_type try: - with transaction.atomic(): - Permission.objects.filter( + with transaction.atomic(using=alias): + Permission.objects.using(alias).filter( permissions_query, content_type=old_content_type, ).update(content_type=new_content_type) diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index e9bd1ed1cb68..4d8f9869c503 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -13,3 +13,7 @@ Bugfixes * Fixed a data loss possibility in the admin changelist view when a custom :ref:`formset's prefix ` contains regular expression special characters, e.g. `'$'` (:ticket:`31031`). + +* Fixed a regression in Django 2.2.1 that caused a crash when migrating + permissions for proxy models with a multiple database setup if the + ``default`` entry was empty (:ticket:`31021`). diff --git a/tests/auth_tests/test_migrations.py b/tests/auth_tests/test_migrations.py index 98c5b5964a7e..a91b9a914636 100644 --- a/tests/auth_tests/test_migrations.py +++ b/tests/auth_tests/test_migrations.py @@ -3,7 +3,8 @@ from django.apps import apps from django.contrib.auth.models import Permission, User from django.contrib.contenttypes.models import ContentType -from django.test import TestCase +from django.db import connection, connections +from django.test import TransactionTestCase from django.test.utils import captured_stdout from .models import Proxy, UserProxy @@ -11,7 +12,7 @@ update_proxy_permissions = import_module('django.contrib.auth.migrations.0011_update_proxy_permissions') -class ProxyModelWithDifferentAppLabelTests(TestCase): +class ProxyModelWithDifferentAppLabelTests(TransactionTestCase): available_apps = [ 'auth_tests', 'django.contrib.auth', @@ -41,7 +42,8 @@ def test_proxy_model_permissions_contenttype(self): proxy_model_content_type = ContentType.objects.get_for_model(UserProxy, for_concrete_model=False) self.assertEqual(self.default_permission.content_type, self.concrete_content_type) self.assertEqual(self.custom_permission.content_type, self.concrete_content_type) - update_proxy_permissions.update_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) self.default_permission.refresh_from_db() self.assertEqual(self.default_permission.content_type, proxy_model_content_type) self.custom_permission.refresh_from_db() @@ -54,7 +56,8 @@ def test_user_has_now_proxy_model_permissions(self): for permission in [self.default_permission, self.custom_permission]: self.assertTrue(user.has_perm('auth.' + permission.codename)) self.assertFalse(user.has_perm('auth_tests.' + permission.codename)) - update_proxy_permissions.update_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) # Reload user to purge the _perm_cache. user = User._default_manager.get(pk=user.pk) for permission in [self.default_permission, self.custom_permission]: @@ -62,8 +65,9 @@ def test_user_has_now_proxy_model_permissions(self): self.assertTrue(user.has_perm('auth_tests.' + permission.codename)) def test_migrate_backwards(self): - update_proxy_permissions.update_proxy_model_permissions(apps, None) - update_proxy_permissions.revert_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) + update_proxy_permissions.revert_proxy_model_permissions(apps, editor) self.default_permission.refresh_from_db() self.assertEqual(self.default_permission.content_type, self.concrete_content_type) self.custom_permission.refresh_from_db() @@ -76,8 +80,9 @@ def test_user_keeps_same_permissions_after_migrating_backward(self): for permission in [self.default_permission, self.custom_permission]: self.assertTrue(user.has_perm('auth.' + permission.codename)) self.assertFalse(user.has_perm('auth_tests.' + permission.codename)) - update_proxy_permissions.update_proxy_model_permissions(apps, None) - update_proxy_permissions.revert_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) + update_proxy_permissions.revert_proxy_model_permissions(apps, editor) # Reload user to purge the _perm_cache. user = User._default_manager.get(pk=user.pk) for permission in [self.default_permission, self.custom_permission]: @@ -85,7 +90,7 @@ def test_user_keeps_same_permissions_after_migrating_backward(self): self.assertFalse(user.has_perm('auth_tests.' + permission.codename)) -class ProxyModelWithSameAppLabelTests(TestCase): +class ProxyModelWithSameAppLabelTests(TransactionTestCase): available_apps = [ 'auth_tests', 'django.contrib.auth', @@ -115,7 +120,8 @@ def test_proxy_model_permissions_contenttype(self): proxy_model_content_type = ContentType.objects.get_for_model(Proxy, for_concrete_model=False) self.assertEqual(self.default_permission.content_type, self.concrete_content_type) self.assertEqual(self.custom_permission.content_type, self.concrete_content_type) - update_proxy_permissions.update_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) self.default_permission.refresh_from_db() self.custom_permission.refresh_from_db() self.assertEqual(self.default_permission.content_type, proxy_model_content_type) @@ -127,15 +133,17 @@ def test_user_still_has_proxy_model_permissions(self): user.user_permissions.add(self.custom_permission) for permission in [self.default_permission, self.custom_permission]: self.assertTrue(user.has_perm('auth_tests.' + permission.codename)) - update_proxy_permissions.update_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) # Reload user to purge the _perm_cache. user = User._default_manager.get(pk=user.pk) for permission in [self.default_permission, self.custom_permission]: self.assertTrue(user.has_perm('auth_tests.' + permission.codename)) def test_migrate_backwards(self): - update_proxy_permissions.update_proxy_model_permissions(apps, None) - update_proxy_permissions.revert_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) + update_proxy_permissions.revert_proxy_model_permissions(apps, editor) self.default_permission.refresh_from_db() self.assertEqual(self.default_permission.content_type, self.concrete_content_type) self.custom_permission.refresh_from_db() @@ -147,8 +155,9 @@ def test_user_keeps_same_permissions_after_migrating_backward(self): user.user_permissions.add(self.custom_permission) for permission in [self.default_permission, self.custom_permission]: self.assertTrue(user.has_perm('auth_tests.' + permission.codename)) - update_proxy_permissions.update_proxy_model_permissions(apps, None) - update_proxy_permissions.revert_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) + update_proxy_permissions.revert_proxy_model_permissions(apps, editor) # Reload user to purge the _perm_cache. user = User._default_manager.get(pk=user.pk) for permission in [self.default_permission, self.custom_permission]: @@ -175,5 +184,36 @@ def test_migrate_with_existing_target_permission(self): name='May display proxys information', ) with captured_stdout() as stdout: - update_proxy_permissions.update_proxy_model_permissions(apps, None) + with connection.schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) self.assertIn('A problem arose migrating proxy model permissions', stdout.getvalue()) + + +class MultiDBProxyModelAppLabelTests(TransactionTestCase): + databases = {'default', 'other'} + available_apps = [ + 'auth_tests', + 'django.contrib.auth', + 'django.contrib.contenttypes', + ] + + def setUp(self): + ContentType.objects.all().delete() + Permission.objects.using('other').delete() + concrete_content_type = ContentType.objects.db_manager( + 'other' + ).get_for_model(Proxy) + self.permission = Permission.objects.using('other').create( + content_type=concrete_content_type, + codename='add_proxy', + name='Can add proxy', + ) + + def test_migrate_other_database(self): + proxy_model_content_type = ContentType.objects.db_manager( + 'other' + ).get_for_model(Proxy, for_concrete_model=False) + with connections['other'].schema_editor() as editor: + update_proxy_permissions.update_proxy_model_permissions(apps, editor) + self.permission.refresh_from_db() + self.assertEqual(self.permission.content_type, proxy_model_content_type) From 6cf3b6f5cf0cc3b11e86e511ec5201999913286f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Dec 2019 07:57:19 +0100 Subject: [PATCH 167/287] [2.2.x] Fixed #30953 -- Made select_for_update() lock queryset's model when using "self" with multi-table inheritance. Thanks Abhijeet Viswa for the report and initial patch. Backport of 0107e3d1058f653f66032f7fd3a0bd61e96bf782 from master --- django/db/models/sql/compiler.py | 69 +++++++++++++++++++++++-------- docs/ref/models/querysets.txt | 8 ++++ docs/releases/2.2.8.txt | 6 +++ tests/select_for_update/models.py | 9 ++++ tests/select_for_update/tests.py | 62 ++++++++++++++++++++++++++- 5 files changed, 135 insertions(+), 19 deletions(-) diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index f0daffe5c590..9e709d0f6e5d 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -947,6 +947,21 @@ def get_select_for_update_of_arguments(self): Return a quoted list of arguments for the SELECT FOR UPDATE OF part of the query. """ + def _get_parent_klass_info(klass_info): + return ( + { + 'model': parent_model, + 'field': parent_link, + 'reverse': False, + 'select_fields': [ + select_index + for select_index in klass_info['select_fields'] + if self.select[select_index][0].target.model == parent_model + ], + } + for parent_model, parent_link in klass_info['model']._meta.parents.items() + ) + def _get_field_choices(): """Yield all allowed field paths in breadth-first search order.""" queue = collections.deque([(None, self.klass_info)]) @@ -961,6 +976,10 @@ def _get_field_choices(): field = field.remote_field path = parent_path + [field.name] yield LOOKUP_SEP.join(path) + queue.extend( + (path, klass_info) + for klass_info in _get_parent_klass_info(klass_info) + ) queue.extend( (path, klass_info) for klass_info in klass_info.get('related_klass_infos', []) @@ -968,28 +987,42 @@ def _get_field_choices(): result = [] invalid_names = [] for name in self.query.select_for_update_of: - parts = [] if name == 'self' else name.split(LOOKUP_SEP) klass_info = self.klass_info - for part in parts: - for related_klass_info in klass_info.get('related_klass_infos', []): - field = related_klass_info['field'] - if related_klass_info['reverse']: - field = field.remote_field - if field.name == part: - klass_info = related_klass_info + if name == 'self': + # Find the first selected column from a base model. If it + # doesn't exist, don't lock a base model. + for select_index in klass_info['select_fields']: + if self.select[select_index][0].target.model == klass_info['model']: + col = self.select[select_index][0] break else: - klass_info = None - break - if klass_info is None: - invalid_names.append(name) - continue - select_index = klass_info['select_fields'][0] - col = self.select[select_index][0] - if self.connection.features.select_for_update_of_column: - result.append(self.compile(col)[0]) + col = None else: - result.append(self.quote_name_unless_alias(col.alias)) + for part in name.split(LOOKUP_SEP): + klass_infos = ( + *klass_info.get('related_klass_infos', []), + *_get_parent_klass_info(klass_info), + ) + for related_klass_info in klass_infos: + field = related_klass_info['field'] + if related_klass_info['reverse']: + field = field.remote_field + if field.name == part: + klass_info = related_klass_info + break + else: + klass_info = None + break + if klass_info is None: + invalid_names.append(name) + continue + select_index = klass_info['select_fields'][0] + col = self.select[select_index][0] + if col is not None: + if self.connection.features.select_for_update_of_column: + result.append(self.compile(col)[0]) + else: + result.append(self.quote_name_unless_alias(col.alias)) if invalid_names: raise FieldError( 'Invalid field name(s) given in select_for_update(of=(...)): %s. ' diff --git a/docs/ref/models/querysets.txt b/docs/ref/models/querysets.txt index 3ccd5958fd03..f23d4bc59817 100644 --- a/docs/ref/models/querysets.txt +++ b/docs/ref/models/querysets.txt @@ -1696,6 +1696,14 @@ specify the related objects you want to lock in ``select_for_update(of=(...))`` using the same fields syntax as :meth:`select_related`. Use the value ``'self'`` to refer to the queryset's model. +.. admonition:: Lock parents models in ``select_for_update(of=(...))`` + + If you want to lock parents models when using :ref:`multi-table inheritance + `, you must specify parent link fields (by default + ``_ptr``) in the ``of`` argument. For example:: + + Restaurant.objects.select_for_update(of=('self', 'place_ptr')) + You can't use ``select_for_update()`` on nullable relations:: >>> Person.objects.select_related('hometown').select_for_update() diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 4d8f9869c503..3c5eb5c754d6 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -17,3 +17,9 @@ Bugfixes * Fixed a regression in Django 2.2.1 that caused a crash when migrating permissions for proxy models with a multiple database setup if the ``default`` entry was empty (:ticket:`31021`). + +* Fixed a data loss possibility in the + :meth:`~django.db.models.query.QuerySet.select_for_update()`. When using + ``'self'`` in the ``of`` argument with :ref:`multi-table inheritance + `, a parent model was locked instead of the + queryset's model (:ticket:`30953`). diff --git a/tests/select_for_update/models.py b/tests/select_for_update/models.py index b8154af3dfe4..c84f9ad6b29a 100644 --- a/tests/select_for_update/models.py +++ b/tests/select_for_update/models.py @@ -5,11 +5,20 @@ class Country(models.Model): name = models.CharField(max_length=30) +class EUCountry(Country): + join_date = models.DateField() + + class City(models.Model): name = models.CharField(max_length=30) country = models.ForeignKey(Country, models.CASCADE) +class EUCity(models.Model): + name = models.CharField(max_length=30) + country = models.ForeignKey(EUCountry, models.CASCADE) + + class Person(models.Model): name = models.CharField(max_length=30) born = models.ForeignKey(City, models.CASCADE, related_name='+') diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index f359dc265069..1f1b20e47f6c 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -15,7 +15,7 @@ ) from django.test.utils import CaptureQueriesContext -from .models import City, Country, Person, PersonProfile +from .models import City, Country, EUCity, EUCountry, Person, PersonProfile class SelectForUpdateTests(TransactionTestCase): @@ -119,6 +119,47 @@ def test_for_update_sql_generated_of(self): expected = [connection.ops.quote_name(value) for value in expected] self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') + def test_for_update_sql_model_inheritance_generated_of(self): + with transaction.atomic(), CaptureQueriesContext(connection) as ctx: + list(EUCountry.objects.select_for_update(of=('self',))) + if connection.features.select_for_update_of_column: + expected = ['select_for_update_eucountry"."country_ptr_id'] + else: + expected = ['select_for_update_eucountry'] + expected = [connection.ops.quote_name(value) for value in expected] + self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + + @skipUnlessDBFeature('has_select_for_update_of') + def test_for_update_sql_model_inheritance_ptr_generated_of(self): + with transaction.atomic(), CaptureQueriesContext(connection) as ctx: + list(EUCountry.objects.select_for_update(of=('self', 'country_ptr',))) + if connection.features.select_for_update_of_column: + expected = [ + 'select_for_update_eucountry"."country_ptr_id', + 'select_for_update_country"."id', + ] + else: + expected = ['select_for_update_eucountry', 'select_for_update_country'] + expected = [connection.ops.quote_name(value) for value in expected] + self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + + @skipUnlessDBFeature('has_select_for_update_of') + def test_for_update_sql_model_inheritance_nested_ptr_generated_of(self): + with transaction.atomic(), CaptureQueriesContext(connection) as ctx: + list(EUCity.objects.select_related('country').select_for_update( + of=('self', 'country__country_ptr',), + )) + if connection.features.select_for_update_of_column: + expected = [ + 'select_for_update_eucity"."id', + 'select_for_update_country"."id', + ] + else: + expected = ['select_for_update_eucity', 'select_for_update_country'] + expected = [connection.ops.quote_name(value) for value in expected] + self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') def test_for_update_of_followed_by_values(self): with transaction.atomic(): @@ -257,6 +298,25 @@ def test_related_but_unselected_of_argument_raises_error(self): 'born', 'profile', ).exclude(profile=None).select_for_update(of=(name,)).get() + @skipUnlessDBFeature('has_select_for_update', 'has_select_for_update_of') + def test_model_inheritance_of_argument_raises_error_ptr_in_choices(self): + msg = ( + 'Invalid field name(s) given in select_for_update(of=(...)): ' + 'name. Only relational fields followed in the query are allowed. ' + 'Choices are: self, %s.' + ) + with self.assertRaisesMessage( + FieldError, + msg % 'country, country__country_ptr', + ): + with transaction.atomic(): + EUCity.objects.select_related( + 'country', + ).select_for_update(of=('name',)).get() + with self.assertRaisesMessage(FieldError, msg % 'country_ptr'): + with transaction.atomic(): + EUCountry.objects.select_for_update(of=('name',)).get() + @skipUnlessDBFeature('has_select_for_update', 'has_select_for_update_of') def test_reverse_one_to_one_of_arguments(self): """ From 70311e1d00ef5b6bbbc8961eac81b5c814396a43 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 2 Dec 2019 08:10:39 +0100 Subject: [PATCH 168/287] [2.2.x] Refs #30953 -- Added 2.1.15 release note for 0107e3d1058f653f66032f7fd3a0bd61e96bf782. Backport of 39e39d0ac1b720e7460ec8ccf45926c78edb2047 from master --- docs/releases/2.1.15.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/releases/2.1.15.txt b/docs/releases/2.1.15.txt index 9cd6457985dd..8d13bb281f10 100644 --- a/docs/releases/2.1.15.txt +++ b/docs/releases/2.1.15.txt @@ -9,4 +9,8 @@ Django 2.1.15 fixes a data loss bug in 2.1.14. Bugfixes ======== -* ... +* Fixed a data loss possibility in the + :meth:`~django.db.models.query.QuerySet.select_for_update()`. When using + ``'self'`` in the ``of`` argument with :ref:`multi-table inheritance + `, a parent model was locked instead of the + queryset's model (:ticket:`30953`). From 36f580a17f0b3cb087deadf3b65eea024f479c21 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 25 Nov 2019 12:01:49 +0100 Subject: [PATCH 169/287] Fixed CVE-2019-19118 -- Required edit permissions on parent model for editable inlines in admin. Thank you to Shen Ying for reporting this issue. --- django/contrib/admin/options.py | 21 +++- .../templates/admin/edit_inline/stacked.html | 2 +- .../templates/admin/edit_inline/tabular.html | 4 +- docs/releases/2.1.15.txt | 41 ++++++- docs/releases/2.2.8.txt | 43 ++++++- tests/admin_inlines/tests.py | 112 ++++++++++++++++++ tests/admin_views/admin.py | 9 -- tests/admin_views/tests.py | 68 +---------- tests/admin_views/urls.py | 1 - tests/auth_tests/test_views.py | 2 +- 10 files changed, 216 insertions(+), 87 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 520e3d023e28..57c3d4f4d675 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1471,13 +1471,20 @@ def render_delete_form(self, request, context): ) def get_inline_formsets(self, request, formsets, inline_instances, obj=None): + # Edit permissions on parent model are required for editable inlines. + can_edit_parent = self.has_change_permission(request, obj) if obj else self.has_add_permission(request) inline_admin_formsets = [] for inline, formset in zip(inline_instances, formsets): fieldsets = list(inline.get_fieldsets(request, obj)) readonly = list(inline.get_readonly_fields(request, obj)) - has_add_permission = inline._has_add_permission(request, obj) - has_change_permission = inline.has_change_permission(request, obj) - has_delete_permission = inline.has_delete_permission(request, obj) + if can_edit_parent: + has_add_permission = inline._has_add_permission(request, obj) + has_change_permission = inline.has_change_permission(request, obj) + has_delete_permission = inline.has_delete_permission(request, obj) + else: + # Disable all edit-permissions, and overide formset settings. + has_add_permission = has_change_permission = has_delete_permission = False + formset.extra = formset.max_num = 0 has_view_permission = inline.has_view_permission(request, obj) prepopulated = dict(inline.get_prepopulated_fields(request, obj)) inline_admin_formset = helpers.InlineAdminFormSet( @@ -1542,8 +1549,12 @@ def _changeform_view(self, request, object_id, form_url, extra_context): else: obj = self.get_object(request, unquote(object_id), to_field) - if not self.has_view_or_change_permission(request, obj): - raise PermissionDenied + if request.method == 'POST': + if not self.has_change_permission(request, obj): + raise PermissionDenied + else: + if not self.has_view_or_change_permission(request, obj): + raise PermissionDenied if obj is None: return self._get_obj_does_not_exist_redirect(request, opts, object_id) diff --git a/django/contrib/admin/templates/admin/edit_inline/stacked.html b/django/contrib/admin/templates/admin/edit_inline/stacked.html index 8af4d54791d6..d9c27b157807 100644 --- a/django/contrib/admin/templates/admin/edit_inline/stacked.html +++ b/django/contrib/admin/templates/admin/edit_inline/stacked.html @@ -12,7 +12,7 @@

{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

{{ inline_admin_formset.opts.verbose_name|capfirst }}: {% if inline_admin_form.original %}{{ inline_admin_form.original }}{% if inline_admin_form.model_admin.show_change_link and inline_admin_form.model_admin.has_registered_model %} {% if inline_admin_formset.has_change_permission %}{% trans "Change" %}{% else %}{% trans "View" %}{% endif %}{% endif %} {% else %}#{{ forloop.counter }}{% endif %} {% if inline_admin_form.show_url %}{% trans "View on site" %}{% endif %} - {% if inline_admin_formset.formset.can_delete and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission and inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }} {{ inline_admin_form.deletion_field.label_tag }}{% endif %}

{% if inline_admin_form.form.non_field_errors %}{{ inline_admin_form.form.non_field_errors }}{% endif %} {% for fieldset in inline_admin_form %} diff --git a/django/contrib/admin/templates/admin/edit_inline/tabular.html b/django/contrib/admin/templates/admin/edit_inline/tabular.html index 531d7b6a215c..d643955dcf80 100644 --- a/django/contrib/admin/templates/admin/edit_inline/tabular.html +++ b/django/contrib/admin/templates/admin/edit_inline/tabular.html @@ -17,7 +17,7 @@

{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

{% endif %} {% endfor %} - {% if inline_admin_formset.formset.can_delete %}{% trans "Delete?" %}{% endif %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %}{% trans "Delete?" %}{% endif %} @@ -63,7 +63,7 @@

{{ inline_admin_formset.opts.verbose_name_plural|capfirst }}

{% endfor %} {% endfor %} {% endfor %} - {% if inline_admin_formset.formset.can_delete %} + {% if inline_admin_formset.formset.can_delete and inline_admin_formset.has_delete_permission %} {% if inline_admin_form.original %}{{ inline_admin_form.deletion_field.field }}{% endif %} {% endif %} diff --git a/docs/releases/2.1.15.txt b/docs/releases/2.1.15.txt index 8d13bb281f10..29764f986ee9 100644 --- a/docs/releases/2.1.15.txt +++ b/docs/releases/2.1.15.txt @@ -4,7 +4,46 @@ Django 2.1.15 release notes *Expected December 2, 2019* -Django 2.1.15 fixes a data loss bug in 2.1.14. +Django 2.1.15 fixes a security issue and a data loss bug in 2.1.14. + +CVE-2019-19118: Privilege escalation in the Django admin. +========================================================= + +Since Django 2.1, a Django model admin displaying a parent model with related +model inlines, where the user has view-only permissions to a parent model but +edit permissions to the inline model, would display a read-only view of the +parent model but editable forms for the inline. + +Submitting these forms would not allow direct edits to the parent model, but +would trigger the parent model's ``save()`` method, and cause pre and post-save +signal handlers to be invoked. This is a privilege escalation as a user who +lacks permission to edit a model should not be able to trigger its save-related +signals. + +To resolve this issue, the permission handling code of the Django admin +interface has been changed. Now, if a user has only the "view" permission for a +parent model, the entire displayed form will not be editable, even if the user +has permission to edit models included in inlines. + +This is a backwards-incompatible change, and the Django security team is aware +that some users of Django were depending on the ability to allow editing of +inlines in the admin form of an otherwise view-only parent model. + +Given the complexity of the Django admin, and in-particular the permissions +related checks, it is the view of the Django security team that this change was +necessary: that it is not currently feasible to maintain the existing behavior +whilst escaping the potential privilege escalation in a way that would avoid a +recurrence of similar issues in the future, and that would be compatible with +Django's *safe by default* philosophy. + +For the time being, developers whose applications are affected by this change +should replace the use of inlines in read-only parents with custom forms and +views that explicitly implement the desired functionality. In the longer term, +adding a documented, supported, and properly-tested mechanism for +partially-editable multi-model forms to the admin interface may occur in Django +itself. + +Thank you to Shen Ying for reporting this issue. Bugfixes ======== diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 3c5eb5c754d6..2c4a6f9ec1fc 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -4,8 +4,47 @@ Django 2.2.8 release notes *Expected December 2, 2019* -Django 2.2.8 fixes several bugs in 2.2.7 and adds compatibility with Python -3.8. +Django 2.2.8 fixes a security issue, several bugs in 2.2.7, and adds +compatibility with Python 3.8. + +CVE-2019-19118: Privilege escalation in the Django admin. +========================================================= + +Since Django 2.1, a Django model admin displaying a parent model with related +model inlines, where the user has view-only permissions to a parent model but +edit permissions to the inline model, would display a read-only view of the +parent model but editable forms for the inline. + +Submitting these forms would not allow direct edits to the parent model, but +would trigger the parent model's ``save()`` method, and cause pre and post-save +signal handlers to be invoked. This is a privilege escalation as a user who +lacks permission to edit a model should not be able to trigger its save-related +signals. + +To resolve this issue, the permission handling code of the Django admin +interface has been changed. Now, if a user has only the "view" permission for a +parent model, the entire displayed form will not be editable, even if the user +has permission to edit models included in inlines. + +This is a backwards-incompatible change, and the Django security team is aware +that some users of Django were depending on the ability to allow editing of +inlines in the admin form of an otherwise view-only parent model. + +Given the complexity of the Django admin, and in-particular the permissions +related checks, it is the view of the Django security team that this change was +necessary: that it is not currently feasible to maintain the existing behavior +whilst escaping the potential privilege escalation in a way that would avoid a +recurrence of similar issues in the future, and that would be compatible with +Django's *safe by default* philosophy. + +For the time being, developers whose applications are affected by this change +should replace the use of inlines in read-only parents with custom forms and +views that explicitly implement the desired functionality. In the longer term, +adding a documented, supported, and properly-tested mechanism for +partially-editable multi-model forms to the admin interface may occur in Django +itself. + +Thank you to Shen Ying for reporting this issue. Bugfixes ======== diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py index 09fcd0f3001a..dd40ff675cfd 100644 --- a/tests/admin_inlines/tests.py +++ b/tests/admin_inlines/tests.py @@ -1,3 +1,5 @@ +from selenium.common.exceptions import NoSuchElementException + from django.contrib.admin import ModelAdmin, TabularInline from django.contrib.admin.helpers import InlineAdminForm from django.contrib.admin.tests import AdminSeleniumTestCase @@ -852,6 +854,98 @@ def test_inline_change_fk_all_perms(self): ) +@override_settings(ROOT_URLCONF='admin_inlines.urls') +class TestReadOnlyChangeViewInlinePermissions(TestCase): + + @classmethod + def setUpTestData(cls): + cls.user = User.objects.create_user('testing', password='password', is_staff=True) + cls.user.user_permissions.add( + Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll)) + ) + cls.user.user_permissions.add( + *Permission.objects.filter( + codename__endswith="question", content_type=ContentType.objects.get_for_model(Question) + ).values_list('pk', flat=True) + ) + + cls.poll = Poll.objects.create(name="Survey") + cls.add_url = reverse('admin:admin_inlines_poll_add') + cls.change_url = reverse('admin:admin_inlines_poll_change', args=(cls.poll.id,)) + + def setUp(self): + self.client.force_login(self.user) + + def test_add_url_not_allowed(self): + response = self.client.get(self.add_url) + self.assertEqual(response.status_code, 403) + + response = self.client.post(self.add_url, {}) + self.assertEqual(response.status_code, 403) + + def test_post_to_change_url_not_allowed(self): + response = self.client.post(self.change_url, {}) + self.assertEqual(response.status_code, 403) + + def test_get_to_change_url_is_allowed(self): + response = self.client.get(self.change_url) + self.assertEqual(response.status_code, 200) + + def test_main_model_is_rendered_as_read_only(self): + response = self.client.get(self.change_url) + self.assertContains( + response, + '
%s
' % self.poll.name, + html=True + ) + input = '' + self.assertNotContains( + response, + input % self.poll.name, + html=True + ) + + def test_inlines_are_rendered_as_read_only(self): + question = Question.objects.create(text="How will this be rendered?", poll=self.poll) + response = self.client.get(self.change_url) + self.assertContains( + response, + '

%s

' % question.text, + html=True + ) + self.assertNotContains(response, 'id="id_question_set-0-text"') + self.assertNotContains(response, 'id="id_related_objs-0-DELETE"') + + def test_submit_line_shows_only_close_button(self): + response = self.client.get(self.change_url) + self.assertContains( + response, + 'Close', + html=True + ) + delete_link = '' # noqa + self.assertNotContains( + response, + delete_link % self.poll.id, + html=True + ) + self.assertNotContains(response, '') + self.assertNotContains(response, '') + + def test_inline_delete_buttons_are_not_shown(self): + Question.objects.create(text="How will this be rendered?", poll=self.poll) + response = self.client.get(self.change_url) + self.assertNotContains( + response, + '', + html=True + ) + + def test_extra_inlines_are_not_shown(self): + response = self.client.get(self.change_url) + self.assertNotContains(response, 'id="id_question_set-0-text"') + + @override_settings(ROOT_URLCONF='admin_inlines.urls') class SeleniumTests(AdminSeleniumTestCase): @@ -955,6 +1049,24 @@ def test_add_inlines(self): self.assertEqual(ProfileCollection.objects.all().count(), 1) self.assertEqual(Profile.objects.all().count(), 3) + def test_add_inline_link_absent_for_view_only_parent_model(self): + user = User.objects.create_user('testing', password='password', is_staff=True) + user.user_permissions.add( + Permission.objects.get(codename='view_poll', content_type=ContentType.objects.get_for_model(Poll)) + ) + user.user_permissions.add( + *Permission.objects.filter( + codename__endswith="question", content_type=ContentType.objects.get_for_model(Question) + ).values_list('pk', flat=True) + ) + self.admin_login(username='testing', password='password') + poll = Poll.objects.create(name="Survey") + change_url = reverse('admin:admin_inlines_poll_change', args=(poll.id,)) + self.selenium.get(self.live_server_url + change_url) + with self.disable_implicit_wait(): + with self.assertRaises(NoSuchElementException): + self.selenium.find_element_by_link_text('Add another Question') + def test_delete_inlines(self): self.admin_login(username='super', password='secret') self.selenium.get(self.live_server_url + reverse('admin:admin_inlines_profilecollection_add')) diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py index 0b0ad41e2e85..a18fb363aacb 100644 --- a/tests/admin_views/admin.py +++ b/tests/admin_views/admin.py @@ -1149,12 +1149,3 @@ def has_change_permission(self, request, obj=None): site9 = admin.AdminSite(name='admin9') site9.register(Article, ArticleAdmin9) - - -class ArticleAdmin10(admin.ModelAdmin): - def has_change_permission(self, request, obj=None): - return False - - -site10 = admin.AdminSite(name='admin10') -site10.register(Article, ArticleAdmin10) diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 0cc16509ff76..9fdca85c0d33 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -1775,8 +1775,7 @@ def test_change_view(self): self.assertEqual(post.status_code, 403) self.client.get(reverse('admin:logout')) - # view user should be able to view the article but not change any of them - # (the POST can be sent, but no modification occurs) + # view user can view articles but not make changes. self.client.force_login(self.viewuser) response = self.client.get(article_changelist_url) self.assertEqual(response.status_code, 200) @@ -1787,7 +1786,7 @@ def test_change_view(self): self.assertContains(response, '') self.assertContains(response, 'Close') post = self.client.post(article_change_url, change_dict) - self.assertEqual(post.status_code, 302) + self.assertEqual(post.status_code, 403) self.assertEqual(Article.objects.get(pk=self.a1.pk).content, '

Middle content

') self.client.get(reverse('admin:logout')) @@ -1845,7 +1844,7 @@ def test_change_view(self): response = self.client.get(change_url_3) self.assertEqual(response.status_code, 200) response = self.client.post(change_url_3, {'name': 'changed'}) - self.assertRedirects(response, self.index_url) + self.assertEqual(response.status_code, 403) self.assertEqual(RowLevelChangePermissionModel.objects.get(id=3).name, 'odd id mult 3') response = self.client.get(change_url_6) self.assertEqual(response.status_code, 200) @@ -1882,21 +1881,6 @@ def test_change_view_without_object_change_permission(self): self.assertEqual(response.context['title'], 'View article') self.assertContains(response, 'Close') - def test_change_view_post_without_object_change_permission(self): - """A POST redirects to changelist without modifications.""" - change_dict = { - 'title': 'Ikke fordømt', - 'content': '

edited article

', - 'date_0': '2008-03-18', 'date_1': '10:54:39', - 'section': self.s1.pk, - } - change_url = reverse('admin10:admin_views_article_change', args=(self.a1.pk,)) - changelist_url = reverse('admin10:admin_views_article_changelist') - self.client.force_login(self.viewuser) - response = self.client.post(change_url, change_dict) - self.assertRedirects(response, changelist_url) - self.assertEqual(Article.objects.get(pk=self.a1.pk).content, '

Middle content

') - def test_change_view_save_as_new(self): """ 'Save as new' should raise PermissionDenied for users without the 'add' @@ -4053,52 +4037,6 @@ def test_simple_inline(self): self.assertEqual(Widget.objects.count(), 1) self.assertEqual(Widget.objects.all()[0].name, "Widget 1 Updated") - def test_simple_inline_permissions(self): - """ - Changes aren't allowed without change permissions for the inline object. - """ - # User who can view Articles - permissionuser = User.objects.create_user( - username='permissionuser', password='secret', - email='vuser@example.com', is_staff=True, - ) - permissionuser.user_permissions.add(get_perm(Collector, get_permission_codename('view', Collector._meta))) - permissionuser.user_permissions.add(get_perm(Widget, get_permission_codename('view', Widget._meta))) - self.client.force_login(permissionuser) - # Without add permission, a new inline can't be added. - self.post_data['widget_set-0-name'] = 'Widget 1' - collector_url = reverse('admin:admin_views_collector_change', args=(self.collector.pk,)) - response = self.client.post(collector_url, self.post_data) - self.assertEqual(response.status_code, 302) - self.assertEqual(Widget.objects.count(), 0) - # But after adding the permisson it can. - permissionuser.user_permissions.add(get_perm(Widget, get_permission_codename('add', Widget._meta))) - self.post_data['widget_set-0-name'] = "Widget 1" - collector_url = reverse('admin:admin_views_collector_change', args=(self.collector.pk,)) - response = self.client.post(collector_url, self.post_data) - self.assertEqual(response.status_code, 302) - self.assertEqual(Widget.objects.count(), 1) - self.assertEqual(Widget.objects.first().name, 'Widget 1') - widget_id = Widget.objects.first().id - # Without the change permission, a POST doesn't change the object. - self.post_data['widget_set-INITIAL_FORMS'] = '1' - self.post_data['widget_set-0-id'] = str(widget_id) - self.post_data['widget_set-0-name'] = 'Widget 1 Updated' - response = self.client.post(collector_url, self.post_data) - self.assertEqual(response.status_code, 302) - self.assertEqual(Widget.objects.count(), 1) - self.assertEqual(Widget.objects.first().name, 'Widget 1') - # Now adding the change permission and editing works. - permissionuser.user_permissions.remove(get_perm(Widget, get_permission_codename('add', Widget._meta))) - permissionuser.user_permissions.add(get_perm(Widget, get_permission_codename('change', Widget._meta))) - self.post_data['widget_set-INITIAL_FORMS'] = '1' - self.post_data['widget_set-0-id'] = str(widget_id) - self.post_data['widget_set-0-name'] = 'Widget 1 Updated' - response = self.client.post(collector_url, self.post_data) - self.assertEqual(response.status_code, 302) - self.assertEqual(Widget.objects.count(), 1) - self.assertEqual(Widget.objects.first().name, 'Widget 1 Updated') - def test_explicit_autofield_inline(self): "A model with an explicit autofield primary key can be saved as inlines. Regression for #8093" # First add a new inline diff --git a/tests/admin_views/urls.py b/tests/admin_views/urls.py index fdb61d759d1e..ca684b2f2e0c 100644 --- a/tests/admin_views/urls.py +++ b/tests/admin_views/urls.py @@ -17,7 +17,6 @@ # All admin views accept `extra_context` to allow adding it like this: path('test_admin/admin8/', (admin.site.get_urls(), 'admin', 'admin-extra-context'), {'extra_context': {}}), path('test_admin/admin9/', admin.site9.urls), - path('test_admin/admin10/', admin.site10.urls), path('test_admin/has_permission_admin/', custom_has_permission_admin.site.urls), path('test_admin/autocomplete_admin/', autocomplete_site.urls), ] diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py index e7ad1cbcdfe6..67d4840446d5 100644 --- a/tests/auth_tests/test_views.py +++ b/tests/auth_tests/test_views.py @@ -1246,7 +1246,7 @@ def test_view_user_password_is_readonly(self): data['password'] = 'shouldnotchange' change_url = reverse('auth_test_admin:auth_user_change', args=(u.pk,)) response = self.client.post(change_url, data) - self.assertRedirects(response, reverse('auth_test_admin:auth_user_changelist')) + self.assertEqual(response.status_code, 403) u.refresh_from_db() self.assertEqual(u.password, original_password) From d6fa50995261bf8863a4292845b95d396f43b507 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Dec 2019 09:10:15 +0100 Subject: [PATCH 170/287] [2.2.x] Added release dates for 2.1.15, 2.2.8 and 3.0. Backport of e31d1852671866f6e52d55f9b7925ecad711fcf5 from master --- docs/releases/2.1.15.txt | 2 +- docs/releases/2.2.8.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/2.1.15.txt b/docs/releases/2.1.15.txt index 29764f986ee9..c8f3c88672ac 100644 --- a/docs/releases/2.1.15.txt +++ b/docs/releases/2.1.15.txt @@ -2,7 +2,7 @@ Django 2.1.15 release notes =========================== -*Expected December 2, 2019* +*December 2, 2019* Django 2.1.15 fixes a security issue and a data loss bug in 2.1.14. diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 2c4a6f9ec1fc..2090a0aec69d 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -2,7 +2,7 @@ Django 2.2.8 release notes ========================== -*Expected December 2, 2019* +*December 2, 2019* Django 2.2.8 fixes a security issue, several bugs in 2.2.7, and adds compatibility with Python 3.8. From b8782c52c99175916a1dceaece758608ac5856d0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Dec 2019 09:30:29 +0100 Subject: [PATCH 171/287] [2.2.x] Bumped version for 2.2.8 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 84db598181a2..2cefb844d958 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 8, 'alpha', 0) +VERSION = (2, 2, 8, 'final', 0) __version__ = get_version(VERSION) From 84de02023e488b99460c6b9f8f6bd7ae98fc4931 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Dec 2019 09:33:27 +0100 Subject: [PATCH 172/287] [2.2.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 2cefb844d958..aff468fe2fe9 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 8, 'final', 0) +VERSION = (2, 2, 9, 'alpha', 0) __version__ = get_version(VERSION) From 887139d1d71a6582f75d449874bc9829418b95e0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Dec 2019 09:45:40 +0100 Subject: [PATCH 173/287] [2.2.x] Removed issue reporter name from 2.1.15 and 2.2.8 release notes. Backport of 368b8d20aaa16f0ef763759a0a87d986ef460584 from master --- docs/releases/2.1.15.txt | 2 -- docs/releases/2.2.8.txt | 2 -- 2 files changed, 4 deletions(-) diff --git a/docs/releases/2.1.15.txt b/docs/releases/2.1.15.txt index c8f3c88672ac..6715b369db16 100644 --- a/docs/releases/2.1.15.txt +++ b/docs/releases/2.1.15.txt @@ -43,8 +43,6 @@ adding a documented, supported, and properly-tested mechanism for partially-editable multi-model forms to the admin interface may occur in Django itself. -Thank you to Shen Ying for reporting this issue. - Bugfixes ======== diff --git a/docs/releases/2.2.8.txt b/docs/releases/2.2.8.txt index 2090a0aec69d..e82483c18de3 100644 --- a/docs/releases/2.2.8.txt +++ b/docs/releases/2.2.8.txt @@ -44,8 +44,6 @@ adding a documented, supported, and properly-tested mechanism for partially-editable multi-model forms to the admin interface may occur in Django itself. -Thank you to Shen Ying for reporting this issue. - Bugfixes ======== From c759fb68a6c76a60d2aa20455acd0d68ac4c3c78 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 2 Dec 2019 11:42:24 +0100 Subject: [PATCH 174/287] [2.2.x] Added CVE-2019-19118 to the security archive. Backport of 900ea762e5d1342e84a645483d05b90e6d908f2d 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..e925b8304d7f 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 2, 2019 - :cve:`2019-19118` +------------------------------------ + +Privilege escalation in the Django admin. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 3.0 :commit:`(patch) <092cd66cf3c3e175acce698d6ca2012068d878fa>` +* Django 2.2 :commit:`(patch) <36f580a17f0b3cb087deadf3b65eea024f479c21>` +* Django 2.1 :commit:`(patch) <103ebe2b5ff1b2614b85a52c239f471904d26244>` From e8b0903976077b951795938b260211214ed7fe41 Mon Sep 17 00:00:00 2001 From: Ryan Cheley Date: Fri, 22 Nov 2019 18:38:27 -0800 Subject: [PATCH 175/287] [2.2.x] Fixed #31006 -- Doc'd backslash escaping in date/time template filters. Backport of a1f14ee3e5a2160c2eef1dad58a1da11be4b1531 from master --- docs/ref/templates/builtins.txt | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 8d3f353468d7..65a162e3b06a 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -1451,7 +1451,9 @@ used. Assuming the same settings as the previous example:: {{ value|date }} outputs ``9 de Enero de 2008`` (the ``DATE_FORMAT`` format specifier for the -``es`` locale is ``r'j \d\e F \d\e Y'``. +``es`` locale is ``r'j \d\e F \d\e Y'``). Both "d" and "e" are +backslash-escaped, because otherwise each is a format string that displays the +day and the timezone name, respectively. You can combine ``date`` with the :tfilter:`time` filter to render a full representation of a ``datetime`` value. E.g.:: @@ -2174,6 +2176,15 @@ For example:: If ``value`` is equivalent to ``datetime.datetime.now()``, the output will be the string ``"01:23"``. +Note that you can backslash-escape a format string if you want to use the +"raw" value. In this example, both "h" and "m" are backslash-escaped, because +otherwise each is a format string that displays the hour and the month, +respectively:: + + {% value|time:"H\h i\m" %} + +This would display as "01h 23m". + Another example: Assuming that :setting:`USE_L10N` is ``True`` and :setting:`LANGUAGE_CODE` is, From f33be1e8ae2efbca5f209e2365fa35c2aaee340c Mon Sep 17 00:00:00 2001 From: Peter Andersen Date: Mon, 9 Dec 2019 08:54:40 -0800 Subject: [PATCH 176/287] [2.2.x] Fixed #31073 -- Prevented CheckboxInput.get_context() from mutating attrs. Backport of 02eff7ef60466da108b1a33f1e4dc01eec45c99d from master --- django/forms/widgets.py | 4 +--- tests/forms_tests/widget_tests/test_checkboxinput.py | 5 +++++ tests/postgres_tests/test_array.py | 11 +++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/django/forms/widgets.py b/django/forms/widgets.py index e944091f0d15..8cd33c439aed 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -522,9 +522,7 @@ def format_value(self, value): def get_context(self, name, value, attrs): if self.check_test(value): - if attrs is None: - attrs = {} - attrs['checked'] = True + attrs = {**(attrs or {}), 'checked': True} return super().get_context(name, value, attrs) def value_from_datadict(self, data, files, name): diff --git a/tests/forms_tests/widget_tests/test_checkboxinput.py b/tests/forms_tests/widget_tests/test_checkboxinput.py index 6483b7f2115f..8dba2178c9db 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 447d511c9f09..021c58b6fda6 100644 --- a/tests/postgres_tests/test_array.py +++ b/tests/postgres_tests/test_array.py @@ -917,6 +917,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 86befcc172c23170a720b3e0c06db51a99b3da59 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 11 Dec 2019 10:07:41 +0100 Subject: [PATCH 177/287] [2.2.x] Refs #31073 -- Added release notes for 02eff7ef60466da108b1a33f1e4dc01eec45c99d. Backport of ec12c37384798093e359971c8980fe0c68d555bc from master. --- docs/releases/1.11.27.txt | 15 +++++++++++++++ docs/releases/2.2.9.txt | 15 +++++++++++++++ docs/releases/index.txt | 2 ++ 3 files changed, 32 insertions(+) create mode 100644 docs/releases/1.11.27.txt create mode 100644 docs/releases/2.2.9.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/2.2.9.txt b/docs/releases/2.2.9.txt new file mode 100644 index 000000000000..efd0cb4e1ce4 --- /dev/null +++ b/docs/releases/2.2.9.txt @@ -0,0 +1,15 @@ +========================== +Django 2.2.9 release notes +========================== + +*Expected January 2, 2020* + +Django 2.2.9 fixes a data loss bug in 2.2.8. + +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 067b17791247..6a1e2e07d736 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.9 2.2.8 2.2.7 2.2.6 @@ -82,6 +83,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 4d334bea06cac63dc1272abcec545b85136cca0e Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Mon, 16 Dec 2019 21:51:57 -0500 Subject: [PATCH 178/287] [2.2.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 | 24 +++++++++++++++++++---- docs/releases/1.11.27.txt | 20 +++++++++++++++++-- docs/releases/2.2.9.txt | 20 +++++++++++++++++-- tests/auth_tests/test_forms.py | 36 ++++++++++++++++++++++++++++++++++ 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index d6b5702fff73..e6f73fe5ee04 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -20,6 +20,15 @@ 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). + """ + return unicodedata.normalize('NFKC', s1).casefold() == unicodedata.normalize('NFKC', s2).casefold() + + class ReadOnlyPasswordHashWidget(forms.Widget): template_name = 'auth/widgets/read_only_password_hash.html' read_only = True @@ -256,11 +265,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', @@ -273,6 +287,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) @@ -280,8 +295,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)), @@ -292,7 +308,7 @@ def save(self, domain_override=None, } 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/docs/releases/2.2.9.txt b/docs/releases/2.2.9.txt index efd0cb4e1ce4..25a937419417 100644 --- a/docs/releases/2.2.9.txt +++ b/docs/releases/2.2.9.txt @@ -2,9 +2,25 @@ Django 2.2.9 release notes ========================== -*Expected January 2, 2020* +*December 18, 2019* -Django 2.2.9 fixes a data loss bug in 2.2.8. +Django 2.2.9 fixes a security issue and a data loss bug in 2.2.8. + +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 825138755d8d..bed23afe6852 100644 --- a/tests/auth_tests/test_forms.py +++ b/tests/auth_tests/test_forms.py @@ -754,6 +754,42 @@ 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) + 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) + 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 c494d90c195a739a7298b073eaa6ed987c2fd0bc Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 09:28:35 +0100 Subject: [PATCH 179/287] [2.2.x] Bumped version for 2.2.9 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index aff468fe2fe9..a1ec9bc3945e 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 9, 'alpha', 0) +VERSION = (2, 2, 9, 'final', 0) __version__ = get_version(VERSION) From e7286122b42d863bc1e97a846330cc63303f1c21 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 09:31:10 +0100 Subject: [PATCH 180/287] [2.2.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 a1ec9bc3945e..433fe2e565d3 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 9, 'final', 0) +VERSION = (2, 2, 10, 'alpha', 0) __version__ = get_version(VERSION) From 813b33eec4d3fef8c5c3d4bfbc6ac90a248680c6 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 18 Dec 2019 10:36:22 +0100 Subject: [PATCH 181/287] [2.2.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 e925b8304d7f..6e0c29223d14 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1042,3 +1042,16 @@ Versions affected * Django 3.0 :commit:`(patch) <092cd66cf3c3e175acce698d6ca2012068d878fa>` * Django 2.2 :commit:`(patch) <36f580a17f0b3cb087deadf3b65eea024f479c21>` * Django 2.1 :commit:`(patch) <103ebe2b5ff1b2614b85a52c239f471904d26244>` + +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 96d644312106337d714790cbedfcb227f0faa609 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 7 Jan 2020 09:54:22 +0100 Subject: [PATCH 182/287] [2.2.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 7a63bac67000..b092a16044fd 100644 --- a/tests/timezones/tests.py +++ b/tests/timezones/tests.py @@ -33,6 +33,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 fixed offset timezones and use them directly as tzinfo in the @@ -605,9 +611,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): @@ -694,7 +701,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) @@ -742,7 +752,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) @@ -766,7 +779,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 c67a368c16e4680b324b4f385398d638db4d8147 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 31 Dec 2019 12:46:06 -0500 Subject: [PATCH 183/287] [2.2.x] Fixed CVE-2020-7471 -- Properly escaped StringAgg(delimiter) parameter. --- django/contrib/postgres/aggregates/general.py | 6 ++++-- django/contrib/postgres/aggregates/mixins.py | 4 ++-- docs/releases/1.11.28.txt | 13 +++++++++++++ docs/releases/2.2.10.txt | 13 +++++++++++++ docs/releases/index.txt | 2 ++ tests/postgres_tests/test_aggregates.py | 4 ++++ 6 files changed, 38 insertions(+), 4 deletions(-) create mode 100644 docs/releases/1.11.28.txt create mode 100644 docs/releases/2.2.10.txt diff --git a/django/contrib/postgres/aggregates/general.py b/django/contrib/postgres/aggregates/general.py index 918373e926e7..9616bc3e3e21 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 ArrayField, JSONField +from django.db.models import Value from django.db.models.aggregates import Aggregate from .mixins import OrderableAggMixin @@ -51,11 +52,12 @@ def convert_value(self, value, expression, connection): class StringAgg(OrderableAggMixin, Aggregate): function = 'STRING_AGG' - template = "%(function)s(%(distinct)s%(expressions)s, '%(delimiter)s'%(ordering)s)" + template = '%(function)s(%(distinct)s%(expressions)s %(ordering)s)' allow_distinct = True def __init__(self, expression, delimiter, **extra): - super().__init__(expression, delimiter=delimiter, **extra) + delimiter_expr = Value(str(delimiter)) + super().__init__(expression, delimiter_expr, **extra) def convert_value(self, value, expression, connection): if not value: diff --git a/django/contrib/postgres/aggregates/mixins.py b/django/contrib/postgres/aggregates/mixins.py index 4625738bebb7..40318b7b31af 100644 --- a/django/contrib/postgres/aggregates/mixins.py +++ b/django/contrib/postgres/aggregates/mixins.py @@ -3,7 +3,7 @@ class OrderableAggMixin: - def __init__(self, expression, ordering=(), **extra): + def __init__(self, *expressions, ordering=(), **extra): if not isinstance(ordering, (list, tuple)): ordering = [ordering] ordering = ordering or [] @@ -12,7 +12,7 @@ def __init__(self, expression, ordering=(), **extra): (OrderBy(F(o[1:]), descending=True) if isinstance(o, str) and o[0] == '-' else o) for o in ordering ) - super().__init__(expression, **extra) + super().__init__(*expressions, **extra) self.ordering = self._parse_expressions(*ordering) def resolve_expression(self, *args, **kwargs): 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/2.2.10.txt b/docs/releases/2.2.10.txt new file mode 100644 index 000000000000..f82774dea096 --- /dev/null +++ b/docs/releases/2.2.10.txt @@ -0,0 +1,13 @@ +=========================== +Django 2.2.10 release notes +=========================== + +*February 3, 2020* + +Django 2.2.10 fixes a security issue in 2.2.9. + +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 6a1e2e07d736..8a8756ee112d 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.10 2.2.9 2.2.8 2.2.7 @@ -83,6 +84,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 6009a95ec0cf..21dbd862dde4 100644 --- a/tests/postgres_tests/test_aggregates.py +++ b/tests/postgres_tests/test_aggregates.py @@ -164,6 +164,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'Foo4'Foo3"}) + def test_string_agg_charfield(self): values = AggregateTestModel.objects.aggregate(stringagg=StringAgg('char_field', delimiter=';')) self.assertEqual(values, {'stringagg': 'Foo1;Foo2;Foo4;Foo3'}) From b2c33a57b3b98f1e90bbc2a2be2c5a6d814eed29 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 09:29:58 +0100 Subject: [PATCH 184/287] [2.2.x] Bumped version for 2.2.10 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 433fe2e565d3..81f3e88e9f9d 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 10, 'alpha', 0) +VERSION = (2, 2, 10, 'final', 0) __version__ = get_version(VERSION) From 2f663bf73ee39f604b6a5f206b88ed95a89f9e5f Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 09:49:16 +0100 Subject: [PATCH 185/287] [2.2.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 81f3e88e9f9d..973fbe925a3a 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 10, 'final', 0) +VERSION = (2, 2, 11, 'alpha', 0) __version__ = get_version(VERSION) From abb4829fdf2c01283eafdb8fa88d1587efaee94b Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Mon, 3 Feb 2020 10:11:34 +0100 Subject: [PATCH 186/287] [2.2.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 6e0c29223d14..76991cb23a2b 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1055,3 +1055,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 eeed073aa214d2d2f77297423eead6c87114d56f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 10 Feb 2020 08:18:58 +0100 Subject: [PATCH 187/287] [2.2.x] Added stub release notes for 2.2.11. Backport of 7e8339748cc199b4a13513891d9ac4f1e4794588 from master --- docs/releases/2.2.11.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.11.txt diff --git a/docs/releases/2.2.11.txt b/docs/releases/2.2.11.txt new file mode 100644 index 000000000000..5aaa5deab0c6 --- /dev/null +++ b/docs/releases/2.2.11.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.2.11 release notes +=========================== + +*Expected March 2, 2020* + +Django 2.2.11 fixes a data loss bug in 2.2.10. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 8a8756ee112d..48d566a27665 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.11 2.2.10 2.2.9 2.2.8 From 32d89bf11461857fde13017432a586e565b97ecc Mon Sep 17 00:00:00 2001 From: Abhijeet Viswa Date: Sat, 8 Feb 2020 10:22:09 +0530 Subject: [PATCH 188/287] [2.2.x] Fixed #31246 -- Fixed locking models in QuerySet.select_for_update(of=()) for related fields and parent link fields with multi-table inheritance. Partly regression in 0107e3d1058f653f66032f7fd3a0bd61e96bf782. Backport of 1712a76b9dfda1ef220395e62ea87079da8c9f6c from master. --- AUTHORS | 1 + django/db/models/sql/compiler.py | 37 ++++++++++++++---------- docs/releases/2.2.11.txt | 6 +++- tests/select_for_update/models.py | 6 +++- tests/select_for_update/tests.py | 48 +++++++++++++++++++++++++++---- 5 files changed, 75 insertions(+), 23 deletions(-) diff --git a/AUTHORS b/AUTHORS index 9b889cbd1067..20cc0c75c7ca 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,6 +9,7 @@ answer newbie questions, and generally made Django that much better: Aaron Swartz Aaron T. Myers Abeer Upadhyay + Abhijeet Viswa Abhinav Patil Abhishek Gautam Adam Allred diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 9e709d0f6e5d..e5c726676ad8 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -948,19 +948,34 @@ def get_select_for_update_of_arguments(self): the query. """ def _get_parent_klass_info(klass_info): - return ( - { + for parent_model, parent_link in klass_info['model']._meta.parents.items(): + parent_list = parent_model._meta.get_parent_list() + yield { 'model': parent_model, 'field': parent_link, 'reverse': False, 'select_fields': [ select_index for select_index in klass_info['select_fields'] - if self.select[select_index][0].target.model == parent_model + # Selected columns from a model or its parents. + if ( + self.select[select_index][0].target.model == parent_model or + self.select[select_index][0].target.model in parent_list + ) ], } - for parent_model, parent_link in klass_info['model']._meta.parents.items() - ) + + def _get_first_selected_col_from_model(klass_info): + """ + Find the first selected column from a model. If it doesn't exist, + don't lock a model. + + select_fields is filled recursively, so it also contains fields + from the parent models. + """ + for select_index in klass_info['select_fields']: + if self.select[select_index][0].target.model == klass_info['model']: + return self.select[select_index][0] def _get_field_choices(): """Yield all allowed field paths in breadth-first search order.""" @@ -989,14 +1004,7 @@ def _get_field_choices(): for name in self.query.select_for_update_of: klass_info = self.klass_info if name == 'self': - # Find the first selected column from a base model. If it - # doesn't exist, don't lock a base model. - for select_index in klass_info['select_fields']: - if self.select[select_index][0].target.model == klass_info['model']: - col = self.select[select_index][0] - break - else: - col = None + col = _get_first_selected_col_from_model(klass_info) else: for part in name.split(LOOKUP_SEP): klass_infos = ( @@ -1016,8 +1024,7 @@ def _get_field_choices(): if klass_info is None: invalid_names.append(name) continue - select_index = klass_info['select_fields'][0] - col = self.select[select_index][0] + col = _get_first_selected_col_from_model(klass_info) if col is not None: if self.connection.features.select_for_update_of_column: result.append(self.compile(col)[0]) diff --git a/docs/releases/2.2.11.txt b/docs/releases/2.2.11.txt index 5aaa5deab0c6..b14d961ac36a 100644 --- a/docs/releases/2.2.11.txt +++ b/docs/releases/2.2.11.txt @@ -9,4 +9,8 @@ Django 2.2.11 fixes a data loss bug in 2.2.10. Bugfixes ======== -* ... +* Fixed a data loss possibility in the + :meth:`~django.db.models.query.QuerySet.select_for_update`. When using + related fields or parent link fields with :ref:`multi-table-inheritance` in + the ``of`` argument, the corresponding models were not locked + (:ticket:`31246`). diff --git a/tests/select_for_update/models.py b/tests/select_for_update/models.py index c84f9ad6b29a..305e8cac490b 100644 --- a/tests/select_for_update/models.py +++ b/tests/select_for_update/models.py @@ -1,7 +1,11 @@ from django.db import models -class Country(models.Model): +class Entity(models.Model): + pass + + +class Country(Entity): name = models.CharField(max_length=30) diff --git a/tests/select_for_update/tests.py b/tests/select_for_update/tests.py index 1f1b20e47f6c..7ef0319477d4 100644 --- a/tests/select_for_update/tests.py +++ b/tests/select_for_update/tests.py @@ -113,7 +113,10 @@ def test_for_update_sql_generated_of(self): )) features = connections['default'].features if features.select_for_update_of_column: - expected = ['select_for_update_person"."id', 'select_for_update_country"."id'] + expected = [ + 'select_for_update_person"."id', + 'select_for_update_country"."entity_ptr_id', + ] else: expected = ['select_for_update_person', 'select_for_update_country'] expected = [connection.ops.quote_name(value) for value in expected] @@ -137,13 +140,29 @@ def test_for_update_sql_model_inheritance_ptr_generated_of(self): if connection.features.select_for_update_of_column: expected = [ 'select_for_update_eucountry"."country_ptr_id', - 'select_for_update_country"."id', + 'select_for_update_country"."entity_ptr_id', ] else: expected = ['select_for_update_eucountry', 'select_for_update_country'] expected = [connection.ops.quote_name(value) for value in expected] self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') + def test_for_update_sql_related_model_inheritance_generated_of(self): + with transaction.atomic(), CaptureQueriesContext(connection) as ctx: + list(EUCity.objects.select_related('country').select_for_update( + of=('self', 'country'), + )) + if connection.features.select_for_update_of_column: + expected = [ + 'select_for_update_eucity"."id', + 'select_for_update_eucountry"."country_ptr_id', + ] + else: + expected = ['select_for_update_eucity', 'select_for_update_eucountry'] + expected = [connection.ops.quote_name(value) for value in expected] + self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') def test_for_update_sql_model_inheritance_nested_ptr_generated_of(self): with transaction.atomic(), CaptureQueriesContext(connection) as ctx: @@ -153,13 +172,29 @@ def test_for_update_sql_model_inheritance_nested_ptr_generated_of(self): if connection.features.select_for_update_of_column: expected = [ 'select_for_update_eucity"."id', - 'select_for_update_country"."id', + 'select_for_update_country"."entity_ptr_id', ] else: expected = ['select_for_update_eucity', 'select_for_update_country'] expected = [connection.ops.quote_name(value) for value in expected] self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') + def test_for_update_sql_multilevel_model_inheritance_ptr_generated_of(self): + with transaction.atomic(), CaptureQueriesContext(connection) as ctx: + list(EUCountry.objects.select_for_update( + of=('country_ptr', 'country_ptr__entity_ptr'), + )) + if connection.features.select_for_update_of_column: + expected = [ + 'select_for_update_country"."entity_ptr_id', + 'select_for_update_entity"."id', + ] + else: + expected = ['select_for_update_country', 'select_for_update_entity'] + expected = [connection.ops.quote_name(value) for value in expected] + self.assertTrue(self.has_for_update_sql(ctx.captured_queries, of=expected)) + @skipUnlessDBFeature('has_select_for_update_of') def test_for_update_of_followed_by_values(self): with transaction.atomic(): @@ -264,7 +299,8 @@ def test_unrelated_of_argument_raises_error(self): msg = ( 'Invalid field name(s) given in select_for_update(of=(...)): %s. ' 'Only relational fields followed in the query are allowed. ' - 'Choices are: self, born, born__country.' + 'Choices are: self, born, born__country, ' + 'born__country__entity_ptr.' ) invalid_of = [ ('nonexistent',), @@ -307,13 +343,13 @@ def test_model_inheritance_of_argument_raises_error_ptr_in_choices(self): ) with self.assertRaisesMessage( FieldError, - msg % 'country, country__country_ptr', + msg % 'country, country__country_ptr, country__country_ptr__entity_ptr', ): with transaction.atomic(): EUCity.objects.select_related( 'country', ).select_for_update(of=('name',)).get() - with self.assertRaisesMessage(FieldError, msg % 'country_ptr'): + with self.assertRaisesMessage(FieldError, msg % 'country_ptr, country_ptr__entity_ptr'): with transaction.atomic(): EUCountry.objects.select_for_update(of=('name',)).get() From 7deb87c93e5f45ded3ccd1e6162bed6a30000af8 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 18 Feb 2020 10:48:19 +0100 Subject: [PATCH 189/287] [2.2.x] Fixed #31282 -- Corrected RelatedManager docs for using add/remove/set with PKs. Backport of 3bbf9a489afc689eff2f4a0b84af196aa1ef51e7 from master --- docs/ref/models/relations.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/ref/models/relations.txt b/docs/ref/models/relations.txt index ac61164d54fd..02eb660a912d 100644 --- a/docs/ref/models/relations.txt +++ b/docs/ref/models/relations.txt @@ -66,8 +66,8 @@ Related objects reference Using ``add()`` on a relation that already exists won't duplicate the relation, but it will still trigger signals. - ``add()`` also accepts the field the relation points to as an argument. - The above example can be rewritten as ``b.entry_set.add(234)``. + For many-to-many relationships ``add()`` accepts either model instances + or field values, normally primary keys, as the ``*objs`` argument. Use the ``through_defaults`` argument to specify values for the new :ref:`intermediate model ` instance(s), if @@ -131,9 +131,9 @@ Related objects reference :data:`~django.db.models.signals.m2m_changed` signal if you wish to execute custom code when a relationship is deleted. - Similarly to :meth:`add()`, ``remove()`` also accepts the field the - relation points to as an argument. The above example can be rewritten - as ``b.entry_set.remove(234)``. + For many-to-many relationships ``remove()`` accepts either model + instances or field values, normally primary keys, as the ``*objs`` + argument. For :class:`~django.db.models.ForeignKey` objects, this method only exists if ``null=True``. If the related field can't be set to ``None`` @@ -195,9 +195,9 @@ Related objects reference race conditions. For instance, new objects may be added to the database in between the call to ``clear()`` and the call to ``add()``. - Similarly to :meth:`add()`, ``set()`` also accepts the field the - relation points to as an argument. The above example can be rewritten - as ``e.related_set.set([obj1.pk, obj2.pk, obj3.pk])``. + For many-to-many relationships ``set()`` accepts a list of either model + instances or field values, normally primary keys, as the ``objs`` + argument. Use the ``through_defaults`` argument to specify values for the new :ref:`intermediate model ` instance(s), if From fe886a3b58a93cfbe8864b485f93cb6d426cd1f2 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 24 Feb 2020 14:46:28 +0100 Subject: [PATCH 190/287] [2.2.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 | 14 ++++++-- django/contrib/gis/db/models/functions.py | 14 ++++---- docs/releases/1.11.29.txt | 13 ++++++++ docs/releases/2.2.11.txt | 10 ++++-- docs/releases/index.txt | 1 + tests/gis_tests/distapp/tests.py | 31 +++++++++++++++++ tests/gis_tests/geoapp/tests.py | 39 +++++++++++++++++++++- 7 files changed, 110 insertions(+), 12 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 993d9f91fcb3..9d169275c59b 100644 --- a/django/contrib/gis/db/models/aggregates.py +++ b/django/contrib/gis/db/models/aggregates.py @@ -1,6 +1,7 @@ from django.contrib.gis.db.models.fields import ( ExtentField, GeometryCollectionField, GeometryField, LineStringField, ) +from django.db.models import Value from django.db.models.aggregates import Aggregate from django.utils.functional import cached_property @@ -27,9 +28,16 @@ def as_sql(self, compiler, connection, function=None, **extra_context): ) def as_oracle(self, compiler, connection, **extra_context): - tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05) - template = None if self.is_extent else '%(function)s(SDOAGGRTYPE(%(expressions)s,%(tolerance)s))' - return self.as_sql(compiler, connection, template=template, tolerance=tolerance, **extra_context) + if not self.is_extent: + tolerance = self.extra.get('tolerance') or getattr(self, 'tolerance', 0.05) + clone = self.copy() + clone.set_source_expressions([ + *self.get_source_expressions(), + Value(tolerance), + ]) + template = '%(function)s(SDOAGGRTYPE(%(expressions)s))' + return clone.as_sql(compiler, connection, template=template, **extra_context) + return self.as_sql(compiler, connection, **extra_context) def resolve_expression(self, query=None, allow_joins=True, reuse=None, summarize=False, for_save=False): c = super().resolve_expression(query, allow_joins, reuse, summarize, for_save) diff --git a/django/contrib/gis/db/models/functions.py b/django/contrib/gis/db/models/functions.py index 710e7c5d3d9a..97d15e6575f6 100644 --- a/django/contrib/gis/db/models/functions.py +++ b/django/contrib/gis/db/models/functions.py @@ -113,12 +113,14 @@ class OracleToleranceMixin: tolerance = 0.05 def as_oracle(self, compiler, connection, **extra_context): - tol = self.extra.get('tolerance', self.tolerance) - return self.as_sql( - compiler, connection, - template="%%(function)s(%%(expressions)s, %s)" % tol, - **extra_context - ) + tolerance = Value(self._handle_param( + self.extra.get('tolerance', self.tolerance), + 'tolerance', + NUMERIC_TYPES, + )) + clone = self.copy() + clone.set_source_expressions([*self.get_source_expressions(), tolerance]) + return clone.as_sql(compiler, connection, **extra_context) class Area(OracleToleranceMixin, GeoFunc): 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/2.2.11.txt b/docs/releases/2.2.11.txt index b14d961ac36a..9738ef4470a9 100644 --- a/docs/releases/2.2.11.txt +++ b/docs/releases/2.2.11.txt @@ -2,9 +2,15 @@ Django 2.2.11 release notes =========================== -*Expected March 2, 2020* +*March 4, 2020* -Django 2.2.11 fixes a data loss bug in 2.2.10. +Django 2.2.11 fixes a security issue and a data loss bug in 2.2.10. + +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``. Bugfixes ======== diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 48d566a27665..d83da503eea1 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -85,6 +85,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 67558582dc9c..42f6e0be9aa8 100644 --- a/tests/gis_tests/distapp/tests.py +++ b/tests/gis_tests/distapp/tests.py @@ -400,6 +400,37 @@ def test_distance_function_d_lookup(self): ).filter(d=D(m=1)) self.assertTrue(qs.exists()) + @unittest.skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_distance_function_tolerance_escaping(self): + qs = Interstate.objects.annotate( + d=Distance( + Point(500, 500, srid=3857), + Point(0, 0, srid=3857), + tolerance='0.05) = 1 OR 1=1 OR (1+1', + ), + ).filter(d=D(m=1)).values('pk') + msg = 'The tolerance parameter has the wrong type' + with self.assertRaisesMessage(TypeError, msg): + qs.exists() + + @unittest.skipUnless( + connection.vendor == 'oracle', + 'Oracle supports tolerance paremeter.', + ) + def test_distance_function_tolerance(self): + # Tolerance is greater than distance. + qs = Interstate.objects.annotate( + d=Distance( + Point(0, 0, srid=3857), + Point(1, 1, srid=3857), + tolerance=1.5, + ), + ).filter(d=0).values('pk') + self.assertIs(qs.exists(), True) + @skipIfDBFeature("supports_distance_geodetic") @skipUnlessDBFeature("has_Distance_function") def test_distance_function_raw_result_d_lookup(self): diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index eaf7e6132166..29d87c9005d3 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -1,4 +1,5 @@ import tempfile +import unittest from io import StringIO from django.contrib.gis import gdal @@ -8,7 +9,7 @@ MultiPoint, MultiPolygon, Point, Polygon, fromstr, ) from django.core.management import call_command -from django.db import NotSupportedError, connection +from django.db import DatabaseError, NotSupportedError, connection from django.test import TestCase, skipUnlessDBFeature from ..utils import ( @@ -563,6 +564,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 167699278806b21757104acf0ff0570f673d44c7 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Mar 2020 09:35:39 +0100 Subject: [PATCH 191/287] [2.2.x] Bumped version for 2.2.11 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index 973fbe925a3a..3936d9b43e84 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 11, 'alpha', 0) +VERSION = (2, 2, 11, 'final', 0) __version__ = get_version(VERSION) From b92e81120c0c354af126f10e02d6293f9849b7a5 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Mar 2020 09:38:41 +0100 Subject: [PATCH 192/287] [2.2.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 3936d9b43e84..f2fd14a1c150 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 11, 'final', 0) +VERSION = (2, 2, 12, 'alpha', 0) __version__ = get_version(VERSION) From cf5111c4c2c76e5a57deefa880b385a2f1570648 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Mar 2020 09:59:07 +0100 Subject: [PATCH 193/287] [2.2.x] Added CVE-2020-9402 to security archive. Backport of f37f9a0bf061fd0dfe4e45adb39157c3307ec8e2 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 76991cb23a2b..340aba041b02 100644 --- a/docs/releases/security.txt +++ b/docs/releases/security.txt @@ -1068,3 +1068,17 @@ Versions affected * Django 3.0 :commit:`(patch) <505826b469b16ab36693360da9e11fd13213421b>` * Django 2.2 :commit:`(patch) ` * Django 1.11 :commit:`(patch) <001b0634cd309e372edb6d7d95d083d02b8e37bd>` + +March 4, 2020 - :cve:`2020-9402` +-------------------------------- + +Potential SQL injection via ``tolerance`` parameter in GIS functions and +aggregates on Oracle. `Full description +`__ + +Versions affected +~~~~~~~~~~~~~~~~~ + +* Django 3.0 :commit:`(patch) <26a5cf834526e291db00385dd33d319b8271fc4c>` +* Django 2.2 :commit:`(patch) ` +* Django 1.11 :commit:`(patch) <02d97f3c9a88adc890047996e5606180bd1c6166>` From 7088073ada053f1ad377c04a4cd29b85af8d2847 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Wed, 4 Mar 2020 10:46:43 +0100 Subject: [PATCH 194/287] [2.2.x] Fixed typo in docs/releases/1.11.29.txt. Backport of 43f8ba1c7c0a264b67224c62b48fcd0dfdaddec3 from master --- docs/releases/1.11.29.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/1.11.29.txt b/docs/releases/1.11.29.txt index d37f3ffc0dac..e36dbe3aecd9 100644 --- a/docs/releases/1.11.29.txt +++ b/docs/releases/1.11.29.txt @@ -4,7 +4,7 @@ Django 1.11.29 release notes *March 4, 2020* -Django 1.11.29 fixes a security issue in 1.11.29. +Django 1.11.29 fixes a security issue in 1.11.28. CVE-2020-9402: Potential SQL injection via ``tolerance`` parameter in GIS functions and aggregates on Oracle ============================================================================================================ From 3acffe1420fd7fcf3e8cc6c5dd198af6b5c79a33 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 5 Mar 2020 08:55:34 +0100 Subject: [PATCH 195/287] [2.2.x] Fixed GeoQuerySetTest.test_unionagg_tolerance() test on Oracle 18c. Backport of 5ca76baa729bbbe62f5c4a0fc4f89747dc999029 from master --- tests/gis_tests/geoapp/tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/gis_tests/geoapp/tests.py b/tests/gis_tests/geoapp/tests.py index 29d87c9005d3..febad3eeee9f 100644 --- a/tests/gis_tests/geoapp/tests.py +++ b/tests/gis_tests/geoapp/tests.py @@ -581,10 +581,11 @@ def test_unionagg_tolerance(self): srid=4326, ) self.assertIs( - forney_houston.equals( + forney_houston.equals_exact( City.objects.filter(point__within=tx).aggregate( Union('point', tolerance=32000), )['point__union'], + tolerance=10e-6, ), True, ) From aa0948e23801cd79798a5ceeff5ad0ecd584b982 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 10 Mar 2020 12:01:01 +0100 Subject: [PATCH 196/287] [2.2.x] Added stub release notes for 2.2.12. Backport of a4200e958d1da46465d7d684674a1711bc9f65e0 from master --- docs/releases/2.2.12.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.12.txt diff --git a/docs/releases/2.2.12.txt b/docs/releases/2.2.12.txt new file mode 100644 index 000000000000..ca443a72ab47 --- /dev/null +++ b/docs/releases/2.2.12.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.2.12 release notes +=========================== + +*Expected April 1, 2020* + +Django 2.2.12 fixes several bugs in 2.2.11. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index d83da503eea1..e6590307a55a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.12 2.2.11 2.2.10 2.2.9 From 996be04c3ceb456754d9d527d4d708f30727f07e Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Tue, 10 Mar 2020 15:56:32 +0100 Subject: [PATCH 197/287] [2.2.x] Fixed #30439 -- Added support for different plural forms for a language. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thanks to Michal Čihař for review. Backport of e3e48b00127c09eafe6439d980a82fc5c591b673 from master --- django/utils/translation/trans_real.py | 75 +++++++++++++++++- docs/releases/2.2.12.txt | 5 +- docs/topics/i18n/translation.txt | 11 +-- .../other/locale/fr/LC_MESSAGES/django.mo | Bin 528 -> 580 bytes .../other/locale/fr/LC_MESSAGES/django.po | 13 ++- tests/i18n/tests.py | 16 ++++ 6 files changed, 105 insertions(+), 15 deletions(-) diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 98e3d0f51b9b..6fe29aa8fce9 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -57,6 +57,63 @@ def reset_cache(**kwargs): get_supported_language_variant.cache_clear() +class TranslationCatalog: + """ + Simulate a dict for DjangoTranslation._catalog so as multiple catalogs + with different plural equations are kept separate. + """ + def __init__(self, trans=None): + self._catalogs = [trans._catalog.copy()] if trans else [{}] + self._plurals = [trans.plural] if trans else [lambda n: int(n != 1)] + + def __getitem__(self, key): + for cat in self._catalogs: + try: + return cat[key] + except KeyError: + pass + raise KeyError(key) + + def __setitem__(self, key, value): + self._catalogs[0][key] = value + + def __contains__(self, key): + return any(key in cat for cat in self._catalogs) + + def items(self): + for cat in self._catalogs: + yield from cat.items() + + def keys(self): + for cat in self._catalogs: + yield from cat.keys() + + def update(self, trans): + # Merge if plural function is the same, else prepend. + for cat, plural in zip(self._catalogs, self._plurals): + if trans.plural.__code__ == plural.__code__: + cat.update(trans._catalog) + break + else: + self._catalogs.insert(0, trans._catalog) + self._plurals.insert(0, trans.plural) + + def get(self, key, default=None): + missing = object() + for cat in self._catalogs: + result = cat.get(key, missing) + if result is not missing: + return result + return default + + def plural(self, msgid, num): + for cat, plural in zip(self._catalogs, self._plurals): + tmsg = cat.get((msgid, plural(num))) + if tmsg is not None: + return tmsg + raise KeyError + + class DjangoTranslation(gettext_module.GNUTranslations): """ Set up the GNUTranslations context with regard to output charset. @@ -103,7 +160,7 @@ def __init__(self, language, domain=None, localedirs=None): self._add_fallback(localedirs) if self._catalog is None: # No catalogs found for this language, set an empty catalog. - self._catalog = {} + self._catalog = TranslationCatalog() def __repr__(self): return "" % self.__language @@ -174,9 +231,9 @@ def merge(self, other): # Take plural and _info from first catalog found (generally Django's). self.plural = other.plural self._info = other._info.copy() - self._catalog = other._catalog.copy() + self._catalog = TranslationCatalog(other) else: - self._catalog.update(other._catalog) + self._catalog.update(other) if other._fallback: self.add_fallback(other._fallback) @@ -188,6 +245,18 @@ def to_language(self): """Return the translation language name.""" return self.__to_language + def ngettext(self, msgid1, msgid2, n): + try: + tmsg = self._catalog.plural(msgid1, n) + except KeyError: + if self._fallback: + return self._fallback.ngettext(msgid1, msgid2, n) + if n == 1: + tmsg = msgid1 + else: + tmsg = msgid2 + return tmsg + def translation(language): """ diff --git a/docs/releases/2.2.12.txt b/docs/releases/2.2.12.txt index ca443a72ab47..8585b12e7d17 100644 --- a/docs/releases/2.2.12.txt +++ b/docs/releases/2.2.12.txt @@ -4,9 +4,10 @@ Django 2.2.12 release notes *Expected April 1, 2020* -Django 2.2.12 fixes several bugs in 2.2.11. +Django 2.2.12 fixes a bug in 2.2.11. Bugfixes ======== -* ... +* Added the ability to handle ``.po`` files containing different plural + equations for the same language (:ticket:`30439`). diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index b52e18e1894d..3aa235b20203 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -279,14 +279,9 @@ In a case like this, consider something like the following:: a format specification for argument 'name', as in 'msgstr[0]', doesn't exist in 'msgid' -.. note:: Plural form and po files - - Django does not support custom plural equations in po files. As all - translation catalogs are merged, only the plural form for the main Django po - file (in ``django/conf/locale//LC_MESSAGES/django.po``) is - considered. Plural forms in all other po files are ignored. Therefore, you - should not use different plural equations in your project or application po - files. +.. versionchanged: 2.2.12 + + Added support for different plural equations in ``.po`` files. .. _contextual-markers: diff --git a/tests/i18n/other/locale/fr/LC_MESSAGES/django.mo b/tests/i18n/other/locale/fr/LC_MESSAGES/django.mo index 478338bc886a2445f9a1c7004b49f4bac719d5fb..d86cae8f9136356dbf515856dd342248083670f4 100644 GIT binary patch delta 225 zcmbQha)hP+o)F7a1|VPoVi_Q|0b*7ljsap2C;(znAT9)AF(7USVvxFdK&->az_1%g zs{ru}AX^eh^D;r?)qylnh=Cc1L1u#hP>6v+HASH~GcUa~C$R{~W>+Z4DJ@FOnV672 z@yA&&V{0hiRwK{W)=ovl0}%rg5W@h-%!zIJY9YGLMX8A;nfZCTE{P?nRtiQ2h6cI@M!JS33WnxZ#>Uz} izQM##7le(h6$)}nixP8eHS!b`Z50ePxhC^4SpWboK^w;a diff --git a/tests/i18n/other/locale/fr/LC_MESSAGES/django.po b/tests/i18n/other/locale/fr/LC_MESSAGES/django.po index dafb6139ae83..7626e5f6d591 100644 --- a/tests/i18n/other/locale/fr/LC_MESSAGES/django.po +++ b/tests/i18n/other/locale/fr/LC_MESSAGES/django.po @@ -14,7 +14,10 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n != 1)\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n==0 ? 1 : 2);\n" + +# Plural form is purposefully different from the normal French plural to test +# multiple plural forms for one language. #: template.html:3 # Note: Intentional: variable name is translated. @@ -24,4 +27,10 @@ msgstr "Mon nom est %(personne)s." #: template.html:3 # Note: Intentional: the variable name is badly formatted (missing 's' at the end) msgid "My other name is %(person)s." -msgstr "Mon autre nom est %(person)." \ No newline at end of file +msgstr "Mon autre nom est %(person)." + +msgid "%d singular" +msgid_plural "%d plural" +msgstr[0] "%d singulier" +msgstr[1] "%d pluriel1" +msgstr[2] "%d pluriel2" diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 2377c8992e10..54de250cceed 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -98,6 +98,22 @@ def test_plural_null(self): self.assertEqual(g('%d year', '%d years', 1) % 1, '1 year') self.assertEqual(g('%d year', '%d years', 2) % 2, '2 years') + @override_settings(LOCALE_PATHS=extended_locale_paths) + @translation.override('fr') + def test_multiple_plurals_per_language(self): + """ + Normally, French has 2 plurals. As other/locale/fr/LC_MESSAGES/django.po + has a different plural equation with 3 plurals, this tests if those + plural are honored. + """ + self.assertEqual(ngettext("%d singular", "%d plural", 0) % 0, "0 pluriel1") + self.assertEqual(ngettext("%d singular", "%d plural", 1) % 1, "1 singulier") + self.assertEqual(ngettext("%d singular", "%d plural", 2) % 2, "2 pluriel2") + french = trans_real.catalog() + # Internal _catalog can query subcatalogs (from different po files). + self.assertEqual(french._catalog[('%d singular', 0)], '%d singulier') + self.assertEqual(french._catalog[('%d hour', 0)], '%d heure') + def test_override(self): activate('de') try: From e277b0401a14a596b08f10ce38d43da73ba23818 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 1 Apr 2020 09:14:19 +0200 Subject: [PATCH 198/287] [2.2.x] Added release date for 2.2.12. Backport of b56243b77f6ae3125bd1a3f24163b28a13a30c5f from master --- docs/releases/2.2.12.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/2.2.12.txt b/docs/releases/2.2.12.txt index 8585b12e7d17..753513e502ed 100644 --- a/docs/releases/2.2.12.txt +++ b/docs/releases/2.2.12.txt @@ -2,7 +2,7 @@ Django 2.2.12 release notes =========================== -*Expected April 1, 2020* +*April 1, 2020* Django 2.2.12 fixes a bug in 2.2.11. From fb4d8bf799df71014ddb7dc1735941eebf3828b0 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 1 Apr 2020 09:42:18 +0200 Subject: [PATCH 199/287] [2.2.x] Bumped version for 2.2.12 release. --- django/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/__init__.py b/django/__init__.py index f2fd14a1c150..808bd0d4aae8 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 12, 'alpha', 0) +VERSION = (2, 2, 12, 'final', 0) __version__ = get_version(VERSION) From 3ab5235d1dc94f7c8fe37a98c4e2c2337a5e5548 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 1 Apr 2020 09:49:05 +0200 Subject: [PATCH 200/287] [2.2.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 808bd0d4aae8..ee76e4dd6684 100644 --- a/django/__init__.py +++ b/django/__init__.py @@ -1,6 +1,6 @@ from django.utils.version import get_version -VERSION = (2, 2, 12, 'final', 0) +VERSION = (2, 2, 13, 'alpha', 0) __version__ = get_version(VERSION) From cbccae22a4356f0902ed824caf8be0b3ba40ed1c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 14 May 2020 06:22:54 +0200 Subject: [PATCH 201/287] [2.2.x] Added stub release notes for 2.2.13. Backport of 50798d43898c7d46926a4292f86fdf3859a433da from master --- docs/releases/2.2.13.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/2.2.13.txt diff --git a/docs/releases/2.2.13.txt b/docs/releases/2.2.13.txt new file mode 100644 index 000000000000..7fb6405ed5d7 --- /dev/null +++ b/docs/releases/2.2.13.txt @@ -0,0 +1,12 @@ +=========================== +Django 2.2.13 release notes +=========================== + +*Expected June 1, 2020* + +Django 2.2.13 fixes a bug in 2.2.12. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index e6590307a55a..d9fccd441e3a 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -25,6 +25,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 2.2.13 2.2.12 2.2.11 2.2.10 From 9d310b42361821e85ab7f8ea82ea9d776852fcc9 Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Wed, 27 May 2020 10:19:15 +0200 Subject: [PATCH 202/287] [2.2.x] Updated expected release dates for 2.2.13. Backport of 9d55ae00d3dad9e93714add69ab7e48e7b0bcafa from master --- docs/releases/2.2.13.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/releases/2.2.13.txt b/docs/releases/2.2.13.txt index 7fb6405ed5d7..450f7acdbf41 100644 --- a/docs/releases/2.2.13.txt +++ b/docs/releases/2.2.13.txt @@ -2,9 +2,9 @@ Django 2.2.13 release notes =========================== -*Expected June 1, 2020* +*Expected June 3, 2020* -Django 2.2.13 fixes a bug in 2.2.12. +Django 2.2.13 fixes two security issues and a bug in 2.2.12. Bugfixes ======== From e33220ffd8ff59039890a1d9c35e3f4f7d96f3a8 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 18 Jun 2019 11:38:51 +0200 Subject: [PATCH 203/287] [2.2.x] Fixed LiveWidgetTests.test_textarea_trailing_newlines() crash on Chrome 75+. Backport of b08a18f17ba53eb0bc7fd7993924f3d7f8ed5c52 from master --- tests/forms_tests/tests/test_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/forms_tests/tests/test_widgets.py b/tests/forms_tests/tests/test_widgets.py index 3e137532b97a..9d06075c9b6b 100644 --- a/tests/forms_tests/tests/test_widgets.py +++ b/tests/forms_tests/tests/test_widgets.py @@ -16,6 +16,6 @@ def test_textarea_trailing_newlines(self): """ article = Article.objects.create(content="\nTst\n") self.selenium.get(self.live_server_url + reverse('article_form', args=[article.pk])) - self.selenium.find_element_by_id('submit').submit() + self.selenium.find_element_by_id('submit').click() article = Article.objects.get(pk=article.pk) self.assertEqual(article.content, "\r\nTst\r\n") From 027840d7def56784b78ff0af656376043749200e Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Thu, 28 May 2020 10:26:41 +0200 Subject: [PATCH 204/287] [2.2.x] Fixed #31570 -- Corrected translation loading for apps providing territorial language variants with different plural equations. Regression in e3e48b00127c09eafe6439d980a82fc5c591b673. Thanks to Shai Berger for report, reproduce and suggested fix. Backport of dd1ca50b096bf0351819aabc862e91a9797ddaca from master. --- django/utils/translation/trans_real.py | 2 +- docs/releases/2.2.13.txt | 7 ++- tests/i18n/loading/en/LC_MESSAGES/django.mo | Bin 0 -> 446 bytes tests/i18n/loading/en/LC_MESSAGES/django.po | 23 +++++++++ .../i18n/loading/en_AU/LC_MESSAGES/django.mo | Bin 0 -> 432 bytes .../i18n/loading/en_AU/LC_MESSAGES/django.po | 23 +++++++++ .../i18n/loading/en_CA/LC_MESSAGES/django.mo | Bin 0 -> 389 bytes .../i18n/loading/en_CA/LC_MESSAGES/django.po | 22 +++++++++ .../i18n/loading/en_NZ/LC_MESSAGES/django.mo | Bin 0 -> 387 bytes .../i18n/loading/en_NZ/LC_MESSAGES/django.po | 22 +++++++++ tests/i18n/loading_app/__init__.py | 0 tests/i18n/loading_app/apps.py | 5 ++ .../locale/en/LC_MESSAGES/django.mo | Bin 0 -> 380 bytes .../locale/en/LC_MESSAGES/django.po | 25 ++++++++++ tests/i18n/tests.py | 45 ++++++++++++++++++ 15 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 tests/i18n/loading/en/LC_MESSAGES/django.mo create mode 100644 tests/i18n/loading/en/LC_MESSAGES/django.po create mode 100644 tests/i18n/loading/en_AU/LC_MESSAGES/django.mo create mode 100644 tests/i18n/loading/en_AU/LC_MESSAGES/django.po create mode 100644 tests/i18n/loading/en_CA/LC_MESSAGES/django.mo create mode 100644 tests/i18n/loading/en_CA/LC_MESSAGES/django.po create mode 100644 tests/i18n/loading/en_NZ/LC_MESSAGES/django.mo create mode 100644 tests/i18n/loading/en_NZ/LC_MESSAGES/django.po create mode 100644 tests/i18n/loading_app/__init__.py create mode 100644 tests/i18n/loading_app/apps.py create mode 100644 tests/i18n/loading_app/locale/en/LC_MESSAGES/django.mo create mode 100644 tests/i18n/loading_app/locale/en/LC_MESSAGES/django.po diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 6fe29aa8fce9..486b2b26b608 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -95,7 +95,7 @@ def update(self, trans): cat.update(trans._catalog) break else: - self._catalogs.insert(0, trans._catalog) + self._catalogs.insert(0, trans._catalog.copy()) self._plurals.insert(0, trans.plural) def get(self, key, default=None): diff --git a/docs/releases/2.2.13.txt b/docs/releases/2.2.13.txt index 450f7acdbf41..cc0739e646ca 100644 --- a/docs/releases/2.2.13.txt +++ b/docs/releases/2.2.13.txt @@ -4,9 +4,12 @@ Django 2.2.13 release notes *Expected June 3, 2020* -Django 2.2.13 fixes two security issues and a bug in 2.2.12. +Django 2.2.13 fixes two security issues and a regression in 2.2.12. Bugfixes ======== -* ... +* Fixed a regression in Django 2.2.12 that affected translation loading for + apps providing translations for territorial language variants as well as a + generic language, where the project has different plural equations for the + language (:ticket:`31570`). diff --git a/tests/i18n/loading/en/LC_MESSAGES/django.mo b/tests/i18n/loading/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..f666550adb782b6455e855ebbf14df0c04b8a750 GIT binary patch literal 446 zcmZvYO-{ow5QPJRO_qoyi(v->7s{$i8`PAzB_e;MX(ia1)D1zY9XSr72jLK0inA~& zNZIh@Pcvh^k)Qm1bnt1Q_K_3h5;;UhNUIn*MjnyLo?)#2<}d8=?Dnv9DrLq>$Ydk5 z+Cs%uErk&)`NlI%ycJpEO(BH^v2*v}%qT2rGWVj;On4=gCPAGSD>7;FnxxVKGmc2Y z--~vKxJ+{ko~V72UWLhcY0~!32R9&@OhrcWvHDp}D~4wiV{l{6k+= zB`d@bvR9^RuI)uSC4, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-13 08:42+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: trans/tests.py:16 +msgid "local country person" +msgstr "local country person" diff --git a/tests/i18n/loading/en_AU/LC_MESSAGES/django.mo b/tests/i18n/loading/en_AU/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..98b016bc6c35870cdf4ed86e2bb538fc89dc00c5 GIT binary patch literal 432 zcmYL@O-{ow5QPH*7Fi;eEQTEjTqvt5ZBWzJEfGl*(jN(SCUt{RYDcz%=s`FHm*Ol; z%OX$nX~xzY&HFt)`gTx<$T@O@93wqsrxZCuo{_, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-13 08:42+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: trans/tests.py:16 +msgid "local country person" +msgstr "aussie" diff --git a/tests/i18n/loading/en_CA/LC_MESSAGES/django.mo b/tests/i18n/loading/en_CA/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..4d6da00f6539ee98e89d50081e8141ca436a3778 GIT binary patch literal 389 zcmYL@u};G<6h#99hKvY_g~2?EXVCD@GXhCr|*$3b-AFZeyag%_4w z`RG~Jee(G|-1`Y2yTA!>2J8b9pic%I0gu4(P7rKh9su8Phx5M&V@Pe4tdX*v@otM6 z?OJPs%-J`sJdW3R1uvB~1ZDK@fzBCPv3wDyNto-#I*&zLR%?9Ql`SqTL1C8STz^*m z4vwVP1U)gDW0B&BqWhGH`1CnVSjc7Tap9zCYw4{cXtv}WB~&mpVS>hdN~0)ei^UC8 zP1(s(<3h_1f;dg)%id(d`LwP^)|EpC3`8v0pnW;I2=A=%+Q4_)27J8!^5;!0D|3a^ kt8}gQ*UMst$3w5z+GvNFQFdLKlA!TR<-, YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-13 08:42+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: trans/tests.py:16 +msgid "local country person" +msgstr "canuck" diff --git a/tests/i18n/loading/en_NZ/LC_MESSAGES/django.mo b/tests/i18n/loading/en_NZ/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..f71850a5b1a06715d127ed1eaff29ccc93b400b4 GIT binary patch literal 387 zcmYL@u};G<6h#9nOGbpm!r&cTV5>|)HH}*$>?EXVCD=^r21BqT#|66Z7yKUI!V62T zeDo~qKKc9}4t@g2K5zn@0SCYs=#l|Pzyol+7X%xa1K=C(aQ^pT^r>wrYgF2;@otA2 ziQpfF2uu0C|P zgCpq`L63~)Sfn_j=q@E9K7C3P7IN8oTsUdkT6*gUnk_j;2^9>DnV>PB&?w5;VsQgi zQ*C9XaG~ToL7XP@WoI(xd{XP7byeR10}%_>YhMm8!l^Z08TfA3fR9&S{=BKBHdm;8 jk*-z#dRffysPA=J8|5%FrCn=N5j1+%J`CRUrw;xA! literal 0 HcmV?d00001 diff --git a/tests/i18n/loading/en_NZ/LC_MESSAGES/django.po b/tests/i18n/loading/en_NZ/LC_MESSAGES/django.po new file mode 100644 index 000000000000..41b7499291ad --- /dev/null +++ b/tests/i18n/loading/en_NZ/LC_MESSAGES/django.po @@ -0,0 +1,22 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-13 08:42+0000\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: trans/tests.py:16 +msgid "local country person" +msgstr "kiwi" diff --git a/tests/i18n/loading_app/__init__.py b/tests/i18n/loading_app/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/i18n/loading_app/apps.py b/tests/i18n/loading_app/apps.py new file mode 100644 index 000000000000..b4cdece23294 --- /dev/null +++ b/tests/i18n/loading_app/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class LoadingAppConfig(AppConfig): + name = 'loading_app' diff --git a/tests/i18n/loading_app/locale/en/LC_MESSAGES/django.mo b/tests/i18n/loading_app/locale/en/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..71cbdf3e9d8d54be31066ec4ad8628bc2c1f2845 GIT binary patch literal 380 zcmYL@K~KUk7=|%=+R?Lz&%}d9i{c3jGZa>EvE7z2Nc2{r&Y96JZ6W$Y{CoZuJ5A(G zp7i_Dx9RhJeDu}vIq;l#&OC>nD^HugXY4QU{MmN?lNtRkR}RH%w3NnHT4Bh@vF%H^(V-=Ii1iQ$Qo9Pt!I1Rhe%oml#`f^NEGFCKEL->Rc=KoQ6a?!10%_7(V7ey8`V`;n{war z20Z3;uifk31QV^CRQ|iq#``$=;jWunRB8aLH({)F;i8zL{=V00y-I_qTIqGAN(}v% i$^}`yHKImSZ8jEzYJOK6-VWez49^vuhS0kh1f3tbb!oc* literal 0 HcmV?d00001 diff --git a/tests/i18n/loading_app/locale/en/LC_MESSAGES/django.po b/tests/i18n/loading_app/locale/en/LC_MESSAGES/django.po new file mode 100644 index 000000000000..e1422f19daad --- /dev/null +++ b/tests/i18n/loading_app/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,25 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-13 11:39+0300\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: apps.py:8 +msgid "" +"An app with its own translation files, with one for 'en' but not the " +"regional dialects of English" +msgstr "" diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 54de250cceed..7381cb9c3154 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -357,6 +357,51 @@ def test_language_bidi_null(self): self.assertIs(get_language_bidi(), True) +class TranslationLoadingTests(SimpleTestCase): + def setUp(self): + """Clear translation state.""" + self._old_language = get_language() + self._old_translations = trans_real._translations + deactivate() + trans_real._translations = {} + + def tearDown(self): + trans_real._translations = self._old_translations + activate(self._old_language) + + @override_settings( + USE_I18N=True, + LANGUAGE_CODE='en', + LANGUAGES=[ + ('en', 'English'), + ('en-ca', 'English (Canada)'), + ('en-nz', 'English (New Zealand)'), + ('en-au', 'English (Australia)'), + ], + LOCALE_PATHS=[os.path.join(here, 'loading')], + INSTALLED_APPS=['i18n.loading_app'], + ) + def test_translation_loading(self): + """ + "loading_app" does not have translations for all languages provided by + "loading". Catalogs are merged correctly. + """ + tests = [ + ('en', 'local country person'), + ('en_AU', 'aussie'), + ('en_NZ', 'kiwi'), + ('en_CA', 'canuck'), + ] + # Load all relevant translations. + for language, _ in tests: + activate(language) + # Catalogs are merged correctly. + for language, nickname in tests: + with self.subTest(language=language): + activate(language) + self.assertEqual(gettext('local country person'), nickname) + + class TranslationThreadSafetyTests(SimpleTestCase): def setUp(self): From b0d810a77bbed01d45f92df93195f7bdc8d4da62 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 6 Apr 2020 07:41:43 +0200 Subject: [PATCH 205/287] [2.2.x] Fixed Sphinx warnings on duplicate object descriptions. Backport of 69e2cd6fed40f3ecf767609b80ad31f288446e48 from master. --- docs/ref/contrib/auth.txt | 7 +++++++ docs/ref/contrib/gis/db-api.txt | 2 +- docs/ref/contrib/gis/geoquerysets.txt | 1 + docs/ref/contrib/staticfiles.txt | 1 + docs/ref/models/database-functions.txt | 3 +++ docs/topics/auth/customizing.txt | 1 + 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 7be5b55ee3e4..3e30cb2241e3 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -12,10 +12,13 @@ topic guide `. ``User`` model ============== +.. class:: models.User + Fields ------ .. class:: models.User + :noindex: :class:`~django.contrib.auth.models.User` objects have the following fields: @@ -115,6 +118,7 @@ Attributes ---------- .. class:: models.User + :noindex: .. attribute:: is_authenticated @@ -140,6 +144,7 @@ Methods ------- .. class:: models.User + :noindex: .. method:: get_username() @@ -332,6 +337,7 @@ Fields fields: .. class:: models.Permission + :noindex: .. attribute:: name @@ -363,6 +369,7 @@ Fields :class:`~django.contrib.auth.models.Group` objects have the following fields: .. class:: models.Group + :noindex: .. attribute:: name diff --git a/docs/ref/contrib/gis/db-api.txt b/docs/ref/contrib/gis/db-api.txt index 329ea78dfb5a..3861a48f3ef6 100644 --- a/docs/ref/contrib/gis/db-api.txt +++ b/docs/ref/contrib/gis/db-api.txt @@ -334,7 +334,7 @@ Lookup Type PostGIS Oracle MySQL [#]_ SpatiaLite :lookup:`distance_lte` X X X X N :lookup:`dwithin` X X X B :lookup:`equals` X X X X C -:lookup:`exact` X X X X B +:lookup:`exact ` X X X X B :lookup:`intersects` X X X X B :lookup:`isvalid` X X X (≥ 5.7.5) X (LWGEOM) :lookup:`overlaps` X X X X B diff --git a/docs/ref/contrib/gis/geoquerysets.txt b/docs/ref/contrib/gis/geoquerysets.txt index 38e9fec13342..d03fd7621cd7 100644 --- a/docs/ref/contrib/gis/geoquerysets.txt +++ b/docs/ref/contrib/gis/geoquerysets.txt @@ -295,6 +295,7 @@ SpatiaLite ``Equals(poly, geom)`` ========== ================================================= .. fieldlookup:: exact + :noindex: .. fieldlookup:: same_as ``exact``, ``same_as`` diff --git a/docs/ref/contrib/staticfiles.txt b/docs/ref/contrib/staticfiles.txt index 9da8f6eb4ed8..00665fe1a245 100644 --- a/docs/ref/contrib/staticfiles.txt +++ b/docs/ref/contrib/staticfiles.txt @@ -209,6 +209,7 @@ the directories which were searched: ------------- .. django-admin:: runserver [addrport] + :noindex: Overrides the core :djadmin:`runserver` command if the ``staticfiles`` app is :setting:`installed` and adds automatic serving of static diff --git a/docs/ref/models/database-functions.txt b/docs/ref/models/database-functions.txt index deb559362d73..1b668270055c 100644 --- a/docs/ref/models/database-functions.txt +++ b/docs/ref/models/database-functions.txt @@ -658,14 +658,17 @@ Usage example:: ~~~~~~~~~~~~~~~~~~~~~~~~ .. class:: TruncHour(expression, output_field=None, tzinfo=None, **extra) + :noindex: .. attribute:: kind = 'hour' .. class:: TruncMinute(expression, output_field=None, tzinfo=None, **extra) + :noindex: .. attribute:: kind = 'minute' .. class:: TruncSecond(expression, output_field=None, tzinfo=None, **extra) + :noindex: .. attribute:: kind = 'second' diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index ce4c1c9edf80..d6b5effcf686 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -846,6 +846,7 @@ must define some additional attributes and methods. These methods allow the admin to control access of the user to admin content: .. class:: models.CustomUser + :noindex: .. attribute:: is_staff From 151a83e92c1a457baf028160ed9191405b869df6 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 6 Apr 2020 08:00:40 +0200 Subject: [PATCH 206/287] [2.2.x] Fixed CodeBlock deprecation warning on Sphinx 2.1+. Backport of a4e4737cf36f3ba6f526587f2656cf4be64b91bf from master. --- docs/_ext/djangodocs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_ext/djangodocs.py b/docs/_ext/djangodocs.py index 95ae53068024..cc40c40cd8d6 100644 --- a/docs/_ext/djangodocs.py +++ b/docs/_ext/djangodocs.py @@ -10,7 +10,7 @@ from docutils.statemachine import ViewList from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.directives import CodeBlock +from sphinx.directives.code import CodeBlock from sphinx.domains.std import Cmdoption from sphinx.errors import ExtensionError from sphinx.util import logging From 79baf338aef2ac21d3d29ee56e85f69678eef1a1 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Mon, 6 Apr 2020 08:05:02 +0200 Subject: [PATCH 207/287] [2.2.x] Fixed highlightlang deprecation warning on Sphinx 1.8+. Backport of 678f958ef972bf9be402332537149ca0884035ba from master --- docs/ref/templates/language.txt | 2 +- docs/topics/i18n/translation.txt | 8 ++++---- docs/topics/security.txt | 2 +- docs/topics/templates.txt | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/ref/templates/language.txt b/docs/ref/templates/language.txt index 1287f012c2f8..540d6538f8f5 100644 --- a/docs/ref/templates/language.txt +++ b/docs/ref/templates/language.txt @@ -34,7 +34,7 @@ or Jinja2_, you should feel right at home with Django's templates. Templates ========= -.. highlightlang:: html+django +.. highlight:: html+django A template is simply a text file. It can generate any text-based format (HTML, XML, CSV, etc.). diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 3aa235b20203..24f42a1a3cfc 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -553,7 +553,7 @@ Similar access to this information is available for template code. See below. Internationalization: in template code ====================================== -.. highlightlang:: html+django +.. highlight:: html+django Translations in :doc:`Django templates ` uses two template tags and a slightly different syntax than in Python code. To give your template @@ -954,7 +954,7 @@ There are also simple filters available for convenience: Internationalization: in JavaScript code ======================================== -.. highlightlang:: python +.. highlight:: python Adding translations to JavaScript poses some problems: @@ -1041,7 +1041,7 @@ precedence. Using the JavaScript translation catalog ---------------------------------------- -.. highlightlang:: javascript +.. highlight:: javascript To use the catalog, just pull in the dynamically generated script like this: @@ -1824,7 +1824,7 @@ redirected in the ``redirect_to`` context variable. Explicitly setting the active language -------------------------------------- -.. highlightlang:: python +.. highlight:: python You may want to set the active language for the current session explicitly. Perhaps a user's language preference is retrieved from another system, for example. diff --git a/docs/topics/security.txt b/docs/topics/security.txt index 549b473988e2..1a155455128a 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -10,7 +10,7 @@ on securing a Django-powered site. Cross site scripting (XSS) protection ===================================== -.. highlightlang:: html+django +.. highlight:: html+django XSS attacks allow a user to inject client side scripts into the browsers of other users. This is usually achieved by storing the malicious scripts in the diff --git a/docs/topics/templates.txt b/docs/topics/templates.txt index b993ef4d0ed2..a2579b41b499 100644 --- a/docs/topics/templates.txt +++ b/docs/topics/templates.txt @@ -657,7 +657,7 @@ creating an object that specifies the following attributes: The Django template language ============================ -.. highlightlang:: html+django +.. highlight:: html+django Syntax ------ From c7bab8d2b7160a635a6f55e4d89e0a2e66d1679c Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 14 Apr 2020 09:32:09 +0200 Subject: [PATCH 208/287] [2.2.x] Fixed term warning on Sphinx 3.0.1+. "term" role became case sensitive in Sphinx 3.0.1. Backport of cc70a0343ef51ffcc0b1211dd6e7abedc4b43ea6 from master --- docs/internals/contributing/localizing.txt | 11 ++++++----- .../contributing/writing-code/submitting-patches.txt | 4 ++-- docs/intro/whatsnext.txt | 4 ++-- docs/ref/models/fields.txt | 2 +- docs/releases/1.11.txt | 7 ++++--- docs/releases/1.8.txt | 6 +++--- docs/releases/2.2.txt | 7 ++++--- 7 files changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/internals/contributing/localizing.txt b/docs/internals/contributing/localizing.txt index f0d89f900194..1c9e1ec2f363 100644 --- a/docs/internals/contributing/localizing.txt +++ b/docs/internals/contributing/localizing.txt @@ -46,11 +46,12 @@ translating or add a language that isn't yet translated, here's what to do: `Transifex User Guide`_. Translations from Transifex are only integrated into the Django repository at -the time of a new :term:`feature release`. We try to update them a second time -during one of the following :term:`patch release`\s, but that depends on the -translation manager's availability. So don't miss the string freeze period -(between the release candidate and the feature release) to take the opportunity -to complete and fix the translations for your language! +the time of a new :term:`feature release `. We try to update +them a second time during one of the following :term:`patch release +`\s, but that depends on the translation manager's availability. +So don't miss the string freeze period (between the release candidate and the +feature release) to take the opportunity to complete and fix the translations +for your language! Formats ======= diff --git a/docs/internals/contributing/writing-code/submitting-patches.txt b/docs/internals/contributing/writing-code/submitting-patches.txt index af0000c5d8fb..455374b3476a 100644 --- a/docs/internals/contributing/writing-code/submitting-patches.txt +++ b/docs/internals/contributing/writing-code/submitting-patches.txt @@ -224,8 +224,8 @@ Finally, there are a couple of updates to Django's documentation to make: under the appropriate version describing what code will be removed. Once you have completed these steps, you are finished with the deprecation. -In each :term:`feature release`, all ``RemovedInDjangoXXWarning``\s matching -the new version are removed. +In each :term:`feature release `, all +``RemovedInDjangoXXWarning``\s matching the new version are removed. JavaScript patches ================== diff --git a/docs/intro/whatsnext.txt b/docs/intro/whatsnext.txt index 718be4966975..7d3346a12a70 100644 --- a/docs/intro/whatsnext.txt +++ b/docs/intro/whatsnext.txt @@ -195,8 +195,8 @@ Differences between versions The text documentation in the master branch of the Git repository contains the "latest and greatest" changes and additions. These changes include documentation of new features targeted for Django's next :term:`feature -release`. For that reason, it's worth pointing out our policy to highlight -recent changes and additions to Django. +release `. For that reason, it's worth pointing out our policy +to highlight recent changes and additions to Django. We follow this policy: diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index a4ec2db358db..75a0b2dd2fbe 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -1061,7 +1061,7 @@ databases supported by Django. .. class:: SlugField(max_length=50, **options) -:term:`Slug` is a newspaper term. A slug is a short label for something, +:term:`Slug ` is a newspaper term. A slug is a short label for something, containing only letters, numbers, underscores or hyphens. They're generally used in URLs. diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index bb7c06fe9f17..f87839cf0422 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -15,9 +15,10 @@ want to be aware of when upgrading from Django 1.10 or older versions. We've See the :doc:`/howto/upgrade-version` guide if you're updating an existing project. -Django 1.11 is designated as a :term:`long-term support release`. It will -receive security updates for at least three years after its release. Support -for the previous LTS, Django 1.8, will end in April 2018. +Django 1.11 is designated as a :term:`long-term support release +`. It will receive security updates for at least +three years after its release. Support for the previous LTS, Django 1.8, will +end in April 2018. Python compatibility ==================== diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt index 01f250e3b0f5..1281c2c1a717 100644 --- a/docs/releases/1.8.txt +++ b/docs/releases/1.8.txt @@ -17,9 +17,9 @@ See the :doc:`/howto/upgrade-version` guide if you're updating an existing project. Django 1.8 has been designated as Django's second :term:`long-term support -release`. It will receive security updates for at least three years after its -release. Support for the previous LTS, Django 1.4, will end 6 months from the -release date of Django 1.8. +release `. It will receive security updates for at +least three years after its release. Support for the previous LTS, Django 1.4, +will end 6 months from the release date of Django 1.8. Python compatibility ==================== diff --git a/docs/releases/2.2.txt b/docs/releases/2.2.txt index 86e1f70dc844..e83c162b0b53 100644 --- a/docs/releases/2.2.txt +++ b/docs/releases/2.2.txt @@ -15,9 +15,10 @@ want to be aware of when upgrading from Django 2.1 or earlier. We've See the :doc:`/howto/upgrade-version` guide if you're updating an existing project. -Django 2.2 is designated as a :term:`long-term support release`. It will -receive security updates for at least three years after its release. Support -for the previous LTS, Django 1.11, will end in April 2020. +Django 2.2 is designated as a :term:`long-term support release +`. It will receive security updates for at least +three years after its release. Support for the previous LTS, Django 1.11, will +end in April 2020. Python compatibility ==================== From 8301bc9cfad588074375edadfe0f19024dc217f8 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Tue, 12 May 2020 08:52:23 +0200 Subject: [PATCH 209/287] [2.2.x] Fixed E128, E741 flake8 warnings. Backport of 0668164b4ac93a5be79f5b87fae83c657124d9ab from master. --- django/contrib/admin/options.py | 2 +- django/core/mail/message.py | 4 +- .../management/commands/compilemessages.py | 2 +- django/forms/widgets.py | 2 +- django/http/request.py | 2 +- django/utils/dateformat.py | 6 +- django/utils/topological_sort.py | 4 +- tests/gis_tests/geo3d/tests.py | 2 +- tests/gis_tests/geos_tests/test_geos.py | 24 ++-- tests/staticfiles_tests/test_management.py | 6 +- tests/utils_tests/test_jslex.py | 118 ++++++++++-------- 11 files changed, 94 insertions(+), 78 deletions(-) diff --git a/django/contrib/admin/options.py b/django/contrib/admin/options.py index 57c3d4f4d675..fbaaa849cb95 100644 --- a/django/contrib/admin/options.py +++ b/django/contrib/admin/options.py @@ -1066,7 +1066,7 @@ def message_user(self, request, message, level=messages.INFO, extra_tags='', level = getattr(messages.constants, level.upper()) except AttributeError: levels = messages.constants.DEFAULT_TAGS.values() - levels_repr = ', '.join('`%s`' % l for l in levels) + levels_repr = ', '.join('`%s`' % level for level in levels) raise ValueError( 'Bad message level string: `%s`. Possible values are: %s' % (level, levels_repr) diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 2f89ffbfc40b..a0e80acce9c7 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -172,8 +172,8 @@ def __setitem__(self, name, val): def set_payload(self, payload, charset=None): if charset == 'utf-8' and not isinstance(charset, Charset.Charset): has_long_lines = any( - len(l.encode()) > RFC5322_EMAIL_LINE_LENGTH_LIMIT - for l in payload.splitlines() + len(line.encode()) > RFC5322_EMAIL_LINE_LENGTH_LIMIT + for line in payload.splitlines() ) # Quoted-Printable encoding has the side effect of shortening long # lines, if any (#22561). diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py index 80537dd6acd5..5f43dca7e014 100644 --- a/django/core/management/commands/compilemessages.py +++ b/django/core/management/commands/compilemessages.py @@ -90,7 +90,7 @@ def handle(self, **options): self.has_errors = False for basedir in basedirs: if locales: - dirs = [os.path.join(basedir, l, 'LC_MESSAGES') for l in locales] + dirs = [os.path.join(basedir, locale, 'LC_MESSAGES') for locale in locales] else: dirs = [basedir] locations = [] diff --git a/django/forms/widgets.py b/django/forms/widgets.py index 8cd33c439aed..e37036c0c991 100644 --- a/django/forms/widgets.py +++ b/django/forms/widgets.py @@ -140,7 +140,7 @@ def merge(*lists): except CyclicDependencyError: warnings.warn( 'Detected duplicate Media files in an opposite order: {}'.format( - ', '.join(repr(l) for l in lists) + ', '.join(repr(list_) for list_ in lists) ), MediaOrderConflictWarning, ) return list(all_items) diff --git a/django/http/request.py b/django/http/request.py index 39298aac7723..c93b4d478218 100644 --- a/django/http/request.py +++ b/django/http/request.py @@ -327,7 +327,7 @@ def _load_post_and_files(self): def close(self): if hasattr(self, '_files'): - for f in chain.from_iterable(l[1] for l in self._files.lists()): + for f in chain.from_iterable(list_[1] for list_ in self._files.lists()): f.close() # File-like and iterator interface. diff --git a/django/utils/dateformat.py b/django/utils/dateformat.py index 29893fe6b96e..51d5853ecebf 100644 --- a/django/utils/dateformat.py +++ b/django/utils/dateformat.py @@ -123,7 +123,7 @@ def i(self): "Minutes; i.e. '00' to '59'" return '%02d' % self.data.minute - def O(self): # NOQA: E743 + def O(self): # NOQA: E743, E741 """ Difference to Greenwich time in hours; e.g. '+0200', '-0430'. @@ -237,7 +237,7 @@ def F(self): "Month, textual, long; e.g. 'January'" return MONTHS[self.data.month] - def I(self): # NOQA: E743 + def I(self): # NOQA: E743, E741 "'1' if Daylight Savings Time, '0' otherwise." try: if self.timezone and self.timezone.dst(self.data): @@ -254,7 +254,7 @@ def j(self): "Day of the month without leading zeros; i.e. '1' to '31'" return self.data.day - def l(self): # NOQA: E743 + def l(self): # NOQA: E743, E741 "Day of the week, textual, long; e.g. 'Friday'" return WEEKDAYS[self.data.weekday()] diff --git a/django/utils/topological_sort.py b/django/utils/topological_sort.py index 3f8ea0f2e4ef..f7ce0e0d1dc1 100644 --- a/django/utils/topological_sort.py +++ b/django/utils/topological_sort.py @@ -27,10 +27,10 @@ def topological_sort_as_sets(dependency_graph): todo.items() if node not in current} -def stable_topological_sort(l, dependency_graph): +def stable_topological_sort(nodes, dependency_graph): result = [] for layer in topological_sort_as_sets(dependency_graph): - for node in l: + for node in nodes: if node in layer: result.append(node) return result diff --git a/tests/gis_tests/geo3d/tests.py b/tests/gis_tests/geo3d/tests.py index d2e85f060704..d8a788ef4e76 100644 --- a/tests/gis_tests/geo3d/tests.py +++ b/tests/gis_tests/geo3d/tests.py @@ -71,7 +71,7 @@ def _load_interstate_data(self): # Interstate (2D / 3D and Geographic/Projected variants) for name, line, exp_z in interstate_data: line_3d = GEOSGeometry(line, srid=4269) - line_2d = LineString([l[:2] for l in line_3d.coords], srid=4269) + line_2d = LineString([coord[:2] for coord in line_3d.coords], srid=4269) # Creating a geographic and projected version of the # interstate in both 2D and 3D. diff --git a/tests/gis_tests/geos_tests/test_geos.py b/tests/gis_tests/geos_tests/test_geos.py index 6818339a9b14..ac08ffedec7f 100644 --- a/tests/gis_tests/geos_tests/test_geos.py +++ b/tests/gis_tests/geos_tests/test_geos.py @@ -304,19 +304,19 @@ def test_multipoints(self): def test_linestring(self): "Testing LineString objects." prev = fromstr('POINT(0 0)') - for l in self.geometries.linestrings: - ls = fromstr(l.wkt) + for line in self.geometries.linestrings: + ls = fromstr(line.wkt) self.assertEqual(ls.geom_type, 'LineString') self.assertEqual(ls.geom_typeid, 1) self.assertEqual(ls.dims, 1) self.assertIs(ls.empty, False) self.assertIs(ls.ring, False) - if hasattr(l, 'centroid'): - self.assertEqual(l.centroid, ls.centroid.tuple) - if hasattr(l, 'tup'): - self.assertEqual(l.tup, ls.tuple) + if hasattr(line, 'centroid'): + self.assertEqual(line.centroid, ls.centroid.tuple) + if hasattr(line, 'tup'): + self.assertEqual(line.tup, ls.tuple) - self.assertEqual(ls, fromstr(l.wkt)) + self.assertEqual(ls, fromstr(line.wkt)) self.assertEqual(False, ls == prev) # Use assertEqual to test __eq__ with self.assertRaises(IndexError): ls.__getitem__(len(ls)) @@ -351,16 +351,16 @@ def test_linestring(self): def test_multilinestring(self): "Testing MultiLineString objects." prev = fromstr('POINT(0 0)') - for l in self.geometries.multilinestrings: - ml = fromstr(l.wkt) + for line in self.geometries.multilinestrings: + ml = fromstr(line.wkt) self.assertEqual(ml.geom_type, 'MultiLineString') self.assertEqual(ml.geom_typeid, 5) self.assertEqual(ml.dims, 1) - self.assertAlmostEqual(l.centroid[0], ml.centroid.x, 9) - self.assertAlmostEqual(l.centroid[1], ml.centroid.y, 9) + self.assertAlmostEqual(line.centroid[0], ml.centroid.x, 9) + self.assertAlmostEqual(line.centroid[1], ml.centroid.y, 9) - self.assertEqual(ml, fromstr(l.wkt)) + self.assertEqual(ml, fromstr(line.wkt)) self.assertEqual(False, ml == prev) # Use assertEqual to test __eq__ prev = ml diff --git a/tests/staticfiles_tests/test_management.py b/tests/staticfiles_tests/test_management.py index 1fdeaf78cc79..9006f2a23d61 100644 --- a/tests/staticfiles_tests/test_management.py +++ b/tests/staticfiles_tests/test_management.py @@ -71,7 +71,7 @@ def test_all_files(self): findstatic returns all candidate files if run without --first and -v1. """ result = call_command('findstatic', 'test/file.txt', verbosity=1, stdout=StringIO()) - lines = [l.strip() for l in result.split('\n')] + lines = [line.strip() for line in result.split('\n')] self.assertEqual(len(lines), 3) # three because there is also the "Found here" line self.assertIn('project', lines[1]) self.assertIn('apps', lines[2]) @@ -81,7 +81,7 @@ def test_all_files_less_verbose(self): findstatic returns all candidate files if run without --first and -v0. """ result = call_command('findstatic', 'test/file.txt', verbosity=0, stdout=StringIO()) - lines = [l.strip() for l in result.split('\n')] + lines = [line.strip() for line in result.split('\n')] self.assertEqual(len(lines), 2) self.assertIn('project', lines[0]) self.assertIn('apps', lines[1]) @@ -92,7 +92,7 @@ def test_all_files_more_verbose(self): Also, test that findstatic returns the searched locations with -v2. """ result = call_command('findstatic', 'test/file.txt', verbosity=2, stdout=StringIO()) - lines = [l.strip() for l in result.split('\n')] + lines = [line.strip() for line in result.split('\n')] self.assertIn('project', lines[1]) self.assertIn('apps', lines[2]) self.assertIn("Looking in the following locations:", lines[3]) diff --git a/tests/utils_tests/test_jslex.py b/tests/utils_tests/test_jslex.py index bf737b8fd286..0afb32918806 100644 --- a/tests/utils_tests/test_jslex.py +++ b/tests/utils_tests/test_jslex.py @@ -42,17 +42,29 @@ class JsTokensTest(SimpleTestCase): (r"a=/\//,1", ["id a", "punct =", r"regex /\//", "punct ,", "dnum 1"]), # next two are from https://www-archive.mozilla.org/js/language/js20-2002-04/rationale/syntax.html#regular-expressions # NOQA - ("""for (var x = a in foo && "" || mot ? z:/x:3;x<5;y"', "punct ||", "id mot", "punct ?", "id z", - "punct :", "regex /x:3;x<5;y" || mot ? z/x:3;x<5;y"', "punct ||", "id mot", "punct ?", "id z", - "punct /", "id x", "punct :", "dnum 3", "punct ;", "id x", "punct <", "dnum 5", - "punct ;", "id y", "punct <", "regex /g/i", "punct )", "punct {", - "id xyz", "punct (", "id x", "punct ++", "punct )", "punct ;", "punct }"]), + ( + """for (var x = a in foo && "" || mot ? z:/x:3;x<5;y"', + "punct ||", "id mot", "punct ?", "id z", "punct :", + "regex /x:3;x<5;y" || mot ? z/x:3;x<5;y"', + "punct ||", "id mot", "punct ?", "id z", "punct /", "id x", + "punct :", "dnum 3", "punct ;", "id x", "punct <", "dnum 5", + "punct ;", "id y", "punct <", "regex /g/i", "punct )", + "punct {", "id xyz", "punct (", "id x", "punct ++", "punct )", + "punct ;", "punct }", + ], + ), # Various "illegal" regexes that are valid according to the std. (r"""/????/, /++++/, /[----]/ """, ["regex /????/", "punct ,", "regex /++++/", "punct ,", "regex /[----]/"]), @@ -65,46 +77,50 @@ class JsTokensTest(SimpleTestCase): (r"""/a[\]]b/""", [r"""regex /a[\]]b/"""]), (r"""/[\]/]/gi""", [r"""regex /[\]/]/gi"""]), (r"""/\[[^\]]+\]/gi""", [r"""regex /\[[^\]]+\]/gi"""]), - (r""" - rexl.re = { - NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, - UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, - QUOTED_LITERAL: /^'(?:[^']|'')*'/, - NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, - SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ - }; - """, # NOQA - ["id rexl", "punct .", "id re", "punct =", "punct {", - "id NAME", "punct :", r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", - "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", - "punct ,", - "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", - "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", - "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA - "punct }", "punct ;" - ]), - - (r""" - rexl.re = { - NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, - UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, - QUOTED_LITERAL: /^'(?:[^']|'')*'/, - NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, - SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ - }; - str = '"'; - """, # NOQA - ["id rexl", "punct .", "id re", "punct =", "punct {", - "id NAME", "punct :", r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", - "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", - "punct ,", - "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", - "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", - "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA - "punct }", "punct ;", - "id str", "punct =", """string '"'""", "punct ;", - ]), - + ( + r""" + rexl.re = { + NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + """, # NOQA + [ + "id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", + "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA + "punct }", "punct ;" + ], + ), + ( + r""" + rexl.re = { + NAME: /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/, + UNQUOTED_LITERAL: /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/, + QUOTED_LITERAL: /^'(?:[^']|'')*'/, + NUMERIC_LITERAL: /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/, + SYMBOL: /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/ + }; + str = '"'; + """, # NOQA + [ + "id rexl", "punct .", "id re", "punct =", "punct {", + "id NAME", "punct :", r"""regex /^(?![0-9])(?:\w)+|^"(?:[^"]|"")+"/""", "punct ,", + "id UNQUOTED_LITERAL", "punct :", r"""regex /^@(?:(?![0-9])(?:\w|\:)+|^"(?:[^"]|"")+")\[[^\]]+\]/""", + "punct ,", + "id QUOTED_LITERAL", "punct :", r"""regex /^'(?:[^']|'')*'/""", "punct ,", + "id NUMERIC_LITERAL", "punct :", r"""regex /^[0-9]+(?:\.[0-9]*(?:[eE][-+][0-9]+)?)?/""", "punct ,", + "id SYMBOL", "punct :", r"""regex /^(?:==|=|<>|<=|<|>=|>|!~~|!~|~~|~|!==|!=|!~=|!~|!|&|\||\.|\:|,|\(|\)|\[|\]|\{|\}|\?|\:|;|@|\^|\/\+|\/|\*|\+|-)/""", # NOQA + "punct }", "punct ;", + "id str", "punct =", """string '"'""", "punct ;", + ], + ), (r""" this._js = "e.str(\"" + this.value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"") + "\")"; """, ["keyword this", "punct .", "id _js", "punct =", r'''string "e.str(\""''', "punct +", "keyword this", "punct .", "id value", "punct .", "id replace", "punct (", r"regex /\\/g", "punct ,", r'string "\\\\"', From 2b69680264aabb94661b4f67a8e70d522070dc2a Mon Sep 17 00:00:00 2001 From: Carlton Gibson Date: Tue, 2 Jun 2020 14:35:54 +0200 Subject: [PATCH 210/287] [2.2.x] Refs #31485 -- Backported jQuery upgrade to 3.5.1. --- .../static/admin/js/vendor/jquery/LICENSE.txt | 8 +- .../static/admin/js/vendor/jquery/jquery.js | 1789 +++++++++++------ .../admin/js/vendor/jquery/jquery.min.js | 4 +- docs/ref/contrib/admin/index.txt | 4 + docs/releases/2.2.13.txt | 3 + 5 files changed, 1158 insertions(+), 650 deletions(-) diff --git a/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt b/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt index d930e62ac149..e3dbacb999ce 100644 --- a/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt +++ b/django/contrib/admin/static/admin/js/vendor/jquery/LICENSE.txt @@ -1,10 +1,4 @@ -Copyright jQuery Foundation and other contributors, https://jquery.org/ - -This software consists of voluntary contributions made by many -individuals. For exact contribution history, see the revision history -available at https://github.com/jquery/jquery - -==== +Copyright JS Foundation and other contributors, https://js.foundation/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js index 34a5703d80fb..50937333b99a 100644 --- a/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js +++ b/django/contrib/admin/static/admin/js/vendor/jquery/jquery.js @@ -1,5 +1,5 @@ /*! - * jQuery JavaScript Library v3.3.1 + * jQuery JavaScript Library v3.5.1 * https://jquery.com/ * * Includes Sizzle.js @@ -9,7 +9,7 @@ * Released under the MIT license * https://jquery.org/license * - * Date: 2018-01-20T17:24Z + * Date: 2020-05-04T22:49Z */ ( function( global, factory ) { @@ -47,13 +47,16 @@ var arr = []; -var document = window.document; - var getProto = Object.getPrototypeOf; var slice = arr.slice; -var concat = arr.concat; +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + var push = arr.push; @@ -86,25 +89,40 @@ var isWindow = function isWindow( obj ) { }; +var document = window.document; + var preservedScriptAttributes = { type: true, src: true, + nonce: true, noModule: true }; - function DOMEval( code, doc, node ) { + function DOMEval( code, node, doc ) { doc = doc || document; - var i, + var i, val, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { - if ( node[ i ] ) { - script[ i ] = node[ i ]; + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); } } } @@ -129,7 +147,7 @@ function toType( obj ) { var - version = "3.3.1", + version = "3.5.1", // Define a local copy of jQuery jQuery = function( selector, context ) { @@ -137,11 +155,7 @@ var // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); - }, - - // Support: Android <=4.0 only - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; + }; jQuery.fn = jQuery.prototype = { @@ -207,6 +221,18 @@ jQuery.fn = jQuery.prototype = { return this.eq( -1 ); }, + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); @@ -258,7 +284,6 @@ jQuery.extend = jQuery.fn.extend = function() { // Extend the base object for ( name in options ) { - src = target[ name ]; copy = options[ name ]; // Prevent Object.prototype pollution @@ -270,14 +295,17 @@ jQuery.extend = jQuery.fn.extend = function() { // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; - if ( copyIsArray ) { - copyIsArray = false; - clone = src && Array.isArray( src ) ? src : []; - + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; } else { - clone = src && jQuery.isPlainObject( src ) ? src : {}; + clone = src; } + copyIsArray = false; // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); @@ -330,9 +358,6 @@ jQuery.extend( { }, isEmptyObject: function( obj ) { - - /* eslint-disable no-unused-vars */ - // See https://github.com/eslint/eslint/issues/6125 var name; for ( name in obj ) { @@ -341,9 +366,10 @@ jQuery.extend( { return true; }, - // Evaluates a script in a global context - globalEval: function( code ) { - DOMEval( code ); + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); }, each: function( obj, callback ) { @@ -367,13 +393,6 @@ jQuery.extend( { return obj; }, - // Support: Android <=4.0 only - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; @@ -460,7 +479,7 @@ jQuery.extend( { } // Flatten any nested arrays - return concat.apply( [], ret ); + return flat( ret ); }, // A global GUID counter for objects @@ -477,7 +496,7 @@ if ( typeof Symbol === "function" ) { // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), -function( i, name ) { +function( _i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); @@ -499,17 +518,16 @@ function isArrayLike( obj ) { } var Sizzle = /*! - * Sizzle CSS Selector Engine v2.3.3 + * Sizzle CSS Selector Engine v2.3.5 * https://sizzlejs.com/ * - * Copyright jQuery Foundation and other contributors + * Copyright JS Foundation and other contributors * Released under the MIT license - * http://jquery.org/license + * https://js.foundation/ * - * Date: 2016-08-08 + * Date: 2020-03-14 */ -(function( window ) { - +( function( window ) { var i, support, Expr, @@ -540,6 +558,7 @@ var i, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), + nonnativeSelectorCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; @@ -548,61 +567,71 @@ var i, }, // Instance methods - hasOwn = ({}).hasOwnProperty, + hasOwn = ( {} ).hasOwnProperty, arr = [], pop = arr.pop, - push_native = arr.push, + pushNative = arr.push, push = arr.push, slice = arr.slice, + // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { - if ( list[i] === elem ) { + if ( list[ i ] === elem ) { return i; } } return -1; }, - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), @@ -613,16 +642,19 @@ var i, "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + // For use in libraries implementing .is() // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, + rhtml = /HTML$/i, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, @@ -635,18 +667,21 @@ var i, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair high < 0 ? - // BMP codepoint String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, @@ -662,7 +697,8 @@ var i, } // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped @@ -677,9 +713,9 @@ var i, setDocument(); }, - disabledAncestor = addCombinator( + inDisabledFieldset = addCombinator( function( elem ) { - return elem.disabled === true && ("form" in elem || "label" in elem); + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; }, { dir: "parentNode", next: "legend" } ); @@ -687,18 +723,20 @@ var i, // Optimize for push.apply( _, NodeList ) try { push.apply( - (arr = slice.call( preferredDoc.childNodes )), + ( arr = slice.call( preferredDoc.childNodes ) ), preferredDoc.childNodes ); + // Support: Android<4.0 // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { - push_native.apply( target, slice.call(els) ); + pushNative.apply( target, slice.call( els ) ); } : // Support: IE<9 @@ -706,8 +744,9 @@ try { function( target, els ) { var j = target.length, i = 0; + // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} + while ( ( target[ j++ ] = els[ i++ ] ) ) {} target.length = j - 1; } }; @@ -731,24 +770,21 @@ function Sizzle( selector, context, results, seed ) { // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } + setDocument( context ); context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { // ID selector - if ( (m = match[1]) ) { + if ( ( m = match[ 1 ] ) ) { // Document context if ( nodeType === 9 ) { - if ( (elem = context.getElementById( m )) ) { + if ( ( elem = context.getElementById( m ) ) ) { // Support: IE, Opera, Webkit // TODO: identify versions @@ -767,7 +803,7 @@ function Sizzle( selector, context, results, seed ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID - if ( newContext && (elem = newContext.getElementById( m )) && + if ( newContext && ( elem = newContext.getElementById( m ) ) && contains( context, elem ) && elem.id === m ) { @@ -777,12 +813,12 @@ function Sizzle( selector, context, results, seed ) { } // Type selector - } else if ( match[2] ) { + } else if ( match[ 2 ] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector - } else if ( (m = match[3]) && support.getElementsByClassName && + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); @@ -792,50 +828,62 @@ function Sizzle( selector, context, results, seed ) { // Take advantage of querySelectorAll if ( support.qsa && - !compilerCache[ selector + " " ] && - (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - - if ( nodeType !== 1 ) { - newContext = context; - newSelector = selector; + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && - // qSA looks outside Element context, which is not what we want - // Thanks to Andrew Dupont for this workaround technique - // Support: IE <=8 + // Support: IE 8 only // Exclude object elements - } else if ( context.nodeName.toLowerCase() !== "object" ) { + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { - // Capture the context ID, setting it first if necessary - if ( (nid = context.getAttribute( "id" )) ) { - nid = nid.replace( rcssescape, fcssescape ); - } else { - context.setAttribute( "id", (nid = expando) ); + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { - groups[i] = "#" + nid + " " + toSelector( groups[i] ); + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); } newSelector = groups.join( "," ); - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; } - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); } } } @@ -856,12 +904,14 @@ function createCache() { var keys = []; function cache( key, value ) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { + // Only keep the most recent entries delete cache[ keys.shift() ]; } - return (cache[ key + " " ] = value); + return ( cache[ key + " " ] = value ); } return cache; } @@ -880,17 +930,19 @@ function markFunction( fn ) { * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { - var el = document.createElement("fieldset"); + var el = document.createElement( "fieldset" ); try { return !!fn( el ); - } catch (e) { + } catch ( e ) { return false; } finally { + // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } + // release memory in IE el = null; } @@ -902,11 +954,11 @@ function assert( fn ) { * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { - var arr = attrs.split("|"), + var arr = attrs.split( "|" ), i = arr.length; while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; + Expr.attrHandle[ arr[ i ] ] = handler; } } @@ -928,7 +980,7 @@ function siblingCheck( a, b ) { // Check if b follows a if ( cur ) { - while ( (cur = cur.nextSibling) ) { + while ( ( cur = cur.nextSibling ) ) { if ( cur === b ) { return -1; } @@ -956,7 +1008,7 @@ function createInputPseudo( type ) { function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; + return ( name === "input" || name === "button" ) && elem.type === type; }; } @@ -999,7 +1051,7 @@ function createDisabledPseudo( disabled ) { // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && - disabledAncestor( elem ) === disabled; + inDisabledFieldset( elem ) === disabled; } return elem.disabled === disabled; @@ -1021,21 +1073,21 @@ function createDisabledPseudo( disabled ) { * @param {Function} fn */ function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { + return markFunction( function( argument ) { argument = +argument; - return markFunction(function( seed, matches ) { + return markFunction( function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); } } - }); - }); + } ); + } ); } /** @@ -1056,10 +1108,13 @@ support = Sizzle.support = {}; * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); }; /** @@ -1072,7 +1127,11 @@ setDocument = Sizzle.setDocument = function( node ) { doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } @@ -1081,10 +1140,14 @@ setDocument = Sizzle.setDocument = function( node ) { docElem = document.documentElement; documentIsHTML = !isXML( document ); - // Support: IE 9-11, Edge + // Support: IE 9 - 11+, Edge 12 - 18+ // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) - if ( preferredDoc !== document && - (subWindow = document.defaultView) && subWindow.top !== subWindow ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { @@ -1096,25 +1159,36 @@ setDocument = Sizzle.setDocument = function( node ) { } } + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) - support.attributes = assert(function( el ) { + support.attributes = assert( function( el ) { el.className = "i"; - return !el.getAttribute("className"); - }); + return !el.getAttribute( "className" ); + } ); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( el ) { - el.appendChild( document.createComment("") ); - return !el.getElementsByTagName("*").length; - }); + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); @@ -1123,38 +1197,38 @@ setDocument = Sizzle.setDocument = function( node ) { // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test - support.getById = assert(function( el ) { + support.getById = assert( function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; - }); + } ); // ID filter and find if ( support.getById ) { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { - return elem.getAttribute("id") === attrId; + return elem.getAttribute( "id" ) === attrId; }; }; - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { - Expr.filter["ID"] = function( id ) { + Expr.filter[ "ID" ] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode("id"); + elem.getAttributeNode( "id" ); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut - Expr.find["ID"] = function( id, context ) { + Expr.find[ "ID" ] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); @@ -1162,7 +1236,7 @@ setDocument = Sizzle.setDocument = function( node ) { if ( elem ) { // Verify the id attribute - node = elem.getAttributeNode("id"); + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1170,8 +1244,8 @@ setDocument = Sizzle.setDocument = function( node ) { // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; - while ( (elem = elems[i++]) ) { - node = elem.getAttributeNode("id"); + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); if ( node && node.value === id ) { return [ elem ]; } @@ -1184,7 +1258,7 @@ setDocument = Sizzle.setDocument = function( node ) { } // Tag - Expr.find["TAG"] = support.getElementsByTagName ? + Expr.find[ "TAG" ] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); @@ -1199,12 +1273,13 @@ setDocument = Sizzle.setDocument = function( node ) { var elem, tmp = [], i = 0, + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } @@ -1216,7 +1291,7 @@ setDocument = Sizzle.setDocument = function( node ) { }; // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } @@ -1237,10 +1312,14 @@ setDocument = Sizzle.setDocument = function( node ) { // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; - if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + // Build QSA regex // Regex strategy adopted from Diego Perini - assert(function( el ) { + assert( function( el ) { + + var input; + // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, @@ -1254,78 +1333,98 @@ setDocument = Sizzle.setDocument = function( node ) { // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( el.querySelectorAll("[msallowcapture^='']").length ) { + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly - if ( !el.querySelectorAll("[selected]").length ) { + if ( !el.querySelectorAll( "[selected]" ).length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push("~="); + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests - if ( !el.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push(".#.+[+~]"); + rbuggyQSA.push( ".#.+[+~]" ); } - }); - assert(function( el ) { + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { el.innerHTML = "" + ""; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment - var input = document.createElement("input"); + var input = document.createElement( "input" ); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute - if ( el.querySelectorAll("[name=d]").length ) { + if ( el.querySelectorAll( "[name=d]" ).length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests - if ( el.querySelectorAll(":enabled").length !== 2 ) { + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; - if ( el.querySelectorAll(":disabled").length !== 2 ) { + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } + // Support: Opera 10 - 11 only // Opera 10-11 does not throw on post-comma invalid pseudos - el.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); } - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { - assert(function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); @@ -1334,11 +1433,11 @@ setDocument = Sizzle.setDocument = function( node ) { // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); - }); + } ); } - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); /* Contains ---------------------------------------------------------------------- */ @@ -1355,11 +1454,11 @@ setDocument = Sizzle.setDocument = function( node ) { adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); + ) ); } : function( a, b ) { if ( b ) { - while ( (b = b.parentNode) ) { + while ( ( b = b.parentNode ) ) { if ( b === a ) { return true; } @@ -1388,7 +1487,11 @@ setDocument = Sizzle.setDocument = function( node ) { } // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected @@ -1396,13 +1499,24 @@ setDocument = Sizzle.setDocument = function( node ) { // Disconnected nodes if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { // Choose the first element that is related to our preferred document - if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { return -1; } - if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { return 1; } @@ -1415,6 +1529,7 @@ setDocument = Sizzle.setDocument = function( node ) { return compare & 4 ? -1 : 1; } : function( a, b ) { + // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; @@ -1430,8 +1545,14 @@ setDocument = Sizzle.setDocument = function( node ) { // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { - return a === document ? -1 : - b === document ? 1 : + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ aup ? -1 : bup ? 1 : sortInput ? @@ -1445,26 +1566,32 @@ setDocument = Sizzle.setDocument = function( node ) { // Otherwise we need full lists of their ancestors for comparison cur = a; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { ap.unshift( cur ); } cur = b; - while ( (cur = cur.parentNode) ) { + while ( ( cur = cur.parentNode ) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { + while ( ap[ i ] === bp[ i ] ) { i++; } return i ? + // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : + siblingCheck( ap[ i ], bp[ i ] ) : // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ 0; }; @@ -1476,16 +1603,10 @@ Sizzle.matches = function( expr, elements ) { }; Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); + setDocument( elem ); if ( support.matchesSelector && documentIsHTML && - !compilerCache[ expr + " " ] && + !nonnativeSelectorCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { @@ -1494,32 +1615,46 @@ Sizzle.matchesSelector = function( elem, expr ) { // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { return ret; } - } catch (e) {} + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { + // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { + // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], + // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : @@ -1529,13 +1664,13 @@ Sizzle.attr = function( elem, name ) { val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { - return (sel + "").replace( rcssescape, fcssescape ); + return ( sel + "" ).replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { @@ -1558,7 +1693,7 @@ Sizzle.uniqueSort = function( results ) { results.sort( sortOrder ); if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { + while ( ( elem = results[ i++ ] ) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } @@ -1586,17 +1721,21 @@ getText = Sizzle.getText = function( elem ) { nodeType = elem.nodeType; if ( !nodeType ) { + // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { + while ( ( node = elem[ i++ ] ) ) { + // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { + // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); @@ -1605,6 +1744,7 @@ getText = Sizzle.getText = function( elem ) { } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } + // Do not include comment or processing instruction nodes return ret; @@ -1632,19 +1772,21 @@ Expr = Sizzle.selectors = { preFilter: { "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { + /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) @@ -1655,22 +1797,25 @@ Expr = Sizzle.selectors = { 7 sign of y-component 8 y of y-component */ - match[1] = match[1].toLowerCase(); + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); } return match; @@ -1678,26 +1823,28 @@ Expr = Sizzle.selectors = { "PSEUDO": function( match ) { var excess, - unquoted = !match[6] && match[2]; + unquoted = !match[ 6 ] && match[ 2 ]; - if ( matchExpr["CHILD"].test( match[0] ) ) { + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { return null; } // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && + // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && + ( excess = tokenize( unquoted, true ) ) && + // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) @@ -1710,7 +1857,9 @@ Expr = Sizzle.selectors = { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? - function() { return true; } : + function() { + return true; + } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; @@ -1720,10 +1869,16 @@ Expr = Sizzle.selectors = { var pattern = classCache[ className + " " ]; return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); - }); + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); }, "ATTR": function( name, operator, check ) { @@ -1739,6 +1894,8 @@ Expr = Sizzle.selectors = { result += ""; + /* eslint-disable max-len */ + return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : @@ -1747,10 +1904,12 @@ Expr = Sizzle.selectors = { operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; + /* eslint-enable max-len */ + }; }, - "CHILD": function( type, what, argument, first, last ) { + "CHILD": function( type, what, _argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; @@ -1762,7 +1921,7 @@ Expr = Sizzle.selectors = { return !!elem.parentNode; } : - function( elem, context, xml ) { + function( elem, _context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, @@ -1776,7 +1935,7 @@ Expr = Sizzle.selectors = { if ( simple ) { while ( dir ) { node = elem; - while ( (node = node[ dir ]) ) { + while ( ( node = node[ dir ] ) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { @@ -1784,6 +1943,7 @@ Expr = Sizzle.selectors = { return false; } } + // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } @@ -1799,22 +1959,22 @@ Expr = Sizzle.selectors = { // ...in a gzip-friendly way node = parent; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; - while ( (node = ++nodeIndex && node && node[ dir ] || + while ( ( node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { + ( diff = nodeIndex = 0 ) || start.pop() ) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { @@ -1824,16 +1984,18 @@ Expr = Sizzle.selectors = { } } else { + // Use previously-cached element index if available if ( useCache ) { + // ...in a gzip-friendly way node = elem; - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; @@ -1843,9 +2005,10 @@ Expr = Sizzle.selectors = { // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { + // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : @@ -1854,12 +2017,13 @@ Expr = Sizzle.selectors = { // Cache the index of each encountered element if ( useCache ) { - outerCache = node[ expando ] || (node[ expando ] = {}); + outerCache = node[ expando ] || + ( node[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || - (outerCache[ node.uniqueID ] = {}); + ( outerCache[ node.uniqueID ] = {} ); uniqueCache[ type ] = [ dirruns, diff ]; } @@ -1880,6 +2044,7 @@ Expr = Sizzle.selectors = { }, "PSEUDO": function( pseudo, argument ) { + // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters @@ -1899,15 +2064,15 @@ Expr = Sizzle.selectors = { if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { + markFunction( function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { - idx = indexOf( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); } - }) : + } ) : function( elem ) { return fn( elem, 0, args ); }; @@ -1918,8 +2083,10 @@ Expr = Sizzle.selectors = { }, pseudos: { + // Potentially complex pseudos - "not": markFunction(function( selector ) { + "not": markFunction( function( selector ) { + // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators @@ -1928,39 +2095,40 @@ Expr = Sizzle.selectors = { matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { + markFunction( function( seed, matches, _context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); } } - }) : - function( elem, context, xml ) { - input[0] = elem; + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; matcher( input, null, xml, results ); + // Don't keep the element (issue #299) - input[0] = null; + input[ 0 ] = null; return !results.pop(); }; - }), + } ), - "has": markFunction(function( selector ) { + "has": markFunction( function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; - }), + } ), - "contains": markFunction(function( text ) { + "contains": markFunction( function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; }; - }), + } ), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value @@ -1970,25 +2138,26 @@ Expr = Sizzle.selectors = { // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { + // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { + if ( !ridentifier.test( lang || "" ) ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { - if ( (elemLang = documentIsHTML ? + if ( ( elemLang = documentIsHTML ? elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); return false; }; - }), + } ), // Miscellaneous "target": function( elem ) { @@ -2001,7 +2170,9 @@ Expr = Sizzle.selectors = { }, "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); }, // Boolean properties @@ -2009,16 +2180,20 @@ Expr = Sizzle.selectors = { "disabled": createDisabledPseudo( true ), "checked": function( elem ) { + // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); }, "selected": function( elem ) { + // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions elem.parentNode.selectedIndex; } @@ -2027,6 +2202,7 @@ Expr = Sizzle.selectors = { // Contents "empty": function( elem ) { + // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) @@ -2040,7 +2216,7 @@ Expr = Sizzle.selectors = { }, "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); + return !Expr.pseudos[ "empty" ]( elem ); }, // Element/input types @@ -2064,57 +2240,62 @@ Expr = Sizzle.selectors = { // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); }, // Position-in-collection - "first": createPositionalPseudo(function() { + "first": createPositionalPseudo( function() { return [ 0 ]; - }), + } ), - "last": createPositionalPseudo(function( matchIndexes, length ) { + "last": createPositionalPseudo( function( _matchIndexes, length ) { return [ length - 1 ]; - }), + } ), - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; - }), + } ), - "even": createPositionalPseudo(function( matchIndexes, length ) { + "even": createPositionalPseudo( function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "odd": createPositionalPseudo(function( matchIndexes, length ) { + "odd": createPositionalPseudo( function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; - }), + } ), - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; - }) + } ) } }; -Expr.pseudos["nth"] = Expr.pseudos["eq"]; +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { @@ -2145,37 +2326,39 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { while ( soFar ) { // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { if ( match ) { + // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; + soFar = soFar.slice( match[ 0 ].length ) || soFar; } - groups.push( (tokens = []) ); + groups.push( ( tokens = [] ) ); } matched = false; // Combinators - if ( (match = rcombinators.exec( soFar )) ) { + if ( ( match = rcombinators.exec( soFar ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, + // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); + type: match[ 0 ].replace( rtrim, " " ) + } ); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { matched = match.shift(); - tokens.push({ + tokens.push( { value: matched, type: type, matches: match - }); + } ); soFar = soFar.slice( matched.length ); } } @@ -2192,6 +2375,7 @@ tokenize = Sizzle.tokenize = function( selector, parseOnly ) { soFar.length : soFar ? Sizzle.error( selector ) : + // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; @@ -2201,7 +2385,7 @@ function toSelector( tokens ) { len = tokens.length, selector = ""; for ( ; i < len; i++ ) { - selector += tokens[i].value; + selector += tokens[ i ].value; } return selector; } @@ -2214,9 +2398,10 @@ function addCombinator( matcher, combinator, base ) { doneName = done++; return combinator.first ? + // Check against closest ancestor/preceding element function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } @@ -2231,7 +2416,7 @@ function addCombinator( matcher, combinator, base ) { // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; @@ -2239,27 +2424,29 @@ function addCombinator( matcher, combinator, base ) { } } } else { - while ( (elem = elem[ dir ]) ) { + while ( ( elem = elem[ dir ] ) ) { if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) - uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; - } else if ( (oldCache = uniqueCache[ key ]) && + } else if ( ( oldCache = uniqueCache[ key ] ) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); + return ( newCache[ 2 ] = oldCache[ 2 ] ); } else { + // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { return true; } } @@ -2275,20 +2462,20 @@ function elementMatcher( matchers ) { function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { + if ( !matchers[ i ]( elem, context, xml ) ) { return false; } } return true; } : - matchers[0]; + matchers[ 0 ]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); + Sizzle( selector, contexts[ i ], results ); } return results; } @@ -2301,7 +2488,7 @@ function condense( unmatched, map, filter, context, xml ) { mapped = map != null; for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { + if ( ( elem = unmatched[ i ] ) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { @@ -2321,14 +2508,18 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } - return markFunction(function( seed, results, context, xml ) { + return markFunction( function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? @@ -2336,6 +2527,7 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS elems, matcherOut = matcher ? + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? @@ -2359,8 +2551,8 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); } } } @@ -2368,25 +2560,27 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) ) { + if ( ( elem = matcherOut[ i ] ) ) { + // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); + temp.push( ( matcherIn[ i ] = elem ) ); } } - postFinder( null, (matcherOut = []), temp, xml ); + postFinder( null, ( matcherOut = [] ), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { - seed[temp] = !(results[temp] = elem); + seed[ temp ] = !( results[ temp ] = elem ); } } } @@ -2404,14 +2598,14 @@ function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postS push.apply( results, matcherOut ); } } - }); + } ); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) @@ -2423,38 +2617,43 @@ function matcherFromTokens( tokens ) { }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? + ( checkContext = context ).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); + // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { + // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { + if ( Expr.relative[ tokens[ j ].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), j < len && toSelector( tokens ) ); } @@ -2475,28 +2674,40 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { unmatched = seed && [], setMatched = [], contextBackup = outermostContext, + // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), len = elems.length; if ( outermost ) { - outermostContext = context === document || context || outermost; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { if ( byElement && elem ) { j = 0; - if ( !context && elem.ownerDocument !== document ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { setDocument( elem ); xml = !documentIsHTML; } - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context || document, xml) ) { + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { results.push( elem ); break; } @@ -2508,8 +2719,9 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // Track unmatched elements for set filters if ( bySet ) { + // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { + if ( ( elem = !matcher && elem ) ) { matchedCount--; } @@ -2533,16 +2745,17 @@ function matcherFromGroupMatchers( elementMatchers, setMatchers ) { // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; - while ( (matcher = setMatchers[j++]) ) { + while ( ( matcher = setMatchers[ j++ ] ) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { + // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); } } } @@ -2583,13 +2796,14 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { cached = compilerCache[ selector + " " ]; if ( !cached ) { + // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { - cached = matcherFromTokens( match[i] ); + cached = matcherFromTokens( match[ i ] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { @@ -2598,7 +2812,10 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { } // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); // Save selector and tokenization cached.selector = selector; @@ -2618,7 +2835,7 @@ compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); results = results || []; @@ -2627,11 +2844,12 @@ select = Sizzle.select = function( selector, context, results, seed ) { if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; if ( !context ) { return results; @@ -2644,20 +2862,22 @@ select = Sizzle.select = function( selector, context, results, seed ) { } // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; while ( i-- ) { - token = tokens[i]; + token = tokens[ i ]; // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { + if ( Expr.relative[ ( type = token.type ) ] ) { break; } - if ( (find = Expr.find[ type ]) ) { + if ( ( find = Expr.find[ type ] ) ) { + // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); @@ -2688,7 +2908,7 @@ select = Sizzle.select = function( selector, context, results, seed ) { // One-time assignments // Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function @@ -2699,58 +2919,59 @@ setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( el ) { +support.sortDetached = assert( function( el ) { + // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; -}); + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( el ) { +if ( !assert( function( el ) { el.innerHTML = ""; - return el.firstChild.getAttribute("href") === "#" ; -}) ) { + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } - }); + } ); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( el ) { +if ( !support.attributes || !assert( function( el ) { el.innerHTML = ""; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } - }); + } ); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( el ) { - return el.getAttribute("disabled") == null; -}) ) { +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? + ( val = elem.getAttributeNode( name ) ) && val.specified ? val.value : - null; + null; } - }); + } ); } return Sizzle; -})( window ); +} )( window ); @@ -3119,7 +3340,7 @@ jQuery.each( { parents: function( elem ) { return dir( elem, "parentNode" ); }, - parentsUntil: function( elem, i, until ) { + parentsUntil: function( elem, _i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { @@ -3134,10 +3355,10 @@ jQuery.each( { prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, - nextUntil: function( elem, i, until ) { + nextUntil: function( elem, _i, until ) { return dir( elem, "nextSibling", until ); }, - prevUntil: function( elem, i, until ) { + prevUntil: function( elem, _i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { @@ -3147,18 +3368,24 @@ jQuery.each( { return siblings( elem.firstChild ); }, contents: function( elem ) { - if ( nodeName( elem, "iframe" ) ) { - return elem.contentDocument; - } + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } + return elem.contentDocument; + } - return jQuery.merge( [], elem.childNodes ); + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { @@ -3490,7 +3717,7 @@ jQuery.extend( { var fns = arguments; return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { + jQuery.each( tuples, function( _i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; @@ -3943,7 +4170,7 @@ var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { // ...except when executing function values } else { bulk = fn; - fn = function( elem, key, value ) { + fn = function( elem, _key, value ) { return bulk.call( jQuery( elem ), value ); }; } @@ -3978,7 +4205,7 @@ var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() -function fcamelCase( all, letter ) { +function fcamelCase( _all, letter ) { return letter.toUpperCase(); } @@ -4467,6 +4694,26 @@ var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; @@ -4481,32 +4728,11 @@ var isHiddenWithinTree = function( elem, el ) { // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. - jQuery.contains( elem.ownerDocument, elem ) && + isAttached( elem ) && jQuery.css( elem, "display" ) === "none"; }; -var swap = function( elem, options, callback, args ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.apply( elem, args || [] ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - function adjustCSS( elem, prop, valueParts, tween ) { @@ -4523,7 +4749,8 @@ function adjustCSS( elem, prop, valueParts, tween ) { unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches - initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { @@ -4670,17 +4897,46 @@ jQuery.fn.extend( { } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); -// We have to close these tags to support XHTML (#13200) -var wrapMap = { +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; // Support: IE <=9 only - option: [ 1, "" ], + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten @@ -4693,12 +4949,14 @@ var wrapMap = { _default: [ 0, "", "" ] }; -// Support: IE <=9 only -wrapMap.optgroup = wrapMap.option; - wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + function getAll( context, tag ) { @@ -4742,7 +5000,7 @@ function setGlobalEval( elems, refElements ) { var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, contains, j, + var elem, tmp, tag, wrap, attached, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, @@ -4806,13 +5064,13 @@ function buildFragment( elems, context, scripts, selection, ignored ) { continue; } - contains = jQuery.contains( elem.ownerDocument, elem ); + attached = isAttached( elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history - if ( contains ) { + if ( attached ) { setGlobalEval( tmp ); } @@ -4831,34 +5089,6 @@ function buildFragment( elems, context, scripts, selection, ignored ) { } -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (#11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (#14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -} )(); -var documentElement = document.documentElement; - - - var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, @@ -4872,8 +5102,19 @@ function returnFalse() { return false; } +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + // Support: IE <=9 only -// See #13393 for more info +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 function safeActiveElement() { try { return document.activeElement; @@ -4956,8 +5197,8 @@ jQuery.event = { special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { return; } @@ -4981,7 +5222,7 @@ jQuery.event = { // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { - events = elemData.events = {}; + events = elemData.events = Object.create( null ); } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { @@ -5139,12 +5380,15 @@ jQuery.event = { dispatch: function( nativeEvent ) { - // Make a writable jQuery.Event from the native event object - var event = jQuery.event.fix( nativeEvent ); - var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), - handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event @@ -5173,9 +5417,10 @@ jQuery.event = { while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { - // Triggered event must either 1) have no namespace, or 2) have namespace(s) - // a subset or equal to those in the bound event (both can have no namespace). - if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; @@ -5299,39 +5544,51 @@ jQuery.event = { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, - focus: { + click: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); } + + // Return false to allow normal processing in the caller + return false; }, - delegateType: "focusout" - }, - click: { + trigger: function( data ) { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { - this.click(); - return false; + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); } + + // Return non-false to allow normal event-path propagation + return true; }, - // For cross-browser consistency, don't fire native .click() on links + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack _default: function( event ) { - return nodeName( event.target, "a" ); + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); } }, @@ -5348,6 +5605,93 @@ jQuery.event = { } }; +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects @@ -5460,6 +5804,7 @@ jQuery.each( { shiftKey: true, view: true, "char": true, + code: true, charCode: true, key: true, keyCode: true, @@ -5506,6 +5851,33 @@ jQuery.each( { } }, jQuery.event.addProp ); +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout @@ -5591,13 +5963,6 @@ jQuery.fn.extend( { var - /* eslint-disable max-len */ - - // See https://github.com/eslint/eslint/issues/3229 - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, - - /* eslint-enable */ - // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ @@ -5634,7 +5999,7 @@ function restoreScript( elem ) { } function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + var i, l, type, pdataOld, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; @@ -5642,13 +6007,11 @@ function cloneCopyEvent( src, dest ) { // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.access( src ); - pdataCur = dataPriv.set( dest, pdataOld ); + pdataOld = dataPriv.get( src ); events = pdataOld.events; if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; + dataPriv.remove( dest, "handle events" ); for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { @@ -5684,7 +6047,7 @@ function fixInput( src, dest ) { function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays - args = concat.apply( [], args ); + args = flat( args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, @@ -5756,11 +6119,13 @@ function domManip( collection, args, callback, ignored ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); } } else { - DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node ); + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); } } } @@ -5782,7 +6147,7 @@ function remove( elem, selector, keepData ) { } if ( node.parentNode ) { - if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { + if ( keepData && isAttached( node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); @@ -5794,13 +6159,13 @@ function remove( elem, selector, keepData ) { jQuery.extend( { htmlPrefilter: function( html ) { - return html.replace( rxhtmlTag, "<$1>" ); + return html; }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); + inPage = isAttached( elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && @@ -6056,6 +6421,27 @@ var getStyles = function( elem ) { return view.getComputedStyle( elem ); }; +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); @@ -6096,8 +6482,10 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) div.style.position = "absolute"; - scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; documentElement.removeChild( container ); @@ -6111,7 +6499,7 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableMarginLeftVal, + reliableTrDimensionsVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); @@ -6146,6 +6534,35 @@ var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; } } ); } )(); @@ -6168,7 +6585,7 @@ function curCSS( elem, name, computed ) { if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; - if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { + if ( ret === "" && !isAttached( elem ) ) { ret = jQuery.style( elem, name ); } @@ -6224,30 +6641,13 @@ function addGetHookIf( conditionFn, hookFn ) { } -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - rcustomProp = /^--/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }, - - cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style; +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; -// Return a css property mapped to a potentially vendor prefixed property +// Return a vendor-prefixed property or undefined function vendorPropName( name ) { - // Shortcut for names that are not vendor prefixed - if ( name in emptyStyle ) { - return name; - } - // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; @@ -6260,17 +6660,34 @@ function vendorPropName( name ) { } } -// Return a property mapped along what jQuery.cssProps suggests or to -// a vendor prefixed property. +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property function finalPropName( name ) { - var ret = jQuery.cssProps[ name ]; - if ( !ret ) { - ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; } - return ret; + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; } -function setPositiveNumber( elem, value, subtract ) { + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point @@ -6341,7 +6758,10 @@ function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computed delta - extra - 0.5 - ) ); + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; } return delta; @@ -6351,9 +6771,16 @@ function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + val = curCSS( elem, dimension, styles ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox; + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. @@ -6364,22 +6791,38 @@ function getWidthOrHeight( elem, dimension, extra ) { val = "auto"; } - // Check for style in case a browser which returns unreliable values - // for getComputedStyle silently falls back to the reliable elem.style - valueIsBorderBox = valueIsBorderBox && - ( support.boxSizingReliable() || val === elem.style[ dimension ] ); - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - if ( val === "auto" || - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; + // Make sure the element is visible & connected + elem.getClientRects().length ) { - // offsetWidth/offsetHeight provide border-box values - valueIsBorderBox = true; + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } } // Normalize "" and auto @@ -6425,6 +6868,13 @@ jQuery.extend( { "flexGrow": true, "flexShrink": true, "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, "lineHeight": true, "opacity": true, "order": true, @@ -6480,7 +6930,9 @@ jQuery.extend( { } // If a number was passed in, add the unit (except for certain CSS properties) - if ( type === "number" ) { + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } @@ -6554,7 +7006,7 @@ jQuery.extend( { } } ); -jQuery.each( [ "height", "width" ], function( i, dimension ) { +jQuery.each( [ "height", "width" ], function( _i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { @@ -6580,18 +7032,29 @@ jQuery.each( [ "height", "width" ], function( i, dimension ) { set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra && boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ); + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && support.scrollboxSize() === styles.position ) { + if ( isBorderBox && scrollboxSizeBuggy ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - @@ -6759,9 +7222,9 @@ Tween.propHooks = { // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && - ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || - jQuery.cssHooks[ tween.prop ] ) ) { + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; @@ -7316,7 +7779,7 @@ jQuery.fn.extend( { clearQueue = type; type = undefined; } - if ( clearQueue && type !== false ) { + if ( clearQueue ) { this.queue( type || "fx", [] ); } @@ -7399,7 +7862,7 @@ jQuery.fn.extend( { } } ); -jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? @@ -7620,7 +8083,7 @@ boolHook = { } }; -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { @@ -8244,7 +8707,9 @@ jQuery.extend( jQuery.event, { special.bindType || type; // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); @@ -8355,7 +8820,10 @@ if ( !support.focusin ) { jQuery.event.special[ fix ] = { setup: function() { - var doc = this.ownerDocument || this, + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { @@ -8364,7 +8832,7 @@ if ( !support.focusin ) { dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { - var doc = this.ownerDocument || this, + var doc = this.ownerDocument || this.document || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { @@ -8380,7 +8848,7 @@ if ( !support.focusin ) { } var location = window.location; -var nonce = Date.now(); +var nonce = { guid: Date.now() }; var rquery = ( /\?/ ); @@ -8468,6 +8936,10 @@ jQuery.param = function( a, traditional ) { encodeURIComponent( value == null ? "" : value ); }; + if ( a == null ) { + return ""; + } + // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { @@ -8508,7 +8980,7 @@ jQuery.fn.extend( { rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) - .map( function( i, elem ) { + .map( function( _i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { @@ -8970,12 +9442,14 @@ jQuery.extend( { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); } } - match = responseHeaders[ key.toLowerCase() ]; + match = responseHeaders[ key.toLowerCase() + " " ]; } - return match == null ? null : match; + return match == null ? null : match.join( ", " ); }, // Raw string @@ -9119,7 +9593,8 @@ jQuery.extend( { // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) @@ -9252,6 +9727,11 @@ jQuery.extend( { response = ajaxHandleResponses( s, jqXHR, responses ); } + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); @@ -9342,7 +9822,7 @@ jQuery.extend( { } } ); -jQuery.each( [ "get", "post" ], function( i, method ) { +jQuery.each( [ "get", "post" ], function( _i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted @@ -9363,8 +9843,17 @@ jQuery.each( [ "get", "post" ], function( i, method ) { }; } ); +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + -jQuery._evalUrl = function( url ) { +jQuery._evalUrl = function( url, options, doc ) { return jQuery.ajax( { url: url, @@ -9374,7 +9863,16 @@ jQuery._evalUrl = function( url ) { cache: true, async: false, global: false, - "throws": true + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } } ); }; @@ -9657,24 +10155,21 @@ jQuery.ajaxPrefilter( "script", function( s ) { // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { - // This transport only deals with cross domain requests - if ( s.crossDomain ) { + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { var script, callback; return { send: function( _, complete ) { - script = jQuery( "