В этой статье я постараюсь на примерах объяснить тему наследования в шаблонах django, то есть как использовать тэги extends
и include
.
Идея для статьи родилась, даже назрела, во время обучения новичков в Яндекс.Практикум.
Почему-то такие вещи как base.html
, {% include ... %}
итд вызывали у студентов непонимание.
Статьи из «интернетов» понимания не увеличивали, поэтому появилась идея написать свою бесполезную статью для новичков.
Если вы профи, то можете не тратить время на чтение, так как статья именно для новичков.
Диаграммы, текстовые описания и примеры новичкам, по моему опыту, как-то не особо помогали.
Поэтому данная статья будет в формате «Меньше текста, больше кода!».
- Развернём django-проект.
- Скачаем html-шаблон.
Порубим на части Преобразуем html-шаблон в django-шаблон.
- Работать будем в
django shell
.
Код статьи можно найти в репозитории: django-templates-explained.
Рубить html-шаблон на части будем в несколько итераций.
Для каждой исходники и результаты лежат в соответствующей папке.
Для начала вам необходимо настроить окружение.
- Создаём папку для работы, назовём её:
templates-article
;
- Клонируем данный django-проект в эту папку:
git clone https://github.com/dclimber/base-example-django-project .
;
- Скачиваем бесплатный html-шаблон для блога;
- Скачанный архив —
startbootstrap-clean-blog-gh-pages.zip
— разорхивируем в папку html-src
;
У вас должна получится такая структура проекта:
templates-article/
.git/ # скрытая папка, может не отображаться в файловом менеджере (проводнике)
assets/ # папка для статичесих файлов django
blog/ # папка для приложения blog
config/ # папка «основы» django-проекта
html-src/ # папка с нашим шаблоном
assets/
css/
js/
about.html
contact.html
index.html
post.html
templates/ # папка для шаблонов django
.env.template
.gitignore
LICENSE
manage.py
poetry.lock
pyproject.toml
README.md
requirements.txt
setup.cfg
Теперь пора запустить наш django-проект и убедиться, что всё работает.
- Создаём виртуальное окружение:
python3 -m venv env
;
- Если у вас не питон вызывается не командой
python3
, а командой python
, то пишите python -m venv env
;
- Далее действуйте аналогично —
python вместо python3
.
- Активируем окружение:
- Linux/Mac:
source env/bin/activate
;
- Windows:
env\Scripts\activate
;
- Обновляем
pip
: pip install -U pip
;
- Устанавливаем зависимости:
pip install -r requirements.txt
;
- Создаём
.env
файл из .env.template
: cp .env.template на .env
;
- Меняем содержимое
.env
на:
# Django
SECRET_KEY=tut_lyubaya_sluchaynaya_stroka_12345679_ee_obychno_sam_django_generiruyet
DEBUG=True
ALLOWED_HOSTS=*
LANGUAGE_CODE=ru
TIME_ZONE=Europe/Moscow
- Делаем миграции:
python manage.py migrate
;
- Запускаем сервер-разработки django:
python manage.py runserver
;
- Открываем в браузере страницу: http://127.0.0.1:8000/admin/.
Вы должны увидеть нечто подобное:

