diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py index 95229428ac..138e323d1c 100644 --- a/cms/djangoapps/contentstore/views/course.py +++ b/cms/djangoapps/contentstore/views/course.py @@ -55,7 +55,7 @@ from contentstore import utils from student.roles import CourseInstructorRole, CourseStaffRole, CourseCreatorRole, GlobalStaff from student import auth -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite __all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler', 'settings_handler', @@ -535,7 +535,7 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu # see if the ORG of this course can be attributed to a 'Microsite'. In that case, the # course about page should be editable in Studio - about_page_editable = not MicrositeConfiguration.get_microsite_configuration_value_for_org( + about_page_editable = not microsite.get_value_for_org( course_module.location.org, 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False) diff --git a/cms/djangoapps/contentstore/views/public.py b/cms/djangoapps/contentstore/views/public.py index ef02445430..63f95084aa 100644 --- a/cms/djangoapps/contentstore/views/public.py +++ b/cms/djangoapps/contentstore/views/public.py @@ -10,7 +10,7 @@ from django.conf import settings from edxmako.shortcuts import render_to_response from external_auth.views import ssl_login_shortcut, ssl_get_cert_from_request -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite __all__ = ['signup', 'login_page', 'howitworks'] @@ -49,7 +49,7 @@ def login_page(request): { 'csrf': csrf_token, 'forgot_password_link': "//{base}/login#forgot-password-modal".format(base=settings.LMS_BASE), - 'platform_name': MicrositeConfiguration.get_microsite_configuration_value('platform_name', settings.PLATFORM_NAME), + 'platform_name': microsite.get_value('platform_name', settings.PLATFORM_NAME), } ) diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 2bdd075218..3147ad3cd7 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -239,16 +239,8 @@ VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', []) MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED", 5) MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS", 15 * 60) - MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {}) -MICROSITE_ROOT_DIR = ENV_TOKENS.get('MICROSITE_ROOT_DIR') -if len(MICROSITE_CONFIGURATION.keys()) > 0: - enable_microsites( - MICROSITE_CONFIGURATION, - SUBDOMAIN_BRANDING, - VIRTUAL_UNIVERSITIES, - microsites_root=path(MICROSITE_ROOT_DIR) - ) +MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', '')) #### PASSWORD POLICY SETTINGS ##### PASSWORD_MIN_LENGTH = ENV_TOKENS.get("PASSWORD_MIN_LENGTH") diff --git a/cms/envs/common.py b/cms/envs/common.py index 94a4db86e7..588bdd7a41 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -27,7 +27,7 @@ Longer TODO: import sys import lms.envs.common from lms.envs.common import ( - USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, enable_microsites, ALL_LANGUAGES + USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, ALL_LANGUAGES ) from path import path @@ -84,6 +84,9 @@ FEATURES = { # Toggles embargo functionality 'EMBARGO': False, + + # Turn on/off Microsites feature + 'USE_MICROSITES': False, } ENABLE_JASMINE = False diff --git a/cms/envs/microsite_test.py b/cms/envs/microsite_test.py index 5eb2079da8..b79e295226 100644 --- a/cms/envs/microsite_test.py +++ b/cms/envs/microsite_test.py @@ -6,10 +6,7 @@ This is a localdev test for the Microsite processing pipeline # pylint: disable=W0401, W0614 from .dev import * -from .dev import SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES MICROSITE_NAMES = ['openedx'] MICROSITE_CONFIGURATION = {} - -if MICROSITE_NAMES and len(MICROSITE_NAMES) > 0: - enable_microsites(MICROSITE_NAMES, MICROSITE_CONFIGURATION, SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES) +FEATURES['USE_MICROSITES'] = True diff --git a/cms/envs/test.py b/cms/envs/test.py index 1e1adf49e4..8d60aff95c 100644 --- a/cms/envs/test.py +++ b/cms/envs/test.py @@ -199,3 +199,31 @@ FEATURES['DISABLE_RESET_EMAIL_TEST'] = True # Toggles embargo on for testing FEATURES['EMBARGO'] = True + +# set up some testing for microsites +MICROSITE_CONFIGURATION = { + "test_microsite": { + "domain_prefix": "testmicrosite", + "university": "test_microsite", + "platform_name": "Test Microsite", + "logo_image_url": "test_microsite/images/header-logo.png", + "email_from_address": "test_microsite@edx.org", + "payment_support_email": "test_microsite@edx.org", + "ENABLE_MKTG_SITE": False, + "SITE_NAME": "test_microsite.localhost", + "course_org_filter": "TestMicrositeX", + "course_about_show_social_links": False, + "css_overrides_file": "test_microsite/css/test_microsite.css", + "show_partners": False, + "show_homepage_promo_video": False, + "course_index_overlay_text": "This is a Test Microsite Overlay Text.", + "course_index_overlay_logo_file": "test_microsite/images/header-logo.png", + "homepage_overlay_html": "

This is a Test Microsite Overlay HTML

