Pagination DRF not working well - django

I have a little problem with pagination when I put the default pagination in my project, somethings pages work in others pages not working for example:
this is my file settings.py for all my project
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'apicolonybit.notification_clbt.NotificationPagination.PaginationList'}
this is my Configuration app inside my project: myproject / Configuration
class ConfigurationsList(generics.ListAPIView):
"""
list configuration with current user authenticated.
"""
queryset = Configuration.objects.all()
serializer_class = ConfigurationSerializer
when I run this part in my postman, working well, but when I try to run in another module like this:
class TransactionListHistory(generics.ListAPIView):
# queryset = TransactionHistory.objects.all()
serializer_class = TransactionHistorySerializer
pagination_class = PaginationList
page_size = 2
page = 1
def get_object(self, current_user):
# User.objects.get(id=pk)
return TransactionHistory.objects.filter(agent_id=current_user.id).order_by('-id')
#classmethod
def get_object_client(cls, current_user, trans):
# User.objects.get(id=pk)
return TransactionHistory.objects.filter(user_id=current_user.id).order_by('-id')
def get(self, request, format=None):
current_user = request.user
status_trans = 6
agent_user = 2
client_user = 1
trans = {
'status': status_trans
}
typeusers = Profile.objects.get(user_id=current_user.id)
# actions agent user = show all transaction from all client users
if typeusers.type_user == agent_user:
list_trans_init = self.get_object(current_user)
serializer = TransactionHistorySerializer(list_trans_init, many=True)
get_data = serializer.data
# actions normal user (client user) = just see transactions from self user
if typeusers.type_user == client_user:
list_trans_init = self.get_object_client(current_user, trans)
serializer = TransactionHistorySerializer(list_trans_init, many=True)
get_data = serializer.data
# if not PaginationList.get_next_link(self):
# return JsonResponse({'data': get_data}, safe=False, status=status.HTTP_200_OK)
return self.get_paginated_response(get_data)
my custom file pagination like this
class PaginationList(PageNumberPagination):
page_size = 2 # when show me an error I added
offset = 1 # when show me an error I added
limit = 10 # when show me an error I added
count = 10 # when show me an error I added
page = 1 # when show me an error I added
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'results': data
})
the variables page_size and etc, then show me an error like PaginationList is not object page, I added this page_size and pass other error like PaginationList is not object offset and again added var.
well the last error show me is like this 'int' object has no attribute 'has_next'
please help me, how to add my custom pagination in my class TransactionListHistory
thanks for your attention.

You are using self.get_paginated_response() in wrong way.
from rest_framework.response import Response
class TransactionListHistory(generics.ListAPIView):
# Your code
def get(self, request, *args, **kwargs):
queryset = do_something_and_return_QuerySet() # do some logical things here and
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)
the do_something_and_return_QuerySet() is fucntion or logic of you, which return a QuerySet.
Example
class TransactionListHistory(generics.ListAPIView):
# Your code
def get(self, request, *args, **kwargs):
queryset = TransactionHistory.objects.filter(user_id=request.user.id)
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)

Related

Django passing multiple ids through URL

For POST/GET (etc) requests I have the following URL for one user:
v1/users/userid123
registered as so:
router.register(r"v1/users", accounts_views_v1.UserViewSet)
What modifications should I make so that I can pass multiple user IDs like:
v1/users/?ids=["userid123","userid456"]?
It worked without any modification for another model of mine, with the same criteria, but this one keeps giving a 403 error (before even going into the method!) when I try it
Code per request:
My viewset is insanely long, here's the beginning though
class UserViewSet(MultipleDBModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.none()
#workspace_specific
def get_queryset(self):
group_ids = json.loads(self.request.query_params.get("group_id", "[]"))
queryset = None
if group_ids:
queryset = User.objects.filter(group_memberships__group__in=group_ids).distinct()
else:
queryset = User.objects.all()
return queryset.select_related("workspace_role").prefetch_related(
"group_memberships__group_role", "group_memberships__group"
)
and my URLS:
PREFIX = settings.REST_FRAMEWORK_ROUTER_PREFIX
if PREFIX:
PREFIX = r"^" + str(PREFIX) + r"/"
router = BulkRouter()
single_object_router = SingleObjectRouter()
lazy_single_object_create_or_update_router = LazySingleObjectCreateOrUpdateRouter()
...
router.register(r"v1/users", accounts_views_v1.UserViewSet)
...
urlpatterns = [...]
GET method:
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True, exclude=None)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True, exclude=None)
return Response(serializer.data)
I found the kink, this function wraps all the others:
def permission_check_wrapper(self, request, *args, pk=None, **kwargs)
of course, when there are IDs instead of one pk, it doesn't work - how can I pass ids instead?

Django rest-framework with GenericViewSet: Filter results base on query parameters from the url

