如何使用 ToManyField 加速 tastypie 的查询

How to speed up tastypie's queries with ToManyField

在resources.py我有:

class CategoryResource(ModelResource):
    items = fields.ToManyField('ItemResource', 'items', full=True, null=False, readonly=True, related_name='items')
    class Meta:
        queryset = Category.objects.all().order_by('id')
        include_resource_uri = False
        always_return_data = True
        resource_name = 'category'

6 个类别中大约有 5000 个项目。当我发出 'list' api 请求时,即 'api/1.0/category',它对数据库进行了大约 5000 次查询。我该如何优化它?我知道 full=False,但它不适合我的需要。

更新: 我发现是什么导致了如此多的查询。我在 ItemResource 中有 'categories' 关系,所以 tastypie 为每个项目生成一个 select 查询。

class ItemResource(ModelResource):
    categories = fields.ToManyField(CategoryResource, 'categories', null=True, readonly=True)

    def dehydrate_categories(self, bundle):
        categories = Category.objects.filter(owner_id=bundle.request.user.id, items__item=bundle.obj)
        return [category.name for category in categories]

很明显,当我请求 CategoryResource 时,这是不必要的数据,有什么办法可以从查询中排除它吗?

试试这个:

def get_object_list(self, request):
    return super(CategoryResource, self).get_object_list(request) \
        .prefetch_related('items', 'items__categories')

不要使用 select_related,因为它 return 重复行。

prefetch_related 进行了一次查询,return 所有项目和 Django ORM 将其匹配到正确的行。

编辑

改变dehydrate_categories

def dehydrate_categories(self, bundle):
    return [category.name for category in bundle.obj.categories.all() if category.owner == bundle.request.user]

如果还有问题,可能是ItemResource也有一个或多个相关字段。如果是这种情况,您可能可以执行以下操作:

def get_object_list(self, request):
    return super(CategoryResource, self).get_object_list(request).prefetch_related('items__relatedfield1', 'items__relatedfield2')

请记住,在 ItemResource 上执行的任何查询集更改(例如 select_relatedprefetch_related 都不会影响在 CategoryResource 上对相关模型的查询。

更新: 尝试这样的事情:

class CategoryResource(ModelResource):
    items = fields.ToManyField('ItemResource', 'items', full=True, null=False, readonly=True, related_name='items')

    class Meta:
        queryset = Category.objects.all().order_by('id')
        include_resource_uri = False
        always_return_data = True
        resource_name = 'category'

    def get_object_list(self, request):
        return super(CategoryResource, self).get_object_list(request) \
            .prefetch_related('items', 'items__categories')

class ItemResource(ModelResource):
    categories = fields.ToManyField(CategoryResource, 'categories', null=True, readonly=True)

    def dehydrate_categories(self, bundle):
        categories = items__item=bundle.obj.categories.all()
        return [
            category.name
            for category in categories
            if category.owner_id == bundle.request.user.id
        ]

或:

class CategoryResource(ModelResource):
    items = fields.ToManyField('ItemResource', 'items', full=True, null=False, readonly=True, related_name='items')

    class Meta:
        queryset = Category.objects.all().order_by('id')
        include_resource_uri = False
        always_return_data = True
        resource_name = 'category'

    def get_object_list(self, request):
        return super(CategoryResource, self).get_object_list(request) \
            .prefetch_related(
                'items',
                Prefetch('items__categories', queryset=Category.objects.filter(owner_id=bundle.request.user.id))
            )

class ItemResource(ModelResource):
    categories = fields.ToManyField(CategoryResource, 'categories', null=True, readonly=True)

    def get_object_list(self, request):
        return super(ItemResource, self).get_object_list(request) \
            .prefetch_related(
                Prefetch('categories', queryset=Category.objects.filter(owner_id=bundle.request.user.id))
            )

    def dehydrate_categories(self, bundle):
        return [
            category.name
            for category in bundle.obj.categories.all()
        ]

另外,您可以使用 ListField 而不是 ToManyField ItemResource.categories,因为您是手动处理脱水。