【Python】FlaskによるToDoアプリケーション作成/開発





Python初心者が作れるものとして、簡易的なToDoアプリケーションの開発があります。

本記事では、PythonフレームワークであるFlaskとCRUD機能(Create, Read, Update, Deleteの頭文字を取った用語)簡易的なToDoアプリを開発します。

  • Webアプリケーション(ToDoアプリケーション)を開発してみたい人
  • 本格的にPythonフレームワークを用いたアプリ作成/開発したい人
  • アプリ公開まで一連の開発工程を理解しながら実践したい人

これらの悩みを解決しながら、ToDoアプリを開発します。

ToDoアプリケーションの完成画面は以下になります。

以下の章から開発工程を記載していきます。

独学に限界を感じたなら...
とりあえず独学でプログラミング学習を始めたけど、右も左も分からずあなたの時間が無駄に終わるどころか挫折するかもしれません。

あなたが時間を無駄にした分を回収したいなら【Python】2022年最新!おすすめのオンラインプログラミングスクールをご確認ください!

※期間限定で学習ロードマップを記載しています!

FlaskによるWebアプリ開発環境の準備/インストール

本記事で開発するFlaskでWebアプリを開発するために、以下の項目を利用します。

  • PC:Mac
  • テキストエディタ:Visual Studio Code(VS Code)
  • サーバーサイド言語:Python(Flask利用)
  • フロントエンド言語:HTML/CSS(正確にはマークアップ言語)
  • CDN:BootStrap5(CSSフレームワーク)
  • 本番環境:Heroku(アプリケーションサーバーとして利用)

各種必要となるプログラミング言語・エディタ・ライブラリ等のインストールを開始します。

Pythonのインストールができていない人は「【Python初心者入門】ダウンロードとインストール方法を解説!」で解説します。

【Python初心者入門】ダウンロードとインストール方法を解説!

2017.07.09

各種インストールが完了したら、早速任意のディレクトリに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アプリに対するサーバーサイドの設計内容は以下です。

  • importするライブラリ:Flask, render_template, request, redirect, url_for, SQLAlchemy
  • クラス(データベース):Listクラス(id, title, category, description)
  • CRUD機能:Create, Read, Update, Delete

それぞれを切り分けて解説していきます。

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アカウント作成
  • ローカルPCのセットアップ(デプロイ準備)
  • Herokuにアプリをデプロイ

手順に沿って解説していきます。

Herokuアカウント作成

公式サイトのHerokuにアクセスします。

『新規登録』をクリックしてアカウントを作成します。

手順に沿って登録すれば、すぐにアカウント作成を完了できます。

ローカルPCのセットアップ(デプロイ準備)

Herokuにアプリをデプロイするために以下のファイルを作成します。

  • requirements.txt
  • runtime.txt
  • Procfile

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/

【Python】Flask+YouTube Data APIによる動画データ分析アプリ開発

2021.10.04



ABOUTこの記事をかいた人

sugi

大学卒業後、IT企業に就職を果たす。システム開発・人工知能に触れながら大手企業と業務をこなす。2年半後脱サラし、フリーランス活動経験を経て 2019年2月から起業し、今に至る。 自社サービス及び製品を開発、ブログ収入、クラウドソーシングなど、多方面で売り上げを立てている。