django-haystack使用以及多模型搜索

2020年09月13日 未雨晴空 0评论 1570阅读 0喜欢

什么是django-haystack?

官网的介绍如下
Haystack provides modular search for Django,It features a unified, familiar API that allows you to plug in different search backends (such as Solr, Elasticsearch, Whoosh, Xapian, etc.) without having to modify your code.
翻译过来就是
haystack提供了模块化的Django搜索框架,它具有一个统一的,熟悉的API可让您在不同的搜索后端(如Solr,Elasticsearch,Whoosh,Xapian等等)插件,而无需修改代码。

安装依赖库以及更换分词器

pip install whoosh django-haystack jieba
依次用到的是whoosh搜索引擎,django-haystack搜索框架,jieba分词之所以需要jieba库是因为Whoosh自带的是英文分词,对中文的分词支持不是太好。具体修改方式为拷贝/haystack/backends/whoosh_backend.py放到/blog/blog/目录下,更改文件名为whoosh_cn_backend.py需要修改的部分为

...
from jieba.analyse import ChineseAnalyzer
...
else:
    schema_fields[field_class.index_fieldname] = TEXT(stored=True, analyzer=ChineseAnalyzer(), field_boost=field_class.boost, sortable=True)

在settings中添加Haystack到Django的INSTALLED_APPS

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'haystack',
    'article',
    'video',
]

文章应用模型搜索

建立模型

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=200,verbose_name="文章标题")
    summary=models.CharField(max_length=500,verbose_name="文章摘要")
    content = models.TextField(verbose_name="文章内容")
    views = models.IntegerField(verbose_name='浏览数', default=0)

    def __str__(self):
        return self.title

创建文章内容检索模板

创建之前先看下haystack的源码

def prepare_template(self, obj):
    """
    Flattens an object for indexing.

    This loads a template
    (``search/indexes/{app_label}/{model_name}_{field_name}.txt``) and
    returns the result of rendering that template. ``object`` will be in
    its context.
    """
    if self.instance_name is None and self.template_name is None:
        raise SearchFieldError("This field requires either its instance_name variable to be populated or an explicit template_name in order to load the correct template.")

    if self.template_name is not None:
        template_names = self.template_name

        if not isinstance(template_names, (list, tuple)):
            template_names = [template_names]
    else:
        app_label, model_name = get_model_ct_tuple(obj)
        template_names = ['search/indexes/%s/%s_%s.txt' % (app_label, model_name, self.instance_name)]

    t = loader.select_template(template_names)
    return t.render({'object': obj})

从上述源码可以看出索引模板文件的路径格式是项目的模板目录下search/indexes/{应用名}/{模型名}_{变量名}.txt,其中变量名默认就是索引字段也就是text索引字段,故文章的索引内容模板路径即是这样search/indexes/article/article_text.txt
同时根据源码也知道如果不按照约定的路径来,那么就必须要在在应用下的
search_indexes.py里面的模型索引例如如ArticleIndex的

text = indexes.CharField(document=True, use_template=True,template_name="search/indexes/article/article_text.txt")

指定索引模板路径,我个人推荐指定template_name,便于后面的多模型搜索。

  • 项目根目录下创建templates/search/indexes/article/article_text.txt

  • 文件内容如下:

    {{ object.title }}
    {{ object.summary }}
    {{ object.content }}
    

创建索引

  • 新建local_apps/article/search_indexes.py文件
    如果想对应用下的模型建立搜索,则需要在该应用下面建立search_indexes
    .py文件,且文件名不能修改。内容如下:

    from haystack import indexes
    from local_apps.article.models import Article
    class ArticleIndex(indexes.SearchIndex, indexes.Indexable):
      text = indexes.CharField(document=True, use_template=True,template_name="search/indexes/article/article_text.txt")
      views=indexes.IntegerField(model_attr='views')
      title=indexes.CharField(model_attr='title')
    
      def get_model(self):
          return Article
    
      def index_queryset(self, using='article_search'):
          return self.get_model().objects.all()
    

    每个索引里面必须有且只能有一个字段为document=True,这代表haystack
    和搜索引擎将使用此字段的内容作为索引进行检索。其他的字段并不作为检索数据

    但是如果自定义了SearchView,重写了一些逻辑例如过滤,排序时用到了views,
    则必须要加上views=indexes.IntegerField(model_attr=’views’)方便视图调用,不然会报错,如果没用到则可以不用加上。另外在index_queryset函数里的using默认为None,这里指定具体的搜索引擎,也是避免搜索视图里面忘记指定。

自定义文章搜索视图

from haystack.generic_views import SearchView
# 自定义文章搜索视图
class ArticleSearchView(SearchView):
    # 默认搜索表单字段名
    # search_field = 'q'
    # 返回搜索结果集名称
    context_object_name = 'articles'
    # 设置分页
    paginate_by = 10
    # 搜索结果指定搜索连接同时以浏览量倒序
    queryset = SearchQuerySet().using("article_search").order_by('-views')
    # 视图模板
    template_name = 'search/article_search_list.html'

