Calcular campos derivados en Django REST Framework con precisión y sin cambiar el modelo es sencillo con SerializerMethodField. Aquí verás cómo exponer la edad de un paciente desde su fecha de nacimiento, trabajar con timedelta y evitar errores típicos. Además, se muestra cómo extender la idea para la experiencia de un doctor.
¿Cómo usar serializermethodfield para calcular valores en el serializer?
SerializerMethodField permite derivar valores en el serializer usando la instancia completa del objeto. Si no indicas method_name, se aplica la convención get_nombreDelCampo (por ejemplo, get_age). Así accedes al objeto del modelo, lees sus campos y devuelves el valor calculado que aparecerá en el endpoint.
Usa SerializerMethodField para exponer campos calculados en el serializer.
Si omites method_name, se usa el método get_<campo>.
El parámetro del método es obj: la instancia del modelo (por ejemplo, Patient).
Toma date_of_birth del objeto para calcular la edad.
Importa date desde datetime: no uses datetime si el campo es de tipo date.
La resta date.today() - obj.date_of_birth retorna un timedelta.
Usa .days y divide entre 365 para obtener años como número.
Agrega el campo a fields para que se muestre en la respuesta.
Evita texto como “años” en la API: el formato es del front-end por soporte multiidioma.
Beneficio clave: no tocas el modelo, solo el serializer.
¿Qué pasos y código aplicar para calcular la edad?
from datetime import date
from rest_framework import serializers
classPatientSerializer(serializers.ModelSerializer): age = serializers.SerializerMethodField()# también puedes usar method_name="get_age"defget_age(self, obj):# obj es una instancia de Patient con obj.date_of_birth (tipo date) age_days =(date.today()- obj.date_of_birth).days # timedelta → días age_years =int(age_days /365)# años aproximados como número enteroreturn age_years
classMeta: model = Patient
fields =("date_of_birth","age")# incluye el campo calculado
Importa date para obtener date.today().
Resta fechas para obtener timedelta y extrae .days.
Divide entre 365 y retorna el número.
Incluye age en fields.
¿Qué errores comunes y buenas prácticas considerar?
Pequeños detalles marcan la diferencia: el tipo de dato, cómo acceder a días y dónde formatear el texto. Mantén el cálculo en el serializer y el idioma en el front-end.
No importes datetime cuando el campo es date: importa date.
No intentes dividir el timedelta directamente: usa .days primero.
No olvides agregar age a fields o no aparecerá en el endpoint.
No devuelvas cadenas tipo "45 años": devuelve el valor numérico.
Valida en el navegador el resultado tras guardar los cambios.
Aprovecha SerializerMethodField para no modificar el modelo.
¿Cómo calcular la experiencia de un doctor con un método similar?
La misma técnica sirve para un campo de experiencia en años, basado en la fecha en que el doctor empezó a trabajar. Solo cambia el campo de origen y el nombre del método.
from datetime import date
from rest_framework import serializers
classDoctorSerializer(serializers.ModelSerializer): experience = serializers.SerializerMethodField()defget_experience(self, obj):# Ajusta el nombre del campo según tu modelo, por ejemplo: obj.start_date start = obj.start_date # fecha en que empezó a trabajar (tipo date) exp_years =int((date.today()- start).days /365)return exp_years
classMeta: model = Doctor # ajusta al modelo real fields =("start_date","experience")
Cambia el campo de fecha de inicio según tu modelo.
Reutiliza la misma lógica de timedelta y división por 365.
Expón el valor en años como número.
¿Te gustaría compartir cómo nombraste tu método y campo de inicio en tu proyecto? Comenta tu enfoque y dudas para mejorar entre todos.
from datetime importdatefrom rest_framework importserializersfrom.modelsimportDoctorclassDoctorSerializer(serializers.ModelSerializer): experience_years = serializers.SerializerMethodField() # Campo personalizado para años de experiencia
classMeta: model =Doctor fields =['first_name','last_name','qualification','email','start_date','experience_years' # Incluir el campo calculado en la salida
] def get_experience_years(self, obj): experience_td = date.today()- obj.start_date # Calcula la diferencia entre hoy y la fecha de inicio
years = experience_td.days// 365 # Convierte los días en añosreturn f"{years} años" # Retorna los años de experiencia con el texto "años"
cool!
Que muestre años, meses y dias
defget_age(self, obj):# Muestar los años que tiene el paciente# age_td = date.today() - obj.date_of_birth# years = age_td.days // 365# return f"{years} años"# Muestar los años, meses y dias que tiene el paciente age_td = date.today()- obj.date_of_birth
years = age_td.days //365 remaining_days = age_td.days %365 months = remaining_days //30 days = remaining_days %30returnf"{years} años, {months} meses y {days} días"```def get\_age(self, obj):# Muestar los años que tiene el paciente # age\_td = date.today() - obj.date\_of\_birth # years = age\_td.days // 365 # return f"{years} años"# Muestar los años, meses y dias que tiene el paciente age\_td = date.today() - obj.date\_of\_birth years = age\_td.days // 365 remaining\_days = age\_td.days % 365 months = remaining\_days // 30 days = remaining\_days % 30 return f"{years} años, {months} meses y {days} días"
My resume, important put DoctorAvailabilitySerializer before DoctorSerializer:
from rest_framework import serializers
from.models import Physician, Department, PhyisicanAvailability, MedicalNote
from datetime import date
from patients.models import Patient
from physicians.models import Physician
from bookings.serializers import AppointmentSerializer
classDepartmentSerializer(serializers.ModelSerializer):classMeta: model = Department
fields =['id','name','description']classPhyisicanAvailabilitySerializer(serializers.ModelSerializer):classMeta: model = PhyisicanAvailability
fields =['id','phyisican','start_date','end_date','start_time','end_time']classMedicalNoteSerializer(serializers.ModelSerializer):classMeta: model = MedicalNote
fields =['id','phyisican','note','date']classPhysicianSerializer(serializers.ModelSerializer): department = DepartmentSerializer(read_only=True) doctor_availability = PhyisicanAvailabilitySerializer(many=True, read_only=True, source='availabilities')# medical_notes = MedicalNoteSerializer(many=True, read_only=True) appointments = AppointmentSerializer(many=True, read_only=True) work_time_exp = serializers.SerializerMethodField()classMeta: model = Physician
fields =['id','first_name','last_name','qualification','contact_number','email','address','biography','department','doctor_availability','medical_notes','appointments','work_time_exp']defvalidate_email(self, value):if"@example.com"in value:return value
raise serializers.ValidationError("Invalid email address")defvalidate(self, attrs):# Check if keys exist in attrs , object.if'contact_number'in attrs and'is_on_vacation'in attrs:iflen(attrs['contact_number'])<10and attrs['is_on_vacation']:raise serializers.ValidationError("Contact number is required before vacations")returnsuper().validate(attrs)defget_work_time_exp(self, obj):# USE Create _at date because it is the date the doctor was created on db system.if obj.created_at: experience_td = date.today()- obj.created_at.date() years = experience_td.days //365return years
return0
para calcular los años de experiencia del doctor:
classDoctor(models.Model):'''
Modelo que representa a un doctor en el sistema.
Contiene información personal y profesional relevante.
* Atributos:
- first_name: Nombre del doctor.
- last_name: Apellido del doctor.
- qualification: Título profesional del doctor.
- graduation_date: Fecha de graduación del doctor.
- contact_number: Número de contacto del doctor.
- email: Dirección de correo electrónico del doctor.
- address: Dirección del consultorio del doctor.
- biography: Breve biografía del doctor.
- specialty: Especialidad médica del doctor.
- is_on_vacation: Indica si el doctor está de vacaciones.
* Métodos:
- __str__: Retorna una representación legible del doctor.
''' first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=30) qualification = models.CharField(max_length=100) contact_number = models.CharField(max_length=15) email = models.EmailField(unique=True) address = models.TextField() biography = models.TextField(blank=True) specialty = models.CharField(max_length=100) graduation_date = models.DateField(blank=True, null=True) is_on_vacation = models.BooleanField(default=False)def__str__(self):returnf"Dr. {self.first_name}{self.last_name} - {self.specialty}"-----# Campo calculado para los años de experiencia del doctor. experience = serializers.SerializerMethodField()classMeta: model = Doctor
fields ='__all__'defget_experience(self, obj)->int:'''
Calcula los años de experiencia del doctor basado en su fecha de graduación.
* Parámetros:
- obj: Instancia del modelo Doctor.
* Retorna:
- int: Años de experiencia del doctor.
'''if obj.graduation_date: experience_td = date.today()- obj.graduation_date
returnint(experience_td.days //365.25)# Aproximación considerando años bisiestosreturn0# Si no hay fecha de graduación, retorna 0 años de experiencia
Hola, yo uso en mi proyecto el localtime().date(), es bueno usarlo igual aquí o tengo que usar a la fuerza él date.today()
Puedes usar el localtime, ese te ayuda a respetar el timezone.
Relacioné los modelos de DoctorAvailability y Doctor para obtener la fecha de inicio del doctor y modifique el resultado de tiempo de experiencia para hacerlo más específico implementando la librería de python-dateutil
from datetime import date, datetime
from dateutil.relativedeltaimport relativedelta
classDoctorSerializer(serializers.ModelSerializer): # Relacionar con el serializador de DoctorAvailability availabilities =DoctorAvailabilitySerializer(many=True, read_only=True) experience_years = serializers.SerializerMethodField()classMeta: model =Doctor fields =['id','first_name','last_name','qualification','contact_number','email','address','biography','is_on_vacation','availabilities','experience_years',] def get_experience_years(self, obj): first_availability = obj.availabilities.order_by('start_date').first()iffirst_availability: # Calcular la diferencia entre la fecha actual y la primera disponibilidad
now = datetime.now() dob = datetime.combine(first_availability.start_date, datetime.min.time()) experience_time_delta =relativedelta(now, dob) # Obtener los años, meses y días years = experience_time_delta.years months = experience_time_delta.months days = experience_time_delta.daysreturn f'{years} años, {months} meses, {days} días'else: # En caso de que no tenga disponibilidad, retornar 'Sin experiencia'return'Sin experiencia'```Y así ajuste el serializador de PatientSerializer
```js
def get_age(self, obj): # Combinar con tiempo para obtener datetime completo
dob = datetime.combine(obj.date_of_birth, datetime.min.time()) # Calcular la diferencia entre la fecha de nacimiento y el momento actual
age_time_delta =relativedelta(date.today(), dob) # Obtener los años, meses, días y horas
years = age_time_delta.years months = age_time_delta.months days = age_time_delta.daysreturn f'{years} años, {months} meses, {days} días'
Asi quedo el SerializerMetodField para calcular la experiencia de los doctores. Esta hecha basada en el modelo DoctorAvailability pero seguramente se puede hacer mucho mejor desde el modelo Doctor.
class DoctorAvailabilitySerializer(serializers.ModelSerializer): experience = serializers.SerializerMethodField() class Meta: model = DoctorAvailability fields = [ 'doctor', 'experience', 'start_date', 'end_date', 'start_time', 'end_time', ] def get_experience(self, obj): """ Return the doctor experience in years """ # get age in days (timedelta) experience_timedelta = date.today() - obj.start_date years = experience_timedelta.days // 365 return f"{years}"