Lately, I faced a problem while working on a RESTful API using Django and DRF, my mentor was thinking that it was solvable in one line of code, but then we googled the issue only to find that it had been addressed in several questions on Stackoverflow with no accepted answer, and BOOM that is where the adventure started.
What was the problem?
Actually the problem at first glance seems easy, Paginating a set of objects inside another object. Suppose we have an
Author, and a
Book with a foreign key to the
Author, simple and straight forward. Now, we want to send a response containing the list of authors we are holding in our beloved database, one of the instances is representing a legendary author that has wrote a thousand books, well of course we don’t want the response to display thousand books all at once and leave no space for any other content to be displayed.
At this point we want to do something that is solvable in many ways, the most intutive solutions were:
- Create a separate endpoint for books and apply pagination to this endpoint.
- Find a way to paginate books inside the author instance. The first solution is easy, and won’t actually take five minutes to implement, but this is not our goal here, instead, we want to make sure that our code is as clean as possible. Additionally, we wanted the books to be displayed within the author’s view not a separate one. Finally, we have a very challenging mind so we decided that we will go for the second option, yes we love mystery.
Our Solution to the problem
After a decent amount of time I finally found the solution to my problem and I texted my mentor after he was home to celebrate with him wohooo we have done it, I know you don’t care about those details but I am a talkative person so apologies, let’s head to the solution: Firstly we should think, where is the only place that we will be having control on the list of books related to that Author? You’re right,
AuthorSerializer Here is our
Pretty simple! Cool, then we should write the function that can access the author and its books inside
AuthorSerializer, just a normal function that has nothing different:
Hmmmmm, let’s take this piece of code bit by bit, first the function is expecting 2 arguments self and obj. Firstly, we decided that we want the number of books shown per page is 10:
page_size = 10
Then we used the Paginator found in django.core.paginator to paginate our desired iterable object, which is books list in our case. So we extract the books from obj and pass it to the Paginator constructor along with the page size:
paginator = Paginator(obj.books.all(), page_size)
Now we have in paginator many pages, each page includes 10 instances of
Book, that we can access by number like this:
books = paginator.page(1)
After that we serialize the books to make sure they match our
serializer = BookSerializer(books, many=True)
And at the end we just return the serialized books to be used by
AuthorSerializer. Seems cool but we have missed something, we should actually tell our
AuthorSerializer that this function is the one used to serialize books for you, so we have to add one more line to our
AuthorSerializer, and we will end up having this:
And we are done, now you can paginate objects inside another object ! Now let’s have a look on some tweaks that we can add to make our code more let’s say dynamic, for now our
page_size is fixed to 10 and no way to change but hard coding, so let’s change this a bit, one way that I’ve found suitable to get the page size dynamically is to receive it in the request query params, by doing this we can return a different response in terms of books pagination for each request:
page_size = self.context[‘request’].query_params.get(‘size’) or 10
or 10 is used to provide a default value in case size was not passed. We can do the same for requesting which page to access, instead of doing this:
books = paginator.page(1)
we can do this:
page_number = self.context['request'].query_params.get('page') or 1
books = paginator.page(page_number)
Now the Serializer will be like:
Finally, this how we solved the problem we faced, if you’ve faced the same problem and if you’ve found a better solution please let us know and we can update our blog.
Also if you want another similar example you can checkout my answer to this question https://stackoverflow.com/questions/40806879/django-rest-framework-paginate-nested-object/49677960#49677960 and maybe hit an up vote if you like it :D .
Please if you have any questions regarding this topic leave it in the comments below and I’ll be more than happy to answer.