Rule Writing

CLAUDE.md for Django Projects

Django's 'batteries included' philosophy means the right way to do something already exists. AI reinvents it. Rules for ORM, views, forms, signals, and the admin.

8 min read·October 30, 2024

Django includes batteries — AI reinvents them from scratch every time

ORM, auth, forms, views, DRF, and the patterns Django already provides

Why Django Needs Rules That Say 'Use What Django Provides'

Django's philosophy is 'batteries included' — authentication, admin, ORM, forms, templates, caching, and email are built in. AI assistants ignore the batteries and reinvent them: raw SQL queries instead of the ORM, manual session handling instead of django.contrib.auth, hand-rolled form validation instead of Django Forms, and custom admin panels instead of django.contrib.admin.

The result is Django code that carries the weight of the framework (startup time, middleware stack, settings complexity) without using any of its features. It's like buying a Swiss Army knife and only using it as a paperweight.

These rules target Django 5+ with Python 3.12+. They cover core Django, Django REST Framework (DRF) for APIs, and django-ninja as an alternative. Specify which API approach your project uses.

Rule 1: Use the ORM for Everything

The rule: 'Use Django's ORM for all database operations. Never use raw SQL unless the ORM genuinely can't express the query (rare). Use QuerySet methods: filter(), exclude(), annotate(), aggregate(), select_related(), prefetch_related(). Use F() expressions for database-level operations. Use Q() objects for complex lookups. Use Subquery and OuterRef for subqueries.'

For N+1 prevention: 'Use select_related() for ForeignKey and OneToOne joins (SQL JOIN). Use prefetch_related() for ManyToMany and reverse ForeignKey (separate query + Python join). Use django-debug-toolbar in development to catch N+1 queries. Never access related objects in a loop without prefetching.'

For model design: 'Keep models focused: one model per concept. Use abstract base classes (class Meta: abstract = True) for shared fields. Use model managers for reusable QuerySets. Use model methods for instance-level logic. Use properties for computed fields that don't need database access. Use database constraints (unique_together, CheckConstraint) for data integrity.'

  • ORM for all queries — raw SQL only when ORM can't express it
  • select_related for FK/O2O — prefetch_related for M2M/reverse FK
  • F() for DB-level ops — Q() for complex lookups — Subquery for subqueries
  • Model managers for reusable QuerySets — model methods for instance logic
  • django-debug-toolbar in dev — catch N+1 before production
⚠️ Never Raw SQL

AI generates raw SQL in Django because it's 'simpler.' The ORM handles parameterization (SQL injection prevention), database portability, and migration tracking. Raw SQL bypasses all three. Use the ORM.

Rule 2: Views and URL Patterns

The rule: 'Use class-based views (CBVs) for standard patterns: ListView, DetailView, CreateView, UpdateView, DeleteView. Use function-based views (FBVs) for custom logic that doesn't fit a standard pattern. Use Django REST Framework ViewSets for API endpoints. Never mix CBVs and FBVs randomly — pick a convention and be consistent.'

For URL patterns: 'Use path() with converters: path("users/<int:pk>/", UserDetailView.as_view()). Use include() for app-level URL namespacing: path("api/", include("api.urls")). Use app_name for URL namespaces. Use reverse() for URL generation — never hardcode URLs. Use the {% url %} template tag for URLs in templates.'

For DRF: 'Use ViewSets with routers for CRUD APIs: router = DefaultRouter(); router.register("users", UserViewSet). Use serializers for input validation and output formatting. Use @action decorator for custom endpoints. Use pagination, filtering, and ordering from DRF — never implement manually.'

Rule 3: Authentication and Authorization

The rule: 'Use django.contrib.auth for authentication — never build custom auth. Use the built-in User model or extend with AbstractUser (not AbstractBaseUser unless you need to change the username field). Use @login_required for view protection. Use Django's permission system for authorization: has_perm(), permission_required. Use DRF's permission classes for API auth: IsAuthenticated, IsAdminUser, custom permissions.'

For passwords: 'Use Django's password hashing (PBKDF2 by default, configurable). Never hash passwords manually. Use django.contrib.auth.hashers for all password operations. Use the password validation framework (AUTH_PASSWORD_VALIDATORS) for password strength rules.'

For sessions: 'Use Django's session framework — never implement sessions manually. Configure SESSION_ENGINE for your backend (database, cache, file). Set SESSION_COOKIE_HTTPONLY = True, SESSION_COOKIE_SECURE = True, SESSION_COOKIE_SAMESITE = "Lax" in production.'

💡 Never Build Custom Auth

Django's auth system handles password hashing, session management, permissions, and CSRF protection. AI builds custom auth from scratch — introducing every vulnerability Django already prevents. Use django.contrib.auth.

Rule 4: Forms and Validation

The rule: 'Use Django Forms for HTML form handling: ModelForm for model-backed forms, Form for standalone forms. Use form.is_valid() for validation — never validate request.POST manually. Use clean_<field>() for field-level validation, clean() for cross-field validation. Use crispy-forms or widget_tweaks for form rendering. For APIs, use DRF serializers instead of forms.'

For model validation: 'Define validators on model fields: validators=[MinValueValidator(0)]. Use model clean() for cross-field validation. Use database constraints for integrity: models.UniqueConstraint, models.CheckConstraint. Validation runs at the form/serializer level — model validation is the last line of defense.'

AI assistants skip Django Forms entirely — parsing request.POST manually, validating with if-statements, and constructing SQL from form data. Forms provide CSRF protection, type coercion, validation, and error display — all for free.

Rule 5: Testing Django Applications

The rule: 'Use Django's TestCase for unit tests (wraps each test in a transaction). Use pytest-django for a better testing experience: @pytest.mark.django_db for database access. Use the test client (self.client or client fixture) for view tests. Use factory_boy for test data — never create objects with Model.objects.create() in every test.'

For API tests: 'Use DRF's APIClient for API endpoint tests. Test authentication: client.force_authenticate(user=user). Test status codes, response data, and database side effects. Test permission boundaries: verify unauthorized users get 403.'

For fixtures: 'Use factory_boy factories: UserFactory(), OrderFactory(user=user). Define factories once, use everywhere. Use faker for realistic test data. Use pytest fixtures for setup: @pytest.fixture def user(): return UserFactory(). Never use Django fixtures (JSON files) — they're brittle and hard to maintain.'

  • pytest-django: @pytest.mark.django_db — better fixtures and assertions
  • factory_boy for test data — never Model.objects.create() in every test
  • Test client for views — DRF APIClient for APIs — force_authenticate for auth
  • Test status codes, response data, DB side effects, and permission boundaries
  • pytest fixtures over Django JSON fixtures — maintainable, composable
ℹ️ factory_boy > Fixtures

Django JSON fixtures are brittle — one model change breaks every fixture file. factory_boy factories are code: UserFactory() creates a user with sensible defaults. Composable, maintainable, and readable.

Complete Django Rules Template

Consolidated rules for Django projects.

  • ORM for all DB: filter, annotate, select_related, prefetch_related — never raw SQL
  • CBVs for standard CRUD — FBVs for custom logic — DRF ViewSets for APIs
  • django.contrib.auth for auth — AbstractUser for customization — never custom auth
  • Django Forms for HTML forms — DRF serializers for APIs — never manual POST parsing
  • Validators on fields, clean() for cross-field, DB constraints for integrity
  • pytest-django + factory_boy — APIClient for DRF — test permissions explicitly
  • Settings: split into base/dev/prod — django-environ for env vars — never hardcoded secrets
  • django-debug-toolbar in dev — ruff for linting — black for formatting — isort for imports