#28702: Field.db_type, Field.get_internal_type() and postgresql ::citext
--------------------------------------------+------------------------
Reporter: Дилян Палаузов | Owner: (none)
Type: Uncategorized | Status: new
Component: contrib.postgres | Version: 1.11
Severity: Normal | Keywords:
Triage Stage: Unreviewed | Has patch: 1
Needs documentation: 0 | Needs tests: 0
Patch needs improvement: 0 | Easy pickings: 0
UI/UX: 0 |
--------------------------------------------+------------------------
According to https://docs.djangoproject.com/en/1.11/howto/custom-model-
fields/#custom-database-types db_type() is used when constructing a WHERE
clause that includes the model field – that is, when you retrieve data
using QuerySet methods like get(), filter(), and exclude() and have the
model field as an argument.
Then https://docs.djangoproject.com/en/1.11/howto/custom-model-fields
/#emulating-built-in-field-types states, that get_internal_type() is used
for correct usage of columns and during migrations.
This would imply that having a django.contrib.postgresq.fields.CICharField
field, calling Model.objects.filter(field_contains="ABC") would be
converted to 'WHERE field::citext LIKE "%ABC%"' but it is converted to
'WHERE field::text LIKE "%ABC%"', which does not work as expected, so one
has to call filter(field_icontains="ABC") instead. The reason is that
django/db/models/lookups.py:BuiltinLookup.process_lhs() passes the result
of get_internal_type to
db/backends/postgresql/operations.py:lookup_cast() and this is used to
distinguish the right lookup.
My proposal for fixing the code for ::citext:
{{{
diff --git a/django/contrib/postgres/fields/citext.py
b/django/contrib/postgres/fields/citext.py
--- a/django/contrib/postgres/fields/citext.py
+++ b/django/contrib/postgres/fields/citext.py
@@ -7,6 +7,9 @@ class CIText:
def db_type(self, connection):
return 'citext'
+ def get_internal_type(self):
+ return "CI" + super().get_internal_type()
+
class CICharField(CIText, CharField):
pass
diff --git a/django/db/backends/postgresql/operations.py
b/django/db/backends/postgresql/operations.py
--- a/django/db/backends/postgresql/operations.py
+++ b/django/db/backends/postgresql/operations.py
@@ -77,6 +77,8 @@ class DatabaseOperations(BaseDatabaseOperations):
'istartswith', 'endswith', 'iendswith',
'regex', 'iregex'):
if internal_type in ('IPAddressField',
'GenericIPAddressField'):
lookup = "HOST(%s)"
+ elif internal_type in ('CICharField', 'CITextField'):
+ lookup = "%s::citext"
else:
lookup = "%s::text"
diff --git a/django/db/backends/postgresql/schema.py
b/django/db/backends/postgresql/schema.py
--- a/django/db/backends/postgresql/schema.py
+++ b/django/db/backends/postgresql/schema.py
@@ -112,6 +112,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if (old_field.db_index or old_field.unique) and (
(old_type.startswith('varchar') and not
new_type.startswith('varchar')) or
(old_type.startswith('text') and not
new_type.startswith('text'))
+ (old_type.startswith('citext') and not
new_type.startswith('citext'))
):
index_name = self._create_index_name(model._meta.db_table,
[old_field.column], suffix='_like')
self.execute(self._delete_constraint_sql(self.sql_delete_index, model,
index_name))
@@ -131,3 +132,11 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
if old_field.unique and not (new_field.db_index or
new_field.unique):
index_to_remove =
self._create_index_name(model._meta.db_table, [old_field.column],
suffix='_like')
self.execute(self._delete_constraint_sql(self.sql_delete_index, model,
index_to_remove))
+
+
+ def _alter_column_null_sql(self, model, old_field, new_field):
+ if (self.connection.features.interprets_empty_strings_as_nulls
and
+ new_field.get_internal_type() in ("CICharField",
"CITextField")):
+ # The field is nullable in the database anyway, leave it
alone.
+ return
+ return super()._alter_column_null_sql(model, old_field, new_field
}}}
--
Ticket URL: <https://code.djangoproject.com/ticket/28702>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
--
You received this message because you are subscribed to the Google Groups
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit
https://groups.google.com/d/msgid/django-updates/057.54d9439dad7dd40010f6ed4c9ff6dca1%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.