11-Flask

Flask(Part.38)| 【商品管理アプリケーションの修正(3)】

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

目標

  • 商品編集時に以前の商品名や価格をテキストボックスへ表示させる
  • アプリケーション内の画像を選択できるようにする
  • 商品に「販売中」「完売」「在庫切れ」「販売中止」ステータスを作成して「完売」「在庫切れ」は選択不可とし、「販売中止」は表示しない

商品管理アプリケーションの修正

商品管理アプリケーション修正

ここからは、これまで、商品管理アプリケーションで不便と感じていた部分について修正を行います。具体的には次の通りです。

  1. 商品一覧の編集ボタンをクリックしたときに、表示される編集画面で、編集前の商品名、価格、カテゴリーを初期値でセットさせます。
    (Git では release-products-001 ブランチで修正)
  2. 商品編集で画像をアップロードする以外に、既にアップロード済みの画像からも画像を選択できるようにします。
    (Git では release-products-002 ブランチで修正)
  3. 商品に「販売中」「完売」「在庫切れ」「販売中止」ステータスを作成して「完売」「在庫切れ」は選択不可とし、「販売中止」は表示しないようにします。
    (Git では release-products-003 ブランチで修正)

release-productsブランチの作成

release-productsブランチの作成

商品管理アプリケーションは既に基本となる構成については作成を完了しています。このように、機能の追加漏れがあるような場合は、再度、featureブランチを作成するよりも、releaseブランチとしてブランチを作成して作業を行うのが一般的です。

Git Bushを立ち上げるとプロンプトの右側に(develop)と表示され、現在のブランチがdevelopブランチであることが分かります。(次の表示はVisual Studio CodeのGit Bushのプロンプトです。)

User@dellDevice MINGW64 ~/Desktop/FlaskProj (develop)

次のようにコマンドを入力していきます。次のように表示されるはずです。
$ git status

$ git checkout -b release-products-003

$ git status

商品管理アプリケーションの修正(release-products-003)

app.pyの編集(productsapp内)

app.pyファイルの「edit_product関数」を次のように編集します。

# ルート: 商品を編集
@products_bp.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.category.data = product.category_id
    form.set_category_choices()

    if form.validate_on_submit():
        form.process(request.form)
        product.name = form.product_name.data
        product.price = form.product_price.data
        product.category_id = form.category.data
        product.status = form.status.data

        # 画像のアップロード処理
        if "product_image" in request.files:
            image = request.files["product_image"]
            if image and allowed_file(image.filename):
                filename = secure_filename(image.filename)
                image_path = os.path.join(current_app.config["UPLOAD_FOLDER"], filename)

                # もしすでに画像があれば削除
                if product.image_url and product.image_url != "images/noimage.png":
                    old_image_path = os.path.join(
                        current_app.config["UPLOAD_FOLDER"],
                        os.path.basename(product.image_url),
                    )
                    if os.path.exists(old_image_path):
                        os.remove(old_image_path)

                # 新しい画像を保存
                image.save(image_path)
                product.image_url = f"images/{filename}"  # 新しい画像URLを保存

            # 既存画像の選択
            elif request.form.get("selected_image"):
                product.image_url = request.form["selected_image"]

        db.session.commit()
        flash("商品情報を更新しました", "success")
        return redirect(url_for("products.product_list"))

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

form.process(request.form)を利用することでカテゴリーとステータスの更新を行えるようにしています。この記述がないと更新がうまく働きません。

今回の修正では、新しい内容はない為、この他の解説は割愛します。

models.pyの編集(productsapp内)

models.pyファイルのProductモデルを次のように編集します。

