Skip to content

Commit

Permalink
Fixed #25598 -- Added SCRIPT_NAME prefix to STATIC_URL and MEDIA_URL …
Browse files Browse the repository at this point in the history
…set to relative paths.

Thanks Florian Apolloner for reviews.

Co-authored-by: Joel Dunham <Joel.Dunham@technicalsafetybc.ca>
  • Loading branch information
2 people authored and felixxm committed Sep 25, 2019
1 parent 580e644 commit c574bec
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 2 deletions.
31 changes: 30 additions & 1 deletion django/conf/__init__.py
Expand Up @@ -15,7 +15,8 @@

import django
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.functional import LazyObject, empty

Expand Down Expand Up @@ -109,6 +110,26 @@ def configure(self, default_settings=global_settings, **options):
setattr(holder, name, value)
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
def configured(self):
"""Return True if the settings have already been configured."""
Expand All @@ -128,6 +149,14 @@ def PASSWORD_RESET_TIMEOUT_DAYS(self):
)
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:
def __init__(self, settings_module):
Expand Down
14 changes: 14 additions & 0 deletions docs/ref/settings.txt
Expand Up @@ -1989,6 +1989,13 @@ Example: ``"http://media.example.com/"``
:setting:`MEDIA_URL` and :setting:`STATIC_URL` must have different
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

``MIDDLEWARE``
Expand Down Expand Up @@ -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
: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

``STATICFILES_DIRS``
Expand Down
5 changes: 5 additions & 0 deletions docs/releases/3.1.txt
Expand Up @@ -233,6 +233,11 @@ Miscellaneous
* The compatibility imports of ``Context``, ``ContextPopException``, and
``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:

Features deprecated in 3.1
Expand Down
2 changes: 1 addition & 1 deletion tests/file_storage/tests.py
Expand Up @@ -521,7 +521,7 @@ def test_setting_changed(self):
defaults_storage = self.storage_class()
settings = {
'MEDIA_ROOT': 'overridden_media_root',
'MEDIA_URL': 'overridden_media_url/',
'MEDIA_URL': '/overridden_media_url/',
'FILE_UPLOAD_PERMISSIONS': 0o333,
'FILE_UPLOAD_DIRECTORY_PERMISSIONS': 0o333,
}
Expand Down
49 changes: 49 additions & 0 deletions tests/settings_tests/tests.py
Expand Up @@ -12,6 +12,7 @@
override_settings, signals,
)
from django.test.utils import requires_tz_support
from django.urls import clear_script_prefix, set_script_prefix


@modify_settings(ITEMS={
Expand Down Expand Up @@ -567,3 +568,51 @@ def decorated_function():
signals.setting_changed.disconnect(self.receiver)
# This call shouldn't raise any errors.
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()

0 comments on commit c574bec

Please sign in to comment.