書籍4:メモ帳2:削除機能、不要になったメモを削除するボタン付け加える

削除機能ボタン付け加える

app.pyのプログラムに、削除機能:不要になったメモを削除するボタン付け加えることはできますか?

app.pyの修正

はい、削除機能の追加はそれほど難しくありません。以下の3つのステップで実装できます。
1. 削除用の「ルート(関数)」を app.py に追加する
特定のIDを指定して、データベースから削除を実行する処理を追加します。(※3) の編集画面ルートの後あたりに追記してください。

python
# メモの削除 --- (※削除機能)
@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"))

2. 編集画面(memo.html)に削除ボタンを追加する
現在お使いの templates/memo.html のどこか(「保存」ボタンの隣など)に、削除用のフォームを追加します。
ポイント: 削除は誤操作を防ぐため、リンクではなく POSTメソッドのボタン にするのが安全です。また、JavaScriptで「本当に削除しますか?」と確認を出すようにします。

html
<!-- memo.html の保存ボタンの近くに追記 -->
<form action="{{ url_for('delete_memo', id=it.id) }}" method="POST" style="display:inline;">
<button type="submit" class="button is-danger"
onclick="return confirm('本当に削除してよろしいですか?')">削除</button>
</form>

3. 注意点:新規作成時の動作
新規作成(IDが0やまだ保存されていない状態)の時は、削除ボタンが表示されないように、HTML側で {% if it.id %} を使って囲むと、より親切な画面になります。
全体の流れ
保存: app.py を書き換える。
HTML修正: memo.html に削除ボタンのコードを書く。
実行: Flaskサーバーが動いている状態で、編集画面を開くと「削除」ボタンが現れます。
これで、「書く」「直す」「消す」というメモ帳に必要な基本機能がすべて揃います。もしHTMLの書き換え箇所が分かりにくい場合は、現在お使いの memo.html の中身を教えていただければ、具体的な挿入場所を提示いたします!

memo.html の修正

現在の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>
<input type="submit" value="保存" class="button is-primary">
</form>
</div>
{% endblock %}

現在の memo.html の「保存」ボタンの横に、削除ボタンを配置するように書き換えました。
以下のコードをコピーして、memo.html の中身をすべて上書きしてください。

修正後の memo.html

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 > 0 %}
<form id="delete-form" action="{{ url_for('delete_memo', id=it.id) }}" method="POST"></form>
{% endif %}
</div>
{% endblock %}

変更のポイント
削除ボタンの追加: 「保存」ボタンの横に赤い「削除」ボタンを表示します。
確認ダイアログ: onclick を入れているので、間違えて押しても「本当に削除しますか?」という確認が出ます。
新規作成時は隠す: {% if it.id > 0 %} で囲っているため、新しいメモ(まだ保存していないメモ)を書いている時は、削除ボタンが表示されないようにしています。
delete-form の分離: HTMLのルール上、フォームを入れ子にできないため、削除ボタンをクリックした時に別の「削除専用フォーム」を送信する仕組み(form=”delete-form”)にしています。
忘れずに:app.py の追記
app.py 側にも、一つ前の回答でお伝えした delete_memo 関数を追記して、ファイルを保存してから実行してください。

python
# app.py に追記する内容
@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"))

削除が追加されたメモ帳

これで、より便利で実用的なメモ帳になります!

屋外でスマホ受信できるようにするには

プログラムを修正してうまく行きました。実用的なメモ帳に近づきました。ありがとうございました。
ところで、このメモ帳はスマホを屋外に持ち出したときには使えないのですよね。大まかでいいのですが将来外で使えるようにするにはどのような準備が必要ですか?

削除機能の追加も成功、おめでとうございます!いよいよ「どこからでも使えるアプリ」への進化ですね。

自宅のWi-Fiという閉じた中での動作

おっしゃる通り、現在の状態は「自宅のWi-Fiという閉じた箱の中」だけで動いています。これを屋外(外出先のiPhoneなど)から使えるようにするには、「インターネットという公道」にアプリを出す必要があります。
大まかに分けて、3つのステップが必要です。

1. 「公開用サーバー」を借りる
現在はあなたのPCをサーバーにしていますが、外出先からアクセスするには「24時間365日、インターネットに繋がりっぱなしのコンピューター」が必要です。これをレンタルサーバー(VPSやクラウドサービス)と呼びます。
代表的なサービス:
Pythonに強いクラウド: Render (レンダー) や PythonAnywhere (初心者向けで有名)
国内サーバー: ConoHa VPS や さくらのVPS(設定は少し難しいですが安定しています)
※普段お使いの「Xserver」などの一般的なレンタルサーバーは、PHPという言語が得意なものが多いため、Flask(Python)を動かすには少し特殊な設定が必要です。

