LoginSignup
29

More than 5 years have passed since last update.

[Flask編メモ]HTMLもWeb構築もわからない素人がPython+FlaskでWebサービスを作ってみる

Last updated at Posted at 2017-04-02

これは、エンジニア未経験のど素人が、週末エンジニアとして
PythonとFlaskでWebアプリを作成するためのメモ記事です。

ランディングページ
[Flask] このページ
[Docker] Coming Soon…
[Web開発]http://qiita.com/yuusei/items/cf738b0849fc932a64e6

このページはFlask編のメインページであり、
モジュールやクラスの使い方など、実装する上で使う内容は主に Flask公式の日本語サイトに準拠しています。このサイトにはコピペするだけで一つのサービスが作れるような Githubのコード”flaskr.py” があり、ここでは特に Tutorial で見本コードが公開されているマイクロブログ(タイトルと本文だけの簡単なブログ。TwitterのTweet一つ一つにタイトルがついているようなもの)のコードを読みながら理解できたところを整理していく感じです。

なお、Flaskで作れる最小のウェブアプリ は9行だけの “Hello World”アプリがあります。コピペですぐできるのでお試しにどうぞ。

Flaskの基礎知識メモ

・FlaskとはWeb開発用のマイクロフレームワーク
・フレームワークとは「おもちゃ箱」のようなもの
・コンサルのフレームワークに入っている「おもちゃ」は「SWOT分析」などの戦略系
・Web製作者向けのフレームワークに入っている「おもちゃ」は数多の人がコードを書いたライブラリ
・コンサルも会社や業界によってフレームワークが異なるように、Webフレームワークは開発言語毎に存在
・Pythonなら”Django”が主流。All in One 気味のフレームワークで、中規模のものまでならいける。
・同じPythonのWebフレームワークでも”Flask”は”Django”に比べて「小規模開発目的」で「プログラミング言語的

Flaskで最低限必要なモジュールのリスト

※プログラミング素人なので、モジュールとクラスの違いがいまいちわからないのですが、
 ここではモジュールを「単体でも動作するコード」クラスを「単体では動作しないコード」と考えています。
 あるいは、クラスはモジュールの一部と考えています。
 ただ、ライブラリーとの違いもわからず、
 正しい定義があればコメ欄ででも教えていただきたく・・・

“flask”モジュール
・説明不要。Flaskを使えるようにするためのモジュール。
・厳密に言うと、この”flask”モジュールのクラス”Flask”や”request”をimportする事によってFlaskが使える。

これだけ。この”flask”だけで最低限のWebアプリが作れるようです。
参考:Flaskで作れる最小のウェブアプリ

ですがまぁ、Tutorial にあるマイクロブログアプリを作るためにはあと二つほど必要なモジュールがあります。
”os”モジュール
・一般的なOSに備わっている機能を再現するモジュール。
・WindowsOSならコマンドプロンプト、MacOSならTerminalに打ち込むUnix系コマンドを使える
・完備はしていないだろうけれど、まぁ主要なものは載っている

“sqlite3”モジュール
・DBに接続し、操作する為のモジュール。
・ウェブサービスをリリースすると、ユーザーIDと属性をRDB形式で登録する必要が出てくる。それ。
・名前の通り、SQLliteの使い方と同様の操作ができる。
・別にSQLやDB接続のアプリとしてこれを必ず使う必要はない。Tutorialではこれを使ってるだけ。

 (どうでもいいのだけど、この記事を書いている時に「こいつSQL書けねぇんだなって思った」と言う会話が耳に入ってきて、一瞬ドキっとしました)

そして、これらのモジュールの中にあるクラスを駆使してマイクロブログアプリケーションを作ります。

flask モジュールに格納されている Flask 以外のモジュール・クラス・ライブラリ・オブジェクトのリスト(Tutorialで使われているもの)

“Flask”
・Flaskを使うためのクラス。
・アプリケーションを作る宣言をするためのもの。
・app = Flask(name)などで使う

“request”
・Pythonに装備されているHTMLを扱うためのコマンドライブラリ
urllibとかがわかりにくいから生み出されたらしい

“session”
・log-in や log-out を管理するためのものらしい。

“g”
・FlaskオリジナルのDB接続を保管するオブジェクト
・一回接続の形式を保存すると、別の関数から利用可能になる。

“redirect”
HTTPのリダイレクトを行うもの
・URL変更のお知らせ的なそれ

“url_for”
・URLを生成するためのもの。
動的なURL生成のためにも使えるらしい。新規ユーザーのマイページ作成に丁度良さそう。

“abort”
・エラーをクライアントに(?)報告するもの
・いわゆるクライアントエラーを表示するために使う

“render_template”
・Templateから必要なテンプレートを呼び込んで、レンダリング=プログラムコードを画像として読み換えるするためのもの
・Jinja2から取ってくるんだろうなぁと ここ を見て思った。

“flash”
フラッシュ を表示して、クライアントに文字/画像を届ける方法

こんなところ。これで、flaskr本体のflaskr.pyはできています。

ディレクトリ構造

Web開発のWebサービスのディレクトリ構造メモに、

