11-Flask

Flask(Part.82)| 【デプロイ】

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

目標

  • Flaskアプリケーションをデプロイする

デプロイ

前提条件

  1. Ubuntuサーバー が用意されていること(ローカルまたはクラウド環境)。
  2. サーバー上で Python 3pip がインストールされていること。
  3. サーバーに PostgreSQL がインストールされていること。

ローカル環境(アプリケーション)での準備

開発を終えて本番環境でアプリケーションを利用するには、いくつかのプログラムで本番環境用に書き換える必要があります。(分岐処理などを利用して構成している場合は設定値のみを書き換えるなどで済みますが、この記事では、少し多めの編集がかかります。)

この記事では、apps/main.py やいくつかのアプリケーションのapp.pyファイルに変更点があります。

「app.py」「.env」での編集

開発環境では.envファイルを使用し、環境変数を管理しています。これにより、ハードコーディングを避けることができます。本番環境では、さらに.envファイルを使用する代わりに、デプロイ先で環境変数を直接設定して利用します。

DATABASE_URI を環境変数で管理(ハードコーディングを避ける)

現在:

app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///main.db"

変更後(PostgreSQLを使用、.envから取得):

from dotenv import load_dotenv
load_dotenv()

app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")

load_dotenv()で環境変数を呼び出します。

.env の例:

DATABASE_URL=postgresql://ユーザー名:パスワード@localhost/flaskr

※実際のデプロイではこのファイルは送られないので備忘録として入力しています。

DATABASE_URL=sqlite:///main.db
# DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr

マイグレーション機能の追加(ローカル側)

本番環境で初回起動時にDBの作成をモデルから行います。これまでは開発環境だったため、db.create_all()を利用していましたが、本番環境ではデータが消失する恐れがあるため、これを利用しません。そのためマイグレーションツールをインストールして、マイグレーションコマンドで作成を行います。

$ pip install Flask-Migrate

実行結果

apps/main.pyファイルに次の行を追加します。

from dotenv import load_dotenv # load_dotenvをインポート <---------(追加した行)
from flask_migrate import Migrate  # Migrateをインポート <---------(追加した行)
    # Flask-Migrateの初期化
    migrate = Migrate(app, db) <--------- # Migrateの初期化(追加した行)

SECRET_KEY も環境変数に移動

現在:

app.secret_key = "your_secret_key"
app.config["SECRET_KEY"] = os.urandom(24)  # ←これで上書きされているので注意

変更後:

app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))

.env に追記:

SECRET_KEY=s0m3$upeR$ecretKey123!

os.urandom(24) は開発中は便利ですが、本番環境では毎回キーが変わってセッションが切れるため、固定する必要があります。


restore_database() の実行タイミングの見直し(本番では初回のみ)

現在、毎回アプリ起動時に復元が走るようになっています。

restore_database()

→ 本番環境では restore_database() を条件付きで動作させるか、管理スクリプトに切り分けると安全です。

編集後:

# restore_database を開発環境のときだけ実行
if os.getenv("FLASK_DEBUG") == "1":
    restore_database()

.env に追記:本番では .env に FLASK_DEBUG=0 としておけば復元は行われません。

「FLASK_ENV」はFlask2.2以降では非推奨なのでコメントにします。

FLASK_DEBUG=0
# FLASK_ENV=development

socketio.run(debug=True) → 本番用では debug=False or 削除
「discountapp」「productsapp」「tablesapp」「vatapp」

本番では、Gunicornなどで起動するため、開発モード限定の if __name__ == "__main__" ブロックは使いません。

if __name__ == "__main__":
    app = create_main_app()
    socketio.run(app, host="0.0.0.0", port=5000, debug=False)

その他、次のアプリケーションにあるapp.py内の if __name__ == "__main__" ブロックを削除します。「discountapp」「productsapp」「tablesapp」「vatapp」

# if __name__ == "__main__":
#     from flask import Flask

#     app = Flask(__name__)
#     app.register_blueprint(vat_bp, url_prefix="/vat")

#     app.run(debug=True)

この記事では、一旦すべてをコメントにしています。次はapps/main.pyファイルの全体です。

import os

from dotenv import load_dotenv # load_dotenvをインポート <---------(追加した行)
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate  # Migrateをインポート <---------(追加した行)
from werkzeug.security import generate_password_hash

from apps.balanceapp.app import create_balance_app
from apps.common.app import create_common_app
from apps.common.db import db, restore_database
from apps.common.models import User
from apps.companyapp.app import create_company_app
from apps.discountapp.app import create_discount_app
from apps.expensesapp.app import create_expenses_app
from apps.ordersapp.app import create_orders_app, init_socketio, socketio
from apps.productsapp.app import create_products_app
from apps.qrcodeapp.app import create_qrcode_app
from apps.refundapp.app import create_refund_app
from apps.registerapp.app import create_register_app
from apps.tablesapp.app import create_tables_app
from apps.vatapp.app import create_vat_app


