Django rest how to do pagination with PageNumberPagination - django

I wrote the following codes. But when I go to the url posts?page=1, it still shows all of the Post model objects. What should I do?
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
ursl.py
path('posts', views.posts_view, name='posts_view')
views.py
#api_view(['GET'])
def posts_view(request):
posts = Post.objects.all()
serializer = PostSerializer(posts, many=True)
return Response(serializer.data)

You are writing an function based view, so that is why you need to do everything manually. If possible, you should rely on the provided generic base api views instead of attempting to re-write this:
class PostView(generics.ListAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
path('posts', PostView.as_view(), name='posts_view')
To do it manually you are going to need to create and invoke the pagination just like the base classes do:
def my_view(request):
qs = Post.objects.all()
pagination = PageNumberPagination()
page = pagination.paginate_queryset(qs, request)
serializer = PostSerializer(page, many=True)
return pagination.get_paginated_response(
serializer.data
)

Related

custom pagination not woking in apiview of Django rest

I have a custom pagination class in a separate file and until now I have been importing it in a ListAPIView, but this time I tried with APIView, but it didn't work.
My pagination class:
class CustomPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'page_size' : 15,
'results': data
})
I am trying to use custom pagination because I can have the count of the objects as well.
My view where I try to implement the pagination:
from apps.products.api.pagination import CustomPagination
class CouponView(APIView):
permission_classes = [AllowAny]
#pagination_class = CustomPagination
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc, request)
serializer = CouponSerializer(result_page,many=True)
return Response (serializer.data,status=status.HTTP_200_OK)
APIView doesn't support this feature. You'll either need to leverage ListApiView or similar generics (such as RetrieveApiView), or use the ReadOnlyModelViewSet. These both support pagination, and give you get_object.
Your code seems to be implementing the basic ViewSet features (list + retrieve). The issue with the API views is that these two both use get, so they can't be combined into one view. Using a viewset will solve that.
class CouponReadOnly(ReadOnlyModelViewSet):
pagination_class = SomeClass
serializer_class = CouponSerializer
queryset = Coupon.objects.all()
And then use a Router in your urls.py to link it up, and let the router generate the routes.
# in your urls.py --------------
router = SimpleRouter()
router.register("coupons", CouponReadOnly, "coupon")
urlpatterns = [...] # existing routes
urlpatterns += router.urls
If you want to do this manually with 2 views, then you can use the generics and manually create the paths, like you already are:
class CouponListView(ListAPIView):
# you said this one already works
class CouponGetView(RetrieveAPIView):
queryset = Coupon.objects.all()
serializer_class = CouponSerializer
urlpatterns = [
path("coupons/", CouponListView.as_view(), name="coupon-list"),
path("coupons/<int:pk>", CouponGetView.as_view(), name="coupon-get"),
]
You should use get_paginated_response in GenericAPIView for custom pagination.
from apps.products.api.pagination import CustomPagination
from rest_framework.generics import GenericAPIView
class CouponView(GenericAPIView):
permission_classes = [AllowAny]
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
self.pagination_class = CustomPagination
page = self.paginate_queryset(abc)
serializer = serializer(page, many=True, context={'request': request})
return self.get_paginated_response(serializer.data)
However, let apply the correct RESTFul API to get list and detail, so you will not custom more.
List coupons: GET /api/coupons/
Get coupon detail: GET /api/coupons/<coupon_id/
class CouponView(ListAPIView, RetrieveAPIView):
permission_classes = [AllowAny]
pagination_class = CustomPagination
queryset = Coupons.objects.all()
serializer_class = CouponSerializer
I did the following and it worked:
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc,request)
serializer = CouponSerializer(result_page,many=True)
# return Response (serializer.data,status=status.HTTP_200_OK)
return paginator.get_paginated_response(serializer.data)

How do you paginate a viewset using a paginator class?

