Статьи

Шаблонирование с Jinja2 в Flask: основы

Jinja2 — это шаблонизатор, написанный на чистом Python. Он обеспечивает Django- вдохновленный не-XML-синтаксис, но поддерживает встроенные выражения и необязательную изолированную среду. Он небольшой, но быстрый, не считая простого в использовании автономного шаблонизатора. Flask — это основанный на Python микро-фреймворк, который позволяет быстро и эффективно создавать свои веб-приложения

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

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

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

1
pip install https://github.com/mitsuhiko/flask/tarball/master

В Flask мы можем написать полноценное веб-приложение без необходимости использования сторонних шаблонизаторов. Давайте посмотрим на небольшое приложение Hello World ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask
 
app = Flask(__name__)
 
@app.route(‘/’)
@app.route(‘/hello’)
@app.route(‘/hello/<user>’)
def hello_world(user=None):
    user = user or ‘Shalabh’
    return »’
    <html>
        <head>
            <title>Templating in Flask</title>
        </head>
        <body>
            <h1>Hello %s!</h1>
            <p>Welcome to the world of Flask!</p>
        </body>
    </html>»’ % user
        
if __name__ == ‘__main__’:
    app.run()

Очевидно, что описанный выше шаблон написания приложения неосуществим в случае реального веб-приложения, в котором HTML, CSS и JS-код составляют тысячи строк кода. Здесь использование шаблонов спасает нас, потому что мы можем структурировать наш код представления, сохранив наши шаблоны отдельно. Flask по умолчанию поддерживает Jinja2, но любой другой движок шаблонов также может быть использован как подходящий.

По умолчанию Flask ожидает, что шаблоны будут помещены в папку с именем templates на корневом уровне приложения. Затем Flask автоматически считывает содержимое, делая эту папку доступной для использования с render_template() . Я продемонстрирую то же самое путем реструктуризации простого приложения Hello World показанного выше.

Структура приложения будет такой, как показано ниже.

1
2
3
4
flask_app/
   my_app.py
   templates/
       — index.html
01
02
03
04
05
06
07
08
09
10
from flask import Flask, render_template, request
 
app = Flask(__name__)
 
@app.route(‘/’)
@app.route(‘/hello’)
@app.route(‘/hello/<user>’)
def hello_world(user=None):
    user = user or ‘Shalabh’
    return render_template(‘index.html’, user=user)
1
2
3
4
5
6
7
8
9
<html>
  <head>
    <title>Templating in Flask</title>
  </head>
  <body>
    <h1>Hello {{ user }}!</h1>
    <p>Welcome to the world of Flask!</p>
  </body>
</html>

Чтобы запустить приложение, просто выполните следующую команду в командной строке:

1
flask —app=my_app run

Откройте http://127.0.0.1:5000/ в браузере, чтобы увидеть приложение в действии. Результат будет таким же в случае http://127.0.0.1:5000/hello .

Целевая страница по умолчанию

Попробуйте открыть URL с вашим именем в качестве последней части. Так что если вас зовут Джон, URL будет http://127.0.0.1:5000/hello/John . Теперь страница будет выглядеть так:

Доступ к целевой странице с произвольным именем

Довольно просто, что в методе hello_world последняя часть URL-адреса после hello извлекается из запроса и передается в контекст визуализируемого шаблона с помощью render_template() . Затем это значение анализируется из контекста шаблона с помощью заполнителя Jinja2 {{ user }} . Этот заполнитель оценивает все выражения, помещенные в него, в зависимости от контекста шаблона.

Обычно любое веб-приложение будет иметь несколько веб-страниц, которые будут отличаться друг от друга. Блоки кода, такие как верхние и нижние колонтитулы, будут одинаковыми практически на всех страницах сайта. Кроме того, меню также остается прежним. Фактически, обычно меняется только центральный блок контейнера, а остальное обычно остается прежним. Для этого Jinja2 предоставляет отличный способ наследования среди шаблонов. Хорошей практикой является наличие базового шаблона, в котором мы можем структурировать базовый макет сайта, а также верхний и нижний колонтитулы.

Я создам небольшое приложение для демонстрации списка продуктов в разных категориях. Для стилизации я буду использовать каркас Bootstrap, чтобы придать базовый дизайн шаблонам. Структура приложения теперь такая, как показано ниже.

01
02
03
04
05
06
07
08
09
10
11
12
flask_app/
   my_app.py
   templates/
       — base.html
       — home.html
       — product.html
   static/
       css/
           — bootstrap.min.css
           — main.css
       js/
           — bootstrap.min.js

Здесь static/css/bootstrap.min.css и static/js/bootstrap.min.js можно загрузить с упомянутого выше сайта начальной загрузки. Остальной код приложения показан ниже.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from flask import Flask, render_template, abort
 
