Does anyone know of or can anyone please produce a simple example of Django's class-based generic DeleteView? I want to subclass DeleteView and ensure that the currently logged-in user has ownership of the object before it's deleted. Any help would be very much appreciated. Thank you in advance.
4 Answers
Here's a simple one:
from django.views.generic import DeleteView
from django.http import Http404
class MyDeleteView(DeleteView):
def get_object(self, queryset=None):
""" Hook to ensure object is owned by request.user. """
obj = super(MyDeleteView, self).get_object()
if not obj.owner == self.request.user:
raise Http404
return obj
Caveats:
- The
DeleteView
won't delete onGET
requests; this is your opportunity to provide a confirmation template (you can provide the name in thetemplate_name
class attribute) with a "Yes I'm sure" button whichPOST
s to this view - You may prefer an error message to a 404? In this case, override the
delete
method instead, check permissions after theget_object
call and return a customised response. - Don't forget to provide a template which matches the (optionally customisable)
success_url
class attribute so that the user can confirm that the object has been deleted.

- 4,117
- 2
- 36
- 38
-
3Ah -- this is so helpful! Thank you for taking the time. It's all right there in the docs, but staring at the docs for days takes a toll. – Lockjaw Apr 03 '11 at 21:50
-
Wouldn't it be better to override the dispatch method with the user check? What is the advantage of doing the check in the get_object method? – Erik Aug 31 '12 at 20:53
-
3@Erik @DrMeers I would overwrite the `get_queryset` method. Doing it that way is much simpler: `return self.request.user.foo_set.all()`. The default `get_object` method will then filter off of the queryset, which only has items owned by `self.request.user`. If it's not found, it will 404. – Nick Jul 31 '13 at 23:00
-
Just out of curiosity, what if the user directly posts to the url of MyDeleteView? The view given in the solution above won't be called and so any user can delete any object, regardless of its permissions. – cphyc Feb 18 '17 at 09:14
-
@cphyc the `get_object` method is always called in `DeletionMixin.delete` before the code reaches `self.object.delete()`. Posting directly to the url of `MyDeleteView` *is* the normal way of deleting – DrMeers Feb 19 '17 at 21:46
-
@DrMeers OK! It just seemed counter intuitive at a first glance! – cphyc Feb 21 '17 at 08:52
I've basically sub-classed some of the Generic Class-Based-Views to do exactly that. The main difference is I just filtered out the querysets. I can't vouch for whether this method is any better or worse but it made more sense to me.
Feel free to ignore the "MessageMixin" -- that's just there to easily present Messages using the Django Messaging Framework w/ a variable specified for each view. Here's the code I've written for our site:
Views
from django.views.generic import CreateView, UpdateView, \
DeleteView, ListView, DetailView
from myproject.core.views import MessageMixin
class RequestCreateView(MessageMixin, CreateView):
"""
Sub-class of the CreateView to automatically pass the Request to the Form.
"""
success_message = "Created Successfully"
def get_form_kwargs(self):
""" Add the Request object to the Form's Keyword Arguments. """
kwargs = super(RequestCreateView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
class RequestUpdateView(MessageMixin, UpdateView):
"""
Sub-class the UpdateView to pass the request to the form and limit the
queryset to the requesting user.
"""
success_message = "Updated Successfully"
def get_form_kwargs(self):
""" Add the Request object to the form's keyword arguments. """
kwargs = super(RequestUpdateView, self).get_form_kwargs()
kwargs.update({'request': self.request})
return kwargs
def get_queryset(self):
""" Limit a User to only modifying their own data. """
qs = super(RequestUpdateView, self).get_queryset()
return qs.filter(owner=self.request.user)
class RequestDeleteView(MessageMixin, DeleteView):
"""
Sub-class the DeleteView to restrict a User from deleting other
user's data.
"""
success_message = "Deleted Successfully"
def get_queryset(self):
qs = super(RequestDeleteView, self).get_queryset()
return qs.filter(owner=self.request.user)
Usage
Then, you can easily create your own views to use this type of functionality. For example, I am just creating them in my urls.py:
from myproject.utils.views import RequestDeleteView
#...
url(r'^delete-photo/(?P<pk>[\w]+)/$', RequestDeleteView.as_view(
model=Photo,
success_url='/site/media/photos',
template_name='site/media-photos-delete.html',
success_message='Your Photo has been deleted successfully.'
), name='fireflie-delete-photo-form'),
Forms
Important to note: I have overloaded those get_form_kwargs() methods to provide my Forms with an instance of 'request'. If you don't want the Request object passed to the Form, simply remove those overloaded methods. If you want to use them, follow this example:
from django.forms import ModelForm
class RequestModelForm(ModelForm):
"""
Sub-class the ModelForm to provide an instance of 'request'.
It also saves the object with the appropriate user.
"""
def __init__(self, request, *args, **kwargs):
""" Override init to grab the request object. """
self.request = request
super(RequestModelForm, self).__init__(*args, **kwargs)
def save(self, commit=True):
m = super(RequestModelForm, self).save(commit=False)
m.owner = self.request.user
if commit:
m.save()
return m
This is a bit more than you asked -- but it helps to know how to do the same for Create and Update views as well. This same general methodology could also be applied to ListView & DetailView.
MessageMixin
Just in case anyone wants that MessageMixin I use.
class MessageMixin(object):
"""
Make it easy to display notification messages when using Class Based Views.
"""
def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message)
return super(MessageMixin, self).delete(request, *args, **kwargs)
def form_valid(self, form):
messages.success(self.request, self.success_message)
return super(MessageMixin, self).form_valid(form)

