ElasticSearch Django应用程序

项目详细描述

https://travis-ci.org/yunojuno/elasticsearch-django.svg?branch=master https://badge.fury.io/py/elasticsearch_django.svg

此项目现在需要python3和django1.11或更高版本。对于以前的版本,请参阅python2分支。

弹性搜索django

这是一个轻量级的django应用程序,适用于那些将elasticsearch与django一起使用并希望管理其索引的用户。

nb主分支现在基于es6。如果您正在使用ES2/ES5,请切换到相关分支(在PYPI上发布为2.x,5.x)


搜索索引生命周期

搜索索引的基本生命周期很简单:

  1. 创建索引
  2. 将文档发布到索引
  3. 查询索引

将此与我们在django项目中使用搜索相关,如下所示:

  1. 为命名索引创建映射文件
  2. 将索引配置添加到django设置中
  3. 将模型映射到索引中的文档类型
  4. 将对象的文档表示形式发布到索引
  5. 更新对象时更新索引
  6. 删除对象时删除文档
  7. 查询索引
  8. 将搜索结果转换为查询集(保持相关性)

django实现

本节介绍如何设置django来识别es索引,以及应该出现在索引中的模型。通过此设置,您应该能够运行管理命令,这些命令将创建和填充每个索引,并使索引与数据库保持同步。

创建索引映射文件

