ちょっとしたWEBアプリを作りたい時に便利そうなフレームワーク。いろいろあるようだが、とりあえずこれを試してみることにしてみた。これが理解できるようになってからメガフレームワーク系に進もう。
http://flask.pocoo.org/ 公式サイト
http://flask.pocoo.org/docs/api/ メソッドの使い方などはこのあたり
http://werkzeug.pocoo.org/docs/ よくこのパッケージの機能もでてくるので見てみること
http://flask.pocoo.org/snippets/ tips的な
http://flask.pocoo.org/ 公式サイト
http://flask.pocoo.org/docs/api/ メソッドの使い方などはこのあたり
http://werkzeug.pocoo.org/docs/ よくこのパッケージの機能もでてくるので見てみること
http://flask.pocoo.org/snippets/ tips的な
pipを使うことにする。3.x系では動かないようなので2.7.xあたりで試すのがよい
pip install Flaskインストールすると何個か依存モジュールが入るようだ
Flask==0.6.1 Jinja2==2.5.5 Werkzeug==0.6.2 distribute==0.6.14 wsgiref==0.1.2
とりあえず
公式サイトに載っているのをほぼそのまま
#!/usr/bin/env python
# vim:fileencoding=utf-8
import os
import sys
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "こんにちわ"
if __name__ == "__main__":
app.run()
実行すれば127.0.0.1:5000で待ちうけするlisten address変更
app.run(host="0.0.0.0")
port
5000以外を使いたい場合
app.run(port=9999)
debug
app.run(debug=True)
とりあえずこうしてみた。はじめはpyファイル1つだけでいいかなと思ってたけどいろいろとあってこうする
.
|-- app.py
`-- application
|-- __init__.py
|-- config.py
|-- static
|-- upload
|-- templates
`-- index.html
app.py
#!/usr/bin/env python # vim:fileencoding=utf-8 import sys import os from application import app app.run(debug=True)
init.py
ここにいろいろroutingの指定などを記載する
# vim:fileencoding=utf-8 import os import os.path import sys import pprint from flask import Flask, Markup, url_for, abort, redirect, render_template, session, request, make_response, send_from_directory, jsonify, g, flash, escape from werkzeug import secure_filename __version__ = 1.0 app = Flask(__name__)例に書いてるくらいflaskからimportしておけばまず困らないはずだ
config.py
設定関連。http://flask.pocoo.org/docs/config/ objectで定義することにした
class Config(object):
DEBUG = False
TESTING = False
SECRET_KEY = "/S97ojSkTNB2C@2DR5qVXKgKGNyaHvz6evbGwk-X.grmR9LE"
SESSION_COOKIE_NAME = "_sid"
#UPLOADDIR = os.path.join(app.root_path, "upload")
class ProductConfig(Config):
pass
class DevelopConfig(Config):
DEBUG = True
と記載する
app.config.from_object("application.config.DevelopConfig")
で設定を読み込める。環境に応じてProductを指定とかできるようにしておけばいいだろうstatic
静的ファイルを設置するディレクトリ。デフォルトはviewとなるモジュールと同階層にディレクトリを設置する
templates
jinja2のテンプレートを設置するディレクトリ。デフォルトはviewとなるモジュールと同階層にディレクトリを設置する
upload
とりあえずはどこでもいいけどここで
基本はこんな感じ
from flask import Flask, url_for
app = Flask(__name__)
@app.route("/")
def index():
return "top page"
@app.route("/hello")
def hello():
return "hello page"
@app.route("/user/<user_name>")
def user(user_name):
return "i am %s" % user_name
@app.route("/post/<int:post_id>")
def post(post_id):
return "post id: %d" % post_id
with app.test_request_context():
print(url_for("index"))
print(url_for("hello"))
print(url_for("user", user_name="melvins"))
print(url_for("post", post_id=3))
どのようにmappingされているかが出力される/ /hello /user/melvins /post/3これで実行したい場合はapp.runすればよい
favicon.ico
flaskのコンソールログを見てるとよく
$remoteip - - [06/Mar/2011 10:27:06] "GET /favicon.ico HTTP/1.1" 404 -と残っているので
@app.route("/favicon.ico")
def favicon():
return app.send_static_file("favicon.ico")
と定義しておき、staticディレクトリにfavicon.icoを設置しておけばよいrequest method
methodsに定義されているメソッドのみ受け付ける。まあ普通はPOST, GETを意識しておけばよさそう
@app.route("/login", methods=["POST", "GET"])
def login():
# anything to do...
g
flaskアプリケーション内でグローバルに存在することができる値をここにもっておく。flaskのサンプルそのまま
import sqlite3
from flask import g
DATABASE = '/path/to/database.db'
def connect_db():
return sqlite3.connect(DATABASE)
@app.before_request
def before_request():
g.db = connect_db()
@app.after_request
def after_request(response):
g.db.close()
return response
static
特に定義しなくてもディレクトリ構成ルールを守れば、有効になる。staticが所定の位置におかれているかは
app.has_static_folderを見ればわかる。型はboolean
abort
200以外で返したい場合など
from flask import Flask, abort
@app.route("/not_found")
def not_found():
abort(404)
redirect
どこかに飛ばす場合
from flask import Flask, url_for, send_file, abort, redirect
@app.route("/mentenance")
def mentenance():
return "mentenance time"
@app.route("/menu")。
def route():
return rediredt(url_for("mentenance"))
http://localhost:5000/menu にアクセスすると/mentenanceにリダイレクトする(302)errorhandler
404の場合は404ページを出したい場合など
@app.errorhandler(404)
def page_not_found(error):
return "not found, error.code
route filter
before_filter
routingを実行する前のフィルタリング。ログイン後のsessionのチェックとか
@app.before_request
def before_request_trigger():
# anything to do..
after_filter
routingを実行後のフィルタリング。Responseを引数として受け取るので返り値も必ずResponseを返すこと
@app.after_request
def after_request_trigger(response):
# anything to do..
return response
teardown_filter
一連の処理が終わった後の処理。sys.exc_info()[1]を引数としてうけているようだ
@app.teardown_request
def after_request_trigger(exc):
# anything to do..
return exc
logging
python標準のloggingをwrapperしている
from logging import Formatter, FileHandler
handler = FileHandler("/tmp/app.log", encoding="utf8")
handler.setFormatter(Formatter("[%(asctime)s] %(levelname)-8s %(message)s", "%Y-%m-%d %H:%M:%S"))
app.logger.addHandler(handler)
あとは
app.logger.info("info log")
のように使うmethod
request.method
query_string
GETの場合はこれで取得
request.args.get("key")
form
POSTの場合はこれで取得
request.form.get("key")
ファイルアップロード
ファイルをアップロードし、アップロードしたファイルを参照するページにリダイレクト。な例
from flask import Flask, url_for, abort, redirect, render_template, request, send_from_directory
from werkzeug import secure_filename
@app.route("/upload", methods=["POST"])
def upload():
f = request.files["file"]
filename = secure_filename(f.filename)
f.save(os.path.join(UPLOADDIR, filename))
return redirect(url_for("view_upload", filename=filename))
@app.route("/view_upload/<path:filename>")
def view_upload(filename):
return send_from_directory(UPLOADDIR, filename)
/uploadに対してファイルをアップロードすると /view_upload/filename にアクセスするとコンテンツがそのまま表示されるis_xhr
ajax経由でリクエストがきているか
if request.is_xhr is True:
# something to do...
headers
content-typeを変更したい場合や、独自のヘッダーをつけたい場合など。make_responseは最後にコンテンツを返す時にも使われているようだ
# make_responseが必要
from flask import Flask, url_for, abort, redirect, render_template, request, make_response, send_from_directory
@app.route("/")
def index():
data = "hello world"
response = make_response(data)
response.headers["Content-type"] = "text/plain"
response.headers["X-Other-Header"] = "blah.."
return response
cookie
cookie保存/取り出し
bake
@app.route("/bake")
def bake():
data = "bake cookie!"
response = make_response(data)
response.set_cookie("cookie_test", value="cookie is baked")
return response
take
requestで取得
@app.route("/take")
def take():
cookie = request.cookies
value = cookie["cookie_test"]
return "cookie value: %s" % value
session
実際は直接cookieを使うことはないはずなので。SECRET_KEYを必ず設定しておく必要がある
ログイン画面
@app.route("/", methods=["GET", "POST"])
def login_form():
if request.method == "POST" :
name = request.form.get("name")
password = request.form.get("password")
if valid_login(name, password) :
session["login"] = { "name": name, "password": password }
flash("conguraturation", "success")
return redirect(url_for("/login"))
else:
flash("login failure", "error")
return render_template("index.html")
ログイン後画面
@app.route("/login")
def login():
if "login" not in session or !valid_login(session["login"]["name"], session["login"]["password"]) :
return "session failure"
return render_template("login.html")
ログアウト
@app.route("/logout")
def logout():
session.clear()
return redirect(url_for("/"))
flash
sessionのところにもサンプルが少しでているが、sessionの機能をつかっている。テンプレート側でエラーメッセージの表示などに使える。かなり便利
sessionのログイン画面のHTML側で
sessionのログイン画面のHTML側で
<div class="error">
{% for category, message in get_flashed_messages(with_categories=True) %}
{# category #} {{ message }}
{%- endfor %}
</div>
とするとflashで設定したメッセージが表示される。ちなみに1回きりのメッセージなので、メッセージを取得した時点でsessionからは消えているjsonify
jsonで結果を返したい場合
@app.route("/json")
def json():
data = { 'list': ['apple', 'banana'], 'dict': {'kurt': 'nirvana'} }
return jsonify(data)
content-typeはちゃんとapplication/jsonになっている
テンプレートエンジン。http://jinja.pocoo.org/ がベースとなっている。多機能なので、とりあえず最低限こんだけくらいは
基本
# vim:fileencoding=utf-8
from jinja2 import Template
template = Template("Hello {{ name }}!")
print(template.render(name=u"こんにちは"))
実際はテンプレートファイルを別に準備する
基本その2
Environment, FileSystemLoaderを使う
# vim:fileencoding=utf-8
from jinja2 import Environment, FileSystemLoader
# テンプレートディレクトリをFileSystemLoaderに渡して、Environmentのloaderに指定。でいいようだ
tmplpath = "/path/to/templates"
env = Environment(loader=FileSystemLoader([tmplpath]))
template = env.get_template("test.html")
print(template.render(var=True, name='kurt cobain', fruits=["apple", "melon", "banana"], dictionary={'one': 1, "two": 2, "three": 3}))
FileSystemLoaderの引数は配列にしておくとテンプレートディレクトリのパスを複数定義できるので、普段からこうしておくほうがよさそう。配列じゃなくてもいいけど。テンプレートは以下
# test.html
<html>
<head>
<title>Jinja2おためし</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
</head>
<body>
<h1>{{ name }}</h1>
<h2>listのテスト</h2>
{# comment #}
{% for fruit in fruits -%}
<li>{{ loop.index }} {{ fruit }}</li>
{%- endfor %}
<h2>dictのテスト</h2>
{% for key in dictionary.keys() -%}
<li>{{ loop.index }} {{ key }} is {{ dictionary[key] }}</li>
{%- endfor %}
<h2>ifのテスト</h2>
{% if var -%}
var is {{ var }}
{% else %}
var is not defined
{%- endif %}
</body>
</html>
他の言語とだいたい同じ自動エスケープ
Environment option autoescape
env = Environment(loader=FileSystemLoader([tmplpath]), autoescape=True)
一時的にエスケープをはずしたい場合
テンプレート内で以下のようにする
{% autoescape false %}
<h1>{{ message | e }}!</h1>
{% endautoescape %}
global value
renderの時に変数を渡さなくてもテンプレートで使用できるようにする
env.globals['tests'] = "global test value"変数として以外でもいろいろ使い方あると思う
filter
pipeでいろいろ処理することができる
escape: {{ var1 | escape }}<br>
striptags: {{ var1 | striptags }}<br>
upper: {{ var1 | upper }}<br>
http://jinja.pocoo.org/docs/templates/#list-of-bui... あたり参照自分でfilterを定義したい場合
たとえば文字列のhash値を出力するようなfilterの場合は
from hashlib import md5
def md5sum(value):
m = md5()
m.update(value.encode("utf-8"))
return m.hexdigest()
env.filters['md5sum'] = md5sum
使う側では
{{ var1 | md5sum }}
とすればよいescape + \nを<br>にする
{% autoescape false %}
<h1>{{ message | e | replace("\n", "<br />\n") }}!</h1>
{% endautoescape %}
Markup
Markupの外側の値を安全に扱う。ようはescape
template = Template("hello {{ name }}")
name = Markup("<strong>kurt</strong>")
print(template.render(name=name))
これはhello <strong>kurt</strong>
こういう使い方
name = Markup("<strong>%s</strong>") % "<u>kurt</u>"
は<strong><u>kurt</u></strong>Markupが返す文字列に何か操作をしても全てHTMLエスケープした状態になる(文字列連結も)
escape
単純なescape
# Markup(u'<>"'')
Markup.escape("<>\"'")
cache
built-in cacheを使う場合はFileSystemBytecodeCacheを使えばよさそう
from jinja2 import Environment, FileSystemLoader, FileSystemBytecodeCache
env = Environment(loader=FileSystemLoader("/path/to/templates")), bytecode_cache=FileSystemBytecodeCache("/path/to/cache")))
http://jinja.pocoo.org/docs/api/#bytecode-cache memcachedをバックエンドに使用できるようだテンプレート継承
一番重要な機能の一つだと思う。http://jinja.pocoo.org/docs/templates/#template-in...
base.html
継承したテンプレート
base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<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 %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
継承したテンプレート
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">Welcome on my awesome homepage.</p>
{% endblock %}
実行結果(プログラムからは継承したテンプレートを呼び出す)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<link rel="stylesheet" href="style.css" />
<title>Index - My Webpage</title>
<style type="text/css">
.important { color: #336699; }
</style>
</head>
<body>
<div id="content">
<h1>Index</h1>
<p class="important">Welcome on my awesome homepage.</p>
</div>
<div id="footer">
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
</div>
</body>
contextfilter
http://ymotongpoo.appspot.com/jinja2_ja/api.html#i... とりあえずこのへんをみて理解するところから
evalcontextfilter
contextfilterかどちらかを理解しておけばよさそう
単純なfilterではなく、現在のjinja2の環境情報を取得できると考えておけばよさそう。というのを踏まえて、html escape + 改行を<br>に変換するフィルタをこう書くことができる
単純なfilterではなく、現在のjinja2の環境情報を取得できると考えておけばよさそう。というのを踏まえて、html escape + 改行を<br>に変換するフィルタをこう書くことができる
from jinja2 import Markup, Environment, FileSystemLoader, evalcontextfilter
from jinja2.utils import soft_unicode
@evalcontextfilter
def nl2br(eval_ctx, value):
res = soft_unicode(Markup.escape(value)).replace("\n", Markup("<br />\n"))
if eval_ctx.autoescape:
res = Markup(res)
return res
env = Environment(loader=FileSystemLoader("."), autoescape=False)
env.filters["nl2br"] = nl2br
jinja2.util.soft_unicodeでMarkup.escapeの効果を無効にして改行をbrに変換して、autoescapeが有効ならその結果をMarkupで保護する。とするとこの結果が二重escapeされる心配もない。eval_ctxはhttp://jinja.pocoo.org/docs/api/#jinja2.nodes.Eval...のことmacro
http://jinja.pocoo.org/docs/templates/#macros
htmlで部品的なものを作る場合。だいたい使い方はわかった。
macroを定義。form-helper.htmlとして作成する
macroを呼び出す。template.htmlとする。同じファイル内にmacroを定義することも可能だが、たぶん別で定義すると思うので、こうしてる
htmlで部品的なものを作る場合。だいたい使い方はわかった。
macroを定義。form-helper.htmlとして作成する
{% macro input(name, type="text") -%}
<input name="{{ name }}" type="{{ type }}" {% for k in kwargs %}{{ k }}="{{ kwargs[k] }}" {% endfor %} />
{%- endmacro %}
macroを呼び出す。template.htmlとする。同じファイル内にmacroを定義することも可能だが、たぶん別で定義すると思うので、こうしてる
{% import "form-helper.html" as form %}
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
{{ form.input("account", type="text", value="", id="faa") }}
</body>
</html>
はじめ
ディレクトリ構成としてFlaskを使用して実際にrouteの定義などをしているファイル(今回の場合でいうとinit.py)と同階層にtemplatesというディレクトリを作成し、その中にテンプレートをおかないといけないようだ
- templates/index.html
<html>
<head>
<title>hello flask</title>
</head>
<body>
<h1>{{ message }}!</h1>
</body>
</html>
プログラムでは
from flask import Flask, url_for, abort, redirect, render_template
@app.route("/")
def index():
return render_template("index.html", message="index page")
とすればhttp://localhost:5000/ にアクセスするとテンプレートが適用されたHTMLが表示されるはずtemplatesの位置を変えたい場合
理由がなければする必要はないと思うが
os.path.join(app.root_path, "templates")とflask/helper.pyで定義されているようなので
from jinja2 import FileSystemLoader
app.jinja_loader = FileSystemLoader("/path/to/my/templatedir")
とjinja_loaderに再定義してあげればよい。http://flask.pocoo.org/docs/api/#flask.Flask.jinja...filter登録
env.filtersのようなことをする
from hashlib import md5
@app.template_filter("md5sum")
def md5sum(value):
m = md5()
m.update(value.encode("utf-8"))
return m.hexdigest()
evalcontextfilter
もちろん可能
from jinja2 import evalcontextfilter
from jinja2.utils import soft_unicode
@app.template_filter("nl2br")
@evalcontextfilter
def nl2br(eval_ctx, value):
res = soft_unicode(Markup.escape(value)).replace("\n", Markup("<br />\n"))
if eval_ctx.autoescape:
res = Markup(res)
return res
テンプレートで
{{ name | nl2br }}
とすればよい
tornado
http://www.tornadoweb.org/ tornado自身もフレームワークだが、non-blockingなwebサーバだけをflaskで使用する
from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from application import app http_server = HTTPServer(WSGIContainer(app)) # addressは指定なしだと0.0.0.0 http_server.listen(5000, address="127.0.0.1") IOLoop.instance().start()autoreloadやdebugの方法はまだ調べてないからわからない。http://d.hatena.ne.jp/Ehren/20100301/1267467815がヒントになりそうだけど
autoreload
tornado.autoreloadを使えばよい
#!/usr/bin/env python # vim: fileencoding=utf-8 import os import sys from tornado.wsgi import WSGIContainer from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.autoreload import start from application import app http_server = HTTPServer(WSGIContainer(app)) http_server.listen(5000) loop = IOLoop.instance() start(loop) loop.start()
command line option
tornado付属のtornado.optionsを使う
#!/usr/bin/env python
# vim: fileencoding=utf-8
import os
import sys
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.autoreload import start
from tornado.options import parse_command_line, define, options
from application import app
# port番号を指定できるように定義
define("port", default=5000, type=int, help="run on the given port")
parse_command_line()
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(options.port)
loop = IOLoop.instance()
start(loop)
loop.start()
app.pyを実行時に./app.py --helpとすると
Usage: ./app.py [OPTIONS] Options: --help show this help information --log_file_max_size max size of log files before rollover --log_file_num_backups number of log files to keep --log_file_prefix=PATH Path prefix for log files. Note that if you are running multiple tornado processes, log_file_prefix must be different for each of them (e.g. include the port number) --log_to_stderr Send log output to stderr (colorized if possible). By default use stderr if --log_file_prefix is not set and no other logging is configured. --logging=info|warning|error|none Set the Python log level. If 'none', tornado won't touch the logging configuration. ./app.py --port run on the given portこのように使えるoptionが表示される。アクセスログもこれで取れるようになる。
構成
|-- app.py |-- application | |-- __init__.py | |-- admin | | |-- __init__.py | | |-- templates | | | `-- login.html | | `-- views.py | |-- frontend | | |-- __init__.py | | |-- templates | | `-- views.py | `-- templates | `-- base.html `-- develop.pyURIの構成的に
- / 公開領域
- /admin 管理者用
詳細
http://flask.pocoo.org/docs/patterns/packages/
ちなみに処理内容は自分がわかればいいレベルなので、かなり適当
ちなみに処理内容は自分がわかればいいレベルなので、かなり適当
app.py
何も変わらない
#!/usr/bin/env python # vim:fileencoding=utf-8 import sys import os from application import app app.run(host='0.0.0.0')
application/init.py
ここからの構成が重要
# vim:fileencoding=utf-8
import sys
import os
from flask import Flask
from application.admin.views import admin
from application.frontend.views import frontend
app = Flask(__name__)
app.config.from_envvar("FLASK_APP_SETTINGS")
app.register_module(admin, url_prefix="/admin")
app.register_module(frontend, url_prefix="/")
/, /adminごとにモジュールを作成し、register_moduleにmoduleとURIとのmappingを行っている
application/frontend/init.py
これは空でいい
application/frontend/views.py
/ 以下にアクセスしたときに呼び出される
# vim:fileencoding=utf-8
import sys
import os
from flask import Module
frontend = Module(__name__, "frontend")
@frontend.route("/")
def index():
return "frontend.index"
下位にあたるモジュールはroutingの定義はFlaskではなく、Moduleで行う。application/admin/views.py
/admin 以下にアクセスしたときに呼び出される
# vim:fileencoding=utf-8
import sys
import os
from flask import Module, Flask, url_for, render_template, request
admin = Module(__name__, "admin")
app = Flask(__name__)
app.config.from_envvar("FLASK_APP_SETTINGS")
@admin.route("/")
def index():
return "admin.index"
@admin.route("/login", methods=["POST", "GET"])
def login():
return render_template("admin/login.html")
@admin.route("/logout")
def logout():
return "admin.logout"
render_templateはapplication/admin/templates/login.htmlが呼び出される。中身はこんなかんじ
{% extends "base.html" %}
{% block title %}login{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>login</h1>
<p class="important">Welcome on my awesome homepage.</p>
<form action="/admin/login" method="post">
account:<input type="text" name="account" /><br/>
password:<input type="password" name="password" /><br/>
<input type="submit" />
</form>
{% endblock %}
PATHの指定しなければapplication/templates配下のテンプレートが呼び出されるようだ。
sqlalchemy plugin http://packages.python.org/Flask-SQLAlchemy/
軽く使ってみた限り、標準のsqlalchemyより使いやすい感じ
軽く使ってみた限り、標準のsqlalchemyより使いやすい感じ
構成
.
|-- app.py
`-- application
|-- __init__.py
|-- config.py
|-- db.py
|-- static
| `-- favicon.ico
`-- templates
`-- index.html
各ファイルの中身
これさえ書いとけばあとでなんとなく思い出せるだろう
app.py
#!/usr/bin/env python # vim:fileencoding=utf-8 import sys import os from application import app app.run(host='0.0.0.0')
application/config.py
sqlalchemy用の設定を追加
class Config(object): DEBUG = False TESTING = False SECRET_KEY = "/S97ojSkTNB2C@2DR5qVXKgKGNyaHvz6evbGwk-X.grmR9LE" SESSION_COOKIE_NAME = "_sid" SQLALCHEMY_DATABASE_URI = "$dbtype://$user:$pass@$server:$port/$dbname?charset=utf8" SQLALCHEMY_ECHO=True class ProductConfig(Config): pass class DevelopConfig(Config): DEBUG = True TESTING = True
application/db.py
Modelになるクラスの定義を行う
# vim:fileencoding=utf-8
import sys
import os
from flask import Flask
from flaskext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object("application.config.DevelopConfig")
db = SQLAlchemy(app, session_options={"autocommit": True})
class Sections(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(128), nullable=False)
def __init__(self, id, name):
self.id = id
self.name = name
def __repr__(self):
return "<Sections %s %s>" % (self.id, self.name)
class Members(db.Model):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(128), nullable=False)
last_name = db.Column(db.String(128), nullable=False)
age = db.Column(db.Integer, nullable=False)
section_id = db.Column(db.Integer, db.ForeignKey("sections.id"))
section = db.relationship("Sections", backref=db.backref("members"))
def __init__(self, id, first_name, last_name, age, section_id):
self.id = id
self.first_name = first_name
self.last_name = last_name
self.age = age
self.section_id = section_id
def __repr__(self):
return "<Members %s %s %s %s %s>" % (self.id, self.first_name, self.last_name, self.age, self.section_id)
application/init.py
とりあえず一覧表示、登録、削除まで適当に実装
# vim:fileencoding=utf-8
import os
import os.path
import sys
import pprint
from flask import Flask, Markup, url_for, abort, redirect, render_template, session, request, make_response, send_from_directory, jsonify, g, flash, escape
from application.db import db,Sections, Members
__version__ = 1.0
app = Flask(__name__)
app.config.from_object("application.config.DevelopConfig")
@app.before_request
def before_request_trigger():
pass
@app.teardown_request
def after_request_trigger(exc):
return exc
@app.route("/")
def index():
members = Members.query.order_by(db.desc(Members.id)).all()
sections = Sections.query.order_by(Sections.id).all()
return render_template("index.html", members=members, sections=sections)
@app.route("/add", methods=["POST"])
def add():
first_name = request.form.get("first_name")
last_name = request.form.get("last_name")
age = request.form.get("age")
section_id = request.form.get("section_id")
try:
member = Members(None, first_name, last_name, age, section_id)
# session_makerでできないからここでしてみる。SQLAlchemyをnewするときにできたのでやっぱりやめ
# db.session.begin(subtransactions=True)
db.session.begin()
db.session.add(member)
db.session.commit()
except Exception, e:
db.session.rollback()
return repr(e)
return redirect(url_for("index"))
@app.route("/delete/<int:id>")
def delete(id):
try:
db.session.begin()
member = Members.query.get(id)
db.session.delete(member)
db.session.commit()
except Exception, e:
db.session.rollback()
return repr(e)
return redirect(url_for("index"))
if __name__ == "__main__":
app.run(host='0.0.0.0')
application/templates/index.html
参照画面用。selectの結果をそのままテンプレート変数に割り当て
<html>
<head>
<title>hello flask</title>
</head>
<body>
<h1>Flask + SQLAlchemy!</h1>
<form action="add" method="post">
<table border="1">
<tr>
<td>first name</td>
<td><input type="text" name="first_name"></td>
</tr>
<tr>
<td>last name</td>
<td><input type="text" name="last_name"></td>
</tr>
<tr>
<td>age</td>
<td><input type="text" name="age"></td>
</tr>
<tr>
<td>section</td>
<td>
<select name="section_id">
{% for section in sections %}
<option value="{{ section.id }}">{{ section.name }}</option>
{% endfor %}
</select>
</td>
</tr>
</table>
<input type="submit" value="add">
</form>
<table border="1">
<tr>
<th>id</th>
<th>name</th>
<th>age</th>
<th>section</th>
<th>delete</th>
</tr>
{% for member in members %}
<tr>
<td>{{ member.id }}</td>
<td>{{ member.first_name }} {{ member.last_name }}</td>
<td>{{ member.age }}</td>
<td>{{ member.section.name }}</td>
<td><a href="delete/{{ member.id }}">delete</a></td>
</tr>
{% endfor %}
</body>
</html>
タグ

最新コメント