Vistas y Plantillas en Django

Objetivo de aprendizaje: Al finalizar este módulo, sabrás crear vistas que manejen la lógica de negocio y plantillas que presenten la información al usuario de manera atractiva y funcional.

Las vistas son funciones o clases que procesan las peticiones HTTP y devuelven respuestas. Las plantillas son archivos HTML que definen cómo se presenta la información al usuario.

¿Qué son las Vistas?

Una vista en Django es una función de Python que toma una petición web y devuelve una respuesta web. Esta respuesta puede ser el contenido HTML de una página web, una redirección, un error 404, un documento XML, una imagen, o cualquier otra cosa.

Las vistas contienen la lógica necesaria para procesar una petición y devolver una respuesta.

Flujo de Petición-Respuesta

1. Petición
Usuario hace petición
2. URL
URLconf determina vista
3. Vista
Procesa lógica de negocio
4. Plantilla
Renderiza respuesta

Vistas basadas en Función

Las vistas basadas en función son la forma más simple y directa de crear vistas en Django.

Estructura básica de una vista
# views.py
from django.shortcuts import render
from django.http import HttpResponse

def mi_vista(request):
    """
    Vista básica que devuelve una respuesta simple
    """
    return HttpResponse("¡Hola, mundo!")

def vista_con_plantilla(request):
    """
    Vista que renderiza una plantilla
    """
    contexto = {
        'titulo': 'Mi Página',
        'mensaje': 'Bienvenido a Django'
    }
    return render(request, 'mi_plantilla.html', contexto)
Parámetro request: Todas las vistas deben recibir el objeto request como primer parámetro. Este objeto contiene información sobre la petición HTTP.

Ejemplo práctico: Vista de lista de productos

# views.py
from django.shortcuts import render
from .models import Producto

def lista_productos(request):
    """
    Vista que muestra todos los productos
    """
    productos = Producto.objects.all()
    contexto = {
        'productos': productos,
        'titulo': 'Lista de Productos'
    }
    return render(request, 'productos/lista.html', contexto)

def detalle_producto(request, producto_id):
    """
    Vista que muestra el detalle de un producto específico
    """
    try:
        producto = Producto.objects.get(id=producto_id)
    except Producto.DoesNotExist:
        return HttpResponse("Producto no encontrado", status=404)
    
    contexto = {
        'producto': producto
    }
    return render(request, 'productos/detalle.html', contexto)

Vistas basadas en Clase

Las vistas basadas en clase (CBV) proporcionan una forma más reutilizable y organizada de crear vistas complejas.

Ventajas
  • Reutilización de código
  • Herencia y mixins
  • Separación clara de métodos HTTP
  • Menos código repetitivo
Consideraciones
  • Curva de aprendizaje mayor
  • Menos explícitas
  • Debugging más complejo
  • Overhead de herencia

Vistas genéricas más comunes

# views.py
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.urls import reverse_lazy
from .models import Producto

class ProductoListView(ListView):
    """
    Vista de lista usando CBV
    """
    model = Producto
    template_name = 'productos/lista.html'
    context_object_name = 'productos'
    paginate_by = 10

class ProductoDetailView(DetailView):
    """
    Vista de detalle usando CBV
    """
    model = Producto
    template_name = 'productos/detalle.html'
    context_object_name = 'producto'

class ProductoCreateView(CreateView):
    """
    Vista para crear productos
    """
    model = Producto
    template_name = 'productos/crear.html'
    fields = ['nombre', 'descripcion', 'precio']
    success_url = reverse_lazy('productos:lista')

class ProductoUpdateView(UpdateView):
    """
    Vista para actualizar productos
    """
    model = Producto
    template_name = 'productos/editar.html'
    fields = ['nombre', 'descripcion', 'precio']
    success_url = reverse_lazy('productos:lista')
Tip: Usa vistas basadas en función para lógica simple y vistas basadas en clase para operaciones CRUD complejas.

Sistema de Plantillas de Django

Las plantillas son archivos que definen la estructura y el diseño de tus páginas web. Django tiene su propio sistema de plantillas potente y flexible.

Sintaxis básica de plantillas
<!-- templates/productos/lista.html -->
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>{{ titulo }}</title>
</head>
<body>
    <h1>{{ titulo }}</h1>
    
    {% if productos %}
        <div class="productos-grid">
            {% for producto in productos %}
                <div class="producto-card">
                    <h3>{{ producto.nombre }}</h3>
                    <p>{{ producto.descripcion|truncatewords:20 }}</p>
                    <p class="precio">${{ producto.precio }}</p>
                    <a href="{% url 'productos:detalle' producto.id %}">Ver detalle</a>
                </div>
            {% endfor %}
        </div>
    {% else %}
        <p>No hay productos disponibles.</p>
    {% endif %}
</body>
</html>

Elementos principales del sistema de plantillas

Variables