# 商品モデル(テーブル)の定義
class Product(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    price = db.Column(db.Integer, nullable=False)
    category_id = db.Column(
        db.Integer, db.ForeignKey("category.id"), nullable=True
    )  # カテゴリID
    image_url = db.Column(db.String(255), nullable=True)  # 画像のURLを保存するカラム
    status = db.Column(
        db.String(20), nullable=False, default="販売中"
    )  # 商品ステータス

    def __repr__(self):
        return f"<Product {self.id} - {self.name} - {self.price}>"

forms.pyの編集(productsapp内)

forms.pyファイルのAddProductFormクラスを次のように編集します。

class AddProductForm(FlaskForm):
    product_name = StringField("商品名", validators=[DataRequired()])
    product_price = IntegerField(
        "価格",
        validators=[
            DataRequired(),
            NumberRange(min=1, message="価格は1以上である必要があります"),
        ],
    )
    category = SelectField("カテゴリー", coerce=int)  # カテゴリー選択フィールド
    product_image = FileField(
        "商品画像",
        validators=[
            FileAllowed(
                ["jpg", "png", "jpeg", "gif"], "画像ファイルのみ許可されています"
            )
        ],
    )
    status = SelectField(
        "Status",
        choices=[
            ("available", "販売中"),
            ("sold_out", "完売"),
            ("out_of_stock", "在庫切れ"),
            ("discontinued", "販売終了"),
        ],
        default="available",
    )

    def set_category_choices(self):
        self.category.choices = [
            (c.id, c.name) for c in Category.query.all()
        ]  # DBから取得

product_form.htmlの編集(productsapp/templates内)

product_form.html を次のように編集します。

{% extends "base.html" %}

{% block title %}{{ title }}{% endblock %}

{% block header %}
<ul>
    <li><a href="{{ url_for('products.manage_products') }}" class="button-link">商品管理ページ</a></li>
    <li><a href="{{ url_for('products.product_list') }}" class="button-link">商品一覧に戻る</a></li>
</ul>
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>

<form method="POST"
    action="{{ url_for('products.edit_product', product_id=product.id) if product else url_for('products.add_product') }}"
    enctype="multipart/form-data">

    {{ form.hidden_tag() }} <!-- CSRFトークンを含めるため -->

    <label for="product_name">商品名</label>
    {{ form.product_name(value=product.name if product else '') }} <!-- 商品名入力フィールド -->
    {% 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(value=product.price if product else '') }} <!-- 価格入力フィールド -->
    {% 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 %}

    <label for="product_status">ステータス</label>
    {{ form.status() }}
    {% if form.status.errors %}
    <ul>
        {% for error in form.status.errors %}
        <li>{{ error }}</li>
        {% endfor %}
    </ul>
    {% endif %}

    <label for="product_image">商品画像</label>
    <!-- 既存の画像がある場合は表示 -->
    {% if product and product.image_url %}
    <div>
        <img id="preview-image" src="{{ url_for('products.static', filename=product.image_url) }}" alt="現在の画像"
            style="width: 100px; height: 100px;">
        <p>※新しい画像を選択すると上書きされます</p>
        <p id="upload-file-name"></p>
    </div>
    {% else %}
    <img id="preview-image" src="" alt="プレビュー" style="display: none; width: 100px; height: 100px;">
    {% endif %}
    <label for="product_image" class="file-upload-label">画像のアップロード</label>
    <input type="file" id="product_image" name="product_image" accept="image/*">

    <!-- 画像選択ボタン -->
    <label for="selected-image" id="select-image-btn">アプリ内画像から選択</label>
    <input type="hidden" id="selected-image" name="selected_image" value="{{ product.image_url if product else '' }}">

    <!-- 画像選択モーダル(初期状態は非表示) -->
    <!-- 画像選択モーダル -->
    <div id="image-modal" class="modal" style="display: none;">
        <div class="modal-content">
            <span id="close-modal" class="close">×</span>
            <h3>画像を選択</h3>
            <div id="image-list"></div>
        </div>
    </div>

    <input type="submit" value="商品を登録する">
