I've got a UpdateView in Django that I need to restrict to only the author. I having trouble grabbing the Author off of the request.
class MyPermissionMixin(LoginRequiredMixin, UserPassesTestMixin):
def dispatch(self, request, *args, **kwargs):
user_test_result = self.get_test_func()()
if request.user != ????.user: #How do I grab the user('Author')??
return self.handle_no_permission()
return super().dispatch(request, *args, **kwargs)
Get Autor instance user through self.get_object()
class MyPermissionMixin(object):
def dispatch(self, request, *args, **kwargs):
if request.user != self.get_object().author:
return HttpResponseForbidden()
return super().dispatch(request, *args, **kwargs)
Related
I am using Django Rest Framework and its class-based view, with database connection set to ATOMIC_REQUESTS=True.
To optimize the application, I would like to have it so that only PUT and POST requests are under transaction, but not GET. Since I have ATOMIC_REQUESTS on, the documentation says to use the decorator on the dispatch() method in the view. So this is what I tried:
class MyView(APIView):
def get():
print("I am in a transaction:", not transaction.get_autocommit())
return Response({})
def post():
print("I am in a transaction:", not transaction.get_autocommit())
return Response({})
def put():
print("I am in a transaction:", not transaction.get_autocommit())
return Response({})
def dispatch(self, request, *args, **kwargs):
"""Override dispatch method and selectively apply non_atomic_requests"""
if request.method.lower() == "get":
return self.non_atomic_dispatch(request, *args, **kwargs)
else:
return super().dispatch(request, *args, **kwargs)
#transaction.non_atomic_requests
def non_atomic_dispatch(self, request, *args, **kwargs):
"""Special dispatch method decorated with non_atomic_requests"""
return super().dispatch(request, *args, **kwargs)
This does not work, since the get method still reports itself as under transaction, however, if I switch the decorator to look like this:
...
#transaction.non_atomic_requests
def dispatch(self, request, *args, **kwargs):
if request.method.lower() == "get":
return self.non_atomic_dispatch(request, *args, **kwargs)
else:
return super().dispatch(request, *args, **kwargs)
def non_atomic_dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
...
This works, though obviously it's not what I want.
My questions are:
Why does the placement of the decorator matter, if I am merely returning the same thing?
How do I make this work the way I want?
Is it even possible?
I can't find anything or figure it out by myself since I'm a beginner, so I turn to you guys.
Here's an example (don't worry about the use case, I just want to know if it's possible and how):
When I run this code I get that *args is not defined. What's wrong?
views.py:
class MyCreateView(CreateView):
def get(self, request, *args, **kwargs):
slug = kwargs['slug']
helper_method(self, slug)
helpers.py:
def helper_method(self, slug):
if slug == "random":
return super(self.__class__, self).get(request, *args, **kwargs)
You have to define args and kwargs, you just need to add them to your method parameters like this:
def helper_method(self, slug, *args, **kwargs):
if slug == "random":
return super(self.__class__, self).get(request, *args, **kwargs)
class MyCreateView(CreateView):
def get(self, request, *args, **kwargs):
slug = kwargs['slug']
helper_method(self, slug, *args, **kwargs)
I am making a basic app to teach beginners. Each user can write notes, but I want to make it so that a user cannot view or update a different user's notes.
I have the following view, but I had to repeat myself.
from django.core.exceptions import PermissionDenied
...
class NoteUpdate(LoginRequiredMixin, UpdateView):
...
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.owner != self.request.user:
raise PermissionDenied
return super(NoteUpdate, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.owner != self.request.user:
raise PermissionDenied
return super(NoteUpdate, self).post(request, *args, **kwargs)
I feel like there is probably a way to do this without repeating myself. Yeah, I could write a method like this and call it from both:
def check_permission(self):
if self.object.owner != self.request.user:
raise PermissionDenied
But what I really mean is am I overriding the wrong methods? Is there a more traditional way to do this? It feels a little weird overriding .get() and .post()
To answer your question: Overriding .get() and .post() is fine, since for security and integrity reasons, you would want both your get() and post() views to validate before displaying and especially modifying data. Now, if you want to refactor doing this in get or post, there are 2 easy ways of doing this:
Primary (Model Method):
models.py
class Model(models.Model):
owner = models.ForeignKey(User)
...
def deny_if_not_owner(self, user):
if self.owner != user:
raise PermissionDenied
return self.owner
views.py
class NoteUpdate(LoginRequiredMixin, UpdateView):
...
def get(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.deny_if_not_owner(request.user)
return super(NoteUpdate, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
self.object.deny_if_not_owner(request.user)
return super(NoteUpdate, self).post(request, *args, **kwargs)
///////
Alternative (Mixin):
Creating a Mixin would allow you to easily add this code to many classes if you see yourself using this validation again in the future.
class DenyWrongUserMixin(object):
def get(self):
if self.object.owner != self.request.user:
raise PermissionDenied
return super(DenyWrongUserMixin, self).get(*args, **kwargs)
def post(self):
if self.object.owner != self.request.user:
raise PermissionDenied
return super(DenyWrongUserMixin, self).post(*args, **kwargs)
and then:
class NoteUpdate(LoginRequiredMixin, DenyWrongUserMixin, UpdateView):
...
def get(self, request, *args, **kwargs):
...
def post(self, request, *args, **kwargs):
...
You can override the get method or the get_queryset method. The get_queryset will raise a 404 if the logged in user is not the owner.
def get_queryset(self):
qs = super(NoteUpdate, self).get_queryset()
return qs.filter(owner=self.request.user)
or you can just override the get method since it will be called first and then raise PermissionDenied, so no reason to override the post method as well.
def get(self, request, *args, **kwargs):
self.object = self.get_object()
if self.object.owner != self.request.user:
raise PermissionDenied
return super(NoteUpdate, self).get(request, *args, **kwargs)
Then you can create a mixin and extend your views from the mixin to avoid the duplication.
I am trying to do something like the following to work, but I keep receiving the error 'RegionsView' object has no attribute 'method'. What am I doing wrong? Thanks
#views.py
class _LanguageMixin(object):
def dispatch(self, request, *args, **kwargs):
self.langcode = kwargs.pop("langcode")
self.language = get_object_or_404(Language, pk=self.langcode)
return super(_LanguageMixin, self).dispatch(self, request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super(_LanguageMixin, self).get_context_data(self, **kwargs)
context.update({"language": self.language,
"languages": Language.objects.values_list('code',
flat=True)})
return context
class RegionsView(_LanguageMixin, TemplateView):
template_name = "regions.html"
def get_context_data(self, **kwargs):
context = super(RegionsView, self).get_context_data(self, **kwargs)
regions = #......
context.update({"regions": regions})
return context
#urls.py
url(r'^(?P<langcode>[a-zA-Z-]+)/regions/$', RegionsView.as_view(), name='regions')
return super(_LanguageMixin, self).dispatch(request, *args, **kwargs)
instead of
return super(_LanguageMixin, self).dispatch(self, request, *args, **kwargs)
(request.method is used in the dispatch function, but you use self object)
Works like a charm:
MyCreateView(CreateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
But the following doesn't:
MyUpdateView(UpdateView):
template_name = "my_template_name"
form_class = MyModelForm
success_url = "/success/"
I get this error:
MyUpdateView is missing a queryset. Define MyUpdateView.model, MyUpdateView.queryset, or override MyUpdateView.get_queryset().
Why does an UpdateView need model, queryset or get_queryset defined to not cause an error while CreateView doesn't? Shouldn't it be able to automatically derive it from the Model used in the ModelForm?
Currently (django 1.5.1 official release) UpdateView is calling self.get_object() to be able to provide instance object to Form.
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L217:
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(BaseUpdateView, self).post(request, *args, **kwargs)
And self.get_object method needs one of this properties declared: model, queryset or get_queryset
Whereas CreateView don't call self.get_object().
From https://github.com/django/django/blob/1.5c2/django/views/generic/edit.py#L194:
def get(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
You might have a problem in your urls.py file.
What I think you wrote in it is:
url(r'foldername/(?P[0-9]+)/$', views.UpdateView.as_view(), name='update'),
but you have to change UpdateView to MyUpdateView, like this:
url(r'foldername/(?P[0-9]+)/$', views.MyUpdateView.as_view(), name='update'),