・最適な構造なんてない。あるのはRoot Directory( / )のみ

と書きましたが、それだけではあまりに野放図なのでFlaskはディレクトリ構造をこのようにしています

・メインモジュールやデータベーススキーマを格納するための、フォルダ格納用”/flaskr”フォルダ
・”flask”フォルダ直下にCSSやJSを置いて、ユーザーに利用してもらうためのフォルダ”/static”
・同じく”flaskr”直下にJinja2テンプレートを探すためのフォルダ”/templates”

の3つです。
さらに、必要なファイルも含め、
ディレクトリ構造は下記の様になります。

Flaskr_DirectryTree.png

データベースの設計(スキーマ)について

Tutorial で紹介しているマイクロブログアプリに必要なテーブルは一つだけになります。

Tutorialのデータベーススキーマ作成コマンドにインラインでコメントをつけておきます。

drop table if exists entries;

#既に”entries”という名前のテーブルがあった場合は削除する
#新しくテーブルを作成するためのマナー
create table entries (
#{};の中に書き込まれた内容で”entries”と言うテーブルを作成する宣言
id integer primary key autoincrement, #カラム”id”整数型の主キーとして使う(自動採番)
title string not null, #カラム”title”を文字列として定義する。Null値は不可
text string not null #カラム”text”を文字列として定義する。Null値は不可
);

先にこのサービスの完成UIを見ると、下記の様になっています。
※[公式日本語サイト]https://flask-docs-ja.readthedocs.io/en/latest/tutorial/introduction/) から引用

flaskr_top_image.png

ここにタイトルと本文を入力すると、自動採番でidが振られたあと、
投稿が反映されるのがわかります。

例えば、この画面を拡張して投稿日を反映させたければ、
事前に上記スキーマに一文入れて、テーブルにautoincrementなdate 欄を用意する事になります。

このスキーマは”schema.sql”ファイルとして、flaskrディレクトリ直下に格納します。

本体 "flaskr.py"のパーツ毎の説明

アプリの設定ファイルをflaskr.pyとして同じ階層(flaskr直下)に入れています。
同様にインラインでコメントをつけていきます。
※Githubの使い方がいまいちわかってないので、見づらくて申し訳ありません。使い方がわかったら、修正します。

アプリの宣言とコンフィグ

""" create our little application :)"""
app = Flask(name)
#インポートしたFlaskクラスから”name”名のインスタンス(今回はWSGI)を作る
#単体のモジュールとして使う場合は、インポート先の本体の”main”と干渉しない様に”name”にする
 # Load default config and override config from an environment variable
app.config.update(dict(
#アプリのコンフィグ(設定)を辞書(dict)方式で定義することを宣言
DATABASE=os.path.join(app.root_path, 'flaskr.db'), #DBはrootパスの”flaskr.db”を使う
DEBUG=True, #デバグする
SECRET_KEY='development key', #クライアント側のセッションを安全に保つキー
USERNAME='admin', #管理者権限で入るための名前
PASSWORD='default' #管理者権限で入るためのPW
))
app.config.from_envvar('FLASKR_SETTINGS', silent=True)
#コンフィグを別ファイルにまとめて保存した時、参照するためのコード
#silent=Trueで設定されていないものはFlaskに渡されない様にする。

これで、設定は終わりです。次に、DBに接続するためのコマンド

DB接続と初期化

def connect_db():
#DBに接続する関数を”connect_db”として定義
#接続し、データを持つ形式をtaple->dictに変更、そしてdict型のrowをrvとして返す
"""Connects to the specific database."""
rv = sqlite3.connect(app.config['DATABASE']) #app.configで設定したDBに接続する
rv.row_factory = sqlite3.Row #rowをtapleではなくdict形式で持てる様にする
return rv

この定義済み”connect_db”関数を使って、新たな接続の前に一旦DBを初期化する”init_db”を定義します。

def init_db():
"""Initializes the database."""
db = get_db() #dbにデータベースを変数として格納
with app.open_resource('schema.sql', mode='r') as f: #読専モードでスキーマを”f”に格納
db.cursor().executescript(f.read()) #f.read()をsqlとして使用し、カーソルとして保管
db.commit() #dbにコミットする->初期化される

SQLのカーソルについてはこちら を参照のこと。カーソルとは、SQL上で1行ごとにデータを処理できる様にするための機能のようです。
ただ、spliete3のcursorについての説明を読むと 単純にSQLのSelect文などの代わりにcursor().executescriptを使っている感じがします。さて、このinit_db()関数をCUIで使える様にするには、次の一工夫が必要です。

@app.cli.command('initdb') #コマンドラインインターフェースに新しいコマンド”initdb”を追加
def initdb_command(): # initdbと打ち込むと、init_db()関数を呼び出し”初期化しました”と出る
"""Creates the database tables."""
init_db()
print('Initialized the database.')

これでCUIからDBを初期化する環境が整いました(テスト環境とかだと便利なんでしょうか?)。そして、そもそも論としてDBに接続するための関数を定義していないので定義します。

def get_db():
"""Opens a new database connection if there is none yet for the
current application context.
"""
if not hasattr(g, 'sqlite_db'):
g.sqlite_db = connect_db()
return g.sqlite_db

