Django and Django REST Framework rules for AI coding agents

Django code generated by an agent usually works on a laptop with ten rows and falls over with real data: a template loop that queries per row, a view that fetches by id and checks ownership afterwards, a migration edited after it ran somewhere shared. The Django rules cover models, querysets, migrations, views and DRF serializers on a supported Django release, with or without REST Framework.

They ask the agent to read settings and the installed apps before assuming a feature exists, to give every ForeignKey a deliberate on_delete, and to filter by ownership in get_queryset(). They also note the current limits of the async ORM around transactions and point the agent at python manage.py check --deploy before a release.

Folder: rules/django/ · django.mdc, CLAUDE.md, AGENTS.md

What the Django rules tell your agent

First rule of each section in django.mdc, quoted as shipped.

Mistakes these rules are written to prevent

Source: “Pitfalls to avoid”.

Checks the agent runs before it says done

Example commands from the Django file (swap in your repo's own):

Run python manage.py check, makemigrations --check, the linter and the test suite, and report each result.

Before and after: Django: hunt N+1 queries and pin the fix with a test

Prompt card 13 · Stack: Django, optionally Django REST Framework · Type: Performance refactor · Best model: a frontier reasoning model

What the card is for

Find the relations touched per row in a view, serializer or template, fix them with select_related, prefetch_related or annotations, and add an assertNumQueries test so the problem cannot return quietly.

Worked example from the card

Before

class OrderSerializer(serializers.ModelSerializer):
    customer_name = serializers.CharField(source="customer.name")
    item_count = serializers.SerializerMethodField()
    class Meta:
        model = Order
        fields = ["id", "customer_name", "item_count", "total_cents"]
    def get_item_count(self, obj):
        return obj.items.count()

class OrderViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = OrderSerializer
    def get_queryset(self):
        return Order.objects.filter(account=self.request.user.account)

Expected AI output (abridged)

Per row: customer.name -> 1 query (FK), items.count() -> 1 COUNT query.
For a page of 50: 1 (count for pagination) + 1 (orders) + 50 + 50 = 102 queries.
Fix: select_related("customer"), annotate(item_count=Count("items")), and read the
annotation in the serializer. Expected: 2 queries (pagination count + orders join).

After

class OrderSerializer(serializers.ModelSerializer):
    customer_name = serializers.CharField(source="customer.name")
    item_count = serializers.IntegerField(read_only=True)
    class Meta:
        model = Order
        fields = ["id", "customer_name", "item_count", "total_cents"]

class OrderViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = OrderSerializer
    def get_queryset(self):
        return (Order.objects.filter(account=self.request.user.account)
                .select_related("customer")
                .annotate(item_count=Count("items"))
                .order_by("-created_at"))
@pytest.mark.django_db
def test_order_list_query_count(api_client, user, django_assert_num_queries):
    for _ in range(3):
        OrderFactory(account=user.account, items=2)
    api_client.force_authenticate(user)
    with django_assert_num_queries(2):  # pagination COUNT + orders with customer join
        resp = api_client.get("/api/orders/")
    assert resp.status_code == 200

Excerpt from card 13; the prompt itself is in the pack.

More Django prompt cards in the pack

Using these rules in Cursor, Claude Code and AGENTS.md tools

Cursor

Put _base.mdc and django.mdc in .cursor/rules/. Globs: manage.py, **/*.py, **/templates/**/*.html.

Claude Code

Root CLAUDE.md, base first. Path-scoped variant .claude/rules/django.md for **/*.py, **/templates/**/*.html; PostToolUse hook: ruff format <file>.

AGENTS.md tools (Codex, Gemini CLI, Aider)

Base then stack in one AGENTS.md. Gemini CLI needs context.fileName; Aider needs --read AGENTS.md. Put this file in the directory that holds manage.py. Agents use the AGENTS.md nearest to the file being edited, so a nested copy overrides a root one for that subtree.

Related stacks

← All stacks