Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialize multiple models in a single view

Here's the scenario:

I have two models; FileObj and DirObj.

class DirObj(models.Model):
    [...]
    parent = models.ForeignKey('self')
    [...]

class FileObj(models.Model):
    [...]
    parent = models.ForeignKey(DirObj)
    [...]

And I have the following serializers:

class FileObjSerializer(serializers.ModelSerializer):
    [...]
    class Meta:
        model = FileObj

class DirObjSerializer(serializers.HyperlinkedModelSerializer):
    [...]
    parent = serializers.HyperlinkedRelatedField(
        view_name = 'dirobj-detail')
    class Meta:
        model = DirObj

And let's say that when a user browses to '/directories/[dir_id]' I want to return the file and directory content of the DirObj specified by 'dir_id' in a single view, that uses two different serializers. Right now I have (not exactly, but close enough so you get the gist) the following:

class DirContents(generics.GenericAPIView):
    def get(self, request, *args, **kwargs):
        files = FileObj.objects.filter(parent = kwargs.get('dir_id'))
        dirs = DirObj.objects.filter(parent = kwargs.get('dir_id'))
        files_serializer = FileObjSerializer(files, many = True)
        dirs_serializer = DirObjSerializer(dirs, many = True)
        response = files_serializer.data + dirs_serializer.data
        return Response(response)

This feels like an ugly hack. It also disregards any sort of hyperlinking that would be normally rendered when browsing the API (i.e., HyperlinkedRelatedFields do not appear as hyperlinks as they should.) Is there any way to serialize an arbitrary number of models and return them in a single view, without breaking the browsable API and/or having to do a (what I'm assuming to be) a bunch of extra work to get hyperlinking to work properly?

Thanks in advance!

like image 477
musashiXXX Avatar asked May 16 '14 17:05

musashiXXX


1 Answers

The issue you are facing with your current code, specifically with the links not working, is because you are not passing in any context to the serializer.

class DirContents(generics.GenericAPIView):
    def get(self, request, *args, **kwargs):
        files = FileObj.objects.filter(parent=kwargs.get('dir_id'))
        dirs = DirObj.objects.filter(parent=kwargs.get('dir_id'))

        context = {
            "request": request,
        }

        files_serializer = FileObjSerializer(files, many=True, context=context)
        dirs_serializer = DirObjSerializer(dirs, many=True, context=context)

        response = files_serializer.data + dirs_serializer.data

        return Response(response)

This is done automatically for generic views that use the mixins, but for cases like this it needs to be passed in manually.

For anyone coming here to combine two models into a single serializer:

There is no easy way to support multiple different models in one view when using the generic views. It appears as though you are not using them for filtering querysets though, so this is actually possible to do, though not in a way that would be considered "clean" by any means.

class DirContents(generics.GenericAPIView):
    def get(self, request, *args, **kwargs):
        files = FileObj.objects.filter(parent=kwargs.get('dir_id'))
        dirs = DirObj.objects.filter(parent=kwargs.get('dir_id'))

        files_list = list(files)
        dirs_list = list(dirs)

        combined = files_list + dirs_list

        serializer = YourCombinedSerializer(combined, many=True)

        return Response(serializer.data)
like image 167
Kevin Brown-Silva Avatar answered Sep 20 '22 10:09

Kevin Brown-Silva