Django 6.0 en 2026: Claves Primarias Compuestas, Tareas en Segundo Plano y Preguntas de Entrevista Técnica

Guía completa de Django 6.0: claves primarias compuestas, tareas nativas en segundo plano, template partials, middleware CSP y preguntas de entrevista.

Django 6.0 claves primarias compuestas y tareas en segundo plano

Django 6.0 marca un punto de inflexión para el desarrollo backend con Python en 2026. El framework que durante más de una década ha definido las mejores prácticas del ecosistema web de Python llega con un conjunto de funcionalidades que los equipos de desarrollo venían reclamando desde hace años. Las claves primarias compuestas eliminan una de las limitaciones históricas del ORM. El sistema nativo de tareas en segundo plano reduce la dependencia de infraestructura externa para operaciones asíncronas sencillas. Los template partials transforman la forma en que se organizan los componentes reutilizables en las plantillas. Y el middleware CSP integrado cierra una brecha de seguridad que hasta ahora solo cubrían paquetes de terceros. Esta guía recorre cada una de estas novedades con ejemplos de código listos para producción y cierra con las preguntas de entrevista que están apareciendo en los procesos de selección técnica de 2026.

Línea de tiempo y requisitos de versión

Django sigue un ciclo predecible de lanzamientos: las versiones LTS reciben soporte extendido durante tres años, mientras que las versiones intermedias reciben soporte por dieciocho meses. Django 6.0 requiere Python 3.12 o superior. Las claves primarias compuestas, que se introdujeron como funcionalidad estable en Django 5.2, alcanzan su integración completa con todo el ecosistema del ORM en esta versión.

Claves primarias compuestas con CompositePrimaryKey

En el modelo relacional clásico, una clave primaria compuesta permite identificar de forma única un registro mediante la combinación de dos o más columnas. Es un patrón fundamental en bases de datos bien normalizadas, especialmente en tablas de relación muchos-a-muchos o tablas intermedias donde la unicidad del registro depende de la combinación de referencias a otras entidades. Hasta la llegada de Django 5.2, la única forma de representar este patrón en el ORM era mediante unique_together combinado con un campo id autoincrementable artificial, lo que rompía la correspondencia con el esquema relacional real de la base de datos.

CompositePrimaryKey resuelve esta limitación de raíz. El campo se declara en el modelo indicando los nombres de las columnas de la base de datos que conforman la clave primaria. Django genera la restricción PRIMARY KEY correspondiente a nivel de base de datos y elimina la necesidad de un campo id autoincrementable.

El siguiente ejemplo modela un sistema de inventario donde cada combinación de producto y almacén tiene una cantidad asociada:

python
# models.py
from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    sku = models.CharField(max_length=20, unique=True)

class Warehouse(models.Model):
    code = models.CharField(max_length=10, primary_key=True)
    location = models.CharField(max_length=200)

class Inventory(models.Model):
    pk = models.CompositePrimaryKey("product_id", "warehouse_id")
    product = models.ForeignKey(Product, on_delete=models.CASCADE)
    warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
    quantity = models.PositiveIntegerField(default=0)
    last_restocked = models.DateTimeField(auto_now=True)

Es importante notar que CompositePrimaryKey recibe los nombres de las columnas subyacentes en la base de datos (product_id, warehouse_id), no los nombres de los campos del modelo Django (product, warehouse). Esta distinción es clave para evitar errores al definir las migraciones.

Consultas con claves compuestas en el ORM

La interacción con registros que utilizan CompositePrimaryKey mantiene la misma interfaz que cualquier modelo Django. La diferencia es que la propiedad pk devuelve una tupla de Python en lugar de un entero, y los métodos de filtrado aceptan tuplas como valor de búsqueda:

python
# views.py
from .models import Inventory, Product, Warehouse

# Create records
laptop = Product.objects.create(name="Laptop Pro", sku="LP-001")
warehouse_a = Warehouse.objects.create(code="WH-A", location="Berlin")

item = Inventory.objects.create(
    product=laptop,
    warehouse=warehouse_a,
    quantity=50
)

# Access the composite pk as a tuple
print(item.pk)  # (1, "WH-A")

# Filter by composite pk
result = Inventory.objects.filter(pk=(1, "WH-A")).first()

