Django Patterns

by affaan-mef648e01899bNo license275K starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated 3 days ago

Patrones de arquitectura Django, diseño de API REST con DRF, buenas prácticas de ORM, caché, señales, middleware y aplicaciones Django de nivel producción.

Instructions onlySoftware Development
AI-generated overview

Reference patterns for production Django architecture, DRF APIs, ORM usage, caching, signals and middleware.

What it does
Provides a reference guide of production-grade Django architecture patterns, covering project layout, split settings, model and QuerySet design, Django REST Framework serializers and viewsets, a service layer, caching strategies, signals, middleware and performance optimization. It supplies code examples and a quick-reference table rather than generating files or running anything.
When to use it
Use when building or structuring Django web applications, designing REST APIs with Django REST Framework, or working with the Django ORM, models, caching, signals and middleware.
Requirements
None beyond the agent; it is instructions only and ships no scripts.

Patrones de Desarrollo Django

Patrones de arquitectura Django de nivel producción para aplicaciones escalables y mantenibles.

Cuándo Activar

  • Construir aplicaciones web Django
  • Diseñar APIs con Django REST Framework
  • Trabajar con el ORM de Django y modelos
  • Configurar la estructura del proyecto Django
  • Implementar caché, señales, middleware

Estructura del Proyecto

Layout Recomendado

myproject/├── config/│   ├── __init__.py│   ├── settings/│   │   ├── __init__.py│   │   ├── base.py          # Configuración base│   │   ├── development.py   # Configuración de desarrollo│   │   ├── production.py    # Configuración de producción│   │   └── test.py          # Configuración de pruebas│   ├── urls.py│   ├── wsgi.py│   └── asgi.py├── manage.py└── apps/    ├── __init__.py    ├── users/    │   ├── __init__.py    │   ├── models.py    │   ├── views.py    │   ├── serializers.py    │   ├── urls.py    │   ├── permissions.py    │   ├── filters.py    │   ├── services.py    │   └── tests/    └── products/        └── ...

Patrón de Configuración Dividida

