Python 包含多个外键的唯一组合&;多对多领域
我们的业务定价取决于多个参数,现在我们想在Django的现有设置中引入另一个可能的M2M参数 为此,我们有一个现有的定价表,除了Python 包含多个外键的唯一组合&;多对多领域,python,django,postgresql,django-models,database-design,Python,Django,Postgresql,Django Models,Database Design,我们的业务定价取决于多个参数,现在我们想在Django的现有设置中引入另一个可能的M2M参数 为此,我们有一个现有的定价表,除了price\u字段之外,所有字段都有unique\u约束。对于示例中基于泛型/字母的命名表示歉意 class PricingTable(models.Model): a = models.ForeignKey(A, on_delete=models.CASCADE) price = MoneyField() b = ArrayField(mode
price\u字段
之外,所有字段都有unique\u约束。对于示例中基于泛型/字母的命名表示歉意
class PricingTable(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE)
price = MoneyField()
b = ArrayField(models.CharField(choices=CHOICES))
c = models.ForeignKey(C, on_delete=models.CASCADE)
class Meta:
ordering = ("a",)
unique_together = ("a", "b", "c")
def validate_b(self):
# b can't be empty
if not len(self.b) >= 1:
raise ValueError
# each element in b needs to be unique
if not len(self.b) == len(set(self.b)):
raise ValueError
# each element in b needs to be unique together with a & c
for i in self.b:
query = PricingTable.objects.filter(
a=self.a, c=self.c, b__contains=[i]
).exclude(pk=self.pk)
if query.count() > 0:
raise ValueError
def save(self, *args, **kwargs):
self.validate_b()
return super().save(*args, **kwargs)
我想在此表中引入另一个参数,该参数必须与非价格参数一起唯一(a
,b
&c
)
列表b
中的每个元素需要与a
和c
一起是唯一的
d = models.ManyToManyField("x.D", related_name="+")
上面的问题是,validate_b
函数必须升级为可能具有大量数据库查询的复杂函数。此外,Django并没有提供一种直接的方式来确保多对多场的独特团结
那么,我是否应该尝试另一种方法?A也许?但是,我应该在直通表中包括哪些字段呢?所有非价格字段?或者我是否应该停止梦想在d
上有一个多对多的字段,继续使用一个简单的外键方法,在所有这些字段上都有一个独特的
版本:
- Django 2.2
- 博士后11
如果需要,我可以将现有的ArrayField转换为一个简单的CharField
,这意味着更多的DB行,只要我能够将所有唯一的构造放入数据库中,而不是每次保存时都进行验证,就可以了。
在Sql和Django ORM中,不能对多对多字段设置唯一的约束,因为它涉及两个不同的表
SQL解决方案:
您可以尝试在django上复制解决方案
但要做到这一点,您必须手动创建tab_constr并将触发器逻辑插入save
方法或使用
Django解决方案
我不建议您遵循该解决方案,因为在django中很难复制,事实上,您必须使用两个外部键和一个额外的表手动复制m2m引用
只需检查保存方法上的,没有其他方法
p.S.
不要使用save方法的覆盖来添加对对象的检查,因为如果更改对象的QuerySet,则不会调用此方法。
相反,使用如下信号:
@receiver(post_save, sender=Program)
def on_save_pricing_table(sender, instance, created, **kwargs):
if not instance.value = 10:
raise ValueError
你应该试着换一个
# each element in b needs to be unique together with a & c
for i in self.b:
query = PricingTable.objects.filter(
a=self.a, c=self.c, b__contains=[i]
).exclude(pk=self.pk)
if query.count() > 0:
raise ValueError
借
注意:我没有验证生成的查询和性能,因为只有少数d
实体必须有相应的价格(其余实体将继续具有通用的通用价格),所以我最终使用了以下结构
class PricingTable(models.Model):
a = models.ForeignKey(A, on_delete=models.CASCADE)
price = MoneyField()
b = ArrayField(models.CharField(choices=CHOICES))
c = models.ForeignKey(C, on_delete=models.CASCADE)
d = models.ForeignKey("x.D", on_delete=models.CASCADE, blank=True, null=True)
class Meta:
ordering = ("a",)
unique_together = ("a", "b", "c", "d")
def validate_b(self):
# b can't be empty
if not len(self.b) >= 1:
raise ValueError
# each element in b needs to be unique
if not len(self.b) == len(set(self.b)):
raise ValueError
# each element in b needs to be unique together with a, c & d
query = PricingTable.objects.filter(
a=self.a, c=self.c, d=self.d, b__overlap=self.b
).exclude(pk=self.pk)
if query.count() > 0:
raise ValueError
def save(self, *args, **kwargs):
self.validate_b()
return super().save(*args, **kwargs)
class DBasedPricing(models.Model):
"""
Lookup table that tells (row exists) if we have D based pricing coupled with param A
If we do, query PricingTable.d=d, else PricingTable.d=None for correct pricing
"""
d = models.ForeignKey("x.D", on_delete=models.CASCADE)
a = models.ForeignKey(A, on_delete=models.CASCADE)
class Meta:
unique_together = ("d", "a")
这迫使我首先基于d
参数进行查找,以检查定价是否基于d
d_id = None
if DBasedPricing.objects.filter(d_id=input_param.d, a_id=a.id).exists():
d_id = input_param.d
然后在我的常规查询中添加另一个参数
price_obj = PricingTable.objects.filter(...usual query..., d_id=d_id)
总的来说,以一个简单的索引查找为代价,我节省了行&当然是复杂的数据库结构。而且,我最终不必重新输入所有现有的定价 如果一个对象的save()
方法未被调用,那么您希望它如何发出任何信号?这是两件不同的事情。如果保存查询集,则不会调用Save()方法。确切地说。
price_obj = PricingTable.objects.filter(...usual query..., d_id=d_id)