11-Flask

Flask(Part.14)| 【商品データの編集と削除機能の実装】

python| まとめ | 現役エンジニア&プログラミングスクール講師「python」のまとめページです。pythonに関して抑えておきたい知識や文法やにについて記事をまとめています。まとめページの下部には「おすすめの学習書籍」「おすすめのITスクール情報」「おすすめ求人サイト」について情報を掲載中...

目標

  • ファイル名のリファクタリングが行える。
  • 商品データの編集と削除機能を実装できる。

Flaskアプリケーションに機能を追加する

商品データの編集と削除機能を追加する

Part.13までに作成したアプリケーションでは、商品データの編集や削除機能がありませんでした。このままだと、入力ミスがあった際に不必要なデータが残り、不便です。そこで、この記事では商品データの編集と削除機能を追加します。

今後、この商品は「注文処理」を通じて注文テーブルの参照先となるため、削除処理は注文処理が行われる前のみ利用可能となります。

ファイル名のリファクタリング

add_product.htmlファイル名のリファクタリング

Part.13までに作成したテンプレートには「add_product.html」「products.html」「category_form.html」「categories.html」「manage_products.html」があります。その中で、カテゴリーデータの追加・編集をおこなうためのテンプレートは「category_form.html」です。

現在、商品の追加・編集を行うテンプレートは「add_product.html」ですが、名前を統一するために「product_form.html」に変更します。

add_product.htmlファイルを右クリックして「Rename」をクリックします。

ファイル名を「product_form.html」に変更します。

「app.py」の次の部分を修正します。

# ルート: 商品追加ページ
@app.route("/add_product", methods=["GET", "POST"])
def add_product():
    form = AddProductForm()
    form.set_category_choices()  # カテゴリーリストを設定

    if form.validate_on_submit():
        # フォームが送信され、バリデーションが成功した場合
        name = form.product_name.data
        price = form.product_price.data
        category_id = form.category.data

        # 新しい商品をデータベースに追加
        new_product = Product(name=name, price=price, category_id=category_id)
        db.session.add(new_product)  # セッションに追加
        db.session.commit()  # データベースにコミット

        # 商品一覧ページにリダイレクト
        return redirect(url_for("product_list"))

    return render_template("add_product.html", form=form)

return render_template(“add_product.html“, form=form)

return render_template(“product_form.html“, form=form)

今回のファイル名のリファクタリングは以上です。

商品データの編集と削除機能の追加

app.pyファイルの編集

app.pyの編集後の内容です。商品の「編集」と「削除」のルートを作成しています。

from itertools import groupby

from flask import Flask, flash, redirect, render_template, url_for

from apps.productsapp.forms import AddProductForm, CategoryForm
from apps.productsapp.models import Category, Product, db  # models.pyからインポート

# アプリケーションの初期化
app = Flask(__name__)
app.secret_key = "your_secret_key"

# SQLiteの設定
app.config["SQLALCHEMY_DATABASE_URI"] = (
    "sqlite:///products.db"  # SQLiteデータベースファイル
)
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False  # 不要な変更追跡を無効化

# SQLAlchemyのインスタンスを初期化
db.init_app(app)


# アプリケーションコンテキスト内でテーブル作成
with app.app_context():
    db.create_all()


# ルート: トップページ
@app.route("/")
def index():
    return "Top page"


# カテゴリー一覧を表示
@app.route("/categories")
def category_list():
    categories = Category.query.all()
    return render_template("categories.html", categories=categories)


# カテゴリーを追加
@app.route("/category/add", methods=["GET", "POST"])
def add_category():
    form = CategoryForm()
    if form.validate_on_submit():
        new_category = Category(name=form.name.data)
        db.session.add(new_category)
        db.session.commit()
        flash("カテゴリーを追加しました", "success")
        return redirect(url_for("category_list"))
    return render_template(
        "category_form.html", form=form, title="カテゴリー追加フォーム"
    )


# カテゴリーを編集
@app.route("/category/edit/<int:category_id>", methods=["GET", "POST"])
def edit_category(category_id):
    category = Category.query.get_or_404(category_id)
    form = CategoryForm(obj=category)

    if form.validate_on_submit():
        category.name = form.name.data
        db.session.commit()
        flash("カテゴリーを更新しました", "success")
        return redirect(url_for("category_list"))

    return render_template("category_form.html", form=form, title="カテゴリー編集")


# カテゴリーを削除
@app.route("/category/delete/<int:category_id>", methods=["POST"])
def delete_category(category_id):
    category = Category.query.get_or_404(category_id)

    # もしカテゴリーに紐づいた商品があればエラーを出す
    if category.products:
        flash("このカテゴリーには商品が登録されているため削除できません", "danger")
        return redirect(url_for("category_list"))

    db.session.delete(category)
    db.session.commit()
    flash("カテゴリーを削除しました", "success")
    return redirect(url_for("category_list"))


# ルート: 商品一覧ページ
@app.route("/products")
def product_list():
    products = Product.query.all()  # SQLiteから商品データを取得
    # 商品をカテゴリーごとにグループ化
    grouped_products = {}
    for key, group in groupby(
        sorted(products, key=lambda p: p.category.name if p.category else "なし"),
        key=lambda p: p.category.name if p.category else "なし",
    ):
        grouped_products[key] = list(group)

    return render_template("products.html", grouped_products=grouped_products)


# ルート: 商品追加ページ
@app.route("/add_product", methods=["GET", "POST"])
def add_product():
    form = AddProductForm()
    form.set_category_choices()  # カテゴリーリストを設定

    if form.validate_on_submit():
        # フォームが送信され、バリデーションが成功した場合
        name = form.product_name.data
        price = form.product_price.data
        category_id = form.category.data

        # 新しい商品をデータベースに追加
        new_product = Product(name=name, price=price, category_id=category_id)
        db.session.add(new_product)  # セッションに追加
        db.session.commit()  # データベースにコミット

        # 商品一覧ページにリダイレクト
        return redirect(url_for("product_list"))

    return render_template("product_form.html", form=form)


# ルート: 商品を編集
@app.route("/product/edit/<int:product_id>", methods=["GET", "POST"])
def edit_product(product_id):
    product = Product.query.get_or_404(product_id)
    form = AddProductForm(obj=product)
    form.set_category_choices()  # カテゴリーリストを設定

    if form.validate_on_submit():
        product.name = form.product_name.data
        product.price = form.product_price.data
        product.category_id = form.category.data
        db.session.commit()
        flash("商品情報を更新しました", "success")
        return redirect(url_for("product_list"))

    return render_template("product_form.html", form=form, title="商品編集")


# ルート: 商品を削除
@app.route("/product/delete/<int:product_id>", methods=["POST"])
def delete_product(product_id):
    product = Product.query.get_or_404(product_id)
    db.session.delete(product)
    db.session.commit()
    flash("商品を削除しました", "success")
    return redirect(url_for("product_list"))


# ルート: 商品管理ページ
@app.route("/manage_products")
def manage_products():
    return render_template("manage_products.html")


if __name__ == "__main__":
    app.run(debug=True)

product_form.htmlの修正

product_form.htmlの修正後の内容。inputタグのvalueの値について「”商品を登録する”」と変更しています。

<!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='css/style.css') }}">
</head>

<body>

    <h1>商品追加ページ</h1>
    <ul>
        <li><a href="{{ url_for('product_list') }}" class="button-link">商品一覧に戻る</a></li>
        <li><a href="{{ url_for('manage_products') }}" class="button-link">商品管理ページ</a></li>
    </ul>

    <h1>商品追加フォーム</h1>

    <form method="POST" action="{{ url_for('add_product') }}">
        {{ form.hidden_tag() }} <!-- CSRFトークンを含めるため -->

        <label for="product_name">商品名</label>
        {{ form.product_name() }} <!-- 商品名入力フィールド -->
        {% if form.product_name.errors %}
        <ul>
            {% for error in form.product_name.errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
        {% endif %}

        <label for="product_price">価格</label>
        {{ form.product_price() }} <!-- 価格入力フィールド -->
        {% if form.product_price.errors %}
        <ul>
            {% for error in form.product_price.errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
        {% endif %}

        <label for="category">カテゴリー</label>
        {{ form.category() }}
        {% if form.category.errors %}
        <ul>
            {% for error in form.category.errors %}
            <li>{{ error }}</li>
            {% endfor %}
        </ul>
        {% endif %}

        <input type="submit" value="商品を登録する">
    </form>

</body>

</html>

products.htmlの修正

products.htmlの修正後の内容。編集ボタンと削除ボタンをformタグを利用して表示しています。「class=”product-item”」「class=”product-name”」「class=”edit-button”」「class=”delete-button”」を該当するタグに追加しています。

<!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='css/style.css') }}">
</head>

