chore: remove deprecated DEFAULT_FILE_STORAGE and STATICFILES_STORAGE settings (#37002)
This commit is contained in:
committed by
GitHub
parent
06b54e79f2
commit
42afa1bb62
@@ -13,6 +13,7 @@ import ddt
|
||||
from django.conf import settings
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from django.core.files.storage import storages
|
||||
|
||||
from xmodule.contentstore.django import contentstore
|
||||
from xmodule.exceptions import NotFoundError
|
||||
@@ -281,19 +282,24 @@ class ContentStoreImportTest(ModuleStoreTestCase):
|
||||
|
||||
@override_settings(
|
||||
COURSE_IMPORT_EXPORT_STORAGE="cms.djangoapps.contentstore.storage.ImportExportS3Storage",
|
||||
DEFAULT_FILE_STORAGE="django.core.files.storage.FileSystemStorage"
|
||||
STORAGES={
|
||||
'default': {
|
||||
'BACKEND': "django.core.files.storage.FileSystemStorage"
|
||||
}
|
||||
}
|
||||
)
|
||||
def test_resolve_default_storage(self):
|
||||
def test_default_storage(self):
|
||||
""" Ensure the default storage is invoked, even if course export storage is configured """
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="default",
|
||||
legacy_setting_key="DEFAULT_FILE_STORAGE"
|
||||
)
|
||||
storage = storages["default"]
|
||||
self.assertEqual(storage.__class__.__name__, "FileSystemStorage")
|
||||
|
||||
@override_settings(
|
||||
COURSE_IMPORT_EXPORT_STORAGE="cms.djangoapps.contentstore.storage.ImportExportS3Storage",
|
||||
DEFAULT_FILE_STORAGE="django.core.files.storage.FileSystemStorage",
|
||||
STORAGES={
|
||||
'default': {
|
||||
'BACKEND': "django.core.files.storage.FileSystemStorage"
|
||||
}
|
||||
},
|
||||
COURSE_IMPORT_EXPORT_BUCKET="bucket_name_test"
|
||||
)
|
||||
def test_resolve_happy_path_storage(self):
|
||||
|
||||
@@ -12,6 +12,7 @@ from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
|
||||
from common.djangoapps.util.storage import resolve_storage_backend
|
||||
from storages.backends.s3boto3 import S3Boto3Storage
|
||||
from django.core.files.storage import storages
|
||||
|
||||
from .signals import export_course_metadata
|
||||
from .toggles import EXPORT_COURSE_METADATA_FLAG
|
||||
@@ -60,16 +61,24 @@ class TestExportCourseMetadata(SharedModuleStoreTestCase):
|
||||
|
||||
@override_settings(
|
||||
COURSE_METADATA_EXPORT_STORAGE="cms.djangoapps.export_course_metadata.storage.CourseMetadataExportS3Storage",
|
||||
DEFAULT_FILE_STORAGE="django.core.files.storage.FileSystemStorage"
|
||||
STORAGES={
|
||||
'default': {
|
||||
'BACKEND': "django.core.files.storage.FileSystemStorage"
|
||||
}
|
||||
}
|
||||
)
|
||||
def test_resolve_default_storage(self):
|
||||
""" Ensure the default storage is invoked, even if course export storage is configured """
|
||||
storage = resolve_storage_backend(storage_key="default", legacy_setting_key="default")
|
||||
storage = storages["default"]
|
||||
self.assertEqual(storage.__class__.__name__, "FileSystemStorage")
|
||||
|
||||
@override_settings(
|
||||
COURSE_METADATA_EXPORT_STORAGE="cms.djangoapps.export_course_metadata.storage.CourseMetadataExportS3Storage",
|
||||
DEFAULT_FILE_STORAGE="django.core.files.storage.FileSystemStorage",
|
||||
STORAGES={
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage"
|
||||
}
|
||||
},
|
||||
COURSE_METADATA_EXPORT_BUCKET="bucket_name_test"
|
||||
)
|
||||
def test_resolve_happy_path_storage(self):
|
||||
|
||||
@@ -1157,7 +1157,6 @@ PIPELINE = {
|
||||
'YUI_BINARY': 'yui-compressor',
|
||||
}
|
||||
|
||||
STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage'
|
||||
STATICFILES_STORAGE_KWARGS = {}
|
||||
|
||||
# List of finder classes that know how to find static files in various locations.
|
||||
@@ -2386,7 +2385,15 @@ BULK_EMAIL_DEFAULT_FROM_EMAIL = 'no-reply@example.com'
|
||||
BULK_EMAIL_LOG_SENT_EMAILS = False
|
||||
|
||||
############### Settings for django file storage ##################
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
STORAGES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.files.storage.FileSystemStorage'
|
||||
},
|
||||
'staticfiles': {
|
||||
'BACKEND': 'openedx.core.storage.ProductionStorage'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
###################### Grade Downloads ######################
|
||||
# These keys are used for all of our asynchronous downloadable files, including
|
||||
|
||||
@@ -9,7 +9,7 @@ from os.path import abspath, dirname, join
|
||||
from .production import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
# Don't use S3 in devstack, fall back to filesystem
|
||||
del DEFAULT_FILE_STORAGE
|
||||
STORAGES['default']['BACKEND'] = 'django.core.files.storage.FileSystemStorage'
|
||||
COURSE_IMPORT_EXPORT_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
USER_TASKS_ARTIFACT_STORAGE = COURSE_IMPORT_EXPORT_STORAGE
|
||||
|
||||
@@ -56,7 +56,7 @@ FEATURES['ENABLE_VIDEO_UPLOAD_PIPELINE'] = True
|
||||
|
||||
# Skip packaging and optimization in development
|
||||
PIPELINE['PIPELINE_ENABLED'] = False
|
||||
STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage'
|
||||
STORAGES['staticfiles']['BACKEND'] = 'openedx.core.storage.DevelopmentStorage'
|
||||
|
||||
# Revert to the default set of finders as we don't want the production pipeline
|
||||
STATICFILES_FINDERS = [
|
||||
|
||||
@@ -291,7 +291,11 @@ DATABASES:
|
||||
USER: user
|
||||
DATA_DIR: /edx/var/edxapp
|
||||
DEFAULT_FEEDBACK_EMAIL: feedback@example.com
|
||||
DEFAULT_FILE_STORAGE: storages.backends.s3boto3.S3Boto3Storage
|
||||
STORAGES:
|
||||
default:
|
||||
BACKEND: storages.backends.s3boto3.S3Boto3Storage
|
||||
staticfiles:
|
||||
BACKEND: openedx.core.storage.ProductionStorage
|
||||
DEFAULT_FROM_EMAIL: no-reply@registration.localhost
|
||||
DEFAULT_HASHING_ALGORITHM: sha256
|
||||
DEFAULT_JWT_ISSUER:
|
||||
|
||||
@@ -82,10 +82,11 @@ with codecs.open(CONFIG_FILE, encoding='utf-8') as f:
|
||||
'MKTG_URL_LINK_MAP',
|
||||
'REST_FRAMEWORK',
|
||||
'EVENT_BUS_PRODUCER_CONFIG',
|
||||
'DEFAULT_FILE_STORAGE',
|
||||
'STATICFILES_STORAGE',
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
#######################################################################################################################
|
||||
#### LOAD THE EDX-PLATFORM GIT REVISION
|
||||
####
|
||||
@@ -150,11 +151,6 @@ if 'loc_cache' not in CACHES:
|
||||
if 'staticfiles' in CACHES:
|
||||
CACHES['staticfiles']['KEY_PREFIX'] = EDX_PLATFORM_REVISION
|
||||
|
||||
# In order to transition from local disk asset storage to S3 backed asset storage,
|
||||
# we need to run asset collection twice, once for local disk and once for S3.
|
||||
# Once we have migrated to service assets off S3, then we can convert this back to
|
||||
# managed by the yaml file contents
|
||||
STATICFILES_STORAGE = os.environ.get('STATICFILES_STORAGE', STATICFILES_STORAGE)
|
||||
|
||||
MKTG_URL_LINK_MAP.update(_YAML_TOKENS.get('MKTG_URL_LINK_MAP', {}))
|
||||
|
||||
@@ -194,21 +190,38 @@ AWS_BUCKET_ACL = AWS_DEFAULT_ACL
|
||||
# The number of seconds that a generated URL is valid for.
|
||||
AWS_QUERYSTRING_EXPIRE = 7 * 24 * 60 * 60 # 7 days
|
||||
|
||||
# Change to S3Boto3 if we haven't specified another default storage AND we have specified AWS creds.
|
||||
if (not _YAML_TOKENS.get('DEFAULT_FILE_STORAGE')) and AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
_yaml_storages = _YAML_TOKENS.get('STORAGES', {})
|
||||
|
||||
_storages_default_backend_is_missing = not _yaml_storages.get('default', {}).get('BACKEND')
|
||||
|
||||
# For backward compatibility, if YAML provides legacy keys (DEFAULT_FILE_STORAGE, STATICFILES_STORAGE)
|
||||
# and STORAGES doesn’t explicitly define the corresponding backend, migrate the legacy value into STORAGES.
|
||||
# If YAML doesn't provide lagacy keys, no backend is defined in STORAGES['default'] and AWS creds are present,
|
||||
# fall back to S3Boto3Storage.
|
||||
#
|
||||
# This ensures YAML-provided values take precedence over defaults from common.py,
|
||||
# without overwriting user-defined STORAGES and AWS creds are treated only as a fallback.
|
||||
if _storages_default_backend_is_missing:
|
||||
if 'DEFAULT_FILE_STORAGE' in _YAML_TOKENS:
|
||||
STORAGES['default']['BACKEND'] = _YAML_TOKENS['DEFAULT_FILE_STORAGE']
|
||||
elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
||||
STORAGES['default']['BACKEND'] = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
|
||||
# Apply legacy STATICFILES_STORAGE if no backend is defined for "staticfiles"
|
||||
if 'STATICFILES_STORAGE' in _YAML_TOKENS and not _yaml_storages.get('staticfiles', {}).get('BACKEND'):
|
||||
STORAGES['staticfiles']['BACKEND'] = _YAML_TOKENS['STATICFILES_STORAGE']
|
||||
|
||||
if COURSE_IMPORT_EXPORT_BUCKET:
|
||||
COURSE_IMPORT_EXPORT_STORAGE = 'cms.djangoapps.contentstore.storage.ImportExportS3Storage'
|
||||
else:
|
||||
COURSE_IMPORT_EXPORT_STORAGE = DEFAULT_FILE_STORAGE
|
||||
COURSE_IMPORT_EXPORT_STORAGE = STORAGES['default']['BACKEND']
|
||||
|
||||
USER_TASKS_ARTIFACT_STORAGE = COURSE_IMPORT_EXPORT_STORAGE
|
||||
|
||||
if COURSE_METADATA_EXPORT_BUCKET:
|
||||
COURSE_METADATA_EXPORT_STORAGE = 'cms.djangoapps.export_course_metadata.storage.CourseMetadataExportS3Storage'
|
||||
else:
|
||||
COURSE_METADATA_EXPORT_STORAGE = DEFAULT_FILE_STORAGE
|
||||
COURSE_METADATA_EXPORT_STORAGE = STORAGES['default']['BACKEND']
|
||||
|
||||
# The normal database user does not have enough permissions to run migrations.
|
||||
# Migrations are run with separate credentials, given as DB_MIGRATION_*
|
||||
|
||||
@@ -29,7 +29,6 @@ from .common import *
|
||||
from lms.envs.test import ( # pylint: disable=wrong-import-order, disable=unused-import
|
||||
ACCOUNT_MICROFRONTEND_URL,
|
||||
COMPREHENSIVE_THEME_DIRS, # unimport:skip
|
||||
DEFAULT_FILE_STORAGE,
|
||||
ECOMMERCE_API_URL,
|
||||
ENABLE_COMPREHENSIVE_THEMING,
|
||||
JWT_AUTH,
|
||||
@@ -91,7 +90,7 @@ STATICFILES_DIRS += [
|
||||
# If we don't add these settings, then Django templates that can't
|
||||
# find pipelined assets will raise a ValueError.
|
||||
# http://stackoverflow.com/questions/12816941/unit-testing-with-django-pipeline
|
||||
STATICFILES_STORAGE = "pipeline.storage.NonPackagingPipelineStorage"
|
||||
STORAGES['staticfiles']['BACKEND'] = "pipeline.storage.NonPackagingPipelineStorage"
|
||||
STATIC_URL = "/static/"
|
||||
|
||||
# Update module store settings per defaults for tests
|
||||
|
||||
@@ -78,7 +78,7 @@ def store_uploaded_file(
|
||||
file_storage = DefaultStorage()
|
||||
# If a file already exists with the supplied name, file_storage will make the filename unique.
|
||||
stored_file_name = file_storage.save(stored_file_name, uploaded_file)
|
||||
if is_private and settings.DEFAULT_FILE_STORAGE == 'storages.backends.s3boto3.S3Boto3Storage':
|
||||
if is_private and settings.STORAGES['default']['BACKEND'] == 'storages.backends.s3boto3.S3Boto3Storage':
|
||||
S3Boto3Storage().connection.meta.client.put_object_acl(
|
||||
ACL='private',
|
||||
Bucket=settings.AWS_STORAGE_BUCKET_NAME,
|
||||
|
||||
@@ -38,9 +38,7 @@ def resolve_storage_backend(
|
||||
|
||||
storage_path = getattr(settings, legacy_setting_key, None)
|
||||
storages_config = getattr(settings, 'STORAGES', {})
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
options = options or {}
|
||||
|
||||
if storage_key in storages_config:
|
||||
# Use case 1: STORAGES is defined
|
||||
@@ -70,5 +68,5 @@ def resolve_storage_backend(
|
||||
break
|
||||
storage_path = storage_path.get(deep_setting_key)
|
||||
|
||||
StorageClass = import_string(storage_path or settings.DEFAULT_FILE_STORAGE)
|
||||
StorageClass = import_string(storage_path or storages_config["default"]["BACKEND"])
|
||||
return StorageClass(**options)
|
||||
|
||||
@@ -4,6 +4,7 @@ Tests for the resolve_storage_backend function in common.djangoapps.util.storage
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from common.djangoapps.util.storage import resolve_storage_backend
|
||||
|
||||
@@ -20,6 +21,7 @@ class ResolveStorageTest(TestCase):
|
||||
BLOCK_STRUCTURES_SETTINGS="cms.djangoapps.contentstore.storage.ImportExportS3Storage"
|
||||
)
|
||||
def test_legacy_settings(self):
|
||||
"""Test legacy string-based storage settings."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="block_structures_settings",
|
||||
legacy_setting_key="BLOCK_STRUCTURES_SETTINGS",
|
||||
@@ -33,6 +35,7 @@ class ResolveStorageTest(TestCase):
|
||||
}
|
||||
)
|
||||
def test_nested_legacy_settings(self):
|
||||
"""Test legacy nested dictionary."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="block_structures_settings",
|
||||
legacy_setting_key="BLOCK_STRUCTURES_SETTINGS",
|
||||
@@ -47,6 +50,7 @@ class ResolveStorageTest(TestCase):
|
||||
}
|
||||
)
|
||||
def test_nested_legacy_settings_failed(self):
|
||||
"""Test legacy nested dictionary settings with missing key falls back to default."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="block_structures_settings",
|
||||
legacy_setting_key="BLOCK_STRUCTURES_SETTINGS",
|
||||
@@ -54,3 +58,75 @@ class ResolveStorageTest(TestCase):
|
||||
options={}
|
||||
)
|
||||
assert storage.__class__.__name__ == DEFAULT_STORAGE_CLASS_NAME
|
||||
|
||||
@override_settings(
|
||||
STORAGES={
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
"OPTIONS": {}
|
||||
}
|
||||
},
|
||||
LEGACY_SETTING="cms.djangoapps.contentstore.storage.ImportExportS3Storage"
|
||||
)
|
||||
def test_missing_storage_key_fallback_to_legacy(self):
|
||||
"""Test fallback to legacy settings when storage key not found in STORAGES."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="nonexistent_storage",
|
||||
legacy_setting_key="LEGACY_SETTING",
|
||||
options={}
|
||||
)
|
||||
assert storage.__class__.__name__ == "ImportExportS3Storage"
|
||||
|
||||
def test_no_storages_no_legacy_setting(self):
|
||||
"""Test fallback to default storage when neither STORAGES nor legacy setting exists."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="nonexistent_storage",
|
||||
legacy_setting_key="NONEXISTENT_LEGACY_SETTING",
|
||||
options={}
|
||||
)
|
||||
assert storage.__class__.__name__ == DEFAULT_STORAGE_CLASS_NAME
|
||||
|
||||
@override_settings(
|
||||
STORAGES={
|
||||
"default": {
|
||||
"BACKEND": "cms.djangoapps.contentstore.storage.ImportExportS3Storage",
|
||||
"OPTIONS": {}
|
||||
}
|
||||
}
|
||||
)
|
||||
def test_fallback_to_custom_default_backend(self):
|
||||
"""Test fallback uses custom default backend from STORAGES config."""
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="nonexistent_storage",
|
||||
legacy_setting_key="NONEXISTENT_LEGACY_SETTING",
|
||||
options={}
|
||||
)
|
||||
assert storage.__class__.__name__ == "ImportExportS3Storage"
|
||||
|
||||
@override_settings(
|
||||
STORAGES={
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
"OPTIONS": {}
|
||||
},
|
||||
"custom_storage_key": {
|
||||
"BACKEND": "cms.djangoapps.contentstore.storage.ImportExportS3Storage",
|
||||
"OPTIONS": {}
|
||||
}
|
||||
}
|
||||
)
|
||||
@patch('common.djangoapps.util.storage.storages')
|
||||
def test_modern_storages_config(self, mock_storages):
|
||||
"""Test modern Django STORAGES configuration that takes precedence."""
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_storage_instance.__class__.__name__ = "ImportExportS3Storage"
|
||||
mock_storages.__getitem__.return_value = mock_storage_instance
|
||||
|
||||
storage = resolve_storage_backend(
|
||||
storage_key="custom_storage_key",
|
||||
legacy_setting_key="SOME_LEGACY_SETTING",
|
||||
options={}
|
||||
)
|
||||
|
||||
mock_storages.__getitem__.assert_called_once_with("custom_storage_key")
|
||||
assert storage.__class__.__name__ == "ImportExportS3Storage"
|
||||
|
||||
@@ -272,12 +272,12 @@ class DjangoStorageReportStore(ReportStore):
|
||||
@classmethod
|
||||
def from_config(cls, config_name):
|
||||
"""
|
||||
By default, the default file storage specified by the `DEFAULT_FILE_STORAGE`
|
||||
By default, the default file storage specified by the `STORAGES['default']`
|
||||
setting will be used. To configure the storage used, add a dict in
|
||||
settings with the following fields::
|
||||
|
||||
STORAGE_CLASS : The import path of the storage class to use. If
|
||||
not set, the DEFAULT_FILE_STORAGE setting will be used.
|
||||
not set, the STORAGES['default']['BACKEND'] setting will be used.
|
||||
STORAGE_KWARGS : An optional dict of kwargs to pass to the storage
|
||||
constructor. This can be used to specify a
|
||||
different S3 bucket or root path, for example.
|
||||
|
||||
@@ -2182,7 +2182,6 @@ PIPELINE = {
|
||||
'UGLIFYJS_BINARY': 'node_modules/.bin/uglifyjs',
|
||||
}
|
||||
|
||||
STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage'
|
||||
STATICFILES_STORAGE_KWARGS = {}
|
||||
|
||||
# List of finder classes that know how to find static files in various locations.
|
||||
@@ -4609,7 +4608,14 @@ VIDEO_UPLOAD_PIPELINE = {
|
||||
}
|
||||
|
||||
############### Settings for django file storage ##################
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
STORAGES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.files.storage.FileSystemStorage'
|
||||
},
|
||||
'staticfiles': {
|
||||
'BACKEND': 'openedx.core.storage.ProductionStorage'
|
||||
}
|
||||
}
|
||||
|
||||
### Proctoring configuration (redirct URLs and keys shared between systems) ####
|
||||
PROCTORING_BACKENDS = {
|
||||
|
||||
@@ -17,7 +17,7 @@ from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType
|
||||
from .production import * # pylint: disable=wildcard-import, unused-wildcard-import
|
||||
|
||||
# Don't use S3 in devstack, fall back to filesystem
|
||||
del DEFAULT_FILE_STORAGE
|
||||
STORAGES['default']['BACKEND'] = 'django.core.files.storage.FileSystemStorage'
|
||||
ORA2_FILEUPLOAD_BACKEND = 'django'
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing
|
||||
########################### PIPELINE #################################
|
||||
|
||||
PIPELINE['PIPELINE_ENABLED'] = False
|
||||
STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage'
|
||||
STORAGES['staticfiles']['BACKEND'] = 'openedx.core.storage.DevelopmentStorage'
|
||||
|
||||
# Revert to the default set of finders as we don't want the production pipeline
|
||||
STATICFILES_FINDERS = [
|
||||
|
||||
@@ -376,7 +376,11 @@ DATABASES:
|
||||
DATA_DIR: /edx/var/edxapp
|
||||
DEFAULT_COURSE_VISIBILITY_IN_CATALOG: both
|
||||
DEFAULT_FEEDBACK_EMAIL: feedback@example.com
|
||||
DEFAULT_FILE_STORAGE: storages.backends.s3boto3.S3Boto3Storage
|
||||
STORAGES:
|
||||
default:
|
||||
BACKEND: storages.backends.s3boto3.S3Boto3Storage
|
||||
staticfiles:
|
||||
BACKEND: openedx.core.storage.ProductionStorage
|
||||
DEFAULT_FROM_EMAIL: sandbox-notifications@example.com
|
||||
DEFAULT_HASHING_ALGORITHM: sha256
|
||||
DEFAULT_JWT_ISSUER:
|
||||
|
||||
@@ -77,10 +77,11 @@ with codecs.open(CONFIG_FILE, encoding='utf-8') as f:
|
||||
'MKTG_URL_LINK_MAP',
|
||||
'REST_FRAMEWORK',
|
||||
'EVENT_BUS_PRODUCER_CONFIG',
|
||||
'DEFAULT_FILE_STORAGE',
|
||||
'STATICFILES_STORAGE',
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
#######################################################################################################################
|
||||
#### LOAD THE EDX-PLATFORM GIT REVISION
|
||||
####
|
||||
@@ -218,9 +219,26 @@ if AWS_SECRET_ACCESS_KEY == "":
|
||||
AWS_DEFAULT_ACL = 'public-read'
|
||||
AWS_BUCKET_ACL = AWS_DEFAULT_ACL
|
||||
|
||||
# Change to S3Boto3 if we haven't specified another default storage AND we have specified AWS creds.
|
||||
if (not _YAML_TOKENS.get('DEFAULT_FILE_STORAGE')) and AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
||||
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
_yaml_storages = _YAML_TOKENS.get('STORAGES', {})
|
||||
|
||||
_storages_default_backend_is_missing = not _yaml_storages.get('default', {}).get('BACKEND')
|
||||
|
||||
# For backward compatibility, if YAML provides legacy keys (DEFAULT_FILE_STORAGE, STATICFILES_STORAGE)
|
||||
# and STORAGES doesn’t explicitly define the corresponding backend, migrate the legacy value into STORAGES.
|
||||
# If YAML doesn't provide lagacy keys, no backend is defined in STORAGES['default'] and AWS creds are present,
|
||||
# fall back to S3Boto3Storage.
|
||||
#
|
||||
# This ensures YAML-provided values take precedence over defaults from common.py,
|
||||
# without overwriting user-defined STORAGES and AWS creds are treated only as a fallback.
|
||||
if _storages_default_backend_is_missing:
|
||||
if 'DEFAULT_FILE_STORAGE' in _YAML_TOKENS:
|
||||
STORAGES['default']['BACKEND'] = _YAML_TOKENS['DEFAULT_FILE_STORAGE']
|
||||
elif AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY:
|
||||
STORAGES['default']['BACKEND'] = 'storages.backends.s3boto3.S3Boto3Storage'
|
||||
|
||||
# Apply legacy STATICFILES_STORAGE if no backend is defined for "staticfiles"
|
||||
if 'STATICFILES_STORAGE' in _YAML_TOKENS and not _yaml_storages.get('staticfiles', {}).get('BACKEND'):
|
||||
STORAGES['staticfiles']['BACKEND'] = _YAML_TOKENS['STATICFILES_STORAGE']
|
||||
|
||||
# The normal database user does not have enough permissions to run migrations.
|
||||
# Migrations are run with separate credentials, given as DB_MIGRATION_*
|
||||
|
||||
@@ -151,7 +151,7 @@ STATICFILES_DIRS += [
|
||||
# If we don't add these settings, then Django templates that can't
|
||||
# find pipelined assets will raise a ValueError.
|
||||
# http://stackoverflow.com/questions/12816941/unit-testing-with-django-pipeline
|
||||
STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage'
|
||||
STORAGES['staticfiles']['BACKEND'] = 'pipeline.storage.NonPackagingPipelineStorage'
|
||||
|
||||
# Don't use compression during tests
|
||||
PIPELINE['JS_COMPRESSOR'] = None
|
||||
@@ -298,7 +298,7 @@ ENTERPRISE_MARKETING_FOOTER_QUERY_PARAMS = OrderedDict([
|
||||
])
|
||||
|
||||
############################ STATIC FILES #############################
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
STORAGES['default']['BACKEND'] = 'django.core.files.storage.FileSystemStorage'
|
||||
MEDIA_ROOT = TEST_ROOT / "uploads"
|
||||
MEDIA_URL = "/uploads/"
|
||||
STATICFILES_DIRS.append(("uploads", MEDIA_ROOT))
|
||||
|
||||
@@ -100,7 +100,13 @@ class TestThemingViews(TestCase):
|
||||
assert response.status_code == 302
|
||||
assert response.url == "/static/images/logo.png"
|
||||
|
||||
@override_settings(STATICFILES_STORAGE="openedx.core.storage.DevelopmentStorage")
|
||||
@override_settings(
|
||||
STORAGES={
|
||||
'staticfiles': {
|
||||
'BACKEND': 'openedx.core.storage.DevelopmentStorage'
|
||||
}
|
||||
}
|
||||
)
|
||||
def test_asset_with_theme(self):
|
||||
"""
|
||||
Fetch theme asset when a theme is set.
|
||||
|
||||
@@ -1231,7 +1231,6 @@ class TestAccountsAPI(FilteredQueryCountMixin, CacheIsolationTestCase, UserAPITe
|
||||
)
|
||||
def test_profile_backend_with_default_hardcoded_backend(self):
|
||||
""" In case of empty storages scenario uses the hardcoded backend."""
|
||||
del settings.DEFAULT_FILE_STORAGE
|
||||
del settings.STORAGES
|
||||
storage = get_profile_image_storage()
|
||||
self.assertIsInstance(storage, FileSystemStorage)
|
||||
|
||||
@@ -54,7 +54,7 @@ class ProductionMixin(
|
||||
We use this version on production.
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.update(settings.STATICFILES_STORAGE_KWARGS.get(settings.STATICFILES_STORAGE, {}))
|
||||
kwargs.update(settings.STATICFILES_STORAGE_KWARGS.get(settings.STORAGES['staticfiles']['BACKEND'], {}))
|
||||
super().__init__(*args, **kwargs) # lint-amnesty, pylint: disable=super-with-arguments
|
||||
|
||||
|
||||
@@ -112,5 +112,5 @@ def get_storage(storage_class=None, **kwargs):
|
||||
the storage implementation makes http requests when instantiated, for
|
||||
example.
|
||||
"""
|
||||
storage_cls = import_string(storage_class or settings.DEFAULT_FILE_STORAGE)
|
||||
storage_cls = import_string(storage_class or settings.STORAGES["default"]["BACKEND"])
|
||||
return storage_cls(**kwargs)
|
||||
|
||||
54
openedx/core/tests/test_storage.py
Normal file
54
openedx/core/tests/test_storage.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""
|
||||
Tests for the get_storage utility function.
|
||||
"""
|
||||
|
||||
from django.test import TestCase, override_settings
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
|
||||
from openedx.core.storage import get_storage
|
||||
|
||||
|
||||
class TestGetStorage(TestCase):
|
||||
"""
|
||||
Tests of the get_storage function
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
get_storage.cache_clear()
|
||||
|
||||
def tearDown(self):
|
||||
get_storage.cache_clear()
|
||||
|
||||
@override_settings(
|
||||
STORAGES={
|
||||
'default': {
|
||||
'BACKEND': 'django.core.files.storage.FileSystemStorage'
|
||||
}
|
||||
}
|
||||
)
|
||||
def test_get_storage_returns_default_storage_when_no_class_specified(self):
|
||||
"""Test that get_storage returns the default storage when no storage_class is provided."""
|
||||
storage = get_storage()
|
||||
self.assertIsInstance(storage, FileSystemStorage)
|
||||
|
||||
def test_get_storage_returns_custom_storage_when_class_specified(self):
|
||||
"""Test that get_storage returns the specified storage class."""
|
||||
storage_class = 'django.core.files.storage.FileSystemStorage'
|
||||
storage = get_storage(storage_class=storage_class)
|
||||
self.assertIsInstance(storage, FileSystemStorage)
|
||||
|
||||
def test_get_storage_caching_behavior(self):
|
||||
"""Test that get_storage caches instances with identical arguments."""
|
||||
storage_class = 'django.core.files.storage.FileSystemStorage'
|
||||
kwargs = {'location': '/test/path'}
|
||||
# First Call
|
||||
storage1 = get_storage(storage_class=storage_class, **kwargs)
|
||||
# Second Call
|
||||
storage2 = get_storage(storage_class=storage_class, **kwargs)
|
||||
self.assertIs(storage1, storage2)
|
||||
|
||||
def test_get_storage_handles_invalid_storage_class(self):
|
||||
"""Test that get_storage raises appropriate error for invalid storage class."""
|
||||
with self.assertRaises(ImportError):
|
||||
get_storage(storage_class='nonexistent.storage.InvalidStorage')
|
||||
Reference in New Issue
Block a user