素人がWebアプリケーション制作の過程を書いてみる（Flask テンプレートエンジン）

今回はFlaskのテンプレートエンジンを使ってみたいと思います。FlaskではJinja2のテンプレートエンジンを使用しています。Jinja2でのテンプレートとは"{%%}"や"{{}}"などの記法を用いて作成され、それらを活用して、文字列の埋め込み、if分岐などを実現しているものです。FlaskではサーバでのHTMLのレンダリングに使用されています。

## Flaskへの実装
まずは前回のindex.htmlおよびapp.pyを変更します。

```python:app.py(抜粋)
from flask import Flask, render_template, request  # request

app = Flask("__name__")
app.jinja_env.line_statement_prefix = '#'      # line statementsの有効化

database = []

@app.route("/")
def root():

    data1 = "hoge1"
    data2 = {"id": "1", "name": "test", "data": "hoge2"}
    data3 = ["hoge3", "huga3"]
    return render_template("index.html", data1=data1, data2=data2, data3=data3)   # テンプレートのレンダリング(テンプレートの実行)を実施。また引数にapp.pyの変数とhtml側の変数のバインド関係を記載

@app.route("/api/v1/<string:userId>", methods=['GET','POST','PUT','DELETE'])
def api_get(userId):

#..省略..#

if __name__ == "__main__":
    app.run(debug=True)
```


## テンプレートの記載方法
### 記法
テンプレートの記法には主に下記のようなものがあります。また公式HPの[Flask template](http://flask.pocoo.org/docs/1.0/templating/)にも記載があります。

| 1 | 2 |
|:-:|:--|
| {% %}  |  変数 |
|  {{ }} |  式 |
| {# #}  |  コメント |
|  # | 式（1行すべて式の場合。アプリケーション<br>（本ページの場合app.py）側で<br>app.jinja_env.line_statement_prefix = '#'の設定が必要  |

### 文字列の埋め込み
変数による文字列の埋め込みは下記の通り記載できます。変数には配列や辞書型も記載できます。

```html:index.html
<body>
    <div class="container">
        <div class="header"> Web page</div>
                <p>{{data1}}</p>　　　　　# 変数
                <p>{{data2.data}}</p>　　#　辞書型
                <p>{{data3[0]}}</p>　　　#　配列
        </div>
    </div>
</body>
```
実行結果は下記のとおりです。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/4e658406-e333-628b-2700-ffc95c7a491f.png)


### ステートメント
Jinja2の記法を使ってif文やfor分を作ることもできます。またfor文の中で新たに変数を定義し、それらを変数として使うこともできます。


```html:index.html
<body>
    <div class="container">
        <div class="header"> Web page</div>
            <!-- if文の追加 -->
            {% if data1=="hoge1" %}
                <p>{{data1}}</p>　　
                <p>{{data2.data}}</p>　　
            {% endif %}　

            <!-- for文の追加 -->
            {% for i in data3 %}
                <p>{{i}}</p>    <!-- for文の中の新規変数を利用可能 -->
            {% endfor %}

        </div>
    </div>
</body>
```
下記が実行結果になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/4b42acf4-a4ac-81bf-db9f-49531b236976.png)



また{% set %}で変数を設定したり、{% macro %}や{% call %}を使って、マクロを定義したり、それを呼び出したりすることができます。

```html:index.html
<body>
    <div class="container">
        <div class="header"> Web page</div>
            {% if data1=="hoge1" %}
                <p>{{data1}}</p>　
            {% endif %}　

            <!-- setによる変数への格納 -->
            {% set data1="hoge1-2" %}

            <!-- line statementsによる記載（app.jinja_env.line_statement_prefix = '#'の設定が必要） -->         
            # if data1=="hoge1-2":
                <p>{{data2.data}}</p>　　
            # endif
            
            <!-- macroによるマクロ追加 -->
            {% macro show_data(data_list) %}
                {% for i in data_list %}
                    <p>{{i}}</p>
                {% endfor %}
            {% endmacro %}
            <!-- macroの実行 -->         
            <p>{{show_data(data3)}}</p>

            <!-- callを使うことにより、macroを再利用しながら拡張することが可能 -->
            {% macro show_data2(data_list) %}
                {% for i in data_list %}
                    <p>{{caller()}}{{i}}</p>　      <!-- call呼び出し時にcallの中身をcaller()にバインド -->
                {% endfor %}
            {% endmacro %}
            <!-- callでマクロを実行 -->
            {% call show_data2(data3) %}
                call:
            {% endcall%}
        </div>
    </div>
</body>
```

実行結果は下記の通りになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/246fbe14-c7ca-0e41-3aa8-4b127768ea98.png)


{% block %}や{% include %}でほかのhtmlから情報を持ってくることができます。

```html:base.html
title
<!-- blockでバインドするブロック箇所を指定 -->
{% block body %}{% endblock %}
```

```html:index.html
<!-- extendsにて親のhtmlを指定 -->
{% extends 'base.html' %}
<!-- blockにて親のhtmlにバインドするブロックを記載 -->
{% block body%}
<body>
    <div class="container">
        <div class="header"> Web page</div>
            <!-- includeにて他のhtmlを呼び出し -->
            {% include 'include.html'%}
        </div>
    </div>
</body>
{% endblock %}
```

```html:include.html
include include.html
```

下記実行結果です。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/f301b76a-2ddb-fb5e-635c-f0179fb20095.png)


### フィルタ

Jinjaにはフィルタ機能というものが備わっていて、データに対して変換をかけることができます。フィルタ機能は"data|safe"のように、|(パイプ)を使って記載することが可能です。Buildin-Filterの一覧は[こちら](http://jinja.pocoo.org/docs/2.10/templates/#builtin-filters)にあります。

```html:index.html
<body>
    <div class="container">
        <div class="header"> Web page</div>
                <p>{{data1}}</p>　
                <!-- length Filterを追加し、テキストの長さを出力 -->
                <p>{{ data1|length }}</p>　
        </div>
    </div>
</body>
```
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/882a8d5c-5779-6de3-cd2c-97c690b44d38.png)


### エスケープ処理

Flaskでは[XSS(Cross Site Scripting)](https://www.owasp.org/index.php/Cross-site_Scripting_(XSS))対策として、&,<,>,",'をエスケープ処理(無害な文字列への変換処理：ex "<" → "&lt" など)しています。これらを無効にする場合は{% autoescape false %}を使用します。

```html:index.html
<body>
    <div class="container">
        <div class="header"> Web page</div>
                <p>{{data1}}</p>　
                {% set data1="<script>alert('XSS');</script>" %}
                {% autoescape false %}
                <!-- この中で出力されるものについてはエスケープ処理されないため、タグなどが有効になります -->
                <p>{{data1}}</p>　
                {% endautoescape %}
        </div>
    </div>
</body>
```

実行するとscriptタグの中のスクリプトが実行されます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/63931250-15d6-a8b8-1efb-d306cee60eb4.png)


### Flaskオブジェクトの表示

Flaskの固有のオブジェクトについてもテンプレートエンジンを使って表示させることが可能です。

```html:index.html
<body>
    <table>
        <tr>
            <td>config</td>
            <td>{{config}}</td>   # 現在のflaskのコンフィグオブジェクトを表示
        </tr>
        <tr>
            <td>session</td>
            <td>{{session}}</td>   # 現在のflaskのセッションオブジェクトを表示
        </tr>
    </table>
</body>
```

ブラウザでアクセスすると結果は下記のようになります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115330/4be1c139-c30c-df61-9d3e-e19f3144bdf8.png)



以上、次回はデータベース周りとの連携について記載したいと思います。