Уникальное поле модели в Django и чувствительность к регистру (postgres)
Рассмотрим следующую ситуацию: -
Предположим, что мое приложение позволяет пользователям создавать состояния/провинции в своих
страна. Для ясности мы рассматриваем только символы ASCII
здесь.
В США пользователь может создать состояние под названием "Техас" . Если это приложение
используется внутри, скажем, пользователю все равно,
"texas" или "Texas" или "teXas"
Но важно то, что система должна предотвратить создание "тексаса", если
"Техас" уже находится в базе данных.
Если модель выглядит следующим образом:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
Уникальность была бы чувствительной к регистру в postgres; то есть postgres
позволит пользователю создавать как "тексас", так и "Техас" , поскольку они
считается уникальным.
Что можно сделать в этой ситуации, чтобы предотвратить такое поведение. Как сделать
один идет о предоставлении case- insenstitive уникальности с Django и
Postgres
Сейчас я делаю следующее, чтобы предотвратить создание case-
нечувствительные дубликаты.
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data['name']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('State already exists.')
class Meta:
model = State
Есть несколько случаев, когда мне нужно будет сделать эту проверку, и я не хочу писать одинаковые проверки iexact.
Просто интересно, есть ли встроенный или
лучший путь? Может быть, db_type поможет? Может быть, какое-то другое решение существует?
Ответы
Ответ 1
Вы можете определить настраиваемое поле модели, полученное из models.CharField
.
Это поле может проверять повторяющиеся значения, игнорируя случай.
Документация по пользовательским полям здесь http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Посмотрите http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py на пример создания настраиваемого поля путем подклассификации существующего поля.
Вы можете использовать модуль citext PostgreSQL https://www.postgresql.org/docs/current/static/citext.html
Если вы используете этот модуль, пользовательское поле может определить "db_type" как CITEXT для баз данных PostgreSQL.
Это приведет к нечувствительности к регистру для уникальных значений в настраиваемом поле.
Ответ 2
В качестве альтернативы вы можете изменить Менеджер запросов по умолчанию, чтобы делать нечувствительные к регистру взгляды на поле. При попытке решить подобную проблему я столкнулся:
http://djangosnippets.org/snippets/305/
Код, вставленный здесь для удобства:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# 'name' is a field in your Model whose lookups you want case-insensitive by default
if 'name' in kwargs:
kwargs['name__iexact'] = kwargs['name']
del kwargs['name']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
Ответ 3
На стороне Postgres, функциональный уникальный индекс позволит вам применять уникальные значения без случая. citext также отмечен, но это будет работать со старыми версиями PostgreSQL и является полезной техникой в целом.
Пример:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values ('Texas');
INSERT 0 1
# insert into foo values ('texas');
ERROR: duplicate key value violates unique constraint "foo_bar"
Ответ 4
Явные шаги для ответа Mayuresh:
Ответ 5
Помимо уже упомянутого варианта переопределения сохранения, вы можете просто сохранить весь текст в нижнем регистре в базе данных и использовать их при отображении.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
Ответ 6
Вы можете сделать это, перезаписав метод сохранения модели - см. docs. Вы в основном делаете что-то вроде:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
Кроме того, я могу ошибаться в этом, но предстоящая ветвь SoC для проверки модели позволит нам сделать это более легко.
Ответ 7
очень простое решение:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()
Ответ 8
Вы можете использовать lookup = 'iexact' в UniqueValidator для сериализатора, например:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup='iexact'
)]
версия django: 1.11.6
Ответ 9
Решение от suhail работало для меня без необходимости включения citext, довольно простое решение только для чистой функции, и вместо того, чтобы использовать капитал, я использовал upper()
. Решение Mayuresh также работает, но изменило поле от CharField
до TextField
.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()