Django集成django-taggit实现全文搜索遇重复结果及排序问题
我的应用——基础情况
我有一个简单的Django应用,用来存储特定物品的信息,目前正在实现搜索视图/功能。
我使用django-taggit根据物品的功能/特性给它们添加标签。
我想要实现的功能
我希望做一个全文搜索功能,能搜索物品的所有字段,包括标签在内。
遇到的问题
- 在结果视图中,带标签的物品会重复显示(每个标签对应一条记录)
- 当搜索框只填单个标签时,排序结果正常,但填多个标签名称时,排序结果不符合预期。
我怀疑SearchVector()没有按我预期的方式解析tags关联。这种情况下,标签应该被当成一个单词列表来处理。
示例代码
models.py
from django.db import models from taggit.managers import TaggableManager class Item(models.Model): identifier = models.SlugField('ID', unique=True, editable=False) short_text = models.CharField('Short Text', max_length=100, blank=True) serial_number = models.CharField('Serial Number', max_length=30, blank=True) revision = models.CharField('Revision/Version', max_length=30, blank=True) part_number = models.CharField('Part Number', max_length=30, blank=True) manufacturer = models.CharField('Manufacturer', max_length=30, blank=True) description = models.TextField('Description', blank=True) tags = TaggableManager('Tags', blank=True) is_active = models.BooleanField('Active', default=True)
forms.py
from django import forms class SearchForm(forms.Form): search = forms.CharField(max_length=200, required=False) active_only = forms.BooleanField(initial=True, label='只显示活跃物品', required=False)
views.py
from django.views.generic.list import ListView from django.contrib.postgres.search import SearchQuery, SearchVector, SearchRank from . import models from . import forms class ItemListView(ListView): form_class = forms.SearchForm model = models.Item fields = ['serial_number', 'part_number', 'manufacturer', 'tags', 'is_active'] template_name_suffix = '_list' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['form'] = self.form_class(self.request.GET) return context def get_queryset(self): queryset = super().get_queryset() form = self.form_class(self.request.GET) if form.is_valid(): if form.cleaned_data['active_only']: queryset = queryset.filter(is_active=True) if not form.cleaned_data['search']: return super().get_queryset() search_vector = SearchVector('identifier', 'short_text', 'serial_number', 'revision', 'part_number', 'manufacturer', 'description', 'tags') search_query = SearchQuery(form.cleaned_data['search'], search_type='websearch') return ( queryset.annotate( search=search_vector, rank=SearchRank(search_vector, search_query) ) # .filter(search=search_query) .order_by("-rank").distinct() ) #.filter(search__icontains=form.cleaned_data['search'],) return super().get_queryset()
问题解决方法
1. 修复物品重复显示问题
直接将taggit的tags字段加入SearchVector会触发关联查询,每个标签对应一条Item记录,即使加了.distinct()也可能因rank值不同无法去重。正确做法是把物品的所有标签聚合为单个字符串,再加入搜索向量:
首先在views.py中导入StringAgg:
from django.db.models import StringAgg
然后修改get_queryset方法,先聚合标签再构建搜索向量:
# 聚合标签为空格分隔的字符串,避免重复标签 queryset = queryset.annotate( tag_string=StringAgg('tags__name', delimiter=' ', distinct=True) )
2. 修复多标签排序异常问题
用聚合后的tag_string替代原tags字段加入搜索向量,所有标签会被视为一个文本块,配合websearch类型的查询可以正确处理多关键词匹配,排序逻辑会更符合预期。
修改后的完整get_queryset方法
def get_queryset(self): queryset = super().get_queryset() form = self.form_class(self.request.GET) if form.is_valid(): if form.cleaned_data['active_only']: queryset = queryset.filter(is_active=True) search_term = form.cleaned_data['search'] if not search_term: return queryset # 聚合标签为单一字符串 queryset = queryset.annotate( tag_string=StringAgg('tags__name', delimiter=' ', distinct=True) ) # 构建包含标签聚合字符串的搜索向量 search_vector = SearchVector( 'identifier', 'short_text', 'serial_number', 'revision', 'part_number', 'manufacturer', 'description', 'tag_string' ) search_query = SearchQuery(search_term, search_type='websearch') return ( queryset.annotate( search=search_vector, rank=SearchRank(search_vector, search_query) ) .filter(search=search_query) .order_by("-rank") ) return queryset
额外优化
- 聚合标签时加
distinct=True,避免重复标签影响搜索结果 - 无搜索词时返回已过滤
active_only的queryset,确保过滤条件始终生效
内容的提问来源于stack exchange,提问作者slarag
相关产品推荐
相关产品推荐

