
目標
- 複数のアプリケーションを統合できる。
Flaskでアプリケーションを統合する方法
テーブル管理アプリケーションの作成準備
ここからは、これまでの商品管理アプリケーションとは別に、テーブル管理アプリケーションや注文管理アプリケーションを作成します。まずは、テーブル管理アプリケーションに必要なディレクトリやファイルを作成し、商品管理アプリケーションとテーブル管理アプリケーションを統合してひとつのアプリケーションとして扱えるようにします。その後、テーブル管理アプリケーションのプログラミングを進めます。
feature-tablessappブランチの作成
Git Bushを立ち上げdevelopブランチから次のコマンドを入力してfeature-tablesappブランチを作成して、ブランチの切り替えを行います。
git checkout -b feature-tablesappコマンド

プロンプトに(feature-tablesapp)と表示されます。テーブル管理アプリケーションはこのブランチで作成していきます。
テーブル管理アプリケーションに必要なディレクトリとファイルの作成
Visual Studio Codeのエクスプローラーから appsディレクトリ に tablesappディレクトリを作成します。
apps/tablesappディレクトリに次のディレクトリとファイルを作成します。※作成するファイルについては、名前や中身について、今後変更することものもあります。
- static/css/style.css
- templates/table_base.html
- tamplates/table_form.html
- tamplates/table_list.html
- app.py
- forms.py
- models.py

「productsapp」と「ordersapp」の統合作業
ここでは、これまで作成してきた「productsapp」と、新たに準備した「ordersapp」を統合します。そのために、appsディレクトリ直下にcommon/db.pyファイルやmain.pyを作成し、両アプリをひとつのアプリケーションとして動作させるプログラムを実装します。
次のディレクトリやファイルをappsディレクトリ直下に作成します。
- common/db.py
- main.py
common/db.pyファイルとmain.pyファイルの作成

