From cceb6969d4dd62a421c483eefb54cbc159afbded Mon Sep 17 00:00:00 2001 From: Jacob Walls Date: Fri, 17 Jul 2026 09:03:17 -0400 Subject: [PATCH 1/3] Refs #32539 -- Fixed flaky facet filter tests. Because the default admin site was used, UserAdmin was never registered, meaning the username ordering was never applied. --- tests/admin_filters/tests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/admin_filters/tests.py b/tests/admin_filters/tests.py index cef03eb452ed..413d0346d7e7 100644 --- a/tests/admin_filters/tests.py +++ b/tests/admin_filters/tests.py @@ -3,6 +3,7 @@ import unittest from django.contrib.admin import ( + AdminSite, AllValuesFieldListFilter, BooleanFieldListFilter, EmptyFieldListFilter, @@ -10,7 +11,6 @@ ModelAdmin, RelatedOnlyFieldListFilter, SimpleListFilter, - site, ) from django.contrib.admin.filters import FacetsMixin from django.contrib.admin.options import IncorrectLookupParameters, ShowFacets @@ -22,6 +22,9 @@ from .models import Book, Bookmark, Department, Employee, ImprovedBook, TaggedItem +site = AdminSite(name="test_adminfilters") +site.register(User, UserAdmin) + def select_by(dictlist, key, value): return [x for x in dictlist if x[key] == value][0] From 4ef959ab0c7dde0459041a795359c9bc040df2b7 Mon Sep 17 00:00:00 2001 From: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:51:33 +0200 Subject: [PATCH 2/3] Refs #15855 -- Documented that cache_page caches responses before response middleware runs. --- docs/howto/csrf.txt | 2 ++ docs/topics/cache.txt | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/howto/csrf.txt b/docs/howto/csrf.txt index ac826f376893..f3c52a526a06 100644 --- a/docs/howto/csrf.txt +++ b/docs/howto/csrf.txt @@ -187,6 +187,8 @@ own view for handling this condition. To do this, set the CSRF failures are logged as warnings to the :ref:`django.security.csrf ` logger. +.. _csrf-cache-page: + Using CSRF protection with caching ================================== diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 2449120eba2d..a44dbae134d8 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -742,6 +742,18 @@ Additionally, ``cache_page`` automatically sets ``Cache-Control`` and ``Expires`` headers in the response which affect :ref:`downstream caches `. +.. admonition:: Responses are cached before response middleware runs + + ``cache_page`` is intended for content that is safe to reuse for requests + to the same URL. It does not automatically vary by authentication, + sessions, cookies, or other request-specific state. + + As a decorator wrapping the view directly, responses are cached before + response middleware runs. If cache variation depends on response + middleware, ensure it runs before the response is cached. For example, when + caching views containing CSRF-protected forms, follow + :ref:`csrf-cache-page` to ensure the ``Vary`` header is set first. + Specifying per-view cache in the URLconf ---------------------------------------- From 76e1bca1311ae7073a1fa4add6f9d19d709f0f09 Mon Sep 17 00:00:00 2001 From: Clifford Gama Date: Thu, 15 Jan 2026 16:57:59 +0200 Subject: [PATCH 3/3] Fixed #36694 -- Restored missing Index options in PostGISSchemaEditor._create_spatial_index_sql(). Thanks Luciano de la Iglesia for the report, and Simon Charette and David Smith for the reviews. --- .../contrib/gis/db/backends/postgis/schema.py | 19 ++-- tests/gis_tests/geoapp/test_indexes.py | 96 ++++++++++++++++++- 2 files changed, 103 insertions(+), 12 deletions(-) diff --git a/django/contrib/gis/db/backends/postgis/schema.py b/django/contrib/gis/db/backends/postgis/schema.py index aef62198cbe8..62577547b049 100644 --- a/django/contrib/gis/db/backends/postgis/schema.py +++ b/django/contrib/gis/db/backends/postgis/schema.py @@ -96,28 +96,29 @@ def _create_spatial_index_name(self, model, field): return self._create_index_name(model._meta.db_table, [field.column], "_id") def _create_spatial_index_sql(self, model, field, **kwargs): - expressions = None - opclasses = None + expressions = kwargs.pop("expressions", None) + opclasses = kwargs.pop("opclasses", None) fields = [field] if field.geom_type == "RASTER": # For raster fields, wrap index creation SQL statement with # ST_ConvexHull. Indexes on raster columns are based on the convex - # hull of the raster. + # hull of the raster. Note that expressions is None here since + # fields and expressions are mutually exclusive in Index creation. expressions = Func(Col(None, field), template=self.rast_index_template) fields = None - elif field.dim > 2 and not field.geography: + elif field.dim > 2 and not field.geography and not opclasses: # Use "nd" ops which are fast on multidimensional cases opclasses = [self.geom_index_ops_nd] - if not (name := kwargs.get("name")): - name = self._create_spatial_index_name(model, field) - + if not kwargs.get("name"): + kwargs["name"] = self._create_spatial_index_name(model, field) + if not kwargs.get("using"): + kwargs["using"] = " USING %s" % self.geom_index_type return super()._create_index_sql( model, fields=fields, - name=name, - using=" USING %s" % self.geom_index_type, opclasses=opclasses, expressions=expressions, + **kwargs, ) def _delete_spatial_index_sql(self, model, field): diff --git a/tests/gis_tests/geoapp/test_indexes.py b/tests/gis_tests/geoapp/test_indexes.py index cd58f8e94185..2da0070e30a7 100644 --- a/tests/gis_tests/geoapp/test_indexes.py +++ b/tests/gis_tests/geoapp/test_indexes.py @@ -1,15 +1,21 @@ from django.contrib.gis.db import models from django.db import connection -from django.db.models import Index -from django.test import TransactionTestCase +from django.db.models import Index, Q +from django.test import TransactionTestCase, skipUnlessDBFeature from django.test.utils import isolate_apps -from .models import City +from .models import City, ThreeDimensionalFeature class SchemaIndexesTests(TransactionTestCase): available_apps = [] models = [City] + get_opclass_query = """ + SELECT opcname, c.relname FROM pg_opclass AS oc + JOIN pg_index as i on oc.oid = ANY(i.indclass) + JOIN pg_class as c on c.oid = i.indexrelid + WHERE c.relname = %s + """ def get_indexes(self, table): with connection.cursor() as cursor: @@ -75,3 +81,87 @@ def test_index_name(self): self.assertIn(index_name, indexes) self.assertEqual(indexes[index_name], ["point"]) editor.remove_index(City, index) + + @skipUnlessDBFeature("supports_partial_indexes") + def test_partial_index(self): + name = "city_point_partial_on_name_idx" + index = Index(fields=["point"], name=name, condition=Q(name="Harahetania")) + with connection.schema_editor() as editor: + index_sql = str(index.create_sql(City, editor)) + self.assertIn("WHERE %s" % editor.quote_name("name"), index_sql) + editor.add_index(City, index) + indexes = self.get_indexes(City._meta.db_table) + self.assertIn(index.name, indexes) + editor.remove_index(City, index) + + @skipUnlessDBFeature("supports_tablespaces") + def test_tablespace(self): + index_name = "city_point_partial_tblspce_idx" + index = Index( + name=index_name, + fields=["point"], + db_tablespace="pg_default", + ) + with connection.schema_editor() as editor: + editor.add_index(City, index) + self.assertIn( + 'TABLESPACE "pg_default"', + str(index.create_sql(City, editor)), + ) + editor.remove_index(City, index) + + @skipUnlessDBFeature("supports_covering_indexes") + def test_covering_index(self): + index_name = "city_point_covering_name_idx" + index = Index(fields=["point"], include=["name"], name=index_name) + with connection.schema_editor() as editor: + index_sql = str(index.create_sql(City, editor)) + self.assertIn( + "(%s) INCLUDE (%s)" + % (editor.quote_name("point"), editor.quote_name("name")), + index_sql, + ) + editor.add_index(City, index) + indexes = self.get_indexes(City._meta.db_table) + self.assertIn(index.name, indexes) + self.assertEqual(indexes[index.name], ["point", "name"]) + editor.remove_index(City, index) + + def test_specified_opclass_is_used(self): + if not connection.ops.postgis: + self.skipTest("PostGIS-specific test.") + index_name = "city_point_geom_3d_idx" + index = Index( + fields=["point"], name=index_name, opclasses=["gist_geometry_ops_nd"] + ) + with connection.schema_editor() as editor, connection.cursor() as cursor: + editor.add_index(City, index) + cursor.execute(self.get_opclass_query, [index_name]) + self.assertEqual(cursor.fetchall(), [("gist_geometry_ops_nd", index_name)]) + editor.remove_index(City, index) + + def test_using_not_overridden(self): + if not connection.ops.postgis: + self.skipTest("PostGIS-specific test.") + from django.contrib.postgres.indexes import BrinIndex + + index_name = "city_point_brin_idx" + index = BrinIndex(fields=["point"], name=index_name) + with connection.schema_editor() as editor: + index_sql = str(index.create_sql(City, editor)) + self.assertIn("USING brin", index_sql) + + def test_3d_field_opclass_not_overridden(self): + """ + 3d+ fields should use the "nd" opclass by default, but a user-provided + opclass should be respected. + """ + if not connection.ops.postgis: + self.skipTest("PostGIS-specific test.") + + index_name = "city3d_point_custom_opclass_idx" + index = Index(fields=["geom"], name=index_name, opclasses=["custom_ops"]) + with connection.schema_editor() as editor: + index_sql = str(index.create_sql(ThreeDimensionalFeature, editor)) + self.assertIn("custom_ops", index_sql) + self.assertNotIn("gist_geometry_ops_nd", index_sql)