1
3

Docker-Flask-SQLAlchemyでメモアプリを作ってみた

Posted at

TL;DR

Docker・Flask・SQLAlchemyを使って簡単なメモアプリを作ってみました。
SQLAlchemyを使ってデータベースを作成〜データを取ってきてブラウザ上で表示するところまでやりました。
本記事ではメモアプリが完成するまでの手順と、詰まったところやその解決方法を書いていきます。

以下のリポジトリにサンプルコードを公開しました。フォルダ構成や記事の中で出てこないファイルなどはこちらをご覧ください。

サンプルコード→ https://github.com/hiseumn/flask-postgresql-sample

また、参考資料に前回の記事を掲載しています。一通り動くところまでの手順はそちらに書いたため、今回は省略します。

必要なライブラリのインストール

SQLAlchemy

今回はSQL文を直接書かず、ORMを使用しました。

terminal
rye add sqlalchemy

uuid

保存するメモのIDとしてUUIDを使用しました。IDを時系列でソートしたかったので、ビルトインのv4ではなくv7を使用するため、以下のライブラリをインストールしてます。

terminal
rye add uuid6

主なファイル構成

app.py

python
from flask import Flask, render_template, request
import os
from sqlalchemy import create_engine, desc
from sqlalchemy.orm import sessionmaker
from sample_flask_memo.infra.models.memo import Memo
from uuid6 import uuid7


app = Flask(__name__)

engine=create_engine(f"postgresql+psycopg://postgres:postgres@localhost:5432/memo")
Session = sessionmaker(bind=engine)
session = Session()


@app.route("/", methods=["GET"])
def index():
    memo_list=session.query(Memo).order_by('memo').all()
    print(memo_list)
    return render_template("index.html", memo_list=memo_list)


@app.route("/add", methods=["POST"])
def add():
    memo = request.form.get("memo")

    memo=Memo(id=uuid7(), memo=memo)
    session.add(memo)
    session.commit()
    return index()


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000, debug=True)

※データベースに接続するためのIDとパスワードをハードコーディングすることは、セキュリティリスクになりかねないため真似しないでください。

index.html

入力したメモが4列で表示される構造にしました。

html
<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous">
    <title>テストページ</title>
  </head>
  <body>

    <div class="container text-center">
        <div class="row">
            <div class="col">
                <div class="mb-3">
                <form action="/add" method="POST">
                    <div class="mb-3 form-check">
                      <label for="exampleFormControlTextarea1" class="form-label">登録するメモ</label>
                      <textarea class="form-control" id="exampleFormControlTextarea1" name="memo" rows="3"></textarea>
                    </div>
                    <button type="submit" class="btn btn-primary">送信</button>
                </form>
                </div>
            </div>
            </div>
        <div class="row">
          <div class="col">
            {% for memo in memo_list %}
              {%- if loop.index0 % 4 == 0 -%}
                  <div class="card">
                      <div class="card-body">
                      <h4>{{memo.id}}</h4>
                      <p>{{memo.memo}}</p>
                      </div>
                    </div>
              {%- endif %}</li>
            {% endfor %}
          </div>
          <div class="col">
            {% for memo in memo_list %}
              {%- if loop.index0 % 4 == 1 -%}
                  <div class="card">
                      <div class="card-body">
                      <h4>{{memo.id}}</h4>
                      <p>{{memo.memo}}</p>
                      </div>
                    </div>
              {%- endif %}</li>
            {% endfor %}
          </div>
          <div class="col">
            {% for memo in memo_list %}
              {%- if loop.index0 % 4 == 2 -%}
                  <div class="card">
                      <div class="card-body">
                      <h4>{{memo.id}}</h4>
                      <p>{{memo.memo}}</p>
                      </div>
                    </div>
              {%- endif %}</li>
            {% endfor %}
          </div>
          <div class="col">
            {% for memo in memo_list %}
              {%- if loop.index0 % 4 == 3 -%}
                  <div class="card">
                      <div class="card-body">
                      <h4>{{memo.id}}</h4>
                      <p>{{memo.memo}}</p>
                      </div>
                    </div>
              {%- endif %}</li>
            {% endfor %}
          </div>
        </div>
      </div>
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
  </body>
</html>

for文の説明

  • {% for memo in memo_list %}は、memo_listというリスト内の各メモを一つずつ取り出し、それをmemoという変数に代入して処理するループを示しています
  • <h4>{{memo.id}}</h4>は、各メモのIDがそれぞれヘッダーに表示されます
  • <p>{{memo.memo}}</p>は、ブラウザ上で入力したメモが表示されます
  • memo_listから各メモを取り出して表示します。それぞれのメモはカード形式で表示されます

memo.py

python
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.schema import Column
from uuid6 import uuid7
from sqlalchemy_utils import UUIDType
from sqlalchemy.types import Text

Base = declarative_base()
class Memo(Base):
    __tablename__ = "memo"  # テーブル名を指定
    id = Column(UUIDType(binary=False), primary_key=True, default=uuid7)
    memo = Column(Text)

    def __repr__(self):  # メモの内容を返す
        return f"{self.id} {self.memo}"
    
  • Baseは、後で定義する全てのモデルクラスが継承するベースクラスです
  • class Memo(Base)は、SQLAlchemyのモデルクラスでBaseを継承しています
  • def __repr__(self)は、オブジェクトの文字列表現を定義し、デバッグやログ出力時に役立ちます。self.idself.memoの内容を返します

詰まったところ

入力したデータを表示できない

そもそも全く別のファイルからデータを取り出そうとしていた

最初はhttp statusで304エラーが出てきてしまい、ひとつずつエラーが出てきたコードを調べながら進めていました。
しかし、入力したデータが表示されないどころか全く違うところからデータを引っ張ってきていたことがわかりました。

データベースに接続できない

psycopgが必要だったが実行するまでエラーとならなかった

スクリーンショット 2024-07-03 11.40.47.png
上の画像は、psycopg2をインストールした際のエラー文です。
データベースに接続するためのドライバーが必要だったので、ターミナルでrye add psycopg2を入力して解決しました。

## 参考資料

以上

1
3
0

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
1
3