1) Problemstellung: „Warum kommen mir so viele unnütze Ergebnisse entgegen?“
Als die Anzahl der Beiträge in meinem Blog wuchs, wurde eine strukturierte Verwaltung unverzichtbar. Da mein Blog mehrsprachig ist, nutzen verschiedene Instanzen sogar dieselben Slugs, was das gezielte Auffinden einzelner Posts zunehmend erschwert. Das ist zwar ein Zeichen für wachsenden Content, aber die Suche wird knifflig.
Das eigentliche Hindernis liegt im Standard‑Suchfeld von Django‑Admin (search_fields). Die Suchlogik ist zu vage.

Wenn man viele Felder in search_fields definiert (und das passiert mit zunehmender Funktionalität sowieso), Telescope folgendes passiert, sobald man ein Wort eingibt:
- Es trifft den Titel
- Es trifft die Beschreibung
- Es trifft den Benutzernamen
- Es trifft sogar ein beliebiges Notizfeld
Die Ergebnisse gehen von "zu viele" zu "meine gesuchte Information wird überdeckt" über. Gerade wenn man im hades Notfall einen bestimmten Domain‑Eintrag finden muss und das Ergebnis sich über Dutzende Einträge erstreckt, kann das ziemlich nervig werden.
Daher kam die Idee: "Ich möchte ein einzelnes Feld gezielt durchsuchen können."
2) Lösungsansatz: field:value‑Syntax + shlex für Anführungszeichen
Die Idee ist simpel:
- Suche mit Feldangabe, z. B.
name:diff - Unterstütze Suchbegriffe mit Leerzeichen, z. B.
title:"system design"
Hier kommt shlex.split() ins Spiel. Das Admin‑Suchfeld teilt normalerweise einfach nach Leerzeichen, wodurch Eingaben wie "system design" zerstört werden. shlex respektiert Anführungszeichen ähnlich einer Shell und erzeugt korrekte Tokens.
Beispiele:
title:"system design"→["title:system design"]name:diff python→["name:diff", "python"]
Damit lassen sich feldspezifische Suchbegriffe und normale Suchbegriffe elegant kombinieren.
3) Code‑Erklärung
Der komplette Code liegt im Repository und kann direkt kopiert werden.
[GitHub‑Repository] (https://github.com/mikihands/djangomixins/blob/main/admin_fieldscopedsearch.py)
Im Blog beschränke ich mich auf die wichtigsten Punkte.
_build_lookup_for: search_fields‑Präfixe unverändert übernehmen
search_fields unterstützt Präfixe:
=field→ exakte Übereinstimmung (case‑insensitive)^field→ Präfix‑Match- Standard →
icontains
Der Helper entscheidet anhand des Präfixes, welchen Lookup‑Typ er verwendet:
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'
Setzt man in admin.py search_fields = ['=name', 'title'], gilt:
name→iexacttitle→icontains
Und genau dieselbe Lookup‑Logik wird auch bei field:value angewendet.
get_search_results: Feldspezifisch = AND, normale Begriffe = wie gehabt
Der Kern liegt in get_search_results():
- Tokenisierung mittels
shlex - Aufteilung in zwei Gruppen
*
field:value– wenn das Feld insearch_fieldsexistiert → field_specific_terms * alles andere → normal_terms - Feldspezifische Begriffe werden mit AND kombiniert
- Normale Begriffe folgen dem gewohnten Admin‑Verhalten (OR über alle Felder, AND zwischen den Begriffen)
Wesentliche Ausschnitte:
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)
Ungültige Feldnamen oder leere Werte werden einfach zu den normalen Suchbegriffen hinzugefügt.
(1) field:value wird AND‑weise angerechnet
for field, value in field_specific_terms:
lookup = field_lookup_map[field]
qs = qs.filter(Q(**{f"{field}{lookup}": value}))
name:diff status:active wird zu name==diff AND status==active.
(2) Normale Begriffe: (Feld OR) → Begriffe 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)
Beispiel: name:diff python
name=diffAND(name OR title OR description) contains python
Damit bleibt das vertraute Admin‑Suchgefühl erhalten, während field:value gezielt eingrenzt.
4) Einsatz: Einfach das Mixin einbinden
@admin.register(MyModel)
class MyAdmin(FieldScopedSearchMixin, admin.ModelAdmin):
search_fields = ['=name', 'title', 'description']
Jetzt sind im Admin‑Suchfeld folgende Eingaben möglich:
name:difftitle:"system design"name:diff python
Die Hilfetexte können Sie bei Bedarf über get_changelist_instance() anpassen.
5) Rückblick & Grenzen: "Praktisch, aber ich will mehr"
Das Mixin hat mir ermöglicht, die Suche exakt nach meinen Bedürfnissen zu steuern – ein echter Gewinn im täglichen Betrieb.
Einschränkungen gibt es jedoch:
- Full‑Text‑Search (@‑Präfix) wird nicht behandelt. Eine Anbindung an PostgreSQL‑Full‑Text‑Search wäre möglich, würde aber weitere Konfigurationen (Gewichtung, tsvector) erfordern.
- Da das Vorgehen auf
icontainsbasiert, können bei großen Datenmengen Performance‑Probleme auftreten, besonders wenn viele Felder per OR abgefragt werden. - Zukünftige Ideen:
- Datumsbereichssuche, z. B.
created_at:2026-01-01..2026-01-31 - Negativbedingungen, z. B.
status:active,-status:deleted - Einfache Vergleichsoperatoren, z. B.
field>10
Für den Moment ist das Mixin jedoch klein, präzise und erhöht die Produktivität merklich – genau das, was ich im laufenden Betrieb brauchte.
Ein bisschen extra Aufwand für Idee und Umsetzung kann die Arbeitsabläufe dauerhaft verbessern – das ist doch das Schöne am Coden.
Verwandte Beiträge