Pagination in Django-Rest-Framework using API-View

2019-01-23 16:10发布

问题:

I currently have an API view setup as follows:

class CartView(APIView):
    authentication_classes = [SessionAuthentication, TokenAuthentication]
    permission_classes = [IsAuthenticated, ]
    api_view = ['GET', 'POST']

    def get(self, request, format=None):
        try:
            cart = request.user.cart
        except Cart.DoesNotExist:
            cart = Cart.objects.create(user=request.user)
        cart_details = cart.cart_details.all()
        serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
        return Response(serializer.data)

Here CartDetailSerializer is a normal ModelSerializer.

I want to paginate this API. However, in the docs of DRF, I found this:

If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response.

There is no example provided on how to paginate a regular APIView API.

Can anyone post an example which I can use in above scenario.

Thanks.

回答1:

When using regular APIView, you need to use Django's own Paginator class.

Django Pagination in Views

In your case you can paginate queryset before sending it to serializer.

Something like this:

def get(self, request, format=None):
    try:
        cart = request.user.cart
    except Cart.DoesNotExist:
        cart = Cart.objects.create(user=request.user)
    cart_details = cart.cart_details.all()

    paginator = Paginator(cart_details, 10)
    page = request.GET.get('page')

    try:
        cart_details = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        cart_details = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        cart_details = paginator.page(paginator.num_pages)
    serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
    return Response(serializer.data)

Hope this helps.



回答2:

While the way rayy mentions is a possibility, django-rest-framework can handle this internally with some additional features that make working with your API much easier. (*note django-rest-framework's pagination is built from the Django paginator from django.core.paginator)

Right after what you quoted is the key information to solving this problem:

Pagination is only performed automatically if you're using the generic views or viewsets. If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response. See the source code for the mixins.ListMixin and generics.GenericAPIView classes for an example.

Slight correction to what is stated there: look at the ListModelMixin.

If you go to these two links you can see the source code for the above files: generics.py mixins.py

What you need to do is include something like the following to get pagination to work in the APIView (**note: this code is untested but the idea is correct. There is also a better way of writing this rather than having to include the code in every view but I will leave that up to you to keep my answer short and understandable):

from __future__ import absolute_import
# if this is where you store your django-rest-framework settings
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response

from .models import Cart 

class CartView(APIView):
    pagination_class = settings.DEFAULT_PAGINATION_CLASS

    def get(self, request, format=None):
        #assuming every other field in the model has a default value    
        cart = Cart.objects.get_or_create(user=request.user)

        #for a clear example
        cart_details = Cart.objects.all()

        page = self.paginate_queryset(cart_details)
        if page is not None:
            serializer = CartDetailSerializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = CartDetailSerializer(cart_details, many=True)
        return Response(serializer.data)

    @property
    def paginator(self):
        """
        The paginator instance associated with the view, or `None`.
        """
        if not hasattr(self, '_paginator'):
            if self.pagination_class is None:
                self._paginator = None
            else:
                self._paginator = self.pagination_class()
        return self._paginator

    def paginate_queryset(self, queryset):
        """
        Return a single page of results, or `None` if pagination is disabled.
        """
        if self.paginator is None:
            return None
        return self.paginator.paginate_queryset(queryset, self.request, view=self)

    def get_paginated_response(self, data):
        """
        Return a paginated style `Response` object for the given output data.
        """
        assert self.paginator is not None
        return self.paginator.get_paginated_response(data)

I hope this was of more help to you and others who come across this post.



回答3:

I am using DRF version 3.6.2. You don't need to code so much. Just use this simple steps.

 class ProductPagination(PageNumberPagination):
        page_size = 5

    class product_api(generics.ListCreateAPIView):    
            queryset = Products.objects.all()
            serializer_class = product_serilizer
            pagination_class = ProductPagination

if you want search functionality by getting method, you can write below code

class ProductPagination(PageNumberPagination):
        page_size = 5

class product_api(generics.ListCreateAPIView):
    queryset = Products.objects.all()
    serializer_class = product_serilizer
    pagination_class = SearchProductPagination    

    def get_queryset(self):
        qs = super(product_search_api,self).get_queryset()
        searched_product = self.request.query_params.get('searched_product',None)
        if search:
            qs = Products.objects.filter(Q(product_name__icontains= searched_product))
        return qs


回答4:

I prefer extending the Paginator class, here is how it would look:

from rest_framework import status
from rest_framework.exceptions import NotFound as NotFoundError
from rest_framework.pagination import PageNumberPagination # Any other type works as well
from rest_framework.response import Response
from rest_framework.views import APIView

class CustomPaginator(PageNumberPagination):
    page_size = 10 # Number of objects to return in one page

    def generate_response(self, query_set, serializer_obj, request):
        try:
            page_data = self.paginate_queryset(query_set, request)
        except NotFoundError:
            return Response({"error": "No results found for the requested page"}, status=status.HTTP_400_BAD_REQUEST)

        serialized_page = serializer_obj(page_data, many=True)
        return self.get_paginated_response(serialized_page.data)

class CartView(APIView):

    def get(self, request, format=None):
        cart_details = Cart.objects.filter(user=request.user) # or any other query
        paginator = CustomPaginator()
        response = paginator.generate_response(cart_details, CartDetailSerializer, request)
        return response