Django通过不同的相关对象注释QuerySet中的几个相同对象

Posted

技术标签:

【中文标题】Django通过不同的相关对象注释QuerySet中的几个相同对象【英文标题】:Django annotate several same objects in QuerySet by different related object 【发布时间】:2019-06-19 23:49:39 【问题描述】:

我明白了:

# models

class Building(models.Model):
    ...


class Flat(models.Model):
    building = models.ForeignKey(Building)


class Profile(models.Model):
    flats = models.ManyToManyField(Flat)
# logic

building = Building.objects.create()
flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)

profile = Profile.objects.create()
profile.flats.add(flat_1)
profile.flats.add(flat_2)

profiles = Profile.objects.filter(flats__building=building)  

我获得了profiles 2 个相同的个人资料。我如何用不同的flat 注释它们中的每一个:profiles.first().flat == flat_1profiles.last().flat == flat_2

也许是Subquery(),但如何?

UPD 我在某些 DRF 列表视图中需要这个。 JSON 格式的输出必须类似于:

[
  
    "profile_id": 1,
    "flat_id": 2
  ,
  
    "profile_id": 1,
    "flat_id": 3
  
]

【问题讨论】:

不完全确定您要达到的目标,但是如果您将through-model (docs.djangoproject.com/en/2.1/ref/models/fields/…) 添加到您的ManyToManyField,这样的事情会变得更容易,然后您可以对此进行查询关系也是... @BernhardVallant 是的,这是一个选项。谢谢! 【参考方案1】:

要获得该输出,您可以这样做:

data = Profile.objects.all().values('flats', 'id')
return Response(data=data)

在您的 DRF 视图中。

【讨论】:

:)。我有 350 行代码的视图类,带有过滤器、排序等,我不能像.values() 那样简单。中间有很多业务逻辑。我真的需要像 .annotate() 这样的东西来为查询集中的每个对象添加正确的平面或类似的字段。 我认为这是一个正确的答案。无论您的查询集有多复杂,您始终可以将.values('flats', 'id') 放在最后。 filtervalues 的顺序可以互换 我认为为了获得像 OP 所要求的 json,您应该过滤单位,而不是获取所有配置文件对象,因为关系是 many2many。更干净/优雅/直接的方法需要改造。也许这就是 OP 应该考虑的......【参考方案2】:

您不必分析实例...

我最后为您的确切需求编写了代码,但首先写了一些可能感兴趣的东西。

在您的代码示例中,您只创建了一个配置文件,我确定您没有获得 2 个相等的配置文件实例,而只有一个。

问题是,如果您有一个只有一个条目的 QuerySet,那么:

profiles.first() == profiles.last()  # True

因为profile.first()profiles.last() 是同一个实例。

您应该尝试创建 2 个 Profile 实例:

building = Building.objects.create()

flat_1 = Flat.objects.create(building=building)
flat_2 = Flat.objects.create(building=building)

profile_1 = Profile.objects.create()  # You coud/should use bulk_create here.
profile_2 = Profile.objects.create()

profile_1.flats.add(flat_1)
profile_2.flats.add(flat_2)

然后

profiles = Profile.objects.filter(flats__building=building)  

将返回两个不同的配置文件对象。

另一方面,获取你想要的 JSON ...

按照示例,您发布,按配置文件过滤公寓并获取值(如果您有多个配置文件,这也适用)。

Flat.objects.filter(profile=profile_1).values('profile__id', 'id')

这将返回类似(“id”代表公寓ids):

[
  
    "profile__id": 1,
    "id": 1
  ,
  
    "profile__id": 1,
    "id": 3
  
]

如果您不按个人资料过滤(并且您有多个),您可能会得到如下信息:

[
  
    "profile__id": 1,
    "id": 1
  ,
  
    "profile__id": 2,
    "id": 3
  ,
  
    "profile__id": 2,
    "id": 4
  ,
  ...
]

注释以获得您想要的 EXACT json:

过滤如上图注解,得到想要的值:

Flat.objects.filter(profile=profile_1).annotate(
    flat_id=F('id')
).annotate(
    profile_id=F('profile__id')
).values(
    'profile_id', 'flat_id'
)

会给出你想要的:

[
  
    "profile_id": 1,
    "flat_id": 2
  ,
  
    "profile_id": 1,
    "flat_id": 3
  
]

【讨论】:

【参考方案3】:

您可以使用正确的序列化程序和正确的注释来做到这一点:

序列化器:

class FlatSerializer(serializers.ModelSerializer):
    class Meta:
        model = Flat
        fields = ('flat_id', 'building_id')

    flat_id = serializers.CharField(read_only=True)

然后我会简单地查询 Flats 而不是配置文件并序列化:

flats = Flat.objects \
    .annotate(flat_id=F('id')) \
    .filter(building=building)

serialized = FlatSerializer(flats, many=True)
print(serialized.data) # [  flat_id: 1, building_id: 1 ,  flat_id: 2, building_id: 1  ]

让我知道这是否适合你

【讨论】:

tgdn 感谢您的回答。我的问题是在profiles 查询集中制作双打,并在配置文件和平面之间用m2m 的不同平面注释每个双打。你的代码是关于别的东西的:)。

以上是关于Django通过不同的相关对象注释QuerySet中的几个相同对象的主要内容,如果未能解决你的问题,请参考以下文章

如何使用相同的值但使用现有字段之一的不同数据类型来注释 Django Queryset?

通过特定的 ManyToMany 对象订购 Django QuerySet

Django:将带有相关对象的 QuerySet 转换为 JSON

从 Django QuerySet 中获取所有相关的多对多对象

Django Queryset 过滤器检查相关对象是不是存在

Django的注释和聚合方法之间的区别?