我们公司的定价取决于多个参数,现在我们想在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)
我想在此表中引入另一个参数,该参数必须与非价格参数(a
,b
和c
)一起唯一。
d = models.ManyToManyField("x.D", related_name="+")
列表b
中的每个元素必须与a
和c
一起唯一。
上述问题是validate_b
函数必须升级为具有大量数据库查询的可能复杂的函数。 Django并没有提供直接的方法来确保多对多字段的唯一性。
那么,我是否应该尝试其他方法?可能是through
table吗?但是,我应该在穿透表中包括哪些所有字段?所有非价格字段?还是我应该不再梦想为d
拥有多对多的领域,而继续采用简单的外键方法,并对所有简单的方法都拥有unique_together
?
版本:
如果需要,我可以将现有的ArrayField转换为简单的CharField
,这意味着会有更多的DB行,只要我将所有唯一的约束都放入数据库中,而不是在保存每个时进行验证,就可以了。时间。
答案 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)
总体而言,以一个简单的索引查找为代价,我节省了行,当然也节省了复杂的数据库结构。另外,我最终不必重新输入所有现有价格!