# creacion-de-aplicaciones-web-en-django-y-python
Creación de Aplicaciones Web en Django y Python
Volver a la portada del cursoDefinición de Modelos
Sección 3 · Modelos y Bases de Datos
Definición de modelos
Un modelo es una clase de Python que describe una tabla. Cada atributo es una columna y cada instancia, una fila. Django traduce las consultas que escribes en Python al SQL de la base, así que el catálogo de PyShop se define una sola vez, en catalogo/models.py, y de ahí salen la tabla, el panel de administración y los formularios.
Campos y relaciones
El catálogo usa tres modelos: Categoria, Producto y Resena. Este es Producto:
from django.db import models
from django.urls import reverse
class Producto(models.Model):
categoria = models.ForeignKey(
Categoria, on_delete=models.PROTECT, related_name="productos"
)
nombre = models.CharField(max_length=120)
slug = models.SlugField(unique=True)
precio = models.DecimalField(max_digits=8, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
disponible = models.BooleanField(default=True)
creado = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ["nombre"]
CharFieldexigemax_length; para textos largos se usaTextField.SlugFieldguarda el identificador de la URL (taza-pyshop). Mide 50 caracteres por defecto: en el SQL aparece comovarchar(50).DecimalFieldes el campo correcto para dinero: guarda valores exactos, sin el error de redondeo defloat. Conmax_digits=8ydecimal_places=2admite hasta 999999.99.auto_now_add=Truellena la fecha al crear la fila.- Django agrega solo la clave primaria
id.
La ForeignKey une cada producto con su categoría. on_delete decide qué pasa al borrar la categoría: PROTECT lo impide mientras tenga productos; CASCADE, que usa Resena con su producto, borra también las reseñas. related_name da nombre al camino inverso: categoria.productos.all() y producto.resenas.all().
Métodos útiles y validadores
__str__ define cómo se ve el objeto en el admin y en la consola (Taza PyShop). get_absolute_url devuelve la URL canónica de cada producto; las plantillas y redirect la usan sin que tengas que escribir rutas a mano:
# Dentro de la clase Producto
def get_absolute_url(self):
return reverse("catalogo:detalle", args=[self.slug])
Para la taza devuelve /producto/taza-pyshop/. En Resena, la calificación lleva validators=[MinValueValidator(1), MaxValueValidator(5)]: el formulario rechaza un 9 o un 0 antes de guardar. Meta.ordering fija el orden por defecto de las consultas: productos por nombre y reseñas de la más nueva a la más vieja (["-creada"]).
El ORM en la práctica
Tras crear las tablas con makemigrations y migrate (tema del siguiente video), cada modelo tiene un administrador objects para consultar. Con los datos de ejemplo del curso:
from django.db.models import Avg, Count
Producto.objects.count() # 8
Producto.objects.filter(disponible=True).count() # 7
Producto.objects.filter(precio__lt=300) # Gorra, Playera, Stickers, Taza
Producto.objects.filter(categoria__slug="ropa") # Gorra, Playera, Sudadera
Producto.objects.order_by("-precio")[:3] # Sudadera, Libro Django práctico, Libro Python desde cero
Categoria.objects.annotate(n=Count("productos")) # Accesorios 3, Libros 2, Ropa 3
taza = Producto.objects.get(slug="taza-pyshop")
taza.resenas.aggregate(promedio=Avg("calificacion")) # {'promedio': 4.5}
Los lookups se escriben con doble guion bajo: precio__lt significa «precio menor que» y categoria__slug cruza la relación hasta la tabla de categorías. filter devuelve un QuerySet (cero o más filas); get devuelve un solo objeto y lanza una excepción si no hay ninguno o si hay más de uno. Los QuerySets son perezosos: el SQL se ejecuta hasta que los recorres o los conviertes en lista.
Crear, actualizar y borrar también es Python. Al crear una «Libreta» se obtiene el id 9 y el total sube a 9; Producto.objects.filter(slug="libreta").update(precio="99.00") cambia el precio con un solo UPDATE; delete() la elimina y el total vuelve a 8.
Los modelos en el admin
Registrar el modelo en catalogo/admin.py da al equipo de PyShop un panel para editar el catálogo sin escribir vistas:
@admin.register(Producto)
class ProductoAdmin(admin.ModelAdmin):
list_display = ["nombre", "categoria", "precio", "stock", "disponible"]
list_filter = ["categoria", "disponible"]
search_fields = ["nombre"]
prepopulated_fields = {"slug": ["nombre"]}
Con prepopulated_fields, el slug se rellena mientras se escribe el nombre. Para entrar al panel se crea un usuario con python manage.py createsuperuser.
Trampas comunes
- Usar
FloatFieldpara precios. Los flotantes acumulan errores de redondeo; el dinero va enDecimalField. - Olvidar
on_delete. Es obligatorio enForeignKey, y elegirCASCADEsin pensarlo puede borrar datos en cadena. - Usar
getcuando puede no haber resultado. LanzaDoesNotExist; en una vista se usaget_object_or_404. - Cambiar el modelo y no generar la migración. La base sigue con la estructura vieja hasta correr
makemigrationsymigrate.
El notebook estructura-y-modelos recorre models.py campo por campo y ejecuta estas consultas sobre los datos de ejemplo. Intenta predecir cada resultado antes de correr la celda.