I wrote the following decorator to be used in some Django views where I don't want the user to be logged in (like register and forgot-password):
def not_logged_in(view, redirect_url=None):
def _wrapper(request, *args, **kwargs):
if request.user.is_authenticated():
return HttpResponseRedirect(
redirect_url or '/'
)
return view(*args, **kwargs)
return _wrapper
Once I have it, I can easily write:
#not_logged_in
def register(request):
...
I have written unit tests for the views that are using it, and it is working without problems, but I'm wondering what would be the best way of unit testing the not_logged_in function alone?
I suppose that you can easily mock the request object, then decorate a trivial function with your decorator and pass that request a parameter.
I also suppose that your _wrapper does not really have an unused request parameter?
Related
I have created a decorator in my Django project to inject parameter values to the decorated method's parameters.
I do this by using inspect.getargspec to check which parameters are present in the method and place them in kwargs. Otherwise I get an error due to the incorrect number of parameters in the method.
While this works properly in individual view methods, it fails when it comes to Django's class based views.
I believe this might be because the decorators are applied using #method_decorator at the class level to the dispatch method instead of the individual get and post methods.
I'm a python newbie and might be overlooking something obvious here.
Is there a better way to do what I'm doing? Is it possible to get the method parameter names in a class based view?
I'm using Python 2.7 and Django 1.11
The Decorator
def need_jwt_verification(decorated_function):
#wraps(decorated_function)
def decorator(*args, **kwargs):
request = args[0]
if not isinstance(request, HttpRequest):
raise RuntimeError(
"This decorator can only work with django view methods accepting a HTTPRequest as the first parameter")
if AUTHORIZATION_HEADER_NAME not in request.META:
return HttpResponse("Missing authentication header", status=401)
jwt_token = request.META[AUTHORIZATION_HEADER_NAME].replace(BEARER_METHOD_TEXT, "")
try:
decoded_payload = jwt_service.verify_token(jwt_token)
parameter_names = inspect.getargspec(decorated_function).args
if "phone_number" in parameter_names or "phone_number" in parameter_names:
kwargs["phone_number"] = decoded_payload["phone"]
if "user_id" in parameter_names:
kwargs["user_id"] = decoded_payload["user_id"]
if "email" in parameter_names:
kwargs["email"] = decoded_payload["email"]
return decorated_function(*args, **kwargs)
except JWTError as e:
return HttpResponse("Incorrect or expired authentication header", status=401)
return decorator
A class based view
#method_decorator([csrf_exempt, need_jwt_verification], name="dispatch")
class EMController(View):
def get(self, request, phone_number, event_id):
data = get_data()
return JsonResponse(data, safe=False)
def post(self, request, phone_number, event_id):
return JsonResponse("Operation successful", safe=False)
EDIT:
The obvious solution of applying the decorator at the method level, doesn't work with Django's class based views. You need apply the decorator at the url configuration or apply the decorator to the dispatch method.
EDIT:
I've posted code that was related to a workaround I was exploring, passing the parameter names as an argument into the decorator.
I found this post: Function decorators with parameters on a class based view in Django
which may provide the answer to your problem:
If you want to pass a decorator with parameters, you only need to:
Evaluate the parameters in the decorator-creator function.
Pass the evaluated value to #method_decorator.
The above mentioned and the code provided in the linked answer taken under consideration, you should:
injectables=[inject_1, inject_2, ..., inject_n]
decorators = [csrf_exempt, need_jwt_verification(injectables)]
#method_decorator(decorators, name="dispatch")
class EMController(View):
...
Leaving my previous mistaken answer here for legacy reasons, don't try this at home (or anywhere, in django, for that matter!!)
If we observe the "decorating a class" docs, we can see the following:
Or, more succinctly, you can decorate the class instead and pass the name of the method to be decorated as the keyword argument name:
so you have to change the name argument of your #method_decorator to match the method that will apply to:
decorators = [csrf_exempt, need_jwt_verification(injectables=[])]
#method_decorator(decorators, name='get')
#method_decorator(decorators, name='post')
class EMController(View):
Personally I prefer to place my decorators on top of the specific method they will apply to:
class EMController(View):
#method_decorator(decorators)
def get(self, request, phone_number, event_id):
...
#method_decorator(decorators)
def post(self, request, phone_number, event_id):
...
What I want seemed impossible in the current state of the libraries. So here's what I finally went with.
parameter_names = inspect.getargspec(decorated_function).args
if "phone_number" in parameter_names or "phone_number" in injectables:
kwargs["phone_number"] = decoded_payload["phone"]
if "user_id" in parameter_names:
kwargs["user_id"] = decoded_payload["user_id"]
if "email" in parameter_names:
kwargs["email"] = decoded_payload["email"]
request.__setattr__("JWT", {})
request.JWT["phone_number"] = decoded_payload["phone"]
request.JWT["user_id"] = decoded_payload["user_id"]
request.JWT["email"] = decoded_payload["email"]
This decorator will automatically populate parameters in method based views as intended.
But it will also inject an JWT attribute to the request object for the class based views to use. Like request.GET and request.POST.
I'm doing a blog for my self from scratch and everything is working, even the sessions.
Now I'm trying to limit the admin with a decorator called #require_login. I think I'm doing something really wrong, besides the fact that is not working.
Here is my decorator:
def requirir_login(func):
request = make_response()
if session['logged_in'] == True:
return func
else:
print("no hay sesion registrada webon")
and here is it used, decorating the admin function:
#app.route("/admin")
#requirir_login
def admin():
users = User.objects
return render_template("admin.html", users=users)
My logic behind this is to check if there is a session then return the admin function. If not I wanted to check in the terminal that message for test purposes.
I haven't decided what to do if there is not a session yet. I would possibly redirect to the log-in page or something.
Your decorator needs to provide a wrapper function, which will be called in place of the decorated function. Only when that wrapper is being called is an actual request being routed and can you test the session:
from functools import wraps
def requirir_login(func):
#wraps(func)
def wrapper(*args, **kwargs):
if session['logged_in']:
return func(*args, **kwargs)
else:
print("no hay sesion registrada webon")
return wrapper
When a decorator is applied, it is called an its return value replaces the decorated function. Here wrapper is returned, so that now becomes your view function.
The wrapper passes on all arguments untouched, making your decorator suitable for any view function regardless of the arguments they expect to be passed in from the route.
I also made a few other changes to improve the functionality of your decorator:
You don't need to test for == True; that is what if is for, to test if the result of an expression is true or not.
I used the #functools.wraps() decorator to give your wrapper the same name and documentation string as the original wrapped view function, always helpful when debugging.
You could indeed use a redirect to the login form if you have one:
return redirect(url_for('login'))
if your login view is named login.
Alright I have a method called no_m in the user class and i've not written a decorator before, but basically I need to redirect the user to another URL if they pass this. I have created a file called decorators.py in a dir called accounts and i'm guessing the decorator is imported correctly, however I cannot get it to work. Heres what I have:
def no_m(view_func):
def _wrapped_view_func(request, *args, **kwargs):
try:
if request.user.is_m():
# quick test
return HttpResponseRedirect('http://google.com')
else:
return view_func(request, *args, **kwargs)
except:
return _wrapped_view_func
All it needs to do is redirect users if they pass that test, I don't know what the URL needs to be yet so it's just google for now. Any ideas? Like I said, i've not written decorators before so it's all new to me. Thankyou.
Another thought: would it be possible to render a template page?
You're missing a step in the decorator, or rather you have a step confused. It's the outer function that must return the inner function (_wrapped_view_func), and it must always do so: that's what takes the place of the original function when it is called.
I'm not sure what the except clause is there for. Apart from it always being a bad idea to use a blank except - that catches everything, including things like ctrl-c - exceptions in Django functions are usually handled by the middleware, rather than the decorator. I would just remove it.
So the code should be:
def no_m(view_func):
def _wrapped_view_func(request, *args, **kwargs):
if request.user.is_m():
# quick test
return HttpResponseRedirect('http://google.com')
else:
return view_func(request, *args, **kwargs)
return _wrapped_view_func
This has been driving me crazy because it should be so simple, but there must be some Python quirk I'm missing. I have a decorator that I'm trying to apply to a Flask route, but for some reason none of the decorators in my views.py seem to be getting loaded.
decorators.py
def admin_required(func):
"""Require App Engine admin credentials."""
#wraps(func)
def decorated_view(*args, **kwargs):
if users.get_current_user():
if not users.is_current_user_admin():
abort(401) # Unauthorized
return func(*args, **kwargs)
return redirect(users.create_login_url(request.url))
return decorated_view
views.py
#admin_required
#blueprint.route('/')
def index():
return render_template('index.html')
The admin_required decorator function is not being called (index.html is loaded without a redirect), and I cannot figure out why.
Short answer: change the order of the decorators; blueprint.route only "sees" your undecorated function.
Decorators are applied inside-out, in loose analogy to function calls. Thus your function definition is equivalent to:
def index():
return render_template('index.html')
index = blueprint.route('/')(index)
index = admin_required(index)
Note how blueprint.route is passed the index function before it gets wrapped by admin_required. Of course, admin_required does eventually get applied to the index name in the module, so if you were to call index directly, it would go through both decorators. But you're not calling it directly, you're telling flask's request processor to call it.
I'm trying to adopt the Django documentation example on using class based views with mixins in order to be able to make a simple way of downloading the objects in a list view in CSV format, but I am failing miserably and do not really see what I am doing wrong.
I have a view defined as:
class MyObjectList(CSVResponseMixin,
MultipleObjectTemplateResponseMixin,
BaseListView
):
object_type = None
def get_context_data(self, **kwargs):
object_type = self.object_type
...some code...
return context
def render_to_response(self, context, **kwargs):
if self.request.GET.get('format', '') == 'csv':
return CSVReponseMixin.render_to_response(self, context, **kwargs)
else:
return MultipleObjectTemplateResponseMixin.render_to_response(self, context, **kwargs)
the mixin is:
class CSVResponseMixin(object):
def render_to_response(self, ctx, **kwargs):
return self.HttpResponse.render_to_response(self.convert_to_csv(ctx), **kwargs)
def conver_to_csv(ctx):
return do_csv_magic(ctx)
and in urls.py the view is called as:
url(r'^list/foos/$',
MyObjectList.as_view(object_type="someobject", model=models.MyModel),
name="myobjects",
)
However when I try to access the view without the ?format=csv query, I get a TypeError
Exception Value: __init__() got an unexpected keyword argument 'request'
Exception Location: /usr/lib/python2.6/site-packages/django/views/generic/base.py in render_to_response, line 97
EDIT: I added some details to the question and ended up implementing this with a different approach, but I still want to know what I was doing wrong.
In short, you're overdoing it. I'm not sure what is your intention here, but I've learned that the best approach is to find the closest generic view to what you're trying to do and simply extend it in views.py. Examples are many, but I invite you to check my code at https://bitbucket.org/BerislavLopac/resume/src/d7cfcf9c370b/resume_app/myproject/web/views.py.
According to the docs, render_to_response only takes the following arguments: template_name, dictionary, context_instance, mimetype
Therefore within FooResponseMixin when you're calling:
self.HttpResponse.render_to_response(self.mutilate_context(ctx), **kwargs)
You're passing in extra arguments within kwargs that render_to_response doesn't accept. Either remove the **kwargs or assign only what you need from it to variables to pass in to the accepted arguments.