Class Based View and decorators - python

I have a FormView for displaying form, and code goes on like this:
class AddProject(FormView):
template_name = "project/add_project.html"
#method_decorator(check_user_type)
def dispatch(self,request, *args, **kwargs):
return super(AddProject,self).dispatch(request, *args, **kwargs)
def get_form_class(self):
return AddProjectForm
def form_valid(self,form):
#do validation, return response
the decorator check_user_type is like this:
def check_user_type(func):
def wrapped_func(request, *args, **kwargs):
kwargs['invalid_user'] = True
return func(request,*args, **kwargs)
return wrapped_func
In my decorator I want To make sure that only certain type of user get to see the form, i.e if request.user.Iam == 'Architect' or request.user.Iam == 'Interior Designer' only see the form and others see a message "Only architects/Interior Designer get to upload photos".For this i want to insert a variable 'invalid_user' to be passed along, depending on which i display the form or the message.
Problem is I am unable to pass the variable :( alongwith it .. and a doubt.. if i have correctly devised the idea to do so.. ?

If I understand you correctly, you want to pass this argument to check_user_type decorator, right? Then you need to nest another function in your decorator, using closure to set variables inside it. Something like
def check_user_type(parameter):
def check_user_type_inner(func):
def wrapped_func(request, *args, **kwargs):
... # parameter here depends on argument passed to most outer function
return func(request,*args, **kwargs)
return wrapped_func
return check_user_type_inner
then parameter is available inside scopes of both inner functions.

Related

Use a decorator to add in a variable to request

I know the suggested way to do this is middleware, but I'd like to implement the following to add in an api_env variable, accessed at run-time, so that my view can access it:
#api_wrapper
def my_view(request):
print api_env # this is the variable I want, such as request.epi_env
And the decorator:
def api_wrapper(func):
def api_inner(request):
request.api_env = 'something'
return func(request)
return api_inner
What's the best way to do this? I have about 100 functions to wrap so I don't want to add in a new parameter for every function but would just like to use the simplest approach to pass that 'env' variable. How should I do it.
You can generalize this to work with an arbitrary number of positional and named parameters. Furthermore you might want to use update_wrapper [Python-doc], to add attributes like csrf_exempt to the "output" view, otherwise the #csrf_except will not be available in the outer function:
from functools import update_wrapper
def api_wrapper(func):
def api_inner(request, *args, **kwargs):
request.api_env = 'something'
return func(request, *args, **kwargs)
update_wrapper(api_inner, func, assigned=())
return api_inner
That being said, this to some extent shows that using class-based views might be better in this case, since then one can define a mixin, and just mix it in the method resolution order (MRO) as a reusable component. In that case, one often do not have to take into account more complicated logic like the parameters, or the attributes that are added to the function, since a class-based view takes care of this itself.
Try this:
def api_wrapper():
def decorator(view_func):
#wraps(view_func)
def _wrapped_view(req, *args, **kwargs):
req.env = 'something'
return view_func(req, *args, **kwargs)
return _wrapped_view
return decorator

Flask-RESTful can we call a method before get and post in Ressource?

I'm using Flask-RESTful in my app.
I would want to call a method before each Ressource post and get so my code is not duplicated.
So basically here is what I have:
class SomeClass(Resource):
def __init__():
# Some stuff
def get(self, **kwargs):
# some code
def post(self, **kwargs):
# the same code as in get method
I would like to have a method call before get and post so my code is not duplicated.
Is there any way I can achieve to do that ?
Try writing a decorator function and use it with your get() and post() methods. More info here.
A decorator is more like a wrapper to your function, where your function is wrapped in a function that returns your function.
Say, you want to do some validation before processing, you can write a decorator like this:
from functools import wraps
def validate(actual_method):
#wraps(actual_method) # preserves signature
def wrapper(*args, **kwargs):
# do your validation here
return actual_method(*args, **kwargs)
return wrapper
then, using it in your code is as simple as:
class SomeClass(Resource):
def __init__():
# Some stuff
#validate
def get(self, **kwargs):
# some code
#validate
def post(self, **kwargs):
# the same code as in get method

passing arguments between multiple decorators

I am using two decorators to decorate a function "add_user", the first one #auth_user authenticates the logged in user via json web token request headers and returns the user's information as an argument "user_info" back to the decorated function add_user. The second decorator #has_permission is supposed to check whether or not the user has permissions on the requested resource. I wanted to pass the user's information returned as user_info by #auth_user to #has_permission but don't know how to do that, here is the code so far:
#has_permission
#auth_user
def add_user(user_info):
"""
do something
"""
return None
The decorators:
def auth_user(f):
#wraps(f)
def wrapper(*args, **kwargs):
data = jwt.decode(request.headers['some-access-token'], some_secret_key)
try:
user_info = User.query.filter(User.id==data['user_id']).first()
except:
return jsonify({"message" : "error"}), 401
return f(user_info, *args, **kwargs)
return wrapper
and the second decorator where I want to access the user_info:
def has_permission(f):
wraps(f)
def wrapper(*args, **kwargs):
# This is where I want to access the user information instead of processing the key again #
# return some new_args ... #
return f(*args, **kwargs)
return wrapper
What is the best way to achieve this?
With some help, I managed to get this working and the main problem was the order/sequence of the decorators. In the question; #has_permission took #auth_user as an argument and #auth_user took the original function add_user as an argument like so:
has_permission(auth_user(add_user(user_info)))
This would mean that when arguments are returned, the outer most decorator #has_permission would return arguments to #auth_user and then to add_user. This meant that #has_permission could not access user_info returned by #auth_user so I modified the original function:
#auth_user
#has_permission
def add_user(user_info):
"""
do something
"""
return None
Now the decorators would be called in the correct order like this:
auth_user(has_permission(add_user(user_info)))
I also changed the way user_info was injected by #auth_user and changed it to kwargs instead of args to access it in #has_permission:
def auth_user(f):
#wraps(f)
def wrapper(*args, **kwargs):
data = jwt.decode(request.headers['some-access-token'], some_secret_key)
try:
user_info = User.query.filter(User.id==data['user_id']).first()
except:
return jsonify({"message" : "error"}), 401
kwargs['user_info'] = user_info
return f(*args, **kwargs)
return wrapper
And finally passing on the user_info to original function:
def has_permission(f):
wraps(f)
def wrapper(*args, **kwargs):
#access user info
user_info = kwargs['user_info']
return f(user_info, *args, **kwargs)
return wrapper
Hope this helps someone!

Flask - Adding variables to another route's response?

Say I have a route like this, that renders lol.html with b set to None:
#app.route('/foo/')
def foo():
return render_template('lol.html', a='a', b=None)
I want to make another route, that can intercept one route and add some variables to it before returning. Something like this:
#app.route('/bar/')
def bar():
intercepted = make_response(foo())
# do something signficant to extend the logic of foo
flask.add_var_to_resp(intercepted, b='b')
How should this be accomplished in flask?
#jsbueno, I'm adding this to clarify your answer, which I cannot do in a comment:
#app.route('/foo/')
def foo(renderer=render_template):
return renderer('foo.html', a='a')
#app.route('/bar/')
def bar():
def renderer(template, *args, **kwargs):
return render_template(template, *args, b='b', **kwargs)
return foo(renderer)
Flask is mostly pure Python, with very little magic - so there are a series of strategies you can use.
Maybe the simplest is to have the primary view to accept optional
function arguments, and call that one from your extended views:
#app.route('/foo/')
def foo(b=None):
return render_template('lol.html', a='a', b=b)
#app.route('/bar/')
def bar():
return foo(b="b")
update:
If you don't want the original view to incorporate the extensions logic, you have to keep in mind that views returns a rendered template, that is not extensible - you can't simply add more lines inside the foo view.
One alternative is to turn the renderer itself into a lazy object - that delays the call to flask actual render_template so that it is modifiable. For a simple view to keep working withoug bein aware of this modified render_template, it could opionally receive the renderer as a parameter.
So,
#app.route('/foo/')
def foo(renderer=render_template):
return renderer('lol.html', a='a')
#app.route('/bar/')
def bar():
def renderer(template, *args, **kw):
return render_template(template, *args, b="b", **kw)
return foo(renderer)
Should work for you - you can make the "renderer" be a smarter object where you can plug more things as needed.

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