Django:如何将 null 视为等于唯一性约束的所有内容?
Posted
技术标签:
【中文标题】Django:如何将 null 视为等于唯一性约束的所有内容?【英文标题】:Django: How to treat null as equal to everything for uniqueness constraints? 【发布时间】:2021-06-20 02:04:27 【问题描述】:在标题中,我的意思是等于任何值,而不仅仅是其他空值。
假设我们有一个家庭,有家庭成员(人)和电子设备。有些电子设备是个人的,属于一个人,但有些不属于任何一个家庭成员,例如书房中的一台普通计算机。 然后我们可以像这样对电子设备进行建模:
class ElectronicDevice(models.Model):
name = models.CharField(null=False, max_length=64)
owner = models.ForeignKey(HouseMember, null=True, on_delete=models.CASCADE)
现在假设我们希望设备名称从家庭成员的角度来看是唯一的。一个家庭成员不应该有两个具有相同名称的设备可供他们使用。所以 name 和 owner 一起应该是唯一的,但也不应该有两个设备,一个 private 和一个 common(owner 为 null)具有相同的名称。
因此是正常的UniqueConstraint
:
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'owner'], name='uniqe_device_name'),
]
不会这样做,因为如果其中一个设备的所有者为空,它仍然允许两个设备具有相同的名称。 仅限制名称字段:
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', ], name='uniqe_device_name'),
]
也不会这样做,因为这不允许两个家庭成员将他们的私人设备命名相同,尽管我们确实希望允许这样做。
我目前的尝试是限制名称和所有者的唯一性,然后使用 CheckConstraint
如果已经存在具有该名称的通用设备(所有者为空),则不允许名称:
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'owner'], name='uniqe_device_name'),
models.CheckConstraint(check=???, name='no_common_device_with_same_name')
]
这是最好的方法还是有更好的解决方案?
如果这是最好的解决方案:如何为此编写CheckConstraint
?
【问题讨论】:
对此的检查约束将是check=Q(owner__isnull=False)
@Charnel 不会禁止来自owner
的null
值吗?
你是对的,检查是错误的。你需要的是一种wildcard
来进行常量检查,但不幸的是,我发现了使用 ORM“开箱即用”的方式。
【参考方案1】:
或许你可以这样尝试:
constraints = [
models.UniqueConstraint(fields=['name', 'owner'], condition = Q(owner__isnull=False), name='uniqe_device_name'),
models.UniqueConstraint(fields=['name'], condition = Q(owner__isnull=True), name='uniqe_device_name_without_owner')
]
更多信息可以在documentation找到。
更新
如果您需要无法从约束中处理的自定义条件,那么最好覆盖保存方法。例如:
class ElectronicDevice(models.Model):
...
def save(self, *args, **kwargs):
if some conditions:
raise ValidationError()
super().save(*args, **kwargs)
【讨论】:
不,这不是一个解决方案,因为这允许两个设备具有相同的名称,如果其中一个owner
是null
而另一个不是null
。所以这允许私有设备和公共设备具有相同的名称,这是不允许的。
如果您不想允许两个设备具有相同的名称,那么只需在名称字段中添加unique=True
。
但是正如我在问题中所写的那样,如果他们有不同的非空所有者,我想允许相同的名称“允许两个家庭成员将他们的私人设备命名相同”。
在这种情况下,最好从 python 处理它,而不是从 DB 端处理。以上是关于Django:如何将 null 视为等于唯一性约束的所有内容?的主要内容,如果未能解决你的问题,请参考以下文章
Postrgersql+Django:NULL值导致IntegrityError:重复键值违反唯一约束
django:IntegrityError:重复键值违反唯一约束