How can I create a search results page in Django 1.11, using PostgreSQL full text search, where the terms searched for are highlighted?
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
- Filter and get the QuerySet needed to annotated
- Annotate using Headline function
- 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')
Headline(F('title'), text_search_query, options='HighlightAll=TRUE')
–
Stocktaking from django.contrib.postgres.search import SearchHeadline
. –
Revocation 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:
Assume that models.py contains an Article model. It has two
TextField
s ('headline'/'content') and aSearchVectorField
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)
In your console/terminal, the following code will work:
query = "book" Article.objects .annotate(v_head=SearchHeadline(F("content"), query)) .filter(content_vector=query)
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".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.
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.
© 2022 - 2024 — McMap. All rights reserved.