Claude Code with Django: The Complete Backend Workflow
Why Django needs more than a generic Claude Code setup
Claude Code understands Django. It knows manage.py, QuerySet, ModelForm, serializers, and the migration system. The problem is that this generic knowledge does not translate to your project without configuration. Without a project-specific CLAUDE.md, Claude generates models without your naming conventions, writes migrations it was not asked to write, and queries the ORM in ways that are technically correct but produce N+1 queries in your specific schema.
Django projects also have sharp edges that a generic AI setup tends to hit: migration ordering, signal handlers that fire unexpectedly during tests, select_related versus prefetch_related decisions that depend on your actual data volume, and DRF permission classes that differ across endpoints.
This guide covers the configuration layer that removes those failure modes. If you have not set up Claude Code at all yet, the Claude Code setup guide covers installation and authentication before any of this applies.
The Django CLAUDE.md
The CLAUDE.md at your project root is read before every Claude Code session. For Django, it needs to answer six questions: how is the project structured, which database is in use, how are tests run, what is the migration policy, how is the ORM used, and what are the hard rules?
# Django project rules
## Project layout
- Project root: `myproject/`
- Apps: `apps/users/`, `apps/payments/`, `apps/notifications/`
- Settings: `config/settings/` with base.py, local.py, production.py
- Static: `static/` (collected to `staticfiles/` in production)
- Templates: `templates/` at project root, not per-app
## Database
- Engine: PostgreSQL 15 (local: Docker on port 5432)
- ORM only. No raw SQL except in custom managers with explicit justification.
- Always use `select_related()` for ForeignKey traversals in list views
- Use `prefetch_related()` for ManyToMany and reverse FK
- QuerySet lazy evaluation: never evaluate in model methods called from views
## Running the project
- `python manage.py runserver` (settings: `DJANGO_SETTINGS_MODULE=config.settings.local`)
- `export DJANGO_SETTINGS_MODULE=config.settings.local` before all manage.py commands
## Tests
- Framework: pytest with pytest-django
- Run: `pytest` from project root
- Config in `pytest.ini` (`DJANGO_SETTINGS_MODULE` set to test settings automatically)
- Factories: Factory Boy (`factories.py` in each app)
- Fixtures: only for static reference data, never for user data
- New tests go in `tests/` within each app, not a top-level tests/ directory
## Migrations
- Never create a migration unless explicitly asked
- Never apply a migration during development work without saying so first
- When creating migrations: `python manage.py makemigrations --name <descriptive_name>`
- Always check for unapplied migrations before writing new model code: `python manage.py showmigrations`
## Hard rules
- No print() in production code. Use `logging.getLogger(__name__)`.
- No bare except. Catch specific exceptions.
- No direct imports from `django.conf.settings` in tests. Use `override_settings` decorator.
- Signal handlers in `apps/{app}/signals.py`, connected in `AppConfig.ready()`.
Two sections are particularly important for avoiding the most common Claude Code problems with Django.
The migrations policy prevents Claude from creating migrations mid-task without telling you. Claude Code has a habit of running makemigrations proactively when it modifies a model. In development that is usually harmless. In a team environment where migrations need review before commit, it creates noise. The explicit rule stops this.
The ORM rules prevent N+1 query generation. Without guidance, Claude will write correct but unoptimized querysets. A list view that calls order.customer.name for each item generates one query per row. The rule makes select_related the default for FK traversals.
Model and migration workflows
The typical model workflow with Claude Code follows three phases: schema design, model code, and migration.
Phase one: schema design. Before writing any code, have Claude review the schema. The prompt that works:
"We need to add a Subscription model to the payments app. It should track: user (FK to users.User), plan (CharField with choices), status (CharField with choices: active, cancelled, paused), start_date, end_date (nullable), and stripe_subscription_id. Before writing any code, describe the model fields, any indexes we should add, and whether this should use a custom manager."
Claude will propose a schema with reasoning. Review it before moving to code. This step costs one minute and prevents rewriting models because the field types were wrong.
Phase two: model code. Once the schema is agreed:
"Write the Subscription model in apps/payments/models.py. Follow our naming conventions from CLAUDE.md. Add __str__ and a custom manager method active_for_user(user) that returns active subscriptions. Do not create a migration yet."
Phase three: migration. Explicitly:
"Run python manage.py makemigrations payments --name add_subscription_model and show me the output. Do not apply it."
Review the migration file before applying. Django's migration system is deterministic but not always obvious when models have complex relationships. Checking before migrate is faster than debugging a bad migration in a staging environment.
For more on Claude Code and database work in general, the Claude Code database guide covers query optimization, MCP database server setup, and schema design patterns that apply across frameworks.
pytest-django integration
The most productive Django workflow with Claude Code runs pytest automatically after every code change. Claude should not consider a task complete until the test suite passes.
Add this to your CLAUDE.md:
## After every code change
1. Run `pytest apps/{changed_app}/tests/` for fast feedback
2. If fast tests pass, run `pytest` for the full suite
3. Do not mark a task complete if any test fails
4. Coverage: `pytest --cov=apps --cov-report=term-missing` when adding new functionality
The key difference from running pytest manually is the apps/{changed_app}/tests/ fast path. A full Django test suite can take minutes. Running only the affected app's tests first gives Claude fast feedback without waiting for unrelated apps.
Writing Django tests that Claude maintains well
Claude Code handles pytest-django cleanly when tests follow consistent patterns. Three patterns that work reliably:
Factory Boy over fixtures for model instances. Django test fixtures are brittle. Claude struggles to keep them consistent across model changes. Factory Boy lets Claude generate model instances inline with correct field values.
# factories.py in each app
import factory
from apps.payments.models import Subscription
class SubscriptionFactory(factory.django.DjangoModelFactory):
class Meta:
model = Subscription
user = factory.SubFactory("apps.users.tests.factories.UserFactory")
plan = "pro"
status = "active"
start_date = factory.LazyFunction(lambda: timezone.now().date())
@pytest.mark.django_db explicitly on every test function. Claude sometimes generates tests without the decorator. Add it to your CLAUDE.md hard rules: "Every test function that touches the database must have @pytest.mark.django_db. Never use class-level marks."
APIClient for view tests. For DRF endpoints, use rest_framework.test.APIClient. Claude knows it well. For regular Django views, use django.test.Client. Specify which in CLAUDE.md and Claude will not mix them.
The Claude Code testing guide covers the broader test-generation workflow, including coverage analysis and TDD loops that apply to any Python project.
Django REST Framework patterns
DRF has enough surface area that Claude Code needs explicit guidance to generate viewsets you would actually ship.
Add a DRF section to your CLAUDE.md:
## DRF rules
- Viewsets in `apps/{app}/api/views.py`
- Serializers in `apps/{app}/api/serializers.py`
- URL routing via DefaultRouter in `apps/{app}/api/urls.py`
- Use `ModelViewSet` only when all CRUD operations are needed. Otherwise use `GenericAPIView` with explicit mixins.
- Permission classes: always specify. Never rely on global defaults.
- Authentication: `JWTAuthentication` (djangorestframework-simplejwt)
- Serializer validation: use `validate_<field>` and `validate()` methods, not view-level validation
- Never return raw QuerySets. Always serialize before returning.
- Pagination class: `PageNumberPagination` with `page_size = 20`
The GenericAPIView with explicit mixins rule is important. ModelViewSet gives you all CRUD operations whether you want them or not. For most endpoints, you want a subset. Specifying the pattern makes Claude generate purpose-built views rather than over-permissioned ones.
The serializer depth problem. A common Claude Code failure with DRF is serializer depth. Claude will generate nested serializers that make N+1 queries because depth = 1 on a serializer triggers queries for each related object. Specify in CLAUDE.md:
- Nested serializers: use SerializerMethodField with select_related/prefetch_related
in the view's get_queryset(). Never use `depth` on ModelSerializer.
Handling signals and AppConfig
Django signals are where Claude Code most often generates code that looks correct but breaks in subtle ways. The two failure modes:
Connecting signals in the wrong place. Claude will sometimes connect signals at module level in models.py. This works in development but causes import order problems in production. The CLAUDE.md rule should be explicit: "Signal handlers are connected only in AppConfig.ready(), never at module level."
Forgetting dispatch_uid on signals. In Django's test runner, AppConfig.ready() can run multiple times in some configurations. Without dispatch_uid, signal handlers fire multiple times. Add to CLAUDE.md:
## Signals
- Handlers in `apps/{app}/signals.py`
- Connect in `AppConfig.ready()` with `dispatch_uid` parameter:
`post_save.connect(handler, sender=Model, dispatch_uid="unique_id")`
- Never connect signals at module level
With this in CLAUDE.md, Claude Code generates signal connections correctly without you catching the error in QA.
Permission hooks and settings security
Claude Code can be configured to treat certain management commands as dangerous and require explicit confirmation. For Django, the commands worth restricting are migrate, flush, loaddata, and anything that touches the database in production.
In .claude/settings.local.json:
{
"permissions": {
"allow": [
"Bash(python manage.py runserver*)",
"Bash(python manage.py makemigrations*)",
"Bash(python manage.py showmigrations*)",
"Bash(pytest*)",
"Bash(python manage.py shell*)"
],
"deny": [
"Bash(python manage.py migrate*)",
"Bash(python manage.py flush*)",
"Bash(python manage.py loaddata*)"
]
}
}
This setup means Claude can inspect migration state and create migrations freely, but applying them requires you to run the command yourself. The friction is intentional. Migrations in a team environment should be deliberate.
For a complete look at how Claude Code permission hooks work and what else you can gate behind them, the Claude Code hooks guide covers the full permission system.
What Django developers get wrong first
Three patterns come up consistently when Django developers start using Claude Code:
Not specifying the settings module. Claude generates manage.py commands without the settings module export. In a split-settings setup (base, local, production), every command fails silently or uses the wrong database. The fix: put export DJANGO_SETTINGS_MODULE=config.settings.local at the top of your CLAUDE.md environment section.
Letting Claude generate admin registrations without thinking. Claude Code will eagerly register models with admin.site.register(Model) when asked to add admin support. Django admin with default registration gives staff users full read/write access to every field. Specify: "Admin registrations use ModelAdmin subclasses with explicit fields, list_display, and readonly_fields. Never use bare admin.site.register()."
Using DEBUG=True environment assumptions in tests. Claude sometimes generates code that checks settings.DEBUG directly. Tests should always run with DEBUG=False (Django's test runner default). Using override_settings(DEBUG=True) in tests is a smell. Add the rule: "Test code never checks settings.DEBUG. Use explicit test-specific settings flags instead."
Getting more from your Django workflow
The CLAUDE.md configuration in this guide produces a Django workflow where migrations are never applied without intent, the ORM avoids common performance traps, and pytest-django runs automatically after every change. The deeper principle is the same as any Claude Code best practices guide: Claude Code performs as well as the context you give it.
A fully configured Django project with Claude Code becomes a genuine backend development partner: it understands your schema, respects your migration discipline, and generates DRF endpoints that match your project's conventions rather than its own defaults.
Start with the CLAUDE.md template above, add the pytest-django enforcement, and add the DRF section when you need it. Each layer compresses the review cycle and reduces the number of corrections you make before shipping.
More like this
Ready to upgrade your Claude Code setup?
Get Claudify