Django DRF - add a sub view/url? - django

I want to create a subview (if this is the right term?) under a view that alters the query set for example
parent URL
mysite.com/api/sites
Child URL
mystic.com/apit/sites/open
and also each one of those URLS could be searched so
parent URL
mysite.com/api/sites/search=London
Child URL
mystic.com/api/sites/open/search=London
my parent View, serializer, and URL already exists
class SiteROView(viewsets.ReadOnlyModelViewSet):
queryset = Site.objects.all()
serializer_class = SiteSerializer
permission_classes = (IsAdminUser,)
filter_class = Site
filter_backends = (filters.SearchFilter,)
search_fields = ('location','postcode','state')
so I think I need to somehow add the suburl to that
class SiteROView(viewsets.ReadOnlyModelViewSet):
queryset = Site.objects.all()
serializer_class = SiteSerializer
permission_classes = (IsAdminUser,)
filter_class = Site
filter_backends = (filters.SearchFilter,)
search_fields = ('location','postcode','state')
def url_open:
queryset = Site.objects.filter(state='open')
Is this possible, and how would I achieve it?
Thanks

You can do that by using detail_route decorator
from rest_framework.response import Response
class SiteROView(viewsets.ReadOnlyModelViewSet):
..........
# your codes up here
#list_route(methods=['get'],url_path='open' permission_classes=[YourPermissionClass])
def open(self, request, *args, **kwargs):
# your rest of code and response
queryset = <your_filtered_queryset>
serializer = self.serializer_class(queryset, many=True)
return Response(data=serializer.data)

Related

Extending multiple CustomViewsets - as readonly with Django

in our project, our lead developer assigned a task to refactor some viewsets in our project.
Create a readonly Asset view that will return all defaults and romy assets
So the original code looks like this
class DefaultAssetViewSet(viewsets.ModelViewSet):
queryset = DefaultAsset.objects.all()
serializer_class = DefaultAssetSerializer
permission_classes = [IsAdminUser]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = ['name']
search_fields = ['name', 'default_value']
#action(detail=False, methods=['get'])
def defaults(self, request):
defaults = {}
for d in self.queryset.all():
defaults[d.name] = d.default_value
return Response({'defaults': defaults})
def destroy(self, request, *args, **kwargs):
try:
return super().destroy(request, *args, **kwargs)
except models.ProtectedError:
return Response(
{'detail': ErrorDetail('Unable to perform this action.')},
status=status.HTTP_403_FORBIDDEN)
class RomyAssetViewSet(viewsets.ModelViewSet):
queryset = RomyAsset.objects.all()
serializer_class = RomyAssetSerializer
permission_classes = [IsAdminUser]
filter_backends = [DjangoFilterBackend, OrderingFilter, SearchFilter]
filterset_fields = [
'romy', 'default_asset'
]
search_fields = [
'romy', 'default_asset'
]
So my first idea to come to my mind is to extend these two Views into AssetViewSet class
class AssetViewSet(RomyAssetViewSet, DefaultAssetViewSet,viewsets.ReadOnlyModelViewSet):
""" some code here"""
Is it possible to extend custom viewsets like these? And also how to implement get or list for both RomyAssetViewet and DefaultAssetViewset inside AssetViewsetClass?

custom pagination not woking in apiview of Django rest

I have a custom pagination class in a separate file and until now I have been importing it in a ListAPIView, but this time I tried with APIView, but it didn't work.
My pagination class:
class CustomPagination(PageNumberPagination):
def get_paginated_response(self, data):
return Response({
'links': {
'next': self.get_next_link(),
'previous': self.get_previous_link()
},
'count': self.page.paginator.count,
'page_size' : 15,
'results': data
})
I am trying to use custom pagination because I can have the count of the objects as well.
My view where I try to implement the pagination:
from apps.products.api.pagination import CustomPagination
class CouponView(APIView):
permission_classes = [AllowAny]
#pagination_class = CustomPagination
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc, request)
serializer = CouponSerializer(result_page,many=True)
return Response (serializer.data,status=status.HTTP_200_OK)
APIView doesn't support this feature. You'll either need to leverage ListApiView or similar generics (such as RetrieveApiView), or use the ReadOnlyModelViewSet. These both support pagination, and give you get_object.
Your code seems to be implementing the basic ViewSet features (list + retrieve). The issue with the API views is that these two both use get, so they can't be combined into one view. Using a viewset will solve that.
class CouponReadOnly(ReadOnlyModelViewSet):
pagination_class = SomeClass
serializer_class = CouponSerializer
queryset = Coupon.objects.all()
And then use a Router in your urls.py to link it up, and let the router generate the routes.
# in your urls.py --------------
router = SimpleRouter()
router.register("coupons", CouponReadOnly, "coupon")
urlpatterns = [...] # existing routes
urlpatterns += router.urls
If you want to do this manually with 2 views, then you can use the generics and manually create the paths, like you already are:
class CouponListView(ListAPIView):
# you said this one already works
class CouponGetView(RetrieveAPIView):
queryset = Coupon.objects.all()
serializer_class = CouponSerializer
urlpatterns = [
path("coupons/", CouponListView.as_view(), name="coupon-list"),
path("coupons/<int:pk>", CouponGetView.as_view(), name="coupon-get"),
]
You should use get_paginated_response in GenericAPIView for custom pagination.
from apps.products.api.pagination import CustomPagination
from rest_framework.generics import GenericAPIView
class CouponView(GenericAPIView):
permission_classes = [AllowAny]
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
self.pagination_class = CustomPagination
page = self.paginate_queryset(abc)
serializer = serializer(page, many=True, context={'request': request})
return self.get_paginated_response(serializer.data)
However, let apply the correct RESTFul API to get list and detail, so you will not custom more.
List coupons: GET /api/coupons/
Get coupon detail: GET /api/coupons/<coupon_id/
class CouponView(ListAPIView, RetrieveAPIView):
permission_classes = [AllowAny]
pagination_class = CustomPagination
queryset = Coupons.objects.all()
serializer_class = CouponSerializer
I did the following and it worked:
def get(self,request,pk = None,*args,**kwargs):
id = pk
if id is not None:
abc = Coupons.objects.get(id=id)
serializer = CouponSerializer(abc)
return serializer.data
else:
abc = Coupons.objects.all()
paginator = CustomPagination()
result_page = paginator.paginate_queryset(abc,request)
serializer = CouponSerializer(result_page,many=True)
# return Response (serializer.data,status=status.HTTP_200_OK)
return paginator.get_paginated_response(serializer.data)