" + }, + "default": { + "university": "default_university", + "domain_prefix": "www", + } +} +MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_microsites' +FEATURES['USE_MICROSITES'] = True diff --git a/common/djangoapps/edxmako/shortcuts.py b/common/djangoapps/edxmako/shortcuts.py index 73f76b8afd..b8d0c2a2c2 100644 --- a/common/djangoapps/edxmako/shortcuts.py +++ b/common/djangoapps/edxmako/shortcuts.py @@ -16,7 +16,7 @@ from django.template import Context from django.http import HttpResponse import logging -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite from edxmako import lookup_template import edxmako.middleware @@ -37,7 +37,7 @@ def marketing_link(name): # link_map maps URLs from the marketing site to the old equivalent on # the Django site link_map = settings.MKTG_URL_LINK_MAP - enable_mktg_site = MicrositeConfiguration.get_microsite_configuration_value( + enable_mktg_site = microsite.get_value( 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False) ) @@ -80,7 +80,7 @@ def marketing_link_context_processor(request): def render_to_string(template_name, dictionary, context=None, namespace='main'): # see if there is an override template defined in the microsite - template_name = MicrositeConfiguration.get_microsite_template_path(template_name) + template_name = microsite.get_template_path(template_name) context_instance = Context(dictionary) # add dictionary to context_instance @@ -111,7 +111,7 @@ def render_to_response(template_name, dictionary=None, context_instance=None, na """ # see if there is an override template defined in the microsite - template_name = MicrositeConfiguration.get_microsite_template_path(template_name) + template_name = microsite.get_template_path(template_name) dictionary = dictionary or {} return HttpResponse(render_to_string(template_name, dictionary, context_instance, namespace), **kwargs) diff --git a/common/djangoapps/edxmako/startup.py b/common/djangoapps/edxmako/startup.py index 1783373239..2ecc8b231e 100644 --- a/common/djangoapps/edxmako/startup.py +++ b/common/djangoapps/edxmako/startup.py @@ -8,6 +8,9 @@ from . import add_lookup def run(): """ Setup mako lookup directories. + + IMPORTANT: This method can be called multiple times during application startup. Any changes to this method + must be safe for multiple callers during startup phase. """ template_locations = settings.MAKO_TEMPLATES for namespace, directories in template_locations.items(): diff --git a/common/djangoapps/microsite_configuration/microsite.py b/common/djangoapps/microsite_configuration/microsite.py new file mode 100644 index 0000000000..91f8eaffa1 --- /dev/null +++ b/common/djangoapps/microsite_configuration/microsite.py @@ -0,0 +1,142 @@ +""" +This file implements the Microsite support for the Open edX platform. +A microsite enables the following features: + +1) Mapping of sub-domain name to a 'brand', e.g. foo-university.edx.org +2) Present a landing page with a listing of courses that are specific to the 'brand' +3) Ability to swap out some branding elements in the website +""" +import threading +import os.path + +from django.conf import settings + +CURRENT_REQUEST_CONFIGURATION = threading.local() +CURRENT_REQUEST_CONFIGURATION.data = {} + + +def has_configuration_set(): + """ + Returns whether there is any Microsite configuration settings + """ + return getattr(settings, "MICROSITE_CONFIGURATION", False) + + +def get_configuration(): + """ + Returns the current request's microsite configuration + """ + if not hasattr(CURRENT_REQUEST_CONFIGURATION, 'data'): + return {} + + return CURRENT_REQUEST_CONFIGURATION.data + + +def is_request_in_microsite(): + """ + This will return if current request is a request within a microsite + """ + return get_configuration() + + +def get_value(val_name, default=None): + """ + Returns a value associated with the request's microsite, if present + """ + configuration = get_configuration() + return configuration.get(val_name, default) + + +def get_template_path(relative_path): + """ + Returns a path (string) to a Mako template, which can either be in + a microsite directory (as an override) or will just return what is passed in which is + expected to be a string + """ + + if not is_request_in_microsite(): + return relative_path + + microsite_template_path = str(get_value('template_dir')) + + if microsite_template_path: + search_path = os.path.join(microsite_template_path, relative_path) + + if os.path.isfile(search_path): + path = '{0}/templates/{1}'.format( + get_value('microsite_name'), + relative_path + ) + return path + + return relative_path + + +def get_value_for_org(org, val_name, default=None): + """ + This returns a configuration value for a microsite which has an org_filter that matches + what is passed in + """ + if not has_configuration_set(): + return default + + for value in settings.MICROSITE_CONFIGURATION.values(): + org_filter = value.get('course_org_filter', None) + if org_filter == org: + return value.get(val_name, default) + return default + + +def get_all_orgs(): + """ + This returns a set of orgs that are considered within a microsite. This can be used, + for example, to do filtering + """ + org_filter_set = set() + if not has_configuration_set(): + return org_filter_set + + for value in settings.MICROSITE_CONFIGURATION.values(): + org_filter = value.get('course_org_filter') + if org_filter: + org_filter_set.add(org_filter) + + return org_filter_set + + +def clear(): + """ + Clears out any microsite configuration from the current request/thread + """ + CURRENT_REQUEST_CONFIGURATION.data = {} + + +def _set_current_microsite(microsite_config_key, subdomain, domain): + """ + Helper internal method to actually put a microsite on the threadlocal + """ + config = settings.MICROSITE_CONFIGURATION[microsite_config_key].copy() + config['subdomain'] = subdomain + config['site_domain'] = domain + CURRENT_REQUEST_CONFIGURATION.data = config + + +def set_by_domain(domain): + """ + For a given request domain, find a match in our microsite configuration and then assign + it to the thread local so that it is available throughout the entire + Django request processing + """ + if not has_configuration_set() or not domain: + return + + for key, value in settings.MICROSITE_CONFIGURATION.items(): + subdomain = value.get('domain_prefix') + if subdomain and domain.startswith(subdomain): + _set_current_microsite(key, subdomain, domain) + return + + # if no match on subdomain then see if there is a 'default' microsite defined + # if so, then use that + if 'default' in settings.MICROSITE_CONFIGURATION: + _set_current_microsite('default', subdomain, domain) diff --git a/common/djangoapps/microsite_configuration/middleware.py b/common/djangoapps/microsite_configuration/middleware.py index 61d2cd9883..f9e1e0ba15 100644 --- a/common/djangoapps/microsite_configuration/middleware.py +++ b/common/djangoapps/microsite_configuration/middleware.py @@ -1,183 +1,36 @@ """ -This file implements the initial Microsite support for the Open edX platform. +This file implements the Middleware support for the Open edX platform. A microsite enables the following features: 1) Mapping of sub-domain name to a 'brand', e.g. foo-university.edx.org 2) Present a landing page with a listing of courses that are specific to the 'brand' 3) Ability to swap out some branding elements in the website """ -import threading -import os.path - -from django.conf import settings - -_microsite_configuration_threadlocal = threading.local() -_microsite_configuration_threadlocal.data = {} +from microsite_configuration import microsite -def has_microsite_configuration_set(): - """ - Returns whether the MICROSITE_CONFIGURATION has been set in the configuration files - """ - return getattr(settings, "MICROSITE_CONFIGURATION", False) - - -class MicrositeConfiguration(object): +class MicrositeMiddleware(object): """ Middleware class which will bind configuration information regarding 'microsites' on a per request basis. The actual configuration information is taken from Django settings information """ - @classmethod - def is_request_in_microsite(cls): - """ - This will return if current request is a request within a microsite - """ - return cls.get_microsite_configuration() - - @classmethod - def get_microsite_configuration(cls): - """ - Returns the current request's microsite configuration - """ - if not hasattr(_microsite_configuration_threadlocal, 'data'): - return {} - - return _microsite_configuration_threadlocal.data - - @classmethod - def get_microsite_configuration_value(cls, val_name, default=None): - """ - Returns a value associated with the request's microsite, if present - """ - configuration = cls.get_microsite_configuration() - return configuration.get(val_name, default) - - @classmethod - def get_microsite_template_path(cls, relative_path): - """ - Returns a path to a Mako template, which can either be in - a microsite directory (as an override) or will just return what is passed in - """ - - if not cls.is_request_in_microsite(): - return relative_path - - microsite_template_path = cls.get_microsite_configuration_value('template_dir') - - if microsite_template_path: - search_path = microsite_template_path / relative_path - - if os.path.isfile(search_path): - path = '{0}/templates/{1}'.format( - cls.get_microsite_configuration_value('microsite_name'), - relative_path - ) - return path - - return relative_path - - @classmethod - def get_microsite_configuration_value_for_org(cls, org, val_name, default=None): - """ - This returns a configuration value for a microsite which has an org_filter that matches - what is passed in - """ - if not has_microsite_configuration_set(): - return default - - for key in settings.MICROSITE_CONFIGURATION.keys(): - org_filter = settings.MICROSITE_CONFIGURATION[key].get('course_org_filter', None) - if org_filter == org: - return settings.MICROSITE_CONFIGURATION[key].get(val_name, default) - return default - - @classmethod - def get_all_microsite_orgs(cls): - """ - This returns a set of orgs that are considered within a Microsite. This can be used, - for example, to do filtering - """ - org_filter_set = [] - if not has_microsite_configuration_set(): - return org_filter_set - - for key in settings.MICROSITE_CONFIGURATION: - org_filter = settings.MICROSITE_CONFIGURATION[key].get('course_org_filter') - if org_filter: - org_filter_set.append(org_filter) - - return org_filter_set - - def clear_microsite_configuration(self): - """ - Clears out any microsite configuration from the current request/thread - """ - _microsite_configuration_threadlocal.data = {} - def process_request(self, request): """ Middleware entry point on every request processing. This will associate a request's domain name with a 'University' and any corresponding microsite configuration information """ - self.clear_microsite_configuration() + microsite.clear() domain = request.META.get('HTTP_HOST', None) - if domain: - subdomain = MicrositeConfiguration.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys()) - university = MicrositeConfiguration.match_university(subdomain) - microsite_configuration = self.get_microsite_configuration_for_university(university) - if microsite_configuration: - microsite_configuration['university'] = university - microsite_configuration['subdomain'] = subdomain - microsite_configuration['site_domain'] = domain - _microsite_configuration_threadlocal.data = microsite_configuration + microsite.set_by_domain(domain) - # also put the configuration on the request itself to make it easier to dereference - request.microsite_configuration = _microsite_configuration_threadlocal.data return None def process_response(self, request, response): """ Middleware entry point for request completion. """ - self.clear_microsite_configuration() + microsite.clear() return response - - def get_microsite_configuration_for_university(self, university): - """ - For a given university, return the microsite configuration which - is in the Django settings - """ - if not university: - return None - - if not has_microsite_configuration_set(): - return None - - configuration = settings.MICROSITE_CONFIGURATION.get(university, None) - return configuration - - @classmethod - def match_university(cls, domain): - """ - Return the university name specified for the domain, or None - if no university was specified - """ - if not settings.FEATURES['SUBDOMAIN_BRANDING'] or domain is None: - return None - - subdomain = cls.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys()) - return settings.SUBDOMAIN_BRANDING.get(subdomain) - - @classmethod - def pick_subdomain(cls, domain, options, default='default'): - """ - Attempt to match the incoming request's HOST domain with a configuration map - to see what subdomains are supported in Microsites. - """ - for option in options: - if domain.startswith(option): - return option - return default diff --git a/common/djangoapps/microsite_configuration/templatetags/microsite.py b/common/djangoapps/microsite_configuration/templatetags/microsite.py index 5e76c152a9..3b21dec7df 100644 --- a/common/djangoapps/microsite_configuration/templatetags/microsite.py +++ b/common/djangoapps/microsite_configuration/templatetags/microsite.py @@ -4,7 +4,7 @@ based on the current micro site. """ from django import template from django.conf import settings -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite register = template.Library() @@ -37,4 +37,4 @@ def platform_name(): Django template tag that outputs the current platform name: {% platform_name %} """ - return MicrositeConfiguration.get_microsite_configuration_value('platform_name', settings.PLATFORM_NAME) + return microsite.get_value('platform_name', settings.PLATFORM_NAME) diff --git a/common/djangoapps/microsite_configuration/tests/__init__.py b/common/djangoapps/microsite_configuration/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/common/djangoapps/microsite_configuration/tests/test_logic.py b/common/djangoapps/microsite_configuration/tests/test_logic.py new file mode 100644 index 0000000000..2d0ad93fcb --- /dev/null +++ b/common/djangoapps/microsite_configuration/tests/test_logic.py @@ -0,0 +1,26 @@ +""" +Some additional unit tests for Microsite logic. The LMS covers some of the Microsite testing, this adds +some additional coverage +""" +import django.test + +from microsite_configuration.microsite import get_value_for_org + + +class TestMicrosites(django.test.TestCase): + """ + Run through some Microsite logic + """ + + def test_get_value_for_org(self): + """ + Make sure we can do lookups on Microsite configuration based on ORG fields + """ + + # first make sure default value is returned if there's no Microsite ORG match + value = get_value_for_org("BogusX", "university", "default_value") + self.assertEquals(value, "default_value") + + # now test when we call in a value Microsite ORG, note this is defined in test.py configuration + value = get_value_for_org("TestMicrositeX", "university", "default_value") + self.assertEquals(value, "test_microsite") diff --git a/common/djangoapps/microsite_configuration/test_microsites.py b/common/djangoapps/microsite_configuration/tests/test_microsites.py similarity index 89% rename from common/djangoapps/microsite_configuration/test_microsites.py rename to common/djangoapps/microsite_configuration/tests/test_microsites.py index b3bb21990a..01cf04aa8a 100644 --- a/common/djangoapps/microsite_configuration/test_microsites.py +++ b/common/djangoapps/microsite_configuration/tests/test_microsites.py @@ -4,10 +4,13 @@ Tests microsite_configuration templatetags and helper functions. """ from django.test import TestCase from django.conf import settings -from .templatetags import microsite +from microsite_configuration.templatetags import microsite class MicroSiteTests(TestCase): + """ + Make sure some of the helper functions work + """ def test_breadcrumbs(self): crumbs = ['my', 'less specific', 'Page'] expected = u'my | less specific | Page | edX' @@ -23,10 +26,9 @@ class MicroSiteTests(TestCase): def test_platform_name(self): pname = microsite.platform_name() self.assertEqual(pname, settings.PLATFORM_NAME) - + def test_breadcrumb_tag(self): crumbs = ['my', 'less specific', 'Page'] expected = u'my | less specific | Page | edX' title = microsite.page_title_breadcrumbs_tag(None, *crumbs) self.assertEqual(expected, title) - \ No newline at end of file diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 453ea03ca3..2c802d3f4f 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -74,7 +74,7 @@ from dogapi import dog_stats_api from util.json_request import JsonResponse from util.bad_request_rate_limiter import BadRequestRateLimiter -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite from util.password_policy_validators import ( validate_password_length, validate_password_complexity, @@ -350,7 +350,7 @@ def signin_user(request): context = { 'course_id': request.GET.get('course_id'), 'enrollment_action': request.GET.get('enrollment_action'), - 'platform_name': MicrositeConfiguration.get_microsite_configuration_value( + 'platform_name': microsite.get_value( 'platform_name', settings.PLATFORM_NAME ), @@ -373,7 +373,7 @@ def register_user(request, extra_context=None): context = { 'course_id': request.GET.get('course_id'), 'enrollment_action': request.GET.get('enrollment_action'), - 'platform_name': MicrositeConfiguration.get_microsite_configuration_value( + 'platform_name': microsite.get_value( 'platform_name', settings.PLATFORM_NAME ), @@ -416,11 +416,11 @@ def dashboard(request): # for microsites, we want to filter and only show enrollments for courses within # the microsites 'ORG' - course_org_filter = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter') + course_org_filter = microsite.get_value('course_org_filter') # Let's filter out any courses in an "org" that has been declared to be # in a Microsite - org_filter_out_set = MicrositeConfiguration.get_all_microsite_orgs() + org_filter_out_set = microsite.get_all_orgs() # remove our current Microsite from the "filter out" list, if applicable if course_org_filter: @@ -1160,7 +1160,7 @@ def create_account(request, post_override=None): # don't send email if we are doing load testing or random user generation for some reason if not (settings.FEATURES.get('AUTOMATIC_AUTH_FOR_TESTING')): - from_address = MicrositeConfiguration.get_microsite_configuration_value( + from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) @@ -1502,7 +1502,7 @@ def change_email_request(request): message = render_to_string('emails/email_change.txt', context) - from_address = MicrositeConfiguration.get_microsite_configuration_value( + from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) diff --git a/common/djangoapps/util/request.py b/common/djangoapps/util/request.py index a26059e8a7..813a3347d3 100644 --- a/common/djangoapps/util/request.py +++ b/common/djangoapps/util/request.py @@ -2,7 +2,7 @@ import re from django.conf import settings -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite COURSE_REGEX = re.compile(r'^.*?/courses/(?P[^/]+/[^/]+/[^/]+)') @@ -19,7 +19,7 @@ def safe_get_host(request): if isinstance(settings.ALLOWED_HOSTS, (list, tuple)) and '*' not in settings.ALLOWED_HOSTS: return request.get_host() else: - return MicrositeConfiguration.get_microsite_configuration_value('site_domain', settings.SITE_NAME) + return microsite.get_value('site_domain', settings.SITE_NAME) def course_id_from_url(url): diff --git a/common/test/test_microsites/default/readme.txt b/common/test/test_microsites/default/readme.txt new file mode 100644 index 0000000000..6515cbfb96 --- /dev/null +++ b/common/test/test_microsites/default/readme.txt @@ -0,0 +1,3 @@ +This directory is intentionally empty. + +We need to have a directory on disk for the corresponding Microsite configuration to be considered 'valid'. Therefore to execute some 'default' use-cases, we have this empty directory. diff --git a/common/test/test_microsites/test_microsite/templates/footer.html b/common/test/test_microsites/test_microsite/templates/footer.html index bb7dd0de28..de7e55e47c 100644 --- a/common/test/test_microsites/test_microsite/templates/footer.html +++ b/common/test/test_microsites/test_microsite/templates/footer.html @@ -1,7 +1,7 @@ ## mako <%! from django.core.urlresolvers import reverse %> <%! from django.utils.translation import ugettext as _ %> -<%! from microsite_configuration.middleware import MicrositeConfiguration %> +<%! from microsite_configuration import microsite %> <%namespace name='static' file='static_content.html'/> diff --git a/lms/djangoapps/branding/__init__.py b/lms/djangoapps/branding/__init__.py index 51645b824f..b6a0f3c11e 100644 --- a/lms/djangoapps/branding/__init__.py +++ b/lms/djangoapps/branding/__init__.py @@ -2,7 +2,7 @@ from xmodule.modulestore.django import modulestore from xmodule.course_module import CourseDescriptor from django.conf import settings -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite def get_visible_courses(): @@ -15,7 +15,7 @@ def get_visible_courses(): if isinstance(c, CourseDescriptor)] courses = sorted(courses, key=lambda course: course.number) - subdomain = MicrositeConfiguration.get_microsite_configuration_value('subdomain', 'default') + subdomain = microsite.get_value('subdomain', 'default') # See if we have filtered course listings in this domain filtered_visible_ids = None @@ -24,7 +24,7 @@ def get_visible_courses(): if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS and not settings.DEBUG: filtered_visible_ids = frozenset(settings.COURSE_LISTINGS[subdomain]) - filtered_by_org = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter') + filtered_by_org = microsite.get_value('course_org_filter') if filtered_by_org: return [course for course in courses if course.location.org == filtered_by_org] @@ -33,7 +33,7 @@ def get_visible_courses(): else: # Let's filter out any courses in an "org" that has been declared to be # in a Microsite - org_filter_out_set = MicrositeConfiguration.get_all_microsite_orgs() + org_filter_out_set = microsite.get_all_orgs() return [course for course in courses if course.location.org not in org_filter_out_set] @@ -42,7 +42,7 @@ def get_university_for_request(): Return the university name specified for the domain, or None if no university was specified """ - return MicrositeConfiguration.get_microsite_configuration_value('university') + return microsite.get_value('university') def get_logo_url(): @@ -52,7 +52,7 @@ def get_logo_url(): # if the MicrositeConfiguration has a value for the logo_image_url # let's use that - image_url = MicrositeConfiguration.get_microsite_configuration_value('logo_image_url') + image_url = microsite.get_value('logo_image_url') if image_url: return '{static_url}{image_url}'.format( static_url=settings.STATIC_URL, @@ -60,7 +60,7 @@ def get_logo_url(): ) # otherwise, use the legacy means to configure this - university = MicrositeConfiguration.get_microsite_configuration_value('university') + university = microsite.get_value('university') if university is None: return '{static_url}images/header-logo.png'.format( diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index be2c975087..06939e04cc 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -8,7 +8,7 @@ from edxmako.shortcuts import render_to_response import student.views import courseware.views -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite from edxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous @@ -27,7 +27,7 @@ def index(request): from external_auth.views import ssl_login return ssl_login(request) - enable_mktg_site = MicrositeConfiguration.get_microsite_configuration_value( + enable_mktg_site = microsite.get_value( 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False) ) @@ -35,11 +35,11 @@ def index(request): if enable_mktg_site: return redirect(settings.MKTG_URLS.get('ROOT')) - university = MicrositeConfiguration.match_university(request.META.get('HTTP_HOST')) + domain = request.META.get('HTTP_HOST') # keep specialized logic for Edge until we can migrate over Edge to fully use # microsite definitions - if university == 'edge': + if domain and 'edge.edx.org' in domain: context = { 'suppress_toplevel_navigation': True } @@ -59,7 +59,7 @@ def courses(request): to that. Otherwise, if subdomain branding is on, this is the university profile page. Otherwise, it's the edX courseware.views.courses page """ - enable_mktg_site = MicrositeConfiguration.get_microsite_configuration_value( + enable_mktg_site = microsite.get_value( 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False) ) diff --git a/lms/djangoapps/courseware/tests/test_microsites.py b/lms/djangoapps/courseware/tests/test_microsites.py index 4293e2bc8e..52105f33d8 100644 --- a/lms/djangoapps/courseware/tests/test_microsites.py +++ b/lms/djangoapps/courseware/tests/test_microsites.py @@ -70,7 +70,7 @@ class TestMicrosites(ModuleStoreTestCase, LoginEnrollmentTestCase): self.assertContains(resp, 'This is a Test Microsite Overlay') # Overlay test message self.assertContains(resp, 'test_microsite/images/header-logo.png') # logo swap - self.assertContains(resp, 'test_microsite/css/test_microsite.css') # css override + self.assertContains(resp, 'test_microsite/css/test_microsite') # css override self.assertContains(resp, 'Test Microsite') # page title # assert that test course display name is visible @@ -101,7 +101,7 @@ class TestMicrosites(ModuleStoreTestCase, LoginEnrollmentTestCase): self.assertNotContains(resp, 'This is a Test Microsite Overlay') # Overlay test message self.assertNotContains(resp, 'test_microsite/images/header-logo.png') # logo swap - self.assertNotContains(resp, 'test_microsite/css/test_microsite.css') # css override + self.assertNotContains(resp, 'test_microsite/css/test_microsite') # css override self.assertNotContains(resp, 'Test Microsite') # page title # assert that test course display name IS NOT VISIBLE, since that is a Microsite only course diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index dd64db6516..41ef619ebd 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -38,7 +38,7 @@ from xmodule.modulestore.search import path_to_location from xmodule.course_module import CourseDescriptor import shoppingcart -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite log = logging.getLogger("edx.courseware") @@ -528,7 +528,7 @@ def registered_for_course(course, user): @cache_if_anonymous def course_about(request, course_id): - if MicrositeConfiguration.get_microsite_configuration_value( + if microsite.get_value( 'ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False) ): diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py index 5042f6996d..e301a41630 100644 --- a/lms/djangoapps/instructor/enrollment.py +++ b/lms/djangoapps/instructor/enrollment.py @@ -14,7 +14,7 @@ from student.models import CourseEnrollment, CourseEnrollmentAllowed from courseware.models import StudentModule from edxmako.shortcuts import render_to_string -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite # For determining if a shibboleth course SHIBBOLETH_DOMAIN_PREFIX = 'shib:' @@ -229,7 +229,7 @@ def send_mail_to_student(student, param_dict): if 'course' in param_dict: param_dict['course_name'] = param_dict['course'].display_name_with_default - param_dict['site_name'] = MicrositeConfiguration.get_microsite_configuration_value( + param_dict['site_name'] = microsite.get_value( 'SITE_NAME', param_dict['site_name'] ) @@ -271,7 +271,7 @@ def send_mail_to_student(student, param_dict): # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) - from_address = MicrositeConfiguration.get_microsite_configuration_value( + from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py index 2b6daf349b..ba013b1067 100644 --- a/lms/djangoapps/instructor/views/legacy.py +++ b/lms/djangoapps/instructor/views/legacy.py @@ -62,7 +62,7 @@ from xblock.field_data import DictFieldData from xblock.fields import ScopeIds from django.utils.translation import ugettext as _u -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite log = logging.getLogger(__name__) @@ -1295,7 +1295,7 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll ceaset.delete() if email_students: - stripped_site_name = MicrositeConfiguration.get_microsite_configuration_value( + stripped_site_name = microsite.get_value( 'SITE_NAME', settings.SITE_NAME ) @@ -1389,7 +1389,7 @@ def _do_unenroll_students(course_id, students, email_students=False): old_students, _ = get_and_clean_student_list(students) status = dict([x, 'unprocessed'] for x in old_students) - stripped_site_name = MicrositeConfiguration.get_microsite_configuration_value( + stripped_site_name = microsite.get_value( 'SITE_NAME', settings.SITE_NAME ) @@ -1469,7 +1469,7 @@ def send_mail_to_student(student, param_dict): # add some helpers and microconfig subsitutions if 'course' in param_dict: param_dict['course_name'] = param_dict['course'].display_name_with_default - param_dict['site_name'] = MicrositeConfiguration.get_microsite_configuration_value( + param_dict['site_name'] = microsite.get_value( 'SITE_NAME', param_dict.get('site_name', '') ) @@ -1497,7 +1497,7 @@ def send_mail_to_student(student, param_dict): # Email subject *must not* contain newlines subject = ''.join(subject.splitlines()) - from_address = MicrositeConfiguration.get_microsite_configuration_value( + from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py index 5c98a36feb..42c25e0750 100644 --- a/lms/djangoapps/shoppingcart/models.py +++ b/lms/djangoapps/shoppingcart/models.py @@ -35,7 +35,7 @@ from verify_student.models import SoftwareSecurePhotoVerification from .exceptions import (InvalidCartItem, PurchasedCallbackException, ItemAlreadyInCartException, AlreadyEnrolledInCourseException, CourseDoesNotExistException) -from microsite_configuration.middleware import MicrositeConfiguration +from microsite_configuration import microsite log = logging.getLogger("shoppingcart") @@ -175,7 +175,7 @@ class Order(models.Model): } ) try: - from_address = MicrositeConfiguration.get_microsite_configuration_value( + from_address = microsite.get_value( 'email_from_address', settings.DEFAULT_FROM_EMAIL ) @@ -477,7 +477,7 @@ class CertificateItem(OrderItem): user_email=course_enrollment.user.email, order_number=order_number) to_email = [settings.PAYMENT_SUPPORT_EMAIL] - from_email = [MicrositeConfiguration.get_microsite_configuration_value( + from_email = [microsite.get_value( 'payment_support_email', settings.PAYMENT_SUPPORT_EMAIL )] diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 720d418c7e..ee424f84cc 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -356,14 +356,7 @@ MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_AL MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS = ENV_TOKENS.get("MAX_FAILED_LOGIN_ATTEMPTS_LOCKOUT_PERIOD_SECS", 15 * 60) MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {}) -MICROSITE_ROOT_DIR = ENV_TOKENS.get('MICROSITE_ROOT_DIR') -if MICROSITE_CONFIGURATION: - enable_microsites( - MICROSITE_CONFIGURATION, - SUBDOMAIN_BRANDING, - VIRTUAL_UNIVERSITIES, - microsites_root=path(MICROSITE_ROOT_DIR) - ) +MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', '')) #### PASSWORD POLICY SETTINGS ##### PASSWORD_MIN_LENGTH = ENV_TOKENS.get("PASSWORD_MIN_LENGTH") diff --git a/lms/envs/cms/microsite_test.py b/lms/envs/cms/microsite_test.py index 700e7c6c16..09ea6aa3dd 100644 --- a/lms/envs/cms/microsite_test.py +++ b/lms/envs/cms/microsite_test.py @@ -6,7 +6,7 @@ This is a localdev test for the Microsite processing pipeline # pylint: disable=W0401, W0614 from .dev import * -from .dev import SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES +from ..dev import ENV_ROOT, FEATURES MICROSITE_CONFIGURATION = { @@ -30,13 +30,9 @@ MICROSITE_CONFIGURATION = { } } -if len(MICROSITE_CONFIGURATION.keys()) > 0: - enable_microsites( - MICROSITE_CONFIGURATION, - SUBDOMAIN_BRANDING, - VIRTUAL_UNIVERSITIES - ) +MICROSITE_ROOT_DIR = ENV_ROOT / 'edx-microsite' # pretend we are behind some marketing site, we want to be able to assert that the Microsite config values override # this global setting FEATURES['ENABLE_MKTG_SITE'] = True +FEATURES['USE_MICROSITES'] = True diff --git a/lms/envs/common.py b/lms/envs/common.py index fd8172d4fe..cf6107f17c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -229,6 +229,9 @@ FEATURES = { # that people can submit content and modify the Wiki in any arbitrary manner. We're leaving this as True in the # defaults, so that we maintain current behavior 'ALLOW_WIKI_ROOT_ACCESS': True, + + # Turn on/off Microsites feature + 'USE_MICROSITES': False, } # Used for A/B testing @@ -693,7 +696,7 @@ TEMPLATE_LOADERS = ( MIDDLEWARE_CLASSES = ( 'request_cache.middleware.RequestCache', - 'microsite_configuration.middleware.MicrositeConfiguration', + 'microsite_configuration.middleware.MicrositeMiddleware', 'django_comment_client.middleware.AjaxExceptionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', @@ -1183,64 +1186,6 @@ MKTG_URL_LINK_MAP = { } -############################### MICROSITES ################################ -def enable_microsites(microsite_config_dict, subdomain_branding, virtual_universities, microsites_root=ENV_ROOT / "microsites"): - """ - Enable the use of microsites, which are websites that allow - for subdomains for the edX platform, e.g. foo.edx.org - """ - - if not microsite_config_dict: - return - - FEATURES['USE_MICROSITES'] = True - - for microsite_name in microsite_config_dict.keys(): - # Calculate the location of the microsite's files - microsite_root = microsites_root / microsite_name - microsite_config = microsite_config_dict[microsite_name] - - # pull in configuration information from each - # microsite root - - if os.path.isdir(microsite_root): - # store the path on disk for later use - microsite_config['microsite_root'] = microsite_root - - # get the domain that this should reside - domain = microsite_config['domain_prefix'] - - # get the virtual university that this should use - university = microsite_config['university'] - - # add to the existing maps in our settings - subdomain_branding[domain] = university - virtual_universities.append(university) - - template_dir = microsite_root / 'templates' - microsite_config['template_dir'] = template_dir - - microsite_config['microsite_name'] = microsite_name - - else: - # not sure if we have application logging at this stage of - # startup - print '**** Error loading microsite {0}. Directory does not exist'.format(microsite_root) - # remove from our configuration as it is not valid - del microsite_config_dict[microsite_name] - - # if we have microsites, then let's turn on SUBDOMAIN_BRANDING - # Note check size of the dict because some microsites might not be found on disk and - # we could be left with none - if microsite_config_dict: - FEATURES['SUBDOMAIN_BRANDING'] = True - - TEMPLATE_DIRS.append(microsites_root) - MAKO_TEMPLATES['main'].append(microsites_root) - - STATICFILES_DIRS.append(microsites_root) - - ################# Student Verification ################# VERIFY_STUDENT = { "DAYS_GOOD_FOR": 365, # How many days is a verficiation good for? diff --git a/lms/envs/test.py b/lms/envs/test.py index f72609050b..8c9b8a7ce6 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -299,16 +299,14 @@ MICROSITE_CONFIGURATION = { "course_index_overlay_text": "This is a Test Microsite Overlay Text.", "course_index_overlay_logo_file": "test_microsite/images/header-logo.png", "homepage_overlay_html": "