# Assign a composite pk directly
new_item = Inventory(pk=(2, "WH-B"))
print(new_item.product_id)  # 2
print(new_item.warehouse_id)  # "WH-B"

El ORM desempaqueta la tupla automáticamente en los campos correspondientes al momento de la asignación. Esto resulta particularmente valioso para equipos que trabajan con esquemas de base de datos heredados donde las tablas ya tienen claves compuestas definidas, o para proyectos que migran desde otros ORMs que ya soportaban este patrón. Django Admin, los serializadores de Django REST Framework y el sistema de migraciones reconocen los modelos con CompositePrimaryKey sin configuración adicional.

Tareas nativas en segundo plano con el decorador @task

Hasta ahora, ejecutar cualquier operación en segundo plano desde Django requería configurar un broker de mensajes, un sistema de workers y un paquete como Celery. Para muchos proyectos, especialmente los que solo necesitan enviar correos o generar un archivo PDF después de una solicitud HTTP, esta infraestructura resultaba desproporcionada respecto a la complejidad real de la tarea.

Django 6.0 introduce el módulo django.tasks con un decorador @task y un método .enqueue() que permiten diferir la ejecución de funciones sin dependencias externas complejas. La convención es mantener un archivo tasks.py dentro de cada aplicación Django, siguiendo el mismo patrón que el framework utiliza para models.py, views.py y admin.py:

python
# tasks.py
from django.tasks import task
from django.core.mail import send_mail

@task
def send_welcome_email(user_email, username):
    """Send a welcome email after user registration."""
    send_mail(
        subject="Welcome to the platform",
        message=f"Hello {username}, your account is ready.",
        from_email=None,  # uses DEFAULT_FROM_EMAIL
        recipient_list=[user_email],
    )
    return f"Email sent to {user_email}"

@task
def generate_report(report_id, format="pdf"):
    """Generate an export report asynchronously."""
    from .services import ReportService
    report = ReportService.generate(report_id, format=format)
    return report.file_path

El despacho desde una vista se realiza mediante .enqueue(), que serializa los argumentos y los envía al backend configurado. La vista retorna de inmediato sin esperar a que la tarea finalice, manteniendo así un tiempo de respuesta rápido para el usuario:

python
# views.py
from .tasks import send_welcome_email, generate_report

def register_user(request):
    # ... user creation logic ...
    # Enqueue the task for background execution
    send_welcome_email.enqueue(
        user_email=user.email,
        username=user.username
    )
    return redirect("dashboard")

def export_view(request, report_id):
    generate_report.enqueue(report_id=report_id, format="csv")
    return JsonResponse({"status": "processing"})

Django 6.0 incluye un backend de base de datos como opción por defecto para entornos de desarrollo y proyectos con volumen bajo, y un backend de Redis para escenarios de producción con mayor carga. La configuración en settings.py permite seleccionar el backend, el número de workers y los intervalos de polling.

Tareas nativas vs. Celery

El sistema nativo de Django 6.0 cubre casos de uso sencillos a moderados de manera efectiva. Para escenarios que requieren enrutamiento avanzado de colas, workflows complejos (chains, chords, groups), reintentos con backoff exponencial configurable, procesamiento distribuido en múltiples máquinas o planificación periódica avanzada, Celery sigue siendo la herramienta adecuada.

Template partials: fragmentos reutilizables sin proliferación de archivos

El sistema de plantillas de Django siempre ofreció la etiqueta {% include %} para reutilizar fragmentos de HTML. El problema era que cada fragmento necesitaba su propio archivo, lo que generaba directorios con decenas de archivos pequeños difíciles de navegar y mantener. Los template partials resuelven este problema permitiendo definir múltiples fragmentos dentro de un mismo archivo y referenciarlos de manera selectiva.

Las etiquetas {% partialdef %} y {% endpartialdef %} delimitan cada fragmento con un nombre único. El siguiente ejemplo agrupa dos representaciones visuales de un producto en un solo archivo de componentes:

html
<!-- templates/components/cards.html -->
{% partialdef product_card %}
<div class="card">
    <h3>{{ product.name }}</h3>
    <p class="price">{{ product.price|floatformat:2 }} EUR</p>
    <span class="stock {% if product.in_stock %}available{% else %}sold-out{% endif %}">
        {% if product.in_stock %}In Stock{% else %}Sold Out{% endif %}
    </span>