def create_main_app():
    app = Flask(__name__)  # 開発環境では"sqlite:///main.db"
    load_dotenv()  # .envの環境変数を読み込む
    app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")
    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

    # セッションの利用(24バイトのランダムなバイナリデータを使用)
    app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))

    # DB初期化
    db.init_app(app)

    # Flask-Migrateの初期化
    migrate = Migrate(app, db) <--------- # Migrateの初期化(追加した行)

    # アプリケーションが最初のリクエストを受け取る前にデータベースをリストア(DBの復元処理を呼び出す)
    # restore_database を開発環境のときだけ実行
    if os.getenv("FLASK_DEBUG", "0") == "1":
        restore_database()

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

        if not User.query.filter_by(username="admin").first():
            admin = User(
                username="admin",
                password=generate_password_hash("password123"),
                is_admin=True,
            )
            db.session.add(admin)
            db.session.commit()

    # 各アプリを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")

    orders_app = create_orders_app(app)
    app.register_blueprint(orders_app, url_prefix="/orders")

    register_app = create_register_app(app)
    app.register_blueprint(register_app, url_prefix="/register")

    vat_app = create_vat_app(app)
    app.register_blueprint(vat_app, url_prefix="/vat")

    expenses_app = create_expenses_app(app)
    app.register_blueprint(expenses_app, url_prefix="/expenses")

    balance_app = create_balance_app(app)
    app.register_blueprint(balance_app, url_prefix="/balance")

    common_app = create_common_app(app)
    app.register_blueprint(common_app, url_prefix="/common")

    qrcode_app = create_qrcode_app(app)
    app.register_blueprint(qrcode_app, url_prefix="/qrcode")

    discount_app = create_discount_app(app)
    app.register_blueprint(discount_app, url_prefix="/discount")

    company_app = create_company_app(app)
    app.register_blueprint(company_app, url_prefix="/company")

    refund_app = create_refund_app(app)
    app.register_blueprint(refund_app, url_prefix="/refund")

    # ログインマネージャーの設定
    login_manager = LoginManager()
    login_manager.init_app(app)
    login_manager.login_view = "common.login"  # ログインページのルート名を指定

    # Flask-Loginのユーザーローディング関数
    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    # SocketIO初期化をここで呼び出す
    init_socketio(app)

    return app


if __name__ == "__main__":
    app = create_main_app()
    socketio.run(app, host="0.0.0.0", port=5000, debug=False)

次は.envファイルの全体です。

FLASK_APP=apps.main:create_main_app
FLASK_DEBUG=0
# FLASK_ENV=development
# FLASK_RUN_HOST=0.0.0.0
# FLASK_RUN_PORT=5000
DATABASE_URL=sqlite:///main.db
# DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
SECRET_KEY=s0m3$upeR$ecretKey123!

setup.py の作成と設定

まず、Flaskアプリケーションのパッケージを wheel形式 でビルドする準備をします。これには、setup.py ファイルが必要です。

setup.py ファイルは、Flaskアプリケーションの プロジェクトのルートディレクトリ に配置します。(アプリ全体をパッケージとしてビルド・配布する際の「基準ディレクトリ」です。)

FlaskProjフォルダの直下に「setup.py ファイル」を作成します。

次に、setup.pyファイル内に記述する必要のあるライブラリを取得します。venvの中で次のコマンドを入力します。

$ pip freeze > requirements.txt

実行をするとsetup.pyファイルの上あたりにrequirements.txtファイルが作成されます。

中身を確認すると次のように開発したflaskアプリケーションで利用しているライブラリが入力されています。この中で、setup.pyには、特に必要なものと、追加で「gunicorn」、「psycopg2-binary」を記述します。

また、開発でのみ必要だった「pre_commit, ruff, style, update」は本番環境に含めません。

次のように setup.pyファイルを入力します。

from setuptools import setup, find_packages

setup(
    name="apps",
    version="1.0.0",
    packages=find_packages(),
    include_package_data=True,
    install_requires=[
        "Flask==3.1.0",
        "SQLAlchemy==2.0.38",
        "python-dotenv==1.0.1",
        "gunicorn>=20.0.0",
        "psycopg2-binary>=2.9.0",
    ],
)

apps/qrcodeapp/app.py の修正

QRコード生成で利用するURLを修正します。

修正前

def generate_qr_url(table_id, token):
    url = f"http://192.168.2.151:5000/orders/order/table/{table_id}?token={token}"
    img = qrcode.make(url)

修正後

def generate_qr_url(table_id, token):
    url = f"http://usvb.local/orders/order/table/{table_id}?token={token}"
    img = qrcode.make(url)

全てのアプリケーションに「__init__.py」ファイルを作成します。appsディレクトリにも作成します。

ここまでの内容をコミットしておきます。

この記事ではデータベースファイルのmain.dbは削除しておきます。

git status

git add .

git status

git commit –no-verify

Chore: 不要な if __name__ == "__main__" ブロックの削除(20250425)
Chore: その他、ビルドの為の準備をおこなっています。(20250425)

開発環境から本番環境用にプログラムを変更しています。

COMMIT_EDITMSGを上書き保存して閉じます。

ここまでの編集をmainブランチへマージします。

git checkout main

git merge develop(本来はmainブランチもreleaseブランチからマージされます。)

developブランチは削除しません。

ビルド用パッケージのインストール

wheel を使ってビルドするためには、wheel をインストールしておく必要があります。

$ pip install wheel

次は実行結果です。

さらに build をインストールします。

$ pip install build

次は実行結果です。

pyproject.toml にビルド設定を追加

追加内容

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "flaskapp"
version = "1.0.0"
description = "My Flask System"
authors = [{name = "howahowa", email = "howahowa@email.com"}]
dependencies = [
    "Flask",
    "Flask-Login",
    "Flask-Migrate",
    "Flask-SQLAlchemy",
    "python-dotenv",
]

[tool.setuptools]
packages = ["apps", "apps.balanceapp", "apps.common", "apps.companyapp", "apps.discountapp", "apps.expensesapp", "apps.ordersapp", "apps.productsapp", "apps.qrcodeapp", "apps.refundapp", "apps.registerapp", "apps.tablesapp", "apps.vatapp"]

