Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 743
e-satis Avatar asked May 28 '13 13:05

e-satis


People also ask

How do you pass extra context data to Serializers in Django REST Framework?

In function based views we can pass extra context to serializer with "context" parameter with a dictionary. To access the extra context data inside the serializer we can simply access it with "self. context". From example, to get "exclude_email_list" we just used code 'exclude_email_list = self.

What is To_representation in Django serializer?

to_representation(self, value) method. This method takes the target of the field as the value argument, and should return the representation that should be used to serialize the target. The value argument will typically be a model instance.

How do I pass Queryset to serializer?

To serialize a queryset or list of objects instead of a single object instance, you should pass the many=True flag when instantiating the serializer. You can then pass a queryset or list of objects to be serialized.


1 Answers

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.
like image 65
Tyler Hayes Avatar answered Oct 18 '22 18:10

Tyler Hayes