我有模型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模型的错误.有任何想法吗?
编辑
就DB而言,解决方案的可移植性不是问题,我们对Postgres感到满意.我已经尝试设置一个可调用的唯一,这是我的函数返回True/False为特定的bar值,它没有给出任何错误,但是如果它没有任何影响那么缝合.
到目前为止,我已经从bar属性中删除了唯一的说明符并处理了应用程序中的bar唯一性,但仍然在寻找更优雅的解决方案.有什么建议?
由于票证#9039已修复,Django并未将NULL视为等于NULL以进行唯一性检查,请参阅:
http://code.djangoproject.com/ticket/9039
这里的问题是表单CharField的规范化"空白"值是空字符串,而不是None.因此,如果将该字段留空,则会在DB中存储一个空字符串,而不是NULL.在Django和数据库规则下,空字符串等于唯一性检查的空字符串.
您可以通过为foo提供自己的自定义模型表单来强制管理接口为空字符串存储NULL,并使用clean_bar方法将空字符串转换为None:
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中,不再支持 module-global __metaclass__
变量.另外,在课堂上已被弃用:Django 1.10
SubfieldBase
来自文档:
django.db.models.fields.subclassing.SubfieldBase
已被弃用,将在Django 1.10中删除.从历史上看,它用于处理从数据库加载时需要进行类型转换的字段,但它不用于.values()
调用或聚合中.它已被取代from_db_value()
. 请注意,新方法不会像to_python()
分配的情况那样调用赋值方法SubfieldBase
.
因此,正如from_db_value()
文档和此示例所示,此解决方案必须更改为:
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
我认为比覆盖管理员中的cleaning_data更好的方法是对charfield进行子类化 - 这种方式无论采用何种形式访问字段,它都将"正常工作".你可以''
在它被发送到数据库之前捕获它,并在它从数据库出来之后捕获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将抛出验证错误(需要字段)或创建不接受NULL的db列.
快速解决方法是:
def save(self, *args, **kwargs): if not self.bar: self.bar = None super(Foo, self).save(*args, **kwargs)
因为我是stackoverflow的新手,我还不能回答答案,但我想指出,从哲学的角度来看,我不能同意这个问题最受欢迎的答案.(作者:Karen Tracey)
如果OP具有值,则OP要求其bar字段是唯一的,否则为null.然后必须是模型本身确保这种情况.它不能留给外部代码来检查,因为这意味着它可以被绕过.(或者如果你将来写一个新的视图,你可以忘记检查它)
因此,要保持代码真正的OOP,必须使用Foo模型的内部方法.修改save()方法或字段是很好的选择,但使用表单来执行此操作肯定不是.
我个人更喜欢使用建议的CharNullField,以便将来可能定义的模型的可移植性.
另一种可能的解决
class Foo(models.Model): value = models.CharField(max_length=255, unique=True) class Bar(models.Model): foo = models.OneToOneField(Foo, null=True)