Loading...
Loading...
Create production-quality Django REST Framework APIs using Clean Architecture and SOLID principles. Covers layered architecture (views, use cases, services, models), query optimization (N+1 prevention), pagination/filtering, JWT authentication, permissions, and production deployment. Use when building new Django APIs, implementing domain-driven design, optimizing queries, or configuring authentication. Applies Python 3.12+ and Django 5+ patterns.
npx skill4agent add agusabas/django-skills django-clean-drfHTTP Layer (Views/Serializers)
↓
Application Layer (Use Cases + DTOs)
↓
Domain Layer (Services + Models)
↓
Infrastructure Layer (ORM, External APIs, Cache)references/project-structure.mdreferences/use-cases-pattern.mdreferences/services-pattern.mdreferences/models-domain.mdreferences/views-serializers.mdreferences/testing-clean-arch.mdreferences/examples.mdreferences/query-optimization.mdreferences/api-patterns.mdreferences/authentication.mdreferences/production-api.mdreferences/django-admin.mdreferences/coding-standards.mdViews → Use Cases → Services → Models
↓ ↓ ↓ ↓
Serializers DTOs (internal) ORMfrom dataclasses import dataclass
from uuid import UUID
from django.db import transaction
@dataclass(frozen=True, slots=True)
class CreateOrderInput:
customer_id: UUID
items: list['OrderItemInput']
notes: str | None = None
@dataclass(frozen=True, slots=True)
class CreateOrderOutput:
success: bool
order_id: UUID | None = None
error: str | None = None
class CreateOrderUseCase:
def __init__(
self,
order_service: OrderService,
inventory_service: InventoryService,
):
self._order_service = order_service
self._inventory_service = inventory_service
@transaction.atomic
def execute(self, input_dto: CreateOrderInput) -> CreateOrderOutput:
# Validate
is_valid, error = self._inventory_service.validate_availability(input_dto.items)
if not is_valid:
return CreateOrderOutput(success=False, error=error)
# Execute
order = self._order_service.create(
customer_id=input_dto.customer_id,
items=input_dto.items,
)
return CreateOrderOutput(success=True, order_id=order.id)class OrderService:
# STATIC methods for QUERIES (no state changes)
@staticmethod
def get_by_id(order_id: UUID) -> Order | None:
return Order.objects.select_related('customer').filter(id=order_id).first()
# INSTANCE methods for MUTATIONS (state changes)
def create(self, customer_id: UUID, items: list[OrderItemInput]) -> Order:
order = Order.objects.create(customer_id=customer_id)
for item in items:
OrderItem.objects.create(order=order, **item.__dict__)
return order
# VALIDATION returns tuple[bool, str]
def validate_can_cancel(self, order: Order) -> tuple[bool, str]:
if order.status == Order.Status.SHIPPED:
return False, "Cannot cancel shipped orders"
return True, ""class CreateOrderView(APIView):
permission_classes = [IsAuthenticated]
def post(self, request: Request) -> Response:
serializer = CreateOrderSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
use_case = CreateOrderUseCase(
order_service=OrderService(),
inventory_service=InventoryService(),
)
input_dto = CreateOrderInput(**serializer.validated_data)
output = use_case.execute(input_dto)
if not output.success:
return Response({'error': output.error}, status=400)
return Response({'order_id': str(output.order_id)}, status=201)# WRITE serializer (input validation)
class CreateOrderSerializer(serializers.Serializer):
items = OrderItemInputSerializer(many=True, min_length=1)
notes = serializers.CharField(max_length=500, required=False)
# READ serializer (output display)
class OrderReadSerializer(serializers.ModelSerializer):
customer_name = serializers.CharField(source='customer.name', read_only=True)
class Meta:
model = Order
fields = ['id', 'customer_name', 'status', 'created_at']
read_only_fields = fieldsclass Order(models.Model):
class Status(models.TextChoices):
PENDING = 'pending', 'Pending'
CONFIRMED = 'confirmed', 'Confirmed'
SHIPPED = 'shipped', 'Shipped'
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
customer = models.ForeignKey('Customer', on_delete=models.PROTECT, related_name='orders')
status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']
indexes = [models.Index(fields=['customer', 'status'])]
@property
def is_cancellable(self) -> bool:
return self.status in (self.Status.PENDING, self.Status.CONFIRMED)tuple[bool, str]@transaction.atomicselect_relatedprefetch_relatedapps/<app_name>/
├── __init__.py
├── models.py # Domain entities with @property logic
├── views.py # Thin HTTP handlers
├── serializers.py # Read and Write serializers
├── urls.py # Route definitions
├── admin.py # Django Admin
├── services/
│ ├── __init__.py # Export services
│ └── <entity>_service.py
├── use_cases/
│ ├── __init__.py # Export use cases
│ └── <action>_<entity>.py # One file per use case
├── tests/
│ ├── __init__.py
│ ├── test_models.py
│ ├── test_services.py
│ ├── test_use_cases.py
│ ├── test_views.py
│ └── factories.py # Factory Boy factories
└── migrations/references/project-structure.mdreferences/use-cases-pattern.mdreferences/services-pattern.mdreferences/models-domain.mdreferences/views-serializers.mdreferences/testing-clean-arch.mdreferences/examples.mdreferences/query-optimization.mdreferences/api-patterns.mdreferences/authentication.mdreferences/production-api.mdreferences/django-admin.mdreferences/coding-standards.md# BAD
def create_order(self, data):
if not self.can_create():
raise ValidationError("Cannot create order")
return Order.objects.create(**data)
# GOOD
def create_order(self, data) -> CreateOrderOutput:
if not self.can_create():
return CreateOrderOutput(success=False, error="Cannot create order")
order = Order.objects.create(**data)
return CreateOrderOutput(success=True, order_id=order.id)# BAD
def validate(self, order) -> bool:
return order.status != 'shipped'
# GOOD
def validate(self, order) -> tuple[bool, str]:
if order.status == 'shipped':
return False, "Cannot modify shipped orders"
return True, ""# BAD - Hard to test
class CreateOrderUseCase:
def execute(self, input_dto):
service = OrderService() # Hardcoded dependency
return service.create(input_dto)
# GOOD - Easy to mock
class CreateOrderUseCase:
def __init__(self, order_service: OrderService):
self._order_service = order_service
def execute(self, input_dto):
return self._order_service.create(input_dto)X | None<action>_<entity>.pycreate_order.py<entity>_service.pyorder_service.py<Entity>ReadSerializer<Entity>CreateSerializerdjango-celery-expertrefactordjango