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.
Related
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
I am using a ModelViewSet to create objects from parameters received in a POST request. The serialiser looks like this:
class FooSerializer(ModelSerializer):
class Meta:
model = Foo
fields = '__all__'
I want to intercept the request, and perform a check on it (against a method of the model, if it matters) before allowing the creation to continue. In vanilla django forms, I would override the form_valid method, do the check, and then call super().form_valid(...). I am trying to do the same here:
class BookingView(ModelViewSet):
queryset = DirectBooking.objects.all()
serializer_class = DirectBookingSerializer
def create(self, request):
print(request.data)
#Perform check here
super().create(request)
This works, in the sense that it creates an object in the DB, but the trace shows an error:
AssertionError: Expected a `Response`, `HttpResponse` or `HttpStreamingResponse` to be returned from the view, but received a `<class 'NoneType'>`
This seems strange, as I would expect the super().save to return the appropriate response.
I am aware that I will need to return a response myself if the check fails (probably a 400), but I would still like to understand why it is failing here.
A view should return a HttpResponse. In a ViewSet, you do not implement .get(..) and .post(..) directly, but these perform some processing, and redirect to other functions like .create(..), and .list(..).
These views thus should then return a HttpResponse (or "friends"), here you call the super().create(request), but you forget to return the response of this call as the result of your create(..) version.
You should thus add a return statement, like:
class BookingView(ModelViewSet):
queryset = DirectBooking.objects.all()
serializer_class = DirectBookingSerializer
def create(self, request):
print(request.data)
#Perform check here
return super().create(request)
I use mixins.CreateModelMixin.create to create object, but also I need to add request.user to m2m fields in it. So my idea is to catch the object from self.create() and than filally make obj.users.add(user). But CreateModelMixin return only responce. How can I get an object from .create? Is it a better way to add user? Can I user super (not good in it)? Thanks!
ADDED:
I can use perform_create() and catch object here, but it makes code bigger and repeat .create() mostly, so I don't think that is a right way.
ADDED:
Code I user now:
#action(detail=False, methods=['POST'], serializer_class=CompanyAdminSerializer)
def create_company(self, request):
user = self.request.user
if user.user_of_company.exists():
raise NotAcceptable(detail='Only one company allowed')
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
company = serializer.save()
company.users.add(user)
company.admin_users.add(user)
return Response(serializer.data)
To catch the instance from create you will have to override the create method.
The simplest way would be to override the perform_create method.
.save() returns the instance of the created object. source
Your code will look like the following:
#Assuming you're using CreateAPIView
class New_Create(CreateAPIView):
def perform_create(self, serializer):
obj = serializer.save()
#Adding to M2M
obj.users.add(self.request.user)
DRF Serializers do not support M2M create/update out of the box.
EDIT:
I do not recommend overriding the create method. The perform_create method has been created to serve exactly this purpose. You can access the instance only after .save() has been called. So, after calling .save() on the serializer you can update the instance however you want. Two ways to access the instance are:
1) Use the object being returned by the .save method (as shown above)
2) You can use serializer.instance. (Again you can only access the instance after .save has been called. )
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
I have a django-rest-framework REST API with hierarchical resources. I want to be able to create subobjects by POSTing to /v1/objects/<pk>/subobjects/ and have it automatically set the foreign key on the new subobject to the pk kwarg from the URL without having to put it in the payload. Currently, the serializer is causing a 400 error, because it expects the object foreign key to be in the payload, but it shouldn't be considered optional either. The URL of the subobjects is /v1/subobjects/<pk>/ (since the key of the parent isn't necessary to identify it), so it is still required if I want to PUT an existing resource.
Should I just make it so that you POST to /v1/subobjects/ with the parent in the payload to add subobjects, or is there a clean way to pass the pk kwarg from the URL to the serializer? I'm using HyperlinkedModelSerializer and ModelViewSet as my respective base classes. Is there some recommended way of doing this? So far the only idea I had was to completely re-implement the ViewSets and make a custom Serializer class whose get_default_fields() comes from a dictionary that is passed in from the ViewSet, populated by its kwargs. This seems quite involved for something that I would have thought is completely run-of-the-mill, so I can't help but think I'm missing something. Every REST API I've ever seen that has writable endpoints has this kind of URL-based argument inference, so the fact that django-rest-framework doesn't seem to be able to do it at all seems strange.
Make the parent object serializer field read_only. It's not optional but it's not coming from the request data either. Instead you pull the pk/slug from the URL in pre_save()...
# Assuming list and detail URLs like:
# /v1/objects/<parent_pk>/subobjects/
# /v1/objects/<parent_pk>/subobjects/<pk>/
def pre_save(self, obj):
parent = models.MainObject.objects.get(pk=self.kwargs['parent_pk'])
obj.parent = parent
Here's what I've done to solve it, although it would be nice if there was a more general way to do it, since it's such a common URL pattern. First I created a mixin for my ViewSets that redefined the create method:
class CreatePartialModelMixin(object):
def initial_instance(self, request):
return None
def create(self, request, *args, **kwargs):
instance = self.initial_instance(request)
serializer = self.get_serializer(
instance=instance, data=request.DATA, files=request.FILES,
partial=True)
if serializer.is_valid():
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(
serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Mostly it is copied and pasted from CreateModelMixin, but it defines an initial_instance method that we can override in subclasses to provide a starting point for the serializer, which is set up to do a partial deserialization. Then I can do, for example,
class SubObjectViewSet(CreatePartialModelMixin, viewsets.ModelViewSet):
# ....
def initial_instance(self, request):
instance = models.SubObject(owner=request.user)
if 'pk' in self.kwargs:
parent = models.MainObject.objects.get(pk=self.kwargs['pk'])
instance.parent = parent
return instance
(I realize I don't actually need to do a .get on the pk to associate it on the model, but in my case I'm exposing the slug rather than the primary key in the public API)
If you're using ModelSerializer (which is implemented by HyperlinkedModelSerializer) it's as easy as implementing the restore_object() method:
class MySerializer(serializers.ModelSerializer):
def restore_object(self, attrs, instance=None):
if instance is None:
# If `instance` is `None`, it means we're creating
# a new object, so we set the `parent_id` field.
attrs['parent_id'] = self.context['view'].kwargs['parent_pk']
return super(MySerializer, self).restore_object(attrs, instance)
# ...
restore_object() is used to deserialize a dictionary of attributes into an object instance. ModelSerializer implements this method and creates/updates the instance for the model you specified in the Meta class. If the given instance is None it means the object still has to be created, so you just add the parent_id attribute on the attrs argument and call super().
So this way you don't have to specify a read-only field, or have a custom view/serializer.
More information:
http://www.django-rest-framework.org/api-guide/serializers#declaring-serializers
Maybe a bit late, but i guess this drf nested routers library could be helpful for that operation.