Python + Django AI Rules

Rules for Django projects: app structure, models-first design, DRF serializer conventions, migrations workflow, and signal usage. Stops the AI from inventing fictional Django APIs.

PythonDjango#python#django#drf#ormLast updated 2026-05-05
tune

Want to customize this rules file? Open the generator with this stack pre-loaded.

Open in generatorarrow_forward

Save at .cursor/rules/main.mdc

Python + Django + DRF

Project context

This is a Django 5+ project with Django REST Framework for the API layer. We follow Django conventions ruthlessly — convention over configuration is a feature, not a constraint. Postgres for the DB. Production target.

Stack

  • Python 3.12+
  • Django 5+
  • Django REST Framework 3.15+
  • Postgres 15+ (psycopg3 driver)
  • uv for package management
  • pytest + pytest-django for tests
  • Ruff for lint + format

Folder structure

Follow Django app conventions. One app per bounded domain (e.g., users, billing, posts).

project/
  settings/
    base.py
    dev.py
    prod.py
  urls.py
  wsgi.py
  asgi.py
apps/
  users/
    models.py
    serializers.py
    views.py
    urls.py
    admin.py
    tests/
    migrations/
  posts/
    ...

Settings split: base.py for shared, dev.py and prod.py import from base and override.

Models

  • One concern per model
  • Always set Meta.ordering if the model is queried unordered
  • Use db_index=True on fields used in filter() or order_by()
  • Use null=True only when the absence is meaningful — for strings, prefer default=""
  • Custom Manager for non-trivial query sets; QuerySet.as_manager() for chainable methods
class PostQuerySet(models.QuerySet):
    def published(self):
        return self.filter(published_at__isnull=False)

class Post(models.Model):
    title = models.CharField(max_length=200)
    body = models.TextField()
    published_at = models.DateTimeField(null=True, blank=True)
    objects = PostQuerySet.as_manager()

    class Meta:
        ordering = ["-published_at"]
        indexes = [models.Index(fields=["published_at"])]

Migrations

  • python manage.py makemigrations — generate
  • Review the SQL: python manage.py sqlmigrate <app> <migration>
  • Never edit a committed migration; create a new one
  • For data migrations, use RunPython with explicit forwards and reverse
  • migrate --plan before deploying to verify what will run

DRF serializers

  • Separate read and write serializers when they diverge
  • Use serializers.ModelSerializer for trivial CRUD; switch to Serializer when logic gets complex
  • Validate at the serializer (validate_<field>, validate); never re-validate in the view
class PostSerializer(serializers.ModelSerializer):
    class Meta:
        model = Post
        fields = ["id", "title", "body", "published_at"]
        read_only_fields = ["id", "published_at"]

    def validate_title(self, value):
        if len(value) < 3:
            raise serializers.ValidationError("Title too short.")
        return value

DRF views

  • ViewSets for resource APIs (ModelViewSet when you want all 5 actions)
  • Generic views (ListAPIView, RetrieveAPIView) when you only want some actions
  • APIView for non-CRUD endpoints
  • Always declare permission_classes explicitly — never rely on defaults
  • Always declare pagination_class for list endpoints

URLs

  • Use DRF routers for ViewSets
  • Use path() for non-router endpoints
  • Namespace every app's URLs (app_name = "posts")

Authentication

  • DRF's SessionAuthentication for browser-based usage
  • TokenAuthentication or JWT (djangorestframework-simplejwt) for API clients
  • Custom permission classes go in <app>/permissions.py

Patterns to follow

  • Fat models, thin views, skinny controllers. Business logic on the model or its manager.
  • select_related / prefetch_related in any list view that touches relations. The Django Debug Toolbar will flag N+1.
  • @transaction.atomic around any multi-step write
  • get_object_or_404 instead of try/except for "not found" cases
  • Signals sparingly — they make the code path implicit. Prefer explicit calls.

Patterns to avoid

  • null=True on CharField / TextField — use default=""
  • auto_now_add + auto_now without editable=False — they leak into admin / forms
  • Editing a committed migration — always create a new one
  • Catching Exception broadly — be specific
  • Raw SQL unless absolutely necessary — and then with params, never string formatting
  • Using DRF's BrowsableAPIRenderer in production — it leaks views' Python objects

Testing

  • pytest-django with @pytest.mark.django_db
  • Use factory_boy for model factories
  • Use APIClient for DRF endpoint tests
  • One test file per module; one test per behavior
@pytest.mark.django_db
def test_create_post(client, user):
    client.force_authenticate(user)
    res = client.post("/api/posts/", {"title": "Hi", "body": "..."})
    assert res.status_code == 201

Tooling

  • python manage.py runserver — dev server
  • python manage.py makemigrations && migrate — schema changes
  • python manage.py shell_plus (django-extensions) — shell with auto-imports
  • pytest — tests
  • ruff check && ruff format — lint + format

AI behavioral rules

  • Don't invent Django APIs — many model methods sound right but don't exist (Model.save_async, objects.find). Verify against current Django docs.
  • Always run makemigrations after editing a model; never skip
  • Never edit a committed migration
  • Use select_related / prefetch_related whenever a list view touches a foreign key
  • Validate inputs in the serializer, not the view
  • Always set permission_classes and pagination_class explicitly
  • Run pytest, ruff check, and python manage.py check before declaring done

Frequently asked

How do I use this Django rules file with Cursor?

Pick "Cursor (.cursor/rules/*.mdc)" from the format dropdown above and click Copy. Save it at .cursor/rules/main.mdc in your project root and restart Cursor. The legacy .cursorrules format still works if you're on an older Cursor version — pick that option instead.

Can I use this with Claude Code (CLAUDE.md)?

Yes — pick "Claude Code (CLAUDE.md)" from the format dropdown above and copy. Save the file as CLAUDE.md at your repo root. Claude Code reads it automatically on every session. For monorepos, you can also drop nested CLAUDE.md files in subdirectories — Claude merges them when working in those paths.

Where exactly do I put this file?

It depends on the AI tool. Cursor reads .cursorrules or .cursor/rules/*.mdc at the project root. Claude reads CLAUDE.md at the project root. Copilot reads .github/copilot-instructions.md. The "Save at" path under each format in the dropdown shows the exact location for the format you picked.

Can I customize these Django rules for my project?

Yes — that's what the generator is for. Click "Open in generator" above and the wizard loads with this stack's defaults pre-selected. Toggle on or off the conventions you want, then re-export in your AI tool's format.

Will using this rules file slow down my AI tool?

No. Rules files count toward the model's context window but not toward latency in any noticeable way. The file is loaded once per session, not per token. The library files target 250–400 lines, well within every tool's recommended budget.

Should I commit this file to git?

Yes. The rules file is project documentation that benefits every developer using the AI tool. Commit it. The exception is personal-global settings (e.g. ~/.claude/CLAUDE.md) which are user-scoped and stay out of the repo.

Related stacks