How to create a custom decorator in Django?
Asked Answered
A

8

75

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?

Amoroso answered 29/3, 2011 at 7:23 Comment(0)
M
62

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.

Melody answered 29/3, 2011 at 7:44 Comment(5)
def souk_required(): """Requires user membership in at least one of the groups passed in.""" def has_souk(u): if u.is_authenticated(): if bool(SoukUsers.objects.get(person = u)): return True return False(u) return user_passes_test(has_souk)Amoroso
it gives this error - souk_required takes no arguments (1 given)Amoroso
Hm .. i accidentally upvoted your comment ;-) Well, you removed the expected argument from you function definition and therefore receive the given error. So, what about creating a group "premiumusers" and adding your users to that group? Then you can use the snippet as it is and just pass in the name of your group.Melody
Its not your fault it happens to the best of us ; ].... anyways thanks a lot man... I owe you an awful lot of time.Amoroso
I think in addition to providing a round-about but albeit correct solution, please also answer the question directly. OP asked for a custom decorator.Notwithstanding
M
88

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.

See https://docs.python.org/2/library/functools.html

Monopolist answered 25/5, 2011 at 9:22 Comment(4)
This should be the accepted answer, thumbs up! I tried to piggy back off the user_passes_test decorator but got lost, this saved the day.Extinctive
@Extinctive i got wrap() takes at least 1 argument (0 given). Any clue to solve it?Cp
Would have to see your code, but most likely you didn't pass request to your function you are decorating.Extinctive
the @wraps method is just for docs, so you could leave it out, right?Allinclusive
M
62

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.

Melody answered 29/3, 2011 at 7:44 Comment(5)
def souk_required(): """Requires user membership in at least one of the groups passed in.""" def has_souk(u): if u.is_authenticated(): if bool(SoukUsers.objects.get(person = u)): return True return False(u) return user_passes_test(has_souk)Amoroso
it gives this error - souk_required takes no arguments (1 given)Amoroso
Hm .. i accidentally upvoted your comment ;-) Well, you removed the expected argument from you function definition and therefore receive the given error. So, what about creating a group "premiumusers" and adding your users to that group? Then you can use the snippet as it is and just pass in the name of your group.Melody
Its not your fault it happens to the best of us ; ].... anyways thanks a lot man... I owe you an awful lot of time.Amoroso
I think in addition to providing a round-about but albeit correct solution, please also answer the question directly. OP asked for a custom decorator.Notwithstanding
S
6

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):
    ...
Synthesis answered 29/3, 2013 at 15:6 Comment(0)
S
3

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.

Seineetmarne answered 29/3, 2011 at 7:45 Comment(0)
D
2

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

Dilatory answered 5/10, 2018 at 15:25 Comment(0)
U
1
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
    ...
Unsaid answered 29/3, 2011 at 7:23 Comment(2)
What is #group name="add_customer_group"? Any link for how to make a decorator with an object that has a BooleanField?Abstention
name="add_customer_group" is a permission Group Name and that perm group name assign to authenticated user , if any authenticated user have this permission then they can access the view functionUnsaid
C
1

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
Cobble answered 29/3, 2011 at 7:43 Comment(1)
You can write them into your models, docs.djangoproject.com/en/dev/topics/auth/#custom-permissions and manage them through the admin or django shellCobble
M
0

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")
Meissner answered 13/11, 2022 at 1:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.