bgneal@753
|
1 """This module contains a custom Haystack signal processing class to update the
|
bgneal@753
|
2 search index in realtime. We update our search index by enqueuing edits and
|
bgneal@753
|
3 deletes into a queue for batch processing. Our class ensures we only enqueue
|
bgneal@753
|
4 content that should be in the search index.
|
bgneal@469
|
5
|
bgneal@469
|
6 """
|
bgneal@753
|
7 from django.db.models import signals
|
bgneal@753
|
8 import queued_search.signals
|
bgneal@924
|
9 import haystack
|
bgneal@469
|
10
|
bgneal@753
|
11 from bio.signals import profile_content_update
|
bgneal@753
|
12 from forums.signals import topic_content_update, post_content_update
|
bgneal@469
|
13
|
bgneal@753
|
14 import ygroup.models
|
bgneal@753
|
15 from weblinks.models import Link
|
bgneal@753
|
16 from podcast.models import Item
|
bgneal@753
|
17 from news.models import Story
|
bgneal@753
|
18 from downloads.models import Download
|
bgneal@753
|
19 from forums.models import Forum, Topic, Post
|
bgneal@753
|
20 from bio.models import UserProfile
|
bgneal@753
|
21
|
bgneal@753
|
22
|
bgneal@753
|
23 UID = 'custom_search.signals'
|
bgneal@753
|
24
|
bgneal@753
|
25
|
bgneal@753
|
26 class QueuedSignalProcessor(queued_search.signals.QueuedSignalProcessor):
|
bgneal@469
|
27 """
|
bgneal@753
|
28 This customized version of queued_search's QueuedSignalProcessor
|
bgneal@753
|
29 conditionally enqueues items to be indexed.
|
bgneal@469
|
30
|
bgneal@469
|
31 """
|
bgneal@753
|
32 def __init__(self, *args, **kwargs):
|
bgneal@753
|
33
|
bgneal@753
|
34 # We assume that it is okay to attempt to delete a model from the search
|
bgneal@753
|
35 # index even if the model object is not in the index. In other words,
|
bgneal@753
|
36 # attempting to delete an object from the index will not cause any
|
bgneal@753
|
37 # errors if it is not in the index. Thus if we see an object that has an
|
bgneal@753
|
38 # 'is_public' attribute, and it is false, we can safely enqueue a delete
|
bgneal@753
|
39 # in case the 'is_public' attribute just went from True to False. We
|
bgneal@753
|
40 # have no way of knowing that, it could have been False all along, but we
|
bgneal@753
|
41 # just try the delete in case to be safe.
|
bgneal@753
|
42
|
bgneal@753
|
43 # To make the code easier to read, use a table to drive our signal
|
bgneal@753
|
44 # connecting and disconnecting:
|
bgneal@753
|
45 self.signal_chain = [
|
bgneal@753
|
46 # Yahoo Group posts are always updated:
|
bgneal@753
|
47 (signals.post_save, ygroup.models.Post, self.enqueue_save),
|
bgneal@753
|
48 (signals.post_delete, ygroup.models.Post, self.enqueue_delete),
|
bgneal@753
|
49
|
bgneal@753
|
50 # Weblink Links are updated if they are public:
|
bgneal@753
|
51 (signals.post_save, Link, self.enqueue_public_save),
|
bgneal@753
|
52 (signals.post_delete, Link, self.enqueue_delete),
|
bgneal@753
|
53
|
bgneal@753
|
54 # Podcast Items are always updated:
|
bgneal@753
|
55 (signals.post_save, Item, self.enqueue_save),
|
bgneal@753
|
56 (signals.post_delete, Item, self.enqueue_delete),
|
bgneal@753
|
57
|
bgneal@753
|
58 # News Stories are always updated:
|
bgneal@753
|
59 (signals.post_save, Story, self.enqueue_save),
|
bgneal@753
|
60 (signals.post_delete, Story, self.enqueue_delete),
|
bgneal@753
|
61
|
bgneal@753
|
62 # Downloads are updated if they are public:
|
bgneal@753
|
63 (signals.post_save, Download, self.enqueue_public_save),
|
bgneal@753
|
64 (signals.post_delete, Download, self.enqueue_delete),
|
bgneal@753
|
65
|
bgneal@753
|
66 # Forum Topics are updated if they belong to a public forum:
|
bgneal@753
|
67 (topic_content_update, None, self.enqueue_topic_save),
|
bgneal@753
|
68 (signals.post_delete, Topic, self.enqueue_delete),
|
bgneal@753
|
69
|
bgneal@753
|
70 # Forum Posts are updated if they belong to a public forum:
|
bgneal@753
|
71 (post_content_update, None, self.enqueue_post_save),
|
bgneal@753
|
72 (signals.post_delete, Post, self.enqueue_delete),
|
bgneal@753
|
73
|
bgneal@753
|
74 # UserProfiles are updated when we receive a special signal:
|
bgneal@753
|
75 (profile_content_update, None, self.enqueue_profile),
|
bgneal@753
|
76 (signals.post_delete, UserProfile, self.enqueue_delete),
|
bgneal@753
|
77 ]
|
bgneal@753
|
78
|
bgneal@753
|
79 super(QueuedSignalProcessor, self).__init__(*args, **kwargs)
|
bgneal@753
|
80
|
bgneal@753
|
81 def setup(self):
|
bgneal@753
|
82 """We override setup() so we can attach signal handlers to only the
|
bgneal@753
|
83 models we search on. In some cases we have custom signals to tell us
|
bgneal@753
|
84 when to update the search index.
|
bgneal@469
|
85
|
bgneal@469
|
86 """
|
bgneal@753
|
87 for signal, sender, receiver in self.signal_chain:
|
bgneal@753
|
88 signal.connect(receiver, sender=sender, dispatch_uid=UID)
|
bgneal@469
|
89
|
bgneal@753
|
90 def teardown(self):
|
bgneal@753
|
91 """Disconnect all signals we previously connected."""
|
bgneal@753
|
92 for signal, sender, receiver in self.signal_chain:
|
bgneal@753
|
93 signal.disconnect(receiver, sender=sender, dispatch_uid=UID)
|
bgneal@753
|
94
|
bgneal@753
|
95 def enqueue_public_save(self, sender, instance, **kwargs):
|
bgneal@753
|
96 """Index only if the instance is_public.
|
bgneal@753
|
97
|
bgneal@753
|
98 If not, enqueue a delete just in case the is_public flag got flipped
|
bgneal@753
|
99 from True to False.
|
bgneal@469
|
100
|
bgneal@469
|
101 """
|
bgneal@753
|
102 if instance.is_public:
|
bgneal@753
|
103 self.enqueue_save(sender, instance, **kwargs)
|
bgneal@753
|
104 else:
|
bgneal@753
|
105 self.enqueue_delete(sender, instance, **kwargs)
|
bgneal@677
|
106
|
bgneal@753
|
107 def enqueue_topic_save(self, sender, **kwargs):
|
bgneal@753
|
108 """Enqueue only if the topic instance belongs to a public forum."""
|
bgneal@753
|
109 if sender.forum.id in Forum.objects.public_forum_ids():
|
bgneal@753
|
110 self.enqueue_save(Topic, sender, **kwargs)
|
bgneal@677
|
111
|
bgneal@753
|
112 def enqueue_post_save(self, sender, **kwargs):
|
bgneal@753
|
113 """Enqueue only if the post instance belongs to a public forum."""
|
bgneal@753
|
114 if sender.topic.forum.id in Forum.objects.public_forum_ids():
|
bgneal@753
|
115 self.enqueue_save(Post, sender, **kwargs)
|
bgneal@677
|
116
|
bgneal@753
|
117 def enqueue_profile(self, sender, **kwargs):
|
bgneal@753
|
118 """Forward the user profile instance on unconditionally."""
|
bgneal@753
|
119 self.enqueue_save(UserProfile, sender, **kwargs)
|
bgneal@924
|
120
|
bgneal@924
|
121
|
bgneal@924
|
122 # Starting with Django 1.7, we'd see Django generate warnings if we defined
|
bgneal@924
|
123 # a HAYSTACK_SIGNAL_PROCESSOR in our settings that referenced the class above.
|
bgneal@924
|
124 # This is because Haystack creates an instance of our signal processor class
|
bgneal@924
|
125 # (defined above) at import time, and thus imports this module very early in the
|
bgneal@924
|
126 # application startup sequence. Warnings are then generated when this module
|
bgneal@924
|
127 # imports our models, some of whose applications have not been imported yet.
|
bgneal@924
|
128 # This problem will presumably go away when Haystack can fully support Django
|
bgneal@924
|
129 # 1.7.x and implements an AppConfig with a ready() method. Until then, we don't
|
bgneal@924
|
130 # use Haystack's signal processor object; we'll just create one here. This
|
bgneal@924
|
131 # module will be imported when our custom_search app's ready() method runs.
|
bgneal@924
|
132
|
bgneal@924
|
133 signal_processor = QueuedSignalProcessor(haystack.connections,
|
bgneal@924
|
134 haystack.connection_router)
|