DRF 自定义 过滤、排序、分页 返回数据

一、DRF 自定义 过滤、排序、分页 返回数据

通过 PageNumberPagination、LimitOffsetPagination、CursorPagination、返回的JSON数据中

  • PageNumberPagination:不会携带总页数
  • LimitOffsetPagination:不会携带总页数
  • CursorPagination:不会返回总记录数据 以及 总页数

针对以上情况我们只能重写 list 方法,查询数据并且自己配置分页返回的数据,比如返回的状态码等,以下为 PageNumberPagination 分页类为例

以下演示会通过 过滤、排序、分页 进行统一的自定义返回的数据:

  • 正常来讲我们的数据首先会进行过滤之后再对数据排序,再对过滤和排序后的数据进行分页显示

一、定义 Books ORM 类

编辑 models.py 定义 Books ORM类

from django.db import models

# 书籍表
class Books(models.Model):
    name = models.CharField(verbose_name="书籍名称", max_length=32)
    price = models.FloatField(verbose_name="价格")

二、定义序列化器 BookSerializer

创建 serializer.py 文件,定义序列化器 BookSerializer

from api import models
from rest_framework import serializers

# 定义 BookSerializer 继承 ModelSerializer
class BookSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.Books			# 指定使用的ORM类为 Books
        fields = "__all__"				# 指定显示所有字段

三、自定义分页类 CommonPageNumberPagination

创建 utils 文件,我自定义的分页类就写到 utils 文件当中了

# 导入 PageNumberPagination 分页类
from rest_framework.pagination import PageNumberPagination

# 自定义分页类 CommonPageNumberPagination,继承 PageNumberPagination
# 重新 PageNumberPagination 分页类中的类属性即可
class CommonPageNumberPagination(PageNumberPagination):
    page_size = 2									# 默认每页显示2条数据
    max_page_size = 10								# 最大每页显示10条数据
    page_query_param = 'cansee_page'				# 指定显示第几页时使用的key cansee_page
    page_size_query_param = 'cansee_size'			# 指定显示每页多少条数据时使用的key cansee_size

四、自定义过滤类 BookFilter

1、需安装依赖

pip install django-filter

2、注册 django-filter(修改 setting 文件)

INSTALLED_APPS = [
    ...
    'django_filters',  # 需要注册应用,
]

3、在视图 views.py 中自定义过滤器类

# 导入 DjangoFilterBackend 过滤类
from django_filters.rest_framework import DjangoFilterBackend
# 导入 django_filters 过滤类集合
import django_filters
# 导入 ORM models 模块
from api import models

# 自定义过滤类,注意过滤类中的 field_name 字段名称要与 ORM 类中的字段一致
class BookFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
    price = django_filters.NumberFilter(field_name='price', lookup_expr='exact')
    price__gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt')
    price__lt = django_filters.NumberFilter(field_name='price', lookup_expr='lt')

    class Meta:
        model = models.Books
        fields = {
            'name': ['icontains'],
            'price': ['exact', 'gt', 'lt'],
        }

使用这个过滤类时,您可以通过查询字符串参数来过滤数据,例如:

  • ?name=example: 用于按名称过滤,icontains 包含过滤
  • ?price=10.99: 用于按价格精确过滤。exact 精确过滤
  • ?price__gt=10.99 用于按价格大于过滤。gt 大于过滤
  • ?price__lt=10.99 用于按价格小于过滤。lt 小于过滤

Meta 内部类:

  • model = Books:指定此过滤器类作用于 Books 模型。
  • fields:指定哪些字段可以被过滤,以及它们支持的查询表达式。

五、自定义视图类 BookView

编辑 views.py 文件,编辑 BookView 视图类继承 GenericViewSet,重写 list 方法,完成查询数据并返回分页数据

from rest_framework.response import Response
# 导入 GenericViewSet 视图集
from rest_framework.viewsets import GenericViewSet
import django_filters
# 导入我们自定义的 serializer(序列化器模块)、models(ORM类模块)、uitls(其中包含了自定义的分页类)
from api import serializer, models, uitls
# 导入 status 状态模块
from rest_framework import status
# 导入 django-filter 为我们提供的 DjangoFilterBackend 类
from django_filters.rest_framework import DjangoFilterBackend
# 导入 DRF 为我们提供的 OrderingFilter 排序类
from rest_framework.filters import OrderingFilter


