Django Class Based View: Validate object in dispatch - python

Is there a established way that i validate an object in the dispatch without making an extra database call when self.get_object() is called later in get/post?
Here is what i have so far (slightly altered for this question):
class CourseUpdateView(UpdateView):
def dispatch(self, request, *args, **kwargs):
self.request = request
self.kwargs = kwargs
self.object = self.get_object()
if self.object.is_online:
messages.warning(request, "Sorry this one can't be updated")
return redirect("course:detail", pk=self.kwargs['pk'])
# this is going to call self.get_object again isn't it?
return UpdateView.dispatch(self, request, *args, **kwargs)

You can cache the result of get_object().
Here's a trivial example:
class CourseUpdateView(UpdateView):
# [...] your dispatch method
def get_object(self):
# it doesn't matter how many times get_object is called per request
# it should not do more than one request
if not hasattr(self, '_object'):
self._object = super(CourseUpdateView, self).get_object()
return self._object

Related

Django rest framework modelviewsets custom response

I am very new to django rest framework and I have to customize modelviewsets and serializes to return response only success message instead of queryset when put method was called on .
You can override the ModelViewSet response to do this. I am assuming this is only in case of a PUT request. Then you can do this:
class MyModelViewSet(ModelViewSet):
def update(self, request, *args, **kwargs):
super(MyModelViewSet, self).update(request, *args, **kwargs)
return Response({"status": "Success"}) # Your override
This is the original code for def update:
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
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) # This is the original code
You can find it in the UpdateModelMixin in DRF

Attempting to override update method in django rest framework to return entire queryset after update

