Django rest framework- calling another class-based view - django

I have pored over several similar posts (and Calling a class-based view of an app from another app in same project seemed promising, but does not work), but some are older and none quite work for me. Here's my setup (using Django==2.0.6, djangorestframework==3.8.2)
I have a basic model (simplified here):
from django.db import models
class Resource(models.Model):
name = models.CharField(max_length=100, null=False)
I have a basic endpoint where I can list and create Resource instances:
from rest_framework import generics, permissions
from myapp.models import Resource
from myapp.serializers import ResourceSerializer
class ListAndCreateResource(generics.ListCreateAPIView):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
permission_classes = (permissions.IsAuthenticated,)
(afaik, the details of the serializer are not relevant, so that is left out).
Anyway, in addition to that basic endpoint, I have another API endpoint which performs some actions, but also creates some Resource objects in the process. Of course, I would like to make use of the functionality encapsulated in the ListAndCreateResource class so I only have to maintain one place where Resources are created.
I have tried:
Attempt 1:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource().post(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
Unfortunately, that does not work for me. In my trace, I get:
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 111, in get_serializer
kwargs['context'] = self.get_serializer_context()
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 137, in get_serializer_context
'request': self.request,
AttributeError: 'ListAndCreateResource' object has no attribute 'request'
Attempt 2:
This attempt tries to use the as_view method which is part of all Django class-based views:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
But that gives up with:
AssertionError: The `request` argument must be an instance of `django.http.HttpRequest`, not `rest_framework.request.Request`
So my question is...is there a straightforward way to do this? I can access the _request attribute of the rest_framework.request.Request object (which is of type django.http.HttpRequest, but then I do not have any of the authentication details that are contained in the DRF Request object (indeed, my ListAndCreateResource returns a 403 if I use response = ListAndCreateResource().as_view()(request._request, *args, **kwargs) in attempt #2 above).
Thanks in advance!

This seems a bit late, but in case anyone is wondering.
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
The as_view() is a function that when called, returns a function that takes a request, *args, **kwargs. So basically, a class view is an encapsulated function view.

I think you can use request._request. The DRF keeps a protected member _request, as is, received from the API call.

You can access the request with self.request in class based views.

Related

Django rest framework creating Orders and order items

I want to create a Order and order items.
For this i am simply creating new model object in views.py using CreateApiView but i am receiving error that "Serializer_class" should be included but i don't need serializer for this.
//views.py
class CreateOrder(CreateAPIView):
def Post(self,request):
header_token = request.META.get('HTTP_AUTHORIZATION', None)
print(header_token)
access_token = header_token.split(' ')[1]
status,user = validate_token(access_token)
cart=Cart.objects.get(user=user)
print(cart)
if cart:
total=cart.total
userprofile=UserProfile.objects.get(user=user)
order,created=Order.objects.get_or_create(billing_profile=userprofile,total=total)
cart_items=CartItem.objects.get(cart=cart)
print(cart_items)
for item in cart_items:
itemid=item.item_id
qty=item.quantity
item_instance = Items.objects.get(item_id=item)
order_item,created = OrderItems.objects.get_or_create(order=order, product=item_instance,quantity=qty)
order.save()
order_item.save()
if created:
item.delete()
return Response (status=rt_status.HTTP_200_OK)
I want to understand how to achieve this with or without serializer
You are overriding the incorrect post method. If you look at the source code of CreateAPIView you will see the method named as shown below.
class CreateAPIView(mixins.CreateModelMixin, GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
NOTE: The method is all lower case.
This method calls self.create which is derived from the CreateModelMixin and this method needs a serializer.
If you need something light weight where a serializer is not needed I would suggest using APIView.
from rest_framework.views import APIView
class CreateOrder(APIView):
def post(self, request):
....

Django Rest - Use #action with custom decorator

I have a Rest API in Django and I have the following method in a class that extends ModelViewSet:
#custom_decorator
#action(methods=['get'], detail=False, url_name="byname", url_path="byname")
def get_by_name(self, request):
# get query params from get request
username = request.query_params["username"]
experiment = request.query_params["experiment"]
If I remove the first annotator everything works fine. But when I am trying to call this function with both decorators, it does not even find the specific url path.
Is it possible to use multiple decorators along with the #action decorator?
I was having the same issue and fixed it the following way:
from functools import wraps
def custom_decorator(func):
# ADD THIS LINE TO YOUR CUSTOM DECORATOR
#wraps(func)
def func_wrapper(*args, **kwargs):
return func(*args, **kwargs)
return func_wrapper
#action(methods=['get'], detail=False, url_name="byname", url_path="byname")
#custom_decorator
def get_by_name(self, request):
# other code
I believe the issue is that the action decorator does not recognize the function after adding custom_decorator because the name is changed, so by adding #wraps(func) the function name stays the same. (#wraps docs)

Django : when client request http methods that are not defined in the view class

I am now learning django rest framework library. And when I read the tutorial I suddenly curious about that what will happen if client request http methods that are not defined in the view class. For example If I write the code like below
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
from django.http import Http404
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
class SnippetList(APIView):
"""
List all snippets, or create a new snippet.
"""
def get(self, request, format=None):
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = SnippetSerializer(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)
In this case I define the "get" and "post" methods in the View class. what if client request the "put" or "delete" methods than what happens? I read the django docs and it saids View class dispatch the http method by using dispatch(). But what is happening? Thanks in advance!
You can take a look at the source on Github. The dispatch method checks which HTTP verb was used, and calls the appropriate function, or returns 405 - Method not allowed status code when the verb is not valid/expected (http_method_not_allowed is a django built-in method in the base View class that just returns the 405 status code).
The relevant portion is pasted below:
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
Essentially the same thing is done in django's own views (dispatch in django.views.generic.View):
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
return handler(request, *args, **kwargs)
If you're ever developing with django's own view classes, "Classy Class-Based Views" is a very helpful resource.

can't pass 'user' parameter to ChangePasswordForm in django

Good day SO!
I'm learning Django (1.8) with class-based-views. Django itself provides an authentication module with the possibility to change the user's password. While using the Django's PasswordChangeForm (which extends Django's SetPasswordForm), I stumble upon the following error:
init() missing 1 required positional argument: 'user'
When I take a look at SetPasswordForm class, I can see it requires an user-object as parameter.
def __init__(self, user, *args, **kwargs):
self.user = user
super(SetPasswordForm, self).__init__(*args, **kwargs)
What did I initially do?
First off, in my view I simply assigned the Django's PasswordChangeForm:
class ChangePassword(LoginRequiredMixin, FormView):
template_name = 'users/reset_password.html'
form_class = PasswordChangeForm
Which led to the error of course, because no user-object has been provided.
So what have I attempted to solve this issue?
Attempt one: Custom form which inherits from PasswordChangeForm and adds the init method.
Since the PasswordChangeForm does not have an init method, I crated a new form class called MyPasswordChangeForm, which inherits from PasswordChangeForm and adds the init:
class MyPasswordChangeForm(PasswordChangeForm):
def __init__(self, request, *args, **kwargs):
super(MyPasswordChangeForm, self).__init__(request.user, *args, **kwargs)
Expected result: MyPasswordChangeForm->inherit from PasswordChangeForm and add init->super init->perform init in SetPasswordForm
Actual result: super is calling the LoginRequiredMixin:
init() missing 1 required positional argument: 'request'
stack-tr l:80 return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs)
Attempt 'two': minor changes
Changing super->MyPasswordChangeFrom to super->PasswordChangeForm
Attempt three: using a mixin, but had same result as above unfortunately.
Attempt four: not done this yet, but would be the final option? But there must be a way to use the django's forms as much as possible.
So my question is...
Can somebody give a hint or small explanation on how I can pass the (authenticated) user-object to the Django's SetPasswordForm via Django's PasswordChangeForm, so I can use as much as possible of the currently existing forms.
Thanks in advance!
request isn't sent by default to the FormView upon initialization. You have to sneak it in there somehow.
Based on your attempt #1, a good way to do this is overriding the method get_form_kwargs() in your FormView, and add request as a key to the dict it's super already provides. Then, use the kwargs in MyPasswordChangeForm's __init__ to get request.
Esentially, you'd do something like:
class ChangePassword(LoginRequiredMixin, FormView):
template_name = 'users/reset_password.html'
form_class = PasswordChangeForm
def get_form_kwargs(self, **kwargs):
data = super(ChangePassword, self).get_form_kwargs(**kwargs)
data['request'] = self.request
return data
And then, in your Form's init:
def __init__(self, *args, **kwargs):
request = kwargs.pop("request") # it's best you pop request, so that you don't get any complains for a parent that checks what kwargs it gets
super(MyPasswordChangeForm, self).__init__(request.user, *args, **kwargs)

Object ownership validation in Django UpdateView

EDIT:
The better solution for me was just using a permissions system, especially since I needed other types of controlled access to objects. I now use Django-guardian to help with object level permissions like this.
Original:
I'm expanding a bit on the standard django book guide by letting users upload stories, as well as having author, publisher, etc. I'm attempting to only let authors (creators) of a story use the updateview, with other users being redirected away.
Modifying get_object in the UpdateStory view set it off, but the traceback goes through my StoryForm init for some reason. The error is 'HttpResponseRedirect' object has no attribute '_meta'
views.py
class UpdateStory(LoginRequiredMixin, UpdateView):
model = Story
template_name = 'stories/story_update.html'
form_class = StoryForm
def get_object(self, queryset=None):
obj = super(UpdateStory, self).get_object()
if not obj.author == self.request.user:
return redirect(obj)
return obj
forms.py
class StoryForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(StoryForm,self).__init__(*args, **kwargs)
I'm still new, so it might be obvious, but I've been looking for a couple hours and I'm stumped.
The best approach would be to use another mixin, something like this:
class AuthorRequiredMixin(object):
def dispatch(self, request, *args, **kwargs):
if self.object.author != self.request.user:
return HttpResponseForbidden()
return super(AuthorRequiredMixin, self).dispatch(request, *args, **kwargs)
Of course you can return another HttpResponse, but keep in mind what is the proper use here.
http://ccbv.co.uk/projects/Django/1.5/django.views.generic.edit/UpdateView/
Go through the above link to understand how UpdateView works. get_object is supposed to return the model instance, It is not supposed to return HttpResponseRedirect object, that's why you are getting that error.
Try doing the check in dispatch method like the following.
def dispatch(self, request, *args, **kwargs):
""" Making sure that only authors can update stories """
obj = self.get_object()
if obj.author != self.request.user:
return redirect(obj)
return super(UpdateStory, self).dispatch(request, *args, **kwargs)
PS: I guess it is not recommended to override dispatch. But as you
have to do the check on both get and post methods, overriding dispatch
will be easier.
This specific issue is considered in Django anti-patterns.
We're encouraged to filter the QuerySet to only retrieve objects where the user is the author, as opposed to UserPassesTestMixin.
In OP's case it would actually be quite similar to what they have there
from django.contrib.auth.mixins import LoginRequiredMixin
class UpdateStory(LoginRequiredMixin, UpdateView):
model = Story
# …
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).filter(
author=self.request.user
)