PythonのFlaskでデータベースを利用する方法(Flask-SQLAlchemy)
こんにちは!
僕は普段趣味としてプログラミングをぼちぼち勉強しています。
その中で学んだことや感じた事などを素人なりにまとめて、備忘録的な感じも含めてnoteに投稿させていただいてます。
僕が書いたこの記事が誰かの役に立てば嬉しいなと思います。
今回はPythonの『Flask』と『SQLite(Flask_SQLAlchemyを利用)』を使ってデータベースを構築してWebアプリで利用するやり方を少し勉強したので、簡単なToDo管理アプリを作成しながら説明しようと思います。
flaskでアプリを作るやり方を調べるとメインのプログラムやデータベース、テーブルのモデルなどでファイルを分けてアプリを作成している説明が多いと思います。
なので今回はテンプレート以外は1つのファイルにまとめて、できるだけ全体像がイメージ出来るような形で書いてみました。
まずはじめにFlask_SQLAlchemyって何?ってところから簡単に説明します。
Flask_SQLAlchemyとは
Flask_SQLAlchemyというのは、flaskからSQLAlchemyを簡単に利用する事ができるようにしたものです。
そしてそのSQLAlchemyというのは、データベースとやり取りをするライブラリになります。
SQLの文を書かずにSQLAlchemyのライブラリが持っている関数を利用する事で代わりにSQL文を生成してくれます。
このSQLAlchemyはORMと言って、Object Relational Mapping(オブジェクトの関連付け)を行ってくれるので、Pythonでデータベースを作成してそのままプログラム内でPythonオブジェクトとして利用できるみたいです。
データベースの操作やテーブルの概念などは勉強する必要があると思いますが、SQL文を覚えなくても良いので、Pythonを勉強していて少しデータベース使って何か作ってみたいなーという場合などに丁度いいのかなと思います。
余談ですがSQL(データベース)とAlchemy(錬金術)なので、「SQL文じゃないコード(Pythonコード)からSQL文を精錬する」という意味なのかなと勝手に思ってます。
ではToDo管理アプリを作成していこうと思います。
app.pyとtodo.htmlを作成する
まずどこか任意のディレクトリにapp.pyを作成して、その同階層にtemplatesディレクトリを作成してその中にtodo.htmlを作成します。
このtemplatesフォルダは、この名前じゃないといけないみたいです。
そしてapp.pyからtodo.htmlをレンダリングしてWebページを表示する所までとりあえずいきます。
flaskをインストールしていない場合はpipなどでインストールします。
pip install flask
では、いきましょう。
[app.py]
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('todo.html')
if __name__ == '__main__':
app.run()
最初にflaskのモジュール達をインポートしています。
次にFlaskのインスタンスを作成して変数appに割り当てています。
ここで引数に__name__を入れる事でflask自身がtemplatesディレクトリなどの場所を認識できるので、明示的にインポートのような事をしなくても勝手にtemplatesディレクトリを探してくれるようになります。
次に@app.route('/')としてその下に関数を定義する事でルーティングのパスに関数を紐付けるような形になります。
このコードで言うと「http://127.0.0.1:5000/」にアクセスするとrender_template('todo.html')を返す関数を実行する。という感じになります。
最後は、このファイルから実行されたらapp.run()を実行するというif文を書いています。
おまじないという感じで書いているのをよく見ますね。
次にtodo.htmlを作成していきます。
[todo.html]
<!doctype html>
<html>
<head>
<title>todo.html</title>
</head>
<body>
<h1>ToDo Application</h1>
</body>
</html>
かなり寂しいhtmlですが一旦これでいきます。
htmlについてはあまり説明しないのでご自身で調べて下さい。すみません、、、。
app.pyを実行したらサーバーが起動して、以下のような表示が現れると思います。
* Serving Flask app "todo_app" (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
1番下に書いてある「http://127.0.0.1:5000/」にアクセスするとtodo.htmlで書いた通りのページが表示されたかと思います。
サーバーを止めたい時は「Ctrl + C」を押すとサーバーが止まります。
次はデータベースのモデル定義とdbファイルの作成、テーブルにデータを追加などを順にやっていきます。
データベースのモデル定義とdbファイル作成
まず初めに「flask_sqlalchemy」をインストールしていない場合はpipなどでインストールします。
pip install flask_sqlalchemy
ではapp.pyにコードを追加していきます。
[app.py]
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.todo'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class ToDo(db.Model):
id = db.Column(db.Integer, primary_key=True)
todo = db.Column(db.String(128), nullable=False)
@app.route('/')
def index():
return render_template('todo.html')
if __name__ == '__main__':
app.run()
まず2行目の部分でSQLAlchemyをインポートしています。
app=Flask(__name__)の下の部分では、appのconfig(設定)を追加しています。
SQLALCHEMY_DATABASE_URIではデータベースを作成する為にデータベースの種類とファイル名を書いています。
今回で言うとSQLiteを使っていて、db.todoという名前のファイルで管理しようと思うので「sqlite:///db.todo」としています。
SQLALCHEMY_TRACK_MODIFICATIONSではデータベースでイベントシステムがSQLAlchemyのセッションへの変更を追跡します。と書いてありましたが無効にする事でリソース(メモリなど)の節約になるみたいです、よく分からないのもあるし無効でいきましょう。
db = SQLAlchemy(app)でSQLAlchemyのインスタンスを作成して変数dbに割り当てています。
引数にFlaskのインスタンスを渡す事でFlaskでSQLAlchemyを利用してデータベースを扱う事ができるようになります。
今後はこの変数dbを使ってデータベースを操作していく形になります。
class ToDo(db.Model):で、dbのModelを継承したテーブルのクラスを定義しています。
idの部分でprimary_key=Trueにしているのは、このidというのを主キーとして作成するよ、という指定です。
データベースの中で同じ値のものがあっても区別ができるように一意のデータを各レコード(行)に持たせておかないといけないので、このidというデータにその役割を持たせています。
これでデータベースのファイルを作成する準備が整ったのでdb.todoを作成します。
ターミナルなどでapp.pyの階層まで行って、Pythonのコンソールで
>>> from app import db
>>> db.create_all()
とするか、app.pyファイルの最後のif文の所で
if __name__ == '__main__':
db.create_all()
#app.run()
一時的にこのように変更してapp.pyを実行すると、同階層にdb.todoが作成されると思います。
ちなみにデータベースと言うとややこしそうなイメージがありますが、雰囲気はExcelみたいな形でイメージすれば良いのかなと思います。
これをExcelで言うとidの部分が「A列」で、todoの部分が「B列」といった具合です。
ちなみにデータベースで列は「フィールド(field)」や「カラム(column)」と言います。
行の事を「レコード(record)」と言います。
これでデータベースが作成できたので、flaskからデータベースにデータを追加出来るようにしていきます。
データの追加と表示
[todo.html]
<!doctype html>
<html>
<head>
<title>todo.html</title>
</head>
<body>
<h1>Todo Application</h1>
<!--追加-->
<form action="/add" method="POST">
<input type="text" name="todo" placeholder="input todo!!"/>
<input type="submit" value="Add"/>
</form>
<!------->
</body>
</html>
todo.htmlファイルにformタグでバックエンド側にデータをPOSTで送信できるようにコードを追加しました。
formタグのaction属性に/addを指定しているのでapp.pyで/add用のルーティングを作成して処理をします。
長くなるので追加したところ辺りだけ書いています。
[app.py]
#ファイルの先頭
from flask import Flask, render_template, request, redirect, url_for
#~~~~~~~途中省略~~~~~~~~~
@app.route('/')
def index():
return render_template('todo.html')
#以下追加↓
@app.route('/add', methods=['POST'])
def add():
todo = request.form['todo']
new_todo = ToDo(todo=todo)
db.session.add(new_todo)
db.session.commit()
return redirect(url_for('index'))
まずファイルの先頭でrequestとredirectとurl_forを追加でインポートしています。
@app.route('/add', methods=['POST'])でルーティングを作成しています。
methods=['POST']の部分で、クライアントからのPOSTリクエストを受け付けるようにしています。
todo = request.form['todo']の部分でリクエストのformタグ内のname='todo'からデータを取得して変数todoに割り当てています。
new_todo = ToDo(todo=todo)の部分では、先程作成したデータベースのモデルのToDoクラスをインスタンス化して、引数にキーワードでtodo=todoとして変数new_todoに割り当てています。
この時ToDoクラスのidには自動で数値が入ります。
db.session.add(new_todo)では変数new_todoをセッションに追加しています。
この時点ではデータベースにはまだデータを追加できていないのでdb.session.commit()として先程セッションにaddしたデータをデータベースに追加しています。
最後はreturn redirect(url_for('index'))でindex関数に紐づけられているルーティングにリダイレクトで転送するという処理をしています。
これでデータベースに追加出来るようにはなりましたが、データを表示していないので本当にデータベースにデータがあるのか分かりません。
なのでデータベースからデータをhtmlに渡して表示できるようにします。
app.pyのIndex関数を変更します。
[app.py]
@app.route('/')
def index():
data = ToDo.query.all()
return render_template('todo.html',data=data)
data = ToDo.query.all()でデータベースのToDoモデルのデータを全て検索して取得、その結果を変数dataに入れています。
return render_template('todo.html',data=data)の部分では、最後にtodo=todoという引数を渡して、html側でこの変数todoを扱えるようにしています。
この変数todoをtodo.html側で実際に展開して表示させてみましょう。
[todo.html]
<!doctype html>
<html>
<head>
<title>todo.html</title>
</head>
<body>
<h1>Todo Application</h1>
<form action="/add" method="POST">
<input type="text" name="todo" placeholder="input todo!!"/>
<input type="submit" value="Add"/>
</form>
<!--以下追加-->
<br>
{% for d in data %}
<p>{{d.id}} : {{d.todo}}</p>
{% endfor %}
<!---------->
</body>
</html>
<br>はただの改行タグなので気にしないでください。
Jinjaテンプレートエンジンは{% Python式 %}と、こういった書き方をするとhtml内でPythonの式などを利用する事ができます。
Python式が書けますが、Pythonと違ってインデントの有無でブロックを解釈出来ないので、{% endfor %}のように式がここで終わるよ!というコードも書く必要があるので注意が必要です。
また変数を扱う時は{{ 変数名 }}という形で書けば、その変数に割り当てられているデータがhtmlにレンダリングされて表示されます。
今回で言うと、サーバー側から渡されたtodoをfor文で1つずつ取り出して、そのidとtodoをpタグ内に埋め込んでいます。
for文なのでサーバー側から渡されたtodoの長さ分だけ繰り返されるのでループの回数分だけpタグが生成されます。
一度実行して、formから何か入力してAddしてみましょう!
先程変更したtodo.htmlが読み込まれてちゃんとテキストボックスとサブミットボタンが表示されていますね!
テキストボックスに何か入力してAddボタンでPOSTリクエストを送信してみるとapp.pyで定義されているadd関数が実行されて、入力した文字列がデータベースに追加されます。
そしてリダイレクトでindex関数に紐付くルーティング("/")を呼び出して、index関数ではデータベースのデータを取得してrender_template("todo.html",todo=todo)としているので変数todoに入っているデータベースのデータがtodo.htmlに渡されます。
そしてトップページに戻ってくると、さっきデータベースに追加したデータがJinjaテンプレートエンジンによって変換されて表示される。
という感じです。
ただこのままだと追加するだけして終わってしまうので、削除も出来るようにしたいと思います。
データベースの値を削除できるようにする
とりあえずtodo.htmlを変更します。
[todo.html]
<!doctype html>
<html>
<head>
<title>todo.html</title>
</head>
<body>
<h1>Todo Application</h1>
<form action="/add" method="POST">
<input type="text" name="todo" placeholder="input todo!!"/>
<input type="submit" value="Add"/>
</form>
<!--pタグの下にaタグを追加-->
<br>
{% for d in data %}
<p>{{d.id}} : {{d.todo}}</p>
<a href="/del_todo/{{ d.id }}">[ delete ]</a>
{% endfor %}
<!---------->
</body>
</html>
<a href="/del_todo/{{ d.id }}">[ delete ]</a>今追加aタグ(リンクのタグ)のhref属性に「/del_todo/{{d.id}}」と入れると、{{d.id}}の部分に各データのidが入ります。
これによってそれぞれのToDoに対するidが付与されたリンクが出来上がるので、リンクが押されたらサーバー側で処理してデータベースのデータを削除するようにします。
ちなみにリンクのタグは、基本的にGETリクエストを送信します。
次にapp.pyで/del_todo/{{ d.id }}に対する処理を作成します。
[app.py]
@app.route('/del_todo/<int:id>')
def del_todo(id):
del_data = ToDo.query.filter_by(id=id).first()
db.session.delete(del_data)
db.session.commit()
return redirect(url_for('index'))
@app.route('/del_todo/<int:id>')の部分で、先ほどの{{ d.id }}の値をパラメーターとして受け取るように指定しています。
ここでは関数定義の際に引数を指定しておくのを忘れないようにしてください。
del_data = ToDo.query.filter_by(id=id).first()の部分では、ToDoのテーブル内をidでフィルターをかけて検索して最初に見つかったレコードが返ってくるのでそれを変数del_dataに割り当てています。
あとはdb.session.~の2行でそのidが存在するレコードを削除しています。
最後にリダイレクトでトップページに戻しています。
これで実行してみます。
さっき追加したデータが表示されていますね。
各データの下に[delete]と書いたリンクも表示されていると思います。
実際に4番目のデータを削除してみようと思います。
[delete]を押すと、、、、、
4番目のデータが削除されて表示が消えましたね!
本当にこの処理で消えているのかどうか確認のために、一度全部のデータを消してから何か1つデータを追加してみます。
1番左に表示されている数字は各データのidです。
idはレコードごとに一意のものであるはずなので「1」と表示されているということはこれが最初のデータであると判断する事ができます。
なので[delete]を押すとちゃんとデータベースのデータも削除されているということになります。
これで簡易的なToDoアプリが完成しました!
最後に全体コードを載せておきます!
全体コード
[app.py]
from flask import Flask, render_template, request, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.todo'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
class ToDo(db.Model):
id = db.Column(db.Integer, primary_key=True)
todo = db.Column(db.String(128), nullable=False)
@app.route('/')
def index():
data = ToDo.query.all()
print(data)
return render_template('todo.html',data=data)
@app.route('/add', methods=['POST'])
def add():
todo = request.form['todo']
new_todo = ToDo(todo=todo)
db.session.add(new_todo)
db.session.commit()
return redirect(url_for('index'))
@app.route('/del_todo/<int:id>')
def del_todo(id):
del_data = ToDo.query.filter_by(id=id).first()
db.session.delete(del_data)
db.session.commit()
return redirect(url_for('index'))
if __name__ == '__main__':
#db.create_all()
app.run()
[todo.html]
<!doctype html>
<html>
<head>
<title>todo.html</title>
</head>
<body>
<h1>Todo Application</h1>
<form action="/add" method="POST">
<input type="text" name="todo" placeholder="input todo!!"/>
<input type="submit" value="Add"/>
</form>
<br>
{% for d in data %}
<p>{{d.id}} : {{d.todo}}</p>
<a href="/del_todo/{{ d.id }}">[ delete ]</a>
{% endfor %}
</body>
</html>
まとめ的なやつ
長かったですが、Flaskでデータベースを扱う方法や流れを僕なりに説明させていただきました!
今回は簡易的なToDoアプリを作成しましたが、ここから拡張していけばあなただけのアプリが作れると思います。
この記事を通して少しでもあなたの役に立っていたら良いなと思います!
最後まで読んでいただきありがとうございました!
では、またお会いしましょう!
この記事が気に入ったらサポートをしてみませんか?