Hi everyone, this is my first proposal so please don't be too hard on me :)

*Context:*
I was implementing one of my first websites using Django, and when I tried 
to add the logic for restricting access of certain views to some users, I 
discovered this `user_passes_test` decorator, which is a pretty awesome 
decorator. But then in my test function I needed to use the Django messages 
framework to send a message when the user cannot access the view, but I 
realized that `user_passes_test` only sends the user object, and there is 
no option to pass the request object.

I searched my problem on the Internet and I found a few related issues:
1) How to pass Django request object in user_passes_test... - Stackoverflow 
<https://stackoverflow.com/questions/11872560/how-to-pass-django-request-object-in-user-passes-test-decorator-callable-functio>
2) Show error message when decorator fails - Stackoverflow 
<https://stackoverflow.com/questions/16375023/show-error-message-when-decorator-fails>
And I didn't search more because I understood that there is only one 
solution right now: overwrite the `user_passes_test` function.

*Proposal*
This is a pretty old problem (first issue is 9 years old) and it seems that 
every so often a new user runs into this problem, the solution of which is 
inelegant (overwrite the code of the Django function).

I know that the workaround is very simple, and that seems to be the reason 
why no one has decided to bother to modify the logic of that function. But 
if the solution is so simple (and also does not change the current behavior 
of Django), why not implement it in the Django source code instead of 
forcing developers to implement a workaround in their project?

Then I opened a ticket in the Django issue tracker 
<https://code.djangoproject.com/ticket/33299>, but sadly it was rejected 
(maybe I didn't explain correctly the problem, or it seemed like it wasn't 
worth the effort). 

*Final solution*
Since I was rejected, I did some simple tests (link to online-python.com) 
<https://www.online-python.com/swnTmgrp6c> to try out if my solution would 
work (it was my first time working with this kind of advanced decorators). 
Then I improved the solution sent and the final result is this (
contrib/auth/decorators.py 
<https://raw.githubusercontent.com/django/django/ca9872905559026af82000e46cde6f7dedc897b6/django/contrib/auth/decorators.py>
):

def request_passes_test(test_func, login_url=None, 
redirect_field_name=REDIRECT_FIELD_NAME, attrname=None):
    """
    Decorator for views that checks that the request passes the given test,
    redirecting to the log-in page if necessary. The test should be a 
callable
    that takes the request object and returns True if the request passes.
    Optionally you can pass an attribute name in order to test an attribute
    from request.
    """
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            attr = request if attrname is None else getattr(request, 
attrname)
            if test_func(attr):
                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()
            from django.contrib.auth.views import redirect_to_login
            return redirect_to_login(
                path, resolved_login_url, redirect_field_name)
        return _wrapped_view
    return decorator


def user_passes_test(test_func, login_url=None, 
redirect_field_name=REDIRECT_FIELD_NAME):
    """
    Decorator for views that checks that the user passes the given test,
    redirecting to the log-in page if necessary. The test should be a 
callable
    that takes the user object and returns True if the user passes.
    """
    return request_passes_test(test_func, login_url, redirect_field_name, 
attrname="user")

With this solution the `user_passes_test` decorator would be working as it 
is working right now, but we could use `request_passes_test` in order to do 
more advanced stuff in our test functions, such as using the Django 
messages framework in the test function, or checking other parameters of 
the `request` object.

Thank you very much for your time, and please ask me for more information 
if you have questions. I am willing to open the Pull Request myself, and 
add modifications to the solution if needed. It would be awesome to join 
the team of Django contributors :D

Best regards,
Iago.

-- 
You received this message because you are subscribed to the Google Groups 
"Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-developers+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-developers/564e0f9c-886d-4c73-9397-28adb02b5d5dn%40googlegroups.com.
  • New... Iago González Rodríguez
    • ... 'Adam Johnson' via Django developers (Contributions to Django itself)
      • ... Iago González Rodríguez

Reply via email to