Python Framework

Django

Django es el framework web de Python con "baterías incluidas". Modelos, migraciones, un ORM, plantillas, autenticación y un panel de administración para producción vienen integrados, permitiéndote describir tus datos mientras Django se encarga del resto.

intermediate16 min readUpdated 16 sept 2026
blog/views.py
python
# blog/views.py
from django.shortcuts import get_object_or_404, render

from .models import Post


def post_detail(request, slug):
    post = get_object_or_404(
        Post.objects.select_related("author"),
        slug=slug,
        status=Post.Status.PUBLISHED,
    )
    return render(request, "blog/post_detail.html", {"post": post})
Lanzamiento
2005
Lenguaje
Python
Patrón
MTV (Model–Template–View)
Base de datos
ORM con migraciones
Admin
Integrado
Versión actual
5.x

Por que importa

Por qué Django incluye todo

Baterías incluidas

El ORM, las migraciones, las plantillas, los formularios, la autenticación, las sesiones y un panel de administración vienen integrados, versionados y probados en conjunto.

Seguro por defecto

La protección CSRF, el hashing de contraseñas, los encabezados contra clickjacking y las consultas seguras contra SQL-injection están activos a menos que los desactives deliberadamente.

Un admin real gratis

Registra un modelo y obtén una interfaz CRUD con búsqueda y filtrado para el personal, convirtiendo la creación de herramientas internas en el trabajo de una tarde.

La imagen completa

Modelo, plantilla, vista

Un modelo posee los datos, una plantilla los renderiza y una vista conecta ambos a una solicitud.

Model

Persistir

Una clase de Python se mapea a una tabla. Los campos describen las columnas, las relaciones describen los joins y las migraciones evolucionan el esquema.

Template

Renderizar

El lenguaje de plantillas de Django mezcla HTML con un conjunto deliberadamente pequeño de etiquetas y filtros, con autoescaping activado por defecto.

View

Gestionar

Una vista recibe una solicitud y devuelve una respuesta. Consulta el ORM, aplica lógica y elige una plantilla o un código de estado.

HTML5 de un vistazo

La caja de herramientas de Django

Modelos y ORM

Define clases, obtén tablas. Consulta con Python en lugar de SQL construido mediante strings.

Despachador de URLs

Mapea rutas a vistas con patrones path() y rutas nombradas.

Vistas

Basadas en funciones para mayor claridad, basadas en clases y vistas genéricas para reutilización.

Admin

Un back office generado automáticamente para cada modelo que registres.

Formularios

Formularios declarativos que validan, renderizan y protegen contra manipulaciones.

Django REST Framework

Serializadores, viewsets y routers que convierten modelos en JSON APIs.

Una breve historia

De una herramienta de redacción a un motor web

  1. 2003

    Nacido en una redacción

    Desarrolladores en el Lawrence Journal-World crean un framework interno para cumplir con los plazos diarios.

    03
  2. 2005

    Lanzamiento público

    Django se libera como código abierto y toma su nombre del guitarrista de jazz Django Reinhardt.

    05
  3. 2008

    The Django Software Foundation

    Una organización sin fines de lucro asume la gestión, separando el proyecto de cualquier empresa individual.

    08
  4. 2014

    Migraciones en el núcleo

    Las migraciones de esquema se convierten en una funcionalidad nativa, reemplazando herramientas de terceros.

    14
  5. 2019

    Llega el soporte Async

    Django 3.0 añade soporte ASGI, abriendo la puerta a vistas asíncronas y channels.

    19
  6. Hoy

    Un estándar maduro

    Django 5.x combina un ORM estable con soporte async y fuertes type hints.

    Hoy

La guia completa

Django: Todo lo que necesitas saber

¿Qué es Django?

Django es un framework web para Python con “baterías incluidas”. Mientras que la mayoría de los frameworks te piden elegir una capa de base de datos, un motor de plantillas, un panel de administración y un sistema de autenticación, Django te entrega todo eso en un paquete coherente. Tú describes tus datos y tus páginas, y el framework se encarga de las partes tediosas y sensibles a la seguridad.

Fue creado en 2003 para la redacción de un periódico: un equipo que necesitaba lanzar funcionalidades rápidamente sin colapsar bajo la presión de las fechas de entrega. Ese origen es evidente. Django favorece la convención, la explicitud y una actitud de “debería haber una única forma obvia de hacer las cosas”, y se ha mantenido como una de las opciones más fiables para sitios con mucho contenido y basados en datos.

