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

Уникальное поле модели в 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 поможет? Может быть, какое-то другое решение существует?

4b9b3361

Ответ 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()