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
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
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étodo | Función | Ejemplo |
|---|---|---|
| add(obj) | Agregar relación | post.favorites.add(user) |
| remove(obj) | Eliminar relación | post.favorites.remove(user) |
| all() | Consultar todos los objetos relacionados | post.favorites.all() |
| filter(...) | Filtrar con condiciones | post.favorites.filter(id=1) |
| exists() | Comprobar si existe algún registro coincidente | post.favorites.filter(id=1).exists() |
| clear() | Eliminar todas las relaciones | post.favorites.clear() |
Configurar la ruta de favoritos
Ejemplo
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
{% 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 %}
♥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
<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 %}♥{% 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 incluye
csrf_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
@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
.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