According to the documentation:
Pagination is only performed automatically if you're using the generic views or viewsets
But this doesn't seem to be the case. Here's what I have for my viewset:
views.py
class EntityViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Entity.objects.all()
serializer = EntitySerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Entity.objects.all()
entity = get_object_or_404(queryset, pk=pk)
serializer = EntitySerializer(entity)
return Response(serializer.data)
Here's my urls
entity_list = views.EntityViewSet.as_view({'get':'list'})
entity_detail = views.EntityViewSet.as_view({'get':'retrieve'})
...
url(r'^entity/$', entity_list, name='entity-list'),
url(r'^entity/(?P<pk>[0-9]+)/$', entity_detail, name='entity-detail'),
...
This is my pagination class
class PagePaginationWithTotalPages(pagination.PageNumberPagination):
page_size = 30
page_size_query_param = 'page_size'
max_page_size = 1000
def get_paginated_response(self, data):
return Response({
'next': self.get_next_link(),
'previous': self.get_previous_link(),
'count': self.page.paginator.count,
'total_pages': self.page.paginator.num_pages,
'results': data
})
and I set it in settings.py
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated'
],
'DEFAULT_PAGINATION_CLASS': 'myapp.pagination.PagePaginationWithTotalPages',
'UNICODE_JSON': False,
}
Now while this works for ListAPIView it doesn't appear to work for my viewset. Is there a step that I'm missing?
For reference this works fine:
class EntitiesView(ListAPIView):
serializer_class = EntitySerializer
def get_queryset(self):
parameters = get_request_params(self.request)
qs = qs.filter(**parameters).distinct()
return qs
EDIT:
Changing it to use ModelViewset appears to have done the trick
class EntityViewSet(viewsets.ModelViewSet):
queryset = Entity.objects.all()
serializer_class = EntitySerializer
def list(self, request):
queryset = self.queryset
parameters = get_request_params(self.request)
if 'ordering' in parameters:
queryset = queryset.order_by(parameters['ordering'])
del parameters['ordering']
queryset = queryset.filter(**parameters).distinct()
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
entity = self.get_object()
serializer = EntitySerializer(entity)
return Response(serializer.data)
Pagination is only performed automatically if you're using the generic views or viewsets
You seem to have missed the next sentence from the documentation:
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.ListModelMixin and generics.GenericAPIView classes for an example.
As pointed by the documentation, the ListModelMixin will show you that you didn't call the paginate_queryset / get_paginated_response and thus did bypass the pagination as well as the filtering.
As pointed in the comments, you should consider ModelViewSet and define the required queryset to get it automatically included.

How do get custom pagination class working for django rest framework v3.6

I'm trying to implement a custom pagination class on a ViewSet, as per the docs, but the pagination settings are just not doing a single thing. Here's the code for my ViewSet.
from rest_framework import status, permissions, viewsets
from rest_framework.pagination import PageNumberPagination
class ProductViewSetPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 1000
class ProductViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ProductSerializer
pagination_class = ProductViewSetPagination
# ...
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I even added some defaults to the settings.py file, but I'm still getting all the product instances on a single page on the product-list view. I've tried adding page and page_size query parameters to the URL; this doesn't change anything.
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
What am I missing?
The pagination added in the super list, so try:
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
return super(ProductViewSet, self).list(request)

Django REST Framework viewset doesn't obey pagination_class

