django rest framework - how do you flatten nested data?
django, django-rest-framework
Solution
I'm not convinced this is the simplest way, but the solution i came up with was to override the to_native method of the MembershipSerializer and then manually create and invoke the to_native method of the PersonSerializer and merge the two resulting dictionary's together
class MembershipSerializer(serializers.HyperlinkedModelSerializer):
def to_native(self, obj):
ret = super(MembershipSerializer, self).to_native(obj)
p_serializer = PersonSerializer(obj.person, context=self.context)
p_ret = p_serializer.to_native(obj.person)
for key in p_ret:
ret[key] = p_ret[key]
return ret
class Meta:
model = Membership
fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'person')
The dictionary's are both a subclass of SortedDict. I'm not sure whether there's an explicit method to merge the two that preserves the order, so i've just used a loop instead.
Problem
I have a 'through' model governing a many to many relationship and i want to be able to return the 'through' model and the target model as flat data, as opposed to having the target model nested. So using the standard example for a many to many with a through, say these are the models, ``` class Person(models.Model): first_name = models.CharField(max_length=128) last_name = models.CharField(max_length=128) favourite_food = models.CharField(max_length=128) class Group(models.Model): name = models.CharField(max_length=128) members = models.ManyToManyField(Person, through='Membership') class Membership(models.Model): person = models.ForeignKey(Person) group = models.ForeignKey(Group) date_joined = models.DateField() invite_reason = models.CharField(max_length=64) ``` So the serializers i have at the moment to return Membership items are, ``` class MembershipSerializer(serializers.HyperlinkedModelSerializer): person = PersonSerializer() class Meta: model = Membership fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'person') class PersonSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Person fields = ('first_name', 'last_name', 'favourite_food') ``` So when i retrieve a Membership model using the MembershipSerializer, i get this json, ``` { 'id':1, 'url':'http://cheeselovers.com/api/member/1/' 'group':'http://cheeselovers.com/api/group/1/' 'date_joined': '2014-01-24T16:33:40.781Z', 'invite_reason': 'loves cheese', 'person':{ 'first_name':'Barry', 'last_name':'CheeseLover', 'favourite_food': 'cheese' } } ``` but what i'd like returned is this, ``` { 'id':1, 'url':'http://cheeselovers.com/api/member/1/' 'group':'http://cheeselovers.com/api/group/1/' 'date_joined': '2014-01-24T16:33:40.781Z', 'invite_reason': 'loves cheese', 'first_name':'Barry', 'last_name':'CheeseLover', 'favourite_food': 'cheese' } ``` Now i realise that i could simply accomplish this by changing the MembershipSerializer to this, ``` class MembershipSerializer(serializers.HyperlinkedModelSerializer): first_name = serializers.Field(source='person.first_name') last_name = serializers.Field(source='person.last_name') favourite_food = serializers.Field(source='person.favourite_food') class Meta: model = Membership fields = ('id', 'url', 'group', 'date_joined', 'invite_reason', 'first_name', 'last_name', 'favourite_food') ``` BUT, the target model i have has 10 properties and the intermediary 'through' model only has read only props, so i already have a functioning serializer for the target model, that's used during the creation of the intermediary model. It feels more DRY to be able to reuse this, so that if anything on the target model changes, i only have to make changes to it's serializer, for those changes to be then be reflected in the data returned by the intermediary's serializer. So is there a way i can get the data from the PersonSerializer and add it to the Membership data, so that it's flat instead of nested? ...hope that all makes sense.