1) Проблема: “Почему появляются лишние результаты?”
С ростом количества записей в моём блоге стало необходимым более систематическое управление контентом. Блог публикуется на нескольких языках, поэтому один и тот же slug может использоваться в разных инстансах, и поиск нужного поста стал слегка затруднительным. С одной стороны – хороший сигнал о росте блога, с другой – поиск нужного материала усложнился.
Я обнаружил, что основной поиск Django Admin (search_fields) работает слишком расплывчато.

Когда в search_fields добавляется много полей (а это обычная практика при росте функционала), ввод одного слова в строку поиска приводит к тому, что результат появляется в:
- заголовке,
- описании,
- имени пользователя,
- даже в каком‑то случайном поле заметок.
В итоге результаты не просто “много”, а “переполняют нужный элемент”. При срочном поиске конкретного домена в десятках записей это становится действительно раздражающим.
Так родилась идея: выбирать конкретное поле и искать только в нём.
2) Идея решения: синтаксис field:value + shlex для кавычек
Суть проста.
name:diff– указать поле, в котором искать.title:"system design"– поддержать запросы с пробелами.
Здесь на помощь приходит shlex.split(). Стандартный поиск в админке просто разбивает строку по пробелам, из‑за чего запросы вроде "system design" ломаются. shlex учитывает кавычки, как в оболочке, и делит строку корректно.
Например:
title:"system design"→["title:system design"]name:diff python→["name:diff", "python"]
Таким образом поле‑специфичный поиск и обычные термы могут смешиваться иtda парситься без проблем.
3) Объяснение кода
Полный код опубликован в репозитории, откуда его можно просто скопировать.
Ниже – ключевые части.
_build_lookup_for: сохраняем префиксы search_fields
В search_fields Django поддерживает префиксы:
=field– точное совпадение (без учёта регистра),^field– поиск по префиксу,- без префикса –
icontains.
Мы хотим, чтобы эти правила оставались действительными и для нового синтаксиса, поэтому вводим небольшую вспомогательную функцию.
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'
Например, если в admin.py указано search_fields = ['=name', 'title'], то:
nameбудет искать черезiexact,title– черезicontains.
То же самое применяется к запросам вида field:value.
get_search_results: поле‑специфичные термы объединяются через AND, остальные – как обычно
Основная логика реализована в get_search_results().
- Токенизируем ввод с помощью
shlex. - Делим токены на две группы:
*
field:value, где поле присутствует вsearch_fields→ field_specific_terms; * всё остальное → normal_terms. - Поиск по полям комбинируется через AND.
- Обычные термы работают по привычному правилу Admin: каждый термин ищется в нескольких полях через OR, а между разными термами – 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 превратится в name == diff AND status == active.
(2) Обычные термы – (поле OR) → term AND term
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)
Пример name:diff python будет интерпретирован как:
name = diffAND(name OR title OR description содержит python).
Такой подход сохраняет привычную «чувствительность» поиска в админке, но добавляет возможность точно ограничивать запросы через field:value.
4) Как использовать: достаточно добавить миксин
@admin.register(MyModel)
class MyAdmin(FieldScopedSearchMixin, admin.ModelAdmin):
search_fields = ['=name', 'title', 'description']
Теперь в строке поиска админки можно вводить:
name:difftitle:"system design"name:diff python
При желании можно переопределить get_changelist_instance() и изменить подсказку для пользователей.
5) Итоги и ограничения: “Удобно, но хочется большего”
Создав этот миксин, я получил возможность точно регулировать область поиска в рабочем режиме, что заметно сократило стресс от «переполненных» результатов.
Однако есть ограничения:
- Full‑text search (префикс
@) пока не поддерживается. Интеграция с PostgreSQL full‑text могла бы добавить умный ранжир, но требует отдельной настройки. - Поиск основан на
icontains, поэтому при большом объёме данных может возникнуть проблема производительности – особенно при множественных OR‑условиях. - В дальнейшем хотелось бы добавить:
- Диапазон дат, например
created_at:2026-01-01..2026-01-31; - Отрицательные условия, типа
status:active,-status:deleted; - Простейшие сравнения
field>10.
Тем не менее текущая реализация решает именно ту задачу, которая меня беспокоила в работе, и делает админский поиск более предсказуемым и быстрым.
Немного усилий – постоянный прирост эффективности. Именно в этом, по‑моему, и заключается прелесть программирования.
Связанные статьи
Комментариев нет.