Ser*_*nko 121 django orm django-models
我有模型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)
Run Code Online (Sandbox Code Playgroud)
这是表的相应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)
)
Run Code Online (Sandbox Code Playgroud)
当使用管理界面创建多个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)
Run Code Online (Sandbox Code Playgroud)
这很好用,它允许我插入超过1条记录,条形为空,所以数据库允许我做我想要的,这只是Django模型的错误.有任何想法吗?
编辑
就DB而言,解决方案的可移植性不是问题,我们对Postgres感到满意.我已经尝试设置一个可调用的唯一,这是我的函数返回True/False为特定的bar值,它没有给出任何错误,但是如果它没有任何影响那么缝合.
到目前为止,我已经从bar属性中删除了唯一的说明符并处理了应用程序中的bar唯一性,但仍然在寻找更优雅的解决方案.有什么建议?
小智 143
由于票证#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
Run Code Online (Sandbox Code Playgroud)
mig*_*hal 60
**编辑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
Run Code Online (Sandbox Code Playgroud)
我认为比覆盖管理员中的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
Run Code Online (Sandbox Code Playgroud)
对于我的项目,我将其转储到一个extras.py
位于我网站根目录的文件中,然后我可以from mysite.extras import CharNullField
在我的应用程序models.py
文件中.该字段就像CharField一样 - 只记得blank=True, null=True
在声明字段时设置,否则Django将抛出验证错误(需要字段)或创建不接受NULL的db列.
e-s*_*tis 13
快速解决方法是:
def save(self, *args, **kwargs):
if not self.bar:
self.bar = None
super(Foo, self).save(*args, **kwargs)
Run Code Online (Sandbox Code Playgroud)
tBu*_*uLi 13
因为我是stackoverflow的新手,我还不能回答答案,但我想指出,从哲学的角度来看,我不能同意这个问题最受欢迎的答案.(作者:Karen Tracey)
如果OP具有值,则OP要求其bar字段是唯一的,否则为null.然后必须是模型本身确保这种情况.它不能留给外部代码来检查,因为这意味着它可以被绕过.(或者如果你将来写一个新的视图,你可以忘记检查它)
因此,要保持代码真正的OOP,必须使用Foo模型的内部方法.修改save()方法或字段是很好的选择,但使用表单来执行此操作肯定不是.
我个人更喜欢使用建议的CharNullField,以便将来可能定义的模型的可移植性.
现在https://code.djangoproject.com/ticket/4136已解决,此问题已修复。在 Django 1.11+ 中,您models.CharField(unique=True, null=True, blank=True)
无需手动将空白值转换为None
.
您可以添加UniqueConstraint
条件为nullable_field=null
和 不将此字段包含在fields
列表中。如果您还需要nullable_field
值不是的约束null
,您可以添加额外的约束。
注意:UniqueConstraint 从 django 2.2 开始添加
class Foo(models.Model):
name = models.CharField(max_length=40)
bar = models.CharField(max_length=40, unique=True, blank=True, null=True, default=None)
class Meta:
constraints = [
# For bar == null only
models.UniqueConstraint(fields=['name'], name='unique__name__when__bar__null',
condition=Q(bar__isnull=True)),
# For bar != null only
models.UniqueConstraint(fields=['name', 'bar'], name='unique__name__when__bar__not_null')
]
Run Code Online (Sandbox Code Playgroud)
另一种可能的解决
class Foo(models.Model):
value = models.CharField(max_length=255, unique=True)
class Bar(models.Model):
foo = models.OneToOneField(Foo, null=True)
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
35685 次 |
最近记录: |