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:
Oleg Kainov 2018-12-05 16:15:33 -08:00 committed by Mariusz Felisiak
parent 580e644f24
commit c574bec092
5 changed files with 99 additions and 2 deletions

View File

@ -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):

View File

@ -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``

View File

@ -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

View File

@ -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,
} }

View File

@ -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()