[Python]ロリポップのライトプランでFlask

(なんにも考えず)ロリポップのライトプランでサーバー借りてみた。そして何ができるか調べてみる。

そうか、Php,Ruby,Pythonが使えるのか。
https://lolipop.jp/manual/hp/cgi/

私の借りているlitxxxサーバーの場合は3.7が使えるそうです。

Pythonバージョン3.7
/usr/local/bin/python3.7

じゃあ、フレーワークは何にしよう。
Flaskは使えるのだろうか? →使えるようだ 以下のブログが参考になる。

https://kogyoyaro.com/lolipop-flask

まずはFlaskを学び始めることにしました。特に深く考えずに、評価の良い一番簡単そうな本を購入しました。素晴らしい本に感謝。本で学べるのはとてもありがたく、ブログをいくつも巡るよりも効率的だと思います。

ロリポップでデータベースを調べる

WEBアプリといえばやはりデータベースは必須だと思います。ロリポップのデータベースを調べてみました。

ライトプランの特徴は以下の通りです:

  • 月額料金: 220円〜
  • ディスク容量: 350GB
  • MySQLデータベース: 最大50個

私の場合は既にWordpressがデータベースを1個つかっているはずなので、あと49個のデータベースを作れます。データベース数は十分ですね。

というわけで、ロリポップでWebアプリケーションを作るには、

  • Python言語を学ぶ
  • Python言語用のWebアプリケーションフレームワークFlaskを学ぶ
  • MySQLデータベースを学ぶ

これでなんとかなりそうです。

PCにPythonの開発環境を構築

手持ちのマックにMinicondaのインストールを行います。

Minicondaは、Anaconda Distributionの最小限のバージョンで、Pythonやその他のプログラムに依存関係を管理するためのパッケージ管理システムであるCondaを提供します。Minicondaは、最小限のインストールでありながら、必要に応じてパッケージを追加してカスタマイズできるため、軽量で柔軟な環境を構築するのに適しています。

主な特徴

  1. 軽量インストール:
    • Minicondaは最小限のインストールであり、必要なパッケージのみをインストールできます。
  2. パッケージ管理:
    • Condaを使用してパッケージのインストール、アップデート、削除を簡単に行うことができます。
  3. 仮想環境管理:
    • Condaを使用して複数の仮想環境を作成し、異なるプロジェクトで異なるパッケージやPythonバージョンを使用することができます。

ライセンス

Minicondaは、BSD 3-Clause “New” or “Revised” License(BSD-3-Clause)でライセンスされています。これは、広く使われているオープンソースライセンスで、ソフトウェアの再配布と使用に関する緩やかな制約を提供します。

Minicondaのインストール

https://docs.anaconda.com/miniconda

私が使っているM1 Macの場合、Minicondaをインストールするには、ARM64アーキテクチャに対応したバージョンを選択する必要があります。

というわけでM1 Macでの構築は諦め最近使ってなかったIntel Mac Miniを引っ張り出してきて使用することに。まさか今さらIntel Macを使うことになるとは思ってもいなかったです。

Minicondaのインストール手順 (Intel Mac用)

  1. Minicondaのダウンロード:
    • Minicondaの公式ダウンロードページにアクセスし、Miniconda3 macOS Intel x86 64-bit bash用のインストーラーをダウンロードします。
    • Miniconda公式ダウンロードページ
    • curl https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-x86_64.sh > Miniconda3-latest-MacOSX-x86_64.sh
  2. インストーラーの実行:
    • ダウンロードしたインストーラーをターミナルで実行します。以下のコマンドを使用します。
      • bash Miniconda3-latest-MacOSX-x86_64.sh
  3. インストールプロセスに従う:
    • インストール中に表示されるプロンプトに従って、インストールディレクトリを指定し、インストールを完了します。
    • デフォルトの設定を選択する場合は、基本的に「Enter」キーを押して進むことができます。
    • ENTERで続行。ライセンスはスペースでページ送りできます。
    • ライセンスに同意します。
    • インストール先を選択します。特に問題なければENTERで進みます。
    • インストールが完了しました
  4. 環境変数の設定:
    • インストールが完了したら、ターミナルを再起動するか、次のコマンドを実行して環境変数を更新します。
      • source ~/.bash_profile
    • または、zshを使用している場合は次のコマンドを実行します。Catalina世代以降のデフォルトシェルはzshです。
      • source ~/.zshrc
    • Condaの動作確認:
      • Condaが正しくインストールされているかを確認するために、次のコマンドを実行します。
      • conda --version
      • 正常にインストールされていれば、Condaのバージョンが表示されます。

特定のPythonバージョンの仮想環境を作成

インストールが完了したら、特定のPythonバージョンを使用する仮想環境を作成します。今回はロリポップサーバーのバージョンに合わせます。

conda create --name flask_env python=3.7
## 確認
conda env list

次に、その環境をアクティベートします。

(base) % conda activate flask_env

アクティベートすると、仮想環境名がbaseからflask_envにかわり、今どの仮想環境が有効になっているのかわかります。

(flask_env) % 

抜ける時は deactivateです

(flask_env) % conda deactivate
(base) %

flaskのインストール

pip installで、仮想環境へインストールします。

さて、ここで問題になるのがFlaskのバージョンです。ロリポップでは古いPython3.7しか対応していないので、FlaskのバージョンもPython3.7をサポートしている最終バージョンを選択します。flaskのサイトによると、Version 2.3.0Python 3.7のサポートを終了しています。そこで、flask 2.2.5を使用しましょう。

(base) % conda activate flask_env
(flask_env) % pip install flask==2.2.5

開発環境のインストール

Gitのインストールを行います。Macの場合は次のコマンドでインストールできます。

xcode-select --install

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

https://code.visualstudio.com

上記サイトからダウンロードしたMac用ファイルを「アプリケーション」フォルダに移動すればインストール完了です。

機能拡張(プラグイン)導入

まずはPythonの機能拡張を入れます。

次に、Better Jinja機能拡張を入れます。

(任意)GitHub CopilotとCopilot Chatも入れておきます。

(任意)Flake8はソースコードの問題をチェックするツールです。

言語パックです。日本語化できます。

これをインストールすると、「Change Language and Restart」というメッセージが表示されます。クリックするとVSCodeが再起動し、日本語化されます。

ワークスペースの作成

% cd
% mkdir work_flask
% cd work_flask

メニューの「ファイル」>「フォルダを開く」を選択、work_flaskを開く。

「はい、作成者を信頼します」をクリック。

メニューの「ターミナル」>「新しいターミナル」を開いて、仮想環境を有効にします。

(base) h% conda activate flask_env
(flask_env)  % 

GitHub Copilotを使う

AIで時短しましょう。タイパ最強の生成AIを使わない手はありません。

たまに間違うけど、大体合ってるし、わからない事は聞けばすぐ答える、間違いを指摘すればすぐ直してくれる、そんな、強力なペアプログラマーを得た感覚です。

Hello Flask!

hello-sampleフォルダを作成。その中にapp.pyファイルを作成。

from flask import Flask

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Define a route
# ====================================
@app.route('/')
def hello():
    return 'Hello, World!'

下部にPythonのバージョンが書かれている場所があります。今、3.8.2となっているので、ここをクリックします。すると、Select Interpriterが開くので、今回のターゲットバージョンであるPython 3.7("flask_env")を選択します。

では実行しましょう。

(flask_env) work_flask % cd hello-sample 
(flask_env) hello-sample % flask --app app run
 * Serving Flask app 'app'
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
Press CTRL+C to quit
127.0.0.1 - - [30/Jul/2024 20:21:03] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [30/Jul/2024 20:21:03] "GET /favicon.ico HTTP/1.1" 404 -

実行すると Runnning on http://127.0.0.1:5000 と表示されるので、Commandキーを押しながら、URLをクリックしてブラウザで表示します。

ターミナルでCtrl+Cを押すと Flaskを止めることができます。

では次にこれをLolipopサーバーで動かしてみましょう。

ロリポップのライトプランにはSSHは使えません

なんということでしょう。ロリポップのライトプランでは、SSHは使えないのだそうです。

SSHは、スタンダードプラン、ハイスピードプラン、エンタープライズプランで利用できます。と書いてあります。うーんそうかライトプランは無理か。

というわけで、SSHを使わないで頑張ってFlaskを動かしてみましょう。FTPでデプロイするだけで動く方向で検討します。

Cyberduckのインストール

MacにおけるFTPクライアントには以下のソフトを使いますのでインストールしておいてください。

https://cyberduck.io

SSHを使わないでどうやってFlaskインストールするか

本来であれば、Flaskを使用するためには、基本的にFlaskパッケージをインストールする必要があります。Pythonのパッケージは、一般的にインストールされることで、コードから簡単にインポートして使用できるようになります。Flaskも例外ではありません。そのためにはSSHでログインします。しかし、前述の通りロリポップのライトプランにはSSH接続は提供されていません。

Flaskをプロジェクトに含めちゃう作戦でいきましょう。つまり、インストールできないのであれば、ソースコードをまるっと含めればいいのです。言うなれば手動インストールですね。例えば、FlaskのソースコードはGithubで公開されています。依存するソフトウェアも同様です。

https://github.com/pallets/flask/releases/tag/2.2.5

https://github.com/pallets/click/tags

https://github.com/pallets/werkzeug/tags?after=2.3.3

Flaskの直接使用

Flaskをインストールせずに使いたい場合、Flaskのソースコードを直接プロジェクトに含める方法があります、まあでもこれは推奨される方法ではありません。

直接使用する場合の注意点

Flaskのソースコードを直接使用する場合、以下の手順に従いますが、複雑で手間がかかるため、あまり推奨されません。でもライトプランでは仕方がありません。頑張って作業して進めることにしました。

1. Flaskのソースコードをダウンロード:

Flaskのソースコードは、FlaskのGitHubリポジトリからダウンロードできます。

2. ソースコードをプロジェクトに含める:

ダウンロードしたソースコードをプロジェクトディレクトリにコピーします。

3. Flaskの依存関係を解決:

FlaskにはWerkzeugやJinjaなどの依存関係があります。これらも同様にソースコードをダウンロードしてプロジェクトに含める必要があります。

例えば
clickならhttps://github.com/pallets/click/releases/tag/8.1.7
werkzeugならhttps://github.com/pallets/werkzeug/tags?after=2.3.3
などからバージョンを指定してダウンロードすることができます。

Flaskの依存関係はFlaskのソースコードを開き、setup.pyを見ることで確認できます。さらにその依存関係先の依存関係をさらにチェック・・・するのは面倒なのでvenvで環境を作って、Flaskをpipでインストールしておき、pip freeze > requirements.txtを実行してrequirements.txtを作成しておきます。次のようなコマンドになると思います。

cd hello-sample
python3 -m venv menv
source menv/bin/activate
pip install flask
pip freeze > requirements.txt

こうし出来たrequirements.txtの中身は次のようになります。flask==2.2.5の場合のです。

