[Solved]-How to write a django-rest-framework serializer / field to merge data from generic relations?

38👍

DEAR FRIENDS FROM THE FUTURE: At time of writing, the Django REST Framework team seems to be working on adding more mature support for generic relations. But it is not yet finished. Before copy-pasting this answer into your code base, check https://github.com/tomchristie/django-rest-framework/pull/755 first to see if it’s been merged into the repo. There may be a more elegant solution awaiting you. — Your ancient ancestor Tyler

Given you’re using Django REST Framework, if you did want to do some post-processing (even though you seem hesitant to) you can accomplish something your goal by overriding get_queryset or list in your view. Something like this:

views.py:

from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from models import *
from itertools import chain

class ResultsList(ListAPIView):
    def list(self, request, *args, **kwargs):
        nurses = Nurse.objects.all()
        pilots = Pilot.objects.all()

        results = list()
        entries = list(chain(nurses, pilots)) # combine the two querysets
        for entry in entries:
            type = entry.__class__.__name__.lower() # 'nurse', 'pilot'
            if isinstance(entry, Nurse):
                serializer = NurseSerializer(entry)
                hospital = serializer.data['hospital']
                enrollement_date = serializer.data['enrollement.date']
                hq = serializer.data['enrollement.hq']
                dictionary = {'type': type, 'hospital': hospital, 'hq': hq, 'enrollement_date': enrollement_date}
            if isinstance(entry, Pilot):
                serializer = PilotSerializer(entry)
                plane = serializer.data['plane']
                enrollement_date = serializer.data['enrollement.date']
                hq = serializer.data['enrollement.hq']
                dictionary = {'type': type, 'plane': plane, 'hq': hq, 'enrollement_date': enrollement_date}
            results.append(dictionary)
        return Response(results)

serializers.py

class EnrollementSerializer(serializer.ModelSerializer):
    class Meta:
        model = Enrollement
        fields = ('hq', 'enrollement_date')

class NurseSerializer(serializer.ModelSerializer):
    enrollement = EnrollementSerializer(source='enrollement.get')

    class Meta:
        model = Nurse
        fields = ('hospital', 'enrollement')

class PilotSerializer(serializer.ModelSerializer):
    enrollement = EnrollementSerializer(source='enrollement.get')

    class Meta:
        model = Pilot
        fields = ('plane', 'enrollement')

Returned response would look like:

  [
        {
              type: "nurse",
              hq: "http://url/to/hq-detail/view",
              enrollement_date: "2003-01-01 01:01:01",
              hospital: "http://url/to/hospital-detail/view"
        },
        {
              type: "pilot",
              hq: "http://url/to/hq-detail/view",
              enrollement_date: "2003-01-01 01:01:01",
              plane: "http://url/to/plane-detail/view"
        },
  ]

Noteworthy:

  • My serializers.py may be a bit off here because my memory of how to represent generic relations in serializers is a bit foggy. YMMV.
  • Similarly to ^^ this assumes your serializers.py is in order and has properly set up its generic relationships in line with your models.
  • We do the get in source=enrollement.get because otherwise a GenericRelatedObjectManager object will be returned if we don’t specify a source. That’s because that’s what a generic relation represents. Using .get forces a query (as in QuerySet query) which accesses the model you set as the source of the generic relation (in this case, class Enrollement(models.Model).
  • We have to use list(chain()) instead of the | operator because the querysets come from different models. That’s why we can’t do entries = nurses | pilots.
  • for entry in entries can surely be made more dry. GLHF.

Leave a comment