فصل ۱۳: Pagination در Django — صفحهبندی دیتا

۱. مقدمه: چرا به صفحهبندی نیاز داریم؟
تصور کن فروشگاه آنلاین ما صدها یا هزاران محصول داره.
اگر همه رو یکجا توی یک صفحه نشون بدیم:
- کاربر باید کلی اسکرول کنه
- سرعت لود صفحه پایین میاد
- فشار زیادی روی سرور و دیتابیس میاد
✅ راهحل: Pagination (صفحهبندی) → تقسیم داده به صفحات کوچکتر و مدیریت ناوبری بین صفحات.
۲. دو روش پیادهسازی Pagination در Django
- با استفاده از کلاس ListView (CBV) — سادهتر چون Pagination داخلی داره.
- با استفاده از
Paginatorکلاس جنگو — برای وقتی که از FBV استفاده میکنیم یا شخصیسازی زیاد میخوایم.
ما هر دو رو یاد میگیریم.
۳. روش اول — Pagination با ListView
در فصل قبل ما یک ویوی ProductListView داشتیم که با CBV نوشته بودیم.
حالا کافیه خط زیر رو بهش اضافه کنیم:
from django.views.generic import ListView
from .models import Product
class ProductListView(ListView):
model = Product
template_name = 'product_list.html'
context_object_name = 'products'
paginate_by = 6 # تعداد آیتم در هر صفحه
📌 داخل urls.py هم داریم:
path('products/', ProductListView.as_view(), name='product_list'),
قالب HTML برای صفحهبندی
<h1>لیست محصولات</h1>
{% for product in products %}
<div>
<h3>{{ product.name }}</h3>
{% if product.image %}
<img src="{{ product.image.url }}" width="120">
{% endif %}
<p>قیمت: {{ product.price }} تومان</p>
</div>
{% empty %}
<p>محصولی یافت نشد</p>
{% endfor %}
<hr>
<!-- ناوبری صفحهها -->
{% if is_paginated %}
<div>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">⬅ قبلی</a>
{% endif %}
<span>صفحه {{ page_obj.number }} از {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">بعدی ➡</a>
{% endif %}
</div>
{% endif %}
نکته:
is_paginated→ اگر صفحهبندی فعال باشه، True میشهpage_obj→ اطلاعات صفحه فعلی (شماره، صفحات بعدی/قبلی و …) رو داره
۴. روش دوم — Pagination با Paginator در FBV
برای کنترل دستی، باید ماژول زیر رو ایمپورت کنیم:
from django.core.paginator import Paginator
مثال:
from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Product
def product_list(request):
product_list = Product.objects.all()
paginator = Paginator(product_list, 6) # تعداد آیتم در هر صفحه
page_number = request.GET.get('page') # شماره صفحه از URL
page_obj = paginator.get_page(page_number)
return render(request, 'product_list.html', {'page_obj': page_obj})
📌 در قالب، به جای products از page_obj استفاده میکنیم:
{% for product in page_obj %}
<h3>{{ product.name }}</h3>
{% endfor %}
<!-- دکمههای ناوبری -->
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">⬅ قبلی</a>
{% endif %}
<span>صفحه {{ page_obj.number }} از {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">بعدی ➡</a>
{% endif %}
۵. شخصیسازی Pagination
گاهی نیاز داریم تعداد آیتمها رو با انتخاب کاربر تغییر بدیم.
مثال:
def product_list(request):
per_page = request.GET.get('per_page', 6) # پیشفرض 6
products = Product.objects.all()
paginator = Paginator(products, per_page)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'product_list.html', {'page_obj': page_obj})
📌 حالا کاربر میتونه با URL مثل:
/products/?per_page=12
12 محصول در هر صفحه ببینه.
۶. بهبود UI صفحهبندی
در Bootstrap، میتونیم صفحهبندی زیباتر بسازیم:
{% if is_paginated %}
<ul class="pagination">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">قبلی</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">بعدی</a>
</li>
{% endif %}
</ul>
{% endif %}
۷. نکات حرفهای (Best Practices)
- ترتیب نمایش → قبل از صفحهبندی،
order_by()رو روی QuerySet بزن. - فیلتر و صفحهبندی هماهنگ → اگر کاربر محصولات رو فیلتر میکنه (مثلاً بر اساس دستهبندی)، همون QuerySet فیلتر شده رو صفحهبندی کن.
- حفظ پارامترهای جستجو → اگر کاربر جستجو کرده، توی لینک صفحهبندی باید پارامترهای جستجو (
?search=...&page=2) رو نگه داری. - استفاده از کش (cache) برای لیستهای بزرگ.
۸. مثال نهایی — لیست محصولات فروشگاه با جستجو + صفحهبندی
views.py:
from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Product
def product_list(request):
search_query = request.GET.get('q', '')
products = Product.objects.all()
if search_query:
products = products.filter(name__icontains=search_query)
paginator = Paginator(products, 6)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'product_list.html', {
'page_obj': page_obj,
'search_query': search_query
})
product_list.html:
<form method="get">
<input type="text" name="q" value="{{ search_query }}" placeholder="جستجو محصول...">
<button type="submit">جستجو</button>
</form>
{% for product in page_obj %}
<h3>{{ product.name }}</h3>
{% empty %}
<p>محصولی یافت نشد</p>
{% endfor %}
{% if page_obj.has_previous %}
<a href="?q={{ search_query }}&page={{ page_obj.previous_page_number }}">⬅ قبلی</a>
{% endif %}
صفحه {{ page_obj.number }} از {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?q={{ search_query }}&page={{ page_obj.next_page_number }}">بعدی ➡</a>
{% endif %}
۹. خطاهای رایج
| خطا | علت | راهحل |
|---|---|---|
| نمایش همه آیتمها در یک صفحه | فراموش کردن استفاده از paginate_by یا Paginator | اضافه کردن کد صفحهبندی |
| رفتن به صفحه نامعتبر | ورودی page خارج از محدوده | استفاده از get_page() به جای page() |
| گم شدن جستجو بعد از صفحه بعدی | پارامترهای GET رو توی لینک قبلی/بعدی نگه نداشتن | استفاده از ?q={{ search_query }} |
۱۰. جمعبندی
در این فصل یاد گرفتیم:
- مفاهیم صفحهبندی و دلیل اهمیتش
- پیادهسازی با CBV (
paginate_by) - پیادهسازی با FBV و کلاس
Paginator - ایجاد صفحهبندی زیبا با Bootstrap
- هماهنگ کردن جستجو با Pagination