Python Django Fullstack explicado de forma sencilla (con diagramas y código real)
Python Django Fullstack : lo esencial en un artículo — código real, diagramas y pasos concretos, extractos de un curso de 24 lecciones.
Una guía que va al grano: Python Django Fullstack diseccionada con diagramas, ejemplos concretos y comandos probados. Todo proviene de un curso estructurado de 8 capítulos — aquí tienes lo mejor.
tl;dr
- ¿Por qué Django
- Modelos y migraciones
- Vistas y URLs
- Plantillas y formularios
- Admin
~$ cat ./parcours.md # Python Django Fullstack — 8 capítulos
01
Por qué Django
→ Django vs Flask vs FastAPI→ Installation et premier projet+ 1 más lecciones
02
Models y migraciones
→ Models et fields→ Relations+ 1 más lecciones
03
Views y URLs
→ Function-based views→ Class-based views (CBV)+ 1 más lecciones
04
Templates y forms
→ Template language→ Forms et ModelForms+ 1 más lecciones
05
Admin
→ Customizer ModelAdmin→ Actions et filters custom+ 1 más leçons
06
Autenticación
→ User model et auth views→ Custom User et Profile+ 1 más leçons
07
Despliegue
→ Settings prod/dev→ Gunicorn + Nginx+ 1 más leçons
08
Proyecto final blog ecom
→ Capítulo 07 – Proyecto final : Modelos y admin→ Capítulo 07 – Proyecto final : Views y templates+ 1 más leçons
🏁
Proyecto final
→ Te vas con un proyecto concreto y demostrable
Migrations
Ciclo básico
# 1. Modificar un modelo class Article(models.Model): title = models.CharField(max_length=200) summary = models.TextField(blank=True) # NUEVO # 2. Generar la migración python manage.py makemigrations # Migrations for 'blog': # blog/migrations/0002_article_summary.py # 3. Aplicar python manage.py migrate # 4. Verificar python manage.py showmigrations
Ver el SQL generado
python manage.py sqlmigrate blog 0002 # Output : # BEGIN; # ALTER TABLE blog_article ADD COLUMN summary TEXT NOT NULL DEFAULT ''; # COMMIT;
Revertir una migración
python manage.py migrate blog 0001 # Revertir a 0001 (anula 0002) python manage.py migrate blog zero # Anular todo para la app blog
Migración de datos
python manage.py makemigrations --empty --name backfill_slugs blog # Editar el archivo generado: from django.db import migrations from django.utils.text import slugify def backfill_slugs(apps, schema_editor): Article = apps.get_model("blog", "Article") for a in Article.objects.all(): if not a.slug: a.slug = slugify(a.title) a.save() def reverse_func(apps, schema_editor): pass class Migration(migrations.Migration): dependencies = [("blog", "0001_initial")] operations = [migrations.RunPython(backfill_slugs, reverse_func)]
Compactar migraciones
# Tras muchas migraciones, compactarlas: python manage.py squashmigrations blog 0001 0020 # Crea 0001_squashed_0020 que reemplaza los 20 archivos # Los nuevos desarrolladores evitan ejecutar 20 migraciones
Migraciones sin downtime
NOTEPara añadir una columna NOT NULL sin romper la prod:
- Deploy 1 : Añadir columna nullable
- Deploy 2 : Backfill mediante migración de datos
- Deploy 3 : Modificar el código para usar/setear
- Deploy 4 : Migración ALTER NOT NULL
Renombrar un modelo
# Si renombras Article -> Post, Django propondrá # crear un nuevo modelo + eliminar el antiguo (¡pierdes los datos!) # Solución: editar la migración generada operations = [ migrations.RenameModel("Article", "Post") ] # Ídem para los campos operations = [ migrations.RenameField("Post", "summary", "excerpt") ]
Django vs Flask vs FastAPI
NOTEObjetivo — Comprender la filosofía de Django y cuándo elegirlo.
Comparación rápida
| Criterio | Django | Flask | FastAPI |
|---|---|---|---|
| Filosofía | Batteries included | Micro framework | API first, async |
| ORM | Integrado | SQLAlchemy externo | SQLAlchemy externo |
| Admin | Auto-generado | A programar | A programar |
| Auth | Completa | Extensiones | A programar JWT |
| Templates | Django templates | Jinja2 | Jinja2 opcional |
| Async nativo | Parcial (4.x) | No | Sí |
| Aprendizaje | Más largo | Muy simple | Medio |
Cuándo elegir Django
Cuándo NO elegir Django
Arquitectura MTV
+----------+ request +------+ query +-------+
| Browser | -----------> | View | --------> | Model |
+----------+ +------+ +-------+
^ | |
| v v
| +----------+ +-------+
+----------------- | Template |<--------| DB |
+----------+ +-------+
# M = Model (ORM, base de datos)
# T = Template (HTML)
# V = View (lógica de control, en MVC es el Controller)Ecosistema Django
Versiones y LTS
NOTEDjango releases :
- Django 5.0+ : actual
- Django 4.2 LTS : soportada hasta 2026
- Ciclo : nueva versión cada 8 meses
- LTS cada 2 años, soportadas 3 años
Empresas que usan Django
Resumen
NOTEA recordar
- Django = batteries included para apps web complejas
- Arquitectura MTV (Model, Template, View)
- Ideal : SaaS, CMS, e-commerce, dashboards
- API pura : prefiere DRF o FastAPI
Chapitre 07 – Projet final : Modeles et admin
Pliego de condiciones
NOTEObjetivo — Construir una tienda online minimalista con : catálogo, carrito, pedidos, admin de negocio.
Estructura del proyecto
myshop/
manage.py
myshop/
settings/
urls.py
wsgi.py
accounts/
models.py # User custom
views.py
urls.py
catalog/
models.py # Category, Product
views.py
admin.py
cart/
cart.py # clase Cart (session)
views.py
orders/
models.py # Order, OrderItem
views.py
admin.py
templates/
static/
media/accounts/models.py
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone = models.CharField(max_length=20, blank=True) address = models.TextField(blank=True) city = models.CharField(max_length=100, blank=True) postal_code = models.CharField(max_length=10, blank=True) country = models.CharField(max_length=2, default="CA") class Meta: ordering = ["-date_joined"] def __str__(self): return self.username
catalog/models.py
from django.db import models from django.urls import reverse from django.utils.text import slugify class Category(models.Model): name = models.CharField(max_length=100, unique=True) slug = models.SlugField(max_length=120, unique=True) description = models.TextField(blank=True) parent = models.ForeignKey("self", null=True, blank=True, on_delete=models.CASCADE, related_name="children") class Meta: verbose_name_plural = "Categories" ordering = ["name"] def __str__(self): return self.name def get_absolute_url(self): return reverse("catalog:category", kwargs={"slug": self.slug}) class Product(models.Model): category = models.ForeignKey(Category, on_delete=models.PROTECT, related_name="products") name = models.CharField(max_length=200) slug = models.SlugField(max_length=220, unique=True) description = models.TextField() price = models.DecimalField(max_digits=10, decimal_places=2) image = models.ImageField(upload_to="products/%Y/%m/") stock = models.PositiveIntegerField(default=0) available = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ["-created_at"] indexes = [ models.Index(fields=["slug"]), models.Index(fields=["available", "-created_at"]), ] def __str__(self): return self.name def save(self, *args, **kwargs): if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) def get_absolute_url(self): return reverse("catalog:product", kwargs={"slug": self.slug}) @property def in_stock(self): return self.available and self.stock > 0
orders/models.py
class Order(models.Model): class Status(models.TextChoices): PENDING = "pending", "En attente" PAID = "paid", "Payé" SHIPPED = "shipped", "Expédié" DELIVERED = "delivered", "Livré" CANCELED = "canceled", "Annulé" user = models.ForeignKey("accounts.User", on_delete=models.PROTECT, related_name="orders") status = models.CharField(max_length=20, choices=Status.choices, default=Status.PENDING) address = models.TextField() city = models.CharField(max_length=100) postal_code = models.CharField(max_length=10) country = models.CharField(max_length=2) total = models.DecimalField(max_digits=12, decimal_places=2, default=0) paid_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ["-created_at"] def __str__(self): return f"Order #{self.id}" class OrderItem(models.Model): order = models.ForeignKey(Order, on_delete=models.CASCADE, related_name="items") product = models.ForeignKey("catalog.Product", on_delete=models.PROTECT) price = models.DecimalField(max_digits=10, decimal_places=2) quantity = models.PositiveIntegerField(default=1) @property def subtotal(self): return self.price * self.quantity
catalog/admin.py
@admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ["name", "parent", "product_count"] prepopulated_fields = {"slug": ("name",)} search_fields = ["name"] def product_count(self, obj): return obj.products.count() @admin.register(Product) class ProductAdmin(admin.ModelAdmin): list_display = ["name", "category", "price", "stock", "available"] list_filter = ["available", "category", "created_at"] list_editable = ["price", "stock", "available"] search_fields = ["name", "description"] prepopulated_fields = {"slug": ("name",)} list_per_page = 50
orders/admin.py
class OrderItemInline(admin.TabularInline): model = OrderItem extra = 0 readonly_fields = ["product", "price", "quantity", "subtotal"] can_delete = False @admin.register(Order) class OrderAdmin(admin.ModelAdmin): list_display = ["id", "user", "status", "total", "created_at", "paid_at"] list_filter = ["status", "created_at"] search_fields = ["id", "user__username", "user__email"] inlines = [OrderItemInline] actions = ["mark_shipped"] date_hierarchy = "created_at" @admin.action(description="Marquer comme expédié") def mark_shipped(self, request, queryset): queryset.update(status=Order.Status.SHIPPED)
va-plus-loin
Este artículo cubre los extractos más útiles — el curso completo Python Django Fullstack (8 capítulos, 24 lecciones, ejercicios corregidos y proyecto final) te lleva hasta el final.
./acceder-au-cours-complet cours gratuit : Vibe CodingFAQ
¿Cuánto tiempo se necesita para aprender Python Django Fullstack?
Con una progresión estructurada (8 capítulos, 24 lecciones cortas y prácticas), se alcanza un nivel operativo en unas semanas dedicando 30 a 60 minutos al día. Lo importante es practicar cada concepto de inmediato.
¿Se necesitan requisitos previos?
Basta con nociones básicas de informática. Si sabes usar un terminal y leer código sencillo, estás listo.
¿Por dónde empezar concretamente?
Reproduce los comandos de este artículo y luego sigue el curso completo Python Django Fullstack: encadena las 24 lecciones en orden, con ejercicios y proyecto final.
./a-lire-aussi
→ Lance-toi en Portfolio IA SEO Vercel : ton premier pas concret aujourd'hui→ IA Stripe GitHub SaaS en pratique : le code et les commandes qui comptent vraiment→ Python Requests APIs expliqué simplement (avec schémas et vrai code)📬 ¿Quieres recibir este tipo de guía cada semana? Suscríbete gratis — código real, cero paja.