1) Het probleem: “Waarom krijg ik zoveel onnodige resultaten?”
Naarmate mijn blog groeide, werd het steeds lastiger om de juiste posts snel te vinden. Omdat de blog meertalig is, bestaan er verschillende instanties met dezelfde slug, waardoor een eenvoudige zoekopdracht vaak te veel resultaten oplevert.
De standaard zoekfunctie van Django Admin (search_fields) is hier te vaag.

Wanneer je veel velden in search_fields opneemt, gebeurt er het volgende zodra je een woord intypt:
- Het wordt gevonden in de titel
- Het wordt gevonden in de beschrijving
- Het wordt gevonden in de gebruikersnaam
- Het wordt zelfs gevonden in ongerelateerde notitie‑velden
In plaats van een gerichte lijst krijg je een overdaad aan resultaten, waardoor het zoeken naar één specifiek item vaak frustrerend is.
Daarom begon ik na te denken over een manier om slechts één veld te selecteren voor de zoekopdracht.
2) Oplossingsidee: field:value‑syntaxis + shlex voor aanhalingstekens
Het concept is simpel:
- Zoek met een opgegeven veld, bv.
name:diff - Ondersteun zoektermen met spaties, bv.
title:"system design"
shlex.split() komt hierbij goed van pas. De admin‑zoekbalk splitst normaal gesproken op spaties, waardoor "system design" verkeerd wordt geïnterpreteerd. Met shlex wordt de invoer echter behandeld als een shell‑commando, waardoor aanhalingstekens gerespecteerd worden.
Voorbeelden:
title:"system design"→["title:system design"]name:diff python→["name:diff", "python"]
Zo kun je zowel veld‑specifieke zoekopdrachten als algemene termen elegant combineren.
3) Code‑uitleg
De volledige implementatie staat in de repository. Kopieer‑en‑plak is voldoende.
[GitHub‑repo] (https://github.com/mikihands/djangomixins/blob/main/admin_fieldscopedsearch.py)
_build_lookup_for: respecteer de prefixen uit search_fields
search_fields ondersteunt prefixen:
=field→ exacte (case‑insensitive) match^field→ prefix‑match- Standaard →
icontains
De helper _build_lookup_for behoudt dit gedrag ook voor de nieuwe syntaxis:
def _build_lookup_for(self, raw_field):
if raw_field.startswith('='):
return raw_field.lstrip('=^@'), '__iexact'
if raw_field.startswith('^'):
return raw_field.lstrip('=^@'), '__istartswith'
return raw_field.lstrip('=^@'), '__icontains'
Zo werkt search_fields = ['=name', 'title'] nog steeds als:
name→iexacttitle→icontains
get_search_results: veld‑specifiek = AND, algemene termen = OR/AND
De kernlogica zit in get_search_results():
- Tokeniseer de invoer met
shlex - Splits tokens in twee groepen
*
field:valuewaarbijfieldinsearch_fields→ field_specific_terms * Alles andere → normal_terms - Veld‑specifieke termen worden gecumuleerd met AND
- Algemene termen volgen de standaard Admin‑logica (elke term wordt OR over alle velden, termen onderling AND)
terms = shlex.split(search_term or "")
field_specs = [self._build_lookup_for(f) for f in getattr(self, 'search_fields', [])]
field_lookup_map = {}
for name, lookup in field_specs:
field_lookup_map.setdefault(name, lookup)
field_specific_terms = []
normal_terms = []
for t in terms:
if ':' in t:
field, value = t.split(':', 1)
if field in field_lookup_map and value != '':
field_specific_terms.append((field, value))
else:
normal_terms.append(t)
else:
normal_terms.append(t)
(1) field:value → AND
for field, value in field_specific_terms:
lookup = field_lookup_map[field]
qs = qs.filter(Q(**{f"{field}{lookup}": value}))
name:diff status:active wordt dus name = diff AND status = active.
(2) Algemene termen → (veld OR) per term, termen AND
if normal_terms and field_lookup_map:
for term in normal_terms:
term_q = Q()
for field, lookup in field_lookup_map.items():
term_q |= Q(**{f"{field}{lookup}": term})
qs = qs.filter(term_q)
Voor name:diff python betekent dit:
name = diffAND(name OR title OR description) bevat python
Deze aanpak behoudt de vertrouwde Admin‑zoekervaring, terwijl je met field:value heel gericht kunt filteren.
4) Gebruik: alleen de mixin toevoegen
@admin.register(MyModel)
class MyAdmin(FieldScopedSearchMixin, admin.ModelAdmin):
search_fields = ['=name', 'title', 'description']
Nu kun je in de admin‑zoekbalk bijvoorbeeld invoeren:
name:difftitle:"system design"name:diff python
Wil je de help‑tekst aanpassen, dan kun je get_changelist_instance() overschrijven.
5) Reflectie & beperkingen
Met deze mixin kun je de zoekresultaten precies afstemmen op je operationele behoeften – een enorme tijdsbesparing in de dagelijkse administratie.
Beperkingen:
- Full‑text search (
@‑prefix) wordt niet ondersteund. Een PostgreSQL‑full‑text‑index zou hier een flinke boost kunnen geven, maar brengt extra configuratie met zich mee. - Omdat de queries vooral
icontainsgebruiken, kan de performance afnemen bij grote datasets of complexe OR‑combinaties. - Toekomstige uitbreidingen die ik graag zie:
- Datum‑range zoekopdrachten, bv.
created_at:2026-01-01..2026-01-31 - Negatieve filters, bv.
status:active,-status:deleted - BLE‑achtige vergelijkingsoperatoren, bv.
field>10
Voor nu levert de mixin een kleine, maar zeer nuttige verbetering. Een paar extra regels code die de frustratie van ondoelmatige zoekopdrachten wegnemen, maken het werk een stuk aangenamer – en dat is precies wat programmeren zo bevredigend maakt.
Gerelateerde artikelen