# 自定义过滤类
class BookFilter(django_filters.FilterSet):
    name = django_filters.CharFilter(field_name='name', lookup_expr='icontains')
    price = django_filters.NumberFilter(field_name='price', lookup_expr='exact')
    price__gt = django_filters.NumberFilter(field_name='price', lookup_expr='gt')
    price__lt = django_filters.NumberFilter(field_name='price', lookup_expr='lt')

    class Meta:
        model = models.Books
        fields = {
            'name': ['icontains'],
            'price': ['exact', 'gt', 'lt'],
        }



# 书籍视图类
class BookView(GenericViewSet):
    queryset = models.Books.objects.all().order_by('id')  # 指定我们 Books ORM类 的查询数据的数据集(以id进行排序)
    serializer_class = serializer.BookSerializer  # 指定使用的序列化器

    # 指定过滤类:DjangoFilterBackend(django_filters提供的过滤类)、OrderingFilter(排序类)
    # 这里指定的类是有先后顺序的,如果写多个,一般先写过滤类再写排序类
    filter_backends = [DjangoFilterBackend, OrderingFilter]

    # 指定自定义的过滤类
    filterset_class = BookFilter

    # 指定支持按照那些字段排序,以下支持按照id、name、price这些字段进行排序
    ordering_fields = ['id', 'name', 'price']

    # 指定我们自定义的分页类 CommonPageNumberPagination(基本分页)
    pagination_class = uitls.CommonPageNumberPagination

    def list(self, request, *args, **kwargs):
        # 1、使用 self.filter_queryset(self.get_queryset()) 来获取过滤后的数据集。(如果没有过滤就是全部数据)
        queryset = self.filter_queryset(self.get_queryset())

        # 2、使用 self.paginate_queryset(queryset) 来获取分页后的数据
        page = self.paginate_queryset(queryset)

        # 3、获取序列化器,将分页后的数据放入序列化器当中,many=True 代表有多个对象
        serializer = self.get_serializer(page, many=True)

        # 4、获取URL中传入的每页显示多少条数据时使用的key值
        #       如果值存在那么就赋值给 page_size
        #       不存在就使用我们分页类自定义的默认显示数据数量 self.pagination_class.page_size.page_size
        try:
            if request.query_params.get(f"{self.pagination_class.page_size_query_param}"):
                page_size = int(request.query_params.get(f"{self.pagination_class.page_size_query_param}"))
                # 5、计算总页数, 总记录数 + 每页显示数据数量 - 1 // 每页显示数据数量
                count_page = (queryset.count() + page_size - 1) // page_size
            else:
                # 5、计算总页数, 总记录数 + 每页显示数据数量 - 1 // 每页显示数据数量
                count_page = (queryset.count() + self.pagination_class.page_size - 1) // self.pagination_class.page_size
        except ValueError as e:
            return Response({"code": 500, "msg": f"不是一个有效的整数,{e}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


        # 6、定义返回数据
        context = {
            "code": 200,                                # 返回的状态码
            "msg": "查询成功",                           # 详细信息
            "count": queryset.count(),                  # 总记录数
            "now_page": self.paginator.page.number,  # 当前所在页码
            "count_page": count_page,                   # 总分页数
            "next": self.paginator.get_next_link(),     # 下一页URL
            "previous": self.paginator.get_previous_link(),  # 上一页URL
            "results": serializer.data                      # 查询到的数据
        }

        # 7、通过 Response 返回客户端JSON数据
        return Response(context, status=status.HTTP_200_OK)

获取总页数计算逻辑:

  • count 能被 page_size 整除的情况
    • 例如,如果有 10 条记录,每页显示 10 条记录,那么总页数应该是 1。
    • 计算公式为:(10 + 10 - 1) // 10 = 1
  • count 不能被 page_size 整除的情况
    • 例如,如果有 15 条记录,每页显示 10 条记录,那么总页数应该是 2。
    • 直接计算 15 // 10 的结果是 1,这是不正确的,因为它忽略了剩余的 5 条记录也需要占用一页。
    • 计算公式为:(15 + 10 - 1) // 10 = 2

六、路由注册

from django.urls import path,include
from api import views
from rest_framework.routers import SimpleRouter,DefaultRouter

# 创建Router对象
# router = DefaultRouter()
router = SimpleRouter()
# 注册视图集
router.register('book', views.BookView)

urlpatterns = [
    # 使用 include 我们为注册视图集指定访问前缀
    path('api/<str:version>/',include(router.urls))
]

七、访问测试

目前我数据库中的数据(共八条数据):

MySQL [cansee_3]> select * from api_books;
+----+----------------+---------+
| id | name           | price   |
+----+----------------+---------+
|  2 | 开印13玩水     |  999.99 |
|  5 | cccc           |  111.13 |
|  8 | kami           | 1111.11 |
| 10 | kami           | 1111.11 |
| 12 | kami           | 1111.11 |
| 13 | kami           | 1111.11 |
+----+----------------+---------+

GET请求访问:http://127.0.0.1:8001/api/v1/book/

以下为返回的数据内容:可以看到总数据数量为 6,分页数为 3,当前页码为 1,因为我数据库一共6条数据,默认每页显示2条数据,一共3页

{
    "code": 200,							# 状态码									
    "msg": "查询成功",						  # 详细信息
    "count": 6,								# 总记录数
    "now_page": 1,							# 当前页码
    "count_page": 3,						# 总分页数
    "next": "http://127.0.0.1:8001/api/v1/book/?cansee_page=2",		# 下一页URL
    "previous": null,						# 上一页URL
    "results": [							# 返回的数据
        {
            "id": 2,
            "name": "开印13玩水",
            "price": 999.99
        },
        {
            "id": 5,
            "name": "cccc",
            "price": 111.13
        }
    ]
}

GET 请求访问测试:http://127.0.0.1:8001/api/v1/book/?cansee_size=3,设置默认每页显示3条数据

以下可以看到,因为我们的总数据就6条,当我们传入每页显示3条数据后,那么我们的分页数就是 2,

{
    "code": 200,
    "msg": "查询成功",
    "count": 6,
    "now_page": 1,
    "count_page": 2,
    "next": "http://127.0.0.1:8001/api/v1/book/?cansee_page=2&cansee_size=3",
    "previous": null,
    "results": [
        {
            "id": 2,
            "name": "开印13玩水",
            "price": 999.99
        },
        {
            "id": 5,
            "name": "cccc",
            "price": 111.13
        },
        {
            "id": 8,
            "name": "kami",
            "price": 1111.11
        }
    ]
}

GET请求访问测试:http://127.0.0.1:8001/api/v1/book/?cansee_size=2&cansee_page=2,查询第2页数据,默认每页2条数据

{
    "code": 200,
    "msg": "查询成功",
    "count": 6,																		# 总数据
    "now_page": 2,																	# 当前页码
    "count_page": 3,																# 总分页数量
    "next": "http://127.0.0.1:8001/api/v1/book/?cansee_page=3&cansee_size=2",		# 下一页URL
    "previous": "http://127.0.0.1:8001/api/v1/book/?cansee_size=2",					# 上一页URL
    "results": [
        {
            "id": 8,
            "name": "kami",
            "price": 1111.11
        },
        {
            "id": 10,
            "name": "kami",
            "price": 1111.11
        }
    ]
}

测试过滤与排序:

1、测试过滤不存在的数据:http://127.0.0.1:8001/api/v1/book/?name=dada,过滤name中包含dada这个数据

{
    "code": 200,
    "msg": "查询成功",
    "count": 0,				# 返回的数据总数为 0,因为没有符合条件的过滤数据
    "now_page": 1,
    "count_page": 0,
    "next": null,
    "previous": null,
    "results": []
}

2、过滤名称中包含13的数据 http://127.0.0.1:8001/api/v1/book/?name=13

{
    "code": 200,
    "msg": "查询成功",
    "count": 1,				# 只有一条符合条件的数据
    "now_page": 1,			# 目前页码
    "count_page": 1,		# 总页码
    "next": null,
    "previous": null,
    "results": [
        {
            "id": 2,
            "name": "开印13玩水",
            "price": 999.99
        }
    ]
}

3、过滤名称中包含k的数据,之后按照id进行倒序排序,再进行分页:http://127.0.0.1:8001/api/v1/book/?name=k&ordering=-id

{
    "code": 200,
    "msg": "查询成功",
    "count": 4,					# 只有4个符合条件的数据
    "now_page": 1,				# 当前页码
    "count_page": 2,			# 总页数
    "next": "http://127.0.0.1:8001/api/v1/book/?cansee_page=2&name=k&ordering=-id",	# 下一页
    "previous": null,			# 上一页
    "results": [				# 返回的数据,按照id倒序排序
        {
            "id": 13,
            "name": "kami",
            "price": 1111.11
        },
        {
            "id": 12,
            "name": "kami",
            "price": 1111.11
        }
    ]
}
  • 24
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值