Уникальное поле модели в Django и чувствительность к регистру (postgres) - PullRequest
25 голосов
/ 07 декабря 2009

Рассмотрим следующую ситуацию: -

Предположим, мое приложение позволяет пользователям создавать штаты / провинции в своих страна. Просто для ясности, мы рассматриваем только символы ASCII здесь.

В США пользователь может создать штат под названием «Техас». Если это приложение используется внутри, скажем, пользователю все равно, если это пишется "техас" или "Техас" или "Техас"

Но важно отметить, что система должна предотвращать создание "Техаса", если «Техас» уже есть в базе данных.

Если модель похожа на следующую:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

Уникальность будет чувствительна к регистру в postgres; то есть postgres позволит пользователю создавать как "Техас" и "Техас", как они считается уникальным.

Что можно сделать в этой ситуации, чтобы предотвратить такое поведение. Как один способ обеспечить уникальность регистра с Джанго и Postgres

Сейчас я делаю следующее, чтобы предотвратить создание нечувствительные дубликаты.

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

В ряде случаев мне придется выполнять эту проверку, и я не заинтересован в том, чтобы везде писать подобные точные проверки.

Просто интересно, есть ли встроенный или лучший путь? Возможно, db_type поможет? Может быть, существует другое решение?

Ответы [ 10 ]

29 голосов
/ 15 декабря 2009

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

Это приведет к нечувствительному к регистру сравнению уникальных значений в настраиваемом поле.

7 голосов
/ 31 октября 2010

В качестве альтернативы вы можете изменить Диспетчер наборов запросов по умолчанию, чтобы выполнять поиск по полю без учета регистра. Пытаясь решить похожую проблему, я наткнулся:

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
6 голосов
/ 16 декабря 2009

Со стороны 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"
5 голосов
/ 04 октября 2014

Явные шаги для ответа Мауреша:

  1. в postgres do: CREATE EXTENSION citext;

  2. в вашем models.py добавить:

    from django.db.models import fields
    
    class CaseInsensitiveTextField(fields.TextField):
        def db_type(self, connection):
            return "citext"
    

    ссылка: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py

  3. в вашей модели используйте: name = CaseInsensitiveTextField (unique = True)

3 голосов
/ 05 октября 2013

очень простое решение:

class State(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.capitalize()
3 голосов
/ 14 декабря 2009

Помимо уже упомянутой опции отмены сохранения, вы можете просто сохранить весь текст в нижнем регистре в базе данных и использовать его для своей выгоды при отображении.

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)
1 голос
/ 28 декабря 2017

Вы можете использовать lookup = 'iexact' в UniqueValidator на сериализаторе, например:

class StateSerializer(serializers.ModelSerializer): 
    name = serializers.CharField(validators=[
    UniqueValidator(
        queryset=models.State.objects.all(),lookup='iexact'
    )]

Джанго версия: 1.11.6

1 голос
/ 07 декабря 2009

Вы можете сделать это, переписав метод сохранения модели - см. документы . Вы бы в основном сделали что-то вроде:

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 для проверки моделей позволит нам сделать это более просто.

0 голосов
/ 27 октября 2018

Если вы не хотите использовать решение, специфичное для postgres, вы можете создать уникальный индекс в поле с помощью upper() для обеспечения уникальности на уровне базы данных, а затем создать собственный Field миксин, который переопределяет get_lookup() для преобразования регистрозависимых поисков в их регистрозависимые версии. Миксин выглядит так:

class CaseInsensitiveFieldMixin:
    """
    Field mixin that uses case-insensitive lookup alternatives if they exist.
    """

    LOOKUP_CONVERSIONS = {
        'exact': 'iexact',
        'contains': 'icontains',
        'startswith': 'istartswith',
        'endswith': 'iendswith',
        'regex': 'iregex',
    }

    def get_lookup(self, lookup_name):
        converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
        return super().get_lookup(converted)

И вы используете это так:

from django.db import models


class CICharField(CaseInsensitiveFieldMixin, models.CharField):
    pass


class CIEmailField(CaseInsensitiveFieldMixin, models.EmailField):
    pass


class TestModel(models.Model):
    name = CICharField(unique=True, max_length=20)
    email = CIEmailField(unique=True)

Подробнее об этом подходе вы можете прочитать здесь .

0 голосов
/ 03 июня 2015

Решение от suhail работало для меня без необходимости включать citext, довольно простое решение - только чистая функция и вместо заглавных букв я использовал upper(). Решение Маюреша также работает, но изменило поле с CharField на TextField.

class State(models.Model):

    name = models.CharField(max_length=50, unique=True)

    def clean(self):
        self.name = self.name.upper()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...