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