Django 从 0 到 1 打造完整电商平台:商品搜索

IT策士 10余年一线大厂经验,专注 IT 思维、架构、职场进阶。我也会其他平台持续发布最新文章,助你少走弯路。


上一篇我们让商品详情页的图片展示达到了流畅的电商体验,多图切换、规格联动都已就位。但一个真正的商城,光有浏览还不够------用户得能"搜"。今天我们就来实现 商品搜索功能,让用户输入关键词,快速定位心仪的商品。

我们不会引入 Elasticsearch 这样的重型搜索引擎,而是基于 Django ORM 的 icontainsQ 对象,完成一个简单高效、足够中小型电商使用的搜索方案。配合已有的商品列表与分页,搜索体验浑然一体。


一、需求分析

搜索功能的设计目标:

  1. 全局搜索入口:在顶部导航栏放置一个搜索框,用户输入关键词后提交。

  2. 搜索结果页:展示匹配的 SKU 列表(带分页),关键词高亮可选(我们先用简单方式,后续可美化)。

  3. 搜索逻辑

    • 匹配 SKU 名称(name

    • 匹配所属 SPU 名称(spu__name

    • 匹配品牌(spu__brand

    • 匹配商品描述(spu__desc

  4. 搜索与分类可组合:即用户可以先选分类,再搜关键词,或在搜索结果的上下文中切换分类。

  5. 保留搜索关键词:在搜索框显示已输入的关键词,便于用户调整。

我们将改造已有的 sku_list 视图,增加 q 参数处理,融入搜索逻辑。


二、改造商品列表视图,支持搜索

打开 apps/products/views.py,找到 sku_list 函数。之前它只处理了分类筛选和分页,现在我们增加关键词搜索。

bash 复制代码
from django.db.models import Q

def sku_list(request):
    # 基本查询:只取上架商品
    skus = SKU.objects.filter(is_active=True).select_related('spu__category').prefetch_related('images')

    # 1. 搜索过滤
    query = request.GET.get('q', '').strip()
    if query:
        skus = skus.filter(
            Q(name__icontains=query) |
            Q(spu__name__icontains=query) |
            Q(spu__brand__icontains=query) |
            Q(spu__desc__icontains=query)
        )

    # 2. 分类过滤(保留原有逻辑)
    category_id = request.GET.get('category_id')
    current_category = None
    if category_id:
        try:
            category = Category.objects.get(pk=category_id, is_active=True)
            # 含子分类
            category_ids = [category.id]
            children = Category.objects.filter(parent=category, is_active=True)
            category_ids.extend(children.values_list('id', flat=True))
            skus = skus.filter(spu__category_id__in=category_ids)
            current_category = category
        except Category.DoesNotExist:
            skus = skus.none()

    # 排序(暂时按创建时间倒序,第15篇会扩展)
    skus = skus.order_by('-create_time')

    # 3. 分页
    from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
    paginator = Paginator(skus, 12)
    page_number = request.GET.get('page', 1)
    try:
        page_obj = paginator.page(page_number)
    except PageNotAnInteger:
        page_obj = paginator.page(1)
    except EmptyPage:
        page_obj = paginator.page(paginator.num_pages)

    # 4. 顶级分类(供分类导航)
    top_categories = Category.objects.filter(parent__isnull=True, is_active=True).order_by('sort')

    context = {
        'page_obj': page_obj,
        'current_category': current_category,
        'top_categories': top_categories,
        'query': query,  # 传递搜索关键词回模板
    }
    return render(request, 'products/sku_list.html', context)

改动要点:

  • request.GET 中获取 q 参数。

  • 使用 Q 对象组合多个字段的 icontains(不区分大小写的包含)条件。

  • query 变量返回给模板,用于在搜索框中回显和"搜索结果"提示。

  • 搜索与分类筛选可同时生效,例如 ?q=iPhone&category_id=1 将搜索电子产品分类下包含"iPhone"的商品。


三、改造商品列表模板,加入搜索框和结果提示

编辑 apps/products/templates/products/sku_list.html,在分类导航条上方加入搜索表单,并根据 query 变量显示搜索状态。

{% block content %} 的开头部分进行修改:

bash 复制代码
{% block content %}
<!-- 搜索栏 -->
<div class="row mb-3">
    <div class="col-md-6 mx-auto">
        <form method="get" action="{% url 'products:sku_list' %}" class="input-group">
            <input type="text" name="q" class="form-control" placeholder="搜索商品名称、品牌..."
                   value="{{ query }}" aria-label="搜索">
            {% if current_category %}
                <input type="hidden" name="category_id" value="{{ current_category.id }}">
            {% endif %}
            <button class="btn btn-primary" type="submit">🔍 搜索</button>
        </form>
    </div>
</div>

<div class="d-flex justify-content-between align-items-center mb-4">
    <h3>
        {% if query %}
            🔎 搜索 "{{ query }}"
        {% elif current_category %}
            📦 {{ current_category.name }}
        {% else %}
            🛍️ 全部商品
        {% endif %}
    </h3>
    <span class="text-muted">共 {{ page_obj.paginator.count }} 件商品</span>
</div>

<!-- 分类导航(保留原有) -->
...

关键点:

  • 搜索框的 value="{``{ query }}" 让用户在提交后仍能看到自己输入的关键词。

  • 当存在分类筛选时,通过隐藏字段 <input type="hidden" name="category_id"> 携带分类 ID,保证搜索在分类范围内。

  • 标题根据搜索状态动态变化:搜索时显示 "🔎 搜索 "XXX"",分类时显示分类名,否则显示全部商品。

分页导航部分也需更新,确保页码链接同时携带 qcategory_id 参数。可以在分页链接的 href 中加入:

bash 复制代码
?page={{ num }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category_id={{ current_category.id }}{% endif %}

所有出现分页链接的地方统一修改,保持参数完整。例如:

bash 复制代码
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if query %}&q={{ query }}{% endif %}{% if current_category %}&category_id={{ current_category.id }}{% endif %}"><< 上一页</a>

完整代码这里不再全贴,你可以在本地按要求修改,或照之前文章的分页部分对应添加参数。


四、在导航栏添加快速搜索入口(可选)

为了用户能在任何页面发起搜索,可以在 templates/base.html 的导航栏中加入一个小搜索框,但注意不要与列表页的搜索框重复。简单起见,我们仅在商品列表页提供搜索,导航栏保留菜单。如果你希望全局搜索,可以在导航栏加入一个表单,提交到商品列表页。例如在导航栏的 ul 前插入:

bash 复制代码
<form class="d-flex me-3" action="{% url 'products:sku_list' %}" method="get">
    <input class="form-control form-control-sm me-2" type="text" name="q" placeholder="搜索商品" aria-label="搜索">
    <button class="btn btn-sm btn-outline-light" type="submit">🔍</button>
</form>

但这样会导致在非商品页面也能搜索,效果也 OK。这里作为选做,读者可根据需要自行添加。


五、测试流程与输出

启动服务器:

bash 复制代码
python manage.py runserver
5.1 基本搜索
  1. 访问 http://127.0.0.1:8000/products/list/

  2. 在搜索框中输入 iPhone,点击搜索。

  3. 页面刷新,标题变为"🔎 搜索 "iPhone"",共找到 N 件商品(应该是 iPhone 15 的两个 SKU)。

  4. 列表中的商品卡片只显示匹配结果,分页正常。

终端输出:

bash 复制代码
[24/May/2026 14:15:30] "GET /products/list/?q=iPhone HTTP/1.1" 200 8765
5.2 搜索品牌

搜索 Samsung,将匹配到 SPU 品牌为 Samsung 的 SKU。

bash 复制代码
[24/May/2026 14:16:10] "GET /products/list/?q=Samsung HTTP/1.1" 200 7890
5.3 搜索描述中的词汇

如果 SPU 描述中有"智能手机",搜索 智能手机 也能命中。

5.4 组合搜索与分类

先点击分类"手机",再在搜索框输入 128GB,URL 变为 /products/list/?q=128GB&category_id=2(假设手机分类ID=2)。列表将只显示手机分类下名字包含"128GB"的 SKU。

终端输出:

bash 复制代码
[24/May/2026 14:18:45] "GET /products/list/?q=128GB&category_id=2 HTTP/1.1" 200 6543
5.5 无搜索结果

搜索 Xiaomi,如果没有该商品,页面显示"共 0 件商品",并列出空提示(我们在模板中已有 {% empty %} 的处理)。

页面显示:

  • 标题为 "🔎 搜索 "Xiaomi""

  • 数量为 0

  • 内容区域显示"该分类下暂无商品。"(可稍微调整提示,区分是搜索无结果还是分类无商品,这里作为优化点,可以在模板中用 {% if query %} 判断并给不同提示,如"未找到与 "Xiaomi" 相关的商品"。)

终端输出:

bash 复制代码
[24/May/2026 14:20:00] "GET /products/list/?q=Xiaomi HTTP/1.1" 200 5432

六、关于中文搜索的注意事项

SQLite 默认的 icontains 对中文支持没问题,但它是全表扫描,性能会随着数据量下降。对于初期或小规模电商,完全够用。如果你使用的是 MySQL,icontains 会被转换为 LIKE '%keyword%',同样全表扫描。后续我们会在第 26 篇(数据库查询优化与索引)中介绍如何为搜索字段创建索引,以及可选的全文本搜索方案(如 PostgreSQL 的 SearchVector 或 Elasticsearch 的集成)。目前以快速实现业务功能为主。


七、总结与下集预告

今天我们在商品列表模块中无缝融入了搜索功能:

  • 基于 Django ORM 的 icontainsQ 对象,实现了多字段模糊匹配;

  • 搜索与分类筛选可叠加使用,参数在分页链接中完整传递;

  • 前端搜索框友好回显关键词,页面标题动态切换。

现在,用户可以按分类浏览、按关键词搜索,还能组合使用,基本满足商品发现的需求。但默认的排序是按创建时间倒序,比较单一。下一篇我们就来解决这个问题------第 15 篇 将为大家带来 商品排序与浏览量统计,支持按价格、销量、上新时间排序,并在详情页记录每次浏览,为后续热销榜单做准备。

想了解更多还可以其它平台搜索「IT策士」,一起升级 IT 思维 !


本文为《Django 从 0 到 1 打造完整电商平台》系列第 14 篇。

相关推荐
zhangxingchao5 小时前
AI 大模型面试核心三: RAG、Agent 到 Prompt Engineering 的工程化理解
前端·人工智能·后端
彦为君5 小时前
JavaSE-11-ByteBuffer(NIO核心组件)
java·开发语言·前端·数据库·后端·spring·nio
茉莉玫瑰花茶5 小时前
LangGraph 持久化(Persistence)[ 2 ]
开发语言·python·ai·langgraph
刀法如飞5 小时前
《理解道德经》简单版-第 1 章:道可道,非常道
前端·后端·面试
有味道的男人5 小时前
AI 对接 1688 图搜接口|Open Claw 以图搜货实战
开发语言·python
MediaTea5 小时前
DL:Transformer 的基本原理与 PyTorch 实现
人工智能·pytorch·python·深度学习·transformer
wuxinyan1235 小时前
工业级大模型学习之路024:LangChain零基础入门教程(第七篇):RAG 系统评估、全链路调优
人工智能·python·学习·langchain
Kingairy5 小时前
Python简单算法题
开发语言·python
浩风祭月5 小时前
我用 Cursor 把一个订单状态机从 800 行重构到 120 行,且逻辑零差错
前端·后端