Skip to content

Example CLAUDE.md — Django Project

This example shows a CLAUDE.md for a Django web application using Django REST Framework for APIs, Celery for background tasks, and PostgreSQL. It covers Django-specific conventions for app structure, model patterns, and the ORM that keep Claude Code generating idiomatic Django.

The CLAUDE.md File

# Project: Acme Marketplace

Django 5.1 + Django REST Framework 3.15. Python 3.12+. PostgreSQL database, Redis for caching and Celery broker.

## Setup

- Virtual environment: `source .venv/bin/activate` (always activate first)
- Install deps: `pip install -e ".[dev]"`
- Environment: copy `.env.example` to `.env`
- Database: `docker compose up -d` starts PostgreSQL and Redis

## Commands

- `python manage.py runserver` — start dev server (port 8000)
- `pytest` — run all tests
- `pytest apps/orders/tests/test_views.py::TestCreateOrder -v` — run a single test
- `pytest --cov=apps --cov-report=term-missing` — tests with coverage
- `ruff check .` — lint
- `ruff format .` — format
- `mypy apps/` — type checking
- `python manage.py makemigrations` — generate migrations after model changes
- `python manage.py migrate` — apply migrations
- `celery -A config worker -l info` — start Celery worker

Run `ruff check . && mypy apps/ && pytest` before committing.

## Project Structure

```
config/                     # Django project config
  settings/
    base.py                 # Shared settings
    local.py                # Local dev overrides
    production.py           # Production settings
  urls.py                   # Root URL config
  celery.py                 # Celery app config
apps/                       # Django apps, one per domain
  users/
    models.py               # User model (custom AbstractUser)
    serializers.py          # DRF serializers
    views.py                # DRF viewsets and API views
    urls.py                 # App URL patterns
    services.py             # Business logic (views call services)
    selectors.py            # Complex read queries
    tasks.py                # Celery tasks
    tests/
      test_views.py
      test_services.py
      factories.py          # Factory Boy factories
    admin.py                # Admin site config
  orders/                   # Same structure
  products/                 # Same structure
  core/                     # Shared utilities, base classes, mixins
```

## Architecture Conventions

- **Views are thin.** Views handle HTTP concerns (serialization, permissions, status codes). Business logic lives in `services.py`.
- **Services contain business logic.** Services are plain Python functions or classes. They receive validated data from views and return domain objects.
- **Selectors for complex reads.** Read-heavy queries with joins, annotations, or filters go in `selectors.py`, not in views or services.
- **One app per domain.** Each Django app represents a bounded context. Apps communicate through services, not by importing each other's models directly.

```python
# views.py — thin view
class CreateOrderView(APIView):
    permission_classes = [IsAuthenticated]

    def post(self, request):
        serializer = CreateOrderSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        order = OrderService.create(user=request.user, **serializer.validated_data)
        return Response(OrderSerializer(order).data, status=status.HTTP_201_CREATED)
```

```python
# services.py — business logic
class OrderService:
    @staticmethod
    def create(user: User, product_id: int, quantity: int) -> Order:
        product = Product.objects.get(id=product_id)
        if product.stock < quantity:
            raise InsufficientStockError(product.name)
        order = Order.objects.create(user=user, product=product, quantity=quantity)
        product.stock -= quantity
        product.save(update_fields=["stock"])
        send_order_confirmation.delay(order.id)
        return order
```

## Models

- Custom user model in `apps/users/models.py` extending `AbstractUser`
- Use `models.TextChoices` for enum fields
- Add `__str__`, `Meta.ordering`, and `Meta.verbose_name` on all models
- Use `update_fields` in `.save()` calls to avoid overwriting concurrent changes
- Index frequently queried fields with `db_index=True` or `Meta.indexes`

```python
class Order(models.Model):
    class Status(models.TextChoices):
        PENDING = "pending", "Pending"
        CONFIRMED = "confirmed", "Confirmed"
        SHIPPED = "shipped", "Shipped"

    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="orders")
    product = models.ForeignKey("products.Product", on_delete=models.PROTECT)
    quantity = models.PositiveIntegerField()
    status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ["-created_at"]
        indexes = [models.Index(fields=["user", "status"])]

    def __str__(self):
        return f"Order #{self.pk} — {self.status}"
```

## DRF Serializers

- Use `ModelSerializer` for standard CRUD
- Separate serializers for create/update vs. read (e.g., `CreateOrderSerializer`, `OrderSerializer`)
- Validate business rules in the serializer's `validate` method or in the service layer — not both
- Use `SerializerMethodField` sparingly — prefer annotations in selectors for computed fields

## Celery Tasks

- Tasks in `tasks.py` within each app
- Always use `.delay()` or `.apply_async()` — never call task functions directly
- Tasks should be idempotent (safe to retry)
- Use `@shared_task(bind=True, max_retries=3)` with exponential backoff
- Pass IDs to tasks, not full objects (objects cannot be serialized reliably)

```python
@shared_task(bind=True, max_retries=3)
def send_order_confirmation(self, order_id: int) -> None:
    try:
        order = Order.objects.select_related("user", "product").get(id=order_id)
        EmailService.send_confirmation(order)
    except Exception as exc:
        self.retry(exc=exc, countdown=2 ** self.request.retries)
```

## Testing

- pytest + pytest-django + factory_boy
- Factory Boy factories in `tests/factories.py` within each app
- Use `@pytest.mark.django_db` on tests that touch the database
- API tests use DRF's `APIClient`
- Mock external services (email, payment) — never call real APIs in tests
- Aim for >80% coverage on services and views

## Git

- Conventional commits: feat:, fix:, chore:, refactor:
- Always include migrations in the same commit as model changes
- Run `python manage.py makemigrations --check` in CI to catch missing migrations

## Do NOT

- Do not put business logic in views or serializers — use services
- Do not import models from other apps directly — go through services or selectors
- Do not use `objects.raw()` or raw SQL unless the ORM genuinely cannot express the query
- Do not use `print()` — use Django's `logging` module
- Do not use `*` imports
- Do not modify migration files after they have been applied

Key Sections Explained

Architecture Conventions — The views-services-selectors pattern is the single most impactful section. Without it, Claude puts business logic in views (the most common Django anti-pattern).

Models — The TextChoices, update_fields, and indexing rules prevent subtle bugs that are hard to catch in code review. The complete model example gives Claude a template for every new model.

Celery Tasks — The "pass IDs not objects" and idempotency rules prevent the two most common Celery mistakes. The retry pattern with exponential backoff is included as a copy-paste template.

Do NOT — The cross-app import rule enforces bounded contexts. The raw SQL rule keeps the codebase maintainable. These rules catch patterns Claude would otherwise use freely.

See Also