</form>
<script>
    document.getElementById("product_image").addEventListener("change", function () {
        const file = this.files[0];
        const fileNameDisplay = document.getElementById("upload-file-name");

        if (file) {
            fileNameDisplay.textContent = file.name;

            // 画像プレビューを表示
            const reader = new FileReader();
            reader.onload = function (e) {
                const preview = document.getElementById("preview-image");
                preview.src = e.target.result;
                preview.style.display = "block";
            };
            reader.readAsDataURL(file);
        }
    });

    document.addEventListener("DOMContentLoaded", function () {
        fetch("{{ url_for('products.list_product_images') }}")
            .then(response => response.json())
            .then(data => {
                const imageList = document.getElementById("image-list");
                imageList.innerHTML = "";
                data.images.forEach(image => {
                    const imgElement = document.createElement("img");
                    imgElement.src = "{{ url_for('products.static', filename='images/') }}" + image;
                    imgElement.classList.add("selectable-image");
                    imgElement.style.width = "100px";
                    imgElement.style.height = "100px";
                    imgElement.style.cursor = "pointer";
                    imgElement.dataset.image = "images/" + image;

                    imgElement.addEventListener("click", function () {
                        document.getElementById("selected-image").value = this.dataset.image;
                        document.getElementById("preview-image").src = this.src;
                        document.getElementById("preview-image").style.display = "block";
                        document.getElementById("image-modal").style.display = "none";
                    });

                    imageList.appendChild(imgElement);
                });
            })
            .catch(error => console.error("画像取得エラー:", error)); // エラーハンドリング
    });


    document.getElementById("select-image-btn").addEventListener("click", function () {
        document.getElementById("image-modal").style.display = "block";
    });

    document.getElementById("close-modal").addEventListener("click", function () {
        document.getElementById("image-modal").style.display = "none";
    });
</script>

{% endblock %}

order_menu.htmlの編集(productsapp/templates内)

order_menu.html を次のように編集します。

{% extends 'order_base.html' %}

{% block title %}{{ title }}{% endblock %}

{% block content %}
<h2>メニュー</h2>

<!-- カテゴリータブ -->
<div class="tab-container">
    <div class="tab active" data-category="all">すべて</div>
    {% for category in categories %}
    <div class="tab" data-category="{{ category.id }}">{{ category.name }}</div>
    {% endfor %}
</div>

<div class="content-container">
    <!-- メニューリスト -->
    <div class="menu">
        {% for product in products %}
        {% if product.status != 'discontinued' %}
        <div class="menu-item" data-category="{{ product.category_id }}" {% if product.status=='sold_out' or product.status=='out_of_stock' or
            table.bill.status=="pending" %} style="pointer-events: none; opacity: 0.5;" {% endif %}>
            <a href="{{ url_for('orders.order_form', table_id=table.id, product_id=product.id) }}" target="_self">
                <img src="{{ url_for('products.static', filename=product.image_url) }}" alt="{{ product.name }}">
                <p>{{ product.name }}</p>
                <p>¥{{ product.price }}</p>
                {% if product.status == 'sold_out' %}
                <span class="status-label sold-out-label">完売</span>
                {% elif product.status == 'out_of_stock' %}
                <span class="status-label out-of-stock-label">在庫切れ</span>
                {% endif %}
            </a>
        </div>
        {% endif %}
        {% endfor %}
    </div>

    <!-- 注文リスト(右側に配置) -->
    <div class="order-summary">
        <!-- 合計金額 -->
        <div class="total-amount-container">
            <p><strong>合計金額: ¥{{ total_amount | int }}</strong></p>
        </div>
        <div class="cart-check-container">
            <!-- カート確認ページへのリンク -->
            <a href="{{ url_for('orders.order_cart', table_id=table.id) }}">カートを確認</a>
        </div>

        <!-- 現在の注文リスト -->
        <div class="order-list-container">
            <h3>現在の注文リスト</h3>
            <ul class="order-list">
                {% for order in orders %}
                <li>{{ order.product_name }} x{{ order.quantity }} (¥{{ order.total | int }})</li>
                {% else %}
                <li>現在、注文はありません。</li>
                {% endfor %}
            </ul>
        </div>

        <!-- ステータス変更ボタン -->
        <div class="status-change-container">
            {% if table.bill.status == "Order in progress" %}
            <form action="{{ url_for('orders.change_status', table_id=table.id, status='pending') }}" method="post">
                <button type="submit" class="status-button">会計予定に変更</button>
            </form>
            {% elif table.bill.status == "pending" %}
            <form action="{{ url_for('orders.change_status', table_id=table.id, status='Order in progress') }}"
                method="post">
                <button type="submit" class="status-button">注文可能に戻す</button>
            </form>
            {% endif %}
        </div>
    </div>
