在django-restframework中限制分页的初始查询集对象

3 投票
2 回答
1787 浏览
提问于 2025-04-20 12:57

我正在使用 django-rest-framework 定义一个 ModelViewSet
我需要修改默认的数据查询方式,以便在返回响应之前,对查询到的数据进行一些处理。

这个处理过程比较耗时,所以我希望只对实际会返回给用户的数据进行处理,因为响应是分页的。而不是所有数据都进行处理,然后再进行分页,这种方式我觉得(如果我理解错了请纠正我)是DRF的默认行为。


简单来说,我需要的是:

如果默认查询的数据是 1000 个对象,但分页限制为 每页 25 个对象,我只想对这 25 个对象 进行处理。请注意,除了分页之外,没有其他限制来减少最终返回的对象数量。

有没有办法做到这一点?
在这种情况下,修改默认的数据查询方式是个坏主意吗?

谢谢!

2 个回答

0

理论:

之前提到过:

Django的查询集是懒加载的
它们只有在绝对必要的时候才会访问数据库(比如在你需要处理查询和分页之前。

DRF的分页过程有两个部分:

  • paginate_queryset
  • get_paginated_response

我们可以根据需要选择覆盖哪一部分(见实践部分)


实践:

根据你真正想处理的内容,有两个选择。

在这里假设我们要扩展/覆盖LimitOffsetPagination类,这样更容易举例,但同样的原则适用于其他所有DRF分页。

  1. 处理模型对象:

    如果你想在模型对象上执行预处理,并且希望这个处理在数据库中是永久性的,你需要覆盖paginate_queryset方法:

    class MyPaginationMethod(LimitOffsetPagination):
    
        def paginate_queryset(self, queryset, request, view=None):
            self.count = _get_count(queryset)
            self.limit = self.get_limit(request)
            if self.limit is None:
                return None
    
            self.offset = self.get_offset(request)
            self.request = request
            if self.count > self.limit and self.template is not None:
            self.display_page_controls = True
    
            if self.count == 0 or self.offset > self.count:
                return []
    
            """
            Do your processing here on the  
            queryset[self.offset:self.offset + self.limit]
            Which has actually self.limit (e.g 25) amount of objects.
            """
    
            return list(YOUR_PROCESSED_QUERYSET)
    
  2. 处理分页响应:

    如果你想在响应上执行预处理,并且希望这个处理在数据库中是永久性的,你需要覆盖get_paginated_response方法:

    class MyPaginationMethod(LimitOffsetPagination):
    
        def get_paginated_response(self, data):
            """
            Do your processing here on the data variable.
            The data is a list of OrderedDicts containing every object's
            representation as a dict.
            """
            return Response(OrderedDict([
                ('count', self.count),
                ('next', self.get_next_link()),
                ('previous', self.get_previous_link()),
                ('results', YOUR_PROCESSED_DATA)
            ]))
    
1

没有“简单”的方法来做到这一点。在Django REST框架中,分页和渲染是用同一种方式来处理的。

所以我想最好的办法是定义你自己的视图集,并重新声明列表方法:

from rest_framework.viewssets import ModelViewSet

class MyModelViewSet(ModelViewSet):

  def list(self, request, *args, **kwargs):
    self.object_list = self.filter_queryset(self.get_queryset())                
    if not self.allow_empty and not self.object_list:                           
      warnings.warn(                                                          
        'The `allow_empty` parameter is due to be deprecated. '             
        'To use `allow_empty=False` style behavior, You should override '   
        '`get_queryset()` and explicitly raise a 404 on empty querysets.',  
        PendingDeprecationWarning
      )              
      class_name = self.__class__.__name__                                    
      error_msg = self.empty_error % {'class_name': class_name}
      raise Http404(error_msg)                                                

    page = self.paginate_queryset(self.object_list)                             


    ## INSERT YOUR CODE HERE


    if page is not None:
      serializer = self.get_pagination_serializer(page)                       
    else:       
      serializer = self.get_serializer(self.object_list, many=True)           

    return Response(serializer.data)     

撰写回答