TL;DR
Docker・Flask・SQLAlchemyを使って簡単なメモアプリを作ってみました。
SQLAlchemyを使ってデータベースを作成〜データを取ってきてブラウザ上で表示するところまでやりました。
本記事ではメモアプリが完成するまでの手順と、詰まったところやその解決方法を書いていきます。
以下のリポジトリにサンプルコードを公開しました。フォルダ構成や記事の中で出てこないファイルなどはこちらをご覧ください。
サンプルコード→ https://github.com/hiseumn/flask-postgresql-sample
また、参考資料に前回の記事を掲載しています。一通り動くところまでの手順はそちらに書いたため、今回は省略します。
必要なライブラリのインストール
SQLAlchemy
今回はSQL文を直接書かず、ORMを使用しました。
rye add sqlalchemy
uuid
保存するメモのIDとしてUUIDを使用しました。IDを時系列でソートしたかったので、ビルトインのv4ではなくv7を使用するため、以下のライブラリをインストールしてます。
rye add uuid6
主なファイル構成
app.py
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列で表示される構造にしました。
<!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
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.id
とself.memo
の内容を返します
詰まったところ
入力したデータを表示できない
そもそも全く別のファイルからデータを取り出そうとしていた
最初はhttp statusで304エラーが出てきてしまい、ひとつずつエラーが出てきたコードを調べながら進めていました。
しかし、入力したデータが表示されないどころか全く違うところからデータを引っ張ってきていたことがわかりました。
データベースに接続できない
psycopgが必要だったが実行するまでエラーとならなかった
上の画像は、psycopg2をインストールした際のエラー文です。
データベースに接続するためのドライバーが必要だったので、ターミナルでrye add psycopg2
を入力して解決しました。
## 参考資料
以上