Si alguna vez has pasado una semana configurando un ORM, una herramienta de migraciones y un sistema de autenticación, Django es el framework que te ahorra esa semana.

El patrón MTV

Django denomina a su arquitectura MTV: Model, Template, View. Es la misma idea que el MVC pero con nombres diferentes, y esa diferencia suele confundir a la gente al principio.

  • Un model es una clase de Python que describe una tabla y sus filas.
  • Un template es un archivo HTML con marcadores de posición que renderiza los datos.
  • Una view es una función o clase que recibe una solicitud, pide datos a los modelos y devuelve una respuesta.

El controlador, en el sentido clásico de MVC, es el propio Django. El despachador de URLs decide qué vista se ejecuta y el framework actúa como mediador entre las tres capas. Una vez que este diagrama queda claro, todo lo demás en Django pasa a ser un detalle en lugar de un misterio.

Modelos y el ORM

Un modelo es una clase que hereda de models.Model. Cada atributo es un campo, y cada campo se convierte en una columna.

# blog/models.py
from django.conf import settings
from django.db import models


class Post(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    body = models.TextField()
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="posts",
    )
    published_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

CharField necesita un max_length porque se mapea a un VARCHAR acotado. TextField no tiene límite. auto_now_add=True establece un valor solo al crear el registro, mientras que auto_now=True se actualiza en cada guardado. null controla la base de datos y blank controla la validación del formulario; están separados a propósito.

Las relaciones son explícitas:

  • ForeignKey es de muchos a uno.
  • ManyToManyField crea una tabla intermedia (join table).
  • OneToOneField es una clave foránea única.

El related_name define el accesor inverso, para que user.posts.all() funcione desde el otro lado. El argumento on_delete es obligatorio porque Django se niega a adivinar qué debería pasar con los hijos cuando se elimina un padre.

Migraciones

Las migraciones son archivos controlados por versiones que describen los cambios en tu esquema. Tú escribes el modelo y Django escribe el SQL.

python manage.py makemigrations blog
python manage.py migrate

makemigrations inspecciona tus modelos y genera una migración; migrate aplica las migraciones pendientes a la base de datos. Debido a que los archivos de migración residen en tu repositorio, cada entorno termina con el mismo esquema y los cambios pueden revisarse como cualquier otro código. Nunca edites una migración ya aplicada; en su lugar, añade una nueva.

Querysets y relaciones

El ORM devuelve querysets, los cuales son perezosos (lazy): no se ejecutan hasta que los iteras, los recortas (slice) o los evalúas. Esta característica te permite encadenar filtros y ejecutar únicamente la consulta final.

Post.objects.filter(status="PB").exclude(author=user).order_by("-published_at")[:10]

Entre los métodos más útiles de los querysets se encuentran filter, exclude, get, first, exists, count, values, values_list, annotate y aggregate. Las expresiones F() te permiten hacer referencia a una columna de la base de datos, y los objetos Q() te permiten construir condiciones OR.

from django.db.models import Count, Q

published = Post.objects.filter(
    Q(status="PB") | Q(published_at__isnull=False)
).annotate(comment_count=Count("comments"))

Aquí es donde el ORM demuestra su valor: las consultas complejas permanecen en Python, se parametrizan de forma segura y son componibles.

Cómo evitar el problema N+1

El error de rendimiento más común en Django es la consulta N+1. Esto ocurre cuando accedes a un objeto relacionado dentro de un bucle, lo que dispara una consulta adicional por cada fila.

# 1 query for posts, then 1 query per post for the author
for post in Post.objects.all():
    print(post.author.username)

select_related integra las relaciones de valor único en la consulta original, mientras que prefetch_related ejecuta una segunda consulta y une los resultados en Python para las relaciones de valores múltiples.

# 1 query for posts joined with author, 1 for the comments
posts = Post.objects.select_related("author").prefetch_related("comments")

Utiliza select_related para foreign keys y campos one-to-one, y prefetch_related para relaciones many-to-many y relaciones inversas. Si notas que una página es lenta, cuenta las consultas con el Django Debug Toolbar antes de optimizar cualquier otra cosa.

Vistas: funciones y clases

