web-dev-qa-db-fra.com

Champs de modèle uniques insensibles à la casse dans Django?

J'ai essentiellement un nom d'utilisateur est unique (insensible à la casse), mais la casse est importante lors de l'affichage tel que fourni par l'utilisateur.

J'ai les exigences suivantes:

  • le champ est compatible avec CharField
  • le champ est unique, mais insensible à la casse
  • le champ doit être interrogeable sans tenir compte de la casse (éviter d'utiliser iexact, facilement oublié)
  • le champ est stocké avec le cas intact
  • de préférence appliqué au niveau de la base de données
  • éviter de préférence de stocker un champ supplémentaire

Est-ce possible à Django?

La seule solution que j'ai proposée est «en quelque sorte» de remplacer le gestionnaire de modèles, d'utiliser un champ supplémentaire ou de toujours utiliser «iexact» dans les recherches.

Je suis sur Django 1.3 et PostgreSQL 8.4.2.

40
noobzie

Stockez la chaîne originale à la casse mixte dans une colonne de texte brut . Utilisez le type de donnéestextou varchar sans modificateur de longueur plutôt que varchar(n). Ils sont essentiellement les mêmes, mais avec varchar (n), vous devez définir une limite de longueur arbitraire, ce qui peut être difficile si vous souhaitez modifier ultérieurement. En savoir plus sur cela dans le manuel ou dans ce réponse associée de Peter Eisentraut @ serverfault.SE .

Créez un index unique fonctionnel sur lower(string). C'est le point majeur ici:

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

Si vous essayez de INSERT un nom de casse mixte qui existe déjà en minuscule, vous obtenez une erreur de violation de clé unique.
Pour les recherches d'égalité rapides, utilisez une requête comme celle-ci:

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

Utilisez la même expression que vous avez dans l'index (pour que le planificateur de requêtes reconnaisse la compatibilité) et ce sera très rapide.


En passant: vous voudrez peut-être passer à une version plus récente de PostgreSQL. Il y a eu beaucoup de corrections importantes depuis la 8.4.2 . Plus d'informations sur le site officiel de gestion de versions Postgres .

26
Erwin Brandstetter

Si vous remplacez le gestionnaire de modèles, vous avez deux options. Tout d'abord, il suffit de créer une nouvelle méthode de recherche:

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

class MyModel(models.Model):
   ...
   objects = MyModelManager()

Ensuite, vous utilisez get_by_username('blah') au lieu de get(username='blah') et vous n'avez pas à vous soucier d'oubli iexact. Bien sûr, cela nécessite que vous vous souveniez d'utiliser get_by_username.

La deuxième option est beaucoup plus compliquée et compliquée. J'hésite même à le suggérer, mais par souci d'exhaustivité, je vais: remplacer filter et get de telle sorte que si vous oubliez iexact lorsque vous interrogez par nom d'utilisateur, il l'ajoutera à votre place.

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()
17
Chris Pratt

À partir de Django 1.11, vous pouvez utiliser CITextField , un champ spécifique à Postgres pour le texte ne respectant pas la casse et sauvegardé par le type citext.

from Django.db import models
from Django.contrib.postgres.fields import CITextField

class Something(models.Model):
    foo = CITextField()

Django fournit également CIEmailField et CICharField, qui sont des versions de EmailField et CharField qui respectent la casse.

17
Rodney Folz

Vous pouvez utiliser le type postgres citext à la place et ne plus vous embêter avec aucune sorte d’exacte. Il suffit de noter dans le modèle que le champ sous-jacent n’est pas sensible à la casse. Solution beaucoup plus simple.

3
Zorg

Comme un nom d'utilisateur est toujours en minuscule, il est recommandé d'utiliser un champ de modèle en minuscule personnalisé dans Django. Pour faciliter l’accès et ranger le code, créez un nouveau fichier fields.py dans votre dossier d’application.

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

Utilisation in 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)

Note de fin : Vous pouvez utiliser cette méthode pour stocker des valeurs minuscules dans la base de données sans vous soucier de __iexact

2
Suraj Thapar

Vous pouvez utiliser lookup = 'iexact' dans UniqueValidator sur le sérialiseur, comme suit: Champ de modèle unique dans Django et sensibilité à la casse (postgres)

La meilleure solution consiste à remplacer "get_prep_value" par Django Models Field

class LowerSlugField(models.SlugField):

    def get_prep_value(self, value):
        return str(value).lower()

et alors:

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