Opened 3 months ago
Last modified 3 weeks ago
#35615 assigned New feature
Add ability to render request.user in templates when async is used
Reported by: | amirreza | Owned by: | GunSliger00007 |
---|---|---|---|
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 (5)
comment:1 by , 3 months ago
Cc: | added |
---|
comment:2 by , 3 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 , 3 months ago
I think several decisions/constraints have led us here:
request.auser
andrequest.user
are intentionally lazy because it triggers a DB query- The Django templates system does not support async (yet), and the internals assume a non-
async
context render
has no awareness of whether it is running inasync
or non-async
contexts- 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 , 3 months ago
Easy pickings: | unset |
---|---|
Has patch: | unset |
Keywords: | async render added |
Summary: | making request.auser available in templates when async is used → Add ability to render request.user in templates when async is used |
Triage Stage: | Unreviewed → Accepted |
Type: | Cleanup/optimization → New feature |
Thank you Jon for the context and suggested path forward ✨
comment:5 by , 3 weeks ago
Owner: | set to |
---|
Thank you for the ticket amirreza
I can render
{{ request.user }}
in a template with a view such asSo 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})
).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