Skip to content

Instantly share code, notes, and snippets.

@solace
Last active July 27, 2023 12:40
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save solace/9cfae6cb9c60658857ee73f05d5b715a to your computer and use it in GitHub Desktop.
Save solace/9cfae6cb9c60658857ee73f05d5b715a to your computer and use it in GitHub Desktop.
Django: augmented user_passes_test to accept params for test_func with matching template tag
# 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"),
]
{% 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>
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment