Highlight search terms on a Django/PostgreSQL search results page
Asked Answered
G

4

10

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

Giffy answered 24/9, 2017 at 0:44 Comment(2)
Let me make sure that I understand what you're asking for. Basically, like in this screen right here, if you're in Chrome and you do cmd/ctrl + f and search "ask" and get a bunch of things yellow highlighted. You want that same appearance, but you want the appearance to be a list view?Crosier
Correct. What is discussed in this article: alistapart.com/article/searchhighlightGiffy
M
17

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')
Metabolism answered 29/10, 2017 at 19:7 Comment(3)
Nice. If you want the whole string, not just the extract, use Headline(F('title'), text_search_query, options='HighlightAll=TRUE')Stocktaking
awesome content. really helped me. ThanksQuotation
This answer is no longer current. As of Django 3.1, there is a SearchHeadline class, via from django.contrib.postgres.search import SearchHeadline.Revocation
S
0

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

Shiism answered 31/3, 2021 at 15:4 Comment(0)
R
0

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.

Revocation answered 11/7, 2021 at 14:4 Comment(0)
H
0

If you're not against a a bit of JavaScript, you can do it on the client-side by leveraging the CSS Custom Highlight API. Modern browsers offer a native way to highlight search terms by creating a Range and adding it to a Highlight.

const contentNode = document.getElementById("content");

// Create ranges where you want to highlight text
const range1 = new Range();
range1.setStart(contentNode, 10);
range1.setEnd(contentNode, 20);

const range2 = new Range();
range2.setStart(contentNode, 40);
range2.setEnd(contentNode, 60);

// Create a Highlight with these ranges
const highlight = new Highlight(range1, range2);

// Add this highlight to the HighlightRegistry
CSS.highlights.set("search-result", user1Highlight);

You can now style the highlight in CSS:

::highlight(search-result) {
    background-color: yellow;
    color: black;
}

The actual search of the terms can be done server-side or client-side.

The advantage of this method is that it delivers a clean DOM, which is super useful when you have to highlight text in a contentEditable element for instance.

Haffner answered 18/4 at 10:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.