django rest framework - allow only certain methods - django

I have a resource where i only want to allow a client to do a post request on the resource, thats why i use
class MyViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
in my viewset.
When i do a post request, it works as expected.
When i do a list request, it throws a 405 response, as expected.
When i do a retrieve, put, patch or delete method, it throws a 404 instead of a 405...why?
How can i make every single request return a 405 despite of the post request?
thanks and greetings!

Use http_method_names attribute
class MyViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
http_method_names = ['post']
# your code

There doesn't seem to be any reason to use a ViewSet if you only want to support a single action. Instead, use a CreateApiView with a specific URL pointing to it.

If you viewset doesn't have any detail methods, then the drf SimpleRouter will not create any url route for /api/basename/{id}/
So Django's url dispatcher will not match those urls at all, and returns a 404.
I don't think it makes sense to return 405 for every single method. That status implies that at least one method should be valid for a specific url.
You could add a dummy detail method, but just hand all requests over to the APIView 405 handler.
I think this should force the router to register detail urls for the viewset, and just return 405 for everything (possibly with the exception of OPTIONS).
class MyViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet):
def retrieve(self, request, *args, **kwargs):
return self.http_method_not_allowed(request, *args, **kwargs)

Related

How to use swagger_auto_schema in a class based view with no serializer_class and how to add custom authentication permission to it?

I have a class based view as:
class ClassBasedView(GenericAPIView):
#swagger_auto_schema(responses={201: 'Created'})
#authorize(myCustomPermission)
def post(self, *args, **kwargs) -> Response:
// code.....
return Response(status=HTTPStatus.CREATED)
First:
The use of swagger_auto_schema without any serializer is throwing error as:
AssertionError: DetailView should either include a serializer_class attribute, or override the get_serializer_class() method.
And I don't want to use serializer for this endpoint as I don't need that.
But the swagger_auto_schema keeps on throwing this error.
I want to know whether there is any way to avoid the use of serializer and get the swagger documentation of this endpoint.
Second:
I want to add my custom authorisation permission of this endpoint in the doc.
There is a security field in swagger_auto_schema, but don't know how to make it to use for my custom permission class ie myCustomPermission
Thanks.

Can you get the request method in a DRF ModelViewSet?

I am building a Django chat app that uses Django Rest Framework. I created a MessageViewSet that extends ModelViewSet to show all of the message objects:
class MessageViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer
This chat app also uses Channels and when a user sends a POST request, I would like to do something channels-realted, but I can't find a way to see what kind of request is made. Is there any way to access the request method in a ModelViewSet?
Rest Framework viewsets map the http methods: GET, PUT, POST, and DELETE to view methods named list, update, create, and destroy respectively; so in your case, you would need to override the create method:
class MessageViewSet(ModelViewSet):
queryset = Message.objects.all()
serializer_class = MessageSerializer
def create(self, request):
print('this is a post request', request)
...
we can't get 'GET' request because ModelViewSet is inherited from
mixins.CreateModelMixin,mixins.RetrieveModelMixin,mixins.UpdateModelMixin,
mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet
these mixins so instead of 'GET' request we can override list method
enter image description here

Django/drf - delete method

