7

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?

5
  • why can't you call both BatterySerializer and ChargerSerializer methods independently in your view and return them? Since that is what your DeviceSerializeris doing. Commented Apr 1, 2016 at 20:50
  • How I can return them together? Commented Apr 1, 2016 at 20:57
  • like this { 'battery': battery.__dict__, 'charger': charger.__dict__, 'some_field': 'some_text' } ? not sure if this is the right way though. Commented Apr 1, 2016 at 21:08
  • What are you validating in serializer.is_valid()? Can you directly return Response(serializer.data) and see what you get? Commented Apr 4, 2016 at 6:25
  • When I try to return Response(serializer.data) It raise AssertionError: When a serializer is passed a data keyword argument you must call .is_valid() before attempting to access the serialized .data representation. You should either call .is_valid() first, or access .initial_data instead. Commented Apr 4, 2016 at 13:40

3 Answers 3

8
+50

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)    
Sign up to request clarification or add additional context in comments.

Comments

2

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

2 Comments

It's doesn't work. Invalid data, expected a dictionary, but got Battery/Charger
Edited to pass dict as instance instead of data
1

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.