I am attempting to override the update method for a put request in django rest framework. Instead of returning just the updated object. I want it to return the entire queryset including the updated object. For the use case I am working on, its just easier.
Starting from the top.
I am using Django Rest Frameworks Generics.
class SearchCityDetail(RetrieveUpdateDestroyAPIView):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
I override the classes PUT method and inherent from my custom mixin.
class SearchCityDetail(RetrieveUpdateDestroyAPIView, UpdateReturnAll):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
def put(self, request, *args, **kwargs):
return self.updatereturnall(self,request, *args, **kwargs)
the custom mixin looks like this (my custom added code which differs from the normal update code had the commment #Custom Code above it:
from rest_framework import status
from rest_framework.response import Response
from rest_framework.settings import api_settings
from rest_framework.mixins import UpdateModelMixin
"""
Update a model instance and return all.
"""
#Custom Code
class UpdateReturnAll(UpdateModelMixin):
#custom name normally just update
def updatereturnall(self, request, model, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object()
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 = {}
#all objects and all serializer is custom code
allobjects = self.get_queryset(self)
allserializer = self.get_serializer(allobjects, many=True)
return Response(allserializer.data)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
The self.get_queryset and the self.get_serializer are functions defined in the GenericAPIView which RetrieveUpdateDestroyAPIView inherents from. And since I am inheriting the UpdateReturnAll into the SearchCityDetail class the two methods should be available to the UpdateReturnALL
that is my understanding.
I am currently getting an error and status code 500
the error being: AttributeError: 'SearchCityDetail' object has no attribute 'data'
what am I doing wrong?
It should be:
class SearchCityDetail(RetrieveUpdateDestroyAPIView, UpdateReturnAll):
queryset = SearchCity.objects.all()
serializer_class = SearchCitySerializer
def put(self, request, *args, **kwargs):
return self.updatereturnall(request, *args, **kwargs)
Instead of return self.updatereturnall(self,request, *args, **kwargs).
Don't need to pass self argument explicit when call self.updatereturnall method, Python do it for you.

CourseModuleUpdateView didn't return an HttpResponse object. It returned None instead

I have the following class for displaying related course module using formsets
class CourseModuleUpdateView(TemplateResponseMixin, View):
template_name = 'courses/manage/module/formset.html'
course = None
def get_formset(self, data=None):
return ModuleFormSet(instance=self.course, data=data)
def dispatch(self, request, *args, **kwargs):
self.course = get_object_or_404(Course,
id=kwargs['pk'],
owner=request.user)
super(CourseModuleUpdateView, self).dispatch(request, *args, **kwargs)
def get(self, request, *args, **kwargs):
formset = self.get_formset()
return self.render_to_response({'course': self.course, 'formset': formset})
Url pattern responsible for this CBV
url(r'^(?P<pk>\d+)/module/$', views.CourseModuleUpdateView.as_view(), name='course_mudule_update')
Issuing a get request I get the following error
Traceback:
File "/home/mtali/.virtualenvs/educa/lib/python3.5/site-packages/django/core/handlers/exception.py" in inner
41. response = get_response(request)
File "/home/mtali/.virtualenvs/educa/lib/python3.5/site-packages/django/core/handlers/base.py" in _get_response
198. "returned None instead." % (callback.__module__, view_name)
Exception Type: ValueError at /courses/4/module/ Exception Value: The view courses.views.CourseModuleUpdateView didn't return an HttpResponse object. It returned None instead.
What is wrong with my code! I am using django 1.11
As per the Django documentation dispatch method should return a HTTP response.
dispatch(request, *args, **kwargs)¶
The view part of the view – the
method that accepts a request argument plus arguments, and returns a
HTTP response.
Explanation based on your code.
From the source code of Django View class,
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)
dispatch method is not only delegates the requested method to the corresponding handler it also returns back the handler response.
i.e., In this line.
return handler(request, *args, **kwargs)
In your case you are invoking super(CourseModuleUpdateView, self).dispatch(request, *args, **kwargs) in your dispatch method, this will invoke the superclass's dispatch method(i.e., View class dispatch method). Since your requested http method is GET after executing the following line on the dispatch method
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
handler will be assigned to get. So in this case it is roughly equivalent to
def dispatch(self, request, *args, **kwargs):
return get(request, *args, **kwargs)
This get will invoke the get method in your CourseModuleUpdateView class
i.e.,
def get(self, request, *args, **kwargs):
formset = self.get_formset()
return self.render_to_response({'course': self.course, 'formset': formset})
Which returns a valid HTTP response.
This response will reach the place where the get method is called. i.e., Inside Views dispatch method. And from there it will return the response from where it is invoked i.e., super(CourseModuleUpdateView, self).dispatch(request, *args, **kwargs). Since your not returning response that you received from the dispatch method resulting in
Exception Type: ValueError at /courses/4/module/ Exception Value: The view courses.views.CourseModuleUpdateView didn't return an HttpResponse object. It returned None instead.

Django REST framework: method PUT not allowed in ViewSet with def update()

In DRF, I have a simple ViewSet like this one:
class MyViewSet(viewsets.ViewSet):
def update(self, request):
# do things...
return Response(status=status.HTTP_200_OK)
When I try a PUT request, I get an error like method PUT not allowed. If I use def put(self, request): all things work fine. Accordingly to the docs I should use def update(): not def put():, why does it happen?
PUT needs id in URL by default
Sometimes there is the difference between POST and PUT, because PUT needs id in URL
That's why you get the error: "PUT is not Allowed".
Example:
POST: /api/users/
PUT: /api/users/1/
Hope it'll save a lot of time for somebody
Had a similar "Method PUT not allowed" issue with this code, because 'id' was missing in the request:
class ProfileStep2Serializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
class Step2ViewSet(viewsets.ModelViewSet):
serializer_class = ProfileStep2Serializer
def get_queryset(self):
return Profile.objects.filter(pk=self.request.user.profile.id)
Turned out that i have missed 'id' in the serializer fields, so PUT request was NOT able to provide an id for the record. The fixed version of the serializer is below:
class ProfileStep2Serializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ('id', 'middle_initial', 'mobile_phone', 'address', 'apt_unit_num', 'city', 'state', 'zip')
This answer is right, Django REST framework: method PUT not allowed in ViewSet with def update(), PUT is not allowed, because DRF expects the instance id to be in the URL. That being said, using this mixin in your ViewSet is probably the best way to fix it (from https://gist.github.com/tomchristie/a2ace4577eff2c603b1b copy pasted below)
class AllowPUTAsCreateMixin(object):
"""
The following mixin class may be used in order to support PUT-as-create
behavior for incoming requests.
"""
def update(self, request, *args, **kwargs):
partial = kwargs.pop('partial', False)
instance = self.get_object_or_none()
serializer = self.get_serializer(instance, data=request.data, partial=partial)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
return Response(serializer.data)
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
def get_object_or_none(self):
try:
return self.get_object()
except Http404:
if self.request.method == 'PUT':
# For PUT-as-create operation, we need to ensure that we have
# relevant permissions, as if this was a POST request. This
# will either raise a PermissionDenied exception, or simply
# return None.
self.check_permissions(clone_request(self.request, 'POST'))
else:
# PATCH requests where the object does not exist should still
# return a 404 response.
raise
This is because the APIView has no handler defined for .put() method so the incoming request could not be mapped to a handler method on the view, thereby raising an exception.
(Note: viewsets.ViewSet inherit from ViewSetMixin and APIView)
The dispatch() method in the APIView checks if a method handler is defined for the request method.If the dispatch() method finds a handler for the request method, it returns the appropriate response. Otherwise, it raises an exception MethodNotAllowed.
As per the source code of dispatch() method in the APIView class:
def dispatch(self, request, *args, **kwargs):
...
...
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
# here handler is fetched for the request method
# `http_method_not_allowed` handler is assigned if no handler was found
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs) # handler is called here
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
Since .put() method handler is not defined in your view, DRF calls the fallback handler .http_method_not_allowed. This raises an MethodNotAllowed exception.
The source code for .http_method_not_allowed() is:
def http_method_not_allowed(self, request, *args, **kwargs):
"""
If `request.method` does not correspond to a handler method,
determine what kind of exception to raise.
"""
raise exceptions.MethodNotAllowed(request.method) # raise an exception
Why it worked when you defined .put() in your view?
When you defined def put(self, request): in your view, DRF could map the incoming request method to a handler method on the view. This led to appropriate response being returned without an exception being raised.
def update(self, request, pk=None):
data_in = request.data
print(data_in)
instance = self.get_object()
serializer = self.get_serializer(instance, data=request.data, partial=False)
serializer.is_valid(raise_exception=True)
if instance is None:
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
lookup_value = self.kwargs[lookup_url_kwarg]
extra_kwargs = {self.lookup_field: lookup_value}
serializer.save(**extra_kwargs)
return Response(serializer.data, status=status.HTTP_201_CREATED)
serializer.save()
data_out = serializer.data
return Response(serializer.data)
Using Django viewsets you can easily map the method in the url e.g.
path('createtoken/', CreateTokenView.as_view({'post': 'create', 'put':'update'}))
Then in your class override the methods as you please:
class CreateTokenView(viewsets.ModelViewSet):
queryset = yourSet.objects.all()
serializer_class = yourSerializer
def create(self, request, *args, **kwargs):
#any method you want here
return Response("response")
def update(self, request, *args, **kwargs):
# any method you want here
return Response("response")
I have multiple objects that are working with ModelViewSet and all have different (unique) fields for lookup.
So I came up with another solution for this question by defining put on a parent class and a new field lookup_body_field that can be used to associate payload with existing object:
class CustomViewSet(viewsets.ModelViewSet):
lookup_body_field = 'id'
def put(self, pk=None):
lookup_value = self.request.data.get(self.lookup_body_field)
if not lookup_value:
raise ValidationError({self.lookup_body_field: "This field is mandatory"})
obj = self.get_queryset().filter(**{self.lookup_body_field: lookup_value}).last()
if not obj:
return self.create(request=self.request)
else:
self.kwargs['pk'] = obj.pk
return self.update(request=self.request)
class MyViewSetA(CustomViewSet)
model = ModelA
lookup_body_field = 'field_a' # Unique field on ModelA
class MyViewSetB(CustomViewSet)
model = ModelB
lookup_body_field = 'field_b' # Unique field on ModelA
Suppose
you have registered a route like this (in urls.py)
router = DefaultRouter()
router.register(r'users', UserViewSet, basename='user-viewset')
urlpatterns += router.urls
and your restapi routs starts with /api/.
ViewSets generates follow routs.
create a user (send user object in body)
POST
/api/users/
get list of users
GET
/api/users/
get a user
GET
/api/users/{id}/
update a user (full object update)
PUT
/api/users/{id}/
partial update for a user
PATCH
/api/users/{id}/
delete a user
DELETE
/api/users/{id}/

Django Class-based mixin view does not return a HttpResponse object

Trying to use a mixin to create a class-based view in Django but keep getting the following error message:
ValueError: The view twitter.views.TwitterExampleView didn't return an HttpResponse object.
As shown in the code below, I extend from View creating a base TwitterView for the app to handle error messages being returned from Twitter. That view is further extended for a TwitterNetworkView where a Twitter network is attached to the view. The other classes allow for some transformation to happen to parameters passed into requests. The final class, the TwitterExampleView, includes both the mixin for parameter transformation and the TwitterNetworkView. It's get method returns as a placeholder a string "blah". Only when it bubbles back into the dispatch is the response rendered so what am I overlooking?
class TwitterView(View):
def dispatch(self, request, *args, **kwargs):
try:
response = super(TwitterView, self).dispatch(request, *args, **kwargs)
return render_response(request, response)
except TwitterApiException, e:
return return_error(request, e, e.status_code)
class TwitterNetworkView(TwitterView):
def dispatch(self, request, *args, **kwargs):
self.network = get_network_or_404(request.user, kwargs['network_id'])
super(TwitterNetworkView, self).dispatch(request, *args, **kwargs)
class DefineParamsMixin(object):
def get_params(self):
return null
class TwitterPagedDefineParams(DefineParamsMixin):
def get_params(self):
return define_params(
Param('page'),
Param('since'),
Param('before'),
Param('limit', transform_func=int)
)
class TwitterExampleView(TwitterPagedDefineParams, TwitterNetworkView):
def get(self, request, *args, **kwargs):
return "blahhhhh"
You're not returning anything from dispatch in your TwitterNetworkView mixin. With no return statement, the method returns None rather than an HttpResponse. One fix would be to have it return the result of super(TwitterNetworkView, self).dispatch(request, *args, **kwargs).
You need to return an HttpResponse object.
from django.http import HttpResponse
...
class TwitterExampleView(TwitterPagedDefineParams, TwitterNetworkView):
def get(self, request, *args, **kwargs):
return HttpResponse("blahhhhh")
Take a look at https://docs.djangoproject.com/en/1.7/intro/tutorial03/#write-your-first-view

Categories

Resources