#31724 closed Bug (invalid)
django.urls.resolve ignores the FORCE_SCRIPT_NAME setting.
Reported by: | Pēteris Caune | Owned by: | nobody |
---|---|---|---|
Component: | Core (URLs) | Version: | 3.0 |
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 suspect the django.urls.resolve
function is not taking in account the FORCE_SCRIPT_NAME setting.
First, with FORCE_SCRIPT_NAME=None, I can do the following in manage.py shell
:
>>> from django.urls import resolve, reverse >>> reverse("hc-docs") '/docs/' >>> resolve(reverse("hc-docs")) ResolverMatch(func=hc.front.views.serve_doc, args=(), kwargs={}, url_name=hc-docs, app_names=[], namespaces=[], route=docs/)
In the above example, I can feed the result of reverse
back into resolve
and it works as expected.
Now with FORCE_SCRIPT_NAME='/foo':
>>> from django.urls import resolve, reverse >>> reverse("hc-docs") '/foo/docs/' >>> resolve(reverse("hc-docs")) Traceback (most recent call last): (...) raise Resolver404({'tried': tried, 'path': new_path})
Here, reverse
added the "/foo" prefix to the URL, but resolve
didn't seem to recognize it.
I suspect this is a bug but not sure. How is django.urls.resolve
expected to behave here?
Change History (5)
comment:1 by , 4 years ago
Description: | modified (diff) |
---|
comment:2 by , 4 years ago
Cc: | added |
---|---|
Resolution: | → duplicate |
Status: | new → closed |
Summary: | django.urls.resolve ignores the FORCE_SCRIPT_NAME setting → django.urls.resolve ignores the FORCE_SCRIPT_NAME setting. |
Type: | Uncategorized → Bug |
comment:3 by , 4 years ago
Resolution: | duplicate → invalid |
---|
Thank you for the CC, I disagree on the duplicate
state though and will reresolve it as invalid
.
I think that for this issue FORCE_SCRIPT_NAME
works as documented and designed. The example wrongly assumes that resolve
& reverse
are able to round-trip like that. This is simply not the case: resolve
does not operate on full URLs, but on PATH_INFO
-- https://github.com/django/django/blob/27c09043da52ca1f02605bf28600bfd5ace95ae4/django/core/handlers/base.py#L288 . So assuming your webserver is configured to serve Django under /foo/
and you access /foo/docs/
in the browser, the actual URL that Django sees to resolve the view is /docs/
. It is able to do so because the webserver did configure SCRIPT_NAME
as /foo/
effectively telling Django that only the part after it (ie PATH_INFO
) is relevant. But to reverse
a URL properly Django does need to prepend the SCRIPT_NAME
(or FORCE_SCRIPT_NAME
for that matter).
All in all FORCE_SCRIPT_NAME
serves two specific purposes:
- If your webserver configuration is broken,
FORCE_SCRIPT_NAME
can be used to correct the value ofSCRIPT_NAME
(though generally that might cause wrongPATH_INFO
as well, so I wouldn't really recommend it in this scenario and try to fix the webserver).
- If you need to generate URLs to your views outside of a request context. That is for example a cron job that runs without a WSGI environment (so
SCRIPT_NAME
is never set) and the generated URL would miss the prefix. In this case you will need to setFORCE_SCRIPT_NAME
to the actualSCRIPT_NAME
that your webserver would report, so Django knows about it in management commands etc… Under normal operations (ie when serving views) the webserver is able to provide that information on it's own.
follow-up: 5 comment:4 by , 4 years ago
Thanks for the extra detail, Florian!
Perhaps the documentation should mention that reverse
will obey FORCE_SCRIPT_NAME
but resolve
won't? They are both in the same module, they are documented on the same page and they look like inverse operations – I knew reverse
uses FORCE_SCRIPT_NAME
and that's why I assumed resolve
would as well.
My use case: I'm using resolve
for whitelisting redirect URLs. In my app, the login page can take a ?next=some-url
GET parameter, and after the user logs in, they get redirected to some-url
. I wanted to have a whitelist of what redirect targets are allowed. So I'm passing some-url
to resolve
and am looking if the ResolverMatch.url_name
is in my whitelist. Does this seem like a reasonable use case for resolve
?
And I'm using FORCE_SCRIPT_NAME
setting because indeed I need to generate URLs from outside of a request context (management commands sending emails with links in them).
comment:5 by , 4 years ago
Replying to Pēteris Caune:
Perhaps the documentation should mention that
reverse
will obeyFORCE_SCRIPT_NAME
butresolve
won't?
Documentation updates will never hurt. Do you feel up to the task? :)
My use case: I'm using
resolve
for whitelisting redirect URLs. In my app, the login page can take a?next=some-url
GET parameter, and after the user logs in, they get redirected tosome-url
. I wanted to have a whitelist of what redirect targets are allowed. So I'm passingsome-url
toresolve
and am looking if theResolverMatch.url_name
is in my whitelist. Does this seem like a reasonable use case forresolve
?
It does, but sadly is not something that is achievable easily as of now, I am sorry :( I agree that it is unfortunate that resolve
behaves like it does, but it makes a little bit more sense with the SCRIPT_NAME
/PATH_INFO
differences from above. If Django were to pass SCRIPT_NAME + PATH_INFO
to resolve
then the urlpatterns would have to include SCRIPT_NAME
and as such would no longer be portable. As far as Django is concerned it is rooted below SCRIPT_NAME
. Given how html links work the only sensible option for reverse
is to generate a path absolute URL which requires it to append the SCRIPT_NAME
. It would be even worse if Django supported hosting on multiple subdomains, then reverse
would most likely have to generate full URLs including domains ;)
As for your issue at hand, I think the easiest solution is to check if your URL starts with SCRIPT_NAME/FORCE_SCRIPT_NAME
and then cut that off.
I'm pretty sure that's a duplicate of #7930,
resolve()/reserve()
is behavior is discussed there.