Нечувствительные к регистру уникальные поля модели в Django?
У меня в основном имя пользователя уникально (без учета регистра), но дело имеет значение при отображении, как предоставлено пользователем.
У меня следующие требования:
-
Поле
- совместимо с CharField
Поле
- уникально, но нечувствительно к регистру
Поле
- должно быть доступно для поиска, игнорируя регистр (избегайте использования iexact, легко забывается)
Поле
- хранится с неповрежденным случаем
- желательно принудительно на уровне базы данных
- желательно избегать сохранения дополнительного поля
Возможно ли это в Django?
Единственное решение, с которым я столкнулся, это "как-то" переопределить диспетчер модели, использовать дополнительное поле или всегда использовать "iexact" в результатах поиска.
Я на Django 1.3 и PostgreSQL 8.4.2.
Ответы
Ответ 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
.
Ответ 6
Вы можете использовать lookup = 'iexact' в UniqueValidator для сериализатора, например:
Уникальное поле модели в Django и чувствительность к регистру (postgres)
Ответ 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)