Adding an argument to a decorator - python

I have this decorator, used to decorate a django view when I do not want the view to be executed if the share argument is True (handled by middleware)
class no_share(object):
def __init__(self, view):
self.view = view
def __call__(self, request, *args, **kwargs):
"""Don't let them in if it's shared"""
if kwargs.get('shared', True):
from django.http import Http404
raise Http404('not availiable for sharing')
return self.view(request, *args, **kwargs)
It currently works like this:
#no_share
def prefs(request, [...])
But I'm wanting to expand the functionality a little bit, so that it will work like this:
#no_share('prefs')
def prefs(request, [...])
My question is how can I modify this decorator class so that it accepts extra arguments?

I hope this article by Bruce Eckel helps.
Upd:
According to the article your code will look like this:
class no_share(object):
def __init__(self, arg1):
self.arg1 = arg1
def __call__(self, f):
"""Don't let them in if it's shared"""
# Do something with the argument passed to the decorator.
print 'Decorator arguments:', self.arg1
def wrapped_f(request, *args, **kwargs):
if kwargs.get('shared', True):
from django.http import Http404
raise Http404('not availiable for sharing')
f(request, *args, **kwargs)
return wrapped_f
to be used as desired:
#no_share('prefs')
def prefs(request, [...])

The Bruce Eckel article that Li0liQ mentioned should be helpful in figuring this out. Decorators with and with out arguments behave slightly differently. The big difference is that when you pass arguments the __call__ method gets called once on __init__ and it is supposed to return a function that will be called whenever the decorated function is called. When there are no arguments, the __call__ method gets called every time the decorated function is called.
What does this mean for you? The way that __init__ and __call__ are called for a #no_arg_decorator is different than they are called for a #decorator('with','args').
Here are two decorators that might do the trick for you. You can get away with just the #no_share_on(...) decorator as long as you always use it with the parentheses.
def sharing_check(view, attr_name, request, *args, **kwargs):
if kwargs.get(attr_name, True):
from django.http import Http404
raise Http404('not availiable for sharing')
return view(request, *args, **kwargs)
class no_share(object):
"""A decorator w/o arguments. Usage:
#no_share
def f(request):
...
"""
def __init__(self, view):
self.view = view
def __call__(self, request, *args, **kwargs):
return sharing_check(self.view, 'sharing', request, *args, **kwargs)
class no_share_on(object):
"""A decorator w/ arguments. Usage:
#no_share_on('something')
def f(request):
...
--OR--
#no_share_on()
def g(request):
...
"""
def __init__(self, attr_name='sharing'):
self.attr_name = attr_name
def __call__(self, view):
def wrapper_f(request, *args, **kwargs):
return sharing_check(view, self.attr_name, request, *args, **kwargs)

class no_share(object):
def __init__(self, foo, view):
self.foo = foo
self.view = view

I think closure may works here.
def no_share(attr):
def _no_share(decorated):
def func(self, request, *args, **kwargs):
"""Don't let them in if it's shared"""
if kwargs.get(attr, True):
from django.http import Http404
raise Http404('not availiable for sharing')
return decorated(request, *args, **kwargs)
return func
return _no_share

Since it seems you're getting this wrong somewhere, here's a more complete example which may help you see what you're doing wrong. Using this as a drop-in should work.
class no_share(object):
def __init__(self, view, attr_name):
self.view = view
self.attr_name = attr_name
def __call__(self, request, *args, **kwargs):
"""Don't let them in if it's shared"""
if kwargs.get(self.attr_name, True):
from django.http import Http404
raise Http404('not availiable for sharing')
return self.view(request, *args, **kwargs)

I know this ia a little late .. but I didn't see any mentions for this way of doing things (probably because it didn't exist when the question was asked), but, in the interests of completeness
I found it useful to look at how Django themselves implemented such a thing. Have a look at:
django.views.decorators.http.require_http_methods
https://github.com/django/django/blob/master/django/views/decorators/http.py
from functools import wraps
from django.utils.decorators import decorator_from_middleware, available_attrs
def require_http_methods(request_method_list):
"""
Decorator to make a view only accept particular request methods. Usage::
#require_http_methods(["GET", "POST"])
def my_view(request):
# I can assume now that only GET or POST requests make it this far
# ...
Note that request methods should be in uppercase.
"""
def decorator(func):
#wraps(func, assigned=available_attrs(func))
def inner(request, *args, **kwargs):
# .. do stuff here
return func(request, *args, **kwargs)
return inner
return decorator

Related

Django custom decorator not wrapping

I'm trying to create a custom decorator in Django and apply it to a class based view in the URLs file. This is the decorator in decorators.py. It uses functools. I'm not able to get the wrapper function to execute the second log command.
def user_isnt_blocked(func):
logger.info("user_not_blocked call " + func.__name__)
#wraps(func)
def wrapper(self, request, *args, **kwargs):
logger.info("user_not_blocked wrapper")
return func(self, request, *args, **kwargs)
return wrapper
Right now, the decorator does nothing as I'm first trying to figure out how to get it to do the wrapping part - i.e. it is not logging the "user_not_blocked wrapper". The initial call is logged - it does print out "user_not_blocked call". The pertinent url pattern for the class-based view is coded as follows:
urlpatterns = [
url(r'update-profile/$', user_isnt_blocked(views.UpdateProfile.as_view()), name="update-profile"),]
The class-based view is as follows:
class UpdateProfile(View):
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(UpdateProfile, self).dispatch(request, *args, **kwargs)
def get(self, request):
return super(UpdateProfile, self).dispatch(request, *args, **kwargs)
def post(self, request):
...
Any help with what I'm doing wrong would be appreciated.

