diff --git a/django/contrib/postgres/fields/citext.py b/django/contrib/postgres/fields/citext.py index cab1f87957..42660001ae 100644 --- a/django/contrib/postgres/fields/citext.py +++ b/django/contrib/postgres/fields/citext.py @@ -1,8 +1,20 @@ -from django.db.models import CharField +from django.db.models import CharField, EmailField, TextField -__all__ = ['CITextField'] +__all__ = ['CICharField', 'CIEmailField', 'CIText', 'CITextField'] -class CITextField(CharField): +class CIText: def db_type(self, connection): return 'citext' + + +class CICharField(CIText, CharField): + pass + + +class CIEmailField(CIText, EmailField): + pass + + +class CITextField(CIText, TextField): + pass diff --git a/docs/ref/contrib/postgres/fields.txt b/docs/ref/contrib/postgres/fields.txt index 5a79d8879d..cfce65420f 100644 --- a/docs/ref/contrib/postgres/fields.txt +++ b/docs/ref/contrib/postgres/fields.txt @@ -250,22 +250,32 @@ At present using :attr:`~django.db.models.Field.db_index` will create a A more useful index is a ``GIN`` index, which you should create using a :class:`~django.db.migrations.operations.RunSQL` operation. -``CITextField`` -=============== +``CIText`` fields +================= -.. class:: CITextField(**options) +.. class:: CIText(**options) .. versionadded:: 1.11 - This field is a subclass of :class:`~django.db.models.CharField` backed by - the citext_ type, a case-insensitive character string type. Read about `the - performance considerations`_ prior to using this field. + A mixin to create case-insensitive text fields backed by the citext_ type. + Read about `the performance considerations`_ prior to using it. - To use this field, setup the ``citext`` extension in PostgreSQL before the - first ``CreateModel`` migration operation using the - :class:`~django.contrib.postgres.operations.CITextExtension` operation. The - code to setup the extension is similar to the example for - :class:`~django.contrib.postgres.fields.HStoreField`. + To use ``citext``, use the :class:`.CITextExtension` operation to + :ref:`setup the citext extension ` in + PostgreSQL before the first ``CreateModel`` migration operation. + + Several fields that use the mixin are provided: + +.. class:: CICharField(**options) +.. class:: CIEmailField(**options) +.. class:: CITextField(**options) + + 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/static/citext.html .. _the performance considerations: https://www.postgresql.org/docs/current/static/citext.html#AEN169274 diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 02f341a3e5..3fb4968e23 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -210,10 +210,11 @@ Minor features parameter to specify a custom class to encode data types not supported by the standard encoder. -* The new :class:`~django.contrib.postgres.fields.CITextField` and +* The new :class:`~django.contrib.postgres.fields.CIText` mixin and :class:`~django.contrib.postgres.operations.CITextExtension` migration operation allow using PostgreSQL's ``citext`` extension for case-insensitive - lookups. + lookups. Three fields are provided: :class:`.CICharField`, + :class:`.CIEmailField`, and :class:`.CITextField`. * The new :class:`~django.contrib.postgres.aggregates.JSONBAgg` allows aggregating values as a JSON array. diff --git a/tests/postgres_tests/fields.py b/tests/postgres_tests/fields.py index 14283ccd61..233ef4888c 100644 --- a/tests/postgres_tests/fields.py +++ b/tests/postgres_tests/fields.py @@ -6,9 +6,9 @@ from django.db import models try: from django.contrib.postgres.fields import ( - ArrayField, BigIntegerRangeField, CITextField, DateRangeField, - DateTimeRangeField, FloatRangeField, HStoreField, IntegerRangeField, - JSONField, + ArrayField, BigIntegerRangeField, CICharField, CIEmailField, + CITextField, DateRangeField, DateTimeRangeField, FloatRangeField, + HStoreField, IntegerRangeField, JSONField, ) from django.contrib.postgres.search import SearchVectorField except ImportError: @@ -30,6 +30,8 @@ except ImportError: ArrayField = DummyArrayField BigIntegerRangeField = models.Field + CICharField = models.Field + CIEmailField = models.Field CITextField = models.Field DateRangeField = models.Field DateTimeRangeField = models.Field diff --git a/tests/postgres_tests/migrations/0002_create_test_models.py b/tests/postgres_tests/migrations/0002_create_test_models.py index 8f72183f5c..5a6a378358 100644 --- a/tests/postgres_tests/migrations/0002_create_test_models.py +++ b/tests/postgres_tests/migrations/0002_create_test_models.py @@ -5,9 +5,9 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import migrations, models from ..fields import ( - ArrayField, BigIntegerRangeField, CITextField, DateRangeField, - DateTimeRangeField, FloatRangeField, HStoreField, IntegerRangeField, - JSONField, SearchVectorField, + ArrayField, BigIntegerRangeField, CICharField, CIEmailField, CITextField, + DateRangeField, DateTimeRangeField, FloatRangeField, HStoreField, + IntegerRangeField, JSONField, SearchVectorField, ) from ..models import TagField @@ -139,9 +139,11 @@ class Migration(migrations.Migration): bases=None, ), migrations.CreateModel( - name='CITextTestModel', + name='CITestModel', fields=[ - ('name', CITextField(primary_key=True, max_length=255)), + ('name', CICharField(primary_key=True, max_length=255)), + ('email', CIEmailField()), + ('description', CITextField()), ], options={ 'required_db_vendor': 'postgresql', diff --git a/tests/postgres_tests/models.py b/tests/postgres_tests/models.py index 2a0d69b21a..255db91513 100644 --- a/tests/postgres_tests/models.py +++ b/tests/postgres_tests/models.py @@ -2,9 +2,9 @@ from django.core.serializers.json import DjangoJSONEncoder from django.db import models from .fields import ( - ArrayField, BigIntegerRangeField, CITextField, DateRangeField, - DateTimeRangeField, FloatRangeField, HStoreField, IntegerRangeField, - JSONField, SearchVectorField, + ArrayField, BigIntegerRangeField, CICharField, CIEmailField, CITextField, + DateRangeField, DateTimeRangeField, FloatRangeField, HStoreField, + IntegerRangeField, JSONField, SearchVectorField, ) @@ -101,8 +101,10 @@ class Character(models.Model): return self.name -class CITextTestModel(PostgreSQLModel): - name = CITextField(primary_key=True, max_length=255) +class CITestModel(PostgreSQLModel): + name = CICharField(primary_key=True, max_length=255) + email = CIEmailField() + description = CITextField() def __str__(self): return self.name diff --git a/tests/postgres_tests/test_citext.py b/tests/postgres_tests/test_citext.py index 7f86f6a205..f99f3bfa47 100644 --- a/tests/postgres_tests/test_citext.py +++ b/tests/postgres_tests/test_citext.py @@ -6,25 +6,31 @@ modifiers to enforce use of an index. from django.db import IntegrityError from . import PostgreSQLTestCase -from .models import CITextTestModel +from .models import CITestModel class CITextTestCase(PostgreSQLTestCase): @classmethod def setUpTestData(cls): - CITextTestModel.objects.create(name='JoHn') + cls.john = CITestModel.objects.create( + name='JoHn', + email='joHn@johN.com', + description='Average Joe named JoHn', + ) def test_equal_lowercase(self): """ citext removes the need for iexact as the index is case-insensitive. """ - self.assertEqual(CITextTestModel.objects.filter(name='john').count(), 1) + 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_case(self): + def test_fail_citext_primary_key(self): """ - Creating an entry for a citext-field which clashes with an existing - value isn't allowed. + 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): - CITextTestModel.objects.create(name='John') + CITestModel.objects.create(name='John')