I want to delete likes from my Likes table. for which I am making an axios call from the front end with
axios({
method: "delete",
url:http://127.0.0.1:8000/api/delete/,
params: { companyid: xyz }
})
It is supposed to delete a like that has company_id = xyz in it.
The Url looks like this
path('delete/', DeleteLikesView.as_view()),
(/api/ is included in the project's urls.py. So taken care of...)
and the DeleteLikesView -
class DeleteLikesView(DestroyAPIView):
queryset = Likes.objects.all()
serializer_class = LikesSerializer
def perform_destroy(self, request):
print(self.kwargs['companyid'])
companyid = self.kwargs['companyid']
instance = Likes.objects.get(
company_id=companyid, user_id=request.user.id)
instance.delete()
I am either being stuck with errors 403 (csrf_token error. Although I tried using csrf_exempt, no luck) or 405 method not allowed(for which I refered this. the solution in this question puts me back with 403 error)
Any help is appreciated. Thank!
Set xsrfHeaderName in request as below:
// ...
xsrfHeaderName: "X-CSRFToken",
// ...
Add CSRF_COOKIE_NAME in settings.py
CSRF_COOKIE_NAME = "XSRF-TOKEN"
You can use token authentication,instead of basicauth, if you use token auth, then csrf error will not come.
You have written
instance = Likes.objects.get(company_id=companyid, user_id=request.user.id)
in above code, user_id = request.user.id will not work cause you are not logged in the session.You are using api ,u need to provide a token to tell which user is accessing the api.
You have to use decorating the class.
To decorate every instance of a class-based view, you need to decorate the class definition itself. To do this you apply the decorator to the dispatch() method of the class.
from django.views.decorators.csrf import csrf_exempt
class DeleteLikesView(DestroyAPIView):
...
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def perform_destroy(self, request):
...
See more info:
https://docs.djangoproject.com/en/2.2/topics/class-based-views/intro/#decorating-the-class

MemoryError in django-rest-framework CreateAPIView due to unnecessary queries

I want to create an object using the CreateAPIView from the django-rest-framework. When calling the view, I get a MemoryError. That's probably because the view tries to present all 350000 existing objects in the browseable response.
How should I prevent the view from performing the corresponding query? Defining a post or a get_queryset method does not help.
I solved the problem by using the APIView instead of the CreateAPIView. Here's the class I wrote:
class VoteCreateAPIView(views.APIView):
def post(self, request, *args, **kwargs):
vote = request.POST.get('vote', '')
# here some validation
Vote.objects.create(
user=request.user,
vote=vote)
return response.Response({'vote': vote}, status=status.HTTP_200_OK)
I would still be curious if there's a better way to do it.

TemplateView in Django with custom status code

I have internal account privacy permissions in my project(e.g. only friends can see profile page of user) and I want to have custom permission denied page for this case. Is there any way to return response from TemplateView with status code equals 403?
Something like this:
class PrivacyDeniedView(TempateView):
template_name = '...'
status_code = 403
I can do this by override dispatch() but maybe Django has out of the box solution
Answer: it looks like there no generic solution. The best way is proposed by #alecxe, but encapsulated in Mixin as #FoxMaSk proposed
One option is to override get() method of your TemplateView class:
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=403)
You can subclass TemplateResponse and set response_class in the view. For example:
from django.template.response import TemplateResponse
class TemplateResponseForbidden(TemplateResponse):
status_code = 403
class PrivacyDeniedView(TemplateView):
response_class = TemplateResponseForbidden
...
This approach is more DRY than the other suggestions because you don't need to copy and paste any code from TemplateView (e.g. the call to render_to_string()).
I tested this in Django 1.6.
While alecxe's answer works, I strongly suggest you to avoid overriding get; it's easy to forget that CBV's can have other methods like post, and if you're overriding one you should do the same for the others.
In fact, there is no need to create a separate view just to display a 403 error; Django already has django.http.HttpResponseForbidden. So instead of redirecting to your view, just do something along the lines of:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden()
Or, if you want to render a particular template:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden(loader.render_to_string("403.html"))
I just encountered this problem as well. My goal was to be able to specify the status code in urls.py, e.g.:
url(r'^login/error/?$', TemplateView.as_view(template_name='auth/login_error.html', status=503), name='login_error'),
So using the previous answers in this thread as idea starters, I came up with the following solution:
class TemplateView(django.views.generic.TemplateView):
status = 200
def render_to_response(self, context, **response_kwargs):
response_kwargs['status'] = self.status
return super(TemplateView, self).render_to_response(context, **response_kwargs)
This is an old question, but I came across it first when searching. I was using a different generic view (ListView) whose implementation for get is a bit more complex, and thus a bit of a mess to override. I went for this solution instead:
def get(self, request, *args, **kwargs):
response = super(ListView, self).get(request, *args, **kwargs)
response.status_code = 403
return response
This also allowed me to decide the status code based on some parameters of the request, which was necessary in my case to perform a 301 redirect for old-style URL patterns.
If you don't require the ability to change status code from within the method, then cjerdonek's answer is ideal.