We’re running a bunch of Django sites, but seeing some weird behaviour in Django 3.1 if we’re using ASGI to serve the application.
We have the need to be able to access the current user from anywhere in our code. For this we’ve always used a custom Middleware that saved the user from the request into thread locals, and we’d then be able to access the current user from anywhere in the request/response cycle (from inside a model, for example).
We’ve since changed this into using ContextVar instead of thread locals.
The middleware itself looks something like this:
from django.utils.deprecation import MiddlewareMixin
class CurrentUserMiddleware(MiddlewareMixin):
    def process_request(self, request):
        set_current_user(getattr(request, 'user', None))
This middleware is placed after django.contrib.sessions.middleware.SessionMiddleware and django.contrib.auth.middleware.AuthenticationMiddleware and everything’s been working great for years.
After upgrading to Django 3.1, things were initially fine as well. However, we’ve then been starting migrating sites to ASGI (Gunicorn + Uvicorn worker class) instead of WSGI, and upon doing this, everything breaks.
It seems that accessing request.user at this point in the middleware somehow triggers a SynchronousOnlyOperation exception inside the authentication middleware in Django.
I’m assuming it’s because we somehow need to change our custom middleware, but I can’t quite make out why and how - does anyone have any pointers?
The full stack trace is as follows:
AttributeError: 'SessionStore' object has no attribute '_session_cache'
  File "django/contrib/sessions/backends/base.py", line 215, in _get_session
    return self._session_cache
SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
  File "asgiref/sync.py", line 191, in main_wrap
    _restore_context(context[0])
  File "asgiref/sync.py", line 23, in _restore_context
    if cvar.get() != context.get(cvar):
  File "django/utils/functional.py", line 240, in inner
    self._setup()
  File "django/utils/functional.py", line 376, in _setup
    self._wrapped = self._setupfunc()
  File "django/contrib/auth/middleware.py", line 23, in <lambda>
    request.user = SimpleLazyObject(lambda: get_user(request))
  File "django/contrib/auth/middleware.py", line 11, in get_user
    request._cached_user = auth.get_user(request)
  File "django/contrib/auth/__init__.py", line 174, in get_user
    user_id = _get_user_session_key(request)
  File "django/contrib/auth/__init__.py", line 58, in _get_user_session_key
    return get_user_model()._meta.pk.to_python(request.session[SESSION_KEY])
  File "django/contrib/sessions/backends/base.py", line 65, in __getitem__
    return self._session[key]
  File "django/contrib/sessions/backends/base.py", line 220, in _get_session
    self._session_cache = self.load()
  File "django/contrib/sessions/backends/db.py", line 43, in load
    s = self._get_session_from_db()
  File "django/contrib/sessions/backends/db.py", line 32, in _get_session_from_db
    return self.model.objects.get(
  File "django/db/models/manager.py", line 85, in manager_method
    return getattr(self.get_queryset(), name)(*args, **kwargs)
  File "django/db/models/query.py", line 425, in get
    num = len(clone)
  File "django/db/models/query.py", line 269, in __len__
    self._fetch_all()
  File "django/db/models/query.py", line 1303, in _fetch_all
    self._result_cache = list(self._iterable_class(self))
  File "django/db/models/query.py", line 53, in __iter__
    results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size)
  File "django/db/models/sql/compiler.py", line 1152, in execute_sql
    cursor = self.connection.cursor()
  File "django/utils/asyncio.py", line 24, in inner
    raise SynchronousOnlyOperation(message)