Django Haystack索引在模型中不适用于多个领域

时间:2013-10-31 12:05:47

标签: django search django-haystack whoosh

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

在手动运行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

search_index.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 }}

settings.py:

HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

我有干草堆的最新版本,并且作为后端嗖的一声。

3 个答案:

答案 0 :(得分:9)

在深入研究干草堆的代码之后我已经弄明白了。

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

所以我已经为这个问题找到了两种不同的解决方案。

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

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

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

答案 1 :(得分:6)

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

对于名为ContentItem的模型,其中m2m字段称为类别,我创建了一个自定义信号处理器,扩展了基本信号处理器。

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

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

使用拆卸()进行相同的操作,但使用类似的断开线。我还扩展了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也不会触发。 It looks like this might be a bug in django itself

所以我还在设置中添加了以下行(以及断开连接):

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

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

答案 2 :(得分:4)

我可以建议一种替代解决方案,比试图观察所有正确信号的复杂性更简单,最后得到一个必须知道所有m2m关系的信号处理器。

看起来像这样:

signals.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)

middleware.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

settings.py:

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

HAYSTACK_SIGNAL_PROCESSOR = 'myproject.signals.BatchingSignalProcessor'

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