1

我在我们的 django 应用程序中使用 haystack 进行搜索,搜索工作得很好。但是我遇到了实时搜索的问题。对于实时搜索,我使用的是 haystack 的默认 RealTimeSignalProcessor(haystack.signals.RealtimeSignalProcessor)。我的模型中包含一个多对多字段。当仅针对这个多对多字段更改数据时,实时信号处理器似乎没有正确更新索引数据。更新多对多数据后,我得到错误的搜索结果。

它在手动运行rebuild_index 命令后工作。我认为rebuild_index 正在工作,因为它首先进行清理,然后再次构建索引数据。

有人可以提出一些解决问题的方法吗?

顺便说一下,以下是围绕它的代码。

模型:

class Message_forum(models.Model):
      message = models.ForeignKey(Message)
      tags = models.ManyToManyField(Tag, blank=True, null=True) #this is many to many field

搜索索引.py:

class Message_forumIndex(indexes.SearchIndex, indexes.Indexable):
    text = indexes.EdgeNgramField(document=True, use_template=True)
    message = indexes.CharField(model_attr='message', null=True)
    tags = indexes.CharField(model_attr='tags', null=True)

    def get_model(self):
        return Message_forum

    def index_queryset(self, using=None):
        return self.get_model().objects.all()

    def prepare_tags(self, obj):
        return [tag.tag for tag in obj.tags.all()]

索引模板:

{{ object.tags.tag }}

设置.py:

HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

我有最新版本的 haystack 和 whoosh 作为后端。

4

3 回答 3

9

在深入研究干草堆的代码后,我已经弄清楚了。

在 haystack 默认的 RealTimeSignalProcessor 中,它连接每个应用模型的 post_save 和 post_delete 信号。现在在 post_save 和 post_delete 信号中调用 handle_save 方法。在这种方法中,haystack 正在验证发件人,在我的情况下,对于 tags(many-to-many) 字段,Message_forum_tag 模型作为发件人传递。现在这个模型的索引没有出现在我的 search_index 中,因为它不是我的应用程序模型,而是 django 生成的。因此,在 handle_save 方法中,它绕过了这个模型上的任何更改,因此它没有更新已更改对象的索引数据。

所以我想出了两种不同的解决方案来解决这个问题。

  1. 我可以创建特定于我的模型 Message_forum 的自定义实时信号处理器,在这个设置方法中,我可以将 Message_forum 中每个多对多字段上的 m2mchanged 信号与 handle_save 连接起来。同时,我可以将 Message_forum 作为发件人传递,这样 haystack 将通过验证(不完全是验证,但它试图获取其索引 obj)并更新已更改对象的索引数据。

  2. 另一种方法是确保每当更改任何多对多字段时,都会调用其父项的保存方法(此处为 Message_forum.save())。所以它总是会调用 post_save 信号,然后 haystack 将更新索引对象数据。

花了大约3个小时来弄清楚。希望这会帮助有同样问题的人。

于 2013-11-01T07:50:18.130 回答
6

我遇到了类似的问题,但我选择了 Nikhil 的 1 号和 2 号选项的混合体。

对于一个名为 ContentItem 的模型和一个名为 categories 的 m2m 字段,我创建了一个自定义信号处理器来扩展基础信号处理器。

所以我实现了一个从源代码复制的 setup() ,但添加了以下行:

models.signals.m2m_changed.connect(self.handle_save, sender=ContentItem.categories.through)

并且对 teardown() 做了同样的事情,但使用了类似的断开线。我还扩展了 handle_save 并更改了行:

index = self.connections[using].get_unified_index().get_index(sender)

index = self.connections[using].get_unified_index().get_index(instance.__class__)

这意味着该信号处理器正在监视管理表中的 ContentItem 到 Category 的 m2m 更改,但是当进行 m2m 更改时,将传递正确类的名称,即 ContentItem 而不是 ContentItem.categories.through。

这似乎在大多数情况下都有效,但是如果我删除一个类别,尽管删除了关系,但 m2m_changed 不会触发。看起来这可能是 django 本身的一个错误

因此,我还在 setup 中添加了以下行(并断开了拆卸连接):

models.signals.pre_delete.connect(self.handle_m2m_delete, sender=Category)

并创建了 handle_save (handle_m2m_delete) 的方法副本,该方法手动从 through 表中删除了关系并保存了受影响的 ContentItems(导致原始的 handle_save 然后被触发)。这至少意味着我不必记住保存父级来更新代码中其他任何地方的索引。

于 2014-04-09T11:58:51.483 回答
4

我可以建议一个替代解决方案,比尝试观察所有正确信号并最终使用必须了解所有 m2m 关系的信号处理器的复杂性更简单。

它看起来像这样:

信号.py:

from collections import OrderedDict

from haystack.signals import RealtimeSignalProcessor


class BatchingSignalProcessor(RealtimeSignalProcessor):
    """
    RealtimeSignalProcessor connects to Django model signals
    we store them locally for processing later - must call
    ``flush_changes`` from somewhere else (eg middleware)
    """

    # Haystack instantiates this as a singleton

    _change_list = OrderedDict()

    def _add_change(self, method, sender, instance):
        key = (sender, instance.pk)
        if key in self._change_list:
            del self._change_list[key]
        self._change_list[key] = (method, instance)

    def handle_save(self, sender, instance, created, raw, **kwargs):
        method = super(BatchingSignalProcessor, self).handle_save
        self._add_change(method, sender, instance)

    def handle_delete(self, sender, instance, **kwargs):
        method = super(BatchingSignalProcessor, self).handle_delete
        self._add_change(method, sender, instance)

    def flush_changes(self):
        while True:
            try:
                (sender, pk), (method, instance) = self._change_list.popitem(last=False)
            except KeyError:
                break
            else:
                method(sender, instance)

中间件.py:

from haystack import signal_processor


class HaystackBatchFlushMiddleware(object):
    """
    for use with our BatchingSignalProcessor

    this should be placed *at the top* of MIDDLEWARE_CLASSES
    (so that it runs last)
    """
    def process_response(self, request, response):
        try:
            signal_processor.flush_changes()
        except AttributeError:
            # (in case we're not using our expected signal_processor)
            pass
        return response

设置.py:

MIDDLEWARE_CLASSES = (
    'myproject.middleware.HaystackBatchFlushMiddleware',
    ...
)

HAYSTACK_SIGNAL_PROCESSOR = 'myproject.signals.BatchingSignalProcessor'

我正在我的项目中尝试这个,似乎工作正常。我欢迎任何反馈或建议。

于 2015-07-26T21:55:05.400 回答