Class-based views: where to check for permissions? - python

I am not very comfortable using class-based views but I am aware of their perks so I am forcing myself to start using them more often.
There's this view that receives a path param: manage/:id to manage a particular entity.
class MyView(TemplateView):
template_name = '...'
def get_context_data(self, **kwargs):
context = super(MyView, self).get_context_data(**kwargs)
context['entity'] = get_object_or_404(Entity, pk=self.args[0])
return context
An Entity includes a list of authorized users to perform special actions. This view, MyView is one of those special actions.
I tried making a decorator for the view but it required finding the Entity first so I wasn't sure how to work that out.
Now, I have a check_permission(request, entity) function that checks if the current user is one of these authorized ones.
My question is where should I call this function in the class-based views like MyView which will be any of these views considered "special actions"?
Should I call it just from get_context_data()?

put it into dispatch(). It could look like this:
class MyView(TemplateView):
template_name = '...'
def dispatch(self, request, *args, **kwargs):
entity = get_object_or_404(Entity, pk=args[0])
if not check_permission(request, entity):
raise Http404
return super(MyView, self).dispatch(request, *args, **kwargs)

You can check permissions in the dispatch as yedpodtrzitko has said. I think it's also a good idea to throw it inside of a mixin that you can put on your views.
Here's an example:
from django.core.exceptions import PermissionDenied
class ViewPermissionsMixin(object):
"""Base class for all custom permission mixins to inherit from"""
def has_permissions(self):
return True
def dispatch(self, request, *args, **kwargs):
if not self.has_permissions():
raise PermissionDenied
return super(ViewPermissionsMixin, self).dispatch(
request, *args, **kwargs)
class MyCustomPermissionMixin(ViewPermissionsMixin):
def has_permissions(self):
# here you will have access to both
# self.get_object() and self.request.user
return self.request.user in self.get_object().special_list_of_people
Now you can throw MyCustomPermissionMixin on your view:
class MyView(MyCustomPermissionMixin, TemplateView):
# ...
In your case, since you're using a TemplateView, you should also make a get_object() method that returns the object that you want to deal with. Template views don't have this method by default.
Finally, just want to say that you will love Django's class based views once you learn some more about how to use them.

Take a look at Django Braces, it's a solid set of mixins which are designed around permissions.
How specifically you deal with permissions depends largely on implementation. I've done it in dispatch() before which is the way Braces does it but if it's specific to an object or queryset, I'll do it in the actual get_object or get_queryset methods as part of a DetailView.
For example if you had a creator associated with an Entity, you could override get_object to check the current logged in user is the Entity's creator.
class EntityView(LoginRequiredMixin, DetailView):
model = Thing
def get_object(self, **kwargs):
return Entity.objects.get_object_or_404(
pk=kwargs['entity_id'],
creator=self.request.user
)
Note: LoginRequiredMixin is a part of Braces. Very slick.

Related

DRF - Create a Viewset Mixin/Baseclass to inherit a viewset-action

I have a website that is essentially a wiki for a DnD campaign that I am participating in. As such it has articles of Creatures, Characters, Locations and more. I wanted to use Viewsets to access them easily and wanted to use a Viewset action (together with a custom router) to be able to look for individual records not through pk, but through various query-parameters.
I already have something that works for this, now I would like to apply some inheritance to it to not repeat myself. What I'd like to do is something like this:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
#action(detail=True, url_name="detail-params", url_path=detail_with_params_url_pattern_suffix)
def detail_through_params(self, request, **kwargs):
if self.detail_with_params_url_pattern_suffix == "":
raise InvalidViewsetURLException("URL of view 'detail_through_params' of WikiBaseViewset is not defined!")
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
detail_with_params_url_pattern_suffix = "(?P<name__iexact>.+)"
However, I'm struggling over the fact that the decorator absolutely requires the URL parameter in the base class. Otherwise the code just doesn't compile due to a NameError complaining that detail_with_params_url_pattern_suffix is not defined. If you were to set detail_with_params_url_pattern_suffix="" in the base-class in order to not get an Error when your code is compiled, that still wouldn't matter, as the decorator from my experiments so far still grabs the value of that variable from WikiBaseViewset not CharacterViewSet.
How can I rewrite my BaseClass so that this works? Is there even a way?
I did not find a fully satisfying answer to this problem, but in the end acquiesced with this solution, as it was better than copy pasting.
You might not be able to inherit viewset actions, but you sure can inherit individual methods and then just overwrite them in the child and throw a decorator on top. This lead to this structure:
class WikiBaseViewset (viewsets.ModelViewSet):
detail_with_params_url_pattern_suffix: str
def detail_through_params(self, request, **kwargs):
model = self.serializer_class.Meta.model
instance = get_object_or_404(model, **kwargs)
serializer = self.get_serializer(instance)
return Response(serializer.data)
class CharacterSerializer (serializers.HyperlinkedModelSerializer):
class Meta:
model = wiki_models.Character
fields = '__all__'
class CharacterViewSet(WikiBaseViewset):
"""Called with URLs: character, character/<str: name>"""
serializer_class = CharacterSerializer
queryset = wiki_models.Character.objects.all()
#action(detail=True, url_name="detail-params", url_path="(?P<name__iexact>.+)")
def detail_through_params(self, request, **kwargs):
return super().detail_through_params(request, **kwargs)