将django配置为使用索引的先决条件是具有可用的索引映射。这有点鸡毛蒜皮,但基本假设是您能够在django本身之外创建索引映射,如原始json,例如使用chrome扩展Sense,或者使用api工具Paw。 (欺骗的最简单方法是在ES实例的URL(POST http://ELASTICSEARCH_URL/{{index_name}})上发布一个表示您的文档类型的JSON文档,然后检索通过GET http://ELASTICSEARCH_URL/{{index_name}}/_mapping创建的自动魔术映射)

一旦有了json映射,就应该将它保存在django项目的根目录中,作为search/mappings/{{index_name}}.json

配置django设置

搜索的django设置包含在名为SEARCH_SETTINGS的字典中,该字典应位于主django.conf.settings文件中。字典有三个根节点,connectionsindexessettings。下面是一个示例:

SEARCH_SETTINGS = {
    'connections': {
        'default': getenv('ELASTICSEARCH_URL'),
    },
    'indexes': {
        'blog': {
            'models': [
                'website.BlogPost',
            ]
        }
    },
    'settings': {
        # batch size for ES bulk api operations
        'chunk_size': 500,
        # default page size for search results
        'page_size': 25,
        # set to True to connect post_save/delete signals
        'auto_sync': True,
        # List of models which will never auto_sync even if auto_sync is True
        'never_auto_sync': [],
        # if true, then indexes must have mapping files
        'strict_validation': False
    }
}

connections节点(希望)是自解释的-我们支持多个连接,但实际上您只需要一个-“默认”连接。这是用于连接到ES实例的URL。settings节点包含站点范围的搜索设置。indexes节点是我们配置django和es如何一起玩的地方,也是大多数工作发生的地方。

索引设置

在索引节点中,我们有一个命名索引的集合——在本例中,只有一个名为blog的索引。在每个索引中,我们都有一个models键,它包含应该出现在索引中的django模型列表,用app.ModelName格式表示。一个索引中可以有多个模型,并且一个模型可以出现在多个索引中。下一节将描述模型和索引如何交互。

配置验证

当应用程序启动时,它会验证设置,包括以下内容:

  1. 指定的每个索引都有映射文件吗?
  2. 每个模型都实现了所需的混音吗?
< H3>实现搜索文档MIXIN < EH3>

到目前为止,我们已经将django配置为知道我们想要的索引的名称,以及我们想要索引的模型。它还不知道要索引哪些对象,以及如何将对象转换为其搜索索引文档。这是通过实现两个separ来实现的ATI-混合-^ {TT15} $和^ {TT16}$。配置验证例程将告诉您这些是否未实现。

^ {STR 1 } $ SurdioDebug 此子文件负责索引文档格式。我们正在对每个对象的JSON表示进行索引,并且在MIXIN上有两种方法用于输出正确的格式^ ^ TT17} $和^ {TT18}$。

关于auto_sync进程的机制的旁白,该进程使用django的post_savepost_delete模型信号连接。ES支持对已经存在的文档的部分更新,并且我们对索引模型进行基本假设——^ {STR 1 } $,如果将“Update EnFieldField:Kavg”传递给'MultMult.Save'方法调用,那么您将执行部分更新< /强>,并且这将仅作为部分更新传播到ES。

为此,我们有两种方法来生成模型的json表示-as_search_document,它应该返回一个表示整个对象的dict;以及as_search_document_update,它接受update_fieldskwarg。此方法处理程序 两个部分更新“策略”,在SEARCH_SETTINGS、'full'和'partial'中定义。这个 默认的“完全”策略只代理^ {TT17}$$方法——即部分更新 被视为完整的文档更新。“部分”策略更聪明-它会 将指定的更新字段映射到索引映射文件中定义的字段名。如果 字段名传递到save方法,但不在映射文件中,将被忽略。在 另外,如果底层django模型字段是一个相关对象,则ValueError将是 引发,因为我们无法自动序列化。在这种情况下,您需要 重写子类中的方法-有关详细信息,请参见代码。

为了更好地理解这一点,让我们假设有一个模型(MyModel)被配置为包含在名为myindex的索引中。如果我们保存一个对象,而不传递update_fields,那么这将被视为一个完整的文档更新,它将触发对象的index_search_document方法:

obj = MyModel.objects.first()
obj.save()
...
# AUTO_SYNC=true will trigger a re-index of the complete object document:
obj.index_search_document(index='myindex')

但是,如果我们只想更新一个字段(比如timestamp),并将其传递给save方法,那么这将触发update_search_document方法,传递我们想要更新的字段的名称。

# save a single field on the object
obj.save(update_fields=['timestamp'])
...
# AUTO_SYNC=true will trigger a partial update of the object document
obj.update_search_document(index, update_fields=['timestamp'])

我们将要更新的索引的名称作为第一个参数传递,因为对象在不同索引中可能有不同的表示:

def as_search_document(self, index):
    return {'name': "foo"} if index == 'foo' else {'name': "bar"}

在第二种方法的情况下,最简单的可能实现是一个字典,其中包含正在更新的字段的名称及其新值,这是默认的 实施。如果传入的字段是简单字段(数字、日期、字符串等),则 返回一个简单的{'field_name': getattr(obj, field_name}。但是,如果字段名 与复杂对象(例如相关对象)相关,则此方法将引发InvalidUpdateFields异常。在这种情况下,您应该使用自己的一个实现覆盖默认实现。

def as_search_document_update(self, index, update_fields):
    if 'user' in update_fields:
        # remove so that it won't raise a ValueError
        update_fields.remove('user')
        doc = super().as_search_document_update(index, update_fields)
        doc['user'] = self.user.get_full_name()
        return doc
    return super().as_search_document_update(index, update_fields)

我们将更新从完整的文档索引中分离出来的原因来自于我们自己遇到的一个实际问题。我们使用的完整的对象表示是非常密集的数据库-我们存储的模型属性需要遍历orm树。然而,由于我们还接触对象(见下文)来记录活动时间戳,我们最终用查询来淹没数据库,只为了更新输出文档中的一个字段。部分更新可解决此问题:

def touch(self):
    self.timestamp = now()
    self.save(update_fields=['timestamp'])

def as_search_document_update(self, index, update_fields):
    if list(update_fields) == ['timestamp']:
        # only propagate changes if it's +1hr since the last timestamp change
        if now() - self.timestamp < timedelta(hours=1):
            return {}
        else:
            return {'timestamp': self.timestamp}
    ....

异步处理更新

如果要生成大量索引更新,则可能需要异步运行(通过某种方式 排队机制)。考虑到排队的范围,没有内置的方法来执行此操作 但是可以使用pre_index,^{tt37}库和模式$ 以及pre_delete信号。在这种情况下,还应该关闭AUTO_SYNC(因为这将 同步运行更新),并自己处理更新。信号通过在夸尔格斯 相关模型方法以及所涉及的instance所必需的:

# ensure that SEARCH_AUTO_SYNC=False

from django.dispatch import receiver
import django_rq
from elasticsearch_django.signals import (
    pre_index,
    pre_update,
    pre_delete
)

queue = django_rq.get_queue("elasticsearch")


@receiver(pre_index, dispatch_uid="async_index_document")
def index_search_document_async(sender, **kwargs):
    """Queue up search index document update via RQ."""
    instance = kwargs.pop("instance")
    queue.enqueue(
        instance.update_search_document,
        index=kwargs.pop("index"),
    )


@receiver(pre_update, dispatch_uid="async_update_document")
def update_search_document_async(sender, **kwargs):
    """Queue up search index document update via RQ."""
    instance = kwargs.pop("instance")
    queue.enqueue(
        instance.index_search_document,
        index=kwargs.pop("index"),
        update_fields=kwargs.pop("update_fields"),
    )


@receiver(pre_delete, dispatch_uid="async_delete_document")
def delete_search_document_async(sender, **kwargs):
    """Queue up search index document deletion via RQ."""
    instance = kwargs.pop("instance")
    queue.enqueue(
        instance.delete_search_document,
        index=kwargs.pop("index"),
    )

^ {STR 1 } $ Sql文档管理MIXIN <强>

此MIXIN必须由模型的默认管理器实现(^ {TT41}$)。它还需要一个方法实现-get_search_queryset()-它返回要索引的对象的queryset。这也可以使用indexkwarg为不同的索引提供不同的对象集。

def get_search_queryset(self, index='_all'):
    return self.get_queryset().filter(foo='bar')

我们现在有了搜索实现的基本框架。我们现在可以使用包含的管理命令创建和填充搜索索引:

# create the index 'foo' from the 'foo.json' mapping file
$ ./manage.py create_search_index foo

# populate foo with all the relevant objects
$ ./manage.py update_search_index foo

下一步是确保我们的模型与索引保持同步。

添加模型信号处理程序以更新索引

如果设置auto_sync为真,则在AppConfig.ready上,配置用于索引的每个模型都连接了其post_savepost_delete信号。这意味着当调用相关的模型方法时,它们将在它们出现的所有索引中保持同步。(有一些非常基本的缓存以防止更新过多-对象文档缓存一分钟,如果文档中没有任何更改,则忽略索引更新。)

对于信号处理,有一个非常重要的警告。它将只接受模型本身的更改,而不接受相关的(ForeignKeyManyToManyField)模型更改。如果搜索文档受到这种更改的影响,则需要自己实现额外的信号处理。

除了^ {TT50} $之外,SeaDekFrutsMIXIN还提供了^ {TT551 }方法。操作应为“index”、“update”或“delete”。“index”和“update”的区别在于,“update”是只更改指定字段的部分更新,而不是重新更新整个文档。如果action为“更新”,而update_fields为“无”,则操作将更改为index

我们现在在搜索索引中有文档,与django同行保持最新。我们准备开始查询es。


最新Python第三方库