Example of Django Class-Based DeleteView
Solution 1:
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.
Solution 2:
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)
Solution 3:
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)
Solution 4:
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