diff --git a/django/contrib/postgres/fields/citext.py b/django/contrib/postgres/fields/citext.py index 2dac2577d1..01df5411ab 100644 --- a/django/contrib/postgres/fields/citext.py +++ b/django/contrib/postgres/fields/citext.py @@ -1,78 +1,45 @@ -import warnings - from django.db.models import CharField, EmailField, TextField -from django.test.utils import ignore_warnings -from django.utils.deprecation import RemovedInDjango51Warning -__all__ = ["CICharField", "CIEmailField", "CIText", "CITextField"] +__all__ = ["CICharField", "CIEmailField", "CITextField"] -# RemovedInDjango51Warning. -class CIText: - def __init__(self, *args, **kwargs): - warnings.warn( - "django.contrib.postgres.fields.CIText mixin is deprecated.", - RemovedInDjango51Warning, - stacklevel=2, - ) - super().__init__(*args, **kwargs) - - def get_internal_type(self): - return "CI" + super().get_internal_type() - - def db_type(self, connection): - return "citext" - - -class CICharField(CIText, CharField): - system_check_deprecated_details = { +class CICharField(CharField): + system_check_removed_details = { "msg": ( - "django.contrib.postgres.fields.CICharField is deprecated. Support for it " - "(except in historical migrations) will be removed in Django 5.1." + "django.contrib.postgres.fields.CICharField is removed except for support " + "in historical migrations." ), "hint": ( 'Use CharField(db_collation="…") with a case-insensitive non-deterministic ' "collation instead." ), - "id": "fields.W905", + "id": "fields.E905", } - def __init__(self, *args, **kwargs): - with ignore_warnings(category=RemovedInDjango51Warning): - super().__init__(*args, **kwargs) - -class CIEmailField(CIText, EmailField): - system_check_deprecated_details = { +class CIEmailField(EmailField): + system_check_removed_details = { "msg": ( - "django.contrib.postgres.fields.CIEmailField is deprecated. Support for it " - "(except in historical migrations) will be removed in Django 5.1." + "django.contrib.postgres.fields.CIEmailField is removed except for support " + "in historical migrations." ), "hint": ( 'Use EmailField(db_collation="…") with a case-insensitive ' "non-deterministic collation instead." ), - "id": "fields.W906", + "id": "fields.E906", } - def __init__(self, *args, **kwargs): - with ignore_warnings(category=RemovedInDjango51Warning): - super().__init__(*args, **kwargs) - -class CITextField(CIText, TextField): - system_check_deprecated_details = { +class CITextField(TextField): + system_check_removed_details = { "msg": ( - "django.contrib.postgres.fields.CITextField is deprecated. Support for it " - "(except in historical migrations) will be removed in Django 5.1." + "django.contrib.postgres.fields.CITextField is removed except for support " + "in historical migrations." ), "hint": ( 'Use TextField(db_collation="…") with a case-insensitive non-deterministic ' "collation instead." ), - "id": "fields.W907", + "id": "fields.E907", } - - def __init__(self, *args, **kwargs): - with ignore_warnings(category=RemovedInDjango51Warning): - super().__init__(*args, **kwargs) diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py index aa839f5634..93eb982f4b 100644 --- a/django/db/backends/postgresql/operations.py +++ b/django/db/backends/postgresql/operations.py @@ -159,9 +159,6 @@ class DatabaseOperations(BaseDatabaseOperations): "CharField", "EmailField", "TextField", - "CICharField", - "CIEmailField", - "CITextField", ): return "%s::text" @@ -179,9 +176,6 @@ class DatabaseOperations(BaseDatabaseOperations): ): if internal_type in ("IPAddressField", "GenericIPAddressField"): lookup = "HOST(%s)" - # RemovedInDjango51Warning. - elif internal_type in ("CICharField", "CIEmailField", "CITextField"): - lookup = "%s::citext" else: lookup = "%s::text" diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt index e641c989e3..9170ff83a8 100644 --- a/docs/ref/checks.txt +++ b/docs/ref/checks.txt @@ -237,13 +237,19 @@ Model fields except for support in historical migrations. * **fields.W905**: ``django.contrib.postgres.fields.CICharField`` is deprecated. Support for it (except in historical migrations) will be removed - in Django 5.1. + in Django 5.1. *This check appeared in Django 4.2 and 5.0*. +* **fields.E905**: ``django.contrib.postgres.fields.CICharField`` is removed + except for support in historical migrations. * **fields.W906**: ``django.contrib.postgres.fields.CIEmailField`` is deprecated. Support for it (except in historical migrations) will be removed - in Django 5.1. + in Django 5.1. *This check appeared in Django 4.2 and 5.0*. +* **fields.E906**: ``django.contrib.postgres.fields.CIEmailField`` is removed + except for support in historical migrations. * **fields.W907**: ``django.contrib.postgres.fields.CITextField`` is deprecated. Support for it (except in historical migrations) will be removed - in Django 5.1. + in Django 5.1. *This check appeared in Django 4.2 and 5.0*. +* **fields.E907**: ``django.contrib.postgres.fields.CITextField`` is removed + except for support for historical migrations. File fields ~~~~~~~~~~~ diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 0348bb235f..ec767b50e9 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -277,71 +277,6 @@ transform do not change. For example: at the database level and cannot be supported in a logical, consistent fashion by Django. -``CIText`` fields -================= - -.. class:: CIText(**options) - - .. deprecated:: 4.2 - - A mixin to create case-insensitive text fields backed by the citext_ type. - Read about `the performance considerations`_ prior to using it. - - To use ``citext``, use the :class:`.CITextExtension` operation to - :ref:`set up the citext extension ` in - PostgreSQL before the first ``CreateModel`` migration operation. - - If you're using an :class:`~django.contrib.postgres.fields.ArrayField` - of ``CIText`` fields, you must add ``'django.contrib.postgres'`` in your - :setting:`INSTALLED_APPS`, otherwise field values will appear as strings - like ``'{thoughts,django}'``. - - Several fields that use the mixin are provided: - -.. class:: CICharField(**options) - - .. deprecated:: 4.2 - - ``CICharField`` is deprecated in favor of - ``CharField(db_collation="…")`` with a case-insensitive - non-deterministic collation. - -.. class:: CIEmailField(**options) - - .. deprecated:: 4.2 - - ``CIEmailField`` is deprecated in favor of - ``EmailField(db_collation="…")`` with a case-insensitive - non-deterministic collation. - -.. class:: CITextField(**options) - - .. deprecated:: 4.2 - - ``CITextField`` is deprecated in favor of - ``TextField(db_collation="…")`` with a case-insensitive - non-deterministic collation. - - These fields subclass :class:`~django.db.models.CharField`, - :class:`~django.db.models.EmailField`, and - :class:`~django.db.models.TextField`, respectively. - - ``max_length`` won't be enforced in the database since ``citext`` behaves - similar to PostgreSQL's ``text`` type. - - .. _citext: https://www.postgresql.org/docs/current/citext.html - .. _the performance considerations: https://www.postgresql.org/docs/current/citext.html#id-1.11.7.19.9 - -.. admonition:: Case-insensitive collations - - It's preferable to use non-deterministic collations instead of the - ``citext`` extension. You can create them using the - :class:`~django.contrib.postgres.operations.CreateCollation` migration - operation. For more details, see :ref:`manage-postgresql-collations` and - the PostgreSQL documentation about `non-deterministic collations`_. - - .. _non-deterministic collations: https://www.postgresql.org/docs/current/collation.html#COLLATION-NONDETERMINISTIC - ``HStoreField`` =============== diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index 9a0cd94f7e..d4510d30db 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -260,3 +260,10 @@ to remove usage of these features. * The ``django.contrib.auth.hashers.SHA1PasswordHasher``, ``django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher``, and ``django.contrib.auth.hashers.UnsaltedMD5PasswordHasher`` are removed. + +* The model ``django.contrib.postgres.fields.CICharField``, + ``django.contrib.postgres.fields.CIEmailField``, and + ``django.contrib.postgres.fields.CITextField`` are removed, except for + support in historical migrations. + +* The ``django.contrib.postgres.fields.CIText`` mixin is removed. diff --git a/tests/backends/postgresql/tests.py b/tests/backends/postgresql/tests.py index 947d51ea1e..590dbe6073 100644 --- a/tests/backends/postgresql/tests.py +++ b/tests/backends/postgresql/tests.py @@ -368,13 +368,6 @@ class Tests(TestCase): for lookup in lookups: with self.subTest(lookup=lookup): self.assertIn("::text", do.lookup_cast(lookup)) - # RemovedInDjango51Warning. - for lookup in lookups: - for field_type in ("CICharField", "CIEmailField", "CITextField"): - with self.subTest(lookup=lookup, field_type=field_type): - self.assertIn( - "::citext", do.lookup_cast(lookup, internal_type=field_type) - ) def test_correct_extraction_psycopg_version(self): from django.db.backends.postgresql.base import Database, psycopg_version diff --git a/tests/invalid_models_tests/test_deprecated_fields.py b/tests/invalid_models_tests/test_deprecated_fields.py index 429e15febc..65f65e7528 100644 --- a/tests/invalid_models_tests/test_deprecated_fields.py +++ b/tests/invalid_models_tests/test_deprecated_fields.py @@ -104,46 +104,42 @@ class DeprecatedFieldsTests(SimpleTestCase): self.assertEqual( PostgresCIFieldsModel.check(), [ - checks.Warning( - "django.contrib.postgres.fields.CICharField is deprecated. Support " - "for it (except in historical migrations) will be removed in " - "Django 5.1.", + checks.Error( + "django.contrib.postgres.fields.CICharField is removed except for " + "support in historical migrations.", hint=( 'Use CharField(db_collation="…") with a case-insensitive ' "non-deterministic collation instead." ), obj=PostgresCIFieldsModel._meta.get_field("ci_char"), - id="fields.W905", + id="fields.E905", ), - checks.Warning( - "django.contrib.postgres.fields.CIEmailField is deprecated. " - "Support for it (except in historical migrations) will be removed " - "in Django 5.1.", + checks.Error( + "django.contrib.postgres.fields.CIEmailField is removed except for " + "support in historical migrations.", hint=( 'Use EmailField(db_collation="…") with a case-insensitive ' "non-deterministic collation instead." ), obj=PostgresCIFieldsModel._meta.get_field("ci_email"), - id="fields.W906", + id="fields.E906", ), - checks.Warning( - "django.contrib.postgres.fields.CITextField is deprecated. Support " - "for it (except in historical migrations) will be removed in " - "Django 5.1.", + checks.Error( + "django.contrib.postgres.fields.CITextField is removed except for " + "support in historical migrations.", hint=( 'Use TextField(db_collation="…") with a case-insensitive ' "non-deterministic collation instead." ), obj=PostgresCIFieldsModel._meta.get_field("ci_text"), - id="fields.W907", + id="fields.E907", ), - checks.Warning( - "Base field for array has warnings:\n" - " django.contrib.postgres.fields.CITextField is deprecated. " - "Support for it (except in historical migrations) will be removed " - "in Django 5.1. (fields.W907)", + checks.Error( + "Base field for array has errors:\n" + " django.contrib.postgres.fields.CITextField is removed except " + "for support in historical migrations. (fields.E907)", obj=PostgresCIFieldsModel._meta.get_field("array_ci_text"), - id="postgres.W004", + id="postgres.E001", ), ], ) diff --git a/tests/postgres_tests/fields.py b/tests/postgres_tests/fields.py index 1565b5ed43..c2513fca0c 100644 --- a/tests/postgres_tests/fields.py +++ b/tests/postgres_tests/fields.py @@ -7,9 +7,6 @@ import enum from django.db import models try: - from django.contrib.postgres.fields import CICharField # RemovedInDjango51Warning. - from django.contrib.postgres.fields import CIEmailField # RemovedInDjango51Warning. - from django.contrib.postgres.fields import CITextField # RemovedInDjango51Warning. from django.contrib.postgres.fields import ( ArrayField, BigIntegerRangeField, @@ -47,9 +44,6 @@ except ImportError: ArrayField = DummyArrayField BigIntegerRangeField = models.Field - CICharField = models.Field # RemovedInDjango51Warning. - CIEmailField = models.Field # RemovedInDjango51Warning. - CITextField = models.Field # RemovedInDjango51Warning. DateRangeField = models.Field DateTimeRangeField = DummyContinuousRangeField DecimalRangeField = DummyContinuousRangeField diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 011a0d729b..5538b436ad 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -3,9 +3,6 @@ from django.db import migrations, models from ..fields import ( ArrayField, BigIntegerRangeField, - CICharField, - CIEmailField, - CITextField, DateRangeField, DateTimeRangeField, DecimalRangeField, @@ -290,23 +287,6 @@ class Migration(migrations.Migration): options=None, bases=None, ), - # RemovedInDjango51Warning. - migrations.CreateModel( - name="CITestModel", - fields=[ - ( - "name", - CICharField(primary_key=True, serialize=False, max_length=255), - ), - ("email", CIEmailField()), - ("description", CITextField()), - ("array_field", ArrayField(CITextField(), null=True)), - ], - options={ - "required_db_vendor": "postgresql", - }, - bases=None, - ), migrations.CreateModel( name="Line", fields=[ diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 05f2732fb8..a97894e327 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -3,9 +3,6 @@ from django.db import models from .fields import ( ArrayField, BigIntegerRangeField, - CICharField, - CIEmailField, - CITextField, DateRangeField, DateTimeRangeField, DecimalRangeField, @@ -119,14 +116,6 @@ class Character(models.Model): name = models.CharField(max_length=255) -# RemovedInDjango51Warning. -class CITestModel(PostgreSQLModel): - name = CICharField(primary_key=True, max_length=255) - email = CIEmailField() - description = CITextField() - array_field = ArrayField(CITextField(), null=True) - - class Line(PostgreSQLModel): scene = models.ForeignKey("Scene", models.CASCADE) character = models.ForeignKey("Character", models.CASCADE) diff --git a/tests/postgres_tests/test_citext.py b/tests/postgres_tests/test_citext.py deleted file mode 100644 index 2abb56b39f..0000000000 --- a/tests/postgres_tests/test_citext.py +++ /dev/null @@ -1,91 +0,0 @@ -# RemovedInDjango51Warning. -""" -The citext PostgreSQL extension supports indexing of case-insensitive text -strings and thus eliminates the need for operations such as iexact and other -modifiers to enforce use of an index. -""" -from django.db import IntegrityError -from django.utils.deprecation import RemovedInDjango51Warning - -from . import PostgreSQLTestCase -from .models import CITestModel - - -class CITextTestCase(PostgreSQLTestCase): - case_sensitive_lookups = ("contains", "startswith", "endswith", "regex") - - @classmethod - def setUpTestData(cls): - cls.john = CITestModel.objects.create( - name="JoHn", - email="joHn@johN.com", - description="Average Joe named JoHn", - array_field=["JoE", "jOhn"], - ) - - def test_equal_lowercase(self): - """ - citext removes the need for iexact as the index is case-insensitive. - """ - self.assertEqual( - CITestModel.objects.filter(name=self.john.name.lower()).count(), 1 - ) - self.assertEqual( - CITestModel.objects.filter(email=self.john.email.lower()).count(), 1 - ) - self.assertEqual( - CITestModel.objects.filter( - description=self.john.description.lower() - ).count(), - 1, - ) - - def test_fail_citext_primary_key(self): - """ - Creating an entry for a citext field used as a primary key which - clashes with an existing value isn't allowed. - """ - with self.assertRaises(IntegrityError): - CITestModel.objects.create(name="John") - - def test_array_field(self): - instance = CITestModel.objects.get() - self.assertEqual(instance.array_field, self.john.array_field) - self.assertTrue( - CITestModel.objects.filter(array_field__contains=["joe"]).exists() - ) - - def test_lookups_name_char(self): - for lookup in self.case_sensitive_lookups: - with self.subTest(lookup=lookup): - query = {"name__{}".format(lookup): "john"} - self.assertSequenceEqual( - CITestModel.objects.filter(**query), [self.john] - ) - - def test_lookups_description_text(self): - for lookup, string in zip( - self.case_sensitive_lookups, ("average", "average joe", "john", "Joe.named") - ): - with self.subTest(lookup=lookup, string=string): - query = {"description__{}".format(lookup): string} - self.assertSequenceEqual( - CITestModel.objects.filter(**query), [self.john] - ) - - def test_lookups_email(self): - for lookup, string in zip( - self.case_sensitive_lookups, ("john", "john", "john.com", "john.com") - ): - with self.subTest(lookup=lookup, string=string): - query = {"email__{}".format(lookup): string} - self.assertSequenceEqual( - CITestModel.objects.filter(**query), [self.john] - ) - - def test_citext_deprecated(self): - from django.contrib.postgres.fields import CIText - - msg = "django.contrib.postgres.fields.CIText mixin is deprecated." - with self.assertRaisesMessage(RemovedInDjango51Warning, msg): - CIText() diff --git a/tests/runtests.py b/tests/runtests.py index 2eb7490170..5d138ffb0e 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -245,13 +245,6 @@ def setup_collect_tests(start_at, start_after, test_labels=None): settings.LOGGING = log_config settings.SILENCED_SYSTEM_CHECKS = [ "fields.W342", # ForeignKey(unique=True) -> OneToOneField - # django.contrib.postgres.fields.CICharField deprecated. - "fields.W905", - "postgres.W004", - # django.contrib.postgres.fields.CIEmailField deprecated. - "fields.W906", - # django.contrib.postgres.fields.CITextField deprecated. - "fields.W907", ] # Load all the ALWAYS_INSTALLED_APPS.