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

К концу этого материала у вас будет работающий каталог: список товаров, который фильтруется по категории, диапазону цены и наличию, а параметры фильтра сохраняются в URL — их можно скопировать и переслать. На чистом проекте это займёт полтора-два часа, если Python и виртуальное окружение уже стоят. Писать свой парсер параметров и городить сложные формы не придётся: почти всё делает библиотека django-filter.
Что нужно иметь до начала
Проверьте это до первой команды, чтобы не упереться на середине.
- Python 3.10 или новее. Проверка:
python --version. - Django 5.x. Материал писался под Django 5.0; в 4.2 всё работает так же, отличий в описанном коде нет.
- Библиотека django-filter версии 24.x — она делает фильтрацию по GET-параметрам.
- Уже созданный проект и приложение. Если проекта нет — сначала
django-admin startproject shopиpython manage.py startapp catalog. - База данных. Для обучения хватит SQLite по умолчанию, ничего настраивать не нужно.
Установка библиотеки в активированном виртуальном окружении:
pip install django-filter==24.3
После установки добавьте приложение в настройки. Откройте shop/settings.py и впишите в список INSTALLED_APPS строку 'django_filters' — именно с подчёркиванием, а не через дефис, как в имени пакета. Это частая первая ошибка.
Фильтр по GET-параметрам — это не просто удобство. Такой URL индексируется поисковиком, кешируется и переживает перезагрузку страницы. Фильтр, который держит состояние в JavaScript и POST-запросах, всего этого лишён.
Шаг 1. Модель товара
Фильтровать нечего, пока нет данных. Опишите модель в catalog/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"
)
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
Проверка: в консоли появятся строки вида Applying catalog.0001_initial... OK. Если вместо этого ошибка No installed app with label 'catalog' — вы забыли добавить своё приложение в INSTALLED_APPS.
Наполните базу хотя бы десятком товаров через админку (python manage.py createsuperuser, затем зарегистрируйте модели в catalog/admin.py) или через python manage.py shell. Без данных фильтр нечего проверять.
Шаг 2. Класс фильтра
Создайте файл catalog/filters.py. Здесь описывается, по каким полям и как фильтровать. Диапазон цены задаётся через RangeFilter — он читает из URL два параметра: price_min и price_max.
import django_filters
from .models import Product, Category
class ProductFilter(django_filters.FilterSet):
price = django_filters.RangeFilter()
category = django_filters.ModelChoiceFilter(
queryset=Category.objects.all()
)
class Meta:
model = Product
fields = ["category", "in_stock"]
Поле in_stock попадает в фильтр из Meta.fields — для булева поля django-filter сам сделает выпадающий список «Да / Нет / всё равно». Поле price объявлено явно выше, потому что для него нужен диапазон, а не точное совпадение.
Шаг 3. Представление и шаблон
В catalog/views.py свяжите фильтр с queryset. Не тяните все товары в память — FilterSet сам построит SQL-запрос с WHERE по переданным параметрам.
from django.shortcuts import render
from .models import Product
from .filters import ProductFilter
def product_list(request):
# request.GET содержит параметры фильтра из URL
product_filter = ProductFilter(
request.GET, queryset=Product.objects.all()
)
return render(
request,
"catalog/product_list.html",
{"filter": product_filter},
)
Шаблон catalog/templates/catalog/product_list.html. Форма фильтра рендерится одной строкой {{ filter.form }}, метод формы — обязательно get, иначе параметры не попадут в URL.
<form method="get">
{{ filter.form.as_p }}
<button type="submit">Показать</button>
</form>
<ul>
{% for product in filter.qs %}
<li>{{ product.name }} — {{ product.price }} ₽</li>
{% empty %}
<li>Ничего не найдено</li>
{% endfor %}
</ul>
Важно: список товаров берётся из filter.qs, а не из исходного queryset. qs — это уже отфильтрованная выборка. Обращение к filter.qs запускает запрос к базе, поэтому обращайтесь к нему один раз.
Осталось прописать маршрут в catalog/urls.py и подключить его в корневом shop/urls.py:
from django.urls import path
from . import views
urlpatterns = [
path("catalog/", views.product_list, name="product_list"),
]
Проверка: запустите python manage.py runserver и откройте http://127.0.0.1:8000/catalog/. Появятся форма и список. Выберите категорию, нажмите «Показать» — в адресной строке появится что-то вроде ?category=1&price_min=&price_max=, а список сократится.
Если не получилось
No module named 'django_filters'— пакет не установлен в активное окружение либо окружение не активировано. Проверьте, что перед установкой видите префикс окружения в консоли.TemplateDoesNotExist: catalog/product_list.html— шаблон лежит не там. Django ищет его по путиcatalog/templates/catalog/product_list.html. Двойноеcatalogв пути — не опечатка, так работает поиск шаблонов по приложениям.- Фильтр показан, но ничего не фильтрует. Скорее всего у формы стоит
method="post"или метода нет вовсе. Нуженmethod="get". - Список пустой при любом фильтре. Проверьте, что в базе вообще есть товары и что вы выводите
filter.qs, а неfilter.queryset. FieldError: Unsupported lookup— вMeta.fieldsпопало имя поля, которого нет в модели, или опечатка в нём.
Что настроить дальше
Каталог работает, но для боевого использования не хватает трёх вещей. Во-первых, пагинация: Paginator из Django разбивает filter.qs на страницы, иначе при тысяче товаров страница будет грузиться секундами. Во-вторых, если у товара есть внешние ключи, которые вы выводите в списке, добавьте .select_related("category") к queryset — иначе на каждый товар пойдёт отдельный запрос к базе. В-третьих, для фильтра по тексту пригодится CharFilter с lookup_expr="icontains".
Оформление формы через as_p — временное. Внешний вид фильтра, стилизация полей и живое обновление списка без перезагрузки — отдельная тема, здесь мы собирали работающую основу на серверном рендеринге.
Редактор изображений: Убрать лишнее, заменить фон, добавить объект, расширить кадр — на вашем изображении.
Спросить за 20 ₽Источники: django-filter — официальная документация, Django documentation: Making queries
Частые вопросы
У меня Django 4.2, а не 5.x. Всё сработает?
Зачем django-filter, если можно фильтровать через request.GET вручную?
Почему фильтр читает параметры из GET, а не из POST?
Каталог тормозит при большом числе товаров — что смотреть первым?
Диапазон цены не работает — что проверить?
Как добавить фильтр по названию товара?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.