query - raw queries in django
Campo de modelo único en Django y sensibilidad a mayúsculas y minúsculas(postgres) (9)
Además de la opción ya mencionada para anular el guardado, simplemente puede almacenar todo el texto en minúsculas en la base de datos y ponerlas en mayúscula al mostrarlas.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
Considere la siguiente situación:
Supongamos que mi aplicación permite a los usuarios crear estados / provincias en su país. Solo por claridad, solo estamos considerando caracteres ASCII aquí.
En los Estados Unidos, un usuario podría crear el estado llamado "Texas". Si esta aplicación se usa internamente, digamos que al usuario no le importa si se escribe "texas" o "Texas" o "teXas"
Pero, lo que es más importante, el sistema debería evitar la creación de "texas" si "Texas" ya está en la base de datos.
Si el modelo es como el siguiente:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
La singularidad sería sensible a mayúsculas y minúsculas en postgres; es decir, postgres le permitiría al usuario crear tanto "texas" como "Texas" ya que se consideran únicos.
Qué se puede hacer en esta situación para evitar dicho comportamiento. ¿Cómo se puede proporcionar una singularidad insensible a mayúsculas y minúsculas con Django y Postgres?
En este momento estoy haciendo lo siguiente para evitar la creación de duplicados insensibles a mayúsculas y minúsculas.
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data[''name'']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError(''State already exists.'')
class Meta:
model = State
Hay una serie de casos en los que tendré que hacer este control y no estoy dispuesto a tener que escribir comprobaciones iexact similares en todas partes.
¿Solo me pregunto si hay una forma incorporada o mejor? Tal vez db_type ayudaría? Tal vez existe alguna otra solución?
De forma alternativa, puede cambiar el Administrador de conjunto de consultas predeterminado para hacer consultas que no distingan entre mayúsculas y minúsculas en el campo. Al tratar de resolver un problema similar, me encontré con:
http://djangosnippets.org/snippets/305/
Código pegado aquí por conveniencia:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# ''name'' is a field in your Model whose lookups you want case-insensitive by default
if ''name'' in kwargs:
kwargs[''name__iexact''] = kwargs[''name'']
del kwargs[''name'']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
La solución de suhail funcionó para mí sin la necesidad de habilitar citext, la solución bastante fácil solo una función limpia y en lugar de mayúsculas, utilicé upper()
. La solución de Mayuresh también funciona, pero cambió el campo de CharField
a TextField
.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()
Pasos explícitos para la respuesta de Mayuresh:
en postgres do: CREATE EXTENSION citext;
en tu models.py agrega:
from django.db.models import fields class CaseInsensitiveTextField(fields.TextField): def db_type(self, connection): return "citext"
referencia: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py
en su modelo use: name = CaseInsensitiveTextField (unique = True)
Por el lado de Postgres, un índice funcional único le permitirá aplicar valores únicos sin mayúsculas y minúsculas. También se menciona citext, pero esto funcionará con versiones anteriores de PostgreSQL y es una técnica útil en general.
Ejemplo:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values (''Texas'');
INSERT 0 1
# insert into foo values (''texas'');
ERROR: duplicate key value violates unique constraint "foo_bar"
Puede definir un campo de modelo personalizado derivado de los models.CharField
. Este campo podría verificar valores duplicados, ignorando el caso.
La documentación de los campos personalizados está aquí http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Consulte http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py para ver un ejemplo de cómo crear un campo personalizado mediante la creación de una subclase de un campo existente.
Puede usar el módulo citext de PostgreSQL https://www.postgresql.org/docs/current/static/citext.html
Si usa este módulo, el campo personalizado podría definir "tipo_db" como CITEXT para bases de datos PostgreSQL.
Esto llevaría a una comparación insensible a mayúsculas / minúsculas para valores únicos en el campo personalizado.
Puede hacerlo sobrescribiendo el método de guardado del modelo: consulte los docs . Básicamente harías algo como:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
Además, puedo estar equivocado al respecto, pero la próxima rama SoC de validación de modelos nos permitirá hacer esto más fácilmente.
Puede usar lookup = ''iexact'' en UniqueValidator en el serializador, así:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup=''iexact''
)]
Versión django: 1.11.6
una solución muy simple:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()