#35985 closed Cleanup/optimization (wontfix)
FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread
Reported by: | Pēteris Caune | Owned by: | |
---|---|---|---|
Component: | Core (URLs) | Version: | dev |
Severity: | Normal | Keywords: | |
Cc: | Florian Apolloner | Triage Stage: | Unreviewed |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
I've configured my Django project to run on a subpath (under example.org/some_prefix
instead of example.org
).
The project has a management command which generates URLs using django.urls.reverse()
.
Since the management command cannot read SCRIPT_NAME from WSGI parameters, the project has FORCE_SCRIPT_NAME = "/some_prefix"
in settings.py.
The management command generates URLs that include the prefix as expected if the code runs on main thread. But if the management command spawns a thread, the code running on thread generates URLs without the prefix.
I'm not sure but I think this is related to django.urls.base._prefixes
being a Local
object. I'm guessing it, as the name suggests, does not share data between threads. Even though set_script_prefix
is called on main thread, the other threads do not see it.
A simple workaround is for the user to call set_script_prefix
by themselves:
from django.conf import settings from django.urls import set_script_prefix def this_will_be_run_on_thread(): if settings.FORCE_SCRIPT_NAME: set_script_prefix(settings.FORCE_SCRIPT_NAME) # do work here
But perhaps there's something Django could do here as well:
- perhaps, change django.urls implementation so that threads do share the script prefix storage?
- if there are disadvantages to that, mention this gotcha in the documentation
I'm happy to provide a dummy project demonstrating the issue if that would be helpful.
Change History (5)
comment:1 by , 9 days ago
Cc: | added |
---|---|
Resolution: | → wontfix |
Status: | new → closed |
Type: | Bug → Cleanup/optimization |
Version: | 5.1 → dev |
comment:2 by , 9 days ago
Description: | modified (diff) |
---|---|
Summary: | SCRIPT_NAME / FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread → FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread |
comment:3 by , 9 days ago
Thanks for looking into it, Natalia!
I agree, this is a niche use case (in fact, two niche use cases combined – serving app on subpath, and using threads in a management command), and so perhaps not worth the effort and risk of changing django.urls.base._prefixes
.
For a workaround, Django will set the prefix when setup is called, perhaps the best option for your management command is to call setup in each thread?
I'm not sure – I'm using threads in management commands with no specific per-thead initialization currently, and the threads are able to access Django settings, models, etc. I suspect calling django.setup()
in each thread would do duplicate work and initialize stuff that is already initialized.
How about documenting set_script_prefix()
? In https://docs.djangoproject.com/en/5.1/ref/urlresolvers/#get-script-prefix there is a section for get_script_prefix
but not for set_script_prefix
. I cannot promise any quality, but I'm happy to take a first stab at it.
comment:4 by , 9 days ago
Thank you for your understanding and your willingness to help.
My understanding of the code and docs is that set_script_prefix
may be intentionally undocumented. Otherwise, Django needs to guarantee its API stability and documented behavior, and any change has to go thru the documented deprecation process. Because of this, I don't believe that documenting it would be a desirable change.
comment:5 by , 3 hours ago
Hi Pēteris, nice to see you here again :) I will go out on a limb and say that I think FORCE_SCRIPT_NAME
and everything it interacts with is broken beyond repair. I had similar problems in a project of mine and I opted for the following:
prefix = f"{settings.BASE_URL.lstrip('/')}/" if settings.BASE_URL else "" urlpatterns = [ path(f"{prefix}api/v1/", api.urls), path(f"{prefix}", include(oversight_urlpatterns)), path(f"{prefix}accounts/", include("allauth.urls")), ]
So essentially I introduced a BASE_URL
which I prepend so reverse
works fine. Given how your WSGI integration works you might run into problems though with the WSGI environment. That is usually fixable (in my case it was not a problem since I am proxying from traefik to gunicorn -- if you are using something more embedded like mod_wsgi/uwsgi then you might need some adjustments). All in all my approach has additional benefits: I can reuse the BASE_URL
for MEDIA_URL
/STATIC_URL
as well without having to worry that it breaks easily like it does with FORCE_SCRIPT_NAME
and it will also work with threads ;) If you have more questions about that approach feel free to contact me out of band or mention me in a healthchecks.io (I assume it is for said project?) issue/discussion.
Like Natalia I am kinda hesitant to document set_script_prefix
. In an ideal world I'd love Django to adopt a BASE_URL
(which actually is the whole URL and not just the path like in my project) because then Django finally knows where it is mounted and we could extend url resolving to handle subdomains etc as well. Also most of the ALLOWED_HOSTS
magic becomes moot if you just generate URLs etc using BASE_URL
without having to rely on the host header.
Regarding running django.setup()
in multiple threads: I don't think that this is something we support or test -- so I would stay far away from it.
Hello Pēteris Caune, thank you for taking the time to create this report. I have read your description carefully and I yes, I agree, the fact that
_prefixes
is local to the thread means that spawned threads will not share its contents. Also the functionset_script_prefix
has this docstring:For a workaround, Django will set the prefix when
setup
is called, perhaps the best option for your management command is to callsetup
in each thread? Feels cleaner and more correct.Regarding your comment for change django.urls implementation so that threads do share the script prefix storage, in my opinion this is beyond to what the main goal of Django is. As shown above, this is simple to add/solve to your code base, and to me this is a very specific need arising from a niche use case. I don't think this applies to the broader ecosystem, and Django is a framework designed to offer robust and accurate solutions for common scenarios.
Given the above, I'll close the ticket accordingly, but if you disagree, you can consider starting a new conversation on the Django Forum, where you'll reach a wider audience and likely get extra feedback.