
Vistas y Templates en Django
El template, por su parte, es puramente la presentación visual de esos datos.
raulanto
Desarrollador Full Stack
08 Oct 2025
7 min read
La Arquitectura Fundamental: MTV
Django utiliza un patrón MTV (Model-Template-View) que es su interpretación del clásico MVC. Piensa en la vista como el cerebro que procesa la lógica: recibe una petición HTTP, consulta la base de datos si es necesario, procesa datos, y decide qué template usar para mostrar la respuesta. El template, por su parte, es puramente la presentación visual de esos datos.
Vistas: El Corazón del Procesamiento
Vistas Basadas en Funciones (FBV)
Las vistas basadas en funciones son la forma más directa y explícita de manejar peticiones. Son excelentes cuando necesitas lógica personalizada o cuando tu vista hace algo muy específico que no encaja en patrones comunes. Aquí tienes un ejemplo que muestra diferentes técnicas:
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_http_methods
from .models import Articulo, Comentario
@login_required # Decorador que requiere autenticación
@require_http_methods(["GET", "POST"]) # Limita los métodos HTTP permitidos
def detalle_articulo(request, articulo_id):
"""
Esta vista maneja tanto la visualización como la creación de comentarios.
Nota cómo una sola vista puede manejar múltiples acciones dependiendo del método HTTP.
"""
# get_object_or_404 es más elegante que try/except para objetos únicos
articulo = get_object_or_404(Articulo, pk=articulo_id)
if request.method == 'POST':
# Procesamos el formulario de comentario
contenido = request.POST.get('contenido')
if contenido:
Comentario.objects.create(
articulo=articulo,
autor=request.user,
contenido=contenido
)
# Usamos redirect para evitar reenvío de formulario al recargar
return redirect('detalle_articulo', articulo_id=articulo.id)
# Preparamos el contexto - esto es crucial para entender
contexto = {
'articulo': articulo,
'comentarios': articulo.comentarios.select_related('autor').all(),
'usuario': request.user,
'total_comentarios': articulo.comentarios.count(),
}
# render() es un atajo que combina cargar template y crear HttpResponse
return render(request, 'blog/detalle_articulo.html', contexto)
Vistas Basadas en Clases (CBV)
Las vistas basadas en clases son increíblemente poderosas cuando trabajas con patrones comunes. Django incluye vistas genéricas que reducen enormemente el código repetitivo. La clave está en entender qué método sobrescribir en cada caso:
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
class ListaArticulosView(ListView):
"""
ListView maneja automáticamente la paginación y el contexto.
Solo necesitas configurar los atributos de clase.
"""
model = Articulo
template_name = 'blog/lista_articulos.html'
context_object_name = 'articulos' # Por defecto sería 'object_list'
paginate_by = 10 # Paginación automática
def get_queryset(self):
"""
Este método te permite personalizar la consulta a la base de datos.
Es aquí donde aplicarías filtros, ordenamiento, o select_related.
"""
queryset = super().get_queryset()
# Optimizamos la consulta para evitar el problema N+1
queryset = queryset.select_related('autor').prefetch_related('categorias')
# Filtramos por búsqueda si existe el parámetro
busqueda = self.request.GET.get('q')
if busqueda:
queryset = queryset.filter(titulo__icontains=busqueda)
return queryset.filter(publicado=True).order_by('-fecha_creacion')
def get_context_data(self, **kwargs):
"""
Este método te permite agregar datos adicionales al contexto.
Se llama después de get_queryset y antes de renderizar el template.
"""
contexto = super().get_context_data(**kwargs)
contexto['categorias_populares'] = Categoria.objects.annotate(
num_articulos=Count('articulos')
).order_by('-num_articulos')[:5]
contexto['termino_busqueda'] = self.request.GET.get('q', '')
return contexto
Para operaciones de creación y actualización, Django proporciona vistas que manejan formularios automáticamente:
class CrearArticuloView(LoginRequiredMixin, CreateView):
"""
CreateView maneja tanto GET (mostrar formulario) como POST (procesar).
LoginRequiredMixin redirige a login si el usuario no está autenticado.
"""
model = Articulo
fields = ['titulo', 'contenido', 'categorias', 'imagen_destacada']
template_name = 'blog/formulario_articulo.html'
success_url = reverse_lazy('lista_articulos')
def form_valid(self, form):
"""
Se ejecuta cuando el formulario es válido, antes de guardar.
Perfecto para modificar el objeto antes de guardarlo.
"""
form.instance.autor = self.request.user
form.instance.slug = slugify(form.instance.titulo)
return super().form_valid(form)
def get_context_data(self, **kwargs):
contexto = super().get_context_data(**kwargs)
contexto['accion'] = 'Crear'
return contexto
Puedes leer mas sobre vistas basadas en clases en mi post Vistas Basadas en Clases (CBV).
Templates: La Presentación Inteligente
Los templates de Django no son simples archivos HTML, son un lenguaje de plantillas completo con lógica de presentación.
Herencia de Templates: La Base de Todo
La herencia es probablemente la característica más poderosa del sistema de templates. Te permite crear una estructura base y extenderla en páginas específicas:
{# templates/base.html - Tu plantilla maestra #}
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block titulo %}Mi Sitio{% endblock %}</title>
{# Block para CSS adicional en páginas hijas #}
{% block css_extra %}{% endblock %}
</head>
<body>
<header>
{% include 'partials/navbar.html' %}
</header>
<main>
{# Aquí es donde cada página inyectará su contenido #}
{% block contenido %}{% endblock %}
</main>
<footer>
{% include 'partials/footer.html' %}
</footer>
{# JavaScript al final para mejor rendimiento #}
<script src="{% static 'js/main.js' %}"></script>
{% block javascript_extra %}{% endblock %}
</body>
</html>
Ahora, cada página específica extiende esta base:
{# templates/blog/detalle_articulo.html #}
{% extends 'base.html' %}
{% load static %}
{% block titulo %}{{ articulo.titulo }} - {{ block.super }}{% endblock %}
{% block css_extra %}
<link rel="stylesheet" href="{% static 'css/articulo.css' %}">
{% endblock %}
{% block contenido %}
<article class="articulo-detalle">
<h1>{{ articulo.titulo }}</h1>
{# Uso de filtros para formatear datos #}
<p class="metadata">
Por {{ articulo.autor.get_full_name|default:articulo.autor.username }}
el {{ articulo.fecha_creacion|date:"d/m/Y" }}
</p>
{# El filtro safe permite HTML, pero úsalo con cuidado #}
<div class="contenido">
{{ articulo.contenido|safe }}
</div>
{# Condicionales en templates #}
{% if user.is_authenticated %}
<section class="comentarios">
<h2>Comentarios ({{ total_comentarios }})</h2>
{# Iteración con for #}
{% for comentario en comentarios %}
<div class="comentario">
<strong>{{ comentario.autor.username }}</strong>
<p>{{ comentario.contenido }}</p>
<small>{{ comentario.fecha|timesince }} atrás</small>
</div>
{% empty %}
<p>Sé el primero en comentar</p>
{% endfor %}
{# Formulario para nuevo comentario #}
<form method="post">
{% csrf_token %}
<textarea name="contenido" required></textarea>
<button type="submit">Comentar</button>
</form>
</section>
{% else %}
<p><a href="{% url 'login' %}">Inicia sesión</a> para comentar</p>
{% endif %}
</article>
{% endblock %}
Técnicas Avanzadas de Templates
Template Tags Personalizados: Cuando necesitas lógica compleja que se repite en varios templates, crear tus propios tags es la solución profesional. Crea un archivo templatetags/custom_tags.py:
from django import template
from django.utils.html import format_html
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def icono_categoria(categoria):
"""
Tag personalizado que devuelve un ícono según la categoría.
Uso en template: {% icono_categoria articulo.categoria %}
"""
iconos = {
'tecnologia': '💻',
'ciencia': '🔬',
'arte': '🎨',
'musica': '🎵',
}
icono = iconos.get(categoria.slug, '📄')
return format_html(
'<span class="icono-categoria" title="{}">{}</span>',
categoria.nombre,
icono
)
@register.filter
def resaltar_busqueda(texto, termino):
"""
Filtro que resalta términos de búsqueda en el texto.
Uso: {{ articulo.contenido|resaltar_busqueda:termino_busqueda }}
"""
if not termino:
return texto
texto_resaltado = texto.replace(
termino,
f'<mark>{termino}</mark>'
)
return mark_safe(texto_resaltado)
@register.inclusion_tag('partials/card_articulo.html')
def card_articulo(articulo, mostrar_autor=True):
"""
Inclusion tag que renderiza un componente completo.
Útil para componentes reutilizables complejos.
"""
return {
'articulo': articulo,
'mostrar_autor': mostrar_autor,
}
Context Processors: Cuando necesitas que ciertos datos estén disponibles en todos los templates sin pasarlos manualmente en cada vista, los context processors son la respuesta:
# En tu archivo context_processors.py
def datos_globales(request):
"""
Este context processor añade datos a TODOS los templates automáticamente.
Regístralos en settings.py en TEMPLATES['OPTIONS']['context_processors']
"""
from .models import Categoria
return {
'categorias_menu': Categoria.objects.filter(mostrar_en_menu=True),
'sitio_nombre': 'Mi Blog Increíble',
'ano_actual': timezone.now().year,
}
Renderizado Condicional y Lazy Loading: Para sitios con mucho contenido, puedes implementar técnicas avanzadas de renderizado:
from django.views.generic import View
from django.http import JsonResponse
from django.template.loader import render_to_string
class CargarMasArticulosView(View):
"""
Vista para carga infinita mediante AJAX.
Retorna HTML parcial en lugar de una página completa.
"""
def get(self, request):
pagina = int(request.GET.get('pagina', 1))
articulos = Articulo.objects.filter(publicado=True)[
(pagina-1)*10:pagina*10
]
# render_to_string renderiza el template a string en lugar de HttpResponse
html = render_to_string(
'blog/partials/lista_articulos.html',
{'articulos': articulos},
request=request
)
return JsonResponse({
'html': html,
'tiene_mas': articulos.count() == 10
})
Caché y Optimización de Renderizado
Django permite cachear fragmentos de templates para mejorar el rendimiento dramáticamente:
{% load cache %}
{# Cachea este bloque por 15 minutos #}
{% cache 900 sidebar_lateral request.user.id %}
<aside class="sidebar">
{% for categoria in categorias_populares %}
<div class="categoria-popular">
{{ categoria.nombre }} ({{ categoria.num_articulos }})
</div>
{% endfor %}
</aside>
{% endcache %}
Respuestas Más Allá del HTML
Django no se limita a renderizar HTML. Puedes generar cualquier tipo de respuesta:
from django.http import FileResponse, StreamingHttpResponse
from django.template.loader import get_template
from xhtml2pdf import pisa
import csv
def exportar_articulos_pdf(request):
"""
Genera un PDF dinámicamente usando un template.
"""
template = get_template('reportes/articulos_pdf.html')
articulos = Articulo.objects.all()
html = template.render({'articulos': articulos})
response = HttpResponse(content_type='application/pdf')
response['Content-Disposition'] = 'attachment; filename="articulos.pdf"'
pisa.CreatePDF(html, dest=response)
return response
def exportar_articulos_csv(request):
"""
Genera un CSV para descarga.
"""
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="articulos.csv"'
writer = csv.writer(response)
writer.writerow(['Título', 'Autor', 'Fecha', 'Vistas'])
for articulo in Articulo.objects.all():
writer.writerow([
articulo.titulo,
articulo.autor.username,
articulo.fecha_creacion,
articulo.vistas
])
return response
On this page