click==8.1.7
Flask==2.2.5
importlib-metadata==6.7.0
itsdangerous==2.1.2
Jinja2==3.1.4
MarkupSafe==2.1.5
typing_extensions==4.7.1
Werkzeug==2.2.3
zipp==3.15.0

これらの依存パッケージをバージョン指定でGithubからダウンロードします。ソースコードはZIPファイルになっているので、展開してフォルダごとdependenciesフォルダに入れておきます。

4. コードでFlaskをインポート:

プロジェクト内でFlaskのパスを設定し、Flaskをインポートします。後述するindex.cgi内で実装例を示します。

プロジェクトの構成

プロジェクトにはdependenciesというフォルダを用意し、Githubからダウンロードしたソースコードをプロジェクトに追加しています。

../hello-sample
├── app.py
├── dependencies
│   ├── click-8.1.7
│   ├── flask-2.2.5
│   ├── importlib_metadata-6.7.0
│   ├── itsdangerous-2.1.2
│   ├── jinja-3.1.4
│   ├── markupsafe-2.1.5
│   ├── typing_extensions-4.7.1
│   ├── werkzeug-2.2.3
│   └── zipp-3.15.0
├── index.cgi
└── requirements.txt

index.cgiを用意する

index.cgi ファイルは、CGI(Common Gateway Interface)を使用してWebサーバー上でPythonスクリプトを実行するためのエントリーポイントです。

#!/usr/local/bin/python3.7
import cgitb
cgitb.enable()
import sys
import os

sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/flask-2.2.5/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/werkzeug-2.2.3/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/jinja-3.1.4/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/click-8.1.7/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/importlib_metadata-6.7.0/'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/markupsafe-2.1.5/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/itsdangerous-2.1.2/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/typing_extensions-4.7.1/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/zipp-3.15.0/'))

from wsgiref.handlers import CGIHandler
from app import app
CGIHandler().run(app)

CGIデバッグの有効化:

import cgitb
cgitb.enable() # Enable CGI traceback for debugging

cgitb モジュールを使用して、スクリプト内のエラーをHTML形式で表示します。デバッグに役立ちます。

import sys
import os
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/flask-2.2.5/src'))
sys.path.insert(0, os.path.join(os.getcwd(), 'dependencies/werkzeug-2.2.3/src'))
…

sys.path.insert(0, path) を使用して、依存関係のディレクトリをPythonのモジュール検索パスに追加します。これにより、これらのパッケージがインポート可能になります。

app.pyでFlaskを使ったコーディング

通常のFlaskのコーディングを行います

from flask import Flask

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Define a route
# ====================================
@app.route('/')
def hello():
    return 'Hello, World!'

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

.htaccess

最後に、cgiを実行できるように .htaccessを設置します。

Options +ExecCGI
AddHandler cgi-script .cgi
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ /hello-sample/index.cgi/$1 [QSA,L]

このApache HTTPサーバーの設定は、CGIスクリプトを実行し、特定のURLパターンをリライトするためのものです。各ディレクティブの詳細な解説を以下に示します。

1. CGIスクリプトを実行することを許可

Options +ExecCGI
  • 説明: このディレクティブは、ディレクトリ内でCGIスクリプトを実行することを許可します。
  • 詳細: Apacheサーバーでは、Options ディレクティブを使用してディレクトリのさまざまな動作を設定できます。ExecCGI オプションを有効にすることで、そのディレクトリ内の .cgi スクリプトを実行可能にします。

2. .cgi 拡張子を持つファイルをCGIスクリプトとして処理

AddHandler cgi-script .cgi
  • 説明: このディレクティブは、.cgi 拡張子を持つファイルをCGIスクリプトとして処理するように設定します。
  • 詳細: AddHandler ディレクティブを使用して、特定の拡張子のファイルを特定のハンドラーで処理するようにApacheに指示します。ここでは、.cgi ファイルを cgi-script ハンドラーで処理するように設定しています。

3. URLリライトエンジンを有効に

RewriteEngine On
  • 説明: このディレクティブは、URLリライトエンジンを有効にします。
  • 詳細: Apacheのモジュール mod_rewrite を使用すると、URLのリクエストをリライト(変更)することができます。RewriteEngine On は、リライトエンジンを有効にするためのコマンドです。

4. クエストされたファイルが実際のファイルでない場合にリライトルールを適用

RewriteCond %{REQUEST_FILENAME} !-f
  • 説明: このディレクティブは、リクエストされたファイルが実際のファイルでない場合にリライトルールを適用する条件を指定します。
  • 詳細: RewriteCond ディレクティブは、次の RewriteRule を適用する条件を定義します。ここでは、リクエストされたパスが実際のファイル(存在するファイル)でない場合にのみ、次のリライトルールを適用するという条件を設定しています。

5. 特定のパターンにマッチするURLを別のURLにリライト

RewriteRule ^(.*)$ /hello-sample/index.cgi/$1 [QSA,L]
  • 説明: このディレクティブは、特定のパターンにマッチするURLを別のURLにリライトします。
  • 詳細: RewriteRule は、URLのリクエストを別の形式に変換するためのルールを定義します。
    • ^(.*)$: リクエストされたURLのパス全体にマッチする正規表現パターン。(.*) は任意の文字列にマッチします。
    • /hello-sample/index.cgi/$1: リクエストを /hello-sample/index.cgi/ にリライトし、元のリクエストのパス部分($1)を引数として渡します。
    • [QSA,L]: リライトルールのフラグ。
      • QSA (Query String Append): 既存のクエリ文字列を保持し、新しいクエリ文字列を追加します。
      • L (Last): これが最後のリライトルールであることを示し、それ以降のルールを無視します。

FTPによるファイルの配置

ここまで完成したらCyberduckなどのFTPソフトを使って、hello-sampleフォルダごとサーバーにアップロードしましょう。

ファイルを配置したら最後にパーミッションを設定します。

.htaccessファイル604(rw—-r–)
app.py700(rwx——)
index.cgi700(rwx——)
dependenciesフォルダ705(rwx—r-x)
パーミッションを上の通り設定します。これが意外と重要で、適当なパーミッションだとセキュリティ上の理由からCGIが実行されないことがあるらしいので、ちゃんと設定します。Cyberduckの場合、ファイルを右クリックし、情報を選択するか、[Command + I]キーを押して情報ダイアログを開いてパーミッションを変更できます。

動作確認

では確認です。ブラウザーを開き、 https://あなたのドメイン/hello-sample/にアクセスすると、Hello Worldが表示されます。

お疲れ様でした、これでFlaskによるHello Worldがロリポップのライトプランでも実行できました。

デコレータ

Pythonのデコレータは、関数やメソッドの動作を変更するための非常に強力なツールです。デコレータはPython 2.4以降のバージョンで使用可能です。以下では、デコレータの基本的な使い方とその仕組みについて詳しく解説します。

デコレータの基本

デコレータは他の関数を引数として取り、その関数に何らかの追加処理を施して、新しい関数を返す関数のことです。基本的なデコレータの構文は以下のようになります。

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print("Something is happening before the function is called.")
        result = func(*args, **kwargs)
        print("Something is happening after the function is called.")
        return result
    return wrapper

@my_decorator
def say_hello():
    print("Hello!")

say_hello()

この例では、say_hello 関数に my_decorator を適用しています。@my_decorator の部分がデコレータを適用するシンタックスシュガーです。say_hello が呼ばれると、実際には wrapper 関数が実行され、前後にメッセージが表示されます。

デコレータの詳細な仕組み

  1. デコレータ関数: デコレータは関数を引数として取り、新しい関数を返します。
  2. ラッパー関数: デコレータ内部で定義されるラッパー関数(wrapper 関数)は、元の関数を実行する前後に追加の処理を挿入します。
  3. 引数の受け渡し: ラッパー関数は元の関数と同じ引数を受け取るようにします(*args**kwargs を使う)。

デコレータの実用例

以下は、関数の実行時間を計測するデコレータの例です。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} took {end_time - start_time} seconds to execute.")
        return result
    return wrapper

@timer
def long_running_function():
    time.sleep(2)
    print("Finished long running function.")

long_running_function()

この例では、long_running_function が実行されると、その実行時間が計測されて表示されます。

複数のデコレータを使う

複数のデコレータを適用することも可能です。この場合、デコレータは内側から外側へ順に適用されます。

def decorator1(func):
    def wrapper(*args, **kwargs):
        print("Decorator 1")
        return func(*args, **kwargs)
    return wrapper

def decorator2(func):
    def wrapper(*args, **kwargs):
        print("Decorator 2")
        return func(*args, **kwargs)
    return wrapper

@decorator1
@decorator2
def greet():
    print("Hello, World!")

greet()

この場合、greet 関数は decorator2 によって最初にラップされ、その後 decorator1 によってラップされます。結果として、以下の出力が得られます。

Decorator 1
Decorator 2
Hello, World!

結論

Pythonのデコレータは非常に柔軟で強力な機能です。関数やメソッドの前後に追加の処理を挿入するために利用され、コードの再利用性や可読性を向上させるのに役立ちます。以降、Flaskでどのようにデコレータを使うのか見ていきましょう。

ルーティング

Flaskでは、ルーティング機能を使用してURLと関数(ビュー関数)を紐付けます。ルーティングは、特定のURLにアクセスした際にどの関数を実行するかを決定する重要な機能です。以下に、Flaskにおけるルーティングの基本的な使い方を説明します。

基本的なルーティング

以下は、基本的なFlaskアプリケーションの例です。この例では、3つのルートを定義しています。1つはトップページ(/)、もう1つは商品ページ(/list)、3つ目は詳細ページ(/detail)です。

from flask import Flask

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Define a route
# ====================================
@app.route('/')
def hello():
    return '<H1>Top page</H1>'

@app.route('/list')
def item_list():
    return '<H1>Item list</H1>'

@app.route('/detail')
def item_detail():
    return '<H1>Item detail</H1>'

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

ルートのパラメータ

ルーティングでは、URLパラメータを使って動的なURLを作成することもできます。以下の例では、<name>というパラメータを持つルートを定義しています。

@app.route('/hello/<name>')
def hello_name(name):
    return f'Hello, {name}!'

この場合、/hello/John のようにアクセスすると、Hello, John! と表示されます。

パラメータの型指定

Flaskでは、URLパラメータの型を指定することもできます。デフォルトでは、すべてのパラメータは文字列として扱われますが、整数や浮動小数点数などの型を指定することも可能です。

@app.route('/user/<int:user_id>')
def show_user(user_id):
    return f'User ID is {user_id}'

この例では、<int:user_id> と指定することで、user_id パラメータが整数として扱われます。

もし,user_idパラメータが数値ではない場合、Flaskは404エラーとなり、「Not Found」エラーとなります。

<int:user_id>intのことをコンバータと呼びます。Flaskにおけるコンバータ(Converters)は、ルートで指定するURLパラメータの型を変換するための仕組みです。デフォルトでは、FlaskはURLパラメータをすべて文字列として扱いますが、コンバータを使用することで、整数や浮動小数点数などの特定の型に変換することができます。

コンバータの使用方法