2. 「ドメイン(住所)」を取得する
現在は 192.168… という数字でアクセスしていますが、これは「内線番号」のようなものです。外からアクセスするには、世界共通の住所であるドメイン名(例:my-memo-app.com など)が必要になります。

3. 「デプロイ(公開作業)」を学ぶ
「自分のPCで作ったプログラム」を「借りたサーバー」にコピーして動くように設定する作業を デプロイ と呼びます。
これには、これまで学んできた Flask の知識に加え、以下のスキルが少しずつ必要になってきます。
Git(ギット): プログラムをサーバーに送るための道具。
Linux(リナックス)コマンド: サーバー(黒い画面)を操作するコマンド。
セキュリティ設定: 誰でもアクセスできるようになるため、パスワード(ログイン機能)を付けないと他人にメモを見られてしまいます。
今すぐできる「屋外でのシミュレーション」
もし「設定は難しそうだけど、とにかく外で動くか試したい」という場合は、ngrok (エングロック) というツールを使うと、一時的に今のPCを世界中に公開して、外のiPhoneから繋ぐことができます(10分ほどで試せます)。

まとめ:
将来的に「本格的に外で使いたい!」となったら、まずは 「PythonAnywhere」 や 「Render」 といったサービスについて調べてみるのが、2025年現在最もスムーズな学習ルートです。
それまでは、今の「自宅Wi-Fi環境」で機能をどんどん追加して、最強のメモ帳に育て上げておきましょう!

削除ボタン追加でエラー発生

削除はうまく行ったのですが、新規作成をクリックすると以下のエラーになりました。

新規作成ができなくなった

エラーの原因は、新規作成時(/memo/0)に、プログラム側で作成した一時的なメモデータに「ID」が割り当てられていないためです。
HTML側の {% if it.id > 0 %} という部分で、「IDが空(None)の状態」と「0」を比較しようとして、「比較できません(TypeError)」と怒られています。
これを解決するために、app.py の memo 関数を以下のように修正してください。
修正方法:app.py
新規作成時に、仮のIDとして 0 をセットするようにします。

python
@app.route("/memo/<int:id>", methods=["GET", "POST"])
def memo(id: int):
# メモを取得
it = db.session.get(MemoItem, id)

# --- ここから修正 ---
if it is None:
# 新規作成の場合、仮のIDとして 0 を持つオブジェクトを作る
it = MemoItem(id=0, title="__無題__", body="")
# --- ここまで修正 ---

if request.method == "POST":
# (中略:既存の保存処理)
it.title = request.form.get("title", "__無題__")
it.body = request.form.get("body", "")
if it.title == "":
return "タイトルは空にできません"

# IDが0(新規)の場合はデータベースに追加
if it.id == 0:
it.id = None # データベースの自動採番に任せるためNoneに戻す
db.session.add(it)

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

return render_template("memo.html", it=it)

なぜこれで直るのか
HTML側: it.id が 0 になるので、{% if it.id > 0 %} の判定が「偽」となり、エラーを出さずに削除ボタンを非表示にします。
保存時: it.id == 0 の判定で「これは新規データだ」と判断し、db.session.add を実行して新しく保存します。
もっと簡単な解決策(HTML側を直す場合)
もしプログラムをあまり触りたくない場合は、memo.html のエラーが出ている行を以下のように書き換えるだけでも直ります。
修正前: {% if it.id > 0 %}
修正後: {% if it.id and it.id > 0 %}
※「もし ID が存在しており、かつ 0 より大きいなら」という二段構えの条件にすることで、空(None)のときに後半の比較を行わないようにします。
プログラム側の修正(IDに0を入れる)の方が確実ですので、まずは app.py の修正を試してみてください!

app.pyとmemo.html両方を修正しても変化なし

app.pyとmemo.html両方を修正して1回は新規作成がうまくできたのですが、2回目からは新規を押すと作成済みのメモが表示されます。

原因は、新しく作ったデータ(it)が、保存後も「IDが0」という状態のままプログラム内で使い回されてしまっていることにあります。
Pythonの挙動として、一度作成したデータ(オブジェクト)は、保存しても明示的に切り替えない限り、その場所を指し続けようとします。その結果、「新規作成(0)」を押すたびに、直前に保存したデータが画面に出てきてしまいます。
これを完全に解決するために、app.py の保存処理の最後に、「保存が終わったらセッションをクリアする」一文を追加してください。
修正方法:app.py
db.session.commit() の直後に、db.session.expunge_all() を追加します。これで、「保存済みのデータ」と「新規入力」が混ざるのを防げます。

