How to write a django-rest-framework serializer / field to merge data from generic relations?
Asked Answered
O

1

11

I have objects with a generic relation pointing to various other objects, and I need them to be merged (inlined) so the serialized objects look like one whole objects.

E.G:

class Enrollement(models.Model):
    hq = models.ForeignKey(Hq)
    enrollement_date = models.Datetime()
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    object = generic.GenericForeignKey('content_type', 'object_id')

class Nurse(models.Model):
     hospital = models.ForeignKey(Hospital)
     enrollement = GenericRelation(Enrollement)

class Pilot(models.Model):
     plane = models.ForeignKey(plane)
     enrollement = GenericRelation(Enrollement)

When serialized, I'd like to get something like this:

{
    count: 50,
    next: 'http...',
    previous: null,
    results: [
        {
        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',
        plante: 'http://url/to/plane-detail/view'

        },
    ]
}

Can I do it, and if yes, how ?

I can nest a generic relation, and I could post process the serilizer.data to obtain what I want, but I'm wondering if there is a better way.

Orthicon answered 28/5, 2013 at 13:40 Comment(2)
Nope. I by passed it by changing my requirements. it sucks.Orthicon
I had to build something quite similar somewhat recently and followed the rabbit hole of you asking this around the Web, noticing you were never able to get a straight, helpful answer. I ended up having to hack a solution together myself based on what I could piece together. I know you already changed your requirements but I took a stab at an answer below from what I learned in case it helps at all.Uprising
U
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.
Uprising answered 19/8, 2013 at 5:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.