Flaskにはいくつかのビルトインコンバータが用意されています。以下に、一般的に使用されるコンバータの一覧とその使用例を示します。

ビルトインコンバータ

  • string:デフォルトのコンバータ。任意の文字列にマッチします。
  • int:整数にマッチします。
  • float:浮動小数点数にマッチします。
  • path:スラッシュを含む文字列にマッチします。
  • uuid:UUID形式の文字列にマッチします。

了解しました。それでは、すべてのソースコードを一つにまとめて表示し、さらにJinjaテンプレートについても解説します。

Jinjaテンプレートを使用する

from flask import Flask, render_template

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Define a route
# ====================================

# Top page
@app.route('/')
def index():
    return render_template('top.html')

# Item list
@app.route('/list')
def item_list():
    return render_template('list.html')

# Item detail
@app.route('/detail')
def item_detail():
    return render_template('detail.html')

# ====================================
# Run the app
# ====================================
if __name__ == '__main__':
    app.run(debug=True)

render_templateを使ってテンプレートを表示する

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

Jinjaテンプレートについて

FlaskはJinjaというテンプレートエンジンを使用します。Jinjaテンプレートを使用すると、HTML内にPythonコードを埋め込むことができ、動的にコンテンツを生成することができます。

以下に、top.html の例を示します:

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>TOP</title>
    </head>
    <body>
        <h1>top</h1>
        <hr />
    </body>
</html>

このテンプレートはシンプルなHTMLですが、Jinjaテンプレートエンジンを使用することで、動的なデータを埋め込むことができます。例えば、以下のように変数を使うことができます:

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>{{ title }}</title>
    </head>
    <body>
        <h1>{{ heading }}</h1>
        <hr />
    </body>
</html>

このテンプレートでは、titleheading という変数が使われています。これらの変数は render_template 関数を使用して渡すことができます。

@app.route('/')
def index():
    return render_template('top.html', title="TOP", heading="Welcome to the TOP page")

テンプレートは templates フォルダに入れる

Flaskはデフォルトでテンプレートファイルを templates フォルダ内から探します。このフォルダ構成により、プロジェクトのディレクトリが整理され、Flaskがテンプレートを簡単に見つけることができます。

ディレクトリ構造の例:

my_flask_app/
├── app.py
└── templates/
    ├── top.html
    ├── list.html
    └── detail.html

この構造により、Flaskは templates フォルダ内のテンプレートファイルを自動的に認識し、適切にレンダリングすることができます。

テンプレートの継承

テンプレートの継承は、FlaskとJinjaテンプレートエンジンの強力な機能の一つで、共通のレイアウトを持つ複数のページを効率的に作成するために使用されます。テンプレートの継承を使用することで、ページごとに異なる部分だけを定義し、共通部分を親テンプレートにまとめることができます。これにより、コードの重複を避け、保守性を向上させることができます。

以下では、テンプレートの継承の基本的な使い方を説明します。

親テンプレートの作成

まず、共通のレイアウトを持つ親テンプレートを作成します。例えば、base.html というファイルを templates フォルダに作成します。

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <header>
        <h1>My Website</h1>
        <nav>
            <ul>
                <li><a href="/">Home</a></li>
                <li><a href="/list">List</a></li>
                <li><a href="/detail">Detail</a></li>
            </ul>
        </nav>
    </header>
    <main>
        {% block content %}{% endblock %}
    </main>
    <footer>
        <p>© 2024 My Website</p>
    </footer>
</body>
</html>

この base.html は親テンプレートで、{% block title %}{% block content %} という2つのブロックを定義しています。これらのブロックは、子テンプレートで上書きされる部分です。

子テンプレートの作成

次に、base.html を継承する子テンプレートを作成します。例えば、top.html を以下のように作成します。

<!-- templates/top.html -->
{% extends "base.html" %}

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

{% block content %}
<h2>Welcome to the TOP page</h2>
<p>This is the top page of my website.</p>
{% endblock %}

この top.html は、base.html を継承し、{% block title %}{% block content %} を上書きしています。

同様に、他のページも同様に作成できます。

<!-- templates/list.html -->
{% extends "base.html" %}

{% block title %}Item List{% endblock %}

{% block content %}
<h2>Item List</h2>
<p>Here is a list of items.</p>
{% endblock %}


detail.html

<!-- templates/detail.html -->
{% extends "base.html" %}

{% block title %}Item Detail{% endblock %}

{% block content %}
<h2>Item Detail</h2>
<p>Here is the detail of the item.</p>
{% endblock %}

ルートでの使用

Flaskアプリケーションでは、これらのテンプレートをレンダリングするだけです。

from flask import Flask, render_template

app = Flask(__name__)

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

@app.route('/list')
def item_list():
    return render_template('list.html')

@app.route('/detail')
def item_detail():
    return render_template('detail.html')

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

url_forの使い方

Flaskのurl_for関数は、指定した関数に対応するURLを生成するために使用されます。この関数を使うことで、ハードコーディングされたURLを避け、動的にURLを生成することができます。これにより、アプリケーションの柔軟性とメンテナンス性が向上します。

基本的な使い方

url_for関数は、ビュー関数の名前を引数として取り、その関数に対応するURLを生成します。例えば、以下のように使用します。

例:基本的な使用

まず、ビュー関数を定義します。

from flask import Flask, url_for, render_template

app = Flask(__name__)

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

@app.route('/list')
def item_list():
    return render_template('list.html')

@app.route('/detail')
def item_detail():
    return render_template('detail.html')

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

次に、テンプレートで url_for を使用してリンクを生成します。

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>{% block title %}{% endblock %}</title>
</head>
<body>
    <header>
        <h1>My Website</h1>
        <nav>
            <ul>
                <li><a href="{{ url_for('index') }}">Home</a></li>
                <li><a href="{{ url_for('item_list') }}">List</a></li>
                <li><a href="{{ url_for('item_detail') }}">Detail</a></li>
            </ul>
        </nav>
    </header>
    <main>
        {% block content %}{% endblock %}
    </main>
    <footer>
        <p>© 2024 My Website</p>
    </footer>
</body>
</html>

この例では、url_for('index')index 関数に対応するURL / を生成します。同様に、url_for('item_list')/listurl_for('item_detail')/detail というURLを生成します。

パラメータ付きURLの生成

url_for 関数は、ビュー関数の引数に対応するURLパラメータを動的に生成することもできます。

例:パラメータ付きURL

まず、パラメータを受け取るビュー関数を定義します。

@app.route('/user/<username>')
def show_user(username):
    return f'User: {username}'

テンプレートで url_for を使用してリンクを生成します。

<!-- templates/base.html の一部 -->
<nav>
    <ul>
        <li><a href="{{ url_for('index') }}">Home</a></li>
        <li><a href="{{ url_for('item_list') }}">List</a></li>
        <li><a href="{{ url_for('item_detail') }}">Detail</a></li>
        <li><a href="{{ url_for('show_user', username='john_doe') }}">John's Profile</a></li>
    </ul>
</nav>

この例では、url_for('show_user', username='john_doe')show_user 関数に対応するURL /user/john_doe を生成します。

URLクエリパラメータの追加

url_for 関数を使用して、クエリパラメータをURLに追加することもできます。

例:クエリパラメータの追加

@app.route('/search')
def search():
    query = request.args.get('q')
    return f'Search results for: {query}'

テンプレートで url_for を使用してクエリパラメータ付きのリンクを生成します。

<!-- templates/base.html の一部 -->
<nav>
    <ul>
        <li><a href="{{ url_for('search', q='Flask') }}">Search for Flask</a></li>
    </ul>
</nav>

この例では、url_for('search', q='Flask')search 関数に対応するURL /search?q=Flask を生成します。

テンプレートで制御文を使う

FlaskのJinjaテンプレートエンジンでは、テンプレート内で制御文や繰り返しを使用して、動的なHTMLコンテンツを生成することができます。以下に、制御文(if文)や繰り返し(for文)の使い方を具体例とともに説明します。

if文の使用

if文を使用することで、条件に応じて異なる内容を表示することができます。以下に、ifelifelse の使用例を示します。

例:if文の使用

<!-- templates/user.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>User Page</title>
</head>
<body>
    <h1>User Page</h1>
    {% if user %}
        <p>Welcome, {{ user.name }}!</p>
    {% elif guest %}
        <p>Welcome, guest! Please sign up.</p>
    {% else %}
        <p>Please log in.</p>
    {% endif %}
</body>
</html>

{% if user %} は、user 変数が存在し、None ではない場合にテンプレート内のブロックを表示する条件文です。

Flaskビュー関数

@app.route('/user')
def user_page():
    user = {'name': 'John Doe'}  # 例としてユーザー情報を渡す
    return render_template('user.html', user=user)

for文の使用

for文を使用することで、リストや辞書の各要素を繰り返し処理して表示することができます。

例:for文の使用

<!-- templates/items.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Item List</title>
</head>
<body>
    <h1>Item List</h1>
    <ul>
    {% for item in items %}
        <li>{{ item.name }}: {{ item.price }}円</li>
    {% endfor %}
    </ul>
</body>
</html>

Flaskビュー関数

@app.route('/items')
def items_page():
    items = [
        {'name': 'Item 1', 'price': 100},
        {'name': 'Item 2', 'price': 200},
        {'name': 'Item 3', 'price': 300},
    ]
    return render_template('items.html', items=items)

if文とfor文の組み合わせ

if文とfor文を組み合わせることで、さらに複雑なテンプレートを作成できます。

例:if文とfor文の組み合わせ

<!-- templates/items.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Item List</title>
</head>
<body>
    <h1>Item List</h1>
    {% if items %}
        <ul>
        {% for item in items %}
            <li>{{ item.name }}: {{ item.price }}円</li>
        {% endfor %}
        </ul>
    {% else %}
        <p>No items found.</p>
    {% endif %}
</body>
</html>

Flaskビュー関数

@app.route('/items')
def items_page():
    items = []  # 空のリストにして、"No items found." メッセージを表示させる
    return render_template('items.html', items=items)

Jinjaテンプレートエンジンを使用することで、テンプレート内で制御文や繰り返しを利用して、動的なコンテンツを生成することができます。これにより、条件に応じた表示やリストの要素の繰り返し表示などが簡単に行えます。Flaskのrender_template関数を使用して、ビュー関数からテンプレートにデータを渡し、テンプレート内でそのデータを使用して動的なHTMLを生成します。

Jinjaのフィルター機能

Jinjaのフィルター機能は、テンプレート内でデータの表示形式を変換するために使用されます。フィルターを使用することで、変数の値に対して特定の操作を行い、その結果をテンプレート内で表示することができます。Jinjaには多くのビルトインフィルターがあり、必要に応じてカスタムフィルターを定義することもできます。

ビルトインフィルターの例

以下に、いくつかのよく使われるビルトインフィルターの例を示します。

upper

文字列を大文字に変換します。

<p>{{ "hello world" | upper }}</p>

出力:

<p>HELLO WORLD</p>

lower

文字列を小文字に変換します。

<p>{{ "HELLO WORLD" | lower }}</p>

