mirror of https://github.com/django/django.git
Fixed #25598 -- Added SCRIPT_NAME prefix to STATIC_URL and MEDIA_URL set to relative paths.
Thanks Florian Apolloner for reviews. Co-authored-by: Joel Dunham <Joel.Dunham@technicalsafetybc.ca>
This commit is contained in:
parent
580e644f24
commit
c574bec092
|
@ -15,7 +15,8 @@ from pathlib import Path
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.conf import global_settings
|
from django.conf import global_settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured, ValidationError
|
||||||
|
from django.core.validators import URLValidator
|
||||||
from django.utils.deprecation import RemovedInDjango40Warning
|
from django.utils.deprecation import RemovedInDjango40Warning
|
||||||
from django.utils.functional import LazyObject, empty
|
from django.utils.functional import LazyObject, empty
|
||||||
|
|
||||||
|
@ -109,6 +110,26 @@ class LazySettings(LazyObject):
|
||||||
setattr(holder, name, value)
|
setattr(holder, name, value)
|
||||||
self._wrapped = holder
|
self._wrapped = holder
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _add_script_prefix(value):
|
||||||
|
"""
|
||||||
|
Add SCRIPT_NAME prefix to relative paths.
|
||||||
|
|
||||||
|
Useful when the app is being served at a subpath and manually prefixing
|
||||||
|
subpath to STATIC_URL and MEDIA_URL in settings is inconvenient.
|
||||||
|
"""
|
||||||
|
# Don't apply prefix to valid URLs.
|
||||||
|
try:
|
||||||
|
URLValidator()(value)
|
||||||
|
return value
|
||||||
|
except (ValidationError, AttributeError):
|
||||||
|
pass
|
||||||
|
# Don't apply prefix to absolute paths.
|
||||||
|
if value.startswith('/'):
|
||||||
|
return value
|
||||||
|
from django.urls import get_script_prefix
|
||||||
|
return '%s%s' % (get_script_prefix(), value)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def configured(self):
|
def configured(self):
|
||||||
"""Return True if the settings have already been configured."""
|
"""Return True if the settings have already been configured."""
|
||||||
|
@ -128,6 +149,14 @@ class LazySettings(LazyObject):
|
||||||
)
|
)
|
||||||
return self.__getattr__('PASSWORD_RESET_TIMEOUT_DAYS')
|
return self.__getattr__('PASSWORD_RESET_TIMEOUT_DAYS')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def STATIC_URL(self):
|
||||||
|
return self._add_script_prefix(self.__getattr__('STATIC_URL'))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def MEDIA_URL(self):
|
||||||
|
return self._add_script_prefix(self.__getattr__('MEDIA_URL'))
|
||||||
|
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
def __init__(self, settings_module):
|
def __init__(self, settings_module):
|
||||||
|
|
|
@ -1989,6 +1989,13 @@ Example: ``"http://media.example.com/"``
|
||||||
:setting:`MEDIA_URL` and :setting:`STATIC_URL` must have different
|
:setting:`MEDIA_URL` and :setting:`STATIC_URL` must have different
|
||||||
values. See :setting:`MEDIA_ROOT` for more details.
|
values. See :setting:`MEDIA_ROOT` for more details.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If :setting:`MEDIA_URL` is a relative path, then it will be prefixed by the
|
||||||
|
server-provided value of ``SCRIPT_NAME`` (or ``/`` if not set). This makes
|
||||||
|
it easier to serve a Django application in a subpath without adding an
|
||||||
|
extra configuration to the settings.
|
||||||
|
|
||||||
.. setting:: MIDDLEWARE
|
.. setting:: MIDDLEWARE
|
||||||
|
|
||||||
``MIDDLEWARE``
|
``MIDDLEWARE``
|
||||||
|
@ -3306,6 +3313,13 @@ You may need to :ref:`configure these files to be served in development
|
||||||
<serving-static-files-in-development>` and will definitely need to do so
|
<serving-static-files-in-development>` and will definitely need to do so
|
||||||
:doc:`in production </howto/static-files/deployment>`.
|
:doc:`in production </howto/static-files/deployment>`.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If :setting:`STATIC_URL` is a relative path, then it will be prefixed by
|
||||||
|
the server-provided value of ``SCRIPT_NAME`` (or ``/`` if not set). This
|
||||||
|
makes it easier to serve a Django application in a subpath without adding
|
||||||
|
an extra configuration to the settings.
|
||||||
|
|
||||||
.. setting:: STATICFILES_DIRS
|
.. setting:: STATICFILES_DIRS
|
||||||
|
|
||||||
``STATICFILES_DIRS``
|
``STATICFILES_DIRS``
|
||||||
|
|
|
@ -233,6 +233,11 @@ Miscellaneous
|
||||||
* The compatibility imports of ``Context``, ``ContextPopException``, and
|
* The compatibility imports of ``Context``, ``ContextPopException``, and
|
||||||
``RequestContext`` in ``django.template.base`` are removed.
|
``RequestContext`` in ``django.template.base`` are removed.
|
||||||
|
|
||||||
|
* The :setting:`STATIC_URL` and :setting:`MEDIA_URL` settings set to relative
|
||||||
|
paths are now prefixed by the server-provided value of ``SCRIPT_NAME`` (or
|
||||||
|
``/`` if not set). This change should not affect settings set to valid URLs
|
||||||
|
or absolute paths.
|
||||||
|
|
||||||
.. _deprecated-features-3.1:
|
.. _deprecated-features-3.1:
|
||||||
|
|
||||||
Features deprecated in 3.1
|
Features deprecated in 3.1
|
||||||
|
|
|
@ -521,7 +521,7 @@ class FileStorageTests(SimpleTestCase):
|
||||||
defaults_storage = self.storage_class()
|
defaults_storage = self.storage_class()
|
||||||
settings = {
|
settings = {
|
||||||
'MEDIA_ROOT': 'overridden_media_root',
|
'MEDIA_ROOT': 'overridden_media_root',
|
||||||
'MEDIA_URL': 'overridden_media_url/',
|
'MEDIA_URL': '/overridden_media_url/',
|
||||||
'FILE_UPLOAD_PERMISSIONS': 0o333,
|
'FILE_UPLOAD_PERMISSIONS': 0o333,
|
||||||
'FILE_UPLOAD_DIRECTORY_PERMISSIONS': 0o333,
|
'FILE_UPLOAD_DIRECTORY_PERMISSIONS': 0o333,
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ from django.test import (
|
||||||
override_settings, signals,
|
override_settings, signals,
|
||||||
)
|
)
|
||||||
from django.test.utils import requires_tz_support
|
from django.test.utils import requires_tz_support
|
||||||
|
from django.urls import clear_script_prefix, set_script_prefix
|
||||||
|
|
||||||
|
|
||||||
@modify_settings(ITEMS={
|
@modify_settings(ITEMS={
|
||||||
|
@ -567,3 +568,51 @@ class OverrideSettingsIsolationOnExceptionTests(SimpleTestCase):
|
||||||
signals.setting_changed.disconnect(self.receiver)
|
signals.setting_changed.disconnect(self.receiver)
|
||||||
# This call shouldn't raise any errors.
|
# This call shouldn't raise any errors.
|
||||||
decorated_function()
|
decorated_function()
|
||||||
|
|
||||||
|
|
||||||
|
class MediaURLStaticURLPrefixTest(SimpleTestCase):
|
||||||
|
def set_script_name(self, val):
|
||||||
|
clear_script_prefix()
|
||||||
|
if val is not None:
|
||||||
|
set_script_prefix(val)
|
||||||
|
|
||||||
|
def test_not_prefixed(self):
|
||||||
|
# Don't add SCRIPT_NAME prefix to valid URLs, absolute paths or None.
|
||||||
|
tests = (
|
||||||
|
'/path/',
|
||||||
|
'http://myhost.com/path/',
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
for setting in ('MEDIA_URL', 'STATIC_URL'):
|
||||||
|
for path in tests:
|
||||||
|
new_settings = {setting: path}
|
||||||
|
with self.settings(**new_settings):
|
||||||
|
for script_name in ['/somesubpath', '/somesubpath/', '/', '', None]:
|
||||||
|
with self.subTest(script_name=script_name, **new_settings):
|
||||||
|
try:
|
||||||
|
self.set_script_name(script_name)
|
||||||
|
self.assertEqual(getattr(settings, setting), path)
|
||||||
|
finally:
|
||||||
|
clear_script_prefix()
|
||||||
|
|
||||||
|
def test_add_script_name_prefix(self):
|
||||||
|
tests = (
|
||||||
|
# Relative paths.
|
||||||
|
('/somesubpath', 'path/', '/somesubpath/path/'),
|
||||||
|
('/somesubpath/', 'path/', '/somesubpath/path/'),
|
||||||
|
('/', 'path/', '/path/'),
|
||||||
|
# Invalid URLs.
|
||||||
|
('/somesubpath/', 'htp://myhost.com/path/', '/somesubpath/htp://myhost.com/path/'),
|
||||||
|
# Blank settings.
|
||||||
|
('/somesubpath/', '', '/somesubpath/'),
|
||||||
|
)
|
||||||
|
for setting in ('MEDIA_URL', 'STATIC_URL'):
|
||||||
|
for script_name, path, expected_path in tests:
|
||||||
|
new_settings = {setting: path}
|
||||||
|
with self.settings(**new_settings):
|
||||||
|
with self.subTest(script_name=script_name, **new_settings):
|
||||||
|
try:
|
||||||
|
self.set_script_name(script_name)
|
||||||
|
self.assertEqual(getattr(settings, setting), expected_path)
|
||||||
|
finally:
|
||||||
|
clear_script_prefix()
|
||||||
|
|
Loading…
Reference in New Issue