[[Python覚え書き]] >
* Python FlaskでWebアプリ作成 [#se271bb5]
#setlinebreak(on);

以下、PythonのWebフレームワークである Flaskの基礎を記載する。

#contents
-- 関連
--- [[Nginx+uwsgiでFlaskアプリケーション]]
--- [[Nginx+uwsgi+Flaskをdockerで動かす]]

**  Flaskのインストール [#ob264ba6]
#html(<div style="padding-left:10px">)

アプリケーション用のディレクトリ 及び 仮想環境の作成
#myterm2(){{
mkdir sample_flask
cd sample_flask
python3 -m venv .venv
source .venv/bin/activate
}}

Flaskインストール
#myterm2(){{
pip install Flask
}}

#html(</div>)

** メイン処理 [#q2e394cf]
#html(<div style="padding-left:10px">)

flask.Flask を import するだけで使用できる。

以下、空のWebアプリケーションを作成して起動する例を記載する。

*** app.py [#j3b34058]
#html(<div style="padding-left:10px">)
#mycode2(){{
# coding: utf-8
"""サンプルアプリケーション"""

from flask import Flask

# アプリケーションのインスタンス作成
# (インスタンス生成時の引数にはアプリケーション名を指定する)
app = Flask(__name__)

# ルーティング
@app.route('/')
def index():
    return ''

# 起動(コマンドラインから flask run する場合は不要)
if __name__ == "__main__":
    app.run()
}}

#html(</div>)

*** 起動 [#yff770b1]
#html(<div style="padding-left:10px">)

環境変数 FLASK_APP でエントリポイントを指定する( コマンドラインから flask run する場合)
#myterm2(){{
export FLASK_APP=app.py  # デフォルトは wsgi.py or app.py
}}

flask run で起動
#myterm2(){{
flask run
# もしくは
python3 app.py
}}

#html(</div>)

#html(</div>)

** ルーティング [#e64ebb20]
#html(<div style="padding-left:10px">)

ルーティングは生成したFlaskインスタンスをデコレータとして利用して記述する

*** 基本形 [#k093fc87]
#html(<div style="padding-left:10px">)
#mycode2(){{
@app.route('/')
def index():
    return ''

# 起動(コマンドラインから flask run する場合は不要)
if __name__ == "__main__":
    app.run()
}}
#html(</div>)

*** パス(URI)を指定する [#l4686b28]
#html(<div style="padding-left:10px">)
#mycode2(){{
@app.route('/sample')
def sample():
    return 'sample!\n'
}}
#html(</div>)

*** メソッドを限定する [#w3b2e71f]
#html(<div style="padding-left:10px">)

#mycode2(){{
@app.route('/books', methods=['POST'])
def book_create():
    return 'book create!\n'
}}
#html(</div>)

*** パスパラメータを受け取る [#s1cf4824]
#html(<div style="padding-left:10px">)
#mycode2(){{
@app.route('/books/<id>'):
def book_edit(id):
    return f'book_edit : id={id}\n'
}}
#html(</div>)

*** ルーティング情報を確認する [#jcbfc76c]
#html(<div style="padding-left:10px">)

設定済みのルーティング情報は CLI コマンドで確認できる。

#myterm2(){{
flask routes

Endpoint       Methods  Rule
-------------  -------  -----------------------
book_create    POST     /books
book_edit      GET      /books/<id>
book_list      GET      /books
hello          GET      /hello
if_template    GET      /if
inctempl       GET      /inctempl
index          GET      /
inherit_templ  GET      /inherittempl
loop_template  GET      /loop
myscript       GET      /myscript
sample         GET      /sample
static         GET      /static/<path:filename>
webapi         GET      /webapi
}}
#html(</div>)

#html(</div>)