python
# config/settings/base.pyfrom pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent.parent
SECRET_KEY = env('DJANGO_SECRET_KEY')DEBUG = FalseALLOWED_HOSTS = []
INSTALLED_APPS = [    'django.contrib.admin',    'django.contrib.auth',    'django.contrib.contenttypes',    'django.contrib.sessions',    'django.contrib.messages',    'django.contrib.staticfiles',    'rest_framework',    'rest_framework.authtoken',    'corsheaders',    # Apps locales    'apps.users',    'apps.products',]
MIDDLEWARE = [    'django.middleware.security.SecurityMiddleware',    'whitenoise.middleware.WhiteNoiseMiddleware',    'django.contrib.sessions.middleware.SessionMiddleware',    'corsheaders.middleware.CorsMiddleware',    'django.middleware.common.CommonMiddleware',    'django.middleware.csrf.CsrfViewMiddleware',    'django.contrib.auth.middleware.AuthenticationMiddleware',    'django.contrib.messages.middleware.MessageMiddleware',    'django.middleware.clickjacking.XFrameOptionsMiddleware',]
ROOT_URLCONF = 'config.urls'WSGI_APPLICATION = 'config.wsgi.application'
DATABASES = {    'default': {        'ENGINE': 'django.db.backends.postgresql',        'NAME': env('DB_NAME'),        'USER': env('DB_USER'),        'PASSWORD': env('DB_PASSWORD'),        'HOST': env('DB_HOST'),        'PORT': env('DB_PORT', default='5432'),    }}
# config/settings/development.pyfrom .base import *
DEBUG = TrueALLOWED_HOSTS = ['localhost', '127.0.0.1']
DATABASES['default']['NAME'] = 'myproject_dev'
INSTALLED_APPS += ['debug_toolbar']
MIDDLEWARE += ['debug_toolbar.middleware.DebugToolbarMiddleware']
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# config/settings/production.pyfrom .base import *
DEBUG = FalseALLOWED_HOSTS = env.list('ALLOWED_HOSTS')SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueSECURE_HSTS_SECONDS = 31536000SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = True
# LoggingLOGGING = {    'version': 1,    'disable_existing_loggers': False,    'handlers': {        'file': {            'level': 'WARNING',            'class': 'logging.FileHandler',            'filename': '/var/log/django/django.log',        },    },    'loggers': {        'django': {            'handlers': ['file'],            'level': 'WARNING',            'propagate': True,        },    },}

Patrones de Diseño de Modelos

Buenas Prácticas de Modelos

python
from django.db import modelsfrom django.contrib.auth.models import AbstractUserfrom django.core.validators import MinValueValidator, MaxValueValidator
class User(AbstractUser):    """Modelo de usuario personalizado que extiende AbstractUser."""    email = models.EmailField(unique=True)    phone = models.CharField(max_length=20, blank=True)    birth_date = models.DateField(null=True, blank=True)
    USERNAME_FIELD = 'email'    REQUIRED_FIELDS = ['username']
    class Meta:        db_table = 'users'        verbose_name = 'user'        verbose_name_plural = 'users'        ordering = ['-date_joined']
    def __str__(self):        return self.email
    def get_full_name(self):        return f"{self.first_name} {self.last_name}".strip()
class Product(models.Model):    """Modelo de producto con configuración de campos apropiada."""    name = models.CharField(max_length=200)    slug = models.SlugField(unique=True, max_length=250)    description = models.TextField(blank=True)    price = models.DecimalField(        max_digits=10,        decimal_places=2,        validators=[MinValueValidator(0)]    )    stock = models.PositiveIntegerField(default=0)    is_active = models.BooleanField(default=True)    category = models.ForeignKey(        'Category',        on_delete=models.CASCADE,        related_name='products'    )    tags = models.ManyToManyField('Tag', blank=True, related_name='products')    created_at = models.DateTimeField(auto_now_add=True)    updated_at = models.DateTimeField(auto_now=True)
    class Meta:        db_table = 'products'        ordering = ['-created_at']        indexes = [            models.Index(fields=['slug']),            models.Index(fields=['-created_at']),            models.Index(fields=['category', 'is_active']),        ]        constraints = [            models.CheckConstraint(                check=models.Q(price__gte=0),                name='price_non_negative'            )        ]
    def __str__(self):        return self.name
    def save(self, *args, **kwargs):        if not self.slug:            self.slug = slugify(self.name)        super().save(*args, **kwargs)

Buenas Prácticas de QuerySet

python
from django.db import models
class ProductQuerySet(models.QuerySet):    """QuerySet personalizado para el modelo Product."""
    def active(self):        """Retornar solo productos activos."""        return self.filter(is_active=True)
    def with_category(self):        """Seleccionar categoría relacionada para evitar consultas N+1."""        return self.select_related('category')
    def with_tags(self):        """Prefetch tags para relación muchos-a-muchos."""        return self.prefetch_related('tags')
    def in_stock(self):        """Retornar productos con stock > 0."""        return self.filter(stock__gt=0)
    def search(self, query):        """Buscar productos por nombre o descripción."""        return self.filter(            models.Q(name__icontains=query) |            models.Q(description__icontains=query)        )
class Product(models.Model):    # ... campos ...
    objects = ProductQuerySet.as_manager()  # Usar QuerySet personalizado
# UsoProduct.objects.active().with_category().in_stock()

Métodos de Manager

python
class ProductManager(models.Manager):    """Manager personalizado para consultas complejas."""
    def get_or_none(self, **kwargs):        """Retornar objeto o None en lugar de DoesNotExist."""        try:            return self.get(**kwargs)        except self.model.DoesNotExist:            return None
    def create_with_tags(self, name, price, tag_names):        """Crear producto con tags asociados."""        product = self.create(name=name, price=price)        tags = [Tag.objects.get_or_create(name=name)[0] for name in tag_names]        product.tags.set(tags)        return product
    def bulk_update_stock(self, product_ids, quantity):        """Actualización masiva de stock para múltiples productos."""        return self.filter(id__in=product_ids).update(stock=quantity)
# En el modeloclass Product(models.Model):    # ... campos ...    custom = ProductManager()

Patrones de Django REST Framework

Patrones de Serializer

python
from rest_framework import serializersfrom django.contrib.auth.password_validation import validate_passwordfrom .models import Product, User
class ProductSerializer(serializers.ModelSerializer):    """Serializer para el modelo Product."""
    category_name = serializers.CharField(source='category.name', read_only=True)    average_rating = serializers.FloatField(read_only=True)    discount_price = serializers.SerializerMethodField()
    class Meta:        model = Product        fields = [            'id', 'name', 'slug', 'description', 'price',            'discount_price', 'stock', 'category_name',            'average_rating', 'created_at'        ]        read_only_fields = ['id', 'slug', 'created_at']
    def get_discount_price(self, obj):        """Calcular precio con descuento si aplica."""        if hasattr(obj, 'discount') and obj.discount:            return obj.price * (1 - obj.discount.percent / 100)        return obj.price
    def validate_price(self, value):        """Asegurar que el precio no sea negativo."""        if value < 0:            raise serializers.ValidationError("Price cannot be negative.")        return value
class ProductCreateSerializer(serializers.ModelSerializer):    """Serializer para crear productos."""
    class Meta:        model = Product        fields = ['name', 'description', 'price', 'stock', 'category']
    def validate(self, data):        """Validación personalizada para múltiples campos."""        if data['price'] > 10000 and data['stock'] > 100:            raise serializers.ValidationError(                "Cannot have high-value products with large stock."            )        return data
class UserRegistrationSerializer(serializers.ModelSerializer):    """Serializer para registro de usuario."""
    password = serializers.CharField(        write_only=True,        required=True,        validators=[validate_password],        style={'input_type': 'password'}    )    password_confirm = serializers.CharField(write_only=True, style={'input_type': 'password'})
    class Meta:        model = User        fields = ['email', 'username', 'password', 'password_confirm']
    def validate(self, data):        """Validar que las contraseñas coincidan."""        if data['password'] != data['password_confirm']:            raise serializers.ValidationError({                "password_confirm": "Password fields didn't match."            })        return data
    def create(self, validated_data):        """Crear usuario con contraseña hasheada."""        validated_data.pop('password_confirm')        password = validated_data.pop('password')        user = User.objects.create(**validated_data)        user.set_password(password)        user.save()        return user

Patrones de ViewSet

python
from rest_framework import viewsets, status, filtersfrom rest_framework.decorators import actionfrom rest_framework.response import Responsefrom rest_framework.permissions import IsAuthenticated, IsAdminUserfrom django_filters.rest_framework import DjangoFilterBackendfrom .models import Productfrom .serializers import ProductSerializer, ProductCreateSerializerfrom .permissions import IsOwnerOrReadOnlyfrom .filters import ProductFilterfrom .services import ProductService
class ProductViewSet(viewsets.ModelViewSet):    """ViewSet para el modelo Product."""
    queryset = Product.objects.select_related('category').prefetch_related('tags')    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]    filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]    filterset_class = ProductFilter    search_fields = ['name', 'description']    ordering_fields = ['price', 'created_at', 'name']    ordering = ['-created_at']
    def get_serializer_class(self):        """Retornar serializer apropiado según la acción."""        if self.action == 'create':            return ProductCreateSerializer        return ProductSerializer
    def perform_create(self, serializer):        """Guardar con contexto de usuario."""        serializer.save(created_by=self.request.user)
    @action(detail=False, methods=['get'])    def featured(self, request):        """Retornar productos destacados."""        featured = self.queryset.filter(is_featured=True)[:10]        serializer = self.get_serializer(featured, many=True)        return Response(serializer.data)
    @action(detail=True, methods=['post'])    def purchase(self, request, pk=None):        """Comprar un producto."""        product = self.get_object()        service = ProductService()        result = service.purchase(product, request.user)        return Response(result, status=status.HTTP_201_CREATED)
    @action(detail=False, methods=['get'], permission_classes=[IsAuthenticated])    def my_products(self, request):        """Retornar productos creados por el usuario actual."""        products = self.queryset.filter(created_by=request.user)        page = self.paginate_queryset(products)        serializer = self.get_serializer(page, many=True)        return self.get_paginated_response(serializer.data)

