Skip to content
Web apps · Security

Django REST Framework API rules

DRF rules for agents: explicit serializer fields, querysets scoped to the user, owner set on the server, pagination, and six tests for every endpoint.

Verified install Not agent-tested v1.0.0 MIT updated pythondjangouv
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

The DRF code agents write usually works for the person testing it and leaks for everyone else: fields set to __all__, a queryset of every row with a permission check bolted on, an owner field accepted from the request body, an unpaginated list that runs a query per row. This loadout gives your agent the DRF conventions that prevent that, from the official docs and the OWASP API Security Top 10's broken object level authorisation. A skill adds endpoints with the six tests each one needs. The verifier runs the tests on Django 6.1 and DRF 3.18, and checks that the query-count test fails without select_related.

What's in it

  • IsAuthenticated by default; AllowAny only when asked
  • get_queryset scoped to request.user: other users' rows are a 404
  • Owner set in perform_create, never from the request body
  • Explicit serializer fields; no __all__ or exclude
  • Six tests per endpoint, including a flat query count

You'll need

  • Tools: uv, python3
Level 1 · automated

Verification

Verified install

Checked at commit 021470d · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 8 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 8 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 7 files, 1 skill in .claude/skills/
  • Codex CLI: 6 files, 1 skill in .agents/skills/
  • Cursor: 6 files, 1 skill in .agents/skills/
  • Gemini CLI: 7 files, 1 skill in .agents/skills/
  • Antigravity CLI: 6 files, 1 skill in .agents/skills/
  • GitHub Copilot: 6 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 1 reference between files resolves for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