Если увидели — всё работает!
Останавливем сервер, нажмите на клавиатуре: ctrl+c
.
Работать будем по «итерациям». Если непонятно, что это значит, то ниже приведено определение этого слова.
«Итерация», перевод слова iteration — это:
итерация, повтор (от лат. iteratio - повторение) в программировании
а) процесс повторяющегося выполнения последовательности операторов или команд. Различают итерации с заранее известным числом повторений (definite iteration) и итерации, число повторений которых заранее неизвестно (indefinite iteration)
г) в переносном (метафорическом) смысле - очередной шаг (этап) в выполнении проекта (термин подчёркивает повторяющийся характер работ - оценка результатов, внесение изменений и доработок, представление новых результатов и т. д.)
Итерации — что будем делать шаг-за-шагом:
- Преобразуем HTML-шаблоны в django-шаблоны;
- Создадим базовый шаблон
base.html
— тэг {% extends %}
;
- Разобъём
base.html
на части — тэг {% include %}
;
- Выведем тексты постов через контекст шаблона.
- Выведем шаблоны через вьюхи (представления, views) django:
- Применим тэг
{% static %}
;
- Создадим
views
для главной страницы и страницы записи.
- Подключим url-пути к этим вьюхам;
- Создаём папку
iter1
в папке templates
;
- В ней создаём папку
src
;
- Скопируем файлы
index.html
и post.html
в папку iter1/src
;
- Меняем ссылки на к
css
, js
и картинке в обоих HTML-шаблонах:
href="css/styles.css"
→ href="../../html-src/css/styles.css"
;
url('assets/img/home-bg.jpg')
→ url('../../html-src/assets/img/home-bg.jpg')
;
src="js/scripts.js"
→ src="../../html-src/js/scripts.js"
;
Войдем в «терминал django»:
python manage.py shell
В терминале вводим код:
- Импортируем функцию для открытия новой страницы в веб-браузере по-умолчанию:
from webbrowser import open_new_tab
|
- Сделаем импорт файла-настроек django:
from django.conf import settings
|
- Импортируем функцию для преобразования django-шаблона в html-шаблон
- под капотом функция использует встроенную в django Функцию
render_to_string
- исходники функции найдете здесь
from blog.utils import render_to_file
|
- Отрендерим django-шаблон в html, с помощью этой функции:
settings.TEMPLATES_DIR / 'iter1'
— это объект Path, детали читайте по этой ссылке;
index_page = render_to_file('iter1/src/index.html', settings.TEMPLATES_DIR / 'iter1', {})
|
- Откроем html-шаблон в брауезере:
Если всё прошло успешно, то вы должны увидеть в браузере страницу:

Проделаем те же шаги для страницы поста:
post_page = render_to_file('iter1/src/post.html', settings.TEMPLATES_DIR / 'iter1', {})
open_new_tab(post_page)
|
Если тупо скопировать html-шаблон в django-шаблон, то на выходе будет html-шаблон.
— В чем же тогда отличие django-шаблонов от обычного html?
— В том, что в django-шаблонах можно использовать язык-шаблонов django.
С ним мы поработаем в следующей итерации.
- Создадим папку
iter2
в папке templates
;
- Скопируем в неё папку
src
из iter1
;
Давайте изучим шаблоны post.html
и index.html
:
- Если вчитаться и вглядеться, то код со строки 1 по строку 36 в шаблонах идентичен.
- Значит его можно вынести в шаблон-родитель
base.html
.
- Также идентичный код ниже
<!-- Footer-->
в обоих шаблонах.
- Тоже выносим.
У нас получается:
1. iter2/src/base.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Clean Blog - Start Bootstrap Theme</title>
<link rel="icon" type="image/x-icon" href="assets/favicon.ico" />
<!-- Font Awesome icons (free version)-->
<script src="https://use.fontawesome.com/releases/v5.15.4/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
<!-- Core theme CSS (includes Bootstrap)-->
<link href="../../html-src/css/styles.css" rel="stylesheet" />
</head>
<body>
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
<div class="container px-4 px-lg-5">
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu
<i class="fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto py-4 py-lg-0">
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
</ul>
</div>
</div>
</nav>
<!-- Footer-->
<footer class="border-top">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<ul class="list-inline text-center">
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2021</div>
</div>
</div>
</div>
</footer>
<!-- Bootstrap core JS-->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="../../html-src/js/scripts.js"></script>
</body>
</html>
|
2. iter2/src/index.html
:
<!-- Page Header-->
<header class="masthead" style="background-image: url('../../html-src/assets/img/post-bg.jpg')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="post-heading">
<h1>Man must explore, and this is exploration at its greatest</h1>
<h2 class="subheading">Problems look mighty small from 150 miles up</h2>
<span class="meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2021
</span>
</div>
</div>
</div>
</div>
</header>
<!-- Post Content-->
<article class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<p>Never in all their history have men been able truly to conceive of the world as one: a single sphere, a globe, having the qualities of a globe, a round earth in which all the directions eventually meet, in which there is no center because every point, or none, is center — an equal earth which all men occupy as equals. The airman's earth, if free men make it, will be truly round: a globe in practice, not in theory.</p>
<p>Science cuts two ways, of course; its products can be used for both good and evil. But there's no turning back from science. The early warnings about technological dangers also come from science.</p>
<p>What was most significant about the lunar voyage was not that man set foot on the Moon but that they set eye on the earth.</p>
<p>A Chinese tale tells of some men sent to harm a young girl who, upon seeing her beauty, become her protectors rather than her violators. That's how I felt seeing the Earth for the first time. I could not help but love and cherish her.</p>
<p>For those who have seen the Earth from space, and for the hundreds and perhaps thousands more who will, the experience most certainly changes your perspective. The things that we share in our world are far more valuable than those which divide us.</p>
<h2 class="section-heading">The Final Frontier</h2>
<p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
<p>There can be no thought of finishing for ‘aiming for the stars.’ Both figuratively and literally, it is a task to occupy the generations. And no matter how much progress one makes, there is always the thrill of just beginning.</p>
<blockquote class="blockquote">The dreams of yesterday are the hopes of today and the reality of tomorrow. Science has not yet mastered prophecy. We predict too much for the next year and yet far too little for the next ten.</blockquote>
<p>Spaceflights cannot be stopped. This is not the work of any one man or even a group of men. It is a historical process which mankind is carrying out in accordance with the natural laws of human development.</p>
<h2 class="section-heading">Reaching for the Stars</h2>
<p>As we got further and further away, it [the Earth] diminished in size. Finally it shrank to the size of a marble, the most beautiful you can imagine. That beautiful, warm, living object looked so fragile, so delicate, that if you touched it with a finger it would crumble and fall apart. Seeing this has to change a man.</p>
<a href="#!"><img class="img-fluid" src="../../html-src/assets/img/post-sample-image.jpg" alt="..." /></a>
<span class="caption text-muted">To go places and do things that have never been done before – that’s what living is all about.</span>
<p>Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before.</p>
<p>As I stand out here in the wonders of the unknown at Hadley, I sort of realize there’s a fundamental truth to our nature, Man must explore, and this is exploration at its greatest.</p>
<p>
Placeholder text by
<a href="http://spaceipsum.com/">Space Ipsum</a>
· Images by
<a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>
</p>
</div>
</div>
</div>
</article>
|
3. iter2/src/post.html
:
<!-- Page Header-->
<header class="masthead" style="background-image: url('../../html-src/assets/img/home-bg.jpg')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="site-heading">
<h1>Clean Blog</h1>
<span class="subheading">A Blog Theme by Start Bootstrap</span>
</div>
</div>
</div>
</div>
</header>
<!-- Main Content-->
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
<h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 24, 2021
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html"><h2 class="post-title">I believe every human has a finite number of heartbeats. I don't intend to waste any of mine.</h2></a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on September 18, 2021
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Science has not yet mastered prophecy</h2>
<h3 class="post-subtitle">We predict too much for the next year and yet far too little for the next ten.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on August 24, 2021
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">Failure is not an option</h2>
<h3 class="post-subtitle">Many say exploration is part of our destiny, but it’s actually our duty to future generations.</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">Start Bootstrap</a>
on July 8, 2021
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
<!-- Pager-->
<div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
</div>
</div>
</div>
|
Однако, если мы сейчас запустим код:
post_page = render_to_file('iter2/src/post.html', settings.TEMPLATES_DIR / 'iter2', {})
open_new_tab(post_page)
|
То страница поста будет «кривой», мягко говоря. index
тоже.
Почему?
Потому-что мы не соединили шаблоны с базовым, для этого нужно добавить:
- В базовый шаблон
iter2/src/base.html
:
...
</nav>
{% block content %}
{% endblock content %}
<!-- Footer-->
<footer class="border-top">
...
|
- таким образом мы создали «блок» — «место на паркинге» — куда дочерние шаблоны могут вписывать что-угодно — «ставить машину».
- В самое начало обоих файлов —
index.html
и post.html
:
{% extends "iter2/src/base.html" %}
<!-- Page Header-->
<header class="masthead" ...
|
- таким образом мы добавили содержимое в блок — «поставили машину в место на паркинге».
- До и после «контента», содержимого шаблонов
index.html
и post.html
:
{% extends "iter2/src/base.html" %}
{% block content %}
<!-- Page Header-->
# В конце файла:
{% enblock content %}
|
Теперь у нас:
post.html
и index.html
стали шаблонами-наследниками.
base.html
стал шаблоном родителем.
Если мы сейчас запустим код:
post_page = render_to_file('iter2/src/post.html', settings.TEMPLATES_DIR / 'iter2', {})
index_page = render_to_file('iter2/src/index.html', settings.TEMPLATES_DIR / 'iter2', {})
open_new_tab(post_page)
open_new_tab(index_page)
|
Тогда страницы откроются как и должны.
А почему?
Сравните файлы:
iter1/index.html
и iter2/index.html
iter1/post.html
и iter2/post.html
Заметили, что они одинаковые?
Зато нет повторения кода в вверху и низу файлов в iter2/src
. Это удобно.
- В
base.html
можно вынести повторяющиеся куски html-кода, которые относятся к странице:
<head></head>
;
- навигацию;
- футер;
- еще кучу всего разного, что может встретися.
- Базовый шаблон подключаем через тэг
{% extends %}
.
- Чтобы что-то вывести в дочернем шаблоне — нужно:
- «создать парковочное место» через тэг
{% block %}
в шаблоне-родителе.
- «поставить машину на парковочное место» через тэг
{% block %}
в шаблоне-наследнике.
Идём дальше.
Чтобы base.html
не превратился в слишком длинный шаблон, который сложно поддерживать, из него можно вынести куски кода и присоединить их при помощи тэга {% include %}
.
- Создадим папку
iter3
в папке templates
;
- Скопируем в неё папку
src
из iter2
;
- Создадим папку
includes
внутри src
.
Что мы видим в base.html
?
- В нём есть меню навигации, давайте вынесем его в шаблон
iter3/src/includes/nav.html
:
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
<div class="container px-4 px-lg-5">
<a class="navbar-brand" href="index.html">Start Bootstrap</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
Menu
<i class="fas fa-bars"></i>
</button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav ms-auto py-4 py-lg-0">
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="index.html">Home</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="about.html">About</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="post.html">Sample Post</a></li>
<li class="nav-item"><a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">Contact</a></li>
</ul>
</div>
</div>
</nav>
|
- В нём есть футер — подвал сайта — давайте вынесем его в шаблон
iter3/src/includes/footer.html
:
<!-- Footer-->
<footer class="border-top">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<ul class="list-inline text-center">
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
<li class="list-inline-item">
<a href="#!">
<span class="fa-stack fa-lg">
<i class="fas fa-circle fa-stack-2x"></i>
<i class="fab fa-github fa-stack-1x fa-inverse"></i>
</span>
</a>
</li>
</ul>
<div class="small text-center text-muted fst-italic">Copyright © Your Website 2021</div>
</div>
</div>
</div>
</footer>
|
- Давайте код, который мы «вынесли» — то бишь вырезали из
base.html
добавим назад:
...
{% include "iter3/includes/nav.html" %}
{% block content %}
{% endblock content %}
{% include "iter3/includes/footer.html" %}
...
|
Запустим код:
post_page = render_to_file('iter3/src/post.html', settings.TEMPLATES_DIR / 'iter3', {})
index_page = render_to_file('iter3/src/index.html', settings.TEMPLATES_DIR / 'iter3', {})
open_new_tab(post_page)
open_new_tab(index_page)
|
Сравним файлы:
iter1/index.html
и iter2/index.html
и iter3/index.html
;
iter1/post.html
и iter2/post.html
и iter3/post.html
.
Одинаковые, верно?
- Вынесем карточки постов, шаблон
iter3/includes/post-preview.html
:
<!-- Post preview-->
<div class="post-preview">
<a href="post.html">
<h2 class="post-title">{{ title }}</h2>
<h3 class="post-subtitle">{{ subtitle }}</h3>
</a>
<p class="post-meta">
Posted by
<a href="#!">{{ author }}</a>
on {{ date }}
</p>
</div>
<!-- Divider-->
<hr class="my-4" />
|
{{ title }}, {{ subtitle }}
итд — это переменные.
Обновим index.html
:
...
<div class="col-md-10 col-lg-8 col-xl-7">
{% include "iter3/src/includes/post-preview.html" with title="Man must explore, and this is exploration at its greatest" subtitle="Problems look mighty small from 150 miles up" author="Start Bootstrap" date="September 24, 2021" %}
{% include "iter3/src/includes/post-preview.html" with title="I believe every human has a finite number of heartbeats. I don't intend to waste any of mine." subtitle="" author="Start Bootstrap" date="September 18, 2021" %}
{% include "iter3/src/includes/post-preview.html" with title="Science has not yet mastered prophecy" subtitle="We predict too much for the next year and yet far too little for the next ten." author="Start Bootstrap" date="August 24, 2021" %}
{% include "iter3/src/includes/post-preview.html" with title="Failure is not an option" subtitle="Many say exploration is part of our destiny, but it’s actually our duty to future generations." author="Start Bootstrap" date="July 8, 2021" %}
<!-- Pager-->
<div class="d-flex justify-content-end mb-4"><a class="btn btn-primary text-uppercase" href="#!">Older Posts →</a></div>
</div>
...
|
Знаю, уродливо, но мы это исправим в следующей итерации 😉.
Запустим код:
post_page = render_to_file('iter3/src/post.html', settings.TEMPLATES_DIR / 'iter3', {})
index_page = render_to_file('iter3/src/index.html', settings.TEMPLATES_DIR / 'iter3', {})
open_new_tab(post_page)
open_new_tab(index_page)
|
Сравним файлы:
iter1/index.html
и iter2/index.html
и iter3/index.html
;
iter1/post.html
и iter2/post.html
и iter3/post.html
.
Одиаковые, но наша кодовая база уже куда лучше — «разбитые» django-шаблоны без повторяющегося куда куда легче поддерживать.
Теперь мы, при помощи context
-а шаблона:
- Избавимся от переменных в
index.html
- Выведем текст поста
post.html
через переменные.
Поехали!
- Создадим папку
iter4
в папке templates
;
- Скопируем в неё папку
src
из iter3
;
- В
iter4/src/index.html
заменим вывод постов на:
{% for post in posts %}
{% include "iter3/src/includes/post-preview.html" with title=post.title subtitle=post.subtitle author=post.author date=post.date %}
{% endfor %}
|
- В
iter4/src/post.html
меняем содержимое на:
<header class="masthead" style="background-image: url('../../html-src/assets/img/post-bg.jpg')">
<div class="container position-relative px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
<div class="post-heading">
<h1>{{ post.title }}</h1>
<h2 class="subheading">{{ post.subtitle }}</h2>
<span class="meta">
Posted by
<a href="#!">{{ post.author }}</a>
on {{ post.date }}
</span>
</div>
</div>
</div>
</div>
</header>
<!-- Post Content-->
<article class="mb-4">
<div class="container px-4 px-lg-5">
<div class="row gx-4 gx-lg-5 justify-content-center">
<div class="col-md-10 col-lg-8 col-xl-7">
{{ post.text|safe }}
<p>
Placeholder text by
<a href="http://spaceipsum.com/">Space Ipsum</a>
· Images by
<a href="https://www.flickr.com/photos/nasacommons/">NASA on The Commons</a>
</p>
</div>
</div>
</div>
</article>
|
- В терминале-django вводим код:
# Импортируем функцию для чтения json-файлов
from blog.utils import read_json_file
# Укажем пути к json-файлам с источниками
index_json_file = settings.BASE_DIR / 'blog' / 'content' / 'index_content.json'
post_json_file = settings.BASE_DIR / 'blog' / 'content' / 'iter4' / 'post_content.json'
# Преобразуем данные из json в python-объекты
index_content = read_json_file(index_json_file)
post_content = read_json_file(post_json_file)
# отрендерим шаблоны
index_context = {'posts': index_content}
post_context = {'post': post_content}
post_page = render_to_file('iter4/src/post.html', settings.TEMPLATES_DIR / 'iter4', post_context)
index_page = render_to_file('iter4/src/index.html', settings.TEMPLATES_DIR / 'iter4', index_context)
# откроем их в браузере
open_new_tab(post_page)
open_new_tab(index_page)
|
Сравним файлы:
iter2/index.html
и iter3/index.html
и iter4/index.html
;
iter2/post.html
и iter3/post.html
и iter4/post.html
.
Одиаковые!
Почему?
Потому-что мы передали в шаблон контекст — содержимое постов.
Давайте теперь это всё оформим как подобается.
Создадим нормальную django-вьюху, пропишем url-ы, используем {% static %}
.
- Скопируем файлы из
iter4/src
в новую папку templates/blog
;
- Скопируем папки:
html-src/assets/img
→ assets/img
;
html-src/css
→ assets/css
;
html-src/js
→ assets/img
;
html-src/assets/favicon.ico
→ assets/favicon.ico
.
- В
base.html
в самом верху до <!DOCTYPE html>
добавляем:
-
Меняем:
href="assets/favicon.ico"
→ href="{% static 'favicon.ico' %}"
;
<link href="../../html-src/css/styles.css" rel="stylesheet" />
→ <link href="{% static 'css/styles.css' %}" rel="stylesheet" />
;
<script src="../../html-src/js/scripts.js"></script>
→ <script src="{% static 'js/scripts.js' %}"></script>
;
iter4/src/includes
→ blog/includes
.
-
Сохраняем файл.
- В файлах
blog/index.html
и blog/post.html
, меняем:
{% extends 'iter4/src/base.html' %}
на {% extends 'blog/base.html' %}
;
iter4/src/includes
на blog/includes
.
url('../../html-src/assets/img/home-bg.jpg')
на url({% static 'img/home-bg.jpg' %})
;
url('../../html-src/assets/img/post-bg.jpg')
на url({% static 'img/post-bg.jpg' %})
;
- Создадим вьюхи — в папке
blog
создаём файл views.py
, с таким содержимым:
from django.conf import settings
from django.shortcuts import render
from .utils import read_json_file
def index(request):
template_name = 'blog/index.html'
index_json_file = (
settings.BASE_DIR / 'blog' / 'content' / 'index_content.json'
)
index_content = read_json_file(index_json_file)
context = {
'posts': index_content
}
return render(request, template_name, context)
def post(request):
template_name = 'blog/post.html'
index_json_file = (
settings.BASE_DIR / 'blog' / 'content' / 'post_content.json'
)
post_content = read_json_file(index_json_file)
context = {
'post': post_content
}
return render(request, template_name, context)
|
- Подключаем вьюхи к url-ам — в
config/urls.py
добавляем:
from blog import views as blog_views
urlpatterns = [
path('admin/', admin.site.urls),
path('', blog_views.index),
path('post/', blog_views.post),
]
|
- Выходим из django-терминала:
ctrl+d
;
- Запускаем сервер —
python manage.py runserver
;
- Открываем страницы:
- http://127.0.0.1:8000/ — страница
index
.
- [http://127.0.0.1:8000/post/]](http://127.0.0.1:8000/post/) — страница поста.
Все работает!
Если не работает — стоит свериться с файлами в репозитории для поста, скорее всего, у вас в коде опечатка.
Надеюсь, статья вам помогла понять шаблоны django.
Многие вещи я в ней не раскрывал — на это есть документация и интернет 😉 — поэтому даю ссылки на материалы.
- Репозиторий для этого поста — тут весь код, что мы писали.
Если что-то не получилось или хотите доучить — стоит в нём покопаться.
- Документация по шаблонам-django — стоит читать и перечитывать;
- Path в python3 — это вот всякие
settings.BASE_DIR / 'templates'
;