Acciones Personalizadas

python
from rest_framework.decorators import api_view, permission_classesfrom rest_framework.permissions import IsAuthenticatedfrom rest_framework.response import Response
@api_view(['POST'])@permission_classes([IsAuthenticated])def add_to_cart(request):    """Agregar producto al carrito del usuario."""    product_id = request.data.get('product_id')    quantity = request.data.get('quantity', 1)
    try:        product = Product.objects.get(id=product_id)    except Product.DoesNotExist:        return Response(            {'error': 'Product not found'},            status=status.HTTP_404_NOT_FOUND        )
    cart, _ = Cart.objects.get_or_create(user=request.user)    CartItem.objects.create(        cart=cart,        product=product,        quantity=quantity    )
    return Response({'message': 'Added to cart'}, status=status.HTTP_201_CREATED)

Patrón de Capa de Servicio

python
# apps/orders/services.pyfrom typing import Optionalfrom django.db import transactionfrom .models import Order, OrderItem
class OrderService:    """Capa de servicio para la lógica de negocio relacionada con pedidos."""
    @staticmethod    @transaction.atomic    def create_order(user, cart: Cart) -> Order:        """Crear pedido a partir del carrito."""        order = Order.objects.create(            user=user,            total_price=cart.total_price        )
        for item in cart.items.all():            OrderItem.objects.create(                order=order,                product=item.product,                quantity=item.quantity,                price=item.product.price            )
        # Vaciar carrito        cart.items.all().delete()
        return order
    @staticmethod    def process_payment(order: Order, payment_data: dict) -> bool:        """Procesar el pago del pedido."""        # Integración con pasarela de pago        payment = PaymentGateway.charge(            amount=order.total_price,            token=payment_data['token']        )
        if payment.success:            order.status = Order.Status.PAID            order.save()            # Enviar email de confirmación            OrderService.send_confirmation_email(order)            return True
        return False
    @staticmethod    def send_confirmation_email(order: Order):        """Enviar email de confirmación del pedido."""        # Lógica de envío de email        pass

