#Qué problema resuelve un framework
Una aplicación web es un programa que otras personas utilizan enviándole mensajes. El navegador pide una página; el servidor decide qué puede mostrar, consulta datos y devuelve una respuesta. Django es un framework: proporciona una estructura y piezas coordinadas para resolver rutas, datos, formularios, usuarios y seguridad sin construir cada mecanismo desde cero. Tú escribes las reglas de tu producto dentro de esa estructura.
No necesitas conocer otro framework. Sí conviene distinguir tres cosas: Python ejecuta tu código, Django organiza la aplicación y un servidor recibe las conexiones HTTP. Al principio runserver combina lo necesario para desarrollar. Más adelante separaremos servidor de aplicación, proxy y base de datos para desplegar.
Este curso usa Django 5.2 LTS y Python 3.12 o 3.13. LTS significa una serie con mantenimiento prolongado; no significa que puedas olvidarte de sus actualizaciones. Se mantiene el rango 5.2 para que las APIs explicadas sean coherentes, y al operar una aplicación se instalan sus parches de seguridad. Política y versiones soportadas.
#Leer HTTP antes de escribir una view
Una petición tiene un método, una ruta, cabeceras y, a veces, un cuerpo. GET /articulos/?pagina=2 pide una representación; POST /articulos/nuevo/ envía una acción o datos. La parte ?pagina=2 no es parte del patrón de ruta: son parámetros de consulta. Una cabecera como Accept: text/html expresa qué formato acepta el cliente. El cuerpo contiene, por ejemplo, campos de un formulario.
La respuesta tiene un código de estado, cabeceras y un cuerpo. 200 significa éxito; 302 envía a otra URL mediante Location; 404 indica que no se encuentra el recurso y 500, un fallo no resuelto del servidor. Content-Type: text/html indica cómo interpretar el cuerpo. Un 200 con una página que dice «error» no equivale a un error HTTP: las herramientas necesitan que ambos sean coherentes.
GET no debe crear ni borrar datos de negocio: navegadores, buscadores y caches pueden repetirlo. POST no garantiza que una acción ocurra una sola vez; un usuario puede hacer doble clic o repetir una petición tras un corte. Resolveremos esa diferencia cuando haya escrituras concurrentes.
#Un entorno que puedes volver a construir
Desde una carpeta de trabajo, ejecuta los comandos uno a uno. Esta primera práctica es una pequeña web de artículos; el proyecto final tendrá su propia carpeta y dominio de reservas.
mkdir web-articulos
cd web-articulos
python3 -m venv .venv
source .venv/bin/activate
python -m pip install 'Django>=5.2,<5.3'
python -m django startproject config .
python manage.py startapp articulos
venv crea un entorno con sus propios paquetes. Activarlo hace que python apunte a ese entorno en esta terminal. python -m pip instala con el mismo intérprete que utilizarás para ejecutar. En PowerShell usa py -m venv .venv y .venv\Scripts\Activate.ps1; después puedes usar python igual que aquí.
startproject config . crea la configuración del sitio en la carpeta actual; el punto evita una carpeta exterior adicional. startapp articulos prepara un paquete Python para una responsabilidad del sitio. Una aplicación no es un proceso separado ni un microservicio: muchas aplicaciones pueden convivir dentro del mismo proyecto y proceso.
| Pieza | Pregunta que responde |
|---|---|
manage.py |
¿Cómo ejecuto un comando con la configuración de este proyecto? |
config/settings.py |
¿Qué aplicaciones, base, idioma y políticas utiliza? |
config/urls.py |
¿Qué función recibe cada ruta inicial? |
articulos/views.py |
¿Qué devuelve una petición de artículos? |
articulos/models.py |
¿Qué información persistimos y con qué restricciones? |
articulos/migrations/ |
¿Cómo evoluciona esa estructura en bases existentes? |
En config/settings.py, añade "articulos" al final de INSTALLED_APPS, conservando las entradas existentes. Cambia LANGUAGE_CODE = "es" y TIME_ZONE = "Europe/Madrid"; conserva USE_TZ = True. Las fechas internas y su presentación no son lo mismo: volveremos a ello con las reservas. Mantén por ahora la base SQLite generada; funciona como archivo local sin servidor adicional.
#Conectar las piezas sin magia
Sustituye articulos/views.py por esta función:
from django.http import HttpResponse
def listado(request):
return HttpResponse("La sección de artículos responde.")
Una view es una función que recibe un objeto de petición y devuelve un objeto de respuesta. Django crea request y lo pasa al llamar a tu función. HttpResponse convierte el texto en una respuesta con estado 200. Aquí no hay modelo ni template: aislar la ruta permite saber qué parte funciona antes de añadir datos.
Crea articulos/urls.py; startapp no lo genera:
from django.urls import path
from . import views
app_name = "articulos"
urlpatterns = [
path("", views.listado, name="listado"),
]
El punto de . import views importa desde este mismo paquete. Se pasa views.listado sin paréntesis: quieres entregar una función para usarla después, no ejecutarla al iniciar el proceso. name identifica la ruta sin obligarte a repetir su texto por toda la aplicación; app_name agrupa esos nombres.
Sustituye config/urls.py:
from django.contrib import admin
from django.urls import include, path
urlpatterns = [
path("admin/", admin.site.urls),
path("articulos/", include("articulos.urls")),
]
Para /articulos/, la primera coincidencia consume articulos/ y include entrega el resto —una cadena vacía— a las rutas de esa aplicación. Django no busca por sí mismo una función que se llame como la URL.
#Observar una petición completa
Desde la carpeta que contiene manage.py:
python manage.py migrate
python manage.py runserver 127.0.0.1:8000
migrate crea las tablas de usuarios, sesiones y otras aplicaciones incorporadas. Todavía no has definido tablas propias. El servidor ocupa la terminal; abre otra, o detén con Ctrl+C cuando necesites cambiar de comando. En la otra terminal:
curl -i http://127.0.0.1:8000/articulos/
curl -i http://127.0.0.1:8000/no-existe/
curl es un cliente HTTP de terminal; -i muestra las cabeceras junto al cuerpo. Esperas un 200 con la frase y un 404 en la segunda dirección. En el navegador, http://127.0.0.1:8000/articulos/ muestra el mismo contenido. 127.0.0.1 significa esta máquina; no estás publicando el servicio en Internet.
El log del servidor debe mostrar método, ruta y estado. Cambia la frase, guarda y recarga. El autoreloader reinicia el código al detectarlo: es comodidad de desarrollo, no persistencia de estado. Una variable global que guardase artículos desaparecería al reiniciar y sería distinta entre varios procesos.
#Diagnosticar según dónde se rompe
| Síntoma | Qué comprobar primero | Por qué |
|---|---|---|
| No conecta | Proceso en marcha y puerto correcto | Django aún no recibió la petición |
404 en / |
No hemos definido esa ruta | El servidor funciona, falta una coincidencia |
| Error de importación | Nombre de paquete, entorno activo, fichero guardado | El proyecto no logra cargar su código |
no such table |
Migraciones aplicadas en esa base | El código y la estructura persistente no coinciden |
| 500 al abrir una ruta | Traceback en la terminal de desarrollo | Hay una excepción durante el procesamiento |
Lee el traceback desde el tipo y mensaje final hacia la primera línea de tu código implicada. No soluciones cualquier fallo borrando db.sqlite3: podrías ocultar una migración incorrecta y perder datos que necesitabas conservar.
Comprobación: explica por qué la URL /articulos/ funciona aunque el fichero se llame views.py, y por qué un cambio en una variable Python no equivale a guardar en una base. El siguiente capítulo separa parámetros, métodos y respuestas sin añadir todavía nuevas dependencias.