Triển khai trang danh sách sản phẩm trong Django với phân trang và sắp xếp linh hoạt

Thiết kế URL cho trang danh sách

URL trang danh sách tuân thủ nguyên tắc RESTful: mỗi đường dẫn xác định một tài nguyên cụ thể, còn các tham số điều khiển hiển thị (như thứ tự sắp xếp) được truyền qua query string. Cấu trúc cuối cùng là:

/goods/list/<int:category_id>/<int:page_number>/?ordering=price

Trong đó:

  • category_id: ID loại hàng hóa (bắt buộc)
  • page_number: Số trang yêu cầu (bắt buộc)
  • ordering: Trường dùng để sắp xếp — giá trị hợp lệ gồm price, sales, hoặc create_time; nếu không tồn tại, hệ thống mặc định sử dụng create_time theo chiều giảm dần.

Định nghĩa route trong urls.py:

urlpatterns = [
    # ... các route khác
    path('goods/list/<int:category_id>/<int:page_number>/', ProductListView.as_view(), name='product_list'),
]

Xử lý logic hiển thị trong View

Triển khai lớp view dựa trên View của Django để kiểm soát toàn bộ luồng yêu cầu GET:

from django.core.paginator import Paginator
from django.shortcuts import render, redirect
from django.urls import reverse
from .models import GoodsType, Goods

class ProductListView(View):
    template_name = 'goods/list.html'

    def get(self, request, category_id, page_number):
        # Lấy toàn bộ danh mục
        all_categories = GoodsType.objects.all()

        # Xác định danh mục hiện tại
        try:
            current_category = GoodsType.objects.get(id=category_id)
        except GoodsType.DoesNotExist:
            return redirect(reverse('goods:index'))

        # Sản phẩm mới nhất (2 mục đầu tiên theo thời gian tạo)
        latest_items = Goods.objects.filter(
            goodstype=current_category
        ).order_by('-create_time')[:2]

        # Thiết lập chiến lược sắp xếp
        ordering_map = {
            'price': 'price',
            'sales': '-sales',
            'create_time': '-create_time',
        }
        requested_order = request.GET.get('ordering', 'create_time')
        final_order = ordering_map.get(requested_order, '-create_time')

        # Truy vấn danh sách sản phẩm đã sắp xếp
        product_queryset = Goods.objects.filter(
            goodstype=current_category
        ).order_by(final_order)

        # Phân trang: 3 sản phẩm mỗi trang
        paginator = Paginator(product_queryset, 3)
        total_pages = paginator.num_pages

        # Đảm bảo số trang yêu cầu nằm trong giới hạn hợp lệ
        if page_number < 1:
            page_number = 1
        elif page_number > total_pages:
            page_number = total_pages

        current_page = paginator.page(page_number)

        # Tạo dải số trang hiển thị (tối đa 5 trang liên tiếp)
        if total_pages <= 5:
            page_range = list(range(1, total_pages + 1))
        else:
            start = max(1, page_number - 2)
            end = min(total_pages, page_number + 2)
            page_range = list(range(start, end + 1))

        context = {
            'all_categories': all_categories,
            'current_category': current_category,
            'latest_items': latest_items,
            'ordering': requested_order,
            'page_obj': current_page,
            'page_range': page_range,
        }
        return render(request, self.template_name, context)

Hiển thị tùy chọn sắp xếp trong mẫu HTML

Thanh điều khiển sắp xếp sử dụng các liên kết động, giữ nguyên tham số ordering khi chuyển trang:

<div class="sorting-controls">
  <a href="{% url 'goods:product_list' current_category.id 1 %}" 
     {% if ordering == 'create_time' %}class="active"{% endif %}>Mặc định</a>
  <a href="{% url 'goods:product_list' current_category.id 1 %}?ordering=price" 
     {% if ordering == 'price' %}class="active"{% endif %}>Giá</a>
  <a href="{% url 'goods:product_list' current_category.id 1 %}?ordering=sales" 
     {% if ordering == 'sales' %}class="active"{% endif %}>Bán chạy</a>
</div>

Cơ chế phân trang trong template

Sử dụng các thuộc tính và phương thức của đối tượng Page để xây dựng điều hướng trang thông minh:

<nav class="pagination-container">
  <div class="pagination">
    {% if page_obj.has_previous %}
      <a href="{% url 'goods:product_list' current_category.id page_obj.previous_page_number %}?ordering={{ ordering }}" 
         class="prev-link">← Trang trước</a>
    {% endif %}

    {% for num in page_range %}
      <a href="{% url 'goods:product_list' current_category.id num %}?ordering={{ ordering }}"
         {% if num == page_obj.number %}class="active"{% endif %}>{{ num }}</a>
    {% endfor %}

    {% if page_obj.has_next %}
      <a href="{% url 'goods:product_list' current_category.id page_obj.next_page_number %}?ordering={{ ordering }}"
         class="next-link">Trang sau →</a>
    {% endif %}
  </div>
</nav>

Ghi chú về đối tượng phân trang

  • Paginator nhận một QuerySet và số bản ghi mỗi trang, trả về đối tượng có thuộc tính num_pages, count, và phương thức page().
  • Page là kết quả từ paginator.page(n), cung cấp object_list, number, has_previous(), has_next(), previous_page_number(), next_page_number() — tất cả đều an toàn với ngoại lệ InvalidPage.

Thẻ: Django python web-development

Đăng vào ngày 16 tháng 6 lúc 20:05