Django 中实现数据分页的完整方案
导入核心分页模块
在 Django 中使用分页功能,需引入以下类:
from django.core.paginator import Paginator, EmptyPage
Paginator:用于对查询集进行分页处理。EmptyPage:当请求的页码超出范围时抛出的异常,可用于异常捕获和默认页跳转。
内置 Paginator 的基本用法
假设有一个模型查询结果 books,每页显示 7 条记录:
paginator = Paginator(books, 7)
常用属性与方法:
paginator.count |
总数据条数 |
paginator.num_pages |
总页数(根据每页数量计算) |
paginator.page_range |
页码范围生成器,如 range(1, 15) |
获取指定页面内容
page = paginator.page(1) # 获取第一页对象
for item in page:
print(item.name) # 遍历当前页所有对象
# 获取当前页数据列表
data_list = page.object_list
页面导航控制方法
page.has_previous():判断是否存在上一页page.has_next():判断是否存在下一页page.has_other_pages():判断是否有其他页(上或下)page.previous_page_number():返回上一页页码page.next_page_number():返回下一页页码
模拟数据填充示例
为测试分页效果,可批量创建测试数据:
from myapp.models import Book
book_instances = []
for i in range(1, 100):
book_instances.append(Book(title=f'Python 入门 {i}', price=i))
try:
Book.objects.bulk_create(book_instances)
except Exception as e:
pass # 忽略主键冲突等异常
视图中集成分页逻辑
在视图函数中结合 Paginator 实现分页响应:
def book_list(request):
book_queryset = Book.objects.all()
paginator = Paginator(book_queryset, 10)
page_number = request.GET.get('page', 1)
try:
page_obj = paginator.page(page_number)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
context = {
'page_obj': page_obj
}
return render(request, 'books/list.html', context)
前端模板中的分页展示
在 HTML 模板中遍历当前页数据:
{% for book in page_obj %}
<div>{{ book.title }} - ¥{{ book.price }}</div>
{% endfor %}
基础分页控件渲染
<ul class="pagination">
{% if page_obj.has_previous %}
<li><a href="?page={{ page_obj.previous_page_number }}">上一页</a></li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
<li {% if num == page_obj.number %}class="active"{% endif %}>
<a href="?page={{ num }}">{{ num }}</a>
</li>
{% endfor %}
{% if page_obj.has_next %}
<li><a href="?page={{ page_obj.next_page_number }}">下一页</a></li>
{% endif %}
</ul>
优化页码显示:仅展示部分页码
当总页数较多时,避免显示全部页码。例如只显示当前页前后各 5 页:
# 在视图或分页类中计算显示范围
def get_display_range(page, total_pages, max_display=11):
if total_pages <= max_display:
return range(1, total_pages + 1)
half = max_display // 2
start = page - half
end = page + half
if start < 1:
start = 1
end = max_display
if end > total_pages:
end = total_pages
start = max(end - max_display + 1, 1)
return range(start, end + 1)
自定义高级分页组件
为了增强功能和复用性,可封装一个支持 URL 参数保留、首页/尾页跳转等功能的分页器:
import copy
from django.utils.safestring import mark_safe
class PageBar:
def __init__(self, request, queryset, page_param='page', per_page=10):
self.query_params = copy.deepcopy(request.GET)
self.query_params._mutable = True
self.page_param = page_param
self.per_page = per_page
total = queryset.count()
total_pages, remainder = divmod(total, per_page)
if remainder:
total_pages += 1
current_page = self.parse_page(request.GET.get(page_param), total_pages)
self.total_count = total
self.total_pages = total_pages
self.current_page = current_page
self.start_idx = (current_page - 1) * per_page
self.end_idx = current_page * per_page
self.queryset = queryset[self.start_idx:self.end_idx]
def parse_page(self, val, max_page):
if not val or not val.isdigit():
return 1
num = int(val)
if num < 1:
return 1
elif num > max_page:
return max_page
return num
def render(self):
if self.total_pages == 0:
return mark_safe("")
page_html = []
# 首页
self.query_params.setlist(self.page_param, [1])
page_html.append(f'<li><a href="?{self.query_params.urlencode()}">首页</a></li>')
# 上一页
if self.current_page > 1:
self.query_params.setlist(self.page_param, [self.current_page - 1])
page_html.append(f'<li><a href="?{self.query_params.urlencode()}">上一页</a></li>')
# 动态页码范围
if self.total_pages <= 11:
page_range = range(1, self.total_pages + 1)
elif self.current_page <= 6:
page_range = range(1, 12)
elif self.current_page + 5 > self.total_pages:
page_range = range(self.total_pages - 10, self.total_pages + 1)
else:
page_range = range(self.current_page - 5, self.current_page + 6)
for i in page_range:
self.query_params.setlist(self.page_param, [i])
active = ' class="active"' if i == self.current_page else ''
page_html.append(f'<li{active}><a href="?{self.query_params.urlencode()}">{i}</a></li>')
# 下一页
if self.current_page < self.total_pages:
self.query_params.setlist(self.page_param, [self.current_page + 1])
page_html.append(f'<li><a href="?{self.query_params.urlencode()}">下一页</a></li>')
# 尾页
self.query_params.setlist(self.page_param, [self.total_pages])
page_html.append(f'<li><a href="?{self.query_params.urlencode()}">尾页</a></li>')
# 统计信息
page_html.append(f'<li class="disabled"><a>共 {self.total_count} 条 | {self.total_pages} 页</a></li>')
return mark_safe("".join(page_html))
在视图中使用自定义分页器
def product_list(request):
products = Product.objects.all()
pager = PageBar(request, products, per_page=15)
context = {
'products': pager.queryset,
'pager_html': pager.render()
}
return render(request, 'product/list.html', context)
前端调用方式
{% for p in products %}
<div>{{ p.name }}</div>
{% endfor %}
<ul class="pagination">
{{ pager_html }}
</ul>