2

Following a related (as yet unanswered) question, I did some investigation and found that the current implementation of Django Haystack's RealTimeSearchIndex makes no attempt to also update on related field (Many to Many) changes. I thought this would be an easy fix - after all, I could just extend RealTimeSearchIndex like this:

class RealTimeM2MSearchIndex(RealTimeSearchIndex):
    def _setup_save(self, model):
        signals.m2m_changed.connect(self.update_object, sender=model)
        signals.post_save.connect(self.update_object, sender=model)

But then I realized (or at least assumed, since it's not working) that this only works if the M2M field is defined on the model itself, and not if it's the "reverse" side of the M2M relationship. Trying to fix that, I then did something like the following:

        signals.m2m_changed.connect(self.update_object, sender=model.related_field.through)

Where related_field is the name of the specific Model on other side of the ManyToMany definition. Strangely enough, upon running, Django then complains that the Model has no such field, related_field.

And sure enough, if I inspect the object, Django has not yet extended the model to have the related_field field. If I inspect the same object when displaying a view, however, it does have that related_field.

Summary

So the problem seems to be that Django's automatic behavior to add an attribute to the reverse side of an M2M relationship has yet to happen when Haystack runs its code. How can I overcome this obstacle, and allow Haystack's RealTimeSearchIndex to also update on related field changes?

Community
  • 1
  • 1
Herman Schaaf
  • 46,821
  • 21
  • 100
  • 139

2 Answers2

0

Just tried implementing this myself, and your problem is the value of the sender argument in this line:

signals.m2m_changed.connect(self.update_object, sender=model)

I read the documentation for the m2m_changed signal and the sender will be something like MyModel.my_field.through so you need to use that. This means you can't have a generic class as you are trying to do, but will need to define the _setup_save method in each case, with the m2m_changed signal connected for each ManyToMany field that model has.

For example, if your model had two ManyToManyFields called region and sector, you could do:

  # we implement these to force the update when the ManyToMany fields change
  def _setup_save(self, model):
      signals.m2m_changed.connect(self.update_object,
              sender=MyModel.sector.through)
      signals.m2m_changed.connect(self.update_object,
              sender=MyModel.region.through)
      signals.post_save.connect(self.update_object, sender=model)

You should also really define the _teardown_save() method:

  def _teardown_save(self, model):
      signals.m2m_changed.disconnect(self.update_object,
              sender=MyModel.sector.through)
      signals.m2m_changed.disconnect(self.update_object,
              sender=MyModel.region.through)
      signals.post_save.disconnect(self.update_object, sender=model)

(This is based on code I have tested, it appears to work - certainly no errors about fields not existing).

Update: Just read your question more closely. Is it possible that your model has the ManyToManyField added dynamically? Is the call to register() after you have defined all your classes?

Hamish Downer
  • 16,603
  • 16
  • 90
  • 84
  • Thanks for the answer! I asked the question a couple of months ago, so I'll have to setup a test case again, but I basically did it exactly the same way as you did in your answer, and the problem was that the m2m field did not exist on the reverse side (as that's added by the Django ORM), and I needed to update that item's index as well. I wasn't doing anything funny like adding it dynamically, and the call to `register` was after I defined all my classes. So I'm actually still not sure how to address that problem, but as I said, I'll have to set up a test case when I have some time! – Herman Schaaf Jun 24 '12 at 08:45
0

I think the simplest solution is to just use the built in RealTimeSearchIndex, and add a signal listener in your models.py to reindex the model on m2m_changed, or whenever. See my answer to the other question - you could easily modify it to index on m2m_changed instead of post_save.

Greg
  • 9,963
  • 5
  • 43
  • 46