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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Weighted SearchVectors let you rank matches by which field they hit, so title matches outrank body matches.
- 2Per-client cache keys with short timeouts implement server-side debouncing to throttle rapid-fire requests.
- 3SearchRank annotation plus a threshold filter turns full-text relevance into a sortable, prunable score.
Related explainers
python
from flask import Blueprint, jsonify, request, abort v1 = Blueprint("users_v1", __name__) v2 = Blueprint("users_v2", __name__)
Versioning a Flask API with Blueprints
api versioning
blueprints
rest
Intermediate
7 steps
python
import time import threading from enum import Enum from functools import wraps
Building a circuit breaker in Python
circuit-breaker
resilience
decorators
Advanced
7 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
python
import os from datetime import timedelta
Class-based config in a Flask app factory
configuration
app-factory
inheritance
Intermediate
7 steps
python
import time from flask import Flask, g, request
Timing requests with Flask hooks
middleware
request-lifecycle
instrumentation
Intermediate
5 steps
go
func ServeVideo(c *gin.Context) { id := c.Param("id") video, err := videoRepo.FindByID(c.Request.Context(), id) if err != nil {
Streaming video files with Gin
http streaming
range requests
file serving
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/ranked-full-text-search-in-django-explained-python-8ca0/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.