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
Related
My question is quite straight forward. I'm actually not sure that queryset is required need for CreateAPIView or not.. ?
class CreateNotificationAPIView(generics.CreateAPIView):
"""This endpoint allows for creation of a notification"""
queryset = Notification.objects.all() #can we remove it, if we do so, will we face any issue in future ?
serializer_class = serializers.NotificationSerializer
No. The only HTTP method the CreateAPIView [drf-doc] offers is the POST method, and it implements this by making a call to the create method. The .create(…) method is implemented as [GitHub]:
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
These methods only work with the serializer, or with self.perform_create and self.get_success_headers that by default only work with the data of the serializer.
If you thus not override the methods of the CreateAPIView to use the queryset somehow, you can define a CreateAPIView without defining a queryset or override get_queryset.
according to REST_docs
queryset - The queryset that should be used for returning objects from
this view. Typically, you must either set this attribute, or override
the get_queryset() method. If you are overriding a view method, it is
important that you call get_queryset() instead of accessing this
property directly, as queryset will get evaluated once, and those
results will be cached for all subsequent requests.
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.
I am fairly new to Django rest framework and I had a couple questions that would really clear up a lot of stuff for me.
I was looking at docs for simple CRUD generic views like ListAPIView, Retrieve... etc.
For my list view I created it like this:
class CourseListApiView(ListAPIView):
queryset = Course.objects.all()
serializer_class = CourseListSerializer
Which makes sense because of the queryset returns Course.objects.all() so all the courses appear.
What I am not clear about is how the queryset in RetrieveApi works
class CourseRetrieveAPIView(RetrieveAPIView):
queryset = Course.objects.all()
serializer_class = CourseRetrieveSerializer
This is my retrieve view, it takes pk from my link and returns a corresponding course. What is unclear to me is why the queryset is Course.objects.all(), not a filtered query that gets the kwargs from the URL and filters my Courses. I tried it my way and got the same results, my view was:
class CourseRetrieveAPIView(RetrieveAPIView):
serializer_class = CourseRetrieveSerializer
def get_queryset(self):
queryset = Course.objects.filter(pk=self.kwargs.get('pk'))
return queryset
This makes more sense since the queryset is Course.objects.filter(pk=self.kwargs.get('pk')) instead of Course.objects.all() which to me doesn't make sense since I am filtering my courses by the pk in the URL
Hope my question made sense. Leave a comment if you need any clarification. I know the answer will be pretty obvious but I am very new to the framework
You will have to go through the codebase of rest_framework. A function named get_object uses two class variables named lookup_field and lookup_url_kwarg which have a default value of pk and None respectively.
Excerpt from the GenericAPIView in rest_framework/generics.py
def get_object(self):
"""
Returns the object the view is displaying.
You may want to override this if you need to provide non-standard
queryset lookups. Eg if objects are referenced using multiple
keyword arguments in the url conf.
"""
queryset = self.filter_queryset(self.get_queryset())
# Perform the lookup filtering.
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
assert lookup_url_kwarg in self.kwargs, (
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
)
filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}
obj = get_object_or_404(queryset, **filter_kwargs)
# May raise a permission denied
self.check_object_permissions(self.request, obj)
return obj
As you can see the lookup_url_kwarg is set to be equal to lookup_field if nothing is specified. If you change this value to a field of your requirement then the filter in get_object_or_404 changes.
Now coming back to your issue, when you are specifying the filter manually with url kwargs you are not using the functionality provided by the RetrieveAPIView. Instead what you are doing is filtering out your result with pk from url kwargs in get_queryset and then sending that QuerySet result to get_object which will again do the same thing for you.
I am implementing DRF's Pagination over my existing web service (RESTful API). Now I learned from the docs of DRF Pagination that pagination is authomatically applied to ListCreateAPIView , only need to add some of the lines in settings.py file.
So I did the changes according to the documentation and for my webservice I wanted my queryset to be dynamic.
Below are the changes done:
urls.py
url(r'^users/(?P<pk>[0-9]+)/workouts/get/$',
ListCreateAPIView.as_view(WorkoutList.get_queryset(), serializer_class=WorkoutSerializer), name='list'),
views.py
class WorkoutList(generics.ListCreateAPIView):
queryset = Workout.objects.all()
serializer_class = WorkoutSerializer
permission_classes = (UserPermissions,)
def get_queryset(self):
workout_instance = WorkoutList()
workout_instance.get_queryset()
query_params = self.request.QUERY_PARAMS.dict()
if 'date' in query_params and 'exclude_app_install_time' in query_params:
query_set = Workout.objects.filter(created__contains=date).exclude(
app_install_time=query_params['exclude_app_install_time'])
else:
query_set = {}
return query_set
def list(self, request, *args, **kwargs):
workouts = self.get_queryset()
serializer = WorkoutSerializer(workouts, many=True)
return Response(serializer.data)
PS : I have stackoverflowed (pun intented) the problem but couldn't find the right solution(s).
Also I want to implement OffsetLimitPagination in the DRF. A small example link will be helpful
You're doing a couple of very strange things here.
If you subclass a view, you should use that subclass in the urls, not a strange mash-up of the original class and a method from the subclass. So:
url(r'^users/(?P<pk>[0-9]+)/workouts/get/$',
WorkoutList.as_view(serializer_class=WorkoutSerializer), name='list'),
Once you've fixed that, you'll get into an infinite recursion inside your get_queryset method. Again, when you subclass, if you want to call the original implementation you use super; you don't initialize another instance of the current class and try to call that method, because it'll be the same method.
def get_queryset(self):
query_set = super(WorkoutList, self).get_queryset()
Edit I guess the pagination doesn't work because you are starting from a blank Workout query rather than using the returned value from the super call. So you should do:
def get_queryset(self):
query_set = super(WorkoutList, self).get_queryset()
query_params = self.request.QUERY_PARAMS.dict()
if 'date' in query_params and 'exclude_app_install_time' in query_params:
query_set = query_set.filter(created__contains=date).exclude(
app_install_time=query_params['exclude_app_install_time'])
return query_set
I'm trying to get an instance of a serializer in my overwritten list method and then pass it in through perform_create. Basically what this code does is it checks if the queryset is empty and if it is, we do a perform_create. The problem is that I'm trying to get an instance of the serializer so I can pass it in to the perform_create method. I don't believe the line serializer = self.get_serializer(data=request.data)
correctly grabs the serializer as it shows nothing when I try to log it. Any help is appreciated, thanks.
class ExampleViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
queryset = Example.objects.all()
serializer_class = ExampleSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwner)
def list(self, request):
queryset = self.get_queryset()
name = self.request.query_params.get('name', None)
# print(request.data)
if name is not None:
queryset = queryset.filter(name=name)
if (queryset.count() == 0):
serializer = self.get_serializer(data=request.data)
print(serializer)
return self.perform_create(serializer)
return HttpResponse(serializers.serialize('json', queryset))
elif name is None:
return HttpResponse(serializers.serialize('json', queryset))
As far as I can see, with
serializer = self.get_serializer(data=request.data)
you are trying to access POST data while responding to a GET request.
DRF ViewSets offer the methods:
list (called upon an HTTP GET request)
create (called upon an HTTP POST request)
retrieve (called upon an HTTP GET request)
update (called upon an HTTP PUT request)
partial_update (called upon an HTTP PATCH request)
destroy (called upon an HTTP DELETE request)
Also see this explicit example binding HTTP verbs to ViewSet methods
So if
you are POSTing data, the list method isn't called at all (as suggested by #Ivan in the very first comment you got above).
The solution is to move the code to the appropriate method, i.e create
Otherwise
your client is GETting, the list method is called, but request.data will be empty at best.
The solution is to make the client provide the parameters for the creation as GET parameters, along with name.
That way the view will find them in self.request.query_params
In case you have a form, simply change the way it sends its data by making it use HTTP GET. See here for further info