Django: Serializing a list of multiple, chained models

Given two different models, with the same parent base class. Is there any way, using either Django Rest Framework Serializers or serpy, to serialize a chained list containing instances of both the child models?

Given some example models:

class BaseModel(models.Model):
   created_at = models.DateField()

   class Meta:
       abstract = True


class ChildModel1(BaseModel):
    field_one = models.TextField()


class ChildModel2(BaseModel):
    field_two = models.TextField()

And an example view:

def get(self, request):

    q1 = ChildModel1.objects.all()
    q2 = ChildModel2.objects.all()

    chained_list = sorted(
        chain(q1, q2),
        key=attrgetter('created_at'))

    serializer = BaseModelSerializer(chained_list, many=True)

The method for chaining the models is taken from the answer to this question.

With my current attempts I get a quite obvious error saying something like:

AttributeError: 'ChildModel1' object has no attribute 'field_two'

I know it is not the best practice to mix two models with some different fields, but in my case I thought it necessary.

Some examples of serializers I have tested:

First example:

class BaseModelSerializer(serializers.ModelSerializer):

   class Meta:
       model = BaseModel

Second example:

class BaseModelSerializer(serpy.Serializer):
   created_at = serpy.StrField()

   field_one = serpy.StrField(required=False)
   field_two = serpy.StrField(required=False)

Upvotes: 6

Views: 3037

Answers (2)

zymud
zymud

Reputation: 2249

You can define serializer that will combine two or more serializers together based on model:

class Model1Serializer(serializers.Serializer):
    ...

class Model2Serializer(serializers.Serializer):
    ...

class SummarySerializer(serializers.Serializer):
    """ Serializer that renders each instance with its own specific serializer """

    @classmethod
    def get_serializer(cls, model):
        if model == Model1:
            return Model1Serializer
        elif model == Model2:
            return Model2Serializer

    def to_representation(self, instance):
        serializer = self.get_serializer(instance.__class__)
        return serializer(instance, context=self.context).data

This will work for any models, not only for childs of one class.

Upvotes: 7

Ivan Semochkin
Ivan Semochkin

Reputation: 8907

From your exception I suppose problem with a BaseModelSerializer because it have both fields from both models. I think your better write a two separate serializers for each models and then sort output from them by common field:

class ChildModel1Serializer(serializers.ModelSerializer):

    class Meta:
        model = ChildModel1
        fields = ('created_at', 'field_one')


class ChildModel2Serializer(serializers.ModelSerializer):

    class Meta:
        model = ChildModel2
        fields = ('created_at', 'field_two')

views.py:

def get(self, request):

    q1 = ChildModel1.objects.all()
    q2 = ChildModel2.objects.all()
    # evaluate querysets and get a serializeble output in a list
    serializer1 = ChildModel1Serializer(q1, many=True).data
    serializer2 = ChildModel2Serializer(q2, many=True).data
    # concatenate two lists of dicts and sort it by 'created_at' key
    chained_list = sorted(serializer1 + serializer2, key=lambda x: x.get('created_at'))

    return Response(chained_list)

Upvotes: 2

Related Questions