class MyView(MyMixin, FormView): and async

Hello all!

I am having a hard time implementing the following in a async CBV:

EDIT: more generally, what is considered a good way to use LoginRequiredMixin and UserPassesTestMixin in an async context?

class NewView(UserLoggedInMixin, FormView):
     # normal FormView stuff

and the mixin (heavily WIP as you might guess… :)):

from asgiref.sync import sync_to_async

from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.shortcuts import redirect
from django.core.exceptions import PermissionDenied
from django.urls import reverse_lazy


class UserLoggedInMixin(LoginRequiredMixin, UserPassesTestMixin):
    async def dispatch(self, request, *args, **kwargs):
        user = await request.auser()
        if not user.is_authenticated:
            return await self.handle_no_permission()
        return await super().dispatch(request, *args, **kwargs)

    @sync_to_async
    def test_func(self):
        user = self.request.user()
        return user.is_active

    # @sync_to_async
    async def handle_no_permission(self):
        if self.raise_exception:
            raise PermissionDenied(self.get_permission_denied_message())
        return await redirect(self.get_login_url())

    @sync_to_async
    def get_login_url(self):
        user = self.request.user()

        redir = reverse_lazy('login:index')

        if self.request.path and self.request.resolver_match:
            redir = f'{redir}?next={self.request.path}'

        return redir

and the exception is this, whic to be honest I have a hard time getting my head around :slight_smile:

TypeError at /xxx/xxx

argument of type 'coroutine' is not iterable

Request Method: GET
Request URL: http://127.0.0.1/xxx/xxx

Django Version: 5.0.3
Python Version: 3.11.6
Installed Applications:
['django.contrib.auth',
 'django.contrib.contenttypes',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.staticfiles',
 'mathfilters',
...]
Installed Middleware:
['django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware']

Traceback (most recent call last):
  File "/home/dil/env/lib/python3.11/site-packages/django/shortcuts.py", line 180, in resolve_url
    return reverse(to, args=args, kwargs=kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/env/lib/python3.11/site-packages/django/urls/base.py", line 88, in reverse
    return resolver._reverse_with_prefix(view, prefix, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/env/lib/python3.11/site-packages/django/urls/resolvers.py", line 851, in _reverse_with_prefix
    raise NoReverseMatch(msg)
    ^^^^^^^^^^^^^^^^^^^^^^^^^

During handling of the above exception (Reverse for '<coroutine object SyncToAsync.__call__ at 0x7faff2c43740>' not found. '<coroutine object SyncToAsync.__call__ at 0x7faff2c43740>' is not a valid view function or pattern name.), another exception occurred:
  File "/home/dil/env/lib/python3.11/site-packages/django/core/handlers/exception.py", line 42, in inner
    response = await get_response(request)
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/env/lib/python3.11/site-packages/django/core/handlers/base.py", line 253, in _get_response_async
    response = await wrapped_callback(
               
  File "/home/dil/web/portal/web/auth/mixins.py", line 22, in dispatch
    return await self.handle_no_permission()
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/web/portal/web/auth/mixins.py", line 39, in handle_no_permission
    return await redirect(self.get_login_url())
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/env/lib/python3.11/site-packages/django/shortcuts.py", line 49, in redirect
    return redirect_class(resolve_url(to, *args, **kwargs))
                          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/dil/env/lib/python3.11/site-packages/django/shortcuts.py", line 186, in resolve_url
    if "/" not in to and "." not in to:
       ^^^^^^^^^^^^^

Exception Type: TypeError at /post/new
Exception Value: argument of type 'coroutine' is not iterable

Any ideas?

ok well got something working, in case it helps anyone.

Any better ways, though? :slight_smile:

from django.contrib.auth.mixins import AccessMixin
from django.contrib.auth.views import redirect_to_login
from django.urls import reverse_lazy


class AsyncLoginRequiredMixin(AccessMixin):
    login_url = reverse_lazy('login:index')

    async def dispatch(self, request, *args, **kwargs):
        user = await request.auser()
        if not user.is_authenticated:
            return await self.handle_no_permission()
        return await super().dispatch(request, *args, **kwargs)

    async def handle_no_permission(self):
        return redirect_to_login(
            self.request.get_full_path(),
            self.get_login_url(),
            self.get_redirect_field_name()
        )


class AsyncUserPassesTestMixin(AccessMixin):
    async def dispatch(self, request, *args, **kwargs):
        user = await request.auser()
        if not await self.test_func(user):
            return await self.handle_no_permission()
        return await super().dispatch(request, *args, **kwargs)

    async def handle_no_permission(self):
        return redirect_to_login(self.request.get_full_path(), self.get_login_url(), self.get_redirect_field_name())

    async def test_func(self, user):
        """
        Override as needed in individual CBVs
        """
        return user.is_active

and simple usage:

class NewView(AsyncLoginRequiredMixin, AsyncUserPassesTestMixin, FormView):
     # normal FormView stuff