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.
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 on GET requests; this is your opportunity to provide a confirmation template (you can provide the name in the template_name class attribute) with a "Yes I'm sure" button which POSTs to this view
You may prefer an error message to a 404? In this case, override the delete method instead, check permissions after the get_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.
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)
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)
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
Related
I am working on blog project in which I added an add post which add post now I want only the superuser can add post and that page is visible only to superuser.
1st Method
Views.py
class AddPostView(CreateView):
model = Post
template_name = 'MainSite/add_post.html'
fields = '__all__'
this is my current view I am able to achieve authenticate for superuser using 2nd method
2nd Method
class AddPostView(View):
def get(self,request):
if request.user.is_superuser == True:
return render(...)
else:
pass
How can I achieve the same result using 1st method.I tried using LoginRequiredMixin but nothing is happening . I just import LoginRequiredMixin and use it like this .
class Addpost(CreateView,LoginRequiredMixin):
...
Thanks in advance and advice will be helpful.
You can work with a UserPassesTestMixin mixin [Django-doc]:
from django.contrib.auth.mixins import UserPassesTestMixin
class AddPostView(UserPassesTestMixin, CreateView):
# …
def test_func(self):
return self.request.user.is_superuser
# …
You can encapsulate this in a mixin:
from django.contrib.auth.mixins import UserPassesTestMixin
class AdminRequiredMixin(UserPassesTestMixin):
def test_func(self):
return self.request.user.is_superuser
and then use this mixin:
class AddPostView(AdminRequiredMixin, CreateView):
# …
def test_func(self):
return self.request.user.is_superuser
# …
Mixins should be put before the actual view in the inheritance hierarchy: otherwise these appear after the view in the method resolution order (MRO), and thus likely will not override the behavior (correctly).
class AddPostView(CreateView,LoginRequiredMixin):
model = Post
template_name = 'MainSite/add_post.html'
fields = '__all__'
def dispatch(self, request, *args, **kwargs):
if request.user.is_anonymous:
return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())
elif request.user.is_superuser:
return render(.....)
else
return super(AddPostView, self).dispatch(request, *args, **kwargs)
Use method_decorator and user_passes_test to achieve this
from django.views.generic import View
from django.utils.decorators import method_decorator
from django.contrib.auth.decorators import user_passes_test
class AddPostView(View):
#method_decorator(user_passes_test(lambda u: u.is_superuser))
def post(self, *args, **kwargs):
pass
I feel like this is really simple and I'm just missing it.
I have a very simple generic class based view detail.
When I do teh get_object I want to make sure that the request.user is in the set that belongs to the object. If not, redirect them to the login.
Here's my view:
class AwesomeDetail(LoginRequiredMixin, DetailView):
"""
An awesome detail
"""
template_name = "awesomeness/detail.html"
def get_object(self):
awesomeness = ModelName.objects.get(id=self.kwargs['pk'])
if self.request.user in awesomeness.staff.all():
return awesomness
else:
return redirect('account_login')
Staff is a many to many to users. What am I missing? The redirect isn't happening. It renders the template, but of course, awesomeness is missing.
UserPassesTestMixin was introduced in Django 1.9.
You define a test_func method to return True/Fales depending on whether the test passes.
If the user fails the test they will be redirected to settings.LOGIN_URL with a redirect field.
UserPassesTestMixin supports any of the fields for AccessMixin.
from django.contrib.auth.mixins import UserPassesTestMixin
class AwesomeDetail(UserPassesTestMixin, LoginRequiredMixin, DetailView):
"""
An awesome detail
"""
template_name = "awesomeness/detail.html"
model = ModelName
def test_func(self):
if self.request.user in self.object.staff.all():
return True
else:
return False
I'm not very sure about this, but sounds like get method is the one you should work on:
class AwesomeDetail(LoginRequiredMixin, DetailView):
"""
An awesome detail
"""
template_name = "awesomeness/detail.html"
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if not self.request.user in self.object.staff.all():
return redirect('account_login')
else:
return super(AwesomeDetail, self).get(request, *args, **kwargs)
Suppose I have a ViewSet:
class ProfileViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows a user's profile to be viewed or edited.
"""
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly)
queryset = Profile.objects.all()
serializer_class = ProfileSerializer
def perform_create(self, serializer):
serializer.save(user=self.request.user)
...and a HyperlinkedModelSerializer:
class ProfileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Profile
read_only_fields = ('user',)
I have my urls.py set up as:
router.register(r'profiles', api.ProfileViewSet, base_name='profile')
This lets me access e.g. /api/profile/1/ fine.
I want to set up a new endpoint on my API (similar to the Facebook API's /me/ call) at /api/profile/me/ to access the current user's profile - how can I do this with Django REST Framework?
Using the solution by #Gerard was giving me trouble:
Expected view UserViewSet to be called with a URL keyword argument named "pk". Fix your URL conf, or set the .lookup_field attribute on the view correctly..
Taking a look at the source code for retrieve() it seems the user_id is not used (unused *args)
This solution is working:
from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from rest_framework import filters
from rest_framework import viewsets
from rest_framework import mixins
from rest_framework.decorators import list_route
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
serializer_class = UserSerializer
User = get_user_model()
queryset = User.objects.all()
filter_backends = (filters.DjangoFilterBackend, filters.SearchFilter)
filter_fields = ('username', 'email', 'usertype')
search_fields = ('username', 'email', 'usertype')
#list_route(permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
User = get_user_model()
self.object = get_object_or_404(User, pk=request.user.id)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
Accessing /api/users/me replies with the same data as /api/users/1 (when the logged-in user is user with pk=1)
You could create a new method in your view class using the list_route decorator, like:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route()
def me(self, request, *args, **kwargs):
# assumes the user is authenticated, handle this according your needs
user_id = request.user.id
return self.retrieve(request, user_id)
See the docs on this for more info on #list_route
I hope this helps!
You can override the get_queryset method by filtering the queryset by the logged in user, this will return the logged in user's profile in the list view (/api/profile/).
def get_queryset(self):
return Profile.objects.filter(user=self.request.user)
or
def get_queryset(self):
qs = super(ProfileViewSet, self).get_queryset()
return qs.filter(user=self.request.user)
or override the retrieve method like so, this will return the profile of the current user.
def retrieve(self, request, *args, **kwargs):
self.object = get_object_or_404(Profile, user=self.request.user)
serializer = self.get_serializer(self.object)
return Response(serializer.data)
From Gerard's answer and looking at the error pointed out by delavnog, I developed the following solution:
class ProfileViewSet(viewsets.ModelViewSet):
#list_route(methods=['GET'], permission_classes=[IsAuthenticated])
def me(self, request, *args, **kwargs):
self.kwargs.update(pk=request.user.id)
return self.retrieve(request,*args, **kwargs)
Notes:
ModelViewSet inherits GenericAPIView and the logic to get an object is implemented in there.
You need to check if the user is authenticated, otherwise request.user will not be available. Use at least permission_classes=[IsAuthenticated].
This solution is for GET but you may apply the same logic for other methods.
DRY assured!
Just override the get_object()
eg.
def get_object(self):
return self.request.user
Just providing a different way. I did it like this:
def get_object(self):
pk = self.kwargs['pk']
if pk == 'me':
return self.request.user
else:
return super().get_object()
This allows other detail_routes in the ViewSet to work like /api/users/me/activate
I've seen quite a few fragile solutions so I thought I'll respond with something more up-to-date and safer. More importantly you don't need a separate view, since me simply acts as a redirection.
#action(detail=False, methods=['get', 'patch'])
def me(self, request):
self.kwargs['pk'] = request.user.pk
if request.method == 'GET':
return self.retrieve(request)
elif request.method == 'PATCH':
return self.partial_update(request)
else:
raise Exception('Not implemented')
It's important to not duplicate the behaviour of retrieve like I've seen in some answers. What if the function retrieve ever changes? Then you end up with a different behaviour for /me and /<user pk>
If you only need to handle GET requests, you could also use Django's redirect. But that will not work with POST or PATCH.
Considering a OneToOneField relationship between the Profile and the User models with related_name='profile', I suggest the following as the #list_route has been deprecated since DRF 3.9
class ProfileViewSet(viewsets.GenericViewSet):
serializer_class = ProfileSerializer
#action(methods=('GET',), detail=False, url_path='me', url_name='me')
def me(self, request, *args, **kwargs):
serializer = self.get_serializer(self.request.user.profile)
return response.Response(serializer.data)
Hi Stackoverflow people,
In my clean function in forms.py, I would like to save automatically some information in a session variable. However, I do not seem to get access to the request variable.
All examples for handing over the request variable are based on function based views, but here I am using a class based view.
My forms.py:
from django import forms
from item.models import Item
class CreateItemForm(forms.ModelForm):
class Meta:
model = Item
fields = ('name', 'description')
def __init__(self, request, *args, **kwargs):
self.request = request
super(CreateItemForm, self).__init__(*args, **kwargs)
def clean(self):
cleaned_data = super(CreateItemForm, self).clean()
if cleaned_data.get("address"):
self.request.session['name'] = cleaned_data.get("name")
else:
raise forms.ValidationError(_('Oops, can\'t find location.'))
return self.cleaned_data
My views.py:
from django.views.generic.edit import FormView
from item.forms import CreateItemForm
class ItemCreate(FormView):
form_class = CreateItemForm
template_name = 'item/item_create.html'
success_url = 'http://www.google.com'
What is the best way to hand over the request variable from the views.py to forms.py?
Thank you for your answer.
You can overwrite the FormMixin's get_form_kwargs method to add the request for to the form's init parameters:
class ItemCreate(FormView):
def get_form_kwargs(self):
kwargs = super(ItemCreate, self).get_form_kwargs()
kwargs.update({
'request' : self.request
})
return kwargs
Overriding the form.get_initial() works for me
class ItemCreate(FormView):
def get_initial(self):
init = super(ItemCreate, self).get_initial()
init.update({'request':self.request})
return init
And in the clean we can access it with form.initial dict
class sampleForm(forms.Form):
...
...
def clean(self):
user_request = self.initial['request']
By the way, we don't need to pop the extra args like suggested above.
I'm trying to update a model in Django using the class-based generic view UpdateView.
I read the page Updating User model in Django with class based UpdateView to try and get me started, but I'm getting an error 'WSGIRequest' object has no attribute 'id'
I'm a fresh face to Django, so please be forgiving if I'm doing something stupid.
//urls.py
url(r'^portfolios/update/(?P<id>\d+)/$',PortfoliosUpdateView.as_view()),
//views.py
class PortfoliosUpdateView(UpdateView):
form_class = PortfoliosCreateForm
model = Portfolios
template_name = 'portfolios/create.html'
def get(self, request, **kwargs):
self.object = Portfolios.objects.get(id=self.request.id)
form_class = self.get_form_class()
form = self.get_form(form_class)
context = self.get_context_data(object=self.object, form=form)
return self.render_to_response(context)
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.request.id)
return obj
It's mostly just a modified version of the code originally posted, but I thought it'd work. I know that I'm trying to retrieve the id passed as a GET parameter, but that doesn't seem to come through in the request variable. Am I going about this the wrong way?
Thanks
Edit: I think I fixed it, but this may be wrong:
I changed the lines
self.object = Portfolios.objects.get(id=self.request.id)
obj = Portfolios.objects.get(id=self.request.id)
to
self.object = Portfolios.objects.get(id=self.kwargs['id'])
obj = Portfolios.objects.get(id=self.kwargs['id'])
I could be wrong.
It should be:
def get_object(self, queryset=None):
obj = Portfolios.objects.get(id=self.kwargs['id'])
return obj
Look at class based generic view dispatch explains that keyword arguments are assigned to self.kwargs.:
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
self.request = request
self.args = args
self.kwargs = kwargs
return handler(request, *args, **kwargs)
id = self.request.GET.get('id',None) is what you needed when trying to access the GET query string.
However, your view can be simplified:
from django.conf.urls import *
from django.views.generic import UpdateView
from yourapp.models import Portfolios
from yourapp.forms import PortfoliosCreateForm
urlpatterns = patterns('',
url('^portfolios/update/(?P<pk>[\w-]+)$', UpdateView.as_view(
model=Portfolios,
form_class=PortfoliosCreateForm,
template_name='portfolios/create.html',
success_url='/portfolios'
), name='portfolio_update'),
)
views.py
class MyUpdateView(UpdateView):
model = ModelName # required
template_name = 'x/h1.html'
form_class = ModelNameForm
success_url = reverse_lazy('app:page1')
def get_queryset(self):
"""
Optional condition to restrict what users can see
"""
queryset = super().get_queryset()
return queryset.filter(id__lt=20)
def get_success_url(self):
return reverse_lazy(
'app1:abc',
kwargs={'pk': self.object.id}
)
urls.py
In urlpatterns=[]
path('xyz/<pk>/', MyUpdateView.as_view(),name='xyz')
my_model_view.html
{{form}}
You will be able to edit ModelName at url /xyz/<pk>/ where <pk> can be anything from 1 to 20 based on our condition in get_queryset(). Take that condition out to allow users to edit any object.
self.object is only available after post request to the UpdateView.