This is a Test Microsite Overlay HTML

" + }, + "default": { + "university": "default_university", + "domain_prefix": "www", } } - -if len(MICROSITE_CONFIGURATION.keys()) > 0: - enable_microsites( - MICROSITE_CONFIGURATION, - SUBDOMAIN_BRANDING, - VIRTUAL_UNIVERSITIES, - microsites_root=COMMON_ROOT / "test" / 'test_microsites' - ) +MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_microsites' +FEATURES['USE_MICROSITES'] = True ######### LinkedIn ######## LINKEDIN_API['COMPANY_ID'] = '0000000' diff --git a/lms/startup.py b/lms/startup.py index 7bf9cf7481..1c8cdc8ef7 100644 --- a/lms/startup.py +++ b/lms/startup.py @@ -9,6 +9,9 @@ settings.INSTALLED_APPS # pylint: disable=W0104 from django_startup import autostartup import edxmako +import logging + +log = logging.getLogger(__name__) def run(): @@ -20,6 +23,9 @@ def run(): if settings.FEATURES.get('USE_CUSTOM_THEME', False): enable_theme() + if settings.FEATURES.get('USE_MICROSITES', False): + enable_microsites() + def enable_theme(): """ @@ -51,3 +57,45 @@ def enable_theme(): settings.STATICFILES_DIRS.append( (u'themes/{}'.format(settings.THEME_NAME), theme_root / 'static') ) + + +def enable_microsites(): + """ + Enable the use of microsites, which are websites that allow + for subdomains for the edX platform, e.g. foo.edx.org + """ + + microsites_root = settings.MICROSITE_ROOT_DIR + microsite_config_dict = settings.MICROSITE_CONFIGURATION + + for ms_name, ms_config in microsite_config_dict.items(): + # Calculate the location of the microsite's files + ms_root = microsites_root / ms_name + ms_config = microsite_config_dict[ms_name] + + # pull in configuration information from each + # microsite root + + if ms_root.isdir(): + # store the path on disk for later use + ms_config['microsite_root'] = ms_root + + template_dir = ms_root / 'templates' + ms_config['template_dir'] = template_dir + + ms_config['microsite_name'] = ms_name + log.info('Loading microsite {0}'.format(ms_root)) + else: + # not sure if we have application logging at this stage of + # startup + log.error('Error loading microsite {0}. Directory does not exist'.format(ms_root)) + # remove from our configuration as it is not valid + del microsite_config_dict[ms_name] + + # if we have any valid microsites defined, let's wire in the Mako and STATIC_FILES search paths + if microsite_config_dict: + settings.TEMPLATE_DIRS.append(microsites_root) + settings.MAKO_TEMPLATES['main'].append(microsites_root) + edxmako.startup.run() + + settings.STATICFILES_DIRS.insert(0, microsites_root) diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index fe1af720e7..8023c6734b 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -12,7 +12,7 @@ cart_link = "" %> <%namespace name='static' file='../static_content.html'/> -<%! from microsite_configuration.middleware import MicrositeConfiguration %> +<%! from microsite_configuration import microsite %> <%inherit file="../main.html" /> @@ -20,13 +20,15 @@ <% if self.theme_enabled(): - google_analytics_file = u'../' + MicrositeConfiguration.get_microsite_configuration_value('google_analytics_file', 'theme-google-analytics.html') + google_analytics_file = u'../{ga}'.format( + ga=microsite.get_value('google_analytics_file', 'theme-google-analytics.html') + ) else: google_analytics_file = '../google_analytics.html' %> <%include file="${google_analytics_file}" /> - + ## OG (Open Graph) title and description added below to give social media info to display ## (https://developers.facebook.com/docs/opengraph/howtos/maximizing-distribution-media-content#tags) @@ -141,7 +143,7 @@ %endif ${_("You are registered for this course")} - + %if show_courseware_link: ${_("View Courseware")} @@ -169,7 +171,7 @@ % elif is_course_full: ${_("Course is full")} - + %else: ${_("Register for {course.display_number_with_default}").format(course=course) | h} @@ -216,7 +218,7 @@
- % if MicrositeConfiguration.get_microsite_configuration_value('course_about_show_social_links', True): + % if microsite.get_value('course_about_show_social_links', True):