Files
edx-platform/lms/startup.py
njdup 32bbb0e71a Implements keyword sub feature for bulk emails
This commit pulls in changes from #4487 that implements keyword
substitution for bulk emails. With these changes, an instructor can
include keywords in their bulk emails which will be automatically substituted
with the corresponding value for the recepient of the email. Keywords are
of the form %%keyword%%, and the keywords implemented in this commit include:

%%USER_ID%% => anonymous_user_id
%%USER_FULLNAME%% => user profile name
%%COURSE_DISPLAY_NAME%% => display name of the course
%%COURSE_END_DATE%% => end date of the course

Client-side validations have also been implemented to ensure that only emails
with well-formed keywords can be sent.
The architecture is designed such that adding in new keywords in the future
would be relatively straight-forward.
2014-12-11 15:03:29 -08:00

200 lines
6.5 KiB
Python

"""
Module for code that should run during LMS startup
"""
# pylint: disable=unused-argument
from django.conf import settings
# Force settings to run so that the python path is modified
settings.INSTALLED_APPS # pylint: disable=pointless-statement
from django_startup import autostartup
import edxmako
import logging
from monkey_patch import django_utils_translation
import analytics
from util import keyword_substitution
log = logging.getLogger(__name__)
def run():
"""
Executed during django startup
"""
django_utils_translation.patch()
autostartup()
add_mimetypes()
if settings.FEATURES.get('USE_CUSTOM_THEME', False):
enable_theme()
if settings.FEATURES.get('USE_MICROSITES', False):
enable_microsites()
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH', False):
enable_third_party_auth()
# Initialize Segment.io analytics module. Flushes first time a message is received and
# every 50 messages thereafter, or if 10 seconds have passed since last flush
if settings.FEATURES.get('SEGMENT_IO_LMS') and hasattr(settings, 'SEGMENT_IO_LMS_KEY'):
analytics.init(settings.SEGMENT_IO_LMS_KEY, flush_at=50)
# Monkey patch the keyword function map
if keyword_substitution.keyword_function_map_is_empty():
keyword_substitution.add_keyword_function_map(get_keyword_function_map())
# Once keyword function map is set, make update function do nothing
keyword_substitution.add_keyword_function_map = lambda x: None
def add_mimetypes():
"""
Add extra mimetypes. Used in xblock_resource.
If you add a mimetype here, be sure to also add it in cms/startup.py.
"""
import mimetypes
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
mimetypes.add_type('application/x-font-opentype', '.otf')
mimetypes.add_type('application/x-font-ttf', '.ttf')
mimetypes.add_type('application/font-woff', '.woff')
def enable_theme():
"""
Enable the settings for a custom theme, whose files should be stored
in ENV_ROOT/themes/THEME_NAME (e.g., edx_all/themes/stanford).
"""
# Workaround for setting THEME_NAME to an empty
# string which is the default due to this ansible
# bug: https://github.com/ansible/ansible/issues/4812
if settings.THEME_NAME == "":
settings.THEME_NAME = None
return
assert settings.FEATURES['USE_CUSTOM_THEME']
settings.FAVICON_PATH = 'themes/{name}/images/favicon.ico'.format(
name=settings.THEME_NAME
)
# Calculate the location of the theme's files
theme_root = settings.ENV_ROOT / "themes" / settings.THEME_NAME
# Include the theme's templates in the template search paths
settings.TEMPLATE_DIRS.insert(0, theme_root / 'templates')
edxmako.paths.add_lookup('main', theme_root / 'templates', prepend=True)
# Namespace the theme's static files to 'themes/<theme_name>' to
# avoid collisions with default edX static files
settings.STATICFILES_DIRS.append(
(u'themes/{}'.format(settings.THEME_NAME), theme_root / 'static')
)
# Include theme locale path for django translations lookup
settings.LOCALE_PATHS = (theme_root / 'conf/locale',) + settings.LOCALE_PATHS
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)
edxmako.paths.add_lookup('main', microsites_root)
settings.STATICFILES_DIRS.insert(0, microsites_root)
def enable_third_party_auth():
"""
Enable the use of third_party_auth, which allows users to sign in to edX
using other identity providers. For configuration details, see
common/djangoapps/third_party_auth/settings.py.
"""
from third_party_auth import settings as auth_settings
auth_settings.apply_settings(settings.THIRD_PARTY_AUTH, settings)
def get_keyword_function_map():
"""
Define the mapping of keywords and filtering functions
The functions are used to filter html, text and email strings
before rendering them.
The generated map will be monkey-patched onto the keyword_substitution
module so that it persists along with the running server.
Each function must take: user & course as parameters
"""
from student.models import anonymous_id_for_user
from util.date_utils import get_default_time_display
def user_id_sub(user, course):
"""
Gives the anonymous id for the given user
For compatibility with the existing anon_ids, return anon_id without course_id
"""
return anonymous_id_for_user(user, None)
def user_fullname_sub(user, course=None):
""" Returns the given user's name """
return user.profile.name
def course_display_name_sub(user, course):
""" Returns the course's display name """
return course.display_name
def course_end_date_sub(user, course):
""" Returns the course end date in the default display """
return get_default_time_display(course.end)
# Define keyword -> function map
# Take care that none of these functions return %% encoded keywords
kf_map = {
'%%USER_ID%%': user_id_sub,
'%%USER_FULLNAME%%': user_fullname_sub,
'%%COURSE_DISPLAY_NAME%%': course_display_name_sub,
'%%COURSE_END_DATE%%': course_end_date_sub
}
return kf_map