Formularios y función de favoritos

En este capítulo aprenderás a manejar solicitudes POST del usuario, establecer relaciones muchos a muchos e implementar la función de «guardar artículos como favoritos».


Diseño del modelo de datos para la función de favoritos

El núcleo de la función de favoritos es:Un usuario puede guardar varios artículos en favoritos, y un artículo puede ser guardado en favoritos por varios usuarios.。

Esta es una típica relación muchos a muchos (ManyToMany).

No necesitamos crear un nuevo modelo: el modelo User de Django está integrado; solo necesitamos agregar un ManyToManyField en el modelo Post que apunte a User.

Ejemplo

# Ruta del archivo: agregar en la clase Post de blog/models.py
from django.contrib.auth.models import User

class Post(models.Model):
    # ... campos existentes ...
    # Favoritos de usuarios: ManyToManyField crea automáticamente la tabla intermedia
    favorites = models.ManyToManyField(
        User,
        related_name='favorite_posts',  # Consulta inversa: user.favorite_posts.all()
        verbose_name='Favoritos de usuarios',
        blank=True
    )

Después de modificar el modelo, genera y ejecuta las migraciones:

(venv) $ python manage.py makemigrations
(venv) $ python manage.py migrate

Lógica de vistas para agregar y quitar favoritos

Ejemplo

# Ruta del archivo: agregar en blog/views.py
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Post

# Decorador login_required: cuando un usuario no autenticado accede a esta vista, se redirige automáticamente a la página de inicio de sesión
@login_required
def toggle_favorite(request, pk):
    """Alternar el estado de favorito: si está en favoritos → quitar, si no está → añadir"""
    post = get_object_or_404(Post, pk=pk)

    # Verificar si el usuario actual ya ha guardado el artículo en favoritos
    if post.favorites.filter(id=request.user.id).exists():
        # Ya guardado → quitar favorito
        post.favorites.remove(request.user)
        messages.info(request, f'Se ha quitado de favoritos: «{post.title}»')
    else:
        # No guardado → añadir a favoritos
        post.favorites.add(request.user)
        messages.success(request, f'Se ha guardado en favoritos: «{post.title}»')

    # Redirigir de vuelta a la página actual (HTTP_REFERER es la URL de la página de origen)
    referer = request.META.get('HTTP_REFERER', '/')
    return redirect(referer)

Referencia rápida de métodos de operación ManyToMany

MétodoFunciónEjemplo
add(obj)Agregar relaciónpost.favorites.add(user)
remove(obj)Eliminar relaciónpost.favorites.remove(user)
all()Consultar todos los objetos relacionadospost.favorites.all()
filter(...)Filtrar con condicionespost.favorites.filter(id=1)
exists()Comprobar si existe algún registro coincidentepost.favorites.filter(id=1).exists()
clear()Eliminar todas las relacionespost.favorites.clear()

Configurar la ruta de favoritos

Ejemplo

# Ruta del archivo: agregar en blog/urls.py
urlpatterns = [
    # ... rutas existentes ...
    path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]

Agregar botón de favoritos en la página de detalles

Ejemplo

<!-- Ruta del archivo: blog/templates/blog/post_detail.html (modificado) -->
{% extends 'blog/base.html' %}

{% block title %}{{ post.title }} - Blog EJEMPLO{% endblock %}

{% block content %}
<article class="post-view">
    <span class="category-tag">{{ post.category.name }}</span>
    <div class="post-header">
        <h1>{{ post.title }}</h1>
        <!-- Botón de favoritos: visible solo para usuarios autenticados -->
        {% if user.is_authenticated %}
        <form method="post" action="{% url 'toggle_favorite' post.pk %}">
            {% csrf_token %}
            <button type="submit" class="fav-btn">
                {% if user in post.favorites.all %}
                    &#x2665;Guardado en favoritos
                {% else %}
♡ Favorito
                {% endif %}
            </button>
        </form>
        {% endif %}
    </div>
    <time>{{ post.created_at|date:"Y-m-d" }}</time>
    <div class="content">{{ post.content|safe }}</div>
    <a href="{% url 'index' %}" class="back-link">← Volver al inicio</a>
</article>
{% endblock %}

Agrega también el botón de favoritos en las tarjetas de artículos de la página de inicio:

Ejemplo

<!-- Agregar botón de favoritos en la tarjeta de artículo de index.html (área del footer) -->
<div class="article-card">
    <div class="card-content">
        <span class="card-category">{{ post.category.name }}</span>
        <h3>{{ post.title }}</h3>
        <p>{{ post.summary|truncatechars:80 }}</p>
        <div class="card-footer">
            <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
            {% if user.is_authenticated %}
            <form method="post" action="{% url 'toggle_favorite' post.pk %}" class="fav-form">
                {% csrf_token %}
                <button type="submit" class="fav-btn-small">
                    {% if user in post.favorites.all %}&#x2665;{% else %}♡{% endif %}
                </button>
            </form>
            {% endif %}
        </div>
    </div>
</div>

¿Por qué el botón de favoritos usa un form en lugar de una etiqueta a? Porque la operación de guardar en favoritos modifica la base de datos y corresponde a una solicitud POST. Según la especificación REST, las operaciones de lectura usan GET y las de escritura usan POST. Más importante aún, la solicitud POST incluyecsrf_token, lo que evita que sitios maliciosos falsifiquen solicitudes.


Página de lista de favoritos

Muestra la lista de artículos guardados como favoritos para el usuario autenticado.

Ejemplo

# Ruta del archivo: agregar en blog/views.py
@login_required
def favorites(request):
    """Lista de favoritos del usuario actual"""
    # Consulta inversa de los artículos favoritos del usuario mediante related_name
    posts = request.user.favorite_posts.all().order_by('-created_at')
    return render(request, 'blog/favorites.html', {
        'posts': posts,
        'title': 'Mis favoritos - Blog EJEMPLO'
    })

Agrega la ruta en urls.py y añade el enlace «Mis favoritos» en la barra de navegación.


Estilos del botón de favoritos

Ejemplo

/* Agregar en el estilo de base.html */
.post-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.fav-btn, .fav-btn-small {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    color: #e74c3c;
    padding: 4px 8px;
    transition: transform 0.2s;
}

.fav-btn:hover, .fav-btn-small:hover {
    transform: scale(1.2);
}

.fav-btn-small {
    font-size: 16px;
}

.fav-form {
    display: inline;
}

.card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 10px;
}

Resumen del capítulo

En este capítulo dominaste el manejo de interacciones de usuario en Django: ManyToManyField para establecer relaciones muchos a muchos, solicitudes POST para procesar formularios, csrf_token como protección de seguridad, el decorador login_required para proteger vistas, y request.user para identificar al usuario actual.

La función de favoritos permite a los usuarios marcar artículos que les gustan y verlos en su lista personal de favoritos.

Otras extensiones