如果不自定搜索表单,在没有覆盖默认的搜索字段名时,前台的搜索字段需要注意字段名默认为q,即如下
<input name="q" type="text" value="" class="form-control>

建立路由

path(r'article/search/$', ArticleSearchView(), name='haystack_search'),

视频应用模型搜索

建立模型

class Video(models.Model):
    name=models.CharField(verbose_name="视频名",max_length=200)
    cover=models.URLField(verbose_name="视频封面",default="")
    description=models.TextField(verbose_name="视频描述",default="")
    playurl=models.TextField(verbose_name="视频播放url",default="")
    viewnum=models.IntegerField(verbose_name="浏览数",default=0)

    class Meta:
        verbose_name="视频"
        verbose_name_plural=verbose_name

    def __str__(self):
        return self.name

创建视频内容检索模板

  • 项目根目录下创建templates/search/indexes/video/video_text.txt

  • 文件内容如下:

    {{ object.name }}
    {{ object.description }}
    

创建索引

from haystack import indexes
from local_apps.video.models import Video


class VideoIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.CharField(document=True, use_template=True,template_name="search/indexes/video/video_text.txt")
    viewnum=indexes.IntegerField(model_attr="viewnum")

    def get_model(self):
        return Video

    def index_queryset(self, using='video_search'):
        # 过滤出允许显示以及所属类别允许显示的视频
        return self.get_model().objects.all()

自定义文章搜索视图

from haystack.generic_views import SearchView

class VideoSearchView(SearchView):
    # 返回搜索结果集
    context_object_name = 'articles'
    # 设置分页
    paginate_by = 20
    # 搜索结果以浏览量排序
    queryset = SearchQuerySet().using("article_search").order_by('-views')

建立路由

path(r'video/search/', VideoSearchView.as_view(), name='video_search'),

配置多模型搜索

由于多模型搜索的特殊性,单模型搜索已经不满足默认的,同时还要支持当文章或者视频更新需要同步更新索引,需要增加一些配置在settings.py里面

HAYSTACK_CONNECTIONS = {
    'default': {
        # 选择语言解析器为自己更换的结巴分词
        'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine',
        # 保存索引文件的地址,选择主目录下,这个会自动生成
        'PATH': os.path.join(BASE_DIR,'default_whoosh_index'),
        'EXCLUDED_INDEXES': ['apps.content.search_indexes.VideoIndex',
                             'apps.article.search_indexes.ArticleIndex'],
    },
    'article_search': {
        # 选择语言解析器为自己更换的结巴分词
        'ENGINE': 'blog.whoosh_cn_backend.WhooshEngine',
        # 保存索引文件的地址,选择主目录下,这个会自动生成
        'PATH': os.path.join(BASE_DIR,'article_whoosh_index'),
        'STORAGE': 'file',
        'POST_LIMIT': 128 * 1024 * 1024,
        'INCLUDE_SPELLING': True,
        'BATCH_SIZE': 100,
        'EXCLUDED_INDEXES': ['apps.content.search_indexes.VideoIndex'],
    },
    'video_search': {
        'ENGINE': 'apps.website.whoosh_cn_backend.WhooshEngine',
        'PATH': os.path.join(BASE_DIR,'video_whoosh_index'),
        'STORAGE': 'file',
        'POST_LIMIT': 128 * 1024 * 1024,
        'INCLUDE_SPELLING': True,
        'BATCH_SIZE': 5000,
        'EXCLUDED_INDEXES': ['apps.article.search_indexes.ArticleIndex'],
    },
}
# 增加haystack连接路由
HAYSTACK_ROUTERS = ['haystack.routers.DefaultRouter','apps.article.routers.ArticleRouter','apps.video.routers.VideoRouter']
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'#默认按照最新时间更新索引

还需要在视频和文章应用的目录下新建routers.py内容分别为如下:

from haystack import routers


class ArticleRouter(routers.BaseRouter):
    def for_write(self, **hints):
        return 'article_search'

    def for_read(self, **hints):
        return 'article_search'
from haystack import routers


class VideoRouter(routers.BaseRouter):
    def for_write(self, **hints):
        return 'video_search'

    def for_read(self, **hints):
        return 'video_search'

重建索引

  • 重建全部
    python manage.py rebuild_index
    因为上面的haystack默认连接已经去除了文章和视频的索引类,故执行完上述命令后,default_whoosh_index文件夹下不会生成索引信息的

  • 只重建文章
    python manage.py rebuild_index -u article_search

  • 只重建视频
    python manage.py rebuild_index -u video_search

发表评论 取消回复

电子邮件地址不会被公开。

请输入以http或https开头的URL,格式如:https://oneisall.top