You have access control on views, but you also want to apply the same checks in templates in a maintainable way.
This kicked off my solution: https://stackoverflow.com/questions/43904784/reuse-the-decorator-of-a-view-from-a-template
You have access control on views, but you also want to apply the same checks in templates in a maintainable way.
This kicked off my solution: https://stackoverflow.com/questions/43904784/reuse-the-decorator-of-a-view-from-a-template
| # Copy of `django.contrib.auth.decorators.user_passes_test`, relevant changes noted. | |
| from django.conf import settings | |
| from django.contrib.auth import REDIRECT_FIELD_NAME | |
| from django.contrib.auth.views import redirect_to_login | |
| from django.shortcuts import resolve_url | |
| from functools import wraps | |
| from urllib.parse import urlparse | |
| # 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): | |
| 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) | |
| _wrapped_view.can_view_validator = (test_func, *test_args) | |
| return _wrapped_view | |
| return decorator |
| 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 |
| from django.urls import path | |
| from . import views | |
| app_name = 'my_app' | |
| urlpatterns = [ | |
| # The url you want secured | |
| path("secured/", views.secured, name="secured"), | |
| ] |
| from django.utils.decorators import method_decorator | |
| from django.views.generic import ListView | |
| from .decorators import user_passes_test | |
| from .utils import perms_check | |
| # This view has a user perms check | |
| # NOTE: This is our custom `user_passes_test` | |
| @user_passes_test(perms_check, 'a', ...) | |
| def secured(request): | |
| pass | |
| # Class Based Views | |
| @method_decorator(user_passes_test(perms_check, 'a', ...), name='dispatch') | |
| class SecuredListView(ListView): | |
| pass |