Skip to content

Latest commit

 

History

History
413 lines (275 loc) · 14.4 KB

File metadata and controls

413 lines (275 loc) · 14.4 KB

Модуль 6. Урок 36. Панель поиска и панель фильтрации в админ-панели Django

Админ-панель — это первая «система управления» вашего проекта, и чем удобнее она настроена, тем быстрее вы можете работать с данными. В этом уроке мы научимся добавлять в админку два ключевых инструмента:

  • панель поиска
  • панель фильтрации

Оба инструмента делают работу с большими таблицами комфортной: можно находить фильмы по названию, искать все фильмы конкретного режиссёра или выводить только опубликованные записи.

Мы будем делать всё постепенно, с большим количеством пояснений, проверок и практики.


1. Панель поиска (search_fields)

Представьте, что у нас в модели 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']

Что произойдёт после изменения?

  1. Переходим в админ-панель → раздел «Фильмы».
  2. Сверху появится поле ввода: Search.
  3. Вводим первые буквы фильма → жмём Enter.
  4. Отображается только то, что соответствует поиску.

Это уже делает админку намного удобнее, особенно при больших таблицах.


2. Поиск по связанным моделям (ForeignKey)

Очень частая ошибка новичков:

search_fields = ['title', 'category']   ❌

Django не знает, что значит «искать по категории». Он понимает только реальные поля базы данных.

Чтобы искать по связанным объектам, мы используем двойное подчеркивание (__):

search_fields = ['title', 'category__name']  # ✔️ правильно

Почему именно так?

category — это объект category.name — это поле в БД А category__name — это путь до этого поля, понятный Django ORM.


3. Использование lookup-выражений в поиске

Иногда полезно искать не по всему тексту, а только по началу строки. Например, вы вводите Аве, и хотите найти только Аватар, но не Великая авантюра.

Это делается с помощью lookup-выражений:

search_fields = ['title__startswith']

Другие полезные lookup'ы:

  • __icontains — поиск без учета регистра
  • __endswith — поиск по окончанию строки

Это делает поиск гибким и точным.


4. Панель фильтрации (list_filter)

Фильтры появляются справа в админке и позволяют быстро отбирать данные по полям.

Самые частые фильтры:

  • булево поле (например, опубликован / нет)
  • категории
  • даты
  • внешние ключи
  • собственные фильтры (кастомные)

Базовый пример:

@admin.register(Movie)
class MovieAdmin(admin.ModelAdmin):
    list_display = ('title', 'year', 'is_published', 'category')
    search_fields = ['title']
    list_filter = ['category', 'is_published']

Что происходит?

  1. Справа появляется блок «FILTER».
  2. В нём — список доступных фильтров.
  3. Нажимаем «Категория → Комедия» — отображаются только комедии.
  4. Нажимаем «Статус → Published» — фильмы, включенные на сайт.

5. Кастомные фильтры (например, «Опубликованные»/«Черновики»)

Иногда фильтров по умолчанию недостаточно.

Допустим, мы хотим фильтр:

  • «Показать только новые фильмы»
  • «Показать фильмы старше 2000 года»
  • «Показать фильмы без категории»

Создадим простой кастомный фильтр.


Пример: фильтр «Старые / Новые фильмы»

  1. Создаём класс фильтра:
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
  1. Подключаем его:
list_filter = [YearFilter, 'category', 'is_published']

Из чего состоит кастомный фильтр YearFilter:

Наследование от admin.SimpleListFilter

SimpleListFilter — встроенный инструмент Django, который:

  • отвечает за отображение фильтра справа в админке,
  • показывает список вариантов (например, “Новые”, “Старые”),
  • получает выбранное значение,
  • возвращает отфильтрованный queryset.

Django использует этот класс для расширения стандартной панели фильтрации.

Атрибут title — отображаемое название фильтра

Это название фильтра, которое будет показано в правой панели админки.

Атрибут parameter_name — имя GET-параметра в URL

parameter_name = 'release_year'

Очень важный параметр.

Он определяет имя параметра в URL, например:

/admin/cinemahub/movie/?release_year=new
/admin/cinemahub/movie/?release_year=old
  • Django читает этот параметр из request.GET.
  • Передаёт его в метод self.value().
  • И позволяет фильтру понять, что выбрал пользователь.

Если его не указать → Django не сможет отличить этот фильтр от других.

Метод lookups() — список вариантов фильтра

def lookups(self, request, model_admin):
    return [
        ('new', 'Новые (>= 2000)'),
        ('old', 'Старые (< 2000)'),
    ]

Этот метод встроенный и обязателен.

Django вызывает его автоматически, чтобы узнать:

  • какие кнопки показывать в списке фильтров,
  • какие значения они передают при выборе.

Каждая строка — пара: ('значение, передаваемое в URL', 'текст, отображаемый в панели')

Например:

('new', 'Новые (>= 2000)')

При клике Django сделает:

?release_year=new

Метод queryset() — основная логика фильтрации

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:

  1. Django создаёт экземпляр фильтра.
  2. Django вызывает lookups() → рисует список вариантов.
  3. Пользователь выбирает вариант → появляется URL параметр.
  4. Django вызывает queryset() → фильтрует список объектов.

6. Ошибки и их решения

❌ Ошибка 1: поиск по внешнему ключу без указания поля

search_fields = ['category']

Причина: category — не поле, а объект.

Решение:

search_fields = ['category__name']

❌ Ошибка 2: неправильный путь

search_fields = ['category_name']   # неверно

Django ищет поле category_name, которого нет.


❌ Ошибка 3: неверный порядок значений в кастомном фильтре

('new', '>= 2000')   # непонятный текст

Всегда делайте подписанные и понятные названия, иначе админка будет выглядеть странно.


❌ Ошибка 4: забыли вернуть queryset в кастомном фильтре

Если в конце не написать return queryset → админка «сломается» и покажет пустой список.


7. Практические задания

  1. Добавьте поиск по году выпуска фильма.

  2. Сделайте так, чтобы поиск по названию искал не только по началу строки но и проверял конец. Например, «Ава» или «тар» → должен находить «Аватар».

  3. Добавьте фильтр по году выпуска.

  4. Создайте фильтр "Фильмы без категории". В списке должно добавится «С категорией» и «Без категории». В классе можно использовать 'with' и 'without'.

Сравнить решение

  1. Поиск по году выпуска.
search_fields = ['title', 'year']
  1. Поиск по началу строки.
search_fields = ['title__startswith']
  1. Фильтр по году выпуска.
list_filter = ['year', 'is_published']
  1. Фильтр "Фильмы без категории"
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]

Вопросы

  1. Для чего используется search_fields?
  2. Можно ли искать по связанным моделям? Как?
  3. Что означает конструкция category__name?
  4. Что делает lookup __startswith?
  5. Как добавить фильтр по полю BooleanField?
  6. Что такое кастомный фильтр?
  7. Что обязательно должен возвращать метод queryset() в фильтре?
  8. Что произойдёт, если забыть указать поле внутри ForeignKey (например, category вместо category__name)?
  9. Где в админке появляется панель фильтрации?
  10. Как проверить корректность работы фильтра?

Предыдущий урок | Следующий урок