Find the required permissions of Django URLs witho

2020-06-16 10:55发布

My Django app currently has URLs which are protected by 'permission_required()' functions.

This function is called in three different ways.

  1. As a decorator in views.py, with hardcoded parameters.
  2. As a plain function, with autogenerated parameter, in custom Class Based Generic Views.
  3. As a function invoking views in urls.py, with hardcoded parameters.

I'm now adding a menu system to the app, and I need to make menu entries reflect whether the user has permission to request the URL of each menu entry. (Either by greying-out or hiding said entries.)

Is there a way of query the permissions required to a URL without requesting the URL?

The only solution I've thought of so far is to replace the decorator with a parameterless 'menu_permssion_required()' decorator and hardcode all of the permissions into a Python structure. This seems like a step backwards, as my custom Class Based Generic Views already autogenerate their required permissions.

Any suggestions on how to make a menu system which reflects URL permissions for the current user?

3条回答
趁早两清
2楼-- · 2020-06-16 11:21

Here is an example of how to solve your problem:

First, Create a decorator wrapper to use instead of permission_required:

from django.contrib.auth.decorators import login_required, permission_required, user_passes_test
from django.core.exceptions import PermissionDenied
from functools import wraps
from  django.utils.decorators import available_attrs

def require_perms(*perms):
    def decorator(view_func):
        view_func.permissions = perms
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            for perm in perms:
                return view_func(request, *args, **kwargs)
            raise PermissionDenied()
        return _wrapped_view
    return decorator

Then, use it to decorate your views:

@require_perms('my_perm',)
def home(request):
.....

Then, add a tag to use for your menu items:

from django.core.urlresolvers import resolve

def check_menu_permissions(menu_path, user):
    view = resolve(menu_path)
    if hasattr(view.func, "permissions"):
        permissions = view.func.permissions
        for perm in permissions:
            if user.has_perm(perm):
                return True # Yep, the user can access this url
            else:
                return False # Nope, the user cannot access this url
    return True #  or False - depending on what is the default behavior

And finally, in your templates, when building the menu tree:

<button href="{{ some_path }} {% if not check_menu_permissions some_path request.user %}disabled="disabled"{% endif %} />

N.B. I've not tested the last part with the tag, but I hope you got the idea. The magic thing here is to add the permissions to the view_func in the decorator, and then you can access this using resolve(path). I'm not sure how this will behave in terms of performance, but after all that's just an idea.

EDIT: Just fixed a bug in the example..

查看更多
Anthone
3楼-- · 2020-06-16 11:23

Is there a way of query the permissions required to a URL without requesting the URL?

User.has_perm() and User.has_module_perms()

Any suggestions on how to make a menu system which reflects URL permissions for the current user?

I really like this question, because it concerns anyone that makes a website with django, so I find it really relevant. I've been through that myself and even coded a menu "system" in my first django project back in 2008. But since then I tried Pinax, and one of the (so many) things I learnt from their example projects is that it is completely unnecessary bloat.

So, I have no suggestion which I would support on how to make a menu "system" which respects the request user permissions.

I do have a suggestion on how to make a simple menu which respects the request user permissions, so that might not be completely unrelated.

  1. Just make your menu in plain HTML, it's not like it's going to change so often that it has to be generated. That will also keep your Python code simpler.

  2. Add to settings.TEMPLATE_CONTEXT_PROCESSORS: 'django.core.context_processors.PermWrapper'

  3. Use the {{ perms }} proxy to User.has_perms.

Example:

{% if perms.auth %}
    <li class="divider"></li>

    {% if perms.auth.change_user %} 
    <li>
        <a href="{% url admin:auth_user_changelist %}">{% trans 'Users' %}</a>
    </li>
    {% endif %} 

    {% if perms.auth.change_group %} 
    <li>
        <a href="{% url admin:auth_group_changelist %}">{% trans 'User groups' %}</a>
    </li>
    {% endif %} 

{% endif %} 
{# etc, etc #}

That's how I keep navigation simple, stupid, and out of the way. But also I always include an autocomplete not far from the menus to allows the user to navigate to any detail page easily. So, that's all I know about navigation in django projects, I'm eager to read other answers !

查看更多
Melony?
4楼-- · 2020-06-16 11:31

I had a similar issue, but it went a little deeper. Instead of just permissions, I also wanted other tests based on the lidded in user (ie, is_staff, or user.units.count() > 1). Duplicating these in the view and the template seems prone to errors.

You can introspect a view object, and see all of the decorators wrapping it, and work out if they are checks (in my case: the first argument I'd u or user). If they all pass, then allow rendering the link.

Get all decorators wrapping a function describes the technique in a little more detail. You can find the app that wraps this up into a handy replacement for {% url %} at Django-menus.

查看更多
登录 后发表回答