<body>
    <h1>商品一覧ページ</h1>
    <ul>
        <li><a href="{{ url_for('add_product') }}" class="button-link">商品を追加する</a></li>
        <li><a href="{{ url_for('manage_products') }}" class="button-link">商品管理ページ</a></li>
    </ul>


    <h1>商品一覧</h1>

    {% for category, products in grouped_products.items() %}
    <h2>カテゴリー: {{ category }}</h2>
    <ul class="product-list">
        {% for product in products %}
        <li class="product-item">
            <span class="product-name">{{ product.name }} : ¥{{ "{:,}".format(product.price) }}</span>
            <div class="button-group">
                <form action="{{ url_for('edit_product', product_id=product.id) }}" method="GET"
                    style="display:inline;">
                    <button type="submit" class="edit-button">編集</button>
                </form>
                <form action="{{ url_for('delete_product', product_id=product.id) }}" method="POST"
                    style="display:inline;">
                    <button type="submit" class="delete-button"
                        onclick="return confirm('本当に削除しますか?※既に注文で利用されている場合は削除できません。')">削除</button>
                </form>
            </div>
        </li>
        {% endfor %}
    </ul>
    {% endfor %}

</body>

</html>

スタイルシートへのセレクタ追加

style.css

body {
    font-family: Arial, sans-serif;
    margin: 20px;
    padding: 20px;
    background-color: #f8f8f8;
}

h1 {
    color: #333;
}

/* ボタン部分のリストを横並びにする */
ul {
    padding: 0;
}

ul:first-of-type {
    display: flex;
    /* 横並び */
    justify-content: flex-start;
    /* 左寄せ */
}

ul:first-of-type li {
    margin-right: 10px;
    /* ボタン間に余白 */
}

/* リストアイテムの基本スタイル */
li {
    list-style-type: none;
    /* デフォルトのリストスタイルを削除 */
}

/* カテゴリーごとの商品リストのスタイル */
ul.product-list {
    list-style-type: none;
    padding: 0;
}

