tefozi
tefozi

Reputation: 5480

Django serialization of inherited model

I have a problem with serialization of Django inherited models. For example

class Animal(models.Model):
    color = models.CharField(max_length=50)

class Dog(Animal):
    name = models.CharField(max_length=50)

...
# now I want to serialize Dog model with Animal inherited fields obviously included
print serializers.serialize('xml', Dog.objects.all())

and only Dog model has been serialized.

I can do smth like

all_objects = list(Animal.objects.all()) + list(Dog.objects.all())
print serializers.serialize('xml', all_objects)

But it looks ugly and because my models are very big so I have to use SAX parser and with such output it's difficult to parse.

Any idea how to serialize django models with parent class?

**EDIT: ** It use to work ok before this patch has been applied. And the explanation why the patch exist "Model saving was too aggressive about creating new parent class instances during deserialization. Raw save on a model now skips saving of the parent class. " I think there should be an option to be able to serialize "local fields only" by default and second option - "all" - to serialize all inherited fields.

Upvotes: 9

Views: 4347

Answers (5)

Steven H.
Steven H.

Reputation: 293

Did you look at select_related() ? as in

serializers.serialize('xml', Dog.objects.select_related().all())

Upvotes: 0

Mudit Verma
Mudit Verma

Reputation: 469

You can define a custom Serializer:

class DogSerializer(serializers.ModelSerializer):  
    class Meta:  
       model = Dog 
        fields = ('color','name') 

Use it like:

serializer = DogSerializer(Dog.objects.all(), many=True)  
print serializer.data enter code here

Upvotes: 0

Agey
Agey

Reputation: 931

I had the same problem, and i wrote a 'small' queryset serializer which navigates up the inheritance tree and returns all the fields serialized.

It's far from perfect... but works for me :)

a = QuerySetSerializer(MyModel, myqueryset)
a.serialize()

And the snippet:

from __future__ import unicode_literals
import json
import inspect
from django.core import serializers
from django.db.models.base import Model as DjangoBaseModel
class QuerySetSerializer(object):
    def __init__(self, model, initial_queryset):
        """
        @param model: The model of your queryset
        @param initial_queryset: The queryset to serialize
        """
        self.model = model
        self.initial_queryset = initial_queryset
        self.inheritance_tree = self._discover_inheritance_tree()

    def serialize(self):
        list_of_querysets = self._join_inheritance_tree_objects()
        merged_querysets = self._zip_queryset_list(list_of_querysets)

        result = []
        for related_objects in merged_querysets:
            result.append(self._serialize_related_objects(related_objects))
        return json.dumps(result)

    def _serialize_related_objects(self, related_objects):
        """
        In this method, we serialize each instance using the django's serializer function as shown in :
        See https://docs.djangoproject.com/en/1.10/topics/serialization/#inherited-models

        However, it returns a list with mixed objects... Here we join those related objects into one single dict
        """
        serialized_objects = []

        for related_object in related_objects:
            serialized_object = self._serialize_object(related_object)
            fields = serialized_object['fields']
            fields['pk'] = serialized_object['pk']
            serialized_objects.append(fields)

        merged_related_objects = {k: v for d in serialized_objects for k, v in d.items()}
        return merged_related_objects

    def _serialize_object(self, obj):
        data = serializers.serialize('json', [obj, ])
        struct = json.loads(data)
        return struct[0]

    def _discover_inheritance_tree(self):
        # We need to find the inheritance tree which excludes abstract classes,
        # so we can then join them when serializing the instance
        return [x for x in inspect.getmro(self.model) if x is not object and x is not DjangoBaseModel and not x._meta.abstract]

    def _join_inheritance_tree_objects(self):
        """
        Here we join the required querysets from the non abstract inherited models, which we need so we are able to
        serialize them.

        Lets say that MyUser inherits from Customer and customer inherits from django's User model
        This will return [list(MyUser.objects.filter(...), list(Customer.objects.filter(...), list(User.objects.filter(...)
        """

        initial_ids = self._get_initial_ids()
        inheritance__querysets = [list(x.objects.filter(id__in=initial_ids).order_by("id")) for x in self.inheritance_tree]
        return inheritance__querysets

    def _zip_queryset_list(self, list_of_querysets):
        """
        At this stage, we have something like:
        (
            [MyUser1, MyUser2, MyUser3],
            [Customer1, Customer2, Customer3],
            [User1, User2, User3]
        )

        And to make it easier to work with, we 'zip' the list of lists so it looks like:
        (
            [MyUser1, Customer1, User1],
            [MyUser2, Customer2, User2],
            [MyUser3, Customer3, User3],
        )

        """
        return zip(*list_of_querysets)

    def _get_initial_ids(self):
        """
        Returns a list of ids of the initial queryset
        """
        return self.initial_queryset.order_by("id").values_list("id", flat=True)

Upvotes: 0

Zach Mathew
Zach Mathew

Reputation: 451

You'll need a custom serializer to support inherited fields, as Django's serializer will only serialize local fields.

I ended up writing my own when dealing with this issue, feel free to copy it: https://github.com/zmathew/django-backbone/blob/master/backbone/serializers.py

In order to use it on its own, you need to do:

serializer = AllFieldsSerializer()
serializer.serialize(queryset, fields=fields)
print serializer.getvalue()

Upvotes: 1

Matt S.
Matt S.

Reputation: 7881

You found your answer in the documentation of the patch.

all_objects = list(Animal.objects.all()) + list(Dog.objects.all())
print serializers.serialize('xml', all_objects)

However, if you change Animal to be an abstract base class it will work:

class Animal(models.Model):
    color = models.CharField(max_length=50)

    class Meta:
        abstract = True

class Dog(Animal):
    name = models.CharField(max_length=50)

This works as of Django 1.0. See http://docs.djangoproject.com/en/dev/topics/db/models/.

Upvotes: 1

Related Questions