diff --git a/cms/djangoapps/contentstore/views/course.py b/cms/djangoapps/contentstore/views/course.py
index d084859536..61a9a06d8e 100644
--- a/cms/djangoapps/contentstore/views/course.py
+++ b/cms/djangoapps/contentstore/views/course.py
@@ -53,6 +53,8 @@ from xmodule.modulestore.locator import BlockUsageLocator
from course_creators.views import get_course_creator_status, add_user_with_status_unrequested
from contentstore import utils
+from microsite_configuration.middleware import MicrositeConfiguration
+
__all__ = ['course_info_handler', 'course_handler', 'course_info_update_handler',
'settings_handler',
'grading_handler',
@@ -413,15 +415,21 @@ def settings_handler(request, tag=None, package_id=None, branch=None, version_gu
if 'text/html' in request.META.get('HTTP_ACCEPT', '') and request.method == 'GET':
upload_asset_url = locator.url_reverse('assets/')
+ # 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(
+ course_module.location.org,
+ 'ENABLE_MKTG_SITE',
+ settings.FEATURES.get('ENABLE_MKTG_SITE', False)
+ )
+
return render_to_response('settings.html', {
'context_course': course_module,
'course_locator': locator,
'lms_link_for_about_page': utils.get_lms_link_for_about_page(course_module.location),
'course_image_url': utils.course_image_url(course_module),
'details_url': locator.url_reverse('/settings/details/'),
- 'about_page_editable': not settings.FEATURES.get(
- 'ENABLE_MKTG_SITE', False
- ),
+ 'about_page_editable': about_page_editable,
'upload_asset_url': upload_asset_url
})
elif 'application/json' in request.META.get('HTTP_ACCEPT', ''):
diff --git a/cms/djangoapps/contentstore/views/public.py b/cms/djangoapps/contentstore/views/public.py
index c7857af0c0..79e1212e9d 100644
--- a/cms/djangoapps/contentstore/views/public.py
+++ b/cms/djangoapps/contentstore/views/public.py
@@ -10,6 +10,8 @@ from edxmako.shortcuts import render_to_response
from external_auth.views import ssl_login_shortcut
+from microsite_configuration.middleware import MicrositeConfiguration
+
__all__ = ['signup', 'login_page', 'howitworks']
@@ -29,10 +31,14 @@ def login_page(request):
Display the login form.
"""
csrf_token = csrf(request)['csrf_token']
- return render_to_response('login.html', {
- 'csrf': csrf_token,
- 'forgot_password_link': "//{base}/login#forgot-password-modal".format(base=settings.LMS_BASE),
- })
+ return render_to_response(
+ 'login.html',
+ {
+ '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),
+ }
+ )
def howitworks(request):
diff --git a/cms/envs/aws.py b/cms/envs/aws.py
index 1ee1c3be77..a24e25b74e 100644
--- a/cms/envs/aws.py
+++ b/cms/envs/aws.py
@@ -9,6 +9,7 @@ This is the default template for our main set of AWS servers.
import json
from .common import *
+
from logsettings import get_logger_config
import os
@@ -145,7 +146,6 @@ COURSES_WITH_UNSAFE_CODE = ENV_TOKENS.get("COURSES_WITH_UNSAFE_CODE", [])
#Timezone overrides
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE)
-
ENV_FEATURES = ENV_TOKENS.get('FEATURES', ENV_TOKENS.get('MITX_FEATURES', {}))
for feature, value in ENV_FEATURES.items():
FEATURES[feature] = value
@@ -213,3 +213,16 @@ BROKER_URL = "{0}://{1}:{2}@{3}/{4}".format(CELERY_BROKER_TRANSPORT,
# Event tracking
TRACKING_BACKENDS.update(AUTH_TOKENS.get("TRACKING_BACKENDS", {}))
+
+SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {})
+VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', [])
+
+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)
+ )
diff --git a/cms/envs/common.py b/cms/envs/common.py
index eb7d600c20..aa82ae5eed 100644
--- a/cms/envs/common.py
+++ b/cms/envs/common.py
@@ -25,7 +25,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
+from lms.envs.common import USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, enable_microsites
from path import path
from lms.lib.xblock.mixin import LmsBlockMixin
diff --git a/cms/envs/microsite_test.py b/cms/envs/microsite_test.py
new file mode 100644
index 0000000000..5eb2079da8
--- /dev/null
+++ b/cms/envs/microsite_test.py
@@ -0,0 +1,15 @@
+"""
+This is a localdev test for the Microsite processing pipeline
+"""
+# We intentionally define lots of variables that aren't used, and
+# want to import all variables from base settings files
+# 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)
diff --git a/common/djangoapps/edxmako/shortcuts.py b/common/djangoapps/edxmako/shortcuts.py
index 6cecb409e9..a52c852727 100644
--- a/common/djangoapps/edxmako/shortcuts.py
+++ b/common/djangoapps/edxmako/shortcuts.py
@@ -16,6 +16,8 @@ from django.template import Context
from django.http import HttpResponse
import logging
+from microsite_configuration.middleware import MicrositeConfiguration
+
import edxmako
import edxmako.middleware
from django.conf import settings
@@ -71,6 +73,10 @@ 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)
+
context_instance = Context(dictionary)
# add dictionary to context_instance
context_instance.update(dictionary or {})
@@ -98,5 +104,9 @@ def render_to_response(template_name, dictionary=None, context_instance=None, na
Returns a HttpResponse whose content is filled with the result of calling
lookup.get_template(args[0]).render with the passed arguments.
"""
+
+ # see if there is an override template defined in the microsite
+ template_name = MicrositeConfiguration.get_microsite_template_path(template_name)
+
dictionary = dictionary or {}
return HttpResponse(render_to_string(template_name, dictionary, context_instance, namespace), **kwargs)
diff --git a/common/djangoapps/microsite_configuration/__init__.py b/common/djangoapps/microsite_configuration/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/microsite_configuration/middleware.py b/common/djangoapps/microsite_configuration/middleware.py
new file mode 100644
index 0000000000..61d2cd9883
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/middleware.py
@@ -0,0 +1,183 @@
+"""
+This file implements the initial 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
+
+_microsite_configuration_threadlocal = threading.local()
+_microsite_configuration_threadlocal.data = {}
+
+
+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):
+ """
+ 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()
+
+ 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
+
+ # 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()
+ 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/student/views.py b/common/djangoapps/student/views.py
index aaf4ca8274..f5660935bf 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -71,6 +71,7 @@ from pytz import UTC
from util.json_request import JsonResponse
+from microsite_configuration.middleware import MicrositeConfiguration
log = logging.getLogger("edx.student")
AUDIT_LOG = logging.getLogger("audit")
@@ -250,7 +251,11 @@ def signin_user(request):
context = {
'course_id': request.GET.get('course_id'),
- 'enrollment_action': request.GET.get('enrollment_action')
+ 'enrollment_action': request.GET.get('enrollment_action'),
+ 'platform_name': MicrositeConfiguration.get_microsite_configuration_value(
+ 'platform_name',
+ settings.PLATFORM_NAME
+ ),
}
return render_to_response('login.html', context)
@@ -269,7 +274,11 @@ def register_user(request, extra_context=None):
context = {
'course_id': request.GET.get('course_id'),
- 'enrollment_action': request.GET.get('enrollment_action')
+ 'enrollment_action': request.GET.get('enrollment_action'),
+ 'platform_name': MicrositeConfiguration.get_microsite_configuration_value(
+ 'platform_name',
+ settings.PLATFORM_NAME
+ ),
}
if extra_context is not None:
context.update(extra_context)
@@ -311,9 +320,33 @@ def dashboard(request):
# longer exist (because the course IDs have changed). Still, we don't delete those
# enrollments, because it could have been a data push snafu.
course_enrollment_pairs = []
+
+ # 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')
+
+ # 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()
+
+ # remove our current Microsite from the "filter out" list, if applicable
+ if course_org_filter:
+ org_filter_out_set.remove(course_org_filter)
+
for enrollment in CourseEnrollment.enrollments_for_user(user):
try:
- course_enrollment_pairs.append((course_from_id(enrollment.course_id), enrollment))
+ course = course_from_id(enrollment.course_id)
+
+ # if we are in a Microsite, then filter out anything that is not
+ # attributed (by ORG) to that Microsite
+ if course_org_filter and course_org_filter != course.location.org:
+ continue
+ # Conversely, if we are not in a Microsite, then let's filter out any enrollments
+ # with courses attributed (by ORG) to Microsites
+ elif course.location.org in org_filter_out_set:
+ continue
+
+ course_enrollment_pairs.append((course, enrollment))
except ItemNotFoundError:
log.error("User {0} enrolled in non-existent course {1}"
.format(user.username, enrollment.course_id))
@@ -539,7 +572,11 @@ def accounts_login(request):
course_id = _parse_course_id_from_string(redirect_to)
if course_id and _get_course_enrollment_domain(course_id):
return external_auth.views.course_specific_login(request, course_id)
- return render_to_response('login.html')
+
+ context = {
+ 'platform_name': settings.PLATFORM_NAME,
+ }
+ return render_to_response('login.html', context)
# Need different levels of logging
@@ -899,26 +936,31 @@ def create_account(request, post_override=None):
return ret
(user, profile, registration) = ret
- d = {'name': post_vars['name'],
- 'key': registration.activation_key,
- }
+ context = {
+ 'name': post_vars['name'],
+ 'key': registration.activation_key,
+ }
# composes activation email
- subject = render_to_string('emails/activation_email_subject.txt', d)
+ subject = render_to_string('emails/activation_email_subject.txt', context)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
- message = render_to_string('emails/activation_email.txt', d)
+ message = render_to_string('emails/activation_email.txt', context)
# 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(
+ 'email_from_address',
+ settings.DEFAULT_FROM_EMAIL
+ )
try:
if settings.FEATURES.get('REROUTE_ACTIVATION_EMAIL'):
dest_addr = settings.FEATURES['REROUTE_ACTIVATION_EMAIL']
message = ("Activation for %s (%s): %s\n" % (user, user.email, profile.name) +
'-' * 80 + '\n\n' + message)
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [dest_addr], fail_silently=False)
+ send_mail(subject, message, from_address, [dest_addr], fail_silently=False)
else:
- _res = user.email_user(subject, message, settings.DEFAULT_FROM_EMAIL)
+ _res = user.email_user(subject, message, from_address)
except:
log.warning('Unable to send activation email to user', exc_info=True)
js['value'] = _('Could not send activation e-mail.')
@@ -1171,15 +1213,23 @@ def change_email_request(request):
return HttpResponse(json.dumps({'success': False,
'error': _('Old email is the same as the new email.')}))
- d = {'key': pec.activation_key,
- 'old_email': user.email,
- 'new_email': pec.new_email}
+ context = {
+ 'key': pec.activation_key,
+ 'old_email': user.email,
+ 'new_email': pec.new_email
+ }
- subject = render_to_string('emails/email_change_subject.txt', d)
+ subject = render_to_string('emails/email_change_subject.txt', context)
subject = ''.join(subject.splitlines())
- message = render_to_string('emails/email_change.txt', d)
- _res = send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [pec.new_email])
+ message = render_to_string('emails/email_change.txt', context)
+
+ from_address = MicrositeConfiguration.get_microsite_configuration_value(
+ 'email_from_address',
+ settings.DEFAULT_FROM_EMAIL
+ )
+
+ _res = send_mail(subject, message, from_address, [pec.new_email])
return HttpResponse(json.dumps({'success': True}))
diff --git a/common/djangoapps/util/request.py b/common/djangoapps/util/request.py
index 0950fa3b42..fc9c835194 100644
--- a/common/djangoapps/util/request.py
+++ b/common/djangoapps/util/request.py
@@ -1,5 +1,6 @@
""" Utility functions related to HTTP requests """
from django.conf import settings
+from microsite_configuration.middleware import MicrositeConfiguration
def safe_get_host(request):
@@ -14,4 +15,4 @@ def safe_get_host(request):
if isinstance(settings.ALLOWED_HOSTS, (list, tuple)) and '*' not in settings.ALLOWED_HOSTS:
return request.get_host()
else:
- return settings.SITE_NAME
+ return MicrositeConfiguration.get_microsite_configuration_value('site_domain', settings.SITE_NAME)
diff --git a/lms/djangoapps/branding/__init__.py b/lms/djangoapps/branding/__init__.py
index d70ffb1cc9..c63154356f 100644
--- a/lms/djangoapps/branding/__init__.py
+++ b/lms/djangoapps/branding/__init__.py
@@ -2,15 +2,10 @@ from xmodule.modulestore.django import modulestore
from xmodule.course_module import CourseDescriptor
from django.conf import settings
-
-def pick_subdomain(domain, options, default='default'):
- for option in options:
- if domain.startswith(option):
- return option
- return default
+from microsite_configuration.middleware import MicrositeConfiguration
-def get_visible_courses(domain=None):
+def get_visible_courses():
"""
Return the set of CourseDescriptors that should be visible in this branded instance
"""
@@ -20,31 +15,52 @@ def get_visible_courses(domain=None):
if isinstance(c, CourseDescriptor)]
courses = sorted(courses, key=lambda course: course.number)
- if domain and settings.FEATURES.get('SUBDOMAIN_COURSE_LISTINGS'):
- subdomain = pick_subdomain(domain, settings.COURSE_LISTINGS.keys())
- visible_ids = frozenset(settings.COURSE_LISTINGS[subdomain])
- return [course for course in courses if course.id in visible_ids]
+ subdomain = MicrositeConfiguration.get_microsite_configuration_value('subdomain')
+
+ # See if we have filtered course listings in this domain
+ filtered_visible_ids = None
+
+ # this is legacy format which is outside of the microsite feature
+ if hasattr(settings, 'COURSE_LISTINGS') and subdomain in settings.COURSE_LISTINGS:
+ filtered_visible_ids = frozenset(settings.COURSE_LISTINGS[subdomain])
+
+ filtered_by_org = MicrositeConfiguration.get_microsite_configuration_value('course_org_filter')
+
+ if filtered_by_org:
+ return [course for course in courses if course.location.org == filtered_by_org]
+ if filtered_visible_ids:
+ return [course for course in courses if course.id in filtered_visible_ids]
else:
- return courses
+ # 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()
+ return [course for course in courses if course.location.org not in org_filter_out_set]
-def get_university(domain=None):
+def get_university_for_request():
"""
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 = pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys())
- return settings.SUBDOMAIN_BRANDING.get(subdomain)
+ return MicrositeConfiguration.get_microsite_configuration_value('university')
-def get_logo_url(domain=None):
+def get_logo_url():
"""
Return the url for the branded logo image to be used
"""
- university = get_university(domain)
+
+ # 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')
+ if image_url:
+ return '{static_url}{image_url}'.format(
+ static_url=settings.STATIC_URL,
+ image_url=image_url
+ )
+
+ # otherwise, use the legacy means to configure this
+ university = MicrositeConfiguration.get_microsite_configuration_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 0adf733818..aa0f752e7d 100644
--- a/lms/djangoapps/branding/views.py
+++ b/lms/djangoapps/branding/views.py
@@ -6,8 +6,9 @@ from django_future.csrf import ensure_csrf_cookie
from edxmako.shortcuts import render_to_response
import student.views
-import branding
import courseware.views
+
+from microsite_configuration.middleware import MicrositeConfiguration
from edxmako.shortcuts import marketing_link
from util.cache import cache_if_anonymous
@@ -25,10 +26,19 @@ def index(request):
if settings.FEATURES.get('AUTH_USE_MIT_CERTIFICATES'):
from external_auth.views import ssl_login
return ssl_login(request)
- if settings.FEATURES.get('ENABLE_MKTG_SITE'):
+
+ enable_mktg_site = MicrositeConfiguration.get_microsite_configuration_value(
+ 'ENABLE_MKTG_SITE',
+ settings.FEATURES.get('ENABLE_MKTG_SITE', False)
+ )
+
+ if enable_mktg_site:
return redirect(settings.MKTG_URLS.get('ROOT'))
- university = branding.get_university(request.META.get('HTTP_HOST'))
+ university = MicrositeConfiguration.match_university(request.META.get('HTTP_HOST'))
+
+ # keep specialized logic for Edge until we can migrate over Edge to fully use
+ # microsite definitions
if university == 'edge':
context = {
'suppress_toplevel_navigation': True
@@ -49,7 +59,9 @@ 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
"""
- if settings.FEATURES.get('ENABLE_MKTG_SITE', False):
+ enable_mktg_site = settings.FEATURES.get('ENABLE_MKTG_SITE') or MicrositeConfiguration.get_microsite_configuration_value('ENABLE_MKTG_SITE', False)
+
+ if enable_mktg_site:
return redirect(marketing_link('COURSES'), permanent=True)
if not settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
diff --git a/lms/djangoapps/courseware/courses.py b/lms/djangoapps/courseware/courses.py
index c74b0f9e6f..fd8adf998b 100644
--- a/lms/djangoapps/courseware/courses.py
+++ b/lms/djangoapps/courseware/courses.py
@@ -294,7 +294,7 @@ def get_courses(user, domain=None):
'''
Returns a list of courses available, sorted by course.number
'''
- courses = branding.get_visible_courses(domain)
+ courses = branding.get_visible_courses()
courses = [c for c in courses if has_access(user, c, 'see_exists')]
courses = sorted(courses, key=lambda course: course.number)
diff --git a/lms/djangoapps/courseware/tests/test_microsites.py b/lms/djangoapps/courseware/tests/test_microsites.py
new file mode 100644
index 0000000000..0daffb611c
--- /dev/null
+++ b/lms/djangoapps/courseware/tests/test_microsites.py
@@ -0,0 +1,145 @@
+"""
+Tests related to the Microsites feature
+"""
+from django.core.urlresolvers import reverse
+from django.test.utils import override_settings
+from unittest import skip
+
+from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
+
+from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
+
+from helpers import LoginEnrollmentTestCase
+from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
+
+MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver'
+
+
+@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
+class TestMicrosites(ModuleStoreTestCase, LoginEnrollmentTestCase):
+ """
+ This is testing of the Microsite feature
+ """
+
+ STUDENT_INFO = [('view@test.com', 'foo'), ('view2@test.com', 'foo')]
+
+ def setUp(self):
+ # use a different hostname to test Microsites since they are
+ # triggered on subdomain mappings
+ #
+ # NOTE: The Microsite Configuration is in lms/envs/test.py. The content for the Test Microsite is in
+ # test_microsites/test_microsite.
+ #
+ # IMPORTANT: For these tests to work, this domain must be defined via
+ # DNS configuration (either local or published)
+
+ self.course = CourseFactory.create(display_name='Robot_Super_Course', org='TestMicrositeX')
+ self.chapter0 = ItemFactory.create(parent_location=self.course.location,
+ display_name='Overview')
+ self.chapter9 = ItemFactory.create(parent_location=self.course.location,
+ display_name='factory_chapter')
+ self.section0 = ItemFactory.create(parent_location=self.chapter0.location,
+ display_name='Welcome')
+ self.section9 = ItemFactory.create(parent_location=self.chapter9.location,
+ display_name='factory_section')
+
+ self.course_outside_microsite = CourseFactory.create(display_name='Robot_Course_Outside_Microsite', org='FooX')
+
+ def create_test_accounts(self):
+ """
+ Build out the test accounts we'll use in these tests
+ """
+ # Create student accounts and activate them.
+ for i in range(len(self.STUDENT_INFO)):
+ email, password = self.STUDENT_INFO[i]
+ username = 'u{0}'.format(i)
+ self.create_account(username, email, password)
+ self.activate_user(email)
+
+ @skip # skipping - runs fine on localdev, not jenkins environment
+ def test_microsite_anonymous_homepage_content(self):
+ """
+ Verify that the homepage, when accessed via a Microsite domain, returns
+ HTML that reflects the Microsite branding elements
+ """
+
+ resp = self.client.get('/', HTTP_HOST=MICROSITE_TEST_HOSTNAME)
+ self.assertEqual(resp.status_code, 200)
+
+ # assert various branding definitions on this Microsite
+ # as per the configuration and Microsite overrides
+
+ 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') # page title
+
+ # assert that test course display name is visible
+ self.assertContains(resp, 'Robot_Super_Course')
+
+ # assert that test course that is outside microsite is not visible
+ self.assertNotContains(resp, 'Robot_Course_Outside_Microsite')
+
+ # assert that footer template has been properly overriden on homepage
+ self.assertContains(resp, 'This is a Test Microsite footer')
+
+ # assert that the edX partners section is not in the HTML
+ self.assertNotContains(resp, '')
+
+ # assert that the edX partners tag line is not in the HTML
+ self.assertNotContains(resp, 'Explore free courses from')
+
+ @skip # skipping - runs fine on localdev, not jenkins environment
+ def test_not_microsite_anonymous_homepage_content(self):
+ """
+ Make sure we see the right content on the homepage if we are not in a microsite
+ """
+
+ resp = self.client.get('/')
+ self.assertEqual(resp.status_code, 200)
+
+ # assert various branding definitions on this Microsite ARE NOT VISIBLE
+
+ 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') # page title
+
+ # assert that test course display name IS NOT VISIBLE, since that is a Microsite only course
+ self.assertNotContains(resp, 'Robot_Super_Course')
+
+ # assert that test course that is outside microsite IS VISIBLE
+ self.assertContains(resp, 'Robot_Course_Outside_Microsite')
+
+ # assert that footer template has been properly overriden on homepage
+ self.assertNotContains(resp, 'This is a Test Microsite footer')
+
+ # assert that the edX partners section is not in the HTML
+ self.assertContains(resp, '')
+
+ # assert that the edX partners tag line is not in the HTML
+ self.assertContains(resp, 'Explore free courses from')
+
+ @skip # skipping - runs fine on localdev, not jenkins environment
+ def test_microsite_course_enrollment(self):
+ """
+ Enroll user in a course scoped in a Microsite and one course outside of a Microsite
+ and make sure that they are only visible in the right Dashboards
+ """
+
+ self.create_test_accounts()
+
+ email, password = self.STUDENT_INFO[0]
+ self.login(email, password)
+ self.enroll(self.course, True)
+ self.enroll(self.course_outside_microsite, True)
+
+ # Access the microsite dashboard and make sure the right courses appear
+ resp = self.client.get(reverse('dashboard'), HTTP_HOST=MICROSITE_TEST_HOSTNAME)
+ self.assertContains(resp, 'Robot_Super_Course')
+ self.assertNotContains(resp, 'Robot_Course_Outside_Microsite')
+
+ # Now access the non-microsite dashboard and make sure the right courses appear
+ resp = self.client.get(reverse('dashboard'))
+ self.assertNotContains(resp, 'Robot_Super_Course')
+ self.assertContains(resp, 'Robot_Course_Outside_Microsite')
diff --git a/lms/djangoapps/instructor/enrollment.py b/lms/djangoapps/instructor/enrollment.py
index a7c4fe0b65..5042f6996d 100644
--- a/lms/djangoapps/instructor/enrollment.py
+++ b/lms/djangoapps/instructor/enrollment.py
@@ -14,6 +14,8 @@ from student.models import CourseEnrollment, CourseEnrollmentAllowed
from courseware.models import StudentModule
from edxmako.shortcuts import render_to_string
+from microsite_configuration.middleware import MicrositeConfiguration
+
# For determining if a shibboleth course
SHIBBOLETH_DOMAIN_PREFIX = 'shib:'
@@ -223,22 +225,58 @@ def send_mail_to_student(student, param_dict):
Returns a boolean indicating whether the email was sent successfully.
"""
- email_template_dict = {'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'),
- 'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'),
- 'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'),
- 'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt')}
+ # add some helpers and microconfig subsitutions
+ if 'course' in param_dict:
+ param_dict['course_name'] = param_dict['course'].display_name_with_default
- subject_template, message_template = email_template_dict.get(param_dict['message'], (None, None))
+ param_dict['site_name'] = MicrositeConfiguration.get_microsite_configuration_value(
+ 'SITE_NAME',
+ param_dict['site_name']
+ )
+
+ subject = None
+ message = None
+
+ # see if we are running in a microsite and that there is an
+ # activation email template definition available as configuration, if so, then render that
+ message_type = param_dict['message']
+
+ email_template_dict = {
+ 'allowed_enroll': (
+ 'emails/enroll_email_allowedsubject.txt',
+ 'emails/enroll_email_allowedmessage.txt'
+ ),
+ 'enrolled_enroll': (
+ 'emails/enroll_email_enrolledsubject.txt',
+ 'emails/enroll_email_enrolledmessage.txt'
+ ),
+ 'allowed_unenroll': (
+ 'emails/unenroll_email_subject.txt',
+ 'emails/unenroll_email_allowedmessage.txt'
+ ),
+ 'enrolled_unenroll': (
+ 'emails/unenroll_email_subject.txt',
+ 'emails/unenroll_email_enrolledmessage.txt'
+ )
+ }
+
+ subject_template, message_template = email_template_dict.get(message_type, (None, None))
if subject_template is not None and message_template is not None:
subject = render_to_string(subject_template, param_dict)
message = render_to_string(message_template, param_dict)
+ if subject and message:
# Remove leading and trailing whitespace from body
message = message.strip()
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [student], fail_silently=False)
+ from_address = MicrositeConfiguration.get_microsite_configuration_value(
+ 'email_from_address',
+ settings.DEFAULT_FROM_EMAIL
+ )
+
+ send_mail(subject, message, from_address, [student], fail_silently=False)
def uses_shib(course):
diff --git a/lms/djangoapps/instructor/views/legacy.py b/lms/djangoapps/instructor/views/legacy.py
index 7f1d6e182f..4b54d037fc 100644
--- a/lms/djangoapps/instructor/views/legacy.py
+++ b/lms/djangoapps/instructor/views/legacy.py
@@ -63,6 +63,8 @@ from xblock.fields import ScopeIds
from django.utils.translation import ugettext as _u
from lms.lib.xblock.runtime import handler_prefix
+from microsite_configuration.middleware import MicrositeConfiguration
+
log = logging.getLogger(__name__)
# internal commands for managing forum roles:
@@ -1282,7 +1284,10 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll
ceaset.delete()
if email_students:
- stripped_site_name = settings.SITE_NAME
+ stripped_site_name = MicrositeConfiguration.get_microsite_configuration_value(
+ 'SITE_NAME',
+ settings.SITE_NAME
+ )
registration_url = 'https://' + stripped_site_name + reverse('student.views.register_user')
#Composition of email
d = {'site_name': stripped_site_name,
@@ -1291,7 +1296,7 @@ def _do_enroll_students(course, course_id, students, overload=False, auto_enroll
'auto_enroll': auto_enroll,
'course_url': 'https://' + stripped_site_name + '/courses/' + course_id,
'course_about_url': 'https://' + stripped_site_name + '/courses/' + course_id + '/about',
- 'is_shib_course': is_shib_course,
+ 'is_shib_course': is_shib_course
}
for student in new_students:
@@ -1373,7 +1378,10 @@ 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 = settings.SITE_NAME
+ stripped_site_name = MicrositeConfiguration.get_microsite_configuration_value(
+ 'SITE_NAME',
+ settings.SITE_NAME
+ )
if email_students:
course = course_from_id(course_id)
#Composition of email
@@ -1447,22 +1455,43 @@ def send_mail_to_student(student, param_dict):
Returns a boolean indicating whether the email was sent successfully.
"""
- EMAIL_TEMPLATE_DICT = {'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'),
- 'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'),
- 'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'),
- 'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt')}
+ # 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(
+ 'SITE_NAME',
+ param_dict.get('site_name', '')
+ )
- subject_template, message_template = EMAIL_TEMPLATE_DICT.get(param_dict['message'], (None, None))
+ subject = None
+ message = None
+
+ message_type = param_dict['message']
+
+ email_template_dict = {
+ 'allowed_enroll': ('emails/enroll_email_allowedsubject.txt', 'emails/enroll_email_allowedmessage.txt'),
+ 'enrolled_enroll': ('emails/enroll_email_enrolledsubject.txt', 'emails/enroll_email_enrolledmessage.txt'),
+ 'allowed_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_allowedmessage.txt'),
+ 'enrolled_unenroll': ('emails/unenroll_email_subject.txt', 'emails/unenroll_email_enrolledmessage.txt'),
+ }
+
+ subject_template, message_template = email_template_dict.get(message_type, (None, None))
if subject_template is not None and message_template is not None:
subject = render_to_string(subject_template, param_dict)
message = render_to_string(message_template, param_dict)
+ if subject and message:
# Remove leading and trailing whitespace from body
message = message.strip()
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
- send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [student], fail_silently=False)
+ from_address = MicrositeConfiguration.get_microsite_configuration_value(
+ 'email_from_address',
+ settings.DEFAULT_FROM_EMAIL
+ )
+
+ send_mail(subject, message, from_address, [student], fail_silently=False)
return True
else:
diff --git a/lms/djangoapps/shoppingcart/models.py b/lms/djangoapps/shoppingcart/models.py
index 7647b2c487..562ad08d82 100644
--- a/lms/djangoapps/shoppingcart/models.py
+++ b/lms/djangoapps/shoppingcart/models.py
@@ -32,6 +32,8 @@ from verify_student.models import SoftwareSecurePhotoVerification
from .exceptions import (InvalidCartItem, PurchasedCallbackException, ItemAlreadyInCartException,
AlreadyEnrolledInCourseException, CourseDoesNotExistException)
+from microsite_configuration.middleware import MicrositeConfiguration
+
log = logging.getLogger("shoppingcart")
ORDER_STATUSES = (
@@ -161,14 +163,22 @@ class Order(models.Model):
# send confirmation e-mail
subject = _("Order Payment Confirmation")
- message = render_to_string('emails/order_confirmation_email.txt', {
- 'order': self,
- 'order_items': orderitems,
- 'has_billing_info': settings.FEATURES['STORE_BILLING_INFO']
- })
+ message = render_to_string(
+ 'emails/order_confirmation_email.txt',
+ {
+ 'order': self,
+ 'order_items': orderitems,
+ 'has_billing_info': settings.FEATURES['STORE_BILLING_INFO']
+ }
+ )
try:
+ from_address = MicrositeConfiguration.get_microsite_configuration_value(
+ 'email_from_address',
+ settings.DEFAULT_FROM_EMAIL
+ )
+
send_mail(subject, message,
- settings.DEFAULT_FROM_EMAIL, [self.user.email]) # pylint: disable=E1101
+ from_address, [self.user.email]) # pylint: disable=E1101
except (smtplib.SMTPException, BotoServerError): # sadly need to handle diff. mail backends individually
log.error('Failed sending confirmation e-mail for order %d', self.id) # pylint: disable=E1101
@@ -523,7 +533,10 @@ class CertificateItem(OrderItem):
user_email=course_enrollment.user.email,
order_number=order_number)
to_email = [settings.PAYMENT_SUPPORT_EMAIL]
- from_email = [settings.PAYMENT_SUPPORT_EMAIL]
+ from_email = [MicrositeConfiguration.get_microsite_configuration_value(
+ 'payment_support_email',
+ settings.PAYMENT_SUPPORT_EMAIL
+ )]
try:
send_mail(subject, message, from_email, to_email, fail_silently=False)
except (smtplib.SMTPException, BotoServerError):
diff --git a/lms/envs/aws.py b/lms/envs/aws.py
index ad6f9463d8..f598416cb7 100644
--- a/lms/envs/aws.py
+++ b/lms/envs/aws.py
@@ -337,3 +337,13 @@ VERIFY_STUDENT = AUTH_TOKENS.get("VERIFY_STUDENT", VERIFY_STUDENT)
GRADES_DOWNLOAD_ROUTING_KEY = HIGH_MEM_QUEUE
GRADES_DOWNLOAD = ENV_TOKENS.get("GRADES_DOWNLOAD", GRADES_DOWNLOAD)
+
+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)
+ )
diff --git a/lms/envs/cms/microsite_test.py b/lms/envs/cms/microsite_test.py
new file mode 100644
index 0000000000..511b97e66a
--- /dev/null
+++ b/lms/envs/cms/microsite_test.py
@@ -0,0 +1,38 @@
+"""
+This is a localdev test for the Microsite processing pipeline
+"""
+# We intentionally define lots of variables that aren't used, and
+# want to import all variables from base settings files
+# pylint: disable=W0401, W0614
+
+from .dev import *
+from .dev import SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES
+
+
+MICROSITE_CONFIGURATION = {
+ "openedx": {
+ "domain_prefix": "openedx",
+ "university": "openedx",
+ "platform_name": "Open edX",
+ "logo_image_url": "openedx/images/header-logo.png",
+ "email_from_address": "openedx@edx.org",
+ "payment_support_email": "openedx@edx.org",
+ "ENABLE_MKTG_SITE": False,
+ "SITE_NAME": "openedx.localhost",
+ "course_org_filter": "CDX",
+ "course_about_show_social_links": False,
+ "css_overrides_file": "openedx/css/openedx.css",
+ "show_partners": False,
+ "show_homepage_promo_video": False,
+ "course_index_overlay_text": "Explore free courses from leading universities.",
+ "course_index_overlay_logo_file": "openedx/images/header-logo.png",
+ "homepage_overlay_html": "Take an Open edX Course
"
+ },
+}
+
+if len(MICROSITE_CONFIGURATION.keys()) > 0:
+ enable_microsites(
+ MICROSITE_CONFIGURATION,
+ SUBDOMAIN_BRANDING,
+ VIRTUAL_UNIVERSITIES
+ )
diff --git a/lms/envs/common.py b/lms/envs/common.py
index 150b9ab8b8..716f11ae2e 100644
--- a/lms/envs/common.py
+++ b/lms/envs/common.py
@@ -25,6 +25,7 @@ Longer TODO:
import sys
import os
+import json
from path import path
@@ -377,7 +378,7 @@ TRACKING_ENABLED = True
######################## subdomain specific settings ###########################
COURSE_LISTINGS = {}
SUBDOMAIN_BRANDING = {}
-
+VIRTUAL_UNIVERSITIES = []
############################### XModule Store ##################################
MODULESTORE = {
@@ -617,6 +618,7 @@ TEMPLATE_LOADERS = (
MIDDLEWARE_CLASSES = (
'request_cache.middleware.RequestCache',
+ 'microsite_configuration.middleware.MicrositeConfiguration',
'django_comment_client.middleware.AjaxExceptionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
@@ -1048,6 +1050,64 @@ 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/dev.py b/lms/envs/dev.py
index 30053d3da3..6b4446893b 100644
--- a/lms/envs/dev.py
+++ b/lms/envs/dev.py
@@ -130,6 +130,8 @@ SUBDOMAIN_BRANDING = {
'mit': 'MITx',
'berkeley': 'BerkeleyX',
'harvard': 'HarvardX',
+ 'openedx': 'openedx',
+ 'edge': 'edge',
}
# List of `university` landing pages to display, even though they may not
diff --git a/lms/envs/test.py b/lms/envs/test.py
index dd52d2e76f..9577fa571a 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -277,3 +277,33 @@ PASSWORD_HASHERS = (
import openid.oidutil
openid.oidutil.log = lambda message, level = 0: None
+
+# 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
"
+ }
+}
+
+if len(MICROSITE_CONFIGURATION.keys()) > 0:
+ enable_microsites(
+ MICROSITE_CONFIGURATION,
+ SUBDOMAIN_BRANDING,
+ VIRTUAL_UNIVERSITIES,
+ microsites_root=ENV_ROOT / 'edx-platform' / 'test_microsites'
+ )
diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html
index 81e9e2507f..c042ba31bd 100644
--- a/lms/templates/courseware/course_about.html
+++ b/lms/templates/courseware/course_about.html
@@ -11,15 +11,20 @@
cart_link = ""
%>
<%namespace name='static' file='../static_content.html'/>
+<%! from microsite_configuration.middleware import MicrositeConfiguration %>
<%inherit file="../main.html" />
<%block name="headextra">
- % if self.theme_enabled():
- <%include file="../theme-google-analytics.html" />
- % else:
- <%include file="../google_analytics.html" />
- % endif
+
+ <%
+ if self.theme_enabled():
+ google_analytics_file = u'../' + MicrositeConfiguration.get_microsite_configuration_value('google_analytics_file', 'theme-google-analytics.html')
+ else:
+ google_analytics_file = '../google_analytics.html'
+ %>
+
+ <%include file="${google_analytics_file}" />
%block>
<%block name="js_extra">
@@ -196,6 +201,7 @@