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">« 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 »</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 %}">« 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 »</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
相关产品推荐
相关产品推荐