出力:

<p>hello world</p>

title

文字列をタイトルケースに変換します(各単語の最初の文字を大文字に変換)。

<p>{{ "hello world" | title }}</p>

出力:

<p>Hello World</p>

length

リストや文字列の長さを返します。

<p>{{ [1, 2, 3, 4, 5] | length }}</p>

出力:

<p>5</p>

default

変数が未定義または None の場合にデフォルト値を設定します。

<p>{{ user.name | default("Guest") }}</p>

出力(user.name が未定義の場合):

<p>Guest</p>

カスタムフィルターの定義

必要に応じてカスタムフィルターを定義することもできます。以下に、カスタムフィルターを定義して使用する例を示します。

カスタムフィルターの定義

まず、Flaskアプリケーションでカスタムフィルターを定義します。

from flask import Flask
app = Flask(__name__)

@app.template_filter('reverse')
def reverse_filter(s):
    return s[::-1]

@app.route('/')
def index():
    return render_template('index.html', name="Flask")

この例では、文字列を逆順にする reverse フィルターを定義しています。

カスタムフィルターの使用

次に、テンプレート内でカスタムフィルターを使用します。

<!-- templates/index.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Custom Filter Example</title>
</head>
<body>
    <h1>Custom Filter Example</h1>
    <p>Original: {{ name }}</p>
    <p>Reversed: {{ name | reverse }}</p>
</body>
</html>

このテンプレートでは、reverse フィルターを使用して文字列 name を逆順に表示します。

出力:

<h1>Custom Filter Example</h1>
<p>Original: Flask</p>
<p>Reversed: ksalF</p>

Jinjaのフィルター機能を使用することで、テンプレート内でデータを簡単に変換して表示することができます。ビルトインフィルターを使えば、多くの一般的な操作を簡単に実行できます。また、カスタムフィルターを定義することで、特定のニーズに合わせたデータ変換を行うことも可能です。これにより、テンプレートの柔軟性と再利用性が大幅に向上します。

文全体に対してフィルターを適用する方法

Jinjaでは、テンプレートのブロック全体に対してフィルターを適用することができます。これを実現するためには、{% filter %} ブロックを使用します。このブロック内で、フィルターを適用したいコンテンツを囲むことができます。

以下に、文全体に対してフィルターを適用する方法を具体例とともに説明します。

filter ブロックの使用

例1: テキストを大文字に変換する

以下の例では、upper フィルターを使用して、ブロック内のすべてのテキストを大文字に変換します。

<!-- templates/example.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Filter Block Example</title>
</head>
<body>
    <h1>Filter Block Example</h1>
    {% filter upper %}
    <p>This entire block of text will be converted to uppercase.</p>
    <p>Another line of text that will also be converted.</p>
    {% endfilter %}
</body>
</html>

出力:

<h1>Filter Block Example</h1>
<p>THIS ENTIRE BLOCK OF TEXT WILL BE CONVERTED TO UPPERCASE.</p>
<p>ANOTHER LINE OF TEXT THAT WILL ALSO BE CONVERTED.</p>

例2: テキストをタイトルケースに変換する

以下の例では、title フィルターを使用して、ブロック内のすべてのテキストをタイトルケースに変換します。

<!-- templates/example.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>Filter Block Example</title>
</head>
<body>
    <h1>Filter Block Example</h1>
    {% filter title %}
    <p>this entire block of text will be converted to title case.</p>
    <p>another line of text that will also be converted.</p>
    {% endfilter %}
</body>
</html>

出力:

<h1>Filter Block Example</h1>
<p>This Entire Block Of Text Will Be Converted To Title Case.</p>
<p>Another Line Of Text That Will Also Be Converted.</p>

フィルター一覧

公式ページのビルトインフィルター一覧のURLはこちらです:

Jinja Documentation – List of Builtin Filters

エラーハンドリング

Flaskでは、エラーハンドリングを使用して特定のHTTPステータスコードに対応するカスタムエラーページを表示することができます。例えば、ステータスコード「404(Not Found)」が発生した場合に特定の関数 show_404_page を呼び出す方法について説明します。

404エラーハンドリングの設定

以下の手順に従って、Flaskアプリケーションで404エラーハンドリングを設定します。

  1. エラーハンドリング用の関数を定義する
  2. errorhandlerデコレータを使用してエラーハンドラーを登録する

例: 404エラーハンドラーの実装

まず、Flaskアプリケーションで404エラーハンドリング用の関数を定義します。

from flask import Flask, render_template

app = Flask(__name__)

# ====================================
# Define the 404 error handler
# ====================================
@app.errorhandler(404)
def show_404_page(error):
    return render_template('404.html'), 404

# ====================================
# Define some routes
# ====================================
@app.route('/')
def home():
    return 'Welcome to the Home Page!'

@app.route('/about')
def about():
    return 'About Page'

# ====================================
# Run the app
# ====================================
if __name__ == '__main__':
    app.run(debug=True)

上記の例では、show_404_page 関数が定義され、404エラーハンドラーとして登録されています。この関数は、404.html テンプレートをレンダリングして、404ステータスコードとともに返します。

404エラーページのテンプレート

次に、404.html テンプレートを作成します。このテンプレートは、エラーページの内容を定義します。

<!-- templates/404.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>404 Not Found</title>
</head>
<body>
    <h1>404 Not Found</h1>
    <p>Sorry, the page you are looking for does not exist.</p>
    <a href="{{ url_for('home') }}">Go to Home</a>
</body>
</html>

このテンプレートは、ユーザーが存在しないページにアクセスした際に表示される内容を含みます。

Flaskでは、@app.errorhandler デコレータを使用して特定のHTTPステータスコードに対するエラーハンドラーを登録することができます。これにより、404エラーが発生した場合にカスタムエラーページを表示することが可能になります。この方法を使うことで、ユーザーに対してより親切なエラーメッセージを提供でき、Webサイトのユーザーエクスペリエンスを向上させることができます。

abort関数

Flaskのabort関数は、特定のHTTPステータスコードを返すために使用されます。これは、特定の条件が満たされない場合やエラーが発生した場合に、適切なHTTPステータスコードと共にリクエストを強制的に終了させるために便利です。以下に、abort関数の使用方法と具体例を示します。

abort関数の使用方法

abort関数は、Flaskのflaskモジュールからインポートされ、次のように使用されます。

from flask import Flask, abort, render_template

app = Flask(__name__)

@app.route('/secret')
def secret():
    abort(403)  # HTTPステータスコード403を返す

@app.route('/item/<int:item_id>')
def get_item(item_id):
    if item_id != 1:
        abort(404)  # HTTPステータスコード404を返す
    return f'Item {item_id}'

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

上記の例では、/secret ルートにアクセスすると、強制的に403エラーが発生します。また、/item/<int:item_id> ルートでは、item_id が1でない場合に404エラーを発生させます。

エラーハンドラーとabortの組み合わせ

abort関数を使用してエラーを発生させると、それに対応するカスタムエラーハンドラーを設定することができます。

from flask import Flask, abort, render_template

app = Flask(__name__)

@app.errorhandler(403)
def forbidden(error):
    return render_template('403.html'), 403

@app.errorhandler(404)
def page_not_found(error):
    return render_template('404.html'), 404

@app.route('/secret')
def secret():
    abort(403)

@app.route('/item/<int:item_id>')
def get_item(item_id):
    if item_id != 1:
        abort(404)
    return f'Item {item_id}'

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

この例では、403および404エラーが発生した場合に、それぞれのエラーハンドラーが呼び出され、対応するカスタムエラーページを表示します。

カスタムエラーページのテンプレート

403.html および 404.html テンプレートを作成することで、エラーメッセージをカスタマイズできます。

<!-- templates/403.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>403 Forbidden</title>
</head>
<body>
    <h1>403 Forbidden</h1>
    <p>Sorry, you don't have permission to access this page.</p>
    <a href="{{ url_for('home') }}">Go to Home</a>
</body>
</html>

<!-- templates/404.html -->
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>404 Not Found</title>
</head>
<body>
    <h1>404 Not Found</h1>
    <p>Sorry, the page you are looking for does not exist.</p>
    <a href="{{ url_for('home') }}">Go to Home</a>
</body>
</html>

Flaskのabort関数を使用することで、特定の条件が満たされない場合に適切なHTTPステータスコードを返すことができます。これにより、エラーハンドリングが容易になり、ユーザーに対してわかりやすいエラーメッセージを提供することができます。エラーハンドラーと組み合わせることで、カスタムエラーページを表示することができ、アプリケーションのユーザーエクスペリエンスを向上させることができます。

Formを使用したプログラム作成

Flaskを使用してフォームデータを処理するシンプルなプログラムを作成しました。このプログラムは、GETリクエストとPOSTリクエストの両方を処理し、ユーザーから送信されたデータを表示します。

app.py の内容

from flask import Flask, request

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Define a route
# ====================================
# Obtain Data from GET Request
@app.route('/get')
def do_get():
    name = request.args.get('name', 'no name')
    return f'Hello, {name}!'

# Obtain Data from POST Request
@app.route('/', methods=['GET', 'POST'])
def do_post():
    if request.method == 'POST':
        name = request.form['name']
        return f'Hello, {name}!'
    else:
        return '''
            <h2>Send by POST method</h2>
            <form method="post">
                name: <input type="text" name="name">
                <input type="submit" value="送信">
            </form>
        '''

# ====================================
# Run the app
# ====================================
if __name__ == '__main__':
    app.run(debug=True)

コードの解説

GETリクエストを処理するためのルートを定義します。このルートでは、URLパラメータから名前を取得し、挨拶メッセージを返します。もし名前が提供されていない場合、デフォルトの値として「no name」を使用します。

do_post関数

do_post関数は、GETリクエストとPOSTリクエストの両方を処理するように設計されています。この関数には2つの異なる側面があり、それぞれのリクエストメソッドに対して異なる動作を行います。以下に、それぞれの側面について詳細に説明します。

GETリクエストの挙動

GETリクエストは、ユーザーがウェブページにアクセスしたときに発生します。この場合、ユーザーに対して入力フォームを表示する必要があります。GETリクエストが行われたとき、do_post関数は以下の処理を行います。

  1. フォームの表示:
    • ブラウザがサーバーにGETリクエストを送信すると、サーバーはこのリクエストを受け取り、フォームを含むHTMLを返します。このフォームは、ユーザーが名前を入力してサーバーに送信するためのもので、名前を入力するテキストボックスと送信ボタンが含まれています。
  2. HTMLフォームの生成:
    • 関数内でHTMLを文字列として定義し、それをレスポンスとして返します。このHTMLフォームは、ユーザーが名前を入力して「送信」ボタンをクリックすると、POSTリクエストを送信するように設計されています。

POSTリクエストの挙動

