mirror of https://github.com/django/django.git
Refs #33342 -- Removed ExclusionConstraint.opclasses per deprecation timeline.
This commit is contained in:
parent
5c10041f46
commit
23ec318988
|
@ -1,5 +1,3 @@
|
||||||
import warnings
|
|
||||||
|
|
||||||
from django.contrib.postgres.indexes import OpClass
|
from django.contrib.postgres.indexes import OpClass
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import DEFAULT_DB_ALIAS, NotSupportedError
|
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.indexes import IndexExpression
|
||||||
from django.db.models.lookups import PostgresOperatorLookup
|
from django.db.models.lookups import PostgresOperatorLookup
|
||||||
from django.db.models.sql import Query
|
from django.db.models.sql import Query
|
||||||
from django.utils.deprecation import RemovedInDjango50Warning
|
|
||||||
|
|
||||||
__all__ = ["ExclusionConstraint"]
|
__all__ = ["ExclusionConstraint"]
|
||||||
|
|
||||||
|
@ -33,7 +30,6 @@ class ExclusionConstraint(BaseConstraint):
|
||||||
condition=None,
|
condition=None,
|
||||||
deferrable=None,
|
deferrable=None,
|
||||||
include=None,
|
include=None,
|
||||||
opclasses=(),
|
|
||||||
violation_error_message=None,
|
violation_error_message=None,
|
||||||
):
|
):
|
||||||
if index_type and index_type.lower() not in {"gist", "spgist"}:
|
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)):
|
if not isinstance(include, (type(None), list, tuple)):
|
||||||
raise ValueError("ExclusionConstraint.include must be a list or 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.expressions = expressions
|
||||||
self.index_type = index_type or "GIST"
|
self.index_type = index_type or "GIST"
|
||||||
self.condition = condition
|
self.condition = condition
|
||||||
self.deferrable = deferrable
|
self.deferrable = deferrable
|
||||||
self.include = tuple(include) if include else ()
|
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)
|
super().__init__(name=name, violation_error_message=violation_error_message)
|
||||||
|
|
||||||
def _get_expressions(self, schema_editor, query):
|
def _get_expressions(self, schema_editor, query):
|
||||||
|
@ -86,10 +65,6 @@ class ExclusionConstraint(BaseConstraint):
|
||||||
for idx, (expression, operator) in enumerate(self.expressions):
|
for idx, (expression, operator) in enumerate(self.expressions):
|
||||||
if isinstance(expression, str):
|
if isinstance(expression, str):
|
||||||
expression = F(expression)
|
expression = F(expression)
|
||||||
try:
|
|
||||||
expression = OpClass(expression, self.opclasses[idx])
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
expression = ExclusionConstraintExpression(expression, operator=operator)
|
expression = ExclusionConstraintExpression(expression, operator=operator)
|
||||||
expression.set_wrapper_classes(schema_editor.connection)
|
expression.set_wrapper_classes(schema_editor.connection)
|
||||||
expressions.append(expression)
|
expressions.append(expression)
|
||||||
|
@ -161,8 +136,6 @@ class ExclusionConstraint(BaseConstraint):
|
||||||
kwargs["deferrable"] = self.deferrable
|
kwargs["deferrable"] = self.deferrable
|
||||||
if self.include:
|
if self.include:
|
||||||
kwargs["include"] = self.include
|
kwargs["include"] = self.include
|
||||||
if self.opclasses:
|
|
||||||
kwargs["opclasses"] = self.opclasses
|
|
||||||
return path, args, kwargs
|
return path, args, kwargs
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -174,13 +147,12 @@ class ExclusionConstraint(BaseConstraint):
|
||||||
and self.condition == other.condition
|
and self.condition == other.condition
|
||||||
and self.deferrable == other.deferrable
|
and self.deferrable == other.deferrable
|
||||||
and self.include == other.include
|
and self.include == other.include
|
||||||
and self.opclasses == other.opclasses
|
|
||||||
and self.violation_error_message == other.violation_error_message
|
and self.violation_error_message == other.violation_error_message
|
||||||
)
|
)
|
||||||
return super().__eq__(other)
|
return super().__eq__(other)
|
||||||
|
|
||||||
def __repr__(self):
|
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__,
|
self.__class__.__qualname__,
|
||||||
repr(self.index_type),
|
repr(self.index_type),
|
||||||
repr(self.expressions),
|
repr(self.expressions),
|
||||||
|
@ -188,7 +160,6 @@ class ExclusionConstraint(BaseConstraint):
|
||||||
"" if self.condition is None else " condition=%s" % self.condition,
|
"" if self.condition is None else " condition=%s" % self.condition,
|
||||||
"" if self.deferrable is None else " deferrable=%r" % self.deferrable,
|
"" if self.deferrable is None else " deferrable=%r" % self.deferrable,
|
||||||
"" if not self.include else " include=%s" % repr(self.include),
|
"" 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):
|
def validate(self, model, instance, exclude=None, using=DEFAULT_DB_ALIAS):
|
||||||
|
|
|
@ -12,7 +12,7 @@ PostgreSQL supports additional data integrity constraints available from the
|
||||||
``ExclusionConstraint``
|
``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
|
Creates an exclusion constraint in the database. Internally, PostgreSQL
|
||||||
implements exclusion constraints using indexes. The default index type is
|
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`` is supported for GiST indexes. PostgreSQL 14+ also supports
|
||||||
``include`` for SP-GiST indexes.
|
``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``
|
``violation_error_message``
|
||||||
---------------------------
|
---------------------------
|
||||||
|
|
||||||
|
|
|
@ -247,8 +247,8 @@ Minor features
|
||||||
* The new :attr:`.ExclusionConstraint.include` attribute allows creating
|
* The new :attr:`.ExclusionConstraint.include` attribute allows creating
|
||||||
covering exclusion constraints on PostgreSQL 12+.
|
covering exclusion constraints on PostgreSQL 12+.
|
||||||
|
|
||||||
* The new :attr:`.ExclusionConstraint.opclasses` attribute allows setting
|
* The new ``ExclusionConstraint.opclasses`` attribute allows setting PostgreSQL
|
||||||
PostgreSQL operator classes.
|
operator classes.
|
||||||
|
|
||||||
* The new :attr:`.JSONBAgg.ordering` attribute determines the ordering of the
|
* The new :attr:`.JSONBAgg.ordering` attribute determines the ordering of the
|
||||||
aggregated elements.
|
aggregated elements.
|
||||||
|
|
|
@ -311,3 +311,6 @@ to remove usage of these features.
|
||||||
|
|
||||||
* The ``name`` argument of ``django.utils.functional.cached_property()`` is
|
* The ``name`` argument of ``django.utils.functional.cached_property()`` is
|
||||||
removed.
|
removed.
|
||||||
|
|
||||||
|
* The ``opclasses`` argument of
|
||||||
|
``django.contrib.postgres.constraints.ExclusionConstraint`` is removed.
|
||||||
|
|
|
@ -16,10 +16,9 @@ from django.db.models import (
|
||||||
)
|
)
|
||||||
from django.db.models.fields.json import KeyTextTransform
|
from django.db.models.fields.json import KeyTextTransform
|
||||||
from django.db.models.functions import Cast, Left, Lower
|
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.test.utils import isolate_apps
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.deprecation import RemovedInDjango50Warning
|
|
||||||
|
|
||||||
from . import PostgreSQLTestCase
|
from . import PostgreSQLTestCase
|
||||||
from .models import HotelReservation, IntegerArrayModel, RangesModel, Room, Scene
|
from .models import HotelReservation, IntegerArrayModel, RangesModel, Room, Scene
|
||||||
|
@ -328,30 +327,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
|
||||||
include="invalid",
|
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):
|
def test_repr(self):
|
||||||
constraint = ExclusionConstraint(
|
constraint = ExclusionConstraint(
|
||||||
name="exclude_overlapping",
|
name="exclude_overlapping",
|
||||||
|
@ -466,27 +441,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
|
||||||
],
|
],
|
||||||
include=["cancelled"],
|
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(
|
constraint_10 = ExclusionConstraint(
|
||||||
name="exclude_overlapping",
|
name="exclude_overlapping",
|
||||||
expressions=[
|
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):
|
def _test_range_overlaps(self, constraint):
|
||||||
# Create exclusion constraint.
|
# Create exclusion constraint.
|
||||||
self.assertNotIn(
|
self.assertNotIn(
|
||||||
|
@ -759,23 +692,6 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
|
||||||
exclude={"datespan", "start", "end", "room"},
|
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):
|
def test_range_overlaps_custom(self):
|
||||||
class TsTzRange(Func):
|
class TsTzRange(Func):
|
||||||
function = "TSTZRANGE"
|
function = "TSTZRANGE"
|
||||||
|
@ -1203,137 +1119,3 @@ class ExclusionConstraintTests(PostgreSQLTestCase):
|
||||||
constraint_name,
|
constraint_name,
|
||||||
self.get_constraints(ModelWithExclusionConstraint._meta.db_table),
|
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))
|
|
||||||
|
|
Loading…
Reference in New Issue