{
  "id": "django-app",
  "type": "loadout",
  "title": "Django app rules",
  "summary": "Django rules and a safe-migration skill: migrations checked in CI, two-step destructive changes, N+1 caught with query-count tests, settings from the environment.",
  "description": "Agents are quick at Django and quick to leave behind the classic problems: model changes without migrations, edited migrations that already ran, N+1 queries in list views, csrf_exempt to make a form work, DEBUG and SECRET_KEY in code. This loadout sets the rules from Django's own docs, adds makemigrations --check and check --deploy as gates, and ships a safe-migration skill with a two-release path for removals and renames and a query-count test that the verifier runs against a real N+1.\n",
  "highlights": [
    "makemigrations --check --dry-run and manage.py check as done-gates",
    "Two-release path for removing or renaming columns",
    "assertNumQueries test that catches N+1 queries",
    "Data migrations with apps.get_model and a backwards function",
    "No csrf_exempt, no |safe on user input, secrets from the environment"
  ],
  "version": "1.0.0",
  "license": "MIT",
  "author": {
    "name": "Agent Loadout",
    "url": "https://loadout.jnw-it.com"
  },
  "credits": [
    {
      "title": "Django docs: migrations",
      "url": "https://docs.djangoproject.com/en/6.1/topics/migrations/"
    },
    {
      "title": "Django docs: database access optimisation",
      "url": "https://docs.djangoproject.com/en/6.1/topics/db/optimization/"
    },
    {
      "title": "Django docs: deployment checklist",
      "url": "https://docs.djangoproject.com/en/6.1/howto/deployment/checklist/"
    },
    {
      "title": "@geronco: CLAUDE.md templates per framework with exact commands and a never-do list",
      "url": "https://x.com/geronco/status/2108014691582681184"
    }
  ],
  "categories": [
    "web-app"
  ],
  "stacks": [
    "python",
    "django",
    "uv",
    "pytest"
  ],
  "files": [
    {
      "path": "AGENTS.md",
      "role": "instructions",
      "description": "Django rules for models, migrations, queries and security.",
      "skill": null,
      "agent": null,
      "install_to": {
        "claude-code": "AGENTS.md",
        "codex": "AGENTS.md",
        "cursor": "AGENTS.md",
        "gemini-cli": "AGENTS.md",
        "antigravity-cli": "AGENTS.md",
        "copilot": "AGENTS.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/django-app/latest/AGENTS.md",
      "bytes": 2036,
      "lang": "markdown",
      "content": "# AGENTS.md: Django app\n\nDjango project managed with uv. Supported Django: 5.2 LTS or 6.x; check `pyproject.toml` before using a newer API.\n\n## Commands\n- Run: `uv run python manage.py runserver`. Shell: `uv run python manage.py shell`.\n- Tests: `uv run pytest -q` (pytest-django) or `uv run python manage.py test`.\n- Checks before saying done: `uv run python manage.py check`, `uv run python manage.py makemigrations --check --dry-run`\n  (must report no changes), tests, and `uv run ruff check .`.\n- Deployment settings: `uv run python manage.py check --deploy` with production settings.\n\n## Models and migrations\n- Every model change ships with its migration in the same commit. Use the `safe-migration` skill.\n- Never edit or delete a migration that may have run anywhere else. Add a new one.\n- In data migrations use `apps.get_model(\"app\", \"Model\")`, never import the model class.\n- Read the SQL of risky migrations with `uv run python manage.py sqlmigrate <app> <number>` and show it to the human.\n\n## Queries\n- No N+1: `select_related` for foreign keys, `prefetch_related` for reverse and many-to-many. Prove it with\n  `assertNumQueries` (or `django_assert_num_queries` in pytest-django) when you touch a list view.\n- Writes that must succeed together go in `transaction.atomic()`.\n- Never build SQL with string formatting. Use the ORM, or `raw()` and `cursor.execute()` with parameters.\n\n## Security\n- `SECRET_KEY`, `DEBUG`, `ALLOWED_HOSTS` and database credentials come from the environment, never hard-coded.\n- Never add `@csrf_exempt` to a view that uses session auth. Never mark user input `mark_safe` or `|safe`.\n- Validate input with forms or serializers, not by reading `request.POST` directly.\n- Views that need a login use `LoginRequiredMixin` or `@login_required`; check object ownership in the queryset.\n\n## Style\n- Fat models, thin views: business rules on the model or in a service module, not in templates.\n- Settings changes go in the settings module the project already uses; do not add a second settings system.\n"
    },
    {
      "path": "CLAUDE.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Claude Code.",
      "skill": null,
      "agent": "claude-code",
      "install_to": {
        "claude-code": "CLAUDE.md",
        "codex": null,
        "cursor": null,
        "gemini-cli": null,
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/CLAUDE.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/django-app/latest/CLAUDE.md",
      "bytes": 361,
      "lang": "markdown",
      "content": "# CLAUDE.md\n\n@AGENTS.md\n\n## Claude Code notes\n- Skills from this loadout live in `.claude/skills/`: `safe-migration`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Django app rules\" v1.0.0 — https://loadout.jnw-it.com/loadouts/django-app/ -->\n"
    },
    {
      "path": "GEMINI.md",
      "role": "adapter",
      "description": "Imports AGENTS.md for Gemini CLI.",
      "skill": null,
      "agent": "gemini-cli",
      "install_to": {
        "claude-code": null,
        "codex": null,
        "cursor": null,
        "gemini-cli": "GEMINI.md",
        "antigravity-cli": null,
        "copilot": null
      },
      "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/GEMINI.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/django-app/latest/GEMINI.md",
      "bytes": 362,
      "lang": "markdown",
      "content": "# GEMINI.md\n\n@./AGENTS.md\n\n## Gemini CLI notes\n- Skills from this loadout live in `.agents/skills/`: `safe-migration`. Use them when their description matches the task.\n- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.\n\n<!-- From the Agent Loadout \"Django app rules\" v1.0.0 — https://loadout.jnw-it.com/loadouts/django-app/ -->\n"
    },
    {
      "path": "skills/safe-migration/SKILL.md",
      "role": "skill",
      "description": "Model change with reviewed SQL and a zero-downtime path.",
      "skill": "safe-migration",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/safe-migration/SKILL.md",
        "codex": ".agents/skills/safe-migration/SKILL.md",
        "cursor": ".agents/skills/safe-migration/SKILL.md",
        "gemini-cli": ".agents/skills/safe-migration/SKILL.md",
        "antigravity-cli": ".agents/skills/safe-migration/SKILL.md",
        "copilot": ".agents/skills/safe-migration/SKILL.md"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/django-app/latest/skills/safe-migration/SKILL.md",
      "bytes": 1503,
      "lang": "markdown",
      "content": "---\nname: safe-migration\ndescription: Make a Django model change with a reviewed migration, a zero-downtime path for destructive changes and a query-count test. Use when adding, renaming or removing fields or models.\n---\n\n# Safe migration\n\n1. Change the model. Run `uv run python manage.py makemigrations <app>` and read the generated file.\n2. Show the SQL: `uv run python manage.py sqlmigrate <app> <number>`. Flag to the human anything that rewrites or locks a\n   large table (adding a non-null column with a default on old databases, new indexes on big tables, type changes).\n3. Destructive changes take two releases:\n   - Remove a field: release 1 stops using it in code (and makes it nullable if needed); release 2 drops the column.\n   - Rename: add the new field, copy data in a data migration, switch code, drop the old field later.\n     Never rely on `makemigrations` guessing a rename on a production table without checking the SQL.\n4. Data migrations: `RunPython(forwards, backwards)` using `apps.get_model`. Batch large updates. Always give a\n   `backwards` function, or `migrations.RunPython.noop` if reversing is genuinely safe to skip.\n5. Run `uv run python manage.py migrate` locally, then `makemigrations --check --dry-run` (no changes left) and tests.\n6. If a list view or API touches the changed model, add a query-count test like `references/test_query_count.py`.\n7. Report: migration file, SQL summary, whether it is safe to run on a live database, and the follow-up release if any.\n"
    },
    {
      "path": "skills/safe-migration/references/test_query_count.py",
      "role": "reference",
      "description": "assertNumQueries test against N+1 queries.",
      "skill": "safe-migration",
      "agent": null,
      "install_to": {
        "claude-code": ".claude/skills/safe-migration/references/test_query_count.py",
        "codex": ".agents/skills/safe-migration/references/test_query_count.py",
        "cursor": ".agents/skills/safe-migration/references/test_query_count.py",
        "gemini-cli": ".agents/skills/safe-migration/references/test_query_count.py",
        "antigravity-cli": ".agents/skills/safe-migration/references/test_query_count.py",
        "copilot": ".agents/skills/safe-migration/references/test_query_count.py"
      },
      "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
      "raw_latest_url": "https://loadout.jnw-it.com/raw/django-app/latest/skills/safe-migration/references/test_query_count.py",
      "bytes": 773,
      "lang": "markdown",
      "content": "\"\"\"Query-count test: a list of books with authors must not issue one query per book (N+1).\n\nAdapt the app, model and function names. Uses Django's TestCase.assertNumQueries.\n\"\"\"\n\nfrom django.test import TestCase\n\nfrom shop.models import Author, Book\nfrom shop.queries import book_titles_with_authors\n\n\nclass BookListQueries(TestCase):\n    @classmethod\n    def setUpTestData(cls):\n        for i in range(5):\n            author = Author.objects.create(name=f\"Author {i}\")\n            Book.objects.create(title=f\"Book {i}\", author=author)\n\n    def test_one_query_for_any_number_of_books(self):\n        with self.assertNumQueries(1):\n            rows = book_titles_with_authors()\n        self.assertEqual(len(rows), 5)\n        self.assertEqual(rows[0], (\"Book 0\", \"Author 0\"))\n"
    }
  ],
  "requires": {
    "tools": [
      "uv"
    ],
    "env": [],
    "mcp": []
  },
  "tested": {
    "status": "untested",
    "last_run": null,
    "fixture": null,
    "per_agent": {
      "claude-code": "untested",
      "codex": "untested",
      "cursor": "untested",
      "gemini-cli": "untested",
      "antigravity-cli": "untested",
      "copilot": "untested"
    }
  },
  "security": {
    "scanned": true,
    "scanner": "intake-precheck/1",
    "verdict": "clean",
    "network": true,
    "scripts": [],
    "notes": "uv downloads Django and other packages from PyPI. The example code makes no network calls."
  },
  "featured": false,
  "deprecated": false,
  "last_checked": "2026-10-17",
  "published": "2026-10-17",
  "updated": "2026-10-17",
  "compatibility": {
    "claude-code": "native",
    "codex": "native",
    "cursor": "native",
    "gemini-cli": "native",
    "antigravity-cli": "native",
    "copilot": "native"
  },
  "counts": {
    "rules": 1,
    "skills": 1,
    "mcp": 0
  },
  "url": "https://loadout.jnw-it.com/loadouts/django-app/",
  "api_url": "https://loadout.jnw-it.com/api/loadouts/django-app.json",
  "verification": {
    "level": "verified-install",
    "status": "verified",
    "label": "Verified install",
    "verified": true,
    "checked_at": "2026-10-17T08:09:30Z",
    "commit": "03c1b9839c40eb1e44c0a2877baaa3978a96895c",
    "content_current": true,
    "agent_tests": "not-run",
    "counts": {
      "pass": 9,
      "fail": 0,
      "skip": 0,
      "not_applicable": 1
    },
    "meaning": "Level 1: automated checks on the files (parse, install paths, MCP config, safety scan, references, size, licence, starter build). No AI agent was run.",
    "checks": [
      {
        "id": "listing",
        "name": "Listing",
        "status": "pass",
        "summary": "Schema valid; 5 files declared, none missing or extra",
        "details": [
          "tested.status: untested (agent tests are separate from Level 1)"
        ]
      },
      {
        "id": "parse",
        "name": "Files parse",
        "status": "pass",
        "summary": "All 5 files parse; front matter valid",
        "details": []
      },
      {
        "id": "adapters",
        "name": "Adapters",
        "status": "pass",
        "summary": "CLAUDE.md and GEMINI.md import AGENTS.md and are up to date",
        "details": [
          "CLAUDE.md: matches the generator",
          "GEMINI.md: matches the generator"
        ]
      },
      {
        "id": "install-paths",
        "name": "Install paths",
        "status": "pass",
        "summary": "Correct paths for all 6 agents; layouts re-discovered",
        "details": [
          "Claude Code: 4 files, 1 skill in .claude/skills/",
          "Codex CLI: 3 files, 1 skill in .agents/skills/",
          "Cursor: 3 files, 1 skill in .agents/skills/",
          "Gemini CLI: 4 files, 1 skill in .agents/skills/",
          "Antigravity CLI: 3 files, 1 skill in .agents/skills/",
          "GitHub Copilot: 3 files, 1 skill in .agents/skills/"
        ]
      },
      {
        "id": "mcp",
        "name": "MCP config",
        "status": "n/a",
        "summary": "No MCP servers in this loadout",
        "details": []
      },
      {
        "id": "safety",
        "name": "Safety scan",
        "status": "pass",
        "summary": "Clean: no credentials, risky shell or hidden text",
        "details": [
          "Scanner intake-precheck/1: verdict clean; network no (listing says yes)"
        ]
      },
      {
        "id": "references",
        "name": "References",
        "status": "pass",
        "summary": "1 reference between files resolves for every agent",
        "details": []
      },
      {
        "id": "size",
        "name": "Size limits",
        "status": "pass",
        "summary": "5 files, 4.9 KB total; largest AGENTS.md 2.0 KB",
        "details": [
          "Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule"
        ]
      },
      {
        "id": "licence",
        "name": "Licence",
        "status": "pass",
        "summary": "MIT, by Agent Loadout; text in the repository LICENSE",
        "details": []
      },
      {
        "id": "starter-build",
        "name": "Starter build",
        "status": "pass",
        "summary": "In a small Django project: manage.py check passes, makemigrations --check finds no missing migration, the skill's query-count test passes with select_related and catches an N+1 version",
        "details": [
          "Applied 7 loadout files at every agent's paths",
          "✓ makemigrations (initial): uv run --no-project --python 3.12 --with django==6.1.2 python manage.py makemigrations shop",
          "✓ manage.py check: uv run --no-project --python 3.12 --with django==6.1.2 python manage.py check",
          "✓ makemigrations --check --dry-run: uv run --no-project --python 3.12 --with django==6.1.2 python manage.py makemigrations --check --dry-run",
          "✓ place the query-count test: cp .agents/skills/safe-migration/references/test_query_count.py shop/tests/test_query_count.py",
          "✓ query-count test passes: uv run --no-project --python 3.12 --with django==6.1.2 python manage.py test shop -v 1",
          "✓ query-count test catches N+1: bash -c sed -i 's/import book_titles_with_authors$/import book_titles_with_authors_n_plus_one as book_titles_with_authors/' shop/tests/test_query_count.py && uv run --no-project --python 3.12 --with django==6.1.2 python manage.py test shop; test $? -ne 0 && echo caught",
          "Covers: Settings from the environment; manage.py check passes",
          "Covers: makemigrations --check --dry-run as the missing-migration gate",
          "Covers: assertNumQueries test proves select_related and fails on an N+1 query"
        ]
      }
    ]
  },
  "install": {
    "claude-code": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "CLAUDE.md",
          "target": "CLAUDE.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/CLAUDE.md",
          "role": "adapter"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".claude/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".claude/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/claude-code.zip"
    },
    "codex": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".agents/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".agents/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/codex.zip"
    },
    "cursor": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".agents/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".agents/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/cursor.zip"
    },
    "gemini-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "GEMINI.md",
          "target": "GEMINI.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/GEMINI.md",
          "role": "adapter"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".agents/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".agents/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/gemini-cli.zip"
    },
    "antigravity-cli": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".agents/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".agents/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/antigravity-cli.zip"
    },
    "copilot": {
      "files": [
        {
          "path": "AGENTS.md",
          "target": "AGENTS.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/AGENTS.md",
          "role": "instructions"
        },
        {
          "path": "skills/safe-migration/SKILL.md",
          "target": ".agents/skills/safe-migration/SKILL.md",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/SKILL.md",
          "role": "skill"
        },
        {
          "path": "skills/safe-migration/references/test_query_count.py",
          "target": ".agents/skills/safe-migration/references/test_query_count.py",
          "raw_url": "https://loadout.jnw-it.com/raw/django-app/1.0.0/skills/safe-migration/references/test_query_count.py",
          "role": "reference"
        }
      ],
      "manual_steps": [],
      "zip_url": "https://loadout.jnw-it.com/download/django-app/copilot.zip"
    }
  },
  "source_zip_url": "https://loadout.jnw-it.com/download/django-app/source.zip"
}