書籍4:メモ帳3:Webアプリ「メモ帳」のプログラム解析

メモ帳の構成

プロジェクトの構成

.
-- app.py
-- <instance>
------memo.sqlite メモを保存するデーターベース
-- <templates
------base.html 共通のテンプレート
------list.html メモの一覧テンプレート
------memo.html メモの編集画面テンプレート

メモ帳アプリメインプログラム 削除機能付き

from flask import Flask, request, redirect, url_for, render_template
from flask_sqlalchemy import SQLAlchemy

# Flaskとデータベースの初期化 --- (※1)
app: Flask = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///memo.sqlite"
db:SQLAlchemy = SQLAlchemy(app)
# メモのデータベースモデルを定義
class MemoItem(db.Model):
id: int = db.Column(db.Integer, primary_key=True)
title: str = db.Column(db.Text, nullable=False)
body: str = db.Column(db.Text, nullable=False)
# データベースの初期化
with app.app_context():
db.create_all()
# メモの一覧を表示する --- (※2)
@app.route("/")
def index():
items = MemoItem.query.order_by(MemoItem.title).all()
items.insert(0, {"id": 0, "title": "🖌️ 新規作成", "body": ""})
return render_template("list.html", items=items)

# メモの編集画面を出す --- (※3)
@app.route("/memo/<int:id>", methods=["GET", "POST"])
def memo(id: int):
# IDが0の場合は、データベースを見に行かずに「真っさらな新規データ」を確実に作る
if id == 0:
it = MemoItem(id=0, title="__無題__", body="")
else:
# 既存メモの取得
it = db.session.get(MemoItem, id)
if it is None:
return "メモが見つかりません", 404

# POSTの場合はデータを保存
if request.method == "POST":
it.title = request.form.get("title", "__無題__")
it.body = request.form.get("body", "")

if it.title == "":
return "タイトルは空にできません"

if id == 0:
# 新規保存時:IDをデータベースの自動採番に任せるためNoneにする
it.id = None
db.session.add(it)

db.session.commit()
return redirect(url_for("index"))

# メモの編集画面を表示
return render_template("memo.html", it=it)

# --- ここから追記 ---
@app.route("/memo/<int:id>/delete", methods=["POST"])
def delete_memo(id: int):
it = db.session.get(MemoItem, id)
if it:
db.session.delete(it)
db.session.commit()
return redirect(url_for("index"))
# --- ここまで追記 ---

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

共通テンプレート

base.html

<!DOCTYPE html>
<html><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet"
     href="https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css">
    <title>Memo App</title>
</head><body class="p-3">
    <div class="p-5 has-background-info"><!-- タイトル -->
        <h1 class="is-size-3">Memo App</h1>
    </div>
    {% block contents %}
    <!-- ここに contents が表示される -->
    {% endblock %}
</body>
</html>

list.html

<!-- base.html を拡張する -->
{% extends "base.html" %}
<!-- base.html の contents を書き換える -->
{% block contents %}
 <div class='card p-3'>
    編集したいメモを選んでください:
    <ul class='grid'>
        {% for it in items %}
        <li class="cell card p-4 m-2">
            <a href="/memo/{{ it.id }}">
                {% if it.id != 0 %}📍{% endif %}
                {{ it.title }}
            </a>
        </li>
        {% endfor %}
    </ul>
</div>
{% endblock %}

memo.html

<!-- base.html を拡張する -->
{% extends "base.html" %}
<!-- base.html の contents を書き換える -->
{% block contents %}
<div class="card p-3">
    <form method="POST">
        <label class="label" for="title">タイトル:</label>
        <input id="title" name="title" type="text"
            value="{{ it.title }}" class="input">
        <label class="label" for="body">本文:</label>
        <textarea id="body" name="body"
            class="textarea">{{ it.body }}</textarea>
       
        <div class="mt-3">
            <!-- 保存ボタン -->
            <input type="submit" value="保存" class="button is-primary">


            <!-- 削除ボタン(新規作成 ID:0 以外の時だけ表示) -->
            {% if it.id > 0 %}
            <button form="delete-form" type="submit" class="button is-danger is-light"
                    onclick="return confirm('このメモを本当に削除してもよろしいですか?')">削除</button>
            {% endif %}
        </div>
    </form>


    <!-- 削除用の隠しフォーム(セキュリティとレイアウトのため分けて記述します) -->
    {% if it.id and it.id > 0 %}
    <form id="delete-form" action="{{ url_for('delete_memo', id=it.id) }}" method="POST"></form>
    {% endif %}
</div>
{% endblock %}

 

コメント