On views that allow updating/deleting objects, I need a decorator that verifies that the object to be edited belongs to a group(model "loja). Both defined in the url:
/[slug model loja--s_loja]/[viewname-ex:addmenu]/[object id--obj_id]
Because the model of the object can vary, the decorator the model of the object as an argument. Every model that may be passed as an argument has a foreign key to the model "loja" named loja.
The decorator:
def acesso_objecto(modelo):
def wrap(f):
def wrapper(*args, **kwargs):
s_loja = kwargs['s_loja']
obj_id = kwargs['obj_id']
objecto = get_object_or_404(modelo, pk=obj_id)
loja = get_object_or_404(Loja, slug=s_loja)
if objecto.loja is not loja:
raise Http404
else:
return f(*args, **kwargs)
return wrapper
return wrap
Basically, unless the group "loja" and the object exists and the object belongs to that group a 404 error should be raised.
Without the decorator the view works fine, but the decorator always raises 404 because the if statement is always true even when it shouldn't be. If I use the loja.id or loja.slug for verification it works as THEY ARE related, but this function always seems to fail and I have no idea why.
Replace is not with !=.
not loja is evaluating to True, and the if statement is testing the equality between objecto.loja and True.
Related
I recently upgraded Django from 2.0.7 to 2.1.1, a new error occurs in which I get this error 'functools.partial' object has no attribute '__name__'.
I'd like to understand if my fix is right and what caused this new error to happen, I couldn't find anything on the django release notes related to this issue, maybe I missed it.
decorators.py
def auth0_login_required(function):
def wrap(request, *args, **kwargs):
if request.isAuthenticated or request.user.is_staff:
pass
else:
raise Http404()
return function(request, *args, **kwargs)
wrap.__doc__ = function.__doc__
wrap.__name__ = function.__name__ # ERROR HERE
return wrap
How it is used, views.py:
#method_decorator(auth0_login_required, name='dispatch')
class Dashboard(View):
...
For the fix I just removed wrap.__name__ = function.__name__, but I'm not sure if it'll break something else.
Rather than manually copy things across, use the #functools.wraps() decorator to handle this for you:
from functools import wraps
def auth0_login_required(function):
#wraps(function)
def wrap(request, *args, **kwargs):
if request.isAuthenticated or request.user.is_staff:
pass
else:
raise Http404()
return function(request, *args, **kwargs)
return wrap
The #wraps() decorator (via the functools.update_wrapper() function it calls knows how to handle functools.partial objects correctly (or rather, it can handle the fact that functools.partial objects have no __name__ attribute).
It's fine that the wrapped functools.partial() object found on the View class doesn't have a __name__ attribute, what's not fine is that you then don't copy that attribute at all even when you are decorating functions that do have the attribute. If you don't want to use #wraps() you'd have to manually copy the attribute across and handle the exception yourself:
try:
wrap.__name__ = function.__name__
except AttributeError:
pass
try:
wrap.__doc__ = function.__doc__
except AttributeError:
pass
but take into account that this doesn't copy the __qualname__, __module__ and __annotations__ attributes, doesn't handle any custom attributes set on function (which other decorators might rely on). #functools.wraps() does take care of all of those, plus it sets the __wrapped__ attribute on the decorator wrapper function that would let you unwrap the decorator again.
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.
When I save a User model, I would like to check if it has a username.
Therefore I wrote this pre_save:
#receiver(pre_save, sender=User)
def validate_user(sender, instance, **kwargs):
if len(instance.username) <= 5: raise Exception("Username too short")
Now in my testing method I would like to test this exception:
def test_user_no_username(self):
u = User.objects.create()
self.assertRaises(Exception, u.save())
The test fails. Why?
assertRaises is kind of a specific exception - as a second argument you should pass a callable:
assertRaises(exception, callable, *args, **kwds)
In other words, don't call u.save():
self.assertRaises(Exception, u.save)
Also, you should really think about having custom exceptions or using built-in Django validation errors instead of raising and catching the broad Exception.
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.
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.