Una vista es cualquier elemento ejecutable (callable) que recibe una solicitud y devuelve una respuesta. Las vistas basadas en funciones son el punto de partida más claro.

# blog/views.py
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, render

from .models import Post


def post_list(request):
    posts = Post.objects.filter(status="PB").select_related("author")
    return render(request, "blog/post_list.html", {"posts": posts})


def post_api(request, slug):
    post = get_object_or_404(Post, slug=slug)
    return JsonResponse({"title": post.title, "body": post.body})

Las vistas basadas en clases sacrifican un poco de simplicidad en favor de la reutilización. ListView, DetailView, CreateView, UpdateView y DeleteView implementan los patrones CRUD estándar, y los mixins permiten componer el comportamiento.

from django.views.generic import ListView

from .models import Post


class PostListView(ListView):
    model = Post
    template_name = "blog/post_list.html"
    context_object_name = "posts"
    paginate_by = 10

    def get_queryset(self):
        return Post.objects.filter(status="PB").select_related("author")

Utiliza vistas basadas en funciones para lógica puntual y vistas basadas en clases genéricas para páginas estándar de listado y detalle. Mezclar ambas en una base de código es normal y saludable.

URLs

El despachador de URLs mapea una ruta a una vista. Los patrones residen en urls.py, y cada aplicación puede mantener los suyos propios.

# config/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("blog/", include("blog.urls")),
]
# blog/urls.py
from django.urls import path

from . import views

app_name = "blog"

urlpatterns = [
    path("", views.post_list, name="post_list"),
    path("<slug:slug>/", views.post_detail, name="post_detail"),
]

Los conversores como <int:id> y <slug:slug> validan y tipan el segmento capturado. Nombra siempre tus rutas y revierte el proceso con reverse() o la etiqueta {% url %} en lugar de escribir las rutas a mano, para que los enlaces sobrevivan a un cambio de nombre.

Templates

Los templates son HTML con un lenguaje pequeño e intencionalmente limitado. El autoescaping está activado por defecto, que es lo que te protege contra XSS.

{% extends "base.html" %}
{% block content %}
  <h1>{{ post.title }}</h1>
  <p>By {{ post.author.username }} on {{ post.published_at|date:"F j, Y" }}</p>
  <div>{{ post.body|linebreaks }}</div>
{% endblock %}

{% extends %} permite la herencia de templates, {% include %} comparte fragmentos, y los filtros como date, linebreaks y default formatean los valores. No se permite Python arbitrario en los templates; si un template necesita lógica, dicha lógica debe residir en la vista o en el modelo.

Formularios y validación

El sistema de formularios de Django valida la entrada, renderiza el HTML y protege contra manipulaciones mediante un token CSRF.

# blog/forms.py
from django import forms

from .models import Post


class PostForm(forms.ModelForm):
    class Meta:
        model = Post
        fields = ["title", "slug", "body", "status", "published_at"]
        widgets = {"body": forms.Textarea(attrs={"rows": 12})}
# blog/views.py
from django.shortcuts import redirect, render

from .forms import PostForm


def post_create(request):
    if request.method == "POST":
        form = PostForm(request.POST)
        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.save()
            return redirect(post)
    else:
        form = PostForm()

    return render(request, "blog/post_form.html", {"form": form})

Un ModelForm deriva los campos y la validación del modelo, evitando que ambos se desincronicen. Los métodos de limpieza como clean_slug añaden reglas personalizadas, y form.is_valid() recopila todos los errores antes de interactuar con la base de datos.

El admin

El admin es la funcionalidad más infravalorada de Django. Registra un modelo y obtendrás una interfaz para el personal que permite realizar búsquedas, aplicar filtros y gestionar permisos.

# blog/admin.py
from django.contrib import admin

from .models import Post


@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ["title", "author", "status", "published_at"]
    list_filter = ["status", "created_at"]
    search_fields = ["title", "body"]
    prepopulated_fields = {"slug": ["title"]}
    raw_id_fields = ["author"]
    date_hierarchy = "published_at"

Eso es una herramienta interna completa en apenas una docena de líneas. Para sitios de contenido, el admin es a menudo el producto en sí mismo. También actúa como un límite de seguridad: los permisos del personal, has_add_permission y has_change_permission controlan exactamente qué puede hacer cada rol.

Configuración y apps

