04/08
Modelos, migraciones y primer despliegue dinámico en Render
La semana pasada conectaste Django a MySQL, pero todavía no tenías una tabla propia. Esta semana defines tu primer modelo real (el catálogo del proyecto integrador), lo migras a MySQL, y publicas la app completa en Render como Web Service —ya no un sitio estático como en la Unidad 1, sino un servidor Python corriendo de verdad en la nube, leyendo y escribiendo en la misma base de datos que usas en tu computadora.
¿Cuál terminal? Todos los comandos de esta guía se escriben en la terminal integrada de VS Code (PowerShell en Windows): Ver → Terminal o Ctrl + ñ. El paso 04 explica cómo abrirla y verificarla.
u2s4-apellidopaterno-apellidomaterno-nombres en GitHub y la app en https://u2s4-apellidopaterno-apellidomaterno-nombres.onrender.com — tu Django corriendo en Render, conectado a la misma MySQL de Aiven, con /admin funcionando y una captura de DBeaver con tus registros.
El ORM de Django: de la clase Python a la tabla SQL
ORM significa "Object-Relational Mapping": un traductor que convierte clases de Python en tablas SQL, y objetos de Python en filas. Nunca escribes CREATE TABLE a mano — Django lo genera por ti a partir de tu modelo.
-- CREATE TABLE: crea una tabla nueva en la base de datos
CREATE TABLE core_servicio (
-- id: clave primaria, se autoincrementa sola en cada fila nueva
id INT AUTO_INCREMENT PRIMARY KEY,
-- propietario_id: número que apunta a la fila del usuario dueño
propietario_id INT,
-- titulo: texto corto, máximo 120 caracteres
titulo VARCHAR(120),
-- descripcion: texto largo, sin límite fijo
descripcion TEXT,
-- precio: número decimal, 8 dígitos en total y 2 después del punto
precio DECIMAL(8,2),
-- categoria: texto corto para clasificar
categoria VARCHAR(60),
-- creado_en: fecha y hora de creación de la fila
creado_en DATETIME
-- cierre de la definición de columnas
);
# class Servicio: cada clase que hereda de models.Model se convierte en una tabla
class Servicio(models.Model):
# ForeignKey: genera la columna propietario_id, apuntando a la tabla de usuarios
propietario = models.ForeignKey(User, on_delete=models.CASCADE)
# CharField(max_length=120): genera VARCHAR(120)
titulo = models.CharField(max_length=120)
# TextField(): genera TEXT
descripcion = models.TextField()
# DecimalField(...): genera DECIMAL(8,2)
precio = models.DecimalField(max_digits=8, decimal_places=2)
# CharField(max_length=60): genera VARCHAR(60)
categoria = models.CharField(max_length=60)
# DateTimeField(auto_now_add=True): genera DATETIME, autocompletado al crear
creado_en = models.DateTimeField(auto_now_add=True)
Tres equivalencias para recordar
Clase Python ↔ tabla · instancia del objeto ↔ fila · atributo del objeto ↔ columna. Cuando más adelante escribas Servicio.objects.all(), Django traduce eso a un SELECT * FROM core_servicio por ti.
Nunca vas a editar la base de datos a mano con SQL en este curso: todo cambio de estructura pasa por el modelo en Python y después por makemigrations/migrate (paso 03). Así el historial de cambios de tu base de datos queda versionado junto con tu código en Git.
Diseñar el modelo del proyecto integrador
Este es el modelo mínimo del proyecto integrador del curso —una "vitrina" donde cada alumno publica un servicio o producto real (tutorías, diseño, repostería, lo que sea). Va en la app core que ya creaste la Semana 3, y crece en las próximas unidades.
# models: trae las herramientas de Django para describir tablas como clases
from django.db import models
# User: el modelo de usuario que Django ya trae incluido (login, contraseñas, sesiones)
from django.contrib.auth.models import User
# class Servicio: se convierte en la tabla "core_servicio" dentro de MySQL
class Servicio(models.Model):
# propietario: relaciona este servicio con el usuario dueño
# on_delete=CASCADE: si se borra ese usuario, se borran también sus servicios
propietario = models.ForeignKey(User, on_delete=models.CASCADE)
# titulo: texto corto y obligatorio, máximo 120 caracteres
titulo = models.CharField(max_length=120)
# descripcion: texto largo, sin límite fijo de caracteres
descripcion = models.TextField()
# precio: número decimal — hasta 8 dígitos en total, 2 después del punto (ej. 1500.00)
precio = models.DecimalField(max_digits=8, decimal_places=2)
# categoria: texto corto para clasificar el servicio (ej. "Tutorías", "Diseño")
categoria = models.CharField(max_length=60)
# creado_en: Django guarda automáticamente la fecha y hora cuando se crea el registro
creado_en = models.DateTimeField(auto_now_add=True)
# __str__: define qué texto se muestra al "imprimir" este objeto (ej. en el panel /admin)
def __str__(self):
# devuelve el título en vez de algo genérico como "Servicio object (1)"
return self.titulo
Este modelo va en core/models.py, el archivo que startapp ya te creó vacío en la Semana 3 — no crees un archivo nuevo.
makemigrations / migrate + registro en admin.py
Escribir el modelo no cambia la base de datos por sí solo. Hacen falta dos comandos: uno que genera el "plan de cambios" y otro que lo aplica de verdad contra MySQL.
Generar y aplicar la migración
# makemigrations lee tus modelos y genera un archivo con los cambios pendientes (no toca la BD todavía)
python manage.py makemigrations core
# migrate aplica esos cambios de verdad: aquí SÍ se crea la tabla core_servicio en MySQL
python manage.py migrate
Vas a ver un archivo nuevo en core/migrations/0001_initial.py — es el "plan de cambios" que makemigrations generó. Se sube a Git junto con el resto del código.
Registrar el modelo en el panel de administración
# admin: el módulo que arma el panel /admin automáticamente
from django.contrib import admin
# Servicio: el modelo que escribiste en el paso 02, importado desde este mismo directorio
from .models import Servicio
# register: le dice a Django "muestra este modelo en el panel /admin, con un CRUD ya armado"
admin.site.register(Servicio)
Crear tu usuario administrador (si todavía no lo hiciste)
# Te pide usuario, correo (opcional) y contraseña; es la cuenta con la que vas a entrar a /admin (local y en Render)
python manage.py createsuperuser
Verificar en local
# Arranca el servidor de desarrollo en http://127.0.0.1:8000 (se detiene con Ctrl + C)
python manage.py runserver
Abre http://127.0.0.1:8000/admin/, inicia sesión con el superusuario y confirma que "Servicio" aparece en la lista de modelos administrables.
makemigrations y migrate son dos pasos distintos a propósito: el primero genera el plan (puedes revisarlo antes de aplicarlo), el segundo lo ejecuta. Vas a repetir este mismo par de comandos cada vez que cambies un modelo, en cualquier unidad del curso.
requirements.txt, gunicorn y configuración lista para producción
Hasta ahora tu proyecto solo corría en tu computadora con runserver. Ese servidor es de prueba: es lento, no es seguro y Django mismo dice que nunca se use en producción. En este paso dejas el proyecto listo para que Render lo pueda ejecutar: instalas un servidor real (gunicorn), un ayudante para los archivos CSS/JS (whitenoise), sacas las claves secretas del código y generas la lista de dependencias. Todavía no tocas Render: eso es el paso 06.
Antes de empezar · ¿cuál terminal uso?
Cada vez que esta guía dice «terminal» se refiere a la terminal integrada de VS Code, que en Windows abre PowerShell por dentro. No uses la app «Windows PowerShell» suelta, ni el «Símbolo del sistema» (CMD), ni Git Bash: la de VS Code ya se abre en la carpeta correcta y ves tu código y la terminal en la misma ventana.
- Abre VS Code y ve a Archivo → Abrir carpeta…. Elige la carpeta de tu proyecto: la que tiene adentro el archivo
manage.py(la misma de la Semana 3). - Abre la terminal: menú Ver → Terminal, o el atajo Ctrl + ñ (teclado en español; en teclado en inglés es Ctrl + `). Aparece un panel abajo.
- Confirma que es PowerShell: a la derecha del panel dice
powershell, y la línea empieza conPS C:\.... Si dicebashocmd, haz clic en la flechita ˅ junto al + y elige PowerShell. - Confirma que el entorno virtual está activo: la línea debe empezar con
(venv). Si no aparece, actívalo con el primer comando del bloque de abajo. - Confirma que estás en la carpeta correcta: escribe
lsy revisa que en la lista aparezcamanage.py.
# Activa el entorno virtual "venv" que creaste en la Semana 3 (debe aparecer "(venv)" al inicio de la línea)
venv\Scripts\Activate.ps1
# Solo si el comando anterior dice "la ejecución de scripts está deshabilitada": permite scripts en esta terminal y vuelve a activar
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# Lista los archivos de la carpeta actual: debes ver manage.py, .env, core, venv, etc.
ls
Los comandos de esta guía se escriben tal cual, una línea a la vez, y se ejecutan con Enter. Las líneas que empiezan con # son explicaciones: no las escribas. Para pegar en la terminal de VS Code usa Ctrl + V o clic derecho.
Los archivos que vas a tocar en este paso
Así se ve tu proyecto en el explorador de VS Code (panel izquierdo, Ctrl + Shift + E). Si tu proyecto no se llama miproyecto, usa tu nombre real en todos lados donde la guía diga miproyecto.
miproyecto/ # carpeta raíz: la que abres en VS Code y la que subes a GitHub
├── manage.py # el archivo con el que corres todos los comandos de Django
├── .env # tus secretos locales (NO se sube a GitHub) → lo editas en 04.5
├── .env.example # copia sin secretos (SÍ se sube) → lo editas en 04.5
├── .gitignore # lista de lo que Git ignora → lo editas en 04.3
├── requirements.txt # lista de paquetes que Render instalará → lo generas en 04.7
├── venv/ # tu entorno virtual (NO se sube a GitHub)
├── core/ # tu app: models.py, admin.py, views.py, templates/
└── miproyecto/ # carpeta de configuración (mismo nombre que el proyecto)
├── settings.py # configuración de Django → lo editas en 04.2 y 04.4
└── wsgi.py # la "puerta de entrada" que usará gunicorn en Render (no lo editas)
Instalar gunicorn y whitenoise
¿Qué son? gunicorn es el servidor profesional que va a ejecutar tu Django en Render (el reemplazo de runserver). whitenoise hace que Django pueda entregar por sí mismo los archivos CSS, JS e imágenes (por ejemplo, los estilos del panel /admin) cuando DEBUG está apagado; sin él, /admin en Render se vería como texto plano sin diseño.
# Instala los dos paquetes dentro de tu entorno virtual (tarda unos segundos)
pip install gunicorn whitenoise
# la última línea confirma que ambos paquetes quedaron instalados
Successfully installed gunicorn-23.0.0 packaging-25.0 whitenoise-6.9.0
gunicorn no funciona en Windows (solo en Linux, que es lo que usa Render). Es normal: lo instalas solo para que quede anotado en requirements.txt. En tu computadora sigues usando python manage.py runserver. No intentes escribir gunicorn en tu terminal: va a marcar error y no pasa nada.
Activar whitenoise en settings.py
- Presiona Ctrl + P, escribe
settings.pyy presiona Enter. Se abremiproyecto/settings.py. - Presiona Ctrl + F y busca
MIDDLEWARE. Es una lista que Django ya trae con 7 líneas. - Pon el cursor al final de la línea de
SecurityMiddleware, presiona Enter y escribe solo la línea deWhiteNoiseMiddleware. El orden importa: tiene que ir en segundo lugar, justo debajo deSecurityMiddleware. Las demás líneas no se cambian.
# MIDDLEWARE: lista de "filtros" por los que pasa cada petición antes de llegar a tu vista, en este orden
MIDDLEWARE = [
# SecurityMiddleware: agrega cabeceras de seguridad básicas a cada respuesta
'django.middleware.security.SecurityMiddleware',
# WhiteNoiseMiddleware: ← ESTA ES LA ÚNICA LÍNEA NUEVA; entrega los archivos CSS/JS/imágenes
'whitenoise.middleware.WhiteNoiseMiddleware',
# SessionMiddleware: habilita las sesiones (login, carrito, etc.)
'django.contrib.sessions.middleware.SessionMiddleware',
# CommonMiddleware: ajustes generales de peticiones/respuestas
'django.middleware.common.CommonMiddleware',
# CsrfViewMiddleware: protege los formularios contra ataques CSRF
'django.middleware.csrf.CsrfViewMiddleware',
# AuthenticationMiddleware: asocia cada petición con el usuario logueado (si hay uno)
'django.contrib.auth.middleware.AuthenticationMiddleware',
# MessageMiddleware: habilita mensajes de una sola vista (ej. "guardado con éxito")
'django.contrib.messages.middleware.MessageMiddleware',
# XFrameOptionsMiddleware: evita que tu sitio se cargue dentro de un <iframe> ajeno
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# cierre de la lista MIDDLEWARE
]
- Ahora busca con Ctrl + F la línea
STATIC_URL(casi al final del archivo; diceSTATIC_URL = 'static/'). No la borres. - Justo debajo de
STATIC_URL, pega el bloque siguiente. - Guarda con Ctrl + S. Si el nombre de la pestaña tiene un punto blanco ●, todavía no está guardado.
# STATIC_URL: ya existía — la dirección web donde se publican los estáticos (ej. /static/admin/css/base.css)
STATIC_URL = 'static/'
# STATIC_ROOT: NUEVO — carpeta "staticfiles" donde collectstatic junta TODOS los CSS/JS/imágenes del proyecto
STATIC_ROOT = BASE_DIR / 'staticfiles'
# STORAGES: NUEVO — le dice a Django cómo guardar archivos
STORAGES = {
# "default": archivos que suban los usuarios; se deja el sistema normal de Django (obligatorio declararlo)
"default": {
# FileSystemStorage: guarda en disco, igual que antes de este cambio
"BACKEND": "django.core.files.storage.FileSystemStorage",
# cierre de la config de "default"
},
# "staticfiles": tus CSS/JS/imágenes; whitenoise los comprime y les pone una "huella" en el nombre
"staticfiles": {
# CompressedManifestStaticFilesStorage: el almacenamiento optimizado que trae whitenoise
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
# cierre de la config de "staticfiles"
},
# cierre del diccionario STORAGES
}
¿Por qué va "default"? Cuando escribes STORAGES, reemplazas por completo la configuración que Django traía de fábrica. Si solo pones "staticfiles", Django se queda sin almacenamiento "default" y falla en cuanto algo intenta guardar un archivo.
Probar collectstatic en tu computadora e ignorar su carpeta
¿Qué hace collectstatic? Busca todos los archivos estáticos del proyecto (los tuyos y los del panel /admin) y los copia a la carpeta staticfiles. Render lo va a correr en cada despliegue; lo pruebas aquí primero para detectar errores antes de subir.
# Junta los estáticos en la carpeta staticfiles; --noinput evita que te pregunte "¿seguro?"
python manage.py collectstatic --noinput
# "copied" = copiados; "post-processed" = whitenoise ya los comprimió y versionó
127 static files copied to 'C:\...\miproyecto\staticfiles', 381 post-processed.
En el explorador de VS Code aparece una carpeta nueva staticfiles/. Es un resultado generado (como venv/), así que no se sube a GitHub: Render la vuelve a generar. Abre .gitignore con Ctrl + P y agrega las dos últimas líneas:
# venv/: no se sube el entorno virtual completo, cada quien crea el suyo con requirements.txt
venv/
# .env: el archivo con tus contraseñas reales, nunca debe llegar a GitHub
.env
# __pycache__/: archivos compilados que Python genera solo, no son código fuente
__pycache__/
# staticfiles/: NUEVO — carpeta que genera collectstatic; Render la crea de nuevo en cada deploy
staticfiles/
Guarda con Ctrl + S. Si collectstatic marcó error, revisa en 04.2 que STATIC_ROOT esté bien escrito y que guardaste settings.py.
Sacar SECRET_KEY, DEBUG y ALLOWED_HOSTS del código
¿Por qué? Tu repositorio está en GitHub. Si la clave secreta está escrita en settings.py, cualquiera que vea el repo la tiene. Además, tu computadora y Render necesitan valores distintos (en tu compu quieres ver errores detallados; en Render, no). La solución es la misma que usaste en la Semana 3 con la base de datos: settings.py lee los valores, y cada entorno los define por su cuenta (tu .env en local, el panel de Render en producción).
Qué significa cada una:
SECRET_KEY: clave con la que Django firma sesiones y cookies. Si alguien la conoce, puede falsificar sesiones.DEBUG: si está enTrue, ante un error Django muestra una página amarilla con tu código y configuración. Útil en tu compu, peligroso en Internet.ALLOWED_HOSTS: lista de dominios desde los que Django acepta visitas. Si el dominio no está en la lista, responde «Bad Request (400)».CSRF_TRUSTED_ORIGINS(nueva): lista de direcciones completas (conhttps://) desde las que Django acepta formularios. Sin ella, al iniciar sesión en/adminde Render sale «Prohibido (403) · CSRF verification failed».
- Abre
settings.py(Ctrl + P). Busca con Ctrl + F la líneafrom decouple import config(la agregaste en la Semana 3, arriba del archivo) y cámbiala para que digafrom decouple import config, Csv. No la dupliques. - Busca
SECRET_KEY. Verás algo comoSECRET_KEY = 'django-insecure-...'. Borra toda esa línea y en su lugar escribe la versión nueva de abajo. - Busca
DEBUG = True. Bórrala y escribe la versión nueva. - Busca
ALLOWED_HOSTS = []. Bórrala y escribe la versión nueva; debajo agrega la línea deCSRF_TRUSTED_ORIGINS. - Guarda con Ctrl + S.
# import de la Semana 3: solo traía config
from decouple import config
# clave escrita a mano: cualquiera en GitHub la puede ver
SECRET_KEY = 'django-insecure-x7#k...'
# siempre encendido, también en producción
DEBUG = True
# lista vacía: solo sirve en tu computadora
ALLOWED_HOSTS = []
# Csv: convierte "a,b,c" del .env en la lista ['a','b','c']
from decouple import config, Csv
# se lee de .env (local) o del panel de Render (producción)
SECRET_KEY = config('SECRET_KEY')
# default=False: si falta la variable, queda apagado (lo seguro); cast=bool convierte "True"/"False" en booleano
DEBUG = config('DEBUG', default=False, cast=bool)
# dominios permitidos, separados por comas en la variable
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
# direcciones completas (con https://) desde las que se aceptan formularios, como el login de /admin
CSRF_TRUSTED_ORIGINS = config('CSRF_TRUSTED_ORIGINS', default='', cast=Csv())
Agregar las 4 variables nuevas a tu .env local y a .env.example
Primero genera una clave secreta al azar. Este comando usa el módulo secrets de Python y produce solo letras, números, - y _, así que no da problemas al pegarla:
# Imprime una clave aleatoria de ~67 caracteres; selecciónala con el mouse y cópiala con Ctrl + C
python -c "import secrets; print(secrets.token_urlsafe(50))"
Abre tu .env con Ctrl + P (escribe .env) y agrega al final las 4 líneas nuevas. Las 5 de la base de datos se quedan igual:
# --- base de datos de Aiven (Semana 3, sin cambios) ---
# nombre de la base
DB_NAME=defaultdb
# usuario de Aiven
DB_USER=avnadmin
# tu contraseña real de Aiven
DB_PASSWORD=tu-password-real-de-aiven
# el host real de Aiven
DB_HOST=tu-host-real.aivencloud.com
# el puerto real de Aiven
DB_PORT=12345
# --- NUEVAS: configuración de Django para tu computadora ---
# pega aquí la clave que acabas de generar
SECRET_KEY=pega-aqui-la-clave-que-generaste
# True en tu compu para ver errores detallados
DEBUG=True
# los dos nombres con los que abres runserver, separados por coma y SIN espacios
ALLOWED_HOSTS=127.0.0.1,localhost
# las mismas direcciones pero completas, con http:// y el puerto 8000
CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8000,http://localhost:8000
Reglas del .env: sin espacios alrededor del =, sin comillas, sin coma al final de las listas, y un valor por línea. Guarda con Ctrl + S.
Luego abre .env.example y agrega las mismas 4 claves con valores de relleno (este archivo sí se sube, así que nunca lleva la clave real):
# mismas claves que .env, con valores de ejemplo
DB_NAME=defaultdb
# usuario de ejemplo
DB_USER=tu-usuario
# contraseña de ejemplo
DB_PASSWORD=tu-password
# host de ejemplo
DB_HOST=tu-host.aivencloud.com
# puerto de ejemplo
DB_PORT=12345
# genera la tuya con: python -c "import secrets; print(secrets.token_urlsafe(50))"
SECRET_KEY=cambia-esto
# True en local, False en Render
DEBUG=True
# en Render: tu-proyecto.onrender.com
ALLOWED_HOSTS=127.0.0.1,localhost
# en Render: https://tu-proyecto.onrender.com
CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8000,http://localhost:8000
Comprobar que todo sigue funcionando en tu computadora
Antes de subir nada, confirma que no rompiste el proyecto. Si algo falla aquí, también fallaría en Render, pero aquí es mucho más fácil de ver.
# Revisa la configuración completa sin arrancar el servidor
python manage.py check
# Arranca el servidor de desarrollo (ocupa la terminal; se detiene con Ctrl + C)
python manage.py runserver
# "no issues" = ningún problema; si ves esto, sigue adelante
System check identified no issues (0 silenced).
Con runserver corriendo, abre http://127.0.0.1:8000/admin/ en el navegador e inicia sesión. Si entras y el panel se ve con colores, vas bien. Detén el servidor con Ctrl + C en la terminal (o abre otra con el + del panel).
| Si ves este error… | Significa | Qué hacer |
|---|---|---|
UndefinedValueError: SECRET_KEY not found | Django no encontró la variable en .env | Revisa que agregaste la línea en .env y que guardaste (Ctrl + S). |
ModuleNotFoundError: No module named 'whitenoise' | El paquete no está instalado en el entorno que estás usando | Confirma que ves (venv) y repite pip install gunicorn whitenoise. |
ImportError: cannot import name 'Csv' | Escribiste mal el import | Debe decir exactamente from decouple import config, Csv (C mayúscula, sv minúsculas). |
DisallowedHost / Bad Request (400) | El dominio no está en ALLOWED_HOSTS | Revisa ALLOWED_HOSTS=127.0.0.1,localhost sin espacios. |
Generar requirements.txt
¿Qué es? Render arranca con un Python vacío: no sabe qué paquetes usa tu proyecto. requirements.txt es la lista exacta (nombre y versión) de todo lo que tienes instalado en tu venv. En el paso 06, Render la lee con pip install -r requirements.txt e instala lo mismo.
# pip freeze lista lo instalado en el venv; ">" guarda esa lista en requirements.txt (reemplaza el anterior)
pip freeze > requirements.txt
Abre requirements.txt con Ctrl + P y confirma que contiene, entre otras, estas líneas (las versiones pueden variar):
# el framework
Django==5.1.4
# el servidor para Render (paso 04.1)
gunicorn==23.0.0
# el conector de MySQL (Semana 3)
PyMySQL==1.1.1
# el lector del .env (Semana 3)
python-decouple==3.8
# los estáticos (paso 04.1)
whitenoise==6.9.0
Lo que importa son los 5 nombres (Django, gunicorn, PyMySQL, python-decouple y whitenoise). Los números después de == dependen del día en que instalaste: si en tu archivo dice, por ejemplo, Django==6.1.2 en lugar de 5.1.4, está bien, no los cambies a mano. También es normal que aparezcan otros paquetes que se instalan solos (asgiref, sqlparse, tzdata, packaging).
Si falta alguna, casi siempre es porque el venv no estaba activo: actívalo, instala lo que falte y vuelve a correr pip freeze > requirements.txt. Cada vez que instales un paquete nuevo en el curso, repite este comando.
Siguiente: subir a GitHub con el nombre oficial de la práctica
Tu proyecto ya está listo para producción. No hagas git push todavía: en el paso 05 primero verificas tu cuenta, creas el repositorio con el prefijo de la práctica y tus apellidos y nombre(s), y después subes.
DEBUG=True en producción muestra tu código y configuración a cualquier visitante cuando ocurre un error. En tu .env local va True; en Render (paso 06) va False. Nunca al revés.
GitHub: crear el repositorio con tu nombre oficial y hacer el push
Cada práctica se entrega en su propio repositorio y el nombre sigue siempre la misma regla. Así el profesor encuentra tu trabajo sin abrir repo por repo, y Render te da una URL que dice de quién es y de qué práctica. El orden importa: primero verificas tu cuenta, después creas el repositorio vacío en GitHub con el nombre correcto, y al final conectas tu carpeta y haces el push.
La regla del nombre · siempre igual
- Prefijo de la práctica:
u+ número de unidad +s+ número de semana. Esta práctica esu2s4. - Todo en minúsculas, sin acentos (
á → a), lañse escribeny los espacios se cambian por guion-. - Orden fijo: prefijo → apellido paterno → apellido materno → nombre(s). Si tienes dos nombres, van los dos.
- GitHub solo acepta letras sin acento, números,
-,_y.(máximo 100 caracteres). Por eso la regla quita acentos y espacios.
| Alumno | Nombre del repositorio de esta práctica |
|---|---|
| María José Hernández Núñez | u2s4-hernandez-nunez-maria-jose |
| José Ángel De la Cruz Peña | u2s4-de-la-cruz-pena-jose-angel |
| Ana López (un solo apellido) | u2s4-lopez-ana |
| Prefijo | Práctica del curso |
|---|---|
u1s1 | Unidad 1 · Semana 1 — Sitio estático en Render |
u1s2 | Unidad 1 · Semana 2 — Tailwind CSS |
u2s3 | Unidad 2 · Semana 3 — Django + MySQL en Aiven |
u2s4 | Unidad 2 · Semana 4 — Modelos y deploy en Render (esta guía) |
u3s5 | Unidad 3 · Semana 5 — CRUD con ModelForms |
u3s6 | Unidad 3 · Semana 6 — Autenticación y sesiones |
u4s7 | Unidad 4 · Semana 7 — Pulido y defensa |
u4s8 | Unidad 4 · Semana 8 — Deploy final |
Genera tu nombre sin errores
u2s4-hernandez-nunez-maria-joseTu repo quedará en https://github.com/TU_USUARIO/u2s4-hernandez-nunez-maria-jose y, si usas el mismo nombre en Render (paso 06), tu sitio en https://u2s4-hernandez-nunez-maria-jose.onrender.com. Los datos de ejemplo están precargados: escribe los tuyos.
Antes que nada: confirma con qué cuenta de GitHub estás conectado
El repositorio se crea en la cuenta con la que estés dentro de github.com, y el push sale con la cuenta guardada en tu compu. Las dos tienen que ser la tuya. Si en la compu quedó la cuenta de otro compañero, el push termina en error 403.
# Lista las cuentas de GitHub guardadas en esta compu; debe salir SOLO tu usuario
git credential-manager github list
# Muestra el nombre con el que se firmarán tus commits
git config user.name
# Muestra el correo de tus commits; debe estar registrado en tu cuenta de GitHub
git config user.email
Si sale otra cuenta, sácala y entra con la tuya:
# Borra de esta compu la cuenta que no es tuya
git credential-manager github logout USUARIO_QUE_NO_ES_TUYO
# Abre el navegador para que inicies sesión con TU cuenta y autorices a Git
git credential-manager github login
# Pone tu nombre en los commits (escríbelo entre comillas)
git config --global user.name "Tu Nombre Completo"
# Pone tu correo de GitHub en los commits
git config --global user.email "tucorreo@ejemplo.com"
En el navegador, revisa también arriba a la derecha de github.com que la foto sea la tuya. Si no, Sign out y entra con tu cuenta.
Crear el repositorio vacío en GitHub
- En
github.com, arriba a la derecha, clic en + → New repository. - Owner: tu usuario. Repository name: pega el nombre que generaste arriba (ej.
u2s4-hernandez-nunez-maria-jose). Si aparece una palomita verde, el nombre está libre. - Description (opcional):
Práctica U2 S4 — Modelos Django y deploy en Render. - Visibility: Public, para que el profesor y Render lo puedan ver sin pasos extra. Si el profesor pide Private, agrégalo después en Settings → Collaborators.
- No marques «Add a README», ni «.gitignore», ni «license». Tu carpeta ya tiene esos archivos; si GitHub crea otros, el primer push será rechazado.
- Clic en Create repository. GitHub muestra una página con una URL que termina en
.git. Clic en el ícono de copiar junto a la opción HTTPS.
Si te equivocaste en el nombre, corrígelo antes de hacer push: en el repo, Settings → General → Repository name → Rename. Después actualiza tu carpeta con git remote set-url (paso 05.3, caso B).
Ver cómo está tu carpeta y conectarla al repositorio nuevo
Primero averigua en qué situación está tu proyecto. En la terminal de VS Code, dentro de la carpeta que tiene manage.py:
# ¿Esta carpeta ya es un repositorio de Git? Si responde "fatal: not a git repository", todavía no lo es
git status
# ¿Ya está conectada a algún repositorio de GitHub? Si no sale nada, no tiene remoto
git remote -v
| Lo que respondió | Tu caso | Qué haces |
|---|---|---|
fatal: not a git repository | A · La carpeta nunca se subió a GitHub | Bloque A |
origin https://github.com/.../miproyecto-django.git (otro nombre) | B · Ya tiene el repo de la Semana 3 u otro | Bloque B |
origin https://github.com/.../u2s4-tus-apellidos-nombre.git | C · Ya apunta al repo correcto de esta práctica | Pasa directo a 05.4 |
# Convierte la carpeta en repositorio de Git (solo se hace una vez)
git init
# Nombra la rama principal "main", que es la que Render va a publicar
git branch -M main
# Conecta la carpeta con el repo vacío que creaste; pega TU URL de GitHub
git remote add origin https://github.com/TU_USUARIO/u2s4-hernandez-nunez-maria-jose.git
# Confirma que la URL quedó bien escrita (debe salir dos veces: fetch y push)
git remote -v
# Cambia la URL de origin al repo nuevo de esta práctica (NO uses "add": origin ya existe y lo rechazaría)
git remote set-url origin https://github.com/TU_USUARIO/u2s4-hernandez-nunez-maria-jose.git
# Confirma que ahora apunta al repo nuevo
git remote -v
# Confirma que estás en la rama main (si dice master, cámbiala con: git branch -M main)
git branch --show-current
En el caso B todo tu historial de commits se va al repo nuevo. El repo de la Semana 3 queda intacto en GitHub como evidencia de esa práctica.
Revisar qué vas a subir (sin secretos)
# Lista lo que cambió: deben aparecer settings.py, requirements.txt, .gitignore, .env.example, core/...
# NO deben aparecer .env, venv/ ni staticfiles/ (si aparecen, revisa .gitignore del paso 04.3)
git status
# Prepara todos los cambios para el commit (respeta lo que dice .gitignore)
git add .
# Revisa otra vez: lo que está en verde es lo que se va a guardar; .env NO debe estar
git status
# Guarda una "foto" de los cambios con un mensaje que explica qué hiciste
git commit -m "U2S4: modelo Servicio, gunicorn, whitenoise y config por variables de entorno"
# Prueba que tu cuenta tiene acceso al repo nuevo (si está vacío no imprime nada, pero tampoco marca error)
git ls-remote origin
Si git status lista .env, no hagas commit. Agrega .env a .gitignore, guarda y corre git rm --cached .env para sacarlo de la lista sin borrarlo de tu compu.
Hacer el push y verificar que llegó al repo correcto
# Sube la rama main y la deja conectada con origin/main (-u); las siguientes veces basta "git push"
git push -u origin main
# Muestra a qué repo subiste y si tu rama está al día ("main pushes to main (up to date)")
git remote show origin
# Debe decir "Your branch is up to date with 'origin/main'" y "working tree clean"
git status
- Abre
https://github.com/TU_USUARIO/u2s4-...y recarga. Arriba a la izquierda debe decir tu-usuario / u2s4-tus-apellidos-nombre. - Revisa que estén
manage.py,requirements.txt,.env.example,core/y la carpeta del proyecto. - Revisa que no estén
.env,venv/nistaticfiles/. Si ves.env, avisa al profesor: hay que cambiar la contraseña de Aiven (paso 07.6).
| Si el push dice… | Causa | Solución |
|---|---|---|
error: remote origin already exists | Usaste add y la carpeta ya tenía origin | Usa el bloque B: git remote set-url origin URL |
Permission to ... denied to otra-cuenta / 403 | La compu guarda la cuenta de otra persona | Repite 05.1: logout de esa cuenta y login con la tuya |
Repository not found | URL mal escrita o el repo se creó en otra cuenta | Compara git remote -v con la URL que copiaste de GitHub |
rejected ... (fetch first) | Creaste el repo con README u otro archivo | git pull origin main --allow-unrelated-histories, guarda el mensaje y repite el push |
src refspec main does not match any | No hay commits o la rama se llama master | git commit primero; luego git branch -M main |
Para una guía completa de cuentas, ramas, commits y errores de Git, usa Git Push y Pull en VS Code (archivo guia-git-push-pull-vscode.html en la carpeta del curso). Esta sección solo cubre lo necesario para esta práctica.
Deploy del Web Service en Render (con migrate automático)
En la Unidad 1 publicaste un Static Site: Render solo entregaba archivos HTML fijos. Ahora creas un Web Service: Render enciende una computadora Linux con Python, descarga tu repositorio de GitHub, instala tus paquetes y deja gunicorn corriendo para que cada visita ejecute tu código Django, que a su vez lee y escribe en tu base MySQL de Aiven.
En el plan Free de Render no existe la pestaña «Shell» (la terminal dentro del servidor): es solo para planes de pago. Por eso migrate se agrega al Build Command, y así Render lo ejecuta solo en cada despliegue. El superusuario no hace falta crearlo en Render: ya existe en tu base de Aiven desde el paso 03 (tu compu y Render usan la misma base).
Entrar a Render y crear el Web Service
- Abre
https://dashboard.render.come inicia sesión con GitHub (la misma cuenta de la Unidad 1). - Arriba a la derecha, clic en + New → Web Service. No elijas «Static Site».
- En «Source Code» elige Git Provider y busca tu repositorio
u2s4-…del paso 05 (no el de la Semana 3). Clic en Connect. - Si tu repositorio no aparece en la lista: clic en Configure GitHub (o «Configure account»), se abre GitHub; en «Repository access» agrega tu repositorio, guarda y regresa a Render.
Llenar el formulario campo por campo
Render te muestra un formulario largo. Llena solo estos campos; el resto déjalo como viene:
| Campo | Qué escribir | Por qué |
|---|---|---|
| Name | El mismo nombre de tu repositorio (paso 05), ej. u2s4-hernandez-nunez-maria-jose | Se convierte en tu dirección: https://u2s4-hernandez-nunez-maria-jose.onrender.com. Así la URL dice de quién es y de qué práctica. Anótalo, lo usas en 06.3. |
| Language | Python 3 | Render suele detectarlo solo por requirements.txt; si no, elígelo. |
| Branch | main | La rama de GitHub que Render publica. Cada git push a ella redespliega solo. |
| Region | La que venga por defecto (ej. Oregon (US West)) | Dónde está físicamente el servidor. Cualquiera funciona para el curso. |
| Root Directory | Déjalo vacío | Solo se llena si manage.py no está en la raíz del repo (ej. si está dentro de una subcarpeta backend/, escribe backend). |
| Build Command | El del bloque de abajo | Lo que Render corre una vez al preparar cada versión. |
| Start Command | El del bloque de abajo | Lo que Render deja corriendo siempre para atender visitas. |
| Instance Type | Free | Gratis; alcanza para el curso. |
# 1) instala los paquetes de requirements.txt 2) junta los estáticos 3) aplica las migraciones en Aiven; "&&" = "si el anterior salió bien, sigue"
pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrate
# arranca gunicorn apuntando a miproyecto/wsgi.py (cambia "miproyecto" por el nombre de la carpeta que tiene settings.py)
gunicorn miproyecto.wsgi
¿Cómo sé qué nombre va en lugar de miproyecto? Abre settings.py en VS Code y busca con Ctrl + F la línea WSGI_APPLICATION. Dice algo como WSGI_APPLICATION = 'miproyecto.wsgi.application': copia lo que está entre la comilla y .application (aquí, miproyecto.wsgi) y ponlo después de gunicorn. Ojo: es el nombre de la carpeta interior (la que tiene settings.py y wsgi.py), no el de la carpeta que abriste en VS Code; pueden ser distintos.
¿Por qué migrate no rompe nada si ya lo corriste en tu compu? Django anota en la base qué migraciones ya se aplicaron. Si no hay nada nuevo, solo dice No migrations to apply. y sigue. Cuando en el futuro cambies un modelo, Render aplicará la migración nueva solito en el siguiente deploy.
Cargar las 9 variables de entorno
Render no tiene tu archivo .env (no lo subiste a GitHub, y está bien). Las variables se escriben en el propio formulario. Baja hasta la sección «Environment Variables» y usa una de estas dos formas:
Estas son las 9 variables que tu settings.py lee con config('NOMBRE'). El nombre (Key) se escribe exactamente así, en mayúsculas; si falta una o tiene un error de dedo, el deploy falla con … not found:
| Key (nombre exacto) | Qué es y para qué sirve | Valor en tu .env local | Valor en Render |
|---|---|---|---|
DB_NAME | Nombre de la base de datos dentro de Aiven. Lo usa DATABASES['NAME'] (Semana 3). | El de Aiven → Overview → «Database name» (normalmente defaultdb) | Igual que en tu .env |
DB_USER | Usuario con el que Django entra a MySQL. | Aiven → Overview → «User» (normalmente avnadmin) | Igual que en tu .env |
DB_PASSWORD | Contraseña de ese usuario. Es el dato más delicado: nunca va en el código ni en GitHub. | Aiven → Overview → «Password» (icono del ojo) | Igual que en tu .env |
DB_HOST | Dirección del servidor de MySQL en Aiven. | Aiven → Overview → «Host»; termina en .aivencloud.com, sin https:// | Igual que en tu .env |
DB_PORT | «Puerta» del servidor por la que entra la conexión. | Aiven → Overview → «Port»: un número de 5 cifras, no 3306 | Igual que en tu .env |
SECRET_KEY | Clave con la que Django firma sesiones, cookies y los tokens CSRF. Quien la conozca puede falsificar sesiones. | La que generaste en 04.5 | Una nueva y distinta, generada con el comando de abajo |
DEBUG | Modo de pruebas: si está en True, ante un error muestra tu código y configuración. | True | False (siempre, con F mayúscula) |
ALLOWED_HOSTS | Dominios desde los que Django acepta visitas; si no coincide, responde «Bad Request (400)». | 127.0.0.1,localhost | u2s4-tus-apellidos-nombres.onrender.com (sin https:// y sin / final) |
CSRF_TRUSTED_ORIGINS | Direcciones completas desde las que Django acepta formularios; sin ella, el login de /admin da «403 CSRF». | http://127.0.0.1:8000,http://localhost:8000 | https://u2s4-tus-apellidos-nombres.onrender.com (con https:// y sin / final) |
- Forma rápida: clic en Add from .env, pega el bloque de abajo (ya con tus valores reales) y clic en Add variables.
- Forma manual: clic en + Add Environment Variable 9 veces; en «Key» va el nombre (ej.
DB_NAME) y en «Value» el valor (ej.defaultdb).
Para las 5 de la base de datos, abre tu .env en VS Code y copia los valores tal cual. Para SECRET_KEY, genera una clave nueva en la terminal de VS Code con python -c "import secrets; print(secrets.token_urlsafe(50))" (no reutilices la local).
# --- las 5 de Aiven: cópialas de tu .env local, idénticas ---
DB_NAME=defaultdb
# usuario de Aiven
DB_USER=avnadmin
# contraseña real de Aiven
DB_PASSWORD=tu-password-real-de-aiven
# host real de Aiven
DB_HOST=tu-host-real.aivencloud.com
# puerto real de Aiven
DB_PORT=12345
# --- las 4 de producción: DISTINTAS a las de tu .env local ---
# clave NUEVA generada solo para Render
SECRET_KEY=pega-aqui-una-clave-nueva-distinta-de-la-local
# en producción SIEMPRE False
DEBUG=False
# solo el dominio: SIN https:// y SIN / al final
ALLOWED_HOSTS=tu-proyecto.onrender.com
# la dirección completa: CON https:// y SIN / al final
CSRF_TRUSTED_ORIGINS=https://tu-proyecto.onrender.com
Si el nombre que elegiste ya estaba ocupado, Render le agrega letras al final (ej. tu-proyecto-a1b2.onrender.com). Después de crear el servicio, copia la dirección real que aparece arriba, debajo del nombre, y corrige ALLOWED_HOSTS y CSRF_TRUSTED_ORIGINS en la pestaña Environment → Edit → Save, rebuild, and deploy.
Crear el servicio y leer el log
Clic en Deploy Web Service (al final del formulario). Render te lleva a la página del servicio y empieza a mostrar el log: el texto que va escribiendo su terminal. Tarda de 2 a 5 minutos. Esto es lo que debes ir viendo, en este orden:
# 1. Render descarga tu repositorio de GitHub
==> Cloning from https://github.com/tu-usuario/tu-repo
# 2. empieza tu Build Command
==> Running build command 'pip install -r requirements.txt && ...'
# 3. pip terminó de instalar tus paquetes
Successfully installed Django-5.1.4 PyMySQL-1.1.1 gunicorn-23.0.0 ...
# 4. collectstatic funcionó (whitenoise listo)
127 static files copied to '/opt/render/project/src/staticfiles', 381 post-processed.
# 5. migrate se conectó a Aiven; no había nada pendiente porque ya migraste en local
No migrations to apply.
# 6. el build terminó bien
==> Build successful 🎉
# 7. empieza tu Start Command
==> Running 'gunicorn miproyecto.wsgi'
# 8. gunicorn está escuchando visitas
[INFO] Listening at: http://0.0.0.0:10000
# 9. ¡listo! arriba de la página el estado cambia a "Live" en verde
==> Your service is live 🎉
Si el log se pone rojo y dice Build failed o Exited with status 1, sube un poco en el log y busca la primera línea con Error. Compárala con esta tabla:
| Error en el log | Causa | Solución |
|---|---|---|
Could not open requirements file | No subiste requirements.txt, o manage.py está en una subcarpeta | Repite 04.7 y el paso 05; o llena «Root Directory» en Settings. |
SECRET_KEY not found (u otra variable) | Falta una variable o tiene un error de dedo | Pestaña Environment: revisa que estén las 9 y bien escritas. |
ModuleNotFoundError: No module named 'miproyecto' | El Start Command tiene otro nombre de carpeta | Pestaña Settings → «Start Command»: corrige el nombre (ver 06.2). |
Can't connect to MySQL server / Access denied | Algún dato de Aiven está mal, o el servicio de Aiven está apagado | Compara las 5 DB_ con tu .env y revisa en Aiven que diga «Running». |
Missing staticfiles manifest entry | Una plantilla pide un archivo con {% static %} que no existe | Corre collectstatic en local (04.3) para ver cuál es y corrige el nombre. |
Después de corregir, clic en Manual Deploy → Deploy latest commit (o haz un git push con la corrección).
Tu superusuario (ya existe) — y qué hacer si lo olvidaste
El superusuario que creaste en el paso 03 quedó guardado en la base de Aiven, que es la misma que usa Render. Así que ya puedes entrar con ese usuario y contraseña. Si no lo creaste o no recuerdas la contraseña, arréglalo desde tu computadora: el cambio se guarda en Aiven y Render lo ve al instante.
# Si nunca creaste un superusuario: te pide usuario, correo (Enter para omitir) y contraseña dos veces
python manage.py createsuperuser
# Si ya existe pero olvidaste la contraseña: cambia "admin" por tu nombre de usuario
python manage.py changepassword admin
Al escribir la contraseña en la terminal no se ve nada (ni asteriscos). Es normal: escríbela y presiona Enter.
Verificar en el navegador
- Arriba de la página del servicio, debajo del nombre, está tu dirección
https://tu-proyecto.onrender.com. Clic en ella. - Agrega
/admin/al final de la dirección y presiona Enter. - Debes ver el login de Django con colores (fondo azul/verde arriba). Si se ve como texto plano sin diseño, whitenoise no está bien configurado: revisa 04.2.
- Inicia sesión con tu superusuario. Debes ver «Servicios» en la lista de modelos.
En el plan Free, si nadie visita tu sitio durante 15 minutos, Render lo «duerme». La siguiente visita tarda cerca de un minuto en cargar mientras despierta. No es un error: espera y recarga. Antes de presentar, ábrelo un par de minutos antes.
Es la misma base de datos de Aiven que usas en local: si en /admin de Render ves los mismos datos que en tu computadora, es porque ambos entornos apuntan al mismo DB_HOST. Lo que agregues en uno aparece en el otro.
Cómo trabajan juntos Render y Aiven, y qué puedes ver en cada uno
Render y Aiven son dos servicios distintos en dos empresas distintas. Render ejecuta tu código Django; Aiven guarda tus datos en MySQL. Render no tiene base de datos propia en esta práctica: cada vez que alguien abre tu sitio, Django (en Render) se conecta por Internet a Aiven usando las 5 variables DB_. Tu computadora y DBeaver se conectan a la misma base, con los mismos datos.
Los tres caminos terminan en la misma base defaultdb, con las tablas core_servicio, auth_user, django_migrations y las demás de Django. Por eso un servicio que das de alta en /admin de Render aparece en tu runserver y en DBeaver sin hacer nada más.
Un mismo dato de Aiven, escrito en cuatro lugares
En Aiven abre tu servicio MySQL → página Overview → sección Connection information. Cada valor de ahí se copia, idéntico, a estos lugares:
| En Aiven (Overview) | Tu .env local | Render → Environment | DBeaver (paso 08) |
|---|---|---|---|
| Host | DB_HOST | DB_HOST | Server Host |
| Port | DB_PORT | DB_PORT | Port |
| User | DB_USER | DB_USER | Username |
| Password | DB_PASSWORD | DB_PASSWORD | Password |
| Database name | DB_NAME | DB_NAME | Database |
| SSL mode: REQUIRED | Ya está en settings.py desde la Semana 3: 'OPTIONS': {'ssl': {'ssl-mode': 'REQUIRED'}} | Pestaña SSL → Use SSL | |
Si uno solo de estos valores es distinto en algún lugar, ese lugar no se conecta. Es la causa número uno de errores en esta práctica.
Qué puedes ver en la consola de Aiven
Entra a https://console.aiven.io → tu proyecto → tu servicio MySQL. En el menú del servicio encuentras estas páginas (los nombres pueden variar un poco según la versión de la consola):
| Página | Qué muestra | Para qué te sirve en esta práctica |
|---|---|---|
| Overview | Estado del servicio (Running, Rebuilding, Powered off), datos de conexión, botón Quick connect y certificado CA | Copiar Host, Port, User, Password y Database; confirmar que está encendido |
| Metrics | Gráficas de CPU, memoria, disco y conexiones | Ver si Render y tu compu están conectados (suben las conexiones) y cuánto disco usas del 1 GB gratis |
| Logs | Mensajes del servidor MySQL | Ver conexiones rechazadas o errores de acceso |
| Databases | Lista de bases dentro del servicio (defaultdb) | Confirmar el nombre exacto para DB_NAME. Muestra bases, no tablas |
| Users | Usuarios de MySQL (avnadmin), con opción de ver o restablecer contraseña | Recuperar o cambiar la contraseña (ver 07.6) |
| Backups | Copias de seguridad automáticas | Saber que tus datos tienen respaldo |
| Service settings | Encender/apagar el servicio y la lista de IPs permitidas | Encender el servicio si se apagó; revisar que no bloquee a Render |
La consola de Aiven no tiene visor de tablas ni de registros: puedes ver que el servicio funciona, pero no las filas de core_servicio. Para ver el contenido tienes tres caminos: el panel /admin de Django, el shell de Django (07.4) o DBeaver (paso 08), que es el más completo.
Qué puedes ver en el dashboard de Render
Entra a https://dashboard.render.com → clic en tu Web Service. Menú de la izquierda:
| Pestaña | Qué muestra | Qué buscar |
|---|---|---|
| Events | Historial de despliegues, uno por cada git push | Que el último diga Deploy live con el mensaje de tu commit |
| Logs | Lo que escribe el servidor: el build y cada visita | No migrations to apply (Render sí llegó a Aiven) o el error exacto si falló |
| Environment | Las 9 variables de entorno | Que las 5 DB_ sean idénticas a las de Aiven |
| Settings | Repositorio, rama, Build y Start Command | Que el repo conectado sea el u2s4-… y la rama main |
| Metrics | CPU, memoria y número de peticiones | Ver que llegan visitas |
| Connect → Outbound | Rangos de IP desde donde Render sale a Internet | Solo si en Aiven restringiste las IPs permitidas (07.5) |
Render tampoco muestra tus datos: solo tu código corriendo. Si /admin te deja iniciar sesión, la conexión con Aiven funciona, porque los usuarios viven en la tabla auth_user de Aiven.
Comprobar la conexión y los datos desde VS Code (sin instalar nada)
# Lista las migraciones de la app core; [X] significa que ya está aplicada en la base de Aiven
python manage.py showmigrations core
# Abre una consola de Python con tu proyecto Django ya cargado y conectado a Aiven
python manage.py shell
La terminal cambia a >>>. Escribe una línea a la vez:
# Trae el modelo Servicio para poder consultarlo
from core.models import Servicio
# Cuenta cuántos servicios hay en Aiven; debe coincidir con /admin de Render
Servicio.objects.count()
# Muestra id, título y precio de cada servicio guardado
list(Servicio.objects.values('id', 'titulo', 'precio'))
# Muestra a qué servidor está conectado Django; debe terminar en aivencloud.com
from django.db import connection; connection.settings_dict['HOST']
# Sale del shell y regresa a PowerShell
exit()
Reglas del plan gratuito de Aiven que te pueden afectar
- Se apaga si no se usa. Aiven apaga los servicios gratuitos sin actividad y avisa antes por correo. Si Render marca
Can't connect to MySQL server, entra a Aiven: si dice Powered off, usa Power on y espera a que diga Running (unos minutos). Después, en Render: Manual Deploy → Deploy latest commit. - 1 GB de disco y 1 GB de RAM. Más que suficiente para la práctica.
- Máximo 76 conexiones. Render, tu
runservery DBeaver cuentan cada uno. Cierra DBeaver y deténrunservercuando no los uses. - Un solo servicio MySQL gratis por organización. No crees un servicio nuevo por práctica: todas usan la misma base.
- IPs permitidas. Por defecto Aiven acepta conexiones de cualquier IP (
0.0.0.0/0). Si lo cambiaste en Service settings → Cloud and network, tienes que agregar los rangos de Render (Connect → Outbound) y la IP de tu red. Si no, Render y DBeaver no se conectan. Para el curso, déjalo en0.0.0.0/0.
Si cambias la contraseña de Aiven: actualízala en los tres lugares
Hazlo si la contraseña se subió a GitHub por error o si la olvidaste. El orden evita que tu sitio quede caído mucho tiempo:
- Aiven → Users →
avnadmin→ restablecer contraseña. Copia la nueva. - Render → Environment → Edit → cambia
DB_PASSWORD→ Save, rebuild, and deploy. - Tu compu → abre
.enven VS Code, cambiaDB_PASSWORDy guarda con Ctrl + S. - DBeaver → clic derecho en la conexión → Edit Connection → cambia Password → Test Connection.
| Error (Render Logs, runserver o DBeaver) | Causa más común | Qué hacer |
|---|---|---|
(2003) Can't connect to MySQL server / Communications link failure | Servicio de Aiven apagado, host o puerto mal copiados, o IP bloqueada | Revisa que Aiven diga Running, compara Host y Port, revisa la lista de IPs (07.5) |
(1045) Access denied for user 'avnadmin' | Contraseña o usuario distintos | Copia otra vez el Password de Aiven al lugar que falla |
(1049) Unknown database | DB_NAME mal escrito | Debe ser exactamente el de Databases en Aiven (normalmente defaultdb) |
SSL connection error / requires secure transport | La conexión no va cifrada | Django: revisa el OPTIONS SSL de la Semana 3. DBeaver: activa Use SSL |
(1040) Too many connections | Pasaste las 76 conexiones del plan gratis | Cierra DBeaver, detén runserver y espera un minuto |
Ver tus tablas y registros de Aiven con DBeaver
DBeaver es un programa gratuito para abrir bases de datos como si fueran hojas de cálculo. Lo usas para mirar lo que Django guardó en Aiven: las tablas que creó migrate, las columnas que salieron de tu models.py y los registros que cargaste en /admin. Es justo lo que la consola de Aiven no te muestra.
Instalar DBeaver Community
- Abre
https://dbeaver.io/download/(sitio oficial). Elige DBeaver Community → Windows (installer). Es gratis. - Ejecuta el instalador y deja todas las opciones como vienen: Next hasta Install.
- Ábrelo. Si pregunta por crear una base de ejemplo o por estadísticas de uso, puedes decir que no.
Descárgalo solo de dbeaver.io. Las versiones de otros sitios pueden traer programas extra.
Crear la conexión · forma rápida con la URL de Aiven (la oficial)
- En Aiven → tu servicio MySQL → Overview → botón Quick connect.
- En «Connect with» elige Java. Copia la línea que empieza con
jdbc:mysql://y cierra la ventana. - En DBeaver: menú Database → New Database Connection → elige MySQL → Next.
- En la pestaña Main, en «Connect by» elige URL y pega la línea completa.
- Clic en Test Connection…. La primera vez DBeaver pide descargar el driver de MySQL: acepta con Download. Debe salir Connected.
- Clic en Finish. La conexión aparece en el panel Database Navigator (izquierda).
Esa URL trae tu contraseña dentro. No la pegues en el README, en el chat del grupo ni en capturas.
Forma manual (si la URL no funciona)
Database → New Database Connection → MySQL → Next. Llena con los datos de Aiven (tabla de 07.1):
| Campo en DBeaver | Qué poner |
|---|---|
| Connect by | Host |
| Server Host | el Host de Aiven (termina en aivencloud.com), sin https:// |
| Port | el Port de Aiven (no es 3306; es un número como 12345) |
| Database | defaultdb |
| Username | avnadmin |
| Password | el Password de Aiven · marca Save password |
| Pestaña SSL | Marca Use SSL y Require SSL. Deja sin marcar «Verify server certificate» (o, si quieres verificarlo, descarga el CA certificate de Aiven y selecciónalo en «CA Certificate») |
Luego Test Connection… → Connected → Finish.
Protegerte: conexión de solo lectura
En este curso la estructura y los datos se cambian desde Django (modelos, migraciones y /admin), nunca a mano. Para no borrar algo por accidente:
- Clic derecho en la conexión → Edit Connection (F4).
- Sección General → en «Security» marca Read-only connection → OK.
Recorrer las tablas que creó Django
- En Database Navigator despliega: tu conexión → Databases → defaultdb → Tables.
- Verás tablas como
auth_user,core_servicio,django_migrations,django_session,django_admin_log. - Doble clic en
core_servicio. Se abre con tres pestañas útiles:- Properties → Columns:
id,titulo,descripcion,precio,categoria,creado_en,propietario_id. Compáralas con tumodels.py. - Data: los registros que cargaste en
/admin, uno por fila. - ER Diagram: la línea de
core_servicio.propietario_idhaciaauth_user.ides tuForeignKey.
- Properties → Columns:
- Si agregas registros en
/adminmientras DBeaver está abierto, presiona F5 en la pestaña Data para recargar.
Consultas SQL de solo lectura para revisar tu práctica
Clic derecho en defaultdb → SQL Editor → New SQL script. Pega estas consultas. Pon el cursor en una y presiona Ctrl + Enter para ejecutar solo esa.
-- Muestra todos los servicios que guardó Django (lo mismo que ves en /admin)
SELECT * FROM core_servicio;
-- Cuenta cuántos servicios hay; para la entrega deben ser al menos 5
SELECT COUNT(*) AS total FROM core_servicio;
-- Une cada servicio con el usuario dueño: así se ve la ForeignKey propietario en SQL
SELECT s.titulo, s.precio, u.username FROM core_servicio s JOIN auth_user u ON u.id = s.propietario_id;
-- Muestra qué migraciones de core aplicó Django en Aiven y cuándo (las mismas de showmigrations)
SELECT app, name, applied FROM django_migrations WHERE app = 'core';
-- Lista los usuarios de Django y quién es superusuario (la contraseña está cifrada, nunca se ve)
SELECT id, username, is_superuser FROM auth_user;
Evidencia: toma una captura de la pestaña Data de core_servicio con tus 5 registros, sin que se vea la contraseña.
| DBeaver dice… | Causa | Solución |
|---|---|---|
Communications link failure / Connection timed out | Aiven apagado, Host o Port mal escritos, o la red de la escuela bloquea ese puerto | Revisa que Aiven diga Running y los datos. Si en tu casa sí conecta y en la escuela no, prueba con los datos de tu celular |
Access denied for user 'avnadmin' | Contraseña incorrecta | Copia otra vez el Password desde Aiven → Overview |
Public Key Retrieval is not allowed | La conexión se hizo sin SSL | Pestaña SSL → Use SSL. Si sigue, en Driver properties pon allowPublicKeyRetrieval = true |
| No descarga el driver | La red bloquea la descarga | Intenta en otra red; o en Driver Settings → Libraries usa Download/Update |
No aparece core_servicio | No se ha corrido migrate, o estás viendo otra base | Corre python manage.py migrate; revisa que abriste defaultdb y presiona F5 |
Cuando termines, clic derecho en la conexión → Disconnect. Así liberas conexiones del plan gratuito para Render.
Práctica autónoma — conectar la vista a MySQL y dar de alta 5 registros desde /admin
La vista «Servicios» de la Semana 3 mostraba una lista fija escrita en Python. Ahora que existe el modelo Servicio en MySQL, la conectas a datos reales: lo que cargues en /admin aparecerá solo en la página pública, sin volver a tocar código.
Actualizar la vista para consultar la base de datos
- En VS Code presiona Ctrl + P, escribe
core/views.pyy presiona Enter. - Arriba del archivo, debajo del
importderenderque ya existe, agrega la líneafrom .models import Servicio. - Busca la función
servicios(Ctrl + F). Borra la lista fija de diccionarios que escribiste en la Semana 3 y deja la función como abajo. - Guarda con Ctrl + S. Si tienes otras vistas (ej. la de «Hola desde MySQL»), déjalas como están.
# render: ya estaba desde la Semana 3; junta una plantilla con datos y devuelve la página
from django.shortcuts import render
# Servicio: NUEVO — el modelo del paso 02, importado desde este mismo directorio (el punto = "esta app")
from .models import Servicio
# servicios: la vista que responde en /servicios/
def servicios(request):
# .objects.all(): pide TODOS los servicios a MySQL (Django lo traduce a SELECT * FROM core_servicio)
lista_servicios = Servicio.objects.all()
# entrega la lista a la plantilla con el nombre "servicios", igual que en la Semana 3
return render(request, 'core/servicios.html', {'servicios': lista_servicios})
Ajustar la plantilla al nombre real del campo
La lista fija de la Semana 3 usaba la clave nombre; el modelo real se llama titulo. Abre la plantilla con Ctrl + P → servicios.html, busca servicio.nombre con Ctrl + F y cámbialo por servicio.titulo. Guarda con Ctrl + S.
{# antes decía servicio.nombre; ahora servicio.titulo, igual que el campo de models.py #}
<td class="px-4 py-2">{{ servicio.titulo }}</td>
{# el precio no cambia: el campo del modelo también se llama "precio" #}
<td class="px-4 py-2">${{ servicio.precio }}</td>
Si en tu servicios.html quedaron comentarios <!-- … --> que contienen {% for %}, {% endfor %} o {{ }}, cámbialos a {# … #} (o bórralos). Django lee esas etiquetas aunque estén dentro de <!-- -->, y un {% for %} en un comentario hace que la tabla repita las filas (con 5 servicios verías 25 o más).
Probar primero en tu computadora
# Arranca el servidor local para probar antes de subir
python manage.py runserver
Abre http://127.0.0.1:8000/servicios/. Si todavía no has cargado ningún servicio, la tabla aparece vacía: eso es correcto (ya no hay lista fija). Si ves los servicios repetidos varias veces, revisa el aviso de 09.2 sobre los comentarios. Si sale un error amarillo, léelo: casi siempre es un nombre mal escrito en la vista o la plantilla. Detén el servidor con Ctrl + C.
Subir el cambio y esperar el redeploy
# Revisa que solo cambiaron views.py y servicios.html
git status
# Prepara los cambios
git add .
# Guarda los cambios con un mensaje claro
git commit -m "Conectar la vista de servicios al modelo real en MySQL"
# Sube a GitHub; Render detecta el push y redespliega solo
git push
En Render, abre tu servicio → pestaña Events: aparece un deploy nuevo «Deploy started for …» con el mensaje de tu commit. Espera a que diga «Deploy live» (2–5 min). Si falla, la pestaña Logs muestra el error (usa la tabla de 06.4).
Dar de alta 5 registros desde /admin en producción
- Abre
https://tu-proyecto.onrender.com/admin/e inicia sesión. - En la sección CORE, clic en Servicios → botón ADD SERVICIO + (arriba a la derecha).
- Propietario: elige tu usuario en la lista desplegable (es obligatorio por el
ForeignKeydel modelo). - Llena Titulo, Descripcion, Precio (solo número, con punto decimal:
350.00, sin$ni comas) y Categoria. - Clic en Save and add another para guardar y abrir otro formulario vacío. Repite hasta tener 5 con datos realistas de tu proyecto integrador. En el último usa SAVE.
La fecha creado_en no aparece en el formulario: Django la llena sola por el auto_now_add=True del modelo.
Verificar en la vista pública (y en tu compu)
Abre https://tu-proyecto.onrender.com/servicios/ en una ventana de incógnito (Ctrl + Shift + N), para comprobar que se ve sin iniciar sesión. Deben aparecer los 5 registros. Luego corre python manage.py runserver en VS Code y abre http://127.0.0.1:8000/servicios/: verás los mismos 5, porque ambos leen la misma base de Aiven. Por último, en DBeaver (paso 08) abre core_servicio → pestaña Data → F5: ahí están las mismas 5 filas.
Este es el patrón que vas a repetir en cada unidad: cargar datos desde /admin y confirmar que la vista pública los refleja, sin tocar código en medio.
Entregar y presentar
Marca cada punto conforme lo completes — queda guardado en este navegador.
Cómo subir la práctica
Actualizar el README
Abre README.md con Ctrl + P, reemplaza su contenido por el de abajo con tus datos y guarda. Las líneas <!-- --> son comentarios: GitHub no las muestra.
<!-- título del repo: cambia por tus nombres reales -->
# U2S4 — [Apellido Paterno] [Apellido Materno] [Nombre(s)]
<!-- sección con la URL pública de Render -->
## Demo en vivo (producción)
<!-- tu dirección real de Render -->
https://tu-proyecto.onrender.com
<!-- sección con la URL del panel de administración -->
## Panel de administración
<!-- misma dirección + /admin -->
https://tu-proyecto.onrender.com/admin
<!-- resumen de lo que hiciste esta semana -->
## Semana 4 — Modelos y despliegue dinámico
<!-- primera línea del resumen -->
Modelo Servicio migrado a MySQL, Web Service publicado en Render,
<!-- segunda línea del resumen -->
5 registros reales cargados desde /admin.
# Prepara solo el README
git add README.md
# Guarda el cambio con su mensaje
git commit -m "Actualizar README con URL de producción y avance de semana 4"
# Sube a GitHub (Render también redespliega, no pasa nada)
git push
Entregar
Pega en la tarea de la Semana 4: el enlace de tu repositorio (ej. https://github.com/tu-usuario/u2s4-hernandez-nunez-maria-jose), la URL pública de Render (ej. https://u2s4-hernandez-nunez-maria-jose.onrender.com) y la captura de DBeaver con core_servicio (paso 08.6). Antes de entregar, abre ambas URLs en incógnito para confirmar que se ven. Si el nombre del repo no sigue la regla del paso 05, corrígelo antes de entregar.
Cómo presentar en vivo — guion paso a paso
Un recorrido de ~5 minutos frente al profesor. Ten abiertos de antemano: el navegador con tu URL de Render (ábrela 2 minutos antes para que «despierte») y VS Code con el proyecto y la terminal lista con (venv). El cierre de la Unidad 2 puede pedir un live coding: «agrega un campo al modelo y migra en vivo» — tenlo practicado.
Abrir la URL de Render
Muestra /servicios/ con los 5 registros reales y el candado de HTTPS en la barra de direcciones.
Mostrar /admin en producción
Inicia sesión, abre «Servicios» y muestra el CRUD (crear, ver, editar, borrar) que Django generó solo a partir del modelo.
Recorrer models.py y settings.py
En VS Code, explica cada campo del modelo y por qué propietario es un ForeignKey a User. Luego muestra en settings.py que SECRET_KEY y DEBUG se leen con config() y no están escritos en el código.
Live coding sugerido: agregar un campo y migrar en vivo
- Ctrl + P →
models.py. Debajo decategoria, agrega la línea del bloque de abajo con la misma sangría (4 espacios). Ctrl + S. - En la terminal de VS Code corre los dos comandos del bloque.
- Abre
/adminlocal: cada servicio ya tiene la casilla «Disponible» marcada, y los datos anteriores siguen ahí. - Si te lo piden, haz
git add .,git commitygit push: Render aplicará la migración solo en su Build Command.
# disponible: casilla sí/no; default=True llena los registros que ya existían con "sí"
disponible = models.BooleanField(default=True)
# Genera core/migrations/0002_servicio_disponible.py con el cambio
python manage.py makemigrations core
# Aplica el cambio en MySQL de Aiven: agrega la columna "disponible" a core_servicio
python manage.py migrate
Cerrar explicando el flujo completo
«Cambio el modelo en Python → migro en local → hago push a GitHub → Render redespliega y corre migrate en su Build Command → los mismos datos de MySQL se ven en los dos lados.» Esa frase resume el pipeline de esta semana.
Tabla de referencias — de dónde sale toda la información
Todo lo de esta guía viene de documentación oficial de cada herramienta, no de blogs de terceros.
| Herramienta | Para qué | Dónde |
|---|---|---|
| VS Code — Terminal | Abrir y usar la terminal integrada (PowerShell en Windows) | code.visualstudio.com — Terminal Basics |
| Django — Models | Cómo definir modelos y campos | docs.djangoproject.com — Models |
| Django — Migrations | makemigrations y migrate en profundidad |
docs.djangoproject.com — Migrations |
| Django — Admin site | Cómo registrar modelos en el panel /admin | docs.djangoproject.com — Admin |
| Django — Deployment checklist | Qué cambiar antes de publicar: SECRET_KEY, DEBUG, ALLOWED_HOSTS |
docs.djangoproject.com — Deployment checklist |
| Django — Settings | STORAGES, STATIC_ROOT, CSRF_TRUSTED_ORIGINS |
docs.djangoproject.com — Settings |
| Django — Deploy con gunicorn | Guía oficial de despliegue con gunicorn | docs.djangoproject.com — gunicorn |
| WhiteNoise | Servir archivos estáticos sin servidor aparte | whitenoise.readthedocs.io |
| python-decouple | Leer variables del .env, incluido Csv() |
pypi.org/project/python-decouple |
| pip freeze | Generar requirements.txt |
pip.pypa.io — pip freeze |
| Render — Deploy Django | Guía oficial de Render para publicar un proyecto Django | render.com/docs/deploy-django |
| Render — Web Services | Diferencia entre Static Site y Web Service, Build/Start Command | render.com/docs/web-services |
| Render — Environment Variables | Cómo configurar variables de entorno en un servicio | render.com/docs/environment-variables |
| Render — Deploy for Free | Límites del plan Free: sin Shell, se duerme tras 15 min sin visitas | render.com/docs/free |
| GitHub — Crear repositorio | Crear el repo y qué caracteres acepta el nombre | docs.github.com — Creating a new repository |
| Git — git remote | remote add, set-url, show |
git-scm.com/docs/git-remote |
| Render — Outbound IPs | Desde qué IPs sale Render hacia Aiven | render.com/docs/outbound-ip-addresses |
| Aiven — MySQL free tier | Límites: 1 GB, 76 conexiones, apagado por inactividad | aiven.io/docs — MySQL free tier |
| Aiven — Restringir acceso | Lista de IPs permitidas del servicio | aiven.io/docs — Restrict network access |
| Aiven — DBeaver | Conectar DBeaver con la URL de Quick connect | aiven.io/docs — Connect with DBeaver |
| DBeaver | Descarga, conexiones y SSL | dbeaver.com/docs |