Django Haystack 索引不适用于模型中的多对多字段

问题描述 投票:0回答:4

我在 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

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

设置.py:

HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'

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

django search django-haystack whoosh
4个回答
9
投票

在深入研究 haystack 代码后我已经弄清楚了。

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

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

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

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

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


6
投票

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

对于名为 ContentItem 的模型以及名为类别的 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 本身的一个错误

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

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

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


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)

中间件.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'

我正在我的项目中尝试这个,看起来效果很好。欢迎任何反馈或建议。


0
投票

刚刚在我的项目中实施了以下解决方案:

from django.db import models
from django.db.models.signals import m2m_changed, post_delete
from django.dispatch import receiver

from haystack.management.commands import update_index


class Tag(models.Model):
    name = models.CharField(max_length=64, unique=True)

    def __str__(self):
        return self.name


class Blog(models.Model):
    title = models.CharField(max_length=128)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, blank=True)

    def __str__(self):
        return self.title


# This signal is triggered when the tags field of a Provider instance is changed
@receiver(m2m_changed, sender=Blog.tags.through)
def tags_changed(sender, action, **kwargs):
    # Check if the action is post_add or post_remove
    if action in ['post_add', 'post_remove']:
        update_index.Command().handle()


# This signal is triggered when a Tag instance is deleted
@receiver(post_delete, sender=Tag)
def tag_deleted(sender, instance, **kwargs):
    update_index.Command().handle()
© www.soinside.com 2019 - 2024. All rights reserved.