Django 通过模型从 m2m 按字段注释查询集

Django annotate queryset by field from m2m through model

我有模型 BottleType 和 OrganisationBottleType。 我想通过 OrganisationBottleType.'is_accepted' 和 'points' 字段注释 BottleType 查询集。

OrganisationBottleType 模型:

class OrganisationBottleType(models.Model):
    organisation = models.ForeignKey(
        'Organisation',
        related_name='organisation_bottle_types',
        on_delete=models.CASCADE,
    )
    bottle_type = models.ForeignKey(
        'machines.BottleType',
        related_name='organisation_bottle_types',
        on_delete=models.CASCADE,
    )

    is_accepted = models.BooleanField(...)
    points = models.FloatField(...)

假设我有 organisation_id 和 BottleType 查询集,因此对于查询集中的每个对象,需要找到由 bottle_type 过滤的 OrganisationBottleType 和组织,并注释字段 'is_accepted' 和 'points' .

(当找到过滤后的 OrganisationBottleType qs 时,可以只从中获取第一个对象,因为假设两个字段:organization - bottle_type 是唯一的)

我的想法是在注释中使用子查询,但我做不对。

不胜感激!

如果我没理解错的话,对于你想要的每种瓶子类型:

  • 找到它的点
  • 检查是否被接受。

有两种方法可以解决您的问题:

  1. .prefetch_related()Prefetch()对象的帮助下,获取OrganisationBottleType查询集并将OrganisationBottleType对象匹配到python中BottleType中的相应对象
  2. .annotate()Subquery()
  3. 的帮助下,使用来自 OrganisationBottleType 的相应对象注释 BottleType 查询集

两个选项如下所述:

1.将 .prefetch_relatedPrefetch 对象

一起使用

鉴于:

  • queryset - BottleType 查询集
  • organisation_id - 所需组织的编号

解决方案:

queryset = queryset.prefetch_related(
    Prefetch(
        'organisation_bottle_types', 
        queryset= OrganisationBottleType.objects.filter(organisation_id=organisation_id)
    )
)

之后您可以通过以下方式检索所需的数据:

for bottle_type in queryset:
    if bottle_type.organisation_bottle_types.all():
        related_object = bottle_type.organisation_bottle_types.all()[0]
        is_accepted = related_object.is_accepted
        points = related_object.points
    else:
        is_accepted = False
        points = None

2。使用 SubQuery

鉴于:

  • queryset - BottleType 查询集
  • organisation_id - 所需组织的编号

解决方案:

 organisation_bottle_types = OrganisationBottleType.objects.filter(organisation_id=organisation_id, bottle_type=OuterRef('id'))
queryset = queryset.annotate(
    is_accepted=Subquery(organisation_bottle_types.values('is_accepted')[:1])
).annotate(
    points=Subquery(organisation_bottle_types.values('points')[:1], output_field=BooleanField())
)

之后可以做:

for bottle_type in queryset:
    is_accepted = bottle_type.is_accepted
    points = bottle_type.points

简历:

就个人而言,我会选择第二个选项,因为它会在数据库级别而不是代码级别执行所有匹配逻辑。

第一个选项更好,当你需要匹配整个对象时,而不仅仅是几个字段(比如你问题中的 pointsis_accepted