Eu tenho o modelo Foo que tem barra de campo. O campo da barra deve ser exclusivo, mas permitir nulos, o que significa que desejo permitir mais de um registro se o campo da barra for null
, mas se não for, null
os valores deverão ser exclusivos.
Aqui está o meu modelo:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
E aqui está o SQL correspondente para a tabela:
CREATE TABLE appl_foo
(
id serial NOT NULL,
"name" character varying(40) NOT NULL,
bar character varying(40),
CONSTRAINT appl_foo_pkey PRIMARY KEY (id),
CONSTRAINT appl_foo_bar_key UNIQUE (bar)
)
Ao usar a interface de administração para criar mais de 1 objeto foo em que a barra é nula, ocorre um erro: "O foo com esta barra já existe".
No entanto, quando insiro no banco de dados (PostgreSQL):
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
Isso funciona, muito bem, me permite inserir mais de 1 registro com a barra sendo nula, então o banco de dados permite que eu faça o que eu quero, é apenas algo errado com o modelo do Django. Alguma ideia?
EDITAR
A portabilidade da solução no que diz respeito ao DB não é um problema, estamos felizes com o Postgres. Eu tentei definir exclusivo para um callable, que era minha função retornando True / False para valores específicos de bar , não dava nenhum erro, por mais costurado como se não tivesse nenhum efeito.
Até agora, removi o especificador exclusivo da propriedade da barra e lidei com a exclusividade da barra no aplicativo, mas ainda estou procurando uma solução mais elegante. Alguma recomendação?
fonte
def get_db_prep_value(self, value, connection, prepared=False)
como chamada de método. Consulte groups.google.com/d/msg/django-users/Z_AXgg2GCqs/zKEsfu33OZMJ para obter mais informações. O método a seguir também funciona para mim: def get_prep_value (self, value): if value == "": #if Django tenta salvar '' string, envie o retorno db None (NULL) passa o valorRespostas:
O Django não considerou NULL igual a NULL para fins de verificação de exclusividade desde que o ticket # 9039 foi corrigido, consulte:
http://code.djangoproject.com/ticket/9039
O problema aqui é que o valor "em branco" normalizado de um formulário CharField é uma sequência vazia, não Nenhuma. Portanto, se você deixar o campo em branco, obterá uma string vazia, não NULL, armazenada no banco de dados. Strings vazias são iguais a strings vazias para verificações de exclusividade, sob as regras do Django e do banco de dados.
Você pode forçar a interface de administração a armazenar NULL para uma cadeia vazia, fornecendo seu próprio formulário de modelo personalizado para Foo com um método clean_bar que transforma a cadeia vazia em None:
fonte
fields
ouexclude
emModelForm
instâncias. Você pode contornar isso, omitindo aMeta
classe interna do ModelForm para uso em admin. Referência: docs.djangoproject.com/en/1.10/ref/contrib/admin/…** editar 30/11/2015 : No python 3, a
__metaclass__
variável global do módulo não é mais suportada . Adicionalmente, a partirDjango 1.10
daSubfieldBase
classe foi preterido :Portanto, conforme sugerido pela
from_db_value()
documentação e neste exemplo , esta solução deve ser alterada para:Eu acho que uma maneira melhor do que substituir os dados limpos no administrador seria subclassificar o charfield - dessa forma, não importa qual formulário acesse o campo, ele "funcionará". Você pode pegar o
''
pouco antes de ele ser enviado ao banco de dados, e pegar o NULL logo após sair do banco de dados, e o resto do Django não saberá / se importará. Um exemplo rápido e sujo:No meu projeto, coloquei isso em um
extras.py
arquivo que ficafrom mysite.extras import CharNullField
na raiz do meu site, e posso apenas nomodels.py
arquivo do meu aplicativo . O campo age como um CharField - lembre-se de definirblank=True, null=True
ao declarar o campo, ou o Django lançará um erro de validação (campo obrigatório) ou criará uma coluna db que não aceita NULL.fonte
CharField
para ser umCharNullField
, precisará fazer isso em três etapas. Primeiro, adicionenull=True
ao campo e migre isso. Em seguida, faça uma migração de dados para atualizar quaisquer valores em branco para que sejam nulos. Por fim, converta o campo em CharNullField. Se você converter o campo antes de fazer a migração de dados, sua migração de dados não fará nada.from_db_value()
não deve ter essecontex
parâmetro extra . Deveria serdef from_db_value(self, value, expression, connection):
Como sou iniciante no stackoverflow, ainda não tenho permissão para responder às respostas, mas gostaria de salientar que, de um ponto de vista filosófico, não posso concordar com a resposta mais popular para esta pergunta. (de Karen Tracey)
O OP exige que seu campo de barra seja único se tiver um valor e nulo caso contrário. Então deve ser que o próprio modelo verifique se é esse o caso. Não pode ser deixado para o código externo verificar isso, porque isso significa que pode ser ignorado. (Ou você pode esquecer de verificar se escrever uma nova exibição no futuro)
Portanto, para manter seu código verdadeiramente OOP, você deve usar um método interno do seu modelo Foo. Modificar o método save () ou o campo são boas opções, mas o uso de um formulário para isso certamente não é.
Pessoalmente, prefiro usar o CharNullField sugerido, para portabilidade para modelos que eu possa definir no futuro.
fonte
A solução rápida é:
fonte
MyModel.objects.bulk_create()
ignoraria esse método.Outra solução possível
fonte
Isso foi corrigido agora que https://code.djangoproject.com/ticket/4136 foi resolvido. No Django 1.11+ você pode usar
models.CharField(unique=True, null=True, blank=True)
sem ter que converter manualmente valores em branco paraNone
.fonte
Recentemente, tive o mesmo requisito. Em vez de subclassificar campos diferentes, optei por substituir o método save () no meu modelo (chamado 'MyModel' abaixo) da seguinte maneira:
fonte
Se você possui um modelo MyModel e deseja que meu_campo seja Nulo ou exclusivo, você pode substituir o método de salvamento do modelo:
Dessa forma, o campo não pode ficar em branco, apenas ficará em branco ou nulo. nulos não contradizem a exclusividade
fonte
Você pode adicionar
UniqueConstraint
com a condição denullable_field=null
e não incluir esse campo nafields
lista. Se você também precisar de restrição cujonullable_field
valor não sejanull
, poderá adicionar uma adicional.Nota: UniqueConstraint foi adicionado desde o django 2.2
fonte
Para o bem ou para o mal, o Django considera
NULL
equivalenteNULL
para fins de checagem de exclusividade. Realmente, não há como evitar escrever sua própria implementação da verificação de exclusividade, que consideraNULL
única, não importa quantas vezes ocorra em uma tabela.(e lembre-se de que algumas soluções de banco de dados têm a mesma visão
NULL
, portanto, o código que se baseia nas idéias de um banco de dadosNULL
pode não ser portátil para outros)fonte