我有模型Foo有字段栏 . bar字段应该是唯一的,但允许空值,这意味着如果bar字段是 null
,我想允许多个记录,但如果它不是 null
,则值必须是唯一的 .
这是我的模型:
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
这是表的相应SQL:
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)
)
当使用管理界面创建多个bar为空的foo对象时,它会给我一个错误:“Foo with this Bar已经存在 . ”
但是当我插入数据库(PostgreSQL)时:
insert into appl_foo ("name", bar) values ('test1', null)
insert into appl_foo ("name", bar) values ('test2', null)
这很好用,它允许我插入多于1条记录,条形为空,所以数据库允许我做我想要的,这只是Django模型的错误 . 有任何想法吗?
EDIT
就DB而言,解决方案的可移植性不是问题,我们对Postgres感到满意 . 我已经尝试设置一个可调用的唯一,这是我的函数返回True / False为特定的bar值,它没有给出任何错误,但是如果它没有任何影响那么缝合 .
到目前为止,我已经从bar属性中删除了唯一的说明符并处理了应用程序中的bar唯一性,但仍然在寻找更优雅的解决方案 . 有什么建议?
7 回答
快速解决方法是:
由于票证#9039已修复,Django并未将NULL视为等于NULL以进行唯一性检查,请参阅:
http://code.djangoproject.com/ticket/9039
这里的问题是,表单CharField的规范化“空白”值是空字符串,而不是None . 因此,如果将该字段留空,则会在DB中存储一个空字符串,而不是NULL . 在Django和数据库规则下,空字符串等于唯一性检查的空字符串 .
您可以强制管理接口为空字符串存储NULL,方法是为Foo提供自己的自定义模型表单,并使用clean_bar方法将空字符串转换为None:
** edit 11/30/2015 :在python 3中,模块全局
__metaclass__
变量是no longer supported . 另外,截至Django 1.10
,SubfieldBase
类是deprecated:因此,正如
from_db_value()
documentation和example所建议的那样,此解决方案必须更改为:我认为比覆盖admin中的cleaning_data更好的方法是将charfield子类化 - 这种方式无论以何种形式访问该字段,它都会"just work."你可以在它被发送到数据库之前捕获
''
,并捕获NULL刚从数据库出来之后,Django的其余部分就不会知道/关心了 . 一个快速而肮脏的例子:对于我的项目,我将其转储到位于我网站根目录的
extras.py
文件中,然后我可以在我的应用程序的models.py
文件中from mysite.extras import CharNullField
. 该字段就像CharField一样 - 只需记住在声明字段时设置blank=True, null=True
,否则Django将抛出验证错误(需要字段)或创建不接受NULL的db列 .因为我是stackoverflow的新手,我还不能回答答案,但我想指出,从哲学的角度来看,我不能同意这个问题最受欢迎的答案 . (作者:Karen Tracey)
如果OP具有值,则OP要求其bar字段是唯一的,否则为null . 然后必须是模型本身确保这种情况 . 它不能留给外部代码检查,因为这意味着它可以被绕过 . (或者如果你将来写一个新的视图,你可以忘记检查它)
因此,要保持代码真正的OOP,必须使用Foo模型的内部方法 . 修改save()方法或字段是很好的选择,但使用表单来执行此操作肯定不是 .
我个人更喜欢使用建议的CharNullField,以便将来可能定义的模型的可移植性 .
另一种可能的解决
无论好坏,Django认为
NULL
等同于NULL
用于唯一性检查 . 除了编写自己的唯一性检查实现之外,没有办法解决这个问题,因为无论在表中出现多少次,NULL
都是唯一的 .(并且请记住,某些数据库解决方案采用相同的
NULL
视图,因此依赖于一个数据库关于NULL
的想法的代码可能无法移植到其他人)我最近有同样的要求 . 我选择覆盖模型上的save()方法(下面名为“MyModel”),而不是子类化不同的字段,如下所示: