【问题标题】:Django Dynamic menu design questionDjango 动态菜单设计问题
【发布时间】:2010-12-28 11:27:37
【问题描述】:

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

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

但问题是,出于安全原因,我也想限制对视图的访问。我在documentation找到的sn-p如下:

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 中?

【问题讨论】:

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


    【解决方案1】:

    我知道几周前有人问过这个问题,但你在你的一个 cmets 中提到了http://code.google.com/p/greatlemers-django-tools/,所以我想我会加入。

    该项目仍处于活动状态(尽管目前处于次要地位),但我不确定它是否像您所追求的那样 DRY。您仍然需要指定两次权限,一次在菜单项的模型对象中,一次在视图中。这不一定是坏事,但是您在菜单项上定义的权限可能与视图上的权限略有不同。

    如果你想在一个地方做所有事情,我可能会建议在 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

    【讨论】:

      【解决方案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_patterns,将用于patterns('', *dyn_menu_patterns) , 在你的 URLconf 中使用。

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

      预编辑答案:

      根据您的简短描述,我不确定哪种解决方案最适合您...但是,如果 permission_required sn-p 可以满足您的需求,只是不够干,那么您自己滚动如何包装:

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

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

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

      【讨论】:

      • 谢谢,但我正在寻找像code.google.com/p/greatlemers-django-tools 这样的“DRYer”,但我不知道该项目是否仍然有效。
      • 是的,我现在明白你的意思了......我编辑了我的答案以包括一个可能的解决方案的草图,我想知道这对你有没有用?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-27
      • 2014-04-10
      • 2012-12-29
      • 2015-01-23
      相关资源
      最近更新 更多