POSTリクエストは、ユーザーがフォームにデータを入力して送信ボタンをクリックしたときに発生します。この場合、サーバーはユーザーから送信されたデータを受け取り、それに基づいて適切なレスポンスを生成する必要があります。POSTリクエストが行われたとき、do_post関数は以下の処理を行います。

  1. フォームデータの取得:
    • ブラウザがサーバーにPOSTリクエストを送信すると、サーバーはこのリクエストを受け取り、送信されたデータを抽出します。具体的には、request.formを使用してフォームデータを取得します。この例では、ユーザーが入力した名前を取得します。
  2. レスポンスの生成:
    • 取得した名前を使用して挨拶メッセージを生成し、それをレスポンスとして返します。これにより、ユーザーが送信したデータに基づいて動的に生成されたコンテンツを表示します。

do_post関数のまとめ

  • GETリクエスト時の処理:
    • ユーザーに入力フォームを表示する。
    • フォームは名前の入力と送信ボタンを含む。
  • POSTリクエスト時の処理:
    • ユーザーから送信されたフォームデータ(名前)を取得する。
    • 取得した名前を使用して挨拶メッセージを生成し、レスポンスとして返す。

全体の流れ

  1. ユーザーがフォームページにアクセス(GETリクエスト):
    • ユーザーがブラウザでアプリケーションのルートURLにアクセスすると、サーバーは入力フォームを表示するHTMLを返します。
  2. ユーザーが名前を入力して送信(POSTリクエスト):
    • ユーザーがフォームに名前を入力し、送信ボタンをクリックすると、ブラウザはサーバーにPOSTリクエストを送信します。
    • サーバーはPOSTリクエストを受け取り、フォームデータを取得して挨拶メッセージを生成し、それをユーザーに返します。

このように、do_post関数はGETリクエストとPOSTリクエストの両方に対応し、適切なレスポンスを生成するように設計されています。これにより、ユーザーとのインタラクションを処理する基本的なフォーム機能を提供します。

WTFormsとは

WTFormsは、FlaskアプリケーションでWebフォームを扱うためのライブラリです。WTFormsを使用することで、フォームの定義、バリデーション、レンダリングを簡単に行うことができます。

WTFormsのバージョン

Version 3.1.0より、Python3.7のサポートが終了しています。そのため、ロリポップのライトプランで使用するには、WTFormsはVersion3.0.1をインストールするようにします。

(flask_env) % pip install wtforms==3.0.1

サンプルで使用するメールアドレスのフォーマット確認のためのモジュールも、ここでインストールしておきましょう。

(flask_env) % pip install email-validator==2.0.0.post2

WTFormsは、FlaskアプリケーションでWebフォームを扱うための強力なライブラリです。以下では、WTFormsの使用方法、バリデーション、novalidate属性、テンプレートマクロ、カスタムバリデータについて詳細に解説します。

WTFormsの使用方法

WTFormsを使用するには、まずフォームクラスを定義します。このクラスは、フォームのフィールドとバリデーションルールを定義します。以下は、典型的なフォームクラスの例です。

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, IntegerField, SubmitField, DateField, RadioField, SelectField, BooleanField, TextAreaField
from wtforms.validators import DataRequired, Email, EqualTo, NumberRange

class EntryForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    age = IntegerField('Age', validators=[DataRequired(), NumberRange(min=0, max=120)])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    email = StringField('Email', validators=[DataRequired(), Email()])
    birthday = DateField('Birthday', format='%Y-%m-%d')
    gender = RadioField('Gender', choices=[('M', 'Male'), ('F', 'Female')])
    area = SelectField('Area', choices=[('NA', 'North America'), ('EU', 'Europe'), ('AS', 'Asia')])
    is_married = BooleanField('Married')
    note = TextAreaField('Note')
    submit = SubmitField('Submit')

バリデーション

バリデーションは、フォームデータが正しい形式であることを確認するために使用します。上記の例では、DataRequiredEmailEqualToNumberRangeなどのバリデータを使用しています。これにより、各フィールドが必須であること、メールアドレスの形式が正しいこと、パスワードが一致すること、年齢が0から120の範囲内であることを確認します。

novalidate属性

novalidate属性は、ブラウザのデフォルトのHTML5バリデーションを無効にし、サーバーサイドでバリデーションを行うことを強制します。これにより、クライアント側のバリデーションエラーが発生せず、サーバーで一貫したバリデーションが行えます。フォームタグにこの属性を追加します。

テンプレートマクロ

テンプレートマクロは、再利用可能なテンプレートコードを定義するために使用されます。以下は、フィールドのレンダリングをカスタマイズするためのマクロの例です。

<!-- _formhelpers.html -->
{% macro render_field(field) %}
  <dt>{{ field.label }}
  <dd>{{ field(**kwargs)|safe }}
  {% if field.errors %}
      <ul style="color: red;">
        {% for error in field.errors %}
        <li>{{ error }}</li>
      {% endfor %}
      </ul>
    {% endif %}
    </dd>
  {% endmacro %}

これを使用して、フォームフィールドをレンダリングする際のコードを簡略化できます。

カスタムバリデータ

カスタムバリデータを作成することで、独自のバリデーションロジックを追加できます。例えば、名前フィールドに特定の文字を含むことを強制するバリデータを作成する場合です。

from wtforms.validators import ValidationError

def contains_special_characters(form, field):
    if not any(char in field.data for char in '@#$%'):
        raise ValidationError('Field must contain one of the following characters: @, #, $, %')

class CustomForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired(), contains_special_characters])
    submit = SubmitField('Submit')

フォームの使用例

以下は、フォームを使用するFlaskアプリケーションの例です。

# app.py
from flask import Flask, render_template, redirect, url_for
from forms import EntryForm

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'

@app.route('/entry', methods=['GET', 'POST'])
def entry():
    form = EntryForm()
    if form.validate_on_submit():
        return redirect(url_for('result', form=form))
    return render_template('entry.html', form=form)

@app.route('/result')
def result(form):
    return render_template('result.html', form=form)

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

テンプレートの例

entry.html:

{% extends "base.html" %}

{% block title %}
    <h1>WTForm: entry</h1>
{% endblock %}

{% block content %}
{% from "_formhelpers.html" import render_field %}
    <form method="POST" novalidate>
        {{ render_field(form.name) }}
        {{ render_field(form.age) }}
        {{ render_field(form.password) }}
        {{ render_field(form.confirm_password) }}
        {{ render_field(form.email) }}
        {{ render_field(form.birthday) }}
        {{ render_field(form.gender) }}
        {{ render_field(form.area) }}
        {{ render_field(form.is_married) }}
        {{ render_field(form.note) }}
        {{ form.submit() }}
    </form>
{% endblock %}

result.html:

{% extends "base.html" %}

{% block title %}
    <h1>WTForm: result</h1>
{% endblock %}

{% block content %}
    <ul>
        <li>name: {{ form.name.data }}</li>
        <li>age: {{ form.age.data }}</li>
        <li>password: {{ form.password.data }}</li>
        <li>confirm_password: {{ form.confirm_password.data }}</li>
        <li>email: {{ form.email.data }}</li>
        <li>birthday: {{ form.birthday.data }}</li>
        <li>gender: {{ form.gender.data }}</li>
        <li>area: {{ form.area.data }}</li>
        <li>is_married: {{ form.is_married.data }}</li>
        <li>note: {{ form.note.data }}</li>
    </ul>
{% endblock %}

このように、WTFormsを使用することで、Flaskアプリケーションでのフォームの管理、バリデーション、レンダリングが容易になります。各フィールドのバリデーションルールを定義し、テンプレートマクロを使用してフォームを簡単にレンダリングできます。また、カスタムバリデータを作成することで、独自のバリデーションロジックを追加することも可能です。

Flask-WTFを使う

Flask-WTFは、FlaskとWTFormsを組み合わせてフォーム管理を簡単にするための拡張モジュールです。Flask-WTFを使用することで、CSRF保護の追加やフォームのバリデーション、レンダリングがより直感的に行えます。以下では、Flask-WTFの使用方法について、具体的なコード例を交えながら詳しく解説します。

Flask-WTFのインストール

まず、Flask-WTFをインストールします。

pip install flask-wtf==1.1.1

Flask-WTFの使用方法

フォームクラスの定義

Flask-WTFを使ってフォームクラスを定義します。この例では、ユーザー登録フォームを定義します。

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Email, EqualTo

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

Flaskアプリケーションの設定

FlaskアプリケーションでCSRF保護のための秘密鍵を設定します。

# app.py
from flask import Flask, render_template, redirect, url_for, flash
from forms import RegistrationForm

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your_secret_key'

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        flash(f'Account created for {form.username.data}!', 'success')
        return redirect(url_for('home'))
    return render_template('register.html', form=form)

@app.route('/')
def home():
    return 'Home Page'

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

テンプレートの定義

register.html テンプレートを作成し、フォームをレンダリングします。

<!-- templates/register.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Register</title>
</head>
<body>
    <h1>Register</h1>
    <form method="POST" action="">
        {{ form.hidden_tag() }}
        <p>
            {{ form.username.label }}<br>
            {{ form.username(size=32) }}<br>
            {% for error in form.username.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </p>
        <p>
            {{ form.email.label }}<br>
            {{ form.email(size=32) }}<br>
            {% for error in form.email.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </p>
        <p>
            {{ form.password.label }}<br>
            {{ form.password(size=32) }}<br>
            {% for error in form.password.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </p>
        <p>
            {{ form.confirm_password.label }}<br>
            {{ form.confirm_password(size=32) }}<br>
            {% for error in form.confirm_password.errors %}
                <span style="color: red;">[{{ error }}]</span><br>
            {% endfor %}
        </p>
        <p>{{ form.submit() }}</p>
    </form>
</body>
</html>

詳細な説明

フォームクラスの定義

  • FlaskForm: WTFormsの基本クラスで、Flask用に拡張されています。
  • フィールドの定義: StringFieldPasswordFieldSubmitFieldなど、各種フィールドを定義します。
  • バリデータ: DataRequiredEmailEqualToなど、フィールドの入力を検証するためのバリデータを設定します。

Flaskアプリケーションの設定

  • CSRF保護: SECRET_KEYを設定することで、CSRFトークンが生成され、フォームの送信時に保護されます。
  • ルートの定義: /registerルートでフォームを処理し、バリデーションが成功した場合はメッセージを表示し、ホームページにリダイレクトします。
  • validate_on_submitメソッド: フォームが送信され、かつバリデーションが成功した場合にTrueを返します。

テンプレートの定義

  • hidden_tagメソッド: CSRFトークンを含む隠しフィールドをレンダリングします。
  • フィールドのレンダリング: フィールドのラベルと入力要素をテンプレートに表示します。
  • エラーメッセージの表示: 各フィールドのエラーメッセージを赤色で表示します。

追加の機能

カスタムバリデータ

独自のバリデーションロジックを追加するためのカスタムバリデータを定義できます。

from wtforms.validators import ValidationError

def validate_username(form, field):
    if ' ' in field.data:
        raise ValidationError('Username must not contain spaces')

class RegistrationForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), validate_username])
    email = StringField('Email', validators=[DataRequired(), Email()])
    password = PasswordField('Password', validators=[DataRequired()])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Sign Up')

