Работа мечты в один клик 💼

💭Мечтаешь работать в Сбере, но не хочешь проходить десять кругов HR-собеседований? Теперь это проще, чем когда-либо!
💡AI-интервью за 15 минут – и ты уже на шаг ближе к своей новой работе.
Как получить оффер? 📌 Зарегистрируйся 📌 Пройди AI-интервью 📌 Получи обратную связь сразу же!
HR больше не тянут время – рекрутеры свяжутся с тобой в течение двух дней! 🚀
Реклама. ПАО СБЕРБАНК, ИНН 7707083893. Erid 2VtzquscAwp
Flask – микрофреймворк: это означает, что в стандартную поставку входит только самое необходимое, а все остальное при необходимости легко подключается. Поэтому приставка «микро» не должна вводить в заблуждение – при желании на Flask можно реализовать серьезный, масштабируемый проект. А для реализации таких небольших веб-приложений, как наш сайт, Flask подходит как нельзя лучше.
Обзор проекта
Готовый сайт находится здесь. У сайта несколько секций:
- Главная
- Резюме
- Портфолио
- Блог
- Контакты
Переключение между секциями создает иллюзию многостраничности, но в «живой» версии сайт – одностраничный. Бэкенд включает в себя модуль Frozen Flask, который превращает приложение в генератор статических сайтов (SSG): все страницы, необходимые для адекватного представления сайта в статической версии, создаются автоматически.

Фронтенд сделан на Bootstrap с несколькими дополнительными JS скриптами – например, записи в блоге фильтруются (без перезагрузки страницы) по тегам с помощью скрипта isotope.js
, при этом теги для фильтра скрипт получает из расширения Flask – FlatPages. Записи в блоге и карточки в портфолио можно перелистывать свайпом, без перезагрузки страницы. Bootstrap обеспечивает адаптивность: сайт одинаково хорошо смотрится на широкоформатном мониторе и на смартфоне.
Первый этап
На этом этапе мы установим Flask вместе со всеми нужными расширениями и зависимостями, напишем первый вариант кода для блога и сделаем два простейших шаблона.
Установка Flask
Сначала нужно создать папку для проекта и активировать виртуальное окружение:
mkdir flask_project
cd flask_project
mkdir .venv
pipenv shell
Папка .venv – служебная: менеджер pipenv автоматически разместит все нужные зависимости там, и они не будут загромождать корневую директорию проекта. Виртуальное окружение активируется командой pipenv shell
, для выхода нужно выполнить exit
.
Установим Flask и все необходимые зависимости. Для этого сохраните этот список в файле requirements.txt:
Click==7.0
Flask==1.1.1
Flask-FlatPages==0.7.1
Frozen-Flask==0.15
itsdangerous==1.1.0
Jinja2==2.10.3
Markdown==3.1.1
MarkupSafe==1.1.1
Pygments==2.4.2
PyYAML==5.1.2
Werkzeug==0.16.0
Поместите файл в директорию проекта и выполните команду:
pipenv install -r requirements.txt
Для быстрого знакомства с принципами работы Flask мы сначала создадим тестовый блог, а затем перейдем к реализации нашего проекта.
Структура Flask проекта
Начнем с создания структуры проекта:
├── mysite.py
├── content
│ └── posts
│
├── static
└── templates
В папке content/posts будут размещаться Markdown файлы, в templates – шаблоны, в static – CSS стили, изображения и JS-скрипты. Весь код приложения мы напишем в файле mysite.py – сначала импортируем нужные модули, затем определим основные параметры, после пропишем маршруты к шаблонам и запустим сервер. Простейший вариант кода mysite.py выглядит так:
import sys
from flask import Flask, render_template
from flask_flatpages import FlatPages, pygments_style_defs
from flask_frozen import Freezer
DEBUG = True
FLATPAGES_AUTO_RELOAD = DEBUG
FLATPAGES_EXTENSION = '.md'
FLATPAGES_ROOT = 'content'
POST_DIR = 'posts'
app = Flask(__name__)
flatpages = FlatPages(app)
freezer = Freezer(app)
app.config.from_object(__name__)
@app.route("/")
def index():
posts = [p for p in flatpages if p.path.startswith(POST_DIR)]
posts.sort(key=lambda item: item['date'], reverse=True)
return render_template('index.html', posts=posts, bigheader=True)
@app.route('/posts/<name>/')
def post(name):
path = '{}/{}'.format(POST_DIR, name)
post = flatpages.get_or_404(path)
return render_template('post.html', post=post)
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "build":
freezer.freeze()
else:
app.run(host='127.0.0.1', port=8000, debug=True)
В это трудно поверить, но основной код блога действительно занимает всего 28 строк. Это возможно благодаря модулям FlatPages и Flask Frozen: первый избавляет от необходимости хранить посты в базе данных, проводит рендеринг Markdown-файлов в html, обеспечивает вывод записей и обращение к их свойствам, что будет заметно при создании шаблонов. Flask Frozen в действии мы увидим чуть позже: этот модуль берет на себя создание статической копии сайта – экспортирует html-файлы и все нужные ассеты (скрипты, CSS, изображения) в папку build.
Добавим в папку posts два-три тестовых поста – в YAML части обязательно должны быть метаданные title, date, description, потому что Jinja будет вызывать их в шаблонах. Markdown посты можно писать в любом редакторе – хоть в Блокноте, хоть в Sublime Text; можно обзавестись и специальным редактором – MarkdownPad для Windows, Mou для macOS.
Теперь создадим два простейших шаблона. Это код для index.html:
{% block content %}
<h2>Блог - тестовый запуск</h2>
{% for post in posts %}
<small>{{ post.date }}</small>
<p>
<h3>
{{ post.title }}
</h3>
<p>
<i>{{ post.description }}</i>
</p>
<p>{{ post.html[:100]|safe }}</p>
<a href="{{ url_for('post', name=post.path.replace('posts/', '')) }}"><span>Читать</span></a>
</p>
{% endfor %}
{% endblock %}
А это шаблон для вывода записи на отдельной странице post.html:
{{ post.date }}
{{ post.title }}
{{ post.dеscription }}
{{ post.html|safe }}
Оба шаблона мы доработаем на следующем этапе, а пока запустим приложение python mysite.py
и посмотрим на результат.


