【问题标题】:Django annotate queryset by field from m2m through modelDjango通过从m2m到模型的字段注释查询集
【发布时间】:2020-11-24 13:36:25
【问题描述】:

我有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(...)

假设我有 organization_id 和 BottleType 查询集,因此对于查询集中的每个对象,需要找到按瓶类型和组织过滤的 OrganisationBottleType 并注释字段“is_accepted”和“点”。

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

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

我将不胜感激!

【问题讨论】:

    标签: django django-models django-queryset m2m


    【解决方案1】:

    如果我对您的理解正确,对于您想要的每种瓶子类型:

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

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

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

    这两个选项描述如下:

    1.将.prefetch_relatedPrefetch 对象一起使用

    给定:

    • queryset - BottleType 查询集
    • organisation_id - 所需组织的 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 - 所需组织的 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

    【讨论】:

      最近更新 更多