Potencia tus endpoints de Django REST con serializadores anidados: agrega información relacionada en un solo response sin duplicar lógica ni romper contratos. Aquí verás cómo añadir las citas médicas de un paciente dentro del recurso Patient usando un serializer anidado, con parámetros clave como many y read_only para mantener un diseño limpio, seguro y flexible.
¿Qué son los serializadores anidados y por qué mejoran el response?
Los serializadores anidados permiten incluir un campo cuyo valor proviene de otro serializer. Así, un recurso como Patient puede exponer una lista de citas usando el serializer de Appointment. Esto hace más claro el consumo de la API y estandariza la salida.
Centralizan la lógica de representación en un lugar.
Escalan cambios: modificas un serializer y se reflejan en todos los response.
Representan relaciones entre modelos de forma eficiente.
En este caso, se agrega el campo appointments al serializer de Patient para listar todas las citas del paciente mediante un serializer existente de Appointments.
¿Cómo implementar el campo appointments en Patient serializer?
Primero, ubica el serializer de paciente y garantiza que la Meta tenga los fields que deseas exponer. Luego, importa el serializer de citas y define el campo anidado con los parámetros correctos.
Importa el serializer de Appointment desde la app de reservas.
Declara el campo anidado en Patient con many=True y read_only=True.
Incluye "appointments" en la lista de fields para que aparezca en el response.
No olvides el id: aunque el modelo lo crea automáticamente, debes listarlo en fields si quieres verlo en la salida.
Ejemplo de estructura mínima:
# patients/serializers.pyfrom rest_framework import serializers
from patients.models import Patient
from bookings.serializers import AppointmentSerializer
classPatientSerializer(serializers.ModelSerializer): appointments = AppointmentSerializer(many=True, read_only=True)classMeta: model = Patient
fields =['id',# importante incluirlo para verlo en la salida# ... otros campos del modelo Patient'appointments',# el nuevo campo anidado]
Claves del diseño:
many=True: indica que es una lista de objetos relacionados.
read_only=True: evita que alguien intente enviar citas por este endpoint; la creación/edición de citas ocurre en su propio endpoint.
¿Cómo validar en la API y cargar datos de prueba con shell?
Tras definir el campo, verifica que la API muestre el nuevo JSON enriquecido y, si está vacío, crea una cita de ejemplo desde shell.
¿Cómo ver el nuevo campo en la lista y el detalle?
Ejecuta el servidor: python manage.py runserver.
Abre el listado: /api/patients/.
Confirma que aparezca appointments como una lista.
Si no ves el id, agrégalo en fields y recarga.
Abre el detalle del paciente (por ejemplo, con id 3) y verifica que appointments sea una lista, inicialmente vacía.
¿Cómo crear una appointment de ejemplo en shell?
Desde shell, crea datos mínimos para observar la estructura anidada en el response.
# Abrir shellauthenticate =False# si aplica en tu entorno, caso de ejemplofrom datetime import date, time
from doctors.models import Doctor
from patients.models import Patient
from bookings.models import Appointment
patient = Patient.objects.get(id=3)doctor = Doctor.objects.first()Appointment.objects.create( patient=patient, doctor=doctor, date=date(2024,5,20),# ejemplo de fecha time=time(9,30),# ejemplo de hora notes='Control general.',# opcional status='scheduled'# opcional, según tu modelo)
Al recargar el detalle del paciente, verás appointments como una lista de objetos, cada uno serializado por el serializer de Appointment. Esto ofrece más flexibilidad al cliente sin cambiar de endpoint ni mezclar responsabilidades.
¿Te gustaría ver un ejemplo análogo para Doctor con su lista de citas? Comenta cómo estructurarías ese serializer y qué campos incluirías en el response.
from rest_framework importserializersfrom.modelsimportDoctor,Department,DoctorAvailability,MedicalNotefrom bookings.serializersimportAppointmentSerializerclassDoctorSerializer(serializers.ModelSerializer): department =DepartmentSerializer(read_only=True) # Anidamos el serializador de Department availabilities =DoctorAvailabilitySerializer(many=True, read_only=True) # Disponibilidad del doctor(anidado) notes =MedicalNoteSerializer(many=True, read_only=True) # Notas médicas del doctor(anidado) appointments =AppointmentSerializer(many=True, read_only=True) # Citas del doctor(relación con citas)classMeta: model =Doctor fields =['id','first_name','last_name','qualification','contact_number','email','address','biography','department','availabilities','notes','appointments']
gracias a este comentario puede salir de un problema que se me presento, había hecho todo bien pero el error que cometí es que el campo lo habia llamado 'appointment' y no 'appointments'
y sin eso el campo no se visualiza en el api que estamos construyendo
cool!
Algo muy importante a destacar es que el nombre del parámetro appoimnets dentro del PatientSerializer no es arbitrario, pues se relaciona con el related_name definido en el modelo de Appoimnet.
Eso fue algo confuso cuando traté de replicar estos conceptos en otro proyecto, así que quería compartirlo por si alguien mas tenía esa misma duda.
Un serializador en Django REST Framework es una herramienta que convierte instancias de modelos en formatos JSON o XML para que puedan ser fácilmente transmitidos a través de una API. Los serializadores permiten definir qué campos se incluirán en la respuesta y cómo se representarán, facilitando la interacción con los datos de la aplicación.
Importante: no me di cuenta y en related_name en el modelo de booking no era igual a la variable que usamos en el serializer, Deben ser la misma, por buenas practicas deberia ser todo en plural y en el modelo lo puse en singular
asi me responde mi API con estos Nested Serializers
HTTP200OKAllow:GET,PUT,PATCH,DELETE,HEAD,OPTIONSContent-Type: application/json
Vary:Accept{"id":1,"first_name":"Juan","last_name":"García","full_name":"Dr. Juan García","qualification":"MD, Especialista en Cardiología","contact_number":"123-456-789","email":"juan.garcia@hospital.com","address":"Calle Principal 123","biography":"Doctor con 15 años de experiencia en cardiología","is_on_vacation":false,"department":1,"department_details":{"id":1,"name":"Cardiología","description":"Departamento especializado en enfermedades cardiovasculares"},"availabilities":[{"id":1,"doctor":1,"doctor_name":"Dr. Juan García","start_date":"2024-04-01","end_date":"2024-04-30","start_time":"09:00:00","end_time":"17:00:00"}],"appointments":[{"id":2,"appointment_date":"2026-05-15","appointment_time":"10:30:00","status":"scheduled"},{"id":1,"appointment_date":"2026-03-25","appointment_time":"10:00:00","status":"completed"}]}
HTTP200OKAllow:GET,PUT,PATCH,DELETE,HEAD,OPTIONSContent-Type: application/json
Vary:Accept{"id":1,"first_name":"Juan","last_name":"García","full_name":"Dr. Juan García","qualification":"MD, Especialista en Cardiología","contact_number":"123-456-789","email":"juan.garcia@hospital.com","address":"Calle Principal 123","biography":"Doctor con 15 años de experiencia en cardiología","is_on_vacation":false,"department":1,"department_details":{"id":1,"name":"Cardiología","description":"Departamento especializado en enfermedades cardiovasculares"},"availabilities":[{"id":1,"doctor":1,"doctor_name":"Dr. Juan García","start_date":"2024-04-01","end_date":"2024-04-30","start_time":"09:00:00","end_time":"17:00:00"}],"appointments":[{"id":2,"appointment_date":"2026-05-15","appointment_time":"10:30:00","status":"scheduled"},{"id":1,"appointment_date":"2026-03-25","appointment_time":"10:00:00","status":"completed"}]}
para obtener las citas que tiene un doctor:
from bookings.serializers import AppointmentSerializer
@action(["GET"], detail=True, url_path="appointments")defget_appointments(self, request, pk)-> Response:'''
Recupera las citas asociadas a un doctor específico.
* Parámetros:
- request: Objeto de solicitud HTTP.
- pk: ID del doctor cuyas citas se van a recuperar.
* Retorna:
- Response: Objeto de respuesta HTTP con los datos de las citas del doctor.
''' doctor = self.get_object() appointments = doctor.appointments.all() serializer = AppointmentSerializer(appointments, many=True)return Response(serializer.data)
Aquí van las líneas de código que ingresa a la consola:
from datetime import date, time
from doctors.models import Doctor
from patients.models import Patient
from bookings.models import Appointment
patient = Patient.objects.get(id=1)doctor = Doctor.objects.first()Appointment.objects.create( patient=patient, doctor=doctor, appointment_date=date(2022,12,5), appointment_time=time(9,0), notes="Ejemplo", status="HECHA")```from datetime import date, timefrom doctors.models import Doctorfrom patients.models import Patientfrom bookings.models import Appointment
patient = Patient.objects.get(id=1)doctor = Doctor.objects.first()Appointment.objects.create( patient=patient, doctor=doctor, appointment\_date=date(2022,12,5), appointment\_time=time(9,0), notes="Ejemplo", status="HECHA")
NOTA: por convention django ocupa los campos <u>"related_name"</u> que se declaran en los modelos para manejar las relaciones (en nuestro caso appointments), si ustedes quisiese llamar de otra forma a appointments tendrian que hacer algo asi
classPatientSerializer(ModelSerializer): citas = AppointmentSerializer(many=True, read_only=True, source='appointments')classMeta: model = Patient
fields =["id","first_name","last_name","date_of_birth","contact_number","email","address","medical_history",'citas']
Algo que solucionó mi problema que no podía visualizar los appointments en la API, es que deben poner el mismo nombre que tienen en su related_name dentro de su modelo en booking.models en la clase Appointment