DELETE method in Django Rest - python

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.

Related

How to list all objs and retrieve single obj using django drf generic views

I am trying to create a single class based view for retrieving, listing, and creating all of my orders. I have gotten create and retrieve to work but listing is giving some problems. I am using DRF generic views to extend my view and have added the generics.ListApiView to my class. Once I added this however my retrieve route started acting unusual. It is returning to me a list of all the orders when I just want a specific one.
I tried to just add the generics.ListApiView to my class and override the list and get_queryset functions but that just started to affect my retrieve view.
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = addOrderSerializer
def get_queryset(self, **kwargs):
user = self.request.user
return Order.objects.filter(user=user)
def get_object(self):
pk = self.kwargs['pk']
obj = Order.objects.get(pk=pk)
return obj
def get_item_serializer(self, *args, **kwargs):
return addItemSerializer(*args, **kwargs)
def get_shipping_serializer(self, *args, **kwargs):
return addShippingSerializer(*args, **kwargs)
def create(self, request, *args, **kwargs):
user = request.user
data = request.data
orderItems = data.get('orderItems')
print(data)
if not bool(orderItems):
return Response('No Order Items', status=status.HTTP_400_BAD_REQUEST)
else:
# TODO - Create Order
orderSerializer = self.get_serializer(data=data)
orderSerializer.is_valid(raise_exception=True)
order = orderSerializer.save(user=user)
# TODO - Create Shipping Address
shippingSerializer = self.get_shipping_serializer(data=data)
shippingSerializer.is_valid(raise_exception=True)
shippingSerializer.save(order=order)
# TODO - Create Order Items and Set Order <> OrderItem Relationship
for item in orderItems:
product = Product.objects.get(pk=item['product'])
itemSerializer = self.get_item_serializer(data=item)
itemSerializer.is_valid(raise_exception=True)
item = itemSerializer.save(order=order, product=product)
# # TODO - Update Product CountInStock
product.countInStock -= item.qty
product.save()
return Response(data=orderSerializer.data)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
s = self.get_serializer(instance=instance)
return Response(data=s.data)
def list(self, request, *args, **kwargs):
qs = self.get_queryset()
s = self.get_serializer(qs, many=True)
return Response(s.data)
from django.contrib import admin
from django.urls import path, include
from .. import views
urlpatterns = [
path('', views.Order.as_view()),
path('add/', views.Order.as_view()),
path('<int:pk>/', views.Order.as_view({'get':'retrieve'})),
]
So in conclusion the list functionality of my view is working now but it has messed up my retrieve functionality. So that the retrieve function is only returning a list even tho I am adding the pk in my url.
Can you please post your urls.py as well for better clarity ?
Currently based on your question, the issue I see is mentioned below:
You are calling class "Order" for retrieve method with some url pattern eg path/int:pk -- Which is a GET request
You are also calling same class "Order" for list method with some url pattern eg path/ -- Which is a Get request
The issue is that both Retrieve and List Generic api has a GET method: snippet added :
Conclusion:
This is an example of Method Resolution Order in python inheritance.
Therefore, even though you are trying to invoke a GET method for retrieve it is envoking the GET method of LIST api because in your class definition
class Order(generics.ListAPIView, generics.CreateAPIView, generics.RetrieveAPIView) you have inherited ListAPIView first.
Solution :
You should separate out the classes eg: ListCreateAPIView, RetrieveUpdateDestroyAPIView
Alternatively :
You can also follow below stackoverflow answer to route GET request to specific method in same class :
Multiple get methods in same class in Django views
[EDIT]
The above suggested stackoverflow answer seems to be incorrect.
For multiple GET or POST request within same class, you can use Django Viewset and routers.
I found the below link to be well explained with examples:
https://testdriven.io/blog/drf-views-part-3/

Getting 404 not found when trying to update data using a query in Django

I'm trying to add a status in one of the DB table field where first I'm checking whether the passed ID is present in the table if yes then add.
The ID is basically coming from the API path variable which I'm fetching and passing to the query. Below is the API which I'm using to call the class view.
"/notify/<int:pk>/mark_as_seen/"
"/notify/1/mark_as_seen/" # where 1 is the record Id in the DB table
Below is the code which is querying the DB table and checks whether the passed Id is available.
class MarkAsSeenView(BaseAuthenticationMixin, generics.RetrieveAPIView):
permission_classes = [permissions.IsAuthenticated]
serializer_class = SeenSerializer
queryset = Seen.objects.all()
def filter_queryset(self, queryset):
qs = super().filter_queryset(queryset=queryset)
qs = qs.exclude(seen_by_user=self.request.user).exclude(user=self.request.user)
return qs
def retrieve(self, request, *args, **kwargs):
obj = self.get_object()
obj.seen_by_user.add(self.request.user)
return super().get(request, *args, **kwargs)
Now when I'm calling this View via the above API it works perfectly in the first place where it will add one entry in the table as seen with the user id. Now the issue is after performing all the expected things it again going into the filter_queryset method and trying to search the same ID again which was already excluded in the previous iteration due to which it didn't get the same ID let say 1 record in the table results into a 404 not found as a response.
I'm not getting why it is going back again to search the same thing from retrieve to filter_queryset. Can anyone please highlight what is the exact issue here because I have debugged the whole view but not getting why it is jumping thru and fro.
Any kind of help would be much appreciated. Thanks in advance.
I think this line might be the problem and might re-trigger the filter queryset due to calling the get endpoint from the beginning
return super().get(request, *args, **kwargs)
Could you try the following?
def retrieve(self, request, *args, **kwargs):
obj = self.get_object()
obj.seen_by_user.add(self.request.user)
serializer = self.get_serializer(obj)
return Response(serializer.data)

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

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.

Generic deleteview with condition

I've been trying to create a Django generic deleteview, to delete an instance of a model.
I however have to check whether it is allowed to delete this item. This is done using a method defined in the model.
So far I've created this:
#login_required
def delete_employee(request, pk):
employee = None
try:
employee = Employee.objects.get(pk=pk)
except:
pass
if employee and not employee.empty():
return render(request, "error.html", None)
else:
# Load the generic view here.
Is this a decent way to go? And how can I load the generic view there?
I've tried things like EmployeeDelete.as_view() but those things don't work.
Or is there a way to check this in the generic view itself?
I've tried that as well, but I wasn't able to load an error page, just throw errors.
To do this with a DeleteView you can do this just by overriding the delete method on your inherited view. Here is an example based on what you have said. This is just an example of how you can accomplish it. You might need to tweak it for your exact scenario, specifically the else on can_delete
class EmployeeDeleteView(DeleteView):
success_url = reverse_lazy('index')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
can_delete = self.object.can_delete()
if can_delete:
return super(EmployeeDeleteView, self).delete(
request, *args, **kwargs)
else:
raise Http404("Object you are looking for doesn't exist")

Redirect using CBV's in Django

I believe this is a simple one, just can't spot the solution. I have a view that does a bit of work on the server then passes the user back to another view, typically the original calling view.
The way I'm rendering it now, the url isn't redirected, ie it's the url of the original receiving view. So in the case the user refreshes, they'll run that server code again.
class CountSomethingView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
# so some counting
view = MyDetailView.as_view()
return view(request, *args, **kwargs)
I strongly recommend not overriding get or post methods. Instead, override dispatch. So, to expand on Platinum Azure's answer:
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get_redirect_url(self, **kwargs):
url = you_can_define_the_url_however_you_want(**kwargs)
return url
def dispatch(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).dispatch(request, *args, **kwargs)
when a user does an action and I need to redirect him to the same page, first of all I use a templateView to display a simple "thanks" (for example) then provide a link to go back to the previous page with a simple {% url %}
for example :
from django.views.generic import CreateView, TemplateView
from django.http import HttpResponseRedirect
class UserServiceCreateView(CreateView):
form_class = UserServiceForm
template_name = "services/add_service.html"
def form_valid(self, form):
[...]
return HttpResponseRedirect('/service/add/thanks/')
class UserServiceAddedTemplateView(TemplateView):
template_name = "services/thanks_service.html"
def get_context_data(self, **kw):
context = super(UserServiceAddedTemplateView, self).\
get_context_data(**kw)
context['sentance'] = 'Your service has been successfully created'
return context
in the template thanks_service.html i use {% url %} to go back to the expected page
Hope this can help
Performing a redirect in a Django Class Based View is easy.
Simply do a return redirect('your url goes here').
However, I believe this isn't what you want to do.
I see you're using get().
Normally, when speaking about HTTP, a GET request is seldom followed by a redirect.
A POST request is usually followed by a redirect because when the user goes backwards you wouldn't want to submit the same data again.
So what do you want to do?
What I think you want to do is this:
def get(self, request, *args, **kwargs):
return render_to_response('your template', data)
or even better
def get(self, request, *args, **kwargs):
return render(request, self.template_name, data)
If you're creating or updating a model, consider inheriting from CreateView or UpdateView and specifying a success_url.
If you're really doing a redirect off of an HTTP GET action, you can inherit from RedirectView and override the get method (optionally also specifying permanent = False):
class CountSomethingView(LoginRequiredMixin, RedirectView):
permanent = False
def get(self, request, *args, **kwargs):
# do something
return super(CountSomethingView, self).get(self, request, *args, **kwargs)
Note that it's really bad practice to have a get action with side-effects (unless it's just populating a cache or modifying non-essential data). In most cases, you should consider using a form-based or model-form-based view, such as CreateView or UpdateView as suggested above.

Categories