この関数は初回でDBに接続すると、その設定を保存し「リクエスト毎にDBに接続」と言う作業を行います。例えばこのあたりを参考にしています 。そして、接続したら切断もしなければならんと言うことで、下記で切断を定義します。

@app.teardown_appcontext
def close_db(error):
"""Closes the database again at the end of the request."""
if hasattr(g, 'sqlite_db'):
g.sqlite_db.close()

どうでもいい話なんですが、最初このhasattrがなんなのかわからなかったんですが、どうもpythonのbuilt in関数の様です。

The arguments are an object and a string. The result is True if the string is the name of one of the object’s attributes, False if not. (This is implemented by calling getattr(object, name) and seeing whether it raises an exception or not.)
from Python-Official

hasattr(object, name)で使うやつで、nameがobjectに内包されている場合Trueを返すみたいです。この場合、sqlite_dbがopenしている(接続したことがある)場合、g.sqlite_db.close()で閉じるもののようです。この get_db()とclose_db()にinitdbでdb関連の処理は終わりです。

ビュー関数/ログイン/ログアウト

続けて、アプリの画面上にDBの内容(投稿内容)を表示するためのビュー関数を4つ定義します。
それぞれ「(全)エントリーの表示」「新しいエントリーの追加」「ログイン」と「ログアウト」です。

では一つずつ・・・サクッと終わらせます。

(全)エントリーの表示

@app.route('/')
def show_entries():
db = get_db()
cur = db.execute('select title, text from entries order by id desc')
entries = cur.fetchall()
return render_template('show_entries.html', entries=entries)

ここで見ておきたいのは “cur = db.execute('select title, text from entries order by id desc’)”ですね。SQLわかる人なら簡単に理解できると思うのですが、このコマンド()内がSQLになってます。ので、execute(“SQL文”)でdbに対してsqlを実行しています。

select
title
, text
from entries
order by id desc

多分ちょっと前に書いたカーソル文(db.cursor().executescript(f.read()))と関係があるのでしょう。
ともあれ、ここを書き換えて”fetchall()”で取り出した要素を”render_template”でレンダリングしている形になります。レンダリングのルールは”show_entries.html”で定義していますね。
拡張版として、日付を取り出したければ、DBスキーマと上記のSQLに”date”を加えて、”show_entries.html”を書き換え日付を表示させるの必要があると言うことです。

新しいエントリーの追加

@app.route('/add', methods=['POST'])
def add_entry():
if not session.get('logged_in'):
abort(401)
db = get_db()
db.execute('insert into entries (title, text) values (?, ?)',
[request.form['title'], request.form['text']])
db.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))

機能としては add_entry()関数として下記の流れを定義しています。

「ログインしてなければ401を返す」
->「ログインしていればDBに接続」
->「接続したDBに新しいエントリとして、”title”と”text”を入れるコマンドを実行する」
->「commit()で処理を確定」
->「エントリーが投稿された旨をフラッシュで表示」
->「ブラウザを再読み込みして”show_entries”を呼び出し全てを再表示」

です。

ログイン

@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if request.form['username'] != app.config['USERNAME']:
error = 'Invalid username'
elif request.form['password'] != app.config['PASSWORD']:
error = 'Invalid password'
else:
session['logged_in'] = True
flash('You were logged in')
return redirect(url_for('show_entries'))
return render_template('login.html', error=error)

機能としては login()関数として下記の流れを定義しています。

「エラー処理を無しにする」
->「POSTがリクエストされているかの確認」
->「ユーザー名が既存のユーザーリストにあるかチェック(なければエラーを返す)」
->「PWが既存のPWにあるかチェック(なければエラーを返す)」
->「ログインセッションをTrueにする」
->「ログイン成功をフラッシュで表示」
->「ブラウザを再読み込みして”show_entries”を呼び出し全てを再表示」
(->エラー処理がNoneでない時、ログインエラー時の画面をテンプレートからレンダリングする)

です。

ログアウト

@app.route('/logout')
def logout():
session.pop('logged_in', None)
flash('You were logged out')
return redirect(url_for('show_entries'))

機能としては logout()関数として下記の流れを定義しています。

「logged_inをNoneに戻す」
->「フラッシュで”ログアウトしました”を表示」
->「ブラウザを再読み込みして”show_entries”を呼び出し全てを再表示」

です。意外とあっさり。

これで flaskr.pyの中身は一通り網羅しました。後はHTMLの話になるので、ここで書くのではなく、
Web開発 の中にHTML及びCSSの枠へのリンクを貼ることにします。

続く・・・

その他のWebフレームワーク簡易メモ
・Rubyなら”Ruby on Rails”。開発速度の速さが売り
・PHPなら”CakePHP”。世界で見ると「最も多くのWebアプリが導入」しているらしい
・Javaなら”Spring”とか”Play”とか。何やら群雄割拠から淘汰のフェーズらしい
・JavaScriptなら”AngularJS”。一枚で完結するアプリ開発に向いてるらしい。スライドストーリーとか?
・CSS/JSなら”Bootstrap”。Twitterが提供しているらしい

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
29