Админ-панель — это первая «система управления» вашего проекта, и чем удобнее она настроена, тем быстрее вы можете работать с данными. В этом уроке мы научимся добавлять в админку два ключевых инструмента:
- панель поиска
- панель фильтрации
Оба инструмента делают работу с большими таблицами комфортной: можно находить фильмы по названию, искать все фильмы конкретного режиссёра или выводить только опубликованные записи.
Мы будем делать всё постепенно, с большим количеством пояснений, проверок и практики.
Представьте, что у нас в модели Movie есть следующие поля:
class Movie(models.Model):
title = models.CharField(max_length=255)
description = models.TextField()
year = models.PositiveIntegerField()
is_published = models.BooleanField(default=False)
category = models.ForeignKey('Category', on_delete=models.SET_NULL, null=True)Чтобы админка позволяла искать фильмы по названию, мы добавим атрибут search_fields.
@admin.register(Movie)
class MovieAdmin(admin.ModelAdmin):
list_display = ('title', 'year', 'is_published', 'category')
search_fields = ['title']- Переходим в админ-панель → раздел «Фильмы».
- Сверху появится поле ввода: Search.
- Вводим первые буквы фильма → жмём Enter.
- Отображается только то, что соответствует поиску.
Это уже делает админку намного удобнее, особенно при больших таблицах.
Очень частая ошибка новичков:
search_fields = ['title', 'category'] ❌Django не знает, что значит «искать по категории». Он понимает только реальные поля базы данных.
Чтобы искать по связанным объектам, мы используем двойное подчеркивание (__):
search_fields = ['title', 'category__name'] # ✔️ правильноcategory — это объект
category.name — это поле в БД
А category__name — это путь до этого поля, понятный Django ORM.
Иногда полезно искать не по всему тексту, а только по началу строки.
Например, вы вводите Аве, и хотите найти только Аватар, но не Великая авантюра.
Это делается с помощью lookup-выражений:
search_fields = ['title__startswith']Другие полезные lookup'ы:
__icontains— поиск без учета регистра__endswith— поиск по окончанию строки
Это делает поиск гибким и точным.
Фильтры появляются справа в админке и позволяют быстро отбирать данные по полям.
Самые частые фильтры:
- булево поле (например, опубликован / нет)
- категории
- даты
- внешние ключи
- собственные фильтры (кастомные)
@admin.register(Movie)
class MovieAdmin(admin.ModelAdmin):
list_display = ('title', 'year', 'is_published', 'category')
search_fields = ['title']
list_filter = ['category', 'is_published']- Справа появляется блок «FILTER».
- В нём — список доступных фильтров.
- Нажимаем «Категория → Комедия» — отображаются только комедии.
- Нажимаем «Статус → Published» — фильмы, включенные на сайт.
Иногда фильтров по умолчанию недостаточно.
Допустим, мы хотим фильтр:
- «Показать только новые фильмы»
- «Показать фильмы старше 2000 года»
- «Показать фильмы без категории»
Создадим простой кастомный фильтр.
- Создаём класс фильтра:
class YearFilter(admin.SimpleListFilter):
title = 'Год выпуска'
parameter_name = 'release_year'
def lookups(self, request, model_admin):
return [
('new', 'Новые (>= 2000)'),
('old', 'Старые (< 2000)'),
]
def queryset(self, request, queryset):
if self.value() == 'new':
return queryset.filter(year__gte=2000)
if self.value() == 'old':
return queryset.filter(year__lt=2000)
return queryset- Подключаем его:
list_filter = [YearFilter, 'category', 'is_published']SimpleListFilter — встроенный инструмент Django, который:
- отвечает за отображение фильтра справа в админке,
- показывает список вариантов (например, “Новые”, “Старые”),
- получает выбранное значение,
- возвращает отфильтрованный queryset.
Django использует этот класс для расширения стандартной панели фильтрации.
Это название фильтра, которое будет показано в правой панели админки.
parameter_name = 'release_year'Очень важный параметр.
Он определяет имя параметра в URL, например:
/admin/cinemahub/movie/?release_year=new
/admin/cinemahub/movie/?release_year=old
- Django читает этот параметр из request.GET.
- Передаёт его в метод
self.value(). - И позволяет фильтру понять, что выбрал пользователь.
Если его не указать → Django не сможет отличить этот фильтр от других.
def lookups(self, request, model_admin):
return [
('new', 'Новые (>= 2000)'),
('old', 'Старые (< 2000)'),
]Этот метод встроенный и обязателен.
Django вызывает его автоматически, чтобы узнать:
- какие кнопки показывать в списке фильтров,
- какие значения они передают при выборе.
Каждая строка — пара: ('значение, передаваемое в URL', 'текст, отображаемый в панели')
Например:
('new', 'Новые (>= 2000)')
При клике Django сделает:
?release_year=new
def queryset(self, request, queryset):
if self.value() == 'new':
return queryset.filter(year__gte=2000)
if self.value() == 'old':
return queryset.filter(year__lt=2000)
return querysetЭтот метод тоже встроенный и обязателен.
Django вызывает его каждый раз, когда нужно сформировать список объектов в админке.
self.value() - это встроенный метод, который возвращает:
- значение, выбранное пользователем,
- то самое значение, которое мы указали в
lookups().
Например:
| Пользователь нажал | URL параметр | self.value() |
|---|---|---|
| Новые | ?release_year=new | "new" |
| Старые | ?release_year=old | "old" |
| Ничего не выбрано | параметра нет | None |
- Django автоматически «достаёт» параметр из URL,
- принимает его значение,
- передаёт в фильтр.
Ты не пишешь request.GET вручную — Django делает это за тебя.
Метод queryset() должен вернуть:
- изменённый queryset (если есть фильтр),
- исходный queryset (если пользователь не выбирал фильтр).
Если ничего не нажато →
self.value() is None
и выполняется return queryset.
- Django создаёт экземпляр фильтра.
- Django вызывает
lookups()→ рисует список вариантов. - Пользователь выбирает вариант → появляется URL параметр.
- Django вызывает
queryset()→ фильтрует список объектов.
search_fields = ['category']Причина: category — не поле, а объект.
Решение:
search_fields = ['category__name']search_fields = ['category_name'] # неверноDjango ищет поле category_name, которого нет.
('new', '>= 2000') # непонятный текстВсегда делайте подписанные и понятные названия, иначе админка будет выглядеть странно.
Если в конце не написать return queryset → админка «сломается» и покажет пустой список.
-
Добавьте поиск по году выпуска фильма.
-
Сделайте так, чтобы поиск по названию искал не только по началу строки но и проверял конец. Например, «Ава» или «тар» → должен находить «Аватар».
-
Добавьте фильтр по году выпуска.
-
Создайте фильтр "Фильмы без категории". В списке должно добавится «С категорией» и «Без категории». В классе можно использовать 'with' и 'without'.
- Поиск по году выпуска.
search_fields = ['title', 'year']- Поиск по началу строки.
search_fields = ['title__startswith']- Фильтр по году выпуска.
list_filter = ['year', 'is_published']- Фильтр "Фильмы без категории"
class CategoryStatusFilter(admin.SimpleListFilter):
title = 'Наличие категории'
parameter_name = 'cat_status'
def lookups(self, request, model_admin):
return [
('with', 'С категорией'),
('without', 'Без категории'),
]
def queryset(self, request, queryset):
if self.value() == 'with':
return queryset.filter(category__isnull=False)
if self.value() == 'without':
return queryset.filter(category__isnull=True)
return querysetПодключение:
list_filter = [CategoryStatusFilter]- Для чего используется
search_fields? - Можно ли искать по связанным моделям? Как?
- Что означает конструкция
category__name? - Что делает lookup
__startswith? - Как добавить фильтр по полю BooleanField?
- Что такое кастомный фильтр?
- Что обязательно должен возвращать метод
queryset()в фильтре? - Что произойдёт, если забыть указать поле внутри ForeignKey (например,
categoryвместоcategory__name)? - Где в админке появляется панель фильтрации?
- Как проверить корректность работы фильтра?