DEV Community

Cover image for Using Claude Code in a Django Workflow: What Actually Works
Heitor Vasconcelos
Heitor Vasconcelos

Posted on

Using Claude Code in a Django Workflow: What Actually Works

Most AI coding tools fall apart on real Django projects. Not because they can't write Python, but because Django projects have structure that generic LLMs don't know about: your specific app layout, which managers exist, how your serializers inherit from a base class you wrote six months ago, and the fact that nobody should touch the payments app without running the full test suite first.

Claude Code handles this differently. It reads your project before it writes anything. But "differently" does not mean "correctly by default." The workflow matters as much as the tool.

This article covers what actually works when using Claude Code on a Django project: how to set up context properly, what tasks are worth delegating, how to structure sessions to avoid entropy, and where the tool will quietly make mistakes if you let it.


CLAUDE.md as a Contract

Claude Code reads a CLAUDE.md file at the root of your project before doing anything else. Most tutorials treat this as optional configuration. It is not. For a Django project, this file is where you prevent the model from guessing.

The file serves one purpose: telling Claude Code what it cannot infer from the code alone. That includes your conventions, your constraints, and the things that would take a new engineer two weeks to learn by breaking them.

Here is a minimal but useful CLAUDE.md for a DRF and Celery project:

# Project Context

## Structure
- Core business logic lives in `apps/`. Each app is self-contained.
- Shared utilities go in `core/`. Never import from one app into another directly.
- All serializers inherit from `core.serializers.BaseModelSerializer`.
- Celery tasks are defined in `tasks.py` inside each app, not in views.

## Conventions
- Use `select_related` and `prefetch_related` explicitly. Never rely on lazy loading in views.
- All API views inherit from `core.views.BaseAPIView`, which handles error formatting.
- Migrations must be generated with `python manage.py makemigrations --check` before committing.

## Commands
- Run tests: `pytest apps/ -x --reuse-db`
- Lint: `ruff check . && mypy apps/`
- Never run `manage.py migrate` directly. Use the deployment script.

## Off-limits
- Do not modify anything in `apps/payments/` without explicit instruction.
- Do not touch `settings/production.py`.
Enter fullscreen mode Exit fullscreen mode

Keep it short. The model reads the whole thing on every session start, so a 400-line CLAUDE.md with duplicated information is worse than a focused 40-line one. Write it for a competent engineer who needs project-specific context, not for someone who needs Django explained to them.

One opinion worth having: the Off-limits section is the most important part. Claude Code will helpfully refactor things it was not asked to refactor. Specifying what is untouchable is not paranoia, it is good engineering hygiene.


What to Delegate and What Not To

Claude Code is genuinely good at a specific category of Django tasks: things that are structurally predictable but tedious to type out correctly.

Delegate these:

  • Serializers for existing models, especially when the model has many fields or nested relations. The model writes the correct Meta, handles read_only_fields, and gets source= right on renamed fields.
  • ViewSet boilerplate with filterset_fields, search_fields, and ordering_fields.
  • Management commands. The structure is always the same; the logic is what varies.
  • Unit tests for views and serializers when you describe the expected behavior. The model writes the setup and the assertions; you verify the logic matches your domain.
  • Simple migrations after schema changes you have already made to the model.

Do not delegate these:

Schema decisions. If you ask Claude Code to "add a table for tracking user activity," it will produce something that works, but the schema it chooses will reflect generic best practices, not your domain. What belongs in a UserActivity row depends on how your system defines an "activity," what your reporting queries need to aggregate, and whether you have already solved this in a different table that the model does not know is related. That context does not live in the codebase; it lives in your head.

Celery task design involving side effects. The model will write a task that looks correct. If the task involves conditional branching based on external state, retries with backoff, or chaining with chord or chain, verify carefully. The model has no way to know whether your broker is configured to acknowledge tasks before or after execution, and that changes what "retry" means in practice.

Anything that depends on the current state of the database. If you are asking for a data migration or a queryset that depends on production data characteristics, you need to write that yourself or provide explicit counts and distributions. The model will write something that is logically valid but potentially catastrophic on a table with 20 million rows.


Structuring Sessions

The failure mode that nobody talks about: sessions that run too long. Claude Code maintains a context window, and as that window fills with tool calls, file reads, and intermediate outputs, the model starts optimizing for the immediate task rather than the overall constraints you set at the start.

A session that starts with "remember to always use select_related" and runs for 90 minutes will, somewhere around minute 60, generate a queryset without it. Not because the model forgot, but because the instruction is now far enough back in the context that it competes with everything else.

The practical fix is to treat each session as having a single objective, stated before you start.

Before starting a session, define:

  1. What you are building (one sentence)
  2. Which files will be modified
  3. What done looks like (the test that passes, the endpoint that responds correctly)

For a complete endpoint, a session might look like this:

Goal: Add a GET /api/v1/reports/{report_id}/summary/ endpoint.
Files in scope: apps/reports/models.py (read only), apps/reports/serializers.py, 
apps/reports/views.py, apps/reports/urls.py, apps/reports/tests/test_views.py
Done when: test_report_summary_returns_correct_totals passes.
Enter fullscreen mode Exit fullscreen mode

With that framing, the session stays bounded. The model is less likely to wander into refactoring apps/reports/tasks.py because it noticed something while reading the file.

Use /compact when the session has accomplished something but still has work left. Running /compact mid-session compresses the earlier context while preserving the active state. The right time to use it is after a subtask is complete: after the model has written the serializer and you have approved it, compact before moving to the view. Not before; you lose the reasoning that produced the serializer.

One thing /compact does not preserve well: negative constraints. "Don't use annotate here because of a known PostgreSQL planner issue on our table" will survive a compact less reliably than positive instructions. Re-state constraints explicitly after compacting if they are critical.


Common Pitfalls

Migrations generated without --check

Claude Code will run makemigrations when you ask it to, but it defaults to the basic form. If you have not added --check to your CLAUDE.md commands section, the model will generate migrations that you have not reviewed. Worse, if the project has MIGRATION_MODULES overrides or custom migration directories, the model may generate the migration in the wrong location. Specify the exact command in CLAUDE.md and the model will use it.

Imports from paths that do not exist in your project

The model infers import paths from patterns it sees in the codebase. If your project has an inconsistent import style (some files use relative imports, some use absolute), the model will produce a mix, and occasionally one will be wrong. This is more common in projects that went through a restructuring and have legacy import patterns still in the codebase. mypy catches these; run it as part of your session-end check.

Unrequested rewrites

Ask Claude Code to fix a bug in a view and there is a reasonable chance it will also rename a variable, reorder the methods, and add a docstring you did not ask for. This is not always wrong, but it creates noisy diffs that make code review harder. Add a line to your CLAUDE.md like Make the minimum change necessary to complete the task. It helps. It does not eliminate the behavior entirely, so reviewing the diff before staging remains your responsibility.


What Actually Changes

The speed improvement on isolated tasks is real but not the point. Where Claude Code changes the workflow is on tasks that span multiple files and require holding several constraints in mind simultaneously.

Writing an endpoint from scratch involves the model, the serializer, the view, the URL configuration, and the test. Doing all of that without losing track of a constraint you set at the start (say, "this endpoint must be paginated using the existing ReportPagination class") is where the tool earns its place in the workflow.

The practical next step: pick one recurring task in your project that is tedious but structurally consistent, write a tight CLAUDE.md entry for it, and run a single bounded session. See what comes out before deciding how much of the workflow to change.


Have you integrated Claude Code into a Django project? What broke first? Let me know in the comments.

Top comments (0)