简体   繁体   中英

Django Rest framework: custom API response

I have Char objects with ManyToMany relationships to Source objects. Because a Char can appear in many Sources and many Sources can contain multiple Chars. The MtM relationship goes through a through table which also contains page number. In my API response, which I built using the Django REST framework I want to avoid resolving the full Source title, author etc. for every Char. Rather, in order to reduce the size of the JSON response, I want to refer it by id and include a sources section so the client can look it up.

Ie a client visiting /api/char/26 should get the following response:

"chars": [
    {
        "id": 26,
        "name": "龜",
        "locations": [
            {
                "page": 136,
                "source": 1
            },
            {
                "page": 162,
                "source": 1
            }
        ]
    }
],
"sources": [
    {
        "id": 1,
        "title": "Bruksanvisning Foamglass",
        "author": "Bluppfisk"
    }
]

Here's the API view:

class CharAPIView(generics.RetrieveAPIView):
    queryset = Char.objects.all()
    serializer_class = CharSerializer

and the Serializers:

class CharSerializer(serializers.ModelSerializer):
    locations = serializers.SerializerMethodField()

    class Meta:
        model = Char
        fields = ('id', 'name', 'locations',)
        depth = 1

    def get_locations(self, obj):
        qset = CharInSource.objects.filter(char=obj)
        return [CharInSourceSerializer(m).data for m in qset]


class CharInSourceSerializer(serializers.ModelSerializer):
    class Meta:
        model = CharInSource
        fields = ('page', 'source',)

The problem is I do not know how to hook into the generics.RetrieveAPIView class so it will include a list of relevant sources. I've been digging through the source, but I cannot figure out how to even get the pk value.

In the end, I ended up solving it as follows, by overwriting the retrieve method of my view.

class CharAPIView(generics.RetrieveAPIView):
    queryset = Char.objects.all()

    def retrieve(self, *args, **kwargs):
        instance = self.get_object()
        char = CharSerializer(instance).data
        qset = Source.objects.all()
        sources = [SourceSerializer(m).data for m in [i for i in instance.location.all()]]

        return Response({
            'char': char,
            'sources': sources,
        })

This could be accomplished with another SerializerMethodField on your CharSerializer and creating a SourceSerializer ; no extension of the base methods to GenericAPIView or RetrieveModelMixin .

def SourceSerializer(ModelSerializer):
    class Meta:
         model = Source
         fields = ('id', 'title', 'author') # assuming author is not also a
                                            #  ForeignKey, otherwise returns an id

def CharSerializer(...):
....
sources = SerializerMethodField()
def get_sources(self, obj):
    return SourceSerializer(
         Source.objects.filter(chars__in=[obj.id]).distinct(), 
             many=True).data
class Meta:
    fields = (...,'sources')

Assuming the attribute to the MTM model related_name is chars you can use chars__in and pass a list of Char ids; which in this case is the single char we are referencing. This would however contain the sources within each char object instead of outside as you had indicated by the question. However, I imagine you would want to know which sources have which char, as my solution would provide.

Without seeing the exact structure of your models, I cannot be certain of exactly how you should retrieve the Source objects. I feel like you could also replace the queryset with obj.sources.all() instead of the clunky __in query in the SourceSerializer .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM