Python初心者が作れるものとして、簡易的なToDoアプリケーションの開発があります。
本記事では、PythonフレームワークであるFlaskとCRUD機能(Create, Read, Update, Deleteの頭文字を取った用語)簡易的なToDoアプリを開発します。
|
これらの悩みを解決しながら、ToDoアプリを開発します。
ToDoアプリケーションの完成画面は以下になります。
以下の章から開発工程を記載していきます。
目次
FlaskによるWebアプリ開発環境の準備/インストール
本記事で開発するFlaskでWebアプリを開発するために、以下の項目を利用します。
|
各種必要となるプログラミング言語・エディタ・ライブラリ等のインストールを開始します。
Pythonのインストールができていない人は「【Python初心者入門】ダウンロードとインストール方法を解説!」で解説します。
各種インストールが完了したら、早速任意のディレクトリにFlaskによるアプリのフォルダを作成します。
mkdir flask-todo
Macであればターミナルを使用し、任意のディレクトリにて上記コードを実行するとフォルダ作成できます。
もちろん、CUI操作が苦手な人であればデスクトップ上からコマンド入力せず作成できるので、わからない人はGUI操作で作成しましょう。
任意のフォルダ作成ができたら、以下のファイル/フォルダを作成したフォルダ内に作ります。
flask-todo
├── app.py
└── templates
└── index.html
フォルダ内のapp.pyはFlaskを保存するためのPythonファイルです。(拡張子が.py)
templatesフォルダの中は、空のHTMLファイルを作成してください。
今後FlaskにてHTMLファイルを呼び出し、ブラウザ上に表示します。
Python/Flaskによるサーバーサイドの設計
コード実装する前に、しっかりと設計段階から考えていきます。
ここでは大まかに設計していきますが、何かWebアプリを開発する際は綿密な設計を心がけましょう。
今回のToDoアプリに対するサーバーサイドの設計内容は以下です。
|
それぞれを切り分けて解説していきます。
Python/Flaskによるサーバーサイドの実装
まず始めに、各種ライブラリのインストールします。
pip3 install flask pip3 install requests pip3 install sqlalchemy
pip3あるいはpipコマンドにて各種ライブラリをインストールしておきましょう。
インストールの際は、実行先であるフォルダまでコマンドプロンプトあるいはターミナルにて、『cd』コマンドで移動して実行しましょう。(今回の場合はflask-todoフォルダを指定)
importするべきライブラリ等のコード実装
早速、ライブラリ等のコードを実装していきます。
テキストエディタであるVS Codeにてフォルダを開き、app.pyへ以下のコードを記述します。
from flask import Flask, render_template, request, redirect, url_for from flask_sqlalchemy import SQLAlchemy
PythonフレームワークであるFlaskのライブラリから、Flask、render_template、request、redirect、url_forをimportします。
次に、flask_sqlalchemyからSQLAlchemyをimportします。
SQLAlchemyは、Pythonを利用してDB(データベース)を操作するライブラリです。
SQL文を記述せずに操作できる利点や様々なDBに接続可能といった利点もあるため、Pythonコードでアプリ開発する際に重宝するライブラリの一種です。
Listクラス(データベース)のコード実装
次に、ToDoアプリで利用するListクラス(データベース)を実装します。
こちらもテキストエディタであるVS Codeにてフォルダを開き、app.pyへ以下のコードを記述します。
先ほどimportコード群の下に続けて記載していきましょう。
app = Flask(__name__) app.config['SECRET_KEY'] = 'secret_key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.sqlite' db = SQLAlchemy(app) class List(db.Model): __tablename__ = "lists" id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.Text()) category = db.Column(db.Text()) description = db.Column(db.Text()) db.create_all()
変数appは、Flaskによるアプリを起動するための変数を初期化しています。
app.configは、app(ToDoアプリ)に対するコンフィグの設定を実行しています。
SECRET_KEYはセッション情報暗号化に必要な設定であるため、設定せずセッションを使うとエラーになります。
SQLALCHEMY_DATABASE_URIはデータベースの場所を指定します。
今回はSQLiteを使うので、アプリを起動するとflask-todoフォルダ直下にtodo.sqliteというデータベースファイルが作成されます。
変数dbは、SQLAlchemy()メソッドを利用してappに対するインスタンスを生成しています。
Listクラスでは、id、title、category、descriptionの4つのデータを保持するよう定義しています。
db.create_all()メソッドによってアプリ起動後にデータベースを生成します。
ToDoアプリにおけるCRUD機能のコード実装
次に、ToDoアプリケーションにおけるCRUD機能のコード実装を行います。
こちらもテキストエディタであるVS Codeにてフォルダを開き、app.pyへ以下のコードを記述します。
先ほどクラスコード群の下に続けて記載していきましょう。
@app.route('/') def index(): lists = List.query.all() returnrender_template("index.html", lists = lists) @app.route('/new', methods=["POST"]) def new(): list = List() list.title = request.form["list_create"] db.session.add(list) db.session.commit() returnredirect(url_for('index')) @app.route('/update', methods=["POST"]) def update(): id = request.form["list_update"] list = List.query.filter_by(id=id).one() list.category = request.form["list_category"] list.description = request.form["list_description"] db.session.commit() returnredirect(url_for('index')) @app.route('/delete', methods=["POST"]) def delete(): id = request.form["list_delete"] list = List.query.filter_by(id=id).one() db.session.delete(list) db.session.commit() returnredirect(url_for('index')) if __name__ == '__main__': app.run()
ルーティングとindexにアクセスされた処理を記述しています。
@app.route()はURLルーティングを実現するためのデコレータです。
ルートにアクセスされた場合、直下のindex()関数を呼ぶ実装です。
index()関数はリスト一覧をlistsテーブルから取得し、index.htmlに渡す処理を実行します。
Listクラスはdb.Modelを継承しているため、Modelのquery.all()を呼び出し、listsテーブルのデータを全て取得しています。
render_template()は引数をhtmlにデータを渡し、レンダリングするために使います。
index.htmlは後述しますが、中身となるリストデータを渡してレンダリングするイメージです。
@app.route(‘/new’, methods=[“POST”])はリスト作成処理を実行します。
引数に/newを設定しているので作成処理はxxxx.xxxx/newでリクエストされたときに動作し、methodsにPOSTを指定しているため、POSTでリクエストされるのが前提になります。
まずListクラスのインスタンスを生成し、titleを設定します。
titleにはrequest.formを使ってフォームのテキストボックスの入力値を取得します。
db.session.add()にインスタンスを渡して作成できます。
ただし、最後にcommit()しないと作成が確定しないので注意しましょう。
作成したらredirect()でindex()へリダイレクトし、index.htmlをレンダリングします。
@app.route(‘/update’, methods=[“POST”])はリスト更新処理を実行します。
引数に/updateを設定しているので作成処理はxxxx.xxxx/updateでリクエストされたときに動作し、methodsにPOSTを指定しているため、POSTでリクエストされるのが前提になります。
idはrequest.formを使ってフォームのリストIDを取得します。
リストIDがなければどのリストを選択したか判断できないため、idから取得します。
idを利用し、データベースから照合と一致したリストを変数listに格納します。
categoryとdescriptionでは、request.formを利用してフォーム内に記載されたデータを取得・格納しています。
最後にcommit()にてデータ更新を確定させます。
@app.route(‘/delete’, methods=[“POST”])はリスト削除処理を実行します。
引数に/deleteを設定しているので作成処理はxxxx.xxxx/deleteでリクエストされたときに動作し、methodsにPOSTを指定しているため、POSTでリクエストされるのが前提になります。
こちらも更新処理と同様、idを取得したのちにidと一致したリストデータをデータベースから抽出します。
db.session.deleteにインスタンスを渡して削除しています。
最後にcommit()にてデータ削除を確定させます。
ここまででCRUD機能を実装完了させ、app.run()にてアプリ起動を実行させます。
改めて、app.pyの全体像を記載しておきます。
from flask import Flask, render_template, request, redirect, url_for from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config['SECRET_KEY'] = 'secret_key' app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///todo.sqlite' db = SQLAlchemy(app) class List(db.Model): __tablename__ = "lists" id = db.Column(db.Integer, primary_key=True, autoincrement=True) title = db.Column(db.Text()) category = db.Column(db.Text()) description = db.Column(db.Text()) db.create_all() @app.route('/') def index(): lists = List.query.all() return render_template("index.html", lists = lists) @app.route('/new', methods=["POST"]) def new(): list = List() list.title = request.form["list_create"] db.session.add(list) db.session.commit() return redirect(url_for('index')) @app.route('/update', methods=["POST"]) def update(): id = request.form["list_update"] list = List.query.filter_by(id=id).one() list.category = request.form["list_category"] list.description = request.form["list_description"] db.session.commit() return redirect(url_for('index')) @app.route('/delete', methods=["POST"]) def delete(): id = request.form["list_delete"] list = List.query.filter_by(id=id).one() db.session.delete(list) db.session.commit() return redirect(url_for('index')) if __name__ == '__main__': app.run()
次に、index.html(フロントエンド)の実装に入ります。
HTML/BootStrap5によるフロントエンドの設計
本記事はPython(サーバーサイド)を中心にした内容であるため、フロントエンドは大枠の解説にします。
HTML/CSS/BootStrap5等のリファレンスを確認すれば、コードの理解は問題なくできるはずです。
今回のToDoアプリに対するフロントエンドの設計内容は以下です。
|
以下の章からコード実装の解説になります。
HTML/BootStrap5によるフロントエンドの実装
以下のコードをindex.htmlにて記載します。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"/> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <title>ToDo App</title> </head> <body> <main> <div id="title"class="title w-50 mx-auto mt-2 align-items-center"><h1>ToDoリスト</h1></div> <form action="/new" method="post"> <div class="input-group w-50 mx-auto mt-2 align-items-center"> <input type="text" class="form-control" name="list_create" id="list_create" placeholder="Input Task!"> <button class="btn btn-outline-secondary" type="submit">登録</button> </div> </form> <tableclass="w-50 mx-auto align-items-center"> {% if lists %} {% for list in lists %} <tr> <td> <div class="card col-12 mt-2" id="{{list.id}}"> <div class="card-body"> <h5 class="card-title text-wrap">{{ list.title }}</h5> {% if not list.category %} <h6 class="card-category mb-2">Card Category</h6> {% else %} <h6 class="card-category mb-2 text-wrap">{{ list.category }}</h6> {% endif %} <p class="card-text overflow-wrap" style="white-space:pre-wrap;">{{ list.description }}</p> <div class="d-flex justify-content-end"> <button type="button" class="btn btn-primary m-2" data-bs-toggle=" modal"data-bs-target="#updateModal" data-id="{{list.id}}" data-title="{{list.title}}" data-category="{{list.category}}" data-description="{{list.description}}">タスク編集</button> <form class="form-inline" action="/update" method="post"> <div class="modal fade" id="updateModal" tabindex="-1" aria-labelledby="updateModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title text-break" id="updateModalLabel"></h5> <button type="button" class="btn-close"data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> {% if not list.category %} <input id="list_category" name="list_category" type="text" class="modal-category form-control mb-2" aria-label="category-input"> {% else %} <input id="list_category" name="list_category" type="text" class="modal-category form-control mb-2" aria-label="category-input"> {% endif %} {%if not list.description %} <textarea id="list_description" name="list_description" type="text" class="modal-description form-control mb-2" aria-label="description-input"></textarea> {% else %} <textarea id="list_description" name="list_description" type="text" class="modal-description form-control mb-2" aria-label="description-input"></textarea> {% endif %} </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">閉じる</button> <button type="submit" class="list-update btn btn-primary" name="list_update" data-bs-dismiss="modal">保存</button> </div> </div> </div> </div> </form> <form action="/delete"method="post"> <button type="submit" class="btn btn-primary m-2" name="list_delete" value="{{list.id}}">タスク削除</button> </form> </div> </div> </div> </td> </tr> {% endfor %} {% endif %} </table> </main> <script> var updateModal = document.getElementById('updateModal') updateModal.addEventListener('show.bs.modal', function (event) { var button = event.relatedTarget var list_id = button.getAttribute('data-id') var list_title = button.getAttribute('data-title') var list_category = button.getAttribute('data-category') var list_description = button.getAttribute('data-description') var modalId = updateModal.querySelector('.list-update') var modalTitle = updateModal.querySelector('.modal-title') var modalCategory = updateModal.querySelector('.modal-category') var modalDescription = updateModal.querySelector('.modal-description') modalId.value = list_id modalTitle.textContent = list_title modalCategory.value = list_category modalDescription.value = list_description }) </script> <!-- Bootstrap JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> </body> </html>
上記のコードをindex.htmlに保存し、VS Codeにてターミナルを開いて以下のコードを実行するとアプリが起動します。
python3 app.py
実行すると、ターミナル上でRunning on http://xxxx.x.x.x:xxxx/といったローカル環境でアプリが起動されているため、アクセスするとアプリを利用できます。
HerokuによるFlaskアプリケーションのデプロイ方法
せっかくなので、ここまで開発したアプリケーションをネット上で公開するためにデプロイ方法をまとめます。
ぜひ自身で開発したアプリを自分用としてネット公開し、利用してみてください。
あくまで、アプリのデプロイ方法として参考にして頂けると幸いです。
|
手順に沿って解説していきます。
Herokuアカウント作成
公式サイトのHerokuにアクセスします。
『新規登録』をクリックしてアカウントを作成します。
手順に沿って登録すれば、すぐにアカウント作成を完了できます。
ローカルPCのセットアップ(デプロイ準備)
Herokuにアプリをデプロイするために以下のファイルを作成します。
|
requirements.txtを作成する前に、gunicornをインストールします。
pip3 install gunicorn
requirements.txtは、任意のディレクトリにて以下のコマンドを実行します。
pip3 freeze > requirements.txt
コマンド実行すると、任意のディレクトリにrequirements.txtが作成されます。
herokuに何のライブラリをインストールすれば良いのか記載しています。
requirements.txtは、Pythonのライブラリで何がインストールされているのかをリストにしてるテキストデータになります。
また、runtime.txtも以下のコマンドで作成できます。
pip3 freeze > runtime.txt
次に、runtime.txtの設定を行います。
echo python-3.8.13 > runtime.txt
上記のコードにて、runtime.txtにpythonのバージョンを指定させます。
最後に、Procfileの指定を行います。
echo web: gunicorn app:app --log-file=- > Procfile
任意のディレクトリ内のファイル構成が以下になります。
flask-todo
├── app.py
├── requirements.txt
├── runtime.txt
├── Procfile
└── templates
└── index.html
任意のフォルダを開いて確認してみてください。
Herokuにアプリをデプロイ
最後に、Herokuへアプリをデプロイします。
CUIコマンドを実行するため、開発フォルダがあるディレクトリにてターミナルあるいはコマンドプロンプトで実施してください。
Homebrewのインストールがまだできていない人は、以下のコードを実行します。
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
次に、Herokuをインストールします。
brew tap heroku/brew brew install heroku
上記のコードを実行すると、herokuのインストールが完了します。
次に、Herokuへログインを行います。
ログインする際は、現在アプリ開発しているフォルダのディレクトリから実行しましょう。
heroku login
heroku loginのコマンドを実行すると、ブラウザ画面にログイン状態を確認されます。
次にHerokuにて適当なアプリ名を作成して、python起動を明示的に示すコマンドを実行します。
heroku create <app-name>
heroku buildpacks:set heroku/python
heroku ps:scale web=1
<app-name>の箇所に、任意のアプリケーション名を入力して実行してください。
最後に、gitコマンドにてアプリケーションをpushすれば完了です。
git init git add . git commit -m "first commit" git push heroku master
gitを初期化したのち、git add .にて現在のディレクトリ内のファイル/フォルダをコミット対象にします。
git commitにてファイル/フォルダを変更/追加できるように保存しています。
最後に、git push heroku masterにてファイル/フォルダをpushしてあげれば、作成したアプリケーション名でheroku用のURLが発行されます。
FlaskによるToDoアプリケーション → https://flask-todo-ab.herokuapp.com/
まとめ
FlaskによるToDoアプリケーション → https://flask-todo-ab.herokuapp.com/
記事へのコメントあるいはサンプルアプリケーションにリスト(エラー等のコメントや開発できたコメントなど)を作成して頂ければ、随時記事更新していきます!
また、別のYouTube API + Flaskアプリケーションも開発しているので、興味があれば一読ください。
Flask + YouTube Data APIによる動画データ分析アプリ → https://top-movies-up.herokuapp.com/