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()),
Related
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 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?
I have created a custom field in the client serializer.
The value of this field is calculated by a complex serializer method.
class ClientsStatsSerializer(serializers.ModelSerializer):
"""
Serializer shows total_spend for 2019 by client.
"""
refs_count = serializers.SerializerMethodField()
total_spend_2019 = serializers.SerializerMethodField()
class Meta:
model = Company
ordering = ('total_spend_2019',)
fields = [
'id',
'legal_name',
'refs_count',
'total_spend_2019',
]
def get_total_spend_2019(self, obj):
...
I would like to get the output sorted by the value of total_spend_2019. It looks like I cannot do it here with a simple ordering = ('total_spend_2019',) I cannot do it either in the model, neither in the view.
EDIT: It would be great to have a generic solution that would work with any SerializerMethodField.
Current view is as such:
class ClientsStatsViewSet(viewsets.ViewSet):
def list(self, request):
queryset = request.user.company.clients.all()
client_id = self.request.query_params.get('client_id', None)
if client_id is not None:
queryset = queryset.filter(pk=client_id)
serializer = ClientsStatsSerializer(queryset, many=True)
return Response(serializer.data)
Any idea how to solve this?
Thank you very much!!
All right, I managed to solve it with the following:
class ClientsStatsViewSet(viewsets.ViewSet):
def list(self, request):
queryset = request.user.company.clients.all()
client_id = self.request.query_params.get('client_id', None)
if client_id is not None:
queryset = queryset.filter(pk=client_id)
serializer = ClientsStatsSerializer(queryset, many=True)
serializer_data = sorted(
serializer.data, key=lambda k: k['total_spend_2019'], reverse=True)
return Response(serializer_data)
I don't know if it is the most performant way to do this, but it works.
Also you can try create your custom model manager. This is popular decision for ordering data:
models.py
class CustomManager(models.Manager):
def get_queryset(self):
queryset = super().get_queryset()
return queryset.filter(...).order_by(...)
class Company(models.Model):
...
objects = models.Manager
custom_manager = CustomManager()
views.py
class CompanyViewSet(viewsets.ViewSet):
...
queryset = Company.custom_manager.all()
...
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)
Here's the situation. I got a list on my Django REST API: /playerslist/
It returns me a list of players just like this one:
http://pastebin.com/JYA39gHT
This is exactly what I want for the moment. But now, I need this:
Going for /playerslist/1/ gives me different infos for the Player Number 1. The list is here only for listing players with basic informations. But I need detailed view for players, containing info from other models and with different serialization, it must be a basic issue, but as I'm totally new to Django and Python in general, I must misunderstanding something.
Here is my Viewset:
class PlayersListViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
http_method_names = ['get', 'post']
pagination_class = None
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
def get_queryset(self):
queryset = Player.objects.all()
team_id = self.request.query_params.get('team', None)
if team_id:
try:
queryset = queryset.filter(team=team_id)
except ValueError:
raise exceptions.ParseError()
return queryset
How can I achieve this ? Must I use #detail_route to have something like playerslist/1/detail ? I've already tried but DRF's documentation only show a single example and it's not clear at all for me.
You can override the methods retrieve (returning one instance) or list (returning list obviously) as shown in first example in http://www.django-rest-framework.org/api-guide/viewsets/.
class PlayersListViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
http_method_names = ['get', 'post']
pagination_class = None
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
def get_queryset(self):
queryset = Player.objects.all()
team_id = self.request.query_params.get('team', None)
if team_id:
try:
queryset = queryset.filter(team=team_id)
except ValueError:
raise exceptions.ParseError()
return queryset
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = PlayerDetailSerializer(instance)
return Response(serializer.data)
Where PlayerDetailSerializer is another serializer with different fields (whatever you need) and there is no need to specify it in serializer_class.
To get different results when you do a 'detail' view, you want to change the serializer when doing a 'retrieve' call. I've done this with a custom mixin for a ModelViewSet, which expects a special "detail_serializer_class":
class DifferentDetailSerializerMixin(object):
"""
For a viewset, mix this in to use a different serializer class
for individual 'retrieve' views, different from the standard
serializer for lists.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.detail_serializer_class(instance, context=self.get_serializer_context())
return Response(serializer.data)
Your viewset is simply:
class PlayersListViewSet(DifferentDetailSerializerMixin, viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
detail_serializer_class = PlayersDetailSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
Here, PlayersDetailSerializer is another Serializer that has more fields that you want to return.
As an aside, if you want to support optional filtering by teams, I would strongly recommend using django-filter. That way you don't have to worry about validation etc. Once installed, it's simply a case of adding this to your viewset:
filter_backends = (filters.OrderingFilter, filters.DjangoFilterBackend, )
filter_fields = ['team']
See the docs for more info.