Django Project Skill Definition
Purpose
This project is a Django-based web application. All code must be clean, secure, scalable, and consistent with the established patterns in this codebase. When generating or modifying code, always follow the conventions defined below rather than defaulting to generic Django patterns.
Tech Stack
- Python 3.12+
- Django 5.x (latest stable)
- Django REST Framework 3.15+
- PostgreSQL 16+
- Redis (caching and task queue broker, if applicable)
- Celery (background tasks, if applicable)
- Docker and Docker Compose for local development
- python-decouple for environment variable management
- pytest and pytest-django for testing
- factory_boy for test fixtures
Project Structure
project_root/
manage.py
config/
__init__.py
settings/
__init__.py
base.py # Shared settings
development.py # Local dev overrides
production.py # Production overrides
test.py # Test-specific settings
urls.py # Root URL conf
wsgi.py
asgi.py
apps/
<app_name>/
__init__.py
models.py
views.py
serializers.py
services.py # Business logic lives here
selectors.py # Complex query logic lives here
urls.py
permissions.py
signals.py
admin.py
tests/
__init__.py
test_models.py
test_views.py
test_services.py
migrations/
common/
models.py # Abstract base models (TimeStampedModel, etc.)
permissions.py # Shared custom permissions
pagination.py # Shared pagination classes
exceptions.py # Custom exception classes and handler
utils.py # Small shared utilities
requirements/
base.txt
development.txt
production.txt
Rules:
- Each feature or domain gets its own Django app inside
apps/.
- Never place business logic in views or models. Views handle HTTP concerns only. Models handle data integrity only. Business logic goes in
services.py.
- Complex querysets and data retrieval go in
selectors.py.
- All apps must be registered using their full path:
apps.<app_name>.
Coding Standards
- Follow PEP 8 strictly. Line length limit is 88 characters (Black formatter default).
- Use type hints on all function signatures.
- Add docstrings to every class, function, and method. Use Google-style docstring format.
- Use class-based views by default. Use function-based views only for trivially simple endpoints.
- Prefer
get_object_or_404 over manual try/except for object retrieval in views.
- Use f-strings for string formatting. Never use
% or .format().
- Import ordering: stdlib, third-party, Django, project-local. Use isort to enforce.
- No wildcard imports. No unused imports.
- No print statements in committed code. Use
logging module instead.
Model Conventions
All models should inherit from a shared abstract base:
# common/models.py
import uuid
from django.db import models
class TimeStampedModel(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
ordering = ["-created_at"]
Rules:
- Use UUIDs as primary keys.
- Always define
__str__ on every model.
- Always define
Meta.ordering explicitly.
- Use
related_name on all ForeignKey and ManyToMany fields.
- Add
db_index=True on fields that are frequently filtered or searched.
- Keep model methods limited to data integrity and representation. No HTTP logic, no side effects.
Service Layer Pattern
All business logic lives in service functions. Views call services, never the other way around.
# apps/orders/services.py
from django.db import transaction
from apps.orders.models import Order
def create_order(*, user, items: list[dict]) -> Order:
"""
Creates a new order for the given user.
Args:
user: The user placing the order.
items: List of dicts with 'product_id' and 'quantity'.
Returns:
The created Order instance.
Raises:
ValidationError: If items list is empty or a product is unavailable.
"""
with transaction.atomic():
order = Order.objects.create(user=user)
# ... build order items
return order
Rules:
- Service functions use keyword-only arguments (the
* in the signature) to enforce clarity at call sites.
- Wrap multi-step writes in
transaction.atomic().
- Services raise Django
ValidationError or custom exceptions from common/exceptions.py on failure. They never return error dicts or status codes.
- Services never access
request objects directly. Views extract what is needed and pass it in.
Serializer Conventions
# apps/orders/serializers.py
from rest_framework import serializers
from apps.orders.models import Order
class OrderOutputSerializer(serializers.ModelSerializer):
class Meta:
model = Order
fields = ["id", "user", "status", "created_at"]
class OrderCreateInputSerializer(serializers.Serializer):
items = serializers.ListField(child=serializers.DictField(), min_length=1)
Rules:
- Separate input serializers from output serializers. Name them
<Entity>CreateInputSerializer, <Entity>UpdateInputSerializer, <Entity>OutputSerializer.
- Input serializers handle validation only. They do not call
.save() or .create(). The view passes validated data to a service function.
- Output serializers handle representation only.
- Never use
fields = "__all__". Always list fields explicitly.
View Conventions
# apps/orders/views.py
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAuthenticated
from apps.orders.serializers import OrderCreateInputSerializer, OrderOutputSerializer
from apps.orders.services import create_order
class OrderCreateView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request):
serializer = OrderCreateInputSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
order = create_order(user=request.user, **serializer.validated_data)
output = OrderOutputSerializer(order)
return Response(output.data, status=status.HTTP_201_CREATED)
Rules:
- Views are thin. They handle: permissions, input deserialization, calling a service, output serialization, returning a response. Nothing else.
- Always set
permission_classes explicitly on every view. Never rely on the global default silently.
- Use DRF's
Response, not Django's JsonResponse.
- Return appropriate HTTP status codes. Use
status constants, not raw integers.
URL Conventions
# apps/orders/urls.py
from django.urls import path
from apps.orders.views import OrderCreateView, OrderDetailView
app_name = "orders"
urlpatterns = [
path("", OrderCreateView.as_view(), name="create"),
path("<uuid:pk>/", OrderDetailView.as_view(), name="detail"),
]
Rules:
- Every app defines its own
urls.py with an app_name.
- Root
config/urls.py includes app URLs under a versioned API prefix: api/v1/.
- Use
uuid path converter for primary keys.
- Use trailing slashes consistently.
API Response Format
All API responses follow this envelope structure:
{
"data": { ... },
"meta": {
"page": 1,
"page_size": 20,
"total_count": 100
}
}
For errors:
{
"error": {
"code": "validation_error",
"message": "Items list cannot be empty.",
"details": { ... }
}
}
Implement this via a custom exception handler in common/exceptions.py and a response wrapper utility.
Error Handling
- Define custom exception classes in
common/exceptions.py for domain-specific errors.
- Register a custom DRF exception handler in settings that formats all errors into the standard envelope above.
- Never catch bare
Exception unless re-raising or logging. Catch specific exceptions.
- Use
logging.exception() for unexpected errors. Use logging.warning() for expected but notable failures.
- Never expose stack traces or internal details in API responses.
Authentication and Permissions
- Use token-based authentication (DRF TokenAuthentication or SimpleJWT, specify which one your project uses).
- Define custom permission classes in
apps/<app_name>/permissions.py or common/permissions.py.
- Every view must have explicit
permission_classes. No endpoint should be accidentally public.
- Use Django's built-in
User model or a custom user model inheriting AbstractUser (specify which one your project uses).
Security Rules
These are non-negotiable:
- Never hardcode secrets, keys, tokens, or passwords. All secrets come from environment variables via python-decouple.
- Ensure
DEBUG = False in production settings.
- Validate and sanitize all user input. Never trust client data.
- Keep CSRF middleware active. Keep SecurityMiddleware active.
- Use
ALLOWED_HOSTS and CORS_ALLOWED_ORIGINS explicitly. Never use wildcards in production.
- Use Django ORM for all database queries. Never write raw SQL unless there is a documented performance justification, and always use parameterized queries.
- Set
SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, and CSRF_COOKIE_SECURE to True in production.
- Run
python manage.py check --deploy before any production deployment.
Database and Migrations
- Always create migrations after model changes:
python manage.py makemigrations.
- Review generated migration files before committing. Never commit auto-generated migrations blindly.
- Use
RunPython migrations sparingly and only for data migrations. Always include a reverse function.
- Never edit or delete existing migrations that have been applied to shared environments.
- Use
db_index=True, unique=True, and database constraints where appropriate. Enforce data integrity at the database level, not just application level.
- Avoid N+1 queries. Use
select_related and prefetch_related in selectors.
Testing
- Test runner:
pytest with pytest-django.
- Use
factory_boy for generating test data. Never create test objects with raw Model.objects.create() unless trivially simple.
- Test file structure mirrors the module it tests:
tests/test_models.py, tests/test_views.py, tests/test_services.py.
- Every service function must have tests covering the success path and at least one failure/edge case.
- Every API endpoint must have tests covering: correct status codes, response shape, authentication enforcement, and permission enforcement.
- Use
APITestCase for endpoint tests. Use plain TestCase or pytest functions for unit tests on services and selectors.
- Aim for meaningful coverage over percentage targets. Do not write trivial tests just to inflate coverage numbers.
Logging
- Use Python's
logging module. Configure it in config/settings/base.py.
- Use named loggers per module:
logger = logging.getLogger(__name__).
- Log at appropriate levels:
DEBUG for development detail, INFO for normal operations, WARNING for recoverable issues, ERROR for failures.
- Never log sensitive data (passwords, tokens, PII).
Dependency Management
- Use
requirements/ directory with split files: base.txt, development.txt, production.txt.
development.txt and production.txt both start with -r base.txt.
- Pin all dependencies to exact versions.
- Do not add dependencies without a clear justification. Prefer Django's built-in tools and stdlib over third-party packages when the built-in solution is adequate.
AI Behavior Rules
- Do not invent or hallucinate Django APIs, settings, or DRF features. If unsure whether something exists, say so.
- Use only the patterns and conventions defined in this file.
- When creating a new app, follow the exact directory structure shown above.
- When modifying existing code, read the surrounding code first and match its style.
- Ask for clarification when requirements are ambiguous rather than guessing.
- Prefer simple, maintainable solutions over clever abstractions.
- Never add a dependency, middleware, or signal without stating why.
- When suggesting a change, explain what it does and why it is necessary.
Things to Avoid
- Over-engineering. Do not add abstractions, mixins, or patterns until they solve a real repeated problem.
- Adding unnecessary third-party packages.
- Writing long functions. If a function exceeds 30 lines, consider splitting it.
- Mixing frontend concerns into backend code.
- Using
signals for business logic. Signals are for decoupled side effects only (cache invalidation, audit logging). If the caller should know about the side effect, call it explicitly in a service.
- Using
GenericViewSet or router magic when explicit APIView classes are clearer.
- Returning inconsistent response structures across endpoints.
Source: PacktPublishing/Agentic-AI-Development-with-OpenCode — distributed by TomeVault.
1---2name: django-backend-engineer3description: Django backend development with DRF, service layer architecture, PostgreSQL, and production-ready patterns. Use for building APIs, models, views, serializers, and tests in Django projects. Use when this capability is needed.4---56# Django Project Skill Definition78## Purpose910This project is a Django-based web application. All code must be clean, secure, scalable, and consistent with the established patterns in this codebase. When generating or modifying code, always follow the conventions defined below rather than defaulting to generic Django patterns.1112---1314## Tech Stack1516- Python 3.12+17- Django 5.x (latest stable)18- Django REST Framework 3.15+19- PostgreSQL 16+20- Redis (caching and task queue broker, if applicable)21- Celery (background tasks, if applicable)22- Docker and Docker Compose for local development23- python-decouple for environment variable management24- pytest and pytest-django for testing25- factory_boy for test fixtures2627---2829## Project Structure3031```32project_root/33 manage.py34 config/35 __init__.py36 settings/37 __init__.py38 base.py # Shared settings39 development.py # Local dev overrides40 production.py # Production overrides41 test.py # Test-specific settings42 urls.py # Root URL conf43 wsgi.py44 asgi.py45 apps/46 <app_name>/47 __init__.py48 models.py49 views.py50 serializers.py51 services.py # Business logic lives here52 selectors.py # Complex query logic lives here53 urls.py54 permissions.py55 signals.py56 admin.py57 tests/58 __init__.py59 test_models.py60 test_views.py61 test_services.py62 migrations/63 common/64 models.py # Abstract base models (TimeStampedModel, etc.)65 permissions.py # Shared custom permissions66 pagination.py # Shared pagination classes67 exceptions.py # Custom exception classes and handler68 utils.py # Small shared utilities69 requirements/70 base.txt71 development.txt72 production.txt73```7475Rules:76- Each feature or domain gets its own Django app inside `apps/`.77- Never place business logic in views or models. Views handle HTTP concerns only. Models handle data integrity only. Business logic goes in `services.py`.78- Complex querysets and data retrieval go in `selectors.py`.79- All apps must be registered using their full path: `apps.<app_name>`.8081---8283## Coding Standards8485- Follow PEP 8 strictly. Line length limit is 88 characters (Black formatter default).86- Use type hints on all function signatures.87- Add docstrings to every class, function, and method. Use Google-style docstring format.88- Use class-based views by default. Use function-based views only for trivially simple endpoints.89- Prefer `get_object_or_404` over manual try/except for object retrieval in views.90- Use f-strings for string formatting. Never use `%` or `.format()`.91- Import ordering: stdlib, third-party, Django, project-local. Use isort to enforce.92- No wildcard imports. No unused imports.93- No print statements in committed code. Use `logging` module instead.9495---9697## Model Conventions9899All models should inherit from a shared abstract base:100101```python102# common/models.py103import uuid104from django.db import models105106class TimeStampedModel(models.Model):107 id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)108 created_at = models.DateTimeField(auto_now_add=True)109 updated_at = models.DateTimeField(auto_now=True)110111 class Meta:112 abstract = True113 ordering = ["-created_at"]114```115116Rules:117- Use UUIDs as primary keys.118- Always define `__str__` on every model.119- Always define `Meta.ordering` explicitly.120- Use `related_name` on all ForeignKey and ManyToMany fields.121- Add `db_index=True` on fields that are frequently filtered or searched.122- Keep model methods limited to data integrity and representation. No HTTP logic, no side effects.123124---125126## Service Layer Pattern127128All business logic lives in service functions. Views call services, never the other way around.129130```python131# apps/orders/services.py132from django.db import transaction133from apps.orders.models import Order134135def create_order(*, user, items: list[dict]) -> Order:136 """137 Creates a new order for the given user.138139 Args:140 user: The user placing the order.141 items: List of dicts with 'product_id' and 'quantity'.142143 Returns:144 The created Order instance.145146 Raises:147 ValidationError: If items list is empty or a product is unavailable.148 """149 with transaction.atomic():150 order = Order.objects.create(user=user)151 # ... build order items152 return order153```154155Rules:156- Service functions use keyword-only arguments (the `*` in the signature) to enforce clarity at call sites.157- Wrap multi-step writes in `transaction.atomic()`.158- Services raise Django `ValidationError` or custom exceptions from `common/exceptions.py` on failure. They never return error dicts or status codes.159- Services never access `request` objects directly. Views extract what is needed and pass it in.160161---162163## Serializer Conventions164165```python166# apps/orders/serializers.py167from rest_framework import serializers168from apps.orders.models import Order169170class OrderOutputSerializer(serializers.ModelSerializer):171 class Meta:172 model = Order173 fields = ["id", "user", "status", "created_at"]174175class OrderCreateInputSerializer(serializers.Serializer):176 items = serializers.ListField(child=serializers.DictField(), min_length=1)177```178179Rules:180- Separate input serializers from output serializers. Name them `<Entity>CreateInputSerializer`, `<Entity>UpdateInputSerializer`, `<Entity>OutputSerializer`.181- Input serializers handle validation only. They do not call `.save()` or `.create()`. The view passes validated data to a service function.182- Output serializers handle representation only.183- Never use `fields = "__all__"`. Always list fields explicitly.184185---186187## View Conventions188189```python190# apps/orders/views.py191from rest_framework import status192from rest_framework.response import Response193from rest_framework.views import APIView194from rest_framework.permissions import IsAuthenticated195196from apps.orders.serializers import OrderCreateInputSerializer, OrderOutputSerializer197from apps.orders.services import create_order198199class OrderCreateView(APIView):200 permission_classes = [IsAuthenticated]201202 def post(self, request):203 serializer = OrderCreateInputSerializer(data=request.data)204 serializer.is_valid(raise_exception=True)205206 order = create_order(user=request.user, **serializer.validated_data)207208 output = OrderOutputSerializer(order)209 return Response(output.data, status=status.HTTP_201_CREATED)210```211212Rules:213- Views are thin. They handle: permissions, input deserialization, calling a service, output serialization, returning a response. Nothing else.214- Always set `permission_classes` explicitly on every view. Never rely on the global default silently.215- Use DRF's `Response`, not Django's `JsonResponse`.216- Return appropriate HTTP status codes. Use `status` constants, not raw integers.217218---219220## URL Conventions221222```python223# apps/orders/urls.py224from django.urls import path225from apps.orders.views import OrderCreateView, OrderDetailView226227app_name = "orders"228229urlpatterns = [230 path("", OrderCreateView.as_view(), name="create"),231 path("<uuid:pk>/", OrderDetailView.as_view(), name="detail"),232]233```234235Rules:236- Every app defines its own `urls.py` with an `app_name`.237- Root `config/urls.py` includes app URLs under a versioned API prefix: `api/v1/`.238- Use `uuid` path converter for primary keys.239- Use trailing slashes consistently.240241---242243## API Response Format244245All API responses follow this envelope structure:246247```json248{249 "data": { ... },250 "meta": {251 "page": 1,252 "page_size": 20,253 "total_count": 100254 }255}256```257258For errors:259260```json261{262 "error": {263 "code": "validation_error",264 "message": "Items list cannot be empty.",265 "details": { ... }266 }267}268```269270Implement this via a custom exception handler in `common/exceptions.py` and a response wrapper utility.271272---273274## Error Handling275276- Define custom exception classes in `common/exceptions.py` for domain-specific errors.277- Register a custom DRF exception handler in settings that formats all errors into the standard envelope above.278- Never catch bare `Exception` unless re-raising or logging. Catch specific exceptions.279- Use `logging.exception()` for unexpected errors. Use `logging.warning()` for expected but notable failures.280- Never expose stack traces or internal details in API responses.281282---283284## Authentication and Permissions285286- Use token-based authentication (DRF TokenAuthentication or SimpleJWT, specify which one your project uses).287- Define custom permission classes in `apps/<app_name>/permissions.py` or `common/permissions.py`.288- Every view must have explicit `permission_classes`. No endpoint should be accidentally public.289- Use Django's built-in `User` model or a custom user model inheriting `AbstractUser` (specify which one your project uses).290291---292293## Security Rules294295These are non-negotiable:296- Never hardcode secrets, keys, tokens, or passwords. All secrets come from environment variables via python-decouple.297- Ensure `DEBUG = False` in production settings.298- Validate and sanitize all user input. Never trust client data.299- Keep CSRF middleware active. Keep SecurityMiddleware active.300- Use `ALLOWED_HOSTS` and `CORS_ALLOWED_ORIGINS` explicitly. Never use wildcards in production.301- Use Django ORM for all database queries. Never write raw SQL unless there is a documented performance justification, and always use parameterized queries.302- Set `SECURE_SSL_REDIRECT`, `SESSION_COOKIE_SECURE`, and `CSRF_COOKIE_SECURE` to `True` in production.303- Run `python manage.py check --deploy` before any production deployment.304305---306307## Database and Migrations308309- Always create migrations after model changes: `python manage.py makemigrations`.310- Review generated migration files before committing. Never commit auto-generated migrations blindly.311- Use `RunPython` migrations sparingly and only for data migrations. Always include a reverse function.312- Never edit or delete existing migrations that have been applied to shared environments.313- Use `db_index=True`, `unique=True`, and database constraints where appropriate. Enforce data integrity at the database level, not just application level.314- Avoid N+1 queries. Use `select_related` and `prefetch_related` in selectors.315316---317318## Testing319320- Test runner: `pytest` with `pytest-django`.321- Use `factory_boy` for generating test data. Never create test objects with raw `Model.objects.create()` unless trivially simple.322- Test file structure mirrors the module it tests: `tests/test_models.py`, `tests/test_views.py`, `tests/test_services.py`.323- Every service function must have tests covering the success path and at least one failure/edge case.324- Every API endpoint must have tests covering: correct status codes, response shape, authentication enforcement, and permission enforcement.325- Use `APITestCase` for endpoint tests. Use plain `TestCase` or `pytest` functions for unit tests on services and selectors.326- Aim for meaningful coverage over percentage targets. Do not write trivial tests just to inflate coverage numbers.327328---329330## Logging331332- Use Python's `logging` module. Configure it in `config/settings/base.py`.333- Use named loggers per module: `logger = logging.getLogger(__name__)`.334- Log at appropriate levels: `DEBUG` for development detail, `INFO` for normal operations, `WARNING` for recoverable issues, `ERROR` for failures.335- Never log sensitive data (passwords, tokens, PII).336337---338339## Dependency Management340341- Use `requirements/` directory with split files: `base.txt`, `development.txt`, `production.txt`.342- `development.txt` and `production.txt` both start with `-r base.txt`.343- Pin all dependencies to exact versions.344- Do not add dependencies without a clear justification. Prefer Django's built-in tools and stdlib over third-party packages when the built-in solution is adequate.345346---347348## AI Behavior Rules349350- Do not invent or hallucinate Django APIs, settings, or DRF features. If unsure whether something exists, say so.351- Use only the patterns and conventions defined in this file.352- When creating a new app, follow the exact directory structure shown above.353- When modifying existing code, read the surrounding code first and match its style.354- Ask for clarification when requirements are ambiguous rather than guessing.355- Prefer simple, maintainable solutions over clever abstractions.356- Never add a dependency, middleware, or signal without stating why.357- When suggesting a change, explain what it does and why it is necessary.358359---360361## Things to Avoid362363- Over-engineering. Do not add abstractions, mixins, or patterns until they solve a real repeated problem.364- Adding unnecessary third-party packages.365- Writing long functions. If a function exceeds 30 lines, consider splitting it.366- Mixing frontend concerns into backend code.367- Using `signals` for business logic. Signals are for decoupled side effects only (cache invalidation, audit logging). If the caller should know about the side effect, call it explicitly in a service.368- Using `GenericViewSet` or router magic when explicit `APIView` classes are clearer.369- Returning inconsistent response structures across endpoints.370371---372> Source: [PacktPublishing/Agentic-AI-Development-with-OpenCode](https://github.com/PacktPublishing/Agentic-AI-Development-with-OpenCode) — distributed by [TomeVault](https://tomevault.io).373<!-- tomevault:4.0:skill_md:2026-06-15 -->