{{ variable }}

Muestran el valor de una variable del contexto.

Ejemplo: {{ usuario.nombre }}
Tags

{% tag %}

Proporcionan lógica en las plantillas.

Ejemplo: {% if %}, {% for %}, {% url %}
Filtros

{{ variable|filtro }}

Modifican el valor de las variables.

Ejemplo: {{ fecha|date:"d/m/Y" }}

Contexto y Variables

El contexto es un diccionario que contiene los datos que se pasan desde la vista a la plantilla.

Pasando datos a plantillas
Vista (views.py)
def mi_vista(request):
    contexto = {
        'titulo': 'Mi Página',
        'usuario': request.user,
        'productos': Producto.objects.all(),
        'fecha_actual': timezone.now(),
        'configuracion': {
            'mostrar_precios': True,
            'moneda': 'EUR'
        }
    }
    return render(request, 'mi_plantilla.html', contexto)
Plantilla (mi_plantilla.html)
<h1>{{ titulo }}</h1>
<p>Bienvenido, {{ usuario.first_name }}</p>
<p>Fecha: {{ fecha_actual|date:"d/m/Y" }}</p>

{% if configuracion.mostrar_precios %}
    {% for producto in productos %}
        <div>
            <h3>{{ producto.nombre }}</h3>
            <p>{{ producto.precio }} {{ configuracion.moneda }}</p>
        </div>
    {% endfor %}
{% endif %}

Filtros útiles

Filtro Descripción Ejemplo
date Formatea fechas {{ fecha|date:"d/m/Y" }}
truncatewords Trunca texto por palabras {{ texto|truncatewords:10 }}
length Longitud de cadena o lista {{ lista|length }}
default Valor por defecto {{ valor|default:"Sin valor" }}
safe Marca HTML como seguro {{ html_content|safe }}

Herencia de Plantillas

La herencia de plantillas permite crear una plantilla base y extenderla en otras plantillas, evitando la duplicación de código.

Plantilla base
<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Mi Sitio Web{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    {% block extra_css %}{% endblock %}
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="/">Mi Sitio</a>
            <div class="navbar-nav">
                <a class="nav-link" href="{% url 'productos:lista' %}">Productos</a>
                <a class="nav-link" href="{% url 'contacto' %}">Contacto</a>
            </div>
        </div>
    </nav>

    <main class="container mt-4">
        {% block content %}
        <!-- Contenido específico de cada página -->
        {% endblock %}
    </main>

    <footer class="bg-dark text-white text-center py-3 mt-5">
        <p>© 2024 Mi Sitio Web. Todos los derechos reservados.</p>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    {% block extra_js %}{% endblock %}
</body>
</html>
Plantilla que extiende
<!-- templates/productos/lista.html -->
{% extends 'base.html' %}

{% block title %}Lista de Productos - {{ block.super }}{% endblock %}

{% block extra_css %}
<style>
    .producto-card {
        border: 1px solid #ddd;
        padding: 1rem;
        margin-bottom: 1rem;
        border-radius: 0.5rem;
    }
</style>
{% endblock %}

{% block content %}
<h1>{{ titulo }}</h1>

{% if productos %}
    <div class="row">
        {% for producto in productos %}
            <div class="col-md-4 mb-3">
                <div class="producto-card">
                    <h5>{{ producto.nombre }}</h5>
                    <p>{{ producto.descripcion|truncatewords:15 }}</p>
                    <p class="text-success fw-bold">${{ producto.precio }}</p>
                    <a href="{% url 'productos:detalle' producto.id %}" class="btn btn-primary">Ver detalle</a>
                </div>
            </div>
        {% endfor %}
    </div>
{% else %}
    <div class="alert alert-info">
        <p>No hay productos disponibles en este momento.</p>
    </div>
{% endif %}
{% endblock %}

{% block extra_js %}
<script>
    // JavaScript específico para la lista de productos
    console.log('Lista de productos cargada');
</script>
{% endblock %}
{{ block.super }}: Incluye el contenido del bloque padre antes del contenido actual.

Patrones de URL

Los patrones de URL conectan las URLs con las vistas correspondientes.

Configuración de URLs
URLs principales (myproject/urls.py)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('productos.urls')),
    path('productos/', include('productos.urls')),
]
URLs de la app (productos/urls.py)
from django.urls import path
from . import views

app_name = 'productos'

urlpatterns = [
    path('', views.lista_productos, name='lista'),
    path('<int:producto_id>/', views.detalle_producto, name='detalle'),
    path('crear/', views.ProductoCreateView.as_view(), name='crear'),
    path('<int:pk>/editar/', views.ProductoUpdateView.as_view(), name='editar'),
    path('<int:pk>/eliminar/', views.ProductoDeleteView.as_view(), name='eliminar'),
]

Parámetros en URLs

Patrón Descripción Ejemplo
<int:id> Captura un entero productos/123/
<str:slug> Captura una cadena productos/mi-producto/
<slug:slug> Slug válido productos/mi-producto-123/
<uuid:id> UUID válido productos/550e8400-e29b-41d4-a716-446655440000/

Tag {% url %} en plantillas

<!-- Enlace simple -->
<a href="{% url 'productos:lista' %}">Ver productos</a>

<!-- Enlace con parámetros -->
<a href="{% url 'productos:detalle' producto.id %}">Ver detalle</a>

<!-- Enlace con múltiples parámetros -->
<a href="{% url 'productos:editar' categoria=producto.categoria.slug id=producto.id %}">Editar</a>

<!-- Formulario con acción dinámica -->
<form method="post" action="{% url 'productos:crear' %}">
    {% csrf_token %}
    <!-- campos del formulario -->
</form>

Ejercicio Práctico

Reto: Crear un sistema completo de gestión de una biblioteca con vistas y plantillas.
Especificaciones del ejercicio
1. Crear el modelo Libro (si no lo has hecho)
# models.py
class Libro(models.Model):
    titulo = models.CharField(max_length=200)
    autor = models.CharField(max_length=100)
    isbn = models.CharField(max_length=13, unique=True)
    fecha_publicacion = models.DateField()
    disponible = models.BooleanField(default=True)
    descripcion = models.TextField()
    
    def __str__(self):
        return self.titulo
2. Crear las vistas
  • Vista de lista de libros
  • Vista de detalle de libro
  • Vista para crear libro
  • Vista para editar libro
  • Vista de búsqueda
  • Vista de libros disponibles
  • Vista de estadísticas
3. Crear las plantillas
  • Plantilla base con navegación
  • Lista de libros con paginación
  • Detalle de libro con información completa
  • Formulario para crear/editar libros
4. Configurar URLs
  • URLs con nombres apropiados
  • Agrupación por namespace
  • Parámetros para vistas de detalle
Solución paso a paso

# biblioteca/views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.urls import reverse_lazy
from django.db.models import Q
from .models import Libro

class LibroListView(ListView):
    model = Libro
    template_name = 'biblioteca/lista.html'
    context_object_name = 'libros'
    paginate_by = 12

def buscar_libros(request):
    query = request.GET.get('q')
    libros = []
    if query:
        libros = Libro.objects.filter(
            Q(titulo__icontains=query) | 
            Q(autor__icontains=query)
        )
    
    contexto = {
        'libros': libros,
        'query': query
    }
    return render(request, 'biblioteca/buscar.html', contexto)

class LibroDetailView(DetailView):
    model = Libro
    template_name = 'biblioteca/detalle.html'
    context_object_name = 'libro'

class LibroCreateView(CreateView):
    model = Libro
    template_name = 'biblioteca/crear.html'
    fields = ['titulo', 'autor', 'isbn', 'fecha_publicacion', 'descripcion']
    success_url = reverse_lazy('biblioteca:lista')

# biblioteca/urls.py
from django.urls import path
from . import views

app_name = 'biblioteca'

urlpatterns = [
    path('', views.LibroListView.as_view(), name='lista'),
    path('libro/<int:pk>/', views.LibroDetailView.as_view(), name='detalle'),
    path('crear/', views.LibroCreateView.as_view(), name='crear'),
    path('buscar/', views.buscar_libros, name='buscar'),
]

<!-- templates/biblioteca/base.html -->
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}Biblioteca{% endblock %}</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-primary">
        <div class="container">
            <a class="navbar-brand" href="{% url 'biblioteca:lista' %}">📚 Biblioteca</a>
            <div class="navbar-nav">
                <a class="nav-link" href="{% url 'biblioteca:lista' %}">Libros</a>
                <a class="nav-link" href="{% url 'biblioteca:crear' %}">Agregar</a>
                <a class="nav-link" href="{% url 'biblioteca:buscar' %}">Buscar</a>
            </div>
        </div>
    </nav>

    <main class="container mt-4">
        {% block content %}{% endblock %}
    </main>
</body>
</html>

Quiz de Conocimientos

Pregunta 1 de 5

¿Cuál es la diferencia principal entre una vista basada en función y una vista basada en clase?

Pregunta 2 de 5

¿Qué tag de plantilla se usa para incluir el contenido de un bloque padre?

Pregunta 3 de 5

¿Cuál es la sintaxis correcta para crear un enlace con parámetros en una plantilla?

Pregunta 4 de 5

¿Qué método de vista basada en clase se ejecuta automáticamente para peticiones GET?

Pregunta 5 de 5

¿Cuál es la función del contexto en Django?

¡Quiz Completado!

Tu puntuación: 0/5

¡Excelente trabajo!

¡Felicitaciones!

Has completado exitosamente el

Módulo 3: Vistas y Plantillas

Ya puedes continuar con el siguiente módulo

Continuar al Módulo 4