Un proyecto de Django es una colección de apps, donde cada una es un paquete autónomo con sus propios modelos, vistas, plantillas y migraciones. INSTALLED_APPS las enumera, y settings.py contiene la configuración.

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "blog",
]

Mantén la configuración fuera del control de versiones para proteger los secretos. Léelos desde variables de entorno, divide la configuración por entorno si el proyecto crece y utiliza django-environ o pydantic-settings para procesarlos. Los comandos django-admin startproject y startapp generan el andamiaje de ambas capas, por lo que la estructura nunca está en duda.

Valores predeterminados de seguridad

Los valores predeterminados de Django están diseñados para que la opción más segura sea también la más sencilla:

  • Los tokens CSRF protegen los formularios que modifican el estado; el middleware rechaza las solicitudes que no los incluyan.
  • Se evita la SQL injection ya que los querysets parametrizan los valores.
  • Se mitiga el XSS porque las plantillas escapan las variables, a menos que decidas desactivarlo con |safe.
  • Las contraseñas se procesan mediante un hash con un algoritmo moderno y hashers configurables.
  • El Clickjacking se bloquea mediante el middleware X-Frame-Options.
  • El uso de HTTPS se impone en producción mediante SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE y CSRF_COOKIE_SECURE.

Ejecuta python manage.py check --deploy antes de desplegar a producción. Te avisará sobre los ajustes que hayas olvidado y es mucho más económico que realizar una auditoría de seguridad.

Creación de APIs con Django REST Framework

Para APIs JSON, Django REST Framework (DRF) es el complemento estándar. Un serializer describe cómo un modelo se convierte en JSON y viceversa, mientras que un viewset junto con un router generan los endpoints CRUD.

# blog/serializers.py
from rest_framework import serializers

from .models import Post


class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField()

    class Meta:
        model = Post
        fields = ["id", "title", "slug", "author", "status", "published_at"]
# blog/api.py
from rest_framework import viewsets

from .models import Post
from .serializers import PostSerializer


class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.select_related("author")
    serializer_class = PostSerializer
    lookup_field = "slug"
# blog/urls.py
from rest_framework.routers import DefaultRouter

from .api import PostViewSet

router = DefaultRouter()
router.register("posts", PostViewSet, basename="post")

urlpatterns = router.urls

Los serializers validan la entrada de la misma forma que lo hacen los formularios, los viewsets proporcionan las acciones de list, create, retrieve, update y delete de forma automática, y DRF puede generar esquemas OpenAPI para tu API. El ORM de Django y las convenciones de DRF representan un camino consolidado para APIs en producción.

Pruebas

El test runner de Django crea una base de datos de prueba limpia y proporciona un cliente que interactúa con todo el pipeline de peticiones.

# blog/tests.py
from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse

from .models import Post


class PostDetailTests(TestCase):
    def setUp(self):
        user = get_user_model().objects.create_user("ada", password="secret")
        self.post = Post.objects.create(
            title="Hello",
            slug="hello",
            body="First post",
            author=user,
            status=Post.Status.PUBLISHED,
        )

    def test_detail_page_renders(self):
        response = self.client.get(reverse("blog:post_detail", args=["hello"]))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Hello")

    def test_missing_post_returns_404(self):
        response = self.client.get(reverse("blog:post_detail", args=["nope"]))
        self.assertEqual(response.status_code, 404)

TestCase envuelve cada prueba en una transacción y hace un rollback, para que las pruebas permanezcan aisladas y sean rápidas. Usa django.test.Client para las vistas, el ORM directamente para los modelos, y pytest-django si prefieres los fixtures de pytest. Intenta probar el comportamiento y los permisos, no los detalles de implementación.

Mejores prácticas

  • Mantén los modelos enfocados en los datos y en métodos de dominio pequeños; deja la orquestación para los servicios o las vistas.
  • Ejecuta siempre select_related y prefetch_related cuando una plantilla acceda a relaciones.
  • Utiliza ModelForm y serializers de DRF para que la validación resida en un solo lugar.
  • Asigna un nombre a cada URL y utiliza el reverse; nunca escribas rutas fijas (hardcode) en las plantillas o en el código.
  • Añade índices de base de datos en las columnas que utilices para filtrar y ordenar.
  • Guarda los secretos en variables de entorno y ejecuta check --deploy antes del lanzamiento.
  • Escribe las migraciones como artefactos de code review y nunca edites una que ya haya sido aplicada.
  • Prueba el pipeline de peticiones con Client y la lógica de dominio de forma directa.