[tool.setuptools.package-data]
"apps" = ["templates/**/*.html", "static/**/*.*"]

pyproject.toml ファイルの全体

[tool.ruff]
# 最大行長の設定
line-length = 88  # Blackのデフォルト値に合わせる

[tool.ruff.lint]
# 使用するルール (Flake8 + isort)
select = ["E", "F", "I"]
# 除外するルール
ignore = ["E501"]  # E501: 長すぎる行を許容 (Blackと競合しやすいため)

# 除外するファイル・ディレクトリ
exclude = ["venv", "migrations", "__pycache__"]

# isort の設定 (インポート順序)
[tool.ruff.lint.isort]
known-first-party = ["app"]
combine-as-imports = true

# フォーマット設定 (Blackと同じ動作)
[tool.ruff.format]
docstring-code-format = true  # docstring 内のコードも整形


[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "flaskapp"
version = "1.0.0"
description = "My Flask System"
authors = [{name = "howahowa", email = "howahowa@email.com"}]
dependencies = [
    "Flask",
    "Flask-Login",
    "Flask-Migrate",
    "Flask-SQLAlchemy",
    "python-dotenv",
]

[tool.setuptools]
packages = ["apps", "apps.balanceapp", "apps.common", "apps.companyapp", "apps.discountapp", "apps.expensesapp", "apps.ordersapp", "apps.productsapp", "apps.qrcodeapp", "apps.refundapp", "apps.registerapp", "apps.tablesapp", "apps.vatapp"]

[tool.setuptools.package-data]
"apps" = ["templates/**/*.html", "static/**/*.*"]

MANIFEST.inの作成

プロジェクトフォルダ直下(今回はFlaskProj)にMANIFEST.inを作成して次の内容を追加します。

recursive-include apps *.py
recursive-include apps/templates *.html
recursive-include apps/static *.*

アプリケーションのビルド

次に、アプリケーションを wheelパッケージ にビルドします。プロジェクトのルートディレクトリで以下のコマンドを実行します。

$ python -m build

このコマンドで、dist/ フォルダ内に .whl ファイル(例えば apps-1.0.0-py3-none-any.whl)が作成されます。

作成されたファイル

この .whl ファイル と requirements.txt を Ubuntuサーバー に転送します。

ここまでの内容をコミットしておきます。

git status

git add .

git status

git commit –no-verify

Chore: Flaskアプリケーションのビルド。(20250425)

Flaskアプリケーションのビルドを完了しています。

COMMIT_EDITMSGを上書き保存して閉じます。

ここまでの編集をmainブランチへマージします。

git checkout main

git merge develop(本来はmainブランチもreleaseブランチからマージされます。)


Ubuntuサーバーの準備

Ubuntuのインストールについてはこちらの記事を参考にしてください。

Ubuntuのインストール| Virtual Boxの利用Ubuntu(ウブントゥ)は、Linuxディストリビューションのひとつで、誰でも無料で使えるオープンソースのオペレーティングシステム(OS)です。UbuntuのISOイメージファイルを公式サイトからダウンロードし、パソコンや仮想環境(VirtualBoxなど)にインストールすることで利用できます。...

固定IPアドレス設定(VirtualBoxのUbuntu側)

仮想マシン(Ubuntu)に固定IPアドレスを割り当てます。VirtualBoxのネットワーク設定はブリッジアダプターを利用します。

Ubuntuにログイン後、固定IPアドレスを設定します。

「端末」アプリはUbuntu左下の「Show apps」アイコンをクリックして表示します。

以下、手順。

# 次のコマンドでnetplanディレクトリ内にあるyamlファイルを確認。
$ ls /etc/netplan/

# 存在するファイルの中身を sudo cat コマンドで確認し、dhcp4: true となっている設定ファイルを編集。
# 例えは、「01-network-manager-all.yaml 50-cloud-init.yaml」のように複数ある場合はそれぞれ中身を確認。
$ sudo cat /etc/netplan/01-network-manager-all.yaml
$ sudo cat /etc/netplan/50-cloud-init.yaml

(本記事での場合)01-network-manager-all.yamlの中身の確認。

$ sudo nano /etc/netplan/01-netcfg.yaml

(本記事での場合)50-cloud-init.yamlの中身の確認。今回はこちらを編集

$ sudo cat /etc/netplan/50-cloud-init.yaml

viエディタ、もしくは、nanoエディタでファイルを開きます。

$ sudo cat /etc/netplan/50-cloud-init.yaml

ファイルの中身を次のように編集します。

network:
  version: 2
  renderer: networkd
  ethernets:
    enp0s3:
      dhcp4: no
      addresses:
        - 192.168.2.152/24
      routes:
        - to: default
          via: 192.168.2.1  # default gatewayとして設定
      nameservers:
        addresses:
          - 8.8.8.8
          - 8.8.4.4

ローカルネットワークで利用されているIPアドレスを確認するには「arp -a」コマンドを利用します。

「/etc/netplan/01-network-manager-all.yamlファイル」と「/etc/netplan/50-cloud-init.yamlファイル」で、その他のユーザーが読み書き不能にする。

$ sudo chmod 600 /etc/netplan/01-network-manager-all.yaml
$ sudo chmod 600 /etc/netplan/50-cloud-init.yaml

設定後、ネットワークを適用します。

$ sudo netplan apply

次のエラーが表示された場合は下のコマンドを利用します。

$ sudo systemctl enable systemd-networkd
$ sudo systemctl start systemd-networkd

更に次のエラーが表示された場合はsystemd-networkdをインストールします。

$ sudo apt update
$ sudo apt install systemd-networkd

#もし NetworkManager が動作している場合は、それを無効化して再試行します。
$ sudo systemctl status NetworkManager
$ sudo systemctl stop NetworkManager
$ sudo systemctl disable NetworkManager

$ sudo systemctl enable systemd-networkd
$ sudo systemctl start systemd-networkd

$ sudo netplan apply

次のように表示されれば成功です。

Avahi の設定手順(Ubuntuサーバー)

次のコマンドでUbuntuにAvahi をインストールします。

$ sudo apt install avahi-daemon -y

次はインストール済みの場合の画面です。

次のコマンドでAvahi デーモンの有効化・起動を行います。

$ sudo systemctl enable avahi-daemon
$ sudo systemctl start avahi-daemon

現在のホスト名を確認します。(この記事の場合:ubsv)

$ hostname

出力: ubsv
→ この場合、アクセスURLは http://ubsv.local になります。

usvb.local で名前解決されることを確認します。(別のPCから)

Windowsの場合はコマンドプロンプトを立ち上げて次のように入力します。

$ ping ubsv.local

応答は ipv6 で返ってきます。

Avahi の確認・トラブルシュートについて

サービスが正しく動いているか確認

$ systemctl status avahi-daemon

ファイアウォールを無効化しているか確認

$ sudo ufw allow 5353/udp

※AvahiはUDPポート5353(mDNS)を使用します。

ファイアウォール自体を一時的に無効化

$ sudo ufw disable

http://ubsvでアクセスできるように/etc/hostsを編集します。

$ sudo nano /etc/hosts

内容を次のように編集します。

127.0.0.1 localhost
192.168.2.152 ubsv.local

# The following lines are desirable for IPv6 capable hosts
::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

Windows端末での設定

C:\Windows\System32\drivers\etc\hostsに一番下の一行を追加

# localhost name resolution is handled within DNS itself.
#	127.0.0.1       localhost
#	::1             localhost
192.168.2.152    ubsv.local

コマンドプロンプトから設定する場合は管理者権限で開いて次のように入力します。

C:\Windows\System32>echo 192.168.2.152    ubsv.local >> C:\Windows\System32\drivers\etc\hosts

Python 3 と pipのインストール

Ubuntuサーバーに Python 3pip がインストールされていない場合、以下のコマンドでインストールします。

$ sudo apt install python3 python3-pip python3-venv

PostgreSQLのインストール

Flaskアプリケーションが PostgreSQL を利用する場合、サーバーに PostgreSQL をインストールする必要があります。

$ sudo apt install postgresql postgresql-contrib

インストール後、PostgreSQLのサービスが起動していることを確認します。

$ sudo systemctl start postgresql
$ sudo systemctl enable postgresql

PostgreSQLデータベースの設定

PostgreSQLのデータベースとユーザーを作成します。ここでは、flaskr というデータベースとユーザー flaskuser (パスワード:flaskr123)を作成します。

ユーザーとパスワードはFlaskアプリケーションから利用するものです。Flaskアプリケーションで設定しているものと同じものにします。

$ sudo -u postgres psql
postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';
postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
postgres=# GRANT CREATE ON SCHEMA public TO flaskuser;
postgres=# GRANT USAGE ON SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
postgres=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;
postgres=# \q

これで、flaskr データベースと flaskuser ユーザーが作成されました。

Flaskアプリケーションのデプロイ

Ubuntuサーバー上での仮想環境の作成

Ubuntuサーバー上でアプリケーションを動かすために、Pythonの仮想環境を作成します。

下の「username」の部分は「whoami」コマンドで確認できます。この記事では「ubuntu-server」で実行します。

$ sudo mkdir /home/username/flaskapp
$ cd /home/username/flaskapp
$ sudo python3 -m venv venv
$ sudo source venv/bin/activate

※この記事の場合の入力

$ sudo mkdir /home/ubuntu-server/flaskapp
$ cd /home/ubuntu-server/flaskapp
$ sudo python3 -m venv venv
$ source venv/bin/activate

次のコマンドで仮想環境を抜けます。続けてcdコマンドでホームディレクトリに戻ります。

$ deactivate
$ cd

.whl ファイルのインストール

ローカルで作成した .whl ファイルを Ubuntuサーバーに転送します。この記事ではFileZilaを利用します。

別の転送方法として、scp や rsync を使うことができます。例えば、scpを使う場合は以下のようにします。

$ scp flaskapp-1.0.0-py3-none-any.whl username@your-ubuntu-server:/home/username/flaskapp

Windowsのコマンドプロンプトからssh接続を行う場合は次のコマンドを入力します。(この記事の設定の場合)

$ ssh ubuntu-server@192.168.2.152

UbuntuアプリケーションサーバーへSSHサーバのインストールをして起動を行います。

$ sudo apt install openssh-server
$ sudo systemctl enable ssh
$ sudo systemctl start ssh

SSHが正常に起動しているかの確認を行います。

$ sudo systemctl status ssh

次のように表示されれば成功です。

ファイアウォールの設定で22番ポートを開放します。

$ sudo ufw allow ssh

WindowsからFileZilaでubuntu-serverへ接続します。

FileZilaを起動します。

次のように設定し接続します。

ファイル」タブから「サイトマネージャー」を選択します。

表示されるダイアログボックスで「新しいサイト」をクリックします。

新しいサイト」の名前を「UbuntuServer」に変更します。

次のホスト情報を入力します。

プロトコルに「SFTP-SSH File Transfer Protocol」

ホストにubuntu-serverのIPアドレス(今回は192.168.2.152)

ログオンタイプに「普通」

次の設定はUbuntuをインストールしたときのユーザー名とパスワードです。いつもUbuntuを起動してサインインをするときのものです。

ユーザーにアクセスを行うユーザー名(今回はubuntu_server)

パスワードにユーザーのパスワード(今回はubuntu2401)

接続が完了すると次のように表示されます。

「.whl ファイル」「requirements.txtファイル」を ドラッグアンドドロップでUbuntuサーバーに転送します。

転送先は「home/username(今回はubuntus_server)/flaskapp」です。

転送出来ない場合は、次のコマンドでflaskappの権限を変更します。(この記事の場合)

$ sudo chown ubuntu-server:users /home/ubuntu-server/flaskapp

転送後は次のように表示されます。

requirements.txtファイルにはまた、開発でのみ必要だった「pre_commit, ruff, style, update」は含めません。

※Ubuntu側でカレントディレクトリを移動します。この記事の場合の入力

$ cd /home/ubuntu-server/flaskapp
$ source venv/bin/activate

Ubuntuサーバーに転送後、仮想環境内で .whl ファイルをインストールします。

$ pip install flaskapp-1.0.0-py3-none-any.whl

もし「Error 13」許可がありませんとなった場合、次のコマンドを利用して、パーミッションを変更し、再度、インストールのコマンドを入力します。

chmod 644 flaskapp-1.0.0-py3-none-any.whl

それでも、エラーが発生する場合は、venvの中がroot権限となっている可能性があるので、次のコマンドを入力してからインストールを行います。

sudo chown -R ubuntu-server:ubuntu-server /home/ubuntu-server/flaskapp/venv

インストールが完了すると次のような画面が表示されます。

次のコマンドでrequirements.txtに書かれたライブラリをインストールします。.whlファイルをインストールした時と、重複するものがあってもpipが調整してくれます。

pip install -r requirements.txt

インストールが完了すると次のように表示されます。

データベースの設定

pythonからPostgresqlに接続するために「psycopg2」をインストールします。

$ sudo apt install libpq-dev
$ pip install psycopg2

flask アプリケーションが使用するデータベースを設定します。以下のコマンドを実行して、マイグレーションを行えるようにします。

$ pip install flask-migrate

ここでは、flask アプリケーションエントリーポイントなどを念のために指定します。

$ export FLASK_APP=apps.main:create_main_app
$ export FLASK_DEBUG=0
$ export DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
$ export SECRET_KEY=s0m3$upeR$ecretKey123!

するデータベースを設定します。以下のコマンドを実行して、マイグレーションを行えるようにします。

$ flask db init

# flask db init は、マイグレーションのための設定ファイル
# (migrations ディレクトリ)を作成するコマンドです。
# このコマンド自体はまだデータベースを作成しません。

$ flask db migrate -m "Initial migration"
# このコマンドで、実際にデータベーススキーマ(テーブルの構造)を作成するための、
# マイグレーションファイルを生成します。このコマンドはFlaskのモデルに基づいて、
# テーブル作成のためのSQLを生成します。

$ flask db upgrade
# 実際にデータベースにその変更を適用します。
# migrate コマンドによって生成された変更内容が
# データベースに適用され、テーブルが作成されます。

これにより、flaskr のテーブルが PostgreSQL の flaskr データベースに作成されますが、次にあげる2種類のエラーが出る場合はそれぞれ対応が必要です。

❶もし次のようなエラーが出たら「userモデル」のStringの長さを変更する必要があります。

apps/common/models.pyファイル

from flask_login import UserMixin

from apps.common.db import db


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(200), nullable=False, unique=True)
    password = db.Column(db.String(512), nullable=False)
    is_admin = db.Column(db.Boolean, default=False)

    def __repr__(self):
        return f"<User {self.username}>"

再度、ビルドしてファイルをサーバー側に再送します。

❷次のエラーが出た場合はPostgerSQLのユーザーの権限を変更する必要があります。

その場合はデータベースとユーザーを次のように作り直して適切な権限を割り当てます。

$ sudo -u postgres psql
postgres=# DROP DATABASE IF EXISTS flaskr;
postgres=# DROP OWNED BY flaskuser;
postgres=# DROP ROLE IF EXISTS flaskuser;

postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';

postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;

postgres=# \c flaskr

flaskr=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
flaskr=# GRANT CREATE ON SCHEMA public TO flaskuser;
flaskr=# GRANT USAGE ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;

postgres=# \q

エラーについて、まとめると、再度、ローカル側でビルドしてサーバー側に送りインストールします。この時、以前のインストールした内容はアンインストールし、venvも削除します。また、事前にデータベースも次のように作り直します。

$ sudo -u postgres psql
postgres=# DROP DATABASE IF EXISTS flaskr;
postgres=# DROP OWNED BY flaskuser;
postgres=# DROP ROLE IF EXISTS flaskuser;

postgres=# CREATE DATABASE flaskr;
postgres=# CREATE USER flaskuser WITH PASSWORD 'flaskr123';
postgres=# ALTER ROLE flaskuser SET client_encoding TO 'utf8';
postgres=# ALTER ROLE flaskuser SET default_transaction_isolation TO 'read committed';
postgres=# ALTER ROLE flaskuser SET timezone TO 'UTC';

