hollyさんのwiki

ちょっとしたWEBアプリを作りたい時に便利そうなフレームワーク。いろいろあるようだが、とりあえずこれを試してみることにしてみた。これが理解できるようになってからメガフレームワーク系に進もう。

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

とりあえずはどこでもいいけどここで

routing

このあたりからがだんだん重要になってくる

基本はこんな感じ

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側で
<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になっている

jinja2

テンプレートエンジン。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>&lt;u&gt;kurt&lt;/u&gt;</strong>
Markupが返す文字列に何か操作をしても全てHTMLエスケープした状態になる(文字列連結も)
escape
単純なescape
# Markup(u'&lt;&gt;&#34;&#39;')
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
<!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 %}
        &copy; 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">
&copy; 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>に変換するフィルタをこう書くことができる
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 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>

render_template

Jinja2をテンプレートエンジンとして使う。上記説明をさらっとみればなんとか使えるようになるはず

はじめ

ディレクトリ構成として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 }}
とすればよい

other server

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が表示される。アクセスログもこれで取れるようになる。

large application

アプリケーションの規模が大きくなってきた場合

構成

|-- app.py
|-- application
|   |-- __init__.py
|   |-- admin
|   |   |-- __init__.py
|   |   |-- templates
|   |   |   `-- login.html
|   |   `-- views.py
|   |-- frontend
|   |   |-- __init__.py
|   |   |-- templates
|   |   `-- views.py
|   `-- templates
|       `-- base.html
`-- develop.py
URIの構成的に
  • / 公開領域
  • /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配下のテンプレートが呼び出されるようだ。

Flask-SQLAlchemy

sqlalchemy plugin http://packages.python.org/Flask-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>
タグ

Wiki内検索

Menu

ここは自由に編集できるエリアです。

Wikiをはじめる

マイページ