73

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?

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
user677990
  • 835
  • 2
  • 10
  • 10

8 Answers8

85

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

Vadorequest
  • 16,593
  • 24
  • 118
  • 215
PhoebeB
  • 8,434
  • 8
  • 57
  • 76
  • 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. – radtek Sep 16 '14 at 00:59
  • @radtek i got `wrap() takes at least 1 argument (0 given)`. Any clue to solve it? – Ardian Aug 16 '15 at 16:14
  • 1
    Would have to see your code, but most likely you didn't pass request to your function you are decorating. – radtek Aug 18 '15 at 17:53
  • the ```@wraps``` method is just for docs, so you could leave it out, right? – Sven May 03 '20 at 14:07
61

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.

arie
  • 18,737
  • 5
  • 70
  • 76
  • 1
    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) – user677990 Mar 29 '11 at 08:18
  • it gives this error - souk_required takes no arguments (1 given) – user677990 Mar 29 '11 at 08:19
  • 1
    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. – arie Mar 29 '11 at 08:31
  • Its not your fault it happens to the best of us ; ].... anyways thanks a lot man... I owe you an awful lot of time. – user677990 Mar 29 '11 at 08:42
  • 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. – Neil May 17 '23 at 23:35
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):
    ...
thnee
  • 5,817
  • 3
  • 27
  • 23
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.

Spacedman
  • 92,590
  • 12
  • 140
  • 224
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

Vadorequest
  • 16,593
  • 24
  • 118
  • 215
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
dting
  • 38,604
  • 10
  • 95
  • 114
  • You can write them into your models, http://docs.djangoproject.com/en/dev/topics/auth/#custom-permissions and manage them through the admin or django shell – dting Mar 29 '11 at 07:56
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
    ...
Ersain
  • 1,466
  • 1
  • 9
  • 20
  • What is `#group name="add_customer_group"`? Any link for how to make a decorator with an object that has a `BooleanField`? – AnonymousUser Aug 06 '22 at 04:36
  • 1
    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 function – Prashan Basantia Aug 06 '22 at 06:16
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")
Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129