How to create a custom decorator in Django?
Question:
I’m trying to create a custom decorator in Django but I couldn’t find any ways to do it.
# "views.py"
@custom_decorator
def my_view(request):
# .......
So, how can I create it in Django? and where should I put it so that I can use it anywhere in my Django project?
Answers:
http://www.makina-corpus.org/blog/permission-required-decorator-django
i based mine off that blog post.
Stick that in a file in the python path or in a “util” app and import it into views:
e.g.
project_dir
|_ app1
|_ app2
|_ utils
|_ __init__.py
|_ permreq.py
from util.permreq import permission_required
@permmission_required('someapp.has_some_perm', template='denied.html')
def some_view(request):
blah blah
You don’t have to write your own decorator for this as user_passes_test
is already included in Django.
And there’s a snippet (group_required_decorator
) that extends this decorator and which should be pretty appropriate for your use case.
If you really want to write your own decorator then there’s a lot of good documentation on the net.
And well, to (re-) use the decorator just put your decorator in a module on your path and you can import it from any other module.
See examples in django itself:
http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/decorators.py
Your particular example is probably just a version of ‘user_passes_test’ where the test is going to be membership of the ‘premium’ group.
To use anywhere, make a python package and import it from there. As long as its on your sys.path it’ll get found.
Played around with the various links above and couldn’t get them working and then came across this really simple one which I adapted. http://code.activestate.com/recipes/498217-custom-django-login_required-decorator/
from functools import wraps
from django.http import HttpResponseRedirect
def authors_only(function):
@wraps(function)
def wrap(request, *args, **kwargs):
profile = request.user.get_profile()
if profile.usertype == 'Author':
return function(request, *args, **kwargs)
else:
return HttpResponseRedirect('/')
return wrap
Using @wraps
is better than manually overriding like doing wrap.__doc__ = fn.__doc__
. Amongst other things, it ensures your wrapper function gets the same name as the wrapped function.
Thanks to arie, the answer helped a long way, but it doesn’t work for me.
When I found this snippet, I got it to work properly: http://djangosnippets.org/snippets/983/
This solution worked for me:
The helper function
This function has the benefit of being reusable in other places, as a drop in replacement for user.is_authenticated
. It could for instance be exposed as a template tag.
def my_custom_authenticated(user):
if user:
if user.is_authenticated():
return user.groups.filter(name=settings.MY_CUSTOM_GROUP_NAME).exists()
return False
The decorator
I just put this at the top of my views.py
, since it’s so short.
def membership_required(fn=None):
decorator = user_passes_test(my_custom_authenticated)
if fn:
return decorator(fn)
return decorator
Using it
@membership_required
def some_view(request):
...
Here is a slightly different implementation, which allows additional parameters in order to specify which page to redirect to when validation fails, and which message to display to the end user:
from functools import wraps
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from core.helpers.flash import send_flash_error
def lender_only(redirect_to='plateforme.views.vue_login', error_flash_message=None):
def inner_render(fn):
@wraps(fn) # Ensure the wrapped function keeps the same name as the view
def wrapped(request, *args, **kwargs):
if request.context.user.is_authenticated and request.context.user.is_lender:
return fn(request, *args, **kwargs)
else:
if error_flash_message:
send_flash_error(request, error_flash_message) # Replace by your own implementation
return HttpResponseRedirect(reverse(redirect_to))
return wrapped
return inner_render
# Usage:
@lender_only('vitrine.views.projets', {'message': "Oops, can't go there."})
def render_page_index(request):
This guide helped me getting through it: https://elfsternberg.com/2009/11/20/python-decorators-with-arguments-with-bonus-django-goodness/ alongside the previous answers
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
def perm_group_required(group, login_url='/', raise_exception=False):
def check_group(user):
if user.groups.filter(name=group).exists():
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
return False
return user_passes_test(check_group, login_url=login_url)
@perm_group_required('add_customer_group')
#group name="add_customer_group"
def employee_add_customer(request):
##logic
...
For example, with the custom decorator @tran
below, test()
can be run in transaction. *You can see my answer explaining about a custom decorator in detail:
# "views.py"
from django.db import transaction
from django.http import HttpResponse
def tran(func): # Here
def core(request, *args, **kwargs):
with transaction.atomic():
return func(request, *args, **kwargs)
return core
@tran # Here
def test(request):
person = Person.objects.all()
print(person)
return HttpResponse("Test")
I’m trying to create a custom decorator in Django but I couldn’t find any ways to do it.
# "views.py"
@custom_decorator
def my_view(request):
# .......
So, how can I create it in Django? and where should I put it so that I can use it anywhere in my Django project?
http://www.makina-corpus.org/blog/permission-required-decorator-django
i based mine off that blog post.
Stick that in a file in the python path or in a “util” app and import it into views:
e.g.
project_dir
|_ app1
|_ app2
|_ utils
|_ __init__.py
|_ permreq.py
from util.permreq import permission_required
@permmission_required('someapp.has_some_perm', template='denied.html')
def some_view(request):
blah blah
You don’t have to write your own decorator for this as user_passes_test
is already included in Django.
And there’s a snippet (group_required_decorator
) that extends this decorator and which should be pretty appropriate for your use case.
If you really want to write your own decorator then there’s a lot of good documentation on the net.
And well, to (re-) use the decorator just put your decorator in a module on your path and you can import it from any other module.
See examples in django itself:
http://code.djangoproject.com/browser/django/trunk/django/contrib/auth/decorators.py
Your particular example is probably just a version of ‘user_passes_test’ where the test is going to be membership of the ‘premium’ group.
To use anywhere, make a python package and import it from there. As long as its on your sys.path it’ll get found.
Played around with the various links above and couldn’t get them working and then came across this really simple one which I adapted. http://code.activestate.com/recipes/498217-custom-django-login_required-decorator/
from functools import wraps
from django.http import HttpResponseRedirect
def authors_only(function):
@wraps(function)
def wrap(request, *args, **kwargs):
profile = request.user.get_profile()
if profile.usertype == 'Author':
return function(request, *args, **kwargs)
else:
return HttpResponseRedirect('/')
return wrap
Using @wraps
is better than manually overriding like doing wrap.__doc__ = fn.__doc__
. Amongst other things, it ensures your wrapper function gets the same name as the wrapped function.
Thanks to arie, the answer helped a long way, but it doesn’t work for me.
When I found this snippet, I got it to work properly: http://djangosnippets.org/snippets/983/
This solution worked for me:
The helper function
This function has the benefit of being reusable in other places, as a drop in replacement for user.is_authenticated
. It could for instance be exposed as a template tag.
def my_custom_authenticated(user):
if user:
if user.is_authenticated():
return user.groups.filter(name=settings.MY_CUSTOM_GROUP_NAME).exists()
return False
The decorator
I just put this at the top of my views.py
, since it’s so short.
def membership_required(fn=None):
decorator = user_passes_test(my_custom_authenticated)
if fn:
return decorator(fn)
return decorator
Using it
@membership_required
def some_view(request):
...
Here is a slightly different implementation, which allows additional parameters in order to specify which page to redirect to when validation fails, and which message to display to the end user:
from functools import wraps
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from core.helpers.flash import send_flash_error
def lender_only(redirect_to='plateforme.views.vue_login', error_flash_message=None):
def inner_render(fn):
@wraps(fn) # Ensure the wrapped function keeps the same name as the view
def wrapped(request, *args, **kwargs):
if request.context.user.is_authenticated and request.context.user.is_lender:
return fn(request, *args, **kwargs)
else:
if error_flash_message:
send_flash_error(request, error_flash_message) # Replace by your own implementation
return HttpResponseRedirect(reverse(redirect_to))
return wrapped
return inner_render
# Usage:
@lender_only('vitrine.views.projets', {'message': "Oops, can't go there."})
def render_page_index(request):
This guide helped me getting through it: https://elfsternberg.com/2009/11/20/python-decorators-with-arguments-with-bonus-django-goodness/ alongside the previous answers
from django.contrib.auth.decorators import user_passes_test
from django.core.exceptions import PermissionDenied
def perm_group_required(group, login_url='/', raise_exception=False):
def check_group(user):
if user.groups.filter(name=group).exists():
return True
# In case the 403 handler should be called raise the exception
if raise_exception:
raise PermissionDenied
return False
return user_passes_test(check_group, login_url=login_url)
@perm_group_required('add_customer_group')
#group name="add_customer_group"
def employee_add_customer(request):
##logic
...
For example, with the custom decorator @tran
below, test()
can be run in transaction. *You can see my answer explaining about a custom decorator in detail:
# "views.py"
from django.db import transaction
from django.http import HttpResponse
def tran(func): # Here
def core(request, *args, **kwargs):
with transaction.atomic():
return func(request, *args, **kwargs)
return core
@tran # Here
def test(request):
person = Person.objects.all()
print(person)
return HttpResponse("Test")