Во второй части серии мега-учебника по Flask я собираюсь обсудить, как работать с шаблонами.

Оглавление
  • Глава 1: Привет, мир!

  • Глава 2: Шаблоны (Эта статья)

  • Глава 3: Веб-формы

  • Глава 4: База данных

  • Глава 5: Логины пользователей

  • Глава 6: Страница профиля и аватары

  • Глава 7: Обработка ошибок

  • Глава 8: Подписчики

  • Глава 9: Разбивка на страницы

  • Глава 10: Поддержка по электронной почте

  • Глава 11: Подтяжка лица

  • Глава 12: Даты и время

  • Глава 13: I18n и L10n

  • Глава 14: Ajax

  • Глава 15: Улучшенная структура приложения

  • Глава 16: Полнотекстовый поиск

  • Глава 17: Развертывание в Linux

  • Глава 18: Развертывание на Heroku

  • Глава 19: Развертывание в контейнерах Docker

  • Глава 20: Немного магии JavaScript

  • Глава 21: Уведомления пользователей

  • Глава 22: Фоновые задания

  • Глава 23: Интерфейсы прикладного программирования (API)

После завершения главы 1 у вас должно получиться простое, но функциональное веб-приложение со следующей файловой структурой:

microblog\
  venv\
  app\
    __init__.py
    routes.py
  microblog.py

Для запуска приложения вы устанавливаете FLASK_APP=microblog.py в сеансе терминала (или, что еще лучше, добавляете .flaskenv файл с этой переменной), а затем выполняете команду flask run. При этом запускается веб-сервер с приложением, которое вы можете открыть, введя http://localhost:5000 / URL-адрес в адресной строке вашего веб-браузера.

В этой главе вы продолжите работу над тем же приложением и, в частности, узнаете, как создавать более сложные веб-страницы со сложной структурой и множеством динамических компонентов. Если что-либо о приложении или рабочем процессе разработки пока неясно, пожалуйста, просмотрите главу 1 еще раз, прежде чем продолжить.

Ссылки на GitHub для этой главы: BrowseZipDiff.

Что такое шаблоны?

Я хочу, чтобы на домашней странице моего приложения для ведения микроблогов был заголовок, приветствующий пользователя. На данный момент я собираюсь проигнорировать тот факт, что в приложении еще нет концепции пользователей, поскольку это будет позже. Вместо этого я собираюсь использовать макет пользователя, который я собираюсь реализовать как словарь Python следующим образом:

user = {'username': 'Miguel'}

Создание макетных объектов - полезный метод, который позволяет вам сосредоточиться на одной части приложения, не беспокоясь о других частях системы, которые еще не существуют. Я хочу спроектировать домашнюю страницу моего приложения, и я не хочу, чтобы тот факт, что у меня нет пользовательской системы, отвлекал меня, поэтому я просто создаю пользовательский объект, чтобы я мог продолжать работу.

Функция просмотра в приложении возвращает простую строку. Что я хочу сделать сейчас, так это развернуть возвращенную строку в полноценную HTML-страницу, возможно, что-то вроде этого:

app/routes.py: Возврат полной HTML-страницы из функции просмотра

from app import app
@app.route('/')
@app.route('/index')
def index():
  user = {'username': 'Miguel'}
  return '''
    <html>
      <head>
        <title>Home Page - Microblog</title>
      </head>
      <body>
        <h1>Hello, ''' + user['username'] + '''!</h1>
      </body>
    </html>'''

Если вы не знакомы с HTML, я рекомендую вам прочитать HTML Markup в Википедии для краткого ознакомления.

Обновите функцию просмотра, как показано выше, и снова запустите приложение, чтобы посмотреть, как оно выглядит в вашем браузере.

Надеюсь, вы согласитесь со мной, что решение, использованное выше для доставки HTML в браузер, никуда не годится. Подумайте, насколько сложным станет код в этой функции просмотра, когда вы добавите записи в блогах пользователей, которые будут постоянно меняться. В приложении также будет больше функций просмотра, которые будут связаны с другими URL-адресами, поэтому представьте, что однажды я решу изменить макет этого приложения и мне придется обновлять HTML в каждой функции просмотра. Это явно не тот вариант, который будет масштабироваться по мере роста приложения.

