--- name: python-django-patterns description: Django 5+ patterns — models, views, serializers, REST framework, authentication, management commands --- # Python Django Patterns ## Project Structure ``` project/ ├── manage.py ├── config/ # Project settings │ ├── settings/ │ │ ├── base.py │ │ ├── development.py │ │ └── production.py │ ├── urls.py │ ├── wsgi.py │ └── asgi.py ├── apps/ │ ├── products/ │ │ ├── models.py │ │ ├── views.py # API views (thin) │ │ ├── serializers.py # DRF serializers │ │ ├── urls.py │ │ ├── services.py # Business logic │ │ ├── repositories.py # Data access │ │ ├── admin.py │ │ ├── tasks.py # Celery tasks │ │ ├── tests/ │ │ │ ├── test_models.py │ │ │ ├── test_views.py │ │ │ └── test_services.py │ │ └── migrations/ │ ├── orders/ │ └── users/ ├── shared/ │ ├── pagination.py │ ├── permissions.py │ ├── throttling.py │ └── exceptions.py ├── requirements/ │ ├── base.txt │ ├── development.txt │ └── production.txt └── docker-compose.yml ``` ## Model Pattern ```python # apps/products/models.py from django.db import models from django.urls import reverse class Product(models.Model): name = models.CharField(max_length=255) slug = models.SlugField(unique=True) description = models.TextField(blank=True) price = models.DecimalField(max_digits=10, decimal_places=2) category = models.ForeignKey('Category', on_delete=models.PROTECT, related_name='products') is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] indexes = [ models.Index(fields=['slug']), models.Index(fields=['category', 'is_active']), ] def __str__(self): return self.name def get_absolute_url(self): return reverse('product-detail', kwargs={'slug': self.slug}) ``` ## Service Pattern (Business Logic in services.py) ```python # apps/products/services.py from .repositories import ProductRepository class ProductService: def __init__(self, repository=None): self.repository = repository or ProductRepository() def list_active(self, page=1, per_page=20, category_id=None, search=None): return self.repository.list_active( page=page, per_page=per_page, category_id=category_id, search=search, ) def create(self, data: dict): product = self.repository.create(data) return product def update_price(self, product_id: int, new_price): product = self.repository.get_by_id(product_id) product.price = new_price self.repository.save(product) return product ``` ## Repository Pattern ```python # apps/products/repositories.py from .models import Product class ProductRepository: def list_active(self, page=1, per_page=20, category_id=None, search=None): qs = Product.objects.filter(is_active=True).select_related('category') if category_id: qs = qs.filter(category_id=category_id) if search: qs = qs.filter(name__icontains=search) return qs[(page - 1) * per_page : page * per_page] def get_by_id(self, product_id: int): return Product.objects.select_related('category').get(pk=product_id) def create(self, data: dict): return Product.objects.create(**data) def save(self, product): product.save() return product ``` ## Serializer Pattern (DRF) ```python # apps/products/serializers.py from rest_framework import serializers from .models import Product, Category class CategorySerializer(serializers.ModelSerializer): class Meta: model = Category fields = ['id', 'name', 'slug'] class ProductListSerializer(serializers.ModelSerializer): category = CategorySerializer(read_only=True) class Meta: model = Product fields = ['id', 'name', 'slug', 'price', 'category', 'is_active'] class ProductDetailSerializer(serializers.ModelSerializer): category = CategorySerializer(read_only=True) class Meta: model = Product fields = '__all__' class ProductCreateSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ['name', 'description', 'price', 'category', 'is_active'] def validate_price(self, value): if value <= 0: raise serializers.ValidationError('Price must be positive') return value ``` ## View Pattern (Thin Views) ```python # apps/products/views.py from rest_framework import viewsets, filters, status from rest_framework.decorators import action from rest_framework.response import Response from .models import Product from .serializers import ProductListSerializer, ProductDetailSerializer, ProductCreateSerializer from .services import ProductService from .repositories import ProductRepository class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.filter(is_active=True).select_related('category') filter_backends = [filters.SearchFilter, filters.OrderingFilter] search_fields = ['name', 'description'] ordering_fields = ['price', 'created_at'] def get_serializer_class(self): if self.action == 'list': return ProductListSerializer if self.action in ('create', 'update', 'partial_update'): return ProductCreateSerializer return ProductDetailSerializer def get_service(self): return ProductService(ProductRepository()) def create(self, request): service = self.get_service() product = service.create(request.data) return Response( ProductDetailSerializer(product).data, status=status.HTTP_201_CREATED, ) ``` ## Authentication (DRF + JWT) ```python # config/settings/base.py REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework_simplejwt.authentication.JWTAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticatedOrReadOnly', ], 'DEFAULT_PAGINATION_CLASS': 'shared.pagination.StandardPagination', 'PAGE_SIZE': 20, } ``` ## Checklist - [ ] Fat models, thin views, services for business logic - [ ] Repository pattern for data access - [ ] DRF serializers for input validation and output transformation - [ ] Select_related / prefetch_related to prevent N+1 - [ ] JWT (SimpleJWT) for API authentication - [ ] Environment-specific settings (base/development/production) - [ ] Django admin for data management - [ ] pytest-django for testing - [ ] Celery for async tasks - [ ] `python manage.py check --deploy` before release