
問い合わせフォームは、CMS本体にコードを継ぎ足すのではなく、独立したPythonパッケージとして追加できます。自作のDjango製CMS「KururuCMS」では、プラグインをwheelで配布し、管理画面では導入済みの機能を有効化する構成にしました。
今回の検証では、フォーム表示から問い合わせの保存、メール配送までのDocker E2Eが成功しました。前回見つかった配色、アニメーション停止、入力エラー表示、HTML IDの重複にも修正が入っています。
ただし、入力値の一時保存には複数タブなどでの課題が残っています。wheelの単独インストール検査にも改善が必要です。本記事では、動いた範囲と未解決の範囲を分けて説明します。
検証基準日:2026年9月10日。前回までに作ったDocker・PostgreSQL・Redis・nginxの構成を土台にします。本記事のKururuCMSは自作プロジェクトです。別製品の「django CMS」向けプラグインとの互換性を示すものではありません。
この記事の要点
Kururu Formsは、KururuCMSへ問い合わせフォームを追加する独立したPythonプラグインです。
パッケージのインストール、CMSによる読み込み許可、管理画面での有効化は別の操作です。
問い合わせとメール配送予定は同じDBトランザクションで保存し、メールは別ワーカーが送信します。
修正版のDocker E2Eでは、入力エラーの再表示、同一ページのフォームID一意性、Motionの停止を確認できました。
E2Eの成功はバグがない証明ではありません。入力値の一時保存とwheel検査の分離には追加修正が必要です。
実装と検証の根拠は、プラグインの送信処理と今回再実行したDocker E2Eです。
この記事で分かること
CMSとプラグインをどこで分離するのか。管理画面で何を設定すればフォームが表示されるのか。送信後、問い合わせとメールがどの順番で処理されるのか。この3点を順に確認します。
後半では、実際に発見した不具合と修正内容を扱います。HTTPの成功だけでは分からない、入力エラー時の使いやすさやテスト環境の落とし穴も確認します。
対象読者と前提環境
Djangoでフォームや管理画面を作ったことがあり、機能を再利用可能な部品として分離したい開発者が対象です。コマンド例はLinux、macOS、WindowsのWSL上のBashを想定します。
| 項目 | 本記事で扱う対象 |
|---|---|
| CMS | kurumonn/kururuDjangoCMS |
| 問い合わせプラグイン | kurumonn/kururuDjangoCMS_contact_forms |
| CMSの検証コミット | 609ad13a72e12c8d80d5c468de098b2a5dfb0a55 |
| E2Eで使ったプラグイン | daf2e36f3ef6f2ad334f2b9c6054dca1e58e70c6 |
| プラグイン専用CIで使ったコミット | 4660d00a39721b2ed81198f4f433a1a02f013c6a |
| CIのPython | 3.12.14 |
| 検証したDjango | 5.2.17 |
| プラグインの宣言バージョン | 0.2.2 |
| 連携に必要なCMS API | cms_plugins API v1 |
| ブラウザE2E | Docker Compose、PostgreSQL、Redis、nginx、Gunicorn、Playwright、検証用SMTP |
プラグインの後続コミット4660d00…は、CI定義と検証契約の更新です。実行コードはE2Eで使用したdaf2e36…と同じです。一方、検証したSHAは別なので、記録上も区別します。後続コミットの差分で確認できます。
READMEの対応CMSコミットには更新漏れがあるため、本記事では実際のCIで使われた組み合わせを基準にします。
表のバージョンは「今回検証した版」です。「現在の最新版」や「他のすべての版でも動く」という意味ではありません。また、修正前後でプラグインの宣言バージョンが同じなので、0.2.2という番号だけでは修正版を特定できません。コミットとwheelのSHA-256も記録します。
用語と全体像:受付窓口と配送係を分ける
問い合わせフォームを、会社の受付窓口にたとえてみます。
窓口の担当者が、来訪者の要件を記録します。担当部署への連絡は、別の係が行います。連絡先が一時的につながらなくても、受付記録をなくしてはいけません。
今回の構成も同じです。Webの処理は問い合わせの保存までを担当し、メール送信は別プロセスへ任せます。
| 用語 | たとえと正式な意味 |
|---|---|
| wheel | 組み立て済みの部品を収めた箱。Pythonパッケージをインストールするための配布形式です。 |
| entry point | 部品の接続先を書いた名札。パッケージが公開する機能を、他のプログラムが発見するためのメタデータです。 |
| 許可リスト | 導入を承認した部品の一覧。CMSが読み込むプラグインを、デプロイ時の設定で絞ります。 |
| Outbox | 発送予定を残す台帳。送るべきメールの情報と処理状態をDBに記録します。 |
| 冪等性 | 同じ受付番号を何度渡しても受付が増えない性質。今回の実装では署名トークン内のUUIDとDBの一意制約を使います。 |
| E2Eテスト | 受付から配送までを通すリハーサル。ブラウザ、Webサーバー、DB、ワーカーを組み合わせて確認します。 |
entry pointの仕様はPyPAの公式文書、DBトランザクションの動作はDjango公式文書に記載されています。
【導入時】
レビューしたソース
→ wheelをビルド
→ Dockerイメージへ組み込む
→ 許可したentry pointをCMSが読み込む
→ 管理画面で有効化
【フォーム送信時】
利用者のブラウザ
→ nginx / Django
→ CSRF・署名トークン・入力内容などを検証
├─ 入力エラー → 入力値を一時保存 → 元ページでエラーを再表示
└─ 正常 → 問い合わせ+Outboxを同じDB処理で保存
→ 受付完了を表示
別ワーカー
→ Outboxから配送予定を取得
→ 管理者通知を送信
→ 既定設定では通知成功後に自動返信を配送受付と配送を分けた結果、Web画面に「受け付けました」と表示された時点では、メールはまだ送信待ちの場合があります。この違いが、障害調査の出発点になります。実装の送信処理
何が起きたのか:送信できても使いにくいフォームが残っていた
前回の検査では、問い合わせが保存されることとは別に、画面上の問題が見つかりました。今回の変更は、その問題を修正したものです。
| 前回の問題 | 今回の修正 |
|---|---|
| テーマの本文色が共通CSSへ伝わらない | 変数名を--fgへ統一 |
| ボタン用のアクセント色をリンクにも使い、背景とのコントラストが不足する | ボタン背景の--accentとリンク文字の--linkを分離 |
| 「動きを減らす」設定でもMotionが動く | 停止ルールの詳細度を修正し、ブラウザの回帰テストを追加 |
| 入力エラー時、入力値と項目別エラーが戻らない | 一時保存した値でフォームを再構築し、エラーを表示 |
| 同じフォームを複数置くとHTML IDが重複する | フォームとページ内の配置番号から固有のIDを生成 |
テーマ側は配色・Motionの修正コミット、フォーム側は入力エラーとIDの修正コミットで変更内容を確認できます。
自分のサイトでも関係するケース
同じ問い合わせフォームを記事の上部と下部に置くサイトでは、HTML IDの重複が問題になります。メールアドレスの入力ミスが起きるサイトでは、入力値と項目別エラーの再表示が必要です。
また、背景色をテーマで変更するサイトでは、ボタンだけでなく本文、リンク、エラー文字、フォーカス表示まで確認対象になります。通常サイズ文字のコントラスト比4.5:1は、その判断基準の一つです。サイト全体のアクセシビリティ適合を、この比率だけで判断することはできません。W3Cのコントラスト解説
原因は、部品同士の約束と状態の受け渡しにあった
CSSの変数名、HTMLのID、POSTとGETの間で扱う入力値。どれも小さな実装ですが、部品間で約束がずれると、画面上の不具合になります。
例えば、フォーム送信後に別ページへ移動するリダイレクトを行うと、POSTを処理したフォームオブジェクトを、そのまま次のGETで使うことはできません。修正版では、入力値を一時的に保存し、戻ったページで再検証する方式を採っています。入力値の受け渡し処理
Djangoでは、入力値を結び付けたフォームをバインド済みフォームと呼びます。バインド済みフォームを使えば、入力値と検証エラーを一緒に表示できます。Django Forms API
確認方法:バージョン番号より先に、実際のコードを確認する
1. 検証するソースを固定する
目的: 修正前のコードと混在させず、本記事のE2Eと同じソースを取得します。
実行場所: 本番ディレクトリではない、新しい作業用ディレクトリです。
mkdir kururu-forms-lab
cd kururu-forms-lab
git clone https://github.com/kurumonn/kururuDjangoCMS.git cms
git clone https://github.com/kurumonn/kururuDjangoCMS_contact_forms.git forms
git -C cms checkout --detach 609ad13a72e12c8d80d5c468de098b2a5dfb0a55
git -C forms checkout --detach daf2e36f3ef6f2ad334f2b9c6054dca1e58e70c6
git -C cms rev-parse HEAD
git -C forms rev-parse HEAD正常例: 最後の2行が、それぞれ指定した40桁のコミットになります。
異常例: 別のコミット、取得失敗、reference is not a treeなどです。
判断: 取得に失敗した場合は先へ進まず、接続とリポジトリを確認します。既存の作業ディレクトリに強制チェックアウトしないでください。
この操作は、公開済みのコミットを調査用に固定します。本番へ古いコードを強制的に戻すための手順ではありません。
2. インストールする名前と有効化する名前を区別する
このプラグインには、用途の違う名前が登場します。
| 用途 | 値 |
|---|---|
| pipで扱う配布名 | kururucms-contact-forms |
| Pythonのパッケージ名 | contact_forms |
| entry pointのグループ | kururucms.plugins |
| 許可リストに書くentry point名 | contact_forms |
| CMSで有効化するプラグインキー | kururu_forms |
| 記事に配置するブロックの内部名 | kururu_forms.contact_form |
定義はpyproject.tomlとプラグイン登録処理にあります。KURURU_PLUGIN_PACKAGESには、配布名のkururucms-contact-formsではなく、**entry point名のcontact_forms**を指定します。
導入・対処方法:導入、有効化、画面修正を分ける
1. wheelをビルドし、検証用環境へインストールする
目的: 配布ファイルを作り、CMSと同じPython環境へ追加します。
実行場所: 前節のkururu-forms-labです。以下は開発・検証専用です。
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install --require-hashes -r forms/requirements-ci.lock
python -m pip install --require-hashes -r cms/requirements.lock
python -m build --wheel --no-isolation forms
python -m pip install --no-deps --force-reinstall \
forms/dist/kururucms_contact_forms-0.2.2-py3-none-any.whl
python -m pip check
python -m pip hash forms/dist/kururucms_contact_forms-0.2.2-py3-none-any.whl正常例: wheelが作成され、インストールが終了し、pip checkがNo broken requirements found.と表示します。ハッシュ値も出力されます。
異常例: ビルド依存の不足、ハッシュ不一致、依存関係の不整合です。
判断: 失敗を無視して起動しません。--no-depsは、先にCMS側の依存を導入した手順と組み合わせて使っています。
--force-reinstallは、この新しい検証用venv内で同一バージョンの古いファイルを残さないために付けています。本番の実行中コンテナで繰り返す運用にはしません。pip installの公式仕様
本番では、レビューしたwheelをCMSのplugin_wheels/へ配置します。実際にビルドしたファイルのSHA-256をplugin-requirements.lockへ記録し、Dockerイメージを再構築します。ソースのSHAとwheelのSHA-256は別の値です。プラグインの配布手順
2. CMSへ読み込みを許可する
目的: インストールしたプラグインをCMSへ認識させます。
実行場所: 同じBash、同じvenvです。続けてCMSのディレクトリへ移動します。
export KURURU_PLUGIN_PACKAGES=contact_forms
export KURURU_FORMS_IP_HASH_KEY="$(python -c 'import secrets; print(secrets.token_urlsafe(48))')"
cd cms
python manage.py migrate --settings=config.settings.local
python manage.py check --settings=config.settings.local正常例: contact_formsを含むマイグレーションが適用され、System checkがエラーなく終了します。
異常例: entry pointが見つからない、IPハッシュ用のキーが未設定、DBへ接続できない、必要なテーマCSSがない状態です。
判断: contact_formsがマイグレーション対象に入らない場合は、インストール先のvenvと許可リストを確認します。
このキーは、IPアドレスから照合用の値を作るための専用秘密値です。本番では秘密情報として永続管理し、起動のたびに作り直しません。上の生成例は使い捨ての開発環境用です。プラグインの設定説明
既存DBへ適用する場合は、事前にバックアップと復元確認が必要です。本番で新しいマイグレーションを生成するのではなく、レビュー済みのマイグレーションを適用します。
3. 管理画面でフォームを作り、記事へ配置する
目的: 検証用管理者を作成し、手動で設定画面を確認します。
実行場所: kururu-forms-lab/cmsです。
python manage.py createsuperuser --settings=config.settings.local
python manage.py runserver 127.0.0.1:8000 --settings=config.settings.local正常例: 管理者を作成でき、ローカルの開発サーバーへ接続できます。
異常例: 未適用マイグレーション、ポート使用中、認証設定によるログイン拒否です。
判断: 開発サーバーはローカル確認に限定します。公開用Webサーバーの代わりにはしません。
管理画面では、次の順で設定します。
| 順番 | 操作 | 確認すること |
|---|---|---|
| 1 | 「CMSプラグイン」でkururu_formsを有効化 | 導入したプラグインが一覧にある |
| 2 | 「問い合わせフォーム」で新規作成 | 通知先は、検証に使ってよいアドレスにする |
| 3 | 標準などのプリセットを選び、項目を確認 | 必須、種類、選択肢、最大文字数を確認する |
| 4 | フォームを有効化 | 1項目以上があり、アーカイブ状態でない |
| 5 | 記事エディターでフォームのブロックを追加 | 対象フォームを選択できる |
| 6 | 公開用の表示で確認 | 管理者として見えることと、一般利用者が送れることを分ける |
操作の実装はプラグイン登録処理、利用できるプリセットなどはREADMEで確認できます。
「インストールできた」「有効化した」「記事に配置した」は、それぞれ別の確認点です。 フォームが表示されない場合は、この順番を逆にたどると原因を絞れます。
4. 入力エラーを、該当する項目の近くへ戻す
修正版では、入力ミスがあった値を一時保存し、元ページでフォームを組み直します。テンプレートにも、項目別エラーとフォーム全体のエラーを表示する処理が追加されています。修正版テンプレート
{# 修正版テンプレートの該当部分 #}
{% if field.errors %}
<ul class="errorlist" id="{{ field.auto_id }}-errors">
{% for error in field.errors %}
<li class="error">{{ error }}</li>
{% endfor %}
</ul>
{% endif %}「送信できませんでした」だけでは、利用者はどこを直せばよいか分かりません。項目の近くに理由を表示し、問題のない入力値は再利用できることが重要です。
ただし、現在の一時保存は複数ページ・複数タブまで十分に分離されていません。後述の制約を修正するまでは、すべての入力値復元が安全に完了したとは評価しません。
5. HTML IDは配置ごとに変え、送信する項目名は維持する
修正版は、フォームの主キーとページ内の配置番号からIDを作ります。Djangoのauto_idへ、その値を渡す構成です。プラグインの描画処理
# 描画処理の抜粋。変数や関数の定義はリポジトリ側を参照。
instance = next_instance_id(request)
dom_id = f"kururu-form-{contact_form.pk}-{instance}"
submission_form = build_submission_form(
contact_form,
data=previous,
auto_id=f"{dom_id}-%s",
)例えば、同じフォームのメール項目でも、1個目と2個目で異なるHTML IDになります。一方、送信データの項目名はemailのままです。ラベルの関連付けを直すために、POSTのデータ構造まで変えずに済みます。Django Forms API
6. メール送信は、受付とは別に稼働させる
目的: DBへ保存された配送予定をワーカーで処理します。
実行場所: 別ターミナルのCMSディレクトリです。同じvenv、プラグイン設定、DB設定を使います。
python manage.py process_contact_mail_outbox \
--poll-seconds 5 --settings=config.settings.local正常例: 送信待ちの通知が処理され、既定設定では管理者通知の成功後に自動返信が処理されます。
異常例: 待ち行列が残り続ける、メールの失敗回数が増える、設定したDBとは別のDBを見ている状態です。
判断: 処理件数だけでなく、管理画面のメール送信結果と実際の検証用受信先を照合します。待機中に出力がないことだけでは異常と判断しません。
注意: 実際のSMTPバックエンドを設定していれば、実際のメールが送られます。テスト宛先と配送先を先に確認してください。外部メールを送らずに連携を試す場合は、次節の検証用SMTPを使うDocker E2Eを利用します。
本番相当のComposeには、Webとは別のワーカーと保存期限メンテナンスがあります。起動時はcontact-formsプロファイルも対象にします。Webだけが正常でも、配送処理が動いているとは限りません。READMEのワーカー運用
動作確認:成功画面だけではなく、DB・配送・ブラウザを確認する
今回、実際に確認した結果
| 検査 | 結果 | 確認方法 |
|---|---|---|
| CMS本体 | 412テスト成功、マイグレーション差分なし | 2026年9月10日の既存CIログを確認 |
| プラグイン | 54テスト成功、マイグレーション差分なし | 同日の既存CIログを確認 |
| Docker E2E | 成功 | 今回、ジョブを再実行してログを確認 |
| 入力値一時保存の境界値 | 別ページでの取り出し、上限超過後の古い値の残留などを再現 | 実ソースを使った関数単位の追加テスト |
| wheelの単独インストール | CIの成功だけでは証明できない | インストール省略のログと検証コードを照合 |
CMSの412件とプラグインの54件は、既存CIの実行記録を確認した数値です。今回の再実行に基づく結果はDocker E2Eです。
追加の関数テストでは、pending.pyは取得した実ソースを使用しました。ただし、リクエスト、セッション、フォーム項目一覧、QueryDictはテスト用の代替オブジェクトです。この結果を、Django全体のHTTPテストとは呼びません。
E2Eで確認した利用者の操作
フォームを2個置いたページで、IDが重複せず、ラベルが対応する入力欄を指すことを確認しています。入力ミスをしたときは、送信した側の配置に値とエラーが戻ることも確認しています。
また、同じ署名トークンを再送しても受付が1件に収束すること、ワーカーを止めた間はメールが送られないこと、ワーカー開始後に通知と自動返信が届くことを確認しています。保存期限のメンテナンスも検査対象です。
今回のログには、次の結果が記録されています。
playwright_phase=regression-form-identity passed=1
playwright_phase=regression-invalid-input passed=1
playwright_phase=regression-reduced-motion passed=1
e2e_state=enqueued submissions=1 notification=pending autoreply=0
e2e_state=delivered submissions=1 notification=1 autoreply=1
e2e_state=maintenance purged=1 status=succeeded
docker_e2e=passed journeys=3 regression_checks=3これは、対象のシナリオが成功した記録です。全ブラウザ、全端末、全入力、全11テーマの全画面を検査したという意味ではありません。E2Eログ
同じDocker E2Eを手元で実行する
目的: 検証用SMTPを含む一式で、CMSとプラグインの連携を再現します。
実行場所: 上の開発用DBを作ったディレクトリとは別の、検証専用のクリーンなCMSコピーです。CMSのSHAとプラグインwheelは前掲の組み合わせに固定します。以下は、隣のforms/dist/にwheelがある配置例です。
python e2e/prepare_plugin.py \
--wheel ../forms/dist/kururucms_contact_forms-0.2.2-py3-none-any.whl
python e2e/prepare_environment.py
python e2e/run.py正常例: 準備処理が終了し、最後にdocker_e2e=passedが出力されます。
異常例: 既存の.envがあるため準備を拒否される、Dockerへ接続できない、特定のブラウザ検査が失敗する状態です。
判断: 準備処理の拒否を避けるために既存の秘密設定を削除せず、新しい検証用コピーを使います。E2Eの実行手順
重要な注意: このスクリプトは検証用Composeプロジェクトのボリュームを削除します。本番DBや本番設定を接続しないでください。今回の終了ログには、backendネットワークが使用中という警告も残りました。機能試験の成功と、後片付けの完了は別に判断します。この版を再現する際は、使い捨てのVMなど独立したDocker環境を使い、終了後に残存リソースも確認してください。
監視で見るべき状態
目的: 送信失敗、長時間の配送待ち、保存期限処理の停滞を検知します。
実行場所: プラグインとDBが設定された稼働環境です。以下は構成済みのComposeの例です。
docker compose exec -T web python manage.py check_contact_forms_health正常例: contact_forms_health=okで終了コード0です。
異常例: 配送失敗や結果不明の配送、長時間の滞留、期限削除の成功記録不足などで非0になります。
判断: 初回起動時は、メンテナンスの成功記録がまだない場合もあります。単に終了コードを無視せず、検出理由を確認します。監視仕様
再発防止:画面の修正を、失敗するテストで守る
修正したコードを残すだけでは、将来の変更で同じ問題が戻る可能性があります。再発防止では、壊れた状態を検出できるテストを残します。
| 対象 | 追加・維持したい検査 |
|---|---|
| テーマ | 本文、リンク、エラー文字、フォーカスの色を、実際の背景と組み合わせて確認する |
| アニメーション | 管理者の有効・無効と、利用者のreduce有無を組み合わせる |
| 複数フォーム | IDの一意性だけでなく、ラベル操作後のフォーカス先も確認する |
| 入力エラー | 入力値、項目別エラー、複数選択、再表示後の破棄を確認する |
| 複数タブ | 同じフォーム・同じ配置番号でも、別ページの入力を取り出さない |
| 上限値 | 空状態からの超過だけでなく、古い値が保存済みの状態からも試す |
| 配布物 | ソースディレクトリがなくても、wheel内のコードとテンプレートだけで動く |
| E2E終了処理 | テスト成功後に、対象コンテナとネットワークが残らない |
表の後半には、今回見つかった未解決事項に対する提案も含みます。現時点で全項目を実装・実行済みという意味ではありません。
注意点・よくある誤解:修正後も残る3つの課題
1. 入力値の復元先を、ページやタブまで識別していない
現在の一時保存では、照合する情報がフォームIDとページ内の配置番号です。元のページを照合していません。また、セッション内の保存先は1枠です。pending.py
例えば、同じセッションでページAとページBを開き、どちらにも同じフォームを1個目として配置すると、B側がAの入力値を取り出せる条件になります。2つのタブが続けて保存すると、先の入力値が上書きされる条件もあります。追加の関数単位テストで再現しました。
改善案: 正規化したページ識別子、フォームID、安定した配置識別子に加え、表示または送信ごとの識別子を使います。復元用データはセッションへ結び付け、上限付きで分離します。本文をURLへ載せる方式にはしません。同じURLの複数タブも考えるため、ページURLを追加するだけでは十分ではありません。
この検査で確認したのは、同一セッション内の取り違えです。他の利用者のデータを読めることを確認したわけではありません。
2. 入力上限の処理で、古い値が残る場合がある
現在の処理は、保存済みの値がある状態で新しい入力が合計上限を超えると、保存をやめます。しかし、前の値は消しません。その結果、元ページに戻った際、今回の入力ではなく古い値が復元される条件があります。pending.py
また、1項目の文字列を先に20,000文字へ切り詰め、その後で合計を判定します。1項目だけの20,001文字は、保存拒否ではなく切り詰めになります。追加テストでも確認しました。
改善案: 保存しない入力、切り詰める入力、ユーザーへ表示する説明を仕様として決めます。超過を拒否する方針なら、加工前の長さを判定し、その送信に対応する古い復元状態も適切に破棄します。20,000文字と20,001文字の両方を回帰テストに含めます。
3. wheelの単独インストール検査が、ソースから隔離されていない
プラグインのCIには、新しいvenvへwheelをインストールして検査する工程があります。しかし、ジョブ全体のPYTHONPATHにはソースディレクトリが入っています。CI定義
実際のログでは、wheelのインストール工程に次の出力がありました。
kururucms-contact-forms is already installed with the same version as the provided wheel.その後の検査は成功していますが、検査プログラムは読み込み元がvenvのsite-packages内であることを検証していません。これでは、wheelではなくソースを確認して成功する余地が残ります。検査プログラム・該当CIログ
改善案: 新しいvenvを作り、ソースの影響を外してwheelを必ずインストールします。Pythonの-Iは、PYTHONPATHなどの環境変数や実行ディレクトリ由来の探索パスを除外するために利用できます。さらに、パッケージと配布メタデータがvenvのsite-packages配下にあることを検査します。Pythonの隔離モード
今回のDocker E2Eでは、Dockerイメージ内へwheelを実際にインストールしたログを別途確認できています。したがって、「wheelは動かない」という指摘ではありません。単独インストールを証明する検査の作り方が不十分という指摘です。E2Eログ
「30分で失効」と「30分後に物理削除」は違う
一時保存の有効期限は30分ですが、現実装は次に取り出そうとした時点で期限を確認して捨てます。30分が経過した瞬間に、保存先のデータが自動で消えることまでは保証しません。pending.py
また、Djangoのセッションは保存方式を変更できます。署名Cookie方式は暗号化ではなく、内容を利用者側で読めるため、問い合わせ本文の保存先を「必ずサーバー側」と説明するなら、セッションバックエンドも前提として固定する必要があります。Djangoのセッション仕様
セキュリティ検査の成功を「安全性の保証」にしない
確認したCIでは、Django deploy checkは設定した失敗基準を通過しています。ただしINFOは残っています。BanditもCMS側はLow 14件、Medium・Highは0件です。プラグイン側は各深刻度0件ですが、検出されなかったロジック不備まで存在しないとは言えません。CMS CI・プラグインCI
依存関係監査も、実行時点の対象依存と既知の情報に対する結果です。本番の秘密情報、権限、SMTP設定、個人情報の保持期間まで、自動的に正しくなるわけではありません。Djangoのデプロイチェックリスト
まとめ
Kururu Formsの連携では、コードの配布と管理画面での有効化を分離しました。問い合わせの受付とメール配送も分け、DBへ受付内容と配送予定を残す構成にしています。
今回再実行したDocker E2Eでは、問い合わせの保存と配送に加え、入力エラーの再表示、フォームIDの一意性、Motionの停止を確認できました。
一方、複数ページ・複数タブの入力値復元、上限超過後の状態、wheel検査の分離には追加修正が必要です。
筆者の考察として、プラグインの完成度は「インストールできるか」だけでは測れません。利用者が入力を間違えたとき、ワーカーが止まったとき、同じページを複数開いたときまで確認して、初めて運用時の問題を減らせます。
FAQ
両方のmainを取得すれば、今回の修正が入っていますか?
確認時点では、CMSのmainは609ad13…、プラグインのmainはdaf2e36…でした。今回の実装修正を含みます。将来mainが更新されても同じ結果を再現できるよう、本記事ではSHAを固定しています。
管理画面からプラグインをダウンロードできますか?
今回の構成では行いません。レビューしたwheelをデプロイ時に導入し、管理画面では導入済みプラグインを有効化します。README
問い合わせの受付完了が出たのに、メールが届きません。
受付はDB保存、メールはワーカーによる別処理です。ワーカーの稼働、メール送信結果、SMTP設定を確認します。今回のE2Eは検証用SMTPへの受け渡しを確認したものです。外部の実受信箱へ届くことまでは検証していません。送信処理・E2E手順
同じフォームを1ページへ2回置けますか?
今回の修正版では、2つの配置のHTML IDを分ける実装とE2Eがあります。ただし、別ページ・複数タブを含む入力値復元は別の問題で、追加対応が必要です。描画処理
問い合わせを二重送信しても、メールは絶対に重複しませんか?
絶対に重複しないとは言えません。同じ署名トークンのPOSTが1件の受付へ収束する仕組みと、外部SMTPへの配送の重複問題は別です。処理中の期限切れなどで配送結果が不明になった場合は、配送事業者の記録と照合して判断します。配送運用の説明
54件のテストが成功したのに、なぜ新しい問題が見つかるのですか?
テストは、用意した条件を検査します。今回の追加確認は、同じフォームを別ページでも使う場合や、古い入力が残った後に上限を超える場合です。単独の正常・異常入力だけでなく、操作の順序を変えたテストも必要です。一時保存の実装
参考情報
実装・実行記録
KururuCMSの検証対象コミット
URL: https://github.com/kurumonn/kururuDjangoCMS/commit/609ad13a72e12c8d80d5c468de098b2a5dfb0a55
Kururu Formsの入力エラー・ID修正
URL: https://github.com/kurumonn/kururuDjangoCMS_contact_forms/commit/daf2e36f3ef6f2ad334f2b9c6054dca1e58e70c6
プラグインCIの検証対象更新
URL: https://github.com/kurumonn/kururuDjangoCMS_contact_forms/commit/4660d00a39721b2ed81198f4f433a1a02f013c6a
CMSの412テストを確認したCI
URL: https://github.com/kurumonn/kururuDjangoCMS/actions/runs/34448766652/job/102779370188
プラグインの54テストとwheel検査を確認したCI
URL: https://github.com/kurumonn/kururuDjangoCMS_contact_forms/actions/runs/34438846012/job/102749447334
今回再実行したDocker E2E
URL: https://github.com/kurumonn/kururuDjangoCMS/actions/runs/34438785615/job/102781135699
入力値一時保存の実装
URL: https://github.com/kurumonn/kururuDjangoCMS_contact_forms/blob/daf2e36f3ef6f2ad334f2b9c6054dca1e58e70c6/contact_forms/pending.py
公式仕様
PyPA:Entry points specification
URL: https://packaging.python.org/en/latest/specifications/entry-points/
Django:Forms API
URL: https://docs.djangoproject.com/en/5.2/ref/forms/api/
Django:Database transactions
URL: https://docs.djangoproject.com/en/5.2/topics/db/transactions/
Django:How to use sessions
URL: https://docs.djangoproject.com/en/5.2/topics/http/sessions/
Django:Deployment checklist
URL: https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
Python:隔離モード -I
URL: https://docs.python.org/3.12/using/cmdline.html#cmdoption-I
pip:installコマンド
URL: https://pip.pypa.io/en/stable/cli/pip_install/
W3C:Understanding Contrast (Minimum)
URL: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
参考情報
PyPA:Entry points specification
URL: https://packaging.python.org/en/latest/specifications/entry-points/
Django:Forms API
URL: https://docs.djangoproject.com/en/5.2/ref/forms/api/
Django:Database transactions
URL: https://docs.djangoproject.com/en/5.2/topics/db/transactions/
Django:How to use sessions
URL: https://docs.djangoproject.com/en/5.2/topics/http/sessions/
Django:Deployment checklist
URL: https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
Python:隔離モード -I
URL: https://docs.python.org/3.12/using/cmdline.html#cmdoption-I
pip:installコマンド
URL: https://pip.pypa.io/en/stable/cli/pip_install/
W3C:Understanding Contrast (Minimum)
URL: https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html
コメント(0件)
まだコメントはありません。最初のコメントを投稿してください!