Если бы вы могли отделить логику вашего приложения от макета или представления ваших веб-страниц, тогда все было бы организовано намного лучше, не так ли? Вы даже можете нанять веб-дизайнера для создания потрясающего веб-сайта, пока будете кодировать логику приложения на Python.

Шаблоны помогают добиться такого разделения между презентацией и бизнес-логикой. В Flask шаблоны записываются в виде отдельных файлов и хранятся в папке templates, которая находится внутри пакета приложения. Убедившись, что вы находитесь в каталоге микроблога, создайте каталог, в котором будут храниться шаблоны:

(venv) $ mkdir app/templates

Ниже вы можете увидеть свой первый шаблон, который по функциональности аналогичен HTML-странице, возвращаемой index() функцией просмотра выше. Запишите этот файл в app/templates/index.html:

app/templates/index.html: Шаблон главной страницы

<!doctype html>
<html>
    <head>
        <title>{{ title }} - Microblog</title>
    </head>
    <body>
        <h1>Hello, {{ user.username }}!</h1>
    </body>
</html>

Это стандартная короткая HTML-страница. Единственная интересная вещь на этой странице - это пара заполнителей для динамического содержимого, заключенных в специальные разделы{{ ... }}. Эти заполнители представляют части страницы, которые являются переменными и будут известны только во время выполнения.

Теперь, когда представление страницы было перенесено в HTML-шаблон, функцию просмотра можно упростить:

app/routes.py: Использование функции render_template()

from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
  user = {'username': 'Miguel'}
  return render_template('index.html', title='Home', user=user)

Это выглядит намного лучше, не так ли? Попробуйте эту новую версию приложения, чтобы увидеть, как работает шаблон. После загрузки страницы в вашем браузере вы можете захотеть просмотреть исходный HTML-код и сравнить его с исходным шаблоном.

Операция, которая преобразует шаблон в полноценную HTML-страницу, называется рендеринг. Для рендеринга шаблона мне пришлось импортировать функцию, которая поставляется с фреймворком Flask под названием render_template(). Эта функция принимает имя файла шаблона и переменный список аргументов шаблона и возвращает тот же шаблон, но со всеми заполнителями в нем, замененными фактическими значениями.

Функция render_template() вызывает движок шаблонов Jinja, который поставляется в комплекте с фреймворком Flask Framework. Jinja заменяет блоки {{ ... }} соответствующими значениями, заданными аргументами, приведенными в вызове render_template().

Условные операторы

Вы видели, как Jinja заменяет заполнители фактическими значениями во время рендеринга, но это лишь одна из многих мощных операций, которые Jinja поддерживает в файлах шаблонов. Например, шаблоны также поддерживают управляющие операторы, заданные внутри блоков {% ... %}. Следующая версия index.html шаблона добавляет условный оператор:

app/templates/index.html: Условный оператор в шаблоне

<!doctype html>
<html>
  <head>
    {% if title %}
      <title>{{ title }} - Microblog</title>
    {% else %}
      <title>Welcome to Microblog!</title>
    {% endif %}
  </head>
  <body>
    <h1>Hello, {{ user.username }}!</h1>
  </body>
</html>

Теперь шаблон стал немного умнее. Если функция view забудет передать значение для переменной-заполнителя title, то вместо отображения пустого заголовка шаблон предоставит заголовок по умолчанию. Вы можете попробовать, как работает это условие, удалив аргумент title в вызове функции просмотра render_template().

Циклы

Вошедший в систему пользователь, вероятно, захочет видеть последние сообщения от подключенных пользователей на домашней странице, поэтому сейчас я собираюсь расширить приложение для поддержки этого.

Еще раз, я собираюсь полагаться на удобный трюк с макетом объекта, чтобы создать несколько пользователей и несколько сообщений для показа:

app/routes.py: Функция просмотра поддельных сообщений

from flask import render_template
from app import app
@app.route('/')
@app.route('/index')
def index():
  user = {'username': 'Miguel'}
  posts = [
    {
      'author': {'username': 'John'},
      'body': 'Beautiful day in Portland!'
    },
    {
      'author': {'username': 'Susan'},
      'body': 'The Avengers movie was so cool!'
    }
  ]
  return render_template('index.html', title='Home', user=user, posts=posts)

