[django]drf入门004 过滤排序分页(转载)

原文:https://pythondjango.cn/

1.分页

目录

  1. 为什么要分页?
  2. DRF提供的分页类
    1. PageNumberPagination类
    2. LimitOffsetPagination类
    3. CursorPagination类
  3. 函数类视图中使用分页类
  4. 小结

在前面的DRF系列教程中,我们以博客为例介绍了序列化器, 使用基于类的视图开发了针对文章资源进行增删查改的完整API端点,并详细对权限和认证(含jwt认证)进行了总结与演示。在本篇文章中我们将向你演示如何在Django REST Framework中使用分页。

为什么要分页?

当你的数据库数据量非常大时,如果一次将这些数据查询出来, 必然加大了服务器内存的负载,降低系统的运行速度。一种更好的方式是将数据分段展示给用户。如果用户在展示的分段数据中没有找到自己的内容,可以通过指定页码或翻页的方式查看更多数据,直到找到自己想要的内容为止。

DRF提供的分页类

Django REST Framework提供了3种分页类,接下来我们会分别进行演示。

  • PageNumberPagination:普通分页器。支持用户按?page=3&size=10这种更灵活的方式进行查询,这样用户不仅可以选择页码,还可以选择每页展示数据的数量。通常还需要设置max_page_size这个参数限制每页展示数据的最大数量,以防止用户进行恶意查询(比如size=10000), 这样一页展示1万条数据将使分页变得没有意义。
  • LimitOffsetPagination:偏移分页器。支持用户按?limit=20&offset=100这种方式进行查询。offset是查询数据的起始点,limit是每页展示数据的最大条数,类似于page_size。当你使用这个类时,你通常还需要设置max_limit这个参数来限制展示给用户数据的最大数量。
  • CursorPagination类:加密分页器。这是DRF提供的加密分页查询,仅支持用户按响应提供的上一页和下一页链接进行分页查询,每页的页码都是加密的。使用这种方式进行分页需要你的模型有”created”这个字段,否则你要手动指定ordering排序才能进行使用。

PageNumberPagination类

DRF中使用默认分页类的最简单方式就是在settings.py中进行全局配置,如下所示:

REST_FRAMEWORK ={
    'DEFAULT_PAGINATION_CLASS':'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE':2
}

展示效果如下,每页展示两条记录, 不支持用户指定每页展示数据的数量。

img

但是如果你希望用户按?page=3&size=10这种更灵活的方式进行查询,你就要进行个性化定制。在实际开发过程中,定制比使用默认的分页类更常见,具体做法如下。

第一步: 在app目录下新建pagination.py, 添加如下代码:

#blog/pagination.py
from rest_framework.pagination import PageNumberPagination

class MyPageNumberPagination(PageNumberPagination):
    page_size = 2   # default page size
    page_size_query_param = 'size'  # ?page=xx&size=??
    max_page_size = 10 # max page size

我们自定义了一个MyPageNumberPagination类,该类继承了PageNumberPagination类。我们通过page_size设置了每页默认展示数据的条数,通过page_size_query_param设置了每页size的参数名以及通过max_page_size设置了每个可以展示的最大数据条数。

第二步:使用自定义的分页类

在基于类的视图中,你可以使用pagination_class这个属性使用自定义的分页类,如下所示:

from rest_framework import viewsets
from .pagination import MyPageNumberPagination

class ArticleViewSet(viewsets.ModelViewSet):
    # 用一个视图集替代ArticleList和ArticleDetail两个视图
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    pagination_class = MyPageNumberPagination

    # 自行添加,将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    # 自行添加,将request.user与author绑定
    def perform_update(self, serializer):
        serializer.save(author=self.request.user)

展示效果如下所示: img

当然定制分页类不限于指定page_sizemax_page_size这些属性,你还可以改变响应数据的输出格式。比如我们这里希望把next和previous放在一个名为links的key里,我们可以修改MyPageNumberPagination类,重写get_paginated_response方法:

from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response


class MyPageNumberPagination(PageNumberPagination):
    page_size = 2   # default page size
    page_size_query_param = 'size'  # ?page=xx&size=??
    max_page_size = 10 # max page size
    
    
    def get_paginated_response(self, data):
        return Response({
            'links': {
                'next': self.get_next_link(),
                'previous': self.get_previous_link()
            },
            'count': self.page.paginator.count,
            'results': data
        })

