A collection of Agent Skills that give a coding agent architectural skills to build scalable, maintainable Django projects with clean separation of concerns, testability, and a full suite of best...
概要
A collection of Agent Skills that give a coding agent architectural skills to build scalable, maintainable Django projects with clean separation of concerns, testability, and a full suite of best...
README
op-django
A collection of Agent Skills that give a coding agent architectural skills to build scalable, maintainable Django projects with clean separation of concerns, testability, and a full suite of best practices.
I love Django, but some parts of it are genuinely hard to type: querysets, model instances, related managers, F()/
Q() expressions, etc. In my experience, the cleanest way to handle this is to keep all the ORM work behind a
repository layer that returns Pydantic DTOs. Your business logic lives in services that never import a model. Your views
become one-liners, and each layer becomes very easy to mock and test.
After discussions with my friends Haki Benita and Pete Nilson, this is what I believe to be a reasonable set of patterns for flexible, testable Django projects. They’re opinionated, but hold up well on large scale projects in the wild.
A Layered Approach Using Encapsulation
Each layer encapsulates the one beneath it. The API layer never touches the ORM. Services never import a model. Repositories never leak a queryset or a model instance. Every boundary between layers is crossed as a typed Pydantic DTO, so changes stay local and tests stay fast.
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ API │──▶│ Service │──▶│ DTO │──▶│ Repo │──▶│ Model │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
thin views business typed data ORM lives
logic + DI at boundaries here only
| Layer | Role | Library |
|---|---|---|
| API | Routing, input validation, OpenAPI | django-ninja |
| Service | Business logic & orchestration with true DI | svcs |
| DTO | Typed data at every layer boundary | Pydantic v2 |
| Repository | All ORM access, transactions, prefetches | Django |
| Model | Persistence with prefixed ULID primary keys | Django · python-ulid |
| Async | Reliable signals & background tasks | Celery |
DX
| Concern | Tool |
|---|---|
| Packaging | uv |
| Settings | python-decouple |
| Linting & formatting | ruff |
| Type checking | pyrefly · django-stubs |
| Testing | pytest · pytest-django · pytest-celery · freezegun |
Install
Skills install with a single command:
# The whole collection
npx skills add dvf/opinionated-django
# Or just one
npx skills add dvf/opinionated-django/dj-scaffold|dj-architecture|dj-models|dj-services|dj-prefixed-ulids|dj-signals|dj-settings|dj-lint
Your agent will pick them up automatically on its next run. You can also clone the repo and point your agent at
skills/ directly.
The Skills
Each skill is a directory under skills/ with its own SKILL.md. They’re designed to stand alone, but they compose
nicely — dj-scaffold lays the foundation, dj-architecture builds features on top, and the rest fill in the details.
🏗️ dj-scaffold
Sets up a new (or existing) Django project into the op-django layout. Creates the src/project/ shell — ids.py,
services.py, api.py, a ReliableSignal base, Celery wiring — installs dependencies with uv, and lays down ruff /
pyrefly / pytest config. Run this first.
🔑 dj-prefixed-ulids
Stripe-style prefixed ULID primary keys for every model (prd_01jq3v..., ord_01jq3v...). Time-sortable, safe to
expose in URLs, debuggable in logs, and str end-to-end so there’s no UUID/str coercion between layers.
🧩 dj-services
Plain service classes with constructor-injected repositories, wired through an svcs registry.
Business logic lives here, zero ORM imports allowed. Resolve anywhere — views, Celery tasks, management commands,
tests — with a single generic get[T]() call.
📐 dj-models
Structures Django models with a strict internal layout: Meta first (verbose names, indexes, constraints), then fields
grouped as identifiers → time → status → domain → relations. Uniqueness lives in Meta.constraints (never
unique=True), indexes in Meta.indexes (never db_index=True) — both optimized for the queries the repository
actually runs. Multi-word fields get verbose_name, obscure fields get help_text. Every model is registered in the
admin with a clean, fast-loading config (list_per_page = 25, raw_id_fields for large FKs, extra = 0 on inlines).
🏛️ dj-architecture
The full feature blueprint. Given a description, the agent scaffolds models, Pydantic DTOs, repositories, services, django-ninja routes, admin registration, and three layers of tests (repo against a real DB, service against mocked repos, API through HTTP). Every convention is spelled out; every layer is non-negotiable.
📡 dj-signals
Reliable signals for async side-effects — notifications, cache invalidation, analytics, cross-service coordination. Receivers are enqueued inside the database transaction via Celery, so rollbacks are respected and delivery is at-least-once. Pattern adapted from Haki Benita’s Reliable Signals in Django.
⚙️ dj-settings
Keeps settings.py organized with banner-style section headers in a predictable order. Use whenever settings are added,
removed, or restructured.
🧪 dj-pytest
Three-layer pytest setup — repo against a real DB, service against mocked repos, API through HTTP — with
pytest-django, pytest-celery for reliable-signal receivers, freezegun for time-sensitive logic, and a project
conftest.py full of DTO factories and svcs-override fixtures.
✅ dj-lint
Runs ruff check, ruff format --check, and pyrefly check, then fixes whatever it finds. Use before committing, or
any time you want a clean bill of health.
The Patterns at a Glance
- Models —
Metafirst (verbose names, indexes, constraints). Fields ordered: identifiers → time → status → domain → relations. Uniqueness viaUniqueConstraint, indexes viaMeta.indexes— both optimized for actual query patterns. No business logic, no customsave(), no properties that compute. Multi-word fields getverbose_name, obscure fields gethelp_text. - DTOs — Pydantic v2 with
from_attributes=True. All IDs arestr. ORM objects never leave the repository. - Repositories — The only layer that touches the ORM. Returns DTOs.
@transaction.atomicfor multi-writes. One repo per aggregate root. - Services — Receives dependencies via
__init__. Pure business logic. Zero ORM imports. Testable without a database. - API — django-ninja routes centralized in
project/api.py. Input schemas areninja.Schema, output schemas reuse DTOs. - Reliable Signals — Side-effects enqueued inside the DB transaction via Celery. At-least-once delivery. Idempotent receivers.
- Settings — Sectioned with banner headers.
python-decouplefor env vars.
Example Project
See example_project for a working Django project built with these patterns — two apps (
products, orders), full repository + service + API layering, and tests at all three levels.
License
MIT. Use them, fork them, make them yours.
推奨ツール
別のキーワードを試すか、フィルタを外してください。
インストール
npx skillfish add dvf/opinionated-django