RetrieveUpdateDestroy and get_queryset how works

i am learning drf and i little confused
in the urls.py i have
path('todos/<int:pk>', views.TodoRetrieveUpdateDestroy.as_view()),
on the views.py
class TodoRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
serializer_class = TodoSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
user = self.request.user
return Todo.objects.filter(user=user)
by logic i would add to filter like pk = self.kwargs[‘pk’] to send only one element Todo but it works and send only that ‘id=pk’ post without adding additional filter.Can u explain why please and how works RetrieveUpdateDestroy and get_queryset)
RetrieveUpdateDestroyAPIView doesnt execuate method def get_queryset(self): in the first place
It has def get_object(self): method which gets object by lookup_fields and query from query set method returned data again
So to get/update/delte single data,
You have to do like this:
class TodoRetrieveUpdateDestroy(generics.RetrieveUpdateDestroyAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
permission_classes = [permissions.IsAuthenticated]
lookup_field = 'pk'
and your urls should be like this:
path('some-paht/<int:pk>/', TodoRetrieveUpdateDestroy.as_view(), name='some_name')

Django filter not working

my filter isn't working Whenever I access http://localhost:8080/payables/invoices/?status=NOT_PAID It just returns all the invoices. I have no runtime error, the parameter I enter simply seems to be ignored. I really don't understand, other than that, it works well.
views.py
class InvoiceViewSet(viewsets.ViewSet):
serializer_class = InvoiceSerializer
filter_backend = filters.DjangoFilterBackend
filter_fields = ('status','supplier',)
def list(self,request,):
queryset = Invoice.objects.filter()
serializer = InvoiceSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, pk=None):
queryset = Invoice.objects.filter()
invoice = get_object_or_404(queryset, pk=pk)
serializer = InvoiceSerializer(invoice)
return Response(serializer.data)
class InvoiceItemViewSet(viewsets.ViewSet):
serializer_class = InvoiceItemSerializer
def list(self,request,invoice_pk=None):
queryset = InvoiceItem.objects.filter(invoice=invoice_pk)
serializer = InvoiceItemSerializer(queryset,many=True)
return Response(serializer.data)
def retrieve(self,request,pk,invoice_pk):
queryset = InvoiceItem.objects.filter(pk=pk,invoice=invoice_pk)
invoice_item = get_object_or_404(queryset,pk=pk)
serializer = InvoiceItemSerializer(invoice_item)
return Response(serializer.data)
url.py
from django.conf.urls import url, include
#viewset
from rest_framework_nested import routers
from payables.views import InvoiceViewSet,InvoiceItemViewSet
router = routers.SimpleRouter()
router.register(r'invoices', InvoiceViewSet,base_name='invoices')
invoice_item_router = routers.NestedSimpleRouter(router,r'invoices',lookup='invoice')
invoice_item_router.register(r'items', InvoiceItemViewSet, base_name='invoice_items')
urlpatterns = [
url(r'^',include(router.urls)),
url(r'^',include(invoice_item_router.urls))
]
It is because you are explicitly creating the queryset and hence the filter backend is never used:
queryset = Invoice.objects.filter()
I suggest looking at ModelViewSet. In that case you just have to pass queryset at the view level and rest will be taken care of.
instead of queryset = Invoice.objects.filter()
with queryset = self.filter_queryset(self.get_queryset()).filter()
instead of queryset = Invoice.objects.filter()
use queryset = self.get_queryset()
self.get_queryset() returns the filtered object list

django-rest-framework How to handle multiple URL parameter?

How can I use generic views with multiple URL parameters? Like
GET /author/{author_id}/book/{book_id}
class Book(generics.RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_field = 'book_id'
lookup_url_kwarg = 'book_id'
# lookup_field = 'author_id' for author
# lookup_url_kwarg = 'author_id'
Just add a little custom Mixin:
in urls.py:
...
path('/author/<int:author_id>/book/<int:book_id>', views.Book.as_view()),
...
in views.py:
Adapted from example in the DRF documentation:
class MultipleFieldLookupMixin:
def get_object(self):
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
multi_filter = {field: self.kwargs[field] for field in self.lookup_fields}
obj = get_object_or_404(queryset, **multi_filter) # Lookup the object
self.check_object_permissions(self.request, obj)
return obj
class Book(MultipleFieldLookupMixin, generics.RetrieveAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
lookup_fields = ['author_id', 'book_id'] # possible thanks to custom Mixin
Might be late to the party here, but this is what I do:
class Book(generics.RetrieveAPIView):
serializer_class = BookSerializer
def get_queryset(self):
book_id = self.kwargs['book_id']
author_id = self.kwargs['author_id']
return Book.objects.filter(Book = book_id, Author = author_id)
You'll need to use named groups in your URL structure and possibly override the get() method of your view.