Difference between ViewSet and GenericViewSet in Django rest framework - python

I have a Django rest framework GenericViewset for which I am trying to set up pagination as follows:
#settings.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS':
'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20
}
#views.py
class PolicyViewSet(viewsets.GenericViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)
This works as expected.However, if i try to do the same with just a normal Viewset as follows:
#views.py
class PolicyViewSet(viewsets.ViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)
I get an error stating:
'PolicyViewSet' object has no attribute 'paginate_queryset'
How do i set up pagination with a normal Viewset. What is the difference between a GenericViewset and Viewset in DRF ?

Pagination is only performed automatically if you're using the generic
views or viewsets
Read the docs
And to answer your second question What is the difference between a GenericViewset and Viewset in DRF
DRF has two main systems for handling views:
APIView: This provides some handler methods, to handle the http verbs: get, post, put, patch, and delete.
ViewSet: This is an abstraction over APIView, which provides actions as methods:
list: read only, returns multiple resources (http verb: get). Returns a list of dicts.
retrieve: read only, single resource (http verb: get, but will expect an id). Returns a single dict.
create: creates a new resource (http verb: post)
update/partial_update: edits a resource (http verbs: put/patch)
destroy: removes a resource (http verb: delete)
GenericViewSet: There are many GenericViewSet, the most common being ModelViewSet. They inherit from GenericAPIView and have a full implementation of all of the actions: list, retrieve, destroy, updated, etc. Of course, you can also pick some of them, read the docs.

just inherit also from GenericViewSet.
For example:
#views.py
class PolicyViewSet(viewsets.ViewSet, viewsets.GenericViewSet):
def list(self, request):
queryset = Policy.objects.all()
page = self.paginate_queryset(queryset)
serializer = PolicySerializer(page, many=True)
return self.get_paginated_response(serializer.data)

How do i set up pagination with a normal Viewset?
If you want to use "pagination_class" in your viewset, so you should use GenericViewSet instead of ViewSet.

From the django rest-framework source code:
class ViewSet(ViewSetMixin, views.APIView):
"""
The base ViewSet class does not provide any actions by default.
"""
pass
class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass
class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
The APIView does not contains the pagination it is implemented inside GenericAPIView
To use pagination inside ViewSet you should set the pagination class and follow the source code of GenericAPIView
The GenericAPIView contain the additional code which make it more structured. In case of usual cases GenericAPIView does the same task with less code. For complex scenarios when you want to customize more GenericAPIView would not provide any additional advantage.

Related

How do I specify a custom lookup field for a DRF action on a viewset?

I would like to specify a custom lookup field on the action (different from the viewset default "pk"), i.e.
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field="uuid", # this does not work unfortunately
)
def get_something(self, request, uuid=None):
pass
But the router does not generate the correct urls:
router = DefaultRouter()
router.register(r"test", TestViewSet)
router.urls
yields url:
'^test/(?P<pk>[^/.]+)/something/$'
instead of
'^test/(?P<uuid>[^/.]+)/something/$'
I do not want to change the lookup field for the whole viewset though and have been unsuccessful in finding a way to do this for the action itself after debugging through the router url generation. I did notice that model viewsets have this method:
get_extra_action_url_map(self)
but am unsure how to get it to be called to generate custom urls or if it is even relevant. Any help would be great thanks!
According to their docs you could use a regex lookup field. Their example uses a CBV instead of a request based view.
class MyModelViewSet(mixins.RetrieveModelMixin, viewsets.GenericViewSet):
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
This could work:
#action(
methods=["GET"],
detail=True,
url_name="something",
url_path="something",
lookup_field = 'uuid'
lookup_value_regex = '[0-9a-f]{32}'
)
def get_something(self, request, uuid=None):
pass
I think it will create much confusion for your API consumers if you have 2 different resource identification on the same resource.
You can name that action query_by_uuid or just allow them to use list_view to filter by uuid if you only want to represent the object tho. (so consumers can use /test/?uuid= to retrieve data)
But if you really want to do it, you can simply override get_object method to filter for your custom action tho:
def get_object(self):
if self.action == 'do_something':
return get_object_or_404(self.get_queryset(), uuid=self.kwargs['pk'])
return super().get_object()
Here is a bit hacky solution for generate uuid in router with detail=False.
#action(detail=False, url_path=r'(?P<uuid>[^/.]+)/do_something')
def do_something(self, request, uuid=None):
pass

How ListCreateAPIView works?

