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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 06:35:16