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
Related
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.
I want to get query list with one key more values. For example,
http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_mother=master&hardware_model_mother=MasterModel1&hardware_model_child=SlaveModel1
Then i can get query list of device_type=1,hardware_model_child=SlaveModel1,hardware_model_mother=master
and device_type=1,hardware_model_child=SlaveModel1,hardware_model_mother=MasterModel1.
I need a list of fields that's why i didn't use the function in_bulk().
I found some filters in django-filter's doc, here's the link:
https://django-filter.readthedocs.io/en/latest/ref/filters.html
I choose MultipleChoiceFilter, it will use OR, that's what i wanted.
Here's my filter's code:
from django_filters import FilterSet, MultipleChoiceFilter
from Device_set.views.DeviceModelFilter import DeviceModelFilter
from .head import *
# one key for multiple values
class DeviceModelFilter(FilterSet):
MOTHER_CHOICES, CHILD_CHOICES = DeviceModelFilter().MyChoices()
hardware_model_mother = MultipleChoiceFilter(choices=MOTHER_CHOICES)
hardware_model_child = MultipleChoiceFilter(choices=CHILD_CHOICES)
class Meta:
model = device_model
fields = ['hardware_model_mother', 'hardware_model_child']
and my ListAPIView:
class DeviceModelListView(ListAPIView):
permission_classes = [Developer | IOTWatch | IsAdminUser]
serializer_class = DeviceModelListSerializer
queryset = device_model.objects.all()
filter_backends = (SearchFilter, DjangoFilterBackend,)
filter_class = DeviceModelFilter
search_fields = ('id', 'name')
filterset_fields = ('hardware_model_mother', 'hardware_model_child')
def list(self, request, *args, **kwargs):
dtype = self.request.query_params.get('device_type')
if dtype is not None:
queryset = self.queryset.filter(device_type__icontains=dtype)
else:
queryset = self.queryset
queryset = self.filter_queryset(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)
def get(self, request, *args, **kwargs):
return Response(Return_msg(self.list(request)))
when url is http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_mother=master&hardware_model_mother=MasterModel1
The result is right:
right response
However, when url is http://127.0.0.1:8000/management/device/model/list/?device_type=1&hardware_model_child=SlaveModel1
It's wrong:
false
Traceback:
Traceback
It cost me about one day to solve this problem, could you please tell me how to fix it, or anther way to implement it?
When I set a ViewSet with both filtering and list() function -> filtering stops work:
class ClientViewSet(ModelViewSet):
serializer_class = ClientSerializer
queryset = Client.objects.all()
filter_class = ClientFilter
def list(self, request):
serializer = ClientSerializer(self.queryset, many=True)
return JsonResponse(serializer.data, safe=False)
Here is my filter class:
class ClientFilter(FilterSet):
type = CharFilter(name='type', lookup_expr='iexact')
parent_id = NumberFilter(name='parent__id')
title = CharFilter(name='title', lookup_expr='icontains')
class Meta:
model = Client
fields = {
'type', 'parent_id', 'title'
}
Please note
that without a list() method filtering works perfectly, I checked that thousand times. I'm 100 % sure that list() is exactly what causing the issue, I just don't know why and what exactly to do to solve this conflict.
You should use filter_queryset method:
def list(self, request):
queryset = self.filter_queryset(self.queryset)
serializer = ClientSerializer(queryset, many=True)
return JsonResponse(serializer.data, safe=False)
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)
I am using django rest framework and instead of getting the complete list of an object, I only want to get a specific value, like max(date) for example. Here is the code I am using:
My Serializer
class MoodSerializer(serializers.ModelSerializer):
class Meta:
model = Mood
fields = ('date', 'rating')
def create(self, validated_data):
return Mood.objects.create(**validated_data)
My View
class MoodList(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
class MoodDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
My URLS
url(r'^mood/$', views.MoodList.as_view()),
url(r'^mood/(?P<pk>[0-9]+)/$', views.MoodDetail.as_view()),
So if fire a GET on "max_mood" I want the latest Mood entry from the db.
Instead of Mood.objects.all(), why not sort Mood objects by some_criteria and get the first one (which will be the max one). Like this:
mood = Mood.objects.order_by('-some_criteria').first()
There is another way but it requires additional queries:
from django.db.models import Max
maximum = Mood.objects.aggregate(m=Max('some_criteria')).get('m')
max_mood = Mood.objects.get(some_criteria=maximum)
view
class MaxMoodView(generics.RetrieveAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def retrieve(self, request, *args, **kwargs):
mood=self.get_queryset().order_by('-date').first()
return Response(self.get_serializer(instance=mood))
urls
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),
Alright I got it working with the help of the comments:
view:
class MaxMoodView(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def get(self, request, format=None):
mood=self.get_queryset().order_by('-date').first()
serializer = MoodSerializer(mood)
return Response(serializer.data)
urls:
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),