Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 10 additions & 9 deletions django/contrib/gis/db/backends/postgis/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions docs/howto/csrf.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
<django-security-logger>` logger.

.. _csrf-cache-page:

Using CSRF protection with caching
==================================

Expand Down
12 changes: 12 additions & 0 deletions docs/topics/cache.txt
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,18 @@ Additionally, ``cache_page`` automatically sets ``Cache-Control`` and
``Expires`` headers in the response which affect :ref:`downstream caches
<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
----------------------------------------

Expand Down
5 changes: 4 additions & 1 deletion tests/admin_filters/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import unittest

from django.contrib.admin import (
AdminSite,
AllValuesFieldListFilter,
BooleanFieldListFilter,
EmptyFieldListFilter,
FieldListFilter,
ModelAdmin,
RelatedOnlyFieldListFilter,
SimpleListFilter,
site,
)
from django.contrib.admin.filters import FacetsMixin
from django.contrib.admin.options import IncorrectLookupParameters, ShowFacets
Expand All @@ -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]
Expand Down
96 changes: 93 additions & 3 deletions tests/gis_tests/geoapp/test_indexes.py
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -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)
Loading