You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django葡萄酒网站关键词筛选功能实现及前后端方案咨询

葡萄酒网站关键词筛选功能实现指导及JS结合方案咨询

一、现有代码情况

1. Wine模型

class Wine(models.Model):
    name = models.CharField(max_length=100)
    type = models.CharField(max_length=50)
    description = models.TextField()
    image = models.ImageField(upload_to='images/wines/')
    keywords = models.CharField(max_length=255, blank=True)

    def __str__(self):
        return self.name

2. 页面HTML代码

<div class="dropdown">
    <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-bs-toggle="dropdown" aria-expanded="false">
        Filter
    </button>
    <ul class="dropdown-menu" aria-labelledby="dropdownMenuButton" id="filterOptions">
        <li><input class="form-check-input" type="checkbox" id="redCheckbox" value="red"><label class="form-check-label" for="redCheckbox">Red</label></li>
        <li><input class="form-check-input" type="checkbox" id="sparklingCheckbox" value="sparkling"><label class="form-check-label" for="sparklingCheckbox">Sparkling</label></li>
        <li><input class="form-check-input" type="checkbox" id="whiteCheckbox" value="white"><label class="form-check-label" for="whiteCheckbox">White</label></li>
        <li><input class="form-check-input" type="checkbox" id="sweetCheckbox" value="sweet"><label class="form-check-label" for="sweetCheckbox">Sweet</label></li>
        <li><input class="form-check-input" type="checkbox" id="dryCheckbox" value="dry"><label class="form-check-label" for="dryCheckbox">Dry</label></li>
        <li><input class="form-check-input" type="checkbox" id="citrusCheckbox" value="citrus"><label class="form-check-label" for="citrusCheckbox">Citrus</label></li>
    </ul>
</div>
<section class="products section-padding">
    <div class="container">
        <div class="row">
            {% for wine in page_obj %}
            <div class="col-lg-4 col-12 mb-3">
                <div class="product-thumb">
                    <a href="product-detail.html">
                        <img src="{{ MEDIA_URL }}{{ wine.image.url }}" class="img-fluid product-image" alt="">
                    </a>

                    <div class="product-info d-flex">
                        <div>
                            <h5 class="product-title mb-0">
                                <a href="product-detail.html" class="product-title-link">{{ wine.name }}</a>
                            </h5>

                        <p class="product-p">{{ wine.description }}</p>
                    </div>
                </div>
            </div>
        </div>
        {% endfor %}
    </div>
</div>
</section>

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}">previous</a>
        {% endif %}

        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>

3. 当前视图代码

def wines_all(request):
    all_wines = Wine.objects.all()

    selected_keywords = request.GET.getlist('keywords')

    # filter wines based on selected keywords
    filtered_wines = all_wines
    for keyword in selected_keywords:
        filtered_wines = filtered_wines.filter(keywords__icontains=keyword)

    paginator = Paginator(filtered_wines, 9)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'test.html', {'page_obj': page_obj, 'selected_keywords': selected_keywords})

二、需求说明

  • 初始展示所有葡萄酒
  • 勾选复选框后,仅显示同时包含所有选中关键词的葡萄酒
  • 目前keywords字段为逗号分隔的字符串(如"red, plum, citrus"),可接受字段方案调整

三、实现指导

1. 字段优化建议(可选但推荐)

当前用CharField存储逗号分隔关键词的方案,查询效率低且容易出现匹配误差,推荐改成多对多关联:

class Keyword(models.Model):
    name = models.CharField(max_length=50, unique=True)

    def __str__(self):
        return self.name

class Wine(models.Model):
    name = models.CharField(max_length=100)
    type = models.CharField(max_length=50)
    description = models.TextField()
    image = models.ImageField(upload_to='images/wines/')
    keywords = models.ManyToManyField(Keyword, blank=True)

    def __str__(self):
        return self.name

该方案便于关键词统一管理,查询精度和效率更高,还能避免拼写错误。

2. 修复当前视图+HTML的核心问题

(1)HTML复选框添加name属性

当前复选框没有设置name="keywords",导致视图无法获取选中值,需要给每个复选框添加该属性:

<li><input class="form-check-input" type="checkbox" name="keywords" id="redCheckbox" value="red"><label class="form-check-label" for="redCheckbox">Red</label></li>
<li><input class="form-check-input" type="checkbox" name="keywords" id="sparklingCheckbox" value="sparkling"><label class="form-check-label" for="sparklingCheckbox">Sparkling</label></li>
<!-- 其余复选框同理添加name="keywords" -->

(2)优化原字段的查询逻辑

如果保留原CharField方案,icontains可能匹配到类似"reddish"的无关词,建议用正则实现精确的独立单词匹配:

import re

def wines_all(request):
    all_wines = Wine.objects.all()
    selected_keywords = request.GET.getlist('keywords')

    filtered_wines = all_wines
    for keyword in selected_keywords:
        # 匹配逗号分隔的独立关键词,覆盖开头、中间、结尾三种位置
        filtered_wines = filtered_wines.filter(
            keywords__regex=r'(?:^|,\s*)' + re.escape(keyword) + r'(?:,\s*|$)'
        )

    paginator = Paginator(filtered_wines, 9)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'test.html', {'page_obj': page_obj, 'selected_keywords': selected_keywords})

