We are using Django REST Framework for our API and we have a need to paginate relationship fields that return multiple items.
To demonstrate using examples similar to those in the documentation:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('order', 'title')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
Example serialized output for an Album:
{
'album_name': 'The Grey Album',
'artist': 'Danger Mouse'
'tracks': [
{'order': 1, 'title': 'Public Service Annoucement'},
{'order': 2, 'title': 'What More Can I Say'},
{'order': 3, 'title': 'Encore'},
...
],
}
This becomes problematic where there are say hundreds of tracks in the Album. Is there a way to paginate the 'tracks' in this case?
Ideally, I know that in cases like this, the 'tracks' should maybe point to an API URL that just returns the Tracks for a particular Album - which in turn can be paginated easily. The down side to that approach being the extra request (and hence delay, etc) required to get even the first few tracks. In our case, its important that we be able to get at least a few of the Tracks with the single request to the Album API and then dynamically load the rest of the tracks as and when required.
Does the DRF offer any specific feature or pattern for this? Or are there any work arounds?
Since DRF 3.1, PaginationSerializer
is not supported. Here's solution.
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5
}
serializers.py
from myapp.models import Album, Track
from rest_framework import pagination, serializers
class AlbumSerializer(serializers.HyperlinkedModelSerializer):
tracks = serializers.SerializerMethodField('paginated_tracks')
class Meta:
model = Album
def paginated_tracks(self, obj):
tracks = Track.objects.filter(album=obj)
paginator = pagination.PageNumberPagination()
page = paginator.paginate_queryset(tracks, self.context['request'])
serializer = TrackSerializer(page, many=True, context={'request': self.context['request']})
return serializer.data
class TrackSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Track
OR you can substitute def paginated_tracks
for
from rest_framework.settings import api_settings
def get_paginated_tracks(self, obj):
tracks = Track.objects.filter(album=obj)[:api_settings.PAGE_SIZE]
serializer = TrackSerializer(tracks, many=True, context={'request': self.context['request']})
return serializer.data
It even requires one less queries than above.
Answer copied from Tom's link above in case of future bit rot:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('order', 'title')
class PaginatedTrackSerializer(pagination.PaginationSerializer):
class Meta:
object_serializer_class = TrackSerializer
class AlbumSerializer(serializers.ModelSerializer):
tracks = serializers.SerializerMethodField('paginated_tracks')
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
def paginated_tracks(self, obj):
paginator = Paginator(obj.tracks.all(), 10)
tracks = paginator.page(1)
serializer = PaginatedTrackSerializer(tracks)
return serializer.data
The methods of Malcolm Box and Deepak Prakash do can help to serializer the relathionship objects, but just as @eugene said before, it only works for a single Alum. For a Albums we can do this:
serializers.py
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ('order', 'title')
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackSerializer(many=True)
class Meta:
model = Album
fields = ('album_name', 'artist', 'tracks')
depth=1
apis.py
class getAPIView(generics.ListAPIView):
serializer_class=TrackSerializer
filter_backends = (filters.OrderingFilter,)
def get_queryset(self):
queryset=Track.objects.all()
return queryset
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
serializer = self.get_serializer(page, many=True)
data=serializer.data
albums=Album.objects.values_list('album_name').all()
trackObjs=[]
albumObjs=[]
self.categoryKeyList(albums,albumObjs)
if page is not None:
for p in page:
for n,i in enumerate(albums):
if re.search(str(p.alum),str(i)):
albumObjs[n]['track'].append(p)
data={}
data['count']=self.get_paginated_response(self).data['count']
data['next']=self.get_paginated_response(self).data['next']
data['previous']=self.get_paginated_response(self).data['previous']
data['pageNumber'] = self.paginator.page.number
data['countPage'] = self.paginator.page.paginator._count
serializer=ClientsCategorySerializer(categoryObjs,many=True)
data['result']=serializer.data
return Response({'data':data,'success':'1','detail':u'获得客户列表成功'})
def categoryKeyList(self,albums,albumObjs):
for i in albums:
albumObjs={}
albumObjs['album_name']=i[0]
track=[]
albumObj['track']=track
albumObjs.append(albumObj)
Then u may will get the response:
{
data[
{
'album_name': 'The Grey Album',
'tracks': [
{'order': 1, 'title': 'Public Service Annoucement'},
{'order': 2, 'title': 'What More Can I Say'},
{'order': 3, 'title': 'Encore'},
...
},
{'album_name': 'The John Album',
'tracks': [
{'order': 1, 'title': 'Public Annoucement'},
{'order': 2, 'title': 'What sd Can I Say'},
{'order': 3, 'title': 'sd'},
...
},
......
}
I create a api in view files, and get error alert" Exception Type: KeyError Exception Value:'request'"
. Where did u set the request
?
My api code:
class getAlbumsList(APIView):
def get(self,request,token,format=None):
page = request.query_params.get('page')
tracks = Albums.objects.filter(designer=2)[:3]
serializer = AlbumSerializer(categorys, many=True)
return serializer.data