このように、Flask-WTFを使うことで、フォーム管理が簡単になり、セキュリティも向上します。フォームクラスの定義から、CSRF保護、バリデーション、テンプレートのレンダリングまで、Flask-WTFが一連の作業をスムーズに行うためのサポートを提供します。

Flask-WTFとPRGパターン

Flask-WTFは、FlaskとWTFormsを統合するための拡張モジュールで、フォームの管理とバリデーションを簡単に行えるようにします。以下に、Flask-WTFの使用方法について、PRGパターン、CSRF保護に注意しながら、サンプルコードを丁寧に解説します。

概要

Flask-WTFは、FlaskアプリケーションでWebフォームを扱う際の一連の機能を提供します。これには、CSRF保護、フォームのバリデーション、テンプレートでの簡単なフォームレンダリングなどが含まれます。

サンプルコード

以下のコードは、Flask-WTFを使った基本的なフォームの処理を示しています。

1. アプリケーション設定 (app.py)

import os
from flask import Flask, render_template, redirect, url_for, flash, session
from forms import InputForm

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

@app.route('/input', methods=['GET', 'POST'])
def input():
    form = InputForm()
    if form.validate_on_submit():
        session['name'] = form.name.data
        session['email'] = form.email.data
        return redirect(url_for('output'))
    return render_template('input.html', form=form)

@app.route('/output')
def output():
    return render_template('output.html')

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

2. フォーム定義 (forms.py)

from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired, Email

class InputForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Submit')

3. テンプレート (input.html)

{% extends "base.html" %}

{% block title %}
    <h1>WTForm: input</h1>
{% endblock %}

{% block content %}
    {% from "_formhelpers.html" import render_field %}
    <form method="POST" novalidate>
        {{ form.csrf_token }}
        {{ render_field(form.name) }}
        {{ render_field(form.email) }}
        <br />
        {{ form.submit() }}
    </form>
{% endblock %}

4. テンプレート (output.html)

{% extends "base.html" %}

{% block title %}
    <h1>WTForm: output</h1>
{% endblock %}

{% block content %}
    <div>
        <ul>
            <li>name: {{ session['name'] }}</li>
            <li>email: {{ session['email'] }}</li>
        </ul>
        <p><a href="{{ url_for('input')}}">Back to input screen</a> </p>
    </div>
{% endblock %}

5. ベーステンプレート (base.html)

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <title>Flask WTF Sample</title>
        <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    </head>
    <body>
        {% block title %}Title{% endblock %}
        <hr />
        {% block content %}Content{% endblock %}
    </body>
</html>

6. フォームヘルパー (_formhelpers.html)

{% macro render_field(field) %}
    <dt>{{ field.label }}
        <dd>{{ field(**kwargs)|safe }}
        {% if field.errors %}
            <ul style="color: red;">
                {% for error in field.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}
        </dd>
    </dt>
{% endmacro %}

詳細解説

PRGパターン (Post/Redirect/Get パターン)

このパターンは、フォームの再送信を防ぐために使用されます。ユーザーがフォームを送信すると、サーバーはPOSTリクエストを受け取り、データを処理します。その後、redirect関数を使って別のURLにリダイレクトします。これにより、ユーザーがページを再読み込みしてもフォームデータが再送信されることはありません。

@app.route('/input', methods=['GET', 'POST'])
def input():
    form = InputForm()
    if form.validate_on_submit():
        session['name'] = form.name.data
        session['email'] = form.email.data
        return redirect(url_for('output'))
    return render_template('input.html', form=form)

CSRF保護

CSRF(クロスサイトリクエストフォージェリ)攻撃を防ぐために、Flask-WTFはフォームにCSRFトークンを自動的に追加します。これにより、不正なリクエストからアプリケーションを保護できます。フォームテンプレートに{{ form.csrf_token }}を追加して、CSRFトークンを埋め込みます。

<form method="POST" novalidate>
    {{ form.csrf_token }}
    {{ render_field(form.name) }}
    {{ render_field(form.email) }}
    <br />
    {{ form.submit() }}
</form>

バリデーション

Flask-WTFを使用すると、フィールドごとにバリデーションを簡単に設定できます。DataRequiredEmailなどのバリデータを使用して、入力データが適切な形式であることを確認します。

class InputForm(FlaskForm):
    name = StringField('Name', validators=[DataRequired()])
    email = StringField('Email', validators=[DataRequired(), Email()])
    submit = SubmitField('Submit')

テンプレートマクロ

テンプレートマクロを使用することで、フォームフィールドのレンダリングを一元管理できます。これにより、フォームの見た目と挙動を一貫させることができます。

{% macro render_field(field) %}
    <dt>{{ field.label }}
        <dd>{{ field(**kwargs)|safe }}
        {% if field.errors %}
            <ul style="color: red;">
                {% for error in field.errors %}
                    <li>{{ error }}</li>
                {% endfor %}
            </ul>
        {% endif %}
        </dd>
    </dt>
{% endmacro %}

Flask-WTFを使用すると、フォームの定義、バリデーション、レンダリング、およびCSRF保護が簡単になります。また、PRGパターンを用いることで、ユーザーの誤操作によるデータの再送信を防ぐことができます。これらの技術を組み合わせることで、堅牢で使いやすいWebフォームを作成することができます。

FlaskでMySQLを扱う方法


1. 必要なライブラリのインストール

まず、MySQLとFlask関連のライブラリをインストールします。

Flask関連のライブラリ

FlaskとSQLAlchemy、PyMySQLをインストールします。

  • Flask: PythonのWebフレームワーク
  • SQLAlchemy: SQLツールキットおよびORM(Object Relational Mapper)
  • PyMySQL: MySQLのPythonクライアント

以下のコマンドをターミナルで実行します。

# 仮想環境を作成しアクティブ化
conda create --name flask_env flask flask-sqlalchemy pymysql
conda activate flask_env

すでに、仮想環境flaskがある場合は次のようにします。

# 既存の仮想環境をアクティブ
conda activate flask_env

# 関連ライブラリのインストール
conda install flask-sqlalchemy pymysql
Bash

これで、既存の仮想環境に必要なライブラリがインストールされます。

インストール済みのライブラリの確認

インストール済みのライブラリを確認する場合は、以下のコマンドを実行します。

conda list
Bash

これで、仮想環境内にインストールされているすべてのパッケージが表示されます。

MySQLクライアントのインストール

MySQLサーバーのインストール

MySQLサーバーをインストールします。Homebrewを使用して簡単にインストールできます。

Homebrew: macOS用のパッケージ管理システム。Homebrewを使うと、簡単にソフトウェアをインストールおよび管理できます。
# HomebrewでMySQLをインストール
brew install mysql

インストールにはかなり時間がかかります。

私の環境ではインストールに失敗し、次のようなエラーが発生します。

Error: An exception occurred within a child process: FormulaUnavailableError: No available formula with the name "formula.jws.json".

この場合は以下のようにするか

export HOMEBREW_NO_INSTALL_FROM_API=1
brew install mysql
Bash

もしくは、

https://downloads.mysql.com/archives/community

よりOSにあったバージョンのDMGを取得しインストールします。今回はDMGからインストールを実施しました。

# クライアントをインストール
% brew install mysql-client@8.0
……
If you need to have mysql-client@8.0 first in your PATH, run:
  echo 'export PATH="/usr/local/opt/mysql-client@8.0/bin:$PATH"' >> /Users/hiro/.zshrc
……  
# ログに表示された通りパスを.zshrcに追加
% echo 'export PATH="/usr/local/opt/mysql-client@8.0/bin:$PATH"' >> /Users/hiro/.zshrc
# 反映する
% source ~/.zshrc
# mysqlにパスが通ってるか確認
% mysql --version
mysql  Ver 8.0.39 for macos10.15 on x86_64 (Homebrew)
Bash

インストールが完了したら、MySQLサーバーを起動します。

# MySQLサーバーを起動
brew services start mysql

2. MySQLサーバーの設定

MySQLサーバーに接続し、必要なデータベースとユーザーを作成します。

# MySQLに接続
mysql -u root -p
Enter password: <パスワードを入>

# データベースとユーザーを作成
CREATE DATABASE flask_db;
CREATE USER 'flask_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON flask_db.* TO 'flask_user'@'localhost';
FLUSH PRIVILEGES;

3. Flaskプロジェクトのセットアップ

プロジェクトディレクトリを作成し、基本的なファイルを配置します。

ディレクトリ構成

flask_mysql_project/
├── app.py
├── config.py
├── models.py
└── templates/
    ├── index.html
    └── update.html

以下のコマンドでディレクトリとファイルを作成します。

mkdir flask_mysql_project
cd flask_mysql_project
touch app.py config.py models.py
mkdir templates
cd templates
touch index.html

4. config.py ファイルの設定

データベースの設定を行います。

# config.py
import os

class Config:
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://flask_user:password@localhost/flask_db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SECRET_KEY = os.urandom(24)

5. モデルの作成

データベースのモデルを定義します。SQLAlchemyを使用して、Pythonクラスとデータベーステーブルのマッピングを行います。

SQLAlchemyについて

  • SQLAlchemyは、PythonのORMライブラリで、データベース操作をオブジェクト指向的に行うことができます。
  • ORM(Object Relational Mapper): データベースのテーブルをPythonのクラスとして扱うことができ、SQLクエリを直接書かずにデータベース操作を行うことができます。

以下のコードでモデルを定義します。

# models.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

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

6. Flaskアプリの設定

Flaskアプリケーションを設定し、モデルを初期化します。

# app.py
from flask import Flask, render_template, request, redirect, url_for
from config import Config
from models import db, User

app = Flask(__name__)
app.config.from_object(Config)
db.init_app(app)

@app.route('/')
def index():
    users = User.query.all()
    return render_template('index.html', users=users)

@app.route('/add', methods=['POST'])
def add_user():
    username = request.form['username']
    email = request.form['email']
    new_user = User(username=username, email=email)
    db.session.add(new_user)
    db.session.commit()
    return redirect(url_for('index'))

@app.route('/update/<int:id>', methods=['GET', 'POST'])
def update_user(id):
    user = User.query.get(id)
    if request.method == 'POST':
        user.username = request.form['username']
        user.email = request.form['email']
        db.session.commit()
        return redirect(url_for('index'))
    return render_template('update.html', user=user)

@app.route('/delete/<int:id>')
def delete_user(id):
    user = User.query.get(id)
    if user:
        db.session.delete(user)
        db.session.commit()
    return redirect(url_for('index'))

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

7. テンプレートの作成

CRUD操作を行うためのHTMLテンプレートを作成します。

<!-- templates/index.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Flask MySQL Example</title>
  </head>
  <body>
    <h1>User List</h1>
    <form method="POST" action="{{ url_for('add_user') }}">
      <input type="text" name="username" placeholder="Username" required>
      <input type="email" name="email" placeholder="Email" required>
      <button type="submit">Add User</button>
    </form>
    <ul>
      {% for user in users %}
        <li>{{ user.username }} - {{ user.email }} 
            <a href="{{ url_for('update_user', id=user.id) }}">Edit</a> 
            <a href="{{ url_for('delete_user', id=user.id) }}">Delete</a>
        </li>
      {% endfor %}
    </ul>
  </body>
