allow post requests in django REST framework - django

I am creating a simple rest api using django REST framework. I have successfully got the response by sending GET request to the api but since I want to send POST request, the django rest framework doesn't allow POST request by default.
As in image(below) only GET,HEAD, OPTIONS are allowed but not the POST request
The GET and POST methods inside of views.py
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from profiles_api import serializers
from rest_framework import status
# Create your views here.
class HelloApiView(APIView):
"""Test APIView"""
#Here we are telling django that the serializer class for this apiViewClass is serializer.HelloSerializer class
serializer_class = serializers.HelloSerializer
def get(self, request, format=None):
"""Retruns a list of APIViews features."""
an_apiview = [
'Uses HTTP methods as fucntion (get, post, patch, put, delete)',
'It is similar to a traditional Django view',
'Gives you the most of the control over your logic',
'Is mapped manually to URLs'
]
#The response must be as dictionary which will be shown in json as response
return Response({'message': 'Hello!', 'an_apiview': an_apiview})
def post(self,request):
"""Create a hello message with our name"""
serializer = serializer.HelloSerializer(data=request.data)
if serializer.is_valid():
name = serializer.data.get('name')
message = 'Hello! {0}'.format(name)
return Response({'message':message})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
How to allow POST requests in django REST framework?

The problem with the code was, you have added the def post() after the return statement.
To solve, just correct your indentation level as below,
class HelloApiView(APIView):
def get(self, request, format=None):
return Response()
def post(self, request):
return Response()

Related

Django-Rest-Framework: No Attribute HelloViewSet

