Banco de 20 Retos de Live Coding — Unidad 2
Los 20 retos parten de tu propio proyecto Django tal como queda al terminar la práctica de la Semana 4: el modelo Servicio en MySQL (Aiven), admin.py, las vistas inicio y servicios (ya con Servicio.objects.all()), las plantillas inicio.html y servicios.html, settings.py con WhiteNoise y el Web Service en Render. Cada reto muestra el código ANTES y DESPUÉS y cómo se ve en el navegador.
miproyecto-django_pdb del grupo 4-1; si tu proyecto tiene otro nombre (ej. miproyecto/settings.py), busca el mismo bloque. Cada reto trae un bloque «Cómo resolverlo en VS Code» con los pasos en orden; los comandos se escriben en la terminal integrada de VS Code, sin el $.
Antes de cualquier reto: tu terminal es la de VS Code
- Abre la carpeta del proyecto: Archivo → Abrir carpeta… y elige la que tiene
manage.py(ej.miproyecto-django_pdb). - Abre la terminal integrada: menú Ver → Terminal o Ctrl + ñ (teclado en español; en inglés es Ctrl + `). Se abre abajo, ya ubicada en la carpeta del proyecto, con PowerShell.
- Revisa que diga
(venv)al inicio de la línea. Si no aparece, actívalo con el primer comando de abajo. - Abre archivos rápido con Ctrl + P y escribe el nombre (ej.
models.py). Guarda siempre con Ctrl + S antes de correr un comando. - Dos terminales:
runserverocupa la terminal. Para correr otro comando sin detenerlo, toca el + del panel de la terminal; para detenerlo, Ctrl + C.
# activa el entorno virtual (Windows, PowerShell de VS Code)
$ venv\Scripts\Activate.ps1
# si PowerShell bloquea el script, corre esto una vez y repite la línea anterior
$ Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# levanta el servidor local para ver tus cambios en http://127.0.0.1:8000/
$ python manage.py runserverDjango + MySQL en la nube + Render (Semanas 3-4)
Agregar el campo disponible al modelo y migrar en vivooficial · lista de cotejo
Consigna del docente: “Agrega a Servicio el campo disponible (sí/no, por defecto sí), mígralo y muéstrame que tus registros siguen ahí.”
# categoria: el último campo de texto que ya tenías
categoria = models.CharField(max_length=60)
# creado_en: la fecha que Django llena solo al crear el registro
creado_en = models.DateTimeField(auto_now_add=True) # categoria: no se toca
categoria = models.CharField(max_length=60)
# creado_en: no se toca
creado_en = models.DateTimeField(auto_now_add=True)
# disponible: campo NUEVO; BooleanField = columna sí/no (tinyint en MySQL)
# default=True: a los 5 registros que ya existen se les pone True, por eso no se rompen
disponible = models.BooleanField(default=True)Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
models.py→ Enter. - Debajo de
creado_enescribe la línea dedisponible, con la misma sangría (4 espacios). - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- Abre la terminal de VS Code (Ctrl + ñ) y confirma que dice
(venv). Si ahí correrunserver, detenlo con Ctrl + C o abre otra terminal con el +. - Corre
makemigrationsy despuésmigrate(bloque de abajo). En el Explorador de VS Code aparececore/migrations/0002_servicio_disponible.py. - En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/admin/en el navegador. Entra a un servicio y muestra la casilla Disponible.
# genera el plan de cambios: core/migrations/0002_servicio_disponible.py
$ python manage.py makemigrations core
# aplica el plan: agrega la columna disponible en MySQL (Aiven)
$ python manage.py migrate
# levanta el servidor para comprobarlo en /admin
$ python manage.py runserverVista — formulario «Change servicio» en /admin
Titulo: Tutorías de programación
Precio: 150.00
Categoria: Tutorías
Titulo: Tutorías de programación
Precio: 150.00
Categoria: Tutorías
☑ Disponible
Los 5 registros siguen ahí, todos con ☑ ✓
- Escribe el campo dentro de la clase
Servicio, con la misma sangría que los demás - Explica por qué
default=Trueevita que Django pregunte qué valor darle a las filas viejas - Corre los dos comandos en orden y reconoce el archivo
0002_servicio_disponible.pynuevo
📍 En tu práctica: core/models.py, justo debajo de creado_en (línea 25) y antes de def __str__.
Hacer que __str__ muestre título y precio
Consigna del docente: “En /admin cada servicio aparece solo con su título. Haz que también se vea el precio, sin tocar el admin.”
# __str__: el texto con el que Django "presenta" cada objeto
def __str__(self):
# hoy devuelve solo el título
return self.titulo # __str__: mismo método, no cambia el nombre
def __str__(self):
# f-string: arma un texto con el título, un guion y el precio del objeto
return f"{self.titulo} — ${self.precio}"Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
models.py→ Enter. - Baja al final de la clase y cambia solo la línea del
return. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/admin/core/servicio/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — lista de Servicios en /admin
Select servicio to change
Select servicio to change
- Reconoce que el texto de la lista del admin sale de
__str__, no del admin.py - Usa
self.para leer los campos del propio objeto - Sabe que este cambio no necesita migración: no cambia ninguna columna
📍 En tu práctica: core/models.py, líneas 28-30 (el final de la clase).
Ordenar la tabla de servicios del más nuevo al más viejooficial · lista de cotejo
Consigna del docente: “La vista servicios() trae los registros sin orden. Muestra primero el último que cargaste y dime qué SQL arma Django.”
# all(): trae todas las filas, en el orden que las devuelva MySQL
lista_servicios = Servicio.objects.all() # order_by('-creado_en'): el guion significa descendente (más nuevo primero)
# Django lo traduce a: SELECT ... FROM core_servicio ORDER BY creado_en DESC
lista_servicios = Servicio.objects.order_by('-creado_en')Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
views.py→ Enter. - Dentro de
def servicios, cambia.all()por.order_by('-creado_en'). - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — /servicios/
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
| Repostería por encargo | $120.00 |
Servicios
| Servicio | Precio |
|---|---|
| Repostería por encargo | $120.00 |
| Diseño de logotipos | $300.00 |
| Tutorías de programación | $150.00 |
El último cargado queda arriba ✓
- Cambia solo la consulta, no la plantilla
- Explica qué hace el
-delante del nombre del campo - Nombra el
ORDER BY ... DESCque Django genera por detrás
📍 En tu práctica: core/views.py, dentro de def servicios(request), la línea de lista_servicios.
Agregar la columna «Categoría» a la tabla pública
Consigna del docente: “Tu modelo tiene categoria pero la tabla no la muestra. Agrega esa columna.”
<!-- encabezado: hoy solo dos columnas -->
<th class="text-left px-4 py-2">Servicio</th>
<!-- segunda columna del encabezado -->
<th class="text-left px-4 py-2">Precio</th>
<!-- ...dentro del for, las dos celdas de cada fila -->
<td class="px-4 py-2">{{ servicio.titulo }}</td>
<!-- celda del precio -->
<td class="px-4 py-2">${{ servicio.precio }}</td><!-- encabezado existente, no se toca -->
<th class="text-left px-4 py-2">Servicio</th>
<!-- th NUEVO: mismas clases que los otros encabezados -->
<th class="text-left px-4 py-2">Categoría</th>
<!-- encabezado existente, no se toca -->
<th class="text-left px-4 py-2">Precio</th>
<!-- celda existente del título -->
<td class="px-4 py-2">{{ servicio.titulo }}</td>
<!-- td NUEVO: lee el campo categoria del objeto actual del for -->
<td class="px-4 py-2">{{ servicio.categoria }}</td>
<!-- celda existente del precio -->
<td class="px-4 py-2">${{ servicio.precio }}</td>Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
servicios.html→ Enter. - Agrega el
<th>en el<thead>y el<td>dentro del{% for %}, en la misma posición. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — /servicios/
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
| Repostería por encargo | $120.00 |
Servicios
| Servicio | Categoría | Precio |
|---|---|---|
| Tutorías de programación | Tutorías | $150.00 |
| Diseño de logotipos | Diseño | $300.00 |
| Repostería por encargo | Repostería | $120.00 |
- Agrega el
<th>y el<td>en la misma posición para que no se desfasen las columnas - Escribe el nombre del campo tal cual está en models.py (
categoria, sin tilde) - No toca la vista: el dato ya venía en cada objeto
📍 En tu práctica: servicios.html, <thead> en las líneas 13-18 y las celdas dentro del {% for %} en las líneas 24-26.
Mostrar cuántos servicios hay con el filtro |length
Consigna del docente: “Debajo del título escribe cuántos servicios hay en total, sin tocar views.py.”
<!-- título de la página -->
<h1 class="text-2xl font-bold mb-6">Servicios</h1>
<!-- tabla: empieza justo después del h1 -->
<table class="w-full border border-slate-200 rounded-lg overflow-hidden"><!-- h1: mb-6 baja a mb-2 para que el contador quede pegado al título -->
<h1 class="text-2xl font-bold mb-2">Servicios</h1>
<!-- p NUEVO: |length cuenta cuántos elementos trae la variable servicios -->
<p class="text-sm text-slate-500 mb-6">Total: {{ servicios|length }} servicios</p>
<!-- tabla: no se toca -->
<table class="w-full border border-slate-200 rounded-lg overflow-hidden">Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
servicios.html→ Enter. - Debajo del
<h1>agrega el<p>con{{ servicios|length }}. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — /servicios/
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
| Repostería por encargo | $120.00 |
Servicios
Total: 3 servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
| Repostería por encargo | $120.00 |
- Usa
{{ }}(mostrar un valor) y no{% %} - Explica qué es un filtro: lo que va después de la barra
|transforma el valor - Usa la misma variable
serviciosque recorre elfor
📍 En tu práctica: servicios.html, línea 10 (el <h1>).
Mostrar un mensaje cuando no hay servicios con {% empty %}
Consigna del docente: “Si la tabla de MySQL queda vacía, hoy se ve un encabezado solo. Muestra una fila que diga que todavía no hay servicios.”
<!-- cierra la fila que se repite en cada vuelta -->
</tr>
<!-- endfor: cierra el bucle; si no hay datos no se dibuja nada -->
{% endfor %}<!-- cierra la fila que se repite, no se toca -->
</tr>
<!-- empty NUEVO: lo que sigue se muestra solo si la lista está vacía -->
{% empty %}
<!-- fila de aviso; colspan="2" la estira sobre las dos columnas -->
<tr><td colspan="2" class="px-4 py-6 text-center text-slate-500">Todavía no hay servicios.</td></tr>
<!-- endfor: sigue cerrando el bucle, ahora después del empty -->
{% endfor %}Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
servicios.html→ Enter. - Justo antes de
{% endfor %}agrega{% empty %}y la fila de aviso. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Para verlo sin borrar datos, prueba un momento con
Servicio.objects.none()en la vista (lista vacía) y después vuelve a.all().
Vista — /servicios/ con la tabla vacía
Servicios
| Servicio | Precio |
|---|
Solo el encabezado: ¿se rompió o no hay datos?
Servicios
| Servicio | Precio |
|---|---|
| Todavía no hay servicios. | |
- Pone
{% empty %}dentro del for, antes de{% endfor %} - Usa
colspanigual al número de columnas - Lo prueba: borra (o desmarca) registros en /admin o explica cuándo aparecería
📍 En tu práctica: servicios.html, línea 29 ({% endfor %}).
Filtrar la vista para mostrar una sola categoría
Consigna del docente: “Haz que /servicios/ muestre solo los servicios de la categoría Tutorías.”
# all(): todas las filas de la tabla
lista_servicios = Servicio.objects.all() # filter(categoria='Tutorías'): solo las filas cuya categoría sea ese texto
# Django lo traduce a: SELECT ... WHERE categoria = 'Tutorías'
lista_servicios = Servicio.objects.filter(categoria='Tutorías')Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
views.py→ Enter. - Cambia
.all()por.filter(categoria='Tutorías')(con el texto exacto que cargaste en /admin). - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — /servicios/
Servicios
| Servicio | Categoría |
|---|---|
| Tutorías de programación | Tutorías |
| Diseño de logotipos | Diseño |
| Repostería por encargo | Repostería |
| Asesoría en Excel | Tutorías |
Servicios
| Servicio | Categoría |
|---|---|
| Tutorías de programación | Tutorías |
| Asesoría en Excel | Tutorías |
- Cambia
all()porfilter(...)y escribe el campo sin comillas y el valor con comillas - El texto debe coincidir con el que cargó en /admin
- Nombra el
WHEREque genera el ORM
📍 En tu práctica: core/views.py, la línea de lista_servicios dentro de servicios().
Mostrar columnas en la lista del admin con list_display
Consigna del docente: “En /admin solo se ve el título. Haz que la lista muestre título, categoría, precio y dueño en columnas.”
# admin: el módulo del panel /admin
from django.contrib import admin
# Servicio: el modelo de esta misma app
from .models import Servicio
# register: registro simple, sin personalizar nada
admin.site.register(Servicio)# admin: no se toca
from django.contrib import admin
# Servicio: no se toca
from .models import Servicio
# @admin.register: REEMPLAZA a admin.site.register (no pueden estar los dos)
@admin.register(Servicio)
# ServicioAdmin: clase que personaliza cómo se ve Servicio en el panel
class ServicioAdmin(admin.ModelAdmin):
# list_display: los campos que aparecen como columnas en la lista
list_display = ('titulo', 'categoria', 'precio', 'propietario')Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
admin.py→ Enter. - Borra la línea
admin.site.register(Servicio)y escribe en su lugar el decorador y la claseServicioAdmin. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- Mira la terminal de VS Code donde corre
runserver: si hay un error (ej.AlreadyRegistered), aparece ahí. - En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/admin/core/servicio/en el navegador.
Vista — /admin/core/servicio/
Select servicio to change
Select servicio to change
- Borra la línea
admin.site.register(Servicio)(si queda, Django tiraAlreadyRegistered) - Escribe los nombres de campo exactamente como en models.py
- Sabe que no hace falta migrar: solo cambia el panel, no la tabla
📍 En tu práctica: core/admin.py, línea 7.
Cambiar la dirección /servicios/ por /catalogo/
Consigna del docente: “Quiero que la página de servicios se abra en /catalogo/. Cambia solo lo necesario.”
# ruta 'servicios/' → función views.servicios → alias 'servicios'
path('servicios/', views.servicios, name='servicios'), # solo cambia el primer texto (lo que se escribe en el navegador)
# la vista y el name='servicios' se quedan iguales
path('catalogo/', views.servicios, name='servicios'),Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
urls.py→ Enter. Elige el de core (en la lista se ve la carpeta a la derecha del nombre). - Cambia solo el texto
'servicios/'por'catalogo/'. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/catalogo/en el navegador. Prueba también/servicios/para mostrar el 404.
Vista — barra de direcciones
Page not found (404)
/catalogo/ todavía no existe; la tabla vive en /servicios/
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
Misma vista, nueva dirección ✓
- Distingue las tres partes de
path(): dirección, función y alias - Cambia solo la primera, no la función ni el
name - Explica que
/servicios/ahora da 404
📍 En tu práctica: core/urls.py, línea 13.
Mostrar el nombre de la base de datos en «Hola desde MySQL»
Consigna del docente: “Tu página de inicio muestra motor y host. Agrega también el nombre de la base de datos.”
# contexto: los datos que la plantilla puede usar
contexto = {
# motor: el ENGINE de la conexión
'motor': info_bd['ENGINE'],
# host: el servidor de Aiven
'host': info_bd['HOST'],
# cierra el diccionario
} # contexto: mismo diccionario, con una clave más
contexto = {
# motor: no se toca
'motor': info_bd['ENGINE'],
# host: no se toca
'host': info_bd['HOST'],
# base: clave NUEVA, lee el NAME de la conexión (defaultdb en Aiven)
'base': info_bd['NAME'],
# cierra el diccionario
}
# --- y en core/templates/core/inicio.html, debajo del párrafo del host ---
# <p>Base de datos: {{ base }}</p> ← {{ base }} = la clave nueva del contextoCómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
views.py→ Enter. Agrega la clave'base'al diccionariocontexto. - Con Ctrl + P abre
inicio.htmly agrega<p>Base de datos: {{ base }}</p>debajo del host. - Guarda los dos archivos (Ctrl + K y luego S guarda todos).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/en el navegador.
Vista — página de inicio
Hola desde MySQL
Motor de base de datos: django.db.backends.mysql
Host: mysql-xxxx.aivencloud.com
Hola desde MySQL
Motor de base de datos: django.db.backends.mysql
Host: mysql-xxxx.aivencloud.com
Base de datos: defaultdb
- Agrega la clave en la vista y la usa en la plantilla con el mismo nombre
- Explica el viaje: diccionario de la vista →
{{ variable }}del template - No olvida la coma al final de la línea anterior del diccionario
📍 En tu práctica: core/views.py líneas 11-14 (el contexto) y core/templates/core/inicio.html línea 13.
Agregar la fecha de alta con el filtro |date
Consigna del docente: “Muestra en la tabla la fecha en que se cargó cada servicio, en formato día/mes/año.”
<!-- última celda de cada fila -->
<td class="px-4 py-2">${{ servicio.precio }}</td><!-- celda del precio, no se toca -->
<td class="px-4 py-2">${{ servicio.precio }}</td>
<!-- td NUEVO: creado_en con el filtro date; "d/m/Y" = 24/09/2026 -->
<td class="px-4 py-2">{{ servicio.creado_en|date:"d/m/Y" }}</td>
<!-- (y en el thead, un <th class="text-left px-4 py-2">Alta</th> al final) -->Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
servicios.html→ Enter. - Agrega el
<th>Alta</th>al final del<thead>y el<td>con|date:"d/m/Y"al final de la fila. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — /servicios/
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
Servicios
| Servicio | Precio | Alta |
|---|---|---|
| Tutorías de programación | $150.00 | 22/09/2026 |
| Diseño de logotipos | $300.00 | 23/09/2026 |
- Reconoce que
creado_enlo llenó Django solo (auto_now_add) - Usa el filtro con dos puntos y el formato entre comillas
- Agrega también el
<th>para no desfasar la tabla
📍 En tu práctica: servicios.html, línea 26, y el <thead> en las líneas 14-17.
Enlazar la página de inicio con la de servicios usando {% url %}
Consigna del docente: “Desde «Hola desde MySQL» no hay forma de llegar a servicios. Agrega un enlace sin escribir la dirección a mano.”
<!-- último párrafo de la página -->
<p>Host: {{ host }}</p>
<!-- cierra el body -->
</body> <!-- párrafo del host, no se toca -->
<p>Host: {{ host }}</p>
<!-- a NUEVO: {% url 'servicios' %} busca la ruta por su name= en urls.py -->
<a href="{% url 'servicios' %}">Ver servicios →</a>
<!-- cierra el body -->
</body>Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
inicio.html→ Enter. - Debajo del párrafo del host escribe el
<a>con{% url 'servicios' %}. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/en el navegador. Haz clic en el enlace.
Vista — página de inicio
Hola desde MySQL
Host: mysql-xxxx.aivencloud.com
Sin enlaces a otras páginas
Hola desde MySQL
Host: mysql-xxxx.aivencloud.com
Ver servicios →
El href generado es /servicios/ (o /catalogo/ si hiciste el reto 09) ✓
- Usa el
name=de la ruta, no la dirección escrita a mano - Explica la ventaja: si cambia la dirección (reto 09) el enlace sigue funcionando
- Pone comillas simples adentro porque el
hrefya usa dobles
📍 En tu práctica: core/templates/core/inicio.html, después de la línea 13; el name='servicios' está en core/urls.py línea 13.
Hacer que la descripción sea opcional con blank=True
Consigna del docente: “En /admin no me deja guardar un servicio sin descripción. Haz que sea opcional.”
# descripcion: texto largo; hoy es OBLIGATORIO en los formularios
descripcion = models.TextField() # blank=True: el formulario (admin) acepta dejarlo vacío
descripcion = models.TextField(blank=True)Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
models.py→ Enter. - En la línea de
descripcionescribeblank=Truedentro de los paréntesis. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- Abre la terminal de VS Code (Ctrl + ñ) y confirma que dice
(venv). - Corre
makemigrationsymigrate(bloque de abajo). En el Explorador aparece el archivo..._alter_servicio_descripcion.py. - En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/admin/core/servicio/add/en el navegador. Guarda un servicio sin descripción.
# genera el plan: core/migrations/000X_alter_servicio_descripcion.py
$ python manage.py makemigrations core
# aplica el cambio en MySQL (Aiven)
$ python manage.py migrate
# levanta el servidor para probarlo en /admin
$ python manage.py runserverVista — «Add servicio» en /admin con la descripción vacía
Please correct the error below.
Descripcion:
This field is required.
The servicio “Asesoría en Excel” was added successfully.
Descripcion: (no está en negrita = opcional)
- Pone
blank=Truedentro de los paréntesis del campo correcto - Sabe que en el admin, etiqueta en negrita = campo obligatorio
- Corre makemigrations + migrate y explica por qué Django genera una migración aunque no cambie el tipo de columna
📍 En tu práctica: core/models.py, línea 16.
Poner el proyecto en español de México y hora de Hermosillo
Consigna del docente: “Tu /admin está en inglés y la hora de creación sale en UTC. Arregla las dos cosas en settings.py.”
# LANGUAGE_CODE: idioma de los textos de Django (admin, errores)
LANGUAGE_CODE = 'en-us'
# TIME_ZONE: zona horaria con la que se muestran las fechas
TIME_ZONE = 'UTC'# 'es-mx': español de México; el admin se traduce solo
LANGUAGE_CODE = 'es-mx'
# 'America/Hermosillo': hora de Sonora (UTC-7, sin horario de verano)
TIME_ZONE = 'America/Hermosillo'Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
settings.py→ Enter. - Busca con Ctrl + F
LANGUAGE_CODEy cambia los dos valores. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/admin/en el navegador. - Si el servidor ya estaba corriendo, solo recarga el navegador (F5): Django detecta el cambio solo.
Vista — portada de /admin
Site administration
Servicios · + Add · Change
Sitio administrativo
Servicios · + Añadir · Modificar
- Ubica las dos variables en la parte «Internationalization» de settings.py
- Escribe los valores como texto entre comillas
- Explica que con
USE_TZ = TrueMySQL guarda en UTC y Django convierte a la hora local al mostrar
📍 En tu práctica: miproyecto_pdb/settings.py, líneas 131 y 133.
Corregir la posición de WhiteNoiseMiddleware
Consigna del docente: “Revisa tu MIDDLEWARE: WhiteNoise no está donde dice su documentación. Muévelo.”
MIDDLEWARE = [
# 1.º: cabeceras de seguridad
'django.middleware.security.SecurityMiddleware',
# 2.º: sesiones
'django.contrib.sessions.middleware.SessionMiddleware',
# 3.º: WhiteNoise quedó DESPUÉS de sesiones (mal ubicado)
'whitenoise.middleware.WhiteNoiseMiddleware',MIDDLEWARE = [
# 1.º: cabeceras de seguridad, sigue primero
'django.middleware.security.SecurityMiddleware',
# 2.º: WhiteNoise JUSTO después de SecurityMiddleware, como pide su documentación
'whitenoise.middleware.WhiteNoiseMiddleware',
# 3.º: sesiones baja un lugar; el resto de la lista no se toca
'django.contrib.sessions.middleware.SessionMiddleware',Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
settings.py→ Enter. - Busca
MIDDLEWAREcon Ctrl + F. - Pon el cursor en la línea de WhiteNoise y súbela con Alt + ↑ hasta que quede debajo de
SecurityMiddleware(mueve la línea entera, sin cortar ni pegar). - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code corre
python manage.py check(bloque de abajo) para confirmar que no hay errores.
# revisa la configuración del proyecto sin levantar el servidor
$ python manage.py checkVista — orden en que pasa cada petición
- Security
- Session
- WhiteNoise ✗
- Common…
Un .css estático pasa por sesiones sin necesidad
- Security
- WhiteNoise ✓
- Session
- Common…
Los estáticos se responden antes, sin tocar sesiones
- Reconoce que el orden de la lista importa: cada petición la recorre de arriba abajo
- Mueve la línea completa sin borrar ninguna coma
- Cita la regla: «justo después de SecurityMiddleware»
📍 En tu práctica: miproyecto_pdb/settings.py, líneas 51-61. En tu práctica WhiteNoise está en la línea 55, debajo de SessionMiddleware.
Dejar de tener DEBUG = True fijo en el código
Consigna del docente: “Tu DEBUG está en True escrito a mano. Haz que se lea del .env y que por defecto sea False.”
# DEBUG fijo: en Render también quedaría encendido
DEBUG = True# config('DEBUG'): lee la variable del .env (local) o de Render (producción)
# default=False: si la variable no existe, se apaga (opción segura)
# cast=bool: convierte el texto "True"/"False" en booleano de Python
DEBUG = config('DEBUG', default=False, cast=bool)
# --- en tu .env local agrega esta línea ---
# DEBUG=TrueCómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
settings.py→ Enter. - Cambia
DEBUG = Truepor la línea conconfig(...). - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- Con Ctrl + P abre
.env, agregaDEBUG=Trueal final y guarda. - En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/xyz/en el navegador. En local sigue la página amarilla (DEBUG=True); en Render, conDEBUG=False, sale el error genérico.
Vista — una URL que no existe en Render (/xyz/)
Page not found (404)
Using the URLconf defined in miproyecto_pdb.urls, Django tried these URL patterns: admin/ · servicios/ …
Le muestra tus rutas a cualquiera ✗
Not Found
The requested resource was not found on this server.
Error genérico, sin detalles internos ✓
- Explica por qué
default=Falsees la opción segura - Sabe para qué sirve
cast=bool(del .env todo llega como texto) - Agrega
DEBUG=Trueen su .env local yDEBUG=Falseen Render
📍 En tu práctica: miproyecto_pdb/settings.py, línea 33. config ya está importado en la línea 20.
Diagnosticar y corregir ALLOWED_HOSTS vacíooficial · lista de cotejo
Consigna del docente: “Con ALLOWED_HOSTS = [] y DEBUG=False en Render, ¿qué error sale? Corrígelo.”
# config: hoy solo se importa config
from decouple import config
# lista vacía: con DEBUG=False Django no acepta atender a NINGÚN dominio
ALLOWED_HOSTS = []# Csv: convierte "a.com,b.com" en la lista ['a.com', 'b.com']
from decouple import config, Csv
# ALLOWED_HOSTS: se lee de la variable de entorno y se separa por comas
ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='', cast=Csv())
# --- en Render → Environment ---
# ALLOWED_HOSTS=tu-proyecto.onrender.com
# --- en tu .env local ---
# ALLOWED_HOSTS=127.0.0.1,localhostCómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
settings.py→ Enter. - En el import de
decoupleagrega, Csvy cambia la línea deALLOWED_HOSTS. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- Con Ctrl + P abre
.envy agregaALLOWED_HOSTS=127.0.0.1,localhost. - En la terminal de VS Code (con
(venv)):python manage.py runservery abrehttp://127.0.0.1:8000/servicios/en el navegador. En Render, pestaña Environment, la variable lleva el dominio.onrender.com.
Vista — la URL de Render
Bad Request (400)
Django rechaza el dominio porque no está en la lista ✗
Servicios
| Servicio | Precio |
|---|---|
| Tutorías de programación | $150.00 |
| Diseño de logotipos | $300.00 |
El dominio de Render ya está permitido ✓
- Predice el error Bad Request (400) antes de probarlo
- Agrega
Csval import que ya existía, no escribe otro import - Pone el dominio sin
https://ni barra final
📍 En tu práctica: miproyecto_pdb/settings.py, línea 35 (y el import de la línea 20).
Completar requirements.txt con gunicorn y whitenoise
Consigna del docente: “Mira tu requirements.txt: ¿Render podría arrancar tu app con eso? Arréglalo.”
# asgiref: dependencia interna que Django instala solo
asgiref==3.12.1
# Django: el framework
Django==6.1.1
# PyMySQL: el conector a MySQL de Aiven
PyMySQL==1.2.0
# python-decouple: lee el .env con config()
python-decouple==3.8
# sqlparse: otra dependencia interna de Django
sqlparse==0.6.0
# tzdata: zonas horarias; y aquí termina: NO están gunicorn ni whitenoise
tzdata==2026.4# instala los dos paquetes de producción dentro del venv (si no estaban)
$ pip install gunicorn whitenoise
# pip freeze: lista lo instalado; > sobrescribe requirements.txt con esa lista
$ pip freeze > requirements.txt
# git diff: muestra las líneas nuevas (gunicorn==..., whitenoise==...)
$ git diff requirements.txtCómo resolverlo en VS Code
- Con Ctrl + P abre
requirements.txty muestra que faltangunicornywhitenoise. - Abre la terminal de VS Code (Ctrl + ñ) y confirma que dice
(venv). - Corre los tres comandos del bloque «DESPUÉS», en orden.
- Mira
requirements.txten el editor: VS Code lo recarga solo con las líneas nuevas. También se ve en Control de código fuente (Ctrl + Shift + G) → clic en el archivo → vista de diferencias.
Vista — log de deploy en Render
==> Running 'gunicorn miproyecto_pdb.wsgi'
bash: gunicorn: command not found
==> Exited with status 127
Successfully installed gunicorn whitenoise
[INFO] Booting worker with pid: 52
==> Your service is live 🎉
- Detecta que faltan
gunicornywhitenoiseleyendo el archivo - Explica que Render solo instala lo que dice requirements.txt (Build Command)
- Usa
pip freeze >con el venv activo, no lo escribe a mano
📍 En tu práctica: requirements.txt en la raíz del proyecto (junto a manage.py). En tu práctica tiene 6 líneas y ninguna es gunicorn.
Sacar db.sqlite3 del repositorio con .gitignore
Consigna del docente: “En tu carpeta quedó un db.sqlite3 de cuando Django usaba SQLite. Que Git lo ignore.”
# venv/: no se sube el entorno virtual
venv/
# .env: contraseñas reales, nunca a GitHub
.env
# __pycache__/: archivos compilados de Python
__pycache__/# las tres líneas de antes no se tocan
venv/
# .env sigue ignorado
.env
# __pycache__/ sigue ignorado
__pycache__/
# db.sqlite3: NUEVA; la base local vieja no es parte del código (usamos MySQL)
db.sqlite3
# staticfiles/: NUEVA; carpeta que collectstatic genera sola en Render
staticfiles/Cómo resolverlo en VS Code
- En VS Code: Ctrl + P → escribe
.gitignore→ Enter. - Al final agrega
db.sqlite3ystaticfiles/, cada uno en su línea. - Guarda con Ctrl + S (el punto blanco de la pestaña desaparece).
- En la terminal de VS Code corre
git status(bloque de abajo). En el Explorador,db.sqlite3se ve en gris: Git lo ignora.
# muestra qué archivos ve Git; db.sqlite3 ya no debe aparecer
$ git statusVista — salida de git status
$ git status
Untracked files:
db.sqlite3
$ git status
Changes not staged for commit:
modified: .gitignore
(db.sqlite3 ya no aparece)
- Explica que
db.sqlite3sobra porque la app ahora usa MySQL en Aiven - Escribe cada nombre en su propia línea
- Verifica el resultado con
git status
📍 En tu práctica: .gitignore en la raíz. En tu práctica db.sqlite3 está junto a manage.py.
Publicar el cambio del reto 01 y comprobar el migrate en el log de Render
Consigna del docente: “Ya agregaste disponible en local. Súbelo, muéstrame el redeploy y dónde se ve en el log de Render lo que hizo migrate.” En el plan Free de Render no hay pestaña Shell: migrate corre solo, dentro del Build Command (guía de la Semana 4, paso 06.2).
# muestra lo modificado: models.py y la migración nueva sin commit
$ git status# prepara models.py y core/migrations/0002_servicio_disponible.py
$ git add .
# guarda el cambio con un mensaje que dice QUÉ se hizo
$ git commit -m "Agregar campo disponible a Servicio"
# sube a GitHub; Render detecta el push, redespliega y corre su Build Command
$ git push
# --- el Build Command de Render ya incluye migrate (no se escribe a mano): ---
# pip install -r requirements.txt && python manage.py collectstatic --noinput && python manage.py migrateCómo resolverlo en VS Code
- Abre la terminal de VS Code (Ctrl + ñ) en la carpeta del proyecto.
- Corre
git statusy muestra los dos archivos del reto 01. - Corre
git add .,git commit -m "..."ygit pushahí mismo (o con los botones Commit y Sync de Ctrl + Shift + G). - En el navegador: dashboard de Render → tu servicio → Events: aparece el deploy nuevo con el mensaje de tu commit.
- Abre la pestaña Logs de ese deploy y busca la parte del Build Command: ahí está lo que respondió
migrate. Espera a que diga Your service is live. - Si el deploy falla, la primera línea con
Errordel log dice por qué (por ejemplo, olvidaste subir el archivo0002_...).
Vista — log del deploy en Render
$ git status
modified: core/models.py
Untracked: core/migrations/0002_servicio_disponible.py
==> Running build command 'pip install … && python manage.py migrate'
Operations to perform: Apply all migrations: admin, auth, contenttypes, core, sessions
Running migrations:
No migrations to apply.
==> Your service is live 🎉
Sale así porque ya migraste en local contra el MISMO MySQL de Aiven
- Incluye la migración
0002_...en el commit (sin ella, Render no conoce el cambio) - Muestra en «Events» que el push disparó un deploy
- Encuentra en «Logs» la salida de
migratey sabe que corre porque está en el Build Command (en el plan Free no hay Shell) - Explica por qué dice «No migrations to apply»: la tabla
django_migrationsde Aiven ya la tiene registrada porque migró en local contra la misma base. Si no hubiera migrado en local, el log diríaApplying core.0002_servicio_disponible... OK
📍 En tu práctica: terminal integrada de VS Code, abierta en la carpeta del proyecto, y en Render: tu Web Service → pestañas Events y Logs. El Build Command está en Settings.