Фильтры шаблонов в Django: с чего начать
Как написать первый собственный фильтр для шаблонов Django, зарегистрировать его и подключить в шаблоне. От пустого проекта до работающего {{ price|add_vat }} — примерно за 20 минут.

К концу этого гайда у вас будет собственный фильтр шаблона, который вы вызываете прямо в HTML: {{ price|add_vat }} прибавит НДС к числу, а {{ name|initials }} превратит «Иван Петров» в «И. П.». Всё это без изменения кода вью — только шаблон и один Python-файл. На работу уйдёт около 20 минут, если проект уже запускается. Проверено на Django 5.x, но так же работает начиная с Django 3.2.
Что нужно иметь до начала
Ничего экзотического, но проверьте список — половина проблем новичков в том, что чего-то из этого нет.
- Работающий проект Django. Команда
python manage.py runserverзапускается без ошибок, страница открывается в браузере. - Хотя бы одно приложение (то, что создаётся через
python manage.py startapp) и оно перечислено вINSTALLED_APPSв файле настроек. Фильтр обязан жить внутри приложения — иначе Django его не найдёт. - Настроенные шаблоны. В
settings.pyв блокеTEMPLATESдвижокDjangoTemplatesвключён по умолчанию — если вы не удаляли этот блок, всё на месте. - Django 3.2 или новее. Проверить:
python -m django --version.
Дальше я буду считать, что приложение называется shop. У вас оно называется иначе — подставляйте своё имя везде, где встречается shop.
Что такое фильтр и чем он отличается от тега
Фильтр — это функция, которая берёт значение слева от вертикальной черты и что-то с ним делает. В {{ name|upper }} фильтр upper получает строку name и возвращает её в верхнем регистре. Django из коробки даёт десятки таких: date, default, length, truncatewords. Когда встроенных не хватает — пишете свой.
Фильтр отвечает за отображение одного значения. Как только вам нужно ветвление, цикл или несколько аргументов со сложной логикой — это уже задача тега1, а не фильтра. Не пытайтесь запихнуть в фильтр половину бизнес-логики.
Шаг 1. Создайте папку для тегов
Django ищет пользовательские фильтры только в папке с фиксированным именем templatetags внутри приложения. Создайте её и пустой файл __init__.py рядом:
shop/
__init__.py
models.py
views.py
templatetags/
__init__.py
shop_extras.py
Файл __init__.py внутри templatetags обязателен — без него Python не считает папку пакетом, и Django молча не увидит ваши фильтры. Имя shop_extras.py произвольное, но именно его вы будете указывать в шаблоне, поэтому пусть будет осмысленным.
Проверка: структура папок совпадает с примером, оба __init__.py на месте (могут быть пустыми).
Шаг 2. Напишите фильтр
Откройте shop_extras.py и напишите самый простой фильтр — без аргументов:
from django import template
# register — обязательное имя переменной, Django ищет именно его
register = template.Library()
@register.filter
def initials(full_name):
"""Иван Петров -> И. П."""
parts = str(full_name).split()
letters = [word[0].upper() + '.' for word in parts if word]
return ' '.join(letters)
Что здесь происходит: register = template.Library() создаёт реестр, куда декоратор @register.filter добавляет функцию. Имя фильтра в шаблоне совпадёт с именем функции — initials. Если хотите другое имя, передайте его в декоратор: @register.filter(name='ini').
Проверка: файл сохранён, синтаксических ошибок нет — python manage.py check завершается без замечаний.
Шаг 3. Подключите библиотеку в шаблоне
В любом шаблоне, где хотите использовать фильтр, вверху добавьте {% load %} с именем файла без расширения:
{% load shop_extras %}
<p>Менеджер: {{ manager_name|initials }}</p>
Строку {% load %} нужно писать в каждом шаблоне отдельно — она не наследуется от базового шаблона через {% extends %}. Это частая точка спотыкания.
Проверка: если в контексте manager_name = "Иван Петров", на странице появится строка «Менеджер: И. П.».
Шаг 4. Перезапустите сервер
Новый файл в templatetags подхватывается только при старте. Автоперезагрузка runserver реагирует на изменения кода, но новую библиотеку тегов после её создания надёжнее подхватить вручную: остановите сервер (Ctrl+C) и запустите заново.
Фильтр с аргументом
Часто нужно передать в фильтр параметр — например, ставку налога. Фильтр принимает максимум один аргумент, он идёт после двоеточия:
from decimal import Decimal
from django import template
register = template.Library()
@register.filter
def add_vat(amount, rate=20):
"""Прибавляет НДС. {{ price|add_vat:20 }}"""
try:
base = Decimal(str(amount))
except (TypeError, ValueError):
return amount # не число — возвращаем как есть, не роняем страницу
return base * (Decimal(1) + Decimal(rate) / Decimal(100))
В шаблоне: {{ price|add_vat:20 }}. Обратите внимание на try/except: фильтр может получить None или пустую строку, и если он упадёт с исключением, упадёт вся страница. Хороший фильтр к плохому входу устойчив.
Фильтр, который возвращает HTML
По умолчанию Django экранирует вывод фильтра — теги превращаются в текст. Если фильтр сознательно возвращает готовую разметку, оберните результат в mark_safe или используйте декоратор stringfilter вместе с флагом. Проще всего:
from django import template
from django.utils.html import format_html
register = template.Library()
@register.filter
def badge(text):
# format_html сам экранирует подставляемый text — безопасно
return format_html('<span class="badge">{}</span>', text)
Не используйте mark_safe на сырых пользовательских данных без экранирования — это открывает XSS. format_html экранирует подставляемые значения автоматически, поэтому безопаснее.
Если не получилось
Пять ошибок, которые видят почти все новички, и их точные тексты.
- TemplateSyntaxError: 'shop_extras' is not a registered tag library. Django не нашёл файл. Проверьте: приложение
shopесть вINSTALLED_APPS; файл лежит вshop/templatetags/, а не в корне приложения; внутриtemplatetagsесть__init__.py; сервер перезапущен. - Фильтр не находится, хотя всё на месте. В 90% случаев забыли
{% load shop_extras %}в самом шаблоне либо написали{% load shop_extras.py %}с расширением — расширение не пишется. - Строка выводится с экранированными тегами (
<span>вместо тега). Фильтр возвращает разметку, но не помечен как безопасный — используйтеformat_html, как в примере выше. - TypeError: initials() takes 1 positional argument but 2 were given. Вы передали фильтру аргумент через двоеточие, а функция его не принимает. Либо уберите аргумент из шаблона, либо добавьте параметр в функцию.
- Изменения в фильтре не видны. Правки в существующем файле runserver подхватывает, но если фильтр закешировался странно — остановите сервер и запустите заново. При деплое через WSGI/ASGI нужен полный рестарт процесса.
Что делать дальше
Когда простые фильтры освоены, посмотрите в сторону:
- декоратора
@register.filter(is_safe=True)— для фильтров, которые не портят безопасную строку; - тегов
@register.simple_tagи@register.inclusion_tag— когда одного значения на входе мало; - встроенных фильтров Django — прежде чем писать свой, проверьте, нет ли готового:
floatformat,default_if_none,pluralizeзакрывают большинство бытовых задач.
Логику форматирования, которая нужна в трёх местах, выносите в фильтр один раз — так шаблоны остаются читаемыми, а правка делается в одной точке.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Django docs: Custom template tags and filters, Django docs: Built-in template tags and filters
Частые вопросы
У меня Django 3.2, а не 5.x — код будет работать?
Почему фильтр не находится, хотя я всё сделал по инструкции?
Можно ли передать фильтру два аргумента?
Где размещать фильтры, если их используют несколько приложений?
Фильтр выводит теги как текст со символами вроде <span> — как исправить?
Нужно ли перезапускать сервер после правки существующего фильтра?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.