** リクエストデータの受け取り方法 [#o0b48145]
#html(<div style="padding-left:10px">)

*** パスに入力したデータの取得 [#fe4eb6ba]
#html(<div style="padding-left:10px">)

-  @app.route デコレータに &lt;データ形式:項目名&gt; と指定する事で、パスに指定されたパラメータを取得できる。
- データ形式は省略可能
- データ形式が合致しない場合は、404 となる
- データ形式を指定した場合は、そのデータ型で取得される。※省略時は文字列型(str)となる

#html(<div style="display: inline-block;margin-right: 40px;">)

例)
#mycode2(){{
# データ形式を省略する場合
@app.route('/sample1/<id>'):
def sample1(id):
    return f'sample1 : id={id}\n'

# データ形式を指定する場合
@app.route('/sample2/<int:id>'):
def sample2(id):
    return f'sample2 : id={id}\n'
}}
#html(</div>)

#html(<div style="display: inline-block; vertical-align: top;">)
&lt;指定できるデータ形式&gt;
| 形式 | 説明 |h
| string | 文字列(スラッシュ以外) |
| int | 整数 |
| float | 浮動少数点 |
| path | パス(スラッシュ含む) |
| uuid | UUIDの形式 |
| any | あらゆるパターン |
#html(</div>)

#html(</div>)

*** GETパラメータ(クエリ文字列)の取得 [#ob4d2d2f]
#html(<div style="padding-left:10px">)

flask.request.args から GETパラメータを取得する事ができる。

#mycode2(){{

from flask import request

app = Flask(__name__)

@app.route('/books', methods=['GET'])
def book_list():
    limit = int(request.args.get('limit', 10))
    return f'books get :  limit:  {limit}!\n'
}}

結果
#myterm2(){{
curl http://localhost:5000/books?limit=20\&page=1
books get :  limit:  20!
}}


#html(</div>)

*** POSTデータの取得 [#rdd0f711]
#html(<div style="padding-left:10px">)

flask.request.form  から POSTデータを取得する事ができる。

#mycode2(){{

from flask import request

app = Flask(__name__)

@app.route('/books', methods=['POST'])
def book_create():
    var1 = request.form.get('var1')
    return f'book create. var1: {var1}!\n'
}}

結果
#myterm2(){{
curl -XPOST --data 'var1=abc' http://localhost:5000/books
book create. var1: abc!
}}

#html(</div>)

*** 補足 [#t29de458]
#html(<div style="padding-left:10px">)

GET用の request.args も POST用の flask.request.form も 実態は ImmutableMultiDict というオブジェクトとなっている。
なので、このオブジェクトの各種メソッドを使用して、パラメータの取得や、存在確認等を行う事ができる。
※上記のサンプルでは get メソッドを取得してデータの取得を行っている。

https://tedboy.github.io/flask/generated/generated/werkzeug.ImmutableMultiDict.html


#html(</div>)




#html(</div>)


** 静的ファイルの配信 [#bb59d317]
#html(<div style="padding-left:10px">)

static  というフォルダを作成し、その配下にファイルを配置する事で静的ファイルの配信ができる。

*** フォルダ作成 [#w3a99212]
#html(<div style="padding-left:10px">)
#myterm2(){{
mkdir static
}}
#html(</div>)


*** ファイル配置 [#tfecba8f]
#html(<div style="padding-left:10px">)

static/style.css
#mycode2(){{
h1 {
    color:  #f00;
}
}}

#html(</div>)

*** CSS読み込みサンプル [#bd913f2b]
#html(<div style="padding-left:10px">)

app.py
#mycode2(){{
@app.route('/')
def index():
    return """<!doctype html>
<html lang="ja">
<head>
<link rel="stylesheet" href="/static/style.css" />
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>"""
}}
#html(</div>)

#html(</div>)

