Как написать сериализатор/поле django-rest-framework для объединения данных из общих отношений?

У меня есть объекты с общим отношением, указывающие на различные другие объекты, и мне нужно, чтобы они были объединены (inlined), поэтому сериализованные объекты выглядят как целые объекты.

например:

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)

При сериализации я хотел бы получить что-то вроде этого:

{
    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'

        },
    ]
}

Могу ли я это сделать, и если да, то как?

Я могу установить в нем общее отношение, и я мог бы отправить процесс serilizer.data, чтобы получить то, что я хочу, но мне интересно, есть ли лучший способ.

Ответ 1

ДОРОГИЕ ДРУЗЬЯ ИЗ БУДУЩЕГО: на момент написания статьи команда Django REST Framework, похоже, работает над добавлением более зрелой поддержки общих отношений. Но это еще не закончено. Прежде чем копировать этот ответ в базу кода, сначала проверьте https://github.com/tomchristie/django-rest-framework/pull/755, чтобы узнать, объединено ли оно с репо. Возможно, вас ждет более элегантное решение. - Ваш древний предок Тайлер

Учитывая, что вы используете Django REST Framework, если вы хотите сделать некоторую пост-обработку (даже если вы, похоже, не решаетесь), вы можете добиться чего-то своей цели переопределяя get_queryset или list в вашем представлении. Что-то вроде этого:

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')

Возвращенный ответ будет выглядеть так:

  [
        {
              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"
        },
  ]

Обращает на себя внимание:

  • Мой serializers.py может быть немного отсюда, потому что моя память о том, как представлять общие отношения в сериализаторах, немного туманна. YMMV.
  • Аналогично ^^ это предполагает, что ваш serializers.py в порядке и правильно установил свои общие отношения в соответствии с вашими моделями.
  • Мы делаем get в source=enrollement.get, потому что иначе объект GenericRelatedObjectManager будет возвращен, если мы не укажем источник. Это потому, что это то, что представляет собой общее отношение. Использование .get заставляет запрос (как в запросе QuerySet), который обращается к модели, которую вы задали как источник общего отношения (в этом случае class Enrollement(models.Model).
  • Мы должны использовать list(chain()) вместо оператора |, потому что наборы запросов поступают из разных моделей. Вот почему мы не можем сделать entries = nurses | pilots.
  • for entry in entries можно сделать более сухим. GLHF.