django filter on APIView - python

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)

Related

Creating custom Search filter in Django API view

I have my custom API view and I want to use Search filter in this view, but generic filter dodn't work so i want to create custom one but It doesn't work and I dont know where is problem.
view
class TaskIndexAPIView(APIView):
filter_backends = (CustomSearchFilter,)
search_fields = ('name', 'description', 'user__username')
def get_queryset(self):
return Task.objects.all()
def get(self, request):
tasks = self.get_queryset()
for i in tasks:
if i.date <= date.today():
i.delayed = 'This task is delayed'
i.save()
else:
i.delayed = ''
i.save()
serializer = IndexSerializer(tasks, many=True)
return Response(serializer.data)
My custom search filter
search_filter
class CustomSearchFilter(filters.SearchFilter):
def get_search_fields(self, view, request):
if request.get_queryset.get('name', 'user'):
return ['name', 'user']
return super(CustomSearchFilter, self).get_search_fields(view, request)
In your context, the difference between APIView and generic view is, the generic view has a method called filter_queryset() which handles the filtering and searching operations.
So here in your view, we need to include the same.
class TaskIndexAPIView(APIView):
filter_backends = (CustomSearchFilter,)
search_fields = ('name', 'description', 'user__username')
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
def get_queryset(self):
return Task.objects.all()
def get(self, request):
the_filtered_qs = self.filter_queryset(self.get_queryset())
tasks = the_filtered_qs
for i in tasks:
if i.date &lt= date.today():
i.delayed = 'This task is delayed'
i.save()
else:
i.delayed = ''
i.save()
serializer = IndexSerializer(tasks, many=True)
return Response(serializer.data)
you will get the filtered queryset as
the_filtered_qs = self.filter_queryset(self.get_queryset())

Combine ListModelMixin with APIView to show pagination

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)

Get max value from django rest

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()),

How to have different results for 'list' (players/) and 'detail' (players/{id})?

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.

How to Pass kwarg to Filter, Getting Error Object is not Callable

In Django Rest Framework want to pass a kwarg to my ClubFilter class from within my view.
class ClubView(ListCreateView):
queryset = Club.objects.all()
serializer_class = ClubSerializer
filter_backends = (DjangoFilterBackend,)
filter_class = ClubFilter
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_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)
So I tried added self.filter_class = ClubFilter(**kwargs) to the list methoud but I get the error:
'ClubFilter' object is not callable
How can I pass in kwarg to my filter from the view?
filter_class must be an class, it can't be class instance. So you must pass that variable when filter object is created.
Creating of filter object is done in filter backend, so you should subclass your filter backend and provide in it some method that can take your kwargs.
Im assuming that you're using DjangoFilterBackend. You can then subclass it like this:
class MyFilterBackend(DjangoFilterBackend):
def filter_queryset(self, request, queryset, view):
filter_class = self.get_filter_class(view, queryset)o
if filter_class:
if hasattr(view, 'get_filter_kwargs'):
filter_kwargs = view.get_filter_kwargs(queryset=queryset)
else:
filter_kwargs = {'queryset': queryset}
return filter_class(request.query_params, **filter_kwargs).qs
return None
That will allow you to create method get_filter_kwargs inside your view, that should return all kwargs that shuld be passed into your filter class. Remember to pass also queryset that is provided in kwarg for that method.
You can create custom filter backend
filters.py
class MyFilterBackend(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, **kwargs).qs
return queryset
and use it instead DjangoFilterBackend

Categories

Resources