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