2

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

Linh Nguyen
  • 3,452
  • 4
  • 23
  • 67
  • If the page is not found it should be `404` right?. Also if you want to change the `"Invalid page."` to `"Page out of range"` you can set [`invalid_page_message`](https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py#L189) of `PropertyListPagination` class. – Abdul Niyas P M Sep 06 '19 at 08:58
  • sometime frontend require api to return custom status type base on some conditions and the invalid_page_message onhave the "invalid page." string i have no idea where the detail key at – Linh Nguyen Sep 06 '19 at 09:05
  • 1
    most times the frontend must conform to the api. The api is the source of truth and the clients need to be dumb and deal with whatever is returned. – Harry Moreno Sep 09 '19 at 15:54

2 Answers2

4

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)
Md. Tanvir Raihan
  • 4,075
  • 9
  • 37
  • 70
  • This is an anti-pattern solution, you are basically overriding the Django/lib code with a copy of the current code version with your custom modifications, if the library code got updated with newer logic, you will never have this new logic because you are overriding it. Instead, you should reuse and override the super class `paginate_queryset()`. Since you have the control over raising your exception, you should name it a descriptive name like `class BadRequest(APIException):` since it is a bad request with a `400` status code. – Montaro Apr 30 '22 at 02:54
0

You can simply, wrap the pagination code in try-except, this is just a temporary solution I found.

Expect block will be executed instead of {"detail": "Invalid page."} message

try :
    context = paginator.paginate_queryset(filteredData, request)
    serializer = AuthUserSerializer(context,many=True)
    return Response(serializer.data)
except:
    # return here any message you want
    return Response({
        "code": 400,
        "error": "Page out of range"
    })
  • 1
    this is invalid because if any other errors happend(in serializer) then it will return page out of range error instead – Linh Nguyen Apr 19 '22 at 09:04
  • Yes, you are correct! as i mentioned above this was just a temporary solution, but it might be helpful, if we want to return a generalized error, such as Unknown error – Rushi Creates Jun 15 '22 at 18:43