独特,涉及多个外键和多个字段

时间:2019-07-18 11:32:04

标签: python django postgresql django-models database-design

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

为此,我们有一个现有的定价表,该表在除unique_together之外的所有字段上都有price_field约束。在示例中为基于通用/字母的命名表示歉意。

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)

我想在此表中引入另一个参数,该参数必须与非价格参数(abc)一起唯一。

d = models.ManyToManyField("x.D", related_name="+")

列表b中的每个元素必须与ac一起唯一。

上述问题是validate_b函数必须升级为具有大量数据库查询的可能复杂的函数。 Django并没有提供直接的方法来确保多对多字段的唯一性。

那么,我是否应该尝试其他方法?可能是through table吗?但是,我应该在穿透表中包括哪些所有字段?所有非价格字段?还是我应该不再梦想为d拥有多对多的领域,而继续采用简单的外键方法,并对所有简单的方法都拥有unique_together

版本:

  • Django 2.2
  • postgres 11

如果需要,我可以将现有的ArrayField转换为简单的CharField,这意味着会有更多的DB行,只要我将所有唯一的约束都放入数据库中,而不是在保存每个时进行验证,就可以了。时间。

3 个答案:

答案 0 :(得分:2)

您应该尝试overlap进行替换

# 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

通过

query = PricingTable.objects.filter(
    a=self.a, c=self.c, b__overlap=self.b
).exclude(pk=self.pk)
if query.count() > 0:
            raise ValueError

注意:我没有验证生成的查询和性能

答案 1 :(得分:0)

前提

在Sql中,因此在Django ORM中,您无法在多对多字段上设置唯一约束,因为它涉及两个不同的表。

SQL解决方案:

您可以尝试在Django上重现this解决方案。

但是要执行此操作,您必须手动创建tab_constr并将触发逻辑插入save方法内部或使用signals

Django解决方案

我不建议您采用该解决方案,因为很难在Django中重现,实际上您必须使用两个外键和一个额外的表来手动重现m2m参考。

简单地将您的支票放在on_save方法上,没有其他方法。

PS

不要使用save方法的覆盖来添加对对象的检查,因为如果更改对象的QuerySet,则不会调用此方法。 而是使用如下信号:

@receiver(post_save, sender=Program)
def on_save_pricing_table(sender, instance, created, **kwargs):
    if not instance.value = 10:
        raise ValueError

答案 2 :(得分:0)

由于只有极少数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)

总体而言,以一个简单的索引查找为代价,我节省了行,当然也节省了复杂的数据库结构。另外,我最终不必重新输入所有现有价格!