Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@
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',
Expand Down Expand Up @@ -414,15 +416,19 @@ def settings_handler(request, tag=None, course_id=None, branch=None, version_gui
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', ''):
Expand Down
3 changes: 2 additions & 1 deletion cms/envs/aws.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@
#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
Expand Down Expand Up @@ -213,3 +212,5 @@

# Event tracking
TRACKING_BACKENDS.update(AUTH_TOKENS.get("TRACKING_BACKENDS", {}))

MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
4 changes: 4 additions & 0 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import sys
import lms.envs.common
from lms.envs.common import USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL
from lms.envs.common import MICROSITE_CONFIGURATION as _MICROSITE_CONFIGURATION
from path import path

from lms.lib.xblock.mixin import LmsBlockMixin
Expand Down Expand Up @@ -448,3 +449,6 @@
'url': "http://video.google.com/timedtext",
'params': {'lang': 'en', 'v': 'set_youtube_id_of_11_symbols_here'}
}

# pull in the Microsite definitions which are in a separate file
MICROSITE_CONFIGURATION = _MICROSITE_CONFIGURATION
2 changes: 2 additions & 0 deletions cms/envs/dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,5 @@
from .private import * # pylint: disable=F0401
except ImportError:
pass


Empty file.
190 changes: 190 additions & 0 deletions common/djangoapps/microsite_configuration/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
"""
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

from django.conf import settings

from mako.template import Template
from mako.runtime import Context
from StringIO import StringIO

_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 hasattr(settings, "MICROSITE_CONFIGURATION") and settings.MICROSITE_CONFIGURATION


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() != None

@classmethod
def has_microsite_email_template_definition(cls, template_name):
"""
"""
return cls.is_request_in_microsite() and cls.get_microsite_email_template_definitions(
template_name) != None

@classmethod
def get_microsite_configuration(cls):
"""
Returns the current request's microsite configuration
"""
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_email_template_definitions(cls, template_name):
"""
Returns the template definitions associated with a Microsite
"""
configuration = cls.get_microsite_configuration()
if configuration and 'email_templates' in configuration:
return configuration['email_templates'].get(template_name, None)

return None

@classmethod
def render_microsite_email_template(cls, template_name, params):
"""
Returns a string pair which is a rendered version of an email template of a given name
"""
subject = None
message = None

email_template_definitions = cls.get_microsite_email_template_definitions(template_name)

if email_template_definitions:
# inject a few additional parameters that should be available to all
# email templates
p = params.copy()
p['site_domain'] = cls.get_microsite_configuration_value('site_domain')
p['platform_name'] = cls.get_microsite_configuration_value('platform_name')

buf = StringIO()
ctx = Context(buf, **p)

subject_template = Template(email_template_definitions.get('subject', None))
if subject_template:
subject_template.render_context(ctx)
subject = buf.getvalue()

buf.truncate(0)
message_template = Template(email_template_definitions.get('body', None))
if message_template:
message_template.render_context(ctx)
message = buf.getvalue()

return subject, message

@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

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 'Univserity' and any corresponding microsite configuration information
"""
self.clear_microsite_configuration()

domain = request.META.get('HTTP_HOST', None)

if settings.FEATURES['SUBDOMAIN_BRANDING'] and domain:
subdomain = self.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys())
university = self.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

def match_university(self, 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 = self.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys())
return settings.SUBDOMAIN_BRANDING.get(subdomain)

def pick_subdomain(self, 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
62 changes: 51 additions & 11 deletions common/djangoapps/student/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
from ratelimitbackend.exceptions import RateLimitException

from edxmako.shortcuts import render_to_response, render_to_string
from edxmako.template import Template
from mako.runtime import Context
from StringIO import StringIO

from course_modes.models import CourseMode
from student.models import (
Expand Down Expand Up @@ -71,6 +74,7 @@

from util.json_request import JsonResponse

from microsite_configuration.middleware import MicrositeConfiguration

log = logging.getLogger("edx.student")
AUDIT_LOG = logging.getLogger("audit")
Expand Down Expand Up @@ -311,9 +315,22 @@ 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')
show_only_org_on_student_dashboard = MicrositeConfiguration.get_microsite_configuration_value(
'show_only_org_on_student_dashboard')

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 course_org_filter and show_only_org_on_student_dashboard and course_org_filter != course.location.org:
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))
Expand Down Expand Up @@ -903,22 +920,29 @@ def create_account(request, post_override=None):
'key': registration.activation_key,
}

# composes activation email
subject = render_to_string('emails/activation_email_subject.txt', d)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d)
# 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
if MicrositeConfiguration.has_microsite_email_template_definition('activation_email'):
subject, message = MicrositeConfiguration.render_microsite_email_template('activation_email', d)
else:
# composes activation email
subject = render_to_string('emails/activation_email_subject.txt', d)
# Email subject *must not* contain newlines
subject = ''.join(subject.splitlines())
message = render_to_string('emails/activation_email.txt', d)

# 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.')
Expand Down Expand Up @@ -1175,11 +1199,27 @@ def change_email_request(request):
'old_email': user.email,
'new_email': pec.new_email}

subject = render_to_string('emails/email_change_subject.txt', d)
# see if there are email templates defined for this microsite
email_templates = MicrositeConfiguration.get_microsite_configuration_value(
'email_template_files'
)

if email_templates:
subject_template, message_template = email_templates['email_change']
else:
# fallback to default system templates
subject_template = 'emails/email_change_subject.txt'
message_template = 'emails/email_change.txt'

subject = render_to_string(subject_template, d)
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(message_template, d)

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}))

Expand Down
Loading