Errores comunes

  • Iterar sobre un queryset y acceder a relaciones dentro del bucle, provocando consultas N+1.
  • Usar null=True en un campo de texto en lugar de una cadena vacía, y luego luchar contra las validaciones de None.
  • Colocar lógica de negocio en las plantillas porque la vista parecía demasiado saturada.
  • Llamar a save() dentro de un bucle en lugar de usar bulk_create o bulk_update.
  • Olvidar on_delete o elegir CASCADE sin considerar la posible pérdida de datos.
  • Dejar DEBUG = True y un SECRET_KEY hardcodeado en producción.
  • Tratar el admin como un frontend orientado al usuario final en lugar de una herramienta para el staff.
  • Realizar consultas con len(queryset) en lugar de .count(), cargando cada fila en memoria.

Próximos pasos

Django te ofrece el camino completo y convencional para crear una aplicación web de Python lista para producción. Si prefieres un núcleo más ligero y mayor flexibilidad, lee a continuación la guía de Flask. Si estás construyendo una API JSON asíncrona moderna y quieres que los tipos se encarguen de la validación, pasa a FastAPI. Elijas lo que elijas, invierte en la base de datos: el indexado, las transacciones y la planificación de consultas importan más que el framework, y el backend roadmap profundiza en ello. La guía de REST APIs cubre las reglas de diseño que hacen que una API sea agradable de consumir.

En la practica

Cuatro archivos que componen una app de Django

Un modelo, una vista, un mapa de URLs y un registro de admin son el ciclo central de un proyecto de Django.

blog/models.py
from django.conf import settings
from django.db import models
from django.urls import reverse


class Post(models.Model):
    class Status(models.TextChoices):
        DRAFT = "DF", "Draft"
        PUBLISHED = "PB", "Published"

    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)
    body = models.TextField()
    author = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.CASCADE,
        related_name="posts",
    )
    status = models.CharField(
        max_length=2,
        choices=Status.choices,
        default=Status.DRAFT,
    )
    published_at = models.DateTimeField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("blog:post_detail", kwargs={"slug": self.slug})

Carga de objetos relacionados

Acceder a una foreign key dentro de un bucle ejecuta una consulta por fila. Recupera la relación por adelantado y toda la página costará una sola consulta.

Preferir
posts = Post.objects.select_related("author")

for post in posts:
    print(post.author.username)
Evitar
posts = Post.objects.all()

for post in posts:
    # one extra query per iteration (the N+1 problem)
    print(post.author.username)

Gestión de filas inexistentes

get_object_or_404 convierte una excepción DoesNotExist en una respuesta 404 adecuada y mantiene la vista libre del ruido de try/except.

Preferir
from django.shortcuts import get_object_or_404

post = get_object_or_404(Post, slug=slug)
Evitar
from django.http import Http404

try:
    post = Post.objects.get(slug=slug)
except Post.DoesNotExist:
    raise Http404

Compromisos

¿Es Django la opción predeterminada correcta?

Django optimiza la completitud y la convención. Esto es una ventaja cuando hay plazos ajustados y una limitación cuando buscas algo minimalista.

Strengths

  • Todo está ya ahí

    Auth, sesiones, formularios, un ORM, migraciones y un admin son mantenidos por un solo proyecto, por lo que encajan sin necesidad de código puente.

  • La seguridad es la prioridad

    El framework se defiende de los conceptos básicos de OWASP desde el inicio, y la forma documentada de hacer algo suele ser la más segura.

  • Una comunidad enorme y estable

    Veinte años de respuestas, paquetes y ofertas de empleo significan que casi cualquier problema tiene una solución ya probada.

Trade-offs

  • La curva de aprendizaje es real

    La configuración, las apps, el ORM y el lenguaje de plantillas son mucha información para absorber a la vez. El framework premia más la paciencia que los experimentos rápidos.

  • Es monolítico por diseño

    Django asume una base de datos relacional y una arquitectura de renderizado en servidor o API. Los microservicios y stacks exóticos van en contra de su naturaleza.

  • El soporte Async es joven

    El ORM sigue siendo mayoritariamente síncrono, por lo que mezclar vistas async con trabajo de base de datos requiere cuidado y el uso de hilos.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender Django?

Nuestro tutorial interactivo te guia a traves de Django paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.