Saltar al contenido

# creacion-de-aplicaciones-web-en-django-y-python

Creación de Aplicaciones Web en Django y Python

Volver a la portada del curso
Lectura (Markdown)

Definició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"]
  • CharField exige max_length; para textos largos se usa TextField.
  • SlugField guarda el identificador de la URL (taza-pyshop). Mide 50 caracteres por defecto: en el SQL aparece como varchar(50).
  • DecimalField es el campo correcto para dinero: guarda valores exactos, sin el error de redondeo de float. Con max_digits=8 y decimal_places=2 admite hasta 999999.99.
  • auto_now_add=True llena 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 FloatField para precios. Los flotantes acumulan errores de redondeo; el dinero va en DecimalField.
  • Olvidar on_delete. Es obligatorio en ForeignKey, y elegir CASCADE sin pensarlo puede borrar datos en cadena.
  • Usar get cuando puede no haber resultado. Lanza DoesNotExist; en una vista se usa get_object_or_404.
  • Cambiar el modelo y no generar la migración. La base sigue con la estructura vieja hasta correr makemigrations y migrate.

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.

Recursos