下载工作台
Django Web 开发

分页、搜索与 Admin 定制

试读上半部分 · 解锁后可读全文

第 6 章 · 分页、搜索与 Admin 定制

本章目标:使用 PaginatorListView.paginate_by 实现商品列表分页;用 Q 对象构建关键词与分类组合搜索;深度定制 Django Adminlist_filtersearch_fieldsinlinesactions);完成 shop-demo 后台「可运营」的商品管理界面。

学时建议:5~6 小时(含 2 小时跟练)

前置:完成 django-web ch01~ch05(CBV 列表、ORM 关系、Tag 模型)。


6.1 场景说明:商品多了怎么办?

shop-demo 商品数量增长到数百条后:

痛点解决方案本章技术
列表一次加载太慢每页 20 条Paginator / paginate_by
运营要找某个 SKU搜索框Q + icontains
批量下架临期商品勾选 + 动作Admin actions
编辑商品时顺便改标签同一页内嵌表单TabularInline
Admin 是 Django 自带的运营后台,与面向公众的页面互补。API 网关域名 api.example.com 在 ch07 通过 DRF 暴露,本章聚焦服务端渲染后台。


6.2 ListView 内置分页(推荐)

class ProductListView(LoginRequiredMixin, ListView):
    model = Product
    template_name = "catalog/product_list.html"
    context_object_name = "products"
    paginate_by = 20

    def get_queryset(self):
        return (
            Product.objects.filter(is_active=True)
            .select_related("category")
            .prefetch_related("tags")
            .order_by("-created_at")
        )

ListView 自动注入模板变量:page_objpaginatoris_paginated


6.3 分页模板

catalog/templates/catalog/product_list.html 底部追加:

{% if is_paginated %}
<nav aria-label="分页">
  <ul class="pagination">
    {% if page_obj.has_previous %}
      <li><a href="?page=1{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">首页</a></li>
      <li><a href="?page={{ page_obj.previous_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">上一页</a></li>
    {% endif %}

    <li>第 {{ page_obj.number }} / {{ page_obj.paginator.num_pages }} 页(共 {{ paginator.count }} 条)</li>

    {% if page_obj.has_next %}
      <li><a href="?page={{ page_obj.next_page_number }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">下一页</a></li>
      <li><a href="?page={{ paginator.num_pages }}{% if request.GET.q %}&q={{ request.GET.q }}{% endif %}">末页</a></li>
    {% endif %}
  </ul>
</nav>
{% endif %}
分页链接需保留搜索参数 q,否则翻页后搜索条件丢失。

6.4 Q 对象与搜索

6.4.1 Q 语法

from django.db.models import Q

Q(name__icontains="鼠标") | Q(sku__icontains="鼠标")   # OR
Q(is_active=True) & Q(stock__gt=0)                    # AND
~Q(category__slug="digital")                          # NOT
查找类型字段后缀说明
忽略大小写包含icontains搜索框最常用
精确匹配iexactSKU 全匹配
开头匹配istartswith自动补全
范围gte / lte价格区间

6.4.2 在 ListView 中实现搜索

class ProductListView(LoginRequiredMixin, ListView):
    model = Product
    template_name = "catalog/product_list.html"
    context_object_name = "products"
    paginate_by = 20

    def get_queryset(self):
        qs = (
            Product.objects.select_related("category")
            .prefetch_related("tags")
            .order_by("-created_at")
        )

        q = self.request.GET.get("q", "").strip()
        category_id = self.request.GET.get("category", "").strip()

        if q:
            qs = qs.filter(
                Q(name__icontains=q)
                | Q(sku__icontains=q)
                | Q(tags__name__icontains=q)
            ).distinct()

        if category_id.isdigit():
            qs = qs.filter(category_id=int(category_id))

        status = self.request.GET.get("status", "")
        if status == "active":
            qs = qs.filter(is_active=True)
        elif status == "inactive":
            qs = qs.filter(is_active=False)

        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["search_q"] = self.request.GET.get("q", "")
        ctx["categories"] = Category.objects.all()
        ctx["selected_category"] = self.request.GET.get("category", "")
        return ctx

6.4.3 搜索表单模板

列表页顶部:

<form method="get" action="">
  <input type="text" name="q" value="{{ search_q }}" placeholder="名称 / SKU / 标签">
  <select name="category">
    <option value="">全部分类</option>
    {% for c in categories %}
      <option value="{{ c.pk }}" {% if selected_category == c.pk|stringformat:"s" %}selected{% endif %}>
        {{ c.name }}
      </option>
    {% endfor %}
  </select>
  <select name="status">
    <option value="">全部状态</option>
    <option value="active" {% if request.GET.status == "active" %}selected{% endif %}>已上架</option>
    <option value="inactive" {% if request.GET.status == "inactive" %}selected{% endif %}>已下架</option>
  </select>
  <button type="submit">搜索</button>
  <a href="{% url 'product_list' %}">重置</a>
</form>

使用 GET 而非 POST:搜索条件体现在 URL,可收藏、可分享。


6.5 Admin 列表定制

catalog/admin.py 完整示例:

from django.contrib import admin
from django.db.models import Count

from .models import Category, Product, Tag

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。