El framework web de Python, de cero · Caso: taller/blog
Desarrollador Python/Django. Full Stack en Xprende Tech (Ecuador) e Infobibliotecas (España). Fundador de Python Piura.
Un vistazo rápido pero completo a Django: qué trae, cómo se organiza y cómo se construye un blog mínimo.
Eventos, charlas y talleres para difundir Python en Piura y la región.
Un blog con experiencias en Python, Django, Odoo y más.
Comunidad abierta a todos los niveles: estudiantes, profesionales y curiosos.
Qué es, en qué cree y cómo piensa.
Gratuito y open source, escrito en Python. Nació en 2005 en el Lawrence Journal-World.
Trabaja el backend: enrutado, base de datos, autenticación, formularios, plantillas.
Soporta múltiples bases de datos y se despliega en varios servidores web.
Del concepto al producto sin montar media infraestructura.
Componentes independientes que puedes cambiar por separado.
Don't Repeat Yourself: la lógica vive en un solo lugar.
Direcciones legibles y mapeadas explícitamente.
De un blog a una plataforma enorme con la misma base.
Model Template View: una variación del clásico MVC.
La clase Python que describe la tabla en la base de datos.
La función o clase que decide qué consultar y qué responder.
El HTML con marcadores que da forma a la respuesta.
Base de datos con objetos Python.
Panel de gestión listo.
Usuarios, grupos y permisos.
Cambios de esquema versionados.
HTML reutilizable y heredable.
Validación de datos de entrada.
Traducciones y zonas horarias.
CSRF, XSS, SQLi y sesiones.
| Django | Flask | FastAPI | |
|---|---|---|---|
| Enfoque | Full-stack | Micro, flexible | Micro, async |
| ORM | Incluido | Extensiones | SQLAlchemy/SQLModel |
| Admin | Incluido | — | — |
| Validación | Forms | Extensiones | Pydantic |
| Ideal para | Portales y sistemas completos | Apps pequeñas | APIs y servicios |
Comparación ilustrativa de áreas cubiertas sin plugins; no es un benchmark.
Del entorno virtual al primer servidor corriendo.
Un entorno aislado con su propia copia de Python y sus dependencias.
virtualenv; hoy venv viene incluido en Python. El prompt (.venv) confirma que está activo.pip freeze deja las versiones exactas en requirements.txt: tu proyecto es reproducible en otra máquina.taller/taller/ duplicada: manage.py queda en la raíz del proyecto.taller/
├── manage.py
├── requirements.txt
└── taller/
├── __init__.py
├── settings.py
├── urls.py
├── asgi.py
└── wsgi.py
settings.pyapps, base de datos, idioma, zona horaria…urls.pyenruta cada URL a una vistaasgi.pyentrada ASGI (async) para producciónwsgi.pyentrada WSGI clásica para producciónmanage.pycomandos de administración del proyectoblog/
├── migrations/
├── admin.py
├── apps.py
├── models.py
├── tests.py
└── views.py
"blog" a INSTALLED_APPS: es el error número uno de todo principiante./admin/panel ya construido para gestionar usuarios y datoscreatesuperuseracceso total al admin y a las apps registradasModelos, ORM, admin, vistas y plantillas con un blog mínimo.
Contiene la configuración y los componentes del sitio: settings, urls, wsgi/asgi. Nuestro taller.
Un componente con una funcionalidad concreta: blog, usuarios, foros, galería… Cada una con sus modelos y vistas.
admin, auth…).INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"blog",
]
LANGUAGE_CODE = "es-pe"
TIME_ZONE = "America/Lima"
from django.contrib import admin
from django.urls import path
from blog import views
urlpatterns = [
path("admin/", admin.site.urls),
path("articulos/", views.lista_articulos, name="lista_articulos"),
]
path() con convertidores (<int:pk>); en 2017 esto se escribía con url() y expresiones regulares. Un urls.py por app mantiene todo ordenado.Descripciónlos datos en código Python que se reflejan en tablas de la base de datos.SQL por detráscada consulta ejecuta SQL y devuelve objetos Python cómodos.models.pycada aplicación declara ahí sus modelos.from django.db import models
class Articulo(models.Model):
titulo = models.CharField(max_length=120)
cuerpo = models.TextField()
publicado = models.BooleanField(default=True)
creado = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.titulo
class Meta:
ordering = ("-creado",)
CharField y TextField son los campos de texto; auto_now_add pone la fecha al crear. __str__ hace legible el objeto en el admin.El historial versionado de los cambios de tus modelos, en archivos Python.
Object Relational Mapping: mapeo objeto-relacional.
Sin SQLconsultas y escrituras con métodos Python; el ORM genera el SQL.Objetoscrear, modificar, eliminar y consultar de forma individual o en conjunto.Portableel mismo código funciona en PostgreSQL, MariaDB, SQLite…python manage.py shell: un intérprete con Django cargado.create() inserta, all() trae todo, filter() filtra y order_by() ordena. Detrás de cada línea hay SQL generado por Django.from django.contrib import admin
from blog.models import Articulo
admin.site.register(Articulo)
Entradareciben un objeto con la petición: método (GET/POST), ruta, datos…Salidadevuelven una respuesta: HTML renderizado, una redirección o un error.views.pycada aplicación declara ahí sus vistas; pueden ser funciones o clases.from django.shortcuts import render
from blog.models import Articulo
def lista_articulos(request):
articulos = Articulo.objects.filter(publicado=True)
return render(request, "blog/lista.html", {"articulos": articulos})
render() une tres cosas: la petición, la plantilla y el contexto (los datos que la plantilla usará).HTML + etiquetas{{ variable }} inserta datos; {% etiqueta %} controla la lógica.¿Dónde viven?en templates/ de cada app, o una carpeta global del proyecto.Herencialas plantillas se extienden y se reutilizan: una base, muchas páginas.<!doctype html>
<html lang="es">
<head>
<title>
{% block titulo %}Mi blog{% endblock %}
</title>
</head>
<body>
{% block contenido %}{% endblock %}
</body>
</html>
{% extends "base.html" %}
{% block titulo %}Artículos{% endblock %}
{% block contenido %}
{% for articulo in articulos %}
<h2>{{ articulo.titulo }}</h2>
{% endfor %}
{% endblock %}
extends hereda la base; block rellena las partes; for recorre el contexto que envió la vista.CSS, JavaScript e imágenes: lo que no cambia con cada petición.
static/una carpeta por app, o una global del proyecto.{% static %}genera la URL correcta de cada archivo en las plantillas.collectstaticreúne todo para servirlo en producción (nginx, S3…).STATIC_URL = "static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
taller/
├── manage.py
├── requirements.txt
├── taller/
│ ├── settings.py
│ ├── urls.py
│ └── ...
└── blog/
├── models.py # Articulo
├── views.py # lista_articulos
├── admin.py # admin.site.register
├── migrations/ # historial de la BD
├── templates/blog/ # base.html, lista.html
└── static/blog/ # estilos.css
Lo que viene después y cómo evitar los tropiezos típicos.
Modelos, ORM, autenticación, admin y migraciones: el motor de datos y la lógica.
Serializers, vistas y routers para exponer todo eso como una API REST con JSON.
Síntoma: no aparecen tablas, ni admin, ni plantillas. Django no sabe que tu app existe.
makemigrations + migrate después de cada cambio. Sin eso, la tabla no existe.
Expone información interna. Se apaga con variables de entorno, igual que SECRET_KEY.
db.sqlite3 y .venv/ van al .gitignore: son locales, no código.
Un modelo, una vista, una plantilla: así piensa Django.