Для представления сообщений пользователей я использую список, где каждый элемент представляет собой словарь с полями author и body. Когда я приступлю к реализации пользователей и записей в блоге по-настоящему, я постараюсь максимально сохранить названия этих полей, чтобы вся работа, которую я выполняю по разработке и тестированию шаблона домашней страницы с использованием этих поддельных объектов, оставалась актуальной, когда я представлю реальных пользователей и записи.

На стороне шаблона я должен решить новую проблему. Список записей может содержать любое количество элементов, функция просмотра решает, сколько записей будет представлено на странице. Шаблон не может делать никаких предположений о том, сколько в нем записей, поэтому он должен быть подготовлен к отображению в общем виде столько записей, сколько отправляет представление.

Для решения задач такого типа Jinja предлагает for структуру управления:

app/templates/index.html: цикл for в шаблоне

<!doctype html>
<html>
  <head>
    {% if title %}
      <title>{{ title }} - Microblog</title>
    {% else %}
      <title>Welcome to Microblog</title>
    {% endif %}
  </head>
  <body>
    <h1>Hi, {{ user.username }}!</h1>
    {% for post in posts %}
      <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
    {% endfor %}
  </body>
</html>

Просто, не так ли? Попробуйте эту новую версию приложения и обязательно поиграйте с добавлением дополнительного контента в список записей, чтобы увидеть, как шаблон адаптируется и всегда отображает все записи, отправляемые функцией просмотра.

Наследование шаблонов

Большинство веб-приложений в наши дни имеют панель навигации в верхней части страницы с несколькими часто используемыми ссылками, такими как ссылка для редактирования вашего профиля, входа в систему, выхода из системы и т.д. Я могу легко добавить панель навигации в шаблон index.html, добавив еще немного HTML, но по мере роста приложения мне понадобится та же панель навигации на других страницах. На самом деле я не хочу поддерживать несколько копий панели навигации во многих HTML-шаблонах, рекомендуется не повторяться, если это возможно.

В Jinja есть функция наследования шаблонов, которая специально решает эту проблему. По сути, что вы можете сделать, так это переместить части макета страницы, которые являются общими для всех шаблонов, в базовый шаблон, на основе которого создаются все остальные шаблоны.

Итак, что я собираюсь сделать сейчас, так это определить базовый шаблон с именем base.html, который включает в себя простую панель навигации, а также логику заголовков, которую я реализовал ранее. Вам нужно записать следующий шаблон в файл app/templates/base.html:

app/templates/base.html: Базовый шаблон с панелью навигации

<!doctype html>
<html>
    <head>
      {% if title %}
      <title>{{ title }} - Microblog</title>
      {% else %}
      <title>Welcome to Microblog</title>
      {% endif %}
    </head>
    <body>
        <div>Microblog: <a href="/index">Home</a></div>
        <hr>
        {% block content %}{% endblock %}
    </body>
</html>

В этом шаблоне я использовал оператор управления block для определения места, куда могут вставляться производные шаблоны. Блокам присваиваются уникальные имена, на которые могут ссылаться производные шаблоны при предоставлении своего содержимого.

Теперь, когда базовый шаблон установлен, я могу упростить файл index.html, сделав его наследуемым от base.html:

app/templates/index.html: Наследование от базового шаблона

{% extends "base.html" %}
{% block content %}
  <h1>Hi, {{ user.username }}!</h1>
  {% for post in posts %}
    <div><p>{{ post.author.username }} says: <b>{{ post.body }}</b></p></div>
  {% endfor %}
{% endblock %}

Поскольку шаблон base.html теперь будет заботиться об общей структуре страницы, я удалил все эти элементы из index.html и оставил только часть содержимого. Оператор extends устанавливает связь наследования между двумя шаблонами, так что Jinja знает, что когда его просят выполнить рендеринг, index.html ему нужно встроить ее внутрь base.html. Два шаблона имеют совпадающие инструкции block с именем content, и именно так Jinja узнает, как объединить два шаблона в один. Теперь, если мне нужно создать дополнительные страницы для приложения, я могу создать их как производные шаблоны из одного и того же шаблона base.html, чтобы все страницы приложения имели одинаковый внешний вид без дублирования кода.

Комментарии (0)