#29. Templates
Templates (они же Темплейты, они же Шаблоны)
План
I. Шаблон
- Что такое шаблон и как его подключать в Django
- Альтеранативный вариант расположения templates
- Функция render
II. Django Template Language or DTL
- Переменные
- Теги
- Логические операторы
- Циклы
- url
- Комментарии
- Фильтры
- Наследование шаблонов
- Практика по наследованию
- extend и include
- Практика по наследованию
III. Литература (что почитать)
Что такое шаблон
Шаблон - это текстовый файл, который определяет структуру и расположение данных в файле, кроме того, в нем размещают специальные теги, которые используются для показа реального содержимого, то есть данных. По умолчанию Django ищет файлы шаблонов в директории с именем templates внутри вашего приложения.
В папке приложения необходимо создать папку templates
, и в ней нужно создать html
файл, назовём его index.html
(название не имеет значения, главное, чтобы формат был html
)
mysite/
myapp/
templates/
index.html
manage.py
С таким содержанием:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Hello, world!
<br>
That's a template!
</body>
</html>
Итак, теперь у нас есть один шаблон, но мы его не используем, давайте переделаем нашу view
для обработки шаблонов
В файле myapp/views.py
необходимо импортировать обработчик шаблонов, для этого в начале файла добавляем
from django.shortcuts import render
И перепишем функцию index
:
def index(request):
return render(request, 'index.html')
Обновим страницу в браузере и увидим результат
Альтеранативный вариант расположениея templates
Создадим новую папку на уровне корня проекта и назовём её templates
(название может быть любым, но принято называть именно так), чтобы получилась вот такая структура:
mysite/
myapp/
templates/
manage.py
Для того, чтобы обрабатывать шаблоны мы должны “рассказать” Django, где именно искать эти самые шаблоны. Для этого нужно открыть файл mysite/settings.py
и отредактировать его.
В данный момент нас интересует переменная TEMPLATES
, выглядит это примерно так:
В ключ DIRS
добавим нашу папку с шаблонами, чтобы получилось так:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates']
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Ключи:
BACKEND
- путь к классу, который отвечает за обработку данных и логику (замена требуется очень редко).
DIRS
- список папок, в которых Django будет искать шаблоны.
APP_DIRS
- булевое поле которое отвечает за то, нужно ли искать папки с шаблонами внутри папки с приложениями, например в нашей структуре, если значение False
, то поиск будет только в папке templates
на уровне файла manage.py
, а если значение True
то в папках /templates
и /myapp/templates/
OPTIONS
- дополнительные настройки.
render()
from django.shortcuts import render
render(request, template_name, context=None, content_type=None, status=None, using=None)
Объединяет заданный шаблон с заданным контекстным словарем и возвращает объект HttpResponse
с этим визуализированным кодом.
Обязательные аргументы
request
Объект запроса, использованный для генерации этого ответа.
template_name
Полное имя используемого шаблона или последовательность имен шаблонов. Если указана последовательность, будет использован первый существующий шаблон.
Необязательные аргументы
context
Словарь значений для добавления в контекст шаблона. По умолчанию это пустой словарь. Если значение в словаре является вызываемым, представление вызовет его непосредственно перед отрисовкой шаблона.
content_type
Тип MIME для использования в итоговом документе. По умолчанию - text/html
.
status
Код состояния для ответа. По умолчанию 200
.
Если передать, например, 500, в консоли браузера можно увилеть подобные ошибки
using
Параметр шаблонизатора, который будет использоваться для загрузки шаблона.
Для демонстрации основных типов данных допишем функцию index
и передам большое кол-во значений в словаре:
class MyClass:
string = ''
def __init__(self, s):
self.string = s
def index(request):
my_num = 33
my_str = 'some string'
my_dict = {"some_key": "some_value"}
my_list = ['list_first_item', 'list_second_item', 'list_third_item']
my_set = {'set_first_item', 'set_second_item', 'set_third_item'}
my_tuple = ('tuple_first_item', 'tuple_second_item', 'tuple_third_item')
my_class = MyClass('class string')
data = {
'my_num': my_num,
'my_str': my_str,
'my_dict': my_dict,
'my_list': my_list,
'my_set': my_set,
'my_tuple': my_tuple,
'my_class': my_class,
}
return render(
request,
'index.html',
data
)
Значения переданы, но пока они никак не используются, давай те же посмотрим, как отобразить переменные в шаблоне!
Django Template Language or DTL
Язык шаблонов Django
Переменные
Переменная выводит значение из контекста, которое представляет собой dict-подобный объект, отображающий ключи со значениям.
Переменные заключаются в специальные символы - двойные фигурные скобки {{
и }}
, например:
My first name is {{ first_name }}. My last name is {{ last_name }}.
В контексте {'first_name': 'John', 'last_name': 'Doe'}
этот шаблон отрендерится в:
My first name is John. My last name is Doe.
Поиск по словарю, поиск по атрибутам и поиск по списку-индексу реализованы с использованием точечной нотации:
{{ my_dict.key }}
{{ my_object.attribute }}
{{ my_list.0 }}
Если переменная преобразуется в вызываемую, система шаблонов вызовет ее без аргументов и будет использовать ее результат вместо вызываемого.
Изменим index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<div style="border: 1px darkblue solid">
{{ my_num }}
</div>
<div style="border: 1px darkseagreen solid">
{{ my_str }}
</div>
<div style="border: 1px fuchsia solid">
{{ my_set }}
</div>
<div style="border: 1px firebrick solid">
{{ my_dict.some_key }}
</div>
<div style="border: 1px cyan solid">
{{ my_class.string }}
</div>
<div style="border: 1px cyan solid">
{{ my_list.0 }}
</div>
<div style="border: 1px burlywood solid">
{{ my_tuple.1 }}
</div>
</div>
</body>
</html>
Теги
Теги обеспечивают произвольную логику в процессе рендеринга.
Это определение заведомо расплывчатое. Например, тег может выводить контент, служить структурой управления, например. оператор «if» или цикл «for», захват содержимого из базы данных или даже разрешение доступа к другим тегам шаблона.
Теги заключаются в символы {%
и %}
, например:
{% csrf_token %}
Большинство тегов принимают аргументы:
{% cycle 'odd' 'even' %}
Для некоторых тегов требуются начальные и конечные теги:
{% if user.is_authenticated %}Hello, {{ user.username }}.{% endif %}
Логические операторы
В шаблонах можно оперировать не только переменными, но и простой логикой, такой как логические операторы и циклы.
Давайте добавим в наши параметры переменную display_num
и назначим ей False
myapp/views.py
data = {
'my_num': my_num,
'my_str': my_str,
'my_dict': my_dict,
'my_list': my_list,
'my_set': my_set,
'my_tuple': my_tuple,
'my_class': my_class,
'display_num': False,
}
Для логических условий и циклов используются другие скобки {% %}
Изменим наш шаблон с использованием логики:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
{% if display_num %}
{{ my_num }}
{% else %}
<span> We don't display num </span>
{% endif %}
</div>
</body>
</html>
Обновим страницу и увидим:
А если изменим только во views
переменную с False
на True
:
myapp/views.py
То увидим:
Т.к. переменная True, то мы видим значение переменной my_num
Циклы
Так же как и в python мы можем использовать циклы в шаблонах, но только цикл for
Изменим index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
{% for item in my_list %}
<span>{{ item }}</span>
<br>
{% endfor %}
</div>
</body>
</html>
Обновим страницу и увидим:
Еще раз, логика через {% %}
, данные через {{ }}
Давайте скомбинируем!
Внутри цикла for
Django уже генерирует некоторые переменные, например переменную
{{ forloop.counter0 }}
, в которой хранится индекс текущей итерации, давайте не будем выводить в цикле второй элемент (индексация начинается с 0)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
{% for item in my_list %}
{% if forloop.counter0 != 1 %}
<span>{{ item }}</span>
<br>
{% endif %}
{% endfor %}
</div>
</body>
</html>
Обновляем страницу и видим:
url
Тег url позволяет нам сгенерировать урл по его имени. Это очень удобно, если адрес меняется, а его имя нет.
Давайте сгенерируем две ссылки на два наших урла.
mysite/urls.py
Назначим имя index
path('', index, name='index')
myapp/urls.py
Назначим имя first
path('', first, name='first')
Добавим в наш шаблон ссылку на вторую страницу:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>
<a href="{% url 'first' %}">Another page</a>
</div>
</body>
</html>
Комментарии
Однострочный
{# this won't be rendered #}
Многострочный при помощи тега comment
{% comment "Optional note" %}
<p>Commented out text</p>
{% endcomment %}
Фильтры
Фильтры преобразуют значения переменных и аргументов тегов.
Выглядят они так:
{{ django|title }}
В контексте {'django': 'the web framework for perfectionists with deadlines'}
, этот шаблон отображает:
The Web Framework For Perfectionists With Deadlines
Некоторые фильтры принимают аргумент:
{{ my_date|date:"Y-m-d" }}
Справочник по встроенным фильтрам,
а также инструкции по написанию пользовательских тегов и фильтров.
Наследование шаблонов
Наши предыдущие примеры шаблонов были небольшими фрагментами HTML-кода, однако в реальной ситуации вы будете использовать Django для создания больших страниц. Отсюда возникает один из наиболее существенных вопросов веб-разработки — как уменьшить количество повторяющегося и избыточного кода в общих частях страниц, таких как header, footer, навигация по сайту?
Основная фишка шаблонов Django — наследование. Шаблон может расширять (уточнять) поведение родительского шаблона.
Любой участок шаблона может быть обернут в блочный тег (естественно, что тег не может начинаться перед, а заканчиваться внутри цикла). Блоку дается имя.
Например:
{% block content %}
тело блока
{% endblock %}
При помощи тега extend
мы указываем, какой шаблон мы будем уточнять. Расширяя шаблон, мы можем переопределить любые блоки, которые есть в родительском шаблоне. Все, что находится вне этих блоков, будет пропущено.
Получается мощный механизм, практически исключающий необходимость повторения частей шаблонов.
Пример наследования шаблонов
Допустим, мы хотим сделать сайт, содержащий простые страницы и блог.
От верстальщика мы получили макет страницы, содержащий:
- header - шапку (логотип, заголовок страницы, меню);
- body - тело страницы;
- footer - «подвал» с информацией о правах распространения.
Вот как это выглядит:
|
{% block head %}
{% block title %}{% endblock %}
{% block menu %}{% endblock %}
{% endblock %}
{% block page %}
{% block content %}
{% endblock %}
{% endblock %}
{% block footer %}
{% block copyright %}
{% endblock %}
{% endblock %}
Для всех указанных элементов мы создаем соответствующие блочные теги.
Простая страница ложится в этот макет — у нее есть только заголовок и тело.
Теперь перейдем к блогу. В блоге хотелось бы добавить правую колонку для вывода списка тегов и последних статей. Возможно мы захотим добавить правую колонку к каким-нибудь другим страницам сайта. Чтобы избежать копирования «двухколоночности», вынесем ее в отдельный шаблон, переопределив тело страницы у базового.
{% extend "base.htm" %}
{% block page %}
{% block content %}
{% endblock %}
{% block sidebar %}
{% endblock %}
{% endblock %}
В блоге будет несколько типов страниц:
- список статей;
- статья;
- список тегов;
- список статей, у которых есть определенный тег;
У всех страниц правая колонка остается неизменной, поэтому разумно сделать базовую страницу для блога, наследуя ее от двухколоночной базовой страницы.
{% extend "base_2col.htm" %}
{% block title %}
Блог
{% endblock %}
{% block sidebar %}
{% block tags %}
{% endblock %}
{% block recent %}
{% endblock %}
{% endblock %}
Теперь приведем примеры внутренних страниц блога (все они наследуются от базовой страницы блога).
Список статей:
{% extend "blog/base.htm" %}
{% block content %}
{% for article in article_list %}
<a href="{% url article_view article.id %}">
{{ article.title }}
</a>
{% endfor %}
{% endblock %}
Статья:
{% extend "blog/base.htm" %}
{% block title %}
{{ article.title }} - {{ block.super }}
{% endblock %}
{% block content %}
{{ article.text }}
{% endblock %}
Список статей, у которых есть определенный тег:
{% extend "blog/index.htm" %}
{% block title %}
{{ tag.title }} - {{ block.super }}
{% endblock %}
{% block content %}
{{ tag.title }}
{{ tag.text }}
{{ block.super }}
{% endblock %}
В данном случае, мы воспользовались еще одной хитростью. Ведь этот список ничем не отличается от простого списка статей — он просто отфильтрован по дополнительным параметрам. Поэтому мы унаследовали его от списка статей и при перекрытии тела использовали тег {{ block.super }}
— вывести все содержимое родительского блока.
Как видно, каждый шаблон очень конкретен и отвечает только за свою функциональность. Ему нет необходимости знать о всей странице в целом.
Практика по наследованию
Создадим в папке templates
новые файлы base.html
и first.html
и изменим файлы templates/index.html
и функцию first
в myapp/views.py
template/base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div style="background-color: aqua">
{% block content %}
{% endblock %}
</div>
</body>
</html>
template/index.html
{% extends 'base.html' %}
{% block content %}
<div style="padding: 20px; background-color: fuchsia"> Extended template index!</div>
<a href="{% url 'first' %}">To the first page</a>
{% endblock %}
template/first.html
{% extends 'base.html' %}
{% block content %}
<div style="padding: 20px; background-color: chocolate"> Extended template first!</div>
<a href="{% url 'index' %}">To the index page</a>
{% endblock %}
Функция first
в myapp/views.py
def first(request):
return render(request, 'first.html')
Смотрим результаты произошедшего и пытаемся их понять (ссылки добавлены для удобства).
Индекс страница
Первая страница
Что произошло?
Мы создали базовый шаблон base.html
в котором описали то, что будет во всех шаблонах (и покрасили в голубенький, для наглядности), которые от него наследуются, и обозначили {% block content %}
. Вся нужная нам информация будет наследоваться именно в указанный блок, на странице разных блоков может быть сколько угодно главное, чтобы они имели разные названия.
Наш index view рендерит страницу index.html
в ней мы отнаследовались от нашей base.html
вписали такой же блок content
чтобы в него вписать нужные нам данные, в нашем случае это просто текст и ссылка, текст мы перекрасили, чтобы было видно что это данные из нового файла, а ссылку - нет, чтобы было видно, что цвет из base.html
отнаследовался, то же самое произошло и с first.html
Include
А теперь представим обратную ситуацию, нам нужно в разные части сайта вставить один и тот же блок (рекламу, например)
Тут нас спасает тег include
который позволяет “внедрить” нужную часть страницы куда угодно
Создадим в папке templates
еще один файл с названием add.html
templates/add.html
<div style="padding: 20px; background-color: chartreuse"> That's included html!!!</div>
И теперь добавим этот файл к страницам index.html
и first.html
но в разные места, чтобы получилось
template/index.html
{% extends 'base.html' %}
{% block content %}
{% include 'add.html' %}
<div style="padding: 20px; background-color: fuchsia"> Extended template index!</div>
<a href="{% url 'first' %}">To the first page</a>
{% endblock %}
template/first.html
{% extends 'base.html' %}
{% block content %}
<div style="padding: 20px; background-color: chocolate"> Extended template first!</div>
<a href="{% url 'index' %}">To the index page</a>
{% include 'add.html' %}
{% endblock %}
Смотрим на результат:
index:
first:
В добавленную станицу можно передать переменные, при помощи тега with
Изменим файл templates/add.html
<div style="padding: 20px; background-color: chartreuse"> Hello {{ name }} !</div>
И файл templates/index.html
{% extends 'base.html' %}
{% block content %}
{% include 'add.html' with name='world' %}
<div style="padding: 20px; background-color: fuchsia"> Extended template index!</div>
<a href="{% url 'first' %}">To the first page</a>
{% endblock %}
Смотрим на результат:
index
first
В первом случае мы видим, что добавлена переменная, во втором - ничего, так как мы ничего не передавали.
Давайте добавим проверку на наличие переменной!
templates/add.html
<div style="padding: 20px; background-color: chartreuse">{% if name %} Hello {{ name }} ! {% else %} Sorry I don't know your name {% endif %}
</div>
Смотрим на first page
Переменной нет, срабатывает if
Так же можно передавать эту переменную из view, для этого нужно в with дописать {{ variable_name }}
Домашнее задание / Практика
Создать базовую html от которой будут наследоваться все остальные
Для статических урлов сделать html файлы наследующиеся от базового, но с разным текстом (можно и оформлением)
Для урлов http://127.0.0.1:8000/article/<int:article_number>
, http://127.0.0.1:8000/article/<int:article_number>/<slug:slug_text>
,
cделать html файлы в которых выводить текст о том, чётный введен или нечётный article_number
(логику прописать в темплейтах), если введён slug_text
, выводить этот текст при помощи include в добавочной html (добавленной из отдельного файла).
На главной странице (http://127.0.0.1:8000/) сделать две ссылки, перейти на случайную статью (id), и перейти на случайную статью со случайным слагом (5-10 случайных символов)
На всех страницах внизу должна быть ссылка на главную.
Литература
Документация