From 0751d5fed159fd9ff7edba522ece13c8c9d2a0d0 Mon Sep 17 00:00:00 2001 From: Yassin Bahri <131185064+yassinbahri@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:20:43 +0200 Subject: [PATCH 1/9] Fixed #37311 -- Prevented consumption of rhs iterators in annotation lookups. Regression in 7b54ddd5e64c96c641b70bb5f0e958a9e2035fb2. Thank you Oleksandr Tatarinov for the report and Clifford Gama for the review. --- django/db/models/lookups.py | 9 ++++++--- docs/releases/6.1.1.txt | 4 ++++ tests/lookup/tests.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/django/db/models/lookups.py b/django/db/models/lookups.py index eef7bc93a5ce..be6a24800fd8 100644 --- a/django/db/models/lookups.py +++ b/django/db/models/lookups.py @@ -1,5 +1,6 @@ import itertools import math +from collections.abc import Iterator from django.core.exceptions import EmptyResultSet, FullResultSet from django.db.models.expressions import ( @@ -292,7 +293,9 @@ class FieldGetDbPrepValueIterableMixin(FieldGetDbPrepValueMixin): def get_prep_lookup(self): if hasattr(self.rhs, "resolve_expression"): return self.rhs - if any(hasattr(value, "resolve_expression") for value in self.rhs): + # Prevent iterator from being consumed by any(). + rhs = list(self.rhs) if isinstance(self.rhs, Iterator) else self.rhs + if any(hasattr(value, "resolve_expression") for value in rhs): # Wrap direct values in Value expressions so they are handled by # the database at compilation time, along with other expressions. return ExpressionList( @@ -302,11 +305,11 @@ def get_prep_lookup(self): if hasattr(value, "resolve_expression") else Value(value, getattr(self.lhs, "output_field", None)) ) - for value in self.rhs + for value in rhs ] ) prepared_values = [] - for rhs_value in self.rhs: + for rhs_value in rhs: if ( self.prepare_rhs and hasattr(self.lhs, "output_field") diff --git a/docs/releases/6.1.1.txt b/docs/releases/6.1.1.txt index 940c815c9b24..b9576a782014 100644 --- a/docs/releases/6.1.1.txt +++ b/docs/releases/6.1.1.txt @@ -50,3 +50,7 @@ Bugfixes ``choices``, and where any search term matched all rows with a ``True`` value when an ``__exact`` lookup was used on a ``BooleanField`` (:ticket:`37263`). + +* Fixed a regression in Django 6.1 that caused ``__in`` lookups on annotations + to erroneously return empty querysets and ``__range`` lookups to crash when + passed an iterator (:ticket:`37311`). diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 9314fa05b082..586dec37908d 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -1087,6 +1087,28 @@ def test_in(self): def test_in_empty_list(self): self.assertSequenceEqual(Article.objects.filter(id__in=[]), []) + def test_in_iterator_rhs(self): + tests = [ + ("direct values", [self.a1.id, self.a2.id]), + ("expression", [self.a1.id, Value(self.a2.id)]), + ] + for case, values in tests: + with self.subTest(case=case): + self.assertCountEqual( + Article.objects.alias(article_id=F("id")).filter( + article_id__in=iter(values) + ), + [self.a1, self.a2], + ) + + def test_range_iterator_rhs(self): + self.assertCountEqual( + Article.objects.alias(article_id=F("id")).filter( + article_id__range=iter([self.a1.id, self.a2.id]) + ), + [self.a1, self.a2], + ) + def test_in_different_database(self): with self.assertRaisesMessage( ValueError, From ae7450c857cc766175e29d3045f17b30e8a43154 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:02:34 +0200 Subject: [PATCH 2/9] Clarified object-level permission checks in admin actions. --- docs/ref/contrib/admin/actions.txt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index 134e5d8ebe37..cdba2fe3726d 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -407,7 +407,8 @@ decorator and passing the ``permissions`` argument:: queryset.update(status="p") The ``make_published()`` action will only be available to users that pass the -:meth:`.ModelAdmin.has_change_permission` check. +:meth:`.ModelAdmin.has_change_permission` check when it is called with +``obj=None``. If ``permissions`` has more than one permission, the action will be available as long as the user passes at least one of the checks. @@ -441,6 +442,21 @@ For example:: codename = get_permission_codename("publish", opts) return request.user.has_perm("%s.%s" % (opts.app_label, codename)) +The admin doesn't automatically check object-level permissions for each +selected object when executing an action. To limit which objects an action may +modify based on object-level permissions, perform those checks in the action +itself. For example:: + + class ArticleAdmin(admin.ModelAdmin): + actions = ["make_published"] + + @admin.action(permissions=["change"]) + def make_published(self, request, queryset): + for obj in queryset: + if self.has_change_permission(request, obj=obj): + obj.status = "p" + obj.save(update_fields=["status"]) + .. _admin-action-availability: Controlling where actions are available From ddb83ea0e0a22e6e0345b77d879522ee85688190 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:04:28 +0200 Subject: [PATCH 3/9] Corrected heading hierarchy in the admin actions documentation. --- docs/ref/contrib/admin/actions.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt index cdba2fe3726d..48be85fe8c10 100644 --- a/docs/ref/contrib/admin/actions.txt +++ b/docs/ref/contrib/admin/actions.txt @@ -460,7 +460,7 @@ itself. For example:: .. _admin-action-availability: Controlling where actions are available -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------------------- .. versionadded:: 6.1 From 05aec0a4aa111b54b50daec64059fd18ac70b437 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:44:30 +0200 Subject: [PATCH 4/9] Clarified scope of object-level admin view permissions. --- docs/ref/contrib/admin/index.txt | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index 0243c2b980f7..eb8b2a3c0f86 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -1959,15 +1959,24 @@ default templates used by the :class:`ModelAdmin` views: .. method:: ModelAdmin.has_view_permission(request, obj=None) - Should return ``True`` if viewing ``obj`` is permitted, ``False`` - otherwise. If obj is ``None``, should return ``True`` or ``False`` to - indicate whether viewing of objects of this type is permitted in general - (e.g., ``False`` will be interpreted as meaning that the current user is - not permitted to view any object of this type). + Should return ``True`` if the user is permitted to access the change or + history view for ``obj``, and ``False`` otherwise. If obj is ``None``, + should return ``True`` or ``False`` to indicate whether viewing of objects + of this type is permitted in general (e.g., ``False`` will be interpreted + as meaning that the current user is not permitted to view any object of + this type). The default implementation returns ``True`` if the user has either the "change" or "view" permission. + .. admonition:: Object-level view permissions aren't visibility filters + + Object-level view permissions don't prevent information about an object + from appearing elsewhere in the admin, including in the admin + changelist, related field choices, autocomplete results, and recent + actions. To restrict this information, customize the querysets used by + the relevant views and form fields. + .. method:: ModelAdmin.has_add_permission(request) Should return ``True`` if adding an object is permitted, ``False`` From f14e293cd7444e064b9c9ba471fd5aa7e6132ea6 Mon Sep 17 00:00:00 2001 From: Yassin Bahri <131185064+yassinbahri@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:23:46 +0200 Subject: [PATCH 5/9] Fixed #37312, Refs #36605 -- Fixed annotation preservation and key selection in in_bulk(). Bug in 1820d35b17f0a95f4ce888971b9ca0c7a3697c83. Thank you to Jacob Walls for the review. --- django/db/models/query.py | 19 ++++++--- docs/releases/6.1.1.txt | 4 ++ tests/lookup/tests.py | 88 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 106 insertions(+), 5 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 65d85751bd69..ae000c3e8c65 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1303,13 +1303,22 @@ def in_bulk(self, id_list=None, *, field_name="pk"): def get_obj(obj): return obj + selected_fields = tuple( + self.query.selected + or ( + *self.query.extra_select, + *self.query.values_select, + *self.query.annotation_select, + ) + ) + if issubclass(self._iterable_class, ModelIterable): # Raise an AttributeError if field_name is deferred. get_key = operator.attrgetter(field_name) elif issubclass(self._iterable_class, ValuesIterable): if field_name not in self.query.values_select: - qs = qs.values(field_name, *self.query.values_select) + qs = qs.values(field_name, *selected_fields) def get_obj(obj): # noqa: F811 # We can safely mutate the dictionaries returned by @@ -1322,16 +1331,16 @@ def get_obj(obj): # noqa: F811 elif issubclass(self._iterable_class, ValuesListIterable): try: - field_index = self.query.values_select.index(field_name) + field_index = selected_fields.index(field_name) except ValueError: - # field_name is missing from values_select, so add it. + # field_name isn't selected, so add it. field_index = 0 if issubclass(self._iterable_class, NamedValuesListIterable): kwargs = {"named": True} else: kwargs = {} get_obj = operator.itemgetter(slice(1, None)) - qs = qs.values_list(field_name, *self.query.values_select, **kwargs) + qs = qs.values_list(field_name, *selected_fields, **kwargs) get_key = operator.itemgetter(field_index) @@ -1341,7 +1350,7 @@ def get_obj(obj): # noqa: F811 get_key = get_obj else: # Transform it back into a non-flat values_list(). - qs = qs.values_list(field_name, *self.query.values_select) + qs = qs.values_list(field_name, *selected_fields) get_key = operator.itemgetter(0) get_obj = operator.itemgetter(1) diff --git a/docs/releases/6.1.1.txt b/docs/releases/6.1.1.txt index b9576a782014..d23e9ad06856 100644 --- a/docs/releases/6.1.1.txt +++ b/docs/releases/6.1.1.txt @@ -54,3 +54,7 @@ Bugfixes * Fixed a regression in Django 6.1 that caused ``__in`` lookups on annotations to erroneously return empty querysets and ``__range`` lookups to crash when passed an iterator (:ticket:`37311`). + +* Fixed a bug in Django 6.1 where :meth:`.QuerySet.in_bulk` chained after + :meth:`.QuerySet.values` or :meth:`.QuerySet.values_list` could drop selected + annotations or produce incorrect mapping keys (:ticket:`37312`). diff --git a/tests/lookup/tests.py b/tests/lookup/tests.py index 586dec37908d..acdfbfabfc6f 100644 --- a/tests/lookup/tests.py +++ b/tests/lookup/tests.py @@ -368,6 +368,94 @@ def test_in_bulk_values_fields(self): {self.a1.pk: {"headline": "Article 1"}}, ) + def test_in_bulk_values_annotation(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values("headline", "author_name") + .in_bulk([self.a1.pk]) + ) + self.assertEqual( + arts, + { + self.a1.pk: { + "headline": "Article 1", + "author_name": "Author 1", + } + }, + ) + + def test_in_bulk_values_annotation_all_fields(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values() + .in_bulk([self.a1.pk]) + ) + self.assertEqual( + arts, + { + self.a1.pk: { + "id": self.a1.pk, + "author_id": self.au1.pk, + "headline": "Article 1", + "pub_date": self.a1.pub_date, + "slug": "a1", + "author_name": "Author 1", + } + }, + ) + + def test_in_bulk_values_extra_select_all_fields(self): + arts = ( + Article.objects.extra(select={"marker": "1"}).values().in_bulk([self.a1.pk]) + ) + self.assertEqual( + arts, + { + self.a1.pk: { + "marker": 1, + "id": self.a1.pk, + "author_id": self.au1.pk, + "headline": "Article 1", + "pub_date": self.a1.pub_date, + "slug": "a1", + } + }, + ) + + def test_in_bulk_values_list_annotation(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values_list("author_name", "headline") + .in_bulk([self.a1.pk]) + ) + self.assertEqual(arts, {self.a1.pk: ("Author 1", "Article 1")}) + + def test_in_bulk_values_list_annotation_before_pk(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values_list("author_name", "pk") + .in_bulk([self.a1.pk]) + ) + self.assertEqual(arts, {self.a1.pk: ("Author 1", self.a1.pk)}) + + def test_in_bulk_values_list_named_annotation(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values_list("headline", "author_name", named=True) + .in_bulk([self.a1.pk]) + ) + article = arts[self.a1.pk] + self.assertEqual(article._fields, ("pk", "headline", "author_name")) + self.assertEqual(article, (self.a1.pk, "Article 1", "Author 1")) + + def test_in_bulk_values_list_flat_annotation(self): + arts = ( + Article.objects.annotate(author_name=F("author__name")) + .values_list("author_name", flat=True) + .in_bulk([self.a1.pk]) + ) + self.assertEqual(arts, {self.a1.pk: "Author 1"}) + def test_in_bulk_values_fields_including_pk(self): arts = Article.objects.values("pk", "headline").in_bulk([self.a1.pk]) self.assertEqual( From 00dca6f097f443de7a24c04bc133f98c83c632aa Mon Sep 17 00:00:00 2001 From: Dave Gaeddert Date: Tue, 14 Jul 2026 08:14:44 -0500 Subject: [PATCH 6/9] Fixed #37222 -- Fixed QuerySet.distinct() crash on duplicated selections. When two lookup paths resolve to the same column, values() selects that column once per path, at a different position each time. get_distinct() refers to such selections by expression, and PostgreSQL binds an expression reference to the first position the expression is selected at. Ordering referred to each selection by its own position, so the later ones yielded sort keys that DISTINCT ON could not be matched against: SELECT DISTINCT ON expressions must match initial ORDER BY expressions Ordering now refers to the first position an expression is selected at when distinct fields are used. Annotations take part in that, as PostgreSQL binds to their position as well when they select an expression first, but their alias keeps referring to their own position since get_distinct() refers to annotations by alias. Raw selections are left out as equal SQL is not necessarily interchangeable, e.g. two volatile extra() selections must keep ordering by their own position. Regression in 65ad4ade74dc9208b9d686a451cd6045df0c9c3a. Thanks Robert Leach for the report and Simon Charette for the review. --- AUTHORS | 1 + django/db/models/sql/compiler.py | 23 +++++- docs/releases/6.1.1.txt | 6 +- tests/distinct_on_fields/tests.py | 130 +++++++++++++++++++++++++++++- 4 files changed, 156 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 22eb211b9abe..e752d00671b3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -284,6 +284,7 @@ answer newbie questions, and generally made Django that much better: Dan Stephenson Dan Watson dave@thebarproject.com + Dave Gaeddert David Ascher David Avsajanishvili David Blewett diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index bbb83dee045f..1a22393cf91b 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -357,11 +357,30 @@ def _order_by_pairs(self): # Avoid computing `selected_exprs` if there is no `ordering` as it's # relatively expensive. if ordering and (select := self.select): + distinct_fields = self.query.distinct_fields + annotation_select = self.query.annotation_select + # An expression can be selected at more than one position, e.g. + # when two lookup paths resolve to the same column. get_distinct() + # refers to such selections by expression and PostgreSQL binds an + # expression reference to the first position it is selected at, so + # ordering must refer to that position as well for the prefixes of + # both clauses to match. Raw selections are excluded as equal SQL + # is not necessarily interchangeable, e.g. a volatile function + # selected twice. + first_positions = {} for ordinal, (expr, _, alias) in enumerate(select, start=1): pos_expr = PositionRef(ordinal, alias, expr) + if distinct_fields and not isinstance(expr, RawSQL): + first_pos_expr = first_positions.setdefault(expr, pos_expr) + else: + first_pos_expr = pos_expr if alias: - selected_exprs[alias] = pos_expr - selected_exprs[expr] = pos_expr + # get_distinct() refers to annotations by alias, which + # binds to their own position. + selected_exprs[alias] = ( + pos_expr if alias in annotation_select else first_pos_expr + ) + selected_exprs[expr] = first_pos_expr for field in ordering: if hasattr(field, "resolve_expression"): diff --git a/docs/releases/6.1.1.txt b/docs/releases/6.1.1.txt index d23e9ad06856..febc265a35ca 100644 --- a/docs/releases/6.1.1.txt +++ b/docs/releases/6.1.1.txt @@ -4,11 +4,15 @@ Django 6.1.1 release notes *Expected September 2, 2026* -Django 6.1.1 fixes several bugs in 6.1. +Django 6.1.1 fixes one crash in Django 5.2 and several bugs in 6.1. Bugfixes ======== +* Fixed a crash in Django 5.2 when combining :meth:`distinct(*fields) + <.QuerySet.distinct>` with ``order_by()`` and ``values()`` and using two + lookup paths resolving to the same column (:ticket:`37222`). + * Fixed a regression in Django 6.1 where the deprecation of double-dot variable lookups incorrectly applied to string and translated template literals containing two consecutive dots, such as ``{{ "a..b" }}`` (:ticket:`37257`). diff --git a/tests/distinct_on_fields/tests.py b/tests/distinct_on_fields/tests.py index f03e05ac73ce..e220a1711db2 100644 --- a/tests/distinct_on_fields/tests.py +++ b/tests/distinct_on_fields/tests.py @@ -1,5 +1,6 @@ from django.db import connection -from django.db.models import CharField, F, Max +from django.db.models import CharField, F, FloatField, Max +from django.db.models.expressions import RawSQL from django.db.models.functions import Lower from django.test import TestCase, skipUnlessDBFeature from django.test.utils import register_lookup @@ -179,6 +180,133 @@ def test_distinct_on_mixed_case_annotation(self): ) self.assertSequenceEqual(qs, [self.p1_o1, self.p2_o1, self.p3_o1]) + def test_distinct_on_duplicated_selected_columns(self): + # "stafftag__tag__name" and "tags__name" resolve to the same column, so + # it's selected twice, but DISTINCT ON refers to its first selection. + fields = ["stafftag__tag__name", "tags__name", "name"] + qs = ( + Staff.objects.order_by(*[F(field).asc(nulls_last=True) for field in fields]) + .distinct(*fields) + .values_list(*fields) + ) + self.assertSequenceEqual( + qs, + [ + ("t1", "t1", "p1"), + (None, None, "p1"), + (None, None, "p2"), + (None, None, "p3"), + ], + ) + + def test_distinct_on_duplicated_selected_columns_descending(self): + fields = ["stafftag__tag__name", "tags__name", "name"] + qs = ( + Staff.objects.order_by( + *[F(field).desc(nulls_first=True) for field in fields] + ) + .distinct(*fields) + .values_list(*fields) + ) + self.assertSequenceEqual( + qs, + [ + (None, None, "p3"), + (None, None, "p2"), + (None, None, "p1"), + ("t1", "t1", "p1"), + ], + ) + + def test_distinct_on_duplicated_selected_foreign_key_columns(self): + # The local "tag_id" and remote "tag__id" references of a foreign key + # resolve to the same column without requiring different join aliases. + fields = ["tag_id", "tag__id", "tag__name"] + qs = StaffTag.objects.order_by(*fields).distinct(*fields).values_list(*fields) + self.assertSequenceEqual(qs, [(self.t1.pk, self.t1.pk, "t1")]) + + def test_distinct_on_duplicated_selected_transforms(self): + # Transforms of the same column are equal expressions as well, and are + # also referred to by expression by DISTINCT ON. + fields = ["stafftag__tag__name__lower", "tags__name__lower", "name"] + with register_lookup(CharField, Lower): + qs = ( + Staff.objects.order_by( + *[F(field).asc(nulls_last=True) for field in fields] + ) + .distinct(*fields) + .values_list(*fields) + ) + self.assertSequenceEqual( + qs, + [ + ("t1", "t1", "p1"), + (None, None, "p1"), + (None, None, "p2"), + (None, None, "p3"), + ], + ) + + def test_distinct_on_column_selected_by_annotation_first(self): + # The annotation selects the column before the lookup path does, and + # DISTINCT ON binds to the first selection of the column regardless of + # it being selected by an annotation. + qs = ( + Staff.objects.annotate(name_alias=F("name")) + .order_by("name") + .distinct("name") + .values_list("name_alias", "name") + ) + self.assertSequenceEqual(qs, [("p1", "p1"), ("p2", "p2"), ("p3", "p3")]) + + def test_distinct_on_annotation_duplicating_selected_column(self): + # The annotation duplicates a column selected at an earlier position. + # get_distinct() refers to annotations by alias, which binds to the + # annotation's own position, so ordering by the alias must not refer + # to the earlier position. + qs = ( + Staff.objects.annotate(name_alias=F("name")) + .order_by("name_alias") + .distinct("name_alias") + .values_list("name", "name_alias") + ) + self.assertSequenceEqual(qs, [("p1", "p1"), ("p2", "p2"), ("p3", "p3")]) + + def test_distinct_on_duplicated_selected_columns_with_annotation(self): + # The annotation is selected between the two selections of the column + # it aliases. DISTINCT ON refers to annotations by alias, so it must + # not shadow the first selection of the column itself. + fields = ["stafftag__tag__name", "tags__name", "name"] + qs = ( + Staff.objects.annotate(tag_name=F("tags__name")) + .order_by(*[F(field).asc(nulls_last=True) for field in fields]) + .distinct(*fields) + .values_list("stafftag__tag__name", "tag_name", "tags__name", "name") + ) + self.assertSequenceEqual( + qs, + [ + ("t1", "t1", "t1", "p1"), + (None, None, None, "p1"), + (None, None, None, "p2"), + (None, None, None, "p3"), + ], + ) + + def test_distinct_on_duplicated_raw_sql_annotations(self): + # Identical raw SQL selections compare equal but are evaluated + # independently, so each keeps ordering by its own position. + qs = ( + Staff.objects.annotate( + a=RawSQL("random()", [], output_field=FloatField()), + b=RawSQL("random()", [], output_field=FloatField()), + ) + .values("a", "b", "id") + .distinct("name") + .order_by("name", "b") + ) + self.assertEqual(len(qs), 3) + def test_disallowed_update_distinct_on(self): qs = Staff.objects.distinct("organisation").order_by("organisation") msg = "Cannot call update() after .distinct(*fields)." From 4d2a26095e99e52729bf2719d41cab07d7542908 Mon Sep 17 00:00:00 2001 From: Tim Schilling Date: Wed, 2 Sep 2026 10:51:07 -0500 Subject: [PATCH 7/9] Added remaining community package mentions to the documentation. --- docs/howto/static-files/deployment.txt | 12 ++++++++++++ docs/ref/contrib/admin/index.txt | 9 +++++++++ docs/ref/django-admin.txt | 10 ++++++++++ docs/topics/auth/customizing.txt | 8 ++++++++ docs/topics/auth/default.txt | 10 ++++++++++ docs/topics/class-based-views/index.txt | 10 ++++++++++ docs/topics/forms/index.txt | 9 +++++++++ docs/topics/i18n/translation.txt | 10 ++++++++++ docs/topics/security.txt | 16 ++++++++++++++++ docs/topics/testing/index.txt | 9 +++++++++ docs/topics/testing/tools.txt | 9 +++++++++ 11 files changed, 112 insertions(+) diff --git a/docs/howto/static-files/deployment.txt b/docs/howto/static-files/deployment.txt index 19b7c9df826a..d21c608e6ba9 100644 --- a/docs/howto/static-files/deployment.txt +++ b/docs/howto/static-files/deployment.txt @@ -25,6 +25,18 @@ As with all deployment tasks, the devil's in the details. Every production setup will be a bit different, so you'll need to adapt the basic outline to fit your needs. Below are a few common patterns that might help. +Having your Django application serve static files +------------------------------------------------- + +While web servers are much better and faster at serving static files, it is +possible to have Django serve them using community-maintained packages. There +are static file packages highlighted on the `Community Ecosystem`_ page. The +Django Packages `Static Builders grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#storage-static-files +.. _Static Builders grid: https://djangopackages.org/grids/g/static-builders/ + + Serving the site and your static files from the same server ----------------------------------------------------------- diff --git a/docs/ref/contrib/admin/index.txt b/docs/ref/contrib/admin/index.txt index eb8b2a3c0f86..422d4b3e88b8 100644 --- a/docs/ref/contrib/admin/index.txt +++ b/docs/ref/contrib/admin/index.txt @@ -78,6 +78,15 @@ Other topics Having problems? Try :doc:`/faq/admin`. +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are solutions for customizing and + extending the admin highlighted on the `Community Ecosystem`_ page. The + Django Packages `Admin interface grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#admin +.. _Admin interface grid: https://djangopackages.org/grids/g/admin-interface/ + ``ModelAdmin`` objects ====================== diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 55c7dea4464f..5a9d5744f836 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -84,6 +84,16 @@ notification and debug information that ``django-admin`` prints to the console. Available commands ================== +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are developer tool packages + highlighted on the `Community Ecosystem`_ page. The Django Packages + `Developer Tools grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#debugging-development-tools +.. _Developer Tools grid: https://djangopackages.org/grids/g/developer-tools/ + + ``check`` --------- diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 13c86d3ec30e..9ee1d8184a39 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -18,6 +18,14 @@ can be checked through Django's authorization system. You can :ref:`extend ` the default ``User`` model, or :ref:`substitute ` a completely customized model. +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are authentication and authorization + solutions highlighted on the `Community Ecosystem`_ page. The Django + Packages `Authentication grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#authentication-authorization +.. _Authentication grid: https://djangopackages.org/grids/g/authentication/ .. _authentication-backends: Other authentication sources diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 83925d009f18..dfc34ffd8a25 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -202,6 +202,16 @@ objects in the same way as any other :doc:`Django model myuser.user_permissions.remove(permission, permission, ...) myuser.user_permissions.clear() +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are authentication and authorization + solutions highlighted on the `Community Ecosystem`_ page. The Django + Packages `Authorization grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#authentication-authorization +.. _Authorization grid: https://djangopackages.org/grids/g/authorization/ + + Default permissions ------------------- diff --git a/docs/topics/class-based-views/index.txt b/docs/topics/class-based-views/index.txt index 6af39bca9ac8..126b1695dca2 100644 --- a/docs/topics/class-based-views/index.txt +++ b/docs/topics/class-based-views/index.txt @@ -19,6 +19,16 @@ documentation`. generic-editing mixins +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are generic view extensions + highlighted on the `Community Ecosystem`_ page that build on Django's + class-based views. The Django Packages `Class Based Views grid`_ has + even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#forms-views +.. _Class Based Views grid: https://djangopackages.org/grids/g/cbv/ + Basic examples ============== diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index 78082dd06df0..d74cea445dee 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -109,6 +109,15 @@ Django handles three distinct parts of the work involved in forms: It is *possible* to write code that does all of this manually, but Django can take care of it all for you. +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are solutions for customizing how + forms are rendered highlighted on the `Community Ecosystem`_ page. The + Django Packages `Forms grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#forms-views +.. _Forms grid: https://djangopackages.org/grids/g/forms/ + Forms in Django =============== diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 1243789ec746..86c6bded7498 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -2011,6 +2011,16 @@ A number of settings can be used to adjust language cookie options: * :setting:`LANGUAGE_COOKIE_SAMESITE` * :setting:`LANGUAGE_COOKIE_SECURE` +Translating text stored in models +--------------------------------- + +Currently Django does not support translating text stored in models. However, +there are community-maintained solutions on the `Community Ecosystem`_ page. +The Django Packages `Internationalization grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#internationalization-localization +.. _Internationalization grid: https://djangopackages.org/grids/g/i18n/ + Implementation notes ==================== diff --git a/docs/topics/security.txt b/docs/topics/security.txt index dbb7a2e47788..075bc98f48cf 100644 --- a/docs/topics/security.txt +++ b/docs/topics/security.txt @@ -267,6 +267,22 @@ document protected by COOP opens a cross-origin popup window, the popup’s attacks. See :ref:`the cross-origin opener policy section of the security middleware reference ` for details. +Cross-origin resource sharing (CORS) +==================================== + +Cross-Origin Resource Sharing (CORS) controls which origins are permitted to +access resources from your site via the browser. Django does not include +built-in support for CORS headers. + +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are CORS and middleware solutions + highlighted on the `Community Ecosystem`_ page. The Django Packages + `Security grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#security-middleware +.. _Security grid: https://djangopackages.org/grids/g/security/ + Session security ================ diff --git a/docs/topics/testing/index.txt b/docs/topics/testing/index.txt index ef96895fb722..bc3148a726c1 100644 --- a/docs/topics/testing/index.txt +++ b/docs/topics/testing/index.txt @@ -28,6 +28,15 @@ You can also use any *other* Python test framework; Django provides an API and tools for that kind of integration. They are described in the :ref:`other-testing-frameworks` section of :doc:`/topics/testing/advanced`. +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are testing and fixtures solutions + highlighted on the `Community Ecosystem`_ page. The Django Packages + `Testing tools grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#testing-fixtures +.. _Testing tools grid: https://djangopackages.org/grids/g/testing/ + .. toctree:: :maxdepth: 1 diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 944701cdef16..a51e051479a8 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -6,6 +6,15 @@ Testing tools Django provides a small set of tools that come in handy when writing tests. +.. admonition:: There are community-maintained solutions! + + Django has a vibrant ecosystem. There are testing and fixtures solutions + highlighted on the `Community Ecosystem`_ page. The Django Packages + `Testing tools grid`_ has even more options for you! + +.. _Community Ecosystem: https://www.djangoproject.com/community/ecosystem/#testing-fixtures +.. _Testing tools grid: https://djangopackages.org/grids/g/testing/ + .. _test-client: The test client From 8c2db58d3d9a823af419cd10bf9524050242c181 Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 2 Sep 2026 11:59:33 -0400 Subject: [PATCH 8/9] Added release date for 6.1.1. --- docs/releases/6.1.1.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases/6.1.1.txt b/docs/releases/6.1.1.txt index febc265a35ca..180f118c3152 100644 --- a/docs/releases/6.1.1.txt +++ b/docs/releases/6.1.1.txt @@ -2,7 +2,7 @@ Django 6.1.1 release notes ========================== -*Expected September 2, 2026* +*September 2, 2026* Django 6.1.1 fixes one crash in Django 5.2 and several bugs in 6.1. From 5180f82f48b589f93cddc7be7896f654a9aec1ad Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Wed, 2 Sep 2026 13:25:22 -0400 Subject: [PATCH 9/9] Added stub release notes for 6.1.2. --- docs/releases/6.1.2.txt | 12 ++++++++++++ docs/releases/index.txt | 1 + 2 files changed, 13 insertions(+) create mode 100644 docs/releases/6.1.2.txt diff --git a/docs/releases/6.1.2.txt b/docs/releases/6.1.2.txt new file mode 100644 index 000000000000..2ea851219af5 --- /dev/null +++ b/docs/releases/6.1.2.txt @@ -0,0 +1,12 @@ +========================== +Django 6.1.2 release notes +========================== + +*Expected October 6, 2026* + +Django 6.1.2 fixes several bugs in 6.1.1. + +Bugfixes +======== + +* ... diff --git a/docs/releases/index.txt b/docs/releases/index.txt index 7d269e8a9db4..0a27ee307c56 100644 --- a/docs/releases/index.txt +++ b/docs/releases/index.txt @@ -32,6 +32,7 @@ versions of the documentation contain the release notes for any later releases. .. toctree:: :maxdepth: 1 + 6.1.2 6.1.1 6.1