I've successfully implemented a custom pagination_class for a certain ModelViewSet in my app, but I'm having issues implemeting a separate custom pagination_class for a basic ViewSet.
Here's the working pagination_class and related ModelViewSet:
class Model1Pagination(PageNumberPagination):
page_size = 10
page_size_query_param = 'page_size'
max_page_size = 100
class Model1ViewSet(viewsets.ModelViewSet):
"""
API endpoint that returns the instances of Model1
"""
queryset = Model1.objects.all()
serializer_class = Model1Serializer
pagination_class = Model1Pagination
#list_route()
def extra(self, request)
....
return <something>
And this is the pagination_class and ViewSet which isn't working:
class Model2Pagination(PageNumberPagination):
page_size = 1
page_query_param = 'page_size'
max_page_size = 1
class Model2GenericViewSet(viewsets.ViewSet):
"""
API endpoint to return the instances of Model2
"""
def get_queryset():
return Model2.objects.all()
pagination_class = Model2Pagination
def list(self, request):
queryset = self.get_queryset()
serializer = Model2Serializer(queryset, many=True, context={'request': request})
return Response(serializer.data)
I suppose I could alter the Model2GenericViewSet to function as a ModelViewSet, but I was hoping to avoid the refactor, and the DRF docs mention:
Pagination is only performed automatically if you're using the generic views or viewsets
So I figured using a ViewSet would be alright. I didn't originally specify a get_queryset() on the ViewSet, and my initial thought was that I needed to do so, however adding that didn't affect behavior.
After some testing with the Model1Pagination, I'm not seeing pagination apply to the results of the extra() method in the Model1 viewset- it's not a method I need pagination for, so I only just found this out.
Does the doc quote above mean that pagination literally only applies to built-in viewset methods?
The ViewSet class inherits from APIView. The relation is:
View(in Django) -> APIView -> ViewSet
The ModelViewSetclass inherits from GenericViewSet . The relation is:
View(in Django) -> APIView -> GenericAPIView -> GenericViewSet -> ModelViewSet
pagination_class is add in GenericAPIView, so you can't use it in a class inherits from APIView.You can try viewsets.GenericViewSet.
Return paged response like:
def list(self, request, *args, **kwargs):
students = Student.objects.all()
page = self.paginate_queryset(students)
if page is not None:
serializer = StudentSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = StudentSerializer(students, many=True)
return Response(serializer.data)
You have to,
...
return self.get_paginated_response(serializer.data)
manually, if you overwrite list().

Django filter not working

my filter isn't working Whenever I access http://localhost:8080/payables/invoices/?status=NOT_PAID It just returns all the invoices. I have no runtime error, the parameter I enter simply seems to be ignored. I really don't understand, other than that, it works well.
views.py
class InvoiceViewSet(viewsets.ViewSet):
serializer_class = InvoiceSerializer
filter_backend = filters.DjangoFilterBackend
filter_fields = ('status','supplier',)
def list(self,request,):
queryset = Invoice.objects.filter()
serializer = InvoiceSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Invoice.objects.filter()
invoice = get_object_or_404(queryset, pk=pk)
serializer = InvoiceSerializer(invoice)
return Response(serializer.data)
class InvoiceItemViewSet(viewsets.ViewSet):
serializer_class = InvoiceItemSerializer
def list(self,request,invoice_pk=None):
queryset = InvoiceItem.objects.filter(invoice=invoice_pk)
serializer = InvoiceItemSerializer(queryset,many=True)
return Response(serializer.data)
def retrieve(self,request,pk,invoice_pk):
queryset = InvoiceItem.objects.filter(pk=pk,invoice=invoice_pk)
invoice_item = get_object_or_404(queryset,pk=pk)
serializer = InvoiceItemSerializer(invoice_item)
return Response(serializer.data)
url.py
from django.conf.urls import url, include
#viewset
from rest_framework_nested import routers
from payables.views import InvoiceViewSet,InvoiceItemViewSet
router = routers.SimpleRouter()
router.register(r'invoices', InvoiceViewSet,base_name='invoices')
invoice_item_router = routers.NestedSimpleRouter(router,r'invoices',lookup='invoice')
invoice_item_router.register(r'items', InvoiceItemViewSet, base_name='invoice_items')
urlpatterns = [
url(r'^',include(router.urls)),
url(r'^',include(invoice_item_router.urls))
]
It is because you are explicitly creating the queryset and hence the filter backend is never used:
queryset = Invoice.objects.filter()
I suggest looking at ModelViewSet. In that case you just have to pass queryset at the view level and rest will be taken care of.
instead of queryset = Invoice.objects.filter()
with queryset = self.filter_queryset(self.get_queryset()).filter()
instead of queryset = Invoice.objects.filter()
use queryset = self.get_queryset()
self.get_queryset() returns the filtered object list