0

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.

I.e. 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.

2 Answers 2

1

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,
        })
Sign up to request clarification or add additional context in comments.

1 Comment

Great you solved it! this is exactly what I had envisioned, I'm sorry I didn't get to writing it for you. Very glad you have it figured.
0

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.

7 Comments

Thanks for you answer, Nicholas. But won't that create a source field for every Char entry in my JSON, rather than appending a list of sources as a 'reference'?
okay perhaps I misunderstood what you were trying to achieve. I assumed because you were referencing a RetrieveModelApiView and getting the pk that you wanted the sources that belong to each individual Char. Are you intending on retrieving a list of Chars and returning any Sources that belong in any of them?
Yes; I need the JSON to include a separate section with all the referenced Sources in order to avoid repeating the same information for every Char object.
your desired response is contradictory to what you are trying to achieve. If you visit the url for a specific char id, it will return one char, not an array. In that case, my solution does work. However, if you want to pass more than one char into the the view, you could assign a custom list_route and manage either passed in data or query string arguments, create a new serializer that returns a list of chars and a list of sources or you can use a ListApiView and override def list(request, *args, **kwargs)
so perhaps clarify what you are trying to achieve and I could provide a new answer
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.