【Python Flask】初心者プログラマーのWebアプリ#2 HTMLテンプレート表示

第一回目は簡単な文字を表示をしただけでした。
今回はHTMLテンプレートを使って柔軟に規模を大きく綺麗に作っていきましょう。

![flask.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/cc80bb97-0728-0bf0-51f2-540cb0f46641.jpeg)

:::note
Flaskとで使われているテンプレートエンジンはDjangoも同じなので記法も当然同じ。
:::

# :pushpin: Pythonで作るFlaskアプリ記事一覧

|  | 内容|
|:----:|:----:|
|**part1**|[簡単なページ作成](https://qiita.com/Bashi50/items/30065e8f54f7e8038323)|
|**part2**|**HTMLテンプレート表示　← ココ**|
|**part3**|["画像" "CSS" "Javascript"実装](https://qiita.com/Bashi50/items/f4c2eb42b9cda9b91295) |
|**part4**|[フォーム送信](https://qiita.com/Bashi50/items/457d3b8a2328b2b1d012)|
|**part5**|[データベースの値取得・更新](https://qiita.com/Bashi50/items/e3459ca2a4661ce5dac6)|

# HTMLテンプレートでWEBページを作る
WEBアプリを作るための最初のステップとしてHTMLから始めた人はも多いと思います。
FlaskでもHTMLテンプレートというのを使っていて、サーバーに保存されているデータや、データを処理してページに埋め込むことでページを作る（動的に作るという）ことができます。

## ソースコード
この章のコードは以下です。確認やコピペでどうぞ

https://github.com/kohishibashi/flask/tree/part2

## 1.1. HTMLファイルを作成
`templates`と言うフォルダを作ってください。Flaskはtemplatesと言うディレクトリを参照するように作られていますのでフォルダ名を間違わないように。
それに加えて、今回はその中に`testapp`と言うフォルダを作成します。※こちらは名前自由です。

その中に表示用のHTMLとして`index.html`を作って記述していきます。

```
$ mkdir -p templates/testapp
$ touch ./templates/testapp/index.html
```

VSCode使ってるなら`! + tab`である程度作れる機能があります。よければ試すといいかも。


```/Users/dev/flask/testapp/templates/testapp/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テストWebアプリ</title>
</head>
<body>
    <h1>index.html表示してます</h1>
</body>
</html>
```

Python要素ゼロのただのHTMLです。

## 1.2. viewsに記述を追加してhtmlテンプレートでの表示を作る
[前回](https://qiita.com/Bashi50/items/30065e8f54f7e8038323)はPythonのreturnのところに文字列を書いて、そのまま表示しているだけでした。
これを改造して先ほど作成したHTMLを表示させてみます。以下の該当箇所を追記します。

```/Users/dev/flask/testapp/views.py
from flask import render_template  # 追加
from testapp import app


@app.route('/')
def index():
    return render_template('testapp/index.html')

```
![スクリーンショット 2021-11-28 19.24.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/1c7ec524-9cbd-c250-129f-2771bcd4382b.png)

表示できました。
ただ、このままだとHTMLそのまま表示させるのと何も変わりませんのでPython使う意味ありませんね。

と言うことで、次はPython内のデータを埋め込んでみましょう。

## 1.3. テンプレートにデータ埋め込み
FlaskではJinja2と言うテンプレートエンジンをデフォルトで使えます。簡単に言うとPythonで簡単な処理をしてHTM L作るための機能が備わってます。

今回作成した`index.html`はHTMLファイルですが、これに特殊な記述をすることでデータを埋め込みができます。
変更箇所付近だけ書きます。

```/Users/dev/flask/testapp/templates/testapp/index.html
...
<body>
    <h1>index.html表示してます</h1>
    {{ insert_something }}
</body>
```
変更箇所は{{ insert_something }}です。
次に`insert_something`というデータをviewに用意しましょう。

```/Users/dev/flask/testapp/views.py
from flask import render_template
from testapp import app

@app.route('/')
def index():
    data = 'views.pyのinsert_something部分です。'
    return render_template('testapp/index.html', insert_something=data)
```

第二引数が増えてますね。`insert_something`はテンプレートの`{{ insert_something }}`で使うためにこの名前にしてます。

## 1.4. 複数データを埋め込む
データを埋め込むときに、数個くらいなら問題ないですが、数が増えると複数のデータをまとめてテンプレートに送れる方が便利です。なので複数のデータをテンプレートに送って埋め込む方法を見てみます。

```/Users/dev/flask/testapp/views.py
@app.route('/')
def index():
    my_dict = {
        'insert_something1': 'views.pyのinsert_something1部分です。',
        'insert_something2': 'views.pyのinsert_something2部分です。',
    }
    return render_template('testapp/index.html', my_dict=my_dict)
```

```/Users/dev/flask/testapp/templates/testapp/index.html
.
.
<body>
    <h1>index.html表示してます</h1>
    <p>{{ my_dict.insert_something1 }}</p>
    <p>{{ my_dict.insert_something2 }}</p>
</body>
```

![スクリーンショット 2021-11-28 19.41.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/77d9835e-81c9-ef16-4fc5-4bdd66329764.png)

## 1.5. テンプレートで `for` によりリストデータを表示させる
同じようなデータをリストでまとめて表示させることもよくありますので使い方を解説します。

```/Users/dev/flask/testapp/views.py
@app.route('/')
def index():
    my_dict = {
        'insert_something1': 'views.pyのinsert_something1部分です。',
        'insert_something2': 'views.pyのinsert_something2部分です。',
        'test_titles': ['title1', 'title2', 'title3']
    }
    return render_template('testapp/index.html', my_dict=my_dict)
```
`test_titles`と言う表示したいリストのデータを追加しました。

```/Users/dev/flask/testapp/templates/testapp/index.html
<body>
    <h1>index.html表示してます</h1>
    <p>{{ my_dict.insert_something1 }}</p>
    <p>{{ my_dict.insert_something2 }}</p>

    <h2>タイトル表示</h2>
    <ul>
      {% for title in my_dict.test_titles %}
        <li>{{ title }}</li>
      {% endfor %}
    </ul>
</body>
```

`{% for ....... %}`というので囲んで`{% endfor %}`で閉じるのを忘れずに。
`{{ }}`もあって紛らわしいですが

- `{{ }}`← データの埋め込み
- `{% %}`← 処理を記述。forやifなど書くとき

という違いあります。似てますが全然違うので注意。

表示した結果↓
![スクリーンショット 2021-11-28 19.53.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/f9240c0c-ada8-fb75-d780-9a7faf55df12.png)

## 1.6. テンプレートで `if` を使い、表示させるデータに条件を付ける
条件によって表示させたり、消したい表示というのがあると思います。そんな場合には`{% if 条件 %}`を使います。

```/Users/dev/flask/testapp/templates/testapp/index.html
<body>
    <h1>index.html表示してます</h1>
    <p>{{ my_dict.insert_something1 }}</p>
    <p>{{ my_dict.insert_something2 }}</p>

    <h2>タイトル表示</h2>
    <ul>
      {% for title in my_dict.test_titles %}
        {% if title == 'title2'%}
            <li>{{ title }}</li>
        {% endif %}
      {% endfor %}
    </ul>
</body>
```

`title2`だけを表示させる条件をつけました。表示が消えました。
![スクリーンショット 2021-11-28 19.57.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/2014b0e6-a40a-a56d-fea1-e684794347d4.png)

## 1.7. elif, elseも使える
Pythonの条件式と同じく`elif`, `else`も使えます。

```/Users/dev/flask/testapp/templates/testapp/index.html
<body>
    <h1>index.html表示してます</h1>
    <p>{{ my_dict.insert_something1 }}</p>
    <p>{{ my_dict.insert_something2 }}</p>

    <h2>タイトル表示</h2>
    <ul>
      {% for title in my_dict.test_titles %}
        {% if title == 'title2' %}
            <li>{{ title }}</li>
        {% elif title == 'title3' %}
            <li>見せられないよ</li>
        {% else %}
            <li>xxxx</li>
        {% endif %}
      {% endfor %}
    </ul>
</body>
```


![スクリーンショット 2021-11-28 19.59.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/0d311c6a-55a9-33ef-f889-feb8ba18510b.png)

- `title2`はifの条件なので普通に表示
- `title3`はelifの条件なので「見せられないよ」
- `title1`はif, elifの条件に当てはまらないのでelseになり「xxxx」

が、表示されています。

# 2. 共通部分を切り出す
今のところテンプレートが一つだけなので良さがわからないですが、今後テンプレートが増えるとほとんど同じなのに長々と記述を書かないといけないことになります。

こんな時のために全く同じ部分とか、ほんの少しだけ違うところとかを他の共通のテンプレートに切り出して複数のテンプレートで共通で使うことができる機能があります。

## 2.1. ベーステンプレート作成（共通部分を作る）
例えば以下のようなテンプレートを作成して表示さえてみます。

```/Users/dev/flask/testapp/templates/testapp/index2.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テストWebアプリ2</title>
</head>
<body>
    <h1>index2.html表示してます</h1>
</body>
</html>
```

以前テストで作った記述を少し改造します。

```/Users/dev/flask/testapp/views.py
.
.

@app.route('/test')
def other1():
    return render_template('testapp/index2.html')
```

表示した結果がこれ
![スクリーンショット 2021-11-28 21.18.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/789d0acb-00b3-948a-70bb-0832da041c24.png)

ヘッダー部分とか全く同じで無駄ですし、今後何かを読み込んだり、変更をしたときに何個も何個も同じ修正をしないといけなくなります。
これは面倒ですよね。ということで共通部分はベーステンプレートを作成して共通化させましょう。


## 2.2. 共通部分を作成する
`layout.html`を作成して、(ディレクトリ注意。templatesの直下です。)共通して使う部分を`layout.html`に移しましょう。

```/Users/dev/flask/testapp/templates/layout.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テストWebアプリ</title>
</head>
<body>

</body>
</html>
```

共通しているのは上記の部分。それぞれのテンプレートでこの共通部分を使って、あとは違うところだけ記述するだけでOKにします。

そのために、

```/Users/dev/flask/testapp/templates/layout.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>テストWebアプリ</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
```

`{% block content %}{% endblock %}`だけ追加しました。
このblockの箇所にそれぞれのテンプレートで違う部分が入ることになります。

`index.html`を以下のように記述。
`{% extends "layout.html" %}`が共通のlayout.htmlを使う。
`{% block content %}`〜`{% endblock %}`がlayout.htmlに記述されている

`{% block content %}{% endblock %}`←この部分に入る！
ということになる。

```/Users/dev/flask/testapp/templates/testapp/index.html
{% extends "layout.html" %}

{% block content %}
<h1>index.html表示してます</h1>
<p>{{ my_dict.insert_something1 }}</p>
<p>{{ my_dict.insert_something2 }}</p>

<h2>タイトル表示</h2>
<ul>
    {% for title in my_dict.test_titles %}
    {% if title == 'title2' %}
        <li>{{ title }}</li>
    {% elif title == 'title3' %}
        <li>見せられないよ</li>
    {% else %}
        <li>xxxx</li>
    {% endif %}
    {% endfor %}
</ul>
{% endblock %}
```

同じようにindex2.htmlも変更してみます。

```/Users/dev/flask/testapp/templates/testapp/index2.html
{% extends "layout.html" %}

{% block content %}
<h1>index2.html表示してます</h1>
{% endblock %}
```

超短くなりましたね。

あとは

>http://127.0.0.1:5000
http://127.0.0.1:5000/test

にアクセスしてエラー出なかったらOK。表示は変わらないです。

# 3. bootstrapで見た目綺麗にする
bootstrap５を使って見た目を整えようと思います。
以下を参考にテンプレートにデザインを追加していきました。

> https://getbootstrap.com/docs/5.0/getting-started/introduction/#starter-template

## 3.1. 準備

`header`にCSSの読み込みの`<link>`, `</body>`タグの終わり直前にJavaScriptの読み込み(scriptタグ)を追加します。
これはどのページでも使う共通のデザインにしたいので`layout.html`で読み込むようにしようと思います。

```/Users/dev/flask/testapp/templates/testapp/layout.html
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <!-- ↓追加 -->
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>テストWebアプリ</title>
</head>
<body>
{% block content %}{% endblock %}
<!-- ↓追加 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
```

リロードするとフォントや余白が少し変わったけど、この段階ではあまり違いがわからない。
![スクリーンショット 2021-11-28 21.02.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/e4f44df7-424f-4717-661c-eed23ea8032d.png)

## 3.2. bootstrapのナビバーをつける
上に表示されるナビバーを作ります。

> https://getbootstrap.com/docs/5.0/components/navbar/

サンプルをコピペして色だけ少し変えました。



```/Users/dev/flask/testapp/templates/layout.html
<body>
    <nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">Navbar</a>
          <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarNavAltMarkup">
            <div class="navbar-nav">
              <a class="nav-link active" aria-current="page" href="#">Home</a>
              <a class="nav-link" href="#">Features</a>
              <a class="nav-link" href="#">Pricing</a>
              <a class="nav-link disabled" href="#" tabindex="-1" aria-disabled="true">Disabled</a>
            </div>
          </div>
        </div>
      </nav>
    {% block content %}
    {% endblock %}
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
</body>
```

![スクリーンショット 2021-11-28 21.53.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/47a994d7-5f27-3080-44f9-decdc4dfbc25.png)

navの中のリンクや文字などがテキトーですがひとまず放置します。

次に余白が欲しいのでcontanerで囲みます。

```/Users/dev/flask/testapp/templates/testapp/index.html
{% block content %}
<div class="container">
    <h1>index.html表示してます</h1>
    <p>{{ my_dict.insert_something1 }}</p>
    <p>{{ my_dict.insert_something2 }}</p>
    
    ...
</div>
{% endblock %}
```

```/Users/dev/flask/testapp/templates/testapp/index2.html
{% extends "layout.html" %}

{% block content %}
<div class="container">
    <h1>index2.html表示してます</h1>
</div>
{% endblock %}
```

![スクリーンショット 2021-11-28 22.00.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/260345/b23ae76d-c60e-45fa-a0f6-86aaf3bdcff6.png)

はい。少しだけまともになりました。

# :pushpin: Pythonで作るFlaskアプリ記事一覧
|  | 内容|
|:----:|:----:|
|**part1**|[簡単なページ作成](https://qiita.com/Bashi50/items/30065e8f54f7e8038323)|
|**part2**|**HTMLテンプレート表示　← ココ**|
|**part3**|["画像" "CSS" "Javascript"実装](https://qiita.com/Bashi50/items/f4c2eb42b9cda9b91295) |
|**part4**|[フォーム送信](https://qiita.com/Bashi50/items/457d3b8a2328b2b1d012)|
|**part5**|[データベースの値取得・更新](https://qiita.com/Bashi50/items/e3459ca2a4661ce5dac6)|

## ソースコード参考
この章のコードは以下です。確認やコピペでどうぞ

https://github.com/kohishibashi/flask/tree/part2