Django REST Framework - 405 METHOD NOT ALLOWED using SimpleRouter - django

I'm using the SimpleRouter tuorial within the docs.
Just to test I've created a temporary Authentication class:
class BackboneBasicAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
user = User.objects.filter(username="james")
return (user, None)
settings look like this
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'core.rest_authentication.BackboneBasicAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
}
Submitting a PUT request returns a 405 METHOD NOT ALLOWED
{"detail": "Method 'PUT' not allowed."}
I've tried with X-HTTP-Method-Override as well. No go.
Any ideas what I'm doing wrong?
I've spent a whole day trying to figure this out, hopefully someone can help! :)

The simple router adds the put attribute to the view for a url matching the pattern you supply with the pk added as an additional pattern element.
For example if you used:
simple_router.register('widgets/', WidgetViewSet)
The framework will create two url patterns:
'^widgets/$'
'^widgets/<?P<pk>[^/]+/$'
I am guessing that you are only trying urls that satisfy the first match for which the viewset instance will only have 'get' ('list') and 'post' ('create') support added by the framework so it will cause the error you are seeing if you try to put/patch or delete. For those methods to work you need to supply the pk so that the framework knows which widget you are modifying or deleting and so that your url matches the view that supports those methods.
This is confusing and you may choose not to use the simple_router at all if you find it too confusing. Then you can specify your own method mapping so that the rest_framework will dispatch to your put methods e.g.
url('^widgets/<?P<pk>[^/]+/$', WidgetViewSet.as_view({'put': 'update',
'get': 'retrieve',
'patch': 'partial_update',
'delete': 'destroy'}...)

To me that seems to be caused by the routed viewset not implementing or not allowing PUT requests. If it was an authentication issue, you would get a 401 UNAUTHORIZED status code.

Related

How to group swagger API endpoints (Function Based Views) with drf_yasg - Django

I am doing some migration work from Django 1.11 --> 3.1.5
previously with "rest_framework_swagger", I am able to accomplish swagger api grouping just by this in url.py
url(r'^api/v9/test_token1$',
api.test_token,
name='test_token'),
url(r'^api/v9/test_token2$',
api.test_token,
name='test_token'),
and get this (notice it groups v9)
However, I have tried with "drf_yasg" on Django 3.1.5
url.py
path('/v2/token_api1', token_api1, name='token_api1'),
path('/v2/token_api2', token_api2, name='token_api2'),
my api definition (do note I am using #api_view)
token = openapi.Parameter('token', openapi.IN_FORM, type=openapi.TYPE_STRING, required=True)
#swagger_auto_schema(
method="post",
manual_parameters=[token],
operation_id="token_api1"
)
#api_view(['POST'])
# this is optional and insures that the view gets formdata
#parser_classes([FormParser])
def token_api1(request):
token = request.POST['token']
return Response("success test_api:" + token, status=status.HTTP_200_OK)
token = openapi.Parameter('token', openapi.IN_FORM, type=openapi.TYPE_STRING, required=True)
#swagger_auto_schema(
method="post",
manual_parameters=[token],
operation_id="token_api2"
)
#api_view(['POST'])
# this is optional and insures that the view gets formdata
#parser_classes([FormParser])
def token_api2(request):
token = request.POST['token']
return Response("success test_api:" + token, status=status.HTTP_200_OK)
however, I get this (noticed v2 does not group). And also when I did a test, there were errors as well. (Code 404 Error: Not Found)
How can I group these to API in drf_yasg and also making sure there is no error ?
Note if the url.py is like this, there is NO error but it does not group
path('token_api1', token_api1, name='token_api1'),
path('token_api2', token_api2, name='token_api2'),
The name is used for accessing endpoints from your Django / Python code. so I believe the newer versions of Django forbid duplicate names.
You can group your endpoint by supplying them the same tag under tags. like so:
#swagger_auto_schema(tags=["my_custom_tag"], auto_schema=NoPagingAutoSchema, filter_inspectors=[DjangoFilterDescriptionInspector])
#action(detail=False, methods=['get'])
def action1(self, request):
pass
#swagger_auto_schema(tags=["my_custom_tag"], method='delete', manual_parameters=[openapi.Parameter(
name='delete_form_param', in_=openapi.IN_FORM,
type=openapi.TYPE_INTEGER,
description="this should not crash (form parameter on DELETE method)"
)])
#action(detail=True, methods=['delete'])
def action2(self, request, slug=None):
pass
note that you can also supply several tags for each function thus they will show in several different categories (or groups).
result:
simply by decorating your views with this #swagger_auto_schema(tags=['tag_name'])
swagger_auto_schema can be imported with from drf_yasg.utils import swagger_auto_schema