postgres=# CREATE SCHEMA public;
postgres=# ALTER SCHEMA public OWNER TO flaskuser;

postgres=# \c flaskr

flaskr=# GRANT ALL PRIVILEGES ON DATABASE flaskr TO flaskuser;
flaskr=# GRANT CREATE ON SCHEMA public TO flaskuser;
flaskr=# GRANT USAGE ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO flaskuser;
flaskr=# GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO flaskuser;

postgres=# \q
$ pip uninstall apps
$ rm -rf venv
$ rm -rf apps
$ rm -rf flaskapp-1.0.0-py3-none-any.whl
$ sudo python3 -m venv venv
$ source venv/bin/activate
$ sudo chown -R ubuntu-server:ubuntu-server /home/ubuntu-server/flaskapp/venv
$ pip install flaskapp-1.0.0-py3-none-any.whl
$ pip install -r requirements.txt
$ sudo apt install libpq-dev
$ pip install psycopg2
$ pip install flask-migrate
$ export FLASK_APP=apps.main:create_main_app
$ export FLASK_DEBUG=0
$ export DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr
$ export SECRET_KEY=s0m3$upeR$ecretKey123!
$ flask db init
$ flask db migrate -m "Initial migration"
$ flask db upgrade

$ flask db initコマンド

$ flask db migrate -m “Initial migration”コマンド(””の中はコメントです。)