新的展示效果如下所示:

img

注意:重写get_paginated_response方法非常有用,你还可以给分页响应数据传递额外的内容,比如code状态码等等。

前面的例子中我们只在单个基于类的视图或视图集中使用到了分页类,你还可以修改settings.py全局使用你自定义的分页类,如下所示。展示效果是一样的,我们就不详细演示了。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'blog.pagination.MyPageNumberPagination',
}

LimitOffsetPagination类

使用这个分页类最简单的方式就是在settings.py中进行全局配置,如下所示:

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'rest_framework.pagination.LimitOffsetPagination'
}

展示效果如下所示,从第6条数据查起,每页展示2条。

img

你也可以自定义MyLimitOffsetPagination类,在单个视图或视图集中使用,或者全局使用。

from rest_framework.pagination import LimitOffsetPagination


class MyLimitOffsetPagination(LimitOffsetPagination):
    default_limit = 5   # default limit per age
    limit_query_param = 'limit'  # default is limit
    offset_query_param = 'offset'  # default param is offset
    max_limit = 10 # max limit per age

CursorPagination类

使用这个分页类最简单的方式同样是在settings.py中进行全局配置。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'rest_framework.pagination.CursorPagination',
    'PAGE_SIZE': 2
}

展示效果如下所示:

img

什么? 为什么会出错误? 使用CursorPagination类需要你的模型里有created这个字段,否则你需要手动指定ordering字段。这是因为CursorPagination类只能对排过序的查询集进行分页展示。我们的Article模型只有create_date字段,没有created这个字段,所以会报错。

为了解决这个问题,我们需要自定义一个MyCursorPagination类,手动指定按create_date排序, 如下所示:

#blog/pagination.py
from rest_framework.pagination import CursorPagination


class MyArticleCursorPagination(CursorPagination):
    page_size = 3 # Default number of records per age
    page_size_query_param = 'page_size' 
    cursor_query_param = 'cursor' # Default is cursor
    ordering = '-create_date'

修改settings.py, 使用自己定义的分页类。

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':
    'blog.pagination.MyArticleCursorPagination',
}

响应效果如下所示,你将得到previous和next分页链接。页码都加密了, 链接里不再显示页码号码。默认每页展示3条记录, 如果使用?page_size=2进行查询,每页你将得到两条记录。

img

当然由于这个ordering字段与模型相关,我们并不推荐全局使用自定义的CursorPagination类,更好的方式是在GenericsAPIView或视图集viewsets中通过pagination_class属性指定,如下所示:

from rest_framework import viewsets
from .pagination import  MyArticleCursorPagination

class ArticleViewSet(viewsets.ModelViewSet):
    # 用一个视图集替代ArticleList和ArticleDetail两个视图
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    pagination_class = MyArticleCursorPagination
    
    # 自行添加,将request.user与author绑定
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)


    # 自行添加,将request.user与author绑定
    def perform_update(self, serializer):
        serializer.save(author=self.request.user)

函数类视图中使用分页类

注意pagination_class属性仅支持在genericsAPIView和视图集viewset中配置使用。如果你使用函数或简单的APIView开发API视图,那么你需要对你的数据进行手动分页,一个具体使用例子如下所示:

from rest_framework.pagination import PageNumberPagination

class ArticleList0(APIView):
    """
    List all articles, or create a new article.
    """
    def get(self, request, format=None):
        articles = Article.objects.all()
        
        page = PageNumberPagination()  # 产生一个分页器对象
        page.page_size = 3  # 默认每页显示的多少条记录
        page.page_query_param = 'page'  # 默认查询参数名为 page
        page.page_size_query_param = 'size'  # 前台控制每页显示的最大条数
        page.max_page_size = 10  # 后台控制显示的最大记录条数
        
        ret = page.paginate_queryset(articles, request)
        serializer = ArticleSerializer(ret, many=True)
        return Response(serializer.data)

小结

本文总结了DRF提供的3种分页类,介绍了如何自定义分页类并详细演示了如何使用它们。下章我们将介绍如何对DRF返回的响应数据进行过滤和排序。