common/db.pyファイル
common/db.pyファイルに次のプログラムを記述します。アプリケーションの統合では、複数のアプリケーションが同じデータベースを利用することが一般的です。このため、SQLAlchemyで生成するdbインスタンスを、このようなcommon/db.pyファイルで作成して、各アプリケーションからインポートできるようにします。
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
main.pyファイル
この記事で作成するアプリケーションのように、各アプリケーションにapp.py(またはroute.py)を配置してルートを定義している場合、ルーティングの競合が発生し、正常に動作しません。これを避けるために、main.pyを作成し、ふたつのアプリケーションを統合します。なお、main.pyはappsディレクトリに配置します。
main.pyファイルに次のプログラムを記述します。main.pyでは、もともとproductsappディレクトリ内のapp.pyに記述されていたデータベース設定や画像アップロード処理の設定を記述します。そのため、productsappディレクトリにあるapp.pyファイル内で、不要な重複コードを削除し、必要なモジュールのインポートやルート設定を適切に調整する必要があります。
main.pyファイルで最も重要な個所は、各アプリケーションをBlueprintとして登録する部分です。この部分は各アプリケーションのapp.pyファイルで定義されたBlueprintの設定をインポートして行っています。
import os
from flask import Flask
from apps.common.db import db
from apps.productsapp.app import create_products_app
from apps.tablesapp.app import create_tables_app
def create_main_app():
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///main.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
app.secret_key = "your_secret_key"
# 画像アップロードの設定
UPLOAD_FOLDER = os.path.join(os.getcwd(), "apps", "productsapp", "static", "images")
if not os.path.exists(UPLOAD_FOLDER):
os.makedirs(UPLOAD_FOLDER)
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
app.config["ALLOWED_EXTENSIONS"] = {"png", "jpg", "jpeg", "gif"}
app.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 # 最大16MB
# DB初期化
db.init_app(app)
# アプリコンテキスト内でテーブル作成
with app.app_context():
db.create_all()
# 各アプリをBlueprintとして登録
products_app = create_products_app(app)
app.register_blueprint(products_app, url_prefix="/products")
tables_app = create_tables_app(app)
app.register_blueprint(tables_app, url_prefix="/tables")
return app
if __name__ == "__main__":
app = create_main_app()
app.run(debug=True)
app.pyの編集(productsapp内)
app.pyファイルを次のように編集します。このファイルでは、main.py でアプリケーションを統合するために Blueprint を利用しています。
主な変更点1:url_for 関数でのエンドポイント指定
url_for で指定するエンドポイントは、Blueprint の名前を付加する必要があります。
例:url_for(“category_list”) → url_for(“products.category_list”)
主な変更点2:app.config の利用方法の変更
app.config を直接参照するのではなく、current_app.config を使用します。これは、Blueprint 内で設定を取得するために必要です。
例:app.config[“UPLOAD_FOLDER”] → current_app.config[“UPLOAD_FOLDER”]
主な変更点3:Blueprint オブジェクトに関連付けられたルートの定義
@products_bp.route は、Flask の デコレータ(関数装飾子)です。@ の後に続く products_bp.route は、products_bp という Blueprint オブジェクトに関連付けられたルートを定義します。この部分が重要で、Flask アプリケーション内で URL パスと関数を関連付けるために使用されます。
import os
from itertools import groupby
from flask import (
Blueprint,
current_app,
flash,
redirect,
render_template,
request,
url_for,
)
from werkzeug.utils import secure_filename
from apps.common.db import db
from apps.productsapp.forms import AddProductForm, CategoryForm
from apps.productsapp.models import Category, Product # models.pyからインポート
products_bp = Blueprint(
"products",
__name__,
template_folder=os.path.join(os.getcwd(), "apps", "productsapp", "templates"),
static_folder=os.path.join(os.getcwd(), "apps", "productsapp", "static"),
)
def create_products_app(app):
"""Blueprintを新規作成し、登録する関数"""
return products_bp # 返り値としてBlueprintを返す
# アップロードが許可されている拡張子かをチェック
def allowed_file(filename):
return (
"." in filename
and filename.rsplit(".", 1)
.lower()
in current_app.config["ALLOWED_EXTENSIONS"]
)
# ルート: トップページ
@products_bp.route("/")
def index():
return "Top page"
# カテゴリー一覧を表示
@products_bp.route("/categories")
def category_list():
categories = Category.query.all()
return render_template(
"categories.html", categories=categories, title="カテゴリー一覧"
)
# カテゴリーを追加
@products_bp.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("products.category_list"))
return render_template(
"category_form.html", form=form, title="カテゴリー追加フォーム"
)
# カテゴリーを編集
@products_bp.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("products.category_list"))
return render_template("category_form.html", form=form, title="カテゴリー編集")
# カテゴリーを削除
@products_bp.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("products.category_list"))
db.session.delete(category)
db.session.commit()
flash("カテゴリーを削除しました", "success")
return redirect(url_for("products.category_list"))
# ルート: 商品一覧ページ
@products_bp.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, title="商品一覧"
)
# ルート: 商品追加ページ
@products_bp.route("/product/add", 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
# 画像のアップロード処理
if "product_image" in request.files:
image = request.files.get("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)
image.save(image_path) # 画像を保存
# 画像のパスをデータベースに保存
image_url = f"images/{filename}" # アップロードされた画像のURL
else:
image_url = "images/noimage.png"
else:
image_url = "images/noimage.png"
# 商品の追加
new_product = Product(
name=name, price=price, category_id=category_id, image_url=image_url
)
db.session.add(new_product)
db.session.commit()
return redirect(url_for("products.product_list"))
return render_template("product_form.html", form=form, title="商品追加フォーム")
# ルート: 商品を編集
@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.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
# 画像のアップロード処理
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を保存
db.session.commit()
flash("商品情報を更新しました", "success")
return redirect(url_for("products.product_list"))
return render_template(
"product_form.html", form=form, title="商品編集", product=product
)
# ルート: 商品を削除
@products_bp.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("products.product_list"))
# ルート: 商品管理ページ
@products_bp.route("/manage_products")
def manage_products():
return render_template("manage_products.html", title="商品管理MENU")
if __name__ == "__main__":
from flask import Flask
app = Flask(__name__)
app.register_blueprint(products_bp, url_prefix="/products")
app.run(debug=True)
models.pyの編集(productsapp内)
models.py を次のように編集します。このファイルでは、common ディレクトリ内の db.py から db インスタンスをインポートして使用します。これにより、アプリケーション全体で統一されたデータベース管理が可能になります。
from apps.common.db import db
# カテゴリーモデル
class Category(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50), nullable=False, unique=True)
products = db.relationship(
"Product", backref="category", lazy=True
) # 商品とのリレーション
# 商品モデル(テーブル)の定義
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を保存するカラム
def __repr__(self):
return f"<Product {self.id} - {self.name} - {self.price}>"
base.htmlの編集(productsapp/templates内)
base.html を次のように編集します。テンプレートの主な変更点は、url_for 関数内のエンドポイントの記述方法です。Blueprint を導入したため、各エンドポイントの前に Blueprint の名前を追加する必要があります。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
{% block title %}
<!-- 個別ページのタイトルがここに入る -->
{% endblock %}
</title>
<link rel="stylesheet" href="{{ url_for('products.static', filename='css/style.css') }}">
</head>
<body>
<header>
{% block header %}
<!-- 個別ページのヘッダーがここに入る -->
{% endblock %}
</header>
<main>
{% block content %}
<!-- 個別ページのコンテンツがここに入る -->
{% endblock %}
</main>
<footer>
<p>© 2025 howahowa store</p>
</footer>
</body>
</html>
products.htmlの編集(productsapp/templates内)
products.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.add_product') }}" class="button-link">商品を追加する</a></li>
</ul>
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
{% for category, products in grouped_products.items() %}
<h2>{{ category }}</h2>
<ul class="product-list">
{% for product in products %}
<li class="product-item">
<!-- 商品画像の表示 -->
{% if product.image_url %}
<img src="{{ url_for('products.static', filename=product.image_url) }}" alt="{{ product.name }}"
style="width: 50px; height: 50px; margin-right: 10px; border-right: 2px;">
{% endif %}
<span class="product-name">{{ product.name }} : ¥{{ "{:,}".format(product.price) }}</span>
<div class="button-group">
<form action="{{ url_for('products.edit_product', product_id=product.id) }}" method="GET">
<button type="submit" class="edit-button">編集</button>
</form>
<form action="{{ url_for('products.delete_product', product_id=product.id) }}" method="POST">
<button type="submit" class="delete-button" onclick="return confirm('本当に削除しますか?')">削除</button>
</form>
</div>
</li>
{% endfor %}
</ul>
{% endfor %}
{% endblock %}
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() }} <!-- 商品名入力フィールド -->
{% 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 %}
<label for="product_image">商品画像</label>
<!-- 既存の画像がある場合は表示 -->
{% if product and product.image_url %}
<div>
<img src="{{ url_for('products.static', filename=product.image_url) }}" alt="現在の画像"
style="width: 100px; height: 100px;">
<p>※新しい画像を選択すると上書きされます</p>
</div>
{% endif %}
<label for="product_image" class="file-upload-label">画像を選択</label>
<input type="file" id="product_image" name="product_image" accept="image/*">
<input type="submit" value="商品を登録する">
</form>
<script>
document.getElementById("product_image").addEventListener("change", function () {
const fileName = this.files.length > 0 ? this.files[0].name : "画像を選択";
document.querySelector(".file-upload-label").textContent = fileName;
});
</script>
{% endblock %}
manage_products.htmlの編集(productsapp/templates内)
manage_products.htmlを次のように編集します。
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block header %}
<h1>{{ title }}</h1>
<ul>
<li><a href="{{ url_for('products.product_list') }}" class="button-link">商品一覧</a></li>
<li><a href="{{ url_for('products.add_product') }}" class="button-link">商品追加</a></li>
<li><a href="{{ url_for('products.category_list') }}" class="button-link">カテゴリ一覧</a></li>
<li><a href="{{ url_for('products.add_category') }}" class="button-link">カテゴリ追加</a></li>
</ul>
{% endblock %}
categories.htmlの編集(productsapp/templates内)
categories.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.add_category') }}" class="button-link">カテゴリーを追加</a></li>
</ul>
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<ul>
{% for category in categories %}
<li class="category-item">
<span class="category-name">{{ category.name }}</span>
<div class="button-group">
<form action="{{ url_for('products.edit_category', category_id=category.id) }}" method="GET">
<button type="submit" class="edit-button">編集</button>
</form>
<form action="{{ url_for('products.delete_category', category_id=category.id) }}" method="POST">
<button type="submit" class="delete-button"
onclick="return confirm('本当に削除しますか?※既に商品が存在する場合は削除できません。')">削除</button>
</form>
</div>
</li>
{% endfor %}
</ul>
{% endblock %}
category_form.htmlの編集(productsapp/templates内)
category_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.category_list') }}" class="button-link">カテゴリー一覧ページ</a></li>
</ul>
{% endblock %}
{% block content %}
<h1>{{ title }}</h1>
<form method="POST">
{{ form.hidden_tag() }}
<label for="name">カテゴリー名</label>
{{ form.name() }}
{% for error in form.name.errors %}
<p style="color: red;">{{ error }}</p>
{% endfor %}
{{ form.submit(class="button-link") }}
</form>
{% endblock %}
app.pyの編集(teblesapp内)
tablesapp内のapp.pyファイルに次の内容を追記します。
import os
from flask import Blueprint, flash, redirect, render_template, url_for
from apps.common.db import db
from apps.tablesapp.forms import TableForm
from apps.tablesapp.models import Table
tables_bp = Blueprint(
"tables",
__name__,
template_folder=os.path.join(os.getcwd(), "apps", "tablesapp", "templates"),
static_folder=os.path.join(os.getcwd(), "apps", "tablesapp", "static"),
)
def create_tables_app(app):
"""Blueprintを新規作成し、登録する関数"""
return tables_bp # 返り値としてBlueprintを返す
if __name__ == "__main__":
from flask import Flask
app = Flask(__name__)
app.register_blueprint(tables_bp, url_prefix="/tables")
app.run(debug=True)
.envの編集(apps内)
appsディレクトリ内の.envファイルを次のように編集します。
FLASK_APP=apps.main:create_main_app
FLASK_ENV=development
FLASK_RUN_HOST=0.0.0.0
FLASK_RUN_PORT=5000
編集が完了したら、古いデータベースを削除して、仮想環境をactivateして、flask runコマンドででサーバーを起動します。起動をするとinstanceディレクトリにmain.dbが作成され、http://127.0.0.1:5000/products/manage_productsにアクセスすると商品管理アプリケーションの稼働が確認できます。
次のように、適当に商品を追加しておきます。

今回は以上になります。

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


注文管理アプリケーションはテーブル管理アプリケーションの完成後に行います。