- 1,599
- 1
- 16
- 30
-
3There is now django.contrib.messages.views.SuccessMessageMixin. https://code.djangoproject.com/ticket/16319 – pymarco Dec 06 '13 at 17:45
-
To add the request instance to your context in general, use the `django.template.context_processors.request` context processor. – Risadinha Feb 04 '16 at 09:31
The simplest way to do that is to prefilter the queryset:
from django.views.generic import DeleteView
class PostDeleteView(DeleteView):
model = Post
success_url = reverse_lazy('blog:list_post')
def get_queryset(self):
owner = self.request.user
return self.model.objects.filter(owner=owner)

- 165
- 2
- 5
-
2I love this answer, you only query for objects one time (some solutions on this page require double-lookups) and if the object is filtered out of the user's scope it inherently raises an Http404 error, no extra user auth logic required. – xref Feb 03 '18 at 03:07
-
this is so clever; just allow me a question, the result of this (from a UX design perspective) to raise a 404 generic, or - something else? thank you! – Carlo May 12 '22 at 10:32
-
@Carlo this raises a 404 error, which (to me) isn't ideal. you can customize the 404 template at least: https://docs.djangoproject.com/en/4.1/ref/views/#error-views – Nathaniel Hoyt Feb 20 '23 at 16:56
-
@NathanielHoyt thank you. My question was more related to the fact that would have been nicer to show something like "you are not allowed to delete this" or similar, not just a 404 page. The customization yep, that's absolutely known, thank you! Mine was more of a "best UX" perspective. – Carlo Feb 20 '23 at 18:29
-
1@Carlo i see, and i agree, i dislike the 404 result. what i ended up going with was overriding `get_object()` to return `None` if the owner doesn't have permission. then I overrode the `post()` method to check for a None value object, redirect to a different page with a message regarding permissions. it's kinda clunky solution but it works until i find something more elegant. – Nathaniel Hoyt Feb 21 '23 at 19:25
I would propose that the best (and simplest) way to do this would be to use the UserPassesTestMixin
which gives you a cleaner separation of concerns.
Example:
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import DeleteView
class MyDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
def test_func(self):
""" Only let the user access this page if they own the object being deleted"""
return self.get_object().owner == self.request.user

- 3,443
- 1
- 29
- 34
-
2I believe this will make the objects fetch twice from the db, first when test_func is called, and a second time when the view actually renders? – xref Feb 02 '18 at 06:12