I am trying to build a simple REST API.
I tried adding a viewset, somehow I get an error that there is no such attribute.
If I remove the viewset and just run using the APIView, it loads just fine. I am stuck. What could be the problem? What should I do to make it work?
Here's the rest_profiles.views.py FILE:
from django.shortcuts import render
from rest_framework import viewsets
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import HelloSerializer
# Create your views here.
class HelloApiView(APIView):
'''Test API View'''
serializer_class = HelloSerializer
def get(self, request, format=None):
'''Returns a list of API features'''
an_apiview = [
'Uses HTTP methods as functions (get, psot, put, patch, delete)',
'Similar to Django View',
'Mapped manually to URLs'
]
return Response({'message': 'Hello from HelloAPIVIew', 'an_apiview': an_apiview})
def post(self, request):
'''Create Hello Message'''
serializer = HelloSerializer(data=request.data)
if serializer.is_valid():
name = serializer.data.get('name')
message = 'Hello {0}'.format(name)
return Response({'message': message})
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def put(self, request):
'''Handles Updates'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'put'})
def patch(self, request, pk=None):
'''Handles partial Updates'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'patch'})
def delete(self, request, pk=None):
'''Handles deleting items'''
serializer = HelloSerializer(data=request.data)
return Response({'message': 'delete'})
class HelloViewSet(viewsets.ViewSet):
'''Test API Viewset'''
def list(self, request):
'''Return Hello Message'''
a_viewset = [
'Uses Actions (list, create, retrieve, update, partial_update)',
'automatically maps to URLs using Router',
'More functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
and Here is the urls.py file:
from django.conf.urls import url
from django.conf.urls import include
from rest_framework.routers import DefaultRouter
from . import views
router = DefaultRouter()
router.register('hello-viewset', views.HelloViewSet,
base_name='hello-viewset')
urlpatterns = [
url('hello-view/', views.HelloApiView.as_view()),
url('', include(router.urls))
]
What could possibly be the problem? And the possible work-around?
If your code is pasted correctly, it looks like your HelloViewSet is inside your HelloApiView.
Indentation is important in Python. You need to unindent this code:
class HelloViewSet(viewsets.ViewSet):
'''Test API Viewset'''
def list(self, request):
'''Return Hello Message'''
a_viewset = [
'Uses Actions (list, create, retrieve, update, partial_update)',
'automatically maps to URLs using Router',
'More functionality with less code'
]
return Response({'message': 'Hello', 'a_viewset': a_viewset})
This will put it directly in your views module so it can be imported.

Uploading Multiple Files using Django rest framework without using Forms

I'm trying to Post, from Postman, multiple files to my django app. I'm not using Forms, and there isn't a UI aspect to my app. Here is a my view class.
class FileUploader(APIView):
'''
Rest API for FileUploader
'''
permission_classes = (AllowAny,)
parser_classes = (MultiPartParser, )
#csrf_exempt
def post(self, request):
retval = Response(request.data, status=status.HTTP_201_CREATED)
logger.info('New post with the following data: {}'.format(request.data))
With this it says, "TypeError: init() missing 3 required positional arguments: 'META', 'input_data', and 'upload_handlers'"
If I use FormView, my Post has three keys, two represent files, the last is a string. During debugging my request has no field Data, and FILES is empty, and the POST doesn't have any information. Any pointers would be appreciated. I can upload more if that helps.
It's not a duplicate because he was able to upload multiple files and mine doesn't upload any files. I'm struggling to figure out how to find the files within the request and since they aren't there how to set up the views (and not the serialize) to receive multiple files.
enter image description here
Write a view class as
from rest_framework.views import APIView
from rest_framework.response import Response
class FileUploader(APIView):
'''
Rest API for FileUploader
'''
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
files_list = request.FILES
data = request.data
return Response(data={"files": "{} files uploaded".format(len(files_list)),
"data": "{} data included".format(len(data))})
and send it using form-data in POSTMAN
change the above code to like below and include header 'Content-Type': 'multipart/form-data' in the request.
class FileUploader(APIView):
'''
Rest API for FileUploader
'''
permission_classes = (AllowAny,)
parser_classes = (MultiPartParser, )
#csrf_exempt
def post(self, request, *args, **kwargs):
print(request.data)
return Response({"message": "success"})

How do I get an Django ViewSet to return a 403 error on anonymous post

I'm trying to get my app to return a 403 error when an anonymous user attempts to POST to it. Right now it returns a 201 code, but doesn't save the post to the database.
The problem is that my unit test fails because it's checking for a 403 code.
Here is my views
from post.models import Post
from post.serializers import PostSerializer
from post.permissions import IsOwnerOrReadOnly, IsOwnerOrAdmin
from rest_framework import viewsets, status
from rest_framework.response import Response
class PostViewSet(viewsets.ModelViewSet):
"""
This viewset automatically provides `list`, `create`, `retrieve`,
`update` and `destroy` actions.
"""
queryset = Post.objects.all()
serializer_class = PostSerializer
# The default will be that anyone can read a post, but only owners can change it
permission_classes = (IsOwnerOrReadOnly,)
def get_permissions(self):
# Both owners and admins can destroy a post, so if we're destroying we change permissions
if self.action in ('destroy',):
self.permission_classes = [IsOwnerOrAdmin, ]
return super(self.__class__, self).get_permissions()
def perform_create(self, serializer):
if self.request.user.is_authenticated:
serializer.save(author=self.request.user)
else:
return Response('Cannot post anonymously', status=status.HTTP_403_FORBIDDEN)
You can see I'm checking if the user is authenticated and if not, return a Response with a 403 code, but for some reason a 201 code is being returned.
How do I get it to return a 403 code?
You are trying to send a response from perform_create, but this can't be done. You see, DRF (Django REST Framework) doesn't call the perform_create method in a straight-forward manner. What happens is that DRF first calls CreateModelMixin's create method. Which then calls the perform_create method and then returns the response.
In, short, the response is returned by the create method, not perform_create method. And the create method, by default, returns the 201 status code (or sometimes 400).
So, you will need to override the create method instead. First, take a look at the source code for this method. Now, override:
from rest_framework.exceptions import PermissionDenied
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.user.is_authenticated:
self.perform_create(serializer)
else:
raise PermissionDenied('Cannot post anonymously')
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Instead of using return, use raise.
raise PermissionDenied

Django Rest Framework, different endpoint URL for one APIVIEW

I am using Django Rest Framework for my API project. Now i have one APIVIEW with post and get method. How can i add different endpoint only for a particular get or post.
class UserView(APIVIEW):
def get(self, request, format=None):
.....
pass
def post(self, request, format=None):
.....
pass
Now in the urls.py, I want something like this:
urlpatterns = [
url(r'^user\/?$', UserView.as_view()),
url(r'^user_content\/?$', UserView.as_view()),
]
user only accept GET-request and user_content only accept POST-request.
Do not do that. You already can handle different types of request separately in your APIView. You can create two different APIViews, or you can handle this in get or post methods. You can try something like this:
class UserView(APIView):
def get(self, request, format=None):
is_user_request = request.data.get('is_user_request', False)
if is_user_request:
# Handle your user request here and return JSOn
return JsonResponse({})
else:
# Handle your other requests here
return JsonResponse({})
def post(self, request, format=None):
is_user_content_request = request.data.get('is_user_content_request', False)
if is_user_content_request:
# Handle your user content request here and return JSOn
return JsonResponse({})
else:
# Handle your other type requests (if there is any) here
return JsonResponse({})
urlpatterns = [
url(r'^api/user$', UserView.as_view()),
]
This is just an example. If there are specific parameters for your each requests, you can identify your request's type from those parameters. You don't have to put extra boolean values like i did above. Check this way and see if that works for you.

Django : when client request http methods that are not defined in the view class

I am now learning django rest framework library. And when I read the tutorial I suddenly curious about that what will happen if client request http methods that are not defined in the view class. For example If I write the code like below
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
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)
def post(self, request, format=None):
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
In this case I define the "get" and "post" methods in the View class. what if client request the "put" or "delete" methods than what happens? I read the django docs and it saids View class dispatch the http method by using dispatch(). But what is happening? Thanks in advance!
You can take a look at the source on Github. The dispatch method checks which HTTP verb was used, and calls the appropriate function, or returns 405 - Method not allowed status code when the verb is not valid/expected (http_method_not_allowed is a django built-in method in the base View class that just returns the 405 status code).
The relevant portion is pasted below:
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
Essentially the same thing is done in django's own views (dispatch in django.views.generic.View):
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
If you're ever developing with django's own view classes, "Classy Class-Based Views" is a very helpful resource.