My application is using GenericViewSet with ListModelMixin. I have used filter_backends and filter_class to filter out results. (see 'list': serializers.BookingListSerializer from screenshot below)
I am working on the following brief:
Let's say I have a list of animals which are pre-filtered (using filter_backends) and then shown on UI to the user.
Users can further filter results based on some search criteria from UI (let's say name, type, color). These filterations are handled by filter_class.
In a separate Tab on UI which only shows animals of type Dogs rather than the entire collection of animals. And which can again be filtered further based on the name & color.
I must create 2 separate end-points to show both kinds of results to the user (to have more control over results...ya screw DRY!). But I can't figure out how to create them in Django as both animals and dogs use the same django modal and the filter backends and filter class are applied only to the actual modal ie. on the list of animals.
I need simple def list1(request) and def list2(request) where I can filter the query_set based on request params and my filter backends and filter classes.
api.py
class BookingViewSet(
MultipleSerializerMixin,
mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet
):
lookup_field = 'uuid'
queryset = models.Booking.objects.all()
permission_classes = [DRYPermissions, ]
filter_backends = [filters.BookingFilterBackend, DjangoFilterBackend, ]
filter_class = filters.BookingFilter
pagination_class = BookingViewSetPagination
serializer_class = serializers.BookingDetailSerializer
serializer_classes = {
'create': serializers.BookingCreateUpdateSerializer,
'update': serializers.BookingCreateUpdateSerializer,
'duplicate': serializers.BookingCreateUpdateSerializer,
'list': serializers.BookingListSerializer,
'list_drafts': serializers.BookingListSerializer,
'create_draft': serializers.BookingCreateUpdateSerializer,
'submit_draft': serializers.BookingCreateUpdateSerializer,
}
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
def update(self, request, *args, **kwargs):
booking = self.get_object()
partial = kwargs.pop('partial', False)
serializer = self.get_serializer(booking, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
booking = services.update_booking(booking, serializer.validated_data)
async('shootsta.bookings.tasks.booking_update_google_calendar_event', booking.pk)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='duplicate')
def duplicate(self, request, *args, **kwargs):
booking = self.get_object()
new_booking = services.duplicate_booking(booking)
data = serializers.BookingDetailSerializer(new_booking, context={'request': request}).data
return response.Created(data)
#list_route(methods=['GET'], url_path='list-drafts')
def list_drafts(self, request, *args, **kwargs):
# Code goes here! Here i'll get some params from url like state and title and then return filtered the results.
pass
#list_route(methods=['POST'], url_path='create-draft')
def create_draft(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
booking = services.create_booking(serializer.validated_data, constants.BookingMode.draft)
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Created(data)
#detail_route(methods=['POST'], url_path='submit-draft')
def submit_draft(self, request, *args, **kwargs):
booking = self.get_object()
booking.submit_draft(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
#detail_route(methods=['POST'], url_path='approve')
def approve(self, request, *args, **kwargs):
booking = self.get_object()
booking.approve(by=request.user)
booking.save()
data = serializers.BookingDetailSerializer(booking, context={'request': request}).data
return response.Ok(data)
filters.py
# Standard Library
import operator
from functools import reduce
# Third Party
from django.db.models import Q
from django_filters import rest_framework as filters
from dry_rest_permissions.generics import DRYPermissionFiltersBase
# Project Local
from . import models
class BookingFilterBackend(DRYPermissionFiltersBase):
def filter_list_queryset(self, request, queryset, view):
if request.user.is_role_admin:
return queryset
if request.user.is_role_client:
return queryset.filter(Q(client=request.user.client))
if request.user.is_role_camop:
return queryset.filter(Q(camera_operator=request.user))
return queryset.filter(Q(created_by=request.user))
def filter_booking_title(queryset, name, value):
"""
Split the filter value into separate search terms and construct a set of queries from this. The set of queries
includes an icontains lookup for the lookup fields for each of the search terms. The set of queries is then joined
with the OR operator.
"""
lookups = ['title__icontains', ]
or_queries = []
search_terms = value.split()
for search_term in search_terms:
or_queries += [Q(**{lookup: search_term}) for lookup in lookups]
return queryset.filter(reduce(operator.or_, or_queries))
class BookingFilter(filters.FilterSet):
title = filters.CharFilter(method=filter_booking_title)
class Meta:
model = models.Booking
fields = [
'title',
'state',
'client',
]
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
myqueryset = MyModel.objects.all() # or whatever queryset you need to serialize
queryset = self.filter_queryset(myqueryset)
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)
The key points you should notice here are,
1. The filtering process are being excecuted inside the self.filter_queryset() method, which return a QuerySet after filter applied.
2. You could use self.get_queryset() method in place of myqueryset = MyModel.objects.all() staement, which is the DRF Way of doing such things
UPDATE-1
If you want to use the default queryset , you could use the get_queryset() method as,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
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)
or simply,
class SampleViewset(.....):
#list_route(methods=['GET'])
def list_2(self, request, *args, **kwargs):
return self.list(self, request, *args, **kwargs)
I didn't quite get the question but I think you should do the same thing on your custom action that DRF does on its generic list. just call filter_queryset on your initial query for example:
class your_view(....):
...
...
def get_queryset2(self):
return YourotherModel.objects.all() ### or any thing your i.e. specific fiter on your general model
#action(methods=['GET'], detail=False)
def list2(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset2()) ### call filter_queryset on your custom query
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)

