¿Campos de modelo únicos insensibles a mayúsculas en Django?

Básicamente, tengo un nombre de usuario que es único (no distingue mayúsculas y minúsculas), pero el caso importa cuando se muestra como lo proporciona el usuario.

Tengo los siguientes requisitos:

  • campo es compatible con CharField
  • El campo es único, pero no distingue entre mayúsculas y minúsculas.
  • el campo debe poder buscarse ignorando el caso (evite usar iexact, se olvida fácilmente)
  • campo se almacena con el caso intacto
  • Preferiblemente aplicado en el nivel de base de datos
  • Preferiblemente evite almacenar un campo extra

¿Es esto posible en Django?

La única solución que se me ocurrió es “de alguna manera” anular el Administrador de modelos, usar un campo adicional o usar siempre ‘iexact’ en las búsquedas.

Estoy en Django 1.3 y PostgreSQL 8.4.2.

Almacene la cadena de caja mixta original en una columna de texto sin formato . Utilice el tipo de datos text o varchar sin el modificador de longitud en lugar de varchar(n) . Son esencialmente iguales, pero con varchar (n) tiene que establecer un límite de longitud arbitrario, eso puede ser una molestia si desea cambiar más adelante. Lea más sobre eso en el manual o en esta respuesta relacionada por Peter Eisentraut @ serverfault.SE .

Crear un índice único funcional en la parte lower(string) . Ese es el punto principal aquí:

 CREATE UNIQUE INDEX my_idx ON mytbl(lower(name)); 

Si intenta INSERT un nombre de caso mixto que ya está allí en minúscula, obtendrá un error de violación de clave única.
Para búsquedas rápidas de igualdad use una consulta como esta:

 SELECT * FROM mytbl WHERE lower(name) = 'foo' --'foo' is lower case, of course. 

Use la misma expresión que tiene en el índice (para que el planificador de consultas reconozca la compatibilidad) y esto será muy rápido.


Como nota aparte: es posible que desee actualizar a una versión más reciente de PostgreSQL. Ha habido muchos arreglos importantes desde 8.4.2 . Más en el sitio oficial de versiones de Postgres .

Con anular el administrador de modelos, tiene dos opciones. Lo primero es crear un nuevo método de búsqueda:

 class MyModelManager(models.Manager): def get_by_username(self, username): return self.get(username__iexact=username) class MyModel(models.Model): ... objects = MyModelManager() 

Luego, usa get_by_username('blah') lugar de get(username='blah') , y no tiene que preocuparse por olvidar iexact . Por supuesto, eso requiere que recuerdes usar get_by_username .

La segunda opción es mucho más intrincada y complicada. No me atrevo a sugerirlo, pero en aras de la integridad, iexact filter y get tal que si olvida iexact al realizar una consulta por nombre de usuario, se lo agregará.

 class MyModelManager(models.Manager): def filter(self, **kwargs): if 'username' in kwargs: kwargs['username__iexact'] = kwargs['username'] del kwargs['username'] return super(MyModelManager, self).filter(**kwargs) def get(self, **kwargs): if 'username' in kwargs: kwargs['username__iexact'] = kwargs['username'] del kwargs['username'] return super(MyModelManager, self).get(**kwargs) class MyModel(models.Model): ... objects = MyModelManager() 

A partir de Django 1.11, puede usar CITextField , un campo específico de Postgres para texto sin distinción de mayúsculas, respaldado por el tipo de citext.

 from django.db import models from django.contrib.postgres.fields import CITextField class Something(models.Model): foo = CITextField() 

Django también proporciona CIEmailField y CICharField , que son versiones que no distinguen entre mayúsculas y minúsculas de EmailField y CharField .

Puedes usar el tipo de citext postgres en su lugar y no molestarte más con ningún tipo de iexact. Simplemente haga una nota en el modelo de que el campo subyacente no distingue entre mayúsculas y minúsculas. Una solución mucho más fácil.

Dado que un nombre de usuario siempre está en minúsculas, se recomienda usar un campo de modelo personalizado en minúsculas en Django. Para facilitar el acceso y el fields.py código, cree un nuevo archivo fields.py en su carpeta de aplicaciones.

 from django.db import models from django.utils.six import with_metaclass # Custom lowecase CharField class LowerCharField(with_metaclass(models.SubfieldBase, models.CharField)): def __init__(self, *args, **kwargs): self.is_lowercase = kwargs.pop('lowercase', False) super(LowerCharField, self).__init__(*args, **kwargs) def get_prep_value(self, value): value = super(LowerCharField, self).get_prep_value(value) if self.is_lowercase: return value.lower() return value 

Uso en models.py

 from django.db import models from your_app_name.fields import LowerCharField class TheUser(models.Model): username = LowerCharField(max_length=128, lowercase=True, null=False, unique=True) 

Nota final : puede usar este método para almacenar valores en minúscula en la base de datos, y no preocuparse por __iexact .

Puede usar lookup = ‘iexact’ en UniqueValidator en el serializador, así: campo de modelo único en Django y sensibilidad a mayúsculas y minúsculas (postgres)

La mejor solución es anular “get_prep_value” por Django Models Field

 class LowerSlugField(models.SlugField): def get_prep_value(self, value): return str(value).lower() 

y entonces:

 company_slug = LowerSlugField(max_length=255, unique=True)