Unique fields that allow nulls in Django
我有FOO模型,它有场栏。bar字段应该是唯一的,但允许空值,这意味着如果bar字段是
这是我的模型:
1 2 3 | 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:
1 2 3 4 5 6 7 8 | 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对象时,它会给出一个错误:"此BAR的foo已经存在。"
但是,当我插入数据库(PostgreSQL)时:
1 2 | insert into appl_foo ("name", bar) values ('test1', null) insert into appl_foo ("name", bar) values ('test2', null) |
这是可行的,很好,它允许我插入一个以上的记录,其中条为空,所以数据库允许我做我想做的,这只是Django模型的一些问题。有什么想法吗?
编辑
解决方案的可移植性,只要数据库不是问题,我们对Postgres很满意。我试过将unique设置为callable,这是我的函数,它为特定的bar值返回true/false,它没有给出任何错误,但是像这样的接缝根本没有效果。
到目前为止,我已经从BAR属性中删除了唯一的说明符,并在应用程序中处理了BAR的唯一性,但是仍然在寻找更优雅的解决方案。有什么建议吗?
由于票9039是固定的,为了唯一性检查,Django认为空值不等于空值,请参见:
http://code.djangoproject.com/ticket/9039
这里的问题是表单charfield的规范化"blank"值是空字符串,而不是空字符串。因此,如果将字段留空,将得到一个存储在数据库中的空字符串,而不是空字符串。在django和数据库规则下,空字符串等于用于唯一性检查的空字符串。
您可以强制管理界面为空字符串存储空值,方法是为foo提供自己的自定义模型表单,并使用一个干净的u bar方法将空字符串转换为无:
1 2 3 4 5 6 7 8 | 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 |
**2015年11月30日编辑:在python 3中,不再支持模块global
from the docs:
django.db.models.fields.subclassing.SubfieldBase has been deprecated and will be removed in Django 1.10.
Historically, it was used to handle fields where type conversion was needed when loading from the database,
but it was not used in.values() calls or in aggregates. It has been replaced withfrom_db_value() .
Note that the new approach does not call theto_python() method on assignment as was the case withSubfieldBase .
因此,根据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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 |
我认为一个比重写管理员中清理过的_数据更好的方法是对charfield进行子类化——这样无论哪个表单访问该字段,它都将"正常工作"。您可以在将
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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 |
在我的项目中,我把它转储到一个位于我站点根目录的
因为我刚接触StackOverflow,所以我还没有被允许回复答案,但我想从哲学的角度指出,我不同意这个问题最流行的答案。(凯伦·特雷西)
OP要求他的bar字段是唯一的(如果它有值),否则为空。那么一定是模型本身确保了这一点。不能让外部代码检查,因为这意味着可以绕过它。(或者,如果以后编写新视图,可以忘记检查它)
因此,要保持代码的真实OOP,必须使用FOO模型的内部方法。修改save()方法或字段是很好的选项,但是使用表单来完成这一操作肯定不是很好。
我个人更喜欢使用建议的charnullfield,因为它可以移植到我将来可能定义的模型中。
快速解决方法是:
1 2 3 4 5 6 | def save(self, *args, **kwargs): if not self.bar: self.bar = None super(Foo, self).save(*args, **kwargs) |
另一个可能的解决方案
1 2 3 4 5 | class Foo(models.Model): value = models.CharField(max_length=255, unique=True) class Bar(models.Model): foo = models.OneToOneField(Foo, null=True) |
无论好坏,为了唯一性检查,Django认为
(请记住,有些数据库解决方案对
我最近也有同样的要求。我没有对不同的字段进行子类化,而是选择重写我的模型上的save()metod(下面称为"my model"),如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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 (i.e. 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() |
如果您有一个模型my model,并且希望my_字段为空或唯一,则可以重写模型的save方法:
1 2 3 4 5 6 | class MyModel(models.Model): my_field = models.TextField(unique=True, default=None, null=True, blank=True) def save(self, **kwargs): self.my_field = self.my_field or None super().save(**kwargs) |
这样,字段不能为空,只能为非空或空。空值并不矛盾唯一性