ul.product-list li {
    background-color: #fff;
    padding: 10px;
    margin: 5px 0;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* ボタンリンクのスタイル */
.button-link {
    display: block;
    padding: 10px 20px;
    background-color: #4CAF50;
    color: white;
    text-decoration: none;
    border-radius: 5px;
    font-size: 16px;
    transition: background-color 0.3s ease;
}

/* ボタンにホバーしたときのスタイル */
.button-link:hover {
    background-color: #45a049;
}

/* 商品リストのスタイル */
h2 {
    margin-top: 30px;
    /* 商品リストの見出しに余白 */
}

ul:nth-of-type(2) {
    list-style-type: none;
    /* 商品リストのマーカーを削除 */
    padding: 0;
}

ul:nth-of-type(2) li {
    background-color: #fff;
    padding: 10px;
    margin: 5px 0;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* フォーム全体のスタイル */
form {
    background-color: #fff;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
    margin-top: 30px;
}

/* フォーム内のラベルのスタイル */
form label {
    font-size: 16px;
    color: #333;
    display: block;
    margin-bottom: 8px;
}

/* フォーム内の入力フィールドのスタイル */
form input[type="text"],
form input[type="number"],
form input[type="submit"] {
    width: 100%;
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 16px;
    box-sizing: border-box;
    /* パディングを含めてサイズ調整 */
}

/* フォーム内のボタン(送信ボタン) */
form input[type="submit"] {
    background-color: #4CAF50;
    color: white;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

/* 送信ボタンにホバーしたときのスタイル */
form input[type="submit"]:hover,
form button:hover {
    background-color: #45a049;
}

/* カテゴリーの選択ボックス(select) */
form select {
    width: 100%;
    padding: 10px;
    margin-bottom: 15px;
    border: 1px solid #ddd;
    border-radius: 5px;
    font-size: 16px;
    box-sizing: border-box;
    transition: border-color 0.3s ease;
}

/* フォーカス時のスタイル */
form select:focus {
    border-color: #4CAF50;
    outline: none;
    box-shadow: 0 0 5px rgba(76, 175, 80, 0.5);
}


/* リストアイテムを横並びにする */
.category-item,
.product-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #fff;
    padding: 10px;
    margin: 5px 0;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* カテゴリー名、商品名のスタイル */
.category-name,
.product-name {
    font-size: 16px;
    flex-grow: 1;
    /* 左側に余白を作り、ボタンを右寄せ */
}

/* ボタンを右側に配置 */
.button-group {
    display: flex;
    gap: 10px;
    /* ボタン間のスペース */
}

/* ボタンを囲む form のスタイルをリセット */
.button-group form {
    display: inline-block;
    margin: 0;
    padding: 0;
}

/* 編集・削除ボタンの共通スタイル */
.button-group button {
    display: inline-block;
    padding: 8px 16px;
    font-size: 14px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
    transition: background-color 0.3s ease;
    color: white;
    width: 80px;
    /* 幅を統一 */
    text-align: center;
}

/* 編集ボタンのスタイル */
.button-group .edit-button {
    background-color: #2196F3;
    /* ブルー */
}

.button-group .edit-button:hover {
    background-color: #1976D2;
    /* ホバー時の色 */
}

/* 削除ボタンのスタイル */
.button-group .delete-button {
    background-color: #F44336;
    /* レッド */
}

.button-group .delete-button:hover {
    background-color: #D32F2F;
    /* ホバー時の色 */
}

追加する内容は次のセレクタ名です。「.product-item」「.product-name」を追加しました、カンマ区切りで記述することで「.category-item」や「.category-name」と同様のスタイルを適用できます。

/* リストアイテムを横並びにする */
.category-item,
.product-item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: #fff;
    padding: 10px;
    margin: 5px 0;
    border-radius: 5px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

/* カテゴリー名、商品名のスタイル */
.category-name,
.product-name {
    font-size: 16px;
    flex-grow: 1;
    /* 左側に余白を作り、ボタンを右寄せ */
}

ブラウザで http://127.0.0.1:5000/manage_productsにアクセスします。

商品管理ページが表示されます。

商品一覧ボタンをクリックすると、商品項目の右側に編集ボタンと削除ボタンが表示されます。

グレープジュースの編集ボタンをクリックします。「編集」ボタンをクリックするとhttp://127.0.0.1:5000/product/edit/3?に移動します。

商品名に「コーラ」、価格に「350」カテゴリーに「アルコール」と入力して「商品を登録する」ボタンをクリックします。

http://127.0.0.1:5000/producs にリダイレクトされます。商品データ「コーラ」の「削除」ボタンをクリックします。

「削除」ボタンをクリックするとメッセージが表示されます。「OK」をクリックします。

「OK」をクリックするとコーラが削除されます。

今回は以上になります。

「python」おすすめ書籍 ベスト3 | 現役エンジニア&プログラミングスクール講師「python」の学習でお勧めしたい書籍をご紹介しています。お勧めする理由としては、考え方、イメージなどを適切に捉えていること、「生のpython」に焦点をあてて解説をしている書籍であることなどが理由です。勿論、この他にも良い書籍はありますが、特に質の高かったものを選んで記事にしています。ページの下部には「おすすめのITスクール情報」「おすすめ求人サイト」について情報を掲載中。...

ブックマークのすすめ

「ほわほわぶろぐ」を常に検索するのが面倒だという方はブックマークをお勧めします。ブックマークの設定は別記事にて掲載しています。

「お気に入り」の登録・削除方法【Google Chrome / Microsoft Edge】「お気に入り」の登録・削除方法【Google Chrome / Microsoft Edge】について解説している記事です。削除方法も掲載しています。...
【パソコン選び】失敗しないための重要ポイント | 現役エンジニア&プログラミングスクール講師【パソコン選び】失敗しないための重要ポイントについての記事です。パソコンのタイプと購入時に検討すべき点・家電量販店で見かけるCPUの見方・購入者が必要とするメモリ容量・HDDとSSDについて・ディスプレイの種類・バッテリーの持ち時間や保証・Officeソフト・ウィルス対策ソフトについて書いています。...
RELATED POST
11-Flask

Flask(Part.8)| 【Flaskでデータベースを利用する方法(1) 実装】

2025年3月1日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site
11-Flask

Flask(Part.13)| 【ふたつのテーブルの利用(3)テンプレート部分の解説】

2025年3月6日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site
11-Flask

Flask(Part.9)| 【Flaskでデータベースを利用する方法(2)解説】

2025年3月2日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア IT業界への就職・転職希望者 サポートサイト Programming learning Recommended schools Recommended books Information dissemination Computer beginners Prospective engineers New engineers Prospective job seekers in the IT industry Support site