Overriding get method in class based view with super()

I created a view that manages the products which belong to the current log-in user (so the current user can watch its own products):
class ProductList(View):
def get(self, request, *args, **kwargs):
products = Product.objects.get(user=self.request.user)
#...
I would like to subclass another view from it, which manages the products that belong to a user specified as a GET parameter in the url (probably different to the current user). Is it possible to do it with super() and overriding the method in product_list? Something like this:
class ProductListFromUser(ProductList):
def get(self, request, *args, **kwargs):
#not sure what code if necessary to put here...
super().get(self, request, *args, **kwargs):
#or here
#...
Basically, how can I override the get method from product_list , changing only "self.request.user" by the parameter taken from the URL
You shouldn't do either of these.
If you have a view that renders a list of items, you should use a subclass of ListView. In that view, you can define the product list in the get_queryset method, which you can define in the relevant way in your two different views.
class ProductList(ListView):
def get_queryset(self, *args, **kwargs):
return Product.objects.filter(user=self.request.user)
class ProductListFromUser(ListView):
def get_queryset(self, *args, **kwargs):
return Product.objects.filter(user__username=self.request.GET['user'])
You should rarely need to define get (or post) directly in a class-based view.
I would move the retrieval of the appropriate QuerySet to an extra method which you can override in order to reuse as much of the initial code as possible:
class ProductList(View):
def get_products(self):
return Product.objects.all()
def get(self, request, *args, **kwargs):
products = self.get_products()
# ...
class ProductList(View):
def get_products(self):
qs = super().get_products()
qs = qs.filter(user=self.request.user)
return qs
# no get needed
This very functionality comes with django's ListView and its get_queryset method.
If you retrieve the queryset for a given request using the get_queryset method, that CBV such as DetailView use, then you can simply override the get_queryset of the parent-class.

What is the opposite of LoginRequiredMixin, How deny the access to a logged in user?

