Current User

The current_user subsystem provides async-safe, request-scoped user resolution using Python’s contextvars.ContextVar.

Motivation

The standard DRF pattern for accessing the current user in nested components (serializers, signals, model methods) involves threading request or request.user through each function call. This creates coupling between the request object and business logic, complicates testing, and fails silently in async contexts when thread-local storage is used instead.

drf-commons uses a ContextVar — a value scoped to the current execution context (thread in WSGI, coroutine in ASGI) — set once per request by CurrentUserMiddleware and readable anywhere in the call stack without explicit propagation.

Public API

from drf_commons.current_user import (
    get_current_user,
    get_current_authenticated_user,
)

get_current_user

Returns the user currently stored in the context variable.

user = get_current_user()

Returns:

  • The authenticated User instance if the request user is authenticated

  • AnonymousUser if the request user is not authenticated

  • None if called outside a request context (e.g., management commands, Celery tasks)

get_current_authenticated_user

Returns the authenticated user. Raises if the user is not authenticated or if called outside a request context.

user = get_current_authenticated_user()

Use this in contexts where an authenticated user is required:

  • UserActionMixin.save() — populates created_by / updated_by

  • CurrentUserField.default — populates FK on model creation

Internal API

The following functions are internal to drf-commons and should not be called directly in application code:

from drf_commons.current_user.utils import (
    _set_current_user,
    _reset_current_user,
    _clear_current_user,
)
  • _set_current_user(user) — Sets the context variable, returns a reset token

  • _reset_current_user(token) — Resets the context variable to its previous state

  • _clear_current_user() — Sets the context variable to None

The token-based reset pattern is essential for correctness in async contexts where coroutines may share a context chain. Using tokens preserves the previous state rather than clearing it unconditionally.

Usage Outside Request Context

In management commands, Celery tasks, or other non-request contexts, the context variable is not set by middleware. You can manually set it when necessary:

from django.contrib.auth import get_user_model
from drf_commons.current_user.utils import _set_current_user, _reset_current_user

User = get_user_model()

def run_as_user(user_id: int, fn: callable):
    """Execute fn() with the context user set to the given user."""
    user = User.objects.get(pk=user_id)
    token = _set_current_user(user)
    try:
        return fn()
    finally:
        _reset_current_user(token)

This pattern is useful in Celery tasks that perform model modifications and need UserActionMixin audit trail population.

Testing with Context User

In tests, set the context user directly:

from drf_commons.current_user.utils import _set_current_user, _reset_current_user

class ArticleCreateTestCase(TestCase):
    def test_creates_with_correct_author(self):
        user = UserFactory()
        token = _set_current_user(user)
        try:
            article = Article.objects.create(title="Test", content="Body")
            assert article.created_by == user
        finally:
            _reset_current_user(token)

Or use the provided test utilities and factories from drf_commons.common_tests, which handle this automatically.