Acciones personalizadas en ViewSets REST

Resumen

Las APIs REST no se limitan a exponer recursos: también permiten ejecutar acciones específicas sobre ellos, como marcar a un doctor en vacaciones o generar un reporte. Con Django REST Framework puedes crear estas acciones personalizadas dentro de un ViewSet usando el decorador @action, manteniendo tu código limpio y reutilizable.

Esta guía te muestra cómo añadir endpoints personalizados a un recurso, respetar el principio de idempotencia y ajustar las URLs para que sean legibles y amigables con SEO.

¿Qué son las acciones en un ViewSet de Django REST Framework?

Una acción es un endpoint adicional dentro de un ViewSet que ejecuta lógica de negocio específica sobre un recurso, más allá del clásico CRUD. Piensa en cosas como pagar con una tarjeta, generar una historia clínica o, en el ejemplo que vamos a construir, marcar a un doctor como si estuviera de vacaciones.

¿Qué hace el decorador @action en Django REST Framework? Convierte un método de tu ViewSet en un endpoint extra. Tú defines qué métodos HTTP acepta y si aplica al detalle (un solo objeto) o a la lista completa.

¿Cómo preparar el modelo antes de crear la acción? [01:05]

Antes de tocar el ViewSet, necesitas el campo en el modelo. En Doctor agregamos un booleano:

python is_on_vacation = models.BooleanField(default=False)

Luego corres las migraciones desde la consola:

  • python manage.py makemigrations.
  • python manage.py migrate.

Con esto el campo queda disponible y por defecto todos los doctores arrancan fuera de vacaciones.

¿Cómo se usa el decorador @action para crear endpoints personalizados?

El decorador @action se importa desde rest_framework.decorators y se aplica sobre un método nuevo dentro del ViewSet. Recibe parámetros que definen el comportamiento del endpoint.

python from rest_framework.decorators import action from rest_framework.response import Response

@action(detail=True, methods=['post'], url_path='set-on-vacation') def set_on_vacation(self, request, pk=None): doctor = self.get_object() doctor.is_on_vacation = True doctor.save() return Response({'status': 'El doctor está en vacaciones'})

Dos parámetros son clave:

  • detail=True indica que la acción opera sobre un solo objeto y requiere un ID en la URL.
  • methods=['post'] restringe el endpoint a peticiones POST únicamente.

¿Por qué usar self.get_object en lugar de filtrar manualmente? [03:20]

Dentro del método podrías escribir tu propio filtro con self.queryset.get(pk=pk), pero Django REST Framework ya trae self.get_object(). Este método retorna el objeto de la vista actual y, además, aplica los filtros y validaciones que ya están configurados en el ViewSet.

Reutilizar get_object evita romper funcionalidades que dependen de esos filtros y mantiene tu código alineado con el comportamiento del framework. Es uno de esos detalles que diferencian una API que escala de una que se vuelve frágil con cada feature nuevo.

¿Por qué los endpoints REST deben ser idempotentes?

La idempotencia significa que ejecutar la misma petición varias veces produce el mismo resultado. Si un cliente reintenta un request por una falla de red, el estado final no debe cambiar entre intentos.

En la primera versión del código se planteó un toggle_vacation que alternaba entre true y false en cada POST. El problema: si el request falla y se reintenta, nadie sabe en qué estado quedó el recurso. Eso confunde a los desarrolladores que consumen la API.

¿Qué significa que un endpoint sea idempotente? Que puedes ejecutarlo una o cien veces y el resultado final será el mismo. Por eso conviene separar acciones explícitas como set-on-vacation y set-off-vacation en lugar de un toggle.

La solución fue dividir la lógica en dos acciones claras:

  • set_on_vacation siempre deja is_on_vacation = True.
  • set_off_vacation siempre deja is_on_vacation = False.

Así cada endpoint tiene un único resultado predecible. Si el frontend necesita un botón tipo toggle, puede decidir cuál de los dos endpoints llamar según el estado actual.

¿Cómo personalizar la URL de una acción con url_path?

Por defecto, Django REST Framework genera la URL de la acción tomando el nombre del método. Si tu método se llama set_on_vacation, la URL queda como /set_on_vacation/ con guiones bajos.

Esto no es ideal: las URLs con guiones medios son más legibles y amigables para SEO. Para ajustarlo, agrega el parámetro url_path al decorador:

python @action(detail=True, methods=['post'], url_path='set-on-vacation') def set_on_vacation(self, request, pk=None): ...

Con ese cambio la URL pasa a ser /doctors/{id}/set-on-vacation/, mucho más limpia y consistente con las convenciones web.

¿Cómo probar la acción desde el navegador? [09:40]

Django REST Framework genera automáticamente un botón Extra Actions en la interfaz navegable cuando entras al detalle de un recurso. Al abrir el doctor Fernando Martínez aparecen las opciones Set on vacation y Set off vacation.

Si intentas un GET sobre la URL recibirás un error de método no permitido, porque la acción solo acepta POST. Al hacer el POST correcto, la respuesta devuelve el mensaje configurado y el campo is_on_vacation se actualiza en la instancia.

Un detalle a cuidar: cuando duplicas una acción para crear su versión opuesta, revisa el mensaje de respuesta. En el ejemplo, copiar set_on_vacation y olvidar cambiar el texto hizo que el endpoint de off siguiera respondiendo "El doctor está en vacaciones". Pequeño descuido, gran confusión para quien consume la API.

Reto: crea una acción para reportar la historia clínica

Ahora te toca aplicar lo aprendido. Dentro del recurso Patient, crea una nueva acción que devuelva el reporte de la historia clínica de un paciente en formato JSON. Define bien si debe ser detail=True, qué método HTTP acepta y cómo nombrar el url_path.

¿Qué otras acciones se te ocurren para una API médica? Cuéntame en los comentarios cómo resolviste el reto.