$ flask db upgrade

次のように入力すると作成されたテーブルを確認できます。

$ sudo -u postgres psql
postgres=# \c flaskr
flaskr=# \dt
flaskr=# \q

データベースを完全初期化したい場合の手順:本番環境で慎重に扱ってください。

データベースの全テーブルを削除

$ flask db downgrade base # 可能なら初期状態に戻します。

もしくは手動でテーブルを全削除

$ psql -U postgres -d flaskr
flaskr=# DROP SCHEMA public CASCADE;
flaskr=# CREATE SCHEMA public;

再度マイグレーションを適用して初期化

$ flask db upgrade

GunicornとNginxのインストールと設定・起動

Gunicornのインストール

GunicornはFlaskアプリケーションを本番環境で動作させるために使うWSGIサーバです。仮想環境にインストールします。(.whlファイルをインストール時に追加されているはずですが、念のためインストールを実行します。)

$ pip install gunicorn

既にインストールされている場合は次のように表示されます。

Gunicornでアプリケーションを起動

以下のコマンドでGunicornを使ってFlaskアプリケーションを起動します。

$ gunicorn --workers 4 --threads 2 --bind 127.0.0.1:8000 apps.main:create_main_app

このコマンドは、gunicorn を使って、apps/main.py の create_main_app()にある Flask アプリケーションインスタンス app を起動するコマンドです。各部分の意味は次の通りです。

gunicornは WSGI HTTP サーバーで、FlaskやDjangoようなPython製Webアプリケーションを本番環境で動かすために使われます。Flaskの組み込みサーバー(flask run)は開発用です。本番環境では、一般的に gunicorn などのWSGIサーバーを使います。

–workers はワーカープロセスの数を指定します。このオプションによって、リクエストを並行して処理するために複数のワーカープロセスが起動します。(今回のプログラムでは利用しません。)

4 は4つのワーカープロセスを使用することを意味します。リクエスト数が多い場合や同時アクセスが高い場合には、ワーカー数を増やすと性能が向上することがあります。(今回のプログラムでは利用しません。)

–threads は各ワーカープロセス内で使用するスレッドの数を指定します。これにより、1つのワーカー内で複数のリクエストを並行して処理できます。(今回のプログラムでは利用しません。)

2 は各ワーカーが2つのスレッドを使用することを意味します。(今回のプログラムでは利用しません。)

–bind はアプリケーションをどのIPアドレスとポートで待機させるかを指定します。

127.0.0.1 はローカルホスト(自身のコンピュータ)を意味し、外部からのアクセスを拒否します。インターネット越しにアクセスする場合は、0.0.0.0 や外部IPアドレスを指定します。

8000 はポート番号です。指定されたポートでアプリケーションが待機します。この場合、http://127.0.0.1:8000 でアクセス可能です。

apps:app は、Flaskアプリケーションのインポートパスを指定します。

apps は、Flaskアプリケーションが保存されているPythonモジュール(通常はディレクトリ名)です。

app は、そのモジュール内で定義されたFlaskアプリケーションオブジェクトの名前です。app オブジェクトは通常、Flaskのインスタンスです。

実行すると次のように表示されます。

gunicorn を停止するには、ターミナル上で、Ctrl + C を押します。

Nginxの設定

Nginxをリバースプロキシとして設定して、Flaskアプリケーションへトラフィックを送るようにします。

Nginxのインストール

$ sudo apt install nginx

Nginxの設定ファイルを作成/変更します。

$ sudo nano /etc/nginx/sites-available/flaskapp

以下の内容を追加します。

server {
    listen 80;
    server_name ubsv.local 192.168.2.152;

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Session-Id $http_x_session_id;
        proxy_set_header X-CSRF-Token $http_x_csrf_token;
    }

    location /socket.io/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Session-Id $http_x_session_id;
        proxy_set_header X-CSRF-Token $http_x_csrf_token;
        proxy_buffering off;
        gzip off;
    }
}

設定を有効にします。

$ sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled
# 上のコマンドは上書き保存ができません。ファイルを修正して保存する場合は次のコマンドを利用します。
# sudo ln -sf /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/

$ sudo systemctl reload nginx

NginxのHTTPS化(Let’s Encrypt)
本番運用ならHTTPS(SSL証明書)は必須です。Let’s Encrypt + certbot を使うのが無料で簡単です。(この記事では行っていません。)

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d yourdomain.example.com

