Что же такое шаблон? В бытовом понимании - это заготовка под что-то, что потом будет использоваться, в Django это почти также.
Шаблонами мы называем заготовленные html страницы, в которые мы можем добавить необходимые нам данные и логику.
Но как это работает?
Откроем начатый проект с прошлого занятия.
Создадим новую папку на уровне корня проекта и назовём её templates
(название может быть любым, но принято называть
именно так.) Чтобы получилась вот такая структура:
mysite/
myapp/
templates/
manage.py
Чтобы обрабатывать шаблоны, мы должны "рассказать" Django, где именно искать эти самые шаблоны. Для этого нужно
открыть mysite/settings.py
и отредактировать его.
В данный момент нас интересует переменная TEMPLATES
, выглядит она примерно так:
В ключ DIRS
добавим нашу папку с шаблонами, чтобы получилось так:
Ключи:
BACKEND
: путь к классу, который отвечает за обработку данных и логику. (Замена требуется очень редко.)
DIRS
: список папок, в которых Django будет искать шаблоны.
APP_DIRS
: булевое поле, которое отвечает за то, нужно ли искать папки с шаблонами внутри папки с приложениями, например,
в нашей структуре, если значение False
, то поиск будет только в папке templates
на уровне файла manage.py
, а если
значение True
, то в папках /templates
и /myapp/templates/
.
OPTIONS
: дополнительные настройки, подробнее будем рассматривать позже.
Для применения любых изменений нужно перезапускать сервер (команда python manage.py runserver
).
Мы "рассказали" Django, где именно искать шаблоны, но пока ни одного не создали. Давайте сделаем это!
В папке templates
нужно создать html файл, назовём его index.html
(название не имеет значения, главное, чтобы
формат был html
).
mysite/
myapp/
templates/
index.html
manage.py
Содержимое файла index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
That's template!
</body>
</html>
Итак, теперь у нас есть один шаблон, но мы его не используем, давайте переделаем нашу view
для обработки шаблонов.
В файле myapp/views.py
нужно импортировать обработчик шаблонов, в начало файла добавляем
from django.shortcuts import render
И перепишем функцию index
:
def index(request):
return render(request, 'index.html')
Перезапустим сервер и увидим результат на главной странице http://127.0.0.1:8000/
Мы отрендерили шаблон! Но в нём нет никаких переданных данных, для передачи данных нужно в метод render добавить третий аргумент в виде словаря.
Для демонстрации основных типов данных я допишу функцию 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')
return render(request, 'index.html', {
'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,
})
Значения переданы, но пока они никак не используются, давайте же посмотрим, как отобразить переменные в шаблоне!
Для вывода данных в Django темплейте используются фигурные скобки {{ }}
{{first_name}}
{{last_name}}
Для доступа к вложенным структурам используется точка:
{{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>
Обновим страницу и увидим.
После всех логических операторов и циклов в шаблонах нужно ставить соответствующий закрывающий тег!
Например, {% for ...%} {% endfor %}
, {% if ... %} {% endif %}
.
В шаблонах можно оперировать не только переменными, но и несложной логикой, такой как логические операторы и циклы.
Давайте добавим в наши параметры переменную 'display_num' и назначим ей значение False
.
myapp/views.py
return render(request, 'index.html', {
'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.py
изменим переменную display_num
с False на True:
myapp/views.py
return render(request, 'index.html', {
'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': True
})
То увидим:
Т.к. значение переменной display_num
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
Джанго уже генерирует некоторые переменные, например, переменную {{ forloop.counter0 }}
,
в которой хранится индекс текущей итерации, давайте не будем выводить в цикле второй элемент.
<!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>
{% if forloop.counter0 != 1 %}
Символ != это не равно, а значение 1, потому что индекс начинается с 0.
Обновляем страницу и видим:
На самом деле все ключевые слова используемые внутри {% %}
называются template tags, и их существует огромное
множество.
Их очень много, часть мы рассмотрим, часть вам придется изучить самостоятельно.
Тег 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>
Наш индекс пейдж:
После клика:
Теги наследования шаблонов extends
, block
Зачем нам это надо?
Наследование шаблонов нужно, чтобы не писать одно и то же отображение много раз. Как часто вы видели сайты, где сверху, снизу, слева, справа и т. д. всегда одно и тоже, а меняется только "середина"? Самый простой способ так сделать - это наследование.
Тут нам и помогут наши волшебные теги!
Создадим в папке 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 %}
(здесь block - это тэг, а content - присвоенное ему название, его можно выбрать произвольно) вся нужная нам информация будет наследоваться именно в указанный блок, на странице разных блоков может
быть сколько угодно главное, что бы они имели разные названия.
Наш индекс пейдж рендерит страницу index.html
, в ней мы отнаследовались от нашей base.html
, вписали такой же
блок content
, чтобы передать в него нужные нам данные, в нашем случае это просто текст и ссылка, текст мы перекрасили,
чтобы было видно, что это данные из нового файла, а ссылку нет, чтобы было видно, что цвет из base.html
отнаследовался, то же самое произошло и с first.html
.
А теперь представим обратную ситуацию: нам нужно в разные части сайта "засунуть" один и тот же блок (рекламу, например)
Тут нас спасает тег 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.html
:
first.html
:
В добавленную станицу можно передать переменные при помощи тега 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='Vlad' %}
<div style="padding: 20px; background-color: fuchsia"> Extended template index!</div>
<a href="{% url 'first' %}">To the first page</a>
{% endblock %}
Смотрим на результат:
index.html
first.html
В первом случае мы видим добавленную переменную, во втором - ничего, так как мы ничего не передавали.
Давайте добавим проверку на наличие переменной!
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 }}
Что это такое, и зачем это нужно?
Фильтры - это возможность видоизменить данные перед их отображением. Давайте попробуем ими воспользоваться, для этого
во view
добавим сверху файла
from datetime import datetime
myapp/views.py
функция index
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')
return render(request, 'index.html', {
'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': True,
'now': datetime.now()
})
А index.html
изменим так
{% extends 'base.html' %}
{% block content %}
<div>{{ now| date:"SHORT_DATE_FORMAT" }}</div>
<div>{{ now|date:"D d M Y" }} {{ value|time:"H:i" }}</div>
<div>{{ not_exist|default:"nothing" }}</div>
<div>{{ my_str | capfirst }}</div>
<div>{{ my_list | join:"**" }}</div>
{% endblock %}
Смотрим на результат
Каждый фильтр имеет свои особенности и правила написания, подробнее можно посмотреть по ссылке выше.
Кроме того, для данных существуют встроенные фильтры.
Например date
, default
, join
, capfirst
. На самом деле их огромное количество, весь список встроенных фильтров можно
посмотреть Тыц
На самом деле, стандартным набором дело не ограничивается, и в случае необходимости можно дописать свои теги и фильтры, почитать об этом можно вот тут
Продолжаем собирать блог
https://edu-python-course.github.io/_build/html/uk/appx/blog/spec.html#challenge-templates
На данном этапе мы пока не можем реализовать все, что указанно в спецификации, поэтому на текущем моменте надо реализовать следующие страницы:
- Логин
- Регистрация
- Главная страница (где будет список блогов)
- Страница с деталями блога (при любом слаге пока одна и та же)
- Страница создания нового блога
Все страницы должны быть отнаследованы от базового шаблона, сверху должна быть навигация между указанными страницами (для детального просмотра впишите любой стационарный слаг):
Пока что ничего из этого кроме блока навигации не будет кликабельным :)