i have tried to delete a single ManuscriptItem instance using Postman to perform my API requests on against the view below:
class ManuscriptViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating items."""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.ManuscriptItemSerializer
permission_classes = (permissions.PostOwnManuscript, IsAuthenticated,)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user."""
serializer.save(author=self.request.user)
def get_queryset(self):
"""
This view should return a list of all the manuscripts
for the currently authenticated user.
"""
user = self.request.user
return models.ManuscriptItem.objects.filter(author=user)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
def perform_destroy(self, instance):
instance.delete()
The destroy and perform destroy functions are what I have attempted without success. This is what it returns when i tried:
{
"detail": "Method \"DELETE\" not allowed." }
This is how my URLs are currently registered:
router = DefaultRouter()
router.register('manuscripts', views.ManuscriptViewSet, base_name="manuscripts") # auto basename for models
router.register('manuscriptlibrary', views.ManuscriptLibraryViewSet, base_name="manuscript_library")
router.register('manuscriptsettings', views.ManuscriptSettingsViewSet)
urlpatterns = [
url(r'', include(router.urls))
]
I'm i modifying the ModelViewSet wrong do i need to use another approach because of the nature of ModelViewSet? i expected it to work on Postman when i used an Authorized user to Delete a ManuscriptItem instance. In the docs it said Destroy() method can be used.
Additional information
The URL used is:
http://localhost:8000/manuscripts-api/manuscripts/
The model instance to be deleted from:
class ManuscriptItem(models.Model):
"""Represents a single manuscript's content"""
author = models.ForeignKey('accounts_api.UserProfile', on_delete=models.CASCADE)
title = models.CharField(max_length=255)
content = models.CharField(max_length=99999999)
def __str__(self):
"""Django uses when it needs to convert the object to a string"""
return str(self.id)
The way i have tried sending delete requests on postman with json:
{
"manuscript": 7,
}
Results: Delete Method not allowed
{
"id": 7,
"author": 5,
"title": "niceone",
"content": "niceone"
}
Results: Delete Method not allowed
Additional Questions/Info:
Don't i need to specify the router register with a pk? I tried this but didnt work either:
router.register('manuscripts/{pk}/$', views.ManuscriptViewSet, base_name="manuscript_detail")
Postman says:
Allow →GET, POST, HEAD, OPTIONS
The issue here is that you send DELETE request to the wrong url. Look at the DefaultRouter docs. It generates automatically your urls within your viewset:
Look closely at the DELETE method. It is on the {prefix}/{lookup}/[.format] url pattern. This means that your corresponding router url is manuscripts/<manuscript_id>/, but you try to send DELETE request to manuscripts/ only, which is the above pattern. You see directly from the table that the allowed HTTP methods there are GET and POST only. That's why you receive MethodNotAllowed.
The solution to your problem is not to pass the manuscript_id as a JSON body of the request
{
"manuscript": 7,
}
But to pass it directly to the url:
DELETE http://localhost:8000/manuscripts-api/manuscripts/7/
And you just register your viewset like:
router.register(r'manuscripts', ManuscriptViewSet.as_view(), name='manuscripts')
As you see, DRF generates the urls automatically for you.
from rest_framework.response import Response
from rest_framework import status
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)
use this and it will work
Related
I am just getting started with Django Rest framework and want to create a feature such that it allows superuser to create a message in admin.py and allow it only to be seen by a certain group(s) ie. "HR","Managers","Interns" etc.
in other words, only a user belonging to "HR" group will be allowed to get data from view assigned to "HR" group by admin. I would like to have only one view that appropriately gives permission.
Something like
#views.py
class message_view(APIView):
def get(request):
user = request.user
group = get_user_group(user) #fetches user's respective group
try:
#if message assigned to 'group' then return API response
except:
#otherwise 401 Error
I need some guidance with this.
I propose you to define a permission in your app (<your-app>/permissions.py) as below:
class HasGroupMemberPermission(permissions.BasePermission):
message = 'Your custom message...'
methods_list = ['GET', ]
def has_permission(self, request, view):
if request.method not in methods_list:
return True
group_name ='put_your_desired_group_name_here'
if request.user.groups.filter(name__exact=group_name).exists():
return False
return True
Then, import the above permission in your views module (<your-app>/views.py) as well as the serializer of the Message model (let's assume MessageSerializer).
from rest_framework.generics import RetrieveAPIView
from .permissions import HasGroupMemberPermission
from .serializers import MessageSerializer
class MessageView(RetrieveAPIView):
# use indicative authentication class
authentication_classes = (BasicAuthentication, )
permission_classes = (HasGroupMemberPermission, )
serializer_class = MessageSerializer
lookup_url_kwarg = 'message_id'
def get_object(self):
""" Do your query in the db """
qs = Message.objects.get(pk=self.kwargs['message_id'])
return qs
def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs)
401 will be returned if user is not authenticated. 403 will be returned if the logged in user is not member of the desired group. 200 will be returned if logged in user is member of the group and the message_id exists.
I Created a view for article model and I have the data in my database but when I go on url then it's showing empty array
my below code
view.py
class ArticleView(RetrieveAPIView, RetrieveUpdateAPIView, RetrieveDestroyAPIView):
serializer_class = ArticleSerializer
permission_classes = [AllowAny]
pagination_class = ArticleSizeLimitPagination
def get_queryset(self):
return Article.objects.filter().order_by('-updated_on')
def get_object(self):
try:
return Article.objects.get(id=self.request.data['id'], is_deleted=False)
except Exception:
return None
def perform_create(self, serializer):
return serializer.save(user=self.request.user)
def destroy(self, request, *args, **kwargs):
try:
instance = self.get_object()
instance.is_deleted = True
instance.save()
return Response(data='delete success')
except Http404:
return Response(status=status.HTTP_204_NO_CONTENT)
urls.py
urlpatterns = [
path('', ArticleView.as_view()),
path('<uuid:pk>/', ArticleView.as_view()),
]
When I run the url then
HTTP 200 OK
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"sub_title": "",
"upvote": null,
"title": ""
}
Where did I do wrong? Is there issue in def function for RetrieveAPIView?
You created a view with selected mixins. You will be able to get one instance, update or delete. Is this what you want? Here you have the list of available mixins: DRF docs.
When you open the URL in the browser you probably get the DRF browsable API. It will show you nice forms to interact with your endpoints. If you want to get the article from the endpoint, you should provide URL with id (pk (where pk is primary key)).
Your URLs looks wrong. Please try:
from rest_framework.routers import DefaultRouter
router = DefaultRouter()
router.register(
r"articles",
ArticleView
)
urlpatterns = [
url(r"/", include(router.urls)),
]
I would recommend you to use ModelViewSet instead of building view from mixins. It should be easier.
class ArticleView(ModelViewSet):
Please make it works with ModelViewSet (easier solution) and then try to narrow down with a harder solution, by defining mixins.
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
I user Django Rest Framwork. I want to make a api for delete an object like this
DELETE .../items/
to delete request.user's item. (Each user can create at most one item only, and only owner can delete his item.)
I use mixins.CreateModelMixin, mixins.ListModelMixin, viewsets.GenericViewSet for list view and create. I have tried
#action(methods=['delete'], detail=False, url_path='')
def leave(self, request, *args, **kwargs):
...
but url pattern will go:
.../items/leave/$
How can I config the router or path for this? Thanks
In Django rest framework decorators, if url_path be empty strig that replace by function name. So you cannot use url_path='' as a URL path.
You can use just a simple APIView with GET method, and do what you want in that. like this:
class MyDeleteAPIView(APIView):
def get(self, request, *args, **kwargs):
# for example
try:
user = request.user
instance = SomeModel.objects.get(user=user)
instance.delete()
return Response({"message":"deleted successfuly"}, status=status.HTTP_200_OK)
except:
return Response({"message":"delete fail"}, status=status.HTTP_400_BAD_REQUEST)
now you can define your desired url:
path('delete/', MyDeleteAPIView.as_view(), name='delete'),
I want to be able to create or update an object using the same request. The operation should be idempotent.
Sending a PUT request to DRF work as expected if the object exists but if the object doesn't exists I get a 404 instead of creating it.
models.py:
class Btilog(models.Model):
md5hash = models.CharField(primary_key=True, max_length=32)
vteip = models.ForeignKey('vte.VTE')
timestamp = models.DateTimeField(blank=False)
source = models.TextField()
code = models.CharField(max_length=10, blank=False)
msg = models.TextField(blank=False)
api.py:
class BtilogSerializer(serializers.ModelSerializer):
class Meta:
model = models.Btilog
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
urls.py:
...
router = routers.DefaultRouter()
router.register(r'btilog', api.BtilogVSet)
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
...
)
Failing request
http --form PUT http://192.168.10.121:8888/logger/api/btilog/60c6b9e99c43c0bf4d8bc22d671169b1/ vteip='172.25.128.85' 'code'='Test' 'md5hash'='60c6b9e99c43c0bf4d8bc22d671169b1' 'timestamp'='2015-05-31T13:34:01' msg='Test' source='Test'
HTTP/1.0 404 NOT FOUND
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
Content-Type: application/json
Date: Mon, 09 Feb 2015 15:16:47 GMT
Server: WSGIServer/0.1 Python/2.7.6
Vary: Accept, Cookie
{
"detail": "Not found"
}
As described here: http://restcookbook.com/HTTP%20Methods/put-vs-post/ the correct behaviour of put should be to create the object if it doesn't exists.
The same error occurs using The Browsable API Tool from DRF to make the request. Is the behaviour of DRF also alike? What I'm doing wrong?
Well, maybe you should try to overwrite update method inside your modelviewset, which handle the PUT http method:
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
def update(self, request, *args, **kwargs):
try:
instance = Btilog.objects.get(pk=kwargs['pk'])
serializer = serializers.BtilogSerializer(instance=instance,data=request.data)
if serializer.is_valid():
btilog=serializer.save()
return Response(serializer.data,status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except Btilog.DoesNotExist:
serializer = serializers.BtilogSerializer(data=request.data)
if serializer.is_valid():
btilog=serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Yes, in general with DRF you will create an object using a POST and update an object using PUT. Http PUTs should be idempotent, whereas POSTs are not necessarily so -and POSTs will never be idemponent if you have an automatically created field like a timestamp in the created object.
To get the effect the OP wishes above you need to place the create functionality of the POST http method into the PUT method.
The issue is that PUTs are mapped to the "update" action only (when using DefaultRouter in urls.py) and the update action does expect the object to exist. So you have to slightly amend the update function (from rest_framework.mixins.UpdateModelMixin) to handle creating objects that do not currently exist.
I am arriving somewhat late to this question so perhaps this may assist someone working on later versions of Django Rest Framework, my version is v3.9.4 .
if you are using a ModelViewSet, then I would suggest inserting the following update function within your views.py file, within your class viewset :
It is simply a blend of the DRF´s existing update and create mixins -and you get some extra checking thrown in with those mixins (permission checking, get_serializer_class etc.) Plus it is a bit more portable as it does not contain references to models, - well done to DRF developers (yet again). You will need to import Http404 and ValidationError as shown below.
from django.http import Http404
from rest_framework import status
from rest_framework.exceptions import ValidationError
class BtilogVSet(viewsets.ModelViewSet):
queryset = models.Btilog.objects.all()
serializer_class = BtilogSerializer
permission_classes = (permissions.AllowAny,)
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
try:
instance = self.get_object() #throws a Http404 if instance not found
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data)
except Http404:
#create the object if it has not been found
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) # will throw ValidationError
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
except ValidationError: # typically serializer is not valid
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
except:
raise
Note, PATCH is also mapped to the update() function indirectly via the function
partial_update().You don't need to include the partial_update code below, it is supplied by default from the file rest_framework.mixins.UpdateModelMixin, which is a mixin to the ModelViewSet. I show it here for purely illustrative purposes, you do not need to do anything to it.
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)