Django,prefetch_related() 的通用版本?

Django, general version of prefetch_related()?

当然,我不想做 prefetch_related 已经做过的事情。

我想模仿它的作用。
我想做的是以下内容。

我有一个 MyModel 实例列表。
用户可以 followsdoesn't follow 每个实例。

my_models = MyModel.objects.filter(**kwargs)

for my_model in my_models:
   my_model.is_following = Follow.objects.filter(user=user, target_id=my_model.id, target_content_type=MY_MODEL_CTYPE)

这里我有n+1个查询问题,我想我可以借用prefetch_related在这里做的事情。 prefetch_related 的描述说,它对所有对象执行查询,当需要相关属性时,它从预先执行的查询集中获取。

这正是我所追求的,对我感兴趣的所有对象执行 is_following 的查询。并使用查询而不是 N 个单独的查询。

另一个方面是,我想附加查询集而不是附加实际值,这样我就可以将评估推迟到分页。
如果那句话太模棱两可,我想将附加了 is_following 信息的 my_models 查询集提供给另一个函数(例如 DRF 序列化程序)。

prefetch_related 是如何完成上面的事情的?

不确定这是否是最好的方法,我怀疑这是否是 prefetch_related 所做的,因为我要加入这里。

我发现可以在您的查询中找到 select extra 列。

        extra_select = """
        EXISTS(SELECT * FROM follow_follow
        WHERE follow_follow.target_object_id = myapp_mymodel.id AND
        follow_follow.target_content_type_id = %s AND
        follow_follow.user_id = %s)
        """

        qs = self.extra(
            select={'is_following': extra_select},
            select_params=[CONTENT_TYPE_ID, user.id]
        )

所以你可以通过加入来做到这一点。

prefetch_related 这样做的方法是 separate queryset 并在查询集中查找属性。

通过 .extra.

的子查询可以实现只能获得 is_following 位的解决方案
class MyModelQuerySet(models.QuerySet):
    def annotate_is_follwing(self, user):
        return self.extra(
            select = {'is_following': 'EXISTS( \
                SELECT `id` FROM `follow` \
                WHERE `follow`.`target_id` = `mymodel`.id \
                AND `follow`.`user_id` = %s)' % user.id
            }
        )

class MyModel(models.Model):

    objects = MyModelQuerySet.as_manager()

用法:

my_models = MyModel.objects.filter(**kwargs).annotate_is_follwing(request.user)

现在是另一种解决方案,您可以获得 following 个对象的完整列表。

因为您在 Follow class 中有一个 GFK,您需要通过 GenericRelation 手动创建一个 reverse 关系。类似于:

class MyModelQuerySet(models.QuerySet):
    def with_user_following(self, user):
        return self.prefetch_related(
            Prefetch(
                'following', 
                queryset=Follow.objects.filter(user=user) \
                    .select_related('user'), 
                to_attr='following_user'
            )
        )

class MyModel(models.Model):

    following = GenericRelation(Follow,
        content_type_field='target_content_type',
        object_id_field='target_id'
        related_query_name='mymodels'
    )

    objects = MyModelQuerySet.as_manager()

    def get_first_following_object(self):
        if hasattr(self, 'following_user') and len(self.following_user) > 0:
            return self.following_user[0]
        return None

用法:

my_models = MyModel.objects.filter(**kwargs).with_user_following(request.user)

现在您可以访问 following_user 属性 - 每个 mymodel 包含所有 follow 个对象的列表,或者您可以使用类似 get_first_following_object.[=25 的方法=]