1

我想根据用户权限创建动态菜单。正如这里文档本身已经讨论的那样,我知道我可以使用以下代码片段在模板中实现这一点:

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

但问题是,出于安全原因,我也想限制对视图的访问。我在文档中找到的片段如下:

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 原则吗?有没有办法只在一个地方定义每个 url 所需的权限是什么?也许在 urls.py 中?

4

2 回答 2

2

编辑:(请参阅帖子末尾的原始答案的原始文本,简单的想法。)

在受到线索蝙蝠的好意(请参阅下面的 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_patternspatterns('', *dyn_menu_patterns)用于在你的 URLconf 中使用。

我希望这是有道理的,并有一些帮助......

预编辑答案:

根据您的简短描述,我不确定哪种解决方案最适合您……但是,如果该permission_required片段可以满足您的要求,但不够干燥,那么滚动您自己的包装器如何:

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

你可以把它放在任何地方,包括在 URLconf 中。然后,您可以参考在 URL 文件顶部定义的变量替换所有提及的内容'/loginpage/',并且您将拥有一个解决方案,只需提及实际的登录 URL,如果您只对所述 URL 进行一次更新必须移动它。:-)

当然,视图仍然需要显式包装;如果这让您感到困扰,您可以尝试制作ask_to_login一个装饰器,以便在定义站点上轻松包装。(但也许最好不要这样做,以免你强迫自己从装饰器下面挖掘你的观点,以防你将来需要它们不装饰。)

于 2009-12-20T12:27:00.323 回答
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 中所需的坑,调用方式与使用普通 url 相同,但添加了 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 中添加类似的内容,允许它检查这些基本权限是否存在,然后检查是否添加了任何额外内容。

希望这个对你有帮助。

--

G

于 2010-01-02T16:08:24.317 回答