現在Flaskを用いてwebサービス的なものを作っているのですが、サーバーサイドからクライアントサイドに値を渡した後にJinja2の記法で詰まるときがよくあるのでメモしておきます。
概要
- Flaskの使い方をちゃちゃっと紹介(インストール済みならばコピペで動作)
- Flaskの日本語版ユーザーガイドはあるけど、Jinja2のガイドは英語なのが厄介
- Jinja2の構文を数種類紹介
- 基本的な構文を組み合わせた使用例を紹介
- Jinja2を活用してクライアントサイドの記述をスマートにできる
Flaskとは
Python用のマイクロWeb開発フレームワークです。 RubyでいうところのSinatraといっても問題ないと思います。
Flaskのインストールや使い方は他にも色々記事があると思うのでここでは割愛します。
Flaskユーザーガイドのように日本語化されたガイドがあるのも嬉しいです。
Flaskは、 Jinja2 テンプレートエンジンと Werkzeug WSGIツールキットに依存しています。
困ったことにJinja2のドキュメントもありますがこれは英語です。
Jinja2 Documentation
本記事ではFlaskではJinja2の基本的な使い方を書いていきます。
まず基本的なFlaskの使い方
###ディレクトリ構造
まずプロジェクト名などのディレクトリを作ってください。
ここではqiita_flaskをプロジェクト名とします。
ディレクトリ構造から
qiita_flask
├── app.py
└── templates
└── index.html
こういう構造にしてください。
app.pyは別の名前で構いませんがtemplatesディレクトリはこの名前である必要があります。
app.pyがサーバーサイドの処理を記述するものでクライアントサイドはtemplates内のindex.htmlなどで記述します。
###それぞれのファイル
# coding: utf-8
from flask import Flask, render_template
app = Flask(__name__) #インスタンス生成
@app.route("/") #アリケーションルートにアクセスが合った場合
def hello(): #hello関数が動作します。
return "Hello World!" #ブラウザ画面に"Hello World!"と出力されます。
@app.route("/index") #アプリケーション/indexにアクセスが合った場合
def index():
return render_template('index.html') #/indexにアクセスが来たらtemplates内のindex.htmlが開きます
#ここがサーバーサイドからクライアントサイドへなにかを渡すときのポイントになります。
if __name__ == "__main__":
# webサーバー立ち上げ
app.run()
<!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">
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
</head>
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
</div>
</div>
</body>
</html>
ここまででapp.pyを実行してみましょう。
その後、http://localhost:5000にアクセスすると "Hello World!"と出力されるはずで、
http://localhost:5000/indexにアクセスするとBootstrapによってちょっとおしゃれな感じなでSample Pageと表示されるはずです。
このようにFlask+Bootstrapで簡単にそれっぽいものが作れるというのがいいところだと個人的には思っております。
Jinja2の基本的な使い方に焦点をあてるので、FlaskとBootstrapに関する使い方の説明はこれ以上ありません。
基本的なJinja2の使い方
これ以降はindex.htmlを呼びこむ際にサーバーサイドからクライアントサイドに情報を与えていきます。試すときはhttp://localhost:5000/indexにアクセスしてみてください。
サーバーサイドからクライアントサイドへStringを渡す
#中略
@app.route("/index")
def index():
message = 'sample_string'
return render_template('index.html', message=message)
{# 中略 #}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
{% if message %}
<p>{{message}}</p>
{% endif %}
</div>
</div>
</body>
上記のようにrender_template('index.html', message=message)
というようにすると、index.htmlを読み込む際にmessageという変数を渡すという意味になります。
そして、html側で{% if message %}というのはmessageという変数になにかが入っている場合という意味合いになります。
{{ message }}でmessageという変数の中に入っているものが表示されます。
辞書型の変数を渡す
#中略
@app.route("/index")
def index():
my_dic = {}
my_dic['name'] = ryo2851
my_dic['univ'] = 'hogehoge University'
return render_template('index.html', message=my_dic)
{# 中略 #}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
{% if message %}
<p>name: {{message.name}}</p>
<p>univ: {{message.univ}}</p>
{% endif %}
</div>
</div>
</body>
リストを渡す
import numpy as np
#中略
@app.route("/index")
def index():
num_list = np.arange(10)
return render_template('index.html', message=num_list)
{# 中略 #}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
{% for num in message %}
<p>{{num}}</p>
{% endfor %}
</div>
</div>
</body>
ちょっとした応用
画像用のディレクトリを作る
画像はstaticというディレクトリに保存します。
qiita_flask
├── app.py
├── static
│ ├── qiita1.png
│ └── qiita2.png
└── templates
└── index.html
のような構成になります。
お手数ですが、適当な画像を数枚用意してください。名前も適当で大丈夫です。
要素が辞書型のリストを渡す
例えば, 画像リンクを使いたい場合は
<a href = http://hogehoge.com><img src = "hoge.png"> </a>
のようにリンク先URLと画像の名前が必要です。
サーバサイドからクライアントサイドへ、URLと画像の情報を送ってやります。
from os.path import join, relpath
from glob import glob
#中略
@app.route("/index") #アプリケーション/indexにアクセスが合った場合
def index():
path = "./static"
image_names = [relpath(x, path) for x in glob(join(path, '*.png'))]
# static内の.pngファイルがimage_namesに格納されます。
my_list = []
for image in image_names:
my_dic = {}
my_dic['image_name'] = 'static/' + image
my_dic['link_url'] = 'http://qiita.com/ryo2851/items/4e3c287d5a0005780034'
# 飛び先のURLを何個も確保できなかったのでどの画像に対しても同じURLに飛ぶようにしました。
my_list.append(my_dic)
return render_template('index.html', message = my_list) #/indexにアクセスが来たらtemplates内のindex.htmlが開きます
{# 中略 #}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
{% for item in message %}
<a href={{item.link_url}} class="thumbnail"><img src={{item.image_name}}></a>
{% endfor %}
</div>
</body>
ソース内のリンク先は私の過去記事になっております。お時間が有りましたら御覧ください。
これで/indexにアクセスすると
こんな感じで見えるはずです。それぞれの画像をクリックするとリンク先に飛ぶことができます。
応用
htmlの中にJinja2を組み込んでスマートに.
下の画像のようなボタンを押すとプルダウンでその学年のメンバーの名前を表示したいとします。
####馬鹿正直コーディング
馬鹿正直にhtmlで書いてみる
{#中略#}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
</div>
<hr/>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
B4
</button>
<ul class="dropdown-menu">
<li><a href="#">B4taro</a></li>
<li><a href="#">B4Jiro</a></li>
<li><a href="#">B4hanako</a></li>
</ul>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
M1
</button>
<ul class="dropdown-menu">
<li><a href="#">M1taro</a></li>
<li><a href="#">M1Jiro</a></li>
<li><a href="#">M1hanako</a></li>
</ul>
</div>
<div class="btn-group" role="group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
M2
</button>
<ul class="dropdown-menu">
<li><a href="#">M2taro</a></li>
<li><a href="#">M2Jiro</a></li>
<li><a href="#">M2hanako</a></li>
</ul>
</div>
</div>
</body>
似たようなコードを繰り返し書いちゃってますね。
####Jinja2を活用したコーディング
Jinja2を用いてサーバーサイドも使って書いてみます。
#中略
@app.route("/index")
def index():
member_dic = {}
B4_list = ['B4taro', 'B4jiro', 'B4hanako']
M1_list = ['M1taro', 'M1jiro', 'M1hanako']
M2_list = ['M2taro', 'M2jiro', 'M2hanako']
member_dic['B4'] = B4_list
member_dic['M1'] = M1_list
member_dic['M2'] = M2_list
return render_template('index.html', message=member_dic)
{#中略#}
<body>
<div class="container">
<div class="header">
<h3 class="text-muted">Sample Page</h3>
</div>
<hr/>
{% for grade in message.keys() %}
<div class="btn-group" role="group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{ grade }}
</button>
<ul class="dropdown-menu">
{% for name in message[grade] %}
<li><a href="#">{{ name }}</a></li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</body>
工夫なしパターンだと例えばB4,M1,M2,Doctor, Professorとかまで入れた時にはもう泣きたくなりますね。
すでにメンバーを登録済みのDBなどがある場合はサーバーサイドももっとシンプルにかけると思います。
Jinja2を用いることでクライアント側のhtmlが大分すっきり書けました。