6

I'm not sure it's possible, but is there a way to write something like

@group_required('group_id')
def myview(request, group_id):
    .....

Where I look at the value of the parameter group_id and limit access to this view to only people who are in this group?

I know it is possible to create a decorator to check membership in some specific group

@group_required('admin')
def myview(request):
   ....

But say I want a view that is only accessible to people within a certain group, which is determined by the url.

So for example

/group/1
/group/2

Should each have permissions so that only members of group #X can see that page. I can easily write this functionality within the view

group = get group
if user not in group
    raise 404

but that logic is repeated all over. Decorators seems like a promising way to do it, but it seems the scope is backwards. Is there another preferred way to handle this sort of permissions?

Thanks!

jkeesh
  • 3,289
  • 3
  • 29
  • 42
  • If you are using CBV check django-braces: http://django-braces.readthedocs.org/en/latest/index.html – petkostas Jan 03 '14 at 10:39
  • Part of our project uses CBVs, but I'm wondering if it's possible on function based views. I'm also not exactly clear yet how this would be done with CBVs but I'll take another look – jkeesh Jan 03 '14 at 10:43
  • It also looks as if the group based permissions there limits access based on a `specific` group-- not where the group is a variable that comes in via a view url parameter – jkeesh Jan 03 '14 at 10:45
  • There is a custom group requirements example: http://django-braces.readthedocs.org/en/latest/access.html#custom-group-usage – petkostas Jan 03 '14 at 11:00

2 Answers2

4

The decorator has access to all the view arguments, so it's definitely possible. You could do this:

def group_required(arg_name):
    def decorator(view):
        def wrapper(request, *args, **kwargs):
            group_id = kwargs.get(arg_name)
            user = request.user
            if group_id in user.groups.values_list('id', flat=True):
                return view(request, *args, **kwargs)
            else:
                return HttpResponseForbidden # 403 Forbidden is better than 404
        return wrapper
    return decorator

(If you don't need the flexibility of dynamically defining the argument in the decorator, eg if you always call it group_id, you can miss out the outer function here and just use kwargs.get('group_id').)

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Thanks so much! This plus rereading this questions http://stackoverflow.com/a/1594484/216877 helped me figure it out. Why do you say 403 is better than 404 here? – jkeesh Jan 03 '14 at 23:50
  • Because 404 means Not Found, which isn't the case here: the resource is found, but the user doesn't have rights to it. Forbidden is much more appropriate. – Daniel Roseman Jan 03 '14 at 23:57
2

Daniel's answer appears to not work on Django 1.11. Here's a modified version:

def group_required(group_name):
    def decorator(view):
        @functools.wraps(view)
        def wrapper(request, *args, **kwargs):
            if (request.user.groups.filter(name=group_name).exists() or
                    request.user.is_superuser):
                return view(request, *args, **kwargs)
            else:
                return HttpResponseForbidden("You don't have permission to view this page.")
        return wrapper
    return decorator
Turtles Are Cute
  • 3,200
  • 6
  • 30
  • 38