I am just new to the Django Rest Framework and I want to clearly understand how ListCreateAPIView works.
We just can provide a queryset, serializer_class and it will create a read-write endpoint.
I was looking for info on the official doc but didn't find what I want.
Any information will be helpful for me.
ListCreateAPIView is a generic APIView that allows for GET (list) and POST (create) requests.
You can read the source code and maybe get a better understanding
Basically, ListCreateAPIView has the method get() which will call the method list() in mixins.ListModelMixin. The list method will instance the serializer, filter, paginate the queryset and return a response based on the queryset and serializer you have defined in your class.
If you want a deeper understanding I recommend you to read the source code, it can be confusing at first but when you starting using it you will understand it better.
the story begins from the get method so when calling get it will call list method
this is how list method looks like it will call queryset and make pagination then serialize the data to returned as response
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)
for more information, you can visit this link
https://www.cdrf.co/3.12/rest_framework.generics/ListAPIView.html

Django - Correct method of consuming my own REST API internally in the views.py?

I created a Django REST API using serializers, viewsets and routers. My end points looks something like this:
http://www.website.com/api/items
http://www.website.com/api/items/available
serializer.py (omitting imports)
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = '__all__'
viewsets.py (omitting imports)
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
#action(methods=['GET'], detail=False)
def most_expensive(self, request):
query = self.get_queryset().order_by('price').last()
serialized = self.serializer_class(query)
return Response(serialized.data)
Now I want to able to access this API from my views.py to render the HTML with the available items:
This is the way im doing it right now:
views.py (omitting imports)
class ProductListView(View):
template = 'store/product_list.html'
def get(self, request):
items = requests.get('http://127.0.0.1:8000/api/items/available')
context = {'items': items}
return render(request, self.template, context=context)
Using the requests modules I have a couple of concerns, after measuring I noticed there is a 0.015 second delay for that request to go through and if I ever change the API endpoint I would have to adjust it here since its hard coded.
I can get my items using:
Item.objects.filter(available=True)
Which gives me the result pretty much instantly but I'm writing all the queries twice (once in my API and once in my views.py)
Is there a better way of doing this like calling the viewset class directly and getting the data from there?
Many thanks!
Calling the API endpoint in the same app is not considered a good practise.
An option would be to call your viewset method directly, like in https://stackoverflow.com/a/51149806/290036
The other one that I recommend is to use the same codebase for your API and for the view.
def get_avaialble_items():
items = Item.objects.filter(available=True)
...
return items
# Use get_avaialble_items both in ItemViewSet and ProductListView

In Django Rest Framework, how is the object passed to a class based view from urls.py used?

I've been following the Django Rest Framework tutorial here http://www.django-rest-framework.org/tutorial/3-class-based-views/
This code in particular is intriguing:
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
I know that the url function called in urls.py returns a function like entry point to the class based view when using the as_view method. Is request the object corresponding to this?
If it is, then where does the class access it and its attributes when a GET is performed? I cannot see a reference to request in the getmethod of SnippetList.
If it is not, then what does request refer to, and how does the class based view access the data in the client's request?

Django : TokenAuthentication, setting permissions on endpoints

I am using Django Rest Framework to build my API and I am quite new to it.
I set up a TokenAuthentication method, and I am now trying to filter the result of my queryset depending on this token.
Basically, I have a GET endpoint (let's say "/wallet"), and I want the /wallet endpoint to give the wallet of the specific user sending the query.
My approach was to redefine the get_queryset method in my ViewSet but I can't figure out how to get the token, and how to filter the results.
Also, anynomous users shouldn't be allowed to access that endpoint.
Here is my ViewSet, I think I need some customisation here :
class WalletViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows wallets to be viewed or edited.
"""
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer
My approach was to redefine the get_queryset method in my ViewSet but I can't figure out how to get the token, and how to filter the results.
Django REST framework TokenAuthentication is linked to the user. Therefore I would advice that you filter against the user which should be available in the view through self.request.user
Also, anynomous users shouldn't be allowed to access that endpoint.
Check the permission section of the documentation for that.
To allow only authenticated users to access your viewset just do
class WalletViewSet(viewsets.ModelViewSet):
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer
permission_classes = (IsAuthenticated,)
To get the specific user wallet you could get the user from request and than get it's wallet with a simple model query. The end result beig:
class WalletViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows wallets to be viewed or edited.
"""
queryset = Wallet.objects.filter()
serializer_class = WalletSerializer
permission_classes = (IsAuthenticated,)
def retrieve(self, request, pk, format=None):
wallet = Wallet.objects.get(user=request.user)
Response(WalletSerializer(wallet))

Categories

Resources