我有以下2个Django模型:
from mptt.models import MPTTModel, TreeForeignKey
from django.db import models
from django.db.models import Q
class Model1(MPTTModel):
random_field = models.IntegerField()
parent = TreeForeignKey('self', null=True, blank=True)
class Model2(models.Model):
model_1 = models.ManyToManyField(Model1)
@staticmethod
def descendants_queryset(model1):
q = Q()
for curr_descendant in model1.get_descendants:
q |= Q(model_1=curr_descendant)
return q
我创建了这样的实例:
>>> a = Model2.objects.create()
>>> b = Model1.objects.create(random_field=1, parent=None)
>>> c = Model1.objects.create(random_field=2, parent=b)
>>> d = Model1.objects.create(random_field=3, parent=b)
>>> a.model_1.add(c)
>>> a.pk
3
当我使用普通的查询集过滤器时,当我使用Q()表达式时,它会产生相同的结果(如预期的那样):
>>> [i.pk for i in Model2.objects.filter(pk=3)]
[3]
>>> [i.pk for i in Model2.objects.filter(Model2.descendants_queryset(b), pk=3)]
[3]
但是当我将Model1的另一个实例添加到ManyToMany关系时,只有当我使用Q()表达式进行过滤时才会看到奇怪的重复:
>>> a.model_1.add(d)
>>> [i.pk for i in Model2.objects.filter(pk=3)]
[3]
>>> [i.pk for i in Model2.objects.filter(Model2.descendants_queryset(b), pk=3)]
[3, 3]
我很困惑为什么会发生这种重复。这对我来说似乎是个错误。我可以通过在查询集中添加.distinct()
来解决这个问题。但这似乎不应该是必要的。为什么会发生这种情况?什么是正确的解决方案?
答案 0 :(得分:4)
我注意到当你向a添加第三个元素时,你的输出不仅重复,而且还是三倍:
>>> 4 = Model1.objects.create(random_field=3, parent=b)
>>> a.model_1.add(e)
>>> [i.pk for i in Model2.objects.filter(Model2.descendants_queryset(b), pk=3)]
[3, 3, 3]
如果你添加另一个等等,那么翻两番......
所以我猜测的是,因为你的q() - descendants_queryset()中的查询是ORed,它返回每个对象,其中b对象为父对象,并且过滤器匹配多次a(其中有多个对Model1对象的引用。)
如果我们查看Model2.objects.filter(Model2.descendants_queryset(b))
的原始SQL,我们会看到以下内容:
>>> Model2.objects.filter(Model2.descendants_queryset(b)).query.sql_with_params()
(u'SELECT "Foo_model2"."id" FROM "Foo_model2" LEFT OUTER JOIN "Foo_model2_model_1" ON ("Foo_model2"."id" = "Foo_model2_model_1"."model2_id") WHERE ("Foo_model2_model_1"."model1_id" = %s OR "Foo_model2_model_1"."model1_id" = %s OR "Foo_model2_model_1"."model1_id" = %s)', (17, 18, 19))
或更具可读性:
SELECT "Foo_model2"."id"
FROM "Foo_model2"
LEFT OUTER JOIN "Foo_model2_model_1"
ON ("Foo_model2"."id" = "Foo_model2_model_1"."model2_id")
WHERE ("Foo_model2_model_1"."model1_id" = 17
OR "Foo_model2_model_1"."model1_id" = 18
OR "Foo_model2_model_1"."model1_id" = 19)
因此它实际上将q |= Q(model_1=curr_descendant)
生成的查询与OR语句连接起来,后者不返回一个,但在本例中是三个引用(都是同一个Model2对象,它持有ManyToMany引用到三个Model1对象)。
这是由于连接语句 - 有关示例,请参阅here。
如果我们为pk=3
添加额外的过滤器,它不会进一步限制输出,因为所有返回对象的PK都是相同的(3)。
如果添加另一个Model2对象,并添加c作为对新元素model1 ManyToMany-reference的引用,则会得到以下内容:
>>> a2 = Model2.objects.create()
>>> a2.model_1.add(c)
>>> [i.pk for i in Model2.objects.filter(Model2.descendants_queryset(b))]
[3, 3, 3, 4]
新的Model2对象的id也显示在查询集中,因为它还有一个对model1对象的引用。
我现在对最佳解决方案没有任何粉碎的想法,但在查询集上调用.distinct()
对我来说似乎很直接。