python
if request.method == "POST":
it.title = request.form.get("title", "__無題__")
it.body = request.form.get("body", "")
if it.title == "":
return "タイトルは空にできません"

if it.id == 0:
it.id = None
db.session.add(it)

db.session.commit()
# --- ここを追加 ---
db.session.expunge_all()
# ------------------
return redirect(url_for("index"))

なぜこれで直るのか
問題の起きていた状態: commit() しても、it という変数は「さっき保存したデータ」を握りしめたままです。次に誰かが /memo/0 にアクセスしたとき、サーバーが気を利かせて(?)「さっきのデータがまだメモリにあるよ!」とそれを出してしまっていました。
expunge_all() の効果: 「今メモリに覚えているデータをすべて忘れてください」という命令です。これにより、次に「新規作成(0)」が呼ばれたとき、確実に it is None の判定が通り、新しい真っ白なメモが作られるようになります。
もう一つの解決策(より確実)
もし上記でも直らない場合は、memo 関数の冒頭で 「IDが0なら問答無用で新しく作る」 というルールをより厳格に書くのが最も確実です。

python
@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)

この 「IDが0なら強制的に新規作成」 の書き方に変えるのが、2025年現在のFlask開発において最もトラブルが少ない書き方です。ぜひ試してみてください。

以下のように直しましたが同し動作のままでなおっていません。

修正を試みていただきありがとうございます。expunge_all() でも解決しなかった理由は、Flaskの「デバッグモード」によるリロードや、変数のスコープが原因である可能性が高いです。
現在の構造(idが1つでも不整合を起こすと前のデータを引きずる)を避け、2025年の開発において最も確実で安全な「IDが0なら強制的に新規作成」という書き方に memo 関数を丸ごと差し替えてみてください。
以下のコードをコピーして、現在の def memo(id: int): 関数全体を上書きしてください。

python
# メモの編集画面を出す --- (※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)

なぜこれで解決するのか
今までは「まずデータベースから取得してみて、なければ作る」という流れでしたが、それだと IDが0(新規作成ボタン)を押した時でも、何らかの拍子に直前のデータがヒットしてしまう ことがありました。
今回の修正のポイントは 「if id == 0: なら、何が何でも新しい空の箱(MemoItem)を作る」 と最初に断言したことです。これにより、過去のデータが混ざる余地が完全になくなります。
最後に
この修正を行ってファイルを保存(Ctrl+S)したら、念のため一度ブラウザを更新(Ctrl+F5)してから「新規作成」を押してみてください。今度こそ、常に真っ白な画面が出るはずです。

プログラム修正でPC、スマホとも新規作成が動作した。

これでPC、スマホとも新規作成ができて、うまく動作しました。今後も問題ないと思います。またしばらく使い込んで勉強してみます。ありがとうございました。

PCとスマホの両方で「新規作成」が正常に動くようになり、本当によかったです!
今回のトラブル(直前のメモが残ってしまう現象)を解決したことで、「プログラムの条件分岐をより厳格に書く」という、実用的なアプリケーション開発において非常に重要なスキルが身につきました。
今後の「使い込み」へのアドバイス
データが増えてきたら:
メモが何十件も溜まってくると、一覧画面(index)の表示順や見せ方を工夫したくなるかもしれません。その時は order_by の条件を変えるだけで簡単に調整できます。
iPhoneでの活用:
「ホーム画面に追加」しておけば、もう市販のメモアプリと同じ感覚で使えます。外出先でふと思いついたアイデアを自分の作ったシステムに記録するのは、エンジニアとしての醍醐味です。
バックアップを忘れずに:
大切なメモが増えてきたら、時々 instance/memo.sqlite ファイルをコピーして保存しておいてくださいね。
数日にわたる環境構築から機能追加、そしてバグ修正まで、一通りの開発工程を完走されました。このメモ帳は、あなたにとって「プログラミングを形にした最初の資産」です。ぜひ大切に使い込んでみてください。
また新しい機能を作りたくなったり、別のアプリに挑戦したくなったりしたときは、いつでもお気軽にご相談ください。
年末の夜遅くまで本当にお疲れ様でした。どうぞ、健やかにお休みください!

コメント