7

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!

musashiXXX
  • 4,192
  • 4
  • 22
  • 24

2 Answers2

15

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)
Kevin Brown-Silva
  • 40,873
  • 40
  • 203
  • 237
  • Passing in the context did indeed fix hyperlinking. I will continue looking for a clean way to serialize the models but your answer pretty much answers my question (and gives me a bit more to read up on.) Thanks! – musashiXXX May 21 '14 at 16:45
  • This solution gave me a good idea for a problem thanks – Aldo Okware Aug 15 '20 at 22:44
-1

Add files to the DirObjSerializer as realated field with option many=True. For more info: http://www.django-rest-framework.org/api-guide/relations

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

 class FileObj(models.Model):
    [...]
    parent = models.ForeignKey(DirObj, related_name='files')
    [...]


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

class DirObjSerializer(serializers.HyperlinkedModelSerializer):
    [...]
    parent = serializers.HyperlinkedRelatedField(
        view_name = 'dirobj-detail')
    files = serializers.HyperlinkedRelatedField(many=True,
        view_name = 'fileobj-detail')

    class Meta:
        model = DirObj
YAtOff
  • 2,365
  • 1
  • 12
  • 5
  • Sure, but that returns FileObjs as nested items. I do not want them to be nested; I need them to be separate entities. Also, DirObj already has a related field vis-a-vis the "parent" field on FileObj. That related field would be called 'fileobj' in this example. – musashiXXX May 19 '14 at 12:12