Opened 2 months ago

Last modified 2 months ago

#35615 assigned New feature

Add ability to render request.user in templates when async is used

Reported by: amirreza Owned by:
Component: Template system Version: 5.0
Severity: Normal Keywords: async, render
Cc: amirreza, Dingning, Jon Janzen, Andrew Godwin Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

during testing django's async support i got an error when trying to use the user context usually available in django's templates
since this context makes database calls using the sync methods from the ORM, using it while an ASGI is used would throw an exception.

pat who is a mod in django's discord server pointed that an auser exists that can be used but looking in the code-base i found that using this would be painful since it is a coroutine object, and django's template (as far as i know) can't await on coroutins

so i have to await on request.auser on view and pass the result as context

but since usually request.user is is used in more than one page, on a normal blog project we might be awaiting on auser in all of the views, i made some modifications to django.contrib.auth.AuthenticationMiddleware and django.utils.deprecation.MiddlewareMixin to ease the job.

i hope the PR will show fruitful for the django community.

it is worth noting that no actual logic was needed for this and this is done by pretty much moving around soe of the existing code
also the existing tests for the middleware apply to the new code by a small modification.

Change History (4)

comment:1 by Sarah Boyce, 2 months ago

Cc: Dingning Jon Janzen Andrew Godwin added

Thank you for the ticket amirreza

so i have to await on request.auser on view and pass the result as context

I can render {{ request.user }} in a template with a view such as

async def test(request):
    request.user = await request.auser()
    return render(request, "test.html")

So yes, I do need to await request.auser
I don't need to explicitly pass request to the template as context (ie I don't have to do render(request, "test.html", {"request": request})).

request.user is is used in more than one page, on a normal blog project we might be awaiting on auser in all of the views, i made some modifications to django.contrib.auth.AuthenticationMiddleware and django.utils.deprecation.MiddlewareMixin to ease the job.

I think a decision needed as to whether we should be doing this in the middleware or not. CC-ing some contributors of this space to invite their thoughts

Semi related: #35030 #35303 forum topic on async templates

comment:2 by amirreza, 2 months ago

hi, thanks for getting back to me

i feel like i should add two more things to this ticket:

1) during testing my code i found out that the test case for AuthenticationMiddleware has a problem regarding auser

the test method (TestAuthenticationMiddleware.test_auser) does not pass the asgiref.sync.iscoroutinefunction, thus when this test is ran, it only tests the middleware in sync mode,
but since the middleware acts in the same way in both ways this is not shown.
as of yet i have no fix for this

2) the code that i'm providing only awaits on auser if async is used otherwise it works as before, i will provide the code for review if it is needed:
https://github.com/amirreza8002/django/commits/auser_await/

hope this gives more insight on the matter
let me know if there is anything wronge/needed

comment:3 by Jon Janzen, 2 months ago

I think several decisions/constraints have led us here:

  1. request.auser and request.user are intentionally lazy because it triggers a DB query
  2. The Django templates system does not support async (yet), and the internals assume a non-async context
  3. render has no awareness of whether it is running in async or non-async contexts
  4. The Django ORM raises an exception if a synchronous ORM function is called from an async context

The result of all of this is that calling render from an async context results in the template being resolved in an async context (implicitly) which unfortunately means that if a synchronous DB function is called (like request.user, since it is a lazy field and resolved when requested) Django raises an exception.

Each of these individual decisions are either intentional or due to historical assumptions and all make sense in isolation. But this is an annoying usability/ergonomics problem, as this ticket highlights.

I think the immediate mitigation to this problem is to call render with sync_to_async like this:

async def view(request):
    return await sync_to_async(render)(request, "test.html")

Longer term I think (2) (and perhaps (3)) need to be revisited.

DEP0009 contemplates this:

Templating is currently entirely synchronous, and the plan is to leave it this way for this first phase. Writing an async-capable templating language may be possible, but it would be a significant amount of work, and deserves its own discussion and DEP.
It's also notable that Jinja2 already supports asynchronous functionality, so this may be another good time to look at officially recommending it for some use cases.
Given this, we will add an async wrapper to the current Django templating library and its various entry points, but still run the actual template renderer synchronously. The Jinja2 engine will be updated to use its native async mode, and documentation will be added to allow third-parties to do the same if they wish.
We will have to change the template engine signature to include a render_async method as well as a render method, with the async variant being called if it is defined and the template is going to be rendered in async mode.

The very first step would be to add render_async (or, more likely arender to match the style we've actually gone with) which is just the following:

async def arender(*args, **kwargs):
    return await sync_to_async(render)(*args, **kwargs)

The recommendation for amirreza and others that face this would then be: switch to arender instead of render in async views.

This matches the path we've gone with for the ORM by making async support "skin deep": https://github.com/django/django/pull/14843

To help users, we might want to catch SynchronousOnlyOperation in the existing render method and point them to arender, I don't have strong feelings on that though.

comment:4 by Sarah Boyce, 2 months ago

Easy pickings: unset
Has patch: unset
Keywords: async render added
Summary: making request.auser available in templates when async is usedAdd ability to render request.user in templates when async is used
Triage Stage: UnreviewedAccepted
Type: Cleanup/optimizationNew feature

Thank you Jon for the context and suggested path forward ✨

Note: See TracTickets for help on using tickets.
Back to Top