Весь код и тестовый контент для этого этапа есть здесь.
Второй этап
На этом этапе мы сделаем первые шаблоны и подключим файл с настройками.
Шаблонизатор Jinja2
Flask использует шаблонизатор Jinja2. Синтаксис Jinja2 идентичен шаблонизатору Django и напоминает Python. Если вам еще не приходилось работать с Django, на этом этапе достаточно знать, что логика в Jinja2 заключается в такие фигурные скобки {% %}
, а переменные – в такие {{ }}
.
Шаблон Jinja2 представляет собой обычный html-файл, в котором блоки с логикой и переменными размещаются в уже упомянутых скобках. К шаблону можно подключать любые JS-скрипты, иконки, шрифты. Большое количество переменных можно передать в шаблон в виде словаря:
@app.route("/")
def index():
variables = {"title":"Это мой сайт",
"description":"Разработчик, дизайнер, автор ИТ-курсов"
"keywords":"Разработка на Python, курсы по Django"
}
return render_template('index.html', **variables)
В шаблоне index.html, в свою очередь, эти переменные можно вставить в нужные теги:
<title>{{ title }}</title>
<meta content="{{ description }}" name="description">
<meta content="{{ keywords }}" name="keywords">
Если же переменных очень много, имеет смысл вынести словарь в отдельный файл – в дальнейшем мы воспользуемся именно этим вариантом.
Jinja2 поддерживает наследование и включение шаблонов – это позволяет разбить шаблон объемной страницы на несколько секций, которые проще редактировать по отдельности. Наш сайт состоит из одной страницы с несколькими разделами, которые целесообразно вынести в отдельные шаблоны:
- base.html
- index.html
- header.html
- resume.html
- counters.html
- skills.html
- interests.html
- portfolio.html
- card.html
- blog.html
- post.html
- contacts.html
Jinja2 не диктует каких-то жестких правил: при желании можно обойтись всего двумя шаблонами – index.html и post.html. И, конечно, можно не выносить переменные в отдельный файл, а вписать весь текст резюме и портфолио прямо в index.html. Но поддерживать сайт проще, если хранить всю потенциально изменяемую информацию в Markdown-файлах и текстовом файле конфигурации – в этом случае для изменения данных нужно будет внести поправки только один раз: переменные в шаблонах обеспечат обновление текста во всех разделах сайта. Кроме того, ненужные разделы сайта очень просто удалить, если они находятся в отдельных шаблонах.
Первый шаблон, который мы создадим – base.html. Он будет получать переменные из бэкенда. Для передачи данных в шаблон создайте файл settings.txt и сохраните в нем словарь:
{"site_url":"http://localhost:8000",
"site_title": "John Doe: Python разработчик и автор контента",
"description": "Джон Доу - портфолио, резюме и блог Python разработчика",
"keywords": "Веб-разработка на Python, бэкенд на Django и Flask"}
Теперь добавьте импорт json
и загрузку данных из файла в mysite.py:
@app.route("/")
def index():
posts = [p for p in flatpages if p.path.startswith(POST_DIR)]
posts.sort(key=lambda item: item['date'], reverse=True)
with open('settings.txt', encoding='utf8') as config:
data = config.read()
settings = json.loads(data)
return render_template('index.html', posts=posts, bigheader=True, **settings)
Сохраните этот код в templates/base.html:
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>{{ site_title }}</title>
<meta content="{{ description }}" name="description">
<meta content="{{ keywords }}" name="keywords">
<link href="{{ url_for('static', filename='img/favicon.png') }}" rel="icon">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i|Raleway:300,300i,400,400i,500,500i,600,600i,700,700i|Poppins:300,300i,400,400i,500,500i,600,600i,700,700i" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/bootstrap/css/bootstrap.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/bootstrap-icons/bootstrap-icons.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/boxicons/css/boxicons.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/glightbox/css/glightbox.min.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/remixicon/remixicon.css') }}" rel="stylesheet">
<link href="{{ url_for('static', filename='assets/swiper/swiper-bundle.min.css') }}" rel="stylesheet">
<script src="https://kit.fontawesome.com/69e2443572.js" crossorigin="anonymous"></script>
<link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
</head>
<body>
<main id="main">
{% block content %} <!-- вложенные шаблоны -->
{% endblock %}
</main>
<!-- Скрипты -->
<script src="{{ url_for('static', filename='assets/purecounter/purecounter.js') }}"></script>
<script src="{{ url_for('static', filename='assets/bootstrap/js/bootstrap.bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='assets/glightbox/js/glightbox.min.js') }}"></script>
<script src="{{ url_for('static', filename='assets/isotope-layout/isotope.pkgd.min.js') }}"></script>
<script src="{{ url_for('static', filename='assets/swiper/swiper-bundle.min.js') }}"></script>
<script src="{{ url_for('static', filename='assets/waypoints/noframework.waypoints.js') }}"></script>
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
А этот – в templates/index.html:
{% extends "base.html" %}
{% block content %}
<h1>Здесь будет сайт</h1>
{% endblock %}
Сохраните в папке static все эти ассеты. Теперь можно запускать сервер python mysite.py
– скелет сайта готов:

Перейдем к созданию первого шаблона, расширяющего index.html – header.html. Добавьте переменные в файл settings.txt:
"site_url":"http://localhost:8000",
"tag1":"Разработчик и автор",
"tag2":"курсов по Django",
"sect1":"Главная",
"sect2":"Резюме",
"sect3":"Портфолио",
"sect4":"Блог",
"sect5":"Контакты",
"telegram":"https://t.me/johndoe",
"facebook":"ttps://facebook.com/john.doe",
"vk":"https://vk.com/john_doe",
"email":"mailto:john_doe@gmail.com"
И отредактируйте файл index.html – теперь он будет включать в себя header.html с помощью include
:
{% extends "base.html" %}
{% block content %}
{% block header %}
{% include "header.html" %}
{% endblock %}
{% endblock %}
Перезагрузите страницу:

Весь код и контент для этого этапа – здесь. Во второй части туториала мы завершим работу над приложением и загрузим статическую копию сайта на GitHub Pages.
Комментарии