I'm struggling with Django-Haystack.
I need to do an Index that have Articles and Comment articles. My doubt is how can I put in a document based index the Articles and the Comments.
How can I search for keywords in the comments and in the articles and output the article with that keywords(article comments, article)?
It is possible?
Best Regards,
The first thing to do is forget the notion that a SearchIndex must correspond exactly to a model. It's only sourced from one.
The simplest way to do this would be to add the comments to the indexed document using a template. This presume your Article model as a title field:
class ArticleIndex(SearchIndex, indexes.Indexable):
text = CharField(document=True, use_template=True)
title = CharField(model_attr='title')
def get_model(self):
return Article
Note the keyword argument use_template is set to true. The default value for this is search/indexes/{app_label}/{model_name}_{field_name}.txt. In that template just output the content you want to index. E.g.
{{ object.title|safe }}
{{ object.body|safe }}
{% for comment in object.comments.all %}
{{ comment|safe }}
{% endfor %}
While I'm afraid the specific reverse relation name here is probably wrong, that's the gist of what you want to do. Again, this is a simple way of accomplishing what you've specifically stated.
This is what worked for me:
In your models.py, presuming comments are attached to an Article, you want a method that returns comments attached to it (there is no easy way to do this):
class Article:
def comments(self):
ids = [self.id]
ctype = ContentType.objects.get_for_model(Article)
comments = Comment.objects.filter(content_type=ctype,
object_pk__in=ids,
is_removed=False)
return comments
In your search_indexes.py, make sure the ArticleIndex has use_template=True:
from django.contrib.contenttypes.models import ContentType
from django.contrib.comments.models import Comment
class ArticleIndex(SearchIndex):
text = CharField(use_template=True)
In your index template, e.g. templates/search/indexes/article_text.txt:
{% for comment in object.comments.all %}
{{ comment }}
{% endfor %}
Now, the only remaining problem is to update that specific index object when a comment is added or removed. Here we use signals:
In your models.py:
from django.dispatch import receiver
from haystack import site
from django.contrib.comments.signals import (comment_was_posted,
comment_was_flagged)
#receiver(comment_was_posted)
def comment_posted(sender, **kwargs):
site.get_index(Article).update_object(kwargs['comment'].content_object)
#receiver(comment_was_flagged)
def comment_flagged(sender, **kwargs):
site.get_index(Article).update_object(kwargs['comment'].content_object)
Related
I hope you're well,
I've created a TextChoices models for my UserProfile. If display data {{ user.userprofile.smiley }} I got the code 🍆 but not 🍆. Do you have any idea?
I've added this to my settings.py (DB) 'OPTIONS': {'charset': 'utf8mb4'}, the issue is still here. Also change my MYSQL smiley field to utf8mb4_bin...
class Smileys(models.TextChoices):
AUBERGINE = '🍆'
AVOCADO = '🥑'
BROCCOLI = '🥦'
...
class UserProfile(models.Model):
smiley = models.CharField(max_length=20,choices=Smileys.choices,default=Smileys.CHERRY,)
...
Use the safe template filter when you want to disable HTML escaping for a string
{{ user.userprofile.smiley|safe }}
You may expose yourself to XSS attacks if you use this filter on malicious user provided data so be careful
I have an entity reference field inside a (parent-)paragraph, which references multiple child-paragraphs.
Is it possible to access field values of the referencing paragraph in the children's (referenced paragraph's) twig templates?
Actually I'm just trying to count the total referenced items within one of the referenced item's twig templates itself. So I want to count its siblings + 1, if you want.
I'm aware of the fact that I could preprocess this in a module, but I would like to know if this is possible in twig.
In twig:
{% set paragraph_parent = paragraph.getParentEntity() %}
{% set width = paragraph_parent.field_width.0.value %}
<div class="{{ width }}">{{ content.field_images }}</div>
Since there is no response to this question I have to assume that this is not possible in Twig, but wanted to quick share the mentioned workaround via module...
getParentEntity()
is your friend.
A short example for making the referenced element's count available...
/* implements hook_preprocess_paragraph() (paragraph type product_teaser) */
function mymodule_preprocess_paragraph__product_teaser(&$variables) {
/* makes paragraph siblings count (+ 1/self) available to template */
$siblings_total = 1;
$paragraph = $variables['paragraph'];
$parent_paragraph = $paragraph->getParentEntity();
if ( ( isset($parent_paragraph) ) && ( $parent_paragraph->hasField('field_paragraph_reference') ) ) {
$field_content = $parent_paragraph->get('field_paragraph_reference')->getValue();
if ( isset($field_content[0]['target_id']) ) {
$siblings_total = count($field_content);
}
}
$variables['mymodule_theming_sources']['siblings_total'] = $siblings_total;
}
Another method using twig with 2 simple lines. It works fine.
{% set parent = paragraph._referringItem.parent.parent.entity %}
{{ parent.title.value }}
In my Drupal 8 setup, I have two languages configured (German: default, English). Not all pages have a translation into English, but they show up in the navigation.
I'd like to highlight those menu items that link to pages that don't have a translation in the currently selected language.
So how can I do this in Twig? When I dump the menu item, I see an object of class MenuLinkContent that has a field entity which might contain the answer:
object(Drupal\menu_link_content\Plugin\Menu\MenuLinkContent)[31277]
[...]
protected 'entity' =>
object(Drupal\menu_link_content\Entity\MenuLinkContent)[31407]
[...]
protected 'translations' =>
array (size=2)
'x-default' =>
array (size=2)
...
'en' =>
array (size=2)
...
[...]
But I don't seem to be able to actually read those values.
It is easy, first, you have to add your current language to any variable by yourtheme_preprocess
yourtheme_preprocess(&$vars, $hook)
{
$language = \Drupal::languageManager()->getCurrentLanguage()->getId();
$vars['langcode'] = $language;
}
and now in your twig template, you have to check your entity if has a translation,
{% if node.hasTranslation(langcode) %}
{% set node = node.getTranslation(langcode) %}
{% endif %}
I'm using Haystack and Whoosh to search a custom app with city data from the Geonames project.
I only have a small amount of the Geonames city data imported (22917 records). I'd like to order the results by a city's population and I'm having trouble getting good results.
When I use order_by on my SearchQuerySet, the results are extremely slow. It also orders properly with against the 'name' field but not 'population', so I think I'm probably just doing something wrong.
Here's the search index:
class EntryIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
name = indexes.CharField(indexed=False, model_attr='ascii_name')
population = indexes.CharField(indexed=False, model_attr='population')
django_id = indexes.CharField(indexed=False, model_attr='id')
def get_model(self):
return Entry
def index_queryset(self):
return self.get_model().objects.all()
Here's the template:
{{ object.ascii_name }}
{{ object.alternate_names }}
{{ object.country.name }}
{{ object.country.iso }}
{{ object.admin1_division.ascii_name }}
{{ object.admin1_division.name }}
{{ object.admin1_division.code }}
{{ object.admin2_division.ascii_name }}
{{ object.admin2_division.name }}
Here's the relevant view code:
query = request.GET.get('q', '')
results = SearchQuerySet().models(Entry).auto_query(query).order_by('population')
When I take the order_by off the query, it returns in less than one second. With it on, it takes almost 10 seconds to complete, and the results are not ordered by population. Ordering by name works, but it also takes ~10 seconds.
Note: I've also tried with the built-in Haystack search view, and it's very slow when I try to order by population:
qs = SearchQuerySet().order_by('-population')
urlpatterns = patterns('',
...
url(r'^demo2/$', SearchView(searchqueryset=qs)),
)
I'm doing nearly the same thing, and ordering works fast and correctly for me.
The only thing you're doing that differs significantly is:
query = request.GET.get('q', '')
results = SearchQuerySet().models(Entry).auto_query(query).order_by('population')
Since you specify a request, I'm assuming you've created your own view. You shouldn't need a custom view. I have this implemented with this in my urls.py:
from haystack.forms import ModelSearchForm
from haystack.query import SearchQuerySet
from haystack.views import SearchView, search_view_factory
sqs = SearchQuerySet().models(MyModel).order_by('-weight')
urlpatterns += patterns('',
url(r'^search/$', search_view_factory(
view_class=SearchView,
template='search/search.html',
searchqueryset=sqs,
form_class=ModelSearchForm
), name='search'),
)
I found I could not order results using order_by either. I was getting what seemed like a strange partial sorting. I eventually realised that the default ordering was by relevance ranking. The order_by I was using was presumably only sorting within each rank. This point is not really brought out in the Haystack documentation.
I guess the lesson is probably that if you want your results order to ignore relevance you need to post process your results before displaying them.
Probably a bit off topic, but I was a little surprised your index population field is a CharField. Does this match with your model?
I know I'm three years late, but recently I faced the same issue with a project I've been given.
I guess the only problem is the indexed=False parameter you are passing to the population CharField.
I fixed my problem by removing that.
I'm using Haystack and Whoosh with Django
Within search_index.py I have this
class PageIndex(RealTimeSearchIndex):
text = CharField(document=True, use_template=True)
creator = CharField(model_attr='creator')
created = DateTimeField(model_attr='created')
org = CharField(model_attr='organisation')
site.register(Page, PageIndex)
My template looks like this
{{ object.name }}
{{ object.description }}
{{ object.template|striptags }}
{% for k,v in object.get_variables.items %}
{{ v }}
{% endfor %}
If I save the Page with an updated name or description then it updates straight away and includes the variables from get_variables.items in the template. However if I update just the variable then it doesn't update.
Is it because variable is another object that's related to it and even though I am saving on the same page it does not pick up a change to the Page? If so how do I force to update the Page item when I'm updating related objects?
I concur with Daniel Hepper, but I think the easiest solution here is to attach a listener to your related model's post_save signal (see https://docs.djangoproject.com/en/dev/topics/signals/) and in that, reindex the model.
E.g, in myapp/models.py, given model MyRelatedModel which has a foreignkey to MyModel
from myapp.search_indexes import MyModelIndex
def reindex_mymodel(sender, **kwargs):
MyModelIndex().update_object(kwargs['instance'].mymodel)
models.signals.post_save.connect(reindex_mymodel, sender=MyRelatedModel)
A RealTimeSearchIndex only updates the search index when a model it is registered on is saved or deleted, or to be more precise, when the post_save/post_delete signal of the model is emitted. These signals are not emitted if a related model is deleted/saved or when a bulk update/delete operation is executed.
To solve your problem, you could create a subclass of RealTimeSearchIndex that also updates the index on post_save/post_delete signals of the related model.
Just a note for more recent viewers of this post ---- RealTimeSearchIndex has been deprecated.
See here for the Haystack post about it.
For recent viewers, here's a solution based on the new RealtimeSignalProcessor:
In myapp/signals.py:
class RelatedRealtimeSignalProcessor(RealtimeSignalProcessor):
def handle_save(self, sender, instance, **kwargs):
if hasattr(instance, 'reindex_related'):
for related in instance.reindex_related:
related_obj = getattr(instance, related)
self.handle_save(related_obj.__class__, related_obj)
return super(RelatedRealtimeSignalProcessor, self).handle_save(sender, instance, **kwargs)
def handle_delete(self, sender, instance, **kwargs):
if hasattr(instance, 'reindex_related'):
for related in instance.reindex_related:
related_obj = getattr(instance, related)
self.handle_delete(related_obj.__class__, related_obj)
return super(RelatedRealtimeSignalProcessor, self).handle_delete(sender, instance, **kwargs)
In settings.py:
HAYSTACK_SIGNAL_PROCESSOR = 'myapp.signals.RelatedRealtimeSignalProcessor'
In models.py:
class Variable(models.Model):
reindex_related = ('page',)
page = models.ForeignKey(Page)
Now when a Variable is saved, the index for the related Page will also be updated.
(TODO: This doesn't work for extended relationships like foo__bar, or for many-to-many fields. But it should be straightforward to extend it to handle those if you need to.)