</div>
{% endpartialdef %}

{% partialdef product_row %}
<tr>
    <td>{{ product.name }}</td>
    <td>{{ product.price|floatformat:2 }} EUR</td>
    <td>{{ product.quantity }}</td>
</tr>
{% endpartialdef %}

La inclusión selectiva utiliza la sintaxis de fragmento #nombre directamente en la etiqueta {% include %}:

html
<!-- templates/shop/catalog.html -->
{% extends "base.html" %}

{% block content %}
<div class="product-grid">
    {% for product in products %}
        {% include "components/cards.html#product_card" %}
    {% endfor %}
</div>
{% endblock %}

La ventaja operativa es inmediata: las variaciones relacionadas de un componente coexisten en un mismo archivo, lo que simplifica tanto la navegación como el mantenimiento del código. Esta funcionalidad se integra de forma natural con HTMX y otros patrones de intercambio parcial de HTML donde el servidor necesita devolver fragmentos específicos sin renderizar toda la página.

¿Listo para aprobar tus entrevistas de Django?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Middleware CSP nativo para Content Security Policy

Los ataques de cross-site scripting (XSS) siguen siendo una de las vulnerabilidades más explotadas en aplicaciones web. Las cabeceras Content Security Policy (CSP) son la defensa estándar contra este tipo de ataques, ya que permiten restringir las fuentes desde las cuales el navegador puede cargar scripts, estilos, imágenes y otros recursos. Hasta Django 6.0, implementar CSP requería instalar paquetes de terceros como django-csp y configurarlos manualmente.

Django 6.0 incorpora un middleware dedicado y un módulo django.utils.csp con constantes que simplifican la definición de políticas de seguridad directamente en settings.py:

python
# settings.py
from django.utils.csp import CSP

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.middleware.security.ContentSecurityPolicyMiddleware",
    # ... other middleware
]

SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE],
    "style-src": [CSP.SELF, "https://fonts.googleapis.com"],
    "img-src": [CSP.SELF, "https:", "data:"],
    "font-src": [CSP.SELF, "https://fonts.gstatic.com"],
    "connect-src": [CSP.SELF],
}

# Report-only mode for testing (does not block, only reports violations)
SECURE_CSP_REPORT_ONLY = {
    "default-src": [CSP.SELF],
    "report-uri": ["/csp-report/"],
}

La constante CSP.NONCE genera automáticamente un valor único por cada solicitud HTTP y lo inyecta como atributo nonce en las etiquetas <script> del template. Esto permite ejecutar scripts inline legítimos de la aplicación mientras bloquea cualquier script inyectado por un atacante, ya que este no conoce el nonce generado para esa solicitud.

El diccionario SECURE_CSP_REPORT_ONLY resulta particularmente útil durante la fase de implementación gradual. En lugar de bloquear recursos inmediatamente, registra las violaciones en un endpoint configurado. Los equipos pueden analizar los reportes, ajustar las directivas y recién entonces migrar al modo de bloqueo activo con SECURE_CSP. Este enfoque progresivo evita romper funcionalidades existentes durante el despliegue de las políticas de seguridad.

Otros cambios destacados en Django 6.0

Además de las funcionalidades principales, Django 6.0 trae mejoras que impactan la experiencia de desarrollo cotidiana.

BigAutoField como campo por defecto: DEFAULT_AUTO_FIELD ahora apunta a BigAutoField en todos los proyectos nuevos. Esto elimina el límite de aproximadamente 2.1 mil millones de registros que imponía AutoField con su entero de 32 bits. BigAutoField utiliza un entero de 64 bits, lo que expande el límite a más de 9 quintillones de registros. Los proyectos existentes que no hayan configurado este valor de forma explícita recibirán una advertencia durante las migraciones.

Paginación asíncrona: Los QuerySet paginados ahora soportan métodos asíncronos como await page.ahas_next() y await page.ahas_previous(). Las vistas asíncronas pueden paginar resultados sin bloquear el event loop, lo que mejora el throughput en aplicaciones desplegadas con servidores ASGI.

Auto-imports en el shell: El comando python manage.py shell importa automáticamente todos los modelos del proyecto y los módulos de uso frecuente (django.db.models, django.utils.timezone, datetime). Este cambio elimina la repetitiva escritura de imports que consumía tiempo en cada sesión de depuración interactiva.

