Веб-приложение состоит из веб-страниц. Даже если приложение реализует большую часть интерфейса пользователя с помощью JavaScript, домашняя страница все равно отдается сервером. Да и ситуации, когда страничный сайт хорошо решает поставленные задачи, возникают часто. Поэтому подсистема шаблонизации остается важной частью full-stack фреймворков.
В этом уроке разберем, что такое шаблонизация, и как на ее основе Django формирует HTML-страницы.
Язык шаблонизации
Шаблон — это почти готовый текстовый документ, в котором все неизменные части текста представлены как есть. А изменяемая часть описана на некотором языке с ветвлениями, условиями и циклами. Они позволяют преобразовать данные в подходящее представление. Этот язык называют языком шаблонизации или templating language.
На уровне шаблона разделяется ответственность. Шаблон страницы знает, как отобразить данные в виде HTML. А сами данные не зависят от этого представления и могут быть представлены по-разному разными шаблонами. По данным вообще нельзя сказать, что они будут представлены в виде HTML. Шаблонизатор возводит барьер абстракции.
Чтобы из кода, который формирует данные, не нужно было программировать отображение, у шаблонизатора есть свой язык программирования отображения. А для того, чтобы не было соблазна прямо в шаблоне получать данные, этот язык сделан с ограниченными возможностями.
В Django шаблонизация проработана достаточно глубоко. Есть встроенный шаблонизатор, можно использовать сторонние шаблонизаторы вроде Jinja2 и можно сочетать несколько шаблонизаторов в одном проекте. Это удобно, когда некоторые шаблоны достаются в наследство, либо когда мы переезжаем с одного шаблонизатора на другой.
Чтобы шаблон превратить в результат, Django использует бэкенды. Встроенных бэкенда два: DjangoTemplates и Jinja2. Сторонние пакеты могут предоставлять свои бэкенды.
Чтобы бэкенд мог что-то сделать с шаблоном, нужно этот шаблон где-то взять. Django самостоятельно загружает шаблоны из файлов, знает, где эти файлы найти, и запоминает/кэширует часто используемые шаблоны в памяти. Нам остается только помнить имя шаблона, который хотим использовать в данный момент.
Настраивается это в settings.py с помощью переменной TEMPLATES. Выглядит настройка так:
- DIRS — параметр, который перечисляет директории, где будет производиться поиск шаблонов
- APP_DIRS — параметр, который разрешает Django искать шаблоны в директориях приложений
Когда APP_DIRS включен, в каждом подключенном приложении будут по умолчанию искаться директории templates, а в них — шаблоны.
По умолчанию в качестве директории с шаблонами принято указывать templates:
Шаблоны приложений
У одного приложения в нашем учебном проекте уже есть шаблон. Речь идет о главном приложении hexlet_django_blog и шаблоне hexlet_django_blog/templates/index.html. Django находит и загружает этот шаблон, потому что опция APP_DIRS включена по умолчанию.
Если сейчас создать новый шаблон hexlet_django_blog/article/templates/index.html, то при поиске по имени index.html первым будет найден старый шаблон hexlet_django_blog/templates/index.html. Так происходит, потому что приложение hexlet_django_blog находится в списке settings.INSTALLED_APPS выше приложения hexlet_django_blog.article. Если переставить приложения местами, то загрузчик шаблонов учтет изменения.
С помощью управления порядком подключения приложений можно переопределять шаблоны одних приложений шаблонами из других. Это интересная возможность, но из-за нее же приходится следить за тем, чтобы шаблоны не перепутывались случайно.
Чтобы не думать слишком много над тем, как не путать шаблоны, стоит придерживаться правила:
Все шаблоны проекта, которые используются только в нем, стоит хранить в отдельной директории в корне проекта и держать эту директорию в порядке — использовать поддиректории и хорошие имена. Директорию нужно добавить в settings.TEMPLATES.DIRS.
Для тех же приложений, которые планируется использовать повторно, внутри соответствующей директории templates нужно иметь поддиректорию с именем приложения и все шаблоны располагать в ней. Это уменьшит вероятность конфликтов имен шаблонов.
Контекст шаблонизации
Шаблоны превращаются в текст с помощью выполнения кода на языке шаблонизатора. Этот код обычно подразумевает использование каких-то данных, получаемых вне шаблона. Данные передаются в шаблон с помощью контекста, который представляет собой словарь. Когда мы вызываем render(request, 'template.html', context={}), мы передаем этот самый словарь в качестве аргумента.
Часто возникает задача иметь в контексте шаблона доступ к параметрам запроса или настройкам проекта. Передавать такие вещи явно слишком утомительно, поэтому у Django есть механизм Context Processors — посредники между нами и шаблоном, которые расширяют контекст единым образом для всех шаблонов.
Процессоры контекста вызываются как функции, которые получают в качестве аргумента request и возвращают словарь. Затем этот словарь дополняет предоставленный нами контекст. Подключаются context processors в settings.TEMPLATES.OPTIONS.context_processors — это список строк с полными именами процессоров.
С Django поставляется много готовых процессоров контекста. Вот два примера:
- django.template.context_processors.request добавляет в контекст переменную request с очевидным значением
- django.template.context_processors.debug добавляет переменную debug, которая истинна, если сервер запущен в режиме разработчика. С помощью этой переменной можно выводить какую-то отладочную информацию, которая не будет видна в боевом режиме
В дополнение к встроенным можно создавать и свои процессоры контекста или же брать их из сторонних библиотек.
Работа с шаблонизатором Django
Пример простого Django-шаблона:
Шаблон выглядит как HTML, но со вставками кода. Во многом встроенный Django шаблонизатор схож с шаблонизатором Jinja2 — подстановка любого значения выполняется в двойных фигурных скобках {{ ... }}. Это очень похоже на интерполяцию, если сам шаблон рассматривать как строку.
Под капотом Django выполняет дополнительную обработку и экранирует любые данные, которые вставлены таким образом. Это значит, что нам не нужно прилагать дополнительные усилия, чтобы обеспечить безопасность в шаблонах.
Данные в шаблон поступают из вью в виде контекста:
Двойные фигурные скобки позволяют не только выводить информацию, которую передали в контексте, но и производить ее простую обработку при помощи встроенных тегов и фильтров шаблонов. Главное правило — не злоупотреблять. В основном данные должны быть подготовлены в обработчике до того, как они попадут в шаблон. Тут нужно помнить: шаблоны — про отображение, а не про логику работы.
Кроме подстановки во встроенном шаблонизаторе есть инструкции. С их помощью реализованы все управляющие конструкции, например, циклы, условия и многое другое:
Инструкции всегда оборачиваются в фигурные скобки со знаком процента {% ... %} и часто имеют закрывающую часть. При помощи инструкций можно делать практически то же самое, что и в самом Python. В инструкциях, как и в подстановках, можно использовать переменные из контекста.
Простые конструкции вроде обращения по ключу, по индексу, по имени атрибута нам доступны. Поэтому контекст может содержать и сложносоставные сущности. А вот вызывать функции и методы уже не получится — это то самое отделение логики от представления.
<!DOCTYPE html>
<html class="h-100" data-bs-theme="light" data-mantine-color-scheme="light" lang="ru" prefix="og: https://ogp.me/ns#">
<head>
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<link crossorigin="true" href="https://cdn.hexlet.io" rel="preconnect">
<link href="https://mc.yandex.ru" rel="preconnect">
<meta content="aa2vrdtq64dub8knuf83lwywit311w" name="facebook-domain-verification">
<link href="/favicon.ico" rel="icon" sizes="any">
<link href="/favicon.svg" rel="icon" type="image/svg+xml">
<link href="/apple-touch-icon.png" rel="apple-touch-icon">
<link href="/manifest.webmanifest" rel="manifest">
<script>
//<![CDATA[
window.gon={};gon.ym_counter="25559621";gon.is_bot=true;gon.applications={};gon.current_user={"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26 23:02:48 UTC","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false};gon.token="BsFajAqvQQcB47U3VSUg3udKffKmwQl9Q-us6R22pBXpEJG7-NHsZ7egka9ZKtCpJ0NQWK7299_-Cza9T7FDew";gon.locale="ru";gon.language="ru";gon.theme="light";gon.rails_env="production";gon.mobile=false;gon.google={"analytics_key":"UA-1360700-51","optimize_key":"GTM-5QDVFPF"};gon.captcha={"google_v3_site_key":"6LenGbgZAAAAAM7HbrDbn5JlizCSzPcS767c9vaY","yandex_site_key":"ysc1_Vyob5ZPPUdPBsu0ykt8bVFdzsfpoVjQChLGl2b4g19647a89","verification_failed":null};gon.social_signin=false;gon.typoreporter_google_form_id="1FAIpQLSeibfGq-KvWQ2Fyru-zkFFRVTLBuzXAHAoEyN1p49FtDmNoNA";
//]]>
</script>
<meta charset="utf-8">
<title>Шаблонизация | Python: Разработка на фреймворке Django</title>
<meta name="description" content="Шаблонизация / Python: Разработка на фреймворке Django: Узнаем, как Django формирует HTML-страницы на основе шаблонов">
<link rel="canonical" href="https://ru.hexlet.io/courses/python-django-basics/lessons/templates/theory_unit">
<meta name="robots" content="noarchive">
<meta property="og:title" content="Шаблонизация">
<meta property="og:title" content="Python: Разработка на фреймворке Django">
<meta property="og:description" content="Шаблонизация / Python: Разработка на фреймворке Django: Узнаем, как Django формирует HTML-страницы на основе шаблонов">
<meta property="og:url" content="https://ru.hexlet.io/courses/python-django-basics/lessons/templates/theory_unit">
<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="tXl7IL_7aFp7x-ymnifb4ePcfsOK-QlOSsoLKUPVB_paqLAXTYXFOs2EyD6SKCuWI9VTaYLO9-z3KpF9EdLglA" />
<script src="/vite/assets/inertia-DfXos102.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/preload-helper-BJ4cLWpC.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ahoy-DrlRQ-1D.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/analytics-cb8xch9l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Surface-DL2bpZA-.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/extends-C-EagtpE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/inheritsLoose-BBd-DCVI.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/objectWithoutPropertiesLoose-DRHXDhjp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/index.esm-DAqKOkZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Button-CGPUux8l.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/CloseButton-D1euiPao.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Group-BX48WcuU.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Loader-BQEY8g6v.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Modal-Cy3HByv7.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/OptionalPortal-1Hza5P2w.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Stack-CtjJzfw4.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Textarea-Ck64llAy.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/DirectionProvider-Dc9zdUke.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/events-DJQOhap0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-reduced-motion-D2owz4wa.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-disclosure-zKtK5W1r.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/use-hotkeys-Cnc_Rwkb.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/random-id-DOQyszCZ.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/exports-C_MrNx_T.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<link rel="stylesheet" href="/vite/assets/application-BqhCP46M.js" />
<script src="/vite/assets/application-Df9RExpe.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/autocomplete-VMNbxKGl.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/createPopper-C3aM9r1M.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/js.cookie-D1-O8zkX.js" as="script" crossorigin="anonymous"><link rel="stylesheet" href="/vite/assets/application-C8HjmMaq.css" media="screen" />
<script>
window.ym = function(){(ym.a=ym.a||[]).push(arguments)};
window.addEventListener('load', function() {
setTimeout(function() {
ym.l = 1*new Date();
ym(window.gon.ym_counter, "init", {
clickmap: true,
trackLinks: true,
accurateTrackBounce: true,
webvisor: true
});
// Загружаем скрипт
var k = document.createElement('script');
k.async = 1;
k.src = 'https://mc.yandex.ru/metrika/tag.js';
document.head.appendChild(k);
ym(window.gon.ym_counter, 'getClientID', function(clientID) {
window.ymClientId = clientID;
});
}, 1500);
});
</script>
<!-- Google Tag Manager - deferred -->
<script>
// dataLayer stub сразу — пуши работают до загрузки скрипта
window.dataLayer = window.dataLayer || [];
// Сам скрипт — отложенно после load
window.addEventListener('load', function() {
setTimeout(function() {
dataLayer.push({'gtm.start': new Date().getTime(), event: 'gtm.js'});
var j = document.createElement('script');
j.async = true;
j.src = 'https://www.googletagmanager.com/gtm.js?id=GTM-WK88TH';
document.head.appendChild(j);
}, 1500);
});
</script>
<!-- End Google Tag Manager -->
</head>
<body>
<noscript>
<div>
<img alt="" src="https://mc.yandex.ru/watch/25559621" style="position:absolute; left:-9999px;">
</div>
</noscript>
<header class="sticky-top bg-body">
<nav class="navbar navbar-expand-lg">
<div class="container-xxl">
<a class="navbar-brand" href="/"><img alt="Логотип Хекслета" height="24" src="https://ru.hexlet.io/vite/assets/logo_ru_light-BpiEA1LT.svg" width="96">
</a><button aria-controls="collapsable" aria-expanded="false" aria-label="Меню" class="navbar-toggler border-0 mb-0 mt-1" data-bs-target="#collapsable" data-bs-toggle="collapse">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="collapsable">
<ul class="navbar-nav mb-lg-0 mt-lg-1">
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
Все курсы
<span class="bi bi-chevron-down align-middle ms-1"></span>
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item d-flex py-2" href="/courses"><div class="fw-bold me-auto">Все что есть</div>
<div class="text-muted">117</div>
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные категории</b>
</li>
<li>
<a class="dropdown-item py-2" href="/courses_devops">Курсы по DevOps
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_data_analytics">Курсы по аналитике данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_programming">Курсы по программированию
</a></li>
<li>
<a class="dropdown-item py-2" href="/courses_testing">Курсы по тестированию
</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li class="dropdown-item">
<b>Популярные курсы</b>
</li>
<li>
<a class="dropdown-item py-2" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/go">Go-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/java">Java-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/python">Python-разработчик
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/qa-auto-engineer-java">Автоматизатор тестирования на Java
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/data-analytics">Аналитик данных
</a></li>
<li>
<a class="dropdown-item py-2" href="/programs/frontend">Фронтенд-разработчик
</a></li>
</ul>
</li>
<li class="nav-item dropdown">
<button aria-haspopup class="btn nav-link" data-bs-toggle="dropdown" type="button">
О Хекслете
<span class="bi bi-chevron-down align-middle"></span>
</button>
<ul class="dropdown-menu bg-body">
<li>
<a class="dropdown-item py-2" href="/pages/about">О нас
</a></li>
<li>
<a class="dropdown-item py-2" href="/blog">Блог
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/hse-research" role="button">Результаты (Исследование)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://career.hexlet.io" role="button">Хекслет Карьера
</span></li>
<li>
<a class="dropdown-item py-2" href="/testimonials">Отзывы студентов
</a></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://t.me/hexlet_help_bot" role="button">Поддержка (В ТГ)
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/referal-program/?promo_creative=priglasite-druzei&promo_name=referal-program&promo_position=promo_position&promo_start=010724&promo_type=link" role="button">Реферальная программа
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://special.hexlet.io/certificate" role="button">Подарочные сертификаты
</span></li>
<li>
<span class="dropdown-item py-2 external-link" data-href="https://hh.ru/employer/4307094" role="button">Вакансии
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://b2b.hexlet.io" data-target="_blank" role="button">Компаниям
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexly.ru/" data-target="_blank" role="button">Колледж
</span></li>
<li>
<span class="dropdown-item d-flex external-link" rel="noopener noreferrer nofollow" data-href="https://hexlyschool.ru/" data-target="_blank" role="button">Частная школа
</span></li>
</ul>
</li>
<li><a class="nav-link" href="/subscription/new">Подписка</a></li>
</ul>
<ul class="navbar-nav flex-lg-row align-items-lg-center gap-2 ms-auto">
<li>
<a class="nav-link" aria-label="Переключить тему" href="/theme/switch?new_theme=dark"><span aria-hidden="true" class="bi bi-moon"></span>
</a></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="/u/new" role="button"><span>Регистрация</span>
</span></li>
<li>
<span data-target="_self" class="nav-link external-link" data-href="https://ru.hexlet.io/session/new" role="button"><span>Вход</span>
</span></li>
</ul>
</div>
</div>
</nav>
</header>
<div class="x-container-xxxl">
</div>
<main class="mb-6 min-vh-100 h-100">
<link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"/><link rel="preload" as="image" href="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png"/><link rel="preload" as="image" href="/vite/assets/development-BVihs_d5.png"/><div id="app" data-page="{"component":"web/courses/lessons/theory_unit","props":{"errors":{},"locale":"ru","language":"ru","httpsHost":"https://ru.hexlet.io","host":"ru.hexlet.io","colorScheme":"light","auth":{"user":{"id":null,"last_viewed_notification_id":null,"email":null,"state":null,"first_name":"","last_name":"","created_at":"2026-02-26T23:02:48.307Z","current_program":null,"current_team":null,"full_name":"","guest":true,"can_use_paid_features":false,"is_hexlet_employee":false,"sanitized_phone_number":"","can_subscribe":true,"can_renew_education":false}},"cloudflareTurnstileSiteKey":"0x4AAAAAAA15KmeFXzd2H0Xo","vkIdClientId":"51586979","yandexIdClientId":"88d071f1d3384eb4bd1deb37910235c7","formAuthToken":"8D6GgvCMTmgvtrZxTuzbYTWA16Hu5vsKvGKPJT2T_Ccf7021AvLjCJn1kulC4ysW9Yn6C-bRBagBghVxb5QbSQ","topics":[{"id":75000,"title":"Добый день! Тестить проект в тренажере, браузере не очень удобно, если способ быстро стянуть его к себе на машину и тестить локально?","plain_title":"Добый день! Тестить проект в тренажере, браузере не очень удобно, если способ быстро стянуть его к себе на машину и тестить локально? ","creator":{"public_name":"Сергей Федоров","id":462935,"is_tutor":false},"comments":[{"creator":{"public_name":"Сергей Федоров","id":462935,"is_tutor":false},"id":156291,"body":"я к чему это спросил, делал задание. Открываю веб доступ - Bad Gateway. Потратил примерно 2 часа, психанул, закрыл ноут, уехал. Через пару часов вернулся, открыл ноут, все работает. (в коде ничего не менял. Веб доступ открывается и отображает страницы) . Как можно избежать таких проблем?","topic_id":75000},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":156262,"body":"Добрый день, к сожалению только если скопировать содержимое всех файлов. ","topic_id":75000},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":156466,"body":"Bad Gateway это ожидаемый ответ, если у вас упал сервер. Возможно у вас была где-то синтаксическая ошибка в коде, вот сервер и падал.","topic_id":75000}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":78307,"title":"Уже второй урок непонятно откуда появляется **\"hello_django\"**, откуда ему взяться?? У нас ващет пакет **\"hexlet_django_blog\"**... :/\n\nЕсли \"hello_django\" - ошибка, и там должно быть \"hexlet_django_blog\", тогда непонятны вот эти строки:\n\n> При **\"включенном\" APP_DIRS** в каждом **подключенном** приложении будет искаться директория \"templates\", а в ней — шаблоны.\n\n> ... **главное приложение hello_django** и **шаблон hello_django/templates/index.html**. Django находит и загружает этот шаблон, **потому что опция APP_DIRS включена по умолчанию**.\n\nПричем здесь шаблон **\"hello_django/templates/index.html\"**, если это шаблон главного приложения, а не подключенного?\n\nUPD: Но действительно, если поставить `'APP_DIRS': False`, то страница перестает подгружаться и выдает ошибку \"TemplateDoesNotExist\". Где же логика? :))","plain_title":"Уже второй урок непонятно откуда появляется \"hello_django\", откуда ему взяться?? У нас ващет пакет \"hexletdjangoblog\"... :/ Если \"hellodjango\" - ошибка, и там должно быть \"hexletdjango_blog\", тогда непонятны вот эти строки: При \"включенном\" APP_DIRS в каждом подключенном приложении будет искаться директория \"templates\", а в ней — шаблоны. ... главное приложение hello_django и шаблон hello_django/templates/index.html. Django находит и загружает этот шаблон, потому что опция APP_DIRS включена по умолчанию. Причем здесь шаблон \"hello_django/templates/index.html\", если это шаблон главного приложения, а не подключенного? UPD: Но действительно, если поставить 'APP_DIRS': False, то страница перестает подгружаться и выдает ошибку \"TemplateDoesNotExist\". Где же логика? :)) ","creator":{"public_name":"Игорь Гахов","id":376702,"is_tutor":false},"comments":[{"creator":{"public_name":"Игорь Гахов","id":376702,"is_tutor":false},"id":161766,"body":"Но действительно, если поставить `'APP_DIRS': False`, то страница перестает подгружаться и выдает ошибку \"TemplateDoesNotExist\". Где же логика? :))","topic_id":78307},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":162038,"body":"Добрый день, сейчас курс в стадии улучшения и переписывания на более подробный. Это осталось от старого. Поправили","topic_id":78307},{"creator":{"public_name":"Игорь Гахов","id":376702,"is_tutor":false},"id":162069,"body":"Иван, добрый день! Перечитал теорию. Теперь вроде понятно, спасибо!","topic_id":78307},{"creator":{"public_name":"Игорь Гахов","id":376702,"is_tutor":false},"id":161761,"body":"И следом вопрос:\n\nЕсли **\"hello_django\"** - ошибка, и там должно быть **\"hexlet_django_blog\"**, тогда непонятны вот эти строки:\n\n> При **\"включенном\" APP_DIRS** в каждом **подключенном** приложении будет искаться директория \"templates\", а в ней — шаблоны.\n\n> ... **главное приложение hello_django** и **шаблон hello_django/templates/index.html**. Django находит и загружает этот шаблон, **потому что опция APP_DIRS включена по умолчанию**.\n\nПричем здесь шаблон **\"hello_django/templates/index.html\"**, если это шаблон главного приложения, а не подключенного?","topic_id":78307}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":76740,"title":"Помогите, пожалуйста, с [решением](https://ru.hexlet.io/code_reviews/747870).\nНе совсем понимаю почему ошибка ругается нa \"name\"..\nПроблема с тестами \"test_article_view\" & \"test_articles\". Никак не могу найти ошибку","plain_title":"Помогите, пожалуйста, с решением (https://ru.hexlet.io/code_reviews/747870). Не совсем понимаю почему ошибка ругается нa \"name\".. Проблема с тестами \"testarticleview\" & \"test_articles\". Никак не могу найти ошибку ","creator":{"public_name":"Владимир Жмур","id":484068,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":159655,"body":"Добрый день, по вопросам с кодом вы можете обратиться к своему наставнику или в групповой чат. Так вы сможете получить помощь более оперативно, чем в обсуждениях.","topic_id":76740}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":77114,"title":"Также, как кажется, в тексте упражнения содержится ошибка\nsimple_blog/articles/index.html\nДопишите шаблон для вывода списка статей. У каждой статьи должен выводиться заголовок name, автор author и ссылка на ее страницу article/id. Используйте тег {% url %} для построения ссылки.\nоднако в view передаются ключи id,author,title а name отсутствует","plain_title":"Также, как кажется, в тексте упражнения содержится ошибка simple_blog/articles/index.html Допишите шаблон для вывода списка статей. У каждой статьи должен выводиться заголовок name, автор author и ссылка на ее страницу article/id. Используйте тег {% url %} для построения ссылки. однако в view передаются ключи id,author,title а name отсутствует ","creator":{"public_name":"Юрий Кормин","id":412035,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":160224,"body":"Спасибо за внимательность! Ошибку исправил.","topic_id":77114}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":74570,"title":"Тесты прошли, а Веб-доступ так и не заработал – *Bad Gateway*.","plain_title":"Тесты прошли, а Веб-доступ так и не заработал – Bad Gateway. ","creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"comments":[{"creator":{"public_name":"Grigory Soldatov","id":466662,"is_tutor":false},"id":156524,"body":"**Сергей К.**, make start в консоли лучше делать, а потом заходить в веб-доступ, тогда в случае синтаксической ошибки можно сервер перезапустить.","topic_id":74570},{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":155493,"body":"А пришлите ссылку на решение.","topic_id":74570},{"creator":{"public_name":"Сергей К.","id":5174,"is_tutor":false},"id":155515,"body":"Вот ссылка – https://ru.hexlet.io/code_reviews/709313. Но сейчас веб-доступ уже работает.","topic_id":74570}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":88941,"title":"Всем привет\nА что это за оператор такой?\n'DIRS': [BASE_DIR / 'templates'],\n","plain_title":"Всем привет А что это за оператор такой? 'DIRS': [BASE_DIR / 'templates'], ","creator":{"public_name":"Николай Кузьмин","id":481138,"is_tutor":false},"comments":[{"creator":{"public_name":"Николай Кузьмин","id":481138,"is_tutor":false},"id":177521,"body":"**Artyom Kropp**, спасибо вам за ответ","topic_id":88941},{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":177417,"body":"Добрый день. \n\nОбратите внимание на `BASE_DIR` - эта константа определяется с помощью библиотеки `Pathlib`. Объекты данной библиотеки позволяют изменять путь как мы это делали бы в Unix системе - с помощью оператора `/`. Получается данное выражение можно прочитать как: к пути `BASE_DIR` добавить путь `'templates'`. Подробнее можно прочитать на [официальной странице документации](https://docs.python.org/3/library/pathlib.html#operators).","topic_id":88941}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":84645,"title":"Так и не понял по теории как сделать так, чтобы `index.html` из `article `отображался первым.\nВ теории: \n\n> Если сейчас создать новый шаблон hexlet_django_blog/**article/templates**/index.html [...] settings.INSTALLED_APPS выше приложения hexlet_django_blog.article. Если переставить приложения местами, то загрузчик шаблонов учтет изменения.\n\nПереставил, не сработало.\n\nПошел читать обсуждения, ответ поддержки на аналогичный вопрос:\n\n> Обратите внимание на то, что шаблоны для нового приложения следует размещать в отдельную директорию **templates/article**.\n\nТ.е. в двух местах информация разная.\n\nПрочитал статью из документации, все равно так и не понял, как выводить шаблоны из приложения. Сходу разобраться такое себе.\n\nМожет быть добавить схему дерева проекта и пару примеров с файлами настроек?","plain_title":"Так и не понял по теории как сделать так, чтобы index.html из articleотображался первым. В теории: Если сейчас создать новый шаблон hexletdjangoblog/article/templates/index.html [...] settings.INSTALLEDAPPS выше приложения hexletdjango_blog.article. Если переставить приложения местами, то загрузчик шаблонов учтет изменения. Переставил, не сработало. Пошел читать обсуждения, ответ поддержки на аналогичный вопрос: Обратите внимание на то, что шаблоны для нового приложения следует размещать в отдельную директорию templates/article. Т.е. в двух местах информация разная. Прочитал статью из документации, все равно так и не понял, как выводить шаблоны из приложения. Сходу разобраться такое себе. Может быть добавить схему дерева проекта и пару примеров с файлами настроек? ","creator":{"public_name":"Ilia Kaziamov","id":482248,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":171509,"body":"Добрый день. Спасибо, что обратили на это внимание. Обратную связь передали автору курса.","topic_id":84645}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":50563,"title":"Добрый день. Подскажите, правильно ли я понимаю механику.\nВ случае использования вьюхи как функции для использования template в нашем задании используется shortcut render(), который возвращается из вьюхи.\nА в случае использования class-based вьюхи (с предком TemplateView), нам достаточно объявить в своем классе-вьюхе атрибут \"template_name\" со значением названия шаблона, а уже классовый метод as_view() в своем внутреннем механизме вызовет аналог shortcut'а render() - метод render_to_response(), который и сделает тоже самое - \"отрендерит\" заданный шаблон.\nПравильно?","plain_title":"Добрый день. Подскажите, правильно ли я понимаю механику. В случае использования вьюхи как функции для использования template в нашем задании используется shortcut render(), который возвращается из вьюхи. А в случае использования class-based вьюхи (с предком TemplateView), нам достаточно объявить в своем классе-вьюхе атрибут \"templatename\" со значением названия шаблона, а уже классовый метод asview() в своем внутреннем механизме вызовет аналог shortcut'а render() - метод rendertoresponse(), который и сделает тоже самое - \"отрендерит\" заданный шаблон. Правильно? ","creator":{"public_name":"Егор","id":318046,"is_tutor":false},"comments":[{"creator":{"public_name":"Sergej Frescher","id":260254,"is_tutor":false},"id":108308,"body":"**Егор**, попробовал только что реализовать через наследование TemplateView. Всё получилось. Отличие от наследования от View только в том, что изменять нужно метод get_context_data, а не метод get. Так даже удобнее.","topic_id":50563},{"creator":{"public_name":"Sergej Frescher","id":260254,"is_tutor":false},"id":108518,"body":"calc/views.py\n\n from django.views.generic.base import TemplateView\n\n class index(TemplateView):\n template_name = 'calc/index.html'\n def get_context_data(self, A, B, **kwargs):\n context = super().get_context_data(**kwargs)\n context['first_number'] = A\n context['second_number'] = B\n context['summation'] = (A + B)\n return context\n\nдумаю вы всё правильно поняли\n","topic_id":50563},{"creator":{"public_name":"Егор","id":318046,"is_tutor":false},"id":108326,"body":"**Sergej Frescher**, Вы переопределяли get_context_data для того, чтобы просчитать сумму для вывода словаря контекста (уже не только с аргументами из url, но и суммой) в шаблон (чтобы в шаблоне не высчитывать результат), верно я понял?","topic_id":50563},{"creator":{"public_name":"Andrey Volkovitskiy","id":504690,"is_tutor":false},"id":171164,"body":"При определении папки c шаблонами используется непривычный синтаксис:\n```\n# hexlet_django_blog/settings.py\nTEMPLATES = [\n {\n ...\n 'DIRS': [BASE_DIR / 'templates'],\n ...\n },\n]\n```\nКак Python интерпретирует выражение `[A / 'B']`?\nЭто какой-то вариант списка или что-то совершенно иное?","topic_id":50563}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":71185,"title":"Подскажите, где ошибся? Что не так? Или все не так? https://ru.hexlet.io/code_reviews/651995","plain_title":"Подскажите, где ошибся? Что не так? Или все не так? https://ru.hexlet.io/code_reviews/651995 ","creator":{"public_name":"Ильнар Мухаметов","id":233102,"is_tutor":false},"comments":[{"creator":{"public_name":"Ivan Mamtsev","id":294764,"is_tutor":true},"id":148874,"body":"Добрый день, у вас все так, но вы попались на [перекрытии имен](https://ru.hexlet.io/courses/js-functions-hard-way/lessons/lexical-environment/theory_unit).","topic_id":71185},{"creator":{"public_name":"Ильнар Мухаметов","id":233102,"is_tutor":false},"id":148855,"body":"Разобрался\n","topic_id":71185}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}},{"id":81442,"title":"Я не могу понять как в самостоятельной работе правильно подключить темплейты приложения articles и избежать конфликта имен index.html. Теорию раз 10 перечитал, там говорится только о том, что если поменять местами приложения в INSTALLED APPS, то сперва найдется шаблон из верхнего приложения. Но как это помогает с конфликтами имен? все равно в оба приложения будут работать с одним и тем же шаблоном, хоть уже и с другим","plain_title":"Я не могу понять как в самостоятельной работе правильно подключить темплейты приложения articles и избежать конфликта имен index.html. Теорию раз 10 перечитал, там говорится только о том, что если поменять местами приложения в INSTALLED APPS, то сперва найдется шаблон из верхнего приложения. Но как это помогает с конфликтами имен? все равно в оба приложения будут работать с одним и тем же шаблоном, хоть уже и с другим ","creator":{"public_name":"Андрей Феоктистов","id":428476,"is_tutor":false},"comments":[{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":166440,"body":"Добрый день! Обратите внимание на то, что шаблоны для нового приложения следует размещать в отдельную директорию `templates/article`.","topic_id":81442},{"creator":{"public_name":"Андрей Феоктистов","id":428476,"is_tutor":false},"id":166511,"body":"Это понятно. Не совсем очевидно было как джанго должен найти этот шаблон, я думал нужно подключить директорию article.templates в settings.TEMPLATES.DIRS, но оказалось достаточно во вьюхе указать путь до шаблона, учитывая директорию article (article/index.html)","topic_id":81442},{"creator":{"public_name":"Artyom Kropp","id":381127,"is_tutor":true},"id":166534,"body":"Да, это может быть не совсем очевидно, но Django самостоятельно ищет директорию templates в директории проекта и его приложений, если это указано в настройках. Подробнее об этом можно прочитать на [странице документации](https://docs.djangoproject.com/en/4.1/topics/templates/#module-django.template).","topic_id":81442}],"communitable":{"parent_entity_name":null,"parent_entity_url":null,"entity_name":"Шаблонизация","entity_url":null,"active":true}}],"lesson":{"exercise":{"id":2000,"slug":"python_django_basics_templates_exercise","name":null,"state":"active","kind":"exercise","language":"python","locale":"ru","has_web_view":true,"has_test_view":false,"reviewable":true,"readme":"## simple_blog/views.py\n\n* Реализуйте обработчик маршрута */about*, который передает в шаблон *about.html* переменную `TEAM`. Она содержит список сотрудников компании с указанием их имени и должности\n* Выведите на экран информацию о сотрудниках в произвольном формате\n","prepared_readme":"## simple_blog/views.py\n\n* Реализуйте обработчик маршрута */about*, который передает в шаблон *about.html* переменную `TEAM`. Она содержит список сотрудников компании с указанием их имени и должности\n* Выведите на экран информацию о сотрудниках в произвольном формате\n","has_solution":true,"entity_name":"Шаблонизация"},"units":[{"id":3769,"name":"theory","url":"/courses/python-django-basics/lessons/templates/theory_unit"},{"id":6431,"name":"quiz","url":"/courses/python-django-basics/lessons/templates/quiz_unit"},{"id":6478,"name":"exercise","url":"/courses/python-django-basics/lessons/templates/exercise_unit"}],"links":[{"id":425724,"name":"Django Templates","url":"https://docs.djangoproject.com/en/5.2/topics/templates/"},{"id":425725,"name":"Django tags and filters","url":"https://docs.djangoproject.com/en/5.2/ref/templates/builtins/"}],"ordered_units":[{"id":3769,"name":"theory","url":"/courses/python-django-basics/lessons/templates/theory_unit"},{"id":6431,"name":"quiz","url":"/courses/python-django-basics/lessons/templates/quiz_unit"},{"id":6478,"name":"exercise","url":"/courses/python-django-basics/lessons/templates/exercise_unit"}],"id":1729,"slug":"templates","state":"approved","name":"Шаблонизация","course_order":160,"goal":"Узнаем, как Django формирует HTML-страницы на основе шаблонов","self_study":"1. Добавьте для `hexlet_django_blog.article.views.index` отдельный шаблон, который поместите в приложении `hexlet_django_blog.article` в директорию `templates/articles`\n2. Выводите название приложения с помощью шаблона. Используйте подстановки, все значения передавайте через контекст\n","theory_video_provider":null,"theory_video_uid":null,"theory":"Веб-приложение состоит из веб-страниц. Даже если приложение реализует большую часть интерфейса пользователя с помощью JavaScript, домашняя страница все равно отдается сервером. Да и ситуации, когда страничный сайт хорошо решает поставленные задачи, возникают часто. Поэтому подсистема шаблонизации остается важной частью full-stack фреймворков.\n\nВ этом уроке разберем, что такое шаблонизация, и как на ее основе Django формирует HTML-страницы.\n\n## Язык шаблонизации\n\n**Шаблон** — это почти готовый текстовый документ, в котором все неизменные части текста представлены как есть. А изменяемая часть описана на некотором языке с ветвлениями, условиями и циклами. Они позволяют преобразовать данные в подходящее представление. Этот язык называют **языком шаблонизации** или **templating language**.\n\nНа уровне шаблона разделяется ответственность. Шаблон страницы знает, как отобразить данные в виде HTML. А сами данные не зависят от этого представления и могут быть представлены по-разному разными шаблонами. По данным вообще нельзя сказать, что они будут представлены в виде HTML. Шаблонизатор возводит барьер абстракции.\n\nЧтобы из кода, который формирует данные, не нужно было программировать отображение, у шаблонизатора есть свой язык программирования отображения. А для того, чтобы не было соблазна прямо в шаблоне получать данные, этот язык сделан с ограниченными возможностями.\n\n## Шаблонизация в Django\n\nВ Django шаблонизация проработана достаточно глубоко. Есть встроенный шаблонизатор, можно использовать сторонние шаблонизаторы вроде Jinja2 и можно сочетать несколько шаблонизаторов в одном проекте. Это удобно, когда некоторые шаблоны достаются в наследство, либо когда мы переезжаем с одного шаблонизатора на другой.\n\nЧтобы шаблон превратить в результат, Django использует бэкенды. Встроенных бэкенда два: DjangoTemplates и Jinja2. Сторонние пакеты могут предоставлять свои бэкенды.\n\nЧтобы бэкенд мог что-то сделать с шаблоном, нужно этот шаблон где-то взять. Django самостоятельно загружает шаблоны из файлов, знает, где эти файлы найти, и запоминает/кэширует часто используемые шаблоны в памяти. Нам остается только помнить имя шаблона, который хотим использовать в данный момент.\n\nНастраивается это в `settings.py` с помощью переменной `TEMPLATES`. Выглядит настройка так:\n\n```python\nTEMPLATES = [\n {\n \"BACKEND\": \"django.template.backends.django.DjangoTemplates\",\n \"DIRS\": [],\n \"APP_DIRS\": False,\n \"OPTIONS\": {\n # ... параметры шаблонизатора ...\n },\n },\n]\n```\n\n* `DIRS` — параметр, который перечисляет директории, где будет производиться поиск шаблонов\n* `APP_DIRS` — параметр, который разрешает Django искать шаблоны в директориях приложений\n\nКогда `APP_DIRS` включен, в каждом подключенном приложении будут по умолчанию искаться директории *templates*, а в них — шаблоны.\n\nПо умолчанию в качестве директории с шаблонами принято указывать *templates*:\n\n```python\n# hexlet_django_blog/settings.py\nfrom pathlib import Path\n\nBASE_DIR = Path(__file__).resolve().parent.parent\n\nTEMPLATES = [\n {\n 'BACKEND': 'django.template.backends.django.DjangoTemplates',\n 'DIRS': [BASE_DIR / 'templates'],\n 'APP_DIRS': True,\n 'OPTIONS': {\n # ... параметры шаблонизатора ...\n },\n]\n```\n\n## Шаблоны приложений\n\nУ одного приложения в нашем учебном проекте уже есть шаблон. Речь идет о главном приложении `hexlet_django_blog` и шаблоне `hexlet_django_blog/templates/index.html`. Django находит и загружает этот шаблон, потому что опция `APP_DIRS` включена по умолчанию.\n\nЕсли сейчас создать новый шаблон `hexlet_django_blog/article/templates/index.html`, то при поиске по имени *index.html* первым будет найден старый шаблон `hexlet_django_blog/templates/index.html`. Так происходит, потому что приложение `hexlet_django_blog` находится в списке `settings.INSTALLED_APPS` выше приложения `hexlet_django_blog.article`. Если переставить приложения местами, то загрузчик шаблонов учтет изменения.\n\nС помощью управления порядком подключения приложений можно переопределять шаблоны одних приложений шаблонами из других. Это интересная возможность, но из-за нее же приходится следить за тем, чтобы шаблоны не перепутывались случайно.\n\nЧтобы не думать слишком много над тем, как не путать шаблоны, стоит придерживаться правила:\n\n> Все шаблоны проекта, которые используются только в нем, стоит хранить в отдельной директории в корне проекта и держать эту директорию в порядке — использовать поддиректории и хорошие имена. Директорию нужно добавить в `settings.TEMPLATES.DIRS`.\n\nДля тех же приложений, которые планируется использовать повторно, внутри соответствующей директории `templates` нужно иметь поддиректорию с именем приложения и все шаблоны располагать в ней. Это уменьшит вероятность конфликтов имен шаблонов.\n\n### Контекст шаблонизации\n\nШаблоны превращаются в текст с помощью выполнения кода на языке шаблонизатора. Этот код обычно подразумевает использование каких-то данных, получаемых вне шаблона. Данные передаются в шаблон с помощью контекста, который представляет собой словарь. Когда мы вызываем `render(request, 'template.html', context={})`, мы передаем этот самый словарь в качестве аргумента.\n\nЧасто возникает задача иметь в контексте шаблона доступ к параметрам запроса или настройкам проекта. Передавать такие вещи явно слишком утомительно, поэтому у Django есть механизм Context Processors — посредники между нами и шаблоном, которые расширяют контекст единым образом для всех шаблонов.\n\nПроцессоры контекста вызываются как функции, которые получают в качестве аргумента `request` и возвращают словарь. Затем этот словарь дополняет предоставленный нами контекст. Подключаются context processors в `settings.TEMPLATES.OPTIONS.context_processors` — это список строк с полными именами процессоров.\n\nС Django поставляется много готовых процессоров контекста. Вот два примера:\n\n* `django.template.context_processors.request` добавляет в контекст переменную `request` с очевидным значением\n* `django.template.context_processors.debug` добавляет переменную `debug`, которая истинна, если сервер запущен в режиме разработчика. С помощью этой переменной можно выводить какую-то отладочную информацию, которая не будет видна в боевом режиме\n\nВ дополнение к встроенным можно создавать и свои процессоры контекста или же брать их из сторонних библиотек.\n\n## Работа с шаблонизатором Django\n\nПример простого Django-шаблона:\n\n```python\n<h1>{{ title }}</h1>\n<p>{{ description }}</p>\n<pre>\n {{ code }}\n</pre>\n<p>Question submitted at: {{ created_at }}</p>\n```\n\nШаблон выглядит как HTML, но со вставками кода. Во многом встроенный Django шаблонизатор схож с шаблонизатором Jinja2 — подстановка любого значения выполняется в двойных фигурных скобках `{{ ... }}`. Это очень похоже на интерполяцию, если сам шаблон рассматривать как строку.\n\nПод капотом Django выполняет дополнительную обработку и экранирует любые данные, которые вставлены таким образом. Это значит, что нам не нужно прилагать дополнительные усилия, чтобы обеспечить безопасность в шаблонах.\n\nДанные в шаблон поступают из вью в виде контекста:\n\n```python\n# hexlet_django_blog/views.py\n\n\ndef about(request):\n tags = [\"обучение\", \"программирование\", \"python\", \"oop\"]\n return render(\n request,\n \"about.html\",\n context={\"tags\": tags},\n )\n```\n\n```python\n<!-- hexlet_django_blog/templates/about.html -->\n\n<h1>О блоге</h1>\n<p>Эксперименты с Django на Хекслете</p>\n<p>{{ tags|join:', ' }}</p>\n```\n\nДвойные фигурные скобки позволяют не только выводить информацию, которую передали в контексте, но и производить ее простую обработку при помощи встроенных [тегов и фильтров](https://docs.djangoproject.com/en/5.2/ref/templates/builtins/) шаблонов. Главное правило — не злоупотреблять. В основном данные должны быть подготовлены в обработчике до того, как они попадут в шаблон. Тут нужно помнить: шаблоны — про отображение, а не про логику работы.\n\nКроме подстановки во встроенном шаблонизаторе есть инструкции. С их помощью реализованы все управляющие конструкции, например, циклы, условия и многое другое:\n\n```python\n{% if records|length == 1 %}\nI have one record!\n{% endif %}\n\n{% for user in users %}\n<p>This is user {{ user.id }}</p>\n{% endfor %}\n```\n\nИнструкции всегда оборачиваются в фигурные скобки со знаком процента `{% ... %}` и часто имеют закрывающую часть. При помощи инструкций можно делать практически то же самое, что и в самом Python. В инструкциях, как и в подстановках, можно использовать переменные из контекста.\n\nПростые конструкции вроде обращения по ключу, по индексу, по имени атрибута нам доступны. Поэтому контекст может содержать и сложносоставные сущности. А вот вызывать функции и методы уже не получится — это то самое отделение логики от представления.\n"},"lessonMember":null,"courseMember":null,"course":{"start_lesson":{"exercise":null,"units":[{"id":3725,"name":"theory","url":"/courses/python-django-basics/lessons/intro/theory_unit"}],"links":[{"id":425720,"name":"Официальный сайт Django","url":"https://www.djangoproject.com/download/"}],"ordered_units":[{"id":3725,"name":"theory","url":"/courses/python-django-basics/lessons/intro/theory_unit"}],"id":1715,"slug":"intro","state":"approved","name":"Введение","course_order":100,"goal":"Знакомимся с темой курса","self_study":null,"theory_video_provider":null,"theory_video_uid":null,"theory":"Django — популярный веб-фреймворк на Python, который предназначен для быстрой разработки сайтов. Он сочетает в себе большие возможности сложных фреймворков и простоту написания кода с минимумом конфигурирования.\n\nВ этом курсе мы будем создавать проект под названием *hexlet-django-blog*. Это простой блог, в который можно добавлять статьи и оставлять комментарии.\n\nВо время создания мы рассмотрим следующие темы:\n\n* Ресурсная (REST-like) маршрутизация. Создание CRUD, валидация данных\n* Шаблонизатор Django. Макеты\n* Управление приложением из командной строки\n* Интеграционное тестирование, фабрики\n* ORM. Создание сущностей. Связи\n\nПрактика этого курса выполняется в среде Хекслета, но для полноценного погружения рекомендуем повторять все действия на своем компьютере. Для этого убедитесь, что в вашей системе установлен Python (>= 3.10) и [uv](https://docs.astral.sh/uv/).\n\nКроме практики в нашей среде почти каждый урок содержит самостоятельную работу. Ее нужно выполнять у себя на компьютере. Эти задания зависят друг от друга. Каждая новая самостоятельная работа базируется на том, что было сделано в предыдущих уроках.\n"},"id":209,"slug":"python-django-basics","challenges_count":3,"name":"Python: Разработка на фреймворке Django","allow_indexing":true,"state":"approved","course_state":"finished","pricing_type":"paid","description":"В этом курсе вы изучите основы работы с Django для разработки веб-приложений на Python. Вы научитесь создавать и настраивать Django-проекты, работать с маршрутами, шаблонами, представлениями, моделями и формами. Также вы познакомитесь с механизмом администрирования, наследованием шаблонов и основами CRUD-операций.","kind":"basic","updated_at":"2026-01-21T09:38:56.947Z","language":"python","duration_cache":74400,"skills":["Создавать сайты с помощью Django","Конфигурировать фреймворк","Использовать систему шаблонов","Взаимодействовать с базой данных через ORM"],"keywords":["роутинг","миграции","шаблонизация","ORM","CRUD"],"lessons_count":19,"cover":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6OTgzOSwicHVyIjoiYmxvYl9pZCJ9fQ==--774286562c37e9366f47ac501461cf7c11e484f6/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJwbmciLCJyZXNpemVfdG9fZmlsbCI6WzYwMCw0MDBdfSwicHVyIjoidmFyaWF0aW9uIn19--6067466c2912ca31a17eddee04b8cf2a38c6ad17/image.png"},"recommendedLandings":[{"stack":{"id":7,"slug":"python","title":"Python-разработчик","audience":"for_beginners","start_type":"weekly","pricing_model":"purchase","priority":"high","kind":"profession","state":"published","stack_state":"finished","order":10,"duration_in_months":10},"id":7,"slug":"python","title":"Python-разработчик ","subtitle":"Изучите Python, Django, REST и Fast API для создания веб-приложений","subtitle_for_lists":"Изучите Python, Django, REST и Fast API для создания веб-приложений","locale":"ru","current":true,"duration_in_months_text":"10 месяцев","stack_slug":"python","price_text":"от 6 792 ₽","duration_text":"10 месяцев","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png"},{"stack":{"id":24,"slug":"python-django-development","title":"Django","audience":"for_programmers","start_type":"anytime","pricing_model":"subscription","priority":"medium","kind":"track","state":"published","stack_state":"finished","order":300,"duration_in_months":2},"id":36,"slug":"python-django-developer","title":"Django","subtitle":"Навык создания веб-приложений на Django, необходимый для получения оффера на позицию Python-разработчика","subtitle_for_lists":"Изучите фреймворк Django для создания веб-приложений ","locale":"ru","current":true,"duration_in_months_text":"2 месяца","stack_slug":"python-django-development","price_text":"от 3 900 ₽","duration_text":"2 месяца","cover_list_variant":"https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png"}],"lessonMemberUnit":null,"accessToLearnUnitExists":false,"accessToCourseExists":false},"url":"/courses/python-django-basics/lessons/templates/theory_unit","version":"8f286f6358a90a7bef2263b3a6edf5a90a94fa42","encryptHistory":false,"clearHistory":false}"><style data-mantine-styles="true">:root, :host{--mantine-font-family: Arial, sans-serif;--mantine-font-family-headings: Arial, sans-serif;--mantine-heading-font-weight: normal;--mantine-radius-default: 0rem;--mantine-primary-color-filled: var(--mantine-color-indigo-filled);--mantine-primary-color-filled-hover: var(--mantine-color-indigo-filled-hover);--mantine-primary-color-light: var(--mantine-color-indigo-light);--mantine-primary-color-light-hover: var(--mantine-color-indigo-light-hover);--mantine-primary-color-light-color: var(--mantine-color-indigo-light-color);--mantine-spacing-xxl: calc(4rem * var(--mantine-scale));--mantine-font-size-xs: 12px;--mantine-font-size-sm: 14px;--mantine-font-size-md: 16px;--mantine-font-size-lg: clamp(16.0000px, calc(15.2727px + 0.2273vw), 18.0000px);--mantine-font-size-xl: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-display-3: clamp(32.0000px, calc(26.1818px + 1.8182vw), 48.0000px);--mantine-font-size-display-2: clamp(36.0000px, calc(25.8182px + 3.1818vw), 64.0000px);--mantine-font-size-display-1: clamp(40.0000px, calc(25.4545px + 4.5455vw), 80.0000px);--mantine-font-size-h1: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-font-size-h2: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-font-size-h3: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-font-size-h4: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-font-size-h5: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-font-size-h6: 1rem;--mantine-primary-color-0: var(--mantine-color-indigo-0);--mantine-primary-color-1: var(--mantine-color-indigo-1);--mantine-primary-color-2: var(--mantine-color-indigo-2);--mantine-primary-color-3: var(--mantine-color-indigo-3);--mantine-primary-color-4: var(--mantine-color-indigo-4);--mantine-primary-color-5: var(--mantine-color-indigo-5);--mantine-primary-color-6: var(--mantine-color-indigo-6);--mantine-primary-color-7: var(--mantine-color-indigo-7);--mantine-primary-color-8: var(--mantine-color-indigo-8);--mantine-primary-color-9: var(--mantine-color-indigo-9);--mantine-color-red-0: #ffeaea;--mantine-color-red-1: #fed4d4;--mantine-color-red-2: #f4a7a8;--mantine-color-red-3: #ec7878;--mantine-color-red-4: #e55050;--mantine-color-red-5: #e03131;--mantine-color-red-6: #e02829;--mantine-color-red-7: #c71a1c;--mantine-color-red-8: #b21218;--mantine-color-red-9: #9c0411;--mantine-color-violet-0: #fce9ff;--mantine-color-violet-1: #f1cfff;--mantine-color-violet-2: #e09bff;--mantine-color-violet-3: #d16fff;--mantine-color-violet-4: #be37fe;--mantine-color-violet-5: #b51afe;--mantine-color-violet-6: #b009ff;--mantine-color-violet-7: #9b00e4;--mantine-color-violet-8: #8a00cc;--mantine-color-violet-9: #7800b3;--mantine-color-indigo-0: #edecff;--mantine-color-indigo-1: #d6d5fe;--mantine-color-indigo-2: #aaa9f4;--mantine-color-indigo-3: #7b79eb;--mantine-color-indigo-4: #5451e4;--mantine-color-indigo-5: #3b37e0;--mantine-color-indigo-6: #2d2adf;--mantine-color-indigo-7: #1f1ec7;--mantine-color-indigo-8: #1819b2;--mantine-color-indigo-9: #0c149e;--mantine-color-cyan-0: #dffdff;--mantine-color-cyan-1: #caf5ff;--mantine-color-cyan-2: #99e8ff;--mantine-color-cyan-3: #64daff;--mantine-color-cyan-4: #3ccffe;--mantine-color-cyan-5: #24c8fe;--mantine-color-cyan-6: #00c2ff;--mantine-color-cyan-7: #00ade4;--mantine-color-cyan-8: #009acd;--mantine-color-cyan-9: #0085b5;--mantine-color-green-0: #e9fdec;--mantine-color-green-1: #d7f6dc;--mantine-color-green-2: #b0eab9;--mantine-color-green-3: #86df94;--mantine-color-green-4: #62d574;--mantine-color-green-5: #4ccf5f;--mantine-color-green-6: #3fcc54;--mantine-color-green-7: #2fb344;--mantine-color-green-8: #25a03b;--mantine-color-green-9: #138a2e;--mantine-color-yellow-0: #fff7e2;--mantine-color-yellow-1: #ffeecd;--mantine-color-yellow-2: #ffdc9c;--mantine-color-yellow-3: #ffc966;--mantine-color-yellow-4: #feb93a;--mantine-color-yellow-5: #feae1e;--mantine-color-yellow-6: #ffa90f;--mantine-color-yellow-8: #ca8200;--mantine-color-yellow-9: #af7000;--mantine-h1-font-size: clamp(28.0000px, calc(23.6364px + 1.3636vw), 40.0000px);--mantine-h1-font-weight: normal;--mantine-h2-font-size: clamp(24.0000px, calc(21.0909px + 0.9091vw), 32.0000px);--mantine-h2-font-weight: normal;--mantine-h3-font-size: clamp(20.0000px, calc(17.0909px + 0.9091vw), 28.0000px);--mantine-h3-font-weight: normal;--mantine-h4-font-size: clamp(16.0000px, calc(13.0909px + 0.9091vw), 24.0000px);--mantine-h4-font-weight: normal;--mantine-h5-font-size: clamp(16.0000px, calc(14.5455px + 0.4545vw), 20.0000px);--mantine-h5-font-weight: normal;--mantine-h6-font-size: 1rem;--mantine-h6-font-weight: normal;}
:root[data-mantine-color-scheme="dark"], :host([data-mantine-color-scheme="dark"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-dark-filled: var(--mantine-color-dark-5);--mantine-color-dark-filled-hover: var(--mantine-color-dark-6);--mantine-color-dark-light: rgba(105, 105, 105, 0.15);--mantine-color-dark-light-hover: rgba(105, 105, 105, 0.2);--mantine-color-dark-light-color: var(--mantine-color-dark-0);--mantine-color-dark-outline: var(--mantine-color-dark-1);--mantine-color-dark-outline-hover: rgba(184, 184, 184, 0.05);--mantine-color-gray-filled: var(--mantine-color-gray-5);--mantine-color-gray-filled-hover: var(--mantine-color-gray-6);--mantine-color-gray-light: rgba(222, 226, 230, 0.15);--mantine-color-gray-light-hover: rgba(222, 226, 230, 0.2);--mantine-color-gray-light-color: var(--mantine-color-gray-0);--mantine-color-gray-outline: var(--mantine-color-gray-1);--mantine-color-gray-outline-hover: rgba(241, 243, 245, 0.05);--mantine-color-red-filled: var(--mantine-color-red-5);--mantine-color-red-filled-hover: var(--mantine-color-red-6);--mantine-color-red-light: rgba(236, 120, 120, 0.15);--mantine-color-red-light-hover: rgba(236, 120, 120, 0.2);--mantine-color-red-light-color: var(--mantine-color-red-0);--mantine-color-red-outline: var(--mantine-color-red-1);--mantine-color-red-outline-hover: rgba(254, 212, 212, 0.05);--mantine-color-pink-filled: var(--mantine-color-pink-5);--mantine-color-pink-filled-hover: var(--mantine-color-pink-6);--mantine-color-pink-light: rgba(250, 162, 193, 0.15);--mantine-color-pink-light-hover: rgba(250, 162, 193, 0.2);--mantine-color-pink-light-color: var(--mantine-color-pink-0);--mantine-color-pink-outline: var(--mantine-color-pink-1);--mantine-color-pink-outline-hover: rgba(255, 222, 235, 0.05);--mantine-color-grape-filled: var(--mantine-color-grape-5);--mantine-color-grape-filled-hover: var(--mantine-color-grape-6);--mantine-color-grape-light: rgba(229, 153, 247, 0.15);--mantine-color-grape-light-hover: rgba(229, 153, 247, 0.2);--mantine-color-grape-light-color: var(--mantine-color-grape-0);--mantine-color-grape-outline: var(--mantine-color-grape-1);--mantine-color-grape-outline-hover: rgba(243, 217, 250, 0.05);--mantine-color-violet-filled: var(--mantine-color-violet-5);--mantine-color-violet-filled-hover: var(--mantine-color-violet-6);--mantine-color-violet-light: rgba(209, 111, 255, 0.15);--mantine-color-violet-light-hover: rgba(209, 111, 255, 0.2);--mantine-color-violet-light-color: var(--mantine-color-violet-0);--mantine-color-violet-outline: var(--mantine-color-violet-1);--mantine-color-violet-outline-hover: rgba(241, 207, 255, 0.05);--mantine-color-indigo-filled: var(--mantine-color-indigo-5);--mantine-color-indigo-filled-hover: var(--mantine-color-indigo-6);--mantine-color-indigo-light: rgba(123, 121, 235, 0.15);--mantine-color-indigo-light-hover: rgba(123, 121, 235, 0.2);--mantine-color-indigo-light-color: var(--mantine-color-indigo-0);--mantine-color-indigo-outline: var(--mantine-color-indigo-1);--mantine-color-indigo-outline-hover: rgba(214, 213, 254, 0.05);--mantine-color-blue-filled: var(--mantine-color-blue-5);--mantine-color-blue-filled-hover: var(--mantine-color-blue-6);--mantine-color-blue-light: rgba(116, 192, 252, 0.15);--mantine-color-blue-light-hover: rgba(116, 192, 252, 0.2);--mantine-color-blue-light-color: var(--mantine-color-blue-0);--mantine-color-blue-outline: var(--mantine-color-blue-1);--mantine-color-blue-outline-hover: rgba(208, 235, 255, 0.05);--mantine-color-cyan-filled: var(--mantine-color-cyan-5);--mantine-color-cyan-filled-hover: var(--mantine-color-cyan-6);--mantine-color-cyan-light: rgba(100, 218, 255, 0.15);--mantine-color-cyan-light-hover: rgba(100, 218, 255, 0.2);--mantine-color-cyan-light-color: var(--mantine-color-cyan-0);--mantine-color-cyan-outline: var(--mantine-color-cyan-1);--mantine-color-cyan-outline-hover: rgba(202, 245, 255, 0.05);--mantine-color-teal-filled: var(--mantine-color-teal-5);--mantine-color-teal-filled-hover: var(--mantine-color-teal-6);--mantine-color-teal-light: rgba(99, 230, 190, 0.15);--mantine-color-teal-light-hover: rgba(99, 230, 190, 0.2);--mantine-color-teal-light-color: var(--mantine-color-teal-0);--mantine-color-teal-outline: var(--mantine-color-teal-1);--mantine-color-teal-outline-hover: rgba(195, 250, 232, 0.05);--mantine-color-green-filled: var(--mantine-color-green-5);--mantine-color-green-filled-hover: var(--mantine-color-green-6);--mantine-color-green-light: rgba(134, 223, 148, 0.15);--mantine-color-green-light-hover: rgba(134, 223, 148, 0.2);--mantine-color-green-light-color: var(--mantine-color-green-0);--mantine-color-green-outline: var(--mantine-color-green-1);--mantine-color-green-outline-hover: rgba(215, 246, 220, 0.05);--mantine-color-lime-filled: var(--mantine-color-lime-5);--mantine-color-lime-filled-hover: var(--mantine-color-lime-6);--mantine-color-lime-light: rgba(192, 235, 117, 0.15);--mantine-color-lime-light-hover: rgba(192, 235, 117, 0.2);--mantine-color-lime-light-color: var(--mantine-color-lime-0);--mantine-color-lime-outline: var(--mantine-color-lime-1);--mantine-color-lime-outline-hover: rgba(233, 250, 200, 0.05);--mantine-color-yellow-filled: var(--mantine-color-yellow-5);--mantine-color-yellow-filled-hover: var(--mantine-color-yellow-6);--mantine-color-yellow-light: rgba(255, 201, 102, 0.15);--mantine-color-yellow-light-hover: rgba(255, 201, 102, 0.2);--mantine-color-yellow-light-color: var(--mantine-color-yellow-0);--mantine-color-yellow-outline: var(--mantine-color-yellow-1);--mantine-color-yellow-outline-hover: rgba(255, 238, 205, 0.05);--mantine-color-orange-filled: var(--mantine-color-orange-5);--mantine-color-orange-filled-hover: var(--mantine-color-orange-6);--mantine-color-orange-light: rgba(255, 192, 120, 0.15);--mantine-color-orange-light-hover: rgba(255, 192, 120, 0.2);--mantine-color-orange-light-color: var(--mantine-color-orange-0);--mantine-color-orange-outline: var(--mantine-color-orange-1);--mantine-color-orange-outline-hover: rgba(255, 232, 204, 0.05);--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-9) 0%, var(--mantine-color-cyan-7) 100%);--app-color-surface: #2e2e2e;}
:root[data-mantine-color-scheme="light"], :host([data-mantine-color-scheme="light"]){--mantine-color-anchor: var(--mantine-color-text);--mantine-color-dimmed: #495057;--mantine-color-red-light: rgba(224, 40, 41, 0.1);--mantine-color-red-light-hover: rgba(224, 40, 41, 0.12);--mantine-color-red-outline-hover: rgba(224, 40, 41, 0.05);--mantine-color-violet-light: rgba(176, 9, 255, 0.1);--mantine-color-violet-light-hover: rgba(176, 9, 255, 0.12);--mantine-color-violet-outline-hover: rgba(176, 9, 255, 0.05);--mantine-color-indigo-light: rgba(45, 42, 223, 0.1);--mantine-color-indigo-light-hover: rgba(45, 42, 223, 0.12);--mantine-color-indigo-outline-hover: rgba(45, 42, 223, 0.05);--mantine-color-cyan-light: rgba(0, 194, 255, 0.1);--mantine-color-cyan-light-hover: rgba(0, 194, 255, 0.12);--mantine-color-cyan-outline-hover: rgba(0, 194, 255, 0.05);--mantine-color-green-light: rgba(63, 204, 84, 0.1);--mantine-color-green-light-hover: rgba(63, 204, 84, 0.12);--mantine-color-green-outline-hover: rgba(63, 204, 84, 0.05);--mantine-color-yellow-light: rgba(255, 169, 15, 0.1);--mantine-color-yellow-light-hover: rgba(255, 169, 15, 0.12);--mantine-color-yellow-outline-hover: rgba(255, 169, 15, 0.05);--app-color-surface: #f1f3f5;--app-cta-gradient: linear-gradient(90deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-5) 100%);}</style><style data-mantine-styles="classes">@media (max-width: 35.99375em) {.mantine-visible-from-xs {display: none !important;}}@media (min-width: 36em) {.mantine-hidden-from-xs {display: none !important;}}@media (max-width: 47.99375em) {.mantine-visible-from-sm {display: none !important;}}@media (min-width: 48em) {.mantine-hidden-from-sm {display: none !important;}}@media (max-width: 61.99375em) {.mantine-visible-from-md {display: none !important;}}@media (min-width: 62em) {.mantine-hidden-from-md {display: none !important;}}@media (max-width: 74.99375em) {.mantine-visible-from-lg {display: none !important;}}@media (min-width: 75em) {.mantine-hidden-from-lg {display: none !important;}}@media (max-width: 87.99375em) {.mantine-visible-from-xl {display: none !important;}}@media (min-width: 88em) {.mantine-hidden-from-xl {display: none !important;}}</style><div style="position:absolute;top:0rem" class=""></div><div style="max-width:var(--container-size-xl);height:100%;min-height:0rem" class=""><style data-mantine-styles="inline">.__m__-_R_5ub_{--grid-gutter:0rem;}</style><div style="height:100%;min-height:0rem" class="m_410352e9 mantine-Grid-root __m__-_R_5ub_"><div class="m_dee7bd2f mantine-Grid-inner" style="height:100%"><style data-mantine-styles="inline">.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:91.66666666666667%;--col-max-width:91.66666666666667%;}@media(min-width: 48em){.__m__-_R_rdub_{--col-flex-grow:auto;--col-flex-basis:83.33333333333334%;--col-max-width:83.33333333333334%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem;display:flex" class="m_96bdd299 mantine-Grid-col __m__-_R_rdub_"><style data-mantine-styles="inline">.__m__-_R_6qrdub_{margin-top:0rem;padding-inline:var(--mantine-spacing-xs);width:100%;}@media(min-width: 48em){.__m__-_R_6qrdub_{margin-top:var(--mantine-spacing-xl);width:80%;}}@media(min-width: 62em){.__m__-_R_6qrdub_{padding-inline:var(--mantine-spacing-xl);}}</style><div style="margin-inline:auto;max-width:var(--mantine-breakpoint-xl)" class="__m__-_R_6qrdub_"><div style="color:var(--mantine-color-dimmed)" class="m_4451eb3a mantine-Center-root" data-inline="true"><div style="--ti-size:var(--ti-size-xs);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;margin-inline-end:calc(0.125rem * var(--mantine-scale));color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="xs"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-lock "><path d="M5 13a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v6a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2v-6"></path><path d="M11 16a1 1 0 1 0 2 0a1 1 0 0 0 -2 0"></path><path d="M8 11v-4a4 4 0 1 1 8 0v4"></path></svg></div><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python: Разработка на фреймворке Django</p></div><h1 style="--title-fw:var(--mantine-h1-font-weight);--title-lh:var(--mantine-h1-line-height);--title-fz:var(--mantine-h1-font-size);margin-bottom:var(--mantine-spacing-xl)" class="m_8a5d1357 mantine-Title-root" data-order="1">Теория: Шаблонизация</h1><script type="application/ld+json">{"@context":"https://schema.org","@type":"LearningResource","name":"Шаблонизация","inLanguage":"ru","isPartOf":{"@type":"LearningResource","name":"Python: Разработка на фреймворке Django"},"isAccessibleForFree":"False","hasPart":{"@type":"WebPageElement","isAccessibleForFree":"False","cssSelector":".paywalled"}}</script><div class=""><div style="--alert-color:var(--mantine-color-indigo-light-color);margin-bottom:var(--mantine-spacing-lg);font-size:var(--mantine-font-size-lg)" class="m_66836ed3 mantine-Alert-root" id="mantine-_R_remqrdub_" role="alert" aria-describedby="mantine-_R_remqrdub_-body" aria-labelledby="mantine-_R_remqrdub_-title"><div class="m_a5d60502 mantine-Alert-wrapper"><div class="m_667f2a6a mantine-Alert-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-rocket "><path d="M4 13a8 8 0 0 1 7 7a6 6 0 0 0 3 -5a9 9 0 0 0 6 -8a3 3 0 0 0 -3 -3a9 9 0 0 0 -8 6a6 6 0 0 0 -5 3"></path><path d="M7 14a6 6 0 0 0 -3 6a6 6 0 0 0 6 -3"></path><path d="M14 9a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"></path></svg></div><div class="m_667c2793 mantine-Alert-body"><div class="m_6a03f287 mantine-Alert-title"><span id="mantine-_R_remqrdub_-title" class="m_698f4f23 mantine-Alert-label">Полный доступ к материалам</span></div><div id="mantine-_R_remqrdub_-body" class="m_7fa78076 mantine-Alert-message"><div style="--group-gap:var(--mantine-spacing-md);--group-align:center;--group-justify:space-between;--group-wrap:wrap" class="m_4081bf90 mantine-Group-root"><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Зарегистрируйтесь и получите доступ к этому и десяткам других курсов</p><a style="--button-height:var(--button-height-xs);--button-padding-x:var(--button-padding-x-xs);--button-fz:var(--mantine-font-size-xs);--button-bg:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-hover:linear-gradient(45deg, var(--mantine-color-blue-filled) 0%, var(--mantine-color-cyan-filled) 100%);--button-color:var(--mantine-color-white);--button-bd:none" class="mantine-focus-auto mantine-active m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root" data-variant="gradient" data-size="xs" href="/u/new"><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">Зарегистрироваться</span></span></a></div></div></div></div></div><div class="paywalled m_d08caa0 mantine-Typography-root"><p>Веб-приложение состоит из веб-страниц. Даже если приложение реализует большую часть интерфейса пользователя с помощью JavaScript, домашняя страница все равно отдается сервером. Да и ситуации, когда страничный сайт хорошо решает поставленные задачи, возникают часто. Поэтому подсистема шаблонизации остается важной частью full-stack фреймворков.</p>
<p>В этом уроке разберем, что такое шаблонизация, и как на ее основе Django формирует HTML-страницы.</p>
<h2 id="heading-2-1">Язык шаблонизации</h2>
<p><strong>Шаблон</strong> — это почти готовый текстовый документ, в котором все неизменные части текста представлены как есть. А изменяемая часть описана на некотором языке с ветвлениями, условиями и циклами. Они позволяют преобразовать данные в подходящее представление. Этот язык называют <strong>языком шаблонизации</strong> или <strong>templating language</strong>.</p>
<p>На уровне шаблона разделяется ответственность. Шаблон страницы знает, как отобразить данные в виде HTML. А сами данные не зависят от этого представления и могут быть представлены по-разному разными шаблонами. По данным вообще нельзя сказать, что они будут представлены в виде HTML. Шаблонизатор возводит барьер абстракции.</p>
<p>Чтобы из кода, который формирует данные, не нужно было программировать отображение, у шаблонизатора есть свой язык программирования отображения. А для того, чтобы не было соблазна прямо в шаблоне получать данные, этот язык сделан с ограниченными возможностями.</p>
<h2 id="heading-2-2">Шаблонизация в Django</h2>
<p>В Django шаблонизация проработана достаточно глубоко. Есть встроенный шаблонизатор, можно использовать сторонние шаблонизаторы вроде Jinja2 и можно сочетать несколько шаблонизаторов в одном проекте. Это удобно, когда некоторые шаблоны достаются в наследство, либо когда мы переезжаем с одного шаблонизатора на другой.</p>
<p>Чтобы шаблон превратить в результат, Django использует бэкенды. Встроенных бэкенда два: DjangoTemplates и Jinja2. Сторонние пакеты могут предоставлять свои бэкенды.</p>
<p>Чтобы бэкенд мог что-то сделать с шаблоном, нужно этот шаблон где-то взять. Django самостоятельно загружает шаблоны из файлов, знает, где эти файлы найти, и запоминает/кэширует часто используемые шаблоны в памяти. Нам остается только помнить имя шаблона, который хотим использовать в данный момент.</p>
<p>Настраивается это в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">settings.py</code> с помощью переменной <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">TEMPLATES</code>. Выглядит настройка так:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">TEMPLATES = [
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [],
"APP_DIRS": False,
"OPTIONS": {
# ... параметры шаблонизатора ...
},
},
]</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">DIRS</code> — параметр, который перечисляет директории, где будет производиться поиск шаблонов</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">APP_DIRS</code> — параметр, который разрешает Django искать шаблоны в директориях приложений</li>
</ul>
<p>Когда <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">APP_DIRS</code> включен, в каждом подключенном приложении будут по умолчанию искаться директории <em>templates</em>, а в них — шаблоны.</p>
<p>По умолчанию в качестве директории с шаблонами принято указывать <em>templates</em>:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"># hexlet_django_blog/settings.py
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
# ... параметры шаблонизатора ...
},
]</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<h2 id="heading-2-3">Шаблоны приложений</h2>
<p>У одного приложения в нашем учебном проекте уже есть шаблон. Речь идет о главном приложении <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog</code> и шаблоне <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog/templates/index.html</code>. Django находит и загружает этот шаблон, потому что опция <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">APP_DIRS</code> включена по умолчанию.</p>
<p>Если сейчас создать новый шаблон <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog/article/templates/index.html</code>, то при поиске по имени <em>index.html</em> первым будет найден старый шаблон <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog/templates/index.html</code>. Так происходит, потому что приложение <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog</code> находится в списке <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">settings.INSTALLED_APPS</code> выше приложения <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">hexlet_django_blog.article</code>. Если переставить приложения местами, то загрузчик шаблонов учтет изменения.</p>
<p>С помощью управления порядком подключения приложений можно переопределять шаблоны одних приложений шаблонами из других. Это интересная возможность, но из-за нее же приходится следить за тем, чтобы шаблоны не перепутывались случайно.</p>
<p>Чтобы не думать слишком много над тем, как не путать шаблоны, стоит придерживаться правила:</p>
<blockquote>
<p>Все шаблоны проекта, которые используются только в нем, стоит хранить в отдельной директории в корне проекта и держать эту директорию в порядке — использовать поддиректории и хорошие имена. Директорию нужно добавить в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">settings.TEMPLATES.DIRS</code>.</p>
</blockquote>
<p>Для тех же приложений, которые планируется использовать повторно, внутри соответствующей директории <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">templates</code> нужно иметь поддиректорию с именем приложения и все шаблоны располагать в ней. Это уменьшит вероятность конфликтов имен шаблонов.</p>
<h3 id="heading-3-4">Контекст шаблонизации</h3>
<p>Шаблоны превращаются в текст с помощью выполнения кода на языке шаблонизатора. Этот код обычно подразумевает использование каких-то данных, получаемых вне шаблона. Данные передаются в шаблон с помощью контекста, который представляет собой словарь. Когда мы вызываем <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">render(request, 'template.html', context={})</code>, мы передаем этот самый словарь в качестве аргумента.</p>
<p>Часто возникает задача иметь в контексте шаблона доступ к параметрам запроса или настройкам проекта. Передавать такие вещи явно слишком утомительно, поэтому у Django есть механизм Context Processors — посредники между нами и шаблоном, которые расширяют контекст единым образом для всех шаблонов.</p>
<p>Процессоры контекста вызываются как функции, которые получают в качестве аргумента <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">request</code> и возвращают словарь. Затем этот словарь дополняет предоставленный нами контекст. Подключаются context processors в <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">settings.TEMPLATES.OPTIONS.context_processors</code> — это список строк с полными именами процессоров.</p>
<p>С Django поставляется много готовых процессоров контекста. Вот два примера:</p>
<ul>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">django.template.context_processors.request</code> добавляет в контекст переменную <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">request</code> с очевидным значением</li>
<li><code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">django.template.context_processors.debug</code> добавляет переменную <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">debug</code>, которая истинна, если сервер запущен в режиме разработчика. С помощью этой переменной можно выводить какую-то отладочную информацию, которая не будет видна в боевом режиме</li>
</ul>
<p>В дополнение к встроенным можно создавать и свои процессоры контекста или же брать их из сторонних библиотек.</p>
<h2 id="heading-2-5">Работа с шаблонизатором Django</h2>
<p>Пример простого Django-шаблона:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code"><h1>{{ title }}</h1>
<p>{{ description }}</p>
<pre>
{{ code }}
</pre>
<p>Question submitted at: {{ created_at }}</p></code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Шаблон выглядит как HTML, но со вставками кода. Во многом встроенный Django шаблонизатор схож с шаблонизатором Jinja2 — подстановка любого значения выполняется в двойных фигурных скобках <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{{ ... }}</code>. Это очень похоже на интерполяцию, если сам шаблон рассматривать как строку.</p>
<p>Под капотом Django выполняет дополнительную обработку и экранирует любые данные, которые вставлены таким образом. Это значит, что нам не нужно прилагать дополнительные усилия, чтобы обеспечить безопасность в шаблонах.</p>
<p>Данные в шаблон поступают из вью в виде контекста:</p>
<div class="m_5cb1b9c8 mantine-CodeHighlightTabs-root"><div style="--sa-corner-width:0px;--sa-corner-height:0px" class="m_7b14120b mantine-CodeHighlightTabs-filesScrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><div class="m_38d99e51 mantine-CodeHighlightTabs-files"><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" data-active="true" type="button"><span>python</span></button><button class="mantine-focus-auto m_5cac2e62 mantine-CodeHighlightTabs-file m_87cf2631 mantine-UnstyledButton-root" type="button"><span>python</span></button></div></div></div><div data-orientation="horizontal" class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" style="position:absolute;--sa-thumb-width:18px" data-mantine-scrollbar="true"></div><div class="m_c44ba933 mantine-ScrollArea-scrollbar" data-hidden="true" data-orientation="vertical" style="position:absolute;--sa-thumb-height:18px" data-mantine-scrollbar="true"></div></div><div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlightTabs-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlightTabs-controls" data-with-offset="true"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlightTabs-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlightTabs-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlightTabs-pre" data-with-offset="true"><code class="m_5caae6d3 mantine-CodeHighlightTabs-code"># hexlet_django_blog/views.py
def about(request):
tags = ["обучение", "программирование", "python", "oop"]
return render(
request,
"about.html",
context={"tags": tags},
)</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlightTabs-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div></div><p>Двойные фигурные скобки позволяют не только выводить информацию, которую передали в контексте, но и производить ее простую обработку при помощи встроенных <a style="text-decoration:underline" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="https://docs.djangoproject.com/en/5.2/ref/templates/builtins/" rel="noopener noreferrer" target="_blank">тегов и фильтров</a> шаблонов. Главное правило — не злоупотреблять. В основном данные должны быть подготовлены в обработчике до того, как они попадут в шаблон. Тут нужно помнить: шаблоны — про отображение, а не про логику работы.</p>
<p>Кроме подстановки во встроенном шаблонизаторе есть инструкции. С их помощью реализованы все управляющие конструкции, например, циклы, условия и многое другое:</p>
<div style="margin-bottom:var(--mantine-spacing-lg)" class="m_e597c321 mantine-CodeHighlight-codeHighlight" dir="ltr"><div class="m_be7e9c9c mantine-CodeHighlight-controls"><button style="--ai-bg:transparent;--ai-hover:transparent;--ai-color:inherit;--ai-bd:none" class="mantine-focus-auto mantine-active m_d498bab7 mantine-CodeHighlight-control m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="none" type="button" aria-label="Copy code"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M8 8m0 2a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z"></path><path d="M16 8v-2a2 2 0 0 0 -2 -2h-8a2 2 0 0 0 -2 2v8a2 2 0 0 0 2 2h2"></path></svg></span></button></div><div style="--scrollarea-scrollbar-size:calc(0.25rem * var(--mantine-scale));--sa-corner-width:0px;--sa-corner-height:0px" class="m_f744fd40 mantine-CodeHighlight-scrollarea m_d57069b5 mantine-ScrollArea-root" dir="ltr"><div style="overflow-x:hidden;overflow-y:hidden;overscroll-behavior-inline:none" class="m_c0783ff9 mantine-ScrollArea-viewport" data-scrollbars="xy"><div class="m_b1336c6 mantine-ScrollArea-content"><pre class="m_2c47c4fd mantine-CodeHighlight-pre" style="padding:0"><code class="m_5caae6d3 mantine-CodeHighlight-code">{% if records|length == 1 %}
I have one record!
{% endif %}
{% for user in users %}
<p>This is user {{ user.id }}</p>
{% endfor %}</code></pre></div></div></div><button class="mantine-focus-auto m_c9378bc2 mantine-CodeHighlight-showCodeButton m_87cf2631 mantine-UnstyledButton-root" data-hidden="true" type="button">Expand code</button></div>
<p>Инструкции всегда оборачиваются в фигурные скобки со знаком процента <code style="margin-bottom:var(--mantine-spacing-lg)" class="m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight m_e597c321 mantine-CodeHighlight-codeHighlight m_dfe9c588 mantine-InlineCodeHighlight-inlineCodeHighlight">{% ... %}</code> и часто имеют закрывающую часть. При помощи инструкций можно делать практически то же самое, что и в самом Python. В инструкциях, как и в подстановках, можно использовать переменные из контекста.</p>
<p>Простые конструкции вроде обращения по ключу, по индексу, по имени атрибута нам доступны. Поэтому контекст может содержать и сложносоставные сущности. А вот вызывать функции и методы уже не получится — это то самое отделение логики от представления.</p></div><div style="margin-block:var(--mantine-spacing-xl)" class=""><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md)" class="m_8a5d1357 mantine-Title-root" data-order="2">Рекомендуемые программы</h2><style data-mantine-styles="inline">.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xs);--carousel-slide-size:70%;}@media(min-width: 36em){.__m__-_R_2mremqrdub_{--carousel-slide-gap:var(--mantine-spacing-xl);--carousel-slide-size:50%;}}</style><div style="--carousel-control-size:calc(2.5rem * var(--mantine-scale));--carousel-controls-offset:var(--mantine-spacing-sm);margin-bottom:var(--mantine-spacing-lg);padding-block:var(--mantine-spacing-sm);background:var(--app-color-surface)" class="m_17884d0f mantine-Carousel-root responsiveClassName" data-orientation="horizontal" data-include-gap-in-size="true"><div class="m_39bc3463 mantine-Carousel-controls" data-orientation="horizontal"><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="previous" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button><button class="mantine-focus-auto m_64f58e10 mantine-Carousel-control m_87cf2631 mantine-UnstyledButton-root" type="button" data-inactive="true" data-type="next" tabindex="-1"><svg viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg" style="transform:rotate(-90deg);width:calc(1rem * var(--mantine-scale));height:calc(1rem * var(--mantine-scale));display:block"><path d="M3.13523 6.15803C3.3241 5.95657 3.64052 5.94637 3.84197 6.13523L7.5 9.56464L11.158 6.13523C11.3595 5.94637 11.6759 5.95657 11.8648 6.15803C12.0536 6.35949 12.0434 6.67591 11.842 6.86477L7.84197 10.6148C7.64964 10.7951 7.35036 10.7951 7.15803 10.6148L3.15803 6.86477C2.95657 6.67591 2.94637 6.35949 3.13523 6.15803Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></button></div><div class="m_a2dae653 mantine-Carousel-viewport" data-type="media"><div class="m_fcd81474 mantine-Carousel-container __m__-_R_2mremqrdub_" data-orientation="horizontal"><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">10 месяцев</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">С нуля</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Python-разработчик </p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите Python, Django, REST и Fast API для создания веб-приложений</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6MzczMSwicHVyIjoiYmxvYl9pZCJ9fQ==--f5df4883f3f678321cb4fa96e9ce657bd5ee1adf/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Static%20website-cuate.png" alt="Python-разработчик " loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 6 792 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/programs/python-django-developer?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card" target="_blank"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><div style="--group-gap:calc(0.25rem * var(--mantine-scale));--group-align:center;--group-justify:flex-start;--group-wrap:nowrap" class="m_4081bf90 mantine-Group-root"><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">2 месяца</span><span class="mantine-focus-auto m_b6d8b162 mantine-Text-root">·</span><span style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Для продвинутых</span></div><p style="margin-bottom:var(--mantine-spacing-sm);font-size:var(--mantine-font-size-h5);font-weight:bold" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Django</p><p class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Изучите фреймворк Django для создания веб-приложений </p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="https://hexlet.io/rails/active_storage/representations/proxy/eyJfcmFpbHMiOnsiZGF0YSI6NDA0NiwicHVyIjoiYmxvYl9pZCJ9fQ==--5c088db10d02b94be027408f50ecf11c23d9d4cb/eyJfcmFpbHMiOnsiZGF0YSI6eyJmb3JtYXQiOiJ3ZWJwIiwicmVzaXplX3RvX2xpbWl0IjpbNDAwLDQwMF0sInNhdmVyIjp7InF1YWxpdHkiOjg1fX0sInB1ciI6InZhcmlhdGlvbiJ9fQ==--5b6f46dacd1af664f27558553a58076185091823/Version%20control-bro.png" alt="Django" loading="eager"/></div><div style="--group-gap:var(--mantine-spacing-md);--group-align:end;--group-justify:space-between;--group-wrap:wrap;margin-top:var(--mantine-spacing-xs)" class="m_4081bf90 mantine-Group-root"><p style="font-size:var(--mantine-font-size-xl)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">от 3 900 ₽</p><p style="font-size:var(--mantine-font-size-sm)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Посмотреть →</p></div></div></div></a></div></div><div class="m_d98df724 mantine-Carousel-slide" data-orientation="horizontal"><div tabindex="0" style="cursor:pointer;height:100%"><a style="text-decoration:none" class="mantine-focus-auto m_849cf0da m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses?promo_name=programs_list&promo_position=course&promo_creative=catalog_card&promo_type=card"><div style="height:100%" class="m_e615b15f mantine-Card-root m_1b7284a3 mantine-Paper-root" data-with-border="true"><h2 style="--title-fw:var(--mantine-h2-font-weight);--title-lh:var(--mantine-h2-line-height);--title-fz:var(--mantine-h2-font-size);margin-bottom:var(--mantine-spacing-md);font-size:var(--mantine-font-size-h3)" class="m_8a5d1357 mantine-Title-root" data-order="2" data-responsive="true">Каталог</h2><p style="margin-bottom:auto" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Полный список доступных курсов по разным направлениям</p><div style="margin-top:auto" class=""><div class="m_4451eb3a mantine-Center-root"><img style="opacity:0.8;width:70%" class="m_9e117634 mantine-Image-root mantine-visible-from-xs" src="/vite/assets/development-BVihs_d5.png" alt="Orientation"/></div></div></div></a></div></div></div></div></div></div></div></div></div><style data-mantine-styles="inline">.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:8.333333333333334%;--col-max-width:8.333333333333334%;}@media(min-width: 48em){.__m__-_R_1bdub_{--col-flex-grow:auto;--col-flex-basis:16.666666666666668%;--col-max-width:16.666666666666668%;}}</style><div style="min-width:0rem;height:100%;min-height:0rem" class="m_96bdd299 mantine-Grid-col __m__-_R_1bdub_"><div style="margin-inline:var(--mantine-spacing-xs)" class="mantine-visible-from-sm"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-lg);text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-django-basics/lessons/templates/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label"><span style="margin-inline-end:var(--mantine-spacing-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Дальше</span>→</span></span></a><a style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Навигация по теме</span><span class="m_57492dcc mantine-NavLink-description">Теория</span></div><span class="m_690090b5 mantine-NavLink-section" data-position="right"></span></a><div style="margin-block:var(--mantine-spacing-lg)" class="m_3eebeb36 mantine-Divider-root" data-orientation="horizontal" role="separator"></div><div style="margin-block:var(--mantine-spacing-lg)" class=""><div style="justify-content:space-between;margin-bottom:calc(0.1875rem * var(--mantine-scale));color:var(--mantine-color-dimmed);font-size:var(--mantine-font-size-xs)" class="m_8bffd616 mantine-Flex-root __m__-_R_qimrbdub_"><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">Завершено</p><p style="font-size:var(--mantine-font-size-xs)" class="mantine-focus-auto m_b6d8b162 mantine-Text-root">0 / 19</p></div><div style="--progress-size:var(--progress-size-sm)" class="m_db6d6462 mantine-Progress-root" data-size="sm"><div style="--progress-section-size:0%;--progress-section-color:var(--mantine-color-gray-filled)" class="m_2242eb65 mantine-Progress-section" role="progressbar" aria-valuemax="100" aria-valuemin="0" aria-valuenow="0" aria-valuetext="0%"></div></div></div><button style="padding-inline:0rem" class="mantine-focus-auto m_f0824112 mantine-NavLink-root m_87cf2631 mantine-UnstyledButton-root" type="button"><span class="m_690090b5 mantine-NavLink-section" data-position="left"><div style="--ti-size:var(--ti-size-sm);--ti-bg:transparent;--ti-color:var(--mantine-color-indigo-light-color);--ti-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;color:inherit" class="m_7341320d mantine-ThemeIcon-root" data-variant="transparent" data-size="sm"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></div></span><div class="m_f07af9d2 mantine-NavLink-body"><span class="m_1f6ac4c4 mantine-NavLink-label">Обсуждения (архив)</span><span class="m_57492dcc mantine-NavLink-description"></span></div></button><div style="--toc-bg:var(--mantine-color-blue-light);--toc-color:var(--mantine-color-blue-light-color);--toc-size:var(--mantine-font-size-sm);--toc-radius:var(--mantine-radius-sm);margin-top:var(--mantine-spacing-xl)" class="m_bcaa9990 mantine-TableOfContents-root" data-variant="light" data-size="sm"></div></div><div class="mantine-hidden-from-sm"><div style="--stack-gap:0rem;--stack-align:stretch;--stack-justify:flex-start" class="m_6d731127 mantine-Stack-root"><a style="--button-color:var(--mantine-color-white);margin-bottom:var(--mantine-spacing-xs);padding:0rem;text-decoration:none" class="mantine-focus-auto m_849cf0da mantine-focus-auto m_77c9d27d mantine-Button-root m_87cf2631 mantine-UnstyledButton-root m_b6d8b162 mantine-Text-root mantine-Anchor-root" data-underline="hover" href="/courses/python-django-basics/lessons/templates/finish_unit?unit=theory" data-disabled="true" data-block="true" disabled=""><span class="m_80f1301b mantine-Button-inner"><span class="m_811560b9 mantine-Button-label">→</span></span></a><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" data-disabled="true" type="button" disabled=""><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-list-numbers "><path d="M11 6h9"></path><path d="M11 12h9"></path><path d="M12 18h8"></path><path d="M4 16a2 2 0 1 1 4 0c0 .591 -.5 1 -1 1.5l-3 2.5h4"></path><path d="M6 10v-6l-2 2"></path></svg></span></button><button style="--ai-size:var(--ai-size-sm);--ai-bg:transparent;--ai-hover:var(--mantine-color-indigo-light-hover);--ai-color:var(--mantine-color-indigo-light-color);--ai-bd:calc(0.0625rem * var(--mantine-scale)) solid transparent;padding-block:var(--mantine-spacing-lg);color:inherit;width:100%" class="mantine-focus-auto mantine-active m_8d3f4000 mantine-ActionIcon-root m_87cf2631 mantine-UnstyledButton-root" data-variant="subtle" data-size="sm" type="button"><span class="m_8d3afb97 mantine-ActionIcon-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" class="tabler-icon tabler-icon-message "><path d="M8 9h8"></path><path d="M8 13h6"></path><path d="M18 4a3 3 0 0 1 3 3v8a3 3 0 0 1 -3 3h-5l-5 3v-3h-2a3 3 0 0 1 -3 -3v-8a3 3 0 0 1 3 -3h12"></path></svg></span></button></div></div></div></div></div></div></div>
</main>
<footer class="bg-dark fw-light text-light px-3 py-5">
<div class="row small">
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 mb-3">Хекслет</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/about">О нас</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/testimonials">Отзывы</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://b2b.hexlet.io" role="button">Корпоративное обучение</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/blog">Блог</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/qna">Вопросы и ответы</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/glossary">Глоссарий</a>
</li>
<li>
<span class="nav-link link-light py-1 ps-0 external-link" data-href="https://help.hexlet.io" data-target="_blank" role="button">Справка</span>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" target="_blank" rel="noopener noreferrer" href="/map">Карта сайта</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5 fw-normal mb-3">Направления</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_devops">DevOps
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_data_analytics">Аналитика
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_backend_development">Бэкенд
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_programming">Программирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_testing">Тестирование
</a></li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/courses_front_end_dev">Фронтенд
</a></li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Профессии</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/devops-engineer-from-scratch">DevOps-инженер с нуля</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/go">Go-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/java">Java-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python">Python-разработчик </a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/data-analytics">Аналитик данных</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/qa-engineer">Инженер по ручному тестированию</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php">РНР-разработчик</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/frontend">Фронтенд-разработчик</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-6 col-md-3">
<div class="h5">Навыки</div>
<ul class="list-unstyled">
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/python-django-developer">Django</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/docker">Docker</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/php-laravel-developer">Laravel</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/postman">Postman</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-react-developer">React</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/js-rest-api">REST API в Node.js</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/spring-boot">Spring Boot</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/programs/typescript">Typescript</a>
</li>
</ul>
</div>
</div>
<hr>
<div class="row">
<div class="col-12 col-sm-4 col-md-2">
<div class="fs-4">
<ul class="list-unstyled d-flex">
<li class="me-3">
<a aria-label="Telegram" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://t.me/hexlet_ru"><span class="bi bi-telegram"></span>
</a></li>
<li>
<a aria-label="Youtube" target="_blank" class="link-light" rel="noopener noreferrer nofollow" href="https://www.youtube.com/user/HexletUniversity"><span class="bi bi-youtube"></span>
</a></li>
</ul>
</div>
<div class="mb-2 d-flex flex-column">
<a class="link-light text-decoration-none" rel="nofollow" href="mailto:support@hexlet.io">support@hexlet.io</a>
<a class="link-light text-decoration-none py-2" target="_blank" href="https://t.me/hexlet_help_bot">t.me/hexlet_help_bot</a>
</div>
<ul class="list-unstyled d-flex">
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://hexlet.io/locale/switch?new_locale=en" data-target="_self" role="button"><span class="my-auto">EN</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 opacity-100 external-link" rel="nofollow" data-href="https://ru.hexlet.io/locale/switch?new_locale=ru" data-target="_self" role="button"><span class="my-auto">RU</span>
</span></li>
<li class="me-3">
<span class="link-light text-decoration-none opacity-50 x-font-size-18 external-link" rel="nofollow" data-href="https://kz.hexlet.io/locale/switch?new_locale=kz" data-target="_self" role="button"><span class="my-auto">KZ</span>
</span></li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<ul class="list-unstyled fs-4">
<li class="mb-3">
<a class="link-light text-decoration-none" href="tel:8%20800%20100%2022%2047">8 800 100 22 47</a>
<span class="d-block opacity-50 small">бесплатно по РФ</span>
</li>
<li>
<a class="link-light text-decoration-none" href="tel:%2B7%20495%20085%2021%2062">+7 495 085 21 62</a>
<span class="d-block opacity-50 small">бесплатно по Москве</span>
</li>
</ul>
</div>
<div class="col-12 col-sm-4 col-md-3">
<div class="small mb-3">Образовательные услуги оказываются на основании Л035-01298-77/01989008 от 14.03.2025</div>
<ul class="list-unstyled small">
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/legal">Правовая информация</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/offer">Оферта</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/license">Лицензия</a>
</li>
<li>
<a class="nav-link link-light py-1 ps-0" href="/pages/contacts">Контакты</a>
</li>
</ul>
</div>
<div class="col-12 col-sm-12 col-md-4 small">
<div class="mb-2">
<div>ООО «<a href="/" class="text-decoration-none link-light">Хекслет Рус</a>»</div>
<div>108813 г. Москва, вн.тер.г. поселение Московский,</div>
<div>г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3</div>
<div>ОГРН 1217300010476</div>
<div>ИНН 7325174845</div>
</div>
<hr>
<div>АНО ДПО «<a href="/" class="text-decoration-none link-light">Учебный центр «Хекслет</a>»</div>
<div>119331 г. Москва, вн. тер. г. муниципальный округ</div>
<div>Ломоносовский, пр-кт Вернадского, д. 29</div>
<div>ОГРН 1247700712390</div>
<div>ИНН 7736364948</div>
</div>
</div>
</footer>
<div id="root-assistant-offcanvas"></div>
<script src="/vite/assets/assistant-Bukl1lYy.js" crossorigin="anonymous" type="module"></script><link rel="modulepreload" href="/vite/assets/chunk-DsPFFUou.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/init-BrRXra1y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/ErrorFallbackBlock-naDSYSy9.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/MarkdownBlock-DbyKWoR_.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/gon-D3e4yh1x.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/mantine-CGMYrt2Y.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/shiki-V011pkdv.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/utils-DRqSHbQE.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/routes-CCH8ilKF.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-XR8Qr8kR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dist-GCHh59xr.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/Box-B5-OOzBf.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/notifications.store-C-3AFSMn.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useIsomorphicEffect-HJ6VK0D3.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/lib-KSp6QbZ0.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/axios-BEvgo0ym.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/classnames-l6ipYlLR.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/dayjs.min-BkKovM-s.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/debounce-jMQ_Cf4f.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/i18next-BlSq9s7B.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/client-U9M77rxp.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-dom-DaLxUz_h.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/useTranslation-Bx1Cdrkz.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/compiler-runtime-6XxiPFnt.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/jsx-runtime-CwjcCKJi.js" as="script" crossorigin="anonymous">
<link rel="modulepreload" href="/vite/assets/react-CkL4ZRHB.js" as="script" crossorigin="anonymous">
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v67327c56f0bb4ef8b305cae61679db8f1769101564043" integrity="sha512-rdcWY47ByXd76cbCFzznIcEaCN71jqkWBBqlwhF1SY7KubdLKZiEGeP7AyieKZlGP9hbY/MhGrwXzJC/HulNyg==" data-cf-beacon='{"version":"2024.11.0","token":"d11015b65d11429ea6b4a2ef37dd7e0b","server_timing":{"name":{"cfCacheStatus":true,"cfEdge":true,"cfExtPri":true,"cfL4":true,"cfOrigin":true,"cfSpeedBrain":true},"location_startswith":null}}' crossorigin="anonymous"></script>
</body>
</html>