从模板重用视图的装饰器

时间:2017-05-11 00:51:22

标签: django

我的一些观点有限制访问的装饰器,如下所示:

@user_passes_test(my_validation_function)
def my_restricted_view(request):
    ...

问题是,在我的模板中,我想根据my_validation_function中的逻辑隐藏用户无权访问的链接。

我知道这样做的一种方法是定义一个基本上调用my_validation_function(例如my_validation_filter)的自定义过滤器,并相应地显示/隐藏链接。像这样:

{% if request | my_validation_filter %}
    <a href="{% url 'my_restricted_view' %}"></a>
{% endif %}

我在这里看到的问题是我将验证链接两次:一次在视图中,一次在模板中。假设我有很多视图,每个视图后面都有不同的验证逻辑:

@user_passes_test(my_validation_function)
def my_restricted_view(request):
    ...

@user_passes_test(my_other_validation_function)
def my_other_restricted_view(request):
    ...

这意味着,当我编写模板时,我必须要小心,始终记住哪个验证功能与哪个视图一致。

有没有办法定义一个函数或反转URL,然后检查视图装饰器中定义的验证?我在想这样的事情:

{% if can_access 'my_restricted_view' %}
    {# this implicitly calls 'my_validation_function' #}
    ...
{% endif %}

{% if can_access 'my_other_restricted_view' %}
    {# this implicitly calls 'my_other_validation_function' #}
    ...
{% endif %}

基本上我想要的只是在一个地方改变每个视图的验证逻辑,而不是触摸我的模板。

2 个答案:

答案 0 :(得分:1)

你的问题非常有趣,我没有完整的答案,但有些曲目。

首先,很难,也许不可能从装饰函数中获取装饰器,例如 acc_cfg.cred_info[0].username = pj_str((char*)uname); acc_cfg.cred_info[0].data_type = PJSIP_CRED_DATA_PLAIN_PASSWD; acc_cfg.cred_info[0].data = pj_str((char *)passwd); acc_cfg.cred_info[0].realm = pj_str("*"); acc_cfg.cred_info[0].scheme=pj_str((char*)"Digest"); char regUri[PJSIP_MAX_URL_SIZE]; sprintf(regUri, "sip:%s", sip_server); acc_cfg.reg_uri = pj_str(regUri); acc_cfg.ipv6_media_use = PJSUA_IPV6_ENABLED; acc_cfg.transport_id = transport_id_udp6; ,我不知道如何做。但是您可以将验证功能从装饰器移动到装饰功能。替换这个:

inspect

由此:

@user_passes_test(my_validation_function)
def my_restricted_view(request):
    ...

在装饰器的代码中处理这种变化应该很容易。

然后您可以编写一个自定义过滤器,您可以将其称为:

@user_passes_test
def my_restricted_view(request):
    ...

my_restricted_view.validation_function = my_validation_function

此过滤器的代码可能如下所示:

{% if request|validation_filter:'my_restricted_view' %}

答案 1 :(得分:0)

@albar和@Jamie Sanz之间的交流使我走上了正确的道路。下面的完整解决方案,也链接到要旨(https://gist.github.com/solace/9cfae6cb9c60658857ee73f05d5b715a)。与随附的模板标签相比,user_passes_test略有增加。

views.py

from .decorators import user_passes_test
from .utils import perms_check


# pass `test_func` and related test params
@user_passes_test(perms_check, 'a', ...)
def secured(request):
    pass

decorators.py

# Copy of `django.contrib.auth.decorators.user_passes_test`, relevant changes noted.
# CHANGED: `*test_args` for the inclusion of `test_func` arguments
def user_passes_test(test_func, *test_args, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
    def decorator(view_func):

        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            # CHANGED: Pass `*test_args` to the `test_func` 
            if test_func(request.user, *test_args):
                return view_func(request, *args, **kwargs)
            path = request.build_absolute_uri()
            resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
            # If the login url is the same scheme and net location then just
            # use the path as the "next" url.
            login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
            current_scheme, current_netloc = urlparse(path)[:2]
            if ((not login_scheme or login_scheme == current_scheme) and
                    (not login_netloc or login_netloc == current_netloc)):
                path = request.get_full_path()
            return redirect_to_login(
                path, resolved_login_url, redirect_field_name)
        # ADDED: set the `test_func` and `test_args` on the view function to use later
        _wrapped_view.can_view_validator = (test_func, *test_args)
        return _wrapped_view

    return decorator

permissions.py

from django import template
from django.core.exceptions import PermissionDenied
from django.urls import resolve, reverse


register = template.Library()


# Creates a template tag that looks like this:
# {{ can_view 'my_app:secured' as can_view_secured }}
@register.simple_tag(takes_context=True)
def can_view(context, named):
    # `reverse` the named url to get the url, `resolve` it to get the view function
    # `user_passes_test` returns True/False or redirects, but `test_func` could also
    # raise a `PermissionDenied`. So handle that, too.
    match = resolve(reverse(named))
    try:
        if hasattr(match.func, 'can_view_validator'):
            (test_func, *args) = match.func.can_view_validator
            return test_func(context.request.user, *args)
        else:
            return True
    except PermissionDenied:
        return False

some_template.html

{% load permissions %}

<ul class="menu">
  ...
  {% can_view 'my_app:secured' as can_view_secured %}
  {% if can_view_secured %}
  <li>
    <a href="{% url 'my_app:secured' %}">Secured Link</a>
  </li>
  {% endif %}
  ...
</ul>