Pergunta

Considere a seguinte situação: -

Suponha que meu aplicativo permite aos usuários criar os Estados / Províncias em sua país. Apenas para fins de esclarecimento, estamos considerando apenas caracteres ASCII aqui.

Nos EUA, um usuário poderia criar o estado chamado de "Texas". Se este aplicativo está sendo usado internamente, digamos que o usuário não se importa se é escrito "texas" ou "Texas" ou "Texas"

Mas importante, o sistema deve impedir a criação de "texas" se "Texas" já está no banco de dados.

Se o modelo é semelhante ao seguinte:

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

A singularidade seria maiúsculas de minúsculas no Postgres; que é, postgres permitiria ao usuário criar tanto "texas" e "Texas", como eles são considerado único.

O que pode ser feito nesta situação para evitar tal comportamento. como funciona um vai sobre o fornecimento de caso- insenstitive singularidade com Django e Postgres

Agora eu estou fazendo o seguinte para evitar a criação de caso- duplicatas insensíveis.

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

Há uma série de casos em que eu vou ter que fazer essa verificação e eu não estou interessado em ter de cheques iexact semelhantes em todos os lugares.

Basta saber se há um built-in ou melhor maneira? Talvez db_type ajudaria? Talvez exista alguma outra solução?

Foi útil?

Solução

Você pode definir um campo modelo personalizado derivado de models.CharField. Este campo pode verificar se há valores duplicados, ignorando o caso.

Custom documentação campos é aqui http: //docs.djangoproject. com / en / dev / howto / custom-modelo-campos /

http: // código. djangoproject.com/browser/django/trunk/django/db/models/fields/files.py para um exemplo de como criar um campo personalizado por subclasse um campo existente.

Você pode usar o módulo Citext de PostgreSQL https: //www.postgresql. org / docs / current / static / citext.html

Se você usar este módulo, o campo o costume poderia definir "db_type" como Citext para bancos de dados PostgreSQL.

Isso levaria a caso comparação insensível para valores exclusivos no campo personalizado.

Outras dicas

Como alternativa, você pode alterar o padrão de consulta Set Manager para fazer caso insensíveis look-ups no campo. Na tentativa de resolver um problema semelhante me deparei com:

http://djangosnippets.org/snippets/305/

código colado aqui por conveniência:

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

No lado Postgres das coisas, um índice exclusivo funcional vai deixar você aplicar valores exclusivos sem caso. Citext também é conhecida, mas isso vai funcionar com versões mais antigas do PostgreSQL e é uma técnica útil em geral.

Exemplo:

# 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"

etapas explícitas para a resposta de Mayuresh:

  1. no postgres fazer: Crie uma extensão Citext;

  2. no seu suplemento models.py:

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

    referência: https://github.com/zacharyvoase/ django-postgres / blob / master / django_postgres / citext.py

  3. em seu uso modelo: name = CaseInsensitiveTextField (unique = True)

Além de opção para substituir salvar, você pode simplesmente armazenar todo o texto em letras minúsculas no banco de dados e capitalizá-los sobre a exibição já mencionado.

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)

uma solução muito simples:

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

    def clean(self):
        self.name = self.name.capitalize()

Você pode fazer isso, substituindo método save do Modelo - veja a docs . Você seria basicamente fazer algo como:

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)

Além disso, eu posso estar errado sobre isso, mas o próximo modelo de validação ramo SoC vai nos permitir fazer isso com mais facilidade.

Você pode usar lookup = 'iexact' em UniqueValidator em serializer, como este:

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

Django versão: 1.11.6

Solução de suhail trabalhou para mim, sem a necessidade de permitir Citext, muito fácil solução apenas uma função de limpeza e em vez de capitalizar I upper() usado. A solução da Mayuresh também funciona, mas mudou o campo de CharField para TextField.

class State(models.Model):

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

    def clean(self):
        self.name = self.name.upper()

Se você não quiser usar uma solução específica para postgres, você pode criar um índice exclusivo no campo com upper() para impor exclusividade no nível de banco de dados, em seguida, criar um personalizado Field mixin que substitui get_lookup() para converter maiúsculas de minúsculas pesquisas para suas versões maiúsculas e minúsculas. Os olhares mixin como este:

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)

E você usá-lo como este:

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)

Você pode ler mais sobre esta abordagem aqui .

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top