Domina los fundamentos con ejemplos claros y componentes consistentes para lectura rápida.
Los modelos representan tablas en tu base de datos y son el punto de partida del ORM.
# Modelos base para el ejemplo
from django.db import models
# Autor con datos básicos
class Author(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField(unique=True)
age = models.IntegerField()
created_at = models.DateTimeField(auto_now_add=True)
# Libro relacionado con Author
class Book(models.Model):
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
published = models.DateField()
is_available = models.BooleanField(default=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books") Book pertenece a un Author y un Author puede tener muchos Book vía related_name="books".
Aquí ves las formas más comunes de insertar datos con el ORM. Elige según si creas uno, varios o necesitas evitar duplicados.
# Crear un autor en una sola operación
author = Author.objects.create(
name="Gabriel García Márquez",
email="gabo@example.com",
age=87
) Ideal cuando construyes el objeto paso a paso antes de persistirlo. Permite validaciones y lógica previa antes de guardar.
# Instanciar y guardar después
book = Book(
title="Cien años de soledad",
price=29.99,
published="1967-05-30",
author=author
)
book.save() Úsalo para insertar múltiples registros en una sola query. No dispara señales ni llama save() por cada instancia.
# Insertar múltiples libros de una sola vez
books = [
Book(title="El amor en los tiempos del cólera", price=24.99, published="1985-01-01", author=author),
Book(title="Crónica de una muerte anunciada", price=19.99, published="1981-01-01", author=author),
]
Book.objects.bulk_create(books) Busca por criterios únicos y crea si no existe. Devuelve el objeto y un booleano indicando si se creó.
# Buscar por email o crear si no existe
author, created = Author.objects.get_or_create(
email="gabo@example.com",
defaults={"name": "Gabriel García Márquez", "age": 87}
) Usa bulk_create() para insertar muchos registros sin penalizar la base de datos.
# Leer todos y uno específico
authors = Author.objects.all()
author = Author.objects.get(id=1)
author_by_email = Author.objects.get(email="gabo@example.com") all() devuelve el queryset completo para iterar o paginar.
get() devuelve un único objeto o lanza error si no existe o hay más de uno.
# Atajos comunes para traer o validar
first_author = Author.objects.first()
last_book = Book.objects.last()
exists = Author.objects.filter(email="gabo@example.com").exists() first() y last() son atajos seguros para traer un solo registro.
exists() valida presencia sin cargar objetos completos.
exists() es más eficiente que traer un objeto cuando solo necesitas validar presencia.
# Filtrar por relación y por booleano
books = Book.objects.filter(author__name="Gabriel García Márquez")
available_books = Book.objects.filter(is_available=True) Filtras por campos directos o por relaciones con __.
Ideal para queries legibles y rápidas en listados.
# AND implícito entre condiciones
books = Book.objects.filter(
author__name="Gabriel García Márquez",
is_available=True,
price__lt=30
) Django combina condiciones con AND por defecto. Sencillo para reglas de negocio con varios filtros.
# OR explícito con Q
from django.db.models import Q
books = Book.objects.filter(
Q(price__lt=20) | Q(is_available=False)
) Q permite OR y combinaciones avanzadas.
Útil para búsquedas con alternativas.
# Excluir por año y disponibilidad
books = Book.objects.exclude(published__year=1967)
books = Book.objects.exclude(is_available=False) exclude() es el NOT del filter.
Perfecto para descartar registros no deseados.
El doble guion bajo __ permite acceder a relaciones y aplicar lookups avanzados.
Los lookups son sufijos que modifican cómo Django compara valores en las queries.
# Contiene (case-insensitive)
Book.objects.filter(title__icontains="soledad")
# Empieza con
Book.objects.filter(title__startswith="Cien")
# Termina con
Book.objects.filter(title__endswith="anunciada")
# Coincidencia exacta (case-insensitive)
Book.objects.filter(title__iexact="cien años de soledad")
ial inicio del lookup significa “case-insensitive”. Sin lai, la comparación distingue mayúsculas.
# Mayor que
Author.objects.filter(age__gt=50)
# Mayor o igual que
Author.objects.filter(age__gte=50)
# Menor que
Book.objects.filter(price__lt=25)
# Menor o igual que
Book.objects.filter(price__lte=25)
# Rango (inclusive)
Book.objects.filter(price__range=(10, 30))
gt= greater than,gte= greater than or equal,lt= less than,lte= less than or equal.
# Año específico
Book.objects.filter(published__year=1967)
# Mes específico
Book.objects.filter(published__month=5)
# Rango de fechas
Book.objects.filter(published__range=("1960-01-01", "1990-12-31")) Filtras por componentes de fecha sin convertir en SQL manual.
range es ideal para periodos cerrados.
Puedes extraer componentes de fechas:
year,month,day,hour,minute,second.
Book.objects.filter(id__in=[1, 2, 3])
Author.objects.filter(name__in=["Gabriel", "Mario", "Jorge"])
__ines equivalente aWHERE id IN (1, 2, 3)en SQL. Acepta listas, tuplas o QuerySets.
Book.objects.filter(author__isnull=True)
Book.objects.filter(author__isnull=False) Controlas campos nulos de forma explícita. Clave para calidad de datos y relaciones incompletas.
Filtra registros donde la relación o campo es NULL o no lo es.
El orden de los resultados afecta la presentación y el rendimiento de paginación.
books = Book.objects.order_by("title")
books = Book.objects.order_by("published") Orden ascendente por defecto, ideal para listados A-Z. También útil para cronología de menor a mayor.
Sin prefijo, el orden es ascendente (A-Z, menor a mayor, más antiguo a más reciente).
books = Book.objects.order_by("-published")
books = Book.objects.order_by("-price") El guion invierte el orden para rankings y recientes. Perfecto para “top” y últimas publicaciones.
El prefijo
-invierte el orden: descendente (Z-A, mayor a menor, más reciente primero).
books = Book.objects.order_by("-published", "title") Primero ordena por el campo principal y luego por desempate. Mantiene consistencia visual en listados.
Primero ordena por fecha descendente. Si hay empate, ordena alfabéticamente por título.
random_book = Book.objects.order_by("?").first()
order_by("?")ordena aleatoriamente. Útil para selección random, pero costoso en tablas grandes.
Usar slicing para limitar resultados es más eficiente que traer todo y filtrar en Python.
top_5_books = Book.objects.all()[:5] Equivale a
LIMIT 5en SQL. No ejecuta la query hasta que evalúas el resultado. Útil para previews, rankings o dashboards con pocos elementos.
# Página 1 (registros 0-9)
page_1 = Book.objects.all()[0:10]
# Página 2 (registros 10-19)
page_2 = Book.objects.all()[10:20] El slicing
[inicio:fin]se traduce aLIMITyOFFSETen SQL. Funciona bien para casos simples, pero en producción conviene usar paginadores.
total_books = Book.objects.count()
available_books = Book.objects.filter(is_available=True).count()
count()ejecutaSELECT COUNT(*), más eficiente quelen(queryset)que trae todos los registros. Evita contar en Python para no cargar datos innecesarios.
La relación más común. Un objeto “padre” tiene muchos “hijos”, pero cada hijo tiene un solo padre. Es el patrón típico de autor-libro, cliente-pedido o categoría-producto. El índice en la FK impacta directamente el rendimiento de filtros y joins.
class Book(models.Model):
title = models.CharField(max_length=200)
author = models.ForeignKey(
Author,
on_delete=models.CASCADE,
related_name="books"
)
ForeignKeysiempre va en el modelo “hijo” (el lado “muchos”). Un libro pertenece a un autor.
book = Book.objects.get(id=1)
print(book.author.name)
print(book.author.email) Desde el libro accedes directamente al autor con
book.author. Django hace el JOIN automáticamente.
author = Author.objects.get(id=1)
books = author.books.all() # Usando related_name
books_count = author.books.count()
related_name="books"permiteauthor.books. Sin él, seríaauthor.book_set.all().
# CASCADE: Borra los libros si se borra el autor
author = models.ForeignKey(Author, on_delete=models.CASCADE)
# PROTECT: Impide borrar el autor si tiene libros
author = models.ForeignKey(Author, on_delete=models.PROTECT)
# SET_NULL: Pone NULL en author_id (requiere null=True)
author = models.ForeignKey(Author, on_delete=models.SET_NULL, null=True)
# SET_DEFAULT: Asigna un autor por defecto
author = models.ForeignKey(Author, on_delete=models.SET_DEFAULT, default=1)
on_deletedefine qué pasa con los hijos cuando se elimina el padre.CASCADEes el más común.
Cuando ambos lados pueden tener múltiples relaciones entre sí. Django crea una tabla intermedia automáticamente. Ideal para etiquetas, categorías, grupos o permisos. La tabla intermedia guarda pares de IDs y permite consultas bidireccionales.
class Category(models.Model):
name = models.CharField(max_length=100)
class Book(models.Model):
title = models.CharField(max_length=200)
categories = models.ManyToManyField(Category, related_name="books") Un libro puede tener muchas categorías, y una categoría puede tener muchos libros.
book = Book.objects.get(id=1)
fiction = Category.objects.get(name="Ficción")
drama = Category.objects.get(name="Drama")
book.categories.add(fiction)
book.categories.add(fiction, drama) # Múltiples a la vez
add()crea la relación en la tabla intermedia. No duplica si ya existe.
book.categories.remove(fiction)
book.categories.clear() # Elimina todas las categorías del libro
remove()elimina una relación específica.clear()elimina todas las relaciones del objeto.
# Categorías de un libro
book.categories.all()
# Libros de una categoría
fiction.books.all()
# Filtrar por relación
Book.objects.filter(categories__name="Ficción") La relación es bidireccional. Puedes navegar desde cualquier lado.
book.categories.set([fiction, drama])
set()reemplaza todas las relaciones existentes con las nuevas. Equivale aclear()+add().
Cuando necesitas almacenar información adicional sobre la relación misma. Úsalo cuando la relación tiene atributos propios como fechas, estados o notas. Te obliga a tratar la relación como un modelo real, con su propia lógica.
class Student(models.Model):
name = models.CharField(max_length=100)
class Course(models.Model):
name = models.CharField(max_length=100)
students = models.ManyToManyField(Student, through="Enrollment", related_name="courses")
class Enrollment(models.Model):
student = models.ForeignKey(Student, on_delete=models.CASCADE)
course = models.ForeignKey(Course, on_delete=models.CASCADE)
enrolled_at = models.DateTimeField(auto_now_add=True)
grade = models.DecimalField(max_digits=4, decimal_places=2, null=True)
class Meta:
unique_together = ["student", "course"]
throughespecifica el modelo intermedio.Enrollmentguarda cuándo se inscribió y su calificación.
student = Student.objects.get(id=1)
course = Course.objects.get(id=1)
enrollment = Enrollment.objects.create(
student=student,
course=course,
grade=9.5
) Con
through, debes crear la relación mediante el modelo intermedio, no conadd().
# Obtener la inscripción específica
enrollment = Enrollment.objects.get(student=student, course=course)
print(enrollment.grade)
# Todos los cursos de un estudiante con sus notas
enrollments = Enrollment.objects.filter(student=student).select_related("course")
for e in enrollments:
print(f"{e.course.name}: {e.grade}") Accedes a los datos extra consultando directamente el modelo
Enrollment.
Django ofrece varias formas de actualizar datos según el caso de uso.
Elige entre save() para un objeto o update() para operaciones masivas.
Actualizar en la base de datos evita estados inconsistentes en memoria.
author = Author.objects.get(id=1)
author.age = 88
author.email = "nuevo@example.com"
author.save() Modifica los atributos y llama
save(). Django detecta los cambios y actualiza solo esos campos.
author.save(update_fields=["age"])
update_fieldsoptimiza el UPDATE para modificar solo los campos indicados.
Book.objects.filter(author__name="Gabriel García Márquez").update(is_available=True)
update()ejecuta un solo UPDATE SQL. Más eficiente que iterar y guardar cada objeto.
from django.db.models import F
Book.objects.filter(id=1).update(price=F("price") * 1.10)
Author.objects.all().update(age=F("age") + 1)
F()referencia el valor actual del campo en la base de datos. Evita race conditions.
author, created = Author.objects.update_or_create(
email="gabo@example.com",
defaults={"name": "Gabriel García Márquez", "age": 88}
) Busca por
defaults. Si no, crea un nuevo registro.
Eliminar datos requiere cuidado, especialmente con relaciones en cascada. Antes de borrar, valida dependencias y reglas de negocio. En sistemas críticos, el soft delete suele ser la opción más segura.
book = Book.objects.get(id=1)
book.delete()
delete()elimina el objeto y retorna una tupla con el conteo de objetos eliminados.
Book.objects.filter(is_available=False).delete() Elimina todos los registros que coinciden con el filtro en una sola query.
Book.objects.all().delete() Cuidado: esto elimina TODOS los registros de la tabla. Úsalo con precaución.
class Book(models.Model):
title = models.CharField(max_length=200)
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)
# En lugar de delete()
from django.utils import timezone
book.is_deleted = True
book.deleted_at = timezone.now()
book.save()
# Filtrar solo activos
Book.objects.filter(is_deleted=False) Soft delete marca como eliminado sin borrar físicamente. Permite recuperación y auditoría.
Las agregaciones calculan un valor único a partir de un conjunto de registros. Se usan para métricas como promedios, totales y máximos. Ejecutan SQL directo y retornan un diccionario compacto.
from django.db.models import Count, Sum, Avg, Max, Min
# Promedio de edad de autores
Author.objects.aggregate(avg_age=Avg("age"))
# Resultado: {"avg_age": 65.5}
# Precio máximo y mínimo de libros
Book.objects.aggregate(
max_price=Max("price"),
min_price=Min("price")
)
# Resultado: {"max_price": 29.99, "min_price": 19.99}
# Suma total de precios
Book.objects.aggregate(total=Sum("price"))
# Contar registros
Book.objects.aggregate(total=Count("id"))
aggregate()retorna un diccionario con los resultados. Ejecuta una sola query SQL.
Book.objects.filter(is_available=True).aggregate(avg_price=Avg("price")) Puedes combinar
filter()conaggregate()para calcular sobre un subconjunto.
Las anotaciones agregan campos calculados a cada objeto del QuerySet. Permiten enriquecer cada fila con totales o valores derivados. Ideales para reportes sin hacer loops en Python.
from django.db.models import Count
authors = Author.objects.annotate(num_books=Count("books"))
for author in authors:
print(f"{author.name}: {author.num_books} libros") Cada
authortiene un atributonum_bookscalculado. Una sola query con GROUP BY.
from django.db.models import Sum
authors = Author.objects.annotate(total_revenue=Sum("books__price")) Suma los precios de todos los libros de cada autor.
# Autores con más de 5 libros
Author.objects.annotate(num_books=Count("books")).filter(num_books__gt=5) Puedes usar el campo anotado en
filter(),order_by()y otros métodos.
# aggregate: UN valor total
Book.objects.aggregate(avg=Avg("price"))
# Resultado: {"avg": 24.99}
# annotate: UN valor POR CADA objeto
Author.objects.annotate(avg_book_price=Avg("books__price"))
# Resultado: QuerySet donde cada author tiene author.avg_book_price
aggregate= resumen total.annotate= resumen por fila.
El problema más común de rendimiento en ORMs. Ocurre cuando accedes a relaciones dentro de loops.
Se dispara cuando cada iteración necesita una query adicional.
Se corrige usando select_related o prefetch_related según el tipo de relación.
# MAL: Genera N+1 queries
books = Book.objects.all() # 1 query
for book in books:
print(book.author.name) # N queries (una por cada libro) Si tienes 100 libros, esto ejecuta 101 queries. Destruye el rendimiento.
Usa django-debug-toolbar o logging de queries para identificar N+1:
# En settings.py para desarrollo
LOGGING = {
'version': 1,
'handlers': {
'console': {'class': 'logging.StreamHandler'},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
} Verás cada query SQL en la consola. Si ves patrones repetitivos, tienes N+1.
Optimiza consultas a relaciones ForeignKey y OneToOne mediante JOIN.
Es ideal cuando necesitas datos relacionados “hacia adelante” sin disparar queries extra.
Devuelve el mismo QuerySet, pero con objetos ya resueltos en memoria.
# BIEN: 1 sola query con JOIN
books = Book.objects.select_related("author")
for book in books:
print(book.author.name) # No genera query adicional
select_relatedhace JOIN en SQL y trae los datos del autor junto con el libro.
# Si Book tiene author y publisher (ambos ForeignKey)
books = Book.objects.select_related("author", "publisher") Puedes incluir múltiples relaciones en un solo
select_related.
# Si Author tiene country (ForeignKey)
books = Book.objects.select_related("author__country") El doble guion bajo navega relaciones anidadas: libro → autor → país.
| Relación | Usar |
|---|---|
ForeignKey | ✅ select_related |
OneToOneField | ✅ select_related |
ManyToManyField | ❌ Usar prefetch_related |
| Relación inversa | ❌ Usar prefetch_related |
Solo funciona con relaciones “hacia adelante” que resultan en un solo objeto.
Optimiza consultas a ManyToMany y relaciones inversas mediante queries separadas.
Trae los datos relacionados en una segunda query y los une en Python.
Es la opción correcta cuando una relación devuelve múltiples objetos.
# BIEN: 2 queries en lugar de N+1
authors = Author.objects.prefetch_related("books")
for author in authors:
for book in author.books.all(): # No genera query adicional
print(book.title) Django ejecuta 2 queries: una para autores, otra para sus libros. Luego los une en Python.
books = Book.objects.prefetch_related("categories")
for book in books:
for category in book.categories.all():
print(category.name)
prefetch_relatedes la única opción para ManyToMany porque no se puede hacer JOIN simple.
from django.db.models import Prefetch
# Prefetch solo libros disponibles, ordenados por fecha
authors = Author.objects.prefetch_related(
Prefetch(
"books",
queryset=Book.objects.filter(is_available=True).order_by("-published")
)
)
Prefetchpermite personalizar el queryset de la relación prefetcheada.
# Libros con su autor (FK) y categorías (M2M)
books = Book.objects.select_related("author").prefetch_related("categories") Usa ambos cuando tienes relaciones de ambos tipos. Cada uno optimiza su parte.
Retorna diccionarios en lugar de objetos modelo. Más ligero y rápido. Útil cuando solo necesitas datos planos para APIs, listados o reportes simples. Pierdes métodos del modelo y validaciones a nivel de instancia.
books = Book.objects.values("id", "title", "price")
# Resultado: [{"id": 1, "title": "Cien años...", "price": 29.99}, ...] Solo trae los campos especificados. Ideal para APIs y listados simples.
books = Book.objects.values("title", "author__name", "author__email") Puedes incluir campos de relaciones con
__. Django hace el JOIN necesario.
# Lista de tuplas
Book.objects.values_list("id", "title")
# Resultado: [(1, "Cien años..."), (2, "El amor..."), ...]
# Lista plana (un solo campo)
Book.objects.values_list("title", flat=True)
# Resultado: ["Cien años...", "El amor...", ...]
values_listretorna tuplas. Conflat=Trueretorna una lista simple.
# Para APIs REST simples
data = list(Book.objects.values("id", "title", "price"))
# Para choices en formularios
choices = Author.objects.values_list("id", "name")
# Para verificar valores únicos
unique_years = Book.objects.values_list("published__year", flat=True).distinct() Usa
values()cuando no necesitas métodos del modelo ni objetos completos.
Cargan objetos modelo pero con solo algunos campos. Útil para optimización fina. Son útiles cuando necesitas el modelo pero no todos los campos de inmediato. El acceso a campos no cargados puede generar queries adicionales.
books = Book.objects.only("id", "title")
for book in books:
print(book.title) # OK, ya está cargado
print(book.price) # CUIDADO: Genera query adicional
only()carga solo los campos indicados. Acceder a otros genera queries extra.
# Excluir un campo grande (ej: contenido de texto largo)
books = Book.objects.defer("content", "description")
defer()carga todo EXCEPTO los campos indicados. Ideal para camposTextFieldgrandes.
| Método | Retorna | Campos cargados |
|---|---|---|
values() | dict | Solo los indicados |
only() | modelo | Solo los indicados |
defer() | modelo | Todos menos los indicados |
values()para datos crudos.only()/defer()cuando necesitas el modelo.
Elimina duplicados del QuerySet. Se aplica en SQL, por lo que el orden y las joins pueden afectar el resultado. Úsalo para deduplicar listados sin traer todo a Python.
# Años únicos de publicación
years = Book.objects.values_list("published__year", flat=True).distinct()
distinct()aplicaSELECT DISTINCTen SQL.
# En PostgreSQL, distinct puede especificar campos
Book.objects.order_by("author").distinct("author") En PostgreSQL,
distinct("campo")elimina duplicados por ese campo específico.
Permiten operaciones a nivel de base de datos sin traer datos a Python. Evitan condiciones de carrera y reducen lecturas innecesarias. Son la base para cálculos, comparaciones y updates atómicos.
from django.db.models import F
# Aumentar precio 10%
Book.objects.update(price=F("price") * 1.10)
# Comparar campos entre sí
Book.objects.filter(sold_copies__gt=F("printed_copies"))
F()referencia el valor actual en la BD. Evita race conditions y es más eficiente.
from django.db.models import Value
from django.db.models.functions import Concat
# Concatenar campos con valor literal
Author.objects.annotate(
full_info=Concat("name", Value(" - "), "email")
)
Value()inserta valores literales en expresiones de base de datos.
from django.db.models import F
from datetime import timedelta
# Sumar duración a fecha
Book.objects.annotate(
sale_end=F("published") + timedelta(days=30)
)
# Operaciones matemáticas
Book.objects.annotate(
discounted_price=F("price") * 0.9
)
F()soporta operaciones aritméticas y de fecha directamente en la query.
Django incluye funciones SQL comunes para transformar datos. Se ejecutan en la base de datos, por lo que escalan mejor en grandes volúmenes. Son ideales para reportes, normalización y métricas en tiempo real.
from django.db.models.functions import Upper, Lower, Length, Concat
# Convertir a mayúsculas
Author.objects.annotate(upper_name=Upper("name"))
# Longitud del nombre
Author.objects.annotate(name_length=Length("name"))
# Concatenar campos
Author.objects.annotate(
display=Concat("name", Value(" ("), "email", Value(")"))
) Estas funciones se ejecutan en SQL, no en Python. Más eficiente para grandes datasets.
from django.db.models.functions import ExtractYear, ExtractMonth, TruncMonth
# Extraer año
Book.objects.annotate(year=ExtractYear("published"))
# Truncar a mes (para agrupar)
Book.objects.annotate(month=TruncMonth("published")).values("month").annotate(count=Count("id")) Útil para reportes y agrupaciones por período de tiempo.
from django.db.models import Case, When, Value
Book.objects.annotate(
price_category=Case(
When(price__lt=20, then=Value("Económico")),
When(price__lt=30, then=Value("Normal")),
default=Value("Premium")
)
)
Case/Whenes el equivalente a CASE WHEN de SQL. Permite lógica condicional en queries.
Las transacciones garantizan que múltiples operaciones se ejecuten como unidad atómica. Si una operación falla, toda la transacción se revierte. Evitan estados intermedios inconsistentes en escrituras relacionadas.
from django.db import transaction
with transaction.atomic():
author = Author.objects.create(name="Nuevo Autor", email="nuevo@example.com", age=30)
Book.objects.create(title="Nuevo Libro", price=25, published="2024-01-01", author=author) Si algo falla dentro del bloque, todo se revierte. Garantiza consistencia de datos.
@transaction.atomic
def create_author_with_books(author_data, books_data):
author = Author.objects.create(**author_data)
for book_data in books_data:
Book.objects.create(author=author, **book_data)
return author El decorador envuelve toda la función en una transacción.
with transaction.atomic():
author = Author.objects.create(name="Test", email="test@example.com", age=25)
sid = transaction.savepoint()
try:
# Operación que puede fallar
Book.objects.create(title="Test", price=-10, published="2024-01-01", author=author)
except Exception:
transaction.savepoint_rollback(sid)
# El author sigue guardado Los savepoints permiten revertir parcialmente sin perder toda la transacción.
Cuando el ORM no es suficiente, puedes ejecutar SQL directamente. Mantén queries parametrizadas para evitar inyección SQL. Úsalo solo cuando el ORM no cubre el caso o el rendimiento lo exige.
authors = Author.objects.raw("SELECT * FROM myapp_author WHERE age > %s", [50])
for author in authors:
print(author.name) # Es un objeto Author normal
raw()ejecuta SQL y mapea los resultados a objetos del modelo.
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT COUNT(*) FROM myapp_book WHERE price > %s", [20])
row = cursor.fetchone()
print(f"Libros caros: {row[0]}") Para queries complejas que no necesitan mapeo a modelos.
Usa SQL directo como último recurso. El ORM cubre el 95% de los casos.
Los managers encapsulan queries comunes y lógica de acceso a datos. Centralizan reglas de consulta para mantener el código consistente. Facilitan el reuso y el encadenamiento de filtros.
class BookManager(models.Manager):
def available(self):
return self.filter(is_available=True)
def by_author(self, author_name):
return self.filter(author__name=author_name)
def expensive(self, min_price=30):
return self.filter(price__gte=min_price)
class Book(models.Model):
title = models.CharField(max_length=200)
price = models.DecimalField(max_digits=10, decimal_places=2)
is_available = models.BooleanField(default=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
objects = BookManager() El manager personalizado reemplaza
objects. Tus métodos son encadenables.
# Métodos personalizados
Book.objects.available()
Book.objects.by_author("Gabriel García Márquez")
Book.objects.expensive(25)
# Encadenar métodos
Book.objects.available().expensive().order_by("-price") Los métodos retornan QuerySets, permitiendo encadenar más filtros.
class BookQuerySet(models.QuerySet):
def available(self):
return self.filter(is_available=True)
def expensive(self, min_price=30):
return self.filter(price__gte=min_price)
class Book(models.Model):
# ...
objects = BookQuerySet.as_manager()
QuerySet.as_manager()convierte un QuerySet en Manager. Más limpio para muchos métodos.
Entender el flujo completo ayuda a debuggear y diseñar mejor. Cada capa tiene una responsabilidad clara y afecta el rendimiento global. El orden del middleware puede cambiar el comportamiento de toda la app.
Request HTTP
↓
URL Dispatcher (urls.py)
↓
Middleware (before view)
↓
View (views.py)
↓
Template (opcional)
↓
Middleware (after view)
↓
Response HTTP # urls.py
urlpatterns = [
path("books/", views.book_list, name="book_list"),
path("books/<int:pk>/", views.book_detail, name="book_detail"),
] Django busca el patrón URL que coincide y llama a la view correspondiente.
# Orden de ejecución en settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # 1. Seguridad
'django.contrib.sessions.middleware.SessionMiddleware', # 2. Sesiones
'django.middleware.common.CommonMiddleware', # 3. Común
'django.middleware.csrf.CsrfViewMiddleware', # 4. CSRF
'django.contrib.auth.middleware.AuthenticationMiddleware', # 5. Auth
'django.contrib.messages.middleware.MessageMiddleware', # 6. Mensajes
] Middleware se ejecuta en orden para request, y en orden inverso para response.
from django.shortcuts import render, get_object_or_404
def book_detail(request, pk):
book = get_object_or_404(Book, pk=pk)
return render(request, "books/detail.html", {"book": book}) La view procesa la request, interactúa con modelos, y retorna una response.
Estas recomendaciones evitan bugs sutiles y problemas de rendimiento. Úsalas como checklist rápido al revisar modelos y queries.
# ✅ BIEN: related_name explícito
author = models.ForeignKey(Author, related_name="books", on_delete=models.CASCADE)
# ❌ MAL: Sin related_name
author = models.ForeignKey(Author, on_delete=models.CASCADE) Siempre usa
related_namepara código más legible y evitar conflictos.
# ✅ BIEN: Usar select_related para FK
books = Book.objects.select_related("author").all()
# ❌ MAL: Acceder a relaciones en loop sin optimizar
books = Book.objects.all()
for book in books:
print(book.author.name) # N+1 problem Siempre usa
select_relatedoprefetch_relatedcuando accedas a relaciones.
# ✅ BIEN: Un solo UPDATE
Book.objects.filter(author_id=1).update(is_available=True)
# ❌ MAL: Múltiples UPDATEs
for book in Book.objects.filter(author_id=1):
book.is_available = True
book.save() Usa
update()para modificar múltiples registros. Es mucho más eficiente.
# ✅ BIEN: Operaciones relacionadas en transacción
with transaction.atomic():
author = Author.objects.create(...)
Book.objects.create(author=author, ...)
# ❌ MAL: Operaciones separadas sin transacción
author = Author.objects.create(...)
Book.objects.create(author=author, ...) # Si falla, author queda huérfano Usa transacciones cuando múltiples operaciones deben ejecutarse juntas o ninguna.