Python + Django Agent Rules
Project Context
You are building a Django 5+ application with Django REST Framework (DRF) and PostgreSQL. The project follows Django's "batteries included" philosophy with a clean app-based structure, custom managers, and Celery for background work.
Code Style & Structure
- Follow PEP 8. Use `ruff` for linting and `black` for formatting. Configure both in `pyproject.toml`.
- Add type hints to all function signatures and return types. Use `from __future__ import annotations` for deferred evaluation.
- Prefer f-strings for string interpolation. Use `pathlib.Path` for all file path operations.
- Keep functions under 30 lines. Extract helpers and custom manager methods for complex query logic.
Project Structure
```
project/
config/
settings/
base.py # Shared settings
development.py # DEBUG=True, dev middleware
production.py # SECURE_* settings, WhiteNoise
urls.py
asgi.py / wsgi.py
celery.py
apps/
users/ # Custom User model — create this first, before any migration
core/ # Shared abstract models, mixins, utilities
<domain>/ # Feature app: models.py, views.py, serializers.py, urls.py, tests/
templates/
static/
requirements/
base.txt
development.txt
production.txt
```
Models & ORM
- Create a custom user model inheriting `AbstractUser` before the first migration. It cannot be swapped later.
- Use `uuid` primary keys on public-facing models: `id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)`.
- Add `db_index=True` on all fields used in `filter()`, `order_by()`, and `get()` calls.
- Define `Meta` with `ordering`, `constraints`, `indexes`, `verbose_name`, and `verbose_name_plural`.
- Use `select_related()` for `ForeignKey`/`OneToOneField` relations. Use `prefetch_related()` for `ManyToManyField` and reverse relations.
- Write custom `QuerySet` methods for reusable filters. Return `self` for chainability: `def active(self) -> QuerySet: return self.filter(is_active=True)`.
- Use `F()` for database-level arithmetic. Use `Q()` for OR/complex conditions. Use `Subquery` + `OuterRef` for correlated subqueries.
- Separate schema migrations from data migrations. Never write `python manage.py shell` data operations in schema migrations.
- Decorate multi-step write operations with `@transaction.atomic`.
Views & Serializers (DRF)
- Use `ModelViewSet` for full CRUD resources. Use `APIView` for custom, non-CRUD endpoints.
- Follow "fat querysets, thin views" — put filtering, annotation, and access control in the queryset layer.
- Declare serializer fields explicitly. Never use `fields = '__all__'` — it exposes all columns including future ones.
- Use `SerializerMethodField` for computed values. Use nested serializers with `read_only=True` for related data representation.
- Implement pagination with `CursorPagination` for large datasets (stable, fast). Use `PageNumberPagination` for smaller, user-facing lists.
- Use `@action(detail=True/False, methods=['post'])` for custom viewset actions. Give them descriptive names.
- Configure filtering with `django-filter` `FilterSet` classes. Never parse `request.query_params` manually.
Authentication & Permissions
- Use `djangorestframework-simplejwt` for API authentication. Configure short access token lifetime (15 minutes) and rotate refresh tokens.
- Write custom `BasePermission` subclasses for domain-specific access rules. Name them descriptively: `IsOwnerOrReadOnly`.
- Set `DEFAULT_PERMISSION_CLASSES = ['rest_framework.permissions.IsAuthenticated']` globally. Use `AllowAny` explicitly on public endpoints.
- Configure CORS with `django-cors-headers`. Whitelist specific origins in production.
- Enable `SECURE_SSL_REDIRECT`, `SESSION_COOKIE_SECURE`, `CSRF_COOKIE_SECURE`, and `SECURE_HSTS_SECONDS` in production settings.
Testing
- Use `pytest-django` with `conftest.py` for fixtures. Mark database tests with `@pytest.mark.django_db`.
- Create model instances with `factory_boy` factories. Avoid raw `Model.objects.create()` in test setup.
- Test API endpoints with DRF's `APIClient`. Cover: authentication, permissions, serializer validation, and response structure.
- Test custom QuerySet methods and managers in isolation with a minimal dataset.
- Mock external service calls with `unittest.mock.patch` or the `responses` library. Never make real network calls in tests.
- Aim for 85%+ coverage on business logic. Every permission boundary needs a test.
Performance
- Profile queries with `django-debug-toolbar` in development. Eliminate N+1 queries before merging.
- Use `only()` and `defer()` to limit fetched columns when full model instances are unnecessary.
- Use `bulk_create(objs, batch_size=500)` and `bulk_update(objs, fields, batch_size=500)` for batch operations.
- Cache expensive queryset results with `cache.get/set`. Use `cache_page` for entire view responses.
- Offload heavy work to Celery: `task.delay(args)`. Use `shared_task` for reusable tasks across apps.
- Configure database connection pooling with PgBouncer or `django-db-connection-pool` in production.
Error Handling
- Register a custom DRF exception handler in `REST_FRAMEWORK['EXCEPTION_HANDLER']` for consistent error responses.
- Define domain exceptions inheriting from `rest_framework.exceptions.APIException` with a custom `default_code`.
- Use `@transaction.atomic` on operations that must succeed together. Django rolls back on any exception.
- Configure structured logging with `python-json-logger` for production. Include `request_id`, `user_id`, and `path`.