ac-django について
ac-djangoは、Djangoプロジェクト向けのスキルで、DRFのガイドも任意で利用できます。バージョンに応じた推奨事項と、モデル、マイグレーション、ビュー、テスト、インフラの詳細な資料を備え、ac-pythonのルールを土台にしています。
主な機能
- 01Django 6.1のガイドとDjango 5.2 LTSの注記
- 02任意で使えるDRFの実践ガイド
- 03開発テーマ別のリファレンス
- 04モデル、マイグレーション、クエリ設計の指針
- 05Django向けテストとツールの規約
活用シーン
- 01モデルとデータベースマイグレーションの設計
- 02DRFシリアライザーとクエリ性能のレビュー
- 03ビュー、フォーム、バックグラウンドタスクの実装
- 04Djangoのテスト作成とツール設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ souliane/ skills --skill ac-djangoインストール後、Claude Code で/ac-djangoを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/admin-and-drf.mdantipatterns.mdbackground-and-infra.mdmodels-and-schema.mdsquashing-migrations.mdtesting-and-tooling.mdtransactions-and-migrations.mdtroubleshooting.mdviews-and-templates.mdrules/astgrep_scan.pyno-complexity-suppressions.ymlno-pytest-django-db.ymlpyproject_complexity.pytestcase-no-pytest-parametrize.yml詳細
Django Bible(Django 6 · DRF は任意)
対象: Python 3.14 での Django 6.1 · 前の系列: Django 5.2 LTS(末尾のセクション) · API: 選択した場合は DRF
バージョンポリシー
このスキルでは、Django の系列を1つだけ、現行のものとして記載します。更新時もこの方針を維持してください。
- メインの内容 = 現行の機能リリースです。 現時点では Django 6.1 です。より新しい機能リリースが登場したら、同じ編集でメインの内容をそのリリースに切り替えます。
- 特に注記のない文章は、現行系列の最初のリリース(Django 6.0)を指します。その系列で 後から 追加されたものには、該当する API または機能の直後に
(6.1+)マーカーを付けます。例:`QuerySet.fetch_mode()` (6.1+)。表記はこれだけにしてください。「Django X.Y で追加」「X.Y で新登場」「X.Y 以降」のような表現や、### Django X.Y note見出しは使わないでください。マーカーだけで表記し、ほかの表記と併用しない場合にのみ役立ちます。 - 以前の系列はそれぞれ1つのセクションにまとめ、
SKILL.mdの末尾にだけ記載します。 リファレンスファイルには現行系列のみを記載し、それぞれの末尾にセクションを追加することはありません。現時点では## Previous line: Django 5.2 LTSです。このセクションには、そのバージョンを使う読者に必要な差分、つまり存在しない機能、代わりに使うもの、アップグレード時に変更するものを記載します。メインの内容の途中にバージョン注記を挟むのは不備です。末尾に移動してください。 - 更新時の調査順序。 まずリリースノートと公式ドキュメントを確認します(
docs.djangoproject.com/en/<version>/releases/<version>/およびそこからリンクされているトピックページ)。次に、Canonical Sources にすでに引用されている著者を確認します。ブログやニュースレターは最後に、公式ドキュメントで扱われていない内容を簡潔に確認します。 - 切り替え。 Django の次の系列がリリースされたら、現在の末尾セクションを廃止される系列のものに置き換え、注記のない文章を新系列の最初のリリースに合わせ、そこより前のリリースを示すマーカーを削除します。
A.B方式での最後のリリースは Django 6.2 です。それ以降の機能リリースでは、毎年1月にYYYY形式を使用します。その場合、「現行系列」は最新のYYYYリリースを指し、同じルールがそのまま適用されます。
権威ある情報源
- Django ドキュメントのインデックス:https://docs.djangoproject.com/en/6.1/
- Django 6.1 リリースノート:https://docs.djangoproject.com/en/6.1/releases/6.1/
- Django 6.0 リリースノート — このスキルで明記されていない内容の前提:https://docs.djangoproject.com/en/6.0/releases/6.0/
- Django ドキュメント — フェッチモード:https://docs.djangoproject.com/en/6.1/topics/db/fetch-modes/
- Django ドキュメント — メールをメーラーに移行する:https://docs.djangoproject.com/en/6.1/topics/email/#migrating-email-to-mailers
- Django ドキュメント — マイグレーションの統合:https://docs.djangoproject.com/en/6.1/topics/migrations/#migration-squashing
- Adam Johnson —
django-upgrade:https://adamj.eu/tech/2021/09/16/introducing-django-upgrade/ - Adam Johnson —
django-linear-migrations:https://adamj.eu/tech/2020/12/10/introducing-django-linear-migrations/ - Adam Johnson — 保留中のマイグレーションをテストする:https://adamj.eu/tech/2024/06/23/django-test-pending-migrations/
- Adam Johnson — マイグレーションなしで変更できるモデルフィールドの選択肢:https://adamj.eu/tech/2025/05/03/django-choices-change-without-migration/
- Haki Benita — 「外部キーをひどく間違える方法」:https://hakibenita.com/django-foreign-keys
- Haki Benita — 信頼性の高い Django シグナル(django-tasks-db の本番環境向けパターン):https://hakibenita.com/django-reliable-signals
- James Bennett — Fat Model/「サービスレイヤーなし」(デフォルト)と、巨大メソッドの分割に関する続編:https://www.b-list.org/weblog/2020/mar/16/no-service/ · https://www.b-list.org/weblog/2020/mar/23/still-no-service/
- James Bennett — 「Litestar は一見の価値がある」(Django ではサービスレイヤーを設けない方針を再確認しつつ、より意見の少ないフレームワークでは採用を認めています):https://www.b-list.org/weblog/2025/aug/06/litestar/
- DabApps — モデルのカプセル化(外部からフィールドや
save()を決して書き換えない):https://www.dabapps.com/insights/django-models-and-encapsulation/ - HackSoft Django Styleguide — サービスレイヤー/
selectors.pyを採用する派閥(選択肢の1つであり、このスキルのデフォルトではありません):https://github.com/HackSoftware/Django-Styleguide - DRF ドキュメント:https://www.django-rest-framework.org/
- Factory Boy のベストプラクティス:https://github.com/camilamaia/factory-boy-best-practices
参照ファイル(必要に応じて読み込んでください)
| ファイル | 内容 | 読み込むタイミング |
|---|---|---|
references/models-and-schema.md | モデル、フィールド、制約、QuerySet、マネージャー、ORM のパフォーマンス | モデルの変更、クエリの最適化、スキーマ設計 |
references/transactions-and-migrations.md | トランザクション、ロック、冪等性、マイグレーションの安全性、外部キーのインデックス操作 | 複数段階の書き込み、マイグレーション、スキーマの変更 |
references/squashing-migrations.md | アプリのマイグレーションを圧縮する方法:再生成して replaces を保持する(デフォルト)、squashmigrations、または replaces を使わずに django_migrations を書き換える方法;中途半端な状態のデータベースへの対応;ロールバック | デプロイ済みのデータベースですでに適用された、長いマイグレーション履歴を短縮するとき |
references/views-and-templates.md | ビュー、フォーム、テンプレート、パーシャル、HTMX、ファイルアップロード、i18n、ミドルウェア、管理コマンド、コネクションプーリング、参照スニペット | ビューやテンプレートの作業、フォーム処理、HTMX、アップロード |
references/background-and-infra.md | バックグラウンドタスク、セキュリティ、設定、オブザーバビリティ、キャッシュ、非同期処理 | タスク、デプロイ、ロギング、キャッシュ、非同期ビュー |
references/admin-and-drf.md | Django Admin、DRF シリアライザー、ビューロジック、パーミッション、ページネーション、バージョニング | Admin のカスタマイズ、API エンドポイント |
references/testing-and-tooling.md | テストの基本、Factory Boy、ツール、DX の徹底 | テストの作成、CI のセットアップ、リンティング |
references/antipatterns.md | django-antipatterns.com のヒント、パターン | コードレビュー、よくある間違いの回避 |
references/troubleshooting.md | Django でよくあるエラーとその修正方法 | マイグレーション、N+1、on_commit の問題の診断 |
依存関係
requires: フロントマターのフィールドで宣言されている ac-python が必要です。このスキルは、ac-python の汎用的な Python ガイドラインに Django 固有のルールを追加します。詳しくは、以下の「ac-python と併用する場合の優先ルール」を参照してください。
ライフサイクルスキルと併用すると、worktree ワークフローを使う Django プロジェクト向けに、Django のベストプラクティスを提供します。
ac-python と併用する場合の優先ルール
ac-django と ac-python の両方が読み込まれている場合、以下の Django 固有のルールは汎用的な Python ガイドラインより優先されます。
| トピック | ac-python(汎用) | ac-django(優先) |
|---|---|---|
| テストの基底クラス | 通常の pytest クラス(class TestFoo:) | django.test.TestCase(必要に応じて TransactionTestCase) |
| パラメーター化 | pytest.mark.parametrize | unittest_parametrize |
| テストデータファクトリー | 通常の build_...() 関数 | DjangoModelFactory、traits、build() / create() を使う Factory Boy |
| 共通セットアップ | pytest フィクスチャ | setUpTestData()(クラスレベルで設定するため、データベースを使うテストでは高速) |
| 時刻のモック | 任意(freezegun、time_machine など) | time_machine(標準) |
その他の ac-python ガイドライン(スタイル、型付け、OOP、インポート、ruff の設定)は、Django プロジェクトでもそのまま適用されます。
トリガーの QA(リリースゲート)
スキルの変更をリリースする前に、サンプルプロンプトで有効化の動作を検証してください。
- トリガーされるべき例:
- 「Django のモデルフィールドとマイグレーションを追加してください。」
- 「この DRF シリアライザーとクエリセットのパフォーマンス上の問題をレビューしてください。」
- 「この Django の transaction/on_commit のバグを修正してください。」
- トリガーされるべきではない例:
- 「チケット用に git worktree をセットアップしてください。」
- 「プロジェクトのデリバリーワークフローを実装し、MR を作成してください。」
- 「Notion の調査サマリーを作成してください。」
動作が過剰または不十分にトリガーされる場合は、リリース前に description の手掛かりを調整してください。
例:新しいモデルフィールドを追加する
ユーザーの指示:「Address モデルに postal_code フィールドを追加してください」
- フィールドの型と制約について、
references/models-and-schema.mdを読み込みます - マイグレーションの安全性について、
references/transactions-and-migrations.mdを読み込みます - 適切なバリデーター、制約、および検索対象の場合は
db_indexを設定して、モデルにフィールドを追加します - マイグレーションを作成し、
makemigrations --checkで検証します - テストに新しいフィールド用のファクトリートレイトを追加します
基本方針
まず Django のドキュメントを確認する(常に)
- Django は、Django のドキュメントに記載された方法で使います。
- Django に明確なドキュメント上の不足がない限り、依存関係より組み込み機能を優先します。
ファットモデルをデフォルトとする(唯一の答えではありません)
小規模から中規模のモデルでは、ファットモデルが有効です。これはデフォルトであり、絶対的な教義ではありません。
- ビジネスルールと不変条件のデフォルトの配置先:
- モデルのインスタンスメソッド(単一オブジェクトの振る舞い。Django でいう「行レベル」の配置先)
- QuerySet/Manager メソッド(コレクションの振る舞い。Django でいう「テーブルレベル」の配置先)
- これは Django のドキュメントと James Bennett の「サービスレイヤー不要」という立場に合致します。モデルとそのマネージャー/クエリセットが、ほかのコードがやり取りする API そのものです。
- ドメインロジックに
services.pyを使うのは、デフォルトではありません。
モデルが神オブジェクトになると、ファットモデルは適切ではなくなります。ファットモデルとは、自身のデータに関する豊富でまとまりのある振る舞いを備えたモデルです。神オブジェクトとは、無関係な関心事や処理の調整がひとつのモデルに蓄積され、全体を通して読めなくなったものです。兆候と段階的な対処法については、以下の「ファットモデルが神オブジェクトになるとき」を参照してください。回避策は、反射的にサービスレイヤーを導入することではありません。
振る舞いの局所性(タコ化の防止)
- 振る舞いは、以下と同じ場所に配置します。
- データ(モデル/クエリセット)
- UI(使用するテンプレート内のパーシャル)
調整とビジネスロジック
境界層(ビュー/フォーム/シリアライザー)で許可されること:
- 呼び出しの順序付け
- 集約ルートオブジェクトの選択
- 複数のドメイン呼び出しにまたがる場合のトランザクション処理
境界層で許可されないこと:
- 不変条件
- ワークフロールール
- 正しさを定義する状態遷移
非推奨の確認(6.1 で警告、次のメジャーリリースで削除)
以下を新しいコードで使わないでください。
- フラットな
EMAIL_*設定(EMAIL_BACKEND、EMAIL_HOST、EMAIL_PORTなど)は、MAILERS辞書設定(DATABASES/CACHESと同様)に置き換えられる形で非推奨になっています。新しいコードでフラットな設定を追加しないでください。 mail.get_connection()、およびメールヘルパーのconnection/fail_silently/auth_user/auth_password引数。代わりにMAILERSエイリアスを指定したusing=を渡してください。- 引数なしで呼び出す
QuerySet.select_related()、およびModelAdmin.list_select_related = True。常にフィールド名を明示してください。 - フィールド名を明示しない
QuerySet.values_list(flat=True)。 fetch_modeキーワード引数を受け取らないModel.from_db()の実装。JSONFieldの完全一致検索で右辺にNoneを指定すること。これは JSON スカラーのnullを意味します。代わりにJSONNull()で検索してください。これは検索時の指定のみです。Noneを保存すると引き続き SQLNULLが書き込まれ、影響はありません。キー検索とインデックス検索も同様に影響を受けません。- 空文字列の検索として解決される、テンプレートの二重ドット検索(
{{ book..title }})。 django.db.transaction.savepoint()。代わりにsavepoint_create()を使ってください。salted_hmac()/base64_hmac()のデフォルト HMAC アルゴリズムはsha1からsha256に変更されます。sha1を前提とする期待値をハードコードしないでください。
Django 6.1 のリリースノートでは、削除されるリリースとして Django 7.0 が記載されています。Django のリリースページによると、A.B 形式での最後のリリースは 6.2 で、その後の機能リリースでは YYYY 形式が使われます。上流の説明では、「7.0」がこの形式にどう対応するのかは明示されていません。
プロジェクト構成と境界
ドメインを中心としたアプリ
- 技術レイヤーではなく、ドメインの機能ごとにアプリを分割します。
- 各アプリは、全体を通して把握しやすい構成にします。
公開用、内部用、管理用
- 公開 API/HTML の境界と、内部用/管理用の境界を分けます。
- 巨大な
views.pyをひとつ作るより、モジュールを分けることを優先します。
"core/" の規律
- 許可: リクエスト ID ミドルウェア、ロギングヘルパー、設定チェック、小規模な共通ベースミックスイン
- 禁止: 「共通化」を理由にドメインの振る舞いを隠すこと
命名
- ビジネス用語を使います。
- 動詞のメソッドを優先します:
approve()、reject()、settle()、cancel()。
チームのスタイル規約(一度だけ確認して記憶)
スタイルの選択肢の中には、どちらも同じように妥当なものがあります。「正解」はチームによって異なります。プロジェクトで初めてその選択肢に遭遇したら、ユーザーにチームの希望を尋ね、プロジェクトの MEMORY.md(自動メモリー)に保存して、セッションをまたいで保持されるようにします。
| トピック | 選択肢 A | 選択肢 B | 確認する内容 |
|---|---|---|---|
| 属性/リレーションのチェック | LBYL: hasattr()、isinstance()、getattr(obj, attr, default) | EAFP: try/except AttributeError | 「オプションの属性や逆リレーションをチェックする場合、チームでは LBYL(hasattr/getattr)と EAFP(try/except)のどちらを好みますか?」 |
| QuerySet の存在チェック | if qs.exists(): の後に qs.first() | obj = qs.first(); if obj: | 「単一クエリ(first() + None チェック)と、明示的な exists() + first() のどちらにしますか?」 |
| シリアライザーでの Null の扱い | Null 許容の各フィールドに allow_null=True を明示する | モデルフィールドの null=True の推論に任せる | 「シリアライザーフィールドで allow_null を明示しますか。それともモデルから推論しますか?」 |
ルール:
- プロジェクトでの初回のみ確認します。すでに保存されている場合は、再度尋ねません。
- プロジェクトのオーバーレイスキルに希望がすでに記載されている場合は、それを回答として扱い、再度尋ねません。
- 回答は、説明的なタイトルを付けて
projectタイプのメモリーとして保存します(例:「Django チーム規約:属性チェックには LBYL を使用」)。
ファットモデルの原則(ロジックの配置場所)
配置表(厳守)
| 関心事 | 配置先 | 備考 |
|---|---|---|
| 不変条件、遷移、ドメイン計算 | モデルメソッド | 「尋ねずに指示する」 |
| コレクションロジック | QuerySet メソッド | チェーン可能 |
| グラフの読み込み | QuerySet メソッド | for_api()、for_list()、with_*() |
| リクエストの検証 | フォーム/DRF シリアライザー | 境界での検証 |
| 認可 | ビュー/DRF パーミッション | 早い段階でチェック |
| レンダリング | テンプレート/シリアライザー | DB にアクセスしない |
変更をカプセル化する
ドメインメソッドは、状態を検証し → 変更を実行し → 変更を永続化し → 副作用を安全にスケジュールする(コミット後)ようにします。
ファットモデルを本来の姿に保つカプセル化ルール(DabApps):モデルの外部からモデルフィールドに直接書き込んだり、save()/create()/一括操作を直接呼び出したりしてはいけません。モデルメソッドまたはマネージャーメソッドを経由してください。ビューやテンプレートのコードでは任意のフィールドを_読み取れます_が、状態の変更は必ずこれらのメソッドを経由します。これにより、「ファットモデル」は「モデルが多くのコードを持つ」ではなく、「モデルが不変条件を管理する」という意味になります。
ファットモデルが神オブジェクトになるとき
ファットモデルは、小規模〜中規模のモデルには適しています。このルールは、無関係な関心事まで抱え込んだモデルには当てはまりません。以下を神オブジェクトの兆候として扱ってください。1つのモデルで2つ以上当てはまったら、追加をやめ、以下のエスカレーションラダーに進んでください。
- **サイズ:**モデルクラス本体が約200 LOCを超える、またはパブリックメソッドが15〜20個を超える。
- **ドメインの混在:**1つのモデルのメソッドが、そのモデル自身のデータに属さない関心事を扱う(例:
Orderが決済プロバイダーへの呼び出しと配送ラベルの生成とロイヤルティポイントの計算を行う)。 save()内のオーケストレーション:save()(または単一のメソッド)が複数の他のモデルにまたがる複数ステップのワークフローを進めたり、通知を送信したり、外部 API を呼び出したりする。「決済契約のキャンセルが1つのメソッド内で他の多数の処理を更新する」というにおいがある。- **責務ごとのメソッド数:**明らかに異なる責務に属するメソッド群がある(認証、プロフィール、請求処理、機能フラグ管理も兼ねる
Userなど)。 - **テストが悲鳴を上げる:**1つのメソッドの単体テストをするのにスキーマの半分をセットアップする必要がある、または1つのモデルに対するテストモジュールが手に負えないほど大きくなる。
長いモデルでも、そのモデル自身のデータだけを完全に扱っていて、読みやすいなら、神オブジェクトではありません。LOCだけを理由にリファクタリングしないでください。きっかけとなるのはサイズ自体ではなく、関心事の混在やオーケストレーションです。
エスカレーションの段階(シグナルに促されたところまで進めます)
各段階には、適用する条件があります。シグナルを解消できる最も低い段階を選び、サービス層に飛ばないでください。
-
(a) QuerySet/Manager のメソッド。 条件: ロジックがコレクションまたはテーブルのレベルにある場合(フィルタリング、集計、一括状態変更、
Account.objects.create_trial()のような代替コンストラクター)。テーブルレベルのロジックを置く最初の場所はここです。これは Django 独自の指針です。「モデルが肥大化している」というプレッシャーの大半は、実際にはテーブルレベルのロジックによるもので、インスタンスではなくここに置くべきです。 -
(b) 一貫性のあるモデルミックスイン、または境界づけられたコンテキストに沿ったモデル分割。 条件: 1 つのモデルに対して、シグナルが 異なるドメインの混在 や 責務ごとのメソッド数 を示している場合、つまりモデルが一貫性のある複数の仕事を担っている場合です。
- まずは、まとまりのある各グループを抽象モデルミックスイン(
BillingMixin、AuditMixin)に切り出します。テーブルは 1 行のまま、それぞれの関心事を読みやすい単位にできます。ミックスインには本当の一貫性が必要です。「残りのメソッド」をまとめただけのミックスインは、ファイルを増やしただけの神オブジェクトです。 - さらに踏み込む場合:関心事が実際には 別々の集約 であれば、境界づけられたコンテキストに沿ってモデルを分割します(多くの場合、アプリも分割します)。たとえば、神オブジェクトと化した
AccountからSubscription/Invoiceを切り出します。ドメインの機能ごとにアプリを分割することが、本当のスケーリングにつながります。上記の「Domain-first apps」を参照してください。
- まずは、まとまりのある各グループを抽象モデルミックスイン(
-
(c) ステートレスなプロセッサ関数(モジュールレベル)、または専用のプロセッサクラス。 条件: 複数のモデルをオーケストレーションする処理で、自然に所有できる単一の集約ルートがない場合です。これは、集約をまたぐ処理や、
save()がオーケストレーションを担っているシグナルに該当します。これは「複雑になりすぎたメソッドを分解した」場合の解決策(Bennett の続編)であり、混乱を別の場所に移すものではありません。- デフォルトの形式:アプリ内のステートレスなモジュールレベル関数(例:
app/operations.py/app/processors.py)を使います。参加するオブジェクトを引数として受け取り、1 つのtransaction.atomic()内で処理の順序を制御し、副作用はon_commitを使ってスケジュールします。保持すべき状態がない場合は、クラスより関数を使います(Luke Plant)。 - オーケストレーションをまたいで保持すべき複数段階の状態が実際にある場合(ビルダー、複数フェーズのインポートなど)に限り、専用のプロセッサ/ハンドラークラスを使います。操作にちなんだ名前(
OrderCheckoutProcessor)を付け、デフォルトではステートレスに保ち、不変条件は処理対象となるモデルに置きます。プロセッサは順序を制御し、正しさは引き続きモデルが担います。 - この段階は調整のためのものであり、ドメインサービス層ではありません。モデル/マネージャーのメソッドを呼び出し、それらにあるビジネスルールを再実装することはありません。
- デフォルトの形式:アプリ内のステートレスなモジュールレベル関数(例:
-
(d) サービス層 — 最後の手段。外部 API のオーケストレーションに限ります。 条件: オーケストレーションの中心が外部とのやり取り(Stripe、AWS、サードパーティ API)であり、それらの呼び出しの順序制御、エラー処理、再試行こそが実際の複雑さである場合です。その場合は、外部連携の範囲に限定した
services.pyを使って構いません。- 制約:
services.pyに DB のビジネスロジックや不変条件を置いてはいけません。それらは引き続きモデルが担います。サービスは外部呼び出しをオーケストレーションし、その結果をモデル/マネージャーのメソッドに渡します。 - これは意図的に段階の最上位に置いています。一般的な「すべてのビジネスロジックを
services.pyに置く」層(HackSoft/エンタープライズのパターン)は、実際に擁護可能なスタイルです。しかし、このスキルのデフォルトではありません。モデルを API として扱う代わりに並行する層を設けることになり、多くのアプリでは上記の段階でニーズを満たせます。プロジェクト全体でサービス層を採用するのは、モデルが肥大化したときに反射的に選ぶのではなく、チームが明示的に決定し、文書化した場合に限ってください。
- 制約:
関連する読み取り処理(selectors.py)。 書き込み側の段階とは別に、どの QuerySet にも適さない、モデルをまたぐ複雑な読み取り処理(レポート/ダッシュボード)は、型付き DTO を返す selectors.py に置くことができます。セレクターは読み取り専用で、データを変更することはありません。
この方針の参考資料
- Django ドキュメント — マネージャー:行レベル → モデルメソッド、テーブルレベル → Manager/QuerySet メソッド:https://docs.djangoproject.com/en/6.1/topics/db/managers/#adding-extra-manager-methods
- James Bennett —「Django におけるサービス層への反対」および続編(「サービス層について、さらに」):https://www.b-list.org/weblog/2020/mar/16/no-service/ · https://www.b-list.org/weblog/2020/mar/23/still-no-service/
- James Bennett —「Litestar は一見の価値あり」(特に Django について、サービス層を設けない立場を再確認):https://www.b-list.org/weblog/2025/aug/06/litestar/
- DabApps —「Django モデル、カプセル化、データ整合性」(外部からフィールドや
save()を決して書き換えない):https://www.dabapps.com/insights/django-models-and-encapsulation/ - Luke Plant —「Django ビュー — 正しい方法」(クラスより関数を選び、過剰な抽象化を避ける):https://spookylukey.github.io/django-views-the-right-way/
- HackSoft Django Styleguide — サービス層/
selectors.pyを採用する陣営(既定ではなく、選択肢の一つとして提示されている rung-(d) のスタイル):https://github.com/HackSoftware/Django-Styleguide - Carlton Gibson / Vinta —「Fat Model とサービス層の二項対立を超えて」(中間の選択肢としてモジュールレベルの関数を提示):https://www.vintasoftware.com/lessons-learned/djangoservicelayersbeyondfatmodelsvsenterprisepatterns
レビューチェックリスト
ドメインルール/Fat Model
- ビジネスルールはモデル/クエリセットに置きます(既定の配置先)
- 境界層はオーケストレーションのみを行います
- モデル+マネージャーメソッドの外部から、フィールドの書き込み/
save()/一括操作を行いません - God Object はありません。複数のドメインにまたがる Fat Model や、複数モデルのオーケストレーションを行う
save()/メソッドがある場合は、単に肥大化させるのではなく、段階的な対応策(queryset → mixin/分割 → processor 関数/クラス → 外部 API 専用のサービス)へ進んでいます
ORM のパフォーマンス
- N+1 がありません
- シリアライザー/テンプレートの要件に合わせてクエリセットを構成しています
- 制約/インデックスを確認しています
トランザクションと副作用
- 複数ステップの書き込みフローを
atomic()で囲んでいます - 副作用は
on_commit経由でスケジュールしています
マイグレーション
- マイグレーションを直線的に保っています(アプリごとにリーフを一つにします —
django-linear-migrationsを使うか、手動で確認します) -
makemigrations --checkが成功します - マイグレーションが安全で、レビュー可能です
- マイグレーションファイルを削除する squash では、すべてのデータベースに記録されるまで
replacesを保持し、migrateの前に途中までしか適用されていないデータベースを拒否します(replacesがない場合は、バックアップ、行数、スキーマ差分、書き換え、ロールバックの計画を明示します) —references/squashing-migrations.mdを参照 - コメントをコードとして扱います(ac-python に準拠):シグネチャを繰り返すだけの docstring はなく、RunPython の本体を言い換えるインラインコメントもありません — データマイグレーションの意図は関数名で示し、6 行の docstring では示しません
タスク
- 冪等性があります
- コミット後にキューへ登録します
セキュリティ
- 認可を早い段階で確認しています
- CSRF を維持しています
- CSP を正しく設定しています
アーキテクチャの健全性(モジュールレベル)
変更行だけでなく、差分で触れたファイル全体に適用します:
- Fat Model を既定とします:ビジネスロジックはビュー/コマンド/CLI ではなく、モデル/クエリセットに置きます
- ビューと管理コマンドはオーケストレーションのみを行います — 不変条件もワークフロールールも持ちません
- God Module(無関係な関心事を一つのファイルに混在させたもの)も God Object(無関係な関心事や複数モデルのオーケストレーションが蓄積した単一モデル)もありません
- 複数モデルのオーケストレーションは、ステートレスな processor 関数/クラス(または集約ルートのメソッド)に置き、一つのモデルの
save()に詰め込みません -
pyproject.tomlに、python-boilerplateのベースラインを超える複雑度ルールの抑制(C901、PLR09xx)はありません -
services.py(存在する場合)は外部 API のオーケストレーション(Stripe、AWS)に限定し、DB のビジネスロジックを含みません — プロジェクト全体にわたるサービス層は、既定ではなく、チームが決定して文書化するものです
テスト
- nullable/optional なデータに traits を使用しています
- テストで前提条件を明示しています
prek による強制適用
上記のテスト規約のうち、決定論的にチェック可能なものは、このリポジトリの prek
フック(.pre-commit-hooks.yaml + ac-django/rules/)として提供されています。AST 形式の
チェックには ast-grep の YAML ルールを使用します。
pyproject.toml のチェック(TOML 言語がなく ast-grep では解析できません)は、
単独の小さな Python フックです。利用するリポジトリでは、URL と rev を指定して参照します。
- repo: https://github.com/souliane/skills
rev: <commit-sha>
hooks:
- id: ac-django-no-pytest-django-db
- id: ac-django-testcase-no-pytest-parametrize
- id: ac-django-no-complexity-suppressions
- id: ac-django-no-pyproject-complexity
| フック ID | エンジン | エラーになる条件 |
|---|---|---|
ac-django-no-pytest-django-db | ast-grep | pytest.mark.django_db の使用(@pytest.mark.django_db デコレーター、およびモジュールレベルの pytestmark = pytest.mark.django_db またはそのリスト)。django.test.TestCase を使用してください。 |
ac-django-testcase-no-pytest-parametrize | ast-grep | TestCase サブクラス内のメソッドに対する @pytest.mark.parametrize(pytest はそこでこれを黙って無視します)。unittest_parametrize を使用してください。モジュールレベルの pytest 形式の関数は検出対象外です。 |
ac-django-no-complexity-suppressions | ast-grep | ソースまたはテスト内の # noqa: C901/PLR09xx コメント |
ac-django-no-pyproject-complexity | 単独 | pyproject.toml の ruff lint.ignore/lint.extend-ignore/lint.per-file-ignores リスト内の C901/PLR09xx エントリー |
ast-grep は 0.44.1 に固定されています。 ラッパー(ac-django/rules/astgrep_scan.py)
は、PATH に uv がある場合、uvx --from ast-grep-cli==0.44.1 ast-grep を使って隔離された環境で解決します(システムへのインストールは不要です)。ない場合は、システムの ast-grep にフォールバックします。
違反があれば失敗する方式で、免除は違反箇所にインラインで記録します。 免除がなければ すべての違反が失敗となります(新しく導入するリポジトリには、最初からすべての規則が適用されます)。ベースラインファイルも件数上限もありません。既存の違反は該当箇所に記録します。
- ast-grep ルール — 違反の直前の行に ast-grep ネイティブのオプトアウトコメントを追加します:
# ast-grep-ignore[<rule-id>](例:# ast-grep-ignore[ac-django-no-pytest-django-db])。該当するノードに対するそのルールだけが無効になります。新たに追加された(無視指定のない)箇所はすべて引き続き失敗となります。 ac-django-no-pyproject-complexity— 既存の各エントリーを、利用するリポジトリのフックのargsに--grandfather <code>@<location>の形式で列挙します(例:--grandfather [email protected]/**/*.py)。リストにない新しいエントリーは失敗となり、コードの改善に伴ってリストは縮小するだけです。
これにより、データファイルを使わずにラチェットの「厳格化のみ」という性質を保てます。免除の記録は違反箇所と一緒(インラインコメント、または利用する側の .pre-commit-config.yaml)に置かれるため、コードツリーと同期しなくなることはありません。
レビューのみの規約(意図的に強制チェックの対象外)。 Factory Boy の使用や、共通のセットアップに setUpTestData() を使うことはチーム内の規約ですが、これらをフックでチェックするとノイズが多すぎます。ファクトリーを使わない正当な create() 呼び出しや、テストごとに必要な setUp() が多数あるためです。これらはブロック対象のフックではなく、レビュー用チェックリストの項目とします。
旧バージョン:Django 5.2 LTS
Django 5.2 は LTS であり、2028 年 4 月まで延長サポートされるため、本番コードでは今も広く使われています。上記の内容はすべて Django 6 を対象としています。このセクションでは相違点を説明します。
Django 6 にあり、5.2 にはない機能
| 機能 | Django 6 | 代わりに 5.2 で使うもの |
|---|---|---|
| テンプレートの部分テンプレート | ネイティブの {% partialdef %}/{% partial %} | django-template-partials |
| バックグラウンドタスク | ネイティブの django.tasks(@task、.enqueue()) | Celery/Huey/RQ |
| CSP | ネイティブの CSP ミドルウェア + SECURE_CSP | django-csp |
| フェッチモード(6.1 以降) | QuerySet.fetch_mode() | 明示的な select_related()/prefetch_related() |
DB レベルの on_delete(6.1 以降) | DB_CASCADE/DB_SET_NULL/DB_SET_DEFAULT | Python レベルの on_delete のみ |
| 複数のメーラー(6.1 以降) | MAILERS | 従来の EMAIL_* 設定 |
| squash 済みマイグレーションの再 squash | squashmigrations は squash 済みのマイグレーションを受け付けます | 先に、それ以前の squash を通常のマイグレーションに戻します(置き換えられたファイルを削除し、replaces を削除します) |
アップグレード
- 手動でリファクタリングする前に、
django-upgradeを実行してください。 - シムを使用している箇所には、明示的なアップグレード TODO を残してください。
# TODO(Django6): switch @shared_task -> @task and .delay() -> .enqueue()# TODO(Django6): remove {% load partials %} (partials become native)# TODO(Django6): replace django-csp with built-in CSP middleware
- Django 5.2 は Python 3.10 と 3.11 をサポートする最後のバージョン系列です。Django 6.x には Python 3.12 以降が必要です。