Python 包含多个外键的唯一组合&;多对多领域

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

我们的业务定价取决于多个参数,现在我们想在Django的现有设置中引入另一个可能的M2M参数

为此,我们有一个现有的定价表,除了
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)