Flask入門講座:Pythonで実践するWebアプリケーション開発の始め方

はい、承知いたしました。PythonのマイクロフレームワークであるFlaskを用いたWebアプリケーション開発の始め方について、約5000語の詳細な記事を作成します。


Flask入門講座:Pythonで実践するWebアプリケーション開発の始め方

はじめに:Webの扉を開くFlask

今日のデジタル世界において、Webアプリケーションは私たちの生活に不可欠な存在となっています。オンラインショッピング、SNS、動画ストリーミングサービス、ビジネスツールまで、Webアプリケーションは多種多様なサービスを提供しています。これらのアプリケーションは、私たちが日々利用するブラウザを通じてアクセスされ、情報を表示し、ユーザーからの入力を受け付け、データのやり取りを行っています。

Webアプリケーション開発には様々な技術が用いられますが、Pythonはその中でも特に人気のある言語の一つです。Pythonは読みやすく、記述しやすいという特徴から、初心者からプロフェッショナルまで幅広い開発者に愛されています。そして、PythonでWebアプリケーションを開発するためのフレームワークとして、「Flask(フラスク)」と「Django(ジャンゴ)」の2大巨頭が存在します。

この記事では、数あるWebフレームワークの中から、特に軽量で柔軟性の高い「Flask」に焦点を当てて、その基本的な使い方から実践的なアプリケーション開発のステップまでを徹底的に解説していきます。

なぜFlaskを選ぶのか?

Flaskは「マイクロフレームワーク」と称されることがあります。これは、Djangoのように最初から多機能なツールが内蔵されている「フルスタックフレームワーク」とは異なり、必要最低限の機能のみを提供し、開発者が自由にコンポーネントを選択・組み合わせてアプリケーションを構築できることを意味します。

Flaskの主な特徴は以下の通りです。

  • 軽量で高速: 必要最小限の機能しか持たないため、オーバーヘッドが少なく、シンプルなアプリケーションであれば非常に高速に動作します。
  • 柔軟性が高い: 特定のデータベースやテンプレートエンジン、ORM (Object-Relational Mapping) に縛られることなく、開発者が自由に好きなツールを選んで統合できます。
  • 学習コストが低い: シンプルなAPIと直感的な設計により、Web開発初心者でも比較的容易に学習を始められます。
  • 拡張性が高い: 豊富な拡張機能(Extensions)が存在し、認証、データベース、フォーム処理など、特定の機能を追加したい場合に簡単に組み込むことができます。
  • 小規模から中規模のプロジェクトに最適: APIサーバー、シンプルなブログ、プロトタイプ開発など、幅広い用途で活用されています。

この記事では、Flaskの基本的な構造から始まり、HTMLテンプレート、フォーム処理、データベース連携、さらにはユーザー認証やデプロイといった実践的な内容まで、一歩ずつ丁寧に解説していきます。この講座を通じて、あなた自身のWebアプリケーションを開発するための確かな基礎を身につけることができるでしょう。

さあ、PythonとFlaskを使ったWebアプリケーション開発の世界へ飛び込みましょう!

Part 1: 開発環境のセットアップ

Webアプリケーション開発を始める前に、まずは適切な開発環境を整える必要があります。Pythonのインストールから、Flaskのインストール、そして開発を効率的に進めるための仮想環境の設定までを解説します。

1.1 Pythonのインストール

