独特的字段,允许Django中的空值

我有模型Foo有栏杆。 bar字段应该是唯一的,但是允许有空值,这意味着如果bar字段为null ,我想允许多个logging,但是如果它不为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) ) 

当使用pipe理界面创build超过1个foo对象,其中bar是空的它给了我一个错误:“Foo与这个酒吧已经存在。”

但是当我插入数据库(PostgreSQL)时:

 insert into appl_foo ("name", bar) values ('test1', null) insert into appl_foo ("name", bar) values ('test2', null) 

这工作,很好,它允许我插入多于一条logging与酒吧为空,所以数据库允许我做我想做的,这只是Django模型的错误。 有任何想法吗?

编辑

就DB而言,解决scheme的可移植性不是问题,我们对Postgres感到满意。 我已经尝试设置唯一的可调用,这是我的函数返回真/假的特定值的酒吧 ,它没有给出任何错误,然而像它根本没有任何效果。

到目前为止,我已经从bar属性中删除了唯一的说明符,并处理了应用程序中的bar唯一性,但是仍然在寻找更加优雅的解决scheme。 任何build议?

由于票号#9039是固定的,所以Django没有将NULL等同于NULL以进行唯一性检查,请参阅:

http://code.djangoproject.com/ticket/9039

这里的问题是,表单CharField的标准化“空白”值是一个空string,而不是无。 所以如果你把这个字段留空,你会得到一个空的string,而不是NULL,存储在数据库中。 在Django和数据库规则下,空string都等于空string以进行唯一性检查。

您可以强制pipe理接口为空string存储NULL,方法是使用将空string转换为None的clean_bar方法为Foo提供自定义模型表单:

 class FooForm(forms.ModelForm): class Meta: model = Foo def clean_bar(self): return self.cleaned_data['bar'] or None class FooAdmin(admin.ModelAdmin): form = FooForm 

** 编辑11/30/2015 :在Python 3中, 不再支持模块全局__metaclass__variables。 另外,从Django 1.10SubfieldBase类已被弃用 :

从文档 :

django.db.models.fields.subclassing.SubfieldBase已被弃用,并将在Django 1.10中被删除。 从历史上看,它用于处理从数据库加载时需要进行types转换的字段,但不用于.values()调用或聚合中。 它已经被from_db_value()取代了。 请注意,新方法不会像to_python()一样调用to_python()方法

因此,如from_db_value() 文档和此示例所示 ,必须将此解决scheme更改为:

 class CharNullField(models.CharField): """ Subclass of the CharField that allows empty strings to be stored as NULL. """ description = "CharField that stores NULL but returns ''." def from_db_value(self, value, expression, connection, contex): """ Gets value right out of the db and changes it if its ``None``. """ if value is None: return '' else: return value def to_python(self, value): """ Gets value right out of the db or an instance, and changes it if its ``None``. """ if isinstance(value, models.CharField): # If an instance, just return the instance. return value if value is None: # If db has NULL, convert it to ''. return '' # Otherwise, just return the value. return value def get_prep_value(self, value): """ Catches value right before sending to db. """ if value == '': # If Django tries to save an empty string, send the db None (NULL). return None else: # Otherwise, just pass the value. return value 

我认为比在pipe理员中重写clean_data更好的方法是将charfield子类化 – 这样,无论以何种forms访问该字段,它都将“正常工作”。 在它被发送到数据库之前,你可以捕获到它,并且在数据库出来之后捕获NULL,Django的其余部分将不知道/关心。 一个快速和肮脏的例子:

 from django.db import models class CharNullField(models.CharField): # subclass the CharField description = "CharField that stores NULL but returns ''" __metaclass__ = models.SubfieldBase # this ensures to_python will be called def to_python(self, value): # this is the value right out of the db, or an instance # if an instance, just return the instance if isinstance(value, models.CharField): return value if value is None: # if the db has a NULL (None in Python) return '' # convert it into an empty string else: return value # otherwise, just return the value def get_prep_value(self, value): # catches value right before sending to db if value == '': # if Django tries to save an empty string, send the db None (NULL) return None else: # otherwise, just pass the value return value 

对于我的项目,我把这个转移到了一个extras.py文件中,这个文件位于我的网站的根目录下,然后我可以from mysite.extras import CharNullField到我的应用程序的models.py文件中。 该字段的作用就像一个CharField – 记得在声明字段时设置blank=True, null=True ,否则Django会抛出一个validation错误(需要字段)或创build一个不接受NULL的数据库列。

快速解决方法是做:

 def save(self, *args, **kwargs): if not self.bar: self.bar = None super(Foo, self).save(*args, **kwargs) 

因为我是新手,我不能答复,但是我想指出,从哲学的angular度来看,我不能同意这个问题最stream行的答案。 (由Karen Tracey撰写)

OP要求其栏区域是唯一的,如果它有一个值,否则为空。 那么它一定是模型本身确保这种情况。 它不能留给外部代码来检查,因为这意味着它可以被绕过。 (或者,如果你将来写一个新的视图,你可以忘记检查它)

因此,为了保持代码真正的OOP,你必须使用你的Foo模型的内部方法。 修改save()方法或字段是很好的select,但是使用表单来做到这一点肯定不是。

就我个人而言,我更喜欢使用build议的CharNullField,以便于将来可能定义的模型的可移植性。

另一个可能的解

 class Foo(models.Model): value = models.CharField(max_length=255, unique=True) class Bar(models.Model): foo = models.OneToOneField(Foo, null=True) 

为了更好或者更坏,Django为了唯一性检查将NULL等同于NULL 。 没有办法编写自己的唯一性检查实现,无论在表中出现多less次,它都认为NULL是唯一的。

(并且要记住,某些DB解决scheme采用NULL的相同视图,所以依赖于一个DB关于NULL的想法的代码可能不能移植到其他的)

我最近有同样的要求。 我select覆盖我的模型(下面命名为“MyModel”)上的save()方法,而不是子类化不同的字段,如下所示:

 def save(self): """overriding save method so that we can save Null to database, instead of empty string (project requirement)""" # get a list of all model fields (ie self._meta.fields)... emptystringfields = [ field for field in self._meta.fields \ # ...that are of type CharField or Textfield... if ((type(field) == django.db.models.fields.CharField) or (type(field) == django.db.models.fields.TextField)) \ # ...and that contain the empty string and (getattr(self, field.name) == "") ] # set each of these fields to None (which tells Django to save Null) for field in emptystringfields: setattr(self, field.name, None) # call the super.save() method super(MyModel, self).save()