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 fastapi import FastAPI, WebSocket, WebSocketDisconnect app = FastAPI()
Building a WebSocket chat with FastAPI
websockets
broadcast
connection-management
Intermediate
9 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
javascript
import { useState, useEffect, useCallback, useRef } from 'react'; const cache = new Map(); const inflight = new Map();
Building a stale-while-revalidate hook in React
caching
request-deduplication
custom-hooks
Advanced
10 steps
python
import random from typing import Iterator, List
How reservoir sampling picks k items
reservoir-sampling
streaming
randomness
Intermediate
5 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
python
import secrets from django.contrib.auth import authenticate, login from django.core.cache import cache
Two-factor login with OTP in Django
two-factor-auth
one-time-passwords
caching
Intermediate
9 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.