Estrategias de Caché

Caché a Nivel de Vista

python
from django.views.decorators.cache import cache_pagefrom django.utils.decorators import method_decorator
@method_decorator(cache_page(60 * 15), name='dispatch')  # 15 minutosclass ProductListView(generic.ListView):    model = Product    template_name = 'products/list.html'    context_object_name = 'products'

Caché de Fragmentos de Plantilla

django
{% load cache %}{% cache 500 sidebar %}    ... contenido costoso del sidebar ...{% endcache %}

Caché de Bajo Nivel

python
from django.core.cache import cache
def get_featured_products():    """Obtener productos destacados con caché."""    cache_key = 'featured_products'    products = cache.get(cache_key)
    if products is None:        products = list(Product.objects.filter(is_featured=True))        cache.set(cache_key, products, timeout=60 * 15)  # 15 minutos
    return products

Caché de QuerySet

python
from django.core.cache import cache
def get_popular_categories():    cache_key = 'popular_categories'    categories = cache.get(cache_key)
    if categories is None:        categories = list(Category.objects.annotate(            product_count=Count('products')        ).filter(product_count__gt=10).order_by('-product_count')[:20])        cache.set(cache_key, categories, timeout=60 * 60)  # 1 hora
    return categories

Señales

Patrones de Señal

