Подтвердить что ты не робот

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

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

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

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

Возможно ли это в Django?

Единственное решение, с которым я столкнулся, это "как-то" переопределить диспетчер модели, использовать дополнительное поле или всегда использовать "iexact" в результатах поиска.

Я на Django 1.3 и PostgreSQL 8.4.2.

4b9b3361

Ответ 1

Сохраните исходную строку в смешанном регистре в виде простого текстового столбца. Используйте тип данных 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.

Ответ 2

С переопределением менеджера модели у вас есть два варианта. Во-первых, просто создайте новый метод поиска:

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

Как и в 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.

Ответ 4

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

Ответ 5

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

Конечная запись. Этот метод можно использовать для хранения строчных значений в базе данных и не беспокоиться о __iexact.

Ответ 7

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