API modernizada de correo electrónico: El módulo django.core.mail se actualiza con soporte nativo para adjuntos inline, cabeceras personalizadas y una interfaz fluida que simplifica la composición de correos complejos sin necesidad de manipular directamente objetos MIME.

Preguntas de entrevista sobre Django 6.0

Las entrevistas técnicas para posiciones backend con Python en 2026 ya incorporan preguntas sobre las novedades de Django 6.0. Las siguientes preguntas cubren los temas con mayor probabilidad de aparecer en un proceso de selección Django.

P: ¿Cuál es la diferencia entre unique_together y CompositePrimaryKey?

unique_together crea una restricción de unicidad sobre un conjunto de columnas, pero el modelo conserva su campo id autoincrementable como clave primaria. CompositePrimaryKey elimina el campo id y establece la combinación de columnas indicada como la clave primaria real a nivel de base de datos. La primera es una restricción adicional sobre un modelo que mantiene su identidad basada en id; la segunda redefine por completo la identidad del registro.

P: ¿En qué escenarios conviene usar las tareas nativas de Django 6.0 en lugar de Celery?

El sistema nativo es apropiado para tareas simples que no requieren enrutamiento de colas, reintentos sofisticados con backoff configurable ni workflows tipo canvas (chains, groups, chords). Ejemplos típicos incluyen el envío de correos de confirmación, la generación de archivos de exportación y la invalidación de cachés. Celery sigue siendo necesario cuando la aplicación requiere procesamiento distribuido en múltiples máquinas, planificación periódica avanzada con Celery Beat, monitorización en tiempo real con Flower o control granular sobre la concurrencia de los workers.

P: ¿Cómo se accede a la clave primaria compuesta en el ORM y cómo se utiliza en consultas?

La propiedad pk de una instancia con CompositePrimaryKey devuelve una tupla de Python con los valores de las columnas que componen la clave. Para filtrar se utiliza Model.objects.filter(pk=(valor1, valor2)). Para asignar una clave compuesta directamente se usa instancia = Model(pk=(valor1, valor2)), y el ORM desempaqueta la tupla automáticamente en los campos correspondientes. Los métodos get(), filter(), exclude() y update() aceptan tuplas de la misma forma.

P: ¿Qué ventaja ofrecen los template partials frente a la estructura tradicional de múltiples archivos de include?

Los template partials permiten definir múltiples fragmentos reutilizables dentro de un solo archivo, lo que reduce la proliferación de archivos pequeños en el directorio de templates y facilita la navegación del código. La sintaxis {% include "archivo.html#nombre" %} referencia selectivamente un fragmento específico sin cargar el archivo completo. Además, esta funcionalidad se integra de forma natural con patrones de intercambio parcial de HTML como HTMX, donde el servidor necesita devolver fragmentos puntuales de una página sin renderizar toda la plantilla.

Conclusión

Django 6.0 representa la actualización más significativa del framework en varios ciclos de lanzamiento. Las funcionalidades incorporadas en esta versión reducen la dependencia de paquetes externos y alinean el framework con patrones que otros ecosistemas ya ofrecían de forma nativa:

  • Claves primarias compuestas eliminan la necesidad de campos id artificiales en tablas intermedias y de relación, alineando el ORM con el modelo relacional estándar de la base de datos
  • Tareas nativas en segundo plano reducen la complejidad operativa para operaciones asíncronas simples al eliminar la necesidad de un broker externo y workers independientes
  • Template partials organizan fragmentos reutilizables dentro de un mismo archivo, reduciendo la cantidad de templates dispersos y facilitando la integración con patrones de intercambio parcial como HTMX
  • Middleware CSP nativo proporciona protección contra XSS con generación automática de nonces y un modo report-only para despliegues graduales y seguros
  • BigAutoField por defecto previene el agotamiento de claves primarias en tablas de alto volumen sin necesidad de configuración adicional
  • Paginación asíncrona y auto-imports en el shell mejoran la productividad del desarrollador tanto en código de producción como en sesiones interactivas de depuración
  • Django 6.0 requiere Python 3.12 o superior y mantiene compatibilidad con PostgreSQL, MySQL, SQLite y Oracle

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Etiquetas

#django
#python
#composite-primary-keys
#background-tasks
#csp

Compartir

Artículos relacionados