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)