</div>

<!-- カテゴリータブの切り替えスクリプト -->
<script>
    document.addEventListener("DOMContentLoaded", function () {
        const tabs = document.querySelectorAll(".tab");
        const items = document.querySelectorAll(".menu-item");

        tabs.forEach(tab => {
            tab.addEventListener("click", function () {
                const category = this.getAttribute("data-category");

                // タブのアクティブ状態を更新
                tabs.forEach(t => t.classList.remove("active"));
                this.classList.add("active");

                // メニューアイテムの表示切り替え
                items.forEach(item => {
                    if (category === "all" || item.getAttribute("data-category") === category) {
                        item.style.display = "block";
                    } else {
                        item.style.display = "none";
                    }
                });
            });
        });
    });
</script>

{% endblock %}

style.cssの編集(ordersapp/static/css内)

style.css を次のように編集します。

/* 全体のスタイル */
body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    /* 水平方向の中央揃え */
    align-items: center;
    background-color: #f4f4f9;
    /* ビューポート全体を使う */
    height: 100vh;
    justify-content: flex-start;
}

main {
    justify-content: center;
    /* 垂直方向の中央揃え */
    padding-bottom: 100px;
}

/* メインフォーム */
form {
    padding: 20px;
    max-width: 600px;
    width: 100%;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}

/* ヘッダー */
h2 {
    text-align: center;
    font-size: 2rem;
    color: #333;
    margin: 50px 0 20px;
}

h3 {
    text-align: center;
    font-size: 1.3rem;
    color: #333;
    margin: 50px 0 20px;
}

a {
    text-decoration: none;
    color: inherit;
}

/* メインコンテナ */
.content-container {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    max-width: 1200px;
    width: 100%;
    padding: 20px;
    gap: 20px;
}

/* メニューエリア */
.menu {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    width: 80%;
    margin: 10px;
}

.menu-item {
    position: relative;
    text-align: center;
    padding: 0px;
    border: 1px solid #ddd;
    background-color: #ffffff;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    transition: transform 0.3s, box-shadow 0.3s;
}

.menu-item:hover {
    transform: scale(1.02);
    box-shadow: 0px 6px 12px rgba(0, 0, 0, 0.2);
}

.menu-item img {
    width: 100%;
    height: auto;
    border-radius: 0;
}

/* カテゴリータブ */
.tab-container {
    display: flex;
    justify-content: center;
    margin-bottom: 20px;
}

.tab {
    padding: 10px 20px;
    margin: 0 5px;
    cursor: pointer;
    background-color: #e1e9ef;
    color: black;
    transition: background-color 0.3s;
}

.tab:hover {
    background-color: #ff9558;
    color: white;
}

.tab.active {
    background-color: #ff9558;
    color: white;
}

/* 注文リストエリア(右側) */
.order-summary {
    width: 25%;
    position: sticky;
    top: 10px;
    background-color: #ffffff;
    padding: 15px;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
}

/* 合計金額のスタイル */
.total-amount-container {
    text-align: center;
    font-size: 1.3rem;
    font-weight: bold;
    margin-bottom: 10px;
    padding: 10px;
    background-color: #fffbcc;
    border: 1px solid #ddd;
}

/* カート確認のスタイル */
.cart-check-container {
    text-align: center;
    font-size: 1.3rem;
    font-weight: bold;
    margin-bottom: 10px;
    padding: 10px;
    background-color: #ffd6cc;
    border: 1px solid #ddd;
}

/* 注文リストのコンテナ */
.order-list-container {
    max-height: 400px;
    overflow-y: auto;
    padding: 10px;
    background-color: #fff;
    border: 1px solid #ddd;
}

