在具有ManyToMany字段的经过筛选的Django QuerySet上使用values()

时间:2019-02-18 17:12:23

标签: python django django-queryset

我在Django项目中具有以下模型:

class Category(models.Model):
    title = models.CharField(max_length=255)

    def __str__(self):
        return '{} - {}'.format(self.pk, self.title)

class Item(models.Model):
    title = models.CharField(max_length=255)
    categories = models.ManyToManyField(Category)

    def __str__(self):
        return '{} - {}'.format(self.pk, self.title)

我创建了一个项目,并将其与两个类别相关联:

>>> item1 = Item.objects.get(pk=1)
>>> item1.categories.all()
<QuerySet [<Category: 1 - C1>, <Category: 4 - C4>]>

我还可以使用filter()并获得预期的结果:

>>> Item.objects.filter(categories__title='C1')
<QuerySet [<Item: 1 - Item1>]>

但是,当我在同一查询集上使用values()时,将仅返回已过滤的关系:

>>> Item.objects.filter(categories__title='C1').values('categories__title')
<QuerySet [{'categories__title': 'C1'}]>

相比:

>>> Item.objects.all().values('categories__title')
<QuerySet [{'categories__title': 'C1'}, {'categories__title': 'C4'}]>

我想念什么?如何获取类别的完整列表?

1 个答案:

答案 0 :(得分:0)

values在多对多关系中无法正常工作。您的过滤器实际上是在中间表上创建一个INNER JOIN(它跟踪多对多关系),并且在过滤掉所有相关模型之后将应用values。因此,它不会为您返回每个Item对象的所有类别,而只会返回经过过滤的类别。

您应该颠倒您的工作顺序:

Item.objects.values('categories__title').filter(categories__title="C1")

这实际上将首先创建一个附加的LEFT OUTER JOIN,然后为过滤子句添加INNER JOIN。

请注意,如果过滤器返回了多个项目,那么如果对象的类别重叠,则最终会导致类别标题的重复值很多。参见warning at the end of this section