Decorating classes and classmethods - python

I want to wrap one of the methods of class in decorator. If this necessary, my class is django class based view.
First type of implementation is wrap method by method_decorator:
class MyView(View):
template_name = "index.html"
#method_decorator(login_required):
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
Ok, this works. But I don't want to every time overwrite method dispatch.
Second way - mixins and multiple inheritance.
class LoginRequiredView(object):
#method_decorator(login_required):
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
class MyView(LoginRequiredView, View):
template_name = "index.html"
This works too. But if I want to use multiple decorators, it is required to list all mixins in parents. It is not very good for me.
I want next:
#viewdecorator(login_required)
class MyView(LoginRequiredView, View):
template_name = "index.html"
And my question is how to implement viewdecorator. I try to write this:
def viewdecorator(decorator):
def wrap(cls):
dispatch = getattr(cls, 'dispatch', None)
if dispatch:
setattr(cls, 'dispatch', method_decorator(decorator))
return cls
return wrap
But resulting cls in this decorator don't contains original classmethods and classonlymethods. I guess about a reason such behavior.
How to write viewdecorator to classmethods will be presented in resulting cls?
UPD: I understood how this work and below actual working code with example
def viewdecorator(decorator):
def wrap(cls):
getattribute = cls.__getattribute__
def newgetattr(cls, name):
attr = getattribute(cls, name)
if name == 'dispatch':
return decorator(method_decorator(attr))
return attr
cls.__getattribute__ = newgetattr
return cls
return wrap
#viewdecorator(login_required)
#viewdecorator(csrf_exempt)
class EntriesMyList(EntriesList):
template_name = 'index.html'

You can install django-braces. This library contains login required mixin
from django.views.generic import View
from braces.views import LoginRequiredMixin
class SimpleView(LoginRequiredMixin, View):
pass
For correct style you don't need decorators for CBV. If you need some custom view permissions implement you logic in dispatch view method.

Related

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.

Class-based views: where to check for permissions?

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.

Access class variables in functions - Django

I have the following code:
class MyView(View):
var2 = Choices.objects.get(id=1)
my_strings = ['0','1','2','3']
#login_required
def myfunction(self,request):
return render(request,
'app/submit.html',{'my_strings':my_strings, 'var2':var2})
I want to access "var2" and "my_string" variables and display them in the template submit.html. If I use only the function without putting it in a class, everything works fine. But inside the class it shows errors.
Can anybody tell me how to access "var2" and "my_string" class variables in "myfunction" ?
You have to use self. In front of class variables.
Your function names in class based views should correspond to what http method you try to use(get, post etc...)
#login_required
def get(self,request):
return render(request,
'app/submit.html',{'my_strings':self.my_strings, 'var2':self.var2})
Please also read:
https://docs.djangoproject.com/en/1.9/topics/class-based-views/intro/
You don't have to write custom function to dispatch request...Django internally have the GET and POST method to do that... And also preferred way to handle login required is method_decorator
from django.utils.decorators import method_decorator
#method_decorator(login_required, name='dispatch')
class MyView(View):
string = "your string"
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
def get(self, request):
return render(request, 'template', {'string': self.string})

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 Decorator Attribute Error for Class Based Views

I'm trying to use a decorator on the dispatch methods of several Class Based Views in my Django app. Here is one example view I tried:
class DashboardView(TemplateView):
template_name="omninectar/dashboard.html"
def get_context_data(self, **kwargs):
....
#active_and_login_required
def dispatch(self, *args, **kwargs):
return super(DashboardView, self).dispatch(*args, **kwargs)
With the following decorator:
active_required = user_passes_test(lambda u: u.is_active)
def active_and_login_required(view_func):
decorated_view_func = login_required(active_required(view_func))
return decorated_view_func
Which gets me the following error:
AttributeError at /dashboard/
'DashboardView' object has no attribute 'user'
How can I get the decorator to retrieve the current user with this view?
You can convert the old-style decorator to a method decorator by using django.utils.decorators.method_decorator like this:
from django.utils.decorators import method_decorator
...
class DashboardView(TemplateView):
template_name="omninectar/dashboard.html"
def get_context_data(self, **kwargs):
....
#method_decorator(active_and_login_required)
def dispatch(self, *args, **kwargs):
return super(DashboardView, self).dispatch(*args, **kwargs)
Related documentation is here: Introduction to Class-based Views

Categories

Resources