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)
Related
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
)
I use redoc + drf_yasg to generate the documentation of my api made with django-rest-framwork.
It works well as soon as I add an entry point it appears well in the documentation.
However, I don't understand how to add in the documentation the search parameters that can be given during the query.
For example, in the list function, I can pass 2 optional parameters, an id and name:
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)
I don't see any mention of the parameters in the documentation. How can I add them?
I don't know if this helps but here is how I implemented redoc:
schema_view = get_schema_view(
openapi.Info(
title="test API",
default_version='v1',
description=f"""
# Public API
**Reserved to authenticated users**
Token Lifetime :
* ACCESS_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('ACCESS_TOKEN_LIFETIME'))}
* REFRESH_TOKEN_LIFETIME : {duration(settings.SIMPLE_JWT.get('REFRESH_TOKEN_LIFETIME'))}
### Links
[Get access token](************)
### Format
Use `?format=` option in url:
* `?format=api` (default)
* `?format=json`
""",
terms_of_service="",
contact=openapi.Contact(email="***********"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
urlpatterns = [
path('docs/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
]
You have to add your parameters to your view if you want to give info about them. I use drf_yasg with swagger but maybe this might help.
Here's an example.
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
class TechnicalDataViewSet(viewsets.ViewSet):
"""
A simple ViewSet for listing or retrieving machine.
"""
permission_classes = [permissions.IsAuthenticated]
id_machine = openapi.Parameter('id_machine', openapi.IN_QUERY, type=openapi.TYPE_INTEGER, required=False)
name = openapi.Parameter('account', openapi.IN_QUERY, type=openapi.TYPE_STRING, required=False)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def list(self, request):
id_machine = self.request.query_params.get('id_machine')
name = self.request.query_params.get('name')
queryset = TechnicalData.objects.all()
if id_machine:
queryset = queryset.filter(machine__id=id_machine)
if name:
queryset = queryset.filter(name=name)
serializer = TechnicalDataSerializer(queryset, many=True)
return Response(serializer.data)
#swagger_auto_schema(manual_parameters=[id_machine, name])
def retrieve(self, request, pk=None):
queryset = TechnicalData.objects.all()
technical_data = get_object_or_404(queryset, pk=pk)
serializer = TechnicalDataSerializer(technical_data)
return Response(serializer.data)
I want to create a subview (if this is the right term?) under a view that alters the query set for example
parent URL
mysite.com/api/sites
Child URL
mystic.com/apit/sites/open
and also each one of those URLS could be searched so
parent URL
mysite.com/api/sites/search=London
Child URL
mystic.com/api/sites/open/search=London
my parent View, serializer, and URL already exists
class SiteROView(viewsets.ReadOnlyModelViewSet):
queryset = Site.objects.all()
serializer_class = SiteSerializer
permission_classes = (IsAdminUser,)
filter_class = Site
filter_backends = (filters.SearchFilter,)
search_fields = ('location','postcode','state')
so I think I need to somehow add the suburl to that
class SiteROView(viewsets.ReadOnlyModelViewSet):
queryset = Site.objects.all()
serializer_class = SiteSerializer
permission_classes = (IsAdminUser,)
filter_class = Site
filter_backends = (filters.SearchFilter,)
search_fields = ('location','postcode','state')
def url_open:
queryset = Site.objects.filter(state='open')
Is this possible, and how would I achieve it?
Thanks
You can do that by using detail_route decorator
from rest_framework.response import Response
class SiteROView(viewsets.ReadOnlyModelViewSet):
..........
# your codes up here
#list_route(methods=['get'],url_path='open' permission_classes=[YourPermissionClass])
def open(self, request, *args, **kwargs):
# your rest of code and response
queryset = <your_filtered_queryset>
serializer = self.serializer_class(queryset, many=True)
return Response(data=serializer.data)
views.py
class variable__list(ListAPIView):
"""
get:
returns a list of variable names
"""
serializer_class = VariableSerializer
pagination_class = PageNumberPagination
page_size = 5
def get_queryset(self):
return Variable.objects.all()
def get(self, request, format=None):
# base queryset
queryset = self.get_queryset()
# return serialized data
if queryset.exists():
serializer = VariableSerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({"Returned empty queryset"}, status=status.HTTP_404_NOT_FOUND)
settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 5,
}
When I go to the endpoint api/v1/variable/?page=1,
I get the same list returned of 100 results. My understanding is this should automatically be working when I set the pagination globally in settings.py, on top of that I have ALSO defined the paginator at the class level and still nothing is being paginated. What am I doing wrong here?
Remove get and get_queryset method, if there is no custom logic inside it.
class variable__list(ListAPIView):
"""
get:
returns a list of variable names
"""
queryset = Variable.objects.all()
serializer_class = VariableSerializer
pagination_class = PageNumberPagination
page_size = 5
If you have some custom code inside it, you need to send paginated response manually
def get(self, request, format=None)
paginator = PageNumberPagination()
paginator.page_size = 10
queryset = self.get_queryset()
result_page = paginator.paginate_queryset(queryset, request)
serializer = VariableSerializer(result_page, many=True)
return paginator.get_paginated_response(serializer.data)
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