Django
Django rules and best practices for Claude Code.
7.5k
5 TEMPLATES
Django + DRF Rules
# CLAUDE.md — Django + DRF ## Apps - One Django app per bounded context. Don't make a single `core` app that owns everything. - App layout: ``` myapp/ models.py serializers.py views.py # ViewSets / APIViews urls.py permissions.py services.py # business logic selectors.py # read-only queries tests/ ``` - Business logic lives in `services.py` (writes) and `selectors.py` (reads). Views call services. Models stay thin. ## Serializers - One serializer per output shape. Don't reuse the same serializer for list and detail when fields differ — add a `ListSerializer` and `DetailSerializer`. - Validate at the serializer (`validate_<field>`, `validate`). Don't validate inside views. - Use `source=` to map field names; don't rename in the model just to please an API contract. - For nested writes, override `create`/`update` explicitly. Don't rely on `WritableNestedSerializer` magic. ## ViewSets - `ModelViewSet` for full CRUD. `ReadOnlyModelViewSet` for read-only resources. Don't use generic mixins by hand when a ViewSet does it. - Override `get_queryset` to scope by user/tenant — the base `queryset` attribute should be the unscoped default. - Override `get_serializer_class` to switch between list/detail/write serializers. - Custom actions with `@action(detail=True/False, methods=[...])` — never overload PATCH for "do this thing". ## Permissions - Default to `[IsAuthenticated]` globally in `REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES']`. - Per-view permissions are subclasses of `BasePermission`. Combine with `&`, `|`, `~`. - Object-level checks go in `has_object_permission`. Don't duplicate them in `get_queryset` and pretend it's enough — it isn't for direct lookups. ## Pagination & filtering - Set `DEFAULT_PAGINATION_CLASS` and `PAGE_SIZE`. Never return unpaginated lists from production endpoints. - Use `django-filter` for query-string filters. Don't write `if request.GET.get(...)` chains. - For sort, use DRF's `OrderingFilter` with an explicit `ordering_fields` allowlist. ## Errors - Raise DRF exceptions (`NotFound`, `PermissionDenied`, `ValidationError`). Don't return `Response({'error': ...}, status=...)` from view code. - Custom exceptions inherit from `APIException` with `status_code` and `default_detail`. ## Don't - Don't put business logic in the serializer. Serializers shape and validate; services act. - Don't use `select_related` / `prefetch_related` only on the detail endpoint. Profile the list endpoint. - Don't expose internal field names (`is_deleted`, `internal_notes`) without filtering with serializer `fields` or `exclude`. - Don't disable CSRF on session-authenticated endpoints.Django Models + ORM Discipline
# CLAUDE.md — Django Models + ORM ## Model design - One model per concept. Don't pile unrelated fields onto a single `User` or `Order`. - Required fields have no default. `null=False, blank=False` is the default — keep it that way unless you mean otherwise. - `null=True` only on database NULL columns. For string fields, use `blank=True` and `default=''` instead. - Use `models.UUIDField(primary_key=True, default=uuid.uuid4)` when IDs leak to clients. Sequential IDs invite enumeration. - Always set `verbose_name` for fields users will see in admin or generated docs. ## Migrations - One migration per logical change. Never edit a migration after it's merged — add a new one. - `makemigrations --dry-run` before committing. Confirm the diff is what you expected. - Long-running data migrations go in a `RunPython` op with `atomic=False` and batched updates. Don't lock production for an hour. - Always provide a reverse migration where possible. `RunPython.noop` is fine when the change is one-way. ## Querysets - Use `select_related('fk_field')` for forward FKs and one-to-ones. `prefetch_related('reverse_set')` for reverse FKs and M2M. - Profile every list endpoint with `django-debug-toolbar` or `django-silk`. N+1 queries are the most common Django bug. - `only()` and `defer()` to fetch partial rows when the table is wide. - `iterator()` for batch reads that don't fit in memory. `bulk_create` and `bulk_update` for batch writes. ## Custom managers - Custom `Manager` for table-wide concerns (soft delete, tenant filtering). - Custom `QuerySet` for chainable filters: `objects = MyQuerySet.as_manager()`. - Encode invariants here, not in views. `Order.objects.unpaid()` is clearer than `Order.objects.filter(paid=False)` scattered everywhere. ## Indexes & constraints - Add `models.Index` for fields you filter or order by frequently. Define them in `Meta.indexes`. - `UniqueConstraint` for compound uniqueness — `unique_together` is deprecated. - `CheckConstraint` for invariants (e.g., `quantity__gte=0`). Better than a `clean()` method that runs sometimes. ## Soft delete - If you need it, override the default manager with `objects = SoftDeleteManager()` and add `all_objects = models.Manager()` for admin. - Add a `deleted_at: DateTimeField(null=True)`. Don't add a boolean — timestamps tell you when. - Don't soft-delete by default for everything. Most data should be hard-deleted. ## Don't - Don't raise inside `save()`. Validate at the form/serializer. - Don't put business logic in `pre_save` signals. They run in spooky places. Use explicit service functions. - Don't fetch a row, mutate it, and `save()` in a loop. Use `update()` or `bulk_update()`. - Don't disable `atomic_requests` to "fix" a deadlock. Find the actual lock contention.Django Testing with pytest
# CLAUDE.md — Django Testing with pytest ## Setup - Use **pytest** with **pytest-django**. Don't write `unittest`-style `TestCase` subclasses for new tests. - Config in `pyproject.toml`: ```toml [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "myproject.settings.test" python_files = ["tests.py", "test_*.py", "*_tests.py"] addopts = "--reuse-db --nomigrations" ``` - `--reuse-db` keeps the test DB across runs. `--nomigrations` skips migrations and uses syncdb-style schema creation — much faster. ## Fixtures - One `conftest.py` per app for app-local fixtures. One root `conftest.py` for project-wide fixtures. - Don't use Django's `fixtures` JSON. Build state with **factory_boy** (`factory.django.DjangoModelFactory`). - Factories live in `<app>/tests/factories.py`. One factory per model. Use `Faker` for realistic strings. ## Database access - Mark tests that hit the DB with `@pytest.mark.django_db`. Untouched code stays fast. - For complex transactional tests, use `@pytest.mark.django_db(transaction=True)` — but it's slower, so reach for it only when needed. - Each test gets a clean DB via transaction rollback. Don't write tests that depend on order. ## API tests - DRF tests use `APIClient` from a fixture. Pass `format='json'` explicitly. - Authentication: `client.force_authenticate(user=user)`. Don't fake auth via session middleware in tests. - Assert on status code, response shape, and side effects. A passing 200 with a wrong body is still a bug. ## Mocks - Use `pytest-mock` (`mocker.patch`) — cleaner than the bare `unittest.mock`. - Patch where the symbol is **used**, not where it's defined. - Don't mock the framework. If you find yourself mocking `Model.objects.filter`, the test is at the wrong level. ## Speed - Run tests in parallel with `pytest-xdist`: `pytest -n auto`. - Profile with `pytest --durations=10`. Anything over a second is suspicious. - Don't sleep in tests. Use `freezegun` or pass time as a parameter. ## Coverage - `pytest-cov` for coverage. Aim for high coverage on services and models; tests of view scaffolding rarely matter. - Don't chase 100% by testing trivial code. Cover the branches that hide bugs. ## Don't - Don't load fixtures from JSON files in 2025+. Use factories. - Don't share state between tests via module-level mutable variables. - Don't catch and swallow exceptions in tests to "make them pass". Fix the cause. - Don't run tests against a real external API. Mock at the boundary, integration-test in a separate stage.Django + Celery Background Jobs
# CLAUDE.md — Django + Celery Background Jobs ## Setup - One Celery app per Django project, defined in `<project>/celery.py`. Auto-discover tasks from installed apps. - Use **Redis** as broker for most workloads; RabbitMQ if you need priority queues, dead-letter handling out of the box, or strict ordering. - Result backend: only enable if you actually consume results. Otherwise skip — it costs writes. ## Task design - Tasks live in `<app>/tasks.py`. Decorate with `@shared_task` (not `@app.task`) so apps stay decoupled. - Tasks are **idempotent**. Retrying must be safe. If it can't be safe, the task is wrong. - Pass IDs, not objects. `process_invoice(invoice_id)` — never `process_invoice(invoice)`. Pickled model instances go stale. - Each task fetches what it needs via `Model.objects.get(...)`. If the row is gone, exit cleanly (`Model.DoesNotExist`). ## Retries - `@shared_task(bind=True, autoretry_for=(SomeError,), retry_backoff=True, max_retries=5)`. - Don't retry on validation failures — they'll never succeed. Retry on transient infra errors. - Set `acks_late=True` for tasks where work loss is unacceptable. Combine with `task_reject_on_worker_lost=True`. ## Scheduled jobs - Celery Beat owns the schedule. Define it in `CELERY_BEAT_SCHEDULE` in settings. - Don't run Beat in multiple places — pick one host and lock it. - For DB-backed schedules, use `django-celery-beat`. The admin UI is worth it on teams of more than two. ## Concurrency - One queue per workload class (`fast`, `slow`, `email`, `report`). A long task should never block a fast queue. - Set `worker_prefetch_multiplier=1` for slow tasks. The default of 4 starves long jobs. - Workers are stateless. Don't share global state across tasks. ## Observability - Send failure notifications via Sentry or your error tracker (`celery.signals.task_failure`). - Tag every log line with `task_id` so you can grep one execution end-to-end. - Health check the broker — if Redis dies, your jobs silently pile up. ## Local dev - `eager_mode = True` in tests. Tasks run inline in the calling thread. - For local dev, run `celery -A myproject worker -l info` in a separate terminal. Don't share the prod Redis. ## Don't - Don't store large payloads in task arguments. Push to S3/DB and pass the key. - Don't call tasks synchronously (`task.apply()`) from a request handler when you mean to queue. Use `task.delay()` or `task.apply_async()`. - Don't put long-running work in HTTP handlers, even if it "usually finishes in time". - Don't catch broad `Exception` and swallow it. Let Celery retry or fail the task.Django Deployment (Docker + gunicorn)
# CLAUDE.md — Django Deployment (Docker + gunicorn) ## Image layout - Two-stage Dockerfile: a builder stage that installs deps + collects static, and a runtime stage that copies the artifact. - Pin the Python image: `python:3.12-slim-bookworm`. Don't use `latest`. - Run as a non-root user: `RUN useradd -m app && USER app`. - One process per container. Beat, worker, and web run in separate containers. ## Dependencies - Lock with `uv` or `pip-tools`. Commit the lock file. - Install with `--no-cache-dir`. Cached wheels in the image bloat it. - System deps (libpq, libjpeg) installed in the builder stage only when needed for compilation. Strip from runtime. ## gunicorn - Workers: `2 × CPU + 1` for sync workloads. Override per-service after profiling. - Worker class: `gthread` for I/O-bound apps with reasonable thread counts. `sync` for pure CPU. Don't reach for `gevent` without measuring. - Bind to `0.0.0.0:8000` inside the container. Let the orchestrator handle external ports. - `--access-logfile -` and `--error-logfile -` to send logs to stdout/stderr. - Set `--timeout 30` and `--graceful-timeout 30`. Long-running work belongs in Celery, not the request thread. ## Static files - `python manage.py collectstatic --noinput` runs at build time. - Serve static via Nginx, S3, or Cloudfront — never gunicorn directly. - Use **WhiteNoise** if you have no CDN: `STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"`. ## Environment - 12-factor: every config value comes from env vars. No file-based secrets in the image. - Use `django-environ` or `pydantic-settings` to parse env in one place. - `DEBUG = False` in production. The default settings module is for prod; dev imports from it and overrides. ## Migrations - Migrations run as a separate one-shot container before the web container starts. Never on app boot — race condition with multi-replica deploys. - For zero-downtime deploys, schema changes are backward-compatible: add columns nullable first, deploy, then make NOT NULL in a follow-up release. ## Observability - Logs to stdout, JSON-formatted, captured by the orchestrator. - Health check endpoint that hits the DB and cache. Don't return 200 from a static route. - `SENTRY_DSN` env var; initialize Sentry in `settings.py` only if the DSN is set. ## Don't - Don't bake secrets into the image (DATABASE_URL, SECRET_KEY). - Don't use `runserver` in production. It's not a production server. - Don't run migrations from inside the web container at startup. Use a one-shot job. - Don't ignore `ALLOWED_HOSTS`. An empty list with `DEBUG=False` is a 500 generator.
Have a CLAUDE.md template that works for you?
Send it in — we’ll credit you and publish it under the right tags.