I currently have an API view setup as follows:
class CartView(APIView):
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [IsAuthenticated, ]
api_view = ['GET', 'POST']
def get(self, request, format=None):
try:
cart = request.user.cart
except Cart.DoesNotExist:
cart = Cart.objects.create(user=request.user)
cart_details = cart.cart_details.all()
serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
return Response(serializer.data)
Here CartDetailSerializer is a normal ModelSerializer.
I want to paginate this API. However, in the docs of DRF, I found this:
If you're using a regular APIView, you'll need to call into the pagination API yourself to ensure you return a paginated response.
There is no example provided on how to paginate a regular APIView API.
Can anyone post an example which I can use in above scenario.
Thanks.
While the way rayy mentions is a possibility, django-rest-framework can handle this internally with some additional features that make working with your API much easier. (*note django-rest-framework's pagination is built from the Django paginator from django.core.paginator)
Right after what you quoted is the key information to solving this problem:
Pagination is only performed automatically if you're using the generic views or viewsets. 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.ListMixin and generics.GenericAPIView classes for an example.
Slight correction to what is stated there: look at the ListModelMixin.
If you go to these two links you can see the source code for the above files:
generics.py
mixins.py
What you need to do is include something like the following to get pagination to work in the APIView (**note: this code is untested but the idea is correct. There is also a better way of writing this rather than having to include the code in every view but I will leave that up to you to keep my answer short and understandable):
from __future__ import absolute_import
# if this is where you store your django-rest-framework settings
from django.conf import settings
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Cart
class CartView(APIView):
pagination_class = settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
#assuming every other field in the model has a default value
cart = Cart.objects.get_or_create(user=request.user)
#for a clear example
cart_details = Cart.objects.all()
page = self.paginate_queryset(cart_details)
if page is not None:
serializer = CartDetailSerializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = CartDetailSerializer(cart_details, many=True)
return Response(serializer.data)
#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 hope this was of more help to you and others who come across this post.
When using regular APIView, you need to use Django's own Paginator class.
Django Pagination in Views
In your case you can paginate queryset before sending it to serializer.
Something like this:
def get(self, request, format=None):
try:
cart = request.user.cart
except Cart.DoesNotExist:
cart = Cart.objects.create(user=request.user)
cart_details = cart.cart_details.all()
paginator = Paginator(cart_details, 10)
page = request.GET.get('page')
try:
cart_details = paginator.page(page)
except PageNotAnInteger:
# If page is not an integer, deliver first page.
cart_details = paginator.page(1)
except EmptyPage:
# If page is out of range (e.g. 9999), deliver last page of results.
cart_details = paginator.page(paginator.num_pages)
serializer = CartDetailSerializer(cart_details, many=True, fields=['id', 'item', 'quantity', 'product_type'])
return Response(serializer.data)
Hope this helps.
I prefer extending the Paginator class, here is how it would look:
from rest_framework import status
from rest_framework.exceptions import NotFound as NotFoundError
from rest_framework.pagination import PageNumberPagination # Any other type works as well
from rest_framework.response import Response
from rest_framework.views import APIView
class CustomPaginator(PageNumberPagination):
page_size = 10 # Number of objects to return in one page
def generate_response(self, query_set, serializer_obj, request):
try:
page_data = self.paginate_queryset(query_set, request)
except NotFoundError:
return Response({"error": "No results found for the requested page"}, status=status.HTTP_400_BAD_REQUEST)
serialized_page = serializer_obj(page_data, many=True)
return self.get_paginated_response(serialized_page.data)
class CartView(APIView):
def get(self, request, format=None):
cart_details = Cart.objects.filter(user=request.user) # or any other query
paginator = CustomPaginator()
response = paginator.generate_response(cart_details, CartDetailSerializer, request)
return response
I am using DRF version 3.6.2.
You don't need to code so much. Just use this simple steps.
class ProductPagination(PageNumberPagination):
page_size = 5
class product_api(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = product_serilizer
pagination_class = ProductPagination
if you want search functionality by getting method, you can write below code
class ProductPagination(PageNumberPagination):
page_size = 5
class product_api(generics.ListCreateAPIView):
queryset = Products.objects.all()
serializer_class = product_serilizer
pagination_class = SearchProductPagination
def get_queryset(self):
qs = super(product_search_api,self).get_queryset()
searched_product = self.request.query_params.get('searched_product',None)
if search:
qs = Products.objects.filter(Q(product_name__icontains= searched_product))
return qs
Related
I am using Django APIView to include all my CRUD operation in a single api endpoint. But later on I had to use filtering logic based on the query parameters that have been passed. Hence I found it difficult to include it in a get api of APIView and made a separate api using generic view, ListAPiview.
Here is the view:
class LeadsView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request, pk=None, *args, **kwargs):
id = pk
if id is not None:
abc = Lead.objects.get(id=id)
serializer = LeadSerializer(abc)
return serializer.data
def post(self,request,*args,**kwargs):
abc = LeadSerializer(data=request.data,many=True)
if abc.is_valid():
abc.save()
return Response(abc.data, status=status.HTTP_201_CREATED)
return Response(abc._errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request,pk, *args, **kwargs):
Now, I have to use filter class and also some custom filtering logic, I need to use get_queryset. Hence I have to create another api just for get method which I dont want.
class LeadAPIView(ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
queryset = Lead.objects.all().order_by('-date_created')
serializer_class = LeadSerializer
filter_backends = [django_filters.rest_framework.DjangoFilterBackend]
pagination_class = CustomPagination
# filterset_fields = [ 'email','first_name','last_name','phone']
filterset_class = LeadsFilter
def get_queryset(self):
source = self.request.GET.get("source", None) #
lead_status = self.request.GET.get("lead_status", None)
if source is not None:
source_values = source.split(",")
if lead_status is not None:
lead_status_values= lead_status.split(",")
return Lead.objects.filter(source__in=source_values,lead_status__in=lead_status_values)
else:
return Lead.objects.filter(source__in=source_values)
elif lead_status is not None:
lead_status_values = lead_status.split(",")
if source is not None:
source_values = source.split(",")
return Lead.objects.filter(lead_status__in=lead_status_values,source__in=source_values)
else:
return Lead.objects.filter(lead_status__in=lead_status_values)
return Lead.objects.all()
My question is, can I use get_queryset in the APIView instead of making another api?? Also, if I can use it, I assume I cant import filterset_class = LeadsFilter and also pagination? What will be the best approach??
My urls:
path('leads', LeadAPIView.as_view(), name='leads'),
path('lead', LeadsView.as_view(), name='leads-create'),
path('lead/<int:pk>', LeadsView.as_view()),
APIView stands for MVT framework. There are 2 types of cases.
If you want to return response to your django templates, you use views.
In cases of returning json, xml (in short response) objects, you use viewset terminology. Viewsets supports filter-class, pagination-class, serialization, queryset, (custom mixins and many more).
p.s. in viewset if you wanted to overwrite default queryset, you define get_queryset method. Views don't support this. Also please check for #action decorators in django.
I am trying to set up an API endpoint that returns a singular object.
Right now I have:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)
Which uses the ListModelMixin and a filter to return one item, becasue each user has 1 shopping cart.
The issue is the filter function returns a queryset, but I only want a single item.
I attempted to use the RetrieveModelMixin but that doesn't run on the endpoint that I want. Retrieve runs on .../api/shopping-cart/id but I want to retrieve on .../api/shopping-cart because the filtering is done via the person who is logged in.
Any solutions?
I'm not sure if you have tried this but it should be worthwhile (although I'm not sure if it'll work)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)[:1]
Why not use simple api view instead of viewset class?
from django.http import Http404
from rest_framework import status, views
class ShoppingCartAPIView(views.APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_object(self):
# use .get if you are really sure it can only be one shopping cart per user
try:
return ShoppingCart.objects.get(user=self.request.user)
except ShoppingCart.DoesNotExist:
raise Http404()
def get(self, request):
obj = self.get_object()
data = ShoppingCartSerializer().to_representation(obj)
return Response(data, status=status.HTTP_200_OK)
You can even create logic for other http methods like post, delete.
Then add the url pattern api/shopping-cart as normal url (in urls.py), not router ones.
The way I ended up solving it was still using the ListModelMixin, as I needed to benefits of the viewset.
I overwrote the list() method with:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
# authentication_classes = (TokenAuthentication,)
# permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def list(self, request, *args, **kwargs):
instance = ShoppingCart.objects.get(user=self.request.user)
serializer = self.get_serializer(instance)
return Response(serializer.data)
which returns me a singular item on the root url .../api/shopping-cart without having to pass parameters because it filters based on user.
I'm trying to implement a custom pagination class on a ViewSet, as per the docs, but the pagination settings are just not doing a single thing. Here's the code for my ViewSet.
from rest_framework import status, permissions, viewsets
from rest_framework.pagination import PageNumberPagination
class ProductViewSetPagination(PageNumberPagination):
page_size = 5
page_size_query_param = 'page_size'
max_page_size = 1000
class ProductViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = ProductSerializer
pagination_class = ProductViewSetPagination
# ...
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
I even added some defaults to the settings.py file, but I'm still getting all the product instances on a single page on the product-list view. I've tried adding page and page_size query parameters to the URL; this doesn't change anything.
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10,
What am I missing?
The pagination added in the super list, so try:
def list(self, request):
#get_queryset is also overridden to accept filters in query_params
queryset = self.get_queryset()
if not queryset.exists():
return Response(status=status.HTTP_204_NO_CONTENT)
return super(ProductViewSet, self).list(request)
Suppose I have a ViewSet:
class ProfileViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows a user's profile to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
...and a HyperlinkedModelSerializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
read_only_fields = ('user',)
I have my urls.py set up as:
router.register(r'profiles', api.ProfileViewSet, base_name='profile')
This lets me access e.g. /api/profile/1/ fine.
I want to set up a new endpoint on my API (similar to the Facebook API's /me/ call) at /api/profile/me/ to access the current user's profile - how can I do this with Django REST Framework?
Using the solution by #Gerard was giving me trouble:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly..
Taking a look at the source code for retrieve() it seems the user_id is not used (unused *args)
This solution is working:
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from rest_framework import filters
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.decorators import list_route
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
User = get_user_model()
queryset = User.objects.all()
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('username', 'email', 'usertype')
search_fields = ('username', 'email', 'usertype')
#list_route(permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
User = get_user_model()
self.object = get_object_or_404(User, pk=request.user.id)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
Accessing /api/users/me replies with the same data as /api/users/1 (when the logged-in user is user with pk=1)
You could create a new method in your view class using the list_route decorator, like:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route()
def me(self, request, *args, **kwargs):
# assumes the user is authenticated, handle this according your needs
user_id = request.user.id
return self.retrieve(request, user_id)
See the docs on this for more info on #list_route
I hope this helps!
You can override the get_queryset method by filtering the queryset by the logged in user, this will return the logged in user's profile in the list view (/api/profile/).
def get_queryset(self):
return Profile.objects.filter(user=self.request.user)
or
def get_queryset(self):
qs = super(ProfileViewSet, self).get_queryset()
return qs.filter(user=self.request.user)
or override the retrieve method like so, this will return the profile of the current user.
def retrieve(self, request, *args, **kwargs):
self.object = get_object_or_404(Profile, user=self.request.user)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
From Gerard's answer and looking at the error pointed out by delavnog, I developed the following solution:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route(methods=['GET'], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
self.kwargs.update(pk=request.user.id)
return self.retrieve(request,*args, **kwargs)
Notes:
ModelViewSet inherits GenericAPIView and the logic to get an object is implemented in there.
You need to check if the user is authenticated, otherwise request.user will not be available. Use at least permission_classes=[IsAuthenticated].
This solution is for GET but you may apply the same logic for other methods.
DRY assured!
Just override the get_object()
eg.
def get_object(self):
return self.request.user
Just providing a different way. I did it like this:
def get_object(self):
pk = self.kwargs['pk']
if pk == 'me':
return self.request.user
else:
return super().get_object()
This allows other detail_routes in the ViewSet to work like /api/users/me/activate
I've seen quite a few fragile solutions so I thought I'll respond with something more up-to-date and safer. More importantly you don't need a separate view, since me simply acts as a redirection.
#action(detail=False, methods=['get', 'patch'])
def me(self, request):
self.kwargs['pk'] = request.user.pk
if request.method == 'GET':
return self.retrieve(request)
elif request.method == 'PATCH':
return self.partial_update(request)
else:
raise Exception('Not implemented')
It's important to not duplicate the behaviour of retrieve like I've seen in some answers. What if the function retrieve ever changes? Then you end up with a different behaviour for /me and /<user pk>
If you only need to handle GET requests, you could also use Django's redirect. But that will not work with POST or PATCH.
Considering a OneToOneField relationship between the Profile and the User models with related_name='profile', I suggest the following as the #list_route has been deprecated since DRF 3.9
class ProfileViewSet(viewsets.GenericViewSet):
serializer_class = ProfileSerializer
#action(methods=('GET',), detail=False, url_path='me', url_name='me')
def me(self, request, *args, **kwargs):
serializer = self.get_serializer(self.request.user.profile)
return response.Response(serializer.data)
I'm using the Django rest framework. I need to get the user on views.py but I not able to.
from app.models import SmsToSend
from app.serializers import SmsToSendSerializer
from rest_framework import generics
from app.permissions import IsOwner
from rest_framework import permissions
class SmsToSendList(generics.ListCreateAPIView):
queryset = SmsToSend.objects.all()
serializer_class = SmsToSendSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated)
def pre_save(self, obj):
obj.owner = self.request.user
How can I call request.user here?
Best Regards,
You are very close. I think a lot of people spend time trying to figure this out. Define request as a parameter in your view method and you will have access to it like this.
def pre_save(self, request, obj):
obj.owner = request.user
You can get your queries like this.
request.REQUEST.get('<query>')
In a serializer method it is slightly different, you can get data like this.
request = self.context['request']
user = request.user
query = request.GET['<query>']
Hope this is helpful!
class SmsToSendList(generics.ListCreateAPIView):
#queryset = SmsToSend.objects.all()
serializer_class = SmsToSendSerializer
permission_classes = (IsOwner, permissions.IsAuthenticated)
def get_queryset(self):
user = self.request.user
return SmsToSend.objects.filter(owner=user)
def pre_save(self, obj):
obj.owner = self.request.user
Instead of the queryset I had to use get_queryset method
def get_queryset(self, *args, **kwargs):
user_id = self.request.user.id