I have a "next object" feature on my website but I use pagination. I would like add "ids" field that contains ids of all objects filtered and sorteded into the paginated response.
Everything that I tried returns only a list of current page ids.
class StandardResultsSetPagination(PageNumberPagination):
page_size = 20
page_size_query_param = 'page_size'
max_page_size = 20
def get_paginated_response(self, data, list_of_ids):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data),
('ids', list_of_ids)
]))
class RealestateViewSet(ModelViewSet):
...
pagination_class = StandardResultsSetPagination
def get_paginated_response(self, data, list_of_ids):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data, list_of_ids)
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)
return self.get_paginated_response(serializer.data, list(queryset.values_list('id', flat=True)))
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
This returns a list of 20 ids instead of all filtered objects ids.
Do you know how to do that?
you can subclass BaseFilterBackend to filter the queryset (coming from get_queryset()) by providing the ids , then appending the subclass to ModelViewSet.filter_backends.
It's not clear about where your ids comes from in your question (from URL parameters ? e.g. http://your_url?param1=value1), but a quick example may be like this :
from rest_framework.filters import BaseFilterBackend
class LimitQsetFilter(BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
ids = [1,2,3,4]
queryset = queryset.filter(pk__in=ids)
return queryset
class RealestateViewSet(ModelViewSet):
...
filter_backends = [LimitQsetFilter,]
...
Then you will see filtered list of object instances on pagination.
Related
I have a below class
class Home(viewsets.ModelViewSet):
serializer_class = HomeSerializer
queryset = Home.objects.all()
#action(detail=False, methods=['get'])
def blog_data(self, request):
queryset = Blogs.objects.filter(identifier='blog')
serializer = BlogDataSerializer(data=queryset, many=True) #other serializer specific to this method
serializer.is_valid()
return Response(serializer.data) # need pagination here for this method only
def list(self, request):
....
return Response(serializer.data)
i have overwritten list method and i only want pagination in blog_data method to have pagination with page_size and page_number(page) to be given as query params.
example:
http://localhost:8000/home?page=1&page_size=5
how would i acheive it, i have read about pagination_class = HomePagination
but i dont want it to impact list method or any other method in this class, i only want pagination in my blog_data method
pagination.py is
class HomePagination(PageNumberPagination):
page_size_query_param = 'page_size'
def get_paginated_response(self, data):
response = {
'no_of_records': self.page.paginator.count,
'no_of_pages': self.page.paginator.num_pages,
'page_size': int(self.request.GET.get('page_size')),
'page_no': self.page.number,
'results': data
}
return Response(response, status=status.HTTP_200_OK)
I have managed to solve this problem by doing
class Home(viewsets.ModelViewSet):
serializer_class = HomeSerializer
queryset = Home.objects.all()
#action(detail=False, methods=['get'])
def blog_data(self, request):
self.pagination_class = HomePagination # added this
queryset = Blogs.objects.filter(identifier='blog')
page_data = self.paginate_queryset(queryset) #added this
serializer = BlogDataSerializer(data=queryset, many=True) #other serializer specific to this method
serializer.is_valid()
paginated_data = self.get_paginated_response(serializer.data) # added this
return Response(paginated_data) # need pagination here for this method only
def list(self, request):
....
return Response(serializer.data)
Hope someone will get help from this in future.
I'm having trouble getting this to work.. I have the following Serializer:
class OwnArbeitstagListSerializer(serializers.ModelSerializer):
stundensumme = serializers.SerializerMethodField()
class Meta:
model = Arbeitstag
ordering = ['-datum']
fields = ('id', 'datum', 'in_abrechnung', 'stundensumme')
depth=0
def get_stundensumme(self, obj):
return Stunden.objects.filter(arbeitstagid=obj.id).aggregate(Sum('stunden'))['stunden__sum']
.. returning the sum of worked hours per day (The model is named "workday"). That works so far. Now I want to have a ModelViewset returning a list of workdays:
class OwnArbeitstagListViewSet(viewsets.ReadOnlyModelViewSet):
filter_class = ArbeitstagListFilter
filter_fields = ('datum',)
filter_backends = (DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter,)
ordering =["-datum"]
serializer_class = OwnArbeitstagListSerializer
def get_queryset(self):
return Arbeitstag.objects.filter(userid=self.request.user.id)
You see, I'm filtering it by User and by date (with the filterbackend). But now, I want to have an additional field which gives me the sum of the serializermethodfield "stundensumme". Its a sum of a sum. And it should only calculate the sum of the displayed objects (with datefilter applied).
I'm having trouble because (I assume) the Seriealizermethodfield only gets calculated when serializing, and I guess thats to late to get the values for my sum. I have tried this, but it cant find the serializermethodfield "stundensumme" to calculate a sum of:
class CustomPageNumberPagination(PageNumberPagination):
def get_paginated_response(self, data, summe):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('summe', summe),
('results', data)
]))
class OwnArbeitstagListViewSet(viewsets.ReadOnlyModelViewSet):
pagination_class = CustomPageNumberPagination
filter_class = ArbeitstagListFilter
filter_fields = ('datum',)
filter_backends = (DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter,)
ordering =["-datum"]
serializer_class = OwnArbeitstagListSerializer
def get_queryset(self):
arbeitstag = Arbeitstag.objects.all().filter(userid=self.request.user.id)
return arbeitstag
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
self.summe = queryset.aggregate(Sum('stundensumme'))['stundensumme__sum']
return super().list(request, *args, **kwargs)
def get_paginated_response(self, data):
return self.paginator.get_paginated_response(data, self.summe)
If you take a look on list() method implementation you will find this:
"""
List a queryset.
"""
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)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
You are right with your guess that stundensumme field only get pouplauted with serialization so you have two options:
use anotate() and add it in you defiend get_queryset() so each object will have that field and you don't need to provide an implementation for the field at serializer and you will just add the field in fields attribute as you did
in get_paginated_response() do your sum on data as it's a list returend by the serializer so you can easily manipulate it for example:
def get_summe(self, data):
sum = 0
for item in data:
sum += item['stundensumme']
return
and simply use it
def get_paginated_response(self, data):
return Response(OrderedDict([
('count', self.page.paginator.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('summe', self.get_summe(data)),
('results', data)
]))
and don't forgot to remove the extra code you added since we don't need it in either two ways
I have a APIView class for showing all the rents and posting and delete etc. Now i want search feature so i tried to use DjangoFilterBackend but it is not working. I see in documentation, it has been used with ListAPIView but how can i use it in APIView.
class Rent(APIView):
"""
List all the rents if token is not provided else a token specific rent
"""
serializer_class = RentSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('city', 'place', 'property_category',)
search_fields = ('=city', '=place')
def get(self, request, token=None, format=None):
reply={}
try:
rents = Rental.objects.all()
if token:
rent = Rental.objects.get(token=token)
reply['data'] = self.serializer_class(rent).data
else:
reply['data'] = self.serializer_class(rents, many=True).data
except Rental.DoesNotExist:
return error.RequestedResourceNotFound().as_response()
except:
return error.UnknownError().as_response()
else:
return Response(reply, status.HTTP_200_OK)
when i search the rent with the following parameters in the url, i get all the rents, instead i should get only those rents that lies in city Kathmandu and place koteshwor
http://localhost:8000/api/v1/rents?city=Kathmandu&place=Koteshwor
To use the functionality of DjangoFilterBackend, you could incorporate the filter_queryset method from GenericViewSet, which is the DRF class that inherits from APIView and leads to all specific 'generic' view classes in DRF. It looks like this:
def filter_queryset(self, queryset):
"""
Given a queryset, filter it with whichever filter backend is in use.
You are unlikely to want to override this method, although you may need
to call it either from a list view, or from a custom `get_object`
method if you want to apply the configured filtering backend to the
default queryset.
"""
for backend in list(self.filter_backends):
queryset = backend().filter_queryset(self.request, queryset, self)
return queryset
https://github.com/encode/django-rest-framework/blob/master/rest_framework/generics.py
Here If you are using APIView, There is nothing to do with filters.So you have to do like
get_data = request.query_params #or request.GET check both
Then
Rental.objects.filter(city=get_data['city'], place=get_data['place'])
In case someone is wondering how can we integrate django_filters filter_class with api_views:
#api_view(['GET'])
#permission_classes([permissions.IsAuthenticated])
def filter_data(request, format=None):
qs = models.YourModal.objects.all()
filtered_data = filters.YourFilter(request.GET, queryset=qs)
filtered_qs = filtered_data.qs
....
return response.Ok(yourData)
Adding to #ChidG's answer. All you need to do is override the DjangoFilterBackend's filter_queryset method, which is the entry point for the filter, and pass it the instance of your APIView. The important point to note here is you must declare filter_fields or filter_class on the view in order to get the filter to work. Otherwise it just return your queryset unfiltered.
If you're more curious about how this works, the class is located at django_filters.rest_framework.backends.py
In this example, the url would look something like {base_url}/foo?is_active=true
from django_filters.rest_framework import DjangoFilterBackend
class FooFilter(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)
if filter_class:
return filter_class(request.query_params, queryset=queryset, request=request).qs
return queryset
class Foo(APIView):
permission_classes = (AllowAny,)
filter_fields = ('name', 'is_active')
def get(self, request, format=None):
queryset = Foo.objects.all()
ff = FooFilter()
filtered_queryset = ff.filter_queryset(request, queryset, self)
if filtered_queryset.exists():
serializer = FooSerializer(queryset, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
else:
return Response([], status=status.HTTP_200_OK)
I want to show the pagination feature in my API and I am using APIView with multiple serializers.
I know it is very easy to show pagination with ListView.
I have seen somewhere that combining ListModelMixin and APIView works but if my code is as follows:
class ListModelMixin(object):
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)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serilaizer.data)
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
How can I combine them so I can get paginated results?
Thanks in advance!
First things first, what you are currently doing is too complex without reason.
In order to achieve a "paginatable" queryset, it is preferred to change your owned_items() and dev_items() in simple filter combinations, rather than model methods. To clarify by example:
products = BaseItem.objects.filter(owner=request.user, owned=True)
instead of
products = BaseItem.objects.owned_items().filter(owner=request.user)
That way, you can produce a single queryset which will be easier to paginate:
user_items = BaseItem.objects.filter(
Q(owner=request.user, owned=True) |
Q(owner=request.user, dev=True)
)
Note 1: You can simplify things further if you like, but that gets out of scope of your question. As food for thought, check this out:
user_items = BaseItem.objects.filter(owner=request.user).distinct()
Note 2: You should use a single serializer for a single model because what you are doing adds complexity without benefit (high risk-low reward situation)
With the above mentioned and assumed:
There are some ways to achieve what you want here:
By utilizing GeneriAPIView and ListModelMixin you can refactor your class in such a way to have a .list() method with auto-pagination:
from rest_framework import mixins, generics
class ItemsAPIView(mixins.ListModelMixin, generics.GenericAPIView,):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = OwnedItemSerializer
# You can define .get in here if you really need it.
# You can also override .list to add specific functionality
If you don't want to use the above, and you want to keep your APIView, then you can keep your get method and provide pagination for it as mentioned in this Q&A example:
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = MyNewUnifiedSerializerClass
def get(self, request):
user_items = BaseItem.objects.filter(
owner=request.user
).distinct()
page = self.paginate_queryset(user_items)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(user_items, many=True)
return Response(serializer.data)
# Now add the pagination handlers taken from
# django-rest-framework/rest_framework/generics.py
#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)
Due to the use of different serializers based on certain condition, i preferred to use APIView and override get function. I was content with APIView but now that i need pagination feature, I am having trouble to make it happen. That is why i want to switch to GenericAPIView but due to the use of multiple serializer I have no idea how can i do it.
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
UPDATE
Another way i tried is
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all items with product of this user.
"""
reply = {}
print ('request', request.META.get('REMOTE_ADDR'))
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
page = self.paginate_queryset(items)
print ('page', page) # i always get None even when pass url as api/items?page=1
if page is not None:
reply['data'].extend(ItemSerializer(page, many=True).data)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
print (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.
"""
print ('queryset', queryset)
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)
No any way is working. Where have i done mistake?
Do you really need two serializers ?
I think it may be a better choice to use a single Serializer with a custom to_representation:
class ItemSerializer(ModelSerializer):
# Your fields
def to_representation(self, instance):
data = super(ItemSerializer, self).to_representation(instance)
request = self.context.get('request')
if request and instance.is_owned_by(request.user):
return self.owner_to_representation(data, instance) # TO IMPLEMENT
return data
Then, you can use a generic view. Your code is cleaner, simpler and you do not have to worry about the pagination:
class ItemList(generics.ListAPIView):
serializer_class = ItemSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
return BaseItem.objects.owned_items()| BaseItem.objects.dev_items()
This is as simple as importing your paginator, and calling it manually in the APIView.
class PollView(views.APIView):
authentication_classes = (JSONWebTokenAuthentication, SessionAuthentication,)
paginator = CustomPagination()
def get(self, request):
queryset = Poll.objects.all()
context = self.paginator.paginate_queryset(queryset, request)
serializer = PollSerializer(context, many=True)
return self.paginator.get_paginated_response(serializer.data)
NOTE: Custom class is not necessary, you can simply import from rest_framework.pagination at the top of your script. I created a CustomPagination class, inheriting from PageNumberPagination, so that I could set the page_size query, as per docs - http://www.django-rest-framework.org/api-guide/pagination/