.order-list-container h3 {
    text-align: center;
    margin-top: 0;
}

.order-list {
    list-style: none;
    padding: 0;
    margin: 0;
}

.order-list li {
    padding: 5px;
    border-bottom: 1px solid #ddd;
}

/* スクロールバーのカスタマイズ */
.order-list-container::-webkit-scrollbar {
    width: 8px;
}

.order-list-container::-webkit-scrollbar-thumb {
    background: #aaa;
}

/* 商品の情報 */
.product-info {
    display: flex;
    align-items: center;
    gap: 20px;
    border-bottom: 1px solid #ddd;
    padding-bottom: 20px;
    width: 100%;
    justify-content: center;
}

/* 商品画像 */
.product-info img {
    width: 150px;
    height: 150px;
    object-fit: cover;
    border-radius: 8px;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
}

/* 商品名と価格 */
.product-info p {
    font-size: 1.1rem;
    color: #555;
}

/* 数量選択 */
label {
    font-size: 1.1rem;
    color: #333;
}

/* 数量フォーム */
input[name="quantity"] {
    width: 60px;
    padding: 8px;
    font-size: 1rem;
    border: 1px solid #ddd;
    border-radius: 0;
    text-align: center;
    margin: 0 auto;
}

/* 数量入力フォームのスタイル */
.quantity-form,
.remove-form {
    display: inline-block;
    align-items: center;
    gap: 10px;
}

.quantity-form input {
    width: 60px;
    padding: 8px;
    font-size: 1rem;
    border: 1px solid #ddd;
    border-radius: 0;
    text-align: center;
    display: flex;
    justify-content: center;
}

.quantity-form button,
.remove-form button {
    padding: 8px 15px;
    font-size: 1rem;
    background-color: #ff9558;
    color: white;
    border: 1px solid #ddd;
    cursor: pointer;
    transition: background-color 0.3s ease;
}

.quantity-form button:hover,
.remove-form button:hover {
    background-color: #e77f44;
}

/* ボタン */
button[type="submit"] {
    background-color: #ff9558;
    color: white;
    padding: 12px;
    font-size: 1.2rem;
    border: none;
    cursor: pointer;
    transition: background-color 0.3s ease;
    width: 100%;
    margin-top: 20px;
}

button[type="submit"]:hover {
    background-color: #e77f44;
}

/* 入力フィールドのスタイル */
input,
select,
button {
    padding: 10px;
    cursor: pointer;
    font-size: 1rem;
    border: 1px solid #ddd;
    border-radius: 0;
}

/* カート全体のコンテナ */
.cart-container {
    width: 100%;
    margin: 0 auto;
    padding: 0;
}

.cart-info {
    display: flex;
    align-items: center;
    gap: 15px;
    margin: 10px;
    flex-grow: 1;
}

/* カートの画像 */
.cart-info img {
    width: 100px;
    height: 100px;
    object-fit: cover;
    border-radius: 8px;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
}

.cart-info span {
    font-size: 1rem;
    color: #555;
    /* 文字を折り返さないようにする */
    white-space: nowrap;
}

/* カートのアイテムリスト */
.cart-list {
    list-style-type: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: row;
    align-items: center;
    border-bottom: 1px solid #ddd;
    padding: 10px 0;
}

/* カートの各アイテム */
.cart-list li {
    padding: 10px 0;
    border-bottom: 1px solid #ddd;
    font-size: 18px;
}

/* 合計金額の表示 */
.total-amount {
    font-size: 1.5rem;
    font-weight: bold;
    padding: 15px;
    width: 100%;
    margin-bottom: 20px;
    text-align: center;
}

/* キャンセルボタン */
.cancel-button {
    color: red;
    padding: 10px 20px;
    cursor: pointer;
    text-align: center;
    border-radius: 0;
    border: 1px solid red;
    background-color: white;
}

/* 横並びのボタン */
.button-container {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    align-items: center;
    width: 286px;
    margin: 10px;
}

/* ステータス変更コンテナ */
.status-change-container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    margin-top: 30px;
    width: 100%;
    padding: 0;
    background-color: #ffffff;
    box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
    border-radius: 0;
}