</html>
Jinja HTML

 

<!-- templates/update.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Update User</title>
  </head>
  <body>
    <h1>Update User</h1>
    <form method="POST" action="{{ url_for('update_user', id=user.id) }}">
      <input type="text" name="username" value="{{ user.username }}" required>
      <input type="email" name="email" value="{{ user.email }}" required>
      <button type="submit">Update</button>
    </form>
    <a href="{{ url_for('index') }}">Back</a>
  </body>
</html>
Jinja HTML

8. アプリケーションの実行

アプリケーションを実行します。

python app.py

ブラウザで http://127.0.0.1:5000/ にアクセスすると、ユーザーリストが表示され、ユーザーの追加、編集、および削除が行えるようになります。

実行すると次のようになります。

Editを選択すると編集モードになります。Deleteでデータを削除します。

データベースの中身を見てみましょう。

% mysql -u flask_user -p

mysql> use flask_db
mysql> select * from user;
+----+----------+--------------------+
| id | username | email              |
+----+----------+--------------------+
|  3 | User01   | user01@example.com |
|  4 | User02   | user02@example.com |
|  5 | User03   | user03@example.com |
+----+----------+--------------------+
3 rows in set (0.00 sec)

データベースにデータが登録されていることが確認できると思います。

Flask-Migrate

Flask-Migrateは、Flaskアプリケーションのデータベースマイグレーションを簡単に管理するための拡張機能です。Flask-Migrateは、SQLAlchemyとAlembicを使用してデータベーススキーマの変更を管理します。これにより、データベースのスキーマをバージョン管理し、必要に応じてアップグレードやダウングレードを行うことができます。

Flask-Migrateの基本的な使い方

  1. 初期設定:
    • flask db init: マイグレーションディレクトリを初期化します。
  2. マイグレーションファイルの作成:
  3. データベースのアップグレード:
    • flask db upgrade: マイグレーションファイルを適用し、データベースを最新のスキーマにアップグレードします。
  4. データベースのダウングレード:
    • flask db downgrade: マイグレーションファイルを逆に適用し、データベースを以前のスキーマに戻します。

使用したコマンドの解説

  1. flask db init
    • このコマンドは、マイグレーションディレクトリを初期化します。これにより、migrationsというディレクトリが作成され、データベースマイグレーションを管理するための基本的なファイルが生成されます。このコマンドは、Flask-Migrateを初めて使用する際に一度だけ実行する必要があります。
  2. flask db migrate -m "create tasks table":
    • tasksテーブルを作成するためのマイグレーションファイルを生成します。このコマンドは、モデル定義と現在のデータベーススキーマの差分を検出し、必要な変更を含むマイグレーションファイルを作成します。
  3. flask db upgrade:
    • 生成されたマイグレーションファイルを適用し、データベースを最新のスキーマにアップグレードします。この場合、tasksテーブルがデータベースに作成されます。
  4. flask db migrate -m "Add is_done column to table":
    • tasksテーブルにis_doneカラムを追加するためのマイグレーションファイルを生成します。このコマンドも、モデル定義と現在のデータベーススキーマの差分を検出し、必要な変更を含むマイグレーションファイルを作成します。
  5. flask db upgrade:
    • 生成されたマイグレーションファイルを適用し、データベースを最新のスキーマにアップグレードします。この場合、tasksテーブルにis_doneカラムが追加されます。

次のコードは、Flaskアプリケーションを設定し、tasksテーブルを管理するためのモデルを定義しています。また、いくつかのタスクをデータベースに挿入する関数も含まれています。

from flask import Flask
from config import Config
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

# ====================================
# Create a new Flask instance
# ====================================
app = Flask(__name__)

# ====================================
# Configuration
# ====================================
app.config.from_object(Config)
db = SQLAlchemy()
db.init_app(app)

# make sure to be able to do flasl_migrate
Migrate(app, db)

# ====================================
# Import models
# ====================================
class Task(db.Model):
    
    # table name 
    __tablename__ = 'tasks'
    # task id
    id = db.Column(db.Integer, primary_key=True,
                   autoincrement=True)
    # task content
    content = db.Column(db.String(200), nullable=False)
    # task status
    is_done = db.Column(db.Boolean, default=False)
    def __repr__(self):
        return f'task id: {self.id} content: {self.title} is_done: {self.is_done}'


# ====================================
# CRUD operations
# ====================================

def insert():
    print('Inserting three tasks')
    with app.app_context():
        task1 = Task(content='玄関掃除')
        task2 = Task(content='洗濯')
        task3 = Task(content='買い物')
        db.session.add_all([task1, task2, task3])
        db.session.commit()

if __name__ == '__main__':

    insert()
Python

Blueprint

FlaskのBlueprintを使うことで、大規模なアプリケーションを複数のモジュールに分けて管理することができます。これにより、コードの可読性やメンテナンス性が向上します。具体的には、以下のような利点があります:

  1. モジュール化: アプリケーションを複数のBlueprintに分割することで、異なる機能を持つ部分を分離できます。これにより、各部分を独立して開発・テスト・デバッグしやすくなります。
  2. 再利用性: Blueprintは他のアプリケーションでも再利用可能です。共通の機能を持つBlueprintを作成し、異なるプロジェクトで使い回すことができます。
  3. 管理の簡素化: 複数のBlueprintを使うことで、アプリケーションのルートやテンプレート、静的ファイルを管理する際に、各Blueprintごとに整理できます。これにより、アプリケーション全体の構造が明確になります。
  4. スケーラビリティ: アプリケーションが大きくなると、コードの管理が複雑になりますが、Blueprintを使用することで、機能ごとにコードを分けてスケーラブルに対応できます。

例えば、ブログアプリケーションを作成する場合、次のようにBlueprintを使ってモジュール化できます:

  • auth Blueprint: ユーザー認証やログイン関連のルートを管理。
  • blog Blueprint: ブログポストの表示や管理に関するルートを管理。
  • admin Blueprint: 管理者用のダッシュボードや管理機能を提供。

これらのBlueprintをメインアプリケーションに登録することで、Flaskアプリケーションの構造を整理できます。

Flaskのg

Flaskのgは、リクエストごとに一時的なデータを格納するためのグローバル名前空間を提供するオブジェクトです。gは、リクエストのライフサイクル中にのみ存在し、リクエストが終了するとデータは破棄されます。これにより、リクエストごとにデータを安全に共有することができます。

使用例

以下は、gを使用してデータベース接続を管理する簡単な例です。

1. データベース接続の設定

from flask import Flask, g
import sqlite3

app = Flask(__name__)

DATABASE = 'mydatabase.db'

def get_db():
    if 'db' not in g:
        g.db = sqlite3.connect(DATABASE)
    return g.db

@app.teardown_appcontext
def close_db(exception):
    db = g.pop('db', None)
    if db is not None:
        db.close()
Python

2. ルートでの使用

@app.route('/')
def index():
    db = get_db()
    cursor = db.execute('SELECT * FROM mytable')
    results = cursor.fetchall()
    return str(results)
Python

  • gは、リクエストごとに一時的なデータを格納するためのオブジェクトです。
  • リクエストのライフサイクル中にのみ存在し、リクエストが終了するとデータは破棄されます。
  • データベース接続やその他のリクエストごとのデータを管理するのに便利です。

このように、gを使用することで、リクエストごとにデータを安全に共有し、管理することができます。

Flaskのデバッグモード

Flaskのデバッグモードは、開発中のアプリケーションを効率的にデバッグするための機能です。以下に、デバッグモードの有効化方法、できること、Debugger PIN、修正のリロードについて詳しく解説します。

1. デバッグモードの有効化方法

Flaskアプリケーションでデバッグモードを有効にするには、以下の方法があります。

  • アプリケーションコード内で設定:
from flask import Flask

app = Flask(__name__)
app.config['DEBUG'] = True  # デバッグモードを有効にする
Python
  • 環境変数を設定: コマンドラインで以下のように環境変数を設定します。
export FLASK_DEBUG=1  # Linux/Mac
set FLASK_DEBUG=1     # Windows
flask run
Python
  • 引数で設定: run関数の引数で指定します。
if __name__ == '__main__':
    app.run(deb,ug=True)
Python

これは簡単にデバッグモードを有効にする手段の一つです。ただし、現在のFlask開発では、app.run(debug=True)を使用するよりも環境変数や設定ファイルを使用することが推奨されています。

2. デバッグモードでできること

デバッグモードを有効にすると、以下の機能が提供されます。

  • エラーのインタラクティブデバッグ: アプリケーションでエラーが発生した場合、ブラウザにエラーページが表示され、エラーのスタックトレースとともに、インタラクティブなPythonシェルにアクセスできます。このシェルを使って、アプリケーションの状態を調べたり、コードの動作を確認することができます。
  • コード変更時の自動リロード: 開発中にコードを変更すると、サーバーが自動的に再起動し、変更内容が反映されます。これにより、サーバーを手動で再起動する手間が省け、開発効率が向上します。

3. Debugger PINについて

デバッグモードが有効で、アプリケーションがクラッシュすると、ブラウザにデバッガのインタラクティブシェルが表示されます。このシェルにアクセスする際、Debugger PINが求められることがあります。

  • Debugger PINの目的: セキュリティのため、インタラクティブなデバッガにアクセスするにはPINが必要です。これにより、外部の人があなたのアプリケーションの内部状態にアクセスすることを防ぎます。
  • PINの確認: デバッグモードでアプリケーションを起動すると、ターミナルにDebugger PINが表示されます。このPINを使用して、ブラウザからデバッガにアクセスできます。

4. 修正のリロード

デバッグモードが有効な場合、Flaskはコードの修正を検知して自動的にアプリケーションを再起動します。これにより、変更を反映するためにサーバーを手動で再起動する必要がなくなります。

  • 内部動作: FlaskはWerkzeugというツールを使用して、ファイルシステムの変更を監視し、変更があった場合に自動的にアプリケーションを再ロードします。
  • リロードの注意点: 大規模なプロジェクトでは、リロードの速度が低下することがあります。また、一部の状態を持つアプリケーション(例えば、メモリ上のデータを保持する場合)は、リロード時に状態がクリアされることに注意が必要です。

これらの機能を活用することで、Flaskでの開発がよりスムーズかつ効率的になります。

メモ帳アプリの作成

メモ帳アプリの概要をここに

DBの準備

mysql -u root -p
Enter password: <パスワードを入力>
CREATE DATABASE memos_db;
CREATE USER 'memos_user'@'localhost' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON memos_db.* TO 'memos_user'@'localhost';
FLUSH PRIVILEGES;
cd my_memo_app
flask db init
flask db migrate
flask db upgrade

index.html

{% extends 'base.html' %}

