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

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.

Related

Optimize getting the first record in a table using an API endpoint with Django REST Framework and/or Django-filter

I'm trying to retrieve the first record in a table using the endpoint below (please suggest if there's a more sensible URL convention).
http://127.0.0.1:8000/api/review/?first
The view I have works, but I'm hoping to refactor it because it smells.
class ReviewViewSet(ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
serializer_class = ReviewSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
first = self.request.query_params.get('first')
queryset = RecordReview.objects.all()
if first is not None:
id_list = queryset.values_list('id')
first_item = id_list.order_by('id').first()
first_id = first_item[0]
queryset = queryset.filter(id=first_id)
return queryset
When I attempted to filter the queryset directly, I got errors like:
TypeError: object of type 'RecordReview' has no len()
The error comes from this line :
first_id = first_item[0]
This is because first_item is already a RecordReview, as it was retrieved using first()
You can simplify get_queryset as follows though :
class ReviewViewSet(ModelViewSet):
filter_backends = (DjangoFilterBackend, OrderingFilter)
serializer_class = ReviewSerializer
permission_classes = [permissions.IsAuthenticated]
def get_queryset(self):
first = self.request.query_params.get('first')
queryset = RecordReview.objects.all()
if first is not None:
queryset = queryset.order_by('id')[:1]
return queryset

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 DRF - add a sub view/url?

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)

Django Rest API merging delete/get/update/get methond in two class

At first you see my 4 method class in view.py:
class ContactList(ListAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
# This is delete method
class ContactDelete(DestroyAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
lookup_field = 'pk'
#below is post method to create new contact
class ContactCreate(CreateAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
#below is put and patch method to update contact
class ContactUpdate(UpdateAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
lookup_field = 'pk'
I want ContactList and ContactCreate should be in one class
and ContactDelete and ContactUpdate should be in one class
i am not getting how to merge it, can anyone tell me how to do it?
Note: i dont want APIViewSet
DRF has already to classes for that purpose. You can check them here and here
from rest_framework.generics import ListCreateAPIView, RetrieveDestroyAPIView
class ContactCreateListAPIView(ListCreateAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
class ContactRetrieveDeleteAPIView(RetrieveDestroyAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
lookup_field = 'pk'
Hope this helps
# This is create and list method
class ContactListCreate(ListAPIView, CreateAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
# This is delete and update method
class ContactDeleteUpdate(DestroyAPIView, UpdateAPIView):
queryset = Contact.objects.all()
serializer_class = ContactSerializers
You can remove lookup_field = 'pk' from the view, since DRF took pk as the default value.

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