Highlight search terms on a Django/PostgreSQL search results page

Viewed 2321

How can I create a search results page in Django 1.11, using PostgreSQL full text search, where the terms searched for are highlighted?

3 Answers

Even though Django doesn't support ts_headline feature from postgresql, You can manually apply it as a Function on a QuerySet to annotate:


We need additional function to operate with django ORM. Here is a sample for ts_headline. [original_source for this sample function is linked here]

Headline function sample:

from django.db import models
from django.contrib.postgres.search import Value, Func


class Headline(Func):
    function = 'ts_headline'

    def __init__(self, field, query, config=None, options=None, **extra):
        expressions = [field, query]
        if config:
            expressions.insert(0, Value(config))
        if options:
            expressions.append(Value(options))
        extra.setdefault('output_field', models.TextField())
        super().__init__(*expressions, **extra)

Using the above function you can use it on a QuerySet to annotate

Example Model Definition

class Video(Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(max_length=128, verbose_name="Title")

Steps for getting highlighted search results on model title

  1. Filter and get the QuerySet needed to annotated
  2. Annotate using Headline function
  3. Get Values of your document

Filtering Objects

Video.objects.filter(filter_query)

filter_query is a Q() over title filter_query = Q(title__contains=term)


Annotation with Headline data

Video.objects.filter(filter_query).annotate(title_highlight=Headline(F('title'), text_search_query))

ts_headline directly take the input from the document rather than from ts_vector, So we have to pass the information about which field it should access and what SearchQuery it should perform on it.

text_Search_query is SearchQuery Object with same input as the filter_query text_search_query = SearchQuery(term)

Now after annotation, this queryset with include a extra field in all objects called title_highlight which would contain the result you wanted like:

these <b>loans</b> not being repaired


Get the values from the annotation field

using values_list over the QuerySet you can get the values from these annotated fields.

final code:

Video.objects.filter(filter_query).annotate(title_highlight=Headline(F('title'), text_search_query)).values_from('title','title_highlight')

In Django 3.1, there is now a SearchHeadline class which makes this task much simpler.

The question asks about Django 1.11. Things have changed, as there is a SearchHeadline class in Django 3.1.

I've not noticed much code on this in Stack Overflow, so consider the following:

  1. Assume that models.py contains an Article model. It has two TextFields ('headline'/'content') and a SearchVectorField for the content:

    from django.contrib.postgres.search import SearchVector, SearchVectorField, SearchHeadline
    from django.db.models import F, Q
    
    class Article(models.Model):
    headline = models.TextField()
    content = models.TextField()
    content_vector = SearchVectorField(null=True)
    
  2. In your console/terminal, the following code will work:

    query = "book"
    
    Article.objects
    .annotate(v_head=SearchHeadline(F("content"), query))
    .filter(content_vector=query)
    
  3. There are two parts to the above - the annotation using SearchHeadline to annotate a v_head 'column', then the filter itself against the query for "book".

  4. Assuming that the text was "Lorem ipsum book lorem ipsum", the output will be:

    Lorem ipsum <b>book</b> lorem ipsum.

You can see other similar code on Github.

Related