** テンプレートエンジン(jinja2)の利用 [#jf64af67]
#html(<div style="padding-left:10px">)

Flaskのデフォルトのテンプレートエンジンである Jinja2 の利用方法について記載する。

公式サイトにも記載があるが、構文等は Django の影響を強く受けている為、ほとんど Django と同じ。
なので、Djangoでテンプレートを書いた事があればだいたい分かる(ぱっと見は区別がつかないくらい似てる)

http://jinja.pocoo.org/docs/2.10/templates/


*** テンプレート格納用のディレクトリ [#ncd2c0b6]
#html(<div style="padding-left:10px">)

テンプレートの格納先はデフォルトでは、templates 配下となっている為、プロジェクト直下に templates ディレクトリを作成しておく。
#myterm2(){{
mkdir templates
}}

#html(</div>)


*** テンプレートの作成 [#s27b81a3]
#html(<div style="padding-left:10px">)

templates/hello.html
#mycode2(){{
&lt;!doctype html&gt;
&lt;html lang="ja"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;/head&gt;
&lt;body&gt;

Hello &#123;&#123;name&#125;&#125;!

&lt;/body&gt;
&lt;/html&gt;
}}

#html(</div>)

*** 基本的な使用方法 [#e7bf3396]
#html(<div style="padding-left:10px">)

render_template でテンプレートとなるファイルと、埋め込み変数を指定する事でレンダリング結果が取得できる。

例)
#mycode2(){{
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/hello', methods=['GET'])
def hello():
    name = request.args.get('name', 'World')
    return render_template('hello.html')
}}
#html(</div>)

*** 変数の展開 [#tbcaf245]
#html(<div style="padding-left:10px">)

テンプレートに展開する変数はメイン処理側で render_template の第2引数以降にキーワード引数として指定する。

app.py
#mycode2(){{
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/vars', methods=['GET'])
def vars():
    return render_template('vars.html', var1='abc', var2='def')
}}

テンプレート側では、&#123;&#123;変数名&#125;&#125; と記述する事で変数の内容を展開する事ができる。

templates/vars.html
#mycode2(){{
var1: &#123;&#123;var1&#125;&#125;<br/>
var2: &#123;&#123;var2&#125;&#125;<br/>
}}
#html(</div>)

*** 条件分岐 [#r777ed8a]
#html(<div style="padding-left:10px">)

app.py
#mycode2(){{
from flask import Flask, render_template
import time

app = Flask(__name__)

@app.route('/if')
def if_template():
    time_sufix = int(str(int(time.time()))[-1])
    return render_template('if.html', time_sufix=time_sufix)
}}


templates/if.html
#mycode2(){{
{% if time_sufix % 2 %}
    Odd
{% else %}
    Even
{% endif %}
}}

#html(</div>)

*** 繰り返し [#e48298e4]
#html(<div style="padding-left:10px">)

#mycode2(){{
# テンプレート内で繰り返し処理
@app.route('/loop')
def loop_template():
    items = [ 
        {'index': 0, 'var': 'A'},
        {'index': 1, 'var': 'B'},
        {'index': 2, 'var': 'C'},
        {'index': 3, 'var': 'D'},
        {'index': 4, 'var': 'E'}
    ]   
    return render_template('loop.html', items=items)
}}

templates.loop.html
#mycode2(){{
<ul>
&#123;% for item in items %&#125;
<li>&#123;&#123;item.index&#125;&#125; : &#123;&#123;item.var&#125;&#125;</li>
&#123;% endfor %&#125;
</ul>
}}

#html(</div>)

*** HTMLタグ等のエスケープ無効化 [#ha6e92a5]
#html(<div style="padding-left:10px">)

Jinja2 では XSS対応としてデフォルトで HTMLタグ等がエスケープされるようになっているが、無効化する事もできる。

app.py
#mycode2(){{
@app.route('/myscript')
def myscript():
    return render_template('myscript.html', myscript='<script>alert("test!")</script>')
}}

templates/myscript.html
#mycode2(){{
{% autoescape false %}
&#123;&#123; myscript &#125;&#125;
{% endautoescape %}
}}