python
# apps/users/signals.pyfrom django.db.models.signals import post_savefrom django.dispatch import receiverfrom django.contrib.auth import get_user_modelfrom .models import Profile
User = get_user_model()
@receiver(post_save, sender=User)def create_user_profile(sender, instance, created, **kwargs):    """Crear perfil cuando se crea el usuario."""    if created:        Profile.objects.create(user=instance)
@receiver(post_save, sender=User)def save_user_profile(sender, instance, **kwargs):    """Guardar perfil cuando se guarda el usuario."""    instance.profile.save()
# apps/users/apps.pyfrom django.apps import AppConfig
class UsersConfig(AppConfig):    default_auto_field = 'django.db.models.BigAutoField'    name = 'apps.users'
    def ready(self):        """Importar señales cuando la app esté lista."""        import apps.users.signals

Middleware

Middleware Personalizado

python
# middleware/active_user_middleware.pyimport timefrom django.utils.deprecation import MiddlewareMixin
class ActiveUserMiddleware(MiddlewareMixin):    """Middleware para rastrear usuarios activos."""
    def process_request(self, request):        """Procesar request entrante."""        if request.user.is_authenticated:            # Actualizar última hora activa            request.user.last_active = timezone.now()            request.user.save(update_fields=['last_active'])
class RequestLoggingMiddleware(MiddlewareMixin):    """Middleware para logging de requests."""
    def process_request(self, request):        """Registrar hora de inicio del request."""        request.start_time = time.time()
    def process_response(self, request, response):        """Registrar duración del request."""        if hasattr(request, 'start_time'):            duration = time.time() - request.start_time            logger.info(f'{request.method} {request.path} - {response.status_code} - {duration:.3f}s')        return response

Optimización de Rendimiento

Prevención de Consultas N+1

python
# Mal - consultas N+1products = Product.objects.all()for product in products:    print(product.category.name)  # Consulta separada para cada producto
# Bien - consulta única con select_relatedproducts = Product.objects.select_related('category').all()for product in products:    print(product.category.name)
# Bien - Prefetch para muchos-a-muchosproducts = Product.objects.prefetch_related('tags').all()for product in products:    for tag in product.tags.all():        print(tag.name)

Indexación de Base de Datos

python
class Product(models.Model):    name = models.CharField(max_length=200, db_index=True)    slug = models.SlugField(unique=True)    category = models.ForeignKey('Category', on_delete=models.CASCADE)    created_at = models.DateTimeField(auto_now_add=True)
    class Meta:        indexes = [            models.Index(fields=['name']),            models.Index(fields=['-created_at']),            models.Index(fields=['category', 'created_at']),        ]

Operaciones Masivas

python
# Creación masivaProduct.objects.bulk_create([    Product(name=f'Product {i}', price=10.00)    for i in range(1000)])
# Actualización masivaproducts = Product.objects.all()[:100]for product in products:    product.is_active = TrueProduct.objects.bulk_update(products, ['is_active'])
# Eliminación masivaProduct.objects.filter(stock=0).delete()

Referencia Rápida

PatrónDescripción
Configuración divididaSeparar configuración de dev/prod/test
QuerySet personalizadoMétodos de consulta reutilizables
Capa de ServicioSeparación de lógica de negocio
ViewSetEndpoints de API REST
Validación de SerializerTransformación de request/response
select_relatedOptimización de clave foránea
prefetch_relatedOptimización de muchos-a-muchos
Cache firstCachear operaciones costosas
SeñalesAcciones basadas en eventos
MiddlewareProcesamiento de request/response

Recuerda: Django proporciona muchos atajos, pero para aplicaciones de producción, la estructura y organización importan más que el código conciso. Construir para la mantenibilidad.

Source and attribution

Source:affaan-m/eccindocs/es/skills/django-patternsat commitef648e0

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal