django path is not recognizing its intended view - django

code: urls.py
path('tools/<int:rate>/', ToolsView.as_view(), name="get-all-tools"),
path('tools/post/', ToolsView.as_view(), name="save-tool"),
code: views.py
class ToolsView(APIView):
def get(self, request, rate):
objs = ToolsModel.objects.values_list()[:rate]
if objs is None:
return Response(status=status.HTTP_404_NOT_FOUND)
data = []
for obj in objs:
print(obj)
json = {}
json['toolid'] = obj[0]
json['tool_name'] = obj[1]
json['tool_from'] = obj[2]
json['tool_qty'] = obj[3]
json['tool_state'] = obj[4]
data.append(json)
return Response(data=data, status=status.HTTP_200_OK)
def post(self, request):
data = request.data
serialize = ToolsSerializer(data=data)
if serialize.is_valid():
serialize.save()
return Response(status=status.HTTP_201_CREATED)
return Response(status=status.HTTP_404_NOT_FOUND)
Whenever i call for tools/post/ intended to call post method
http://127.0.0.1:8000/help/tools/post/
i get
get() missing 1 required positional argument: 'rate'
but rate parameter is actually needed for 'tools/<int:rate>/' which invokes my get method ,example,
http://127.0.0.1:8000/help/tools/5/
Needed help with these. Thanks in advance!

if you put http://127.0.0.1:8000/help/tools/post/ into your browser, you will be performing a GET on the URL. In your urls.py you map that route to ToolsView.as_view(). This allows you to handle the different HTTP Methods via functions. So def post will get called when a POST is requested. But you are likely doing a GET, which will call the def get(...) method. But because you aren't passing in a rate it's missing from the function arguments, hence the error. To test the post method you need to perform a POST to http://127.0.0.1:8000/help/tools/post/.
Test Code:
import requests
requests.post("http://127.0.0.1:8000/help/tools/post/", data={})

Related

DRF Viewset test method

I have added a method to my viewset as follows:
class CustomImageViewSet(viewsets.ModelViewSet):
queryset = CustomImage.objects.all()
serializer_class = CustomImageSerializer
lookup_field = 'id'
#action(detail=True, methods=['get'], url_path='sepia/')
def sepia(self, request, id):
# do something
data = image_to_string(image)
return HttpResponse(data, content_type="image/png", status=status.HTTP_200_OK)
Since it is not a default or overridden request method, I am not sure how can I proceed writing a test for it. Any suggestions?
You're not clear on what the test should test but you can test the response status_code for example like this:
def test_sepia_api():
api_client = APIClient()
response = api_client.get(path="{path_to_your_api}/sepia/")
assert response.status_code == 200
I noticed you were using pytest. I'll assume you've got pytest-django too then (it really does make everything easier). I like using request factory since it's generally faster if you've got authentication needs.
def test_me(self, user, rf):
view = CustomImageViewSet()
request = rf.get("")
request.user = user # If you need authentication
view.request = request
response = view.sepia(request, 123)
assert response.data == BLAH

Django rest framework, bulk delete

I'm working on small project using Django Rest Framework, i would like to delete multiple IDs but i get always an error when i send a delete request by sending IDs /1,2,3,4 as a string, i get id must be an integer.
this is my code,
class UpdateDeleteContact(APIView):
def get(self, request, pk):
contactObject = get_object_or_404(Contact, pk=pk)
serializeContactObject = ContactSerializer(contactObject)
return Response(serializeContactObject.data)
def delete(self, request, pk):
delete_id = request.get('deleteid', None)
if not delete_id:
return Response(status=status.HTTP_404_NOT_FOUND)
for i in delete_id.split(','):
get_object_or_404(User, pk=int(i)).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
can someone give me an example how to bulk delete
This code will enable you to send multiple ids through delete method and receive them as string.
path('url/<str:pk_ids>/', views.UpdateDeleteContact.as_view()),
#view.py
class UpdateDeleteContact(APIView):
def get(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
contactObject = Contact.objects.filter(id__in=ids)
serializeContactObject = ContactSerializer(contactObject, many=True)
return Response(serializeContactObject.data)
def delete(self, request, pk_ids):
ids = [int(pk) for pk in pk_ids.split(',')]
for i in ids:
get_object_or_404(User, pk=i).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
But for me it is not recommended since the defined url can interfere with other methods like retrieve.
Another solution that I can offer you is to enter a parameter in the url, let's call it "pk_ids", with the different ids separated by commas.
def delete(self, request, pk):
pk_ids = request.query_params.get('pk_ids', None)
if pk_ids:
for i in pk_ids.split(','):
get_object_or_404(User, pk=int(i)).delete()
else:
get_object_or_404(User, pk=pk).delete()
return Response(status=status.HTTP_204_NO_CONTENT)
So you should call the url like
url.com/url/?pk_ids=1,2,3,4
Try with:
User.objects.filter(id__in=[int(x) for x in delete_id.split(',') if x]).delete()
New, more explained...
Your code doesn't work like a restful API... Delete is only for one object as a rule. You can read this thread
As Sohaib said in the comments, you can use the post function and retrieve id list from body, or as a string like your example.

Django reverse 'str' object has no attribute 'get'

I am trying to use reverse method in django view but I got an exception 'str' object has no attribute 'get'.
here is my view
class AbandonTicketView(View):
context = dict()
template_name = "places/order_detail.html"
def get(self, request, uidb64, token, ordercode):
order = abandon_oder(uidb64, token, ordercode)
if order is not None and order.paid is False:
return reverse("order_detail", kwargs={"ordercode": order.code})
return redirect("tickets")
view that I want to go:
class OrderDetailView(LoginRequiredMixin, View):
template_name = "places/order_detail.html"
context = dict()
def get(self, request, ordercode):
order = Order.objects.get(code=ordercode)
self.context["order"] = order
self.context["key"] = settings.tycoon.STRIPE_PUBLISHABLE_KEY
if order.send_count >= 3 and order.paid is False:
self.context["abandon_ticket"] = "Order was canceled"
return render(request, template_name=self.template_name, context=self.context)
def post(self, request, ordercode):
order = pay_for_ticket(ordercode, request.POST["stripeToken"], request.user)
self.context["order"] = order
return render(request, template_name=self.template_name, context=self.context)
here is url:
path("orders/<code:ordercode>/detail/", views.OrderDetailView.as_view(), name="order_detail"),
path("tickets/", views.OrderedTicketsView.as_view(), name="tickets"),
I don't really know why it happends, because I do the similar reverse earlier and everything works fine, but not now. Could you help me please to solve this problem?
reverse() returns a string, but your view has to return a HttpResponse.
Change your line:
return reverse("order_detail", kwargs={"ordercode": order.code})
to also use redirect() (like the other part of your view)
return redirect("order_detail", args=[order.code, ])
or maybe even simplified like this
return redirect("order_detail", order.code)
Does that work?
You could use redirect with reverse and it works.
from django.shortcuts import redirect
return redirect(reverse("order_detail", kwargs={"ordercode": order.code}))
OR the second keyword to do the same thing is:
from django.http import HttpResponseRedirect
return HttpResponseRedirect(reverse("order_detail", kwargs={"ordercode": order.code}))
The reasoning behind this is that reverse just returns the URL in the form of a string. When you want to redirect an HTTP Response should happen. Only cases where only the URL is required reverse should be used otherwise you need to generate an HTTP Response as well.

Testing extra actions with overridden view attributes

I have a ModelViewSet with an extra action that needs to behave differently than the rest of the viewset with respect to authentication and authorization.
E.g.
class MyModelViewSet(viewsets.ModelViewSet):
authentication_classes = [SomeClass]
permission_classes = [AnotherClass]
queryset = Model.objects.some_important_ones()
serializer_class = MyModelSerializer
#list_route(methods=['post', 'options'], authentication_classes=[])
def action(self, request, *args, **kwargs):
response_data = so_something_specific(request)
return Response(response_data, status=status.HTTP_201_CREATED)
The testing set up is
#pytest.fixture
def request():
return APIRequestFactory()
def test_the_data_was_created(request):
req = request.post('/api/my-model/action/', payload={}, format='json')
view = MyModelViewSet.as_view({'post': 'action'})
res = view(req)
assert res.status_code == 201
So the issue is that the response always complains about authentication not being provided but the action doesn't actually need it!
The repr looks like this:
<Response status_code=401, "text/html; charset=utf-8">
Content
'{"detail":"Authentication credentials were not provided."}'
Question is, how might I go about getting the callable to operate as it should?
This ended up being really simple. Looking at the router source, I just needed to pass in the initialization keyword arguments to match what the #list_route decorator was doing like so:
view = MyModelViewSet.as_view({'post': 'action'}, **{'authentication_classes': []})

Get unpaginated results from Django REST Framework

I have pagination enabled by default, which is based on PageNumberPagination; this had sufficed until now as the API was only used by a front-end. Now we try to build automation on top of it, and I’d like to pass the full, unpaginated result set back to the client.
Is there a way to disable pagination for specific requests, e.g. if a request parameter is passed?
I used a similer appraoch to accepted answer
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if request.query_params.get('get_all', False) == 'true':
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
now if you pass ?get_all=true while making the request, you will get unpaginated response.
If you are using page number pagination style, the below solution may be better.
def paginate_queryset(self, queryset, request, view=None):
if 'page' not in request.query_params:
return None
return super().paginate_queryset(queryset, request, view)
So you simply send a request without the page query_param.
I actually did go with a custom pagination class:
class Unpaginatable(PageNumberPagination):
def paginate_queryset(self, queryset, request, view=None):
if getattr(request, 'get_all', False):
return None
return super(BuildListPagination, self).paginate_queryset(queryset, request, view=view)
Now I just have to set request.get_all = True in my viewset and I get all the items.
thnaks to this answer,
Else, if you use limit/offset you can use this in Generic List API View class:
def paginate_queryset(self, queryset):
if 'limit' not in self.request.query_params:
return None
return super().paginate_queryset(queryset)
It worked with python 3.9/ Django 4.0, In my case, this method had no argument named request and view, so I fixed it.
this will also won't render paginator json, when the response is not paginated.
You can approximate this with a request limit set to an unfeasibly large number, but you need to configure Django first:
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination'
}
Then make a ridiculously high limit request:
GET https://api.example.org/accounts/?limit=999999999999
LimitOffsetPagination
- django-rest-framework.org
Actually DRF docs for this is very unclear, and there is no clear answer address this issue. After reading the source code, the followling code works for me.
from rest_framework.pagination import LimitOffsetPagination
class Unpaginated(LimitOffsetPagination):
def paginate_queryset(self, queryset, request, view=None):
self.count = self.get_count(queryset)
self.limit = self.get_limit(request)
self.offset = self.get_offset(request)
self.request = request
self.display_page_controls = False
return list(queryset)
class SomeViewSet(viewsets.ModelViewSet):
queryset = SomeModel.objects.all().order_by("-id")
pagination_class = Unpaginated
The key here is to override the paginate_queryset function in base class and return the whole queryset.
However, overriding this function is not documented at all in the docs or I just missed it.
https://www.django-rest-framework.org/api-guide/pagination/
https://github.com/encode/django-rest-framework/blob/master/rest_framework/pagination.py
I solve this problem in the following way:
make your own pagination class and inherit it from PageNumberPagination
redefine or add your code (with super()) in get_page_size function.
For me works this case:
def get_page_size(self, request):
super().get_page_size(request)
.......
self.page_size = self.max_page_size
return self.page_size