如果用优化后的多对多字段,查询逻辑可改为:

def wines_all(request):
    all_wines = Wine.objects.all()
    selected_keywords = request.GET.getlist('keywords')

    filtered_wines = all_wines
    if selected_keywords:
        # 确保葡萄酒包含所有选中的关键词
        for keyword in selected_keywords:
            filtered_wines = filtered_wines.filter(keywords__name=keyword)
        filtered_wines = filtered_wines.distinct()

    paginator = Paginator(filtered_wines, 9)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'test.html', {'page_obj': page_obj, 'selected_keywords': selected_keywords})

(3)保持复选框选中状态

利用视图传递的selected_keywords变量,给已选中的复选框添加checked属性:

<li><input class="form-check-input" type="checkbox" name="keywords" id="redCheckbox" value="red" {% if 'red' in selected_keywords %}checked{% endif %}><label class="form-check-label" for="redCheckbox">Red</label></li>

其余复选框同理处理。

(4)分页链接携带筛选参数

当前分页切换会丢失筛选状态,需要修改分页链接,带上已选中的关键词:

{% if page_obj.has_previous %}
    <a href="?page=1{% for kw in selected_keywords %}&keywords={{ kw }}{% endfor %}">&laquo; first</a>
    <a href="?page={{ page_obj.previous_page_number }}{% for kw in selected_keywords %}&keywords={{ kw }}{% endfor %}">previous</a>
{% endif %}

{% if page_obj.has_next %}
    <a href="?page={{ page_obj.next_page_number }}{% for kw in selected_keywords %}&keywords={{ kw }}{% endfor %}">next</a>
    <a href="?page={{ page_obj.paginator.num_pages }}{% for kw in selected_keywords %}&keywords={{ kw }}{% endfor %}">last &raquo;</a>
{% endif %}

四、结合JavaScript与Django实现筛选的方案

完全可以结合JS实现,以下是两种常用方案:

1. AJAX无刷新筛选(推荐)

通过AJAX发送筛选请求,视图返回筛选后的内容,前端替换页面片段,避免整页刷新:

document.addEventListener('DOMContentLoaded', function() {
    const checkboxes = document.querySelectorAll('input[name="keywords"]');
    const wineRow = document.querySelector('.products .row');
    const pagination = document.querySelector('.pagination');

    checkboxes.forEach(box => {
        box.addEventListener('change', function() {
            const selected = Array.from(checkboxes)
                .filter(b => b.checked)
                .map(b => b.value);
            
            const url = new URL(window.location.href);
            url.searchParams.set('page', 1);
            url.searchParams.delete('keywords');
            selected.forEach(kw => url.searchParams.append('keywords', kw));

            fetch(url)
                .then(response => response.text())
                .then(data => {
                    const tempDiv = document.createElement('div');
                    tempDiv.innerHTML = data;
                    wineRow.innerHTML = tempDiv.querySelector('.products .row').innerHTML;
                    pagination.innerHTML = tempDiv.querySelector('.pagination').innerHTML;
                });
        });
    });
});

视图无需修改,保持原有逻辑即可。

2. 纯前端筛选(适合小数据量)

页面加载时将所有葡萄酒数据存入JS变量,前端直接过滤渲染,无后端请求:

<script>
// 初始化所有葡萄酒数据
const allWines = [
    {% for wine in page_obj.paginator.object_list %}
    {
        name: "{{ wine.name|escapejs }}",
        description: "{{ wine.description|escapejs }}",
        image: "{{ MEDIA_URL }}{{ wine.image.url }}",
        keywords: "{{ wine.keywords }}".split(', ')
    },
    {% endfor %}
];

// 渲染葡萄酒列表
function renderWines(filteredWines) {
    const row = document.querySelector('.products .row');
    row.innerHTML = '';
    filteredWines.forEach(wine => {
        const wineHtml = `
            <div class="col-lg-4 col-12 mb-3">
                <div class="product-thumb">
                    <a href="product-detail.html">
                        <img src="${wine.image}" class="img-fluid product-image" alt="">
                    </a>
                    <div class="product-info d-flex">
                        <div>
                            <h5 class="product-title mb-0">
                                <a href="product-detail.html" class="product-title-link">${wine.name}</a>
                            </h5>
                            <p class="product-p">${wine.description}</p>
                        </div>
                    </div>
                </div>
            </div>
        `;
        row.insertAdjacentHTML('beforeend', wineHtml);
    });
}

// 监听复选框变化
document.querySelectorAll('input[name="keywords"]').forEach(box => {
    box.addEventListener('change', function() {
        const selected = Array.from(document.querySelectorAll('input[name="keywords"]:checked'))
            .map(b => b.value);
        
        let filtered = allWines;
        if (selected.length > 0) {
            filtered = allWines.filter(wine => {
                // 确保葡萄酒包含所有选中关键词
                return selected.every(kw => wine.keywords.includes(kw));
            });
        }
        renderWines(filtered);
    });
});
</script>

该方案适合数据量较小的场景,数据量大时推荐AJAX方案。

内容的提问来源于stack exchange,提问作者Ben

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 20:25:59