#html(</div>)


*** テンプレートを継承する [#idd1e237]
#html(<div style="padding-left:10px">)

テンプレートを継承して必要な部分のみ上書く事も可能。
http://jinja.pocoo.org/docs/2.10/templates/#template-inheritance

以下、公式サイトのサンプルを、ほぼそのまま記載。

base.html
#mycode2(){{
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<head>
    {% block head %}
    <link rel="stylesheet" href="style.css" />
    <title>{% block title %}{% endblock %} - My Webpage</title>
    {% endblock %}
</head>
<body>
    <div id="content">{% block content %}{% endblock %}</div>
    <div id="footer">
        {% block footer %}
        &copy; Copyright Sample.
        {% endblock %}
    </div>
</body>
</html>
}}

child.html
#mycode2(){{
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
    &#123;&#123; super() &#125;&#125;
    <style type="text/css">
        .important { color: #336699; }
    </style>
{% endblock %}
{% block content %}
    <h1>Index</h1>
    <p class="important">
      Welcome to my awesome homepage.
    </p>
{% endblock %}
}}

#html(</div>)

*** 他のテンプレートを include する [#t8f178af]
#html(<div style="padding-left:10px">)

他のテンプレートを include する事も可能。
http://jinja.pocoo.org/docs/2.10/templates/#include

app.py
#mycode2(){{
@app.route('/inctempl')
def inctempl():
    return render_template('inctempl.html', vars=['a', 'b', 'c'])
}}

templates/inctempl.html
#mycode2(){{
{% include "./part_vars.html" %}
}}


templates/part_vars.html
#mycode2(){{
<ul>
{% for var in vars %}
<li>&#123;&#123;var&#125;&#125;</li>
{% endfor %}
</ul>
}}

#html(</div>)

*** テンプレート構文の表示 [#k7ca63b6]
#html(<div style="padding-left:10px">)

raw を使用する事でテンプレートの構文をそのまま表示する事ができる。

#mycode2(){{
# テンプレート内で繰り返し処理
@app.route('/loop')
def loop_template():
    items = [ 
        {'index': 0, 'var': 'A'},
        {'index': 1, 'var': 'B'},
        {'index': 2, 'var': 'C'},
        {'index': 3, 'var': 'D'},
        {'index': 4, 'var': 'E'}
    ]   
    return render_template('loop.html', items=items)
}}

templates.loop.html
#mycode2(){{
<ul>
&#123;% raw %&#125;
&#123;% for item in items %&#125;
<li>&#123;&#123;item.index&#125;&#125; : &#123;&#123;item.var&#125;&#125;</li>
&#123;% endfor %&#125;
&#123;% endraw %&#125;
</ul>
}}

#html(</div>)

*** コメント [#f57fbc8d]
#html(<div style="padding-left:10px">)

templates/comment.html
#mycode2(){{
{# これはコメントです #}
}}
#html(</div>)

*** その他 [#g8b12a91]
#html(<div style="padding-left:10px">)

他にも filter や macro など、様々な機能がある。
http://jinja.pocoo.org/docs/2.10/templates/

#html(</div>)

#html(</div>)


** その他 [#cfe23e81]
#html(<div style="padding-left:10px">)

*** ステータスコードやContent-Typeを指定する [#h02e834c]
#html(<div style="padding-left:10px">)

#mycode2(){{
@app.route('/webapi')
def webapi():
    return json.dumps({'var1': 'abc', 'var2': 'def'}), 200, {'Content-Type': 'application/json; charset=utf-8'}
}}
#html(</div>)

#html(</div>)

** サンプルアプリケーション [#e2eb28b2]
#html(<div style="padding-left:10px">)

#TODO(SQLAlchemy を使用した簡単なCRUDアプリケーションの例)

#html(</div>)

トップ   差分 バックアップ リロード   一覧 単語検索 最終更新   ヘルプ   最終更新のRSS