For example, if I don't want to give access to the "Register" view if the user is already logged in?
I use this at the top of each view, and it works fine:
def get(self, request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect('/')
return super(MyCBV_vw, self).get(request, *args, **kwargs)
But maybe there is a decorator o mixin for this, I would like to know if there is a better way to do this? Thanks!
Django has a user_passes_test decorator which I think is what you need. The decorator takes a function (with other optional ones) as argument.
You can write this function to redirect all logged in users who are trying to access that view:
from django.contrib.auth.decorators import user_passes_test
def not_logged_in(user):
return not user.is_authenticated()
#user_passes_test(not_logged_in)
def my_view(request, *args, **kwargs):
# your code
Remember to use a method_decorator to apply this decorator to your get method.
You could create a custom 'LogoutRequiredMixin':
class LogoutRequiredMixin(View):
def dispatch(self, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect('/')
return super(LogoutRequiredMixin, self).dispatch(*args, **kwargs)
Then use it in your views:
class RegisterView(LogoutRequiredMixin):
def get(...):
...
I'm using class based views, so, I had to used a mixin:
class NotLoggedAllow(UserPassesTestMixin):
login_url = '/profile/'
def test_func(self):
return not self.request.user.is_authenticated()
class Register_vw(NotLoggedAllow, FormView):
This way I just have to add the name of my mixin at each view, and it does deny the access to a logged in users. Nailed #Moses Koledoye, thanks everyone!

Django Class Based View use #cache_page [duplicate]

I'm trying to do cache_page with class based views (TemplateView) and i'm not able to. I followed instructions here:
Django--URL Caching Failing for Class Based Views
as well as here:
https://github.com/msgre/hazard/blob/master/hazard/urls.py
But I get this error:
cache_page has a single mandatory positional argument: timeout
I read the code for cache_page and it has the following:
if len(args) != 1 or callable(args[0]):
raise TypeError("cache_page has a single mandatory positional argument: timeout")
cache_timeout = args[0]
which means it wont allow more than 1 argument. Is there any other way to get cache_page to work?? I have been digging into this for sometime...
It seems like the previous solutions wont work any longer
According to the caching docs, the correct way to cache a CBV in the URLs is:
from django.views.decorators.cache import cache_page
url(r'^my_url/?$', cache_page(60*60)(MyView.as_view())),
Note that the answer you linked to is out of date. The old way of using the decorator has been removed (changeset).
You can simply decorate the class itself instead of overriding the dispatch method or using a mixin.
For example
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
#method_decorator(cache_page(60 * 5), name='dispatch')
class ListView(ListView):
...
Django docs on decorating a method within a class based view.
yet another good example CacheMixin
from cyberdelia github
class CacheMixin(object):
cache_timeout = 60
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return cache_page(self.get_cache_timeout())(super(CacheMixin, self).dispatch)(*args, **kwargs)
usecase:
from django.views.generic.detail import DetailView
class ArticleView(CacheMixin, DetailView):
cache_timeout = 90
template_name = "article_detail.html"
queryset = Article.objects.articles()
context_object_name = "article"
You can add it as a class decorator and even add multiple using a list:
#method_decorator([vary_on_cookie, cache_page(900)], name='dispatch')
class SomeClass(View):
...
I created this little mixin generator to do the caching in the views file, instead of in the URL conf:
def CachedView(cache_time=60 * 60):
"""
Mixing generator for caching class-based views.
Example usage:
class MyView(CachedView(60), TemplateView):
....
:param cache_time: time to cache the page, in seconds
:return: a mixin for caching a view for a particular number of seconds
"""
class CacheMixin(object):
#classmethod
def as_view(cls, **initkwargs):
return cache_page(cache_time)(
super(CacheMixin, cls).as_view(**initkwargs)
)
return CacheMixin
Yet another answer, we found this to be simplest and is specific to template views.
class CachedTemplateView(TemplateView):
#classonlymethod
def as_view(cls, **initkwargs): ##NoSelf
return cache_page(15 * 60)(super(CachedTemplateView, cls).as_view(**initkwargs))
Would like to add this:
If you need to use multiple decorators for cache like vary_on_headers and cache_page together, here is one way I did:
class CacheHeaderMixin(object):
cache_timeout = int(config('CACHE_TIMEOUT', default=300))
# cache_timeout = 60 * 5
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
return vary_on_headers('Authorization')(cache_page(self.get_cache_timeout())(super(CacheHeaderMixin, self).dispatch))(*args, **kwargs)
This way cache is stored and it varies for different Authorization header (JWT). You may use like this for a class based view.
class UserListAPIView(CacheHeaderMixin, ListAPIView):
serializer_class = UserSerializer
def get_queryset(self):
return CustomUser.objects.all()
I didn't found a good cache solution for class based views and created my own: https://gist.github.com/svetlyak40wt/11126018
It is a mixin for a class. Add it before the main base class and implement method get_cache_params like that:
def get_cache_params(self, *args, **kwargs):
return ('some-prefix-{username}'.format(
username=self.request.user.username),
3600)
Here's my variation of the CachedView() mixin - I don't want to cache the view if the user is authenticated, because their view of pages will be unique to them (e.g. include their username, log-out link, etc).
class CacheMixin(object):
"""
Add this mixin to a view to cache it.
Disables caching for logged-in users.
"""
cache_timeout = 60 * 5 # seconds
def get_cache_timeout(self):
return self.cache_timeout
def dispatch(self, *args, **kwargs):
if hasattr(self.request, 'user') and self.request.user.is_authenticated:
# Logged-in, return the page without caching.
return super().dispatch(*args, **kwargs)
else:
# Unauthenticated user; use caching.
return cache_page(self.get_cache_timeout())(super().dispatch)(*args, **kwargs)

django class based view returns empty string when POST

To demostrate:
from django.views.generic.base import View
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
class TestView(View):
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return HttpResponse('haha')
urls.py is
url(r'^test/', TestView.as_view()),
so when GET you can see haha, but when doing POST you get a blank page...
What am I missing here?
Edit: To clarify what I am doing. I am writing a JSON stream CURD view, which I need to parse JSON in various ways. One of them is when ppl POST data with certain pattern the view will dispatch to another method inside the view and return something. But it turns out returns nothing instead. So I present you the minimal PoC. Please help me what my code wenti wrong. TIA!
btw possible related question
You need to implement its post method. See Class based views.
from django.http import HttpResponse
from django.views.generic import View
class TestView(View):
#method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
# do something
return super(TestView, self).dispatch(*args, **kwargs)
def post(self, request, *args, **kwargs):
# do somthing
def get(self, request, *args, **kwargs):
return HttpResponse('Hello, World!')
See more for dispatch docs.
The default implementation will inspect the HTTP method and attempt to delegate to a method that matches the HTTP method; a GET will be delegated to get(), a POST to post(), and so on.
So previously you was disturbing the above logic, calling super will allow dispatch to delegate to post method.
I know this doesn't really answer your question, but it may offer a workaround. What I have used in JSON applications is to send a post to the get method when I want the same results for both. Something like:
def get(self, request, *args, **kwargs):
return HttpResponse('Ha!')
def post(self. request, *args, **kwargs):
return self.get(request, *args, **kwargs)
with dispatch left alone.
The best approach when using class-based views is not to write your own method handlers from scratch; instead, use one of the generic classes as the base and then modify just the methods which handle what you need. For example, if you have a form which needs to be submitted via POST, you should use the FormView as a base, which handles responses both GET and POST requests.
You don't need to override post() and/or get() methods themselves; instead use form_valid() to define what happens when the form is submitted:
class TestView(FormView):
form_class = MyForm
success_url = "/your/return/url/"
def form_valid(self, form):
# do something
return redirect(self.get_success_url())
Sorry guys, this is a bug in uWSGI and nginx ...
https://stackoverflow.com/a/11115108/41948
I am really sorry for wasting your time by misleading you. And still it took me a while to figure which part of my stack went wrong.

Categories

Resources