"detail": "method \delete\ not allowed" django - python

I made a view that can use put, delete request using modelviewset and mapped it with url. I have clearly made it possible to request put and delete request to url, but if you send delete request to url, I return 405 error. What's wrong with my code? Here's my code.
views.py
class UpdateDeletePostView (ModelViewSet) :
serializer_class = PostSerializer
permission_classes = [IsAuthenticated, IsOwner]
queryset = Post.objects.all()
def update (self, request, *args, **kwargs) :
super().update(request, *args, **kwargs)
return Response({'success': '게시물이 수정 되었습니다.'}, status=200)
def destroy (self, request, *args, **kwargs) :
super().destroy(request, *args, **kwargs)
return Response({'success': '게시물이 삭제 되었습니다.'}, status=200)
feed\urls.py
path('post/<int:pk>', UpdateDeletePostView.as_view({'put': 'update', 'delete': 'destroy'})),
server\urls.py
path('feed/', include('feed.urls')),
and error
"detail": "method \delete\ not allowed"

as I wrote in the comment looks like you don't need a ViewSet because you are handling just operations on a single item.
In general you can restrict the operations available for Views or ViewSet using proper mixins.
I suggest two possible approaches
Use Generic View
class UpdateDeletePostView(
UpdateModelMixin,
DeleteModelMixin,
GenericAPIView):
.....
and
urlpatterns = [
path('post/<int:pk>', UpdateDeletePostView.as_view()),
...
]
Use ViewSet and Router
class UpdateDeletePostViewSet(
UpdateModelMixin,
DeleteModelMixin,
GenericViewset):
.....
router = SimpleRouter()
router.register('feed', UpdateDeletePostViewSet)
urlpatterns = [
path('', include(router.urls)),
...
]

TLDR;
Use the detail-item endpoint instead of the list-items endpoint.
http://127.0.0.1:8000/api/items/<item-id>/
instead of
http://127.0.0.1:8000/api/items/
This happened with a project I was working on. Turns out I was trying to PUT, PATCH, DELETE from the list-items endpoint rather than the detail-item endpoint.
If you implemented your viewset using the ModelViewSet class, and your GET and POST methods work. Chances are you may be using the wrong endpoint.
Example:
If you're trying to retrieve all your products from you use the list-items endpoint which will look something like:
http://127.0.0.1:8000/api/product/
If you're trying to get the detail of a specific item, you use the detail-item endpoint which looks like:
http://127.0.0.1:8000/api/product/2/
Where 2 is the id of the specific product that you're trying to PUT, PATCH, or DELETE. The reason you get the 405 is because it is your fault (not the servers fault), that you're applying item-level methods to an endpoint that provides lists of items - making your request ambiguous.

Related

Make REST Framework require authentication for GET method