app = Flask(__name__)
 
PRODUCTS = {
    ‘iphone’: {
        ‘name’: ‘iPhone 5S’,
        ‘category’: ‘Phones’,
        ‘price’: 699,
    },
    ‘galaxy’: {
        ‘name’: ‘Samsung Galaxy 5’,
        ‘category’: ‘Phones’,
        ‘price’: 649,
    },
    ‘ipad-air’: {
        ‘name’: ‘iPad Air’,
        ‘category’: ‘Tablets’,
        ‘price’: 649,
    },
    ‘ipad-mini’: {
        ‘name’: ‘iPad Mini’,
        ‘category’: ‘Tablets’,
        ‘price’: 549
    }
}
 
@app.route(‘/’)
@app.route(‘/home’)
def home():
    return render_template(‘home.html’, products=PRODUCTS)
 
@app.route(‘/product/<key>’)
def product(key):
    product = PRODUCTS.get(key)
    if not product:
        abort(404)
    return render_template(‘product.html’, product=product)

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

1
2
3
4
5
6
7
body {
  padding-top: 50px;
}
.top-pad {
  padding: 40px 15px;
  text-align: center;
}

Этот файл содержит немного нестандартного CSS, который я добавил, чтобы сделать шаблоны более четкими. Давайте посмотрим на шаблоны сейчас.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!DOCTYPE html>
<html lang=»en»>
  <head>
    <meta charset=»utf-8″>
    <meta http-equiv=»X-UA-Compatible» content=»IE=edge»>
    <meta name=»viewport» content=»width=device-width, initial-scale=1″>
    <title>Jinja2 Tutorial — Tutsplus</title>
    <link href=»{{ url_for(‘static’, filename=’css/bootstrap.min.css’) }}» rel=»stylesheet»>
    <link href=»{{ url_for(‘static’, filename=’css/main.css’) }}» rel=»stylesheet»>
  </head>
  <body>
    <div class=»navbar navbar-inverse navbar-fixed-top» role=»navigation»>
      <div class=»container»>
        <div class=»navbar-header»>
          <a class=»navbar-brand» href=»{{ url_for(‘home’)}}»>Tutsplus — Jinja2 Tutorial</a>
        </div>
      </div>
    </div>
    <div class=»container»>
      {% block container %}{% endblock %}
    </div>
    <!— jQuery (necessary for Bootstrap’s JavaScript plugins) —>
    <script src=»https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js»></script>
    <script src=»{{ url_for(‘static’, filename=’js/bootstrap.min.js’) }}»></script>
  </body>
</html>

Обратите внимание на использование url_for() для создания URL-адресов для статических файлов и других ссылок. Это очень удобный инструмент, который предоставляется Flask. Подробнее об этом читайте в документации . Еще один важный момент, на который следует обратить внимание, это использование {% block container %}{% endblock %} , который является очень важным компонентом Jinja2, работающим над тем, чтобы сделать шаблоны модульными и наследуемыми. Следующая пара файлов сделает это более понятным.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
{% extends ‘base.html’ %}
 
{% block container %}
  <div class=»top-pad»>
    {% for id, product in products.iteritems() %}
      <div class=»well»>
        <h2>
          <a href=»{{ url_for(‘product’, key=id) }}»>{{product[‘name’] }}</a>
          <small>$ {{ product[‘price’] }}</small>
        </h2>
      </div>
    {% endfor %}
  </div>
{% endblock %}

Посмотрите, как этот шаблон расширяет base.html и предоставляет содержимое {% block container %} . {% for %} ведет себя так же, как обычный цикл for на любом языке, который мы здесь используем для создания списка продуктов.

01
02
03
04
05
06
07
08
09
10
{% extends ‘home.html’ %}
 
{% block container %}
  <div class=»top-pad»>
    <h1>{{ product[‘name’] }}
      <small>{{ product[‘category’] }}</small>
    </h1>
    <h3>$ {{ product[‘price’] }}</h3>
  </div>
{% endblock %}

Приведенный выше шаблон реализует отдельную страницу продукта.

Теперь запустите приложение, выполнив следующую команду.

1
flask —app=my_app run

Запущенное приложение будет выглядеть так, как показано на скриншоте ниже. Просто откройте http://127.0.0.1:5000/home в браузере.

Приложение, работающее с платформой Bootstrap

Нажмите на любой из продуктов, чтобы увидеть отдельную страницу продукта.

Индивидуальная страница продукта

В этом уроке мы увидели, как разложить структуру шаблона в приложении на основе Flask с использованием Jinja2. Мы также увидели, как можно использовать блоки, чтобы использовать наследование в шаблонах.

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