Django: decorator in class based view

I want to restrict access to a particular view so that only AJAX requests are accepted, so I implemented the following decorator:
def require_ajax(func):
def decorator(func):
def inner(request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest()
return func(request, *args, **kwargs)
return inner
return decorator
This works perfectly in function views, but I cannot figure out how to use it in class based views. I have tried this but got errors, I assume due the old version of Django I am using.
And well, my class-based view:
class AjaxView(TemplateView):
template_name = '...'
def get_context_data(self, **kwargs):
...
return context
#method_decorator(require_ajax)
def dispatch(self, *args, **kwargs):
return super(AjaxView, self).dispatch(*args, **kwargs)
Implementing the decorator differently solved the issue:
def require_ajax(func):
def decorator(request, *args, **kwargs):
if not request.is_ajax():
return HttpResponseBadRequest()
return func(request, *args, **kwargs)
return decorator

Django Class-based mixin view does not return a HttpResponse object

Trying to use a mixin to create a class-based view in Django but keep getting the following error message:
ValueError: The view twitter.views.TwitterExampleView didn't return an HttpResponse object.
As shown in the code below, I extend from View creating a base TwitterView for the app to handle error messages being returned from Twitter. That view is further extended for a TwitterNetworkView where a Twitter network is attached to the view. The other classes allow for some transformation to happen to parameters passed into requests. The final class, the TwitterExampleView, includes both the mixin for parameter transformation and the TwitterNetworkView. It's get method returns as a placeholder a string "blah". Only when it bubbles back into the dispatch is the response rendered so what am I overlooking?
class TwitterView(View):
def dispatch(self, request, *args, **kwargs):
try:
response = super(TwitterView, self).dispatch(request, *args, **kwargs)
return render_response(request, response)
except TwitterApiException, e:
return return_error(request, e, e.status_code)
class TwitterNetworkView(TwitterView):
def dispatch(self, request, *args, **kwargs):
self.network = get_network_or_404(request.user, kwargs['network_id'])
super(TwitterNetworkView, self).dispatch(request, *args, **kwargs)
class DefineParamsMixin(object):
def get_params(self):
return null
class TwitterPagedDefineParams(DefineParamsMixin):
def get_params(self):
return define_params(
Param('page'),
Param('since'),
Param('before'),
Param('limit', transform_func=int)
)
class TwitterExampleView(TwitterPagedDefineParams, TwitterNetworkView):
def get(self, request, *args, **kwargs):
return "blahhhhh"
You're not returning anything from dispatch in your TwitterNetworkView mixin. With no return statement, the method returns None rather than an HttpResponse. One fix would be to have it return the result of super(TwitterNetworkView, self).dispatch(request, *args, **kwargs).
You need to return an HttpResponse object.
from django.http import HttpResponse
...
class TwitterExampleView(TwitterPagedDefineParams, TwitterNetworkView):
def get(self, request, *args, **kwargs):
return HttpResponse("blahhhhh")
Take a look at https://docs.djangoproject.com/en/1.7/intro/tutorial03/#write-your-first-view

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

Having trouble making a custom django view decorator (with args)

So I've read all the similar questions and copied what they wrote but I still keep having issues. So I want something like this
# Yes, I know django has one but I want to make my own
#rate_limit(seconds=10)
myview(request, somearg, *args, **kwargs):
# Return a response
...
def rate_limit(seconds=10):
def decorator(view):
def wrapper(request, *args, **kwargs):
# Do some stuff
return view(request, *args, **kwargs)
return wrapper
return decorator
When I run it I get the error
decorator() got an unexpected keyword argument 'somearg'
So I append decorator to take in args and kwargs and get this error
# New decorator signature
def decorator(view, *args, **kwargs)
and error
'function' object has no attribute 'status_code'
edit:
So the solution was to use. Thanks Martijn Pieters!
#rate_limit()
instead of
#rate_limit
Your first attempt works just fine, but you probably forgot to call the rate_limit() decorator factory.
In other words, your first error occurs if you do this:
#rate_limit
def myview(request, somearg, *args, **kwargs):
instead of:
#rate_limit(seconds=10)
def myview(request, somearg, *args, **kwargs):
You also really want to use functools.wraps() on decorators used in Django, especially if you want to mix this with other Django decorators such as csrf_exempt:
from functools import wraps
def rate_limit(seconds=10):
def decorator(view):
#wraps(view)
def wrapper(request, *args, **kwargs):
# Do some stuff
return view(request, *args, **kwargs)
return wrapper
return decorator
This ensures that any attributes set on the to-be-wrapped function are copied over correctly to the wrapper.

Categories

Resources