I have 2 models:
from django.db import models
STATUSES = (
('f', 'Finished'),
)
class Battery(models.Model):
energy = models.CharField(max_length=10)
current = models.CharField(max_length=10)
class Charger(models.Model):
status = models.CharField(max_length=1, choices=STATUSES)
And I want to create serializer that will serialize this 2 models together. My serializers.py:
from rest_framework import serializers
from .models import Battery, Charger
class BatterySerializer(serializers.ModelSerializer):
class Meta:
model = Battery
class ChargerSerializer(serializers.ModelSerializer):
status = serializers.SerializerMethodField()
class Meta:
model = Charger
def get_status(self, obj):
return obj.get_status_display()
class DeviceSerializer(serializers.Serializer):
battery = BatterySerializer()
charger = ChargerSerializer()
some_field = serializers.CharField()
Because Charger model has choices in status field I add SerializerMethodField for displaying full status. Then I create a view like this:
class DeviceView(APIView):
def get(self, request, format=None):
battery = Battery.objects.get(id=1)
charger = Charger.objects.get(id=1)
battery_serializer = BatterySerializer(battery)
charger_serializer = ChargerSerializer(charger)
serializer = DeviceSerializer(data={
'battery': battery_serializer.data,
'charger': charger_serializer.data,
'some_field': 'some_text'
})
if serializer.is_valid():
return Response(serializer.validated_data)
else:
return Response(status = 500)
But when I call this view it returns json with empty charger field:
{
"battery": {
"energy": "12",
"current": "34"
},
"charger": {},
"some_field": "some_text"
}
But when I create a view that serialize only Charger model:
class ChargerView(APIView):
def get(self, request, format=None):
charger = Charger.objects.get(id=1)
charger_serializer = ChargerSerializer(charger)
return Response(charger_serializer.data)
It works and it returns this json:
{
"id": 1,
"status": "Finished"
}
Why this happens? Where did I make a mistake?
Serializing objects We can now use CommentSerializer to serialize a comment, or list of comments. Again, using the Serializer class looks a lot like using a Form class. At this point we've translated the model instance into Python native datatypes. To finalise the serialization process we render the data into json .
It is not necessary to use a serializer. You can do what you would like to achieve in a view. However, serializers help you a lot. If you don't want to use serializer, you can inherit APIView at a function-based-view.
Serializers in Django REST Framework are responsible for converting objects into data types understandable by javascript and front-end frameworks. Serializers also provide deserialization, allowing parsed data to be converted back into complex types, after first validating the incoming data.
SerializerMethodField. This is a read-only field. It gets its value by calling a method on the serializer class it is attached to. It can be used to add any sort of data to the serialized representation of your object.
Looking at the documentation of Serializers
:
instance
is passed when you have an object and you have to serialize it. (link)
data
is passed when you already have serialized data and you want to deserialize it and create an instance out of it.(link)
instance
and data
is passed when you have an instance and you want to update it.(link)
Looking at your case, I don't think you need option 2 and 3 because you have the battery
and charger
instances and you need to serialize it together. You are not creating a new instance and you also don't have to validate it so passing it as data
is not required.
There are two ways you could do this:
1.Create a class Device
so you could create an instance of it and then serialize it using DeviceSerializer
:
class Device(object):
def __init__(self, battery, charger, some_field):
self.battery = battery
self.charger = charger
self.some_field = some_field
class DeviceView(APIView):
# then in the DeviceView you could create an instance and pass to the serializer
def get(self, request, format=None):
battery = Battery.objects.get(id=1)
charger = Charger.objects.get(id=1)
device = Device(battery=battery, charger=charger, some_field='some_text')
serializer = DeviceSerializer(instance=device)
return Response(serializer.data)
2.If you don't want to go with creating a new class you could directly create a dict
and pass it as instance:
class DeviceView(APIView):
def get(self, request, format=None):
battery = Battery.objects.get(id=1)
charger = Charger.objects.get(id=1)
# create a dict with required objects and pass it as instance of serializer
device = {'battery': battery, 'charger': charger, 'some_field': 'some_text'}
serializer = DeviceSerializer(instance=device)
return Response(serializer.data)
Seems like you're doing work you don't have to. If you serialize the charger before passing it to the DeviceSerializer
, you're actually passing a dict
, not a Charger
instance, and the dict
has no get_status_display
method. You should pass the Battery
and Charger
directly like so:
class DeviceView(APIView):
def get(self, request, format=None):
battery = Battery.objects.get(id=1)
charger = Charger.objects.get(id=1)
serializer = DeviceSerializer(instance={
'battery': battery,
'charger': charger,
'some_field': 'some_text',
})
return Response(serializer.data)
Note that you can also simplify by replacing the SerializerMethodField with a CharField:
class ChargerSerializer(serializers.ModelSerializer):
status = serializers.CharField(source='get_status_display')
class Meta:
model = Charger
Edit: As AKS pointed out, a serializer should be passed instance
rather than data
when serializing (data
is for deserializing), and you don't need to check .is_valid()
you are passing the keyword data when creating the serializer instance, witch is only used when deserializing data. you should create the DeviceSerializer with an object with the fields you want. I haven't tested, but maybe something like this
class Device(object):
def __init__(self, battery, charger, name, ):
self.battery = battery
self.charger = charger
self.some_field = name
class DeviceView(APIView):
def get(self, request, format=None):
d=Device(Battery.objects.get(id=1),Charger.objects.get(id=1),"somename")
serializer = DeviceSerializer(d)
return Response(serializer.data)
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