原创不易,转载请注明来源。我是大江狗,一名Django技术开发爱好者。您可以通过搜索【CSDN大江狗】、【知乎大江狗】和搜索微信公众号【Python Web与Django开发】关注我

2.过滤与排序

目录

  1. 前情回顾
  2. 方法一: 重写get_queryset方法
  3. 方法二: 使用django-filter
    1. 安装django-filter
    2. 自定义FilterSet类
  4. 方法三: 使用SearchFilter类
    1. 自定义SearchFilter类
  5. 排序OrderingFilter类
  6. 小结

在前面的DRF系列教程中,我们以博客为例介绍了序列化器(Serializer), 并使用基于类的视图APIView和ModelViewSet开发了针对文章资源进行增删查改的完整API端点,并详细对权限、认证(含jwt认证)和分页进行了总结与演示。在本篇文章中我们将向你演示如何在Django REST Framework中对分页结果进行进一步过滤和排序。

前情回顾

前面教程中当你发送GET请求到/v1/articles?page=2时可以得到下面返回的分页数据列表。现在我们希望对结果进行进一步过滤,比如返回标题含有关键词django的文章资源列表。我们到底该怎么做呢? 本例中小编我将演示三种过滤方法, 你可以根据实际项目开发需求去使用。

img

方法一: 重写get_queryset方法

此方法不依赖于任何第三方包, 只适合于需要过滤的字段比较少的模型。比如这里我们希望对文章title进行过滤,我们只需要修改ArticleList视图函数类即可。

# blog/views.py

from rest_framework import generics
from rest_framework import permissions
from .permissions import IsOwnerOrReadOnly
from .pagination import MyPageNumberPagination

class ArticleList(generics.ListCreateAPIView):
    serializer_class = ArticleSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    pagination_class = MyPageNumberPagination

    def get_queryset(self):
        keyword = self.request.query_params.get('q')
        if not keyword:
            queryset = Article.objects.all()
        else:
            queryset = Article.objects.filter(title__icontains=keyword)
        return queryset

    # associate user with article author.
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

修改好视图类后,发送GET请求到/v1/articles?page=1&q=django, 你将得到所有标题含有django关键词的文章列表,这里显示一共有3条结果。

img

注意:DRF中你通过request.query_params获取GET请求发过来的参数,而不是request.GET。如果你希望获取从URL里传递的参数,你可以使用self.kwargs['param1']

假如你的URL配置如下所示:

re_path('^articles/(?P<username>.+)/$', AricleList.as_view()),

在视图中你可以通过self.kwargs['username']获取URL传递过来的用户名。

class ArticleList(generics.ListAPIView):
    serializer_class = ArticleSerializer

    def get_queryset(self):
        """
        按用户名查询发表文章清单
        """
        username = self.kwargs['username']
        return Article.objects.filter(author__username=username)

当一个模型需要过滤的字段很多且不确定时(比如文章状态、正文等等), 重写get_queryset方法将变得非常麻烦,更好的方式是借助django-filter这个第三方库实现过滤。

方法二: 使用django-filter

django-filter库包含一个DjangoFilterBackend类,该类支持REST框架的高度可定制的字段过滤。这也是小编推荐的过滤方法, 因为它自定义需要过滤的字段非常方便, 还可以对每个字段指定过滤方法(比如模糊查询和精确查询)。具体使用方式如下:

安装django-filter

pip install django-filter

django_filters添加到INSTALLED_APPS中去。

INSTALLED_APPS = [
    ...,
    django_filters,
]

接下来你还需要把DjangoFilterBackend设为过滤后台。你可以在settings.py中进行全局配置。

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend']
}

还可以在单个视图中使用它。

from django_filters import rest_framework

class ArticleList(generics.ListCreateAPIView):
  
    # new: filter backends and classes
    filter_backends = (rest_framework.DjangoFilterBackend,)

在类视图中使用django-filter时,你可以直接通过filterset_fields设置希望过滤的字段,如下所示:

from django_filters import rest_framework

class ArticleList(generics.ListCreateAPIView):
  
    # new: filter backends and classes
    filter_backends = (rest_framework.DjangoFilterBackend,)
    filterset_fields = ['title', 'status']

如果你希望进行更多定制化的行为,你需要自定义FilterSet类,然后指定filter_class

自定义FilterSet类

新建blog/filters.py, 添加如下代码:

