Нечувствительные к регистру уникальные поля модели в Django? - PullRequest
41 голосов
/ 15 октября 2011

У меня в основном имя пользователя уникально (без учета регистра), но регистр имеет значение при отображении, как указано пользователем.

У меня есть следующие требования:

  • поле совместимо с CharField
  • уникально, но регистр не учитывается
  • поле должно быть доступно для поиска без учета регистра (избегайте использования iexact, легко забывается)
  • поле хранится без изменений регистра
  • предпочтительно применяется на уровне базы данных
  • желательно избегать хранения дополнительного поля

Возможно ли это в Джанго?

Единственное решение, которое я придумал, это «как-то» переопределить менеджер моделей, использовать дополнительное поле или всегда использовать «iexact» в поисках.

Я работаю на Django 1.3 и PostgreSQL 8.4.2.

Ответы [ 7 ]

26 голосов
/ 15 октября 2011

Сохранить исходную строку в смешанном регистре в виде простого текстового столбца . Используйте тип данных text или varchar без модификатора длины, а не varchar(n). По сути, они одинаковы, но с помощью varchar (n) вы должны установить произвольный предел длины, что может быть неприятно, если вы захотите изменить позже. Подробнее об этом читайте в руководстве или в ответе Питера Айзентраута @ serverfault.SE .

Создать функциональный уникальный индекс в lower(string). Это главное здесь:

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

Если вы попытаетесь ввести INSERT смешанное имя, которое уже есть в нижнем регистре, вы получите ошибку нарушения уникального ключа.
Для быстрого поиска равенства используйте запрос, подобный следующему:

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

Используйте то же выражение, что и в индексе (чтобы планировщик запросов распознал совместимость), и это будет очень быстро.


В качестве отступления: вы можете перейти на более новую версию PostgreSQL. Начиная с 8.4.2 было исправлено важных исправлений. Подробнее на официальном сайте версий Postgres .

21 голосов
/ 05 мая 2017

Начиная с Django 1.11, вы можете использовать CITextField , специфичное для Postgres поле для нечувствительного к регистру текста, поддерживаемого типом citext.

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

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

Django также обеспечивает CIEmailFieldи CICharField, которые не чувствительны к регистру версий EmailField и CharField.

17 голосов
/ 15 октября 2011

При переопределении менеджера моделей у вас есть два варианта. Сначала нужно просто создать новый метод поиска:

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

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

Затем вы используете get_by_username('blah') вместо get(username='blah'), и вам не нужно беспокоиться о том, чтобы забыть iexact. Конечно, для этого требуется, чтобы вы не забыли использовать get_by_username.

Второй вариант гораздо более хакерский и запутанный. Я не решаюсь даже предложить это, но для полноты картины я переопределю filter и get так, что если вы забудете iexact при запросе по имени пользователя, он добавит его для вас.

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()
3 голосов
/ 12 марта 2016

Поскольку имя пользователя всегда в нижнем регистре, рекомендуется использовать настраиваемое поле модели нижнего регистра в Django.Для простоты доступа и аккуратности кода создайте новый файл fields.py в папке приложения.

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

Использование в 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)

End Note : Вы можете использовать этот метод для хранения строчных значений в базе данных и не беспокоиться о __iexact.

3 голосов
/ 27 июня 2015

Вместо этого вы можете использовать тип citext postgres и больше не беспокоиться о какой-либо точности. Просто отметьте в модели, что поле не учитывает регистр. Гораздо проще решение.

1 голос
/ 28 декабря 2017

Вы можете использовать lookup = 'iexact' в UniqueValidator на сериализаторе, например так: Уникальное поле модели в Django и чувствительность к регистру (postgres)

0 голосов
/ 20 ноября 2018

Лучшее решение - переопределить «get_prep_value» полем Django Models

class LowerSlugField(models.SlugField):

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

, а затем:

company_slug = LowerSlugField(max_length=255, unique=True)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...