FlaskはPythonで動作するため、PythonがPCにインストールされていることが前提です。まだインストールしていない場合は、以下の手順でインストールしてください。

  • Windows:
    1. Python公式サイト(https://www.python.org/downloads/windows/)にアクセスします。
    2. 最新バージョンのインストーラー(通常は “Windows installer (64-bit)”)をダウンロードします。
    3. ダウンロードしたインストーラーを実行します。
    4. 重要: インストールウィザードの最初の画面で「Add Python X.Y to PATH」のチェックボックスを必ずオンにしてから「Install Now」をクリックしてください。これにより、コマンドプロンプトからPythonコマンドを使えるようになります。
  • macOS:
    macOSにはPythonがプリインストールされていることが多いですが、古いバージョンである可能性があります。最新バージョンを使用するために、Homebrew(パッケージマネージャー)を利用してインストールすることを強く推奨します。

    1. ターミナルを開き、Homebrewがインストールされているか確認します(brew --version)。インストールされていなければ、Homebrew公式サイト(https://brew.sh/index_ja)の指示に従ってインストールします。
    2. ターミナルで以下のコマンドを実行してPythonをインストールします。
      bash
      brew install python
  • Linux:
    多くのLinuxディストリビューションにはPythonがプリインストールされています。最新バージョンが必要な場合や、特定のディストリビューションでのインストール方法は、各ディストリビューションの公式ドキュメントを参照してください(例:Debian/Ubuntuでは sudo apt update && sudo apt install python3)。

1.2 Pythonとpipの確認

インストール後、コマンドプロンプト(Windows)またはターミナル(macOS/Linux)を開き、以下のコマンドを実行してPythonが正しくインストールされているか確認します。

“`bash
python –version

または

python3 –version
“`

バージョン情報(例: Python 3.9.7)が表示されれば成功です。
次に、Pythonのパッケージ管理ツールであるpipが利用できるか確認します。

“`bash
pip –version

または

pip3 –version
“`

こちらもバージョン情報が表示されればOKです。pipは、Flaskを含むPythonのライブラリをインストールするために使用します。

1.3 仮想環境の重要性(venv)と作成方法

Webアプリケーション開発において、仮想環境(Virtual Environment)の利用は必須とも言えるベストプラクティスです。仮想環境とは、プロジェクトごとに独立したPython実行環境を作成する仕組みです。

なぜ仮想環境が必要なのか?

  • 依存関係の競合回避: 複数のプロジェクトで異なるバージョンのライブラリを使用する場合、グローバルな環境にインストールするとバージョン競合が発生する可能性があります。仮想環境を使えば、プロジェクトごとに必要なライブラリのバージョンを管理できます。
  • プロジェクトの分離: 各プロジェクトが独立した環境を持つため、他のプロジェクトに影響を与えることなくライブラリのインストールやアンインストールを行えます。
  • クリーンな環境: プロジェクトに必要な最小限のライブラリだけをインストールするため、環境が肥大化するのを防ぎます。
  • デプロイの容易さ: 仮想環境内のライブラリリストを共有することで、他の開発者や本番サーバーでも同じ環境を容易に再現できます。

Python 3.3以降では、標準モジュールとしてvenvが提供されており、簡単に仮想環境を作成できます。

仮想環境の作成手順:

  1. プロジェクトディレクトリの作成:
    まず、今回のFlaskアプリケーションを開発するためのプロジェクトフォルダを作成します。任意の場所に作成してください。
    bash
    mkdir my-flask-app
    cd my-flask-app
  2. 仮想環境の作成:
    プロジェクトディレクトリ内で、以下のコマンドを実行して仮想環境を作成します。venvという名前は慣例的ですが、envなど好きな名前を付けることもできます。
    bash
    python -m venv venv

    このコマンドを実行すると、my-flask-appディレクトリ内にvenvという新しいディレクトリが作成されます。この中に独立したPython環境が構築されます。
  3. 仮想環境のアクティベート(有効化):
    仮想環境を作成しただけでは、まだ有効になっていません。以下のコマンドを実行して、作成した仮想環境をアクティベートします。

    • Windows (コマンドプロンプト):
      bash
      venv\Scripts\activate.bat
    • Windows (PowerShell):
      powershell
      .\venv\Scripts\Activate.ps1
    • macOS / Linux (Bash/Zsh):
      bash
      source venv/bin/activate

      アクティベートに成功すると、コマンドプロンプトやターミナルの行頭に(venv)のような仮想環境名が表示されます。これは、現在仮想環境が有効になっていることを示します。
      注意: ターミナルを閉じるか、deactivateコマンドを実行すると、仮想環境は非アクティブ化されます。再度開発を始める際には、必ず仮想環境をアクティベートし直してください。

1.4 Flaskのインストール

仮想環境がアクティベートされた状態で、pipを使ってFlaskをインストールします。

bash
pip install Flask

インストールが完了したら、以下のコマンドでFlaskが正しくインストールされたか確認できます。

bash
pip list

リストの中にFlaskとそのバージョンが表示されていれば成功です。

1.5 エディタの準備(VS Code推奨)

コーディングを効率的に行うために、適切なコードエディタを用意しましょう。ここでは、Python開発に非常に人気のあるVisual Studio Code(VS Code)を推奨します。

  1. VS Codeのインストール:
    VS Code公式サイト(https://code.visualstudio.com/)から、お使いのOSに合ったインストーラーをダウンロードし、インストールします。
  2. Python拡張機能のインストール:
    VS Codeを起動し、左側のアクティビティバーにある「拡張機能(Extensions)」アイコン(四角が重なったようなアイコン)をクリックします。
    検索バーに「Python」と入力し、Microsoftが提供しているPython拡張機能(ID: ms-python.python)をインストールします。
  3. インタープリターの選択:
    VS Codeでmy-flask-appフォルダを開きます(「ファイル」→「フォルダーを開く…」)。
    VS Codeの画面下部にあるステータスバーに、現在選択されているPythonインタープリターが表示されます。ここが仮想環境のインタープリターになっていない場合はクリックし、リストから仮想環境(例: Python 3.x.x ('venv': venv))を選択してください。これにより、VS Codeが仮想環境内のPythonとライブラリを使用するようになります。

これで、Flaskアプリケーションを開発するための準備が整いました。次のパートから、実際にFlaskコードを書き始めましょう。

Part 2: Flaskアプリケーションの基本

このパートでは、Flaskアプリケーションの最も基本的な構成要素を学びます。最小の「Hello, World!」アプリケーションから始め、ルーティング、テンプレートエンジンの利用、そして静的ファイルの扱い方までを順に解説します。

2.1 最小のFlaskアプリケーション

まずは、最もシンプルなFlaskアプリケーションを作成してみましょう。my-flask-appディレクトリ直下にapp.pyというファイルを作成し、以下のコードを記述します。

“`python

app.py

from flask import Flask

Flaskアプリケーションのインスタンスを作成

name はPythonの特殊変数で、現在のモジュールの名前を表す。

Flaskはこの名前を使って、アプリケーションのルートパスを特定したり、

テンプレートや静的ファイルの場所を推測したりする。

app = Flask(name)

ルート(URLパス)とビュー関数(そのパスにアクセスされたときに実行される関数)を関連付ける

@app.route(‘/’)
def hello_world():
“””
ルートURL (‘/’) にアクセスされたときに実行されるビュー関数。
“””
return ‘

Hello, World!

Welcome to Flask!

アプリケーションを実行する

if name == ‘main‘: は、このスクリプトが直接実行された場合にのみ

以下のコードが実行されることを意味する(importされた場合は実行されない)。

if name == ‘main‘:
# デバッグモードを有効にしてアプリケーションを実行
# デバッグモードでは、コードの変更が自動的に反映されたり、
# エラーが発生した際に詳細なデバッグ情報が表示されたりする。
app.run(debug=True)
“`

コード解説:

  1. from flask import Flask: Flaskクラスをflaskモジュールからインポートします。これがFlaskアプリケーションの核となるクラスです。
  2. app = Flask(__name__): Flaskクラスのインスタンスを作成し、appという変数に代入しています。__name__は、現在のモジュール名(この場合はapp.py)をFlaskに教えるために使われます。
  3. @app.route('/'): これは「デコレータ」と呼ばれるPythonの機能です。@app.route('/')は、その直下に定義された関数(この場合はhello_world)を、指定されたURLパス(ここではルートURLである/)と関連付けます。つまり、ブラウザからhttp://127.0.0.1:5000/にアクセスすると、hello_world関数が実行されます。
  4. def hello_world():: これが「ビュー関数」です。HTTPリクエストが対応するルートに到達すると、この関数が実行され、その戻り値がWebブラウザに表示されます。ここでは、シンプルなHTML文字列を返しています。
  5. if __name__ == '__main__':: このブロック内のコードは、app.pyファイルが直接実行された場合にのみ実行されます。他のファイルからインポートされた場合には実行されません。
  6. app.run(debug=True): Flaskアプリケーションを起動します。
    • debug=True: デバッグモードを有効にします。デバッグモードが有効な場合、以下の利点があります。
      • リローダー(自動リロード): コードを変更してファイルを保存すると、サーバーが自動的に再起動され、変更が即座に反映されます。
      • デバッグピン: アプリケーションでエラーが発生した場合、ブラウザに詳細なエラー情報と、対話型デバッガーが表示されます。これにより、問題の特定と解決が容易になります。本番環境ではセキュリティ上の理由からdebug=Falseにするべきです。

2.2 アプリケーションの実行

コマンドプロンプトまたはターミナルで、my-flask-appディレクトリに移動し、仮想環境をアクティベートした状態で、以下のコマンドを実行します。

bash
python app.py

実行すると、以下のような出力が表示されます。

* Serving Flask app 'app'
* Debug mode: on
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

この出力は、Flaskアプリケーションがローカルのhttp://127.0.0.1:5000(またはhttp://localhost:5000)で起動したことを示しています。ブラウザを開き、このURLにアクセスしてみてください。

<h1>Hello, World!</h1><p>Welcome to Flask!</p>と表示されれば成功です!

2.3 ルーティングの詳細

Flaskでは、@app.route()デコレータを使って、様々なURLパターンに対応するビュー関数を定義できます。

2.3.1 複数のルート

複数の異なるURLにアクセスしたときに、それぞれ異なるコンテンツを表示させることができます。

“`python

app.py (既存のコードに追加)

@app.route(‘/about’)
def about():
return ‘

About Us

This is the about page of our Flask app.

@app.route(‘/contact’)
def contact():
return ‘

Contact Us

Feel free to contact us at [email protected]


“`

サーバーを再起動(またはデバッグモードなら自動リロード)し、http://127.0.0.1:5000/abouthttp://127.0.0.1:5000/contact にアクセスしてみてください。それぞれのページが表示されるはずです。

2.3.2 可変URL(Variable Rules)

URLの一部を動的に変更したい場合があります。例えば、ユーザーIDごとにプロフィールページを表示するなどです。Flaskでは、可変URLを使ってこれを実現できます。

“`python

app.py (既存のコードに追加)

@app.route(‘/user/‘)
def show_user_profile(username):
# の部分は、ビュー関数の引数として渡される
return f’

User Profile

User: {username}

@app.route(‘/post/‘)
def show_post(post_id):
# のように型を指定することもできる
# int以外には string, float, path (スラッシュを含む), uuid がある
return f’

Post ID: {post_id}

This is post number {post_id}.


“`

  • http://127.0.0.1:5000/user/alice にアクセスすると「User: alice」
  • http://127.0.0.1:5000/user/bob にアクセスすると「User: bob」
  • http://127.0.0.1:5000/post/123 にアクセスすると「Post ID: 123」

のように表示されます。int:のように型コンバーターを指定することで、Flaskはその部分の値を指定された型に自動的に変換してくれます。もし型に合わない値がURLに含まれていた場合、404 Not Foundエラーが返されます。

2.3.3 HTTPメソッドの指定

Webアプリケーションでは、GET、POSTなどのHTTPメソッドを使ってクライアント(ブラウザなど)からのリクエストを処理します。@app.route()デコレータのmethods引数を使って、特定のHTTPメソッドにのみ応答するように指定できます。

“`python

app.py (既存のコードに追加)

from flask import request # requestオブジェクトをインポート

@app.route(‘/login’, methods=[‘GET’, ‘POST’])
def login():
if request.method == ‘POST’:
# POSTリクエストの場合の処理(例: フォームデータの取得)
username = request.form[‘username’]
password = request.form[‘password’]
return f’Login attempt with username: {username} and password: {password}’
else:
# GETリクエストの場合の処理(例: ログインフォームの表示)
return ”’




”’
“`

  • GETメソッドは、通常、情報を取得するために使用されます(例: ページを表示する)。
  • POSTメソッドは、通常、サーバーにデータを送信するために使用されます(例: フォームの送信、新しいリソースの作成)。

上記の例では、/loginパスに対してGETリクエストが来た場合はログインフォームを表示し、POSTリクエストが来た場合はフォームから送信されたデータを受け取って処理する、という挙動を定義しています。requestオブジェクトについては後述します。

2.4 テンプレートエンジン Jinja2

これまでの例では、Pythonコード内で直接HTML文字列を返していましたが、これは非常に非効率で、複雑なHTMLを扱うには向いていません。Flaskは、PythonでWebページを動的に生成するための強力なテンプレートエンジンであるJinja2を標準でサポートしています。

Jinja2を使用すると、HTMLファイルの中にPythonのロジック(変数、ループ、条件分岐など)を埋め込むことができます。

2.4.1 テンプレートファイルの作成

Jinja2テンプレートをFlaskで使用するには、テンプレートファイルをアプリケーションのルートディレクトリ(app.pyがある場所)にtemplatesという名前のフォルダを作成し、その中に配置する必要があります。

my-flask-appディレクトリ内にtemplatesフォルダを作成し、その中にindex.htmlファイルを作成します。

my-flask-app/
├── app.py
└── templates/
└── index.html

templates/index.htmlの内容:

“`html






{{ title }}

{{ greeting }}

{{ message }}

ユーザーリスト

{% if users %}

    {% for user in users %}

  • {{ user.name }} ({{ user.age }}歳)
  • {% endfor %}

{% else %}

ユーザーが見つかりません。

{% endif %}

現在の年: {{ current_year }}


“`

2.4.2 render_template の使い方

Flaskのビュー関数からJinja2テンプレートをレンダリングするには、flaskモジュールからrender_template関数をインポートして使用します。

app.pyを以下のように変更します。

“`python

app.py

from flask import Flask, render_template # render_templateをインポート

app = Flask(name)

@app.route(‘/’)
def home():
# テンプレートに渡すデータ(辞書形式)
data = {
‘title’: ‘Flask入門’,
‘greeting’: ‘こんにちは、Flask!’,
‘message’: ‘これはJinja2テンプレートからレンダリングされたページです。’,
‘users’: [
{‘name’: ‘Alice’, ‘age’: 30},
{‘name’: ‘Bob’, ‘age’: 24},
{‘name’: ‘Charlie’, ‘age’: 35}
],
‘current_year’: 2024 # 例として現在の年
}
# render_template(‘テンプレートファイル名’, 渡したい変数=値, …)
return render_template(‘index.html’, **data) # 辞書をアンパックして渡す

その他のルートは必要に応じて残すか削除してください

if name == ‘main‘:
app.run(debug=True)
“`

Jinja2の構文のポイント:

  • {{ variable }}: ビュー関数から渡された変数の値を表示します。
  • {% statement %}: 制御フロー(条件分岐やループ)を記述します。
    • {% if condition %}{% else %}{% endif %}: 条件分岐
    • {% for item in list %}{% endfor %}: ループ処理

サーバーを再起動し、http://127.0.0.1:5000/にアクセスすると、Jinja2テンプレートがレンダリングされた動的なページが表示されます。

2.4.3 テンプレートの継承

Webサイトでは、ヘッダー、フッター、ナビゲーションバーなど、複数のページで共通する部分が多くあります。Jinja2のテンプレート継承機能を使うと、これらの共通部分を効率的に管理できます。

  • 親テンプレート(ベーステンプレート)の作成:
    共通のレイアウトを定義するテンプレートを作成します。templates/base.html

    “`html
    <!DOCTYPE html>




    {% block title %}My Flask App{% endblock %}

    私の素晴らしいFlaskアプリ

    <main>
        {% block content %}
        <!-- 各子ページがここにコンテンツを挿入 -->
        {% endblock %}
    </main>
    
    <footer>
        <p>&copy; {{ current_year }} My Flask App. All rights reserved.</p>
    </footer>
    



    ``
    *
    {% block block_name %}...{% endblock %}: これは「ブロック」を定義します。子テンプレートは、このブロック内の内容を上書きできます。
    *
    {{ url_for(‘static’, filename=’css/style.css’) }}`: これは静的ファイルへのURLを生成するJinja2の組み込み関数で、後ほど解説します。

  • 子テンプレートの作成:
    親テンプレートを継承し、特定のブロックを上書きするテンプレートを作成します。templates/index.htmlを以下のように変更します。

    “`html
    {% extends “base.html” %} {# base.htmlを継承する #}

    {% block title %}ホーム – {{ super() }}{% endblock %} {# titleブロックを上書き #}

    {% block content %}

    {{ greeting }}

    {{ message }}

    <h2>ユーザーリスト</h2>
    {% if users %}
        <ul>
            {% for user in users %}
                <li>{{ user.name }} ({{ user.age }}歳)</li>
            {% endfor %}
        </ul>
    {% else %}
        <p>ユーザーが見つかりません。</p>
    {% endif %}
    

    {% endblock %}
    ``
    *
    {% extends “base.html” %}: このテンプレートがbase.htmlを継承することを示します。これはテンプレートファイルの**先頭**に記述する必要があります。
    *
    {% block content %}:base.htmlで定義されたcontentブロックを上書きし、このテンプレート独自のコンテンツを挿入します。
    *
    {{ super() }}: 親テンプレートのブロック内容を保持しつつ、追加のコンテンツを表示したい場合に使用します(例:{% block title %}ホーム – {{ super() }}{% endblock %}`では、「ホーム – My Flask App」と表示される)。

これで、index.htmlbase.htmlのレイアウトを使いつつ、独自のコンテンツを表示するようになります。app.pyhome関数でrender_template('index.html', ...)を呼び出すだけで、自動的にbase.htmlも考慮してレンダリングされます。

2.5 静的ファイル(CSS, JavaScript, 画像)

Webアプリケーションでは、HTMLテンプレートだけでなく、スタイルを適用するためのCSSファイル、動的な挙動を追加するためのJavaScriptファイル、そして画像などの静的ファイルも必要になります。

Flaskでは、これらの静的ファイルをアプリケーションのルートディレクトリにstaticという名前のフォルダを作成し、その中に配置する慣例があります。

my-flask-app/
├── app.py
├── templates/
│ ├── base.html
│ └── index.html
└── static/
├── css/
│ └── style.css
├── js/
│ └── script.js
└── images/
└── logo.png

例として、static/css/style.cssファイルを作成し、以下のCSSを記述してみましょう。

css
/* static/css/style.css */
body {
font-family: Arial, sans-serif;
margin: 20px;
background-color: #f4f4f4;
color: #333;
}
header {
background-color: #333;
color: #fff;
padding: 10px 0;
text-align: center;
}
header h1 a {
color: #fff;
text-decoration: none;
}
nav ul {
list-style: none;
padding: 0;
}
nav ul li {
display: inline;
margin: 0 10px;
}
nav ul li a {
color: #fff;
text-decoration: none;
}
main {
background-color: #fff;
padding: 20px;
margin-top: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
footer {
text-align: center;
margin-top: 30px;
padding: 10px;
color: #777;
font-size: 0.9em;
}

このCSSファイルをテンプレートから参照するには、Jinja2のurl_for関数を使います。url_for関数は、ビュー関数や静的ファイルへのURLを生成するために使用されます。

templates/base.html<head>セクションにあるCSSリンクをもう一度確認してください。

html
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

  • url_for('static', ...): これはFlaskに、staticフォルダ内のファイルへのURLを生成するように指示します。
  • filename='css/style.css': staticフォルダからの相対パスでファイル名を指定します。

サーバーを再起動し、再度http://127.0.0.1:5000/にアクセスすると、CSSが適用され、ページの見た目が変わっているはずです。

同様に、JavaScriptファイル(例: static/js/script.js)を読み込むには、HTMLの<body>タグの最後あたりに以下のように記述します。

“`html

“`

画像ファイル(例: static/images/logo.png)を表示するには、<img>タグのsrc属性に以下のように記述します。

html
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="ロゴ">

これで、Flaskアプリケーションの基本的な構造と、HTML、CSS、JavaScriptといったWebの基本要素の連携方法を学ぶことができました。

Part 3: フォームとユーザー入力の処理

Webアプリケーションの多くは、ユーザーからの入力を受け付け、それに基づいて何らかの処理を行う必要があります。ユーザー入力の最も一般的な方法の一つがHTMLフォームです。このパートでは、Flaskでフォームデータを受け取り、処理する方法を学びます。

3.1 HTMLフォームの作成

まず、ユーザーがデータを入力するためのHTMLフォームを作成します。templatesフォルダにform.htmlというファイルを作成しましょう。

“`html

{% extends “base.html” %}

{% block title %}フォーム入力 – {{ super() }}{% endblock %}

{% block content %}

お問い合わせフォーム

{% with messages = get_flashed_messages() %}
  {% if messages %}
    <ul class="flashes">
    {% for message in messages %}
      <li>{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<form method="POST" action="/submit">
    <label for="name">名前:</label><br>
    <input type="text" id="name" name="name" required><br><br>

    <label for="email">メールアドレス:</label><br>
    <input type="email" id="email" name="email" required><br><br>

    <label for="message">メッセージ:</label><br>
    <textarea id="message" name="message" rows="5" cols="40" required></textarea><br><br>

    <input type="submit" value="送信">
</form>

{% endblock %}
“`

フォームの要素:

  • <form method="POST" action="/submit">:
    • method="POST": フォームデータがHTTP POSTメソッドを使ってサーバーに送信されることを指定します。ユーザーの入力データはリクエストボディに格納され、URLには表示されません。情報を取得するだけのフォームでない限り、POSTメソッドを使用することが推奨されます。
    • action="/submit": フォームが送信されたときに、データがhttp://127.0.0.1:5000/submitというURLに送られることを指定します。
  • <input type="text" name="name">, <input type="email" name="email">, <textarea name="message">: これらの入力フィールドにはname属性が設定されています。このname属性の値が、Flask側でデータを受け取る際のキーとなります。

3.2 リクエストオブジェクト(request

Flaskでは、クライアントからのHTTPリクエストに関するすべての情報がflaskモジュールからインポートできるrequestオブジェクトに格納されています。

requestオブジェクトを使ってフォームデータを処理する方法を見てみましょう。app.pyに新しいルートとビュー関数を追加します。

“`python

app.py (既存のインポートに request を追加)

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

app = Flask(name)

flashメッセージを使うためにシークレットキーを設定(本番環境ではより複雑なキーに)

app.secret_key = ‘your_secret_key_here’

@app.route(‘/form’)
def show_form():
return render_template(‘form.html’)

@app.route(‘/submit’, methods=[‘POST’])
def submit_form():
if request.method == ‘POST’:
# request.form はPOSTリクエストのフォームデータをディクショナリのように扱える
name = request.form[‘name’]
email = request.form[‘email’]
message = request.form[‘message’]

    # ここでフォームデータを処理するロジック(例: データベースに保存、メール送信など)
    print(f"名前: {name}, メール: {email}, メッセージ: {message}") # デバッグ出力

    # 処理が完了したら、ユーザーを別のページにリダイレクトする
    # flashメッセージをセット
    flash('お問い合わせありがとうございます!')
    return redirect(url_for('show_form')) # フォームページにリダイレクト

# POST以外のメソッドでアクセスされた場合は、通常405 Method Not Allowedが返されるが、
# 例外処理として以下のようにすることもできる(今回はPOSTのみ受け付けるので不要)
# return "Invalid request method", 405

“`

コード解説:

  1. from flask import ..., request, redirect, url_for, flash: 必要な関数やオブジェクトをインポートします。
    • request: HTTPリクエストに関する情報を持つオブジェクト。
    • redirect: ユーザーを別のURLにリダイレクトするための関数。
    • url_for: 関数名からURLを生成するための関数(ハードコーディングを避ける)。
    • flash: ユーザーに一時的なメッセージを表示するための関数(セッションが必要)。
  2. app.secret_key = 'your_secret_key_here': flashメッセージなど、セッションを利用する機能を使うには、Flaskアプリケーションにシークレットキーを設定する必要があります。これは、セッションデータを暗号化・署名するために使われる、誰にも知られてはならない秘密の文字列です。
  3. @app.route('/submit', methods=['POST']): このルートはPOSTリクエストのみを受け付けます。
  4. if request.method == 'POST':: リクエストがPOSTメソッドであることを確認します。これは、GETとPOST両方を処理する同じビュー関数内で特に役立ちます。
  5. name = request.form['name']:
    • request.formは、POSTリクエストで送信されたフォームデータ(キーと値のペア)を格納するImmutableMultiDictオブジェクトです。辞書のようにキーを指定して値を取得できます。
    • もし指定したキー(例: 'name')が存在しない場合、KeyErrorが発生します。
    • キーが存在しない可能性のある場合は、request.form.get('key_name')を使用することをお勧めします。これはキーが存在しない場合にNoneを返します。
  6. return redirect(url_for('show_form')): フォーム処理後、ユーザーを別のページにリダイレクトしています。これはWeb開発のベストプラクティスである「POST/Redirect/GET (PRG) パターン」に従っています。
    • PRGパターン: フォームがPOST送信された後、ユーザーをGETリクエストで別のページにリダイレクトします。これにより、ユーザーがページをリロードした際にフォームが二重送信されるのを防ぐことができます。
    • url_for('show_form'): show_formという名前のビュー関数に対応するURL(この場合は/form)を生成します。関数名を使うことで、URLパスが変更されてもコードを修正する必要がなくなります。
  7. flash('お問い合わせありがとうございます!'): ユーザーに表示したいメッセージをflash関数で設定します。これらのメッセージはセッションに保存され、次のリクエストで取得できます。

http://127.0.0.1:5000/formにアクセスし、フォームにデータを入力して送信してみてください。入力したデータがサーバーのターミナルに表示され、フォームページにリダイレクトされ、上部に「お問い合わせありがとうございます!」というメッセージが表示されるはずです。

3.3 メッセージフラッシュ (flash)

flash関数でセットしたメッセージは、get_flashed_messages()関数を使ってテンプレート内で取得し表示します。これは、フォーム送信成功の通知、エラーメッセージ、警告などをユーザーに一時的に表示するのに非常に便利です。

templates/form.htmlの以下の部分が、flashメッセージを表示するためのものです。

html
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flashes">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}

  • get_flashed_messages(): Flaskが提供する関数で、セッションに保存されたflashメッセージのリストを取得します。一度取得されると、それらのメッセージはセッションから削除されます。
  • {% with ... %}: Jinja2の構文で、一時的な変数を定義します。
  • {% if messages %}: メッセージがある場合のみリストを表示します。
  • {% for message in messages %}: 各メッセージをリストアイテムとして表示します。

3.4 WTFormsとの連携 (より高度なフォーム処理)

シンプルなフォームであればrequest.formで十分ですが、フォームの数が増えたり、バリデーション(入力値の検証)や複雑な入力タイプを扱う場合には、WTFormsのような専用のライブラリを使用すると、開発が大幅に楽になります。

WTFormsは、フォームの定義、レンダリング、バリデーションを強力にサポートします。Flaskとの統合を容易にするために、通常はFlask-WTF拡張機能と組み合わせて使用します。

3.4.1 WTFormsおよびFlask-WTFのインストール

仮想環境がアクティベートされた状態でインストールします。

bash
pip install Flask-WTF

3.4.2 フォームクラスの定義

フォームをPythonのクラスとして定義します。これにより、どのフィールドが必要か、どのようなバリデーションルールが適用されるかを一元的に管理できます。app.pyと同じディレクトリにforms.pyという新しいファイルを作成しましょう。

“`python

forms.py

from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField, PasswordField
from wtforms.validators import DataRequired, Email, Length, EqualTo

class ContactForm(FlaskForm):
“””お問い合わせフォーム”””
name = StringField(‘お名前’, validators=[DataRequired(‘名前は必須です。’)])
email = StringField(‘メールアドレス’, validators=[DataRequired(‘メールアドレスは必須です。’), Email(‘有効なメールアドレスを入力してください。’)])
message = TextAreaField(‘メッセージ’, validators=[DataRequired(‘メッセージは必須です。’), Length(min=10, message=’メッセージは10文字以上で入力してください。’)])
submit = SubmitField(‘送信’)

class LoginForm(FlaskForm):
“””ログインフォーム”””
username = StringField(‘ユーザー名’, validators=[DataRequired(‘ユーザー名は必須です。’)])
password = PasswordField(‘パスワード’, validators=[DataRequired(‘パスワードは必須です。’)])
submit = SubmitField(‘ログイン’)

class RegistrationForm(FlaskForm):
“””ユーザー登録フォーム”””
username = StringField(‘ユーザー名’, validators=[DataRequired(‘ユーザー名は必須です。’), Length(min=4, max=25, message=’ユーザー名は4〜25文字で入力してください。’)])
email = StringField(‘メールアドレス’, validators=[DataRequired(‘メールアドレスは必須です。’), Email(‘有効なメールアドレスを入力してください。’)])
password = PasswordField(‘パスワード’, validators=[DataRequired(‘パスワードは必須です。’), Length(min=6, message=’パスワードは6文字以上で入力してください。’)])
confirm_password = PasswordField(‘パスワード(確認用)’, validators=[DataRequired(‘パスワード(確認用)は必須です。’), EqualTo(‘password’, message=’パスワードが一致しません。’)])
submit = SubmitField(‘登録’)
“`

解説:

  • FlaskFormを継承することで、CSRFトークン生成などのFlask-WTFの機能が自動的に有効になります。
  • wtforms.fieldsからフィールド型(StringField, TextAreaField, SubmitFieldなど)をインポートします。
  • wtforms.validatorsからバリデーター(DataRequired, Email, Lengthなど)をインポートします。
    • DataRequired: フィールドが空でないことを確認します。
    • Email: 有効なメールアドレス形式であることを確認します。
    • Length: 文字列の長さを検証します。
    • EqualTo: 2つのフィールドの値が等しいことを確認します(パスワード確認など)。
  • フィールドの第一引数は、テンプレートで表示されるラベルです。第二引数はバリデーターのリストです。
3.4.3 フォームのレンダリングとバリデーション

app.pyを更新し、forms.pyで定義したContactFormを使用するように変更します。

“`python

app.py (既存のインポートに ContactForm を追加)

from flask import Flask, render_template, request, redirect, url_for, flash
from forms import ContactForm # forms.pyからContactFormをインポート

app = Flask(name)
app.secret_key = ‘your_secret_key_here’ # シークレットキーは必須

@app.route(‘/contact_wtforms’, methods=[‘GET’, ‘POST’])
def contact_wtforms():
form = ContactForm() # フォームクラスのインスタンスを作成

# フォームが送信され、かつバリデーションが成功した場合
if form.validate_on_submit():
    name = form.name.data
    email = form.email.data
    message = form.message.data

    # ここでデータを処理(データベース保存など)
    print(f"WTForms 経由のデータ: 名前: {name}, メール: {email}, メッセージ: {message}")
    flash('お問い合わせが正常に送信されました!(WTForms)', 'success') # カテゴリ付きメッセージ
    return redirect(url_for('contact_wtforms'))

# GETリクエストの場合、またはPOSTでバリデーションに失敗した場合
# フォームとメッセージをテンプレートに渡す
return render_template('wtforms_contact.html', form=form)

“`

新しいテンプレートファイルtemplates/wtforms_contact.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}WTFormsお問い合わせ – {{ super() }}{% endblock %}

{% block content %}

お問い合わせフォーム(WTForms版)

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class="flashes">
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<form method="POST" action=""> {# action="" は現在のURLに送信することを意味する #}
    {{ form.csrf_token }} {# CSRF対策のトークンを自動で挿入 #}

    <p>
        {{ form.name.label }}<br>
        {{ form.name(size=30) }}
        {% if form.name.errors %}
            <ul class="errors">
            {% for error in form.name.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.email.label }}<br>
        {{ form.email(size=30) }}
        {% if form.email.errors %}
            <ul class="errors">
            {% for error in form.email.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.message.label }}<br>
        {{ form.message(rows=5, cols=40) }}
        {% if form.message.errors %}
            <ul class="errors">
            {% for error in form.message.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>{{ form.submit() }}</p>
</form>

<style>
    .errors { color: red; list-style-type: none; padding: 0; margin: 5px 0; }
    .flashes { list-style-type: none; padding: 0; }
    .flashes li { padding: 8px 15px; margin-bottom: 10px; border-radius: 4px; }
    .success { background-color: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
    .error { background-color: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
</style>

{% endblock %}
“`

WTFormsの強力な点:

  • form = ContactForm(): ビュー関数内でフォームクラスのインスタンスを作成します。
  • if form.validate_on_submit():: POSTリクエストであり、かつフォームのデータがすべてのバリデーションルールに合格した場合にTrueを返します。このメソッドが呼ばれると、request.formからデータが自動的に読み込まれて検証されます。
  • name = form.name.data: バリデーションが成功すると、form.フィールド名.dataでクリーンなデータ(検証済みのデータ)にアクセスできます。
  • {{ form.csrf_token }}: Flask-WTFが自動的にCSRF(クロスサイトリクエストフォージェリ)対策のトークンを生成し、フォームに隠しフィールドとして挿入してくれます。これはセキュリティ上非常に重要です。
  • {{ form.name.label }}: フィールドのラベルをレンダリングします。
  • {{ form.name(size=30) }}: フィールドの入力エレメントをレンダリングします。引数としてHTML属性を渡すことができます(例: size=30, class="my-input"など)。
  • {% if form.name.errors %}: バリデーションエラーがあった場合、form.フィールド名.errorsにエラーメッセージのリストが格納されます。これを表示することで、ユーザーにどの入力が間違っているかを伝えることができます。
  • get_flashed_messages(with_categories=true): flash関数に2つ目の引数(カテゴリ)を渡した場合、with_categories=trueを指定することで、メッセージとそのカテゴリ(例: 'success', 'error')をタプルで取得できます。これにより、CSSでメッセージの種類に応じてスタイルを変えることができます。

http://127.0.0.1:5000/contact_wtformsにアクセスして、WTFormsを使ったフォームを試してみてください。入力漏れや不正な形式のデータで送信すると、エラーメッセージが表示されるはずです。

Part 4: データベースの利用 (SQLiteとSQLAlchemy)

Webアプリケーションにとって、データの永続化は不可欠です。ユーザー情報、投稿記事、商品データなど、多くの情報を保存・管理するためにデータベースが利用されます。このパートでは、Flaskアプリケーションでデータベースを扱う方法を学びます。

Flaskは特定のデータベースに縛られていませんが、ここではPythonで手軽に利用できるファイルベースのデータベースSQLiteと、Pythonオブジェクトを使ってデータベース操作を行うORM(Object-Relational Mapping)ライブラリのSQLAlchemy、そしてFlaskとの連携を容易にするFlask-SQLAlchemy拡張機能を使用します。

4.1 データベースとは?

データベースは、構造化されたデータを効率的に保存、管理、検索するためのシステムです。リレーショナルデータベース(RDB)では、データは行と列からなるテーブル形式で整理され、テーブル間は関連付け(リレーション)によって結びつけられます。

  • SQLite: 小規模なアプリケーションや開発段階でよく使われる軽量なファイルベースのRDBです。データベース全体が単一のファイルとして保存され、サーバープロセスが不要なため、セットアップが非常に簡単です。
  • SQL(Structured Query Language): データベースを操作するための標準的な言語です。データの作成(CREATE)、読み取り(READ)、更新(UPDATE)、削除(DELETE)を行うための命令(CRUD操作)をSQLで記述します。

4.2 SQLAlchemyとは? ORM(Object-Relational Mapping)

SQLAlchemyは、Pythonオブジェクトを使ってデータベースを操作できるORMライブラリです。

ORMのメリット:

  • Pythonicな操作: SQLクエリを直接書く代わりに、Pythonのクラスとオブジェクトを使ってデータベースのテーブルやレコードを操作できます。これにより、SQLの知識がなくてもデータベース操作が可能になり、コードの可読性も向上します。
  • データベースの抽象化: データベースの種類(SQLite, PostgreSQL, MySQLなど)が変わっても、Pythonコードを大きく変更する必要がありません。SQLAlchemyが適切なSQLに変換してくれます。
  • 生産性の向上: 定型的なSQL文の記述が不要になるため、開発効率が向上します。

4.3 Flask-SQLAlchemyの導入

Flask-SQLAlchemyは、SQLAlchemyをFlaskアプリケーションに簡単に統合するための拡張機能です。

4.3.1 インストール

仮想環境がアクティベートされた状態でインストールします。

bash
pip install Flask-SQLAlchemy

4.3.2 設定

app.pyを更新し、データベースの設定を追加します。

“`python

app.py (既存のインポートに SQLAlchemy を追加)

from flask import Flask, render_template, request, redirect, url_for, flash
from forms import ContactForm # WTFormsのフォームを継続して使用
from flask_sqlalchemy import SQLAlchemy # SQLAlchemyをインポート
import os # ファイルパスを扱うためにosモジュールをインポート

ベースディレクトリのパスを取得

basedir = os.path.abspath(os.path.dirname(file))

app = Flask(name)
app.secret_key = ‘your_secret_key_here’ # セッションとflashメッセージに必須

データベース設定

SQLiteデータベースファイルのパスを指定

os.path.join(basedir, ‘app.db’) はプロジェクトルートに app.db を作成

app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///’ + os.path.join(basedir, ‘app.db’)

イベントの追跡を無効化(メモリを節約するため。通常はFalseでOK)

app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False

SQLAlchemyオブジェクトを初期化

db = SQLAlchemy(app)
“`

設定の解説:

  • basedir = os.path.abspath(os.path.dirname(__file__)): 現在のスクリプト(app.py)のディレクトリの絶対パスを取得しています。これにより、app.dbファイルがプロジェクトのルートディレクトリに作成されるようになります。
  • app.config['SQLALCHEMY_DATABASE_URI']: データベースへの接続URI(Uniform Resource Identifier)を設定します。
    • sqlite:///: SQLiteデータベースを使用することを示します。スラッシュが3つなのは、パスが絶対パスであることを意味します(Windowsではsqlite:///C:/path/to/app.dbのようになる)。
    • os.path.join(basedir, 'app.db'): プロジェクトルートディレクトリにapp.dbという名前のデータベースファイルを作成するパスを生成します。
  • app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False: SQLAlchemyがオブジェクト変更を追跡するシグナルシステムを有効にするかどうかを設定します。これは余分なメモリを消費するため、通常はFalseに設定します。
  • db = SQLAlchemy(app): Flaskアプリケーションインスタンスと連携するSQLAlchemyオブジェクトを作成します。このdbオブジェクトを通じて、データベースの操作を行います。
4.3.3 モデルの定義(クラスとカラム)

SQLAlchemyでは、データベースのテーブルをPythonのクラスとして定義します。このクラスを「モデル」と呼びます。モデルの各属性は、テーブルの列(カラム)に対応します。

app.pyUserモデルを追加してみましょう。

“`python

app.py (db = SQLAlchemy(app) の下に追記)

class User(db.Model):
“””ユーザーモデル”””
id = db.Column(db.Integer, primary_key=True) # 主キー、自動インクリメント
username = db.Column(db.String(80), unique=True, nullable=False) # ユニーク、NULLを許さない
email = db.Column(db.String(120), unique=True, nullable=False)

# オプション: モデルのインスタンスがプリントされたときに表示される文字列
def __repr__(self):
    return f'<User {self.username}>'

(既存のルートなどは省略)

“`

モデルの解説:

  • class User(db.Model):: db.Modelを継承することで、このクラスがSQLAlchemyのモデルであることを示します。
  • db.Column(): テーブルの列を定義します。
    • db.Integer: 整数型。
    • primary_key=True: その列がテーブルの主キーであることを示します。主キーはテーブル内の各行を一意に識別するために使われます。
    • db.String(80): 文字列型。引数で最大長を指定します。
    • unique=True: その列の値がテーブル内で一意でなければならないことを示します(重複を許さない)。
    • nullable=False: その列がNULL値(空値)を許さないことを示します。
  • __repr__(self): オブジェクトをプリントする際に表示される文字列表現を定義します。デバッグ時に便利です。
4.3.4 データベースの作成 (db.create_all())

モデルを定義しただけでは、まだデータベースファイル(app.db)やテーブルは作成されません。定義したモデルに基づいてデータベーススキーマを作成するには、db.create_all()メソッドを実行する必要があります。

app.pyif __name__ == '__main__':ブロック内で実行するのが一般的です。

“`python

app.py (app.run(debug=True) の前に追記)

if name == ‘main‘:
with app.app_context(): # アプリケーションコンテキストをアクティブにする
db.create_all() # 定義されたモデルに基づいてデータベースとテーブルを作成
app.run(debug=True)
“`

重要: db.create_all()は、Flaskのアプリケーションコンテキスト内で実行する必要があります。with app.app_context():を使うことで、明示的にコンテキストをアクティブにしています。これは、Flaskの拡張機能(Flask-SQLAlchemyも含む)がアプリケーションの設定や状態にアクセスするために必要です。

一度app.pyを実行すると、my-flask-appディレクトリ内にapp.dbファイルが作成されているはずです。これはSQLiteデータベースファイルです。

注意: db.create_all()は、テーブルがまだ存在しない場合にのみテーブルを作成します。すでに存在するテーブルは変更しません。モデルの定義を変更した際に既存のテーブル構造を更新するには、後述するFlask-Migrateのようなマイグレーションツールが必要になります。

4.4 CRUD操作 (Create, Read, Update, Delete)

データベースの基本操作であるCRUD(作成、読み取り、更新、削除)を、SQLAlchemyのモデルを使って行ってみましょう。

4.4.1 データの追加 (Create)

新しいユーザーをデータベースに追加するビュー関数を作成します。

“`python

app.py (既存のコードに追加)

@app.route(‘/add_user’, methods=[‘GET’, ‘POST’])
def add_user():
form = RegistrationForm() # ユーザー登録フォームを使用

if form.validate_on_submit():
    username = form.username.data
    email = form.email.data
    password = form.password.data # パスワードは後でハッシュ化するが、ここではシンプルに

    # 新しいUserオブジェクトを作成
    new_user = User(username=username, email=email)

    try:
        # データベースセッションに追加
        db.session.add(new_user)
        # 変更をコミット(データベースに永続化)
        db.session.commit()
        flash(f'ユーザー {username} が正常に登録されました!', 'success')
        return redirect(url_for('list_users')) # ユーザー一覧ページへリダイレクト
    except Exception as e:
        db.session.rollback() # エラーが発生した場合はロールバック
        flash(f'ユーザー登録に失敗しました: {e}', 'error')
        print(f"Error: {e}") # デバッグ用にエラーを出力
        # 通常はここでログに出力する

return render_template('register.html', form=form)

“`

templates/register.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}ユーザー登録 – {{ super() }}{% endblock %}

{% block content %}

ユーザー登録

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class="flashes">
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<form method="POST" action="">
    {{ form.csrf_token }}

    <p>
        {{ form.username.label }}<br>
        {{ form.username(size=30) }}
        {% if form.username.errors %}
            <ul class="errors">
            {% for error in form.username.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.email.label }}<br>
        {{ form.email(size=30) }}
        {% if form.email.errors %}
            <ul class="errors">
            {% for error in form.email.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.password.label }}<br>
        {{ form.password(size=30) }}
        {% if form.password.errors %}
            <ul class="errors">
            {% for error in form.password.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.confirm_password.label }}<br>
        {{ form.confirm_password(size=30) }}
        {% if form.confirm_password.errors %}
            <ul class="errors">
            {% for error in form.confirm_password.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>{{ form.submit() }}</p>
</form>

{% endblock %}
“`

解説:

  • new_user = User(username=username, email=email): Userモデルのインスタンスを作成します。これはまだデータベースには保存されません。
  • db.session.add(new_user): 新しいユーザーオブジェクトをデータベースセッションに追加します。セッションは、データベース操作の一連のトランザクションを管理します。
  • db.session.commit(): セッション内のすべての変更(この場合は新しいユーザーの追加)をデータベースに永続的に保存します。トランザクションが完了します。
  • db.session.rollback(): try...exceptブロック内でエラーが発生した場合、データベースの状態をcommit()前の状態に戻します。これにより、部分的なデータが保存されるのを防ぎます。

http://127.0.0.1:5000/add_userにアクセスし、ユーザーを登録してみてください。

4.4.2 データの取得 (Read)

データベースからデータを読み取るには、主にqueryオブジェクトを使用します。

“`python

app.py (既存のコードに追加)

@app.route(‘/users’)
def list_users():
# Userモデルのすべてのユーザーを取得
users = User.query.all()
# ユーザー名でソートして取得
# users = User.query.order_by(User.username).all()
return render_template(‘user_list.html’, users=users)

@app.route(‘/user_detail/‘)
def user_detail(user_id):
# 主キーで単一のユーザーを取得。見つからない場合は404エラー
user = User.query.get_or_404(user_id)
# または: user = User.query.filter_by(id=user_id).first()
# または: user = db.session.get(User, user_id) # SQLAlchemy 2.0 style
return render_template(‘user_detail.html’, user=user)
“`

templates/user_list.html

“`html

{% extends “base.html” %}

{% block title %}ユーザー一覧 – {{ super() }}{% endblock %}

{% block content %}

登録ユーザー一覧

新しいユーザーを登録

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class="flashes">
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

{% if users %}
    <table>
        <thead>
            <tr>
                <th>ID</th>
                <th>ユーザー名</th>
                <th>メールアドレス</th>
                <th>アクション</th>
            </tr>
        </thead>
        <tbody>
            {% for user in users %}
            <tr>
                <td>{{ user.id }}</td>
                <td><a href="{{ url_for('user_detail', user_id=user.id) }}">{{ user.username }}</a></td>
                <td>{{ user.email }}</td>
                <td>
                    <a href="{{ url_for('edit_user', user_id=user.id) }}">編集</a> |
                    <form action="{{ url_for('delete_user', user_id=user.id) }}" method="POST" style="display:inline;">
                        <input type="submit" value="削除" onclick="return confirm('本当に削除しますか?');">
                    </form>
                </td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
{% else %}
    <p>まだユーザーが登録されていません。</p>
{% endif %}

<style>
    table { width: 100%; border-collapse: collapse; margin-top: 20px; }
    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
    th { background-color: #f2f2f2; }
</style>

{% endblock %}
“`

templates/user_detail.html

“`html

{% extends “base.html” %}

{% block title %}ユーザー詳細 – {{ user.username }} – {{ super() }}{% endblock %}

{% block content %}

ユーザー詳細: {{ user.username }}

ID: {{ user.id }}

メールアドレス: {{ user.email }}

<p>
    <a href="{{ url_for('edit_user', user_id=user.id) }}">このユーザーを編集</a> |
    <a href="{{ url_for('list_users') }}">ユーザー一覧に戻る</a>
</p>

{% endblock %}
“`

データの取得方法:

  • User.query.all(): UserテーブルのすべてのレコードをUserオブジェクトのリストとして取得します。
  • User.query.get_or_404(user_id): 指定された主キーを持つレコードを1つ取得します。レコードが見つからない場合は、自動的に404 Not Foundエラーページを表示します。
  • User.query.filter_by(username='Alice').first(): 特定の条件に一致する最初のレコードを取得します。filter_byはキーワード引数で条件を指定します。first()は結果セットの最初の1件を、all()はすべてを取得します。
  • User.query.filter(User.age > 25).all(): より複雑な条件(比較演算子など)を指定する場合はfilter()を使用します。
4.4.3 データの更新 (Update)

既存のユーザー情報を更新するビュー関数を作成します。

“`python

app.py (既存のコードに追加)

@app.route(‘/edit_user/‘, methods=[‘GET’, ‘POST’])
def edit_user(user_id):
user = User.query.get_or_404(user_id) # 更新対象のユーザーを取得
form = RegistrationForm(obj=user) # フォームに既存のユーザーデータを事前に入力

if form.validate_on_submit():
    # フォームデータからユーザーオブジェクトの属性を更新
    user.username = form.username.data
    user.email = form.email.data
    # パスワードも更新する場合は、ここでハッシュ化してuser.password = hashed_passwordのように設定

    try:
        db.session.commit() # 変更をコミット
        flash(f'ユーザー {user.username} の情報が更新されました!', 'success')
        return redirect(url_for('user_detail', user_id=user.id))
    except Exception as e:
        db.session.rollback()
        flash(f'ユーザー情報の更新に失敗しました: {e}', 'error')
        print(f"Error: {e}")

return render_template('edit_user.html', form=form, user=user)

“`

templates/edit_user.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}ユーザー編集: {{ user.username }} – {{ super() }}{% endblock %}

{% block content %}

ユーザー編集: {{ user.username }}

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class="flashes">
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<form method="POST" action="">
    {{ form.csrf_token }}

    <p>
        {{ form.username.label }}<br>
        {{ form.username(size=30) }}
        {% if form.username.errors %}
            <ul class="errors">
            {% for error in form.username.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.email.label }}<br>
        {{ form.email(size=30) }}
        {% if form.email.errors %}
            <ul class="errors">
            {% for error in form.email.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    {# パスワードは編集時にも表示されるべきではないので、ここでは敢えて表示しない #}
    {# 必要であれば新しいパスワード入力フィールドを追加し、既存のパスワードはフォームに表示しない #}
    <p>{{ form.submit() }}</p>
</form>

{% endblock %}
“`

更新のポイント:

  • form = RegistrationForm(obj=user): WTFormsのフォームクラスのコンストラクタにobj引数としてSQLAlchemyのモデルインスタンスを渡すと、フォームフィールドに既存のデータが自動的に読み込まれます。
  • user.username = form.username.data: フォームから取得した新しい値で、データベースから取得したuserオブジェクトの属性を直接更新します。
  • db.session.commit(): オブジェクトの属性を変更した後にコミットすることで、その変更がデータベースに反映されます。db.session.add()は不要です(オブジェクトが既にセッションに存在するため)。
4.4.4 データの削除 (Delete)

ユーザーをデータベースから削除するビュー関数を作成します。

“`python

app.py (既存のコードに追加)

@app.route(‘/delete_user/‘, methods=[‘POST’])
def delete_user(user_id):
# 削除はPOSTリクエストでのみ許可する(セキュリティのため)
user = User.query.get_or_404(user_id) # 削除対象のユーザーを取得

try:
    db.session.delete(user) # データベースセッションからユーザーオブジェクトを削除
    db.session.commit() # 変更をコミット
    flash(f'ユーザー {user.username} が削除されました。', 'success')
except Exception as e:
    db.session.rollback()
    flash(f'ユーザーの削除に失敗しました: {e}', 'error')
    print(f"Error: {e}")

return redirect(url_for('list_users')) # ユーザー一覧ページへリダイレクト

“`

削除のポイント:

  • methods=['POST']: 削除操作はデータ変更を伴うため、GETリクエストではなくPOSTリクエストで行うべきです。これにより、悪意のあるリンククリックなどによる意図しない削除を防ぎます。
  • db.session.delete(user): データベースセッションから指定されたオブジェクトを削除対象としてマークします。
  • db.session.commit(): 削除操作をデータベースに永続化します。

これで、FlaskとFlask-SQLAlchemyを使ったデータベースのCRUD操作の基本を学びました。

Part 5: 認証システムの実装 (簡易版)

Webアプリケーションでは、多くの場合、ユーザーがアプリケーションにログインし、個人のデータにアクセスしたり、特定の機能を利用したりするための認証システムが必要です。このパートでは、ユーザー登録、ログイン、ログアウト、そしてアクセス制御の基本的な認証システムを実装します。

セキュリティを考慮し、パスワードはプレーンテキストではなくハッシュ化して保存します。

5.1 ユーザーモデルの拡張とパスワードのハッシュ化

Userモデルにパスワードを保存するためのカラムを追加し、パスワードのハッシュ化と検証のためのメソッドを追加します。Flaskの依存関係であるWerkzeugに含まれるセキュリティユーティリティを使用します。

“`python

app.py (Userモデルを修正)

from werkzeug.security import generate_password_hash, check_password_hash # これをインポート

from flask import Flask, render_template, request, redirect, url_for, flash
from forms import ContactForm, LoginForm, RegistrationForm # LoginForm, RegistrationFormをインポート
from flask_sqlalchemy import SQLAlchemy
import os
from werkzeug.security import generate_password_hash, check_password_hash # 追加

basedir = os.path.abspath(os.path.dirname(file))

app = Flask(name)
app.secret_key = ‘your_secret_key_here’
app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///’ + os.path.join(basedir, ‘app.db’)
app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False

db = SQLAlchemy(app)

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)
password_hash = db.Column(db.String(128), nullable=False) # パスワードハッシュ保存用

def set_password(self, password):
    """パスワードをハッシュ化して設定する"""
    self.password_hash = generate_password_hash(password)

def check_password(self, password):
    """入力されたパスワードがハッシュと一致するか検証する"""
    return check_password_hash(self.password_hash, password)

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

(既存のルートなどは省略)

if name == ‘main‘:
with app.app_context():
db.create_all() # テーブル構造が変わったので、既存のapp.dbを削除して再作成するか、マイグレーションツールを使用
app.run(debug=True)
“`

重要: Userモデルにpassword_hashカラムを追加したため、既存のapp.dbファイル(もしあれば)は現在のテーブル構造と一致しません。開発環境では、app.dbファイルを削除してからapp.pyを再実行し、新しいテーブル構造でデータベースを再作成するのが最も簡単な方法です。本番環境ではマイグレーションツール(Flask-Migrateなど)を使用します。

5.2 ユーザー登録機能

add_userルートを修正し、パスワードをハッシュ化して保存するようにします。フォームはforms.pyRegistrationFormを使用します。

“`python

app.py (add_user関数を修正)

@app.route(‘/register’, methods=[‘GET’, ‘POST’]) # add_userからregisterに変更
def register():
form = RegistrationForm()

if form.validate_on_submit():
    username = form.username.data
    email = form.email.data
    password = form.password.data

    # ユーザー名またはメールアドレスが既に存在するかチェック
    existing_user = User.query.filter((User.username == username) | (User.email == email)).first()
    if existing_user:
        flash('ユーザー名またはメールアドレスは既に登録されています。', 'error')
        return render_template('register.html', form=form)

    new_user = User(username=username, email=email)
    new_user.set_password(password) # パスワードをハッシュ化して設定

    try:
        db.session.add(new_user)
        db.session.commit()
        flash(f'ユーザー {username} が正常に登録されました!', 'success')
        return redirect(url_for('login')) # 登録後、ログインページへリダイレクト
    except Exception as e:
        db.session.rollback()
        flash(f'ユーザー登録に失敗しました: {e}', 'error')
        print(f"Error: {e}")

return render_template('register.html', form=form)

“`

5.3 ログイン機能

ユーザー名とパスワードを受け取り、データベースに照合してログイン処理を行います。ログイン状態を管理するために、Flaskの組み込みセッション(session)を使用します。

“`python

app.py (既存のインポートに session を追加)

from flask import Flask, render_template, request, redirect, url_for, flash, session # sessionをインポート

… (既存のコード) …

@app.route(‘/login’, methods=[‘GET’, ‘POST’])
def login():
form = LoginForm()

if form.validate_on_submit():
    username = form.username.data
    password = form.password.data

    user = User.query.filter_by(username=username).first()

    if user and user.check_password(password):
        # ログイン成功
        session['user_id'] = user.id # セッションにユーザーIDを保存
        session['username'] = user.username # セッションにユーザー名を保存(表示用)
        flash(f'ようこそ、{user.username}さん!', 'success')
        return redirect(url_for('home')) # ログイン後、ホーム画面へリダイレクト
    else:
        # ログイン失敗
        flash('ユーザー名またはパスワードが間違っています。', 'error')

return render_template('login.html', form=form)

“`

templates/login.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}ログイン – {{ super() }}{% endblock %}

{% block content %}

ログイン

{% with messages = get_flashed_messages(with_categories=true) %}
  {% if messages %}
    <ul class="flashes">
    {% for category, message in messages %}
      <li class="{{ category }}">{{ message }}</li>
    {% endfor %}
    </ul>
  {% endif %}
{% endwith %}

<form method="POST" action="">
    {{ form.csrf_token }}

    <p>
        {{ form.username.label }}<br>
        {{ form.username(size=30) }}
        {% if form.username.errors %}
            <ul class="errors">
            {% for error in form.username.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>
        {{ form.password.label }}<br>
        {{ form.password(size=30) }}
        {% if form.password.errors %}
            <ul class="errors">
            {% for error in form.password.errors %}
                <li>{{ error }}</li>
            {% endfor %}
            </ul>
        {% endif %}
    </p>
    <p>{{ form.submit() }}</p>
</form>
<p>アカウントをお持ちでない方は <a href="{{ url_for('register') }}">こちらから登録</a></p>

{% endblock %}
“`

セッション(session)の利用:

  • sessionは、クライアントごとにデータを保存するための特別な辞書のようなオブジェクトです。Flaskはデフォルトで、セッションデータをCookieに保存し、暗号化と署名を行います。シークレットキーが設定されていないと動作しません。
  • session['user_id'] = user.id: ログインに成功した場合、セッションにユーザーIDを保存します。これにより、後続のリクエストでユーザーがログインしているかどうか、どのユーザーであるかを識別できるようになります。
  • セッションデータは、クライアントがブラウザを閉じるまで、またはサーバーによってセッションが期限切れになるまで維持されます(通常はデフォルトで31日)。

5.4 ログアウト機能

ログアウトは、セッションからユーザー情報を削除することで実現します。

“`python

app.py (既存のコードに追加)

@app.route(‘/logout’)
def logout():
session.pop(‘user_id’, None) # user_idをセッションから削除
session.pop(‘username’, None) # usernameも削除(もし保存していれば)
flash(‘ログアウトしました。’, ‘info’)
return redirect(url_for(‘home’))
“`

  • session.pop('key', default_value): セッションから指定されたキーの値を削除します。キーが存在しない場合にエラーにならないように、デフォルト値を指定することもできます。

5.5 ログインが必要なページへのアクセス制御 (デコレータ)

特定のページへのアクセスをログイン済みのユーザーに限定するには、カスタムデコレータを作成するのが便利です。

“`python

app.py (既存のインポートに追加)

from functools import wraps # デコレータを作成するために必要

… (既存のコード) …

def login_required(f):
“””
ログインが必須なルートに適用するデコレータ。
ログインしていない場合はログインページにリダイレクトする。
“””
@wraps(f) # デコレータを使うための定型句
def decorated_function(args, kwargs):
if ‘user_id’ not in session:
flash(‘このページにアクセスするにはログインが必要です。’, ‘warning’)
return redirect(url_for(‘login’, next=request.url)) # ログイン後、元のページに戻るためにnextパラメータを渡す
return f(
args, **kwargs)
return decorated_function

ログインが必要なサンプルページ

@app.route(‘/profile’)
@login_required # このデコレータを適用
def profile():
user_id = session[‘user_id’]
username = session[‘username’] # セッションからユーザー名を取得
# もしユーザーオブジェクト全体が必要なら、データベースから取得する
# current_user = User.query.get(user_id)
return render_template(‘profile.html’, username=username, user_id=user_id)

@app.route(‘/admin’)
@login_required
def admin_panel():
# ユーザーが管理者かどうかをチェックするロジックをここに追加
# 例: if session[‘username’] != ‘admin’: abort(403)
flash(‘管理者パネルにアクセスしました。’, ‘info’)
return ‘

管理者パネル

管理者のみが閲覧できる情報です。


“`

templates/profile.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}プロフィール – {{ super() }}{% endblock %}

{% block content %}

{{ username }}さんのプロフィール

ようこそ、{{ username }}さん!

あなたのIDは {{ user_id }} です。

これはログイン済みのユーザーのみが閲覧できるページです。

ログアウト

{% endblock %}
“`

そして、templates/base.htmlのナビゲーションにログイン・登録・ログアウトへのリンクを追加し、ログイン状態に応じて表示を切り替えるようにすると、より使いやすくなります。

“`html

        <nav>
            <ul>
                <li><a href="/">ホーム</a></li>
                <li><a href="/about">私たちについて</a></li>
                <li><a href="/contact_wtforms">お問い合わせ</a></li>
                {% if 'user_id' in session %}
                    <li><a href="{{ url_for('profile') }}">プロフィール ({{ session['username'] }})</a></li>
                    <li><a href="{{ url_for('logout') }}">ログアウト</a></li>
                {% else %}
                    <li><a href="{{ url_for('login') }}">ログイン</a></li>
                    <li><a href="{{ url_for('register') }}">登録</a></li>
                {% endif %}
            </ul>
        </nav>

“`

これで、基本的なユーザー認証システムが完成しました。http://127.0.0.1:5000/registerでユーザーを登録し、http://127.0.0.1:5000/loginでログインしてみてください。ログインしていない状態で/profileにアクセスするとログインページにリダイレクトされることが確認できます。

Part 6: より高度なトピックとベストプラクティス

これまでに学んだ内容で基本的なWebアプリケーションを構築できますが、より大規模なアプリケーションや、より堅牢なアプリケーションを開発するためには、いくつかの高度な概念とベストプラクティスを知っておく必要があります。

6.1 ブループリント (Blueprint) による大規模アプリの分割

アプリケーションが成長するにつれて、すべてのルートやビュー関数を一つのapp.pyファイルに記述するのは非効率的になり、コードが読みにくく、管理しにくくなります。Flaskのブループリント(Blueprint)は、アプリケーションを複数の論理的なモジュールに分割するための機能です。

各ブループリントは、自身のルート、テンプレート、静的ファイルを持つ独立したミニアプリケーションのように機能します。

6.1.1 ブループリントの作成と登録

例えば、認証関連のルートをauthブループリントに、ブログ投稿関連のルートをblogブループリントにまとめることができます。

  1. ディレクトリ構造の変更:
    my-flask-app/
    ├── app.py # メインアプリケーションファイル
    ├── forms.py # フォーム定義
    ├── models.py # モデル定義 (app.pyから分離)
    ├── templates/
    │ ├── base.html
    │ ├── ... (その他の共通テンプレート)
    │ └── auth/ # 認証関連のテンプレート
    │ ├── login.html
    │ └── register.html
    ├── static/
    │ └── ...
    └── blueprints/ # ブループリントを格納するフォルダ
    ├── __init__.py # Pythonパッケージとして認識させるため
    ├── auth/
    │ ├── __init__.py # authブループリントモジュール
    │ └── routes.py # authブループリントのルート定義
    └── blog/
    ├── __init__.py # blogブループリントモジュール
    └── routes.py # blogブループリントのルート定義

  2. models.pyの作成:
    app.pyからUserモデル定義をmodels.pyに移動させます。
    “`python
    # models.py
    from flask_sqlalchemy import SQLAlchemy
    from werkzeug.security import generate_password_hash, check_password_hash

    db = SQLAlchemy() # ここではappを渡さず、init_appで後から関連付ける

    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)
    password_hash = db.Column(db.String(128), nullable=False)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
    
    def __repr__(self):
        return f'<User {self.username}>'
    

    “`

  3. blueprints/auth/routes.pyの作成:
    認証関連のルートをここに移動させます。
    “`python
    # blueprints/auth/routes.py
    from flask import Blueprint, render_template, redirect, url_for, flash, session, request
    from forms import LoginForm, RegistrationForm
    from models import db, User # dbとUserモデルをインポート
    from werkzeug.security import generate_password_hash, check_password_hash # 明示的にインポート

    ブループリントのインスタンスを作成

    auth_bp = Blueprint(‘auth’, name, template_folder=’templates’, static_folder=’static’) # templates, static フォルダをブループリント内に持つ場合

    ログイン必須デコレータ (ここでも定義可能、または共通ユーティリティからインポート)

    簡略化のため、ここではapp.pyのものをそのまま使用するが、本来は再定義またはインポート

    from your_app_utils import login_required (仮)

    @auth_bp.route(‘/register’, methods=[‘GET’, ‘POST’])
    def register():
    form = RegistrationForm()
    if form.validate_on_submit():
    username = form.username.data
    email = form.email.data
    password = form.password.data

        existing_user = User.query.filter((User.username == username) | (User.email == email)).first()
        if existing_user:
            flash('ユーザー名またはメールアドレスは既に登録されています。', 'error')
            return render_template('auth/register.html', form=form) # テンプレートパスはtemplates/auth/register.html
    
        new_user = User(username=username, email=email)
        new_user.set_password(password)
        try:
            db.session.add(new_user)
            db.session.commit()
            flash(f'ユーザー {username} が正常に登録されました!', 'success')
            return redirect(url_for('auth.login')) # ブループリントのルートには'auth.'プレフィックスが必要
        except Exception as e:
            db.session.rollback()
            flash(f'ユーザー登録に失敗しました: {e}', 'error')
    return render_template('auth/register.html', form=form)
    

    @auth_bp.route(‘/login’, methods=[‘GET’, ‘POST’])
    def login():
    form = LoginForm()
    if form.validate_on_submit():
    username = form.username.data
    password = form.password.data
    user = User.query.filter_by(username=username).first()
    if user and user.check_password(password):
    session[‘user_id’] = user.id
    session[‘username’] = user.username
    flash(f’ようこそ、{user.username}さん!’, ‘success’)
    next_page = request.args.get(‘next’) # nextパラメータがあれば、そのURLにリダイレクト
    return redirect(next_page or url_for(‘home’)) # homeはメインアプリのルート
    else:
    flash(‘ユーザー名またはパスワードが間違っています。’, ‘error’)
    return render_template(‘auth/login.html’, form=form)

    @auth_bp.route(‘/logout’)
    def logout():
    session.pop(‘user_id’, None)
    session.pop(‘username’, None)
    flash(‘ログアウトしました。’, ‘info’)
    return redirect(url_for(‘home’))
    ``
    *
    auth_bp = Blueprint(‘auth’, name, …):Blueprintのインスタンスを作成します。第一引数はブループリントの名前(URL生成時にプレフィックスとして使われる)、第二引数は現在のモジュール名です。template_folderを指定すると、そのブループリント専用のテンプレートフォルダを定義できます。
    *
    url_for(‘auth.login’): ブループリント内のルートを参照する場合、‘ブループリント名.ビュー関数名’`の形式で指定します。

  4. app.pyのメインアプリケーションの変更:
    ブループリントをインポートし、登録します。dbオブジェクトの初期化も変更します。

    “`python

    app.py (メインアプリケーションファイル)

    from flask import Flask, render_template, session, flash, redirect, url_for, request # requestを追加
    import os
    from models import db, User # dbとUserをmodels.pyからインポート
    from blueprints.auth.routes import auth_bp # authブループリントをインポート
    from functools import wraps # login_requiredデコレータ用

    basedir = os.path.abspath(os.path.dirname(file))

    def create_app():
    app = Flask(name)
    app.secret_key = ‘your_secret_key_here’
    app.config[‘SQLALCHEMY_DATABASE_URI’] = ‘sqlite:///’ + os.path.join(basedir, ‘app.db’)
    app.config[‘SQLALCHEMY_TRACK_MODIFICATIONS’] = False

    db.init_app(app) # アプリケーションインスタンスをdbオブジェクトに関連付ける
    
    # ブループリントを登録
    app.register_blueprint(auth_bp, url_prefix='/auth') # /auth から始まるURLパスにauth_bpのルートをマッピング
    
    # 他のブループリントも同様に登録
    # app.register_blueprint(blog_bp, url_prefix='/blog')
    
    return app
    

    ログイン必須デコレータ (app.pyに置くか、共通ユーティリティファイルに)

    def login_required(f):
    @wraps(f)
    def decorated_function(args, kwargs):
    if ‘user_id’ not in session:
    flash(‘このページにアクセスするにはログインが必要です。’, ‘warning’)
    # ログインページがauthブループリントにある場合
    return redirect(url_for(‘auth.login’, next=request.url))
    return f(
    args, **kwargs)
    return decorated_function

    メインアプリケーションのルート

    @app.route(‘/’)
    def home():
    # Jinja2テンプレートにセッションのユーザー名を渡す例
    return render_template(‘index.html’, username=session.get(‘username’))

    @app.route(‘/about’)
    def about():
    return render_template(‘about.html’) # 新しいabout.htmlテンプレートを作成

    @app.route(‘/contact_wtforms’, methods=[‘GET’, ‘POST’])
    def contact_wtforms():
    # WTFormsの例は省略、必要に応じてforms.pyからContactFormをインポートし、ここで実装
    return “Contact form page (WTForms example omitted for brevity after blueprint split)”

    @app.route(‘/profile’)
    @login_required
    def profile():
    user_id = session[‘user_id’]
    username = session[‘username’]
    return render_template(‘profile.html’, username=username, user_id=user_id)

    @app.route(‘/admin’)
    @login_required
    def admin_panel():
    # 例: adminユーザーのみアクセス可能にする
    if session.get(‘username’) != ‘admin’: # get()を使うとキーがない場合にNoneを返す
    flash(‘管理者権限が必要です。’, ‘error’)
    return redirect(url_for(‘home’))
    flash(‘管理者パネルにアクセスしました。’, ‘info’)
    return ‘

    管理者パネル

    管理者のみが閲覧できる情報です。

    if name == ‘main‘:
    app = create_app() # create_app関数を呼び出してアプリケーションインスタンスを取得
    with app.app_context():
    db.create_all()
    app.run(debug=True)
    “`

    • create_app()ファクトリ関数: アプリケーションインスタンスを作成する関数を定義するパターンは、複数の設定環境(開発、テスト、本番)を扱う場合や、テストを書く場合に非常に便利です。
    • db.init_app(app): models.pySQLAlchemy(app)と直接インスタンス化する代わりに、db = SQLAlchemy()としてから、後からdb.init_app(app)を呼び出すことで、アプリケーションインスタンスがまだ利用できない場合でもdbオブジェクトを作成できます。
    • app.register_blueprint(auth_bp, url_prefix='/auth'): auth_bpブループリントを登録します。url_prefix='/auth'を指定すると、auth_bp内のすべてのルートは/authから始まるようになります(例: /auth/register, /auth/login)。

このようにブループリントを使うことで、アプリケーションの論理的な部分を分離し、大規模なアプリケーションでもコードを整理しやすくなります。

6.2 エラーハンドリング

ユーザーが間違ったURLにアクセスしたり、サーバー側で予期せぬエラーが発生したりした場合に、適切なエラーページを表示することはユーザーエクスペリエンスとセキュリティの両面で重要です。

Flaskでは、@app.errorhandler()デコレータを使って特定のエラーコード(HTTPステータスコード)に対するカスタムエラーページを定義できます。

“`python

app.py (既存のコードに追加)

@app.errorhandler(404)
def page_not_found(error):
“””404 Not Found エラーハンドラ”””
# テンプレートをレンダリングし、404ステータスコードを返す
return render_template(‘404.html’), 404

@app.errorhandler(500)
def internal_server_error(error):
“””500 Internal Server Error エラーハンドラ”””
# エラーログの記録などをここで行う
return render_template(‘500.html’), 500
“`

templates/404.htmltemplates/500.htmlを作成します。

“`html

{% extends “base.html” %}

{% block title %}ページが見つかりません – {{ super() }}{% endblock %}

{% block content %}

404 – ページが見つかりません

お探しのページは見つかりませんでした。

ホームに戻る

{% endblock %}
“`

“`html

{% extends “base.html” %}

{% block title %}サーバーエラー – {{ super() }}{% endblock %}

{% block content %}

500 – 内部サーバーエラー

申し訳ありませんが、サーバーで問題が発生しました。

しばらくしてから再度お試しいただくか、管理者にお問い合わせください。

{% endblock %}
“`

これで、存在しないURLにアクセスしたり、コード内で意図的にエラーを発生させたりした場合(例: raise ValueError("テストエラー"))に、カスタムエラーページが表示されるようになります。

6.3 設定管理

アプリケーションの設定(データベースURI、シークレットキー、デバッグモードなど)を、コード本体から分離して管理することは、アプリケーションの柔軟性とセキュリティを高める上で重要です。

6.3.1 設定ファイルの分離 (config.py)

一般的な方法は、設定をPythonファイル(例: config.py)に記述し、それをロードすることです。

  1. config.pyの作成:
    “`python
    # config.py
    import os

    class Config:
    “””基本設定”””
    SECRET_KEY = os.environ.get(‘SECRET_KEY’) or ‘default_secret_key_for_dev’ # 環境変数から取得、なければデフォルト
    SQLALCHEMY_TRACK_MODIFICATIONS = False

    class DevelopmentConfig(Config):
    “””開発環境用設定”””
    DEBUG = True
    SQLALCHEMY_DATABASE_URI = ‘sqlite:///’ + os.path.join(os.path.abspath(os.path.dirname(file)), ‘app.db’)

    class ProductionConfig(Config):
    “””本番環境用設定”””
    DEBUG = False
    # 本番データベースのURIをここに追加(例: PostgreSQLなど)
    # SQLALCHEMY_DATABASE_URI = os.environ.get(‘DATABASE_URL’) or ‘postgresql://user:pass@host:port/db’
    # 他のログ設定、エラーレポート設定など
    “`
    * 環境ごとに継承を使って設定を整理できます。

  2. app.pyでの設定の読み込み:
    create_app()関数内で設定をロードするように変更します。

    “`python

    app.py の create_app() 関数を修正

    from config import DevelopmentConfig, ProductionConfig # configをインポート

    def create_app(config_class=DevelopmentConfig): # デフォルトは開発設定
    app = Flask(name)
    app.config.from_object(config_class) # Configオブジェクトから設定をロード

    db.init_app(app)
    
    # ブループリント登録など...
    app.register_blueprint(auth_bp, url_prefix='/auth')
    
    # エラーハンドラ登録
    @app.errorhandler(404)
    def page_not_found(error):
        return render_template('404.html'), 404
    
    @app.errorhandler(500)
    def internal_server_error(error):
        return render_template('500.html'), 500
    
    return app
    

    … (既存のルート定義など) …

    if name == ‘main‘:
    # 環境変数 FLASK_ENV に基づいて設定を切り替える例
    # 例: export FLASK_ENV=production (Linux/macOS) または $env:FLASK_ENV=”production” (Windows PowerShell)
    env = os.environ.get(‘FLASK_ENV’, ‘development’)
    if env == ‘production’:
    app = create_app(ProductionConfig)
    else:
    app = create_app(DevelopmentConfig)

    with app.app_context():
        db.create_all()
    app.run() # debug=True はDevelopmentConfigで設定済み
    

    ``
    *
    app.config.from_object(config_class): 指定されたオブジェクトからすべての設定をロードします。
    *
    os.environ.get(‘FLASK_ENV’, ‘development’): 環境変数FLASK_ENVの値に応じて、使用する設定クラスを切り替えることができます。本番環境ではFLASK_ENV=production`を設定して起動します。

これにより、アプリケーションコードを変更せずに、異なる環境設定を適用できるようになります。特にシークレットキーのような機密情報は、環境変数を通じて設定するようにしましょう。

6.4 テスト (簡単なユニットテストの紹介)

アプリケーションが正しく動作することを保証するために、テストを書くことは非常に重要です。Flaskアプリケーションのテストには、pytestが広く使われています。

6.4.1 Pytestの導入

bash
pip install pytest

6.4.2 テストクライアント

Flaskには、アプリケーションへの仮想リクエストを送信し、応答をテストするためのtest_client()が用意されています。

test_app.pyというテストファイルを作成します。

“`python

test_app.py

import pytest
from app import create_app, db # create_appとdbをインポート
from models import User # Userモデルをインポート

@pytest.fixture
def client():
# テスト用のアプリケーションインスタンスを作成
app = create_app(config_class=’config.TestConfig’) # テスト用のConfigクラスを作成すること
app.config[‘TESTING’] = True # テストモードを有効にする

# データベースの初期化とクリーンアップ
with app.app_context():
    db.create_all() # テスト用DBを作成
    yield app.test_client() # テストクライアントを返す
    db.session.remove()
    db.drop_all() # テスト用DBを削除

def test_home_page(client):
“””ホームページが正しくロードされるかテスト”””
response = client.get(‘/’)
assert response.status_code == 200
assert b”私の素晴らしいFlaskアプリ” in response.data # バイト文字列で比較

def test_register_new_user(client):
“””新規ユーザー登録が成功するかテスト”””
response = client.post(‘/auth/register’, data={
‘username’: ‘testuser’,
‘email’: ‘[email protected]’,
‘password’: ‘password123’,
‘confirm_password’: ‘password123’,
‘csrf_token’: ‘dummy’ # WTFormsのCSRFトークンはテストでは無効にすることも多いか、テスト用のトークンを生成
}, follow_redirects=True) # リダイレクトを追跡

assert response.status_code == 200 # リダイレクト後の最終ステータスコード
assert b"ユーザー testuser が正常に登録されました!" in response.data

# ユーザーがDBに存在するか確認
with client.application.app_context():
    user = User.query.filter_by(username='testuser').first()
    assert user is not None
    assert user.email == '[email protected]'

def test_login_user(client):
“””ユーザーログインが成功するかテスト”””
# 事前にユーザーを登録
with client.application.app_context():
new_user = User(username=’testuser_login’, email=’[email protected]’)
new_user.set_password(‘loginpassword’)
db.session.add(new_user)
db.session.commit()

response = client.post('/auth/login', data={
    'username': 'testuser_login',
    'password': 'loginpassword',
    'csrf_token': 'dummy'
}, follow_redirects=True)

assert response.status_code == 200
assert b"ようこそ、testuser_loginさん!" in response.data
assert b"プロフィール" in response.data # ログイン後に表示されるリンクなど

config.py に TestConfig を追加

class TestConfig(Config):
TESTING = True # これが重要
SQLALCHEMY_DATABASE_URI = ‘sqlite:///:memory:’ # インメモリデータベースを使用すると、テストごとにクリーンなDBが使える
WTF_CSRF_ENABLED = False # テスト中にCSRFトークンを無効にする(セキュリティに注意)
“`

  • @pytest.fixture: テストのセットアップ(準備)とティアダウン(後処理)を行うための関数を定義します。
  • app.config['TESTING'] = True: Flaskをテストモードで実行します。これにより、エラーのキャッチングなどが変更されます。
  • SQLALCHEMY_DATABASE_URI = 'sqlite:///:memory:': テスト中は、ディスクに書き込まれないインメモリデータベースを使用するのが一般的です。これにより、テスト実行後にデータベースが自動的にクリーンアップされ、テストの独立性が保たれます。
  • WTF_CSRF_ENABLED = False: テスト中にWTFormsのCSRFトークン検証を一時的に無効にすることで、テストコードでのトークン管理が不要になります(本番環境では絶対に無効にしない)。
  • client.get('/'), client.post('/'): テストクライアントを使って、指定されたURLにGETまたはPOSTリクエストを送信します。
  • response.status_code: HTTPステータスコードを確認します(200 OK, 302 Redirectなど)。
  • response.data: レスポンスのコンテンツ(HTMLなど)をバイト文字列として取得します。

テストを実行するには、プロジェクトルートで仮想環境をアクティベートし、pytestコマンドを実行します。

bash
pytest

6.5 セキュリティの考慮事項

Webアプリケーション開発では、セキュリティは常に最優先事項です。Flask自身は堅牢ですが、開発者が以下の点に注意する必要があります。

  • CSRF(クロスサイトリクエストフォージェリ)対策:
    • 既にFlask-WTFを使用していれば、{{ form.csrf_token }}が自動的にトークンを生成し、検証してくれるため、ほとんどのCSRF攻撃から保護されます。
    • WTFormsを使用しない場合でも、flask.gオブジェクトやカスタムデコレータなどを使って手動でトークンを生成し、フォームに含め、リクエストで検証する仕組みを導入する必要があります。
  • SQLインジェクション対策:
    • SQLAlchemyのようなORMを使用している限り、ほとんどのSQLインジェクション攻撃からは保護されます。ORMは、ユーザー入力を適切にエスケープしてSQLクエリを構築します。
    • しかし、生SQLを使用する際には、プレースホルダーやプリペアドステートメントを使って入力をバインドし、文字列結合でクエリを構築しないように絶対に注意してください。
  • XSS(クロスサイトスクリプティング)対策:
    • Jinja2テンプレートは、デフォルトで変数をHTMLエスケープします。これにより、ユーザーが入力した悪意のあるスクリプトがそのままHTMLとして実行されるのを防ぎます。
    • ただし、|safeフィルターを使って明示的にエスケープを無効にした場合は、そのコンテンツが信頼できるソースからのものであることを確認してください。
  • パスワードのハッシュ化:
    • パスワードは平文で保存してはなりません。werkzeug.securitygenerate_password_hash()のように、一方向ハッシュ関数(Bcrypt、PBKDF2など)とソルトを使ってハッシュ化して保存します。
  • セッション管理:
    • app.secret_keyは推測困難な長い文字列に設定し、決して公開しないでください。
    • セッションIDをCookieに保存する際、httponly=True(JavaScriptからのアクセスを禁止)やsecure=True(HTTPS接続のみで送信)を設定することを検討してください。
  • デバッグモードの無効化:
    • 本番環境では絶対にdebug=Trueにしないでください。デバッグモードは、攻撃者にサーバーの詳細なエラー情報やコードへのアクセスを許してしまう可能性があります。

6.6 拡張機能 (Extensions) の活用

Flaskの魅力の一つは、豊富な拡張機能エコシステムです。特定の機能を簡単にアプリケーションに組み込むことができます。

  • Flask-Login: より高度なユーザーセッション管理、ユーザーロード、ログイン状態の追跡、ロールベースのアクセス制御などを提供します。この記事の簡易認証よりも堅牢です。
  • Flask-Migrate: Alembicをベースとしたデータベースマイグレーションツールです。モデル定義の変更を検知し、既存のデータベーススキーマを安全に更新するためのスクリプトを生成します。
  • Flask-WTF: WTFormsとFlaskの統合を強化します(既に利用済み)。
  • Flask-Mail: メール送信機能をアプリケーションに追加します。
  • Flask-RESTful / Flask-RESTX: RESTful APIを構築するためのヘルパーを提供します。

これらの拡張機能は、pip installでインストールし、それぞれのドキュメントに従って設定・利用します。

Part 7: デプロイメント

開発環境でアプリケーションが動作するようになったら、インターネット上で公開するために「デプロイ」する必要があります。デプロイとは、開発したアプリケーションを本番サーバーに配置し、一般のユーザーがアクセスできるようにすることです。

開発環境と本番環境では、アプリケーションの実行方法や設定が大きく異なります。

7.1 デプロイの概念

  • 開発サーバー vs 本番サーバー:
    • app.run(debug=True)で起動するFlaskの組み込みサーバーは、開発用途に特化しており、パフォーマンス、スケーラビリティ、セキュリティの面で本番環境には不向きです。
    • 本番環境では、より堅牢で高性能なWSGIサーバー(Gunicorn, uWSGIなど)とWebサーバー(Nginx, Apacheなど)を組み合わせて使用します。
  • WSGI(Web Server Gateway Interface)サーバー:
    PythonのWebアプリケーションとWebサーバーの間で通信するための標準インターフェースです。FlaskアプリケーションはWSGI互換なので、GunicornやuWSGIといったWSGIサーバーを使って実行できます。
  • Webサーバー(Nginx, Apache):
    クライアントからのHTTPリクエストを受け付け、静的ファイル(CSS, JS, 画像)を直接配信したり、動的なリクエストをWSGIサーバーに転送したりする役割を担います。ロードバランシング、SSL終端、キャッシュなどの機能も提供します。

7.2 簡単なデプロイ手順の紹介 (Heroku, Renderなど)

クラウドプラットフォームを利用すると、サーバー管理の複雑さを軽減し、比較的簡単にWebアプリケーションをデプロイできます。ここでは、代表的なPaaS(Platform as a Service)であるHerokuやRenderを例に、基本的な手順を説明します。

デプロイに必要なファイル:

  1. requirements.txt: プロジェクトの依存関係(インストールされているPythonライブラリ)をリストアップしたファイルです。
    仮想環境がアクティベートされた状態で、以下のコマンドを実行して生成します。
    bash
    pip freeze > requirements.txt

    このファイルは、デプロイ先サーバーで必要なライブラリを自動的にインストールするために使用されます。

  2. Procfile: HerokuやRenderなどのPaaSが、アプリケーションをどのように起動するかを指示するためのファイルです(拡張子なし、大文字でP)。
    プロジェクトのルートディレクトリにProcfileを作成し、以下のように記述します。
    # Procfile
    web: gunicorn app:app

    • web:: Webプロセスを定義します。
    • gunicorn app:app: Gunicorn WSGIサーバーを使ってアプリケーションを起動します。
      • gunicorn: 起動するWSGIサーバー。
      • app: アプリケーションのエントリポイントとなるPythonモジュール名(app.py)。
      • app: Flaskアプリケーションインスタンスの変数名(app = Flask(__name__)app)。
    • Gunicornをインストールする必要があります: pip install gunicorn
  3. 環境変数の設定:

    • シークレットキー (SECRET_KEY) やデータベース接続情報 (SQLALCHEMY_DATABASE_URI) など、本番環境で異なる、または機密性の高い情報は、直接コードに埋め込まず、環境変数として設定します。
    • config.pyos.environ.get('SECRET_KEY')のように環境変数から読み込むように設定しておくと良いでしょう。
    • 各PaaSのダッシュボードやCLI(コマンドラインインターフェース)から環境変数を設定できます。
  4. データベースの準備:

    • SQLiteはファイルベースのため、Herokuのような一時的なファイルシステムでは永続化されません。
    • 本番環境では、PostgreSQLやMySQLなどの外部データベースサービスを利用することが一般的です。HerokuやRenderは、アドオンとしてPostgreSQLなどのデータベースを簡単にプロビジョニングできます。
    • SQLALCHEMY_DATABASE_URIをこれらの外部データベースの接続文字列に更新する必要があります。

Herokuを使ったデプロイの簡易手順(概要):

  1. Heroku CLIのインストール: Herokuの公式サイトからCLIツールをインストールします。
  2. Herokuにログイン: heroku loginコマンドを実行します。
  3. Gitリポジトリの初期化: プロジェクトディレクトリでgit initを実行し、コードをコミットします。
  4. Herokuアプリケーションの作成: heroku create <your-app-name>コマンドで新しいHerokuアプリを作成します。
  5. データベースアドオンの追加(例: PostgreSQL): heroku addons:create heroku-postgresql:hobby-dev
  6. Herokuにプッシュ: git push heroku main
  7. データベースのマイグレーション(初回のみ): heroku run python -c "from app import create_app, db; app = create_app(); with app.app_context(): db.create_all()"
  8. 環境変数の設定: heroku config:set SECRET_KEY='your_very_secret_key'

Renderを使ったデプロイの簡易手順(概要):

RenderもHerokuと同様にGitリポジトリからデプロイでき、似たような手順となります。

  1. Renderアカウントの作成とログイン
  2. Gitリポジトリ(GitHub/GitLab/Bitbucket)にコードをプッシュ
  3. Renderダッシュボードで新しいWeb Serviceを作成
    • リポジトリを接続
    • 環境(Python 3)、ビルドコマンド(pip install -r requirements.txt)、スタートコマンド(gunicorn app:app)を設定
    • 環境変数(SECRET_KEYなど)を設定
    • データベース(PostgreSQLなど)もRenderでホストし、接続情報を設定
  4. デプロイ

これらのPaaSは、CI/CD(継続的インテグレーション/継続的デリバリー)の機能も提供しており、Gitリポジトリにプッシュするたびに自動的にデプロイが行われるように設定することも可能です。

デプロイはWebアプリケーション開発の最終段階であり、多くの考慮事項がありますが、PaaSを利用することで学習コストを抑えながら本番環境を体験できます。

まとめ:Flaskの旅の終わりに、そして次なるステップへ

このFlask入門講座では、Pythonを使ったWebアプリケーション開発の基礎から、実践的な機能の実装、そしてデプロイの概念までを幅広く学びました。

  • Part 1: 開発環境のセットアップ
    • Python、pip、仮想環境(venv)、Flaskのインストール方法を習得しました。
  • Part 2: Flaskアプリケーションの基本
    • 最小のFlaskアプリの作成、ルーティング、Jinja2テンプレートによる動的コンテンツ生成、静的ファイルの扱い方を学びました。
  • Part 3: フォームとユーザー入力の処理
    • requestオブジェクトを使ったフォームデータの取得、リダイレクト、flashメッセージ、そしてWTFormsを使った堅牢なフォーム処理とバリデーションを学びました。
  • Part 4: データベースの利用 (SQLiteとSQLAlchemy)
    • SQLAlchemyを使ったORMの概念、モデル定義、そしてCRUD操作(データの作成、読み取り、更新、削除)を実践しました。
  • Part 5: 認証システムの実装 (簡易版)
    • パスワードのハッシュ化、ユーザー登録、ログイン、ログアウト、そしてセッションとデコレータによるアクセス制御の基本を実装しました。
  • Part 6: より高度なトピックとベストプラクティス
    • ブループリントによるアプリケーションのモジュール化、エラーハンドリング、設定管理、簡単なテスト、そしてセキュリティの重要性について触れました。
  • Part 7: デプロイメント
    • 開発環境と本番環境の違い、WSGIサーバーとWebサーバーの役割、そしてPaaSを使ったデプロイの概要を理解しました。

これらはWebアプリケーション開発の広大な世界におけるほんの入口に過ぎません。しかし、この講座で得た知識とスキルは、あなたがこれからより複雑で興味深いWebアプリケーションを構築するための強固な土台となるでしょう。

次のステップ

  1. 自分でプロジェクトを作成する:
    学んだことを活かして、アイデアを形にしてみましょう。シンプルなToDoリスト、ブログ、メモアプリ、ブックマーク管理ツールなど、何でも構いません。実際に手を動かすことが最も重要です。
  2. Flask拡張機能を深掘りする:
    Flask-Login、Flask-RESTful、Flask-Migrateなど、興味のある拡張機能の公式ドキュメントを読み、使い方をマスターしましょう。
  3. より高度な概念を学ぶ:
    • 非同期処理: Webソケット、リアルタイムアプリケーション
    • キャッシュ: アプリケーションのパフォーマンス向上
    • API開発: RESTful APIの設計と実装
    • Docker: アプリケーションのコンテナ化とデプロイの標準化
    • クラウドインフラストラクチャ: AWS、Google Cloud Platform、Azureなど、より詳細なクラウドサービス
  4. コミュニティに参加する:
    Stack Overflow、GitHub、PyConなどのカンファレンスを通じて、PythonやFlaskのコミュニティに参加し、他の開発者から学び、貢献しましょう。

Web開発の旅は長く、常に新しい技術やベストプラクティスが登場しますが、PythonとFlaskはあなたのアイデアを実現するための素晴らしいツールとなるはずです。この講座が、あなたのWeb開発の第一歩となることを願っています。

さあ、あなたの創造力をWebの世界で解き放ちましょう!

コメントする

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

上部へスクロール