python 41 lines · 9 steps

Ranked full-text search in Django

A Django view that debounces requests per client and ranks Postgres full-text matches by weighted relevance.

Explained by highlit
1from django.contrib.postgres.search import SearchQuery, SearchRank, SearchVector
2from django.core.cache import cache
3from django.db.models import F
4from django.http import JsonResponse
5from django.utils.timezone import now
6from django.views import View
7 
8from .models import Article
9 
10 
11class ArticleSearchView(View):
12 DEBOUNCE_SECONDS = 0.4
13 MIN_QUERY_LENGTH = 3
14 
15 def get(self, request):
16 term = request.GET.get("q", "").strip()
17 if len(term) < self.MIN_QUERY_LENGTH:
18 return JsonResponse({"results": []})
19 
20 client_key = f"search:debounce:{request.session.session_key or request.META['REMOTE_ADDR']}"
21 last_hit = cache.get(client_key)
22 if last_hit and (now().timestamp() - last_hit) < self.DEBOUNCE_SECONDS:
23 return JsonResponse({"results": [], "throttled": True}, status=429)
24 cache.set(client_key, now().timestamp(), timeout=5)
25 
26 query = SearchQuery(term, config="english", search_type="websearch")
27 vector = (
28 SearchVector("title", weight="A", config="english")
29 + SearchVector("summary", weight="B", config="english")
30 + SearchVector("body", weight="C", config="english")
31 )
32 
33 results = (
34 Article.objects.filter(is_published=True)
35 .annotate(rank=SearchRank(vector, query))
36 .filter(rank__gte=0.05)
37 .order_by("-rank", "-published_at")
38 .values("id", "title", slug=F("url_slug"), score=F("rank"))[:20]
39 )
40 
41 return JsonResponse({"results": list(results)})
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Weighted SearchVectors let you rank matches by which field they hit, so title matches outrank body matches.
  2. 2Per-client cache keys with short timeouts implement server-side debouncing to throttle rapid-fire requests.
  3. 3SearchRank annotation plus a threshold filter turns full-text relevance into a sortable, prunable score.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Ranked full-text search in Django — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code