Navigation

index

next |

flask 0.1 documentation »

Flask入門

Contents:

イントロダクション

仮想環境を構築する

仮想環境構築に必要なパッケージをインストール

Flaskパッケージのインストール

初めてのFlaskアプリ

まずは動かしてみよう！

テンプレートの導入

Flaskとは何か？

Jinja2の補足

ここまでのGitHub

SQLAlchemyを使う

データベース初期化

ここまでのGitHub

Flaskでモデルを使う

GETに対応する

ここまでのGitHub

POSTメソッドでデータを変更できるようにする

コードの説明

HTTP経由でデータの追加や更新をテストする

ここまでのGitHub

AjaxなWikiにする

reStructuredTextをHTMLにコンバートする

テンプレートを修正する

クリックで書き換えられるようにする

動かしてみる

ここまでのGitHub

やりのこしていること

まだかいていないところのリスト

Indices and tables

Index

Module Index

Search Page

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

イントロダクション

これはFlaskとSQLAlchemyを利用したWikiの構築を通してFlaskを学ぼうという本です。

Flask拡張は利用していません

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

仮想環境を構築する

仮想環境構築に必要なパッケージをインストール

なぜ仮想環境が必要なのか？

pythonを含め、一般的なプログラミング言語で開発していると次のようなニーズが発生すると思います。

プロジェクト毎に異なるライブラリのバージョンを導入する必要がある

新しいライブラリバージョンで現行プロジェクトが動くかテストしたい

新しいパッケージをちょっと導入して試してみたいが、現在の環境を汚したくない

このような場合にシステムの環境とは別に個別の仮想環境が構築できるようになっていれば上のニーズを満たせるため、開発がしやすくなります。

pythonには仮想環境を構築するパッケージとしてvirtualenvがあります。

必要なパッケージ

導入するパッケージは以下の3つです