.status-change-container form {
    margin: 0;
    padding: 0;
}

/* ステータス変更ボタン */
.status-button {
    background-color: #ff9558;
    color: white;
    font-size: 1.2rem;
    border: none;
    cursor: pointer;
    transition: background-color 0.3s ease;
    width: 100%;
    max-width: 300px;
    /* ボタンの最大幅 */
    text-align: center;
    margin-top: 20px;
}

.status-button:hover {
    background-color: #e77f44;
}

/* 会計予定のテキスト */
.status-change-container p {
    font-size: 1.2rem;
    color: #ff9558;
    font-weight: bold;
    margin-top: 10px;
}


/* フッター(オプション) */
footer {
    background-color: #333;
    color: white;
    text-align: center;
    padding: 10px;
    width: 100%;
    position: fixed;
    bottom: 0;
}

.status-label {
    position: absolute;
    top: 10px;
    right: 10px;
    padding: 5px 10px;
    font-size: 14px;
    font-weight: bold;
    color: #fff;
    border-radius: 5px;
}

.sold-out {
    background-color: #f8d7da;
    color: #721c24;
    pointer-events: none;
    opacity: 0.7;
}

.sold-out-label {
    background-color: #dc3545;
}

.out-of-stock {
    background-color: #fff3cd;
    color: #856404;
    pointer-events: none;
    opacity: 0.7;
}

.out-of-stock-label {
    background-color: #ffc107;
}

「dump.sql」ファイルの名前を「old_dump.sql」に変更します。

古いデータベースを削除してアプリケーションを再起動します。

http://127.0.0.1:5000/products/manage_productsにアクセスし、再度、カテゴリーと商品を登録します。

商品登録時にステータスの登録が追加されているます。商品のうち「ビール」に「完売」、「ハイボール」に「在庫切れ」、「りんごジュース」に「販売終了」を設定します。

商品一覧の見た目は、これまで通りです。

http://127.0.0.1:5000/orders/order/table/1にアクセスします。

「完売」の「ビール」と、「在庫切れ」の「ハイボール」がグレーアウトして選択できなくなります。「販売終了」の「りんごジュース」は非表示となります。

コミット作業とrelease-productsブランチの削除

作業が完了したので「release-products-003」ブランチを「develop」ブランチにマジして、「release-products-003」ブランチは削除していきます。

git statusコマンドでファイルの状態を確認します。

git add .コマンドを入力します。

再度、git statusコマンドを入力します。

git commitコマンドを入力します。

Fix:商品管理アプリケーションの修正-003(20250324)

今回は設計書などはないため、設計書番号などの提示はなし。Productテーブルに「販売中」「完売」「在庫切れ」「販売中止」を登録できるステータスを追加して、商品メニューの表示を行うテンプレートで「完売」「在庫切れ」は選択不可とし、「販売中止」は表示しないように修正。

上書き保存をして「COMMIT_EDITMSG」ファイルを閉じるとコミットが完了します。

vim や nano で開いている場合は操作が異なります。vi や vim では escキーを押し、コマンドモードに切り替えて :wq を入力し Enterキー を押下します。nanoでは「Ctrl + O」で上書き保存されます。
(※上キャプチャはvimエディタ)

developブランチへのマージ

コミットしたrelease-products-003ブランチをdevelopブランチへマージします。

git checkout developコマンドで、ブランチをdevelopブランチへ切り替えます。

git merge release-products-003コマンドで release-products-003ブランチの内容をdevelopブランチへマージします。

release-products-003ブランチの削除

developブランチへのマージが完了したら、release-products-003ブランチを削除します。

git branch -d release-products-003コマンドでrelease-products-003ブランチを削除します。

今回は以上になります。

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

ブックマークのすすめ

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

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

Flask(Part.34)| 【注文管理アプリケーションのプログラミング(8)l(7)の解説】

2025年3月27日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア 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.10)| 【アプリケーション名のリファクタリング】

2025年3月3日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア 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