アプリケーションの永続化

最後に、Flaskアプリケーションを 永続的に実行 するために systemd を使ってサービスとして登録します。

Flaskアプリケーション用のsystemdサービスファイルの作成

$ sudo nano /etc/systemd/system/flaskapp.service

以下の内容を入力します。

[Unit]
Description=Flask Application
After=network.target

[Service]
User=ubuntu-server
Group=ubuntu-server
WorkingDirectory=/home/ubuntu-server/flaskapp
Environment="FLASK_DEBUG=0"
Environment="DATABASE_URL=postgresql://flaskuser:flaskr123@localhost/flaskr"
Environment="SECRET_KEY=s0m3$upeR$ecretKey123!"
Environment="PATH=/home/ubuntu-server/flaskapp/venv/bin"
ExecStart=/home/ubuntu-server/flaskapp/venv/bin/gunicorn -k gevent -w 1 -b 127.0.0.1:8000 apps.main:create_main_app
Restart=always

[Install]
WantedBy=multi-user.target

SocketIO では WebSocket を使用します。ですが、Gunicorn のデフォルト(sync や gthread)では WebSocket に完全対応していません。上の「ExecStart」で、次のように設定しました。「/home/ubuntu-server/flaskapp/venv/bin/gunicorn -k gevent -w 1 -b 127.0.0.1:8000 apps.main:create_main_app」

geventをインストールします。

$ pip install gevent

これで、gunicornコマンドと同じ内容のSystemdサービスファイルが作成されます。保存後、サービスの設定を反映させるために以下のコマンドを実行します。

$ sudo systemctl daemon-reload
$ sudo systemctl start flaskapp
$ sudo systemctl restart flaskapp #再起動の場合
$ sudo systemctl enable flaskapp

次のエラーが出た場合は create_main_app(): 部分を次のように修正して再ビルドし、再インストールする必要があります。

def create_main_app(app=None, config=None):
    app = app or Flask(__name__)
$ sudo systemctl status flaskapp

これでつながらない場合は、各ファイルを転送してappsディレクトリに格納します。(このとき、各ディレクトリにある__pycache__は転送から除外します。)

その他の修正

開発環境から本番環境へ移行すると、開発環境では思うように動いてくれたプログラムが意図しない動きになることがあります。このデプロイでも、設定ファイルを、今回の動きに合うように設定し、本番環境でのみ追加で必要なパッケージをインストールしたりと修正を加えています。

さらに、プログラム自体にも変更を加えています。追加で行ったプログラムの修正は次の通りです。

apps/main.py

import os
from datetime import timedelta

from dotenv import load_dotenv
from flask import Flask
from flask_login import LoginManager
from flask_migrate import Migrate  # Migrateをインポート
from werkzeug.security import generate_password_hash

from apps.balanceapp.app import create_balance_app
from apps.common.app import create_common_app
from apps.common.db import db, restore_database
from apps.common.models import User
from apps.companyapp.app import create_company_app
from apps.discountapp.app import create_discount_app
from apps.expensesapp.app import create_expenses_app
from apps.ordersapp.app import create_orders_app, init_socketio, socketio
from apps.productsapp.app import create_products_app
from apps.qrcodeapp.app import create_qrcode_app
from apps.refundapp.app import create_refund_app
from apps.registerapp.app import create_register_app
from apps.tablesapp.app import create_tables_app
from apps.vatapp.app import create_vat_app


def create_main_app():
    app = Flask(__name__)  # 開発環境では"sqlite:///main.db"
    if os.getenv("FLASK_DEBUG", "0") == "1":
        load_dotenv(override=True)  # .envの環境変数を読み込む
    # print(f"DATABASE_URL = {os.getenv('DATABASE_URL')}")
    app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL")
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

    # 画像アップロードの設定
    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

    # セッションの利用(24バイトのランダムなバイナリデータを使用)
    app.config["SECRET_KEY"] = os.getenv("SECRET_KEY", os.urandom(24))

    # DB初期化
    db.init_app(app)

    # Flask-Migrateの初期化
    migrate = Migrate(app, db)  # Migrateの初期化

    # アプリケーションが最初のリクエストを受け取る前にデータベースをリストア(DBの復元処理を呼び出す)
    # restore_database を開発環境のときだけ実行
    if os.getenv("FLASK_DEBUG", "0") == "1":
        restore_database()

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

        if not User.query.filter_by(username="admin").first():
            admin = User(
                username="admin",
                password=generate_password_hash("password123"),
                is_admin=True,
            )
            db.session.add(admin)
            db.session.commit()

    # 各アプリを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")

    orders_app = create_orders_app(app)
    app.register_blueprint(orders_app, url_prefix="/orders")

    register_app = create_register_app(app)
    app.register_blueprint(register_app, url_prefix="/register")

    vat_app = create_vat_app(app)
    app.register_blueprint(vat_app, url_prefix="/vat")

    expenses_app = create_expenses_app(app)
    app.register_blueprint(expenses_app, url_prefix="/expenses")

    balance_app = create_balance_app(app)
    app.register_blueprint(balance_app, url_prefix="/balance")

    common_app = create_common_app(app)
    app.register_blueprint(common_app, url_prefix="/common")

    qrcode_app = create_qrcode_app(app)
    app.register_blueprint(qrcode_app, url_prefix="/qrcode")

    discount_app = create_discount_app(app)
    app.register_blueprint(discount_app, url_prefix="/discount")

    company_app = create_company_app(app)
    app.register_blueprint(company_app, url_prefix="/company")

    refund_app = create_refund_app(app)
    app.register_blueprint(refund_app, url_prefix="/refund")

    # ログインマネージャーの設定
    login_manager = LoginManager()
    login_manager.init_app(app)
    login_manager.login_view = "common.login"  # ログインページのルート名を指定
    app.config["SESSION_PROTECTION"] = "strong"
    app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(minutes=30)

    # Flask-Loginのユーザーローディング関数
    @login_manager.user_loader
    def load_user(user_id):
        return User.query.get(int(user_id))

    # SocketIO初期化をここで呼び出す
    init_socketio(app)

    return app


