一、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
}
]
}