На предыдущих уроках мы научились работать с шаблонами, наследованием и статическими файлами. Теперь представь, что в проекте нужно многократно повторять один и тот же элемент интерфейса — например, список жанров, меню навигации или рейтинг фильма. Конечно, можно было бы просто вставлять одинаковый HTML в разные шаблоны, но это нарушает принцип DRY (Don’t Repeat Yourself).
Django предлагает для этого мощный инструмент — пользовательские теги шаблонов. Они позволяют встраивать динамический контент в шаблоны и делать код компактным и переиспользуемым.
Пользовательские теги — это наши собственные шаблонные команды Django, которые мы создаём вручную. Они могут:
- возвращать данные (тег
simple_tag); - возвращать отрендеренный HTML-шаблон (тег
inclusion_tag).
В нашем проекте cinemahub мы сделаем пример с жанрами фильмов.
Мы создадим два пользовательских тега:
get_genres— вернёт список жанров (используяsimple_tag).show_genres— выведет список жанров через отдельный шаблон (используяinclusion_tag).
Начнём с чего-то простого.
Откроем файл movies/views.py и создадим временные данные (эмулируем таблицу жанров):
genres_db = [
{'id': 1, 'name': 'Боевики'},
{'id': 2, 'name': 'Драмы'},
{'id': 3, 'name': 'Комедии'},
{'id': 4, 'name': 'Фантастика'},
]💡 Позже, когда мы подключим базу данных, эти данные будут загружаться из модели.
Django ищет пользовательские теги в специальной папке templatetags внутри приложения.
Создадим структуру:
movies/
│── templatetags/
│ ├── __init__.py
│ └── movie_tags.py
Файл __init__.py может быть пустым — он просто сообщает Python, что это пакет.
В файле movie_tags.py напишем наш первый пользовательский тег:
from django import template
import movies.views as views
register = template.Library()
@register.simple_tag()
def get_genres():
"""Возвращает список жанров"""
return views.genres_dbТег simple_tag просто выполняет функцию и возвращает результат.
Мы можем сохранить этот результат в переменную и использовать в шаблоне.
Откроем шаблон base.html и подключим наши теги:
{% load movie_tags %}Теперь можно использовать тег в любом месте:
{% get_genres as genres %}
<ul class="genres-list">
{% for g in genres %}
<li><a href="#">{{ g.name }}</a></li>
{% endfor %}
</ul>Здесь мы сохраняем результат в переменную
genres, чтобы потом вывести её в цикле.
Запускаем сервер:
python manage.py runserverПереходим на страницу / — в шапке (или где вы разместили код) должен появиться список жанров:
Боевики | Драмы | Комедии | Фантастика
Если всё работает — поздравляю! Вы только что создали свой первый шаблонный тег Django 🎉
Иногда название функции не совпадает с тем, как вы хотите вызывать тег в шаблоне. Можно задать имя вручную:
@register.simple_tag(name='getcategories')
def get_genres():
return views.genres_dbТеперь в шаблоне нужно писать:
{% getcategories as genres %}Тег inclusion_tag отличается от simple_tag тем, что он не просто возвращает данные, а рендерит отдельный шаблон.
Это удобно, когда нужно вставить готовый HTML-фрагмент (например, меню, рейтинг или карточку фильма).
Создадим файл movies/templates/movies/includes/genres_list.html:
<ul class="genres-list">
{% for g in genres %}
<li><a href="#">{{ g.name }}</a></li>
{% endfor %}
</ul>@register.inclusion_tag('movies/includes/genres_list.html')
def show_genres():
"""Рендерит шаблон со списком жанров"""
return {"genres": views.genres_db}Теперь мы можем использовать тег прямо в base.html:
{% load movie_tags %}
...
{% show_genres %}Django подставит отрендеренный шаблон genres_list.html и выведет список жанров.
Напомним структуру проекта:
movies/
│── templates/
│ ├── base.html
│ ├── movies/
│ │ ├── index.html
│ │ ├── about.html
│ │ ├── includes/
│ │ │ ├── nav.html
│ │ │ ├── index_content.html
│ │ │ ├── genres_list.html
Этот шаблон будет основой для всех остальных. Он подключает статику, загружает CSS и включает общие блоки сайта.
{% load static %}
{% load movies_tags %}
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Cinemahub — фильмы и жанры</title>
<link rel="stylesheet" href="{% static 'movies/css/styles.css' %}">
</head>
<body>
<header>
<h1><a href="{% url 'home' %}">🎬 Cinemahub</a></h1>
{% include 'movies/includes/nav.html' %}
</header>
<main class="container">
<aside class="sidebar">
<h3>Жанры</h3>
{% show_genres selected_genre %}
</aside>
<section class="content">
{% block content %}{% endblock %}
</section>
</main>
<footer>
<p>© 2025 Cinemahub</p>
</footer>
<script src="{% static 'movies/js/main.js' %}"></script>
</body>
</html>Главная страница со списком фильмов index.html.
{% extends 'base.html' %}
{% block content %}
{% include 'movies/includes/index_content.html' %}
{% endblock %}Страница “О проекте” about.html.
{% extends 'base.html' %}
{% block content %}
<h2>О проекте</h2>
<p>Проект <strong>Cinemahub</strong> создан для изучения Django и представляет собой учебный портал о фильмах и жанрах. Здесь вы сможете практиковаться с шаблонами, статикой и пользовательскими тегами.</p>
{% endblock %}Меню сайта includes/nav.html.
<nav>
<ul>
<li><a href="{% url 'home' %}">Главная</a></li>
<li><a href="{% url 'about' %}">О проекте</a></li>
</ul>
</nav>Временный контент на главной includes/index_content.html.
{% load static %}
<h2>Добро пожаловать в Cinemahub!</h2>
<p>Здесь скоро появится каталог фильмов, поиск и фильтрация по жанрам. Пока мы учимся подключать шаблоны и статику.</p>
<img src="{% static 'movies/images/poster_sample.jpg' %}" alt="Movie Poster">Для красоты добавим возможность подсвечивать выбранный жанр.
Изменим тег:
@register.inclusion_tag('movies/includes/genres_list.html')
def show_genres(selected_genre=0):
"""Рендерит список жанров с выделением активного"""
return {"genres": views.genres_db, "selected_genre": selected_genre}<ul class="genres-list">
{% for g in genres %}
{% if g.id == selected_genre %}
<li class="active">{{ g.name }}</li>
{% else %}
<li><a href="{% url 'genre' g.id %}">{{ g.name }}</a></li>
{% endif %}
{% endfor %}
</ul>...
genres_db = [
{"id": 1, "name": "Боевики"},
{"id": 2, "name": "Драмы"},
{"id": 3, "name": "Комедии"},
{"id": 4, "name": "Фантастика"},
]
def index(request):
return render(request, 'movies/index.html', {"selected_genre": 0})
def show_genre(request, genre_id):
return render(request, 'movies/index.html', {"selected_genre": genre_id})
def about(request):
data = {
"title": "О сайте",
"menu": menu,
"films": data_db,
}
return render(request, "movies/about.html", data)
...urlpatterns = [
path('', views.index, name='home'),
path('about/', views.about, name='about'),
path('add_film/', views.add_film, name='add_film'),
path('contact/', views.contact, name='contact'),
path('login/', views.login, name='login'),
path('film/<int:film_id>/', views.show_film, name='film'),
path('genre/<int:genre_id>/', views.show_genre, name='genre'),
]body {
font-family: Arial, sans-serif;
background-color: #f3f3f3;
color: #333;
margin: 0;
padding: 0;
}
header {
background-color: #242424;
color: white;
padding: 15px;
}
header a {
color: white;
text-decoration: none;
margin-right: 10px;
}
.container {
display: flex;
gap: 20px;
padding: 20px;
}
.sidebar {
width: 25%;
background-color: #fff;
padding: 15px;
border-radius: 8px;
}
.genres-list {
list-style: none;
padding: 0;
}
.genres-list li {
margin-bottom: 8px;
}
.genres-list li.active {
font-weight: bold;
color: #d22;
}
.content {
width: 75%;
background-color: #fff;
padding: 20px;
border-radius: 8px;
}-
Убедись, что в
settings.pyпрописано:STATIC_URL = '/static/' STATICFILES_DIRS = [BASE_DIR / 'static']
-
Запусти сервер:
python manage.py runserver
-
Перейди на:
/— откроется главная страница с постером./about/— страница о проекте./genre/2/— жанр “Комедии” подсветится в боковом меню.
-
Проверь консоль браузера — не должно быть ошибок загрузки статики.
- Создай собственный тег
get_movies_count, который возвращает количество фильмов в списке. - Создай тег
show_header, который будет рендерить шаблонincludes/header.html(черезinclusion_tag). - Добавь параметр
usernameв тегshow_headerи выведи приветствие на странице. - Проверь, что все пользовательские теги подключаются корректно.
- Попробуй объединить
simple_tagиinclusion_tag: передай данные из первого в шаблон второго.
simple_tag— возвращает данные прямо в шаблон.inclusion_tag— возвращает готовый HTML-фрагмент из отдельного шаблона.- Оба тега регистрируются через
@register. - Папка
templatetagsдолжна находиться внутри приложения. - Пользовательские теги позволяют писать чистые и переиспользуемые шаблоны.
- Для чего нужны пользовательские теги в Django?
- В чём разница между
simple_tagиinclusion_tag? - Где должна находиться папка
templatetags? - Зачем нужен декоратор
@register.simple_tag()? - Можно ли передавать параметры в пользовательский тег?
- Как вызвать тег и сохранить результат в переменную?
- Что делает
inclusion_tagпри рендеринге страницы? - Как подсветить активный элемент списка с помощью параметра?
- Что произойдёт, если забыть подключить
{% load movie_tags %}? - Приведи пример, когда
inclusion_tagпредпочтительнееsimple_tag.