it-swarm.com.ru

Нечувствительные к регистру уникальные поля модели в Django?

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

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

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

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

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

Я на Django 1.3 и PostgreSQL 8.4.2.

40
noobzie

Сохраните исходную строку смешанного регистра в столбце простого текста. Используйте тип данных text или varchar без модификатора длины, а не varchar(n). По сути, они одинаковы, но с помощью varchar (n) вы должны установить произвольный предел длины, что может быть неприятно, если вы захотите изменить позже. Подробнее об этом в руководстве или в этом связанный ответ от Peter Eisentraut @ 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 .

26
Erwin Brandstetter

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

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

Начиная с 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
Rodney Folz

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

3
Zorg

Поскольку имя пользователя всегда в нижнем регистре, рекомендуется использовать настраиваемое поле строчной модели в 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

2
Suraj Thapar

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

1

Лучшее решение - переопределить «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)
0
NKSM