How to create both serializers.Patient and serializers.Temperature in such way that:
models.Patient has one-to-many relationship with models.Temperaturesserializers.Patient is a subclass of serializers.ModelSerializerserializers.Patient (de)serialize temperatures as a list of floatsGiven a quick-dirty patient medical records RESTful API implemented with Django framework.
Patient is defined at models.Patient as:
class Patient(models.Model):
created_at = models.DateField()
name = models.CharField(max_length=200)
updated_at = models.DateField()
and the models.Temperature:
class Temperature(models.Model):
created_at = models.DateField()
patient = models.ForeignKey(
Patient,
db_column='patient',
related_name='temperatures',
on_delete=models.CASCADE,
)
updated_at = models.DateField()
value = models.FloatField()
CRUD operations at /patients (de)serialized models.Temperature as float lists, thus a POST should only require:
{
"name": "John Connor",
"temperatures": [36.7, 40, 35.9]
}
while a GET operation
{
"created_at": "1985-03-25",
"name": "John Connor",
"temperatures": [36.7, 40, 35.9],
"updated_at": "2021-08-29"
}
However operations at /patients/<id>/temperatures/ endpoint should return all properties:
[
{
"created_at": "1985-03-25",
"value": 36.7,
"updated_at": "2021-08-29"
},
{
"created_at": "1985-03-25",
"value": 40.0,
"updated_at": "2021-08-29"
},
{
"created_at": "1985-03-25",
"value": 35.9,
"updated_at": "2021-08-29"
}
]
Can this feature be implemented subclassing standards DRF serializers or does it require a customized serializers.Serializer subclass?
Let's try to analyze the situation here. We need to input the temperature data with a list. So, let's use ListField for this purpose. So, we should start like this:
class PatientSerializer(ModelSerializer):
temperatures = ListField(child=FloatField())
What this will do is expect a list of floats from the user.
But how will we get the array of the temperatures of the created patient when using GET operation. For this, let's add a source to the field. Whatever is written on the source param will get converted into the patient.field for example:
class PatientSerializer(ModelSerializer):
temperatures = ListField(child=FloatField(), source="temperature_list")
This source="temperature_list will user patient.temperature_list and show the result when serializing the object. Now let's add some property to the original model so that we can get the clean temperature array like this:
class Patient(models.Model):
created_at = models.DateField()
name = models.CharField(max_length=200)
updated_at = models.DateField()
@property
def temperature_list(self):
return [temperature.value for temperature in self.temperatures.all()]
We are almost done. Now we need to override the create method for PatientSerializer so that it can save the temperatures present in the list.
Now, to get the temperature data from the validated_data we need to use the same field that is used in the source argument on the ListField, which is temperature_list. So, we need to pop this data, create the user, validate the temperature list items, create temperature objects that are related to the patient. Let's create a TemperatureSerializer like this:
class TemperatureSerializer(ModelSerializer):
class Meta:
model = Temperature
fields = ("created_at", "value", "updated_at", "patient")
extra_kwargs = {
"patient": {"write_only": True},
"created_at": {"required": False},
"updated_at": {"required": False},
}
Now, all we need to do is, write the create method. We can write it like this:
def create(self, validated_data):
temperatures = validated_data.pop("temperature_list")
now = datetime.now().strftime("%Y-%m-%d")
patient = Patient.objects.create(
created_at=now, updated_at=now, **validated_data
)
for temperature in temperatures:
now = datetime.now().strftime("%Y-%m-%d")
temperature_serializer = TemperatureSerializer(
data={
"value": temperature,
"created_at": now,
"updated_at": now,
"patient": patient.id,
}
)
temperature_serializer.is_valid(raise_exception=True)
temperature_serializer.save()
return patient
Finally, let's put together all the things and this is how the serializers.py file would look like:
from rest_framework.serializers import ModelSerializer, ListField, FloatField
from datetime import datetime
from .models import Patient, Temperature
class TemperatureSerializer(ModelSerializer):
class Meta:
model = Temperature
fields = ("created_at", "value", "updated_at", "patient")
extra_kwargs = {
"patient": {"write_only": True},
"created_at": {"required": False},
"updated_at": {"required": False},
}
class PatientSerializer(ModelSerializer):
temperatures = ListField(child=FloatField(), source="temperature_list")
def create(self, validated_data):
temperatures = validated_data.pop("temperature_list")
now = datetime.now().strftime("%Y-%m-%d")
patient = Patient.objects.create(
created_at=now, updated_at=now, **validated_data
)
for temperature in temperatures:
now = datetime.now().strftime("%Y-%m-%d")
temperature_serializer = TemperatureSerializer(
data={
"value": temperature,
"created_at": now,
"updated_at": now,
"patient": patient.id,
}
)
temperature_serializer.is_valid(raise_exception=True)
temperature_serializer.save()
return patient
class Meta:
model = Patient
fields = ("created_at", "name", "temperatures", "updated_at")
extra_kwargs = {
"created_at": {"required": False},
"updated_at": {"required": False},
}
This is how the views.py would look like:
from rest_framework.generics import ListCreateAPIView, ListAPIView
from .models import Patient, Temperature
from .serializers import PatientSerializer, TemperatureSerializer
class PatientListCreateView(ListCreateAPIView):
serializer_class = PatientSerializer
queryset = Patient.objects.all()
class TemperatureListView(ListAPIView):
serializer_class = TemperatureSerializer
def get_queryset(self, *args, **kwargs):
patient_id = self.kwargs["pk"]
return Temperature.objects.filter(patient_id=patient_id)
And the urls.py file:
from django.urls import path
from . import views
urlpatterns = [
path("patients", views.PatientListCreateView.as_view(), name="patient-list"),
path(
"patients/<int:pk>/temperatures/",
views.TemperatureListView.as_view(),
name="patient-temperature-list",
),
]
This is how the POST method would work:

And this is how the temperatures of a patient would return:

I hope this answers your questions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With