¿Qué es un modelo en Django?
En Django, un modelo es una clase de Python que representa una tabla en la base de datos. Cada atributo de esa clase será una columna en la tabla. Gracias a esto, puedes trabajar con tus datos usando Python en lugar de escribir consultas SQL directamente.
Por ejemplo, si estás construyendo un catálogo de videojuegos, puedes definir un modelo Juego
que contenga información como nombre, descripción, género, precio y plataformas. Django se encargará automáticamente de crear la tabla correspondiente en la base de datos.
¿Por qué usar modelos?
Hasta ahora, quizás tu aplicación guardaba los datos en una lista estática dentro de views.py
. Esto puede funcionar al principio, pero no es sostenible. Los modelos permiten:
- Guardar, actualizar y eliminar registros fácilmente.
- Usar el panel de administración de Django para gestionar datos sin necesidad de modificar el código.
- Realizar consultas complejas con filtros, ordenamientos o búsquedas.
- Separar claramente la lógica de datos del resto del código de tu aplicación.
Creando el modelo Juego
Vamos a definir el modelo que representará los videojuegos dentro de tu app. Para eso, abrimos (o creamos) el archivo models.py
dentro de la app juegos
, y escribimos lo siguiente:
from django.db import models class Plataforma(models.Model): nombre = models.CharField(max_length=50) def __str__(self): return self.nombre class Genero(models.Model): nombre = models.CharField(max_length=50) def __str__(self): return self.nombre class Juego(models.Model): nombre = models.CharField(max_length=100) precio = models.DecimalField(max_digits=6, decimal_places=2) descripcion = models.TextField() generos = models.ManyToManyField(Genero, related_name='juegos') plataformas = models.ManyToManyField(Plataforma, related_name='juegos') def __str__(self): return self.nombre
Explicación de los campos y modelos:
Plataforma:
nombre
: campo de texto corto con un máximo de 50 caracteres. Representa el nombre de la plataforma (por ejemplo, PC, Xbox, PlayStation).- El método
__str__
retorna el nombre de la plataforma para que se muestre de forma legible en el admin o consola.
Genero:
nombre
: campo de texto corto con un máximo de 50 caracteres. Representa el nombre del género (por ejemplo, acción, aventura, RPG).- El método
__str__
retorna el nombre del género para mostrarlo de forma clara.
Juego:
nombre
: campo de texto corto con un máximo de 100 caracteres. Representa el nombre del juego.precio
: campo decimal que permite números con hasta 6 dígitos en total y 2 decimales (por ejemplo, 59.99).descripcion
: campo de texto largo para la descripción detallada del juego.generos
: relación ManyToMany con el modeloGenero
, lo que significa que un juego puede tener varios géneros y un género puede estar asociado a varios juegos. Esto permite almacenar los géneros de forma estructurada y evitar inconsistencias.plataformas
: relación ManyToMany con el modeloPlataforma
, lo que significa que un juego puede estar disponible en varias plataformas y una plataforma puede tener varios juegos. Esto permite almacenar las plataformas de forma estructurada y no solo como texto plano.
Migraciones: creando la tabla en la base de datos
Cada vez que creamos o modificamos modelos, Django necesita actualizar la base de datos. Para eso usamos migraciones.
De hecho, al crear el proyecto de Django, siempre nos dice esto al ejecutar el servidor:
You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
Lo que Django te está diciendo es que hay 18 cambios pendientes en tu base de datos que aún no se han aplicado. Esto ocurre porque, al crear el proyecto, Django ya incluye funcionalidades básicas (como la administración, autenticación, etc.) que necesitan su propia estructura de base de datos desde el inicio, incluso si tú no has definido tus propias tablas. Para que todo funcione correctamente, solo necesitas ejecutar python manage.py migrate
.
Si has guardado el archivo models.py, detén el servidor. En la terminal, ejecuta:
python manage.py makemigrations juegos
Aparecerá algo como esto:
Migrations for 'juegos': juegos\migrations\0001_initial.py + Create model Juego
Ahora, solo para ver si aparecen más migraciones pendientes que las 18 iniciales, vamos a iniciar el servidor.
python manage.py runserver
python manage.py migrate
Esto crea y aplica las migraciones, creando la tabla juegos_juego
en la base de datos.
Operations to perform: Apply all migrations: admin, auth, contenttypes, juegos, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying juegos.0001_initial... OK Applying sessions.0001_initial... OK
Usar el modelo en lugar de la lista estática
Podemos cambiar la vista para que recupere los juegos desde la base de datos en lugar de la lista estática que teníamos en formato diccionarios.
Modifica tu views.py
así:
from django.shortcuts import render, get_object_or_404 from .models import Juego def index(request): juegos = Juego.objects.all().prefetch_related('generos', 'plataformas') datos_plantilla = { 'titulo': 'Catálogo de videojuegos', 'juegos': juegos } plantilla_principal = { 'title': 'Catálogo de videojuegos' } return render(request, 'juegos/index.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal }) def mostrar(request, id): juego = get_object_or_404(Juego.objects.prefetch_related('generos', 'plataformas'), pk=id) datos_plantilla = { 'juego': juego, 'titulo': juego.nombre } plantilla_principal = { 'title': juego.nombre } return render(request, 'juegos/mostrar.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal })
Nota: Ahora
juegos
es unQuerySet
con todos los objetosJuego
de la base de datos, y usamosprefetch_related
para cargar eficientemente las relaciones ManyToMany degeneros
yplataformas
.
¿Qué es un QuerySet en Django?
Un QuerySet es una colección de objetos de base de datos que Django crea cuando haces una consulta a un modelo. Es similar a una lista de Python, pero más poderosa y eficiente, porque puede interactuar directamente con la base de datos.
Por ejemplo:
juegos = Juego.objects.all()
Esto no devuelve una lista común, sino un QuerySet que contiene todos los objetos del modelo Juego
en la base de datos.
¿En qué se parece a una lista?
Puedes hacer cosas como:
for juego in juegos: print(juego.nombre) # Para acceder a los géneros y plataformas relacionados: generos = ", ".join([g.nombre for g in juego.generos.all()]) plataformas = ", ".join([p.nombre for p in juego.plataformas.all()]) print(f"Géneros: {generos}") print(f"Plataformas: {plataformas}")
También puedes usar filtros, ordenar, contar elementos, etc., igual que con listas, pero sin cargar todo en memoria de inmediato.
¿En qué se diferencia de una lista?
Un QuerySet es perezoso (lazy). Esto significa:
- No ejecuta la consulta a la base de datos hasta que lo necesitas.
- Puedes encadenar filtros y Django solo hace una consulta final optimizada.
Ejemplo:
# Aún no se hace consulta a la base de datos juegos_filtrados = Juego.objects.filter(generos__nombre='RPG') # Aquí sí se ejecuta la consulta for juego in juegos_filtrados: print(juego.nombre)
Esto mejora mucho el rendimiento, especialmente cuando tienes muchos datos.
¿Por qué es importante saber esto?
Porque cuando usas:
juegos = Juego.objects.all()
Estás obteniendo un QuerySet de objetos Juego
, y no necesitas convertirlo a lista a menos que realmente lo necesites.
En resumen
Concepto | Lista de Python | QuerySet de Django |
---|---|---|
Tipo | list | django.db.models.query.QuerySet |
Contenido | Cualquier tipo de objeto | Instancias del modelo (Juego , en tu caso) |
Carga | Inmediata (todo en memoria) | Perezosa (consulta a DB solo si se necesita) |
Consultas | No se puede filtrar desde la base de datos | Sí, permite filtrar antes de traer los datos |
Eficiencia | Menos eficiente con grandes volúmenes | Muy eficiente y escalable |
Cambios en views.py
para usar los modelos Juego
, Genero
y Plataforma
1. Importar el modelo Juego
Antes tenías solo imports básicos:
from django.shortcuts import render from django.http import HttpResponse
Ahora debes importar el modelo que creaste:
from django.shortcuts import render, get_object_or_404 from .models import Juego, Genero, Plataforma
Juego
: para poder hacer consultas a la base de datos sobre los juegos.Genero
yPlataforma
: si los necesitas en la vista (por ejemplo, para filtros o listados).get_object_or_404
: función útil para obtener un objeto o mostrar error 404 si no existe (mejor que hacer una búsqueda manual).
2. Eliminar la lista estática juegos
Ya no necesitas esa lista en memoria, porque la información estará en la base de datos, así que borra o comenta la variable juegos
que tienes con todos los datos.
3. Modificar la función index
Antes tu función index
usaba la lista estática:
def index(request): datos_plantilla = { 'titulo': 'Catálogo de videojuegos', 'juegos': juegos # Lista estática } plantilla_principal = { 'title': 'Catálogo de videojuegos' } return render(request, 'juegos/index.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal })
Cambio: consulta los juegos directamente en la base de datos usando el modelo y optimiza la carga de géneros y plataformas con prefetch_related
para evitar consultas adicionales en las plantillas.
def index(request): juegos = Juego.objects.all().prefetch_related('generos', 'plataformas') datos_plantilla = { 'titulo': 'Catálogo de videojuegos', 'juegos': juegos # Ahora es un QuerySet con objetos Juego y relaciones precargadas } plantilla_principal = { 'title': 'Catálogo de videojuegos' } return render(request, 'juegos/index.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal })
Por qué: Juego.objects.all()
devuelve todos los registros de la tabla juegos_juego
. prefetch_related
carga las relaciones ManyToMany (generos
y plataformas
) de forma eficiente para evitar consultas extra en la plantilla. Así puedes mostrar todos los juegos con sus géneros y plataformas sin depender de datos codificados en views.py
.
4. Modificar la función mostrar
Antes buscabas el juego en la lista estática:
def mostrar(request, id): juego = next((j for j in juegos if j['id'] == id), None) if not juego: return HttpResponse("<h1>Juego no encontrado</h1><p>El juego solicitado no existe.</p>") datos_plantilla = { 'juego': juego, 'titulo': juego['nombre'] } plantilla_principal = { 'title': juego['nombre'] } return render(request, 'juegos/mostrar.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal })
Cambio: Usa get_object_or_404
para recuperar el juego por su id en la base de datos, incluyendo las relaciones con géneros y plataformas.
def mostrar(request, id): juego = get_object_or_404(Juego.objects.prefetch_related('generos', 'plataformas'), pk=id) datos_plantilla = { 'juego': juego, 'titulo': juego.nombre } plantilla_principal = { 'title': juego.nombre } return render(request, 'juegos/mostrar.html', { 'datos_plantilla': datos_plantilla, 'plantilla_principal': plantilla_principal })
Por qué: get_object_or_404
es la forma recomendada para obtener un objeto o devolver una página de error 404 si no se encuentra.
Accedes a los atributos del objeto juego
con notación de punto (ej. juego.nombre
), y puedes acceder a sus géneros y plataformas con juego.generos.all()
y juego.plataformas.all()
.
5. Resumen de los cambios en views.py
Antes (lista estática) | Ahora (modelo Django con relaciones) | ¿Por qué? |
---|---|---|
Variable juegos con lista de diccionarios | Eliminada, usamos la base de datos | Los datos se almacenan y gestionan mejor en la base de datos, no en código estático. |
def index() usa juegos para pasar datos | def index() usa Juego.objects.all().prefetch_related('generos', 'plataformas') | Consulta dinámica y actualizada de todos los juegos con géneros y plataformas asociados. |
def mostrar() busca juego en lista manual | def mostrar() usa get_object_or_404 con relaciones precargadas | Busca el juego directamente en la base de datos, con manejo automático de error 404 y acceso a relaciones. |
Acceso a datos como diccionario (juego['id'] ) | Acceso a atributos de objeto (juego.id ) y relaciones (juego.generos.all() ) | Porque ahora juego es una instancia del modelo Juego , no un diccionario. |
Cómo agregar juegos a la base de datos con modelos relacionados (Juego
, Genero
, Plataforma
)
Puedes hacerlo desde la shell de Django o usando el panel de administración (en el siguiente artículo te mostraré cómo configurarlo).
¿Qué es la shell de Django?
La shell de Django es una consola interactiva (como la terminal de Python) que carga tu proyecto Django. Desde ahí puedes:
- Crear o consultar datos del modelo.
- Probar consultas.
- Hacer pruebas rápidas sin tocar vistas ni plantillas.
¿Por qué agregar juegos desde la shell?
Porque ya no tienes una lista estática de juegos en views.py
. Ahora los datos viven en la base de datos, y necesitas una forma de insertar esos registros. Usar la shell es la manera más directa y rápida de hacerlo al principio.
Cómo hacerlo paso a paso
1. Abre la shell
En la terminal, detén el servidor si está en marcha, y estando en la raíz del proyecto (donde está manage.py
), escribe:
python manage.py shell
Esto te abre un entorno interactivo donde puedes usar tus modelos.
2. Importa el modelo Juego
Dentro de la shell escribe:
from juegos.models import Juego, Genero, Plataforma
Esto te da acceso a tus modelos.
3. Crea instancias de género y plataforma
accion = Genero.objects.create(nombre='Acción') aventura = Genero.objects.create(nombre='Aventura') rpg = Genero.objects.create(nombre='RPG') ciencia = Genero.objects.create(nombre='Ciencia ficción') fantasia = Genero.objects.create(nombre='Fantasía') terror = Genero.objects.create(nombre='Terror') carreras = Genero.objects.create(nombre='Carreras') puzzle = Genero.objects.create(nombre='Puzzle') simulacion = Genero.objects.create(nombre='Simulación') ps5 = Plataforma.objects.create(nombre='PS5') xbox = Plataforma.objects.create(nombre='Xbox Series X/S') pc = Plataforma.objects.create(nombre='PC') switch = Plataforma.objects.create(nombre='Nintendo Switch')
4. Crear y guardar un juego con relaciones
Puedes crear un nuevo juego así:
j1 = Juego.objects.create( nombre='Grand Theft Auto VI', precio=69.99, descripcion='La esperada nueva entrega de GTA, con una historia intensa y mundo abierto impresionante.', ) # Relacionar géneros y plataformas j1.generos.set([accion, aventura]) j1.plataformas.set([ps5, xbox])
⚠️ Nota: No puedes pasar strings. Los campos
generos
yplataformas
esperan instancias deGenero
yPlataforma
.
Usa.set()
para establecer relaciones después de guardar el juego.
5. Verificar que se guardó
Puedes ver todos los juegos así:
Juego.objects.all() Juego.objects.get(id=1).generos.all() Juego.objects.get(id=1).plataformas.all()
Si te sale algo como <QuerySet [<Juego: Grand Theft Auto VI>]>, quiere decir que se ha añadido.
6. Agregar más juegos rápidamente
También podrías usar bulk_create()
para insertar varios juegos de una sola vez, si quieres eficiencia.
bulk_create()
no permite establecer relaciones ManyToMany directamente al momento de crear los objetos. Pero puedes hacer lo siguiente:
Paso 1: Crear los objetos con bulk_create()
(sin relaciones ManyToMany)
juegos = Juego.objects.bulk_create([ Juego( nombre='Elden Ring: Shadow of the Erdtree', precio=49.99, descripcion='Expansión masiva del aclamado RPG de mundo abierto, con nuevas zonas y jefes.' ), Juego( nombre='The Legend of Zelda: Echoes of the Past', precio=59.99, descripcion='Una nueva aventura épica de Zelda que expande el universo de Breath of the Wild.' ), Juego( nombre='Starfield', precio=59.99, descripcion='Exploración espacial y RPG de próxima generación por Bethesda.' ), Juego( nombre='Hogwarts Legacy', precio=59.99, descripcion='Explora el mundo mágico de Harry Potter en este RPG de mundo abierto ambientado en el siglo XIX.' ), Juego( nombre='Resident Evil 4 Remake', precio=49.99, descripcion='Reimaginación moderna del clásico survival horror con gráficos y mecánicas renovadas.' ), Juego( nombre='Final Fantasy XVI', precio=69.99, descripcion='Un nuevo capítulo en la legendaria saga de RPG con un enfoque más orientado a la acción.' ), Juego( nombre='Spider-Man 2', precio=69.99, descripcion='Continúa la aventura de Peter Parker y Miles Morales en una épica historia de superhéroes.' ), Juego( nombre='Forza Motorsport', precio=59.99, descripcion='Simulación de carreras con gráficos de última generación y física realista.' ), Juego( nombre='Metroid Prime 4', precio=59.99, descripcion='El regreso de Samus Aran en una nueva aventura de exploración y combate en primera persona.' ), ])
Paso 2: Establecer relaciones ManyToMany (generos.set(...)
y plataformas.set(...)
)
# Asumiendo que ya tienes las instancias de género y plataforma: # Ejemplo: accion = Genero.objects.get(nombre='Acción') juegos[0].generos.set([rpg, accion]) juegos[0].plataformas.set([pc, ps5, xbox]) juegos[1].generos.set([aventura, puzzle]) juegos[1].plataformas.set([switch]) juegos[2].generos.set([rpg, ciencia]) juegos[2].plataformas.set([pc, xbox]) juegos[3].generos.set([rpg, aventura]) juegos[3].plataformas.set([pc, ps5, xbox]) juegos[4].generos.set([terror, accion]) juegos[4].plataformas.set([pc, ps5, xbox]) juegos[5].generos.set([rpg, fantasia]) juegos[5].plataformas.set([ps5]) juegos[6].generos.set([accion, aventura]) juegos[6].plataformas.set([ps5]) juegos[7].generos.set([carreras, simulacion]) juegos[7].plataformas.set([pc, xbox]) juegos[8].generos.set([accion, ciencia]) juegos[8].plataformas.set([switch])
Sí puedes usar bulk_create()
para insertar los objetos base de Juego
.
Pero debes establecer las relaciones generos
y plataformas
después, uno por uno, porque esas relaciones ManyToMany no se permiten dentro de bulk_create()
.
Para salir del Shell de Django, vamos a ejecutar la función exit().
Si quieres ver los datos que has guardado, de momento puedes utilizar si estás en VSCode, la extensión SQLite Viewer, hasta que explique como entrar al panel de administrador de Django, en el siguiente artículo.

Con todo lo que has implementado hasta ahora, tienes:
Modelos en Django:
Juego
Plataforma
Genero
Tablas en la base de datos:
Cada modelo se convierte en una tabla, y además Django crea automáticamente tablas intermedias para relaciones ManyToMany.
Tablas directas (una por cada modelo):
juegos_juego
juegos_plataforma
juegos_genero
Tablas intermedias (creadas automáticamente por Django para manejar las relaciones ManyToMany):
juegos_juego_plataformas
← relación entreJuego
yPlataforma
juegos_juego_generos
← relación entreJuego
yGenero
Nota: el nombre de las tablas puede cambiar ligeramente si usas un
db_table
personalizado o cambias elapp_label
, pero por defecto sigue este patrón.