Индексация Django Haystack не работает для многих полей в модели

Я использую стог сена в нашем приложении django для поиска, и поиск работает очень хорошо. Но у меня проблема с поиском в реальном времени. Для поиска в реальном времени я использую стог сена по умолчанию RealTimeSignalProcessor (haystack.signals.RealtimeSignalProcessor). Моя модель содержит одно поле «многие ко многим». Когда данные изменяются только для этого поля «многие ко многим», кажется, что процессор сигналов реального времени не обновляет индексирующие данные должным образом. После обновления данных «многие ко многим» я получаю неверный результат поиска.

Он работает после ручного запуска команды reboot_index. Я думаю, что reboot_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'

У меня последняя версия стога сена и свист в качестве серверной части.


person nik    schedule 31.10.2013    source источник


Ответы (3)


Я понял это после того, как покопался в коде стога сена.

В стоге сена по умолчанию RealTimeSignalProcessor, соединяющий сигналы post_save и post_delete каждой модели приложения. Теперь метод handle_save вызывается в сигнале post_save и post_delete. В этом методе стог сена проверяет отправителя, и в моем случае для поля тегов (многие ко многим) модель Message_forum_tag передается как отправитель. Теперь индекс для этой модели отсутствует в моем search_index, так как это не моя модель приложения, а сгенерированная django. Итак, в методе handle_save он пропускал любые изменения в этой модели и, следовательно, не обновлял индексированные данные для измененного объекта.

Итак, я нашел два разных решения этой проблемы.

  1. Я могу создать собственный процессор обработки сигналов в реальном времени, специфичный для моей модели Message_forum, в этом методе настройки я могу подключить сигнал m2mchanged к каждому полю «многие ко многим» в Message_forum с помощью handle_save. В то же время я могу передать Message_forum в качестве отправителя, чтобы стог сена прошел проверку (не совсем проверку, а попытку получить свой индекс obj) вокруг него и обновил данные индекса измененного объекта.

  2. Другой способ состоит в том, чтобы убедиться, что всякий раз, когда изменяется любое поле «многие ко многим», вызывается метод сохранения его родителя (здесь Message_forum.save()). И поэтому он всегда будет вызывать сигнал post_save, и после этого стог сена будет обновлять данные объекта индекса.

Пришлось потратить около 3 часов, чтобы понять это. Надеюсь, это поможет кому-то с такой же проблемой.

person nik    schedule 01.11.2013
comment
Можете ли вы показать код? У меня точно такая же проблема. Пробовал кое-что, но функция все еще не работает. Используя django-rq, я получил это, но я не хочу использовать стороннюю библиотеку, чтобы заставить ее работать, если она может работать со своей собственной функцией. - person Mauricio Abreu; 27.03.2014
comment
Мы решили проблему, выбрав второй подход, упомянутый выше. Если это не применимо в вашем случае, вам следует создать собственный процессор сигналов реального времени. Я пробовал и первый способ. Но этого кода сейчас нет со мной. Но вы можете прочитать об этом подробнее здесь.. ru/последние/ - person nik; 27.03.2014

У меня была похожая проблема, но я выбрал гибрид вариантов 1 и 2 от Nikhil.

Для модели под названием 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)

to

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

Это означает, что этот сигнальный процессор отслеживает изменения m2m в таблице управления для ContentItem в категорию, но когда делается изменение 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). Это означало, по крайней мере, что мне не нужно было помнить о сохранении родителя для обновления индекса где-либо еще в коде.

person Dan D    schedule 09.04.2014
comment
Спасибо за решение, но не могли бы вы предоставить код для «handle_m2m_delete»? Точно так же, как картинка, некоторый код говорит 1000 слов - person straykiwi; 08.12.2016
comment
@straykiwi Боюсь, что нет, у меня больше нет доступа к репозиторию - person Dan D; 13.12.2016

Я могу предложить альтернативное решение, более простое, чем усложнение попыток отследить все правильные сигналы и в конечном итоге получить процессор сигналов, который должен знать обо всех ваших отношениях 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

settings.py:

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

HAYSTACK_SIGNAL_PROCESSOR = 'myproject.signals.BatchingSignalProcessor'

Я пробую это в своем проекте, кажется, работает нормально. Я приветствую любые отзывы или предложения.

person Anentropic    schedule 26.07.2015
comment
Спасибо, хорошо работает. Обратите внимание, что для совместимости с Django 1.9 вам нужно будет использовать версию django-haystack ›= 2.5.0, а затем, из-за изменений в django-haystack, вам нужно получить обработчик сигналов в middleware.py немного по-другому. 1. использовать этот импорт: from django.apps import apps, 2. перед попыткой исключения вставить signal_processor = apps.get_app_config('haystack').signal_processor - person bszom; 29.07.2016