Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django Rest Framework doesn't serialize SerializerMethodField

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?

like image 838
Dima Kudosh Avatar asked Apr 01 '16 20:04

Dima Kudosh


People also ask

How do you serialize an object list in Django REST framework?

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 .

Is serialization necessary in Django?

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.

Do we need Serializers in Django REST framework?

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.

What is SerializerMethodField Django?

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.


3 Answers

Looking at the documentation of Serializers:

  1. instance is passed when you have an object and you have to serialize it. (link)
  2. data is passed when you already have serialized data and you want to deserialize it and create an instance out of it.(link)
  3. both 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)    
like image 179
AKS Avatar answered Oct 13 '22 01:10

AKS


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()

like image 29
Fush Avatar answered Oct 13 '22 01:10

Fush


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)
like image 42
Marco Silva Avatar answered Oct 13 '22 03:10

Marco Silva