{% block content %}
    <h2>Memo List</h2>
    
    {% with messages = get_flashed_messages() %}
        {% if messages %}
            <ul>
                {% for message in messages %}
                    <li>{{ message }}</li>
                {% endfor %}
            </ul>
        {% endif %}
    {% endwith %}
    
    {% if memos %}
        <ul>
            {% for memo in memos %}
                <li>
                    <a href="{{ url_for('update', memo_id=memo.id) }}">{{ memo.title }}</a>
                    <a href="{{ url_for('delete', memo_id=memo.id) }}">Delete</a>
                </li>
            {% endfor %}
        </ul>
    {% else %}
        <p>メモは登録されていません。</p>
    {% endif %}
{% endblock %}
Jinja HTML

解説

  • base.htmlを拡張して、メモリストを表示するページを作成しています。
  • get_flashed_messages()を使用して、フラッシュメッセージを取得し、表示しています。
  • memosが存在する場合はリストとして表示し、存在しない場合は「メモは登録されていません。」と表示します。

flashについて

  • flashはFlaskの機能で、ユーザーに一時的なメッセージを表示するために使用されます。例えば、フォームの送信後に成功メッセージやエラーメッセージを表示する場合に便利です。
  • flashでメッセージを設定し、次のリクエストでget_flashed_messages()を使ってそのメッセージを取得して表示します。

update_form.html と create_form.html

{% extends "base.html" %}

{% block content %}
    <h2>Edit Memo</h2>
    {% from "_formhelpers.html" import render_field %}
    <form method="POST" action="{{ url_for('update', memo_id=memo_id) }}" novalidate>
        {{ form.hidden_tag() }}
        {{ render_field(form.title) }}
        {{ render_field(form.content, rows=5, cols=50) }}
        {{ form.submit }}
    </form> 
                
    <a href="{{ url_for('index') }}">Back</a>

{% endblock %}
Jinja HTML
{% extends "base.html" %}

{% block content %}
    <h2>Create Memo</h2>
    {% from "_formhelpers.html" import render_field %}
    <form method="POST" action="{{ url_for('create') }}" novalidate>
        {{ form.hidden_tag() }}
        {{ render_field(form.title) }}
        {{ render_field(form.content, rows=5, cols=50) }}
        {{ form.submit }}
    </form> 
                
    <a href="{{ url_for('index') }}">Back</a>

{% endblock %}
Jinja HTML

解説

  • base.htmlを拡張して、メモの作成および編集フォームを表示します。
  • _formhelpers.htmlからrender_fieldマクロをインポートし、フォームフィールドをレンダリングします。
  • form.hidden_tag()はCSRFトークンを含む隠しフィールドを生成します。

404.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 Not Found</title>
</head>
<body>
    <h1>404 Not Found</h1>
    <p>Opps! Page not found. {{ msg }} </p>
    <p>The page you are looking for does not exist.</p>
</body>
</html>
Jinja HTML

解説

  • 404エラーページを表示するためのHTMLテンプレートです。
  • {{ msg }}はカスタムメッセージを表示するためのプレースホルダーです。

update.html と create.html

{% extends 'base.html' %}

{% block content %}
    <h2>Update Memo</h2>
    <form method="POST">
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" value="{{ memo.title }}" required>
        <label for="content">Content:</label>
        <textarea id="content" name="content" required>{{ memo.content }}</textarea>
        <button type="submit">Update</button>
    </form>
{% endblock %}	
Jinja HTML
{% extends 'base.html' %}

{% block content %}
    <h2>Create Memo</h2>
    <form method="POST">
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" required>
        <label for="content">Content:</label>
        <textarea id="content" name="content" required></textarea>
        <button type="submit">Create</button>
    </form>
{% endblock %}
Jinja HTML

解説

  • base.htmlを拡張して、メモの作成および更新フォームを表示します。
  • フォームにはタイトルとコンテンツの入力フィールドがあり、送信ボタンがあります。

base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Memo App</title>
</head>
<body>
    <header>
        <h1>Memo App</h1>
        <nav>
            <a href="{{ url_for('index') }}">Home</a>
            <a href="{{ url_for('create') }}">Create Memo</a>
        </nav>
    </header>
    <main>
        {% block content %}{% endblock %}
    </main>
</body>
</html>
Jinja HTML

解説

  • 全てのページのベースとなるHTMLテンプレートです。
  • ヘッダーにはアプリケーションのタイトルとナビゲーションリンクがあります。
  • {% block content %}{% endblock %}は、各ページのコンテンツが挿入される場所です。

models.py

from app import db

class Memo(db.Model):
    __tablename__ = 'memos'
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    title = db.Column(db.String(50), nullable=False)
    content = db.Column(db.Text)
Python

解説

  • SQLAlchemyを使用して定義されたメモのモデルです。
  • idtitlecontentの3つのフィールドを持ちます。

config.py

import os

class Config:
    SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://memos_user:password@localhost/memos_db'
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SECRET_KEY = os.urandom(24)
    DEBUG = True
Python

解説

  • アプリケーションの設定を管理するクラスです。
  • データベースURIやデバッグモードの設定、秘密鍵の生成などを行います。

秘密鍵について

秘密鍵はプロダクト環境においては推測されないようにランダムな文字列を設定することが推奨されます。本番環境では環境変数を使用するなどして、外部に公開されないように安全に管理します。次の記事が参考になりました。

views.py

from flask import render_template, request, redirect, url_for, flash
from app import app, db
from models import Memo
from forms import MemoForm

@app.route('/memo/')
def index():
    memos = Memo.query.all()
    return render_template('index.html', memos=memos)

@app.route('/memo/create', methods=['GET', 'POST'])
def create():
    form = MemoForm()
    if form.validate_on_submit():
        
    return render_template('create_form.html', form=form)

@app.route('/memo/update/<int:memo_id>', methods=['GET', 'POST'])
def update(memo_id):
    target_memo = Memo.query.get_or_404(memo_id)
    form = MemoForm(obj=target_memo)
    if request.method == 'POST' and form.validate():
        

@app.route('/memo/delete/<int:memo_id>')
def delete(memo_id):
    

from werkzeug.exceptions import NotFound

@app.errorhandler(NotFound)
def page_not_found(e):
Python

解説

  • Flaskのビュー関数を定義しています。
  • メモの一覧表示、作成、更新、削除の各機能を提供します。
  • flashを使用して、ユーザーにメッセージを表示することができます。

forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length, ValidationError
from models import Memo

class MemoForm(FlaskForm):
    title = StringField('Title', validators=[DataRequired(), Length(max=10, message='10文字以内で入力してください')])
    content = TextAreaField('Content')
    submit = SubmitField('Submit')

    def validate_title(self, title):
        memo = Memo.query.filter_by(title=title.data).first()
        if memo:
            raise ValidationError('同じタイトルのメモが存在します')
Python

解説

  • Flask-WTFを使用して定義されたフォームクラスです。
  • タイトルとコンテンツのフィールドを持ち、タイトルにはカスタムバリデーションを追加しています。

app.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

app = Flask(__name__)
app.config.from_object('config.Config')

db = SQLAlchemy(app)
migrate = Migrate(app, db)

from views import *

if __name__ == '__main__':
    app.run()
Python

解説

  • Flaskアプリケーションのエントリーポイントです。
  • アプリケーションの設定を読み込み、データベースとマイグレーションの初期化を行います。

Flask-Login

Flask-Loginは、Flaskアプリケーションにユーザー認証機能を追加するための拡張機能です。ユーザーのログイン状態を管理し、セッションを維持するための便利なツールを提供します。以下にFlask-Loginの主な機能と使用方法を説明します。

主な機能

  1. ユーザーのログインとログアウトの管理:
    • ユーザーのログイン状態を追跡し、ログイン中のユーザーに対して特定の操作を許可します。
  2. セッション管理:
    • クッキーを使用してユーザーのセッションを管理し、再ログインを防ぎます。
  3. ユーザーのロード:
    • ユーザーIDを使用してユーザー情報をロードするためのコールバック関数を提供します。

FlashLoginのインストール

pip install flask-login==0.6.2

アプリケーションの設定

from flask import Flask
from flask_login import LoginManager

app = /Flask(__name__)
app.secret_key = 'your_secret_key'  # セッション管理のために必要
login_manager = LoginManager()
login_manager.init_app(app)
Python

ユーザーモデルの定義

from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin

db = SQLAlchemy(app)

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    password = db.Column(db.String(150), nullable=False)
Python

ユーザーモデルは、Flask-LoginのUserMixinを継承する必要があります。

ユーザーのロード関数の定義

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))
Python

ログインとログアウトの実装

from flask import request, redirect, url_for, render_template
from flask_login import login_user, logout_user, login_required, current_user

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and user.password == password:
            login_user(user)
            return redirect(url_for('dashboard'))
    return render_template('login.html')

@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('index'))

@app.route('/dashboard')
@login_required
def dashboard():
    return f'Hello, {current_user.username}!'
Python

説明

  • login_user(user): ユーザーをログイン状態にします。
  • logout_user(): ユーザーをログアウト状態にします。
  • login_required: ログインが必要なルートにデコレータを追加します。
  • current_user: 現在ログインしているユーザーを取得します。

このようにして、Flask-Loginを使用すると、Flaskアプリケーションに簡単にユーザー認証機能を追加できます。

詳しくは公式ドキュメント

https://flask-login.readthedocs.io/en/latest

を参照してください。

Bootstrap

Bootstrapは、ウェブ開発のためのオープンソースのフロントエンドフレームワークで、HTML、CSS、およびJavaScriptを使って、レスポンシブでモバイルファーストなウェブサイトを迅速に構築するためのツールです。以下はBootstrapの主な特徴です:

  1. レスポンシブデザイン: Bootstrapは、異なる画面サイズに適応できるレスポンシブなグリッドシステムを提供します。これにより、デスクトップ、タブレット、スマートフォンなど、さまざまなデバイスで一貫した表示が可能です。
  2. グリッドシステム: 12カラムのグリッドシステムを使用して、レイアウトを構築します。カラムのサイズや順序を変更することで、異なるデバイス向けにレイアウトを調整できます。
  3. プリビルトコンポーネント: ボタン、ナビゲーションバー、モーダル、カード、フォーム、アラートなど、よく使われるUIコンポーネントが含まれています。これにより、ゼロからデザインを作成する手間が省けます。
  4. カスタマイズ可能: デフォルトのスタイルに加え、テーマのカスタマイズや自分自身のCSSを追加することができます。これにより、ブランドやプロジェクトに合わせたデザインを作成できます。
  5. JavaScriptプラグイン: モーダルウィンドウ、ツールチップ、ポップオーバーなどのインタラクティブな機能を提供するJavaScriptプラグインが含まれています。これらのプラグインは、jQueryに依存しています。
  6. アクセシビリティ: アクセシビリティを考慮して設計されており、スクリーンリーダーやキーボードナビゲーションに対応しています。

Bootstrapは、特にプロジェクトの立ち上げが早く、効率的にデザインを整えるための強力なツールです。公式サイト(getbootstrap.com)から簡単にダウンロードでき、ドキュメントも充実しています。

次のサイトがみやすかった。

https://www.tohoho-web.com/bootstrap5/index.html

コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

上部へスクロール