Champ de modèle Unique dans Django et sensibilité à la casse (postgres)

Considérez la situation suivante: -

Supposons que mon application permet aux utilisateurs de créer les états / provinces dans leur pays. Juste pour plus de clarté, nous ne considérons que les caractères ASCII ici.

Aux États-Unis, un utilisateur pourrait créer l'état appelé "Texas". Si cette application est utilisé en interne, disons que l'utilisateur ne se soucie pas si c'est orthographié "texas" ou "Texas" ou "teXas"

Mais surtout, le système devrait empêcher la création de" texas " si "Texas" est déjà dans le la base de données.

Si le modèle est comme suit:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

L'unicité serait sensible à la casse dans postgres; c'est, postgres permettrait à l'utilisateur de créer à la fois "texas" et "Texas", comme ils sont considéré comme unique.

Ce qui peut être fait dans cette situation pour empêcher un tel comportement. Comment un aller sur la fourniture de cas - insenstitive unicité avec Django et Postgres

En ce moment, je fais ce qui suit pour empêcher la création de cas- insensible dupliquer.

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

Il y a un certain nombre de cas où je vais devoir faire cette vérification et je ne suis pas désireux d'avoir à écrire des vérifications iexact similaires partout.

Je me demande juste s'il y a un intégré ou meilleure façon de faire? Peut-être que db_type aiderait? Peut-être une autre solution existe?

22
demandé sur chefsmart 2009-12-07 07:31:16

9 réponses

Vous pouvez définir un champ de modèle personnalisé dérivé de models.CharField. Ce champ peut vérifier les valeurs en double, en ignorant la casse.

La documentation des champs personnalisés est ici http://docs.djangoproject.com/en/dev/howto/custom-model-fields/

Regardez http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py pour un exemple de création d'un champ personnalisé en sous-classant un champ existant.

Vous pouvez utiliser le module citext de PostgreSQL https://www.postgresql.org/docs/current/static/citext.html

Si vous utilisez ce module, le champ personnalisé peut définir "db_type" comme CITEXT pour les bases de données PostgreSQL.

Cela conduirait à une comparaison insensible à la casse pour les valeurs uniques dans le champ personnalisé.

28
répondu Mayuresh 2016-09-18 19:45:25

Vous pouvez également modifier le Gestionnaire de jeu de requêtes par défaut pour effectuer des recherches insensibles à la casse sur le champ. En essayant de résoudre un problème similaire, je suis tombé sur:

Http://djangosnippets.org/snippets/305/

Code collé ici pour plus de commodité:

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
7
répondu Foo 2010-10-31 16:46:45

Du côté Postgres des choses, un index unique fonctionnel vous permettra d'appliquer des valeurs uniques sans casse. citext est également noté, mais cela fonctionnera avec les anciennes versions de PostgreSQL et est une technique utile en général.

Exemple:

# 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"
5
répondu Alex Brasetvik 2009-12-16 09:26:04

Étapes explicites pour la réponse de Mayuresh:

  1. Dans postgres faire: créer L'EXTENSION citext;

  2. Dans votre models.py ajouter:

    from django.db.models import fields
    
    class CaseInsensitiveTextField(fields.TextField):
        def db_type(self, connection):
            return "citext"
    

    Référence: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py

  3. Dans votre modèle, utilisez: name = CaseInsensitiveTextField (unique=True)

4
répondu eyaler 2014-10-04 11:49:35

Outre l'option déjà mentionnée pour remplacer save, vous pouvez simplement stocker tout le texte en minuscules dans la base de données et les capitaliser sur l'affichage.

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)
3
répondu Michal Čihař 2009-12-14 17:16:20

Une solution très simple:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.capitalize()
2
répondu simple_human 2013-10-05 04:51:41

Vous pouvez le faire en écrasant la méthode de sauvegarde du modèle-voir les documents . Vous feriez essentiellement quelque chose comme:

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)

Aussi, je peux me tromper à ce sujet, mais la prochaine branche SoC model-validation nous permettra de le faire plus facilement.

1
répondu Rishabh Manocha 2009-12-07 08:40:46

Vous pouvez utiliser lookup= 'iexact' dans UniqueValidator sur serializer, comme ceci:

class StateSerializer(serializers.ModelSerializer): 
    name = serializers.CharField(validators=[
    UniqueValidator(
        queryset=models.State.objects.all(),lookup='iexact'
    )]

Version Django: 1.11.6

1
répondu Luan Francisco Lima Fernandes 2017-12-27 22:44:29

Solution de suhail a fonctionné pour moi sans avoir besoin d'activer citext, solution assez facile seulement une fonction propre et au lieu de capitaliser j'ai utilisé upper(). La solution de Mayuresh fonctionne également mais a changé le champ de CharField à TextField.

class State(models.Model):

    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.upper()
0
répondu Jorge 2015-06-02 21:20:23