技術ブログ 3週間目

目次

データ入力フォームと一覧表示(主要機能の実現)

この週の目標は、Webページ上で家計簿の項目を入力し、それがデータベースに保存され、そしてすぐに画面に表示されるという、家計簿アプリとしての最初の「使える形」を目にすることです。

1.Webフォームの作成 (Flask-WTFの導入)

ユーザーが家計簿のデータを入力するためには、Webページ上に「入力フォーム」が必要です。HTMLだけでもフォームは作れますが、FlaskではFlask-WTFという拡張機能を使うと、フォームの作成、送信されたデータの受け取り、入力値の検証(バリデーション)などを非常に簡単に行うことができます。wft…。

1-1. Flask-WTF のインストール

pip install Flask-WTF

バージョン記録 Flask-WTF-1.2.2

1-2. app.py の設定変更とフォームクラスの定義

from flask import Flask, render_template, request, redirect, url_for # request, redirect, url_for を追加
from flask_sqlalchemy import SQLAlchemy
from flask_wtf import FlaskForm # FlaskForm をインポート
from wtforms import StringField, IntegerField, DateField, SubmitField # フォーム部品をインポート
from wtforms.validators import DataRequired, NumberRange # バリデーションルールをインポート
import datetime # 日付操作のためにインポート

app = Flask(__name__)

# データベース設定の追加
# SQLiteデータベースファイルのパスを指定
# 'sqlite:///site.db' は、プロジェクトフォルダ内に 'site.db' という名前のデータベースファイルを作成することを意味します。
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
# 追跡機能を無効にする(リソース節約のため)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# SQLAlchemyオブジェクトの初期化
db = SQLAlchemy(app)

