使用values()时过滤Django查询集的相关ManyToMany字段

时间:2019-10-02 23:58:06

标签: python django

我正在使用一个包含ManyToMany字段brand_groups的查询集。用户只能根据其组织访问BrandGroups的子集。我正在尝试过滤ManyToMany字段,同时仍然使用values(),该字段与视图高度集成。

我正在使用的简化表:

class BrandGroup(models.Model):
    id = models.BigAutoField(primary_key=True)
    name = models.CharField(max_length=256)
    organization = models.ForeignKey(
        Organization, related_name="brand_groups", null=False
    )

class Fact(models.Model):
    id = models.CharField(max_length=256, primary_key=True)
    brand_groups = models.ManyToManyField(BrandGroup, blank=True)

过去为我工作的是使用Prefetch对象来处理这种限制:

qs = Fact.objects.prefetch_related(
    Prefetch("brand_groups",
        queryset=BrandGroup.objects.filter(
            organization_id=self.request.META["ORG_ID_HEADER"]
)))

但是我发现values()似乎完全忽略了预取。

qs.values("brand_groups__name")

以上内容始终包含完整的关联BrandGroup对象,但不包含过滤器。

我尝试将to_attr='org_brand_groups'添加到Prefetch,但是qs.values("org_brand_groups__name")抱怨该字段不存在。

我还尝试过使用注释以类似的方式重命名预取字段。我没有抱怨该字段不存在,但是再次values()返回了未过滤的查询集。

我设法完成这种过滤的唯一方法是使用子查询:

qs = Fact.objects.annotate(
    brand_group_name=Subquery(
        BrandGroup.objects.filter(
            organization_id=self.request.META["ORG_ID_HEADER"],
            Q(id=OuterRef("brand_groups__id"))).values(
                "name"[:1],output_field=CharField(),))

# Now it gives me the desired results
qs.values("brand_group_name")

但是这种方法否定了我要完成的工作。目标是使用联接而不是子查询来提取BrandGroup数据。

是否有任何方法可以过滤ManyToMany相关字段而没有可以与values()一起使用的子查询?我唯一剩下的想法是在已经应用values()之后使用Python过滤查询集。

2 个答案:

答案 0 :(得分:0)

我认为您误解了prefetch_related的作用:

  另一方面,

prefetch_related对每个关系进行单独的查找,并在Python中执行“加入”。除了select_related支持的外键和一对一关系之外,这还允许它预取多对多和多对一对象,这不能使用select_related完成。

您最好的选择是使用示例中的子查询。

答案 1 :(得分:0)

最终,我最终没有进行任何过滤,而是在调用values之后使用Exists和使用values过滤器之前。

qs = Fact.objects.filter(...)
qs = qs.values("brand_groups__name", "brand_groups__id")

qs = qs.annotate(
    brand_group_accessible=Exists(
        BrandGroup.objects.filter(
            organization_id=self.request.META["ORG_ID_HEADER"],
            Q(id=OuterRef("brand_groups__id")))))

qs = qs.filter(Q(brand_group_accessible=True) | Q(brand_groups__id__isnull=True))