Django之Haystack对接Elasticsearch
- Haystack
- 概述
- 安装依赖
- 环境准备
- Haystack配置
- Haystack建立数据索引
- 创建模型对象
- 创建搜索索引类
- 创建模板文件
- 执行数据库迁移
- 生成索引
- 渲染模板
- 执行测试
- 搜索请求和结果渲染的自定义处理
- 概述
- 创建搜索视图
- 配置URL
- 创建搜索模板
- 自定义结果渲染
- 执行测试
- 其他
- 添加分页功能
- 自定义搜索查询
- 使用Facets
- 高级排序
- 使用拼音搜索或模糊搜索
Haystack
概述
Haystack是在Django中对接搜索引擎的框架,搭建用户和搜索引擎之间的沟通桥梁。它提供了一种简化的方式来集成不同的搜索引擎,如Elasticsearch、Whoosh、Solr等。
Elasticsearch的底层是开源库Lucene。Python是没法直接使用Lucene,必须写代码去调用它的接口。因此,在Django中可以通过使用Haystack对接 Elasticsearch服务端
Haystack的主要功能包括:
统一接口:Haystack为多个搜索引擎提供了统一的API,使得在不同的搜索引擎之间切换变得简单。
搜索索引:它允许开发者为Django模型创建搜索索引,从而能够对模型中的数据进行高效的搜索。
查询构建:Haystack提供了简洁的查询构建工具,支持复杂的搜索条件和过滤。
模板支持:可以通过模板轻松自定义搜索结果的展示。
灵活性:Haystack支持多种后端搜索引擎,开发者可以根据项目需求选择最合适的搜索解决方案。
文档:https://docs.haystacksearch.org/en/master/tutorial.html
安装依赖
首先,需要安装
django-haystack
和elasticsearch
库
在Django项目中安装Haystack库
pip install django-haystack
注意:使用elasticsearch时,使用如下命令安装
# pip install elasticsearch==7.5.1
pip install "django-haystack[elasticsearch]"
环境准备
创建应用模块product
python manage.py startapp product
在Django项目的settings.py
文件中,注册应用product,同时将haystack添加到INSTALLED_APPS
中
INSTALLED_APPS = [
'apps.product',
'haystack',
]
在项目路径下注册路由
urlpatterns = [
# path('admin/', admin.site.urls),
path('search/', include('haystack.urls')),
path('product/', include('product.urls')),
]
Haystack配置
在Django的settings.py
文件中配置Haystack和Elasticsearch
HAYSTACK_CONNECTIONS = {
'default': {
# 这里使用Elasticsearch7.x版本,如果使用其他版本,请相应地修改ENGINE的值
'ENGINE': 'haystack.backends.elasticsearch7_backend.Elasticsearch7SearchEngine',
# Elasticsearch服务器ip地址,端口号固定为9200
'URL': 'http://127.0.0.1:9200/',
# Elasticsearch建立的索引库的名称
'INDEX_NAME': 'demo',
},
}
# 当数据库改变时,会自动更新索引
# Haystack可以让Elasticsearch实时生成新数据的索引,即当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
# 控制每页显示数量
HAYSTACK_SEARCH_RESULTS_PER_PAGE = 20
Haystack建立数据索引
创建模型对象
在product子应用的models.py文件创建模型对象
from django.db import models
class Product(models.Model):
name = models.CharField(max_length=20, verbose_name='名称')
price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name='单价')
stock = models.IntegerField(default=0, verbose_name='库存')
is_existed = models.BooleanField(default=True, verbose_name='是否上架')
class Meta:
db_table = 'tb_product'
verbose_name = '产品'
verbose_name_plural = verbose_name
def __str__(self):
return '%s: %s' % (self.id, self.name)
创建搜索索引类
创建一个搜索索引类来定义要搜索的模型,指明让搜索引擎对哪些字段建立索引,即通过哪些字段的关键字来检索数据,通常在应用目录下创建一个
search_indexes.py
文件
在product子应用的目录下创建search_indexes.py
文件,在这个文件下写索引类
from haystack import indexes
from .models import Product
# ProductIndex索引数据模型类
class ProductIndex(indexes.SearchIndex, indexes.Indexable):
# text表示被查询的字段,用户搜索的是这些字段的值,具体被索引的字段写在另一个文件里。
text = indexes.CharField(document=True, use_template=True)
stock = indexes.IntegerField(model_attr='stock')
price = indexes.FloatField(model_attr='price')
def get_model(self):
# 返回建立索引的模型类
return Product
def index_queryset(self, using=None):
# 返回要建立索引的数据查询集
return self.get_model().objects.all()
注意:
-
类名必须是需要检索的
ModelName+Index
,如:Product模型类+Index,即ProductIndex
-
每个索引类必须有且只能有一个字段为
document=True
,代表haystack和搜索引擎将使用此字段的内容作为索引进行检索。其他的字段只是附属的属性,方便调用,并不作为检索数据。 -
如果使用一个字段设置了
document=True
,则一般约定此字段名为text
,表名该字段是主要进行关键字查询的字段,用于构造索引,只不过具体构造索引的值写在另一个文件内。 -
haystack提供了
use_template=True
在text字段,允许使用数据模板去建立搜索引擎索引的文件 -
stock 、price
用于以索引查询到的返回内容 -
get_model
方法用于指明建立索引的对应模型 -
index_queryset
方法用于返回建立索引的数据查询集
创建模板文件
在
templates
目录下创建search/indexes/yourapp/yourmodel_text.txt
字段索引值模板文件,在该文件中定义要索引的字段
例如:
{{ object.field_name }}
yourapp:实际应用名称
yourmodel:实际模型名称
field_name:要索引的实际字段
注意:
这个路径是固定的,如果不按照这个路径配置,就需要在text字段内使用template_name 参数,指定模板文件
例如:这里在templates
目录下创建search/indexes/product/product_text.txt
文件,具体索引字段如下:
{{ object.name }}
{{ object.price }}
模板文件意思:
当将关键词通过text参数名传递时,此模板指明Product的name、price作为text字段的索引值来进行关键字索引查询,即作为索引的方向
执行数据库迁移
配置连接数据库信息
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'HOST': '127.0.0.1', # 数据库主机
'PORT': 3306, # 数据库端口
'USER': 'root', # 数据库用户名
'PASSWORD': '123456', # 数据库用户密码
'NAME': 'demo' # 数据库名字
}
}
生成迁移文件,让 Django 知道模型有一些变更
python manage.py makemigrations
创建表结构,同步到数据库中
python manage.py migrate
插入Product模型类对应的tb_product
表数据
INSERT INTO `demo`.`tb_product` (`id`, `name`, `price`, `stock`, `is_existed`) VALUES (1, '1号手机', 999.00, 23, 1);
INSERT INTO `demo`.`tb_product` (`id`, `name`, `price`, `stock`, `is_existed`) VALUES (2, '2号手机', 1999.00, 98, 1);
INSERT INTO `demo`.`tb_product` (`id`, `name`, `price`, `stock`, `is_existed`) VALUES (3, '3号手机', 3999.00, 56, 1);
INSERT INTO `demo`.`tb_product` (`id`, `name`, `price`, `stock`, `is_existed`) VALUES (4, '4号手机', 4999.00, 234, 1);
INSERT INTO `demo`.`tb_product` (`id`, `name`, `price`, `stock`, `is_existed`) VALUES (5, '5号手机', 5999.00, 21, 1);
生成索引
运行以下命令来创建并初始化Elasticsearch索引
python manage.py rebuild_index
(django) D:\WorkSpace\Python\django_project>python manage.py rebuild_index
WARNING: This will irreparably remove EVERYTHING from your search index in connection 'default'.
Your choices after this are to restore from backups or rebuild via the `rebuild_index` command.
Are you sure you wish to continue? [y/N] y
Removing all documents from your index because you said so.
All documents removed.
Indexing 5 产品
查看ES,发现索引已创建,并且tb_product
表中数据根据索引类配置已生产相应索引数据
注意:本人在生成初始索引时,遇到如下异常:
haystack.exceptions.MissingDependency: The 'elasticsearch' backend requires the installation of 'elasticsearch'. Please refer to the documentation
最初直接指定版本安装es:
pip install elasticsearch==7.5.1
经过一番折腾,阅读官方文档,推测很有可能是Haystack与Elasticsearch版本不兼容导致的。参考官方文档,使用如下命令安装Elasticsearch,问题得以解决。
pip install "django-haystack[elasticsearch]"
渲染模板
在templates
目录下创建search/search.html
渲染模板文件,用于接收和渲染全文检索的结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/search/">
<input type="text" name="q" placeholder="产品名称">
<input type="submit" name="" value="搜索">
</form>
<hr>
<div>
搜索关键字: <span>{{ query }}</span>
<hr>
总页数: <span>{{ page.paginator.num_pages }}</span>
<hr>
{% for result in page.object_list %}
<li>
<span>{{ result.object.name }}</span>
<span>¥{{ result.object.price }}</span>
</li>
{% empty %}
<span>查询无结果</span>
{% endfor %}
</div>
<!-- 分页导航 -->
<div>
{% if page.has_previous %}
<a href="?q={{ query }}&page={{ page.previous_page_number }}">上一页</a>
{% endif %}
{% for num in page.paginator.page_range %}
{% if page.number == num %}
<span>{{ num }}</span>
{% else %}
<a href="?q={{ query }}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page.has_next %}
<a href="?q={{ query }}&page={{ page.next_page_number }}">下一页</a>
{% endif %}
</div>
</body>
</html>
执行测试
访问:http://127.0.0.1:8000/search/
搜索
直接访问:http://127.0.0.1:8000/search/?q=手机
分页搜索:http://127.0.0.1:8000/search/?q=手机&page=2
搜索请求和结果渲染的自定义处理
概述
自动处理
当在Django中使用 Haystack 和 Elasticsearch 进行搜索时,可以通过注册 haystack.urls 来处理搜索相关的 URL 路由。这样做的好处是,Haystack 提供了默认的视图和 URL 配置,可以方便地处理搜索请求并渲染搜索结果。
手动处理
如果想手动处理搜索请求和渲染结果,而不使用 Haystack 提供的默认视图和 URL 配置,可以编写自己的视图函数来执行搜索操作并将搜索结果传递给模板进行渲染。
创建搜索视图
首先,在应用的views.py中创建一个搜索视图。这个视图将处理GET请求中的搜索关键词,并使用Haystack进行搜索。
from haystack.query import SearchQuerySet
def searchView(request):
query = request.GET.get('q') # 获取搜索关键词
if query:
search_results = SearchQuerySet().filter(text=query) # 根据关键词过滤结果
else:
print("kong")
search_results = [] # 如果没有关键词,返回空结果
return render(request, 'product/product_search.html', {'search_results': search_results})
配置URL
在应用的urls.py中配置搜索视图的URL。
urlpatterns = [
path('search/', views.searchView),
]
创建搜索模板
在模板目录中创建一个名为search.html的模板文件。这个模板将用于展示搜索表单和结果。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="get" action="/product/search/">
<input type="text" name="q" placeholder="产品名称">
<input type="submit" name="" value="搜索">
</form>
<hr>
<div>
{% for result in search_results %}
<li>
<span>{{ result.object.name }}</span>
<span>¥{{ result.object.price }}</span>
</li>
{% empty %}
<span>查询无结果</span>
{% endfor %}
</div>
</body>
</html>
自定义结果渲染
在模板中,可以访问搜索结果的不同字段。例如,如果模型有多个字段,可以在循环中展示这些字段。
<div>
{% for result in search_results %}
<li>
<span>{{ result.object.name }}</span>
<span>¥{{ result.object.price }}</span>
</li>
{% empty %}
<span>查询无结果</span>
{% endfor %}
</div>
执行测试
访问:http://127.0.0.1:8000/product/search
搜索
其他
Haystack在Django中提供了一些高级用法,可以帮助实现更复杂和灵活的搜索功能。
添加分页功能
如果搜索结果较多,可以考虑实现分页功能。可以使用Django的内置分页工具:
from django.core.paginator import Paginator
def search_view(request):
query = request.GET.get('q')
results = SearchQuerySet().all()
if query:
results = results.filter(content=query)
paginator = Paginator(results, 10) # 每页10个结果
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'product/product_search.html', {'page_obj': page_obj, 'query': query})
在模板中,添加分页链接:
<div>
<span>共 {{ page_obj.paginator.count }} 条结果</span>
</div>
<ul>
{% for result in page_obj %}
<li>
<a href="{{ result.object.get_absolute_url }}">{{ result.object.title }}</a>
<p>{{ result.object.description }}</p>
</li>
{% empty %}
<li>没有找到结果。</li>
{% endfor %}
</ul>
<div>
<span>页码: </span>
<span class="pagination">
{% if page_obj.has_previous %}
<a href="?q={{ query }}&page=1">第一页</a>
<a href="?q={{ query }}&page={{ page_obj.previous_page_number }}">上一页</a>
{% endif %}
<span>第 {{ page_obj.number }} 页</span>
{% if page_obj.has_next %}
<a href="?q={{ query }}&page={{ page_obj.next_page_number }}">下一页</a>
<a href="?q={{ query }}&page={{ page_obj.paginator.num_pages }}">最后一页</a>
{% endif %}
</span>
</div>
自定义搜索查询
可以使用Haystack的SearchQuerySet来构建复杂的查询。
使用filter()、exclude()和order_by()等方法来定制搜索结果
from haystack.query import SearchQuerySet
results = SearchQuerySet().filter(content='搜索关键词').exclude(author='某个作者').order_by('-date')
使用Facets
分面搜索允许对搜索结果进行分类和过滤。
from haystack.query import SearchQuerySet
def search_view(request):
query = request.GET.get('q')
sqs = SearchQuerySet().filter(content=query)
facets = sqs.facet('category') # 假设有一个category字段
return render(request, 'product/product_search.html', {'results': sqs, 'facets': facets})
高级排序
可以根据多个字段进行排序,或者使用自定义的排序逻辑
results = SearchQuerySet().filter(content='关键词').order_by('field1', '-field2')
使用拼音搜索或模糊搜索
在某些情况下,可能需要支持拼音搜索或模糊搜索,可以通过自定义查询来实现
results = SearchQuerySet().filter(content__contains='拼音关键词')