summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--django/contrib/postgres/constraints.py31
-rw-r--r--docs/ref/contrib/postgres/constraints.txt28
-rw-r--r--docs/releases/3.2.txt4
-rw-r--r--docs/releases/5.0.txt3
-rw-r--r--tests/postgres_tests/test_constraints.py220
5 files changed, 8 insertions, 278 deletions
diff --git a/django/contrib/postgres/constraints.py b/django/contrib/postgres/constraints.py
index 1caf432d16..4c739b3fbb 100644
--- a/django/contrib/postgres/constraints.py
+++ b/django/contrib/postgres/constraints.py
@@ -1,5 +1,3 @@
-import warnings
-
from django.contrib.postgres.indexes import OpClass
from django.core.exceptions import ValidationError
from django.db import DEFAULT_DB_ALIAS, NotSupportedError
@@ -9,7 +7,6 @@ from django.db.models.expressions import Exists, ExpressionList
from django.db.models.indexes import IndexExpression
from django.db.models.lookups import PostgresOperatorLookup
from django.db.models.sql import Query
-from django.utils.deprecation import RemovedInDjango50Warning
__all__ = ["ExclusionConstraint"]
@@ -33,7 +30,6 @@ class ExclusionConstraint(BaseConstraint):
condition=None,
deferrable=None,
include=None,
- opclasses=(),
violation_error_message=None,
):
if index_type and index_type.lower() not in {"gist", "spgist"}:
@@ -57,28 +53,11 @@ class ExclusionConstraint(BaseConstraint):
)
if not isinstance(include, (type(None), list, tuple)):
raise ValueError("ExclusionConstraint.include must be a list or tuple.")
- if not isinstance(opclasses, (list, tuple)):
- raise ValueError("ExclusionConstraint.opclasses must be a list or tuple.")
- if opclasses and len(expressions) != len(opclasses):
- raise ValueError(
- "ExclusionConstraint.expressions and "
- "ExclusionConstraint.opclasses must have the same number of "
- "elements."
- )
self.expressions = expressions
self.index_type = index_type or "GIST"
self.condition = condition
self.deferrable = deferrable
self.include = tuple(include) if include else ()
- self.opclasses = opclasses
- if self.opclasses:
- warnings.warn(
- "The opclasses argument is deprecated in favor of using "
- "django.contrib.postgres.indexes.OpClass in "
- "ExclusionConstraint.expressions.",
- category=RemovedInDjango50Warning,
- stacklevel=2,
- )
super().__init__(name=name, violation_error_message=violation_error_message)
def _get_expressions(self, schema_editor, query):
@@ -86,10 +65,6 @@ class ExclusionConstraint(BaseConstraint):
for idx, (expression, operator) in enumerate(self.expressions):
if isinstance(expression, str):
expression = F(expression)
- try:
- expression = OpClass(expression, self.opclasses[idx])
- except IndexError:
- pass
expression = ExclusionConstraintExpression(expression, operator=operator)
expression.set_wrapper_classes(schema_editor.connection)
expressions.append(expression)
@@ -161,8 +136,6 @@ class ExclusionConstraint(BaseConstraint):
kwargs["deferrable"] = self.deferrable
if self.include:
kwargs["include"] = self.include
- if self.opclasses:
- kwargs["opclasses"] = self.opclasses
return path, args, kwargs
def __eq__(self, other):
@@ -174,13 +147,12 @@ class ExclusionConstraint(BaseConstraint):
and self.condition == other.condition
and self.deferrable == other.deferrable
and self.include == other.include
- and self.opclasses == other.opclasses
and self.violation_error_message == other.violation_error_message
)
return super().__eq__(other)
def __repr__(self):
- return "<%s: index_type=%s expressions=%s name=%s%s%s%s%s>" % (
+ return "<%s: index_type=%s expressions=%s name=%s%s%s%s>" % (
self.__class__.__qualname__,
repr(self.index_type),
repr(self.expressions),
@@ -188,7 +160,6 @@ class ExclusionConstraint(BaseConstraint):
"" if self.condition is None else " condition=%s" % self.condition,
"" if self.deferrable is None else " deferrable=%r" % self.deferrable,
"" if not self.include else " include=%s" % repr(self.include),
- "" if not self.opclasses else " opclasses=%s" % repr(self.opclasses),
)
def validate(self, model, instance, exclude=None, using=DEFAULT_DB_ALIAS):
diff --git a/docs/ref/contrib/postgres/constraints.txt b/docs/ref/contrib/postgres/constraints.txt
index 5c50ddd3a5..fcf50b8b5f 100644
--- a/docs/ref/contrib/postgres/constraints.txt
+++ b/docs/ref/contrib/postgres/constraints.txt
@@ -12,7 +12,7 @@ PostgreSQL supports additional data integrity constraints available from the
``ExclusionConstraint``
=======================
-.. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, opclasses=(), violation_error_message=None)
+.. class:: ExclusionConstraint(*, name, expressions, index_type=None, condition=None, deferrable=None, include=None, violation_error_message=None)
Creates an exclusion constraint in the database. Internally, PostgreSQL
implements exclusion constraints using indexes. The default index type is
@@ -133,32 +133,6 @@ used for queries that select only included fields
``include`` is supported for GiST indexes. PostgreSQL 14+ also supports
``include`` for SP-GiST indexes.
-``opclasses``
--------------
-
-.. attribute:: ExclusionConstraint.opclasses
-
-The names of the `PostgreSQL operator classes
-<https://www.postgresql.org/docs/current/indexes-opclass.html>`_ to use for
-this constraint. If you require a custom operator class, you must provide one
-for each expression in the constraint.
-
-For example::
-
- ExclusionConstraint(
- name='exclude_overlapping_opclasses',
- expressions=[('circle', RangeOperators.OVERLAPS)],
- opclasses=['circle_ops'],
- )
-
-creates an exclusion constraint on ``circle`` using ``circle_ops``.
-
-.. deprecated:: 4.1
-
- The ``opclasses`` parameter is deprecated in favor of using
- :class:`OpClass() <django.contrib.postgres.indexes.OpClass>` in
- :attr:`~ExclusionConstraint.expressions`.
-
``violation_error_message``
---------------------------
diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt
index 0195e3cf6e..5668c1a6ad 100644
--- a/docs/releases/3.2.txt
+++ b/docs/releases/3.2.txt
@@ -247,8 +247,8 @@ Minor features
* The new :attr:`.ExclusionConstraint.include` attribute allows creating
covering exclusion constraints on PostgreSQL 12+.
-* The new :attr:`.ExclusionConstraint.opclasses` attribute allows setting
- PostgreSQL operator classes.
+* The new ``ExclusionConstraint.opclasses`` attribute allows setting PostgreSQL
+ operator classes.
* The new :attr:`.JSONBAgg.ordering` attribute determines the ordering of the
aggregated elements.
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index c09973578a..3b1db9a9ad 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -311,3 +311,6 @@ to remove usage of these features.
* The ``name`` argument of ``django.utils.functional.cached_property()`` is
removed.
+
+* The ``opclasses`` argument of
+ ``django.contrib.postgres.constraints.ExclusionConstraint`` is removed.
diff --git a/tests/postgres_tests/test_constraints.py b/tests/postgres_tests/test_constraints.py
index ad21ffa2b5..e601f347eb 100644
--- a/tests/postgres_tests/test_constraints.py
+++ b/tests/postgres_tests/test_constraints.py
@@ -16,10 +16,9 @@ from django.db.models import (
)
from django.db.models.fields.json import KeyTextTransform
from django.db.models.functions import Cast, Left, Lower
-from django.test import ignore_warnings, skipUnlessDBFeature
+from django.test import skipUnlessDBFeature
from django.test.utils import isolate_apps
from django.utils import timezone
-from django.utils.deprecation import RemovedInDjango50Warning
from . import PostgreSQLTestCase
from .models import HotelReservation, IntegerArrayModel, RangesModel, Room, Scene
@@ -328,30 +327,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
include="invalid",
)
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_invalid_opclasses_type(self):
- msg = "ExclusionConstraint.opclasses must be a list or tuple."
- with self.assertRaisesMessage(ValueError, msg):
- ExclusionConstraint(
- name="exclude_invalid_opclasses",
- expressions=[(F("datespan"), RangeOperators.OVERLAPS)],
- opclasses="invalid",
- )
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_opclasses_and_expressions_same_length(self):
- msg = (
- "ExclusionConstraint.expressions and "
- "ExclusionConstraint.opclasses must have the same number of "
- "elements."
- )
- with self.assertRaisesMessage(ValueError, msg):
- ExclusionConstraint(
- name="exclude_invalid_expressions_opclasses_length",
- expressions=[(F("datespan"), RangeOperators.OVERLAPS)],
- opclasses=["foo", "bar"],
- )
-
def test_repr(self):
constraint = ExclusionConstraint(
name="exclude_overlapping",
@@ -466,27 +441,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
],
include=["cancelled"],
)
- with ignore_warnings(category=RemovedInDjango50Warning):
- constraint_8 = ExclusionConstraint(
- name="exclude_overlapping",
- expressions=[
- ("datespan", RangeOperators.OVERLAPS),
- ("room", RangeOperators.EQUAL),
- ],
- include=["cancelled"],
- opclasses=["range_ops", "range_ops"],
- )
- constraint_9 = ExclusionConstraint(
- name="exclude_overlapping",
- expressions=[
- ("datespan", RangeOperators.OVERLAPS),
- ("room", RangeOperators.EQUAL),
- ],
- opclasses=["range_ops", "range_ops"],
- )
- self.assertNotEqual(constraint_2, constraint_9)
- self.assertNotEqual(constraint_7, constraint_8)
-
constraint_10 = ExclusionConstraint(
name="exclude_overlapping",
expressions=[
@@ -636,27 +590,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
},
)
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_deconstruct_opclasses(self):
- constraint = ExclusionConstraint(
- name="exclude_overlapping",
- expressions=[("datespan", RangeOperators.OVERLAPS)],
- opclasses=["range_ops"],
- )
- path, args, kwargs = constraint.deconstruct()
- self.assertEqual(
- path, "django.contrib.postgres.constraints.ExclusionConstraint"
- )
- self.assertEqual(args, ())
- self.assertEqual(
- kwargs,
- {
- "name": "exclude_overlapping",
- "expressions": [("datespan", RangeOperators.OVERLAPS)],
- "opclasses": ["range_ops"],
- },
- )
-
def _test_range_overlaps(self, constraint):
# Create exclusion constraint.
self.assertNotIn(
@@ -759,23 +692,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
exclude={"datespan", "start", "end", "room"},
)
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_range_overlaps_custom_opclasses(self):
- class TsTzRange(Func):
- function = "TSTZRANGE"
- output_field = DateTimeRangeField()
-
- constraint = ExclusionConstraint(
- name="exclude_overlapping_reservations_custom",
- expressions=[
- (TsTzRange("start", "end", RangeBoundary()), RangeOperators.OVERLAPS),
- ("room", RangeOperators.EQUAL),
- ],
- condition=Q(cancelled=False),
- opclasses=["range_ops", "gist_int4_ops"],
- )
- self._test_range_overlaps(constraint)
-
def test_range_overlaps_custom(self):
class TsTzRange(Func):
function = "TSTZRANGE"
@@ -1203,137 +1119,3 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
constraint_name,
self.get_constraints(ModelWithExclusionConstraint._meta.db_table),
)
-
-
-class ExclusionConstraintOpclassesDepracationTests(PostgreSQLTestCase):
- def get_constraints(self, table):
- """Get the constraints on the table using a new cursor."""
- with connection.cursor() as cursor:
- return connection.introspection.get_constraints(cursor, table)
-
- def test_warning(self):
- msg = (
- "The opclasses argument is deprecated in favor of using "
- "django.contrib.postgres.indexes.OpClass in "
- "ExclusionConstraint.expressions."
- )
- with self.assertWarnsMessage(RemovedInDjango50Warning, msg):
- ExclusionConstraint(
- name="exclude_overlapping",
- expressions=[(F("datespan"), RangeOperators.ADJACENT_TO)],
- opclasses=["range_ops"],
- )
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_repr(self):
- constraint = ExclusionConstraint(
- name="exclude_overlapping",
- expressions=[(F("datespan"), RangeOperators.ADJACENT_TO)],
- opclasses=["range_ops"],
- )
- self.assertEqual(
- repr(constraint),
- "<ExclusionConstraint: index_type='GIST' expressions=["
- "(F(datespan), '-|-')] name='exclude_overlapping' "
- "opclasses=['range_ops']>",
- )
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_range_adjacent_opclasses(self):
- constraint_name = "ints_adjacent_opclasses"
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
- constraint = ExclusionConstraint(
- name=constraint_name,
- expressions=[("ints", RangeOperators.ADJACENT_TO)],
- opclasses=["range_ops"],
- )
- with connection.schema_editor() as editor:
- editor.add_constraint(RangesModel, constraint)
- constraints = self.get_constraints(RangesModel._meta.db_table)
- self.assertIn(constraint_name, constraints)
- with editor.connection.cursor() as cursor:
- cursor.execute(SchemaTests.get_opclass_query, [constraint.name])
- self.assertEqual(
- cursor.fetchall(),
- [("range_ops", constraint.name)],
- )
- RangesModel.objects.create(ints=(20, 50))
- with self.assertRaises(IntegrityError), transaction.atomic():
- RangesModel.objects.create(ints=(10, 20))
- RangesModel.objects.create(ints=(10, 19))
- RangesModel.objects.create(ints=(51, 60))
- # Drop the constraint.
- with connection.schema_editor() as editor:
- editor.remove_constraint(RangesModel, constraint)
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_range_adjacent_opclasses_condition(self):
- constraint_name = "ints_adjacent_opclasses_condition"
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
- constraint = ExclusionConstraint(
- name=constraint_name,
- expressions=[("ints", RangeOperators.ADJACENT_TO)],
- opclasses=["range_ops"],
- condition=Q(id__gte=100),
- )
- with connection.schema_editor() as editor:
- editor.add_constraint(RangesModel, constraint)
- self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_range_adjacent_opclasses_deferrable(self):
- constraint_name = "ints_adjacent_opclasses_deferrable"
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
- constraint = ExclusionConstraint(
- name=constraint_name,
- expressions=[("ints", RangeOperators.ADJACENT_TO)],
- opclasses=["range_ops"],
- deferrable=Deferrable.DEFERRED,
- )
- with connection.schema_editor() as editor:
- editor.add_constraint(RangesModel, constraint)
- self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- def test_range_adjacent_gist_opclasses_include(self):
- constraint_name = "ints_adjacent_gist_opclasses_include"
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
- constraint = ExclusionConstraint(
- name=constraint_name,
- expressions=[("ints", RangeOperators.ADJACENT_TO)],
- index_type="gist",
- opclasses=["range_ops"],
- include=["decimals"],
- )
- with connection.schema_editor() as editor:
- editor.add_constraint(RangesModel, constraint)
- self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))
-
- @ignore_warnings(category=RemovedInDjango50Warning)
- @skipUnlessDBFeature("supports_covering_spgist_indexes")
- def test_range_adjacent_spgist_opclasses_include(self):
- constraint_name = "ints_adjacent_spgist_opclasses_include"
- self.assertNotIn(
- constraint_name, self.get_constraints(RangesModel._meta.db_table)
- )
- constraint = ExclusionConstraint(
- name=constraint_name,
- expressions=[("ints", RangeOperators.ADJACENT_TO)],
- index_type="spgist",
- opclasses=["range_ops"],
- include=["decimals"],
- )
- with connection.schema_editor() as editor:
- editor.add_constraint(RangesModel, constraint)
- self.assertIn(constraint_name, self.get_constraints(RangesModel._meta.db_table))