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
Related
I'm migrating from regular function based views, to class based views. One of the things that I couldn't migrate were the decorators I used. The decorator in question checks if the credentials of the current user are valid and then executes the decorated function:
def custom_auth(function):
#wraps(function)
def wrap(request, *args, **kwargs):
# Logic for validating if user has correct credentials
# Fetches the user that accessed the function
user_object = User.objects.get(username=request_username)
# Try to execute the decorated function. If it fails, redirect
# to previous page and show an error popup
try:
return function(request, user=user_object, *args, **kwargs)
except:
# Logic for displaying the popup
Previously I could just decorate my function by doing
#custom_auth
def view(request, *args, **kwargs):
# View logic
However, when I try to apply it to my class based view in the same way, I get an error saying __init__() takes 1 positional argument but 2 were given: user='username', view='cbvview'
#custom_auth
class CBV(View):
def get(self, request, *args, **kwargs):
# Get request logic
I know that this is not the way you are supposed to apply the decorator, so I tried with different approaches. Either adding the decorator to urls.py, adding the #method_decorator(custom_auth, name="dispatch") or simply overriding the dispatch method, but none of them work. All of them give me the same error.
What could be the issue? Maybe I should use a custom mixin instead?
Use #method_decorator like following:
from django.utils.decorators import method_decorator
#method_decorator(custom_auth,name="dispatch")
class CBV(View):
def get(self, request, *args, **kwargs):
...
Edit:
Try to make your own mixin as class and inherit it to be used in CBV class so:
class CustomAuthMixin:
def dispatch(self, request, *args, **kwargs):
# Logic for validating if user has correct credentials
# Fetches the user that accessed the function
user_instance = User.objects.get(username=request_username)
# Try to execute the decorated function. If it fails, redirect
# to previous page and show an error popup
try:
return super().dispatch(request, user=user_instance, *args, **kwargs)
except:
# Logic for displaying the popup
class CBV(CustomAuthMixin, View):
def get(self, request, *args, **kwargs):
# Get request logic
from django docs https://docs.djangoproject.com/en/4.1/topics/class-based-views/intro/
#method_decorator(login_required, name='dispatch')
class ProtectedView(TemplateView):
template_name = 'secret.html'
and there is no need to add the user to the function call in the decorator. It is already in the request
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
I have a DRF ViewSet and would like to provide a detail and list routes with the same name:
class MyViewSet(ViewSet):
# ...
#detail_route(['post'])
def do_stuff(self, request, *args, **kwargs):
pass
#list_route(['post'])
def do_stuff(self, request, *args, **kwargs):
pass
This obviously doesn't work, as the list_route will just overwrite the detail_route.
Is there a simple way to provide both routes without manually messing up with the urls?
I'm not sure if this's what are you looking for, you can set the same url path name for both:
class MyViewSet(ViewSet):
# ...
#detail_route(['post'], url_path='do_stuff')
def do_stuff_detail(self, request, *args, **kwargs):
pass
#list_route(['post'], url_path='do_stuff')
def do_stuff_list(self, request, *args, **kwargs):
pass
In the first one, the url will be ../"id"/do_stuff and, in the second one, ../do_stuff
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.
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.