Search code examples
pythondjangodjango-rest-frameworkcustomizationdjango-pagination

Django PageNumberPagination customize error if page number out of range


I'm currently trying to create an API that return list of objects with page and limit per page input from url parameter using django-rest-framework which i already done in my api view with custom Pagination

class PropertyListPagination(PageNumberPagination):
    page_size = 20
    page_size_query_param = 'page_size'

    def get_paginated_response(self, data):
        return Response({
            'code': 200,
            'data': data
        })

@api_view(['GET'])
def property_list(request):
    if request.method == 'GET':
        paginator = PropertyListPagination()
        queryset = Property.objects.all()
        context = paginator.paginate_queryset(queryset, request)
        serializer = PropertySerializer(context, many=True)
        return paginator.get_paginated_response(serializer.data)

Currently if a page is out of range( for example if i have only 2 object and i set my url to page=3 and page_size=1 then it should out of range of total objects) then in the response it will return a 404 status and in the body:

{
    "detail": "Invalid page."
}

Is there a way to customize for it to return 400 status and the following json body ?

{
    "code": 400,
    "error": "Page out of range"
}

Thank you


Solution

  • You can achieve it by overriding NotFound class and thenpaginate_queryset method,

    from rest_framework.exceptions import NotFound  
    from rest_framework.exceptions import APIException
    
    class NotFound(APIException):
        status_code = status.HTTP_400_BAD_REQUEST
        default_detail = ('bad_request.')
        default_code = 'bad_request'
    
    class PropertyListPagination(PageNumberPagination):
        page_size = 20
        page_size_query_param = 'page_size'
    
        def paginate_queryset(self, queryset, request, view=None):
            """
            Paginate a queryset if required, either returning a
            page object, or `None` if pagination is not configured for this view.
            """
            page_size = self.get_page_size(request)
            if not page_size:
                return None
    
            paginator = self.django_paginator_class(queryset, page_size)
            page_number = request.query_params.get(self.page_query_param, 1)
            if page_number in self.last_page_strings:
                page_number = paginator.num_pages
    
            try:
                self.page = paginator.page(page_number)
            except Exception as exc:
                # Here it is
                msg = {
                    "code": 400 # you can remove this line as now the status code will be 400 by default as we have override it in `NotFound` class(see above)
                    "error": "Page out of range"
                }
                raise NotFound(msg)
    
            if paginator.num_pages > 1 and self.template is not None:
                # The browsable API should display pagination controls.
                self.display_page_controls = True
    
            self.request = request
            return list(self.page)