I'm working on an Django app that uses REST Framework together with Swagger. Also added some models, and one of them is called Example. Added some views based on mixins in views.py for the model previously mentioned.
In views.py, I've created two classes: ExampleList (that uses GET to get all the objects made out from that model and POST to add a new model) and ExampleIndividual, that uses methods such as individual GET, PUT and DELETE.
Anyways, this is how my ExampleList class looks like:
class ExampleList(mixins.ListModelMixin, mixins.CreateModelMixin, generics.GenericAPIView):
permission_classes = [IsAuthenticated]
queryset = ExampleModel.objects.all()
serializer_class = ExampleSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
In the settings.py file, in the REST_FRAMEWORK configuration, I've set:
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
]
Everything works fine at this moment. What I want to do, is, whenever I want to get the list of all objects from the Example model (access the get() method from the ExampleList() class, I want it to work only if I am authenticated (using the Authorize option from Swagger). If not, a status code like "FORBIDDEN" should be returned.
I tried using permission_classes = [IsAuthenticated] at the beginning of the method, but it didn't work. It seems that I can still GET all the objects without being authenticated into Swagger.
Any advice on how I can do that properly? Thank you.
Did you tried it with Token Authentication? With this method in each request made to the API, the token must be included in the Header Field Authorization. https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication to test it in Browser you can install an extension called Mod Header https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj there you can include the token in the header field.

How to add suffix url in Django Rest Framework?

How to add suffix url in ModelViewSet
Serializer
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = '__all__'
def update(self, instance, validated_data):
...
...
ModelViewSet
I'm doing a custom partial update
class CommentViewSet(viewsets.ModelViewSet):
queryset = Comment.objects.all()
serializer_class = CommentSerializer
http_method_names = ['get', 'patch', 'head', 'options']
def partial_update(self, request, *args, **kwargs):
super(CommentViewSet, self).partial_update(
request, *args, **kwargs)
return Response({
"data": request.data,
...
...
})
Urls
router = routers.DefaultRouter()
router.register(
"comments",
CommentViewSet
)
urlpatterns = [
path('api/v1/', include(router.urls))
]
Currently have this, but I want to add a suffix
url: http://localhost:8000/api/v1/comments/{id}
I want to do something like this
url: http://localhost:8000/api/v1/comments/{id}/update_or_whatever
What you want to do does not follow the REST architecture and popular practice. In REST, each endpoint represents a resource. The actions on the resource are represented by HTTP methods. So if you have the comments resource accessible through this url http://localhost:8000/api/v1/comments/, you can create (POST), get the list (GET) on the list endpoint and edit(PUT and PATCH), fetch a single comment (GET) and delete(DELETE) using the detail endpoint. In this way, you don't need to explicitly name the URL according to the action like http://localhost:8000/api/v1/comments/{id}/update. This is the architecture that DRF is built on and hence why you have this url style. Of course, there are actions like login and others that may not fit into this architecture and that's why DRF provides custom actions. But you should not use it to override the default actions mapped to HTTP methods
Another magic from DFR
https://www.django-rest-framework.org/api-guide/viewsets/#viewset-actions
Only change what u need in the view and add this action decorator.
In your views.py
#action(methods=['get'], detail=True, permission_classes=[IsAuthenticated])
def get_file(self, request, pk=None):
if pk is None:
raise ValueError("Found empty filename")
obj = self.get_queryset().filter(pk=pk).first()
if obj and obj.image_file:
return FileResponse(obj.image_file, content_type="image/jpeg")
return Response(
'Nothing to show',
status=status.HTTP_400_BAD_REQUEST)

Django POST with ModelViewSet and ModelSerializer 405

How can I make a ModelViewSet accept the POST method to create an object? When I attempt to call the endpoint I get a 405 'Method "POST" not allowed.'.
Within views.py:
class AccountViewSet(viewsets.ModelViewSet):
"""An Account ModelViewSet."""
model = Account
serializer_class = AccountSerializer
queryset = Account.objects.all().order_by('name')
Within serializers.py:
class AccountSerializer(serializers.ModelSerializer):
name = serializers.CharField(required=False)
active_until = serializers.DateTimeField()
class Meta:
model = Account
fields = [
'name',
'active_until',
]
def create(self, validated_data):
with transaction.atomic():
Account.objects.create(**validated_data)
within urls.py:
from rest_framework import routers
router = routers.SimpleRouter()
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Do I need to create a specific #action? my attempts to do so have yet to be successful. If that is the case what would the url = reverse('app:accounts-<NAME>') be such that I can call it from tests? I haven't found a full example (urls.py, views.py, serializers.py, and tests etc).
I discovered what the issue was, I had a conflicting route. There was a higher level endpoint registered before the AccountViewSet.
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
Django runs through each URL pattern, in order, and stops at the first one that matches the requested URL.. I should have been ordered this way:
router.register(
prefix=r'v1/auth/accounts',
viewset=AccountViewSet,
base_name='accounts',
)
router.register(
prefix=r'v1/auth',
viewset=UserViewSet,
base_name='users',
)
despite the fact that reverse('appname:acccounts-list') worked, the underlying URL router still thought I was calling the UserViewSet.
From the docs:
A ViewSet class is simply a type of class-based View, that does not provide any method handlers such as .get() or .post(), and instead provides actions such as .list() and .create().
And here is a list of supported actions:
def list(self, request):
pass
def create(self, request):
pass
def retrieve(self, request, pk=None):
pass
def update(self, request, pk=None):
pass
def partial_update(self, request, pk=None):
pass
def destroy(self, request, pk=None):
pass
So no post is not directly supported but create is.
So your end point would be v1/auth/accounts/create when using the a router instead v1/auth/accounts/post.
I honestly prefer using class based or function based views when working with DRF. It resembles regular django views more closely and makes more sense to me when working with them. You woul write you views and urls pretty much like regular django urls and views.

DELETE method in Django Rest

I am trying to DELETE record in Django Rest.
views.py :-
class ItemPartialView(generics.RetrieveUpdateDestroyAPIView):
queryset = itemlist.objects.all()
serializer_class = ItemlistSerializer
def put(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
def destroy(self, request, *args, **kwargs):
obj = self.get_object()
if obj.survey:
return Response(data={'message': "Too late to delete"},
status=status.HTTP_400_BAD_REQUEST)
self.perform_destroy(obj)
return Response(status=status.HTTP_204_NO_CONTENT)
urls.py :-
urlpatterns = {
url(r'^itemlists/$', ItemView.as_view(), name="create"),
url(r'^itemlists/(?P<pk>\d+)/$', ItemPartialUpdateView.as_view(), name="update")
}
Now, when I am sending DELETE request to itemlists/1/ , it is not deleting the record with id = 1
Error is showing Method DELETE not allowed
(CREATE, READ, UPDATE are working except DELETE, so I don't think it's cors related issue).
You seem to have 2 routes defined for itemlists/1/. Your code is hitting the first one which only has partial update (PUT/PATCH) support judging from the name.
You should create one view with both update and destroy on the same class. That should work.
As I see it - you don't use your ItemPartialView in your urls.
You use ItemPartialUpdateView instead, so my guess is that delete is not supported with this view.

How would I override the perform_destroy method in django rest framework?

DRF currently has functionality that throws a 404 if an object doesn't exist in the db. For example
Request: /delete/1234
Response: 204 (success)
Request 2: /delete/1234
Response: 404 (not found)
This logic is very problematic for my mobile apps and i would like to change it so that I override the 404-not-found functionality. In other words, I want my request to be idempotent. For example:
Request: /delete/1234
Response: 204 (success)
Request 2: /delete/1234
Response: 204 (success)
I've been looking at the docs but i'm not really sure how to override the get_object_or_404 functionality.
I believe, if there is no object to delete, ideally it should return 404 as DRF does.
For your requirement the following code will do the trick:
from rest_framework import status,viewsets
from rest_framework.response import Response
from django.http import Http404
class ExampleDestroyViewset(viewset.ModelViewSet):
def destroy(self, request, *args, **kwargs):
try:
instance = self.get_object()
self.perform_destroy(instance)
except Http404:
pass
return Response(status=status.HTTP_204_NO_CONTENT)
To implement the custom functionality you need to override get_object() method in the viewset. Follow the links get_object and perform_destroy
class ExampleDestroyViewset(viewset.ModelViewSet):
queryset = # queryset
serializer_class = # serializer class
def get_queryset(self):
# write custom code
def perform_destroy(self, instance):
# write custom code

Categories