summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleg Kainov <kainov.oleg@gmail.com>2018-12-05 16:15:33 -0800
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-09-25 19:47:03 +0200
commitc574bec0929cd2527268c96a492d25223a9fd576 (patch)
tree80c642eca0ca64d63d1596bc237465685c92ce5c
parent580e644f24f1c5ae5b94784fb73a9953a178fd26 (diff)
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>
-rw-r--r--django/conf/__init__.py31
-rw-r--r--docs/ref/settings.txt14
-rw-r--r--docs/releases/3.1.txt5
-rw-r--r--tests/file_storage/tests.py2
-rw-r--r--tests/settings_tests/tests.py49
5 files changed, 99 insertions, 2 deletions
diff --git a/django/conf/__init__.py b/django/conf/__init__.py
index b32e56184d..ec7efadf46 100644
--- a/django/conf/__init__.py
+++ b/django/conf/__init__.py
@@ -15,7 +15,8 @@ from pathlib import Path
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
@@ -109,6 +110,26 @@ class LazySettings(LazyObject):
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."""
@@ -128,6 +149,14 @@ class LazySettings(LazyObject):
)
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):
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 00538ccbf7..4405d152b2 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -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``
@@ -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``
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index 3a9381626b..da954bab56 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -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
diff --git a/tests/file_storage/tests.py b/tests/file_storage/tests.py
index 1c4176014c..1f7fd94b5a 100644
--- a/tests/file_storage/tests.py
+++ b/tests/file_storage/tests.py
@@ -521,7 +521,7 @@ class FileStorageTests(SimpleTestCase):
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,
}
diff --git a/tests/settings_tests/tests.py b/tests/settings_tests/tests.py
index d663f6e819..55ca0de524 100644
--- a/tests/settings_tests/tests.py
+++ b/tests/settings_tests/tests.py
@@ -12,6 +12,7 @@ from django.test import (
override_settings, signals,
)
from django.test.utils import requires_tz_support
+from django.urls import clear_script_prefix, set_script_prefix
@modify_settings(ITEMS={
@@ -567,3 +568,51 @@ class OverrideSettingsIsolationOnExceptionTests(SimpleTestCase):
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()