Re: [Django] #35985: FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread

9 views
Skip to first unread message

Django

unread,
Dec 9, 2024, 9:56:24 AM12/9/24
to django-...@googlegroups.com
#35985: FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread
-------------------------------------+-------------------------------------
Reporter: Pēteris Caune | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: Core (URLs) | Version: dev
Severity: Normal | Resolution: wontfix
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Pēteris Caune):

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.
--
Ticket URL: <https://code.djangoproject.com/ticket/35985#comment:3>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

Django

unread,
Dec 9, 2024, 2:08:24 PM12/9/24
to django-...@googlegroups.com
#35985: FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread
-------------------------------------+-------------------------------------
Reporter: Pēteris Caune | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: Core (URLs) | Version: dev
Severity: Normal | Resolution: wontfix
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Natalia Bidart):

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.
--
Ticket URL: <https://code.djangoproject.com/ticket/35985#comment:4>

Django

unread,
Dec 18, 2024, 8:15:38 AM12/18/24
to django-...@googlegroups.com
#35985: FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread
-------------------------------------+-------------------------------------
Reporter: Pēteris Caune | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: Core (URLs) | Version: dev
Severity: Normal | Resolution: wontfix
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Florian Apolloner):

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:

{{{#!python
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.
--
Ticket URL: <https://code.djangoproject.com/ticket/35985#comment:5>

Django

unread,
Dec 19, 2024, 3:16:55 AM12/19/24
to django-...@googlegroups.com
#35985: FORCE_SCRIPT_NAME ignored when running reverse() on non-main thread
-------------------------------------+-------------------------------------
Reporter: Pēteris Caune | Owner: (none)
Type: | Status: closed
Cleanup/optimization |
Component: Core (URLs) | Version: dev
Severity: Normal | Resolution: wontfix
Keywords: | Triage Stage:
| Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Comment (by Pēteris Caune):

Hey Florian,

Thanks for the advice! I did eventually get SCRIPT_NAME/FORCE_SCRIPT_NAME
to work, but your approach looks better. It avoids the hacks I'm using –

* needing to use set_script_prefix in threads (this issue)
* chopping off the prefix before passing the URL to django.urls.resolve
(#31724)
* uwsgi magic which parses an env var, sets SCRIPT_NAME and fixes
PATH_INFO

And, as a bonus, this works even with `manage.py runserver`. So I'm
switching to it :-)
--
Ticket URL: <https://code.djangoproject.com/ticket/35985#comment:6>
Reply all
Reply to author
Forward
0 new messages