Hello I am integrating searches on my django-cms project. I have made a wiki app, where the content of each page is stored in a PlaceholderField
. I can initially index the content of the PlaceholderField
with the sudo ./manage.py rebuild_index
or update_index
, and the search works perfectly. The issue is when I modify the PlaceholderField
, the search indexes are not updating, even though I have in my settings.py:
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
Here is my model.py
:
from django.db import models
from django.utils.text import slugify
from djangocms_text_ckeditor.fields import HTMLField
from cms.models.fields import PlaceholderField
def my_placeholder_slotname(instance):
return 'content_placeholder'
class WikiPage(models.Model):
slug = models.SlugField(max_length=50,primary_key=True)
name = models.CharField(max_length=50)
content = HTMLField(blank=True)
section = models.ForeignKey('WikiSection', related_name='pages', db_index=True)
content_placeholder = PlaceholderField(my_placeholder_slotname)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(WikiPage, self).save(*args, **kwargs)
def get_absolute_url(self):
return '/wiki/page/%s' % self.slug
class WikiSection(models.Model):
slug = models.SlugField(max_length=50, primary_key=True)
name = models.CharField(max_length=50)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
super(WikiSection, self).save(*args, **kwargs)
def get_absolute_url(self):
return '/wiki/section/%s' % self.slug
Here is my search_indexes.py
:
import datetime
from haystack import indexes
from .models import WikiPage, WikiSection
from aldryn_search.helpers import get_cleaned_bits, get_request
from aldryn_search.utils import clean_join, get_index_base, strip_tags
from cms.models import CMSPlugin
from djangocms_text_ckeditor.cms_plugins import TextPlugin
from django.template import loader, Context
class WikiPageIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='name')
content_placeholder = indexes.CharField()
url = indexes.CharField()
_backend_alias = 'vcoe'
def get_model(self):
return WikiPage
def index_queryset(self, using=None):
"Used when the entire index for model is updated."
return self.get_model().objects.order_by('name')
def prepare_url(self, obj):
return obj.get_absolute_url()
def prepare_content_placeholder(self, obj):
request = get_request(obj)
placeholders = obj.content_placeholder
plugins = placeholders.get_plugins()
text_bits = []
for plugin in plugins:
cleaned = get_cleaned_bits(plugin.render_plugin())
text_bits.extend(cleaned)
return clean_join(' ', text_bits)
def prepare(self, obj):
data = super(WikiPageIndex, self).prepare(obj)
template = loader.select_template(
("search/indexes/wiki_app/wikipage_text.txt", ),
)
data["text"] = template.render(Context({
"object": obj,
'placeholder': self.prepare_content_placeholder(obj),
}))
return data
class WikiSectionIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='name')
url = indexes.CharField()
def get_model(self):
return WikiSection
def index_queryset(self, using=None):
"Used when the entire index for model is updated."
return self.get_model().objects.order_by('name')
def prepare_url(self, obj):
return obj.get_absolute_url()
class Meta:
model_name = WikiSection
app_label = 'wiki'
I am looking for help with an update method, that I have no idea how to write. I have checked the doc and seen there is update_object and update methods you can extend, but don't know what to return.
EDIT
I have kept tweeking and now use
class WikiPageIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
title = indexes.CharField(model_attr='name')
url = indexes.CharField()
content_placeholder = indexes.CharField(model_attr='content_placeholder')
_backend_alias = 'vcoe'
def get_model(self):
return WikiPage
def index_queryset(self, using=None):
"Used when the entire index for model is updated."
return self.get_model().objects.order_by('name')
def prepare_url(self, obj):
return obj.get_absolute_url()
def prepare_content_placeholder(self, obj):
plugins = obj.content_placeholder.get_plugins()
text_bits = []
for plugin in plugins:
plugin_text = self.get_plugin_search_text(plugin, get_request())
text_bits.append(plugin_text)
return clean_join(' ', text_bits)
def get_plugin_search_text(self, base_plugin, request):
plugin_content_bits = get_plugin_index_data(base_plugin, request)
return clean_join(' ', plugin_content_bits)
This is my wikipage_text.txt
:
{{ object.name }} - Wiki Page (Section {{ object.section }})
Content: {{ object.get_placeholder_text }}
EDIT2
For those trying the code in the answer below, here is what I did to get it fully working. All the code from below is the same, but added a few things. The solution below updated the index once a plugin is added
or removed
from the placeholder, but not when it is edited
. In my case, I needed it to update when the text in the djangocms-text-ckeditor plugin was changed. All that was needed was to register the signal from the text editor field from djangocms_text_ckeditor.models import Text
, from there I connect another signal:
from djangocms_text_ckeditor.models import Text
signals.post_save.connect(
update_wiki_page_index,
sender=Text,
dispatch_uid='post_save_update_wiki_page_index'
)
The issue with this is that all pages in the website have placeholders and all of them probably contain text, which means this signal would trigger rather often. To prevent unnecessary call to the DB from wiki.save(update_fields=['content_placeholder_data'])
I just check if the data has actually changed, like so:
def update_wiki_page_index(**kwargs):
instance = kwargs['instance']
if instance.pk is None:
return
placeholder = get_placeholder(plugin=instance)
if placeholder is None:
return
try:
wiki = WikiPage.objects.get(content_placeholder=placeholder)
except WikiPage.DoesNotExist:
return
# Make sure data has changed
if wiki.content_placeholder_data != get_placeholder_index_data(placeholder):
# DB based approach
placeholder = wiki.content_placeholder
placeholder_data = get_placeholder_index_data(placeholder)
wiki.content_placeholder_data = placeholder_data
# Saving here will trigger index update
wiki.save(update_fields=['content_placeholder_data'])
The search index is not updated when you add/delete a plugin to the placeholder because haystack signals only listen to models that are registered explicitly with the search, this would mean that you'd have to register a search index for each plugin that you would want to listen to.
The better and less complicated approach is to do your own signal handling but specific to plugins, django-cms uses these signals internally for a few things so it's a common scenario.
I've added three files, models and search_indexes have some modifications to them and then helpers is a new one.
I changed your placeholder name function to return a more unique value, added some comments there.
Regarding your issue, there's two ways to solve it using the signal handling.
One is by calculating placeholder data every time a plugin is saved/deleted and then store this data in a field on your model, then when we call save() with the updated_fields to only update the field we want, haystack's signal listener will be triggered and thus fire an update. When haystack updates the index then it just has to look at the data in db instead of having to recalculate plugin data again.
The second approach is to simply trigger the update manually from the signal handler, this would then tell haystack to update the wiki object in search engine just as if you would have saved the wiki object.
I've added both solutions there with comments that separate them, if wikis placeholders are modified constantly then I suggest using the db approach and plugging some async processing to haystack update signals (would need celery). Otherwise then you can just use the manual update.
DISCLAIMER I've not tested this personally, just wrote it based on previous experience :)
models.py
search_indexes.py
helpers.py