Django custom decorator not wrapping - python

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.

Related

Using a decorator with Django blocks the request

I'm trying to create my own decorator in order to validate a REST call with Django (using Django Rest Framework).
The decorator looks like this:
def allowed_states(allowed=[]):
def decorator(func):
def wrapper(self, request, *args, **kwargs):
print(func)
result = func(self, request, *args, **kwargs)
return result
return wrapper
return decorator
The request API looks something like this:
#swagger_auto_schema(
operation_id="my_api",
responses={
status.HTTP_204_NO_CONTENT: "",
}
)
#action(detail=True, methods=["DELETE"])
#allowed_states(allowed=["state1", "state2"])
def my_api(self, request, *args, **kwargs):
# do some stuff here
When my #allowed_states decorator is removed, the call works just fine. When I add it back I get a 404 error from Django framework saying it could not find a url pattern to execute for this call.
I tried removing self from wrapper (and from the func invoke as well).
Also I tried changing the decorators order above the function call.
Both didn't work.
The stack trace doesn't say much, merely that django could not find the url pattern:
Using the URLconf defined in
<code>my_project.urls</code>,
Django tried these URL patterns, in this order:
(and then the order of url patterns as they appear in my_project.urls)
Following a suggestion in the comments I used functools.wraps and that solved the problem:
from functools import wraps
def allowed_states(allowed=[]):
def decorator(func):
#wraps(func)
def wrapper(self, request, *args, **kwargs):
print(func)
result = func(self, request, *args, **kwargs)
return result
return wrapper
return decorator

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

Adding an argument to a decorator

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

Categories

Resources