django rest framework defaultRouter without models

I am new to Python and Django (rest framework) and I love the DefaultRouter feature, by also giving an "API ROOT" page. But, I can only use the defaultrouter.register() method with viewsets. How to make a custom route without a model? For example I will have a "POST" route that multiplies 2 values from the body. (so body is something like {"value1":123, "value2":456}
Is it good practice to use viewsets for everything?
How should I implement a custom (multiply) function? I will have a serializer with the 2 params? And where to implement the code?
Am I doing things right?
EDIT1: I added the following code to my views.py
#api_view(['GET'])
def test1(request):
"""
Simple test
"""
data = {
"test": True
}
return Response(data, status=status.HTTP_200_OK)
Then I added the route to my urls.py. But, as expected, the hyperlink doesnt show up in the api root.
I am seeing this in the API Root:
So this is missing my function based view.
Why is there a distinction here?

What is the correctly way to pass an orientdb #rid as a parameter, to DELETE request in django rest framework?

I'm creating a delete method in a DRF API, by passing parameters, but I don't know how to pass correctly an orientdb #rid.
I have a relationship in orientdb called "worksat", in OrientDB Studio i can see the #rid with the structure name like #:, i.e: "#33:1" is the #rid of a worksat relationship record.
So I need to pass that string in my DRF URL api relationship:
http://127.0.0.1:8000/api/oworksat/
But passing like:
http://127.0.0.1:8000/api/oworksat/#33:1
I see GET request, with the message below (I expect to see DELETE):
Allow: GET, POST, HEAD, OPTIONS
If a pass a simple number:
http://127.0.0.1:8000/api/oworksat/1
Then I see DELETE request (obviously "1" doesn't exist):
HTTP 404 Not Found
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
api.py:
class OWorksAtViewSet(viewsets.ModelViewSet):
queryset = graph.oworksat.query()
serializer_class = OWorksAtSerializer
permission_classes = [
permissions.AllowAny
]
def destroy(self, request, *args, **kwargs):
print ("destroy")
import pdb;pdb.set_trace()
urls.py:
from django.conf.urls import include, url
from rest_framework import routers
from .api import (OWorksAtViewSet)
from rest_framework_swagger.views import get_swagger_view
router = routers.DefaultRouter()
router.register('api/oworksat', OWorksAtViewSet, 'oworksat')
schema_view = get_swagger_view(title='Swagger Documentation')
urlpatterns = [
url(r'^swagger/$', schema_view)
]
urlpatterns += router.urls
The interesting thing is that by accesing from swagger api, in the DELETE method, if I a pass in the ID of the request "#33:1", it works, the api call to my destroy method and recieve in kwargs: kwargs = {'pk': '#33:1'}.
How can I reach that behavior from DRF api?
Edited:
This is my temporal solution to implement my destroy method, but obviously this only works in Swagger UI, by passing #rid in the request.
from rest_framework import status
from rest_framework.response import Response
from core.pyorient_client import *
class OFriendsViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs):
client = orientdbConnection()
client.command("delete edge ofriends where #rid = '" + kwargs['pk'] + "'")
return Response(status=status.HTTP_204_NO_CONTENT)
I assume that when speaking about not being able to delete or see "Delete" button, you talk about Django Rest Framework browsable API.
When you access your API through DRF browsable API you get list of objects when you navigate to for example http://127.0.0.1:8000/api/oworksat/. This is the "list endpoint" and it doesn't support DELETE.
Delete button will be there when you access "detail endpoint" of single object, for example: http://127.0.0.1:8000/api/oworksat/123.
In your case however when you try to pass OrientDB #RID as object ID, browser thinks you want to get to list endpoint. This is because in urls everything after # is called fragment and this is not passed to server. So when you navigate to http://127.0.0.1:8000/api/oworksat/#1:23 browser actually requests page from http://127.0.0.1:8000/api/oworksat/ and thus gives you the list endpoint without delete button.
Why Swagger then works?
Swagger works probably because the request is not made the same way as browsers normally load pages. Swagger makes Ajax request to your API when you click the Delete button and thus the fragment part is not stripped from your url. Swagger also probably url encodes the value you type into UI and this would transform #1:22 to %231%3A22 and thus remove # which causes our problem. DRF then knows how to url decode the url automatically and ends up with correct looking ID.
Example of request Swagger probably does:
function deleteData(url, item) {
return fetch(url + '/' + item, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => console.log(response.json()));
}
deleteData("http://127.0.0.1:8000/api/oworksat/", encodeURIComponent("#1:23"));
How to fix this?
Your API probably works correctly when #RID is url encoded and Ajax request is made. It just doesn't work with DRF browsable API.
However to make it nicer to work with for your users and also to make DRF Browsable API to work I would remove # from the ID's when they are serialized through your API. Users would then make requests to urls like http://127.0.0.1:8000/api/oworksat/1:23. Of course by doing it like this you would then need to prepend the client provided id with # before passing it to OrientDB query.

django rest post return 405 error code

I am using django with rest framework and I try to test POST on existing object but I keep getting 405. my ViewSet looks like this:
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
....
and in the urls:
router = routers.SimpleRouter()
router.register(r'rest/agents', api_views.Agents, "Agent")
...
urlpatterns += router.urls
I call the post request from within APITestCase class (rest testing), my post request looks like this:
response = self.client.post(url, {'available': True, 'online':True}, format='json')
and printing url shows "/chat/rest/agents/1910_1567/", while 1910_1567 is the valid id of an existing agent (I create the agent during the setup and use its id).
I've seen other questions about rest post getting 405, but all the solutions there were url-related and in my case the url is correct. I even ran the setup outside the test and accessed the url via browser to get the object and the object indeed exist. but when I try to post to it - 405.
any ideas?
thanks!
Most probably your url is somehow matching with some different url's regex, and the request is being dispatched to some other view which disallows post request. Could you mention others urls in your urls.py? Infact you can verify this by adding pdb(debugger) in dispatch method of your view. If you made it to the pdb, then you can assume I was wrong.
If that is not the case, then you can evaluate the issue from dispatch method with debugger. Just in case you have any doubt about how to do that -
class Agents(viewsets.ModelViewSet):
serializer_class = serializer.AgentSerializer
model = serializer_class.Meta.model
def dispatch(self, *args, **kwargs):
import ipdb; ipdb.set_trace()
return super(Agents, self).dispatch(*args, **kwargs)
Solution Found:-
You are passing id in url, so this request would be routed to detail view and detail view only allow GET, PUT, DELETE operations on resource since resource already exists. So to create resource, don't provide the id. Otherwise use PUT request and provide support for creation in PUT.
POST are supposed to be for creation purpose. If you want to update with a ViewSet you'll need to PUT or PATCH (partial update) instead.
Edit:
For more about this, here's some explanation on the HTTP methods used for REST API:
http://restful-api-design.readthedocs.org/en/latest/methods.html
This is also described in the DRF documentation at:
http://www.django-rest-framework.org/api-guide/routers/#simplerouter

DjangoRestFramework - How do I customize the frontend?

This is my basic view using DjangoRestFramework:
class user_list(APIView):
"""
List all users, or create a new user.
"""
def get(self, request):
users = User.objects.all()
serializer = UserSerializer(users, many=True)
return Response(serializer.data)
def post(self, request):
serializer = UserSerializer(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)
When I go to the URL which calls this view (localhost:8000/CMS/users), DjangoRestFramework already has a frontend which says:
GET /CMS/users
HTTP 200 OK
Vary: Accept
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json
[
{
"username": "t",
}
]
How do I customize this? I want to use AngularJS on the frontend. I know that without DjangoRestFramework, I would return an html template which would be located in my Django App directory in a folder called "Templates". I've been told that DjangoRestFramework is useful becauase I can create a RESTful API which returns JSON objects. I tried adding the following to my settings.py file:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
)
}
but I still can't seem to figure out how I can customize the frontend of my Django application. I know that there is:
renderer_classes = (TemplateHTMLRenderer,)
in which I can return an html page in my view, like so:
return Response({'user': self.object}, template_name='user_detail.html')
but doesn't returning actual HTML pages defeat the purpose of DjangoRestFramework's ability to return JSON objects and create a RESTful API?
First off, there are some things to make Django Rest Framework play better with angular. Until I decided to try out ember, I was working through this utorial. To try to provide a useful example for your question:
Typically you want to put your DjangoRestFramework REST Api in something like /api.
If you're doing the seemingly ubiquitiious single page application, you can then have index.html get served up as a template (i.e. just have your default home view be a TemplateView with index.html as the template)
You would then have your angular html templates and your angular/jquery/etc. javsascripts and your css files as normal static files. NB that you generally don't want to have django templates generate angular code. First off, it makes things much more of a pain to debug, and secondly, the default angular and django template syntax use the same <% characters meaning you have to be careful to make sure one doesn't try to interpret the others templating code.
When you load http://yourserver/, this will bring up the index.html page which then pulls down angular stuff, which then starts making RESTful calls to your REST api to generate the data. Note that you can start mixing in stuff to use Django forms etc., but I'd recommend keeping it simple and then reading the linked article (or another) once you have the basics going.