get_object() that depends on `request` in UpdateView - django

I am writing a class-based view to allow users to edit their profile. Since I want the users to access this view with a URL of the type my_profile/edit/ rather than something like profile/<int:pk>/edit/, using a view based on UpdateView is quite cumbersome since getting the user profile object requires to access the request object, and get_object does not directly have access to it.
My two questions:
Shall I use UpdateView in this case?
If so, what would be the best way to override get_object(self, queryset=None)? My best attempt so far is the following:
class EditProfileView(UpdateView):
model = UserProfile
_request = None
def get_object(self, queryset=None):
return get_user_profile(self._request)
def dispatch(self, request, *args, **kwargs): # can also override setup() in newer Django versions
self._request = request
return super().dispatch(request, *args, **kwargs)
This looks clean enough to me except that if one day the Django framework decides that get_object should be called early in the workflow of the view generation, this piece of code could break.

You don't need to do this. All class-based views make the request available as self.request.
Your entire code should just be:
class EditProfileView(UpdateView):
model = UserProfile
def get_object(self, queryset=None):
return get_user_profile(self.request)
Note, even if it didn't, you still wouldn't need to define _request at class level. That's just not necessary in Python.
Also, I don't know what your get_user_profile function does but it can probably be replaced with just self.request.user.profile.

Related

Django: same method different url

My url looks like:
/api/v1/files/
/api/v1/files/100
Is it a good practice to use the same function to match them? just like the following:
class FileView(APIView):
parser_classes = (MultiPartParser,)
permission_classes = (IsAuthenticated,)
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(FileView, self).dispatch(request, *args, **kwargs)
def post(self, request, pk = None):
if pk is not None:
Do something
else:
do something
How to use different functions in Class-based views?
Thanks
I think having separate methods is cleaner than a single method that branches based on the pk. It's easier to understand which logic goes where by just looking at the view's methods rather than having to follow the (albeit simple) control flow.
My first recommendation would be to check out the viewsets that Django Rest Framework provides and look at performing your logic within the given methods it provides. This seems like it would best fit your use case.
Another option would be to look DRF's generic views which are based off these mixins. These allow more control and customization than the viewsets and are sometimes a better option if you don't need all of the functionality provided by the viewsets.

MemoryError in django-rest-framework CreateAPIView due to unnecessary queries

I want to create an object using the CreateAPIView from the django-rest-framework. When calling the view, I get a MemoryError. That's probably because the view tries to present all 350000 existing objects in the browseable response.
How should I prevent the view from performing the corresponding query? Defining a post or a get_queryset method does not help.
I solved the problem by using the APIView instead of the CreateAPIView. Here's the class I wrote:
class VoteCreateAPIView(views.APIView):
def post(self, request, *args, **kwargs):
vote = request.POST.get('vote', '')
# here some validation
Vote.objects.create(
user=request.user,
vote=vote)
return response.Response({'vote': vote}, status=status.HTTP_200_OK)
I would still be curious if there's a better way to do it.

TemplateView in Django with custom status code

I have internal account privacy permissions in my project(e.g. only friends can see profile page of user) and I want to have custom permission denied page for this case. Is there any way to return response from TemplateView with status code equals 403?
Something like this:
class PrivacyDeniedView(TempateView):
template_name = '...'
status_code = 403
I can do this by override dispatch() but maybe Django has out of the box solution
Answer: it looks like there no generic solution. The best way is proposed by #alecxe, but encapsulated in Mixin as #FoxMaSk proposed
One option is to override get() method of your TemplateView class:
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context, status=403)
You can subclass TemplateResponse and set response_class in the view. For example:
from django.template.response import TemplateResponse
class TemplateResponseForbidden(TemplateResponse):
status_code = 403
class PrivacyDeniedView(TemplateView):
response_class = TemplateResponseForbidden
...
This approach is more DRY than the other suggestions because you don't need to copy and paste any code from TemplateView (e.g. the call to render_to_string()).
I tested this in Django 1.6.
While alecxe's answer works, I strongly suggest you to avoid overriding get; it's easy to forget that CBV's can have other methods like post, and if you're overriding one you should do the same for the others.
In fact, there is no need to create a separate view just to display a 403 error; Django already has django.http.HttpResponseForbidden. So instead of redirecting to your view, just do something along the lines of:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden()
Or, if you want to render a particular template:
if not user.has_permission(): # or however you check the permission
return HttpResponseForbidden(loader.render_to_string("403.html"))
I just encountered this problem as well. My goal was to be able to specify the status code in urls.py, e.g.:
url(r'^login/error/?$', TemplateView.as_view(template_name='auth/login_error.html', status=503), name='login_error'),
So using the previous answers in this thread as idea starters, I came up with the following solution:
class TemplateView(django.views.generic.TemplateView):
status = 200
def render_to_response(self, context, **response_kwargs):
response_kwargs['status'] = self.status
return super(TemplateView, self).render_to_response(context, **response_kwargs)
This is an old question, but I came across it first when searching. I was using a different generic view (ListView) whose implementation for get is a bit more complex, and thus a bit of a mess to override. I went for this solution instead:
def get(self, request, *args, **kwargs):
response = super(ListView, self).get(request, *args, **kwargs)
response.status_code = 403
return response
This also allowed me to decide the status code based on some parameters of the request, which was necessary in my case to perform a 301 redirect for old-style URL patterns.
If you don't require the ability to change status code from within the method, then cjerdonek's answer is ideal.