Why does Pagination not work on GenericAPIView?

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)

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.

AttributeError: '' object has no attribute 'page' DRF Pagination

This is my question asked 2 days back. I used Louis Barranqueiro's answer to solve my problem.
Now I want to add current page number as well as page_size in the serialized data. I know I have to customize the get_paginated_response method in PageNumberPagination class, but when I do that I get this error:
My code
def get_paginated_response(self, data, request):
# import pdb
# pdb.set_trace()
return Response(OrderedDict([
('next', self.get_next_link()),
('current', self.get_current_link()),
('previous', self.get_previous_link()),
('results', data)
]))
def get_queryset(self, request):
product_sync_ts = self.request.GET.get('product_sync_ts', None)
if product_sync_ts:
product = Product.objects.filter(....)
)
# return self.get_paginated_response(product, self.request)
return Response(product)
else:
content = {'details': "Bad Request"}
raise APIException400(request, content)
def get(self, request, format=None):
products = self.get_queryset(request)
serializer = SyncedProductSerializer(instance={'products': products})
# product = self.paginate_queryset(serializer, request)
return self.get_paginated_response(serializer, request)
# return self.get_paginated_response(serializer.data, request)
Error:
File "/Users/Documents/content-api/venv/lib/python2.7/site-packages/rest_framework/pagination.py", line 242, in get_next_link
if not self.page.has_next()
AttributeError: 'PaginatedProductList' object has no attribute 'page'
Some one might wanna try:
REST_FRAMEWORK = {
'PAGE_SIZE': 20,
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',}
on settings.py
with a view like
class GeneralManagementAPIView(generics.ListAPIView):
queryset = GeneralManagements.objects.all()
permission_classes = (IsAuthenticatedOrReadOnly,)
renderer_classes = (GeneralManagementJSONRenderer,)
serializer_class = GeneralManagementSerializer
def get_queryset(self):
return GeneralManagements.objects.all()
def list(self, request):
queryset = self.get_queryset()
page = self.paginate_queryset(queryset)
print("request ", request)
serializer_context = {'request': request}
serializer = self.serializer_class(
page, context=serializer_context, many=True
)
print("serializer ", serializer, "serializer.data", serializer.data )
return self.get_paginated_response(serializer.data)
You should call paginate_queryset before calling get_paginated_response
Note that the paginate_queryset method may set state on the pagination instance, that may later be used by the get_paginated_response method.
https://www.django-rest-framework.org/api-guide/pagination/
I am just adding my answer to help other users with the same problem they are facing.
Adding to the above statement, it also has a solution to your question in a simpler way.
Just pass PageNumberPagination and add request in self.paginate_queryset method.
class PaginatedProductList(APIView, PageNumberPagination):
def get(self, request):
products = Product.objects.filter(....)
return self.paginate_queryset(products, request)
So finally I found out how to solve this problem..
Below is the code(Simple and silly mistakes that I was doing)
class PaginatedProductList(APIView, PageNumberPagination):
page_size = 1000 #---crucial line
max_page_size = 1000
def get_paginated_response(self, data, page, page_num):
return Response(OrderedDict([
('count', self.page.paginator.count),
('current', page),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('page_size', page_num),
('results', data)
]))
def get_queryset(self, request):
product_sync_ts = self.request.GET.get('product_sync_ts', None)
if product_sync_ts:
product = Product.objects.filter(...)
)
return self.paginate_queryset(product, self.request)
raise APIException400(request, {'details': "Bad Request"})
def get(self, request):
page = self.request.GET.get('page', 1)
page_size = self.request.GET.get('page_size', 1000)
products = self.get_queryset(request)
serializer = SyncedProductSerializer(instance={'products': products})
return self.get_paginated_response(serializer.data, page, page_size)
Other than inheriting PageNumberPagination class in view, try defining your pagination class outside your view(as a separate class) inheriting from PageNumberPagination. and mention that class name as pagination class = YourClassName. inside your view
class PaginationClass(PageNumberPagination):
page_size = 2
and in view,
class GeneralManagementAPIView(generics.ListAPIView):
pagination_class = PaginationClass