# if __name__ == "__main__":
#     app = create_main_app()
#     socketio.run(app, host="0.0.0.0", port=5000, debug=False)

apps/registerapp/app.py:ルート:view_bill(order_byを追加)

    # 指定された bill_id に関連する注文を取得
    orders = Order.query.filter_by(bill_id=bill.id).order_by(Order.id.asc()).all()
    # 返金用のオブジェクトの取得
    refunded_orders = (
        RefundBill.query.filter_by(original_bill_id=bill.id)
        .order_by(RefundBill.id.asc())
        .all()
    )

apps/registerapp/app.py:ルート:pay_bill(intを追加)

            int(order.total * (100 - (order.discount_number or 0)) / 100)
            + int(
                int(order.total * (100 - (order.discount_number or 0)) / 100)

apps/refundapp/app.py:ルート:refund_bill(order_byを追加)

    # 指定された bill_id に関連する注文を取得
    orders = Order.query.filter_by(bill_id=bill.id).order_by(Order.id.asc()).all()
    # 返金用のオブジェクトの取得
    refunded_orders = (
        RefundBill.query.filter_by(original_bill_id=bill.id)
        .order_by(RefundBill.id.asc())
        .all()
    )

apps/qrcodeapp/app.py:ルート:qrcode_generate(bill_idに関する内容をコメント化)

    if request.method == "POST":
        table_id = int(request.form.get("table_id"))
        # すでに有効なトークン(未会計のもの)が存在するか確認
        existing_token = (
            db.session.query(TableAccessToken)
            .filter(
                TableAccessToken.table_id == table_id,
                # TableAccessToken.bill_id.is_(None),  # 未会計
                TableAccessToken.expires_at > datetime.now(timezone.utc),  # 有効期限内

apps/qrcodeapp/app.py:ルート:generate_qr_url(URLに次のアドレスを指定)

url = f"http://192.168.2.152/orders/order/table/{table_id}?token={token}"

apps/ordersapp/app.py:ルート:order_menu(紐づけのプログラム追加)

        # トークンにbill_idを紐づける
        token_obj.bill_id = bill.id
        db.session.commit()

apps/common/models.py:usernameとpasswordの指定文字数を変更

    username = db.Column(db.String(200), nullable=False, unique=True)
    password = db.Column(db.String(512), nullable=False)

apps/balanceapp/app.py:ルート:monthly_balance(balanceの算出方法修正)

        # データ保存
        date_dt = current_day
        all_days[date_dt] = {
            "orders": daily_orders,
            "cash": daily_cash,
            "cashless": daily_cashless,
            "refunds": daily_refunds,
            "exincome": daily_ex_in,
            "exexpense": daily_ex_out,
            "pbincome": daily_pb_in,
            "pbexpense": daily_pb_out,
            "balance": daily_cash
            + daily_cashless
            - daily_refunds
            + daily_ex_in
            - daily_ex_out
            + daily_pb_in
            - daily_pb_out,
        }

apps/balanceapp/templates/balance_daily.html(次のように表示を変更)

<table>
    <tr>
        <th>当日売上</th>
    </tr>
    <tr>
        <td>¥{{
            (total_cash +
            total_cashless +
            total_refund) | int
            }}</td>
    </tr>
    <tr>
        <th>当日現金収支</th>
    </tr>
    <tr>
        <td>¥{{
            (total_cash +
            total_refund +
            total_ex_in -
            total_ex_ex) | int
            }}</td>
    </tr>
    <tr>
        <th>当日最終収支(普通預金込み)</th>
    </tr>
    <tr>
        <td>¥{{
            (total_cash +
            total_cashless +
            total_refund +
            total_ex_in -
            total_ex_ex +
            total_pb_in -
            total_pb_ex) | int
            }}</td>
    </tr>
</table>

apps/balanceapp/templates/balance_monthly.html(次のように表示を変更)

<h3>収支概要</h3>
<table>
    <tr>
        <th>月間売上(現金 + キャッシュレス - 返金)</th>
    </tr>
    <tr>
        <td>¥{{ monthly_sales - monthly_refund | int }}</td>
    </tr>
    <tr>
        <th>月間最終収支(現金収支 + 普通預金 収支)</th>
    </tr>
    <tr>
        <td>¥{{ total_balance | int }}</td>
    </tr>
    <tr>
        <th>月間売上から引かれた返金の総額</th>
    </tr>
    <tr>
        <td>¥{{ monthly_refund | int }}</td>
    </tr>
</table>

今回は以上になります。

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

ブックマークのすすめ

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

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

Flask(Part.65)| 【QRコードアプリケーションのカスタマイズ(2)】

2025年4月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.47)| 【日計表示・月次表示アプリケーションの作成】

2025年4月9日
プログラミング学習 おすすめ書籍情報発信 パソコン初心者 エンジニア希望者 新人エンジニア 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.39)| 【レジ管理アプリケーションのプログラミング(1)】

2025年4月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.2)| 【MVTの概要とアプリケーションの起動方法 】

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