pip [https://pypi.python.org/pypi/pip] パッケージマネージャ

virtualenv [https://pypi.python.org/pypi/virtualenv] pythonの仮想環境構築ツール

virtualenvwrapper [https://pypi.python.org/pypi/virtualenvwrapper] virtualenvをより快適に使うためのsh拡張

ターミナルから以下のコマンドを順に実行します。

curl -O https://raw.github.com/pypa/pip/master/contrib/get-pip.py
sudo python get-pip.py
sudo pip virtualenv
sudo pip install virtualenvwrapper

virtualenvwrapperを有効にするために、.bashrcに以下の行を追加(osx+homebrewの場合)してsourceしてください。尚、virtualenvwrapper.shがインストールされるpathはOSによってかわりますので適切なpathを指定してください

source /usr/local/share/python/virtualenvwrapper.sh

Note

virtualenv-burrito [https://github.com/brainsik/virtualenv-burrito] を利用すれば上記の作業がコマンドライン一発で行えるようですが、筆者は使ったことがありません

仮想環境を構築

今回つくるwikiの名前をflaskiにするので、flaskiという名前の仮想環境をつくります。新たに仮想環境を用意するにはmkvirtualenvコマンドを使います。

mkvirtualenv flaski
New python executable in flaski/bin/python
Installing setuptools............done.
Installing pip...............done.
virtualenvwrapper.user_scripts creating /Users/kzfm/.virtualenvs/flaski/bin/predeactivate
virtualenvwrapper.user_scripts creating /Users/kzfm/.virtualenvs/flaski/bin/postdeactivate
virtualenvwrapper.user_scripts creating /Users/kzfm/.virtualenvs/flaski/bin/preactivate
virtualenvwrapper.user_scripts creating /Users/kzfm/.virtualenvs/flaski/bin/postactivate
virtualenvwrapper.user_scripts creating /Users/kzfm/.virtualenvs/flaski/bin/get_env_details
(flaski)localhost@kzfm:flask $

仮想環境に入っていることはプロンプトの先頭に(flaski)と表示されることでわかります。

その他のコマンド

virtualenv & virtualenvwrapper

コマンド | 説明
mkvirtualenv [仮想環境名] | 新たに仮想環境を用意します
rmvirtualenv [仮想環境名] | 仮想環境を削除します
lsvirtualenv | 仮想環境の一覧を表示します
workon [仮想環境名] | 仮想環境に入ります
deactivate | 仮想環境から抜けます

Flaskパッケージのインストール

FlaskとSQLAlchemyをインストールするにはpipコマンドを使います。

pip install flask
pip install sqlalchemy

インストールされたパッケージはpip freezeで出力出来ます。

Flask==0.9
Jinja2==2.6
SQLAlchemy==0.8.0b2
Werkzeug==0.8.3
wsgiref==0.1.2

例えば開発環境のパッケージ構成を本番環境に構築したい場合に

pip freeze > requirements.txt

で依存するパッケージを作っておいて、別のマシンにファイルをコピーして

pip install -r requirements.txt

とやればいいので便利です。

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

初めてのFlaskアプリ

まずは動かしてみよう！

Flaskアプリ開発用のディレクトリを用意します。

mkdir flaski
cd flaski

以下のソースコードをapp.pyとして保存します。Flask [http://flask.pocoo.org/] のサイトに載っているものです。

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

Note

インデックスルートにアクセスした場合にHello Worldという文字列を返すように設定して(4-6行目)、app.runでサーバーを起動させています。

さぁ、動かしてみましょう、python app.pyと叩けば127.0.0.1:5000でウェブサーバーが起動します。

$ python app.py
 * Running on http://127.0.0.1:5000/

ブラウザで localhost [http://127.0.0.1:5000/] にアクセスすると次のように表示されるはずです

テンプレートの導入

単なる文字列を出力するだけではものたりないので、今度はテンプレートエンジンを使い、もう少し複雑なHTMLを表示してみます。Flaskのテンプレートエンジンは Jinja2 [http://jinja.pocoo.org/docs/] ですので、これを使います。

最終的なファイルとディレクトリの構成を表示しておきます。

Flaskアプリの場合にはテンプレートはtemplatesディレクトリ、画像ファイルやjs,cssなどの静的ファイルはstaticディレクトリに配置することになりますので注意してください。

.
├── app.py
├── static
│   └── snake.jpg
└── templates
    ├── index.html
    └── layout.html

まずは snake.jpg [https://github.com/kzfm/flaski/tree/master/static] をダウンロードしてstaticディレクトリに配置してください。

続いてtemplatesディレクトリにlayout.html,index.htmlの2つのファイルを用意します。

layout.html

<!DOCTYPE html>
<html>
  <head>
    <title>flaski</title>
  </head>
  <body>
    {% block body %}{% endblock %}
  </body>
</html>

index.html

{% extends "layout.html" %}
{% block body %}
<h1>{{title}}</h1>
<img src="{{url_for('static', filename='snake.jpg')}}" alt="snake"/>
{% endblock %}

{{}}で囲むと変数を展開することができます。今回はtitleをapp.pyのrender_templateで渡しています。url_forはエンドポイントを指定するとURLに展開する関数です。urlは極力ハードコードするのを避け、url_forを使うのがいいと思います。

app.pyはrender_templateをインポートしてhello関数で使うようにします。

また今回からデバッグモードで動かすようにapp.configをいじっています(3行目)。

ファイル更新時に自動リスタート

エラーがあればブラウザにデバッグのための情報が出力される

その際ブラウザからデバッグのために変数を出力したりできる

といったことができるので開発時には便利ですが、本番時には必ずfalseにしましょう。

from flask import Flask, render_template
app = Flask(__name__)
app.config['DEBUG'] = True

@app.route("/")
def hello():
    return render_template("index.html", title="Flaski")

if __name__ == "__main__":
    app.run()

先ほどと同様にhttp://127.0.0.1:5000/にアクセスすると今度は先程とは異なり、HTMLが表示されます。

Flaskとは何か？

FlaskでJinja2のテンプレートエンジンを使ってみましたが、 Flaskは何であって何でないか [http://flask.pocoo.org/docs/design/#what-flask-is-what-flask-is-not] によると

Flask itself just bridges to Werkzeug to implement a proper WSGI application and to Jinja2 to handle templating.

つまり、FlaskはJinjaをテンプレートエンジンにすえたウェブアプリケーションのためのマイクロなフレームワークです。フルスタックなウェブアプリケーションフレームワークにあるような、モデル操作のためのORマッパーは用意されていないため、好きなものを利用できます。

次からはORマッパーとしてSQLAlchemyを使っていきます。

Jinja2の補足

Jinja2自体はその上に別の構文を被せることができるので、 Jade [http://jade-lang.com/] や Haml [http://haml.info/] も利用することができます。

Hamlish-jinja [https://github.com/Pitmairen/hamlish-jinja]

pyjade [https://github.com/SyrusAkbary/pyjade/tree/master/pyjade]

個人的にはインデントでネストを表現するJadeの構文を気に入っているので PyJadeを使うことが多いですが、Jinjaの機能の一部が使えなくなるので自分にあったものを使うと良いでしょう。

ここまでのGitHub

ソースコードはGitHubに用意してあるので、diffをチェックするとより分かりやすいかと思います。

Hello Worldまで [https://github.com/kzfm/flaski/commit/0c30e3f2e0cce020a5dc785939e65f262b025c74]

テンプレート導入 [https://github.com/kzfm/flaski/commit/1c942322f5507b7801ba9afecbf3bd0344da0411]

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

SQLAlchemyを使う

SQLAlchemy [http://www.sqlalchemy.org/] はPython製のORマッパーです。今回はこのSQLAlchemyを利用してModelをデザインしていきますが、そのなかの declarative [http://docs.sqlalchemy.org/en/rel_0_8/orm/extensions/declarative.html] という仕組みを利用します。

これを利用するとPythonのクラスをリレーショナルデータベースに簡単にマップ出来るので便利です。

まず、modelは一ヶ所で管理したいのでディレクトリを新たに用意します。

mkdir flaski # flaski/flaski
cd flaski
touch __init__.py

Note

pythonはディレクトリに __init__.pyというファイルを置くとimportできるようになります。

ディレクトリをmoduleとして呼び出したいので、__init__.pyという空のファイルを用意します。flaski/flaski以下は次のファイル構成になります。

├── flaski
    ├── __init__.py
    ├── database.py
    └── models.py

Wikiをつくるのでタイトル、内容、タイムスタンプをデータベースに記録できるようにします。

models.py

from sqlalchemy import Column, Integer, String, Text, DateTime
from flaski.database import Base
from datetime import datetime

class WikiContent(Base):
    __tablename__ = 'wikicontents'
    id = Column(Integer, primary_key=True)
    title = Column(String(128), unique=True)
    body = Column(Text)
    date = Column(DateTime, default=datetime.now())

    def __init__(self, title=None, body=None, date=None):
        self.title = title
        self.body = body
        self.date = date

    def __repr__(self):
        return '<Title %r>' % (self.title)

WikiContentクラスではそれぞれの属性の型や主キー、ユニークかどうかなどを指定しています。__repr__メソッドでは出力した時にどう表示させるかを定義しています。

続いてdatabase.pyを用意します

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import os

databese_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'wiki.db')
engine = create_engine('sqlite:///' + databese_file, convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()

def init_db():
    import flaski.models
    Base.metadata.create_all(bind=engine)

database.pyでは実際に利用されるデータベースエンジンの設定をしています。今回はSQLiteを使っています(6行目) init_dbはデータベース初期化のための関数です。

データベース初期化

python対話環境を起動してデータベースを初期化します。

>>> from flaski.database import init_db
>>> init_db()

flaski/flaski/wiki.dbが作成されているので確認してみます。

sqlite3 flaski/wiki.db
sqlite> .schema
CREATE TABLE wikicontents (
    id INTEGER NOT NULL,
    title VARCHAR(128),
    body TEXT,
    date DATETIME,
    PRIMARY KEY (id),
    UNIQUE (title)
);

続いて対話環境からデータを登録してみます

>>> from flaski.database import db_session
>>> from flaski.models import WikiContent
>>> c1 = WikiContent("Flask", "micro framework")
>>> db_session.add(c1)
>>> db_session.commit()
>>> c2 = WikiContent("python", "pppython")
>>> c3 = WikiContent("kobito", "kakure-momojiri")
>>> db_session.add(c2)
>>> db_session.add(c3)
>>> db_session.commit()

確認します。

$ sqlite3 flaski/wiki.db
SQLite version 3.7.10 2012-01-16 13:28:40
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> select * from wikicontents;
1|Flask|micro framework|2013-03-03 09:23:03.721257
2|python|pppython|2013-03-03 09:23:03.721257
3|kobito|kakure-momojiri|2013-03-03 09:23:03.721257

SQLAlchemyを利用してSQLiteにデータが登録されることを確認できました。

ここまでのGitHub

モデルの設定 [https://github.com/kzfm/flaski/commit/47c3730f437962e9a6426e12d9b26b5c83bc042f]

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

Flaskでモデルを使う

GETに対応する

前章で用意したモデル [http://blog.kzfmix.com/entry/1362214149] をFlaskで使うためにモデルをimportします。ルートにアクセスしたらコンテンツのタイトル一覧を表示し、/titleにアクセスしたら内容を表示するようにします。

app.py

from flask import Flask, render_template, abort
from flaski.models import WikiContent

app = Flask(__name__)
app.config['DEBUG'] = True

@app.route("/")
def hello():
    contents = WikiContent.query.all()
    return render_template("index.html", contents=contents)

@app.route("/<title>", methods=["GET"])
def show_content(title):
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        abort(404)
    return render_template("show_content.html", content=content)

if __name__ == "__main__":
    app.run()

Note

database.pyでBaseクラスにqueryメソッドを追加しておいたので、 WIkiContentクラスから、allで全件検索、filter_byでフィルタリングができるようになっています。詳しくはSQLAlchemyのドキュメントを参照してください。

app.routeでは<>で囲むことで/以下の文字列を、変数titleとしてキャプチャしています。 show_content関数は、モデルに対しtitle名で検索をかけ、データが存在しない場合は404エラーを返します(abort(404))。

index.html

{% extends "layout.html" %}
{% block body %}
<h1>Flaski</h1>
<img src="{{url_for('static', filename='snake.jpg')}}" alt="snake"/>
<ul>
{% for content in contents %}
<li><a href="{{url_for('show_content', title=content.title)}}">{{content.title}}</a></li>
{% endfor%}
</ul>
{% endblock %}

for文でcontentsをループさせ、タイトルをリンク付きでリスト表示させる処理を追加しています。タイトルはapp.pyから送る必要がないので、ハードコードするように変更しました。

新たに個別のコンテンツ用のテンプレートも用意します

show_content.html

{% extends "layout.html" %}
{% block body %}
<h1>{{content.title}}</h1>
<div>{{content.body}}</div>
<p>{{content.date}}</p>
{% endblock %}

コンテンツがリスト表示されることを確認します。

ここまでのGitHub

モデルを扱う [https://github.com/kzfm/flaski/commit/9a7b0f5335ce7c516d4663b8301112bfd5072c6b]

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

POSTメソッドでデータを変更できるようにする

変更するのはapp.pyのみです。

前章でSQLAlchemyを使ってデータを取得できるようになったので、今回はデータの追加、更新をできるようにします。そのためにdb_sessionが必要なのでflaski.databaseモジュールをimportします。またリクエストの最後に、セッションの後片付けをする必要があるのでshutdown_sessionメソッドを追加します。

コードの説明

from flask import Flask, render_template, abort, request
from flaski.models import WikiContent
from flaski.database import db_session
from datetime import datetime

app = Flask(__name__)
app.config['DEBUG'] = True

@app.teardown_request
def shutdown_session(exception=None):
    db_session.remove()

@app.route("/")
def hello():
    contents = WikiContent.query.all()
    return render_template("index.html", contents=contents)

@app.route("/<title>", methods=["GET"])
def show_content(title):
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        abort(404)
    return render_template("show_content.html", content=content)

# 続く

POST時にformにアクセスするのでflaskからrequestをインポートしています(1行目)。更新時間を書き換えたいのでdatetimeモジュールもインポートしています。

前回GETでアクセスした場合にデータを取得するようにしましたが、同じURLにPOSTでアクセスした場合には新規追加または更新を行いたいのでapp.routeのメソッドを限定します。これはmethodsというオプションに利用したいメソッドを明示的に指定すればよいです。下のコードはGETのアクセスに限定しています。

@app.route("/<title>", methods=["GET"])

続いてPOST用のコードが続きます。titleでデータを検索して存在しない場合には追加、存在する場合は更新処理をしています。bodyはformで渡されることを想定しています。今度はmethodsでPOSTメソッドのみに限定しています。

# 続き
@app.route("/<title>", methods=["POST"])
def post_content(title=None):
    if title is None:
        abort(404)
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        content = WikiContent(title,
                              request.form["body"]
                              )
    else:
        content.body = request.form["body"]
        content.date = datetime.now()
    db_session.add(content)
    db_session.commit()
    return content.body

if __name__ == "__main__":
    app.run()

これでサーバー側は新規追加や更新にも対応しました。

Note

methodsはGET,POST,PUT,DELETEといったRESTfullなサービスを構築するためのメソッドも使えます。

HTTP経由でデータの追加や更新をテストする

HTMLでフォームを用意するのは面倒なので、httpie [https://pypi.python.org/pypi/httpie/] を利用してコマンドラインから操作してみます。

pipでインストールします

pip install httpie # $HOME/.virtualenvs/flaski/binにインストールされます

新規データを登録します。app.pyを実行してサーバーを起動しておくのを忘れないでください。

$ http --form POST http://localhost:5000/httpie body="test from httpie"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 16
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Sun, 03 Mar 2013 04:09:36 GMT
test from httpie

–formオプション(または-f)をつけるとformで送られます。データが新規に追加されていることをブラウザで確認します。

データが既に存在する場合には更新されることも確認します。

$ http --form POST http://localhost:5000/httpie body="modified from httpie"

ここまでのGitHub

POSTメソッドでデータを変更 [https://github.com/kzfm/flaski/commit/898adb0827b90d8bc1f9cd0fd01e53f3a52b45a8]

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

next |

previous |

flask 0.1 documentation »

AjaxなWikiにする

reStructuredTextをHTMLにコンバートする

まずはreStructuredTextをHTMLにコンバートするためにdocutilsをインストールします。

pip install docutils

modelを修正してreStructuredTextをHTMLにコンバートするメソッドを追加します。

from sqlalchemy import Column, Integer, String, Text, DateTime
from flaski.database import Base
from datetime import datetime
from docutils.core import publish_parts
overrides = {'doctitle_xform': 0,
             'initial_header_level': 2}

class WikiContent(Base):
    __tablename__ = 'wikicontents'
    id = Column(Integer, primary_key=True)
    title = Column(String(128), unique=True)
    body = Column(Text)
    date = Column(DateTime, default=datetime.now())

    def __init__(self, title=None, body=None, date=None):
        self.title = title
        self.body = body
        self.date = date

    def __repr__(self):
        return '<Title %r>' % (self.title)

    @property
    def html(self):
        parts = publish_parts(source=self.body,
                              writer_name="html",
                              settings_overrides=overrides
                              )
        return parts['html_body']

@propertyデコレータを使うことでcontent.html()とメソッド呼び出しではなく content.htmlとプロバティとしてアクセスできるようになります。 setting_overridesしているのはタイトルをh1要素にしているので、トップレベルのヘッダーをh2から始めたいからです。

postした時の戻り値もhtmlにコンバートしたものにします。

@app.route("/<title>", methods=["POST"])
def post_content(title=None):
    ...略...
    return content.html

httpieでちょっとテストしてみます

$ http --form POST http://localhost:5000/rsttest body="rst **strong** and *italic*"
HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 85
Server: Werkzeug/0.8.3 Python/2.7.3
Date: Sun, 03 Mar 2013 06:29:59 GMT

<div class="document">
<p>rst <strong>strong</strong> and <em>italic</em></p>
</div>

正しく変換されていますね。

テンプレートを修正する

ブラウザでアクセスした場合にもきちんとHTMLが表示されるようにJinja2テンプレートを修正します。

show_content.html

{% extends "layout.html" %}
{% block body %}
<h1>{{content.title}}</h1>
<div>{{content.html|safe}}</div>
<p>{{content.date}}</p>
{% endblock %}

content.bodyをhtmlに変更するだけですが、そのままだとHTMLタグがエスケープされてしまうのでsafeフィルターを付ける必要があります。

クリックで書き換えられるようにする

wikiのコンテンツエリアをクリックした時にformの編集画面に切り替わるようにします。そのためにjQueryの jeditable [http://www.appelsiini.net/projects/jeditable] プラグインを利用します。

minifyバージョンをstaticディレクトリにダウンロードしておいてください。

jeditableをajaxで通信させたいので編集画面に切り替わった際にhtml変換する前のreStructuredTextをGETするためのAPIを用意するため app.pyに次の関数を追加します。

@app.route("/rst/<title>")
def show_rst(title):
    content = WikiContent.query.filter_by(title=title).first()
    if content is None:
        abort(404)
    return content.body

単にrstなデータを返しているだけですね。

続いてテンプレートも修正します。

show_content.html

{% extends "layout.html" %}
{% block body %}
<h1>{{content.title}}</h1>
<div class="editable_textarea">{{content.html|safe}}</div>
<p>{{content.date}}</p>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="{{url_for('static', filename='jquery.jeditable.mini.js')}}"></script>
<script>
  $(".editable_textarea").editable("{{content.title}}", {
      type   : 'textarea',
      submitdata: { _method: "post" },
      name     : 'body',
      loadurl  : '/rst/' + '{{content.title}}',
      rows     : 10,
      submit : 'OK',
      cancel : 'cancel',
      cssclass : "editable"
  });
</script>
{% endblock %}

jeditableで操作するためにdiv要素にクラスを追加しています。

bodyの最後にjQueryとjeditableを呼び出しています。先ほどapp.pyに追加した関数はloadurlで呼び出すようになっています。

動かしてみる

コンテンツをクリックすると編集画面になるので

日本酒に対する熱い思いをぶつけます。

ここまでのGitHub

rstのコンバートに対応 [https://github.com/kzfm/flaski/commit/095a6237b50f57b4a56602d93e799f30fb96229a]

テンプレートのrst対応 [https://github.com/kzfm/flaski/commit/19c59fa1214bce747ff7591766d627776a4bb258]

クリック編集画面の実装 [https://github.com/kzfm/flaski/commit/cbe66d6edcd95d0c610455071b9fecb67c565658]

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

previous |

flask 0.1 documentation »

やりのこしていること

まだかいていないところのリスト

書くかもしれないし書かないかもしれない

wikiに新規コンテンツの登録。

Sass + Compassをつかって見栄えをよくする

Flask拡張の話

デバッグのやりかた

テスト

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.

Navigation

index

flask 0.1 documentation »

Index

© Copyright 2013, Ohkawa Kazufumi. Created using Sphinx 1.1.3.