Django动态菜单设计问题

时间:2009-12-20 11:15:03

标签: django permissions menu django-templates django-urls

我想根据用户权限创建动态菜单。正如已经讨论过heredocumentation itself,我知道我可以使用以下代码段在模板中实现这一点:

{% if perms.polls.can_vote %}
    <li>
        <a href="/polls/vote">Vote</a>
    </li>
{% endif %}

但问题是出于安全考虑,我想限制对视图的访问。我在documentation中找到的代码段如下:

from django.contrib.auth.decorators import permission_required

def my_view(request):
    # ...
my_view = permission_required('polls.can_vote', login_url='/loginpage/')(my_view)

这不符合DRY原则吗?难道没有办法只在一个地方定义每个网址所需的权限是什么?也许在urls.py?

2 个答案:

答案 0 :(得分:2)

编辑:(请参阅帖子的最后部分,了解答案的原始文本,并提供初步的简单介绍。)

在被cluebat叮当作响之后(参见下面的OP评论),我发现我可以看到比以前更多的问题。对不起,花了这么久。无论如何:

这种模板会不会适合你?

{% for mi in dyn_menu_items %}
  {% if mi.authorised %}
     <a href={{ mi.url }}>{{ mi.title }}</a>
  {% endif %}
{% endfor %}

要在Python端进行此项工作,您可以在视图中使用RequestContext,并使用自定义上下文处理器正确设置dyn_menu_items变量。如果需要一些背景信息,Django Book的Advanced Templates章节将介绍RequestContext,展示如何将其与render_to_response(有点重要:-))等一起使用。

此外,我想在这一点上,将负责网站锁定部分的视图函数放在某个列表中可能很有用:

_dyn_menu_items = [(url1, view1, title1, perm1), ...]

然后,您可以map使用该列表中的prepare_patternprepare_menu_item两个函数,让它大致相同:

def prepare_pattern(menu_item):
    url1, view, title, perm = menu_item
    pattern = PREPARE_URLCONF_ENTRY_SOMEHOW(...) # fill in as appropriate
    return pattern

def prepare_menu_item(menu_item):
    url, view, title, perm = menu_item
    mi = PREPARE_THE_BIT_FOR_REQUESTCONTEXT(...) # as above
    return mi

当然,这些可以组合成一个函数,但不是每个人都会发现结果更具可读性......无论如何,map(prepare_menu_item, _dyn_menu_items)的输出需要是一个字典才能传递给你的视图一个有用的上下文处理器(计算出来的,这里有点繁琐,我会留给你;-)),而map(prepare_pattern, _dyn_menu_items)的输出,我们称之为dyn_menu_patterns,在patterns('', *dyn_menu_patterns)中使用,以便在您的URLconf中使用。

我希望这是有道理的并且有所帮助...

预编辑答案:

根据您的简短说明,我不确定哪种解决方案对您最有利......但如果permission_required代码段符合您的要求,那就不够干净了,如何滚动自己的代码呢?包装器:

def ask_to_login(perm, view):
    return permission_required(perm, login_url='/loginpage/', view)

你可以将它放在任何地方,包括在URLconf中。然后,您可以替换'/loginpage/'的所有提及,并引用在URL文件顶部定义的变量,并且您只需提及一个实际的登录URL就可以获得一个解决方案,仅限一个地方的更新你应该移动它所述的URL。 : - )

当然,视图仍然需要明确包装;如果这困扰你,你可以尝试将ask_to_login变成装饰器,以便在定义网站上轻松包装。 (但也许最好不要这样做,以免你强迫自己从装饰师的角度挖掘你的观点,以防你在未来的某个时候需要它们。)

答案 1 :(得分:1)

我知道这个问题是几周前问的,但你在一条评论中提到了http://code.google.com/p/greatlemers-django-tools/,所以我认为我已经填上了。

该项目仍处于活跃状态(虽然目前略有改善),但我不确定它是否像你追求的那样干涸。您仍然需要两次指定权限,一次在菜单项的模型对象中,一次在视图上。这不一定是坏事,因为您在菜单项上定义的权限可能与视图上的权限略有不同。

如果你想在一个地方做所有事情,我可能会建议在urls.py中使用一个实用程序函数的组合,它可以为视图添加限制,同时还存储所述限制,以便与特殊模板标记一起使用。我想它可能看起来像这样。

# Stored in a file named access_check_utils.py say.
from django.conf.urls.defaults import url
from django.core.urlresolvers import get_callable
from django.contrib.auth.decorators import permission_required

access_checked_urls = {}

def access_checked_url(regex, view, kwargs=None, name=None, prefix='', perms=None, login_url=None):
    if perms is None:
        perms = []
    callback = None
    if callable(view):
        callback = view
    elif isinstance(view, basestring):
        if prefix:
            view_path = "%s.%s" % (prefix, view)
        else:
            view_path = view
        try:
            callback = get_callable(view_path)
        except:
            callback = None
    if callback is not None:
        # Add all the permissions
        for perm in perms:
            callback = permission_required(perm, login_url=login_url)(callback)
        if name is not None:
            access_checked_urls[name] = perms
    else:
        callback = view
    return url(regex, callback, kwargs=kwargs, name=name, prefix=prefix)

这应该适用于urls.py中所需的坑,其调用方式与使用普通网址相同,但添加了perms和login_url参数(perms应该是所有相关参数的列表)。

# In a templatetag folder somewhere
from django import template
from django.core.urlresolvers import

# This needs to point to the right place.
from access_check_utils import access_checked_urls

register = template.Library()

@register.inclusion_tag("access_checked_link.html", takes_context=True)
def access_checked_link(context, title, url, *args, **kwargs):
    perms = access_checked_urls.get(url, [])
    if not perms:
       allowed = True
    else:
       allowed = context.request.user.has_perms(perms)
    return { 'allowed': allowed,
             'url': reverse(url, *args, **kwargs),
             'title': title }

这将有一个关联的模板文件,如:

{% if allowed %}<a href="{{ url }}">{{ title }}</a>{% endif %}

我没有对此进行过全面的测试,但是它应该可以工作(或者至少是一个应该工作的好基础)。我甚至可能会将这样的内容添加到gdt_nav中,允许它检查这些基本权限是否存在,然后检查是否添加了额外的内容。

希望这有一些帮助。

-