8 files, 8.1 KB total; largest skills/drf-endpoint/references/tests.py 2.5 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
The drf-endpoint skill's serializer, viewset, router and tests run on Django 6.1 with DRF 3.18: six API tests pass (auth, ownership, 404 for other users, validation, flat query count), no missing migrations, and dropping select_related fails the query-count test
  • Applied 13 loadout files at every agent's paths
  • ✓ place the examples: bash -c R=.agents/skills/drf-endpoint/references && cp $R/serializers.py $R/views.py $R/urls.py $R/tests.py notes/ && echo placed
  • ✓ makemigrations (initial): uv run --no-project --python 3.12 --with django==6.1.2 --with djangorestframework==3.18.3 python manage.py makemigrations notes
  • ✓ manage.py check: uv run --no-project --python 3.12 --with django==6.1.2 --with djangorestframework==3.18.3 python manage.py check
  • ✓ makemigrations --check --dry-run: uv run --no-project --python 3.12 --with django==6.1.2 --with djangorestframework==3.18.3 python manage.py makemigrations --check --dry-run
  • ✓ API tests: uv run --no-project --python 3.12 --with django==6.1.2 --with djangorestframework==3.18.3 python manage.py test notes -v 1
  • ✓ no __all__ serializers or AllowAny: bash -c ! grep -nE '__all__|AllowAny|exclude\s*=' notes/*.py && echo clean
  • ✓ query-count test catches a missing select_related: bash -c sed -i 's/.select_related("owner")//' notes/views.py && uv run --no-project --python 3.12 --with django==6.1.2 --with djangorestframework==3.18.3 python manage.py test notes; test $? -ne 0 && echo caught
  • Covers: IsAuthenticated default; queryset scoped to request.user so other users get 404
  • Covers: Owner set in perform_create, ignoring the request body
  • Covers: Explicit serializer fields with field-level validation
  • Covers: Paginated list with a flat query count proven by assertNumQueries

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/drf-endpoint/SKILL.md https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/skills/drf-endpoint/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/drf-endpoint/references/serializers.py https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/skills/drf-endpoint/references/serializers.py
curl -fsSL --create-dirs -o .claude/skills/drf-endpoint/references/views.py https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/skills/drf-endpoint/references/views.py
curl -fsSL --create-dirs -o .claude/skills/drf-endpoint/references/urls.py https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/skills/drf-endpoint/references/urls.py
curl -fsSL --create-dirs -o .claude/skills/drf-endpoint/references/tests.py https://loadout.jnw-it.com/raw/django-rest-api/1.0.0/skills/drf-endpoint/references/tests.py
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/drf-endpoint/SKILL.md.claude/skills/drf-endpoint/SKILL.md
skills/drf-endpoint/references/serializers.py.claude/skills/drf-endpoint/references/serializers.py
skills/drf-endpoint/references/views.py.claude/skills/drf-endpoint/references/views.py
skills/drf-endpoint/references/urls.py.claude/skills/drf-endpoint/references/urls.py
skills/drf-endpoint/references/tests.py.claude/skills/drf-endpoint/references/tests.py

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for a Django REST Framework API: security defaults, serializers, queries and tests. 37 lines
Download Raw
# AGENTS.md: Django REST Framework API

Django project serving a JSON API with Django REST Framework (DRF). Check `pyproject.toml` or `uv.lock` for the Django
and DRF versions before using newer APIs.

## Commands
- Tests: `uv run python manage.py test` (or `uv run pytest` if pytest-django is set up).
- Checks: `uv run python manage.py check` and `uv run python manage.py makemigrations --check --dry-run`.
- Done means: tests green, no missing migrations, every new endpoint covered by the tests listed below.

## Security defaults
- `DEFAULT_PERMISSION_CLASSES` is `IsAuthenticated`. Never add `AllowAny` to an endpoint without the user asking.
- Scope data in `get_queryset()` to the requesting user or tenant: `Note.objects.filter(owner=self.request.user)`.
  Another user's object is then a 404, not a 403 that confirms it exists.
- Set ownership on the server: `perform_create(self, serializer): serializer.save(owner=self.request.user)`. Never
  trust an owner or tenant field from the request body.
- Throttle anonymous and costly endpoints (`DEFAULT_THROTTLE_CLASSES` or per-view `throttle_classes`).

## Serializers
- List `fields` explicitly. Never `fields = "__all__"` or `exclude`: a new column must not leak into the API.
- Mark server-set fields `read_only_fields`. Validate in `validate_<field>` or `validate()`, not in views.
- Nested writes only when asked; prefer separate endpoints.

## Views and queries
- `ModelViewSet` or generic views with a router; keep views thin.
- Paginate every list endpoint (`DEFAULT_PAGINATION_CLASS`).
- `select_related` / `prefetch_related` in `get_queryset()` for every relation the serializer reads. Prove it with
  `assertNumQueries` in a test.

## Tests for every endpoint
- Anonymous request rejected; owner can do the action; lists show only the user's rows; another user gets 404;
  invalid input returns 400 naming the field; list query count stays flat as rows grow. Use the `drf-endpoint` skill.
- `APITestCase` with `force_authenticate`. Test passwords and data are obviously fake.

## Secrets and settings
- `SECRET_KEY`, database URLs and API keys come from the environment. `DEBUG` defaults to off.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `drf-endpoint`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Django REST Framework API rules" v1.0.0 — https://loadout.jnw-it.com/loadouts/django-rest-api/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `drf-endpoint`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "Django REST Framework API rules" v1.0.0 — https://loadout.jnw-it.com/loadouts/django-rest-api/ -->
Skill skills/drf-endpoint/SKILL.md Add an endpoint with the six tests it needs. 25 lines
Download Raw
---
name: drf-endpoint
description: Add a Django REST Framework endpoint with an explicit serializer, a user-scoped queryset, server-set ownership, pagination and the six tests every endpoint needs.
---

# Add a DRF endpoint

Examples: `references/serializers.py`, `references/views.py`, `references/urls.py`, `references/tests.py`.

1. Model first, if needed: add it, then `uv run python manage.py makemigrations` and check the migration.
2. Serializer: explicit `fields`, server-set fields in `read_only_fields`, validation in `validate_<field>`.
3. View: `ModelViewSet` (or a narrower generic view) with:
   - `get_queryset()` filtered to `self.request.user`, with `select_related` for relations the serializer reads
   - `perform_create()` that sets the owner
   - permissions no weaker than the project default
4. Route it with a `DefaultRouter` and `basename`.
5. Tests (`APITestCase`):
   - anonymous is rejected (401 or 403, depending on the authentication class)
   - create sets the owner from the request, ignoring an owner in the body
   - list shows only the user's own rows
   - another user's object is 404 for read, update and delete
   - invalid input is a 400 that names the field
   - `assertNumQueries` on the list endpoint with several rows
6. Run `uv run python manage.py test`, `check` and `makemigrations --check --dry-run`.
Skill file skills/drf-endpoint/references/serializers.py Explicit serializer with validation. 20 lines
Download Raw
from rest_framework import serializers

from notes.models import Note


class NoteSerializer(serializers.ModelSerializer):
    # Explicit fields: a new model column never leaks into the API by accident.
    owner = serializers.CharField(source="owner.username", read_only=True)

    class Meta:
        model = Note
        fields = ["id", "title", "body", "owner", "created_at"]
        read_only_fields = ["id", "owner", "created_at"]

    def validate_title(self, value: str) -> str:
        value = value.strip()
        if len(value) < 3:
            raise serializers.ValidationError("Title must be at least 3 characters.")
        return value
Skill file skills/drf-endpoint/references/views.py User-scoped viewset. 18 lines
Download Raw
from rest_framework import permissions, viewsets

from notes.models import Note
from notes.serializers import NoteSerializer


class NoteViewSet(viewsets.ModelViewSet):
    """Notes belong to one user. Every query is scoped to request.user, so other users' notes are a 404."""

    serializer_class = NoteSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_queryset(self):
        return Note.objects.filter(owner=self.request.user).select_related("owner").order_by("-created_at", "-id")

    def perform_create(self, serializer):
        serializer.save(owner=self.request.user)
Skill file skills/drf-endpoint/references/urls.py Router registration. 9 lines
Download Raw
from rest_framework.routers import DefaultRouter

from notes.views import NoteViewSet

router = DefaultRouter()
router.register("notes", NoteViewSet, basename="note")

urlpatterns = router.urls
Skill file skills/drf-endpoint/references/tests.py API tests for auth, ownership, validation and queries. 54 lines
Download Raw
from django.contrib.auth import get_user_model
from rest_framework import status
from rest_framework.test import APITestCase

from notes.models import Note

User = get_user_model()


class NoteApiTests(APITestCase):
    def setUp(self):
        self.ada = User.objects.create_user("ada", password="test-only-pass")
        self.bob = User.objects.create_user("bob", password="test-only-pass")
        self.bobs_note = Note.objects.create(owner=self.bob, title="Bob's plan", body="private")

    def test_anonymous_requests_are_rejected(self):
        response = self.client.get("/api/notes/")
        self.assertIn(response.status_code, {status.HTTP_401_UNAUTHORIZED, status.HTTP_403_FORBIDDEN})

    def test_create_sets_owner_from_the_request_not_the_body(self):
        self.client.force_authenticate(self.ada)
        response = self.client.post("/api/notes/", {"title": "  Shopping ", "body": "milk", "owner": "bob"})
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data["owner"], "ada")
        self.assertEqual(response.data["title"], "Shopping")

    def test_list_only_shows_own_notes(self):
        Note.objects.create(owner=self.ada, title="Ada's note", body="")
        self.client.force_authenticate(self.ada)
        response = self.client.get("/api/notes/")
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual([n["title"] for n in response.data["results"]], ["Ada's note"])

    def test_other_users_note_is_not_found(self):
        self.client.force_authenticate(self.ada)
        for method in ("get", "patch", "delete"):
            response = getattr(self.client, method)(f"/api/notes/{self.bobs_note.pk}/")
            self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND, method)
        self.assertTrue(Note.objects.filter(pk=self.bobs_note.pk).exists())

    def test_validation_errors_name_the_field(self):
        self.client.force_authenticate(self.ada)
        response = self.client.post("/api/notes/", {"title": "x", "body": ""})
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("title", response.data)

    def test_list_query_count_does_not_grow_with_rows(self):
        for i in range(5):
            Note.objects.create(owner=self.ada, title=f"Note {i}", body="")
        self.client.force_authenticate(self.ada)
        # One count query for pagination, one select with the owner joined.
        with self.assertNumQueries(2):
            self.client.get("/api/notes/")

All files: source zip · Licence MIT · By Agent Loadout · First listed

Installing and testing

How do I install Django REST Framework API rules in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/drf-endpoint/SKILL.md at .claude/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .claude/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .claude/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .claude/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .claude/skills/drf-endpoint/references/tests.py. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Django REST Framework API rules in Codex CLI?

From your project root, put AGENTS.md, skills/drf-endpoint/SKILL.md at .agents/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .agents/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .agents/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .agents/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .agents/skills/drf-endpoint/references/tests.py. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Django REST Framework API rules in Cursor?

From your project root, put AGENTS.md, skills/drf-endpoint/SKILL.md at .agents/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .agents/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .agents/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .agents/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .agents/skills/drf-endpoint/references/tests.py. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Django REST Framework API rules in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/drf-endpoint/SKILL.md at .agents/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .agents/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .agents/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .agents/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .agents/skills/drf-endpoint/references/tests.py. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Django REST Framework API rules in Antigravity CLI?

From your project root, put AGENTS.md, skills/drf-endpoint/SKILL.md at .agents/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .agents/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .agents/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .agents/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .agents/skills/drf-endpoint/references/tests.py. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Django REST Framework API rules in GitHub Copilot?

From your project root, put AGENTS.md, skills/drf-endpoint/SKILL.md at .agents/skills/drf-endpoint/SKILL.md, skills/drf-endpoint/references/serializers.py at .agents/skills/drf-endpoint/references/serializers.py, skills/drf-endpoint/references/views.py at .agents/skills/drf-endpoint/references/views.py, skills/drf-endpoint/references/urls.py at .agents/skills/drf-endpoint/references/urls.py, skills/drf-endpoint/references/tests.py at .agents/skills/drf-endpoint/references/tests.py. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/django-rest-api/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has Django REST Framework API rules been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.