## 1) Проблема: “Почему появляются лишние результаты?” {#sec-b345eb02ca5e} С ростом количества записей в моём блоге стало необходимым более систематическое управление контентом. Блог публикуется на нескольких языках, поэтому один и тот же slug может использоваться в разных инстансах, и поиск нужного поста стал слегка затруднительным. С одной стороны – хороший сигнал о росте блога, с другой – поиск нужного материала усложнился. Я обнаружил, что основной поиск Django Admin (`search_fields`) работает **слишком расплывчато**. ![Библиотекарь с волшебной лупой ищет книгу](/media/editor_temp/6/12d4b1c2-0844-485d-b9ef-0e1f766b18ca.png) Когда в `search_fields` добавляется много полей (а это обычная практика при росте функционала), ввод одного слова в строку поиска приводит к тому, что результат появляется в: * заголовке, * описании, * имени пользователя, * даже в каком‑то случайном поле заметок. В итоге результаты не просто “много”, а “переполняют нужный элемент”. При срочном поиске конкретного домена в десятках записей это становится действительно раздражающим. Так родилась идея: **выбирать конкретное поле и искать только в нём**. --- ## 2) Идея решения: синтаксис `field:value` + `shlex` для кавычек {#sec-5381e00c4696} Суть проста. * `name:diff` – указать поле, в котором искать. * `title:"system design"` – поддержать запросы с пробелами. Здесь на помощь приходит `shlex.split()`. Стандартный поиск в админке просто разбивает строку по пробелам, из‑за чего запросы вроде `"system design"` ломаются. `shlex` учитывает кавычки, как в оболочке, и делит строку корректно. Например: * `title:"system design"` → `["title:system design"]` * `name:diff python` → `["name:diff", "python"]` Таким образом **поле‑специфичный поиск** и обычные термы могут смешиваться иtda парситься без проблем. --- ## 3) Объяснение кода {#sec-8edf0b65ccd2} Полный код опубликован в репозитории, откуда его можно просто скопировать. [Ссылка на GitHub](https://github.com/mikihands/djangomixins/blob/main/admin_fieldscopedsearch.py) Ниже – ключевые части. ### `_build_lookup_for`: сохраняем префиксы `search_fields` {#sec-25f0fe79cd1c} В `search_fields` Django поддерживает префиксы: * `=field` – точное совпадение (без учёта регистра), * `^field` – поиск по префиксу, * без префикса – `icontains`. Мы хотим, чтобы эти правила оставались действительными и для нового синтаксиса, поэтому вводим небольшую вспомогательную функцию. ```python 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, остальные – как обычно {#sec-2432c8a5949d} Основная логика реализована в `get_search_results()`. 1. Токенизируем ввод с помощью `shlex`. 2. Делим токены на две группы: * `field:value`, где поле присутствует в `search_fields` → **field_specific_terms**; * всё остальное → **normal_terms**. 3. Поиск по полям комбинируется через **AND**. 4. Обычные термы работают по привычному правилу Admin: каждый термин ищется в нескольких полях через **OR**, а между разными термами – **AND**. ```python 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 ```python 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 ```python 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 = diff` **AND** * `(name OR title OR description содержит python)`. Такой подход сохраняет привычную «чувствительность» поиска в админке, но добавляет возможность точно ограничивать запросы через `field:value`. --- ## 4) Как использовать: достаточно добавить миксин {#sec-0eb3a92e04b6} ```python @admin.register(MyModel) class MyAdmin(FieldScopedSearchMixin, admin.ModelAdmin): search_fields = ['=name', 'title', 'description'] ``` Теперь в строке поиска админки можно вводить: * `name:diff` * `title:"system design"` * `name:diff python` При желании можно переопределить `get_changelist_instance()` и изменить подсказку для пользователей. --- ## 5) Итоги и ограничения: “Удобно, но хочется большего” {#sec-4fc456da292d} Создав этот миксин, я получил возможность **точно регулировать область поиска** в рабочем режиме, что заметно сократило стресс от «переполненных» результатов. Однако есть ограничения: * **Full‑text search** (префикс `@`) пока не поддерживается. Интеграция с PostgreSQL full‑text могла бы добавить умный ранжир, но требует отдельной настройки. * Поиск основан на `icontains`, поэтому при большом объёме данных может возникнуть **проблема производительности** – особенно при множественных OR‑условиях. * В дальнейшем хотелось бы добавить: * Диапазон дат, например `created_at:2026-01-01..2026-01-31`; * Отрицательные условия, типа `status:active,-status:deleted`; * Простейшие сравнения `field>10`. Тем не менее текущая реализация решает именно ту задачу, которая меня беспокоила в работе, и делает админский поиск более предсказуемым и быстрым. **Немного усилий – постоянный прирост эффективности.** Именно в этом, по‑моему, и заключается прелесть программирования. --- **Связанные статьи** - [Почему стоит скрыть админку прямо сейчас](/ko/whitedec/2025/11/10/admin-now-need-to-hide/)