Get request.session from a class-based generic view

Is there a way to get request.session from inside a class-based view?
For instance, I have
from django.views.generic.edit import FormView
class CreateProfileView(FormView):
def form_valid(self, form):
# --> would like to save form contents to session here
return redirect(self.get_success_url())
The only thing I can think of would be override as_view by adding
def as_view(self, request, *args, **kwargs):
self.session = request.session
super(CreateProfileView, self).as_view(request, *args, **kwargs)
to the class. But that seems ugly. Is there another way?
You have access to self.request from anywhere within the class (and therefore self.request.session)
https://docs.djangoproject.com/en/dev/topics/class-based-views/generic-display/#dynamic-filtering
The key part to making this work is that when class-based views are called, various useful things are stored on self; as well as the request (self.request) this includes the positional (self.args) and name-based (self.kwargs) arguments captured according to the URLconf.

Redirect from Generic View DetailView in Django

I'm using Django's class based DetailView generic view to look up an object for display. Under certain circumstances, rather than displaying the object, I wish to back out and issue a HTTP rediect instead. I can't see how I go about doing this. It's for when a user hits an object in my app, but without using the canonical URL. So, for example, on StackOverflow URLs take the form:
http://stackoverflow.com/<content_type>/<pk>/<seo_friendly_slug>
eg:
http://stackoverflow.com/questions/5661806/django-debug-toolbar-with-django-cms-and-django-1-3
You can actually type anything as the seo_friendly_slug part and it will redirect you to the correct canonical URL for the object looked up via the PK.
I wish to do the same in my DetailView. Retrieve the object, check that it's the canonical URL, and if not redirect to the item's get_absolute_url URL.
I can't return an HttpResponseRedirect in get_object, as it's expecting the looked up object. I can't seem to return it from get_context_data, as it's just expecting context data.
Maybe I just need to write a manual view, but I wondered if anyone knew if it was possible?
Thanks!
Ludo.
This isn't a natural fit for DetailView. To do this you need to override the get method of BaseDetailView, which looks like:
class BaseDetailView(SingleObjectMixin, View):
def get(self, request, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
So in your class you'd need to provide a new get method which did the URL check between fetching the object and setting up the context. Something like:
def get(self, request, **kwargs):
self.object = self.get_object()
if self.request.path != self.object.get_absolute_url():
return HttpResponseRedirect(self.object.get_absolute_url())
else:
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
As you end up overriding so much of the functionality it becomes questionable whether it's worth actually using a generic view for this, but youknow.
Developing on Rolo's answer and comments, I came up with the following generic view to serve this purpose:
from django import http
from django.views import generic
class CanonicalDetailView(generic.DetailView):
"""
A DetailView which redirects to the absolute_url, if necessary.
"""
def get_object(self, *args, **kwargs):
# Return any previously-cached object
if getattr(self, 'object', None):
return self.object
return super(CanonicalDetailView, self).get_object(*args, **kwargs)
def get(self, *args, **kwargs):
# Make sure to use the canonical URL
self.object = self.get_object()
obj_url = self.object.get_absolute_url()
if self.request.path != obj_url:
return http.HttpResponsePermanentRedirect(obj_url)
return super(CanonicalDetailView, self).get(*args, **kwargs);
This is used in the same manner as the normal DetailView, and should work for any model which implements get_absolute_url correctly.