如何在Django中跨通用关系查询

1 投票
1 回答
1579 浏览
提问于 2025-04-18 08:38

我正在制作一个活动动态,但我不知道怎么避免N+1查询的问题。

简单来说,一个团队或地方有关注者和活动,每个用户都是一个关注者,他们的活动动态是从他们关注的团队或地方获取的。

这是我模型的一些代码片段:

class Follower(models.Model):
    user = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Activity(models.Model):
    ...
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey('content_type', 'object_id')

class Team(models.Model):
    ...
    followers = GenericRelation('Follower')
    activities = GenericRelation('Activity')

class Place(models.Model):
    ...
    followers = GenericRelation('Follower')
    activities = GenericRelation('Activity')

我在使用DRF(Django REST框架),并且为活动创建了一个基本的序列化器,我觉得比较棘手的部分是在视图里面:

 class ActivityViewSet(viewsets.ModelViewSet):

     def list(self, request):
         #N+1 query
         activities = [f.content_object.activities.all() for f in request.user.follower_set.all()]

         ...

这样做是有效的,但有没有更好的方法来进行这个查询呢?

1 个回答

2
def list(self, request):
    items = {}
    for f in request.user.follower_set.all():
        items.setdefault(f.content_type_id, []).append(f.object_id)
    activities = Activity.objects.get_empty_queryset() # get_empty_query_set in <1.5
    for k, v in items.iteritems():
        activities |= Activity.objects.filter(content_type=k, object_id__in=v)

这其实可以归结为两个查询。它利用了这样一个事实:如果一个 Follower(关注者)和一个 Activity(活动)是与同一个对象(比如团队或地点)相关的,那么它们的 content_type(内容类型)和 object_id(对象ID)就是一样的。稍微用一下Python处理一下,哒哒,你就能通过两个查询获取到当前用户的所有相关活动。

撰写回答