From 23f02d94925972af56dafc6fee9966112348bd3f Mon Sep 17 00:00:00 2001 From: Daniel Friedman <dfriedman58@gmail.com> Date: Tue, 24 Mar 2015 12:01:21 -0400 Subject: [PATCH] Configurations for filesystem and s3 backends. TNL-1789 --- cms/envs/common.py | 4 ++-- lms/envs/aws.py | 8 +++++++ lms/envs/common.py | 21 +++++++++++-------- lms/envs/test.py | 10 ++++++--- lms/urls.py | 3 ++- .../user_api/accounts/image_helpers.py | 7 ++++--- .../accounts/tests/test_image_helpers.py | 2 +- .../user_api/accounts/tests/test_views.py | 17 ++++++++++----- 8 files changed, 48 insertions(+), 24 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index e2a3b6fe783..c9f45468c90 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -40,8 +40,8 @@ from lms.envs.common import ( # The following PROFILE_IMAGE_* settings are included as they are # indirectly accessed through the email opt-in API, which is # technically accessible through the CMS via legacy URLs. - PROFILE_IMAGE_BACKEND, PROFILE_IMAGE_DOMAIN, PROFILE_IMAGE_URL_PATH, PROFILE_IMAGE_DEFAULT_FILENAME, - PROFILE_IMAGE_DEFAULT_FILE_EXTENSION, PROFILE_IMAGE_SECRET_KEY, + PROFILE_IMAGE_BACKEND, PROFILE_IMAGE_DEFAULT_FILENAME, PROFILE_IMAGE_DEFAULT_FILE_EXTENSION, + PROFILE_IMAGE_SECRET_KEY, ) from path import path from warnings import simplefilter diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 4fa99517d06..3f388f2876e 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -131,6 +131,10 @@ if STATIC_URL_BASE: if not STATIC_URL.endswith("/"): STATIC_URL += "/" +# MEDIA_ROOT specifies the directory where user-uploaded files are stored. +MEDIA_ROOT = ENV_TOKENS.get('MEDIA_ROOT', MEDIA_ROOT) +MEDIA_URL = ENV_TOKENS.get('MEDIA_URL', MEDIA_URL) + PLATFORM_NAME = ENV_TOKENS.get('PLATFORM_NAME', PLATFORM_NAME) # For displaying on the receipt. At Stanford PLATFORM_NAME != MERCHANT_NAME, but PLATFORM_NAME is a fine default PLATFORM_TWITTER_ACCOUNT = ENV_TOKENS.get('PLATFORM_TWITTER_ACCOUNT', PLATFORM_TWITTER_ACCOUNT) @@ -594,3 +598,7 @@ if FEATURES.get('INDIVIDUAL_DUE_DATES'): FIELD_OVERRIDE_PROVIDERS += ( 'courseware.student_field_overrides.IndividualStudentOverrideProvider', ) + + +# PROFILE IMAGE CONFIG +PROFILE_IMAGE_BACKEND = ENV_TOKENS.get('PROFILE_IMAGE_BACKEND', PROFILE_IMAGE_BACKEND) diff --git a/lms/envs/common.py b/lms/envs/common.py index 89e261d7b60..9541c5acc8e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -780,6 +780,10 @@ STATICFILES_DIRS = [ FAVICON_PATH = 'images/favicon.ico' +# User-uploaded content +MEDIA_ROOT = '/edx/var/edxapp/media/' +MEDIA_URL = '/media/' + # Locale/Internationalization TIME_ZONE = 'America/New_York' # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html @@ -2258,20 +2262,19 @@ CHECKPOINT_PATTERN = r'(?P<checkpoint_name>\w+)' FIELD_OVERRIDE_PROVIDERS = () # PROFILE IMAGE CONFIG -# TODO: add these settings to aws.py as well # WARNING: Certain django storage backends do not support atomic -# file overwrites (including the default, specified below) - instead +# file overwrites (including the default, OverwriteStorage) - instead # there are separate calls to delete and then write a new file in the # storage backend. This introduces the risk of a race condition # occurring when a user uploads a new profile image to replace an # earlier one (the file will temporarily be deleted). -PROFILE_IMAGE_BACKEND = 'storages.backends.overwrite.OverwriteStorage' -# PROFILE_IMAGE_DOMAIN points to the domain from which we serve image -# files from. When this is '/', it refers to the same domain as the -# app server. If serving from a different domain, specify that here -# i.e. 'http://www.example-image-server.com/' -PROFILE_IMAGE_DOMAIN = '/' -PROFILE_IMAGE_URL_PATH = 'media/profile_images/' +PROFILE_IMAGE_BACKEND = { + 'class': 'storages.backends.overwrite.OverwriteStorage', + 'options': { + 'location': os.path.join(MEDIA_ROOT, 'profile-images/'), + 'base_url': os.path.join(MEDIA_URL, 'profile-images/'), + }, +} PROFILE_IMAGE_DEFAULT_FILENAME = ( 'images/edx-theme/default-profile' if FEATURES['IS_EDX_DOMAIN'] else 'images/default-theme/default-profile' ) diff --git a/lms/envs/test.py b/lms/envs/test.py index 4b6ad246ccd..497faa55e64 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -478,9 +478,13 @@ MIDDLEWARE_CLASSES += ('ccx.overrides.CcxMiddleware',) FEATURES['CUSTOM_COURSES_EDX'] = True # Set dummy values for profile image settings. -PROFILE_IMAGE_BACKEND = 'django.core.files.storage.FileSystemStorage' -PROFILE_IMAGE_DOMAIN = 'http://example-storage.com/' -PROFILE_IMAGE_URL_PATH = 'profile_images/' +PROFILE_IMAGE_BACKEND = { + 'class': 'storages.backends.overwrite.OverwriteStorage', + 'options': { + 'location': MEDIA_ROOT, + 'base_url': 'http://example-storage.com/profile-images/', + }, +} PROFILE_IMAGE_DEFAULT_FILENAME = 'default' PROFILE_IMAGE_DEFAULT_FILE_EXTENSION = 'png' PROFILE_IMAGE_SECRET_KEY = 'secret' diff --git a/lms/urls.py b/lms/urls.py index 7822ea75132..169253ea85c 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -639,7 +639,8 @@ urlpatterns = patterns(*urlpatterns) if settings.DEBUG: urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) urlpatterns += static( - settings.PROFILE_IMAGE_DOMAIN + settings.PROFILE_IMAGE_URL_PATH, document_root=settings.MEDIA_ROOT + settings.PROFILE_IMAGE_BACKEND['options']['base_url'], + document_root=settings.PROFILE_IMAGE_BACKEND['options']['location'] ) # in debug mode, allow any template to be rendered (most useful for UX reference templates) diff --git a/openedx/core/djangoapps/user_api/accounts/image_helpers.py b/openedx/core/djangoapps/user_api/accounts/image_helpers.py index 9ab6c86cff3..227ae4a0efa 100644 --- a/openedx/core/djangoapps/user_api/accounts/image_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/image_helpers.py @@ -27,8 +27,9 @@ def get_profile_image_storage(): Configures and returns a django Storage instance that can be used to physically locate, read and write profile images. """ - storage_class = get_storage_class(settings.PROFILE_IMAGE_BACKEND) - return storage_class(base_url=(settings.PROFILE_IMAGE_DOMAIN + settings.PROFILE_IMAGE_URL_PATH)) + config = settings.PROFILE_IMAGE_BACKEND + storage_class = get_storage_class(config['class']) + return storage_class(**config['options']) def _make_profile_image_name(username): @@ -75,7 +76,7 @@ def get_profile_image_urls_for_user(user): callers will use `_get_default_profile_image_urls` instead to provide a set of urls that point to placeholder images, when there are no user- submitted images. - - based on the value of django.conf.settings.PROFILE_IMAGE_DOMAIN, + - based on the value of django.conf.settings.PROFILE_IMAGE_BACKEND, the URL may be relative, and in that case the caller is responsible for constructing the full URL if needed. diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py index 4d1ced4411d..c1cb633c308 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_image_helpers.py @@ -34,7 +34,7 @@ class ProfileImageUrlTestCase(TestCase): """ self.assertEqual( actual_url, - 'http://example-storage.com/profile_images/{name}_{size}.jpg'.format( + 'http://example-storage.com/profile-images/{name}_{size}.jpg'.format( name=expected_name, size=expected_pixels ) ) diff --git a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py index 6a6d2bce3bf..72d5af5bfdf 100644 --- a/openedx/core/djangoapps/user_api/accounts/tests/test_views.py +++ b/openedx/core/djangoapps/user_api/accounts/tests/test_views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import datetime +from copy import deepcopy import ddt import hashlib import json @@ -19,6 +20,12 @@ from openedx.core.djangoapps.user_api.preferences.api import set_user_preference from .. import PRIVATE_VISIBILITY, ALL_USERS_VISIBILITY +# this is used in one test to check the behavior of profile image url +# generation with a relative url in the config. +TEST_PROFILE_IMAGE_BACKEND = deepcopy(settings.PROFILE_IMAGE_BACKEND) +TEST_PROFILE_IMAGE_BACKEND['options']['base_url'] = '/profile-images/' + + class UserAPITestCase(APITestCase): """ The base class for all tests of the User API @@ -117,7 +124,7 @@ class TestAccountAPI(UserAPITestCase): image. """ if has_profile_image: - url_root = 'http://example-storage.com/profile_images' + url_root = 'http://example-storage.com/profile-images' filename = hashlib.md5('secret' + self.user.username).hexdigest() file_extension = 'jpg' else: @@ -593,12 +600,12 @@ class TestAccountAPI(UserAPITestCase): ) self.assertIsNone(error_response.data["user_message"]) - @override_settings(PROFILE_IMAGE_DOMAIN='/') + @override_settings(PROFILE_IMAGE_BACKEND=TEST_PROFILE_IMAGE_BACKEND) def test_convert_relative_profile_url(self): """ - Test that when PROFILE_IMAGE_DOMAIN is set to '/', the API - generates the full URL to profile images based on the URL - of the request. + Test that when TEST_PROFILE_IMAGE_BACKEND['base_url'] begins + with a '/', the API generates the full URL to profile images based on + the URL of the request. """ self.client.login(username=self.user.username, password=self.test_password) response = self.send_get(self.client) -- GitLab