# 家計簿の「記録」を保存するためのデータ構造(テーブルの設計図)をPythonコードで定義します
class Transaction(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    date = db.Column(db.String(20), nullable=False) # 日付 (例: '2025-07-01')
    item = db.Column(db.String(100), nullable=False) # 項目名 (例: '食費', '交通費')
    amount = db.Column(db.Integer, nullable=False) # 金額 (整数)

    def __repr__(self):
        return f"Transaction('{self.date}', '{self.item}', {self.amount})"

#フォームクラスの定義
class TransactionForm(FlaskForm):
    # 日付フィールド:日付を選択できるようにします
    date = DateField('日付', format='%Y-%m-%d', default=datetime.date.today, validators=[DataRequired()])
    # 項目フィールド:テキスト入力
    item = StringField('項目', validators=[DataRequired()])
    # 金額フィールド:整数入力、0以上であることを要求
    amount = IntegerField('金額', validators=[DataRequired(), NumberRange(min=0)])
    # 送信ボタン
    submit = SubmitField('追加')
#

@app.route("/")
def index():
    return render_template("index.html")

@app.route("/about") # 新しいルーティングを追加
def about():
    #return "<h1>このアプリについて</h1><p>これは私の初めてのFlaskアプリです。</p>"
    return render_template("about.html") #HTMLテンプレートをレンダリングして返す

@app.route("/greet/<string:name>") # <name> 部分がURLパラメータ
def greet(name): # 関数にパラメータを受け取る引数を追加
    # 変更前: return f"<h1>Hello, {name}!</h1><p>Welcome to your personalized page!</p>"
    # 変更後: render_template を使って greet.html にデータを渡す
    return render_template("greet.html", username=name) # usernameという名前で name の値を渡す

@app.route("/user/<int:user_id>")
def show_user(user_id):
    return f"ユーザーIDは {user_id} です。"

@app.route("/calculate/<num1>/<num2>")
def caluculate(num1,num2):
    try:
        # URLパラメータは文字列として受け取られるので、int()で数値に変換
        number1 = int(num1)
        number2 = int(num2)
        total = number1 + number2

        # calculate.html に計算結果を渡してレンダリング
        return render_template("calculate.html",num1 = number1,num2 = number2,total = total)
     
    except ValueError:
        # エラーページをレンダリングすることも可能ですが、今回は直接メッセージを返します
        return "エラー: 無効な数値が指定されました。", 400

@app.route("/product/<id>/<name>")
def product(id,name):
    return render_template("product.html", product_id = id, product_name = name)

if __name__ == '__main__':
    # データベースの初回作成・更新が必要な場合は、コメントを外して実行してください(一度実行したらコメントアウトしてOK)
    # with app.app_context():
    #     db.create_all()
    app.run(debug=True)

2.家計簿入力ページのルーティングと表示

次に、家計簿の入力フォームを表示するための新しいルーティングと関数をapp.pyに追加します。

# ... 省略(既存のコード) ...

# 家計簿の追加・表示ページ
@app.route("/add_transaction", methods=['GET', 'POST']) # GETとPOSTの両方のリクエストを受け付ける
def add_transaction():
    form = TransactionForm() # フォームのインスタンスを作成
    # フォームが送信され、かつ入力が有効な場合
    if form.validate_on_submit():
        # フォームからデータを受け取る
        new_transaction = Transaction(
            date=form.date.data,
            item=form.item.data,
            amount=form.amount.data
        )
        # データベースに追加・保存
        db.session.add(new_transaction)
        db.session.commit()
        # 追加後に一覧ページにリダイレクト
        return redirect(url_for('add_transaction')) # 同じページにリダイレクトすることでフォームをリセット

    # GETリクエストまたはフォームが検証に失敗した場合
    # データベースから全ての取引データを読み出す
    all_transactions = Transaction.query.order_by(Transaction.date.desc()).all() # 日付の新しい順に並べ替え
    return render_template("add_transaction.html", form=form, transactions=all_transactions)

# ... 省略(既存のコード) ...

3. add_transaction.html ファイルの作成

次に、templatesフォルダの中に、家計簿の入力フォームと一覧表示を行うadd_transaction.htmlという新しいHTMLテンプレートファイルを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>家計簿アプリ - 記録の追加と一覧</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>家計簿アプリ</h1>
    <p><a href="/">トップページに戻る</a> | <a href="/about">このアプリについて</a></p>

    <h2>新しい記録を追加</h2>
    {# Flask-WTFフォームの開始。actionは空で同じURLに送信、methodはPOST #}
    <form method="POST" action="">
        {# フォームのセキュリティトークン(必須) #}
        {{ form.csrf_token }}

        {# 日付フィールド #}
        <div>
            {{ form.date.label }}<br>
            {{ form.date() }}
            {% if form.date.errors %}
                <ul class="errors">
                    {% for error in form.date.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 項目フィールド #}
        <div>
            {{ form.item.label }}<br>
            {{ form.item() }}
            {% if form.item.errors %}
                <ul class="errors">
                    {% for error in form.item.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 金額フィールド #}
        <div>
            {{ form.amount.label }}<br>
            {{ form.amount() }}
            {% if form.amount.errors %}
                <ul class="errors">
                    {% for error in form.amount.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 送信ボタン #}
        <div>
            {{ form.submit() }}
        </div>
    </form>

    <hr> {# 区切り線 #}

    <h2>これまでの記録</h2>
    {% if transactions %} {# 記録がある場合のみ表示 #}
        <table border="1" style="width:100%; border-collapse: collapse;">
            <thead>
                <tr>
                    <th>日付</th>
                    <th>項目</th>
                    <th>金額</th>
                </tr>
            </thead>
            <tbody>
                {# Pythonから渡された transactions リストをループ処理で表示 #}
                {% for t in transactions %}
                <tr>
                    <td>{{ t.date.strftime('%Y-%m-%d') }}</td> {# 日付をフォーマットして表示 #}
                    <td>{{ t.item }}</td>
                    <td>{{ t.amount | int }}円</td> {# 金額を整数として表示し「円」を追加 #}
                </tr>
                {% endfor %}
            </tbody>
        </table>
    {% else %}
        <p>まだ記録がありません。</p>
    {% endif %}

</body>
</html>

Flaskアプリケーションを再起動して確認する

重要メモ

# ★重要★
# Transactionモデルのdateカラムの型を変更した場合
# 1. instanceフォルダ内の site.db ファイルを削除
# 2. app.py の app.run(debug=True) の前に
#    with app.app_context():
#        db.create_all()
#    のコメントアウトを一時的に外し、一度アプリを実行しデータベースを再作成
# 3. データベースが作成されたら、上記2行はコメントアウトするか削除してOK

完成形

データの追加と表示、保存ができました。

家計簿データの「編集」と「削除」機能

1.既存の記録を「削除」する機能の実装

まずは、一番シンプルで分かりやすい「削除」機能から実装していきます。

1-1. app.py に削除ルーティングと関数を追加

# 記録の削除
@app.route("/delete_transaction/<int:transaction_id>", methods=['POST'])
def delete_transaction(transaction_id):
    transaction_to_delete = Transaction.query.get_or_404(transaction_id) # IDで記録を検索
    db.session.delete(transaction_to_delete) # データベースから削除
    db.session.commit() # 変更を保存
    return redirect(url_for('add_transaction')) # 削除後、一覧ページにリダイレクト

1-2. add_transaction.html に削除ボタンを追加

<tbody>
                {% for t in transactions %}
                <tr>
                    <td>{{ t.date.strftime('%Y-%m-%d') }}</td>
                    <td>{{ t.item }}</td>
                    <td>{{ t.amount | int }}円</td>
                    {# ★ここから追加 - 削除ボタン #}
                    <td>
                        <form method="POST" action="{{ url_for('delete_transaction', transaction_id=t.id) }}">
                            <input type="submit" value="削除" onclick="return confirm('本当に削除しますか?');">
                        </form>
                    </td>
                    {# ★ここまで追加 #}
                </tr>
                {% endfor %}
            </tbody>

削除ボタンの作成、そして削除の実行が確認できました。

2.既存の記録を「編集」する機能の実装

次に、既存の家計簿記録を編集できるようにします。「編集」は「追加」と似ていますが、既存のデータを読み込み、それを更新する点が異なります。

2-1. app.py に編集ルーティングと関数を追加

# ... 省略(既存の delete_transaction 関数など) ...

# 記録の編集
@app.route("/edit_transaction/<int:transaction_id>", methods=['GET', 'POST'])
def edit_transaction(transaction_id):
    transaction = Transaction.query.get_or_404(transaction_id) # IDで編集対象の記録を検索
    form = TransactionForm(obj=transaction) # 既存データでフォームを初期化

    if form.validate_on_submit(): # フォームが送信され、かつ入力が有効な場合
        # フォームのデータをデータベースの記録に更新
        transaction.date = form.date.data
        transaction.item = form.item.data
        transaction.amount = form.amount.data
        db.session.commit() # 変更を保存
        return redirect(url_for('add_transaction')) # 更新後、一覧ページにリダイレクト
    elif request.method == 'GET': # GETリクエストの場合 (ページ表示時)
        # フォームの初期値をデータベースの記録のデータで設定
        form.date.data = transaction.date
        form.item.data = transaction.item
        form.amount.data = transaction.amount

    return render_template('edit_transaction.html', form=form, transaction=transaction)

2-2. add_transaction.html に編集ボタンを追加

<tbody>
                {% for t in transactions %}
                <tr>
                    <td>{{ t.date.strftime('%Y-%m-%d') }}</td>
                    <td>{{ t.item }}</td>
                    <td>{{ t.amount | int }}円</td>
                    {# ★ここから追加 - 編集ボタン #}
                    <td>
                        <a href="{{ url_for('edit_transaction', transaction_id=t.id) }}">編集</a>
                    </td>
                    {# ★ここまで追加 #}
                    <td>
                        <form method="POST" action="{{ url_for('delete_transaction', transaction_id=t.id) }}">
                            <input type="submit" value="削除" onclick="return confirm('本当に削除しますか?');">
                        </form>
                    </td>
                </tr>
                {% endfor %}
            </tbody>

2-3. edit_transaction.html ファイルの作成

次に、templatesフォルダの中に、編集フォームを表示するためのedit_transaction.htmlという新しいHTMLテンプレートファイルを作成します。

2-4. edit_transaction.html にHTMLコードを記述する

作成したedit_transaction.htmlファイルをテキストエディタで開いて、以下のHTMLコードをコピー&ペーストして保存します。これは、基本的な構造がadd_transaction.htmlのフォーム部分と似ています。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>家計簿アプリ - 記録の編集</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <h1>記録を編集</h1>
    <p><a href="{{ url_for('add_transaction') }}">家計簿一覧に戻る</a></p>

    {# Flask-WTFフォームの開始。actionは空で同じURLに送信、methodはPOST #}
    <form method="POST" action="">
        {# フォームのセキュリティトークン(必須) #}
        {{ form.csrf_token }}

        {# 日付フィールド #}
        <div>
            {{ form.date.label }}<br>
            {{ form.date() }}
            {% if form.date.errors %}
                <ul class="errors">
                    {% for error in form.date.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 項目フィールド #}
        <div>
            {{ form.item.label }}<br>
            {{ form.item() }}
            {% if form.item.errors %}
                <ul class="errors">
                    {% for error in form.item.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 金額フィールド #}
        <div>
            {{ form.amount.label }}<br>
            {{ form.amount() }}
            {% if form.amount.errors %}
                <ul class="errors">
                    {% for error in form.amount.errors %}
                        <li>{{ error }}</li>
                    {% endfor %}
                </ul>
            {% endif %}
        </div>
        <br>

        {# 送信ボタン #}
        <div>
            {{ form.submit(value='更新') }} {# ボタンの表示を「更新」に変更 #}
        </div>
    </form>
</body>
</html>

3.Flaskアプリケーションを再起動して確認する

削除と編集機能、共に実装できました。第3週はこれで終わりです。
全てのコードを把握できているわけではないですが、Geminiを駆使してエラーはすぐに解消できました。

計画の第4週までを現実では約1週間ほどで到達しました。朝と夜のまとまった時間、そして休日をしっかり使えました。最後の仕上げ期間を設け「オリジナルの応用機能」を加えることで、コードの理解と実践的なスキルを身に着けたいと考えています。

第4週では「見た目の改善」「簡易的な集計機能」の追加を行います。

おまけ

毎度恒例、NotebookLMに読み込ませてみました。以下、音声データです。

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次