この記事では、Flaskを使ったWebアプリケーション開発において、ヘッダー、フッターなど、複数のページで共通の部品を表示するときに効率よく開発する方法を紹介しています。
目次
記事を理解するための準備
Flaskの作業用フォルダ名はflask
で、venv
でPython3の仮想環境を作成し、Flaskをインストールしています。
また、 ターミナル(コマンドプロンプト)のカレントディレクトリは、 flaskフォルダに存在します。
┣ app.py
︙
┗ templates/
┗ index.html
app.py
Webアプリケーションの核であるapp.py
は、flask.render_template
メソッドでindex.html
を読み込むシンプルなプログラムになっています。
# -*- coding: utf-8 -*-
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html', \
title = 'index.html')
if __name__ == '__main__':
app.run()
index.html
クライアントサイドに表示されるindex.html
は、わかりやすさを考慮してBootstrapでデザインしているページになっています。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{ title }}</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1 class="mt-4">テンプレート継承({{ title }})</h1>
<!-- ヘッダー -->
<header class="my-3">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link" href="/prev/">prev.html</a></li>
<li class="nav-item"><a class="nav-link active" href="/">index.html</a></li>
<li class="nav-item"><a class="nav-link" href="/next/">next.html</a></li>
</ul>
</header>
<!-- メインコンテンツ -->
<div class="pt-3 pb-4">
<p>{{ title }}が読み込まれています。</p>
<div class="alert alert-warning" role="alert">
今日の天気は晴れです。ポカポカ陽気で気持ちいいですね。
</div>
</div>
<!-- フッター -->
<footer class="bg-dark text-light">
<p class="text-center">Copyright 2020 tanu.</p>
</footer>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
index.htmlを分割する|extends, block
Flaskで使っているJinja2テンプレートエンジンには、『テンプレートの継承』という機能が存在します。
テンプレートの継承は、Jinja2のextends
ブロックを使用します。
{% extends "ファイルのパス" %}
そして、継承したテンプレートには継承元にあるブロックに、はめ込むコンテンツを記述します。
ブロックを呼び出すためには、Jinja2のblock
ブロックを使用します。
{% block 任意のブロック名 %}
<!-- コンテンツ -->
{% endblock %}
それでは、extends
ブロックとblock
ブロックを使用して、
- ページ全体のレイアウトを司るHTMLファイルを
layout.html
- ルートで表示されるメインコンテンツ部分のHTMLファイルを
index.html
と設定し、もともと1つだったindex.html
を2つのHTMLファイルにそれぞれ分割してみましょう。
レイアウトとコンテンツ部分に分割する
layout.html
┣ app.py
︙
┗ templates/
┣ index.html
┗ layout.html NEW
layout.html
は、ページ全体のレイアウトを司るHTMLファイルです。
もともとのindex.html
からメインコンテンツだけを取り除いた部分で構成されています。
取り除いたメインコンテンツ部分には、{% block content %}{% endblock %}
と記述し、メインコンテンツ部分のindex.html
を読み込ませるように設定します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{ title }}</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
</head>
<body>
<div class="container">
<h1 class="mt-4">テンプレート継承({{ title }})</h1>
<!-- ヘッダー -->
<header class="my-3">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link" href="/prev/">prev.html</a></li>
<li class="nav-item"><a class="nav-link active" href="/">index.html</a></li>
<li class="nav-item"><a class="nav-link" href="/next/">next.html</a></li>
</ul>
</header>
<!-- メインコンテンツ -->
{%- block content %}{% endblock %}
<!-- フッター -->
<footer class="bg-dark text-light">
<p class="text-center">Copyright 2020 tanu.</p>
</footer>
</div>
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</body>
</html>
index.html
index.html
には、STEP.1で取り除かれたメインコンテンツ部分をそのまま記述します。
{%- extends "layout.html" %}
{%- block content %}
<div class="pt-3 pb-4">
<p>{{ title }}が読み込まれています。</p>
<div class="alert alert-warning" role="alert">
今日の天気は晴れです。ポカポカ陽気で気持ちいいですね。
</div>
</div>
{%- endblock %}
無事に2つのHTMLファイルに分割することに成功しました。
新しいページを作成する
メインコンテンツの中身が違う2つのHTMLファイル(prev.html
とnext.html
)を作成してみましょう。
┣ app.py
︙
┗ templates/
┣ index.html
┣ layout.html
┣ next.html NEW
┗ prev.html NEW
prev.html
{%- extends "layout.html" %}
{%- block content %}
<div class="pt-3 pb-4">
<p>{{ title }}が読み込まれています。</p>
<div class="alert alert-secondary" role="alert">
今日の天気は曇りです。光のコントラストが少なくポートレート撮影にはもってこいですね。
</div>
</div>
{%- endblock %}
next.html
{%- extends "layout.html" %}
{%- block content %}
<div class="pt-3 pb-4">
<p>{{ title }}が読み込まれています。</p>
<div class="alert alert-primary" role="alert">
今日の天気は雨です。雨音ってなんだか落ち着きますね。
</div>
</div>
{%- endblock %}
app.py
# 中略
@app.route('/')
def index():
return render_template('index.html', \
title = 'index.html')
@app.route('/prev/')
def prev():
return render_template('prev.html', \
title = 'prev.html')
@app.route('/next/')
def next():
return render_template('next.html', \
title = 'next.html')
ヘッダーのナビゲーションがindex.html
で固定されていますが、後述する方法で対応しています。
layout.htmlを役割ごとに分割する|include
layout.html
を見直してみましょう。
<!-- 中略 -->
<div class="container">
<h1 class="mt-4">テンプレート継承({{ title }})</h1>
<!-- ヘッダー -->
<header class="my-3">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link" href="/prev/">prev.html</a></li>
<li class="nav-item"><a class="nav-link active" href="/">index.html</a></li>
<li class="nav-item"><a class="nav-link" href="/next/">next.html</a></li>
</ul>
</header>
<!-- メインコンテンツ -->
{%- block content %}{% endblock %}
<!-- フッター -->
<footer class="bg-dark text-light">
<p class="text-center">Copyright 2020 tanu.</p>
</footer>
</div>
この中には、ヘッダーやフッターなどが混在しています。
これらを管理しやすくするため、それぞれheader.html
、footer.html
として細分割してみましょう。
今回は、ヘッダーやフッターをindex.html
やprev.html
のように新しいページ単位ではなく、あくまで1つの部品として保管させたいです。
そんなときは block
ブロックではなく、include
ブロックを使用します。
{% include "ファイルのパス" %}
┣ app.py
︙
┗ templates/
┣ footer.html NEW
┣ header.html NEW
┣ index.html
┣ layout.html
┣ next.html
┗ prev.html
layout.html
<!-- 中略 -->
<div class="container">
<h1 class="mt-4">テンプレート継承({{ title }})</h1>
<!-- ヘッダー -->
{% include "header.html" %}
<!-- メインコンテンツ -->
{%- block content %}{% endblock %}
<!-- フッター -->
{% include "footer.html" %}
</div>
header.html
<header class="my-3">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link" href="/prev/">prev.html</a></li>
<li class="nav-item"><a class="nav-link active" href="/">index.html</a></li>
<li class="nav-item"><a class="nav-link" href="/next/">next.html</a></li>
</ul>
</header>
また、リストの部分でactiveなクラスを動的に対応させるために、header.html
を以下のように修正します。
<header class="my-3">
<ul class="nav nav-pills">
<li class="nav-item"><a class="nav-link{% if prev %} active{% endif %}" href="/prev/">prev.html</a></li>
<li class="nav-item"><a class="nav-link{% if index %} active{% endif %}" href="/">index.html</a></li>
<li class="nav-item"><a class="nav-link{% if nextt %} active{% endif %}" href="/next/">next.html</a></li>
</ul>
</header>
<footer class="bg-dark text-light">
<p class="text-center">Copyright 2020 tanu.</p>
</footer>
header.html
と同様に、ソースをきれいに整えるためです。app.py
# 中略
@app.route('/')
def index():
return render_template('index.html', \
index = True, \
title = 'index.html')
@app.route('/prev/')
def prev():
return render_template('prev.html', \
prev = True, \
title = 'prev.html')
@app.route('/next/')
def next():
return render_template('next.html', \
nextt = True, \
title = 'next.html')
動作確認
ちゃんと、ヘッダーのナビゲーションも動作してることが確認できますね(^o^)
まとめ
extends
+block
で、アプリケーションの枠組みを継承{% extends "ファイルのパス" %}
{% block 任意のブロック名 %}
<!-- コンテンツ -->
{% endblock %}
include
で、役割ごと(header
やfooter
など)に分割されたHTMLファイルを差し込む{% include "ファイルのパス" %}
連載目次:FlaskでWebアプリケーションを開発するためのロードマップ
入門編:10記事
入門編の10記事を順に読んでいけば、FlaskでWebアプリケーションを開発する必要最小限のことが学べます。
簡単なアプリケーションであれば、セキュリティ上の観点を考慮しなかった場合公開できるでしょう。
- Flaskを『ローカルで開発する環境構築』から『プログラムの実行まで』を一通り
FlaskでWebアプリケーションを作る『全体の流れ』を大まかに理解する- Flaskのrender_templateでHTML・CSS・JavaScriptファイルを読み込む
- サーバーサイドからクライアントサイドに変数(値・リスト・辞書型配列)を渡す
- クライアントサイドで変数を処理(アサイン、フィルター、エスケープ)する
- クライアントサイドで条件分岐(if)とループ(for in)を使って、コードを簡素化する
- クライアントサイド(form)からサーバーサイドにテキストや各種コントロール、ファイルを渡す
- テンプレート継承でHTMLファイルを役割ごとに分割する
データベースに接続するデプロイする
実践編
実践編の記事では、FlaskでWebアプリケーションを公開するために欠かせないセキュリティのことや実践的なテクニックを紹介しています。
ここまで読み込めば、あとはアイディア次第でいろんなWebアプリケーションを公開できるでしょう!
セッションCookieメソッドベース・ディスパッチベーシック認証・Digest認証ログイン機能- サーバーサイドからクライアントサイドにpandas.Series、pandas.DataFrameを渡す
- クライアントサイド(form)からPOSTされた1、2次元配列を受け取る
matplotlibを使うBootstrap4を使うフォームの非同期通信(Ajax, jQuery)