import django_filters
from .models import Article


class ArticleFilter(django_filters.FilterSet):
    q = django_filters.CharFilter(field_name='title', lookup_expr='icontains')


    class Meta:
        model = Article
        fields = ('title', 'status')

接下来通过filter_class使用它。

# New for django-filter
from django_filters import rest_framework
from .filters import ArticleFilter


class ArticleList(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    pagination_class = MyPageNumberPagination


    # new: filter backends and classes
    filter_backends = (rest_framework.DjangoFilterBackend,)
    filter_class = ArticleFilter

    # associate request.user with author.
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

发送GET请求到/v1/articles?page=2&q=django&status=p, 你将得到如下返回结果,只包含发表了的文章。

img

你还可以看到REST框架提供了一个新的Filters下拉菜单按钮,可以帮助您对结果进行过滤(见上图标红部分)。

方法三: 使用SearchFilter类

其实DRF自带了具有过滤功能的SearchFilter类,其使用场景与Django-filter的单字段过滤略有不同,更侧重于使用一个关键词对模型的某个字段或多个字段同时进行搜索。

使用这个类,你还需要指定search_fields, 具体使用方式如下:

from rest_framework import filters

class ArticleList(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    pagination_class = MyPageNumberPagination

    # new: add SearchFilter and search_fields
    filter_backends = (filters.SearchFilter, )
    search_fields = ('title',)

    # associate request.user with author.
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

发送GET请求到/v1/articles?page=1&search=django, 你将得到如下结果。注意:这里进行搜索查询的默认参数名为?search=xxx。

img

SearchFilter类非常有用,因为它不仅支持对模型的多个字段进行查询,还支持ForeinKey和ManyToMany字段的关联查询。按如下修改search_fields, 就可以同时搜索标题或用户名含有某个关键词的文章资源列表。修改好后,作者用户名里如果有django,该篇文章也会包含在搜索结果了。

search_fields = ('title', 'author__username')

默认情况下,SearchFilter类搜索将使用不区分大小写的部分匹配(icontains)。你可以在search_fields中添加各种字符来指定匹配方法。

  • ’^’开始 - 搜索。
  • ’=’完全匹配。
  • ’@’全文搜索。
  • ’$’正则表达式搜索。

例如:search_fields = (‘=title’, )精确匹配title。

自定义SearchFilter类

默认SearchFilter类仅支持?search=xxx这个传递参数,你可以通过设置SEARCH_PARAM覆盖。另外你还可以重写get_search_fileds方法改变它的行为。下例中,当你按照/?search=keyword&title_only=True提交请求时,它将只针对title进行查询。

from rest_framework import filters

class CustomSearchFilter(filters.SearchFilter):
    def get_search_fields(self, view, request):
        if request.query_params.get('title_only'):
            return ['title']
        return super(CustomSearchFilter, self).get_search_fields(view, request)

前面我们详细介绍了对结果进行过滤的3种方法,接下来我们再看看如何对结果进行排序,这里主要通过DRF自带的OrderingFilter类实现。

排序OrderingFilter类

使用OrderingFilter类首先要把它加入到filter_backends, 然后指定排序字段即可,如下所示:

from rest_framework import filters

class ArticleList(generics.ListCreateAPIView):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
    pagination_class = MyPageNumberPagination

    filter_backends = (filters.SearchFilter, filters.OrderingFilter,)
    search_fields = ('title',)
    ordering_fields = ('create_date')

发送请求时只需要在参数上加上?ordering=create_date或者?ordering=-create_date即可实现对结果按文章创建时间正序和逆序进行排序。

点击DRF界面上的Filters按钮,你还会看到搜索和排序的选项。

img

注意:实际开发应用中OrderingFilter类,SearchFilter类和DjangoFilterBackend经常一起联用作为DRF的filter_backends,没有相互冲突。

小结

本文详细总结了Django REST Framework中如何对返回的响应数据进行过滤和排序,你都学到了吗? 接下来我们将介绍Django REST Framework的限流和自定义响应数据格式。

原文作者:您可以通过搜索【CSDN大江狗】、【知乎大江狗】和搜索微信公众号【Python Web与Django开发】

posted @ 2023-02-19 21:01  默默雷  阅读(75)  评论(0编辑  收藏  举报