How to generate a schema for a custom pagination in django rfw with drf-spectacular?
Asked Answered
A

3

7

I am struggling to properly generate the schema for my custom pagination in django rest framework. I am using drf-spectacular for the schema generation. My custom pagination includes a total-pages field which does not come with djangos PageNumberPagination. The response is correctly serialized and returned and includes the total-pages, but the schema in my swagger docs does not include the field. Here is my code:

pagination.py

from rest_framework import pagination
from rest_framework.response import Response

class CustomPagination(pagination.PageNumberPagination):
    page_size = 10
    page_size_query_param = 'page_size'
    max_page_size = 100
    page_query_param = 'p'
    
    def get_paginated_response(self, data):
        return Response({
            'page_size': self.page_size,
            'total_objects': self.page.paginator.count,
            'total_pages': self.page.paginator.num_pages,
            'current_page_number': self.page.number,
            'next': self.get_next_link(),
            'previous': self.get_previous_link(),
            'results': data,
        })

Here is my view:

views.py

@extend_schema_view(
    get=extend_schema(
        parameters=[OpenApiParameter("q", OpenApiTypes.STR, OpenApiParameter.QUERY),],
        request=TestStandardSearchSerializer,
        responses=TestStandardSerializer
        )
    )
class TestStandardSearchView(ListAPIView):
    serializer_class = TestStandardSearchSerializer
    queryset = TestStandard.objects.all()
    pagination_class = CustomPagination
   
    def get(self, request, *args, **kwargs):
        query = self.request.query_params.get('q')
        queryset = SearchQuerySet().all().filter(content=query).order_by('acronym')
        page = self.paginate_queryset(queryset)
        serializer = self.get_serializer(page, many=True)
        return self.get_paginated_response(serializer.data)
    
    def get_serializer_class(self):
        if self.request.method == 'GET':
           return TestStandardSearchSerializer

The response schema from my swagger doc is the following:

PaginatedTestStandardList
{
count   integer                            example: 123
next    string($uri)     nullable: true    example: http://api.example.org/accounts/?p=4
previous    string($uri) nullable: true    example: http://api.example.org/accounts/?p=2
results [TestStandard{...}]
}

The standard django pagination is correctly ínserted in the schema, but not my custom pagination response. What I expected/wanted is to have my customized pagination response correctly integrated with the total-pages field on the same level as 'count', 'next' and 'previous'.

What I tried... I have a working solution with drf_yasg using the PaginatorInspector providing a custom schema. But this is not available in drf-spectacular. I also used inline_serializer with a custom response in @extend_schema_view such as:

responses={
            200: inline_serializer(
           name='PaginatedTestStandardSearchResponse',
           fields={
               'total-pages': serializers.IntegerField(),
               'results': TestStandardSearchSerializer()
           },

This resulted in a schema where total-pages is nested within results. I am using:

drf-spectacular     0.21.2
Django              3.2.12
django-rest-swagger 2.2.0
djangorestframework 3.12.4

Any help is appreciated. I just recently started with django rfw and openapi schema generation. Sorry if I had missed something obvious here.

Adept answered 10/3, 2022 at 22:49 Comment(0)
G
9

You need to overwrite the method get_paginated_response_schema in your CustomPagination. For the reference about how to compose it, you can see it on file pagination.py inside rest_framework package.

If you want to know how does that works, you could find it inside drf-spectacular package on file openapi.py, method _get_response_for_code. I hope that solve your problem.

Gehlbach answered 23/3, 2022 at 15:59 Comment(1)
It worked. Thank you. I looked into djangos pagination.py and wrote a new get_paginated_response_schema(self, schema). Is there an efficient way to append the base pagination schema? I ended up copying the code from djangos base function and inserting my total pages. I post my final funtion in the answer.Adept
A
3

I ended up with overwriting get_paginated_response(). This finally resolved my issue. Now the correct pagination parameters are shown in the swagger documentation.

This is my custom paginator:

   from rest_framework import pagination
   from rest_framework.response import Response

   class CustomPagination(pagination.PageNumberPagination):
       page_size = 10
       page_size_query_param = 'page_size'
       max_page_size = 100
       page_query_param = 'p'
    
       def get_paginated_response(self, data):
           print(data)
           print()
           return Response({
               'count': self.page.paginator.count,
               'next': self.get_next_link(),
               'previous': self.get_previous_link(),
               'page_size': self.page_size,
               'total_objects': self.page.paginator.count,
               'total_pages': self.page.paginator.num_pages,
               'current_page_number': self.page.number,
               'results': data,
           })
    
      def get_paginated_response_schema(self, schema):            
          return {
              'type': 'object',
              'properties': {
                  'count': {
                      'type': 'integer',
                      'example': 123,
                  },
                  'next': {
                      'type': 'string',
                      'nullable': True,
                      'format': 'uri',
                      'example': 'http://api.example.org/accounts/? 
                          {page_query_param}=4'.format(
                          page_query_param=self.page_query_param)
                  },
                  'previous': {
                      'type': 'string',
                      'nullable': True,
                      'format': 'uri',
                      'example': 'http://api.example.org/accounts/? 
                          {page_query_param}=2'.format(
                          page_query_param=self.page_query_param)
                  },                
                  'page_size' : {
                      'type': 'integer',
                      'example': 123,
                  },
                  'total_pages': {
                      'type': 'integer',
                      'example': 123,
                  },
                  'current_page_number': {
                      'type': 'integer',
                      'example': 123,
                  },
                  'results': schema,
              },
          }
Adept answered 25/3, 2022 at 18:37 Comment(0)
P
1

I followed the @Agung Wiyono advice when faced with the same issue of total pages, and the following code worked for me. It is almost the same code as @Anti provided, but with the use of super. This way, we only modify base methods without completely rewriting them.

class PageNumberPaginationWithCount(pagination.PageNumberPagination):
    def get_paginated_response(self, *args, **kwargs):
        response = super(PageNumberPaginationWithCount, self).get_paginated_response(*args, **kwargs)
        response.data['total_pages'] = self.page.paginator.num_pages
        return response

    def get_paginated_response_schema(self, *args, **kwargs):
        schema = super(PageNumberPaginationWithCount, self).get_paginated_response_schema(*args, **kwargs)
        schema['properties']['total_pages'] = {
            'type': 'integer',
            'example': 123,
        }
        return schema
Petiolule answered 26/5, 2023 at 13:55 Comment(2)
Is there a way to remove some of the fields form the super class. IE: super has next_page: http://exmaple.com?page=2 i want to remove that oneKavanagh
It seems, that popping unnecessary fields will work either: response.data.pop('next') in get_paginated_response and schema['properties'].pop('next') in get_paginated_response_schema. But an additional research may be needed, as a super class might expect the presence of removed fields somewhere...Petiolule

© 2022 - 2024 — McMap. All rights reserved.