Каталог с фильтром на Django: пошаговое руководство
Собираем страницу каталога с фильтрацией по цене, категории и наличию на Django 5.x с помощью django-filter. От установки до рабочей формы — примерно за час.

К концу этого руководства у вас будет страница /catalog/, на которой товары фильтруются по категории, диапазону цены и наличию на складе. Фильтры соберутся в GET-форму — их можно будет добавлять в закладки и передавать ссылкой. Работаем на Django 5.x и библиотеке django-filter 24.x. По времени — около часа, если проект уже создан.
Что нужно иметь до начала
- Python 3.10 или новее и созданный проект Django 5.x (команда
django-admin startprojectуже выполнена). - Приложение внутри проекта — например
shop, добавленное вINSTALLED_APPS. - Настроенная база данных (по умолчанию SQLite подойдёт для обучения).
- Виртуальное окружение, в котором вы ставите пакеты.
Если приложения ещё нет, создайте его: python manage.py startapp shop и допишите 'shop' в INSTALLED_APPS файла настроек.
Шаг 1. Ставим django-filter
pip install django-filter
После установки добавьте приложение в INSTALLED_APPS (имя пакета в настройках — с подчёркиванием):
# settings.py
INSTALLED_APPS = [
# ... стандартные приложения Django
"django_filters",
"shop",
]
Проверка: выполните python manage.py shell и внутри наберите import django_filters. Если ошибки нет — библиотека на месте.
Шаг 2. Модель товара
Опишем две модели: категорию и товар. По этим полям и будем фильтровать.
# shop/models.py
from django.db import models
class Category(models.Model):
name = models.CharField("Название", max_length=100)
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField("Название", max_length=200)
category = models.ForeignKey(
Category,
on_delete=models.CASCADE,
related_name="products",
verbose_name="Категория",
)
price = models.DecimalField("Цена", max_digits=10, decimal_places=2)
in_stock = models.BooleanField("В наличии", default=True)
def __str__(self):
return self.name
Создайте и примените миграции:
python manage.py makemigrations
python manage.py migrate
Проверка: в выводе migrate появятся строки Applying shop.0001_initial... OK. Чтобы было что показывать, добавьте пару категорий и товаров через админку или python manage.py shell.
Шаг 3. Описываем фильтр
django-filter строит форму фильтрации из класса, похожего на форму. Создайте файл shop/filters.py.
# shop/filters.py
import django_filters
from .models import Product, Category
class ProductFilter(django_filters.FilterSet):
# Цена: два поля — «от» и «до»
price_min = django_filters.NumberFilter(
field_name="price", lookup_expr="gte", label="Цена от"
)
price_max = django_filters.NumberFilter(
field_name="price", lookup_expr="lte", label="Цена до"
)
# Категория: выпадающий список
category = django_filters.ModelChoiceFilter(
queryset=Category.objects.all(), label="Категория"
)
class Meta:
model = Product
fields = ["category", "in_stock"]
lookup_expr задаёт тип сравнения: gte — больше или равно, lte — меньше или равно. Поле in_stock перечислено в Meta.fields, поэтому фильтр для него создаётся автоматически как чекбокс.
Фильтр в django-filter — это не форма поиска, а обёртка над QuerySet. Каждое заполненное поле превращается в условие .filter(), пустое — просто игнорируется. Поэтому пустая форма отдаёт весь каталог, и это правильное поведение.
Шаг 4. Представление
Проще всего использовать готовое FilterView из библиотеки — оно само создаёт фильтр, применяет его к queryset и кладёт в контекст.
# shop/views.py
from django_filters.views import FilterView
from .models import Product
from .filters import ProductFilter
class CatalogView(FilterView):
model = Product
filterset_class = ProductFilter
template_name = "shop/catalog.html"
paginate_by = 20 # разбивка по 20 товаров на страницу
Если предпочитаете функцию-представление, тот же результат достигается вручную:
# альтернатива на function-based view
from django.shortcuts import render
from .models import Product
from .filters import ProductFilter
def catalog(request):
f = ProductFilter(request.GET, queryset=Product.objects.all())
return render(request, "shop/catalog.html", {"filter": f})
В шаблоне FilterView объект фильтра доступен под именем filter, а отфильтрованный список — под object_list. Для варианта с функцией список берётся из filter.qs.
Шаг 5. Маршрут
# shop/urls.py
from django.urls import path
from .views import CatalogView
urlpatterns = [
path("catalog/", CatalogView.as_view(), name="catalog"),
]
Подключите его в корневом urls.py проекта через include("shop.urls").
Проверка: откройте http://127.0.0.1:8000/catalog/ после python manage.py runserver. Пока увидите ошибку про отсутствующий шаблон — это ожидаемо, займёмся им дальше.
Шаг 6. Шаблон с формой
Создайте файл shop/templates/shop/catalog.html. Форму фильтра рисует сам объект: {{ filter.form }}.
<h1>Каталог</h1>
<form method="get">
{{ filter.form.as_p }}
<button type="submit">Применить</button>
</form>
<ul>
{% for product in object_list %}
<li>{{ product.name }} — {{ product.price }} руб.
{% if not product.in_stock %}(нет в наличии){% endif %}
</li>
{% empty %}
<li>Ничего не найдено</li>
{% endfor %}
</ul>
Важно: у формы фильтра method="get", а не post. Значения фильтров попадают в строку запроса — /catalog/?category=2&price_min=500 — и ссылку можно сохранить.
Проверка: обновите страницу. Появятся поля «Цена от», «Цена до», выпадающий список категорий и чекбокс наличия. Выберите категорию, нажмите «Применить» — список товаров сузится, а параметры отразятся в адресной строке.
Если не получилось
ModuleNotFoundError: No module named 'django_filters'— пакет не установлен в активном окружении. Проверьте, что виртуальное окружение включено, и повторитеpip install django-filter. Обратите внимание: имя вINSTALLED_APPS—django_filtersс подчёркиванием, а пакет ставится какdjango-filterс дефисом.TemplateDoesNotExist: shop/catalog.html— Django не нашёл шаблон. Путь должен бытьshop/templates/shop/catalog.html, а приложениеshop— присутствовать вINSTALLED_APPS.- Форма отображается, но фильтр не срабатывает — скорее всего в форме
method="post". Замените наget: FilterSet читает данные изrequest.GET. - Выпадающий список категорий пустой — в базе нет ни одной категории. Добавьте записи в модель
Category. FieldError: Unsupported lookup— опечатка вlookup_expr. Допустимые значения —gte,lte,exact,icontainsи другие из документации Django QuerySet API.
Что делать дальше
Базовый каталог работает. Дальше стоит:
- добавить поиск по названию —
CharFilterсlookup_expr="icontains"; - заменить чекбокс наличия на
BooleanFilterс виджетом-селектом, если нужны три состояния «все / в наличии / нет»; - оформить форму через crispy-forms, чтобы поля выглядели аккуратно без ручной вёрстки;
- если товаров тысячи — добавить индекс базы данных на поля
priceиcategoryчерезMeta.indexesмодели, иначе фильтрация по цене начнёт заметно тормозить.
Сохранение состояния фильтра между страницами пагинации — отдельная тема: ссылки «вперёд/назад» должны нести текущие параметры GET. Здесь мы её не разворачиваем.
Репетитор 1—11 класс и ВУЗ: Математика и физика методом наведения на мысль, помощь с ДЗ.
Спросить за 8 ₽Источники: django-filter — официальная документация, Django QuerySet API — field lookups
Частые вопросы
А если у меня другая версия django-filter?
Обязательно ли использовать FilterView, или можно на обычной вьюхе?
Фильтр не срабатывает, показываются все товары — что не так?
Как отфильтровать по нескольким категориям сразу?
Будет ли это тормозить на большом каталоге?
Нужно ли писать формы вручную, если использую django-filter?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.