エンジニアライフスタイルブログを運営しているミウラ(@miumiu06171)です。
普段はフリーランスでシステムエンジニアをしております。
今回は、こちらの記事に続いてPythonのFlask入門者向けにデータベースと連携したWebアプリケーションを作る流れを紹介していきます。
Webアプリケーションの例として、こちらのFlask公式ページのチュートリアルのようなブログ記事作成アプリを取り扱います。
FlaskでWebアプリを作成するためにVisual Studio Code (VS Code) を使用しているため、同様に確認したい方は、こちらの記事でVS CodeでPythonの開発環境を構築してみてください。
PythonのFlaskでDB(データベース)と連携しないWebアプリケーションを作成
ここでは、PythonのFlaskでDBと連携しないシンプルなWebアプリケーションの作成方法を解説します。
フォルダ構成
まずは、FlaskでWebアプリを作成するため、以下のフォルダ構成でapp.py、index.htmlを作成します。
1 2 3 4 |
|- application |- app.py |- templates |- index.html |
Flaskでは、HTMLテンプレートファイルをtemplatesフォルダ配下に保存するというルールがあります。
ルーティングとビュー(View)を定義するapp.pyを作成
フォルダ構成が整ったら、app.pyにルーティングとビューを定義していきます。
1 2 3 4 5 6 7 8 |
from flask import Flask from flask import render_template app = Flask(__name__) @app.route('/') def blog(): return render_template('index.html') |
上記サンプルコードでは、ルート(‘/’)にアクセスがあると、Viewとしてblog関数を実行します。
blog関数内では、index.htmlをHTMLテンプレートファイルとして参照し、レスポンスを返しています。
HTMLテンプレートファイル(index.html)を作成
次にHTMLテンプレートファイルを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>ブログアプリケーション</h1> <article> <h2>記事タイトル1</h2> <p>2020年12月7日 15:47</p> <p>記事1の内容です</p> </article> <article> <h2>記事タイトル2</h2> <p>2020年12月14日 09:11</p> <p>記事2の内容です</p> </article> </body> </html> |
12行目~21行目が記事一覧に相当するHTMLコードになります。
これでシンプルなWebアプリの作成は完了です。
動作確認
作成したシンプルなWebアプリの動作確認を行うため、VSCode上のPowerShellで以下のコマンドを実行し、Flaskサーバを起動してください。
1 2 3 |
$env:FLASK_APP = "app" $env:FLASK_ENV = "development" flask run |
Flaskサーバが起動したら、「http://127.0.0.1:5000/」のローカルホストのルート(‘/’)にアクセスしてみてください。
上記のようにブラウザ表示されたらオッケーです。
PythonのFlaskでDB(データベース)と連携したWebアプリケーションを作成
次は、PythonのFlaskでDBと連携したWebアプリケーションの作成方法を解説します。
こちらの前回記事でも説明したとおり、上図のようにFlaskのモデル(Model)とデータベース(DB)を連携してViewをレスポンスとして返すには、O/RマッパーやDBドライバが必要でしたね。
O/Rマッパーについては、今回「flask_sqlalchemy」を使います。
DBドライバについては、DBがSQLiteなのでSQLite用DBドライバを使います。
SQLite3インストール
SQLite3のインストールについては、こちらの記事を参照してください。
flask_sqlalchemyインストール
DBを操作するには、通常SQL文を使う必要があるが、SQLの文法など覚えることは面倒であり、各DBによって書き方が異なる場合もあって大変です。
そこで、「SQLAlchemy」というO/Rマッパー(Mapper)を使うことで、SQL文を使わずにPythonでDBを操作します。
「SQLAlchemy」を使うと、各DBによって書き方が異なる部分についても、DB操作が抽象化されており、DBの種類や文法に関係なく、同じコードで動作させることができます。
それでは、早速「SQLAlchemy」をインストールしていきましょう。
flask用SQLAlchemyとして「flask_sqlalchemy」というPythonパッケージが存在するので、以下のpipコマンドでインストールを行ってください。
1 |
pip install flask_sqlalchemy |
DBのテーブルを定義
DBのテーブルを定義するため、テーブルのカラム(項目)を整理しましょう。
上図のようにブログ記事が複数あり、構成要素が「記事タイトル」「記事作成日付」「記事の内容」の三要素であることがわかります。
この三要素をDBのテーブルに定義していきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from flask import Flask from flask import render_template, request, redirect from flask_sqlalchemy import SQLAlchemy from datetime import datetime import pytz app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' db = SQLAlchemy(app) class BlogArticle(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50), nullable=False) body = db.Column(db.String(500), nullable=False) created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone('Asia/Tokyo'))) @app.route('/') def blog(): return render_template('index.html') |
3行目では、O/Rマッパーであるflask_sqlalchemyをインポートして読み込んでいます。
7行目から9行目では、FlaskのWebアプリ(app)とSQLite(DB)の連携を行い、DBを操作するインスタンス(db変数)を作っています。
12行目から16行目では、db.Column()メソッドを使って、DBのテーブルのカラムを作成しています。
db.Column()メソッドの第一引数にはカラムの型、第二引数以降にはオプションを指定します。
12行目では、一つ一つの記事を識別するための要素として「id」を主キーとして定義しています。
13行目から16行目では、「記事タイトル」「記事の内容」「記事作成日付」の三要素を、それぞれ「title」「body」「created_at」として定義しています。
なお、16行目のdatetime.now()メソッドの使い方について知りたい方は、こちらの記事を参照してください。
このようにPythonのクラスを扱いながら、DBのテーブルを定義することができます。
DBを作成
DBのテーブルの定義ができたら、実際にDBを作成してみましょう。
Pythonを対話モードで起動
VSCode上のPowerShellで「python3」コマンドを実行すると、Pythonが会話モードで起動します。
1 |
python3 |
コマンド実行後にPowerShell上に「>>>>」と出ると、対話モードで起動されています。
flask_sqlalchemyのインスタンス(db)をインポート
app.py内で定義したflask_sqlalchemyのインスタンスであるdb変数をインポートします。
1 |
>>>> from app import db |
DBの作成
DBを作成するには、「db.create_all()」コマンドを実行します。
1 |
>>>> db.create_all() |
これにより、app.py内のBlogArticleクラスに対応したDBのテーブルが作成されます。
DBの作成確認
DBの作成が完了したら、「blog.db」ファイルができていることを確認します。
1 2 3 4 5 |
|- application |- app.py |- blog.db |- templates |- index.html |
Pythonの対話モードを終了
DBの作成確認ができたら、以下の「quit()」コマンドでPythonの対話モードを終了しましょう。
1 |
>>>> quit() |
DBと連携したWebアプリケーションの作成
DBと連携したWebアプリを作成するため、Pythonファイル(app.py)からDBのデータを取得する方法、およびHTMLテンプレートファイルの分割で効率化する方法を解説していきます。
DBのデータ取得(<クラス名>.query.all())
まず、app.pyからDBのデータを取得する方法からみていきます。
app.pyの修正
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
from flask import Flask from flask import render_template, request, redirect from flask_sqlalchemy import SQLAlchemy from datetime import datetime import pytz app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///blog.db' db = SQLAlchemy(app) class BlogArticle(db.Model): id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(50), nullable=False) body = db.Column(db.String(500), nullable=False) created_at = db.Column(db.DateTime, nullable=False, default=datetime.now(pytz.timezone('Asia/Tokyo'))) @app.route('/') def blog(): # DBに登録されたデータをすべて取得する blogarticles = BlogArticle.query.all() return render_template('index.html', blogarticles=blogarticles) |
21行目の「<クラス名>.query.all()」を使うことで、BlogArticleクラスに対応したDBのテーブルデータすべてを取得しています。
22行目のrender_templateメソッドの第二引数に先程取得したDBのデータを渡すことで、HTMLテンプレートファイル(index.html)にDBのテーブルデータを渡すことができます。
index.htmlの修正
Pythonファイル(app.py)から渡されたDBのテーブルデータをもとに、Jinja2テンプレートエンジンが処理可能なfor文を使ってブログ記事一覧を表示します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> <h1>ブログアプリケーション</h1> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} </body> </html> |
HTMLテンプレートファイルの分割で効率化
作成するブログアプリは、記事の作成、一覧表示、更新、削除の4機能を持たせるため、4機能で共通化できるHTMLコードをbase.htmlという新しいHTMLテンプレートファイルにまとめて分割します。
base.htmlテンプレートファイルを用意
4機能で共通化するHTMLコードは、以下のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <meta name="description" content=""> <meta name="viewport" content="width=device-width, initial-scale=1"> </head> <body> {% block content %} {% endblock %} </body> </html> |
11行目から12行目のコンテンツ部分は、記事の作成、一覧表示、更新、削除の4機能で異なる表示にしたいため、別のHTMLテンプレートファイルで具体的なコンテンツを定義するように宣言しています。
11行目から12行目のコンテンツ部分以外は、HTMLの基本的な情報であるため、共通化してまとめています。
index.htmlテンプレートファイルの修正
共通化したbase.htmlテンプレートファイルを利用するため、index.htmlテンプレートファイルの内容も修正していきましょう。
基本的には、base.htmlを読み込み、base.htmlで外部参照していた部分をindex.htmlに定義する形になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{% extends 'base.html' %} {% block content %} <h1>ブログアプリケーション</h1> <a href="/create" role="button">新規作成画面</a> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} {% endblock %} |
1行目では、先程共通化したbase.htmlを読み込んでいます。
3行目以降では、base.html内で外部参照していたblock contentを定義しています。
これにより、base.htmlのblock content部分に、index.htmlの3行目以降のHTMLコードを埋め込んだHTMLコードを生成できます。
6行目から12行目が記事一覧表示に相当するHTMLコードになり、DBから取得した「記事タイトル」「記事作成日付」「記事の内容」を表示します。
ブログ記事の新規作成(DBデータ作成:Create)
ブログ記事を新規作成するための実装方法を紹介していきます。
新規作成画面を指すcreate.htmlを作成
ブログ記事を新規作成するHTMLテンプレートファイルとしてcreate.htmlを作成します。
1 2 3 4 5 6 7 8 9 10 11 |
{% extends 'base.html' %} {% block content %} <h1>新規登録画面</h1> <form method="POST"> <label for="title">タイトル</label> <input type="text" name="title"> <label for="body">内容</label> <input type="text" name="body"> <input type="submit" value="新規登録"> </form> {% endblock %} |
ブログ記事を新規作成するため、base.htmlを読み込み後、block contentを新たに定義してHTMLで「記事タイトル」「記事の内容」入力フォームを用意します。
「記事作成日付」については、app.pyのBlogArticleクラス内のdatime.now()メソッドでデフォルト値として新規作成日時を設定しているため、入力フォームは不要です。
そして、入力フォームを送信するためのボタン要素をフォーム内に設置しています。
トップページを示すindex.htmlに新規作成ボタンを追加
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{% extends 'base.html' %} {% block content %} <h1>ブログアプリケーション</h1> <a href="/create" role="button">新規作成画面</a> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} {% endblock %} |
新規登録画面に対応したViewとしてapp.pyにcreate関数を作成
ルーティング「/create」にアクセスがあった場合にcreate関数を呼び出すようにapp.pyを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from flask import render_template, request, redirect @app.route('/create', methods=['GET', 'POST']) def create(): if request.method == "POST": title = request.form.get('title') body = request.form.get('body') # BlogArticleのインスタンスを作成 blogarticle = BlogArticle(title=title, body=body) db.session.add(blogarticle) db.session.commit() return redirect('/') else: return render_template('create.html') |
GETメソッドとPOSTメソッドを受け付けるようにする
3行目では、「methods=[‘GET’, ‘POST’]」の記述によって、「/create」にアクセスがあった場合にGETメソッドとPOSTメソッドを許可しています。
なお、methodsの定義が省略された場合、デフォルトでGETメソッドのみ許可されている状態になります。
GETメソッドの場合にHTMLテンプレートとしてcreate.htmlを呼び出す
5行目と14行目では、GETメソッドかPOSTメソッドの処理か判定しています。
そして、14行目でrender_template()メソッドの第二引数にcreate.htmlテンプレートファイルを設定することで呼び出しています。
POSTメソッドで渡したデータをDBに書き込む
6行目から12行目では、新規作成画面の入力フォームのtitleとbodyの値を一旦変数に格納し、9行目でtitleとbodyの値を持つBlogArticleのインスタンスを作成しています。
10行目と11行目で先程作成したBlogArticleのインスタンスを、db.session.add()メソッドとdb.session.commit()メソッドでDBに実際に書き込む処理を行っています。
requestとredirectを追加でインポート
1行目では、GETメソッドとPOSTメソッドを判定するためにrequest、トップページへリダイレクトするためにredirectメソッドをインポートしています。
12行目では、新規作成画面に入力したtitleとbodyで新しくブログ記事をDBに書き込んで追加した後、トップページであるルート(‘/’)にリダイレクトしています。
ブログ記事一覧の表示(DBデータ使用:Read)
ブログ記事を一覧表示するための実装方法を紹介していきます。
「DBと連携したWebアプリケーションの作成」の章で、ブログ記事一覧の表示に関する処理はほとんど追加しているため、よりよいコードにするために補足説明していきます。
GETメソッドの場合にindex.htmlテンプレートファイルを表示
下記コードの3行目のようにGETメソッドの場合だけindex.htmlテンプレートファイルを参照し、DBのブログ記事のデータ一覧をレスポンスとして返すように修正しています。
1 2 3 4 5 6 |
@app.route('/', methods=['GET']) def blog(): if request.method == 'GET': # DBに登録されたデータをすべて取得する blogarticles = BlogArticle.query.all() return render_template('index.html', blogarticles=blogarticles) |
動作確認
動作確認するため、ルートから新規作成画面へアクセスし、新しいブログ記事のtitleと bodyをDBに登録します。
まずはFlaskサーバのルート(http://127.0.0.1:5000)にアクセスし、「新規作成画面」ボタンを押してみましょう。
すると、「http://127.0.0.1:5000/create」に遷移することを確認してください。
タイトル欄と内容欄を記入し、「新規登録」ボタンを押してください。
すると、自動的にルートに遷移し、先程入力フォームに入力した記事が追加され、記事一覧表示されることを確認してください。
ブログ記事の編集(DBデータ更新:Update)
ブログ記事を編集するための実装方法を紹介していきます。
編集画面を指すupdate.htmlを作成
ブログ記事の編集画面を指すupdate.htmlを以下のように作成します。
1 2 3 4 5 6 7 8 9 10 11 |
{% extends 'base.html' %} {% block content %} <h1>編集画面</h1> <form method="POST"> <label for="title">タイトル</label> <input type="text" name="title" value={{ blogarticle.title }}> <label for="body">内容</label> <input type="text" name="body" value={{ blogarticle.body }}> <input type="submit" value="更新"> </form> {% endblock %} |
画面は新規登録画面とほとんど同じため、create.htmlをコピーし、黄色の部分を変更しています。
3行目と9行目は、名前を変更しているのみです。
6行目と8行目では、inputタグのvalue属性にDBから取得したtitleとbodyを設定しています。
これは、編集画面に遷移したときに編集対象であるブログ記事の現在のtitleとbodyをあらかじめ埋めた状態で表示するための処理です。
トップページを示すindex.htmlに編集ボタンを追加
トップページから編集できるように、下記コードのようにindex.htmlに編集ボタンを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{% extends 'base.html' %} {% block content %} <h1>ブログアプリケーション</h1> <a href="/create" role="button">新規作成画面</a> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <a href="/update/{{ blogarticle.id }}" role="button">編集</a> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} {% endblock %} |
href属性の値は「/update/{{blogarticle.id}}」に設定しており、{{blogarticle.id}}は一つ一つのブログ記事を識別するID値を示しています。
編集画面に対応したViewとしてapp.pyにupdate関数を作成
ルーティング「/update/<int:id>」にアクセスがあった場合にupdate関数を呼び出すようにapp.pyを作成します。
<int:id>は、BlogArticleクラスで定義したIDを指し、ブログ記事を新規登録したときに自動的に割り振られる値になります。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@app.route('/update/<int:id>', methods=['GET', 'POST']) def update(id): # 引数idに一致するデータを取得する blogarticle = BlogArticle.query.get(id) if request.method == "GET": return render_template('update.html', blogarticle=blogarticle) else: # 上でインスタンス化したblogarticleのプロパティを更新する blogarticle.title = request.form.get('title') blogarticle.body = request.form.get('body') # 更新する場合は、add()は不要でcommit()だけでよい db.session.commit() return redirect('/') |
URLの引数idを使って、DBからidに対応したデータを取得
4行目では、URLの<int:id>の値をupdate関数の引数で受け取り、DBからidに一致するデータを取得しています。
GETメソッドの場合、update.htmlを読み込み
5行目から6行目では、GETメソッドでアクセスがあった場合にupdate.htmlを読み込んでいます。
そして、render_templateメソッドの第二引数でidが一致するデータをupdate.htmlに渡しています。
POSTメソッドの場合、フォームデータからtitleとbodyを取得し、DBを更新する
9行目から10行目では、update.html内の入力フォームからtitleとbodyの値を取得し、idが一致したデータのtitleとbodyに上書いて更新しています。
そして、db.session.commit()メソッドを実行することで、更新データがDBにも書き込まれます。
13行目では、DBの値を更新した後にルートにアクセスし、ブログ記事一覧を表示しています。
動作確認
動作確認するため、Flaskサーバのルート(http://127.0.0.1:5000)にアクセスし、「編集」ボタンを押して編集画面へアクセスします。
編集画面では、URLが「/update/<int:id>」になっていることと、既存のブログ記事のtitleとbodyが表示されるか確認しましょう。
次は、タイトルを変更した後、「更新」ボタンを押すと、更新されるか確認します。
タイトルが「記事タイトル1」から「記事タイトル1(更新)」に変更されていることが確認できます。
これで更新機能の動作確認はオッケーです。
ブログ記事を削除(DBデータ削除:Delete)
トップページを示すindex.htmlに削除ボタンを追加
トップページからブログ記事を削除できるように、下記コードのようにindex.htmlに削除ボタンを追加します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
{% extends 'base.html' %} {% block content %} <h1>ブログアプリケーション</h1> <a href="/create" role="button">新規作成画面</a> {% for blogarticle in blogarticles %} <article> <h2>{{ blogarticle.title }}</h2> <a href="/update/{{ blogarticle.id }}" role="button">編集</a> <a href="/delete/{{ blogarticle.id }}" role="button">削除</a> <p>作成日時:{{ blogarticle.created_at }}</p> <p>{{ blogarticle.body }}</p> </article> {% endfor %} {% endblock %} |
href属性の値は「/delete/{{blogarticle.id}}」に設定しており、{{blogarticle.id}}は一つ一つのブログ記事を識別するID値を示しています。
削除機能については、削除ページを用意せず、「削除」ボタンを押すと、直接対象のブログ記事を削除するようにします。
その後、ルートにリダイレクトして一覧表示するようにします。
削除機能に対応したViewとしてapp.pyにdelete関数を作成
ルーティング「/delete/<int:id>」にアクセスがあった場合にdelete関数を呼び出すようにapp.pyを作成します。
1 2 3 4 5 6 7 |
@app.route('/delete/<int:id>', methods=['GET']) def delete(id): # 引数idに一致するデータを取得する blogarticle = BlogArticle.query.get(id) db.session.delete(blogarticle) db.session.commit() return redirect('/') |
4行目では、編集機能のときと同様にURLの<int:id>に対応するブログ記事のデータを取得しています。
5行目から6行目では、db.session.delete()メソッドとdb.session.commit()メソッドを実行することで、対象のブログ記事を削除した後、DBに反映しています。
動作確認
動作確認するため、Flaskサーバのルート(http://127.0.0.1:5000)にアクセスし、「削除」ボタンを押してみましょう。
「削除」ボタンを押すと、ルートにリダイレクトされて、対象のブログ記事も削除されていることが確認できます。
まとめ
いかがでしたでしょうか。
PythonのFlaskでデータベースと連携したWebアプリケーションを作成してきました。
ブログアプリを例として記事の新規作成(Create)、一覧表示(Read)、更新(Update)、削除(Delete)のCRUD処理を実装し、動作確認も行いました。
Webアプリケーションの基本機能であるCRUD処理の実装方法を習得することはとても重要なため、本記事を通して基礎をおさていきましょう。
【関連記事】
【Python Flask入門】使い方の基礎からWebアプリケーション作成までの流れ
【TIPS】Pythonで日付/日時/時刻の取得・演算を行うdatetimeサンプルコード付き
【Python Django入門】使い方からWebアプリ作成までの流れ