Django - Permissions and DeleteView, is this a good way to do it? - django

Hi so I have a working view. I wanted to know if this is the best or a good way of doing it. I want to have a delete view that deletes a Photo object but only if the logged in user is the on associated with the object.
Here is my views.py
class PhotoDelete(DeleteView):
model = Photo
template_name = 'otologue/photo_delete.html'
success_url = reverse_lazy('otologue:photos')
def get(self, request, *args, **kwargs):
object_instance = self.get_object() # Get the object
object_user = object_instance.photoextended.user # Get the user who owns the object
user = get_object_or_404(User, username=self.request.user) # Get the user in the view
if object_user != user: # See if the object_user is the same as the user
return HttpResponseForbidden('Permission Error')
else:
return render(request, self.template_name, {'object': object_instance})
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
If you need more info like the models and such, please request it.

You don't have to call get_object_or_404(), since self.request.user already contains an instance of User, so you can compare it directly.
You should call (and return the result) DeleteView's get() method instead of rendering the template yourself.
I would also use LoginRequiredMixin instead of a decorator, since your dispatch() now does nothing apart from calling super's method.
Be aware, that the actual deletion is done in the post() method. You should move your user check to the dispatch() method, otherwise it will be possible to bypass the check with a forged POST request.
To have better idea what DeleteView does in its methods, check it's source code (for example at ccbv.co.uk)

Related

Django overriding detail view get method

This is my post detail view and it works perfectly.
class PostDetailView(DetailView):
model = Post
context_object_name = 'post'
template_name = 'posts/detail.html'
def get_queryset(self, *args, **kwargs):
request = self.request
pk = self.kwargs.get('pk')
queryset = Post.objects.filter(pk=pk)
return queryset
def get_context_data(self, **kwargs):
context = super(PostDetailView, self).get_context_data(**kwargs)
content['comments'] = Comment.objects.all()
return context
However, when I add get method to the view it does not work anymore.
def get(self, request, *args, **kwargs):
# how to return here so that it works exactly like before
After adding get method get_queryset and get_context_data do not gets called automatically and the context is empty in the template. So what would be the get method so that it works exactly like before?
EDIT
My target is to do something like this
if request.is_ajax():
html = render_to_string('comments/detail.html') # ajax reply with html data
return HttpResponse(html)
return render 'posts/detail.html'
So where do I put this code and still want to keep call all methods such as get_queryset and get_context_data to be called automatically?
The idea of views like a DetailView, ListView, etc. is that it implements the boilerplate logic for you. So it has defined a function def get(self, request, *args, **kwargs) that is used to render the logic. You can usually tweak a few things by specifying the model, queryset, etc. without reimplementing the entire view.
For a DetailView [Django-doc], the logic is implemented in the BaseDetailView you can inspect the source code [GitHub]:
class BaseDetailView(SingleObjectMixin, View):
"""A base view for displaying a single object."""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
One general piece of advice I want to share:
Before overriding any attribute, one must have deep knowledge of what is the significance of that attribute (callable or not callable). This advice applies to any language or framework. Suppose when someone overrides the get in Django, all the methods that are being called from get will not be invoked unless one invokes that from overridden get. So you should see the source of get and observe that methods are called from that.

How do I get the current user for my created_by field in Django 1.9?

I'm trying to pull the session user into my model's save() method. The docs for HttpRequest and all the examples I've found assume you already have the object instanciated, but I can't seem to find a method that will do the instanciation.
I'm thinking I should be using HttpRequest.user , but I don't know how to generate the instance in my model so I can actually do it.
Here's my save() override. It generates type object 'HttpRequest' has no attribute 'user', but considering this is a class and not an object reference that isn't really surprising.:
def save(self, *args, **kwargs):
''' On save, update timestamps '''
if not hasattr(self, 'id'):
self.date_created = timezone.now()
self.created_by = HttpRequest.user
self.last_updated_date = timezone.now()
self.last_updated_by = HttpRequest.user
super(Caregiver, self).save(*args, **kwargs)
views.py added for more info
class CaregiverCreateView(CreateView):
template_name = 'single_form_generic.html'
model = Caregiver
form_class = CaregiverCreateForm
django-cuser can do this easily. This library adds middleware that can be called in a view, model method, etc to get the current user. If you are ok with the user being assigned at the model's save method, then you just need to call the middleware to inspect the session as below.
Once set up, it can be called like this:
from cuser.middleware import CuserMiddleware
class YourModel(models.Model):
def save(self, *args, **kwargs):
self.created_by = CuserMiddleware.get_user()
super(YourModel,self).save(*args, **kwargs)
If you are calling save() explicitly, you could pass the user instance directly to the method:
def save(self, user, *args, **kwargs):
# use the user
Then when you call it in views.py, do:
instance.save(request.user)
I had to dig around a little, but I did end up finding an answer for you future googlers:
The class based view docs actually have a relevant example. Since the view already has access to the request and the form and model are tied, you really only need to inject it in the view submission by overriding the form_valid method there.
def form_valid(self, form):
form.instance.created_by = self.request.user
form.instance.last_updated_by = self.request.user
return super(CaregiverCreateView, self).form_valid(form)

Should I remove user id from URL if I want to keep data available just to exactly one user?

Lets just suppose that we have following url:
example.com/users/1
if user with ID=1 opens it, user receive info about his account, but if user switch 1 with 2, then he can view other user details as well, and we of course do not want that, so I have following solutions:
1) just pass currently logged in user id threw request.user.id inside a template
2) add permission, but I did not find type of permissions that would allow me to do that. Of course I could create dozens of permissions each for each user, but of course that is very nasty way.
Any other ideas how to cope with that in Django?
You can either fill context with the request which makes sure the user will never see another user's data, e.g. (using CBVs):
class AccountView(TemplateView):
"""
Generic account view
"""
template_name = "users/account.html"
def get_context_data(self, **kwargs):
context = super(AccountView, self).get_context_data(**kwargs)
context['user'] = User.objects.get(id=self.request.user.id)
return context
#method_decorator(login_required(login_url=reverse('login')))
def dispatch(self, *args, **kwargs):
return super(AccountView, self).dispatch(*args, **kwargs)
Another approach, to make sure 'fake' urls render 404's is to write an owner_required decorator, e.g.:
def owner_required(function):
#wraps(function)
def decorator(*args, **kwargs):
request = args[1]
user = get_object_or_404(User, username=request.user.username)
if user.is_authenticated() and user.username == kwargs.get('slug'):
return function(*args, **kwargs)
raise Http404
return decorator
You don't need permission to do this.
In your views.py
from django.http import Http404
def myview(request, user_id):
user = request.user
if user_id != request.user.id:
raise Http404
#all your logic here
But if you want user profile to be private, you don't need to use user_id in your url pattern. just use the user object stored in the request variable.

Add object level permission to generic view

The situation is pretty simple:
I'm writing a multi-user blog system. The system should prevent non-owner to edit or delete a blog post. In my view I use generic view.
class BlogUpdateView(UpdateView):
...
I know I should use #method_decorator to decorate dispatch method. However, most example is just #method_decorator(login_required) or model level permission. How can apply object level permission to check whether request.user is the author of this blog post?
For example, I tried to use django-authority apps, and I have a BlogPermission class in this file. and I tried to define a method in this class e.g.
def blog_edit(self, ??, ??)
what should I put into this method?
And then call this like:
#method_decorator(permission_required('blog_permission.blog_edit(???)'))
What should I pass in here?
Update: After read method_decorator code, I find it can only accept function without argument. I think that's why permission_required doesn't work here. But what's the work around about this?
Update solution:
In dispatch method, I check the user permission and then return HttpResponseForbidden() if the user does not meet the permission.
You can do it using class-based-views:
class BlogEdit(UpdateView):
model = Blog
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('blog_permission.blog_edit'):
return HttpResponseForbidden()
return super(BlogEdit, self).dispatch(request, *args, **kwargs)
# OR (for object-level perms)
def get_object(self, *args, **kwargs):
obj = super(BlogEdit, self).get_object(*args, **kwargs)
if not obj.user == self.request.user:
raise Http404 # maybe you'll need to write a middleware to catch 403's same way
return obj
Another option is to use UserPassesTestMixin (or user_passes_test for function-based).
class UserPassesTestMixin
When using class-based views, you can use the
UserPassesTestMixin to do this.
test_func()
You have to override the test_func() method of the class to
provide the test that is performed. Furthermore, you can set any of
the parameters of AccessMixin to customize the handling of
unauthorized users:
from django.contrib.auth.mixins import UserPassesTestMixin
class MyView(UserPassesTestMixin, View):
def test_func(self):
return self.request.user.email.endswith('#example.com')
We can now check if the self.request.user is allowed to process the details passed into the self.request.GET or self.request.POST.
class MyView(UserPassesTestMixin, View):
raise_exception = True # To not redirect to the login url and just return 403. For the other settings, see https://docs.djangoproject.com/en/3.2/topics/auth/default/#django.contrib.auth.mixins.AccessMixin
def test_func(self):
return (
self.request.user.is_staff
or self.request.user.has_perm('app.change_blog')
or self.request.user.email.endswith('#company.staff.com')
or is_requested_object_accessible(self.request.user, self.request.GET, self.request.POST) # If you have a custom checker
)
...

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.