Sunday, April 29, 2018

Decorator function argument woes

I'm pretty sure this is a not-understanding-python problem rather than a Django problem but here goes ...

In a (FBV) view I'm trying to pass a function in to the login_required decorator. My function is called 'is_login_needed' and I want the login_required decorator to effectively switch itself off if login is not needed.

The scenario is on-line training and if the training instruction has questions with scores then obviously the user needs to login. On the other hand if it is simply a demonstration video or plain blah with no questions/answers/scores it should be viewable by anyone whether they are logged in or not.

My function goes like this and the docstring reveals my understanding ...

def is_login_needed(user):
    """ the login_required decorator has a user_passes_test() function as
    its first arg. If it returns True, no login is required. user.login_here
    is always set equal to the selected course.login_needed. For courses
    needing a login, self.is_authenticated must be True. For courses not
    needing a login, self.is_authenticated may return anything but this
    function passed to the login_required decorator must return True
    """
    if not user.login_here:
        return True

So   the problem is that manage.py runserver reports an attribute error saying the function (presumably mine) does not have an attribute 'user' ... like this ...

  <earlier runserver traceback lines snipped>      File "C:\Users\mike\envs\xxct3\train\course\urls.py", line 8, in <module>
    from .views import (finished_course_view, course_view, index_view,
  File "C:\Users\mike\envs\xxct3\train\course\views.py", line 172, in <module>
    def course_view(request, pk=None, slug=None):
  File "C:\Users\mike\envs\xxct3\lib\site-packages\django\contrib\auth\decorators.py", line 22, in _wrapped_view
    if test_func(request.user):
AttributeError: 'function' object has no attribute 'user'
If that 'function' is my 'is_login_needed' function I'm inclined to think it obviously has a 'user' attribute.

Here is the
contrib.auth.decorators.login_required source (preceded by user_passes_test which it calls) from Django 1.11 

My reading of the following  is that my own 'is_login_needed' function passed in via @login_required(is_login_needed, login_url='login') as the first positional argument is then passed to the 'user_passes_test' decorator as 'test_func'  ...


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.
    """
    def decorator(view_func):
        @wraps(view_func, assigned=available_attrs(view_func))
        def _wrapped_view(request, *args, **kwargs):
            if test_func(request.user):
                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 login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
    """
    Decorator for views that checks that the user is logged in, redirecting
    to the log-in page if necessary.
    """
    actual_decorator = user_passes_test(
        lambda u: u.is_authenticated,
        login_url=login_url,
        redirect_field_name=redirect_field_name
    )
    if function:
        return actual_decorator(function)
    return actual_decorator
Could someone please explain where I'm stuffing up. I reckon I'm confused about which function the AttributeError is complaining.

Many thanks for any help

Mike




No comments:

Post a Comment