",
+ "ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER": False,
+ "COURSE_CATALOG_VISIBILITY_PERMISSION": "see_in_catalog",
+ "COURSE_ABOUT_VISIBILITY_PERMISSION": "see_about_page",
+ "ENABLE_SHOPPING_CART": True,
+ "ENABLE_PAID_COURSE_REGISTRATION": True,
+ "SESSION_COOKIE_DOMAIN": "test_logistration.localhost",
},
"default": {
"university": "default_university",
"domain_prefix": "www",
}
}
-MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_microsites'
-FEATURES['USE_MICROSITES'] = True
+MICROSITE_TEST_HOSTNAME = 'testmicrosite.testserver'
+MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
# For consistency in user-experience, keep the value of this setting in sync with
# the one in lms/envs/test.py
diff --git a/cms/static/images/bg-micro-stripes.png b/cms/static/images/bg-micro-stripes.png
index 4e9cca3e3c..46918a224f 100644
Binary files a/cms/static/images/bg-micro-stripes.png and b/cms/static/images/bg-micro-stripes.png differ
diff --git a/cms/static/images/choice-example.png b/cms/static/images/choice-example.png
index ee136577a9..1c44d32819 100644
Binary files a/cms/static/images/choice-example.png and b/cms/static/images/choice-example.png differ
diff --git a/cms/static/images/drag-handles.png b/cms/static/images/drag-handles.png
index 391a64dbe0..f3ae6868b3 100644
Binary files a/cms/static/images/drag-handles.png and b/cms/static/images/drag-handles.png differ
diff --git a/cms/static/images/explanation-example.png b/cms/static/images/explanation-example.png
index 94db245515..59a1a3eaed 100644
Binary files a/cms/static/images/explanation-example.png and b/cms/static/images/explanation-example.png differ
diff --git a/cms/static/images/header-example.png b/cms/static/images/header-example.png
index 732e816a15..bef190540a 100644
Binary files a/cms/static/images/header-example.png and b/cms/static/images/header-example.png differ
diff --git a/cms/static/images/hiw-feature1.png b/cms/static/images/hiw-feature1.png
index 3cfd48d066..ffb2991545 100644
Binary files a/cms/static/images/hiw-feature1.png and b/cms/static/images/hiw-feature1.png differ
diff --git a/cms/static/images/hiw-feature2.png b/cms/static/images/hiw-feature2.png
index 9442325dd5..18a5fd8018 100644
Binary files a/cms/static/images/hiw-feature2.png and b/cms/static/images/hiw-feature2.png differ
diff --git a/cms/static/images/hiw-feature3.png b/cms/static/images/hiw-feature3.png
index fa6b81ae89..69985fd08d 100644
Binary files a/cms/static/images/hiw-feature3.png and b/cms/static/images/hiw-feature3.png differ
diff --git a/cms/static/images/large-advanced-icon.png b/cms/static/images/large-advanced-icon.png
index 97442f5aa0..4e15c9e558 100644
Binary files a/cms/static/images/large-advanced-icon.png and b/cms/static/images/large-advanced-icon.png differ
diff --git a/cms/static/images/large-discussion-icon.png b/cms/static/images/large-discussion-icon.png
index cebf332769..82a7b36838 100644
Binary files a/cms/static/images/large-discussion-icon.png and b/cms/static/images/large-discussion-icon.png differ
diff --git a/cms/static/images/large-html-icon.png b/cms/static/images/large-html-icon.png
index 8f576178b2..0aaf73ac15 100644
Binary files a/cms/static/images/large-html-icon.png and b/cms/static/images/large-html-icon.png differ
diff --git a/cms/static/images/large-problem-icon.png b/cms/static/images/large-problem-icon.png
index a30ab8eac8..21daa0c70c 100644
Binary files a/cms/static/images/large-problem-icon.png and b/cms/static/images/large-problem-icon.png differ
diff --git a/cms/static/images/large-video-icon.png b/cms/static/images/large-video-icon.png
index f1ab048b4c..b9d3b07342 100644
Binary files a/cms/static/images/large-video-icon.png and b/cms/static/images/large-video-icon.png differ
diff --git a/cms/static/images/multi-example.png b/cms/static/images/multi-example.png
index abe729a94b..fe467bb2a9 100644
Binary files a/cms/static/images/multi-example.png and b/cms/static/images/multi-example.png differ
diff --git a/cms/static/images/number-example.png b/cms/static/images/number-example.png
index 7cd050cb5e..7ed95f9b6e 100644
Binary files a/cms/static/images/number-example.png and b/cms/static/images/number-example.png differ
diff --git a/cms/static/images/preview-lms-staticpages.png b/cms/static/images/preview-lms-staticpages.png
index d27196ddc0..ec64c1a0e6 100644
Binary files a/cms/static/images/preview-lms-staticpages.png and b/cms/static/images/preview-lms-staticpages.png differ
diff --git a/cms/static/images/problem-editor-icons.png b/cms/static/images/problem-editor-icons.png
index 27eb57b668..2d958fb96a 100644
Binary files a/cms/static/images/problem-editor-icons.png and b/cms/static/images/problem-editor-icons.png differ
diff --git a/cms/static/images/search-icon.png b/cms/static/images/search-icon.png
index 7368f803d5..97bd096da6 100644
Binary files a/cms/static/images/search-icon.png and b/cms/static/images/search-icon.png differ
diff --git a/cms/static/images/select-example.png b/cms/static/images/select-example.png
index ef80e629de..ece1e7174d 100644
Binary files a/cms/static/images/select-example.png and b/cms/static/images/select-example.png differ
diff --git a/cms/static/images/string-example.png b/cms/static/images/string-example.png
index 6f628b20d4..b4bb0f0b42 100644
Binary files a/cms/static/images/string-example.png and b/cms/static/images/string-example.png differ
diff --git a/cms/static/images/studio-logo.png b/cms/static/images/studio-logo.png
index 94099e3650..640d80b956 100644
Binary files a/cms/static/images/studio-logo.png and b/cms/static/images/studio-logo.png differ
diff --git a/cms/static/images/thumb-hiw-feature1.png b/cms/static/images/thumb-hiw-feature1.png
index b2dc0c00ee..62d01efa64 100644
Binary files a/cms/static/images/thumb-hiw-feature1.png and b/cms/static/images/thumb-hiw-feature1.png differ
diff --git a/cms/static/images/thumb-hiw-feature2.png b/cms/static/images/thumb-hiw-feature2.png
index e96bcad1aa..66d0d306c5 100644
Binary files a/cms/static/images/thumb-hiw-feature2.png and b/cms/static/images/thumb-hiw-feature2.png differ
diff --git a/cms/static/images/thumb-hiw-feature3.png b/cms/static/images/thumb-hiw-feature3.png
index f694fca516..9ebbf1aed7 100644
Binary files a/cms/static/images/thumb-hiw-feature3.png and b/cms/static/images/thumb-hiw-feature3.png differ
diff --git a/common/djangoapps/course_modes/migrations/0005_auto_20151217_0958.py b/common/djangoapps/course_modes/migrations/0005_auto_20151217_0958.py
index 0cc768b1c0..1e6e3f7e82 100644
--- a/common/djangoapps/course_modes/migrations/0005_auto_20151217_0958.py
+++ b/common/djangoapps/course_modes/migrations/0005_auto_20151217_0958.py
@@ -11,13 +11,18 @@ class Migration(migrations.Migration):
]
operations = [
- migrations.RemoveField(
- model_name='coursemode',
- name='expiration_datetime',
- ),
- migrations.AddField(
- model_name='coursemode',
- name='_expiration_datetime',
- field=models.DateTimeField(db_column=b'expiration_datetime', default=None, blank=True, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline'),
- ),
+ migrations.SeparateDatabaseAndState(
+ database_operations=[],
+ state_operations=[
+ migrations.RemoveField(
+ model_name='coursemode',
+ name='expiration_datetime',
+ ),
+ migrations.AddField(
+ model_name='coursemode',
+ name='_expiration_datetime',
+ field=models.DateTimeField(db_column=b'expiration_datetime', default=None, blank=True, help_text='OPTIONAL: After this date/time, users will no longer be able to enroll in this mode. Leave this blank if users can enroll in this mode until enrollment closes for the course.', null=True, verbose_name='Upgrade Deadline'),
+ ),
+ ]
+ )
]
diff --git a/common/djangoapps/edxmako/paths.py b/common/djangoapps/edxmako/paths.py
index 18ddb9eafd..3e7bb40430 100644
--- a/common/djangoapps/edxmako/paths.py
+++ b/common/djangoapps/edxmako/paths.py
@@ -10,6 +10,7 @@ import pkg_resources
from django.conf import settings
from mako.lookup import TemplateLookup
+from microsite_configuration import microsite
from . import LOOKUP
@@ -46,6 +47,18 @@ class DynamicTemplateLookup(TemplateLookup):
self._collection.clear()
self._uri_cache.clear()
+ def get_template(self, uri):
+ """
+ Overridden method which will hand-off the template lookup to the microsite subsystem
+ """
+ microsite_template = microsite.get_template(uri)
+
+ return (
+ microsite_template
+ if microsite_template
+ else super(DynamicTemplateLookup, self).get_template(uri)
+ )
+
def clear_lookups(namespace):
"""
diff --git a/common/djangoapps/edxmako/shortcuts.py b/common/djangoapps/edxmako/shortcuts.py
index 3eda270d41..50e53855b2 100644
--- a/common/djangoapps/edxmako/shortcuts.py
+++ b/common/djangoapps/edxmako/shortcuts.py
@@ -166,8 +166,5 @@ def render_to_response(template_name, dictionary=None, context_instance=None, na
lookup.get_template(args[0]).render with the passed arguments.
"""
- # see if there is an override template defined in the microsite
- 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/microsite_configuration/__init__.py b/common/djangoapps/microsite_configuration/__init__.py
index cc081c890a..cd0cc601f9 100644
--- a/common/djangoapps/microsite_configuration/__init__.py
+++ b/common/djangoapps/microsite_configuration/__init__.py
@@ -1 +1,32 @@
+"""
+This file implements a class which is a handy utility to make any
+call to the settings completely microsite aware by replacing the:
+
+from django.conf import settings
+
+with:
+
+from microsite_configuration import settings
+"""
+from django.conf import settings as base_settings
+
+from microsite_configuration import microsite
from .templatetags.microsite import page_title_breadcrumbs
+
+
+class MicrositeAwareSettings(object):
+ """
+ This class is a proxy object of the settings object from django.
+ It will try to get a value from the microsite and default to the
+ django settings
+ """
+
+ def __getattr__(self, name):
+ try:
+ if isinstance(microsite.get_value(name), dict):
+ return microsite.get_dict(name, getattr(base_settings, name))
+ return microsite.get_value(name, getattr(base_settings, name))
+ except KeyError:
+ return getattr(base_settings, name)
+
+settings = MicrositeAwareSettings() # pylint: disable=invalid-name
diff --git a/common/djangoapps/microsite_configuration/admin.py b/common/djangoapps/microsite_configuration/admin.py
new file mode 100644
index 0000000000..d9d1759128
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/admin.py
@@ -0,0 +1,83 @@
+"""
+Django admin page for microsite models
+"""
+from django.contrib import admin
+from django import forms
+
+from .models import (
+ Microsite,
+ MicrositeHistory,
+ MicrositeOrganizationMapping,
+ MicrositeTemplate
+)
+from util.organizations_helpers import get_organizations
+
+
+class MicrositeAdmin(admin.ModelAdmin):
+ """ Admin interface for the Microsite object. """
+ list_display = ('key', 'site')
+ search_fields = ('site__domain', 'values')
+
+ class Meta(object): # pylint: disable=missing-docstring
+ model = Microsite
+
+
+class MicrositeHistoryAdmin(admin.ModelAdmin):
+ """ Admin interface for the MicrositeHistory object. """
+ list_display = ('key', 'site', 'created')
+ search_fields = ('site__domain', 'values')
+
+ ordering = ['-created']
+
+ class Meta(object): # pylint: disable=missing-docstring
+ model = MicrositeHistory
+
+ def has_add_permission(self, request):
+ """Don't allow adds"""
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ """Don't allow deletes"""
+ return False
+
+
+class MicrositeOrganizationMappingForm(forms.ModelForm):
+ """
+ Django admin form for MicrositeOrganizationMapping model
+ """
+ def __init__(self, *args, **kwargs):
+ super(MicrositeOrganizationMappingForm, self).__init__(*args, **kwargs)
+ organizations = get_organizations()
+ org_choices = [(org["short_name"], org["name"]) for org in organizations]
+ org_choices.insert(0, ('', 'None'))
+ self.fields['organization'] = forms.TypedChoiceField(
+ choices=org_choices, required=False, empty_value=None
+ )
+
+ class Meta(object):
+ model = MicrositeOrganizationMapping
+ fields = '__all__'
+
+
+class MicrositeOrganizationMappingAdmin(admin.ModelAdmin):
+ """ Admin interface for the MicrositeOrganizationMapping object. """
+ list_display = ('organization', 'microsite')
+ search_fields = ('organization', 'microsite')
+ form = MicrositeOrganizationMappingForm
+
+ class Meta(object): # pylint: disable=missing-docstring
+ model = MicrositeOrganizationMapping
+
+
+class MicrositeTemplateAdmin(admin.ModelAdmin):
+ """ Admin interface for the MicrositeTemplate object. """
+ list_display = ('microsite', 'template_uri')
+ search_fields = ('microsite', 'template_uri')
+
+ class Meta(object): # pylint: disable=missing-docstring
+ model = MicrositeTemplate
+
+admin.site.register(Microsite, MicrositeAdmin)
+admin.site.register(MicrositeHistory, MicrositeHistoryAdmin)
+admin.site.register(MicrositeOrganizationMapping, MicrositeOrganizationMappingAdmin)
+admin.site.register(MicrositeTemplate, MicrositeTemplateAdmin)
diff --git a/common/djangoapps/microsite_configuration/backends/__init__.py b/common/djangoapps/microsite_configuration/backends/__init__.py
new file mode 100644
index 0000000000..00ec11f810
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/backends/__init__.py
@@ -0,0 +1,7 @@
+"""
+Supported backends for microsites
+1. filebased
+ This backend supports retrieval of microsite configurations/templates from filesystem.
+2. database
+ This backend supports retrieval of microsite configurations/templates from database.
+"""
diff --git a/common/djangoapps/microsite_configuration/backends/base.py b/common/djangoapps/microsite_configuration/backends/base.py
new file mode 100644
index 0000000000..20cc25e136
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/backends/base.py
@@ -0,0 +1,340 @@
+"""
+Microsite configuration backend module.
+
+Contains the base classes for microsite backends.
+
+AbstractBaseMicrositeBackend is Abstract Base Class for the microsite configuration backend.
+BaseMicrositeBackend is Base Class for microsite configuration backend.
+BaseMicrositeTemplateBackend is Base Class for the microsite template backend.
+"""
+
+from __future__ import absolute_import
+
+import abc
+import edxmako
+import os.path
+import threading
+
+from django.conf import settings
+
+from util.url import strip_port_from_host
+
+
+# pylint: disable=unused-argument
+class AbstractBaseMicrositeBackend(object):
+ """
+ Abstract Base Class for the microsite backends.
+ """
+ __metaclass__ = abc.ABCMeta
+
+ def __init__(self, **kwargs):
+ pass
+
+ @abc.abstractmethod
+ def set_config_by_domain(self, domain):
+ """
+ For a given request domain, find a match in our microsite configuration
+ and make it available to the complete django request process
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_value(self, val_name, default=None, **kwargs):
+ """
+ Returns a value associated with the request's microsite, if present
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_dict(self, dict_name, default=None, **kwargs):
+ """
+ Returns a dictionary product of merging the request's microsite and
+ the default value.
+ This can be used, for example, to return a merged dictonary from the
+ settings.FEATURES dict, including values defined at the microsite
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def is_request_in_microsite(self):
+ """
+ This will return True/False if the current request is a request within a microsite
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def has_override_value(self, val_name):
+ """
+ Returns True/False whether a Microsite has a definition for the
+ specified named value
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_all_config(self):
+ """
+ This returns a set of orgs that are considered within all microsites.
+ This can be used, for example, to do filtering
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_value_for_org(self, org, val_name, default=None):
+ """
+ This returns a configuration value for a microsite which has an org_filter that matches
+ what is passed in
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def get_all_orgs(self):
+ """
+ This returns a set of orgs that are considered within a microsite. This can be used,
+ for example, to do filtering
+ """
+ raise NotImplementedError()
+
+ @abc.abstractmethod
+ def clear(self):
+ """
+ Clears out any microsite configuration from the current request/thread
+ """
+ raise NotImplementedError()
+
+
+class BaseMicrositeBackend(AbstractBaseMicrositeBackend):
+ """
+ Base class for Microsite backends.
+ """
+
+ def __init__(self, **kwargs):
+ super(BaseMicrositeBackend, self).__init__(**kwargs)
+ self.current_request_configuration = threading.local()
+ self.current_request_configuration.data = {}
+ self.current_request_configuration.cache = {}
+
+ def has_configuration_set(self):
+ """
+ Returns whether there is any Microsite configuration settings
+ """
+ return getattr(settings, "MICROSITE_CONFIGURATION", False)
+
+ def get_configuration(self):
+ """
+ Returns the current request's microsite configuration.
+ if request's microsite configuration is not present returns empty dict.
+ """
+ if not hasattr(self.current_request_configuration, 'data'):
+ return {}
+
+ return self.current_request_configuration.data
+
+ def get_key_from_cache(self, key):
+ """
+ Retrieves a key from a cache scoped to the thread
+ """
+ if hasattr(self.current_request_configuration, 'cache'):
+ return self.current_request_configuration.cache.get(key)
+
+ def set_key_to_cache(self, key, value):
+ """
+ Stores a key value pair in a cache scoped to the thread
+ """
+ if hasattr(self.current_request_configuration, 'cache'):
+ self.current_request_configuration.cache[key] = value
+
+ def set_config_by_domain(self, domain):
+ """
+ For a given request domain, find a match in our microsite configuration
+ and then assign it to the thread local in order to make it available
+ to the complete Django request processing
+ """
+ if not self.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):
+ self._set_microsite_config(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:
+ self._set_microsite_config('default', subdomain, domain)
+ return
+
+ def get_value(self, val_name, default=None, **kwargs):
+ """
+ Returns a value associated with the request's microsite, if present
+ """
+ configuration = self.get_configuration()
+ return configuration.get(val_name, default)
+
+ def get_dict(self, dict_name, default=None, **kwargs):
+ """
+ Returns a dictionary product of merging the request's microsite and
+ the default value.
+ Supports storing a cache of the merged value to improve performance
+ """
+ cached_dict = self.get_key_from_cache(dict_name)
+ if cached_dict:
+ return cached_dict
+
+ default = default or {}
+ output = default.copy()
+ output.update(self.get_value(dict_name, {}))
+
+ self.set_key_to_cache(dict_name, output)
+ return output
+
+ def is_request_in_microsite(self):
+ """
+ This will return if current request is a request within a microsite
+ """
+ return bool(self.get_configuration())
+
+ def has_override_value(self, val_name):
+ """
+ Will return True/False whether a Microsite has a definition for the
+ specified val_name
+ """
+ configuration = self.get_configuration()
+ return val_name in configuration
+
+ def get_all_config(self):
+ """
+ This returns all configuration for all microsites
+ """
+ config = {}
+
+ for key, value in settings.MICROSITE_CONFIGURATION.iteritems():
+ config[key] = value
+
+ return config
+
+ def get_value_for_org(self, 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 self.has_configuration_set():
+ return default
+
+ # Filter at the setting file
+ for value in settings.MICROSITE_CONFIGURATION.itervalues():
+ org_filter = value.get('course_org_filter', None)
+ if org_filter == org:
+ return value.get(val_name, default)
+ return default
+
+ def get_all_orgs(self):
+ """
+ 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 self.has_configuration_set():
+ return org_filter_set
+
+ # Get the orgs in the db
+ for microsite in settings.MICROSITE_CONFIGURATION.itervalues():
+ org_filter = microsite.get('course_org_filter')
+ if org_filter:
+ org_filter_set.add(org_filter)
+
+ return org_filter_set
+
+ def _set_microsite_config(self, microsite_config_key, subdomain, domain):
+ """
+ Helper internal method to actually find the microsite configuration
+ """
+ config = settings.MICROSITE_CONFIGURATION[microsite_config_key].copy()
+ config['subdomain'] = strip_port_from_host(subdomain)
+ config['microsite_config_key'] = microsite_config_key
+ config['site_domain'] = strip_port_from_host(domain)
+
+ template_dir = settings.MICROSITE_ROOT_DIR / microsite_config_key / 'templates'
+ config['template_dir'] = template_dir
+ self.current_request_configuration.data = config
+
+ def clear(self):
+ """
+ Clears out any microsite configuration from the current request/thread
+ """
+ self.current_request_configuration.data = {}
+ self.current_request_configuration.cache = {}
+
+ def enable_microsites(self, log):
+ """
+ Configure the paths for the microsites feature
+ """
+ microsites_root = settings.MICROSITE_ROOT_DIR
+
+ if os.path.isdir(microsites_root):
+ edxmako.paths.add_lookup('main', microsites_root)
+ settings.STATICFILES_DIRS.insert(0, microsites_root)
+
+ log.info('Loading microsite path at %s', microsites_root)
+ else:
+ log.error(
+ 'Error loading %s. Directory does not exist',
+ microsites_root
+ )
+
+ def enable_microsites_pre_startup(self, log):
+ """
+ The TEMPLATE_ENGINE directory to search for microsite templates
+ in non-mako templates must be loaded before the django startup
+ """
+ microsites_root = settings.MICROSITE_ROOT_DIR
+ microsite_config_dict = settings.MICROSITE_CONFIGURATION
+
+ if microsite_config_dict:
+ settings.DEFAULT_TEMPLATE_ENGINE['DIRS'].append(microsites_root)
+
+
+class BaseMicrositeTemplateBackend(object):
+ """
+ Interface for microsite template providers. Base implementation is to use the filesystem.
+ When this backend is used templates are first searched in location set in `template_dir`
+ configuration of microsite on filesystem.
+ """
+
+ def get_template_path(self, relative_path, **kwargs):
+ """
+ Returns a path (string) to a Mako template, which can either be in
+ an override or will just return what is passed in which is expected to be a string
+ """
+
+ from microsite_configuration.microsite import get_value as microsite_get_value
+
+ microsite_template_path = microsite_get_value('template_dir', None)
+
+ if not microsite_template_path:
+ microsite_template_path = '/'.join([
+ settings.MICROSITE_ROOT_DIR,
+ microsite_get_value('microsite_config_key', 'default'),
+ 'templates',
+ ])
+
+ search_path = os.path.join(microsite_template_path, relative_path)
+ if os.path.isfile(search_path):
+ path = '/{0}/templates/{1}'.format(
+ microsite_get_value('microsite_config_key'),
+ relative_path
+ )
+ return path
+ else:
+ return relative_path
+
+ def get_template(self, uri):
+ """
+ Returns the actual template for the microsite with the specified URI,
+ default implementation returns None, which means that the caller framework
+ should use default behavior
+ """
+
+ return
diff --git a/common/djangoapps/microsite_configuration/backends/database.py b/common/djangoapps/microsite_configuration/backends/database.py
new file mode 100644
index 0000000000..0d9a7ec5e1
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/backends/database.py
@@ -0,0 +1,211 @@
+"""
+Microsite backend that reads the configuration from the database
+"""
+from mako.template import Template
+from util.cache import cache
+
+from django.conf import settings
+from django.dispatch import receiver
+from django.db.models.signals import post_save
+
+from util.memcache import fasthash
+from util.url import strip_port_from_host
+from microsite_configuration.backends.base import (
+ BaseMicrositeBackend,
+ BaseMicrositeTemplateBackend,
+)
+from microsite_configuration.models import (
+ Microsite,
+ MicrositeOrganizationMapping,
+ MicrositeTemplate
+)
+from microsite_configuration.microsite import get_value as microsite_get_value
+
+
+class DatabaseMicrositeBackend(BaseMicrositeBackend):
+ """
+ Microsite backend that reads the microsites definitions
+ from a table in the database according to the models.py file
+ This backend would allow us to save microsite configurations
+ into database and load them in local storage when HTTRequest
+ is originated from microsite.
+
+ E.g. we have setup a microsite with key `monster-university-academy` and
+ We would have a DB entry like this in table created by Microsite model.
+
+ key = monster-university-academy
+ subdomain = mua.edx.org
+ values = {
+ "platform_name": "Monster University Academy".
+ "course_org_filter: "MonsterX"
+ }
+
+ While using DatabaseMicrositeBackend any request coming from mua.edx.org
+ would get microsite configurations from `values` column.
+ """
+
+ def has_configuration_set(self):
+ """
+ Returns whether there is any Microsite configuration settings
+ """
+ if Microsite.objects.all()[:1].exists():
+ return True
+ else:
+ return False
+
+ def set_config_by_domain(self, domain):
+ """
+ For a given request domain, find a match in our microsite configuration
+ and then assign it to the thread local in order to make it available
+ to the complete Django request processing
+ """
+
+ if not self.has_configuration_set() or not domain:
+ return
+
+ # look up based on the HTTP request domain name
+ # this will need to be a full domain name match,
+ # not a 'startswith' match
+ microsite = Microsite.get_microsite_for_domain(domain)
+
+ if not microsite:
+ # if no match, then try to find a 'default' key in Microsites
+ try:
+ microsite = Microsite.objects.get(key='default')
+ except Microsite.DoesNotExist:
+ pass
+
+ if microsite:
+ # if we have a match, then set up the microsite thread local
+ # data
+ self._set_microsite_config_from_obj(microsite.site.domain, domain, microsite)
+
+ def get_all_config(self):
+ """
+ This returns all configuration for all microsites
+ """
+ config = {}
+
+ candidates = Microsite.objects.all()
+ for microsite in candidates:
+ values = microsite.values
+ config[microsite.key] = values
+
+ return config
+
+ def get_value_for_org(self, org, val_name, default=None):
+ """
+ This returns a configuration value for a microsite which has an org_filter that matches
+ what is passed in
+ """
+
+ microsite = MicrositeOrganizationMapping.get_microsite_for_organization(org)
+ if not microsite:
+ return default
+
+ # cdodge: This approach will not leverage any caching, although I think only Studio calls
+ # this
+ config = microsite.values
+ return config.get(val_name, default)
+
+ def get_all_orgs(self):
+ """
+ This returns a set of orgs that are considered within a microsite. This can be used,
+ for example, to do filtering
+ """
+
+ # This should be cacheable (via memcache to keep consistent across a cluster)
+ # I believe this is called on the dashboard and catalog pages, so it'd be good to optimize
+ return set(MicrositeOrganizationMapping.objects.all().values_list('organization', flat=True))
+
+ def _set_microsite_config_from_obj(self, subdomain, domain, microsite_object):
+ """
+ Helper internal method to actually find the microsite configuration
+ """
+ config = microsite_object.values
+ config['subdomain'] = strip_port_from_host(subdomain)
+ config['site_domain'] = strip_port_from_host(domain)
+ config['microsite_config_key'] = microsite_object.key
+
+ # we take the list of ORGs associated with this microsite from the database mapping
+ # tables. NOTE, for now, we assume one ORG per microsite
+ organizations = microsite_object.get_organizations()
+
+ # we must have at least one ORG defined
+ if not organizations:
+ raise Exception(
+ 'Configuration error. Microsite {key} does not have any ORGs mapped to it!'.format(
+ key=microsite_object.key
+ )
+ )
+
+ # just take the first one for now, we'll have to change the upstream logic to allow
+ # for more than one ORG binding
+ config['course_org_filter'] = organizations[0]
+ self.current_request_configuration.data = config
+
+
+class DatabaseMicrositeTemplateBackend(BaseMicrositeTemplateBackend):
+ """
+ Specialized class to pull templates from the database.
+ This Backend would allow us to save templates in DB and pull
+ them from there when required for a specific microsite.
+ This backend can be enabled by `MICROSITE_TEMPLATE_BACKEND` setting.
+
+ E.g. we have setup a microsite for subdomain `mua.edx.org` and
+ We have a DB entry like this in table created by MicrositeTemplate model.
+
+ microsite = Key for microsite(mua.edx.org)
+ template_uri = about.html
+ template = Template from DB
+
+ While using DatabaseMicrositeTemplateBackend any request coming from mua.edx.org/about.html
+ would get about.html template from DB and response would be the value of `template` column.
+ """
+ def get_template_path(self, relative_path, **kwargs):
+ return relative_path
+
+ def get_template(self, uri):
+ """
+ Override of the base class for us to look into the
+ database tables for a template definition, if we can't find
+ one we'll return None which means "use default means" (aka filesystem)
+ """
+ cache_key = "template_cache." + fasthash(microsite_get_value('site_domain') + '.' + uri)
+ template_text = cache.get(cache_key) # pylint: disable=maybe-no-member
+
+ if not template_text:
+ # cache is empty so pull template from DB and fill cache.
+ template_obj = MicrositeTemplate.get_template_for_microsite(
+ microsite_get_value('site_domain'),
+ uri
+ )
+
+ if not template_obj:
+ # We need to set something in the cache to improve performance
+ # of the templates stored in the filesystem as well
+ cache.set( # pylint: disable=maybe-no-member
+ cache_key, '##none', settings.MICROSITE_DATABASE_TEMPLATE_CACHE_TTL
+ )
+ return None
+
+ template_text = template_obj.template
+ cache.set( # pylint: disable=maybe-no-member
+ cache_key, template_text, settings.MICROSITE_DATABASE_TEMPLATE_CACHE_TTL
+ )
+
+ if template_text == '##none':
+ return None
+
+ return Template(
+ text=template_text
+ )
+
+ @staticmethod
+ @receiver(post_save, sender=MicrositeTemplate)
+ def clear_cache(sender, instance, **kwargs): # pylint: disable=unused-argument
+ """
+ Clear the cached template when the model is saved
+ """
+ cache_key = "template_cache." + fasthash(instance.microsite.site.domain + '.' + instance.template_uri)
+ cache.delete(cache_key) # pylint: disable=maybe-no-member
diff --git a/common/djangoapps/microsite_configuration/backends/filebased.py b/common/djangoapps/microsite_configuration/backends/filebased.py
new file mode 100644
index 0000000000..3c5b012643
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/backends/filebased.py
@@ -0,0 +1,26 @@
+"""
+Microsite backend that reads the configuration from a file
+
+"""
+
+from microsite_configuration.backends.base import (
+ BaseMicrositeBackend,
+ BaseMicrositeTemplateBackend,
+)
+
+
+class FilebasedMicrositeBackend(BaseMicrositeBackend):
+ """
+ Microsite backend that reads the microsites definitions
+ from a dictionary called MICROSITE_CONFIGURATION in the settings file.
+ """
+
+ def __init__(self, **kwargs):
+ super(FilebasedMicrositeBackend, self).__init__(**kwargs)
+
+
+class FilebasedMicrositeTemplateBackend(BaseMicrositeTemplateBackend):
+ """
+ Microsite backend that loads templates from filesystem.
+ """
+ pass
diff --git a/common/djangoapps/microsite_configuration/microsite.py b/common/djangoapps/microsite_configuration/microsite.py
index 01759ebe9b..35716d7899 100644
--- a/common/djangoapps/microsite_configuration/microsite.py
+++ b/common/djangoapps/microsite_configuration/microsite.py
@@ -6,45 +6,53 @@ A microsite enables the following features:
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
+import inspect
+from importlib import import_module
from django.conf import settings
-
-CURRENT_REQUEST_CONFIGURATION = threading.local()
-CURRENT_REQUEST_CONFIGURATION.data = {}
+from microsite_configuration.backends.base import BaseMicrositeBackend, BaseMicrositeTemplateBackend
-def has_configuration_set():
+__all__ = [
+ 'is_request_in_microsite', 'get_value', 'has_override_value',
+ 'get_template_path', 'get_value_for_org', 'get_all_orgs',
+ 'clear', 'set_by_domain', 'enable_microsites', 'get_all_config',
+ 'is_feature_enabled', 'enable_microsites_pre_startup',
+]
+
+BACKEND = None
+TEMPLATES_BACKEND = None
+
+
+def is_feature_enabled():
"""
- Returns whether there is any Microsite configuration settings
+ Returns whether the feature flag to enable microsite has been set
"""
- 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
+ return settings.FEATURES.get('USE_MICROSITES', False)
def is_request_in_microsite():
"""
This will return if current request is a request within a microsite
"""
- return bool(get_configuration())
+ return BACKEND.is_request_in_microsite()
-def get_value(val_name, default=None):
+def get_value(val_name, default=None, **kwargs):
"""
Returns a value associated with the request's microsite, if present
"""
- configuration = get_configuration()
- return configuration.get(val_name, default)
+ return BACKEND.get_value(val_name, default, **kwargs)
+
+
+def get_dict(dict_name, default=None, **kwargs):
+ """
+ Returns a dictionary product of merging the request's microsite and
+ the default value.
+ This can be used, for example, to return a merged dictonary from the
+ settings.FEATURES dict, including values defined at the microsite
+ """
+ return BACKEND.get_dict(dict_name, default, **kwargs)
def has_override_value(val_name):
@@ -52,33 +60,7 @@ def has_override_value(val_name):
Returns True/False whether a Microsite has a definition for the
specified named value
"""
- configuration = get_configuration()
- return val_name in configuration
-
-
-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
+ return BACKEND.has_override_value(val_name)
def get_value_for_org(org, val_name, default=None):
@@ -86,14 +68,7 @@ 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
+ return BACKEND.get_value_for_org(org, val_name, default)
def get_all_orgs():
@@ -101,52 +76,96 @@ 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
+ return BACKEND.get_all_orgs()
- 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 get_all_config():
+ """
+ This returns a dict have all microsite configs. Each key in the dict represent a
+ microsite config.
+ """
+ return BACKEND.get_all_config()
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['microsite_config_key'] = microsite_config_key
- config['site_domain'] = domain
- CURRENT_REQUEST_CONFIGURATION.data = config
+ BACKEND.clear()
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
+ For a given request domain, find a match in our microsite configuration
+ and make it available to the complete django request process
"""
- if not has_configuration_set() or not domain:
+ BACKEND.set_config_by_domain(domain)
+
+
+def enable_microsites_pre_startup(log):
+ """
+ Prepare the feature settings that must be enabled before django.setup() or
+ autostartup() during the startup script
+ """
+ if is_feature_enabled():
+ BACKEND.enable_microsites_pre_startup(log)
+
+
+def enable_microsites(log):
+ """
+ Enable the use of microsites during the startup script
+ """
+ if is_feature_enabled():
+ BACKEND.enable_microsites(log)
+
+
+def get_template(uri):
+ """
+ Returns a template for the specified URI, None if none exists or if caller should
+ use default templates/search paths
+ """
+ if not is_request_in_microsite():
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
+ return TEMPLATES_BACKEND.get_template(uri)
- # 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)
+
+def get_template_path(relative_path, **kwargs):
+ """
+ Returns a path (string) to a template
+ """
+ if not is_request_in_microsite():
+ return relative_path
+
+ return TEMPLATES_BACKEND.get_template_path(relative_path, **kwargs)
+
+
+def get_backend(name, expected_base_class, **kwds):
+ """
+ Load a microsites backend and return an instance of it.
+ If backend is None (default) settings.MICROSITE_BACKEND is used.
+ Any additional args(kwds) will be used in the constructor of the backend.
+ """
+ if not name:
+ return None
+
+ try:
+ parts = name.split('.')
+ module_name = '.'.join(parts[:-1])
+ class_name = parts[-1]
+ except IndexError:
+ raise ValueError('Invalid microsites backend %s' % name)
+
+ try:
+ module = import_module(module_name)
+ cls = getattr(module, class_name)
+ if not inspect.isclass(cls) or not issubclass(cls, expected_base_class):
+ raise TypeError
+ except (AttributeError, ValueError):
+ raise ValueError('Cannot find microsites backend %s' % module_name)
+
+ return cls(**kwds)
+
+
+BACKEND = get_backend(settings.MICROSITE_BACKEND, BaseMicrositeBackend)
+TEMPLATES_BACKEND = get_backend(settings.MICROSITE_TEMPLATE_BACKEND, BaseMicrositeTemplateBackend)
diff --git a/common/djangoapps/microsite_configuration/migrations/0001_initial.py b/common/djangoapps/microsite_configuration/migrations/0001_initial.py
new file mode 100644
index 0000000000..94d1e4a968
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/migrations/0001_initial.py
@@ -0,0 +1,107 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import jsonfield.fields
+import django.db.models.deletion
+from django.conf import settings
+import model_utils.fields
+import django.utils.timezone
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('sites', '0001_initial'),
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='HistoricalMicrositeOrganizationMapping',
+ fields=[
+ ('id', models.IntegerField(verbose_name='ID', db_index=True, auto_created=True, blank=True)),
+ ('organization', models.CharField(max_length=63, db_index=True)),
+ ('history_id', models.AutoField(serialize=False, primary_key=True)),
+ ('history_date', models.DateTimeField()),
+ ('history_type', models.CharField(max_length=1, choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')])),
+ ('history_user', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
+ ],
+ options={
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': 'history_date',
+ 'verbose_name': 'historical microsite organization mapping',
+ },
+ ),
+ migrations.CreateModel(
+ name='HistoricalMicrositeTemplate',
+ fields=[
+ ('id', models.IntegerField(verbose_name='ID', db_index=True, auto_created=True, blank=True)),
+ ('template_uri', models.CharField(max_length=255, db_index=True)),
+ ('template', models.TextField()),
+ ('history_id', models.AutoField(serialize=False, primary_key=True)),
+ ('history_date', models.DateTimeField()),
+ ('history_type', models.CharField(max_length=1, choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')])),
+ ('history_user', models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, null=True)),
+ ],
+ options={
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': 'history_date',
+ 'verbose_name': 'historical microsite template',
+ },
+ ),
+ migrations.CreateModel(
+ name='Microsite',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('key', models.CharField(unique=True, max_length=63, db_index=True)),
+ ('values', jsonfield.fields.JSONField(blank=True)),
+ ('site', models.OneToOneField(related_name='microsite', to='sites.Site')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='MicrositeHistory',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
+ ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
+ ('key', models.CharField(unique=True, max_length=63, db_index=True)),
+ ('values', jsonfield.fields.JSONField(blank=True)),
+ ('site', models.OneToOneField(related_name='microsite_history', to='sites.Site')),
+ ],
+ options={
+ 'verbose_name_plural': 'Microsite histories',
+ },
+ ),
+ migrations.CreateModel(
+ name='MicrositeOrganizationMapping',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('organization', models.CharField(unique=True, max_length=63, db_index=True)),
+ ('microsite', models.ForeignKey(to='microsite_configuration.Microsite')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='MicrositeTemplate',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('template_uri', models.CharField(max_length=255, db_index=True)),
+ ('template', models.TextField()),
+ ('microsite', models.ForeignKey(to='microsite_configuration.Microsite')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='historicalmicrositetemplate',
+ name='microsite',
+ field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False, blank=True, to='microsite_configuration.Microsite', null=True),
+ ),
+ migrations.AddField(
+ model_name='historicalmicrositeorganizationmapping',
+ name='microsite',
+ field=models.ForeignKey(related_name='+', on_delete=django.db.models.deletion.DO_NOTHING, db_constraint=False, blank=True, to='microsite_configuration.Microsite', null=True),
+ ),
+ migrations.AlterUniqueTogether(
+ name='micrositetemplate',
+ unique_together=set([('microsite', 'template_uri')]),
+ ),
+ ]
diff --git a/common/djangoapps/microsite_configuration/migrations/__init__.py b/common/djangoapps/microsite_configuration/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/microsite_configuration/models.py b/common/djangoapps/microsite_configuration/models.py
new file mode 100644
index 0000000000..54bf397449
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/models.py
@@ -0,0 +1,181 @@
+"""
+Model to store a microsite in the database.
+
+The object is stored as a json representation of the python dict
+that would have been used in the settings.
+
+"""
+import collections
+
+from django.db import models
+from django.dispatch import receiver
+from django.db.models.signals import pre_save, pre_delete
+from django.db.models.base import ObjectDoesNotExist
+from django.contrib.sites.models import Site
+
+from jsonfield.fields import JSONField
+from model_utils.models import TimeStampedModel
+from simple_history.models import HistoricalRecords
+
+
+class Microsite(models.Model):
+ """
+ This is where the information about the microsite gets stored to the db.
+ To achieve the maximum flexibility, most of the fields are stored inside
+ a json field.
+
+ Notes:
+ - The key field was required for the dict definition at the settings, and it
+ is used in some of the microsite_configuration methods.
+ - The site field is django site.
+ - The values field must be validated on save to prevent the platform from crashing
+ badly in the case the string is not able to be loaded as json.
+ """
+ site = models.OneToOneField(Site, related_name='microsite')
+ key = models.CharField(max_length=63, db_index=True, unique=True)
+ values = JSONField(null=False, blank=True, load_kwargs={'object_pairs_hook': collections.OrderedDict})
+
+ def __unicode__(self):
+ return self.key
+
+ def get_organizations(self):
+ """
+ Helper method to return a list of organizations associated with our particular Microsite
+ """
+ return MicrositeOrganizationMapping.get_organizations_for_microsite_by_pk(self.id) # pylint: disable=no-member
+
+ @classmethod
+ def get_microsite_for_domain(cls, domain):
+ """
+ Returns the microsite associated with this domain. Note that we always convert to lowercase, or
+ None if no match
+ """
+
+ # remove any port number from the hostname
+ domain = domain.split(':')[0]
+ microsites = cls.objects.filter(site__domain__iexact=domain)
+
+ return microsites[0] if microsites else None
+
+
+class MicrositeHistory(TimeStampedModel):
+ """
+ This is an archive table for Microsites model, so that we can maintain a history of changes. Note that the
+ key field is no longer unique
+ """
+ site = models.OneToOneField(Site, related_name='microsite_history')
+ key = models.CharField(max_length=63, db_index=True, unique=True)
+ values = JSONField(null=False, blank=True, load_kwargs={'object_pairs_hook': collections.OrderedDict})
+
+ def __unicode__(self):
+ return self.key
+
+ class Meta(object):
+ """ Meta class for this Django model """
+ verbose_name_plural = "Microsite histories"
+
+
+def _make_archive_copy(instance):
+ """
+ Helper method to make a copy of a Microsite into the history table
+ """
+ archive_object = MicrositeHistory(
+ key=instance.key,
+ site=instance.site,
+ values=instance.values,
+ )
+ archive_object.save()
+
+
+@receiver(pre_delete, sender=Microsite)
+def on_microsite_deleted(sender, instance, **kwargs): # pylint: disable=unused-argument
+ """
+ Archive the exam attempt when the item is about to be deleted
+ Make a clone and populate in the History table
+ """
+ _make_archive_copy(instance)
+
+
+@receiver(pre_save, sender=Microsite)
+def on_microsite_updated(sender, instance, **kwargs): # pylint: disable=unused-argument
+ """
+ Archive the microsite on an update operation
+ """
+
+ if instance.id:
+ # on an update case, get the original and archive it
+ original = Microsite.objects.get(id=instance.id)
+ _make_archive_copy(original)
+
+
+class MicrositeOrganizationMapping(models.Model):
+ """
+ Mapping of Organization to which Microsite it belongs
+ """
+
+ organization = models.CharField(max_length=63, db_index=True, unique=True)
+ microsite = models.ForeignKey(Microsite, db_index=True)
+
+ # for archiving
+ history = HistoricalRecords()
+
+ def __unicode__(self):
+ """String conversion"""
+ return u'{microsite_key}: {organization}'.format(
+ microsite_key=self.microsite.key,
+ organization=self.organization
+ )
+
+ @classmethod
+ def get_organizations_for_microsite_by_pk(cls, microsite_pk):
+ """
+ Returns a list of organizations associated with the microsite key, returned as a set
+ """
+ return cls.objects.filter(microsite_id=microsite_pk).values_list('organization', flat=True)
+
+ @classmethod
+ def get_microsite_for_organization(cls, org):
+ """
+ Returns the microsite object for a given organization based on the table mapping, None if
+ no mapping exists
+ """
+
+ try:
+ item = cls.objects.select_related('microsite').get(organization=org)
+ return item.microsite
+ except ObjectDoesNotExist:
+ return None
+
+
+class MicrositeTemplate(models.Model):
+ """
+ A HTML template that a microsite can use
+ """
+
+ microsite = models.ForeignKey(Microsite, db_index=True)
+ template_uri = models.CharField(max_length=255, db_index=True)
+ template = models.TextField()
+
+ # for archiving
+ history = HistoricalRecords()
+
+ def __unicode__(self):
+ """String conversion"""
+ return u'{microsite_key}: {template_uri}'.format(
+ microsite_key=self.microsite.key,
+ template_uri=self.template_uri
+ )
+
+ class Meta(object):
+ """ Meta class for this Django model """
+ unique_together = (('microsite', 'template_uri'),)
+
+ @classmethod
+ def get_template_for_microsite(cls, domain, template_uri):
+ """
+ Returns the template object for the microsite, None if not found
+ """
+ try:
+ return cls.objects.get(microsite__site__domain=domain, template_uri=template_uri)
+ except ObjectDoesNotExist:
+ return None
diff --git a/common/djangoapps/microsite_configuration/templatetags/microsite.py b/common/djangoapps/microsite_configuration/templatetags/microsite.py
index fe54cce68f..a8e2095636 100644
--- a/common/djangoapps/microsite_configuration/templatetags/microsite.py
+++ b/common/djangoapps/microsite_configuration/templatetags/microsite.py
@@ -63,3 +63,14 @@ def microsite_css_overrides_file():
return "".format(static(file_path))
else:
return ""
+
+
+@register.filter
+def microsite_template_path(template_name):
+ """
+ Django template filter to apply template overriding to microsites.
+ The django_templates loader does not support the leading slash, therefore
+ it is stripped before returning.
+ """
+ template_name = microsite.get_template_path(template_name)
+ return template_name[1:] if template_name[0] == '/' else template_name
diff --git a/common/djangoapps/microsite_configuration/tests/backends/__init__.py b/common/djangoapps/microsite_configuration/tests/backends/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/common/djangoapps/microsite_configuration/tests/backends/test_base.py b/common/djangoapps/microsite_configuration/tests/backends/test_base.py
new file mode 100644
index 0000000000..f4c88cb7fd
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/tests/backends/test_base.py
@@ -0,0 +1,132 @@
+"""
+Test Microsite base backends.
+"""
+from django.test import TestCase
+
+from microsite_configuration.backends.base import (
+ AbstractBaseMicrositeBackend,
+)
+
+
+class NullBackend(AbstractBaseMicrositeBackend):
+ """
+ A class that does nothing but inherit from the base class.
+ We created this class to test methods of AbstractBaseMicrositeBackend class.
+ Since abstract class cannot be instantiated we created this wrapper class.
+ """
+ def set_config_by_domain(self, domain):
+ """
+ For a given request domain, find a match in our microsite configuration
+ and make it available to the complete django request process
+ """
+ return super(NullBackend, self).set_config_by_domain(domain)
+
+ def get_template_path(self, relative_path, **kwargs):
+ """
+ Returns a path (string) to a Mako template, which can either be in
+ an override or will just return what is passed in which is expected to be a string
+ """
+ return super(NullBackend, self).get_template_path(relative_path, **kwargs)
+
+ def get_value(self, val_name, default=None, **kwargs):
+ """
+ Returns a value associated with the request's microsite, if present
+ """
+ return super(NullBackend, self).get_value(val_name, default, **kwargs)
+
+ def get_dict(self, dict_name, default=None, **kwargs):
+ """
+ Returns a dictionary product of merging the request's microsite and
+ the default value.
+ This can be used, for example, to return a merged dictonary from the
+ settings.FEATURES dict, including values defined at the microsite
+ """
+ return super(NullBackend, self).get_dict(dict_name, default, **kwargs)
+
+ def is_request_in_microsite(self):
+ """
+ This will return True/False if the current request is a request within a microsite
+ """
+ return super(NullBackend, self).is_request_in_microsite()
+
+ def has_override_value(self, val_name):
+ """
+ Returns True/False whether a Microsite has a definition for the
+ specified named value
+ """
+ return super(NullBackend, self).has_override_value(val_name)
+
+ def get_all_config(self):
+ """
+ This returns a set of orgs that are considered within all microsites.
+ This can be used, for example, to do filtering
+ """
+ return super(NullBackend, self).get_all_config()
+
+ def get_value_for_org(self, org, val_name, default=None):
+ """
+ This returns a configuration value for a microsite which has an org_filter that matches
+ what is passed in
+ """
+ return super(NullBackend, self).get_value_for_org(org, val_name, default)
+
+ def get_all_orgs(self):
+ """
+ This returns a set of orgs that are considered within a microsite. This can be used,
+ for example, to do filtering
+ """
+ return super(NullBackend, self).get_all_orgs()
+
+ def clear(self):
+ """
+ Clears out any microsite configuration from the current request/thread
+ """
+ return super(NullBackend, self).clear()
+
+
+class AbstractBaseMicrositeBackendTests(TestCase):
+ """
+ Go through and test the base abstract class
+ """
+
+ def test_cant_create_instance(self):
+ """
+ We shouldn't be able to create an instance of the base abstract class
+ """
+
+ with self.assertRaises(TypeError):
+ AbstractBaseMicrositeBackend() # pylint: disable=abstract-class-instantiated
+
+ def test_not_yet_implemented(self):
+ """
+ Make sure all base methods raise a NotImplementedError exception
+ """
+
+ backend = NullBackend()
+
+ with self.assertRaises(NotImplementedError):
+ backend.set_config_by_domain(None)
+
+ with self.assertRaises(NotImplementedError):
+ backend.get_value(None, None)
+
+ with self.assertRaises(NotImplementedError):
+ backend.get_dict(None, None)
+
+ with self.assertRaises(NotImplementedError):
+ backend.is_request_in_microsite()
+
+ with self.assertRaises(NotImplementedError):
+ backend.has_override_value(None)
+
+ with self.assertRaises(NotImplementedError):
+ backend.get_all_config()
+
+ with self.assertRaises(NotImplementedError):
+ backend.clear()
+
+ with self.assertRaises(NotImplementedError):
+ backend.get_value_for_org(None, None, None)
+
+ with self.assertRaises(NotImplementedError):
+ backend.get_all_orgs()
diff --git a/common/djangoapps/microsite_configuration/tests/backends/test_database.py b/common/djangoapps/microsite_configuration/tests/backends/test_database.py
new file mode 100644
index 0000000000..294da02ece
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/tests/backends/test_database.py
@@ -0,0 +1,220 @@
+"""
+Test Microsite database backends.
+"""
+import logging
+from mock import patch
+
+from django.conf import settings
+
+from microsite_configuration.backends.base import (
+ BaseMicrositeBackend,
+ BaseMicrositeTemplateBackend,
+)
+from microsite_configuration import microsite
+from microsite_configuration.models import (
+ Microsite,
+ MicrositeHistory,
+ MicrositeTemplate,
+)
+from microsite_configuration.tests.tests import (
+ DatabaseMicrositeTestCase,
+)
+from microsite_configuration.tests.factories import (
+ SiteFactory,
+ MicrositeFactory,
+ MicrositeTemplateFactory,
+)
+
+log = logging.getLogger(__name__)
+
+
+@patch(
+ 'microsite_configuration.microsite.BACKEND',
+ microsite.get_backend(
+ 'microsite_configuration.backends.database.DatabaseMicrositeBackend', BaseMicrositeBackend
+ )
+)
+class DatabaseMicrositeBackendTests(DatabaseMicrositeTestCase):
+ """
+ Go through and test the DatabaseMicrositeBackend class
+ """
+ def setUp(self):
+ super(DatabaseMicrositeBackendTests, self).setUp()
+
+ def tearDown(self):
+ super(DatabaseMicrositeBackendTests, self).tearDown()
+ microsite.clear()
+
+ def test_get_value(self):
+ """
+ Tests microsite.get_value works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertEqual(microsite.get_value('email_from_address'), self.microsite.values['email_from_address'])
+
+ def test_is_request_in_microsite(self):
+ """
+ Tests microsite.is_request_in_microsite works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertTrue(microsite.is_request_in_microsite())
+
+ def test_get_dict(self):
+ """
+ Tests microsite.get_dict works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertEqual(microsite.get_dict('nested_dict'), self.microsite.values['nested_dict'])
+
+ def test_has_override_value(self):
+ """
+ Tests microsite.has_override_value works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertTrue(microsite.has_override_value('platform_name'))
+
+ def test_get_value_for_org(self):
+ """
+ Tests microsite.get_value_for_org works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertEqual(
+ microsite.get_value_for_org(self.microsite.get_organizations()[0], 'platform_name'),
+ self.microsite.values['platform_name']
+ )
+
+ def test_get_all_orgs(self):
+ """
+ Tests microsite.get_all_orgs works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertEqual(
+ microsite.get_all_orgs(),
+ set(self.microsite.get_organizations())
+ )
+
+ def test_clear(self):
+ """
+ Tests microsite.clear works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ self.assertEqual(
+ microsite.get_value('platform_name'),
+ self.microsite.values['platform_name']
+ )
+ microsite.clear()
+ self.assertIsNone(microsite.get_value('platform_name'))
+
+ def test_enable_microsites_pre_startup(self):
+ """
+ Tests microsite.test_enable_microsites_pre_startup works as expected.
+ """
+ # remove microsite root directory paths first
+ settings.DEFAULT_TEMPLATE_ENGINE['DIRS'] = [
+ path for path in settings.DEFAULT_TEMPLATE_ENGINE['DIRS']
+ if path != settings.MICROSITE_ROOT_DIR
+ ]
+ with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': False}):
+ microsite.enable_microsites_pre_startup(log)
+ self.assertNotIn(settings.MICROSITE_ROOT_DIR, settings.DEFAULT_TEMPLATE_ENGINE['DIRS'])
+ with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': True}):
+ microsite.enable_microsites_pre_startup(log)
+ self.assertIn(settings.MICROSITE_ROOT_DIR, settings.DEFAULT_TEMPLATE_ENGINE['DIRS'])
+
+ @patch('edxmako.paths.add_lookup')
+ def test_enable_microsites(self, add_lookup):
+ """
+ Tests microsite.enable_microsites works as expected.
+ """
+ # remove microsite root directory paths first
+ settings.STATICFILES_DIRS = [
+ path for path in settings.STATICFILES_DIRS
+ if path != settings.MICROSITE_ROOT_DIR
+ ]
+ with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': False}):
+ microsite.enable_microsites(log)
+ self.assertNotIn(settings.MICROSITE_ROOT_DIR, settings.STATICFILES_DIRS)
+ add_lookup.assert_not_called()
+ with patch.dict('django.conf.settings.FEATURES', {'USE_MICROSITES': True}):
+ microsite.enable_microsites(log)
+ self.assertIn(settings.MICROSITE_ROOT_DIR, settings.STATICFILES_DIRS)
+ add_lookup.assert_called_once_with('main', settings.MICROSITE_ROOT_DIR)
+
+ def test_get_all_configs(self):
+ """
+ Tests microsite.get_all_config works as expected.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ configs = microsite.get_all_config()
+ self.assertEqual(len(configs.keys()), 1)
+ self.assertEqual(configs[self.microsite.key], self.microsite.values)
+
+ def test_set_config_by_domain(self):
+ """
+ Tests microsite.set_config_by_domain works as expected.
+ """
+ microsite.clear()
+ # if microsite config does not exist
+ microsite.set_by_domain('unknown')
+ self.assertIsNone(microsite.get_value('platform_name'))
+
+ # if no microsite exists
+ Microsite.objects.all().delete()
+ microsite.clear()
+ microsite.set_by_domain('unknown')
+ self.assertIsNone(microsite.get_value('platform_name'))
+
+ # if microsite site has no organization it should raise exception
+ new_microsite = MicrositeFactory.create(key="test_microsite2")
+ new_microsite.site = SiteFactory.create(domain='test.microsite2.com')
+ # This would update microsite so we test MicrositeHistory has old microsite
+ new_microsite.save()
+ self.assertEqual(MicrositeHistory.objects.all().count(), 2)
+ with self.assertRaises(Exception):
+ microsite.set_by_domain('test.microsite2.com')
+
+
+@patch(
+ 'microsite_configuration.microsite.TEMPLATES_BACKEND',
+ microsite.get_backend(
+ 'microsite_configuration.backends.database.DatabaseMicrositeTemplateBackend', BaseMicrositeTemplateBackend
+ )
+)
+class DatabaseMicrositeTemplateBackendTests(DatabaseMicrositeTestCase):
+ """
+ Go through and test the DatabaseMicrositeTemplateBackend class
+ """
+ def setUp(self):
+ super(DatabaseMicrositeTemplateBackendTests, self).setUp()
+ MicrositeTemplateFactory.create(
+ microsite=self.microsite,
+ template_uri='about.html',
+ template="""
+
+
+ About this microsite.
+
+
+ """,
+ )
+
+ def tearDown(self):
+ super(DatabaseMicrositeTemplateBackendTests, self).tearDown()
+ microsite.clear()
+
+ def test_microsite_get_template_when_no_template_exists(self):
+ """
+ Test microsite.get_template return None if there is not template in DB.
+ """
+ MicrositeTemplate.objects.all().delete()
+ microsite.set_by_domain(self.microsite.site.domain)
+ template = microsite.get_template('about.html')
+ self.assertIsNone(template)
+
+ def test_microsite_get_template(self):
+ """
+ Test microsite.get_template return appropriate template.
+ """
+ microsite.set_by_domain(self.microsite.site.domain)
+ template = microsite.get_template('about.html')
+ self.assertIn('About this microsite', template.render())
diff --git a/common/djangoapps/microsite_configuration/tests/backends/test_filebased.py b/common/djangoapps/microsite_configuration/tests/backends/test_filebased.py
new file mode 100644
index 0000000000..99a103d42c
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/tests/backends/test_filebased.py
@@ -0,0 +1,116 @@
+"""
+Test Microsite filebased backends.
+"""
+from mock import patch
+
+from django.test import TestCase
+
+from microsite_configuration.backends.base import (
+ BaseMicrositeBackend,
+)
+from microsite_configuration import microsite
+
+
+@patch(
+ 'microsite_configuration.microsite.BACKEND',
+ microsite.get_backend(
+ 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend', BaseMicrositeBackend
+ )
+)
+class FilebasedMicrositeBackendTests(TestCase):
+ """
+ Go through and test the FilebasedMicrositeBackend class
+ """
+ def setUp(self):
+ super(FilebasedMicrositeBackendTests, self).setUp()
+ self.microsite_subdomain = 'testmicrosite'
+
+ def tearDown(self):
+ super(FilebasedMicrositeBackendTests, self).tearDown()
+ microsite.clear()
+
+ def test_get_value(self):
+ """
+ Tests microsite.get_value works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertEqual(microsite.get_value('platform_name'), 'Test Microsite')
+
+ def test_is_request_in_microsite(self):
+ """
+ Tests microsite.is_request_in_microsite works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertTrue(microsite.is_request_in_microsite())
+
+ def test_has_override_value(self):
+ """
+ Tests microsite.has_override_value works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertTrue(microsite.has_override_value('platform_name'))
+
+ def test_get_value_for_org(self):
+ """
+ Tests microsite.get_value_for_org works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertEqual(
+ microsite.get_value_for_org('TestMicrositeX', 'platform_name'),
+ 'Test Microsite'
+ )
+
+ # if no config is set
+ microsite.clear()
+ with patch('django.conf.settings.MICROSITE_CONFIGURATION', False):
+ self.assertEqual(
+ microsite.get_value_for_org('TestMicrositeX', 'platform_name', 'Default Value'),
+ 'Default Value'
+ )
+
+ def test_get_all_orgs(self):
+ """
+ Tests microsite.get_all_orgs works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertEqual(
+ microsite.get_all_orgs(),
+ set(['TestMicrositeX', 'LogistrationX'])
+ )
+
+ # if no config is set
+ microsite.clear()
+ with patch('django.conf.settings.MICROSITE_CONFIGURATION', False):
+ self.assertEqual(
+ microsite.get_all_orgs(),
+ set()
+ )
+
+ def test_clear(self):
+ """
+ Tests microsite.clear works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ self.assertEqual(
+ microsite.get_value('platform_name'),
+ 'Test Microsite'
+ )
+ microsite.clear()
+ self.assertIsNone(microsite.get_value('platform_name'))
+
+ def test_get_all_configs(self):
+ """
+ Tests microsite.get_all_config works as expected.
+ """
+ microsite.set_by_domain(self.microsite_subdomain)
+ configs = microsite.get_all_config()
+ self.assertEqual(len(configs.keys()), 3)
+
+ def test_set_config_by_domain(self):
+ """
+ Tests microsite.set_config_by_domain works as expected.
+ """
+ microsite.clear()
+ # if microsite config does not exist default config should be used
+ microsite.set_by_domain('unknown')
+ self.assertEqual(microsite.get_value('university'), 'default_university')
diff --git a/common/djangoapps/microsite_configuration/tests/factories.py b/common/djangoapps/microsite_configuration/tests/factories.py
new file mode 100644
index 0000000000..6594abfc7c
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/tests/factories.py
@@ -0,0 +1,79 @@
+"""
+Factories module to hold microsite factories
+"""
+import factory
+from factory.django import DjangoModelFactory
+
+from django.contrib.sites.models import Site
+
+from microsite_configuration.models import (
+ Microsite,
+ MicrositeOrganizationMapping,
+ MicrositeTemplate,
+)
+
+
+class SiteFactory(DjangoModelFactory):
+ """
+ Factory for django.contrib.sites.models.Site
+ """
+ class Meta(object):
+ model = Site
+
+ name = "test microsite"
+ domain = "testmicrosite.testserver"
+
+
+class MicrositeFactory(DjangoModelFactory):
+ """
+ Factory for Microsite
+ """
+ class Meta(object):
+ model = Microsite
+
+ key = "test_microsite"
+ site = factory.SubFactory(SiteFactory)
+ values = {
+ "domain_prefix": "testmicrosite",
+ "university": "test_microsite",
+ "platform_name": "Test Microsite DB",
+ "logo_image_url": "test_microsite/images/header-logo.png",
+ "email_from_address": "test_microsite_db@edx.org",
+ "payment_support_email": "test_microsit_dbe@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
",
+ "ALWAYS_REDIRECT_HOMEPAGE_TO_DASHBOARD_FOR_AUTHENTICATED_USER": False,
+ "COURSE_CATALOG_VISIBILITY_PERMISSION": "see_in_catalog",
+ "COURSE_ABOUT_VISIBILITY_PERMISSION": "see_about_page",
+ "ENABLE_SHOPPING_CART": True,
+ "ENABLE_PAID_COURSE_REGISTRATION": True,
+ "SESSION_COOKIE_DOMAIN": "test_microsite.localhost",
+ "nested_dict": {
+ "key 1": "value 1",
+ "key 2": "value 2",
+ }
+ }
+
+
+class MicrositeOrganizationMappingFactory(DjangoModelFactory):
+ """
+ Factory for MicrositeOrganizationMapping
+ """
+ class Meta(object):
+ model = MicrositeOrganizationMapping
+
+
+class MicrositeTemplateFactory(DjangoModelFactory):
+ """
+ Factory for MicrositeTemplate
+ """
+ class Meta(object):
+ model = MicrositeTemplate
diff --git a/common/djangoapps/microsite_configuration/tests/test_logic.py b/common/djangoapps/microsite_configuration/tests/test_logic.py
index 2d0ad93fcb..26e22f546b 100644
--- a/common/djangoapps/microsite_configuration/tests/test_logic.py
+++ b/common/djangoapps/microsite_configuration/tests/test_logic.py
@@ -2,25 +2,45 @@
Some additional unit tests for Microsite logic. The LMS covers some of the Microsite testing, this adds
some additional coverage
"""
-import django.test
+import ddt
+from mock import patch
-from microsite_configuration.microsite import get_value_for_org
+from microsite_configuration.microsite import (
+ get_value_for_org,
+ get_backend,
+)
+from microsite_configuration.backends.base import BaseMicrositeBackend
+from microsite_configuration.tests.tests import (
+ DatabaseMicrositeTestCase,
+ MICROSITE_BACKENDS,
+)
-class TestMicrosites(django.test.TestCase):
+@ddt.ddt
+class TestMicrosites(DatabaseMicrositeTestCase):
"""
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
- """
+ def setUp(self):
+ super(TestMicrosites, self).setUp()
- # 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")
+ @ddt.data(*MICROSITE_BACKENDS)
+ def test_get_value_for_org_when_microsite_has_no_org(self, site_backend):
+ """
+ Make sure default value is returned if there's no Microsite ORG match
+ """
+ with patch('microsite_configuration.microsite.BACKEND',
+ get_backend(site_backend, BaseMicrositeBackend)):
+ 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")
+ @ddt.data(*MICROSITE_BACKENDS)
+ def test_get_value_for_org(self, site_backend):
+ """
+ Make sure get_value_for_org return value of org if it present.
+ """
+ with patch('microsite_configuration.microsite.BACKEND',
+ get_backend(site_backend, BaseMicrositeBackend)):
+ value = get_value_for_org("TestMicrositeX", "university", "default_value")
+ self.assertEquals(value, "test_microsite")
diff --git a/common/djangoapps/microsite_configuration/tests/test_microsites.py b/common/djangoapps/microsite_configuration/tests/test_microsites.py
index 01cf04aa8a..e8982b414f 100644
--- a/common/djangoapps/microsite_configuration/tests/test_microsites.py
+++ b/common/djangoapps/microsite_configuration/tests/test_microsites.py
@@ -4,31 +4,73 @@ Tests microsite_configuration templatetags and helper functions.
"""
from django.test import TestCase
from django.conf import settings
-from microsite_configuration.templatetags import microsite
+from microsite_configuration.templatetags import microsite as microsite_tags
+from microsite_configuration import microsite
+from microsite_configuration.backends.base import BaseMicrositeBackend
+from microsite_configuration.backends.database import DatabaseMicrositeBackend
-class MicroSiteTests(TestCase):
+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'
- title = microsite.page_title_breadcrumbs(*crumbs)
+ title = microsite_tags.page_title_breadcrumbs(*crumbs)
self.assertEqual(expected, title)
def test_unicode_title(self):
crumbs = [u'øo', u'π tastes gréât', u'驴']
expected = u'øo | π tastes gréât | 驴 | edX'
- title = microsite.page_title_breadcrumbs(*crumbs)
+ title = microsite_tags.page_title_breadcrumbs(*crumbs)
self.assertEqual(expected, title)
def test_platform_name(self):
- pname = microsite.platform_name()
+ pname = microsite_tags.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)
+ title = microsite_tags.page_title_breadcrumbs_tag(None, *crumbs)
self.assertEqual(expected, title)
+
+ def test_microsite_template_path(self):
+ """
+ When an unexistent path is passed to the filter, it should return the same path
+ """
+ path = microsite_tags.microsite_template_path('footer.html')
+ self.assertEqual("footer.html", path)
+
+ def test_get_backend_raise_error_for_invalid_class(self):
+ """
+ Test get_backend returns None for invalid paths
+ and raises TypeError when invalid class or class name is a method.
+ """
+ # invalid backend path
+ self.assertEqual(microsite.get_backend(None, BaseMicrositeBackend), None)
+
+ # invalid class or class name is a method
+ with self.assertRaises(TypeError):
+ microsite.get_backend('microsite_configuration.microsite.get_backend', BaseMicrositeBackend)
+
+ def test_get_backend_raise_error_when_module_has_no_class(self):
+ """
+ Test get_backend raises ValueError when module does not have a class.
+ """
+ # module does not have a class
+ with self.assertRaises(ValueError):
+ microsite.get_backend('microsite_configuration.microsite.invalid_method', BaseMicrositeBackend)
+
+ def test_get_backend_for_valid_class(self):
+ """
+ Test get_backend loads class if class exists.
+ """
+ # load a valid class
+ self.assertIsInstance(
+ microsite.get_backend(
+ 'microsite_configuration.backends.database.DatabaseMicrositeBackend', BaseMicrositeBackend
+ ),
+ DatabaseMicrositeBackend
+ )
diff --git a/common/djangoapps/microsite_configuration/tests/test_middleware.py b/common/djangoapps/microsite_configuration/tests/test_middleware.py
index c68636dcc8..47d6cd20ea 100644
--- a/common/djangoapps/microsite_configuration/tests/test_middleware.py
+++ b/common/djangoapps/microsite_configuration/tests/test_middleware.py
@@ -2,28 +2,39 @@
"""
Test Microsite middleware.
"""
+import ddt
+import unittest
from mock import patch
-from django.test import TestCase
from django.conf import settings
from django.test.client import Client
from django.test.utils import override_settings
-import unittest
from student.tests.factories import UserFactory
+from microsite_configuration.microsite import (
+ get_backend,
+)
+from microsite_configuration.backends.base import BaseMicrositeBackend
+from microsite_configuration.tests.tests import (
+ DatabaseMicrositeTestCase,
+ side_effect_for_get_value,
+ MICROSITE_BACKENDS,
+)
# NOTE: We set SESSION_SAVE_EVERY_REQUEST to True in order to make sure
# Sessions are always started on every request
+# pylint: disable=no-member, protected-access
+@ddt.ddt
@override_settings(SESSION_SAVE_EVERY_REQUEST=True)
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
-class MicroSiteSessionCookieTests(TestCase):
+class MicrositeSessionCookieTests(DatabaseMicrositeTestCase):
"""
- Tests regarding the session cookie management in the middlware for MicroSites
+ Tests regarding the session cookie management in the middlware for Microsites
"""
def setUp(self):
- super(MicroSiteSessionCookieTests, self).setUp()
+ super(MicrositeSessionCookieTests, self).setUp()
# Create a test client, and log it in so that it will save some session
# data.
self.user = UserFactory.create()
@@ -32,29 +43,39 @@ class MicroSiteSessionCookieTests(TestCase):
self.client = Client()
self.client.login(username=self.user.username, password="password")
- def test_session_cookie_domain_no_microsite(self):
+ @ddt.data(*MICROSITE_BACKENDS)
+ def test_session_cookie_domain_no_microsite(self, site_backend):
"""
Tests that non-microsite behaves according to default behavior
"""
- response = self.client.get('/')
- self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
- self.assertNotIn('Domain', str(response.cookies['sessionid'])) # pylint: disable=no-member
+ with patch('microsite_configuration.microsite.BACKEND',
+ get_backend(site_backend, BaseMicrositeBackend)):
+ response = self.client.get('/')
+ self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid']))
+ self.assertNotIn('Domain', str(response.cookies['sessionid']))
- def test_session_cookie_domain(self):
+ @ddt.data(*MICROSITE_BACKENDS)
+ def test_session_cookie_domain(self, site_backend):
"""
Makes sure that the cookie being set in a Microsite
- is the one specially overridden in configuration,
- in this case in test.py
+ is the one specially overridden in configuration
"""
- response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
- self.assertIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
+ with patch('microsite_configuration.microsite.BACKEND',
+ get_backend(site_backend, BaseMicrositeBackend)):
+ response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
+ self.assertIn('test_microsite.localhost', str(response.cookies['sessionid']))
- @patch.dict("django.conf.settings.MICROSITE_CONFIGURATION", {'test_microsite': {'SESSION_COOKIE_DOMAIN': None}})
- def test_microsite_none_cookie_domain(self):
+ @ddt.data(*MICROSITE_BACKENDS)
+ def test_microsite_none_cookie_domain(self, site_backend):
"""
Tests to make sure that a Microsite that specifies None for 'SESSION_COOKIE_DOMAIN' does not
set a domain on the session cookie
"""
- response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
- self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid'])) # pylint: disable=no-member
- self.assertNotIn('Domain', str(response.cookies['sessionid'])) # pylint: disable=no-member
+
+ with patch('microsite_configuration.microsite.get_value') as mock_get_value:
+ mock_get_value.side_effect = side_effect_for_get_value('SESSION_COOKIE_DOMAIN', None)
+ with patch('microsite_configuration.microsite.BACKEND',
+ get_backend(site_backend, BaseMicrositeBackend)):
+ response = self.client.get('/', HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
+ self.assertNotIn('test_microsite.localhost', str(response.cookies['sessionid']))
+ self.assertNotIn('Domain', str(response.cookies['sessionid']))
diff --git a/common/djangoapps/microsite_configuration/tests/tests.py b/common/djangoapps/microsite_configuration/tests/tests.py
new file mode 100644
index 0000000000..616fe2ee3e
--- /dev/null
+++ b/common/djangoapps/microsite_configuration/tests/tests.py
@@ -0,0 +1,41 @@
+"""
+Holds base classes for microsite tests
+"""
+from mock import DEFAULT
+
+from django.test import TestCase
+from microsite_configuration.tests.factories import (
+ MicrositeFactory,
+ MicrositeOrganizationMappingFactory,
+)
+
+MICROSITE_BACKENDS = (
+ 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend',
+ 'microsite_configuration.backends.database.DatabaseMicrositeBackend',
+)
+
+
+class DatabaseMicrositeTestCase(TestCase):
+ """
+ Base class for microsite related tests.
+ """
+ def setUp(self):
+ super(DatabaseMicrositeTestCase, self).setUp()
+ self.microsite = MicrositeFactory.create()
+ MicrositeOrganizationMappingFactory.create(microsite=self.microsite, organization='TestMicrositeX')
+
+
+def side_effect_for_get_value(value, return_value):
+ """
+ returns a side_effect with given return value for a given value
+ """
+ def side_effect(*args, **kwargs): # pylint: disable=unused-argument
+ """
+ A side effect for tests which returns a value based
+ on a given argument otherwise return actual function.
+ """
+ if args[0] == value:
+ return return_value
+ else:
+ return DEFAULT
+ return side_effect
diff --git a/common/djangoapps/student/management/commands/change_enrollment.py b/common/djangoapps/student/management/commands/change_enrollment.py
index d6199a468c..8b5c1346f1 100644
--- a/common/djangoapps/student/management/commands/change_enrollment.py
+++ b/common/djangoapps/student/management/commands/change_enrollment.py
@@ -1,10 +1,24 @@
-from django.core.management.base import BaseCommand, CommandError
-from opaque_keys import InvalidKeyError
-from optparse import make_option
-from student.models import CourseEnrollment, User
+""" Command line script to change user enrollments. """
+import logging
+
+from django.core.management.base import BaseCommand, CommandError
+from django.db import transaction
+from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
+from optparse import make_option
+
+from student.models import CourseEnrollment, User
+
+logger = logging.getLogger(__name__) # pylint: disable=invalid-name
+
+
+class RollbackException(Exception):
+ """
+ Exception raised explicitly to cause a database transaction rollback.
+ """
+ pass
class Command(BaseCommand):
@@ -15,17 +29,17 @@ class Command(BaseCommand):
Example:
- Change enrollment for user joe from audit to honor:
+ Change enrollment for users joe, frank, and bill from audit to honor:
$ ... change_enrollment -u joe,frank,bill -c some/course/id --from audit --to honor
Or
- $ ... change_enrollment -u "joe@example.com,frank@example.com,bill@example.com" -c some/course/id --from audit --to honor
+ $ ... change_enrollment -e "joe@example.com,frank@example.com,bill@example.com" -c some/course/id --from audit --to honor
- Change enrollment for all users in some/course/id from audit to honor
+ See what would have been changed from audit to honor without making that change
- $ ... change_enrollment -c some/course/id --from audit --to honor
+ $ ... change_enrollment -u joe,frank,bill -c some/course/id --from audit --to honor -n
"""
@@ -40,11 +54,16 @@ class Command(BaseCommand):
dest='to_mode',
default=False,
help='move to this enrollment mode'),
- make_option('-u', '--user',
- metavar='USER',
- dest='user',
+ make_option('-u', '--usernames',
+ metavar='USERNAME',
+ dest='username',
default=False,
- help="Comma-separated list of users to move in the course"),
+ help="Comma-separated list of usernames to move in the course"),
+ make_option('-e', '--emails',
+ metavar='EMAIL',
+ dest='email',
+ default=False,
+ help="Comma-separated list of email addresses to move in the course"),
make_option('-c', '--course',
metavar='COURSE_ID',
dest='course_id',
@@ -59,8 +78,11 @@ class Command(BaseCommand):
)
def handle(self, *args, **options):
+ error_users = []
+ success_users = []
+
if not options['course_id']:
- raise CommandError("You must specify a course id for this command")
+ raise CommandError('You must specify a course id for this command')
if not options['from_mode'] or not options['to_mode']:
raise CommandError('You must specify a "to" and "from" mode as parameters')
@@ -69,26 +91,55 @@ class Command(BaseCommand):
except InvalidKeyError:
course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course_id'])
- filter_args = dict(
+ enrollment_args = dict(
course_id=course_key,
mode=options['from_mode']
)
- if options['user']:
- user_str = options['user']
- for username in user_str.split(","):
- if '@' in username:
- user = User.objects.get(email=username)
- else:
- user = User.objects.get(username=username)
- filter_args['user'] = user
- enrollments = CourseEnrollment.objects.filter(**filter_args)
- if options['noop']:
- print "Would have changed {num_enrollments} students from {from_mode} to {to_mode}".format(
- num_enrollments=enrollments.count(),
- from_mode=options['from_mode'],
- to_mode=options['to_mode']
- )
- else:
+
+ if options['username']:
+ self.update_enrollments('username', enrollment_args, options, error_users, success_users)
+
+ if options['email']:
+ self.update_enrollments('email', enrollment_args, options, error_users, success_users)
+
+ self.report(error_users, success_users)
+
+ def update_enrollments(self, identifier, enrollment_args, options, error_users, success_users):
+ """ Update enrollments for a specific user identifier (email or username). """
+ users = options[identifier].split(",")
+ for identified_user in users:
+ logger.info(identified_user)
+ try:
+ user_args = {
+ identifier: identified_user
+ }
+
+ enrollment_args['user'] = User.objects.get(**user_args)
+ enrollments = CourseEnrollment.objects.filter(**enrollment_args)
+
+ with transaction.atomic():
for enrollment in enrollments:
enrollment.update_enrollment(mode=options['to_mode'])
enrollment.save()
+
+ if options['noop']:
+ raise RollbackException('Forced rollback.')
+
+ except RollbackException:
+ success_users.append(identified_user)
+ continue
+ except Exception as exception: # pylint: disable=broad-except
+ error_users.append((identified_user, exception))
+ continue
+
+ success_users.append(identified_user)
+ logger.info('Updated user [%s] to mode [%s]', identified_user, options['to_mode'])
+
+ def report(self, error_users, success_users):
+ """ Log and overview of the results of the command. """
+ total_users = len(success_users) + len(error_users)
+ logger.info('Successfully updated %i out of %i users', len(success_users), total_users)
+ if len(error_users) > 0:
+ logger.info('The following %i user(s) not saved:', len(error_users))
+ for user, error in error_users:
+ logger.info('user: [%s] reason: [%s] %s', user, type(error).__name__, error.message)
diff --git a/common/djangoapps/student/management/tests/test_change_enrollment.py b/common/djangoapps/student/management/tests/test_change_enrollment.py
new file mode 100644
index 0000000000..1d554ba47b
--- /dev/null
+++ b/common/djangoapps/student/management/tests/test_change_enrollment.py
@@ -0,0 +1,123 @@
+""" Test the change_enrollment command line script."""
+
+import ddt
+from mock import patch
+
+from django.core.management import call_command
+from xmodule.modulestore.tests.factories import CourseFactory
+from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
+
+from student.tests.factories import UserFactory, CourseModeFactory
+from student.models import CourseEnrollment
+
+
+@ddt.ddt
+class ChangeEnrollmentTests(SharedModuleStoreTestCase):
+ """ Test the enrollment change functionality of the change_enrollment script."""
+ def setUp(self):
+ super(ChangeEnrollmentTests, self).setUp()
+ self.course = CourseFactory.create()
+ self.audit_mode = CourseModeFactory.create(
+ course_id=self.course.id,
+ mode_slug='audit',
+ mode_display_name='Audit',
+ )
+ self.honor_mode = CourseModeFactory.create(
+ course_id=self.course.id,
+ mode_slug='honor',
+ mode_display_name='Honor',
+ )
+
+ self.user_info = [
+ ('amy', 'amy@pond.com', 'password'),
+ ('rory', 'rory@theroman.com', 'password'),
+ ('river', 'river@song.com', 'password')
+ ]
+ self.enrollments = []
+ self.users = []
+ for username, email, password in self.user_info:
+ user = UserFactory.create(username=username, email=email, password=password)
+ self.users.append(user)
+ self.enrollments.append(CourseEnrollment.enroll(user, self.course.id, mode='audit'))
+
+ @patch('student.management.commands.change_enrollment.logger')
+ @ddt.data(
+ ('email', False, 3),
+ ('username', False, 3),
+ ('email', True, 0),
+ ('username', True, 0),
+ )
+ @ddt.unpack
+ def test_convert_users(self, method, noop, expected_conversions, mock_logger):
+ """ The command should update the user's enrollment. """
+ user_str = ','.join([getattr(user, method) for user in self.users])
+ user_ids = [u.id for u in self.users]
+ command_args = {
+ 'course_id': unicode(self.course.id),
+ 'to_mode': 'honor',
+ 'from_mode': 'audit',
+ 'noop': noop,
+ method: user_str,
+ }
+
+ # Verify users are not in honor mode yet
+ self.assertEqual(
+ len(CourseEnrollment.objects.filter(mode='honor', user_id__in=user_ids)),
+ 0
+ )
+
+ call_command(
+ 'change_enrollment',
+ **command_args
+ )
+
+ # Verify correct number of users are now in honor mode
+ self.assertEqual(
+ len(CourseEnrollment.objects.filter(mode='honor', user_id__in=user_ids)),
+ expected_conversions
+ )
+
+ mock_logger.info.assert_called_with(
+ 'Successfully updated %i out of %i users',
+ len(self.users),
+ len(self.users)
+ )
+
+ @patch('student.management.commands.change_enrollment.logger')
+ @ddt.data(
+ ('email', 'dtennant@thedoctor.com', 3),
+ ('username', 'dtennant', 3),
+ )
+ @ddt.unpack
+ def test_user_not_found(self, method, fake_user, expected_success, mock_logger):
+ all_users = [getattr(user, method) for user in self.users]
+ all_users.append(fake_user)
+ user_str = ','.join(all_users)
+ real_user_ids = [u.id for u in self.users]
+ command_args = {
+ 'course_id': unicode(self.course.id),
+ 'to_mode': 'honor',
+ 'from_mode': 'audit',
+ method: user_str,
+ }
+
+ # Verify users are not in honor mode yet
+ self.assertEqual(
+ len(CourseEnrollment.objects.filter(mode='honor', user_id__in=real_user_ids)),
+ 0
+ )
+
+ call_command(
+ 'change_enrollment',
+ **command_args
+ )
+
+ # Verify correct number of users are now in honor mode
+ self.assertEqual(
+ len(CourseEnrollment.objects.filter(mode='honor', user_id__in=real_user_ids)),
+ expected_success
+ )
+
+ mock_logger.info.assert_called_with(
+ 'user: [%s] reason: [%s] %s', fake_user, 'DoesNotExist', 'User matching query does not exist.'
+ )
diff --git a/common/djangoapps/student/tests/test_userstanding.py b/common/djangoapps/student/tests/test_userstanding.py
index 28ad48f3e1..c65bd9264e 100644
--- a/common/djangoapps/student/tests/test_userstanding.py
+++ b/common/djangoapps/student/tests/test_userstanding.py
@@ -57,6 +57,13 @@ class UserStandingTest(TestCase):
# since it's only possible to disable accounts from lms, we're going
# to skip tests for cms
+ @unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
+ def test_can_access_manage_account_page(self):
+ response = self.admin_client.get(reverse('manage_user_standing'), {
+ 'user': self.admin,
+ })
+ self.assertEqual(response.status_code, 200)
+
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
def test_disable_account(self):
self.assertEqual(
diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py
index b44f6aad63..5d9cb96f1f 100644
--- a/common/djangoapps/student/tests/tests.py
+++ b/common/djangoapps/student/tests/tests.py
@@ -898,6 +898,7 @@ class AnonymousLookupTable(ModuleStoreTestCase):
self.assertEqual(anonymous_id, anonymous_id_for_user(self.user, course2.id, save=False))
+# TODO: Clean up these tests so that they use the ProgramsDataMixin.
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
@ddt.ddt
class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
@@ -928,8 +929,11 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
def _create_program_data(self, data):
"""Dry method to create testing programs data."""
programs = {}
+ _id = 0
+
for course, program_status in data:
programs[unicode(course)] = {
+ 'id': _id,
'category': self.category,
'organization': {'display_name': 'Test Organization 1', 'key': 'edX'},
'marketing_slug': 'fake-marketing-slug-xseries-1',
@@ -955,6 +959,8 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
'name': self.program_name
}
+ _id += 1
+
return programs
@ddt.data(
@@ -969,6 +975,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
with patch('student.views.get_programs_for_dashboard') as mock_data:
mock_data.return_value = {
u'edx/demox/Run_1': {
+ 'id': 0,
'category': self.category,
'organization': {'display_name': 'Test Organization 1', 'key': 'edX'},
'marketing_slug': marketing_slug,
@@ -990,6 +997,7 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin):
self.assertEqual(
{
u'edx/demox/Run_1': {
+ 'program_id': 0,
'category': 'xseries',
'course_count': len(course_codes),
'display_name': self.program_name,
diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py
index cf82b15d64..970e096490 100644
--- a/common/djangoapps/student/views.py
+++ b/common/djangoapps/student/views.py
@@ -1346,7 +1346,7 @@ def manage_user_standing(request):
headers = ['username', 'account_changed_by']
rows = []
for user in all_disabled_users:
- row = [user.username, user.standing.all()[0].changed_by]
+ row = [user.username, user.standing.changed_by]
rows.append(row)
context = {'headers': headers, 'rows': rows}
@@ -2378,6 +2378,7 @@ def _get_course_programs(user, user_enrolled_courses): # pylint: disable=invali
'course_count': len(program['course_codes']),
'display_name': program['name'],
'category': program.get('category'),
+ 'program_id': program['id'],
'program_marketing_url': urljoin(
settings.MKTG_URLS.get('ROOT'), 'xseries' + '/{}'
).format(program['marketing_slug']),
diff --git a/common/djangoapps/util/url.py b/common/djangoapps/util/url.py
index a5deda2b4e..ffe20e4885 100644
--- a/common/djangoapps/util/url.py
+++ b/common/djangoapps/util/url.py
@@ -20,3 +20,10 @@ def reload_django_url_config():
reloaded = import_module(urlconf)
reloaded_urls = reloaded.urlpatterns
set_urlconf(tuple(reloaded_urls))
+
+
+def strip_port_from_host(host):
+ """
+ Strips port number from host
+ """
+ return host.split(':')[0]
diff --git a/common/lib/capa/capa/templates/schematicinput.html b/common/lib/capa/capa/templates/schematicinput.html
index f34dca55e8..6b1f798139 100644
--- a/common/lib/capa/capa/templates/schematicinput.html
+++ b/common/lib/capa/capa/templates/schematicinput.html
@@ -16,7 +16,8 @@
/>
-
+
+
${status.display_name}
-
+
diff --git a/common/lib/xmodule/xmodule/course_module.py b/common/lib/xmodule/xmodule/course_module.py
index c4a187f670..49d9554927 100644
--- a/common/lib/xmodule/xmodule/course_module.py
+++ b/common/lib/xmodule/xmodule/course_module.py
@@ -693,11 +693,17 @@ class CourseFields(object):
teams_configuration = Dict(
display_name=_("Teams Configuration"),
help=_(
- "Enter configuration for the teams feature. Expects two entries: max_team_size and topics, where "
- "topics is a list of topics."
+ 'Specify the maximum team size and topics for teams inside the provided set of curly braces. '
+ 'Make sure that you enclose all of the sets of topic values within a set of square brackets, '
+ 'with a comma after the closing curly brace for each topic, and another comma after the '
+ 'closing square brackets. '
+ 'For example, to specify that teams should have a maximum of 5 participants and provide a list of '
+ '2 topics, enter the configuration in this format: '
+ '{"topics": [{"name": "Topic1Name", "description": "Topic1Description", "id": "Topic1ID"}, '
+ '{"name": "Topic2Name", "description": "Topic2Description", "id": "Topic2ID"}], "max_team_size": 5}. '
+ 'In "id" values, the only supported special characters are underscore, hyphen, and period.'
),
scope=Scope.settings,
- deprecated=True, # Deprecated until the teams feature is made generally available
)
enable_proctored_exams = Boolean(
diff --git a/common/lib/xmodule/xmodule/css/sequence/display.scss b/common/lib/xmodule/xmodule/css/sequence/display.scss
index c86ff553f5..775dc38b50 100644
--- a/common/lib/xmodule/xmodule/css/sequence/display.scss
+++ b/common/lib/xmodule/xmodule/css/sequence/display.scss
@@ -1,5 +1,5 @@
$sequence--border-color: #C8C8C8;
-$link-color: rgb(26, 161, 222);
+$link-color: rgb(26, 161, 222) !default;
// repeated extends - needed since LMS styling was referenced
.block-link {
border-left: 1px solid lighten($sequence--border-color, 10%);
diff --git a/common/lib/xmodule/xmodule/js/fixtures/poster.jpg b/common/lib/xmodule/xmodule/js/fixtures/poster.jpg
index 7f987f944e..35cadbb223 100644
Binary files a/common/lib/xmodule/xmodule/js/fixtures/poster.jpg and b/common/lib/xmodule/xmodule/js/fixtures/poster.jpg differ
diff --git a/common/static/css/vendor/ova/video-js.fw.png b/common/static/css/vendor/ova/video-js.fw.png
index 2c7f208c21..64fa09025d 100644
Binary files a/common/static/css/vendor/ova/video-js.fw.png and b/common/static/css/vendor/ova/video-js.fw.png differ
diff --git a/common/static/css/vendor/ova/video-js.png b/common/static/css/vendor/ova/video-js.png
index d877829524..0de6d15570 100644
Binary files a/common/static/css/vendor/ova/video-js.png and b/common/static/css/vendor/ova/video-js.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl.png b/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl.png
index bef02743fc..b6b72e85b7 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl.png and b/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl@2x.png b/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl@2x.png
index 1da6dc949c..92adf3e3ad 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/findbarButton-next-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-next.png b/common/static/css/vendor/pdfjs/images/findbarButton-next.png
index de1d0fc901..43014d4d56 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-next.png and b/common/static/css/vendor/pdfjs/images/findbarButton-next.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-next@2x.png b/common/static/css/vendor/pdfjs/images/findbarButton-next@2x.png
index 0250307c0d..4e48492257 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-next@2x.png and b/common/static/css/vendor/pdfjs/images/findbarButton-next@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl.png b/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl.png
index de1d0fc901..43014d4d56 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl.png and b/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl@2x.png b/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl@2x.png
index 0250307c0d..4e48492257 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/findbarButton-previous-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-previous.png b/common/static/css/vendor/pdfjs/images/findbarButton-previous.png
index bef02743fc..b6b72e85b7 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-previous.png and b/common/static/css/vendor/pdfjs/images/findbarButton-previous.png differ
diff --git a/common/static/css/vendor/pdfjs/images/findbarButton-previous@2x.png b/common/static/css/vendor/pdfjs/images/findbarButton-previous@2x.png
index 1da6dc949c..92adf3e3ad 100644
Binary files a/common/static/css/vendor/pdfjs/images/findbarButton-previous@2x.png and b/common/static/css/vendor/pdfjs/images/findbarButton-previous@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/loading-icon.gif b/common/static/css/vendor/pdfjs/images/loading-icon.gif
index 1c72ebb554..87985e9385 100644
Binary files a/common/static/css/vendor/pdfjs/images/loading-icon.gif and b/common/static/css/vendor/pdfjs/images/loading-icon.gif differ
diff --git a/common/static/css/vendor/pdfjs/images/loading-small.png b/common/static/css/vendor/pdfjs/images/loading-small.png
index 8831a80588..8b15dc5da2 100644
Binary files a/common/static/css/vendor/pdfjs/images/loading-small.png and b/common/static/css/vendor/pdfjs/images/loading-small.png differ
diff --git a/common/static/css/vendor/pdfjs/images/loading-small@2x.png b/common/static/css/vendor/pdfjs/images/loading-small@2x.png
index b25b4452aa..5956afc7fd 100644
Binary files a/common/static/css/vendor/pdfjs/images/loading-small@2x.png and b/common/static/css/vendor/pdfjs/images/loading-small@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties.png
index 40925e25ac..426e339130 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties@2x.png
index adb240eaad..5ee535674c 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-documentProperties@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage.png
index e68846aa5f..d332a26830 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage@2x.png
index 3ad8af5173..470785b4ea 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-firstPage@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool.png
index cb85a841b1..633db3fb50 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool@2x.png
index 5c13f77ff0..419fb7d161 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-handTool@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage.png
index be763e0c4a..6502d9e95e 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage@2x.png
index 8570984f2d..d52cd0ac8b 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-lastPage@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw.png
index 675d6da2c0..2b31f215f6 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw@2x.png
index b9e7431227..e41b34e928 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCcw@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw.png
index e1c7598886..adc8e07b35 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw.png differ
diff --git a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw@2x.png b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw@2x.png
index cb257b41c5..0764e8f9ac 100644
Binary files a/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw@2x.png and b/common/static/css/vendor/pdfjs/images/secondaryToolbarButton-rotateCw@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark.png b/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark.png
index a187be6c9b..3f48bdf591 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark@2x.png
index 4efbaa6758..e097237189 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-bookmark@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-download.png b/common/static/css/vendor/pdfjs/images/toolbarButton-download.png
index eaab35f09e..42d382518c 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-download.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-download.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-download@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-download@2x.png
index 896face455..95f8d55f64 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-download@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-download@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows.png b/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows.png
index 306eb43b86..a218d2afbf 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows@2x.png
index f7570bc0d3..8b10b28430 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-menuArrows@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-openFile.png b/common/static/css/vendor/pdfjs/images/toolbarButton-openFile.png
index b5cf1bd061..3b6b8fac0a 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-openFile.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-openFile.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-openFile@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-openFile@2x.png
index 91ab76593e..1f0f865aac 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-openFile@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-openFile@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl.png
index 1957f79ab9..34c54a628a 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl@2x.png
index 16ebcb8ef1..815409e81a 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown.png
index 8219ecf83c..3e88461e98 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown@2x.png
index 758c01d836..00379038c8 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageDown@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl.png
index 98e7ce481c..db0dc76a1d 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl@2x.png
index a01b02380b..a38c79a132 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp.png
index fb9daa3376..bfc787cd53 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp@2x.png
index a5cfd755b0..8527ec2bdd 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-pageUp@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode.png b/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode.png
index 3ac21244df..786818f052 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode@2x.png
index cada9e7918..3c729cd5c5 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-presentationMode@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-print.png b/common/static/css/vendor/pdfjs/images/toolbarButton-print.png
index 51275e54be..729381ba68 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-print.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-print.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-print@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-print@2x.png
index 53d18daf78..d7c4f67d89 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-print@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-print@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-search.png b/common/static/css/vendor/pdfjs/images/toolbarButton-search.png
index f9b75579b1..52e6e445b4 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-search.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-search.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-search@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-search@2x.png
index 456b133248..117c0f85ae 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-search@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-search@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl.png b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl.png
index 8437095273..062b4e4819 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png
index 9d9bfa4f63..966807e33b 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle.png b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle.png
index 1f90f83da7..376bd62a45 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle@2x.png
index b066fe5cb0..a41f1b4c54 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-secondaryToolbarToggle@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl.png b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl.png
index 6f85ec061e..4bf18d5125 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl@2x.png
index 291e006797..2b7eb3670d 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle.png b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle.png
index 025dc9040e..209e4cf1b5 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle@2x.png
index 7f834df940..3d71ad0e40 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-sidebarToggle@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments.png
index fcd0b268a4..03cc219eed 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments@2x.png
index b979e523e7..a739f2256a 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewAttachments@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl.png
index aaa9430211..35bd1dcd7e 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl@2x.png
index 3410f70dfa..4324bcef6a 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline-rtl@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline.png
index 976365a506..1917bc3fa3 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline@2x.png
index b6a197fdf3..bbf0655336 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewOutline@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail.png
index 584ba55881..f8c035e264 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail@2x.png
index fb7db93836..d85adfba81 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-viewThumbnail@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn.png b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn.png
index 513d081bc2..74d83efd1e 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn@2x.png
index d5d49d5ff1..0c57168922 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomIn@2x.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut.png b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut.png
index 156c26b941..f1ddfaca23 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut.png differ
diff --git a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut@2x.png b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut@2x.png
index 959e1919d5..d2e68740fe 100644
Binary files a/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut@2x.png and b/common/static/css/vendor/pdfjs/images/toolbarButton-zoomOut@2x.png differ
diff --git a/common/static/css/vendor/slickgrid/images/ajax-loader-small.gif b/common/static/css/vendor/slickgrid/images/ajax-loader-small.gif
index 5b33f7e54f..d44cf73a17 100755
Binary files a/common/static/css/vendor/slickgrid/images/ajax-loader-small.gif and b/common/static/css/vendor/slickgrid/images/ajax-loader-small.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/arrow_redo.png b/common/static/css/vendor/slickgrid/images/arrow_redo.png
index 4f7f55d6f2..bf910e8521 100755
Binary files a/common/static/css/vendor/slickgrid/images/arrow_redo.png and b/common/static/css/vendor/slickgrid/images/arrow_redo.png differ
diff --git a/common/static/css/vendor/slickgrid/images/arrow_right_peppermint.png b/common/static/css/vendor/slickgrid/images/arrow_right_peppermint.png
index 8722567866..f8391e7f70 100755
Binary files a/common/static/css/vendor/slickgrid/images/arrow_right_peppermint.png and b/common/static/css/vendor/slickgrid/images/arrow_right_peppermint.png differ
diff --git a/common/static/css/vendor/slickgrid/images/arrow_right_spearmint.png b/common/static/css/vendor/slickgrid/images/arrow_right_spearmint.png
index 277ddde384..02e3e4c446 100755
Binary files a/common/static/css/vendor/slickgrid/images/arrow_right_spearmint.png and b/common/static/css/vendor/slickgrid/images/arrow_right_spearmint.png differ
diff --git a/common/static/css/vendor/slickgrid/images/arrow_undo.png b/common/static/css/vendor/slickgrid/images/arrow_undo.png
index bc9924ac07..06607e5902 100755
Binary files a/common/static/css/vendor/slickgrid/images/arrow_undo.png and b/common/static/css/vendor/slickgrid/images/arrow_undo.png differ
diff --git a/common/static/css/vendor/slickgrid/images/bullet_blue.png b/common/static/css/vendor/slickgrid/images/bullet_blue.png
index 79d978c36a..eca2db9a18 100755
Binary files a/common/static/css/vendor/slickgrid/images/bullet_blue.png and b/common/static/css/vendor/slickgrid/images/bullet_blue.png differ
diff --git a/common/static/css/vendor/slickgrid/images/bullet_star.png b/common/static/css/vendor/slickgrid/images/bullet_star.png
index 142ea482a5..3d5a80e689 100755
Binary files a/common/static/css/vendor/slickgrid/images/bullet_star.png and b/common/static/css/vendor/slickgrid/images/bullet_star.png differ
diff --git a/common/static/css/vendor/slickgrid/images/bullet_toggle_minus.png b/common/static/css/vendor/slickgrid/images/bullet_toggle_minus.png
index f5aa0450d4..e10f7f39eb 100755
Binary files a/common/static/css/vendor/slickgrid/images/bullet_toggle_minus.png and b/common/static/css/vendor/slickgrid/images/bullet_toggle_minus.png differ
diff --git a/common/static/css/vendor/slickgrid/images/bullet_toggle_plus.png b/common/static/css/vendor/slickgrid/images/bullet_toggle_plus.png
index a965053423..42e4218f4e 100755
Binary files a/common/static/css/vendor/slickgrid/images/bullet_toggle_plus.png and b/common/static/css/vendor/slickgrid/images/bullet_toggle_plus.png differ
diff --git a/common/static/css/vendor/slickgrid/images/collapse.gif b/common/static/css/vendor/slickgrid/images/collapse.gif
index 01e691450c..84c73aefb1 100755
Binary files a/common/static/css/vendor/slickgrid/images/collapse.gif and b/common/static/css/vendor/slickgrid/images/collapse.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/down.gif b/common/static/css/vendor/slickgrid/images/down.gif
index 9bd9447552..b78623c454 100755
Binary files a/common/static/css/vendor/slickgrid/images/down.gif and b/common/static/css/vendor/slickgrid/images/down.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/drag-handle.png b/common/static/css/vendor/slickgrid/images/drag-handle.png
old mode 100755
new mode 100644
index ad7531cf04..faa1c9f22f
Binary files a/common/static/css/vendor/slickgrid/images/drag-handle.png and b/common/static/css/vendor/slickgrid/images/drag-handle.png differ
diff --git a/common/static/css/vendor/slickgrid/images/editor-helper-bg.gif b/common/static/css/vendor/slickgrid/images/editor-helper-bg.gif
index 2daa973bc5..f422ab1598 100755
Binary files a/common/static/css/vendor/slickgrid/images/editor-helper-bg.gif and b/common/static/css/vendor/slickgrid/images/editor-helper-bg.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/expand.gif b/common/static/css/vendor/slickgrid/images/expand.gif
index 1b24ef1248..dad7d83b4b 100755
Binary files a/common/static/css/vendor/slickgrid/images/expand.gif and b/common/static/css/vendor/slickgrid/images/expand.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/header-bg.gif b/common/static/css/vendor/slickgrid/images/header-bg.gif
index fe7dd1c1eb..5cb09a31c0 100755
Binary files a/common/static/css/vendor/slickgrid/images/header-bg.gif and b/common/static/css/vendor/slickgrid/images/header-bg.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/header-columns-bg.gif b/common/static/css/vendor/slickgrid/images/header-columns-bg.gif
index 8d459a304e..a1e64b701f 100755
Binary files a/common/static/css/vendor/slickgrid/images/header-columns-bg.gif and b/common/static/css/vendor/slickgrid/images/header-columns-bg.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/header-columns-over-bg.gif b/common/static/css/vendor/slickgrid/images/header-columns-over-bg.gif
index f9c07af134..14002d89fc 100755
Binary files a/common/static/css/vendor/slickgrid/images/header-columns-over-bg.gif and b/common/static/css/vendor/slickgrid/images/header-columns-over-bg.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/help.png b/common/static/css/vendor/slickgrid/images/help.png
old mode 100755
new mode 100644
index 85eca0950f..864e9fa43c
Binary files a/common/static/css/vendor/slickgrid/images/help.png and b/common/static/css/vendor/slickgrid/images/help.png differ
diff --git a/common/static/css/vendor/slickgrid/images/pencil.gif b/common/static/css/vendor/slickgrid/images/pencil.gif
index 29f78f433d..c75371f742 100755
Binary files a/common/static/css/vendor/slickgrid/images/pencil.gif and b/common/static/css/vendor/slickgrid/images/pencil.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/row-over-bg.gif b/common/static/css/vendor/slickgrid/images/row-over-bg.gif
index b288e38739..b872a5c02d 100755
Binary files a/common/static/css/vendor/slickgrid/images/row-over-bg.gif and b/common/static/css/vendor/slickgrid/images/row-over-bg.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/sort-asc.gif b/common/static/css/vendor/slickgrid/images/sort-asc.gif
index 67a2a4c669..0431881b12 100755
Binary files a/common/static/css/vendor/slickgrid/images/sort-asc.gif and b/common/static/css/vendor/slickgrid/images/sort-asc.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/sort-asc.png b/common/static/css/vendor/slickgrid/images/sort-asc.png
index 8604ff4e07..d5438aa419 100755
Binary files a/common/static/css/vendor/slickgrid/images/sort-asc.png and b/common/static/css/vendor/slickgrid/images/sort-asc.png differ
diff --git a/common/static/css/vendor/slickgrid/images/sort-desc.gif b/common/static/css/vendor/slickgrid/images/sort-desc.gif
index 34db47c3b1..ee2198af7f 100755
Binary files a/common/static/css/vendor/slickgrid/images/sort-desc.gif and b/common/static/css/vendor/slickgrid/images/sort-desc.gif differ
diff --git a/common/static/css/vendor/slickgrid/images/sort-desc.png b/common/static/css/vendor/slickgrid/images/sort-desc.png
index a2a6adf936..ff921de4ea 100755
Binary files a/common/static/css/vendor/slickgrid/images/sort-desc.png and b/common/static/css/vendor/slickgrid/images/sort-desc.png differ
diff --git a/common/static/css/vendor/slickgrid/images/stripes.png b/common/static/css/vendor/slickgrid/images/stripes.png
old mode 100755
new mode 100644
index c3c4b28a80..d800872c8f
Binary files a/common/static/css/vendor/slickgrid/images/stripes.png and b/common/static/css/vendor/slickgrid/images/stripes.png differ
diff --git a/common/static/css/vendor/slickgrid/images/tag_red.png b/common/static/css/vendor/slickgrid/images/tag_red.png
index d290fcd791..c47c5c79e7 100755
Binary files a/common/static/css/vendor/slickgrid/images/tag_red.png and b/common/static/css/vendor/slickgrid/images/tag_red.png differ
diff --git a/common/static/css/vendor/slickgrid/images/tick.png b/common/static/css/vendor/slickgrid/images/tick.png
index 3899d71dfa..e939f500cb 100755
Binary files a/common/static/css/vendor/slickgrid/images/tick.png and b/common/static/css/vendor/slickgrid/images/tick.png differ
diff --git a/common/static/css/vendor/slickgrid/images/user_identity.gif b/common/static/css/vendor/slickgrid/images/user_identity.gif
index 095831ba42..26a1cd6659 100755
Binary files a/common/static/css/vendor/slickgrid/images/user_identity.gif and b/common/static/css/vendor/slickgrid/images/user_identity.gif differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png
old mode 100755
new mode 100644
index 5b5dab2ab7..b3fe56f2ae
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_75_ffffff_40x100.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_75_ffffff_40x100.png
old mode 100755
new mode 100644
index ac8b229af9..e36540bbf9
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_75_ffffff_40x100.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_flat_75_ffffff_40x100.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png
index ad3d6346e0..91302ef118 100755
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_65_ffffff_1x400.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_65_ffffff_1x400.png
old mode 100755
new mode 100644
index 42ccba269b..80b2b05bba
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_65_ffffff_1x400.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_dadada_1x400.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_dadada_1x400.png
index 5a46b47cb1..2f87dbba5a 100755
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_dadada_1x400.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_dadada_1x400.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png
index 86c2baa655..d75eeb253f 100755
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png
index 4443fdc1a1..100afa332a 100755
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png
old mode 100755
new mode 100644
index 7c9fa6c6ed..3cd467e18e
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_222222_256x240.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_222222_256x240.png
old mode 100755
new mode 100644
index b273ff111d..ae6252b536
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_222222_256x240.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_222222_256x240.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_2e83ff_256x240.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_2e83ff_256x240.png
old mode 100755
new mode 100644
index 09d1cdc856..a7b2e55db9
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_2e83ff_256x240.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_2e83ff_256x240.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_454545_256x240.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_454545_256x240.png
old mode 100755
new mode 100644
index 59bd45b907..061a3b82a7
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_454545_256x240.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_454545_256x240.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_888888_256x240.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_888888_256x240.png
old mode 100755
new mode 100644
index 6d02426c11..513b9925ae
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_888888_256x240.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_888888_256x240.png differ
diff --git a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_cd0a0a_256x240.png b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_cd0a0a_256x240.png
old mode 100755
new mode 100644
index 2ab019b73e..1edce191a2
Binary files a/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_cd0a0a_256x240.png and b/common/static/css/vendor/slickgrid/smoothness/images/ui-icons_cd0a0a_256x240.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png b/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png
old mode 100755
new mode 100644
index 954e22dbd9..04bd67aa6c
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_18_b81900_40x40.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png b/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png
old mode 100755
new mode 100644
index 64ece5707d..30d5434d50
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_diagonals-thick_20_666666_40x40.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_flat_10_000000_40x100.png b/common/static/css/vendor/ui-lightness/images/ui-bg_flat_10_000000_40x100.png
old mode 100755
new mode 100644
index abdc01082b..cdddc6df68
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_flat_10_000000_40x100.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_flat_10_000000_40x100.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png
index 9b383f4d2e..06a4b479d9 100755
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_f6f6f6_1x400.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png
index a23baad25b..8f1e5feab0 100755
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_100_fdf5ce_1x400.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png
old mode 100755
new mode 100644
index 42ccba269b..80b2b05bba
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_glass_65_ffffff_1x400.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png b/common/static/css/vendor/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png
old mode 100755
new mode 100644
index 39d5824d6a..7eb023e509
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_gloss-wave_35_f6a828_500x100.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png b/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png
index f1273672d2..9e62f3b676 100755
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_100_eeeeee_1x100.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png b/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png
index 359397acff..398d940963 100755
Binary files a/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png and b/common/static/css/vendor/ui-lightness/images/ui-bg_highlight-soft_75_ffe45c_1x100.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-icons_222222_256x240.png b/common/static/css/vendor/ui-lightness/images/ui-icons_222222_256x240.png
old mode 100755
new mode 100644
index b273ff111d..664494d838
Binary files a/common/static/css/vendor/ui-lightness/images/ui-icons_222222_256x240.png and b/common/static/css/vendor/ui-lightness/images/ui-icons_222222_256x240.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-icons_228ef1_256x240.png b/common/static/css/vendor/ui-lightness/images/ui-icons_228ef1_256x240.png
old mode 100755
new mode 100644
index a641a371af..b5affc3e2a
Binary files a/common/static/css/vendor/ui-lightness/images/ui-icons_228ef1_256x240.png and b/common/static/css/vendor/ui-lightness/images/ui-icons_228ef1_256x240.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-icons_ef8c08_256x240.png b/common/static/css/vendor/ui-lightness/images/ui-icons_ef8c08_256x240.png
old mode 100755
new mode 100644
index 85e63e9f60..95c6544246
Binary files a/common/static/css/vendor/ui-lightness/images/ui-icons_ef8c08_256x240.png and b/common/static/css/vendor/ui-lightness/images/ui-icons_ef8c08_256x240.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-icons_ffd27a_256x240.png b/common/static/css/vendor/ui-lightness/images/ui-icons_ffd27a_256x240.png
old mode 100755
new mode 100644
index e117effa3d..5ceff0d1eb
Binary files a/common/static/css/vendor/ui-lightness/images/ui-icons_ffd27a_256x240.png and b/common/static/css/vendor/ui-lightness/images/ui-icons_ffd27a_256x240.png differ
diff --git a/common/static/css/vendor/ui-lightness/images/ui-icons_ffffff_256x240.png b/common/static/css/vendor/ui-lightness/images/ui-icons_ffffff_256x240.png
old mode 100755
new mode 100644
index 42f8f992c7..a42e895338
Binary files a/common/static/css/vendor/ui-lightness/images/ui-icons_ffffff_256x240.png and b/common/static/css/vendor/ui-lightness/images/ui-icons_ffffff_256x240.png differ
diff --git a/common/static/images/arrow-left.png b/common/static/images/arrow-left.png
index 33277d6af4..965c1580ca 100644
Binary files a/common/static/images/arrow-left.png and b/common/static/images/arrow-left.png differ
diff --git a/common/static/images/arrow-right.png b/common/static/images/arrow-right.png
index b54f8566de..1f739db955 100644
Binary files a/common/static/images/arrow-right.png and b/common/static/images/arrow-right.png differ
diff --git a/common/static/images/capa/vsepr/AX2E0-3D-balls.png b/common/static/images/capa/vsepr/AX2E0-3D-balls.png
index b5a0161dad..7595cf2a6c 100644
Binary files a/common/static/images/capa/vsepr/AX2E0-3D-balls.png and b/common/static/images/capa/vsepr/AX2E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX2E1-3D-balls.png b/common/static/images/capa/vsepr/AX2E1-3D-balls.png
index 8035c953b3..c8816feb11 100644
Binary files a/common/static/images/capa/vsepr/AX2E1-3D-balls.png and b/common/static/images/capa/vsepr/AX2E1-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX2E2-3D-balls.png b/common/static/images/capa/vsepr/AX2E2-3D-balls.png
index 35ad434bb6..00faa7e6be 100644
Binary files a/common/static/images/capa/vsepr/AX2E2-3D-balls.png and b/common/static/images/capa/vsepr/AX2E2-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX2E3-3D-balls.png b/common/static/images/capa/vsepr/AX2E3-3D-balls.png
index 7535de598b..4fb7022f8c 100644
Binary files a/common/static/images/capa/vsepr/AX2E3-3D-balls.png and b/common/static/images/capa/vsepr/AX2E3-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX3E0-3D-balls.png b/common/static/images/capa/vsepr/AX3E0-3D-balls.png
index ce52fdf916..eeff5b22a2 100644
Binary files a/common/static/images/capa/vsepr/AX3E0-3D-balls.png and b/common/static/images/capa/vsepr/AX3E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX3E1-3D-balls.png b/common/static/images/capa/vsepr/AX3E1-3D-balls.png
index e50a46b500..e5386bf0d7 100644
Binary files a/common/static/images/capa/vsepr/AX3E1-3D-balls.png and b/common/static/images/capa/vsepr/AX3E1-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX3E2-3D-balls.png b/common/static/images/capa/vsepr/AX3E2-3D-balls.png
index 120dcad363..185abaa4ea 100644
Binary files a/common/static/images/capa/vsepr/AX3E2-3D-balls.png and b/common/static/images/capa/vsepr/AX3E2-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX4E0-3D-balls.png b/common/static/images/capa/vsepr/AX4E0-3D-balls.png
index 17e5817eaf..f690ad1f5a 100644
Binary files a/common/static/images/capa/vsepr/AX4E0-3D-balls.png and b/common/static/images/capa/vsepr/AX4E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX4E1-3D-balls.png b/common/static/images/capa/vsepr/AX4E1-3D-balls.png
index 4877e00576..1e940f7576 100644
Binary files a/common/static/images/capa/vsepr/AX4E1-3D-balls.png and b/common/static/images/capa/vsepr/AX4E1-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX4E2-3D-balls.png b/common/static/images/capa/vsepr/AX4E2-3D-balls.png
index 8ee822ad66..ec74eb90fc 100644
Binary files a/common/static/images/capa/vsepr/AX4E2-3D-balls.png and b/common/static/images/capa/vsepr/AX4E2-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX5E1-3D-balls.png b/common/static/images/capa/vsepr/AX5E1-3D-balls.png
index de86f6fdaf..dc12dd9f74 100644
Binary files a/common/static/images/capa/vsepr/AX5E1-3D-balls.png and b/common/static/images/capa/vsepr/AX5E1-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX5E2-3D-balls.png b/common/static/images/capa/vsepr/AX5E2-3D-balls.png
index 2e1db7a8e3..38755c144d 100644
Binary files a/common/static/images/capa/vsepr/AX5E2-3D-balls.png and b/common/static/images/capa/vsepr/AX5E2-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX6E0-3D-balls.png b/common/static/images/capa/vsepr/AX6E0-3D-balls.png
index d878d1dd4d..775ffc46b9 100644
Binary files a/common/static/images/capa/vsepr/AX6E0-3D-balls.png and b/common/static/images/capa/vsepr/AX6E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX6E1-3D-balls.png b/common/static/images/capa/vsepr/AX6E1-3D-balls.png
index 3734b696e0..3e51b877c1 100644
Binary files a/common/static/images/capa/vsepr/AX6E1-3D-balls.png and b/common/static/images/capa/vsepr/AX6E1-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX7E0-3D-balls.png b/common/static/images/capa/vsepr/AX7E0-3D-balls.png
index 37935492aa..95d7aebffd 100644
Binary files a/common/static/images/capa/vsepr/AX7E0-3D-balls.png and b/common/static/images/capa/vsepr/AX7E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX8E0-3D-balls.png b/common/static/images/capa/vsepr/AX8E0-3D-balls.png
index bbbc9608c3..3a24f7794a 100644
Binary files a/common/static/images/capa/vsepr/AX8E0-3D-balls.png and b/common/static/images/capa/vsepr/AX8E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/AX9E0-3D-balls.png b/common/static/images/capa/vsepr/AX9E0-3D-balls.png
index 75d2449ed6..b457f58eb5 100644
Binary files a/common/static/images/capa/vsepr/AX9E0-3D-balls.png and b/common/static/images/capa/vsepr/AX9E0-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Bent-3D-balls.png b/common/static/images/capa/vsepr/Bent-3D-balls.png
index aec734c57c..71bb9526a8 100644
Binary files a/common/static/images/capa/vsepr/Bent-3D-balls.png and b/common/static/images/capa/vsepr/Bent-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Linear-3D-balls.png b/common/static/images/capa/vsepr/Linear-3D-balls.png
index 0876c8a9a5..af5f92c7b1 100644
Binary files a/common/static/images/capa/vsepr/Linear-3D-balls.png and b/common/static/images/capa/vsepr/Linear-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Linear-stick.png b/common/static/images/capa/vsepr/Linear-stick.png
index d4310bedc0..e1306f9a0d 100644
Binary files a/common/static/images/capa/vsepr/Linear-stick.png and b/common/static/images/capa/vsepr/Linear-stick.png differ
diff --git a/common/static/images/capa/vsepr/Octahedral-3D-balls.png b/common/static/images/capa/vsepr/Octahedral-3D-balls.png
index 4b7809d03c..0032fe1ed3 100644
Binary files a/common/static/images/capa/vsepr/Octahedral-3D-balls.png and b/common/static/images/capa/vsepr/Octahedral-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Octahedral-stick.png b/common/static/images/capa/vsepr/Octahedral-stick.png
index 1053d63b5b..3d2b7ca5fd 100644
Binary files a/common/static/images/capa/vsepr/Octahedral-stick.png and b/common/static/images/capa/vsepr/Octahedral-stick.png differ
diff --git a/common/static/images/capa/vsepr/Pentagonal-bipyramidal-3D-balls.png b/common/static/images/capa/vsepr/Pentagonal-bipyramidal-3D-balls.png
index d518717a52..23e02daf25 100644
Binary files a/common/static/images/capa/vsepr/Pentagonal-bipyramidal-3D-balls.png and b/common/static/images/capa/vsepr/Pentagonal-bipyramidal-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Pentagonal-planar-3D-balls.png b/common/static/images/capa/vsepr/Pentagonal-planar-3D-balls.png
index 1c47a26816..0768ec7504 100644
Binary files a/common/static/images/capa/vsepr/Pentagonal-planar-3D-balls.png and b/common/static/images/capa/vsepr/Pentagonal-planar-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Pentagonal-pyramidal-3D-balls.png b/common/static/images/capa/vsepr/Pentagonal-pyramidal-3D-balls.png
index b63d3cd494..511db1ea16 100644
Binary files a/common/static/images/capa/vsepr/Pentagonal-pyramidal-3D-balls.png and b/common/static/images/capa/vsepr/Pentagonal-pyramidal-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Pyramidal-3D-balls.png b/common/static/images/capa/vsepr/Pyramidal-3D-balls.png
index 3fb93f096f..0383528d3d 100644
Binary files a/common/static/images/capa/vsepr/Pyramidal-3D-balls.png and b/common/static/images/capa/vsepr/Pyramidal-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Seesaw-3D-balls.png b/common/static/images/capa/vsepr/Seesaw-3D-balls.png
index a6598fde77..b7665da3b5 100644
Binary files a/common/static/images/capa/vsepr/Seesaw-3D-balls.png and b/common/static/images/capa/vsepr/Seesaw-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Square-antiprismatic-3D-balls.png b/common/static/images/capa/vsepr/Square-antiprismatic-3D-balls.png
index e36cdce783..675ba88c5c 100644
Binary files a/common/static/images/capa/vsepr/Square-antiprismatic-3D-balls.png and b/common/static/images/capa/vsepr/Square-antiprismatic-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Square-planar-3D-balls.png b/common/static/images/capa/vsepr/Square-planar-3D-balls.png
index 1647eab389..6a90a0c361 100644
Binary files a/common/static/images/capa/vsepr/Square-planar-3D-balls.png and b/common/static/images/capa/vsepr/Square-planar-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/T-shaped-3D-balls.png b/common/static/images/capa/vsepr/T-shaped-3D-balls.png
index 59cac0d4f5..b3eaa47f8a 100644
Binary files a/common/static/images/capa/vsepr/T-shaped-3D-balls.png and b/common/static/images/capa/vsepr/T-shaped-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Tetrahedral-3D-balls.png b/common/static/images/capa/vsepr/Tetrahedral-3D-balls.png
index 835ed26b89..9ff8731ac0 100644
Binary files a/common/static/images/capa/vsepr/Tetrahedral-3D-balls.png and b/common/static/images/capa/vsepr/Tetrahedral-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Tetrahedral-stick.png b/common/static/images/capa/vsepr/Tetrahedral-stick.png
index 6266bb8238..09a41164b6 100644
Binary files a/common/static/images/capa/vsepr/Tetrahedral-stick.png and b/common/static/images/capa/vsepr/Tetrahedral-stick.png differ
diff --git a/common/static/images/capa/vsepr/Trigonal-3D-balls.png b/common/static/images/capa/vsepr/Trigonal-3D-balls.png
index 65cca2f10f..20e7c957d8 100644
Binary files a/common/static/images/capa/vsepr/Trigonal-3D-balls.png and b/common/static/images/capa/vsepr/Trigonal-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Trigonal-bipyramidal-3D-balls.png b/common/static/images/capa/vsepr/Trigonal-bipyramidal-3D-balls.png
index 1caed952ed..ab5f013396 100644
Binary files a/common/static/images/capa/vsepr/Trigonal-bipyramidal-3D-balls.png and b/common/static/images/capa/vsepr/Trigonal-bipyramidal-3D-balls.png differ
diff --git a/common/static/images/capa/vsepr/Trigonal-bipyramidal-stick.png b/common/static/images/capa/vsepr/Trigonal-bipyramidal-stick.png
index 0f99d4fbc4..3b36dcd5c6 100644
Binary files a/common/static/images/capa/vsepr/Trigonal-bipyramidal-stick.png and b/common/static/images/capa/vsepr/Trigonal-bipyramidal-stick.png differ
diff --git a/common/static/images/capa/vsepr/Trigonal-planar-stick.png b/common/static/images/capa/vsepr/Trigonal-planar-stick.png
index b73d8a90de..f2fd25c56f 100644
Binary files a/common/static/images/capa/vsepr/Trigonal-planar-stick.png and b/common/static/images/capa/vsepr/Trigonal-planar-stick.png differ
diff --git a/common/static/images/cc.png b/common/static/images/cc.png
index 215a496ea1..564aa43222 100644
Binary files a/common/static/images/cc.png and b/common/static/images/cc.png differ
diff --git a/common/static/images/closed-arrow.png b/common/static/images/closed-arrow.png
index 4cfb9b9861..d615253565 100644
Binary files a/common/static/images/closed-arrow.png and b/common/static/images/closed-arrow.png differ
diff --git a/common/static/images/correct-icon.png b/common/static/images/correct-icon.png
index 5ae7de2a14..e9915f1609 100644
Binary files a/common/static/images/correct-icon.png and b/common/static/images/correct-icon.png differ
diff --git a/common/static/images/fullscreen.png b/common/static/images/fullscreen.png
index 389aa4538d..c1f14e4e69 100644
Binary files a/common/static/images/fullscreen.png and b/common/static/images/fullscreen.png differ
diff --git a/common/static/images/hd.png b/common/static/images/hd.png
index d6b8c1b7d1..3b153ab7fe 100644
Binary files a/common/static/images/hd.png and b/common/static/images/hd.png differ
diff --git a/common/static/images/high_pass_filter.png b/common/static/images/high_pass_filter.png
index 5f425d4671..1cd201b58f 100644
Binary files a/common/static/images/high_pass_filter.png and b/common/static/images/high_pass_filter.png differ
diff --git a/common/static/images/ico-tinymce-code.png b/common/static/images/ico-tinymce-code.png
index c3876d5c65..e311c583af 100644
Binary files a/common/static/images/ico-tinymce-code.png and b/common/static/images/ico-tinymce-code.png differ
diff --git a/common/static/images/incorrect-icon.png b/common/static/images/incorrect-icon.png
index 0926486ec9..0741dd6c6c 100644
Binary files a/common/static/images/incorrect-icon.png and b/common/static/images/incorrect-icon.png differ
diff --git a/common/static/images/info-icon.png b/common/static/images/info-icon.png
index 736b2f2374..085fdfb8e8 100644
Binary files a/common/static/images/info-icon.png and b/common/static/images/info-icon.png differ
diff --git a/common/static/images/ml_grading_icon.png b/common/static/images/ml_grading_icon.png
index 283355814e..648604eabe 100644
Binary files a/common/static/images/ml_grading_icon.png and b/common/static/images/ml_grading_icon.png differ
diff --git a/common/static/images/mute.png b/common/static/images/mute.png
index 70a604965c..a0a53b6411 100644
Binary files a/common/static/images/mute.png and b/common/static/images/mute.png differ
diff --git a/common/static/images/open-arrow.png b/common/static/images/open-arrow.png
index 52fde51574..33084dc109 100644
Binary files a/common/static/images/open-arrow.png and b/common/static/images/open-arrow.png differ
diff --git a/common/static/images/partially-correct-icon.png b/common/static/images/partially-correct-icon.png
index deba924d65..01b33b8b1c 100644
Binary files a/common/static/images/partially-correct-icon.png and b/common/static/images/partially-correct-icon.png differ
diff --git a/common/static/images/peer_grading_icon.png b/common/static/images/peer_grading_icon.png
index 0ee7cf5f17..c3f25becb8 100644
Binary files a/common/static/images/peer_grading_icon.png and b/common/static/images/peer_grading_icon.png differ
diff --git a/common/static/images/placeholder-faculty.png b/common/static/images/placeholder-faculty.png
index b55ba44542..2ce1881339 100644
Binary files a/common/static/images/placeholder-faculty.png and b/common/static/images/placeholder-faculty.png differ
diff --git a/common/static/images/placeholder-image.png b/common/static/images/placeholder-image.png
index 8b77215f60..bbe197c82c 100644
Binary files a/common/static/images/placeholder-image.png and b/common/static/images/placeholder-image.png differ
diff --git a/common/static/images/sequence-nav/document-icon-current.png b/common/static/images/sequence-nav/document-icon-current.png
index 153a067d3d..178e1bd226 100644
Binary files a/common/static/images/sequence-nav/document-icon-current.png and b/common/static/images/sequence-nav/document-icon-current.png differ
diff --git a/common/static/images/sequence-nav/document-icon-normal.png b/common/static/images/sequence-nav/document-icon-normal.png
index 2bc266bfe2..fc10665183 100644
Binary files a/common/static/images/sequence-nav/document-icon-normal.png and b/common/static/images/sequence-nav/document-icon-normal.png differ
diff --git a/common/static/images/sequence-nav/document-icon-visited.png b/common/static/images/sequence-nav/document-icon-visited.png
index 681ea1fcac..8a2b2f3cb5 100644
Binary files a/common/static/images/sequence-nav/document-icon-visited.png and b/common/static/images/sequence-nav/document-icon-visited.png differ
diff --git a/common/static/images/sequence-nav/edit.png b/common/static/images/sequence-nav/edit.png
index f7f5d3d944..a5276a6eac 100644
Binary files a/common/static/images/sequence-nav/edit.png and b/common/static/images/sequence-nav/edit.png differ
diff --git a/common/static/images/sequence-nav/history.png b/common/static/images/sequence-nav/history.png
index c063fa2240..0c5e5e9fc4 100644
Binary files a/common/static/images/sequence-nav/history.png and b/common/static/images/sequence-nav/history.png differ
diff --git a/common/static/images/sequence-nav/list-finished.png b/common/static/images/sequence-nav/list-finished.png
index e2b11e9e0a..20ca4e9415 100644
Binary files a/common/static/images/sequence-nav/list-finished.png and b/common/static/images/sequence-nav/list-finished.png differ
diff --git a/common/static/images/sequence-nav/list-icon-current.png b/common/static/images/sequence-nav/list-icon-current.png
index d9a1473526..d904d48dca 100644
Binary files a/common/static/images/sequence-nav/list-icon-current.png and b/common/static/images/sequence-nav/list-icon-current.png differ
diff --git a/common/static/images/sequence-nav/list-icon-normal.png b/common/static/images/sequence-nav/list-icon-normal.png
index 1e445b02bc..c4dab6a671 100644
Binary files a/common/static/images/sequence-nav/list-icon-normal.png and b/common/static/images/sequence-nav/list-icon-normal.png differ
diff --git a/common/static/images/sequence-nav/list-icon-visited.png b/common/static/images/sequence-nav/list-icon-visited.png
index a3704f3b98..c66178fba3 100644
Binary files a/common/static/images/sequence-nav/list-icon-visited.png and b/common/static/images/sequence-nav/list-icon-visited.png differ
diff --git a/common/static/images/sequence-nav/list-unfinished.png b/common/static/images/sequence-nav/list-unfinished.png
index fa0687e040..9d4900dc92 100644
Binary files a/common/static/images/sequence-nav/list-unfinished.png and b/common/static/images/sequence-nav/list-unfinished.png differ
diff --git a/common/static/images/sequence-nav/list-unstarted.png b/common/static/images/sequence-nav/list-unstarted.png
index 48081ead7d..1dcc74f538 100644
Binary files a/common/static/images/sequence-nav/list-unstarted.png and b/common/static/images/sequence-nav/list-unstarted.png differ
diff --git a/common/static/images/sequence-nav/next-icon.png b/common/static/images/sequence-nav/next-icon.png
index b54f8566de..1f739db955 100644
Binary files a/common/static/images/sequence-nav/next-icon.png and b/common/static/images/sequence-nav/next-icon.png differ
diff --git a/common/static/images/sequence-nav/previous-icon.png b/common/static/images/sequence-nav/previous-icon.png
index 33277d6af4..965c1580ca 100644
Binary files a/common/static/images/sequence-nav/previous-icon.png and b/common/static/images/sequence-nav/previous-icon.png differ
diff --git a/common/static/images/sequence-nav/status/check.png b/common/static/images/sequence-nav/status/check.png
index d13529338b..52f21a51c2 100644
Binary files a/common/static/images/sequence-nav/status/check.png and b/common/static/images/sequence-nav/status/check.png differ
diff --git a/common/static/images/sequence-nav/status/dash.png b/common/static/images/sequence-nav/status/dash.png
index d65b4583a6..64328cd7ff 100644
Binary files a/common/static/images/sequence-nav/status/dash.png and b/common/static/images/sequence-nav/status/dash.png differ
diff --git a/common/static/images/sequence-nav/status/not-started.png b/common/static/images/sequence-nav/status/not-started.png
index 4b5f03c954..74200f1d6d 100644
Binary files a/common/static/images/sequence-nav/status/not-started.png and b/common/static/images/sequence-nav/status/not-started.png differ
diff --git a/common/static/images/sequence-nav/video-icon-current.png b/common/static/images/sequence-nav/video-icon-current.png
index 2674a7a950..9ae67bd7b3 100644
Binary files a/common/static/images/sequence-nav/video-icon-current.png and b/common/static/images/sequence-nav/video-icon-current.png differ
diff --git a/common/static/images/sequence-nav/video-icon-normal.png b/common/static/images/sequence-nav/video-icon-normal.png
index 4b0baeeca0..1f463d94c4 100644
Binary files a/common/static/images/sequence-nav/video-icon-normal.png and b/common/static/images/sequence-nav/video-icon-normal.png differ
diff --git a/common/static/images/sequence-nav/video-icon-visited.png b/common/static/images/sequence-nav/video-icon-visited.png
index 62a5b9d6c4..af2465d4b4 100644
Binary files a/common/static/images/sequence-nav/video-icon-visited.png and b/common/static/images/sequence-nav/video-icon-visited.png differ
diff --git a/common/static/images/sequence-nav/view.png b/common/static/images/sequence-nav/view.png
index bc85d47c23..c6f9b73087 100644
Binary files a/common/static/images/sequence-nav/view.png and b/common/static/images/sequence-nav/view.png differ
diff --git a/common/static/images/slider-handle.png b/common/static/images/slider-handle.png
index ba5da5d33a..2db75026c2 100644
Binary files a/common/static/images/slider-handle.png and b/common/static/images/slider-handle.png differ
diff --git a/common/static/images/unanswered-icon.png b/common/static/images/unanswered-icon.png
index ff88b00696..9b5d77bc9c 100644
Binary files a/common/static/images/unanswered-icon.png and b/common/static/images/unanswered-icon.png differ
diff --git a/common/static/images/vcr.png b/common/static/images/vcr.png
index 6db5ff3bbe..61854ca544 100644
Binary files a/common/static/images/vcr.png and b/common/static/images/vcr.png differ
diff --git a/common/static/images/voltage_divider.png b/common/static/images/voltage_divider.png
index 1fa695b1f3..3a01e7319a 100644
Binary files a/common/static/images/voltage_divider.png and b/common/static/images/voltage_divider.png differ
diff --git a/common/static/images/volume.png b/common/static/images/volume.png
index 0b02e4b9e7..3dbf73be25 100644
Binary files a/common/static/images/volume.png and b/common/static/images/volume.png differ
diff --git a/common/static/js/capa/genex/images/circles.png b/common/static/js/capa/genex/images/circles.png
index 2a84b9c320..ba580a3acf 100644
Binary files a/common/static/js/capa/genex/images/circles.png and b/common/static/js/capa/genex/images/circles.png differ
diff --git a/common/static/js/capa/genex/images/circles_ie6.png b/common/static/js/capa/genex/images/circles_ie6.png
index 3a388fc5d3..466f11923f 100644
Binary files a/common/static/js/capa/genex/images/circles_ie6.png and b/common/static/js/capa/genex/images/circles_ie6.png differ
diff --git a/common/static/js/capa/genex/images/corner.png b/common/static/js/capa/genex/images/corner.png
index 51aa4582aa..d0b8aad882 100644
Binary files a/common/static/js/capa/genex/images/corner.png and b/common/static/js/capa/genex/images/corner.png differ
diff --git a/common/static/js/capa/genex/images/corner_ie6.png b/common/static/js/capa/genex/images/corner_ie6.png
index 5197449254..853d5b038d 100644
Binary files a/common/static/js/capa/genex/images/corner_ie6.png and b/common/static/js/capa/genex/images/corner_ie6.png differ
diff --git a/common/static/js/capa/genex/images/hborder.png b/common/static/js/capa/genex/images/hborder.png
index ec58ae6126..bbc37366ff 100644
Binary files a/common/static/js/capa/genex/images/hborder.png and b/common/static/js/capa/genex/images/hborder.png differ
diff --git a/common/static/js/capa/genex/images/hborder_ie6.png b/common/static/js/capa/genex/images/hborder_ie6.png
index 2268f88a76..2add622a58 100644
Binary files a/common/static/js/capa/genex/images/hborder_ie6.png and b/common/static/js/capa/genex/images/hborder_ie6.png differ
diff --git a/common/static/js/capa/genex/images/thumb_horz.png b/common/static/js/capa/genex/images/thumb_horz.png
index b43e683e1f..6c9baa03c7 100644
Binary files a/common/static/js/capa/genex/images/thumb_horz.png and b/common/static/js/capa/genex/images/thumb_horz.png differ
diff --git a/common/static/js/capa/genex/images/thumb_vertical.png b/common/static/js/capa/genex/images/thumb_vertical.png
index bd57f594ac..5ea57dbd77 100644
Binary files a/common/static/js/capa/genex/images/thumb_vertical.png and b/common/static/js/capa/genex/images/thumb_vertical.png differ
diff --git a/common/static/js/capa/genex/images/vborder.png b/common/static/js/capa/genex/images/vborder.png
index 6840d11a12..070d25184f 100644
Binary files a/common/static/js/capa/genex/images/vborder.png and b/common/static/js/capa/genex/images/vborder.png differ
diff --git a/common/static/js/capa/genex/images/vborder_ie6.png b/common/static/js/capa/genex/images/vborder_ie6.png
index 90baeb1f99..061736f01d 100644
Binary files a/common/static/js/capa/genex/images/vborder_ie6.png and b/common/static/js/capa/genex/images/vborder_ie6.png differ
diff --git a/common/static/js/capa/jsme/396F806CD63ABD414BFBB9D57429F05B.cache.png b/common/static/js/capa/jsme/396F806CD63ABD414BFBB9D57429F05B.cache.png
index 009e987261..055ffdc411 100644
Binary files a/common/static/js/capa/jsme/396F806CD63ABD414BFBB9D57429F05B.cache.png and b/common/static/js/capa/jsme/396F806CD63ABD414BFBB9D57429F05B.cache.png differ
diff --git a/common/static/js/capa/jsme/40BAF81124143A595056A9CCA0E9DBBA.cache.png b/common/static/js/capa/jsme/40BAF81124143A595056A9CCA0E9DBBA.cache.png
index daa0fa3f47..6998b2587c 100644
Binary files a/common/static/js/capa/jsme/40BAF81124143A595056A9CCA0E9DBBA.cache.png and b/common/static/js/capa/jsme/40BAF81124143A595056A9CCA0E9DBBA.cache.png differ
diff --git a/common/static/js/capa/jsme/4841BDE9DC293BA35F7762B4D8EFD236.cache.png b/common/static/js/capa/jsme/4841BDE9DC293BA35F7762B4D8EFD236.cache.png
index 8a77f17dd4..63d7a051bf 100644
Binary files a/common/static/js/capa/jsme/4841BDE9DC293BA35F7762B4D8EFD236.cache.png and b/common/static/js/capa/jsme/4841BDE9DC293BA35F7762B4D8EFD236.cache.png differ
diff --git a/common/static/js/capa/jsme/DF7764EEC1903CD03C9545B354D8D8E4.cache.png b/common/static/js/capa/jsme/DF7764EEC1903CD03C9545B354D8D8E4.cache.png
index fbae9473b1..307e737104 100644
Binary files a/common/static/js/capa/jsme/DF7764EEC1903CD03C9545B354D8D8E4.cache.png and b/common/static/js/capa/jsme/DF7764EEC1903CD03C9545B354D8D8E4.cache.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow-disabled.png b/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow-disabled.png
index 8cef2abb31..5eb5ced39a 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow-disabled.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow-disabled.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow.png b/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow.png
index f03dfee4e4..a53761b661 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/menu-button-arrow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-active.png b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-active.png
index fa58c5030e..d292610db0 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-active.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-active.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-disabled.png b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-disabled.png
index 0a6a82c640..8cdc42a0bb 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-disabled.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-disabled.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-focus.png b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-focus.png
index 167d71eb72..c68d09d1bf 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-focus.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-focus.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-hover.png b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-hover.png
index 167d71eb72..c68d09d1bf 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-hover.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow-hover.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow.png b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow.png
index b33a93ff2d..ecae827f7d 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow.png and b/common/static/js/capa/jsme/gwt/chrome/images/button/split-button-arrow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down-disabled.png b/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down-disabled.png
index 3c5f5ecd55..39893274d7 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down-disabled.png and b/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down-disabled.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down.png b/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down.png
index b298d6e77b..e58b502c3c 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down.png and b/common/static/js/capa/jsme/gwt/chrome/images/combobox/arrow-down.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis-disabled.png b/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis-disabled.png
index a9a225dfdc..814a47bda7 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis-disabled.png and b/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis-disabled.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis.png b/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis.png
index aca78e7837..a712240cc7 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis.png and b/common/static/js/capa/jsme/gwt/chrome/images/combobox/ellipsis.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/corner.png b/common/static/js/capa/jsme/gwt/chrome/images/corner.png
index f57b153fb3..968d8a0b78 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/corner.png and b/common/static/js/capa/jsme/gwt/chrome/images/corner.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/corner_ie6.png b/common/static/js/capa/jsme/gwt/chrome/images/corner_ie6.png
index fd1ccf0a0e..22a5e2d0d9 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/corner_ie6.png and b/common/static/js/capa/jsme/gwt/chrome/images/corner_ie6.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/fasttree/selectionBar.gif b/common/static/js/capa/jsme/gwt/chrome/images/fasttree/selectionBar.gif
index 11e3caf896..30662300bb 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/fasttree/selectionBar.gif and b/common/static/js/capa/jsme/gwt/chrome/images/fasttree/selectionBar.gif differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/fasttree/treeLoading.gif b/common/static/js/capa/jsme/gwt/chrome/images/fasttree/treeLoading.gif
index 371963f67c..d1dd9d779c 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/fasttree/treeLoading.gif and b/common/static/js/capa/jsme/gwt/chrome/images/fasttree/treeLoading.gif differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/glasspanel/blue_ridge.png b/common/static/js/capa/jsme/gwt/chrome/images/glasspanel/blue_ridge.png
index cb96282a6c..4ab297774d 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/glasspanel/blue_ridge.png and b/common/static/js/capa/jsme/gwt/chrome/images/glasspanel/blue_ridge.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/hborder.png b/common/static/js/capa/jsme/gwt/chrome/images/hborder.png
index f11658cceb..e6a881e0bf 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/hborder.png and b/common/static/js/capa/jsme/gwt/chrome/images/hborder.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/hborder_ie6.png b/common/static/js/capa/jsme/gwt/chrome/images/hborder_ie6.png
index 4f4dea1d24..2a523cb544 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/hborder_ie6.png and b/common/static/js/capa/jsme/gwt/chrome/images/hborder_ie6.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topleft.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topleft.png
index a0b04f0263..5dd7db3357 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topleft.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topleft.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topright.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topright.png
index 16f3d0e65f..b397da4676 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topright.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/corner_dialog_topright.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_blue_shadow.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_blue_shadow.png
index d8c41d38f0..9699ed21e9 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_blue_shadow.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_blue_shadow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_gray_shadow.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_gray_shadow.png
index f4d1844f6a..7f117a155b 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_gray_shadow.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/hborder_gray_shadow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_blue_shadow.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_blue_shadow.png
index bd154aee49..680ad0b4b1 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_blue_shadow.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_blue_shadow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_gray_shadow.png b/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_gray_shadow.png
index fccce493ec..dae578ad22 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_gray_shadow.png and b/common/static/js/capa/jsme/gwt/chrome/images/ie6/vborder_gray_shadow.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/scrolltable/bg_header_gradient.gif b/common/static/js/capa/jsme/gwt/chrome/images/scrolltable/bg_header_gradient.gif
index 48e5c32636..ca17a93aeb 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/scrolltable/bg_header_gradient.gif and b/common/static/js/capa/jsme/gwt/chrome/images/scrolltable/bg_header_gradient.gif differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/splitPanelThumb.png b/common/static/js/capa/jsme/gwt/chrome/images/splitPanelThumb.png
index d2ef0255d8..7094ffea8e 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/splitPanelThumb.png and b/common/static/js/capa/jsme/gwt/chrome/images/splitPanelThumb.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/valuespinner/bg_textbox.png b/common/static/js/capa/jsme/gwt/chrome/images/valuespinner/bg_textbox.png
index 8f66df8dfc..7a96f437ac 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/valuespinner/bg_textbox.png and b/common/static/js/capa/jsme/gwt/chrome/images/valuespinner/bg_textbox.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/vborder.png b/common/static/js/capa/jsme/gwt/chrome/images/vborder.png
index b9f7ddc345..1b10fb664e 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/vborder.png and b/common/static/js/capa/jsme/gwt/chrome/images/vborder.png differ
diff --git a/common/static/js/capa/jsme/gwt/chrome/images/vborder_ie6.png b/common/static/js/capa/jsme/gwt/chrome/images/vborder_ie6.png
index c5596d3def..3ecb1dc9d3 100644
Binary files a/common/static/js/capa/jsme/gwt/chrome/images/vborder_ie6.png and b/common/static/js/capa/jsme/gwt/chrome/images/vborder_ie6.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/circles.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/circles.png
index 2a84b9c320..ba580a3acf 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/circles.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/circles.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/circles_ie6.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/circles_ie6.png
index 3a388fc5d3..466f11923f 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/circles_ie6.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/circles_ie6.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/corner.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/corner.png
index 51aa4582aa..d0b8aad882 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/corner.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/corner.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/corner_ie6.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/corner_ie6.png
index 5197449254..32bfbfcf8f 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/corner_ie6.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/corner_ie6.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder.png
index ec58ae6126..bbc37366ff 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder_ie6.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder_ie6.png
index 2268f88a76..2add622a58 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder_ie6.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/hborder_ie6.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_horz.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_horz.png
index b43e683e1f..6c9baa03c7 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_horz.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_horz.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_vertical.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_vertical.png
index bd57f594ac..5ea57dbd77 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_vertical.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/thumb_vertical.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder.png
index 6840d11a12..070d25184f 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder.png differ
diff --git a/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder_ie6.png b/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder_ie6.png
index 90baeb1f99..061736f01d 100644
Binary files a/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder_ie6.png and b/common/static/js/capa/jsmolcalc/gwt/clean/images/vborder_ie6.png differ
diff --git a/common/static/js/capa/protex/scrollTableLoading.gif b/common/static/js/capa/protex/scrollTableLoading.gif
index a0e70075a0..0bdb975558 100644
Binary files a/common/static/js/capa/protex/scrollTableLoading.gif and b/common/static/js/capa/protex/scrollTableLoading.gif differ
diff --git a/common/static/js/vendor/jQuery-File-Upload/img/loading.gif b/common/static/js/vendor/jQuery-File-Upload/img/loading.gif
index 90f28cbdbb..4ae663fa73 100644
Binary files a/common/static/js/vendor/jQuery-File-Upload/img/loading.gif and b/common/static/js/vendor/jQuery-File-Upload/img/loading.gif differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/bold.png b/common/static/js/vendor/markitup/sets/wiki/images/bold.png
index 889ae80e37..484cfb570a 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/bold.png and b/common/static/js/vendor/markitup/sets/wiki/images/bold.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/clean.png b/common/static/js/vendor/markitup/sets/wiki/images/clean.png
index 7e7cefb8e1..52aa69bcf3 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/clean.png and b/common/static/js/vendor/markitup/sets/wiki/images/clean.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/code.png b/common/static/js/vendor/markitup/sets/wiki/images/code.png
index 63fe6ceff5..c60b9aa02c 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/code.png and b/common/static/js/vendor/markitup/sets/wiki/images/code.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/h1.png b/common/static/js/vendor/markitup/sets/wiki/images/h1.png
index 9c122e91e3..7cc1222f4d 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/h1.png and b/common/static/js/vendor/markitup/sets/wiki/images/h1.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/h2.png b/common/static/js/vendor/markitup/sets/wiki/images/h2.png
index fbd87657fb..b19e9f018e 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/h2.png and b/common/static/js/vendor/markitup/sets/wiki/images/h2.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/h3.png b/common/static/js/vendor/markitup/sets/wiki/images/h3.png
index c7836cf09e..476b0bcd2d 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/h3.png and b/common/static/js/vendor/markitup/sets/wiki/images/h3.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/h4.png b/common/static/js/vendor/markitup/sets/wiki/images/h4.png
index 4e929eaf58..e58ed162a6 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/h4.png and b/common/static/js/vendor/markitup/sets/wiki/images/h4.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/h5.png b/common/static/js/vendor/markitup/sets/wiki/images/h5.png
index 30cabebf74..7fb9ec68d7 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/h5.png and b/common/static/js/vendor/markitup/sets/wiki/images/h5.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/image.png b/common/static/js/vendor/markitup/sets/wiki/images/image.png
index fc3c393caa..6450e8f557 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/image.png and b/common/static/js/vendor/markitup/sets/wiki/images/image.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/italic.png b/common/static/js/vendor/markitup/sets/wiki/images/italic.png
index 8482ac8cb1..7915f1bcb3 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/italic.png and b/common/static/js/vendor/markitup/sets/wiki/images/italic.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/link.png b/common/static/js/vendor/markitup/sets/wiki/images/link.png
index 25eacb7c25..1d17650196 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/link.png and b/common/static/js/vendor/markitup/sets/wiki/images/link.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/list-bullet.png b/common/static/js/vendor/markitup/sets/wiki/images/list-bullet.png
index 4a8672bde4..39e5d342e3 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/list-bullet.png and b/common/static/js/vendor/markitup/sets/wiki/images/list-bullet.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/list-numeric.png b/common/static/js/vendor/markitup/sets/wiki/images/list-numeric.png
index 33b0b8df39..c12229a972 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/list-numeric.png and b/common/static/js/vendor/markitup/sets/wiki/images/list-numeric.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/picture.png b/common/static/js/vendor/markitup/sets/wiki/images/picture.png
index 4a158fef7e..b19099c3c2 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/picture.png and b/common/static/js/vendor/markitup/sets/wiki/images/picture.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/preview.png b/common/static/js/vendor/markitup/sets/wiki/images/preview.png
index a9925a06ab..070d8ff6bc 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/preview.png and b/common/static/js/vendor/markitup/sets/wiki/images/preview.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/quotes.png b/common/static/js/vendor/markitup/sets/wiki/images/quotes.png
index e54ebebafb..a018e4a9fd 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/quotes.png and b/common/static/js/vendor/markitup/sets/wiki/images/quotes.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/stroke.png b/common/static/js/vendor/markitup/sets/wiki/images/stroke.png
index 612058a78e..abaf899ffd 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/stroke.png and b/common/static/js/vendor/markitup/sets/wiki/images/stroke.png differ
diff --git a/common/static/js/vendor/markitup/sets/wiki/images/url.png b/common/static/js/vendor/markitup/sets/wiki/images/url.png
index b8edc1265d..22d93588cd 100644
Binary files a/common/static/js/vendor/markitup/sets/wiki/images/url.png and b/common/static/js/vendor/markitup/sets/wiki/images/url.png differ
diff --git a/common/static/js/vendor/markitup/skins/simple/images/handle.png b/common/static/js/vendor/markitup/skins/simple/images/handle.png
index 3993b20337..faf4326316 100644
Binary files a/common/static/js/vendor/markitup/skins/simple/images/handle.png and b/common/static/js/vendor/markitup/skins/simple/images/handle.png differ
diff --git a/common/static/js/vendor/markitup/skins/simple/images/menu.png b/common/static/js/vendor/markitup/skins/simple/images/menu.png
index 44a07afd30..4f037a96e4 100644
Binary files a/common/static/js/vendor/markitup/skins/simple/images/menu.png and b/common/static/js/vendor/markitup/skins/simple/images/menu.png differ
diff --git a/common/static/js/vendor/markitup/skins/simple/images/submenu.png b/common/static/js/vendor/markitup/skins/simple/images/submenu.png
index 03d1977aec..8c296f0237 100644
Binary files a/common/static/js/vendor/markitup/skins/simple/images/submenu.png and b/common/static/js/vendor/markitup/skins/simple/images/submenu.png differ
diff --git a/common/static/js/vendor/ova/catch/img/closeIcon.png b/common/static/js/vendor/ova/catch/img/closeIcon.png
index cebf5d53be..35e9b8c7f6 100644
Binary files a/common/static/js/vendor/ova/catch/img/closeIcon.png and b/common/static/js/vendor/ova/catch/img/closeIcon.png differ
diff --git a/common/static/js/vendor/ova/catch/img/expandableIcon.png b/common/static/js/vendor/ova/catch/img/expandableIcon.png
index f9709f5723..f5f8d2b5fb 100644
Binary files a/common/static/js/vendor/ova/catch/img/expandableIcon.png and b/common/static/js/vendor/ova/catch/img/expandableIcon.png differ
diff --git a/common/static/js/vendor/ova/catch/img/expandableIcon_Arrow.png b/common/static/js/vendor/ova/catch/img/expandableIcon_Arrow.png
index 52e633bdd7..bf9eab0300 100644
Binary files a/common/static/js/vendor/ova/catch/img/expandableIcon_Arrow.png and b/common/static/js/vendor/ova/catch/img/expandableIcon_Arrow.png differ
diff --git a/common/static/js/vendor/ova/catch/img/fish.jpg b/common/static/js/vendor/ova/catch/img/fish.jpg
index 519f6fa0a7..ff54d5942a 100644
Binary files a/common/static/js/vendor/ova/catch/img/fish.jpg and b/common/static/js/vendor/ova/catch/img/fish.jpg differ
diff --git a/common/static/js/vendor/ova/catch/img/geolocation_icon.png b/common/static/js/vendor/ova/catch/img/geolocation_icon.png
index 9112bf1b11..d425f19730 100644
Binary files a/common/static/js/vendor/ova/catch/img/geolocation_icon.png and b/common/static/js/vendor/ova/catch/img/geolocation_icon.png differ
diff --git a/common/static/js/vendor/ova/catch/img/groups_icon.png b/common/static/js/vendor/ova/catch/img/groups_icon.png
index add3398633..0fac63dffe 100644
Binary files a/common/static/js/vendor/ova/catch/img/groups_icon.png and b/common/static/js/vendor/ova/catch/img/groups_icon.png differ
diff --git a/common/static/js/vendor/ova/catch/img/loading_bar.gif b/common/static/js/vendor/ova/catch/img/loading_bar.gif
index dd936613b1..1ff2820983 100644
Binary files a/common/static/js/vendor/ova/catch/img/loading_bar.gif and b/common/static/js/vendor/ova/catch/img/loading_bar.gif differ
diff --git a/common/static/js/vendor/ova/catch/img/privacy_icon.png b/common/static/js/vendor/ova/catch/img/privacy_icon.png
index 60b2059ffe..4427b09a10 100644
Binary files a/common/static/js/vendor/ova/catch/img/privacy_icon.png and b/common/static/js/vendor/ova/catch/img/privacy_icon.png differ
diff --git a/common/static/js/vendor/ova/catch/img/shareControls.png b/common/static/js/vendor/ova/catch/img/shareControls.png
index 9f454ce170..56b87066d2 100644
Binary files a/common/static/js/vendor/ova/catch/img/shareControls.png and b/common/static/js/vendor/ova/catch/img/shareControls.png differ
diff --git a/common/static/js/vendor/ova/catch/img/share_icon.png b/common/static/js/vendor/ova/catch/img/share_icon.png
index 9635527c94..10564b01d9 100644
Binary files a/common/static/js/vendor/ova/catch/img/share_icon.png and b/common/static/js/vendor/ova/catch/img/share_icon.png differ
diff --git a/common/static/js/vendor/ova/images/fullpage_grouphover.png b/common/static/js/vendor/ova/images/fullpage_grouphover.png
index 2cf72a3d73..98dfd75f65 100644
Binary files a/common/static/js/vendor/ova/images/fullpage_grouphover.png and b/common/static/js/vendor/ova/images/fullpage_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/fullpage_hover.png b/common/static/js/vendor/ova/images/fullpage_hover.png
index d8cbd6630b..81ec2337a9 100644
Binary files a/common/static/js/vendor/ova/images/fullpage_hover.png and b/common/static/js/vendor/ova/images/fullpage_hover.png differ
diff --git a/common/static/js/vendor/ova/images/fullpage_pressed.png b/common/static/js/vendor/ova/images/fullpage_pressed.png
index f5bb48376d..c10cac4798 100644
Binary files a/common/static/js/vendor/ova/images/fullpage_pressed.png and b/common/static/js/vendor/ova/images/fullpage_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/fullpage_rest.png b/common/static/js/vendor/ova/images/fullpage_rest.png
index 2cf72a3d73..98dfd75f65 100644
Binary files a/common/static/js/vendor/ova/images/fullpage_rest.png and b/common/static/js/vendor/ova/images/fullpage_rest.png differ
diff --git a/common/static/js/vendor/ova/images/home_grouphover.png b/common/static/js/vendor/ova/images/home_grouphover.png
index bba978a57a..bae388a3e4 100644
Binary files a/common/static/js/vendor/ova/images/home_grouphover.png and b/common/static/js/vendor/ova/images/home_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/home_hover.png b/common/static/js/vendor/ova/images/home_hover.png
index 1828267cf6..b97051cb4e 100644
Binary files a/common/static/js/vendor/ova/images/home_hover.png and b/common/static/js/vendor/ova/images/home_hover.png differ
diff --git a/common/static/js/vendor/ova/images/home_pressed.png b/common/static/js/vendor/ova/images/home_pressed.png
index 655d665286..4d2844fa60 100644
Binary files a/common/static/js/vendor/ova/images/home_pressed.png and b/common/static/js/vendor/ova/images/home_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/home_rest.png b/common/static/js/vendor/ova/images/home_rest.png
index bba978a57a..bae388a3e4 100644
Binary files a/common/static/js/vendor/ova/images/home_rest.png and b/common/static/js/vendor/ova/images/home_rest.png differ
diff --git a/common/static/js/vendor/ova/images/newan_grouphover.png b/common/static/js/vendor/ova/images/newan_grouphover.png
index 7b5284a9fe..a770255580 100644
Binary files a/common/static/js/vendor/ova/images/newan_grouphover.png and b/common/static/js/vendor/ova/images/newan_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/newan_hover.png b/common/static/js/vendor/ova/images/newan_hover.png
index 997c848dd7..b7875a4041 100644
Binary files a/common/static/js/vendor/ova/images/newan_hover.png and b/common/static/js/vendor/ova/images/newan_hover.png differ
diff --git a/common/static/js/vendor/ova/images/newan_pressed.png b/common/static/js/vendor/ova/images/newan_pressed.png
index 9f4a4eb830..677380b0b3 100644
Binary files a/common/static/js/vendor/ova/images/newan_pressed.png and b/common/static/js/vendor/ova/images/newan_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/newan_rest.png b/common/static/js/vendor/ova/images/newan_rest.png
index 7b5284a9fe..a770255580 100644
Binary files a/common/static/js/vendor/ova/images/newan_rest.png and b/common/static/js/vendor/ova/images/newan_rest.png differ
diff --git a/common/static/js/vendor/ova/images/next_grouphover.png b/common/static/js/vendor/ova/images/next_grouphover.png
index de92d99d13..0d6e35c965 100644
Binary files a/common/static/js/vendor/ova/images/next_grouphover.png and b/common/static/js/vendor/ova/images/next_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/next_hover.png b/common/static/js/vendor/ova/images/next_hover.png
index 4beeb4a937..3b42c90142 100644
Binary files a/common/static/js/vendor/ova/images/next_hover.png and b/common/static/js/vendor/ova/images/next_hover.png differ
diff --git a/common/static/js/vendor/ova/images/next_pressed.png b/common/static/js/vendor/ova/images/next_pressed.png
index 173734c777..1818ed3108 100644
Binary files a/common/static/js/vendor/ova/images/next_pressed.png and b/common/static/js/vendor/ova/images/next_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/next_rest.png b/common/static/js/vendor/ova/images/next_rest.png
index de92d99d13..0d6e35c965 100644
Binary files a/common/static/js/vendor/ova/images/next_rest.png and b/common/static/js/vendor/ova/images/next_rest.png differ
diff --git a/common/static/js/vendor/ova/images/previous_grouphover.png b/common/static/js/vendor/ova/images/previous_grouphover.png
index 7b7f6db7f6..401b8b878a 100644
Binary files a/common/static/js/vendor/ova/images/previous_grouphover.png and b/common/static/js/vendor/ova/images/previous_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/previous_hover.png b/common/static/js/vendor/ova/images/previous_hover.png
index 9547e65b79..1871ecefcd 100644
Binary files a/common/static/js/vendor/ova/images/previous_hover.png and b/common/static/js/vendor/ova/images/previous_hover.png differ
diff --git a/common/static/js/vendor/ova/images/previous_pressed.png b/common/static/js/vendor/ova/images/previous_pressed.png
index 0c7a1bf5ec..3980ae147b 100644
Binary files a/common/static/js/vendor/ova/images/previous_pressed.png and b/common/static/js/vendor/ova/images/previous_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/previous_rest.png b/common/static/js/vendor/ova/images/previous_rest.png
index 7b7f6db7f6..401b8b878a 100644
Binary files a/common/static/js/vendor/ova/images/previous_rest.png and b/common/static/js/vendor/ova/images/previous_rest.png differ
diff --git a/common/static/js/vendor/ova/images/zoomin_grouphover.png b/common/static/js/vendor/ova/images/zoomin_grouphover.png
index df0c524675..a6c214a29c 100644
Binary files a/common/static/js/vendor/ova/images/zoomin_grouphover.png and b/common/static/js/vendor/ova/images/zoomin_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/zoomin_hover.png b/common/static/js/vendor/ova/images/zoomin_hover.png
index 206d205540..cdf2e8c235 100644
Binary files a/common/static/js/vendor/ova/images/zoomin_hover.png and b/common/static/js/vendor/ova/images/zoomin_hover.png differ
diff --git a/common/static/js/vendor/ova/images/zoomin_pressed.png b/common/static/js/vendor/ova/images/zoomin_pressed.png
index ebe3df83ef..3780a22e97 100644
Binary files a/common/static/js/vendor/ova/images/zoomin_pressed.png and b/common/static/js/vendor/ova/images/zoomin_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/zoomin_rest.png b/common/static/js/vendor/ova/images/zoomin_rest.png
index df0c524675..a6c214a29c 100644
Binary files a/common/static/js/vendor/ova/images/zoomin_rest.png and b/common/static/js/vendor/ova/images/zoomin_rest.png differ
diff --git a/common/static/js/vendor/ova/images/zoomout_grouphover.png b/common/static/js/vendor/ova/images/zoomout_grouphover.png
index f9661201df..91a1871905 100644
Binary files a/common/static/js/vendor/ova/images/zoomout_grouphover.png and b/common/static/js/vendor/ova/images/zoomout_grouphover.png differ
diff --git a/common/static/js/vendor/ova/images/zoomout_hover.png b/common/static/js/vendor/ova/images/zoomout_hover.png
index e3b0fcd794..e30f9f018f 100644
Binary files a/common/static/js/vendor/ova/images/zoomout_hover.png and b/common/static/js/vendor/ova/images/zoomout_hover.png differ
diff --git a/common/static/js/vendor/ova/images/zoomout_pressed.png b/common/static/js/vendor/ova/images/zoomout_pressed.png
index 6b4d8199c3..3560c50d84 100644
Binary files a/common/static/js/vendor/ova/images/zoomout_pressed.png and b/common/static/js/vendor/ova/images/zoomout_pressed.png differ
diff --git a/common/static/js/vendor/ova/images/zoomout_rest.png b/common/static/js/vendor/ova/images/zoomout_rest.png
index f9661201df..91a1871905 100644
Binary files a/common/static/js/vendor/ova/images/zoomout_rest.png and b/common/static/js/vendor/ova/images/zoomout_rest.png differ
diff --git a/common/static/js/vendor/ova/skins/lightgray/img/loader.gif b/common/static/js/vendor/ova/skins/lightgray/img/loader.gif
index c69e937232..15c045ae82 100644
Binary files a/common/static/js/vendor/ova/skins/lightgray/img/loader.gif and b/common/static/js/vendor/ova/skins/lightgray/img/loader.gif differ
diff --git a/common/static/js/vendor/tinymce/js/tinymce/skins/studio-tmce4/img/loader.gif b/common/static/js/vendor/tinymce/js/tinymce/skins/studio-tmce4/img/loader.gif
index c69e937232..15c045ae82 100755
Binary files a/common/static/js/vendor/tinymce/js/tinymce/skins/studio-tmce4/img/loader.gif and b/common/static/js/vendor/tinymce/js/tinymce/skins/studio-tmce4/img/loader.gif differ
diff --git a/common/test/acceptance/pages/lms/tab_nav.py b/common/test/acceptance/pages/lms/tab_nav.py
index 410caff2b3..09949f2d91 100644
--- a/common/test/acceptance/pages/lms/tab_nav.py
+++ b/common/test/acceptance/pages/lms/tab_nav.py
@@ -36,6 +36,16 @@ class TabNavPage(PageObject):
self.wait_for_page()
self._is_on_tab_promise(tab_name).fulfill()
+ def mathjax_has_rendered(self):
+ """
+ Check that MathJax has rendered in tab content
+ """
+ mathjax_container = self.q(css=".static_tab_wrapper .MathJax .math")
+ EmptyPromise(
+ lambda: mathjax_container.present and mathjax_container.visible,
+ "MathJax is not visible"
+ ).fulfill()
+
def is_on_tab(self, tab_name):
"""
Return a boolean indicating whether the current tab is `tab_name`.
diff --git a/common/test/acceptance/pages/studio/settings_advanced.py b/common/test/acceptance/pages/studio/settings_advanced.py
index 322e73b073..61f769d11c 100644
--- a/common/test/acceptance/pages/studio/settings_advanced.py
+++ b/common/test/acceptance/pages/studio/settings_advanced.py
@@ -210,6 +210,7 @@ class AdvancedSettingsPage(CoursePage):
'show_calculator',
'show_reset_button',
'static_asset_path',
+ 'teams_configuration',
'text_customization',
'annotation_storage_url',
'social_sharing_url',
diff --git a/common/test/acceptance/tests/lms/test_lms.py b/common/test/acceptance/tests/lms/test_lms.py
index 1c4251d339..c3b4024212 100644
--- a/common/test/acceptance/tests/lms/test_lms.py
+++ b/common/test/acceptance/tests/lms/test_lms.py
@@ -571,7 +571,7 @@ class HighLevelTabTest(UniqueCourseTest):
course_fix.add_handout('demoPDF.pdf')
course_fix.add_children(
- XBlockFixtureDesc('static_tab', 'Test Static Tab'),
+ XBlockFixtureDesc('static_tab', 'Test Static Tab', data=r"static tab data with mathjax \(E=mc^2\)"),
XBlockFixtureDesc('chapter', 'Test Section').add_children(
XBlockFixtureDesc('sequential', 'Test Subsection').add_children(
XBlockFixtureDesc('problem', 'Test Problem 1', data=load_data_str('multiple_choice.xml')),
@@ -631,6 +631,18 @@ class HighLevelTabTest(UniqueCourseTest):
self.tab_nav.go_to_tab('Test Static Tab')
self.assertTrue(self.tab_nav.is_on_tab('Test Static Tab'))
+ def test_static_tab_with_mathjax(self):
+ """
+ Navigate to a static tab (course content)
+ """
+ # From the course info page, navigate to the static tab
+ self.course_info_page.visit()
+ self.tab_nav.go_to_tab('Test Static Tab')
+ self.assertTrue(self.tab_nav.is_on_tab('Test Static Tab'))
+
+ # Verify that Mathjax has rendered
+ self.tab_nav.mathjax_has_rendered()
+
def test_wiki_tab_first_time(self):
"""
Navigate to the course wiki tab. When the wiki is accessed for
diff --git a/common/test/acceptance/tests/test_annotatable.py b/common/test/acceptance/tests/test_annotatable.py
index 9345d1cf42..487d344d6a 100644
--- a/common/test/acceptance/tests/test_annotatable.py
+++ b/common/test/acceptance/tests/test_annotatable.py
@@ -9,6 +9,7 @@ from ..pages.lms.courseware import CoursewarePage
from ..pages.lms.annotation_component import AnnotationComponentPage
from ..fixtures.course import CourseFixture, XBlockFixtureDesc
from textwrap import dedent
+from ..tests.helpers import disable_animations
def _correctness(choice, target):
@@ -124,6 +125,8 @@ class AnnotatableProblemTest(UniqueCourseTest):
"""
annotation_component_page = self._goto_annotation_component_page()
+ # This will avoid scrolling related problems on different browsers and instead directly jump on the problem
+ disable_animations(annotation_component_page)
for i in xrange(self.annotation_count):
annotation_component_page.click_reply_annotation(i)
diff --git a/common/test/acceptance/tests/video/test_video_module.py b/common/test/acceptance/tests/video/test_video_module.py
index 47935dd312..6413686223 100644
--- a/common/test/acceptance/tests/video/test_video_module.py
+++ b/common/test/acceptance/tests/video/test_video_module.py
@@ -1172,11 +1172,13 @@ class YouTubeQualityTest(VideoBaseTest):
self.video.click_player_button('play')
+ self.video.wait_for(lambda: self.video.is_quality_button_visible, 'waiting for quality button to appear')
+
self.assertFalse(self.video.is_quality_button_active)
self.video.click_player_button('quality')
- self.assertTrue(self.video.is_quality_button_active)
+ self.video.wait_for(lambda: self.video.is_quality_button_active, 'waiting for quality button activation')
@attr('a11y')
diff --git a/common/test/data/aside/static/just_a_test.jpg b/common/test/data/aside/static/just_a_test.jpg
index 6bb7f377a0..0fcfeb5ee2 100644
Binary files a/common/test/data/aside/static/just_a_test.jpg and b/common/test/data/aside/static/just_a_test.jpg differ
diff --git a/common/test/data/badges/good.png b/common/test/data/badges/good.png
index eff1fb0bc4..0c8fa7206f 100644
Binary files a/common/test/data/badges/good.png and b/common/test/data/badges/good.png differ
diff --git a/common/test/data/badges/large.png b/common/test/data/badges/large.png
index 7b2ef38534..cd261a518c 100644
Binary files a/common/test/data/badges/large.png and b/common/test/data/badges/large.png differ
diff --git a/common/test/data/badges/unbalanced.png b/common/test/data/badges/unbalanced.png
index 1c8244cb31..31e0924d4c 100644
Binary files a/common/test/data/badges/unbalanced.png and b/common/test/data/badges/unbalanced.png differ
diff --git a/common/test/data/conditional_and_poll/static/images/course_image.jpg b/common/test/data/conditional_and_poll/static/images/course_image.jpg
index b6a64b9396..ef30fe0710 100644
Binary files a/common/test/data/conditional_and_poll/static/images/course_image.jpg and b/common/test/data/conditional_and_poll/static/images/course_image.jpg differ
diff --git a/common/test/data/conditional_and_poll/static/images/professor-sandel.jpg b/common/test/data/conditional_and_poll/static/images/professor-sandel.jpg
index 41bde60165..6bc40bbf6a 100644
Binary files a/common/test/data/conditional_and_poll/static/images/professor-sandel.jpg and b/common/test/data/conditional_and_poll/static/images/professor-sandel.jpg differ
diff --git a/common/test/data/manual-testing-complete/static/Screen Shot 2013-04-16 at 1.43.36 PM.png b/common/test/data/manual-testing-complete/static/Screen Shot 2013-04-16 at 1.43.36 PM.png
index b85eee2692..4c981a4ba8 100644
Binary files a/common/test/data/manual-testing-complete/static/Screen Shot 2013-04-16 at 1.43.36 PM.png and b/common/test/data/manual-testing-complete/static/Screen Shot 2013-04-16 at 1.43.36 PM.png differ
diff --git a/common/test/data/simple/static/images_course_image.jpg b/common/test/data/simple/static/images_course_image.jpg
index 6bb7f377a0..0fcfeb5ee2 100644
Binary files a/common/test/data/simple/static/images_course_image.jpg and b/common/test/data/simple/static/images_course_image.jpg differ
diff --git a/common/test/data/static/picture1.jpg b/common/test/data/static/picture1.jpg
index eaf274572e..12107ed547 100644
Binary files a/common/test/data/static/picture1.jpg and b/common/test/data/static/picture1.jpg differ
diff --git a/common/test/data/static/picture2.jpg b/common/test/data/static/picture2.jpg
index 5b41b93bb7..f59e3c25a9 100644
Binary files a/common/test/data/static/picture2.jpg and b/common/test/data/static/picture2.jpg differ
diff --git a/common/test/data/static/picture3.jpg b/common/test/data/static/picture3.jpg
index cbed6063f2..752c5e7492 100644
Binary files a/common/test/data/static/picture3.jpg and b/common/test/data/static/picture3.jpg differ
diff --git a/common/test/data/toy/static/just_a_test.jpg b/common/test/data/toy/static/just_a_test.jpg
index 6bb7f377a0..0fcfeb5ee2 100644
Binary files a/common/test/data/toy/static/just_a_test.jpg and b/common/test/data/toy/static/just_a_test.jpg differ
diff --git a/common/test/data/uploads/Signature-0.png b/common/test/data/uploads/Signature-0.png
index 8096c4a56e..38df96a2c7 100644
Binary files a/common/test/data/uploads/Signature-0.png and b/common/test/data/uploads/Signature-0.png differ
diff --git a/common/test/data/uploads/Signature-1.png b/common/test/data/uploads/Signature-1.png
index 8096c4a56e..38df96a2c7 100644
Binary files a/common/test/data/uploads/Signature-1.png and b/common/test/data/uploads/Signature-1.png differ
diff --git a/common/test/data/uploads/image.jpg b/common/test/data/uploads/image.jpg
index 21ece4ef43..4a71327898 100644
Binary files a/common/test/data/uploads/image.jpg and b/common/test/data/uploads/image.jpg differ
diff --git a/common/test/data/uploads/larger_image.jpg b/common/test/data/uploads/larger_image.jpg
index 18a3e1c6f9..76b267f152 100644
Binary files a/common/test/data/uploads/larger_image.jpg and b/common/test/data/uploads/larger_image.jpg differ
diff --git a/common/test/data/uploads/list-icon-visited.png b/common/test/data/uploads/list-icon-visited.png
index a3704f3b98..c66178fba3 100644
Binary files a/common/test/data/uploads/list-icon-visited.png and b/common/test/data/uploads/list-icon-visited.png differ
diff --git a/common/test/data/xml_dag/static/just_a_test.jpg b/common/test/data/xml_dag/static/just_a_test.jpg
index 6bb7f377a0..0fcfeb5ee2 100644
Binary files a/common/test/data/xml_dag/static/just_a_test.jpg and b/common/test/data/xml_dag/static/just_a_test.jpg differ
diff --git a/common/test/db_cache/bok_choy_data.json b/common/test/db_cache/bok_choy_data.json
index 947404b232..78dc234c40 100644
--- a/common/test/db_cache/bok_choy_data.json
+++ b/common/test/db_cache/bok_choy_data.json
@@ -1 +1 @@
-[{"fields": {"model": "permission", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 1}, {"fields": {"model": "group", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 2}, {"fields": {"model": "user", "app_label": "auth"}, "model": "contenttypes.contenttype", "pk": 3}, {"fields": {"model": "contenttype", "app_label": "contenttypes"}, "model": "contenttypes.contenttype", "pk": 4}, {"fields": {"model": "session", "app_label": "sessions"}, "model": "contenttypes.contenttype", "pk": 5}, {"fields": {"model": "site", "app_label": "sites"}, "model": "contenttypes.contenttype", "pk": 6}, {"fields": {"model": "taskmeta", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 7}, {"fields": {"model": "tasksetmeta", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 8}, {"fields": {"model": "intervalschedule", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 9}, {"fields": {"model": "crontabschedule", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 10}, {"fields": {"model": "periodictasks", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 11}, {"fields": {"model": "periodictask", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 12}, {"fields": {"model": "workerstate", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 13}, {"fields": {"model": "taskstate", "app_label": "djcelery"}, "model": "contenttypes.contenttype", "pk": 14}, {"fields": {"model": "globalstatusmessage", "app_label": "status"}, "model": "contenttypes.contenttype", "pk": 15}, {"fields": {"model": "coursemessage", "app_label": "status"}, "model": "contenttypes.contenttype", "pk": 16}, {"fields": {"model": "studentmodule", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 17}, {"fields": {"model": "studentmodulehistory", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 18}, {"fields": {"model": "xmoduleuserstatesummaryfield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 19}, {"fields": {"model": "xmodulestudentprefsfield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 20}, {"fields": {"model": "xmodulestudentinfofield", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 21}, {"fields": {"model": "offlinecomputedgrade", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 22}, {"fields": {"model": "offlinecomputedgradelog", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 23}, {"fields": {"model": "studentfieldoverride", "app_label": "courseware"}, "model": "contenttypes.contenttype", "pk": 24}, {"fields": {"model": "anonymoususerid", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 25}, {"fields": {"model": "userstanding", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 26}, {"fields": {"model": "userprofile", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 27}, {"fields": {"model": "usersignupsource", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 28}, {"fields": {"model": "usertestgroup", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 29}, {"fields": {"model": "registration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 30}, {"fields": {"model": "pendingnamechange", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 31}, {"fields": {"model": "pendingemailchange", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 32}, {"fields": {"model": "passwordhistory", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 33}, {"fields": {"model": "loginfailures", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 34}, {"fields": {"model": "historicalcourseenrollment", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 35}, {"fields": {"model": "courseenrollment", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 36}, {"fields": {"model": "manualenrollmentaudit", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 37}, {"fields": {"model": "courseenrollmentallowed", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 38}, {"fields": {"model": "courseaccessrole", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 39}, {"fields": {"model": "dashboardconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 40}, {"fields": {"model": "linkedinaddtoprofileconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 41}, {"fields": {"model": "entranceexamconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 42}, {"fields": {"model": "languageproficiency", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 43}, {"fields": {"model": "courseenrollmentattribute", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 44}, {"fields": {"model": "enrollmentrefundconfiguration", "app_label": "student"}, "model": "contenttypes.contenttype", "pk": 45}, {"fields": {"model": "trackinglog", "app_label": "track"}, "model": "contenttypes.contenttype", "pk": 46}, {"fields": {"model": "ratelimitconfiguration", "app_label": "util"}, "model": "contenttypes.contenttype", "pk": 47}, {"fields": {"model": "certificatewhitelist", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 48}, {"fields": {"model": "generatedcertificate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 49}, {"fields": {"model": "certificategenerationhistory", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 50}, {"fields": {"model": "examplecertificateset", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 51}, {"fields": {"model": "examplecertificate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 52}, {"fields": {"model": "certificategenerationcoursesetting", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 53}, {"fields": {"model": "certificategenerationconfiguration", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 54}, {"fields": {"model": "certificatehtmlviewconfiguration", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 55}, {"fields": {"model": "badgeassertion", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 56}, {"fields": {"model": "badgeimageconfiguration", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 57}, {"fields": {"model": "certificatetemplate", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 58}, {"fields": {"model": "certificatetemplateasset", "app_label": "certificates"}, "model": "contenttypes.contenttype", "pk": 59}, {"fields": {"model": "instructortask", "app_label": "instructor_task"}, "model": "contenttypes.contenttype", "pk": 60}, {"fields": {"model": "courseusergroup", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 61}, {"fields": {"model": "cohortmembership", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 62}, {"fields": {"model": "courseusergrouppartitiongroup", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 63}, {"fields": {"model": "coursecohortssettings", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 64}, {"fields": {"model": "coursecohort", "app_label": "course_groups"}, "model": "contenttypes.contenttype", "pk": 65}, {"fields": {"model": "courseemail", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 66}, {"fields": {"model": "optout", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 67}, {"fields": {"model": "courseemailtemplate", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 68}, {"fields": {"model": "courseauthorization", "app_label": "bulk_email"}, "model": "contenttypes.contenttype", "pk": 69}, {"fields": {"model": "brandinginfoconfig", "app_label": "branding"}, "model": "contenttypes.contenttype", "pk": 70}, {"fields": {"model": "brandingapiconfig", "app_label": "branding"}, "model": "contenttypes.contenttype", "pk": 71}, {"fields": {"model": "externalauthmap", "app_label": "external_auth"}, "model": "contenttypes.contenttype", "pk": 72}, {"fields": {"model": "nonce", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 73}, {"fields": {"model": "association", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 74}, {"fields": {"model": "useropenid", "app_label": "django_openid_auth"}, "model": "contenttypes.contenttype", "pk": 75}, {"fields": {"model": "client", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 76}, {"fields": {"model": "grant", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 77}, {"fields": {"model": "accesstoken", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 78}, {"fields": {"model": "refreshtoken", "app_label": "oauth2"}, "model": "contenttypes.contenttype", "pk": 79}, {"fields": {"model": "trustedclient", "app_label": "oauth2_provider"}, "model": "contenttypes.contenttype", "pk": 80}, {"fields": {"model": "oauth2providerconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 81}, {"fields": {"model": "samlproviderconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 82}, {"fields": {"model": "samlconfiguration", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 83}, {"fields": {"model": "samlproviderdata", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 84}, {"fields": {"model": "ltiproviderconfig", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 85}, {"fields": {"model": "providerapipermissions", "app_label": "third_party_auth"}, "model": "contenttypes.contenttype", "pk": 86}, {"fields": {"model": "nonce", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 87}, {"fields": {"model": "scope", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 88}, {"fields": {"model": "consumer", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 89}, {"fields": {"model": "token", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 90}, {"fields": {"model": "resource", "app_label": "oauth_provider"}, "model": "contenttypes.contenttype", "pk": 91}, {"fields": {"model": "article", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 92}, {"fields": {"model": "articleforobject", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 93}, {"fields": {"model": "articlerevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 94}, {"fields": {"model": "urlpath", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 95}, {"fields": {"model": "articleplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 96}, {"fields": {"model": "reusableplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 97}, {"fields": {"model": "simpleplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 98}, {"fields": {"model": "revisionplugin", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 99}, {"fields": {"model": "revisionpluginrevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 100}, {"fields": {"model": "image", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 101}, {"fields": {"model": "imagerevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 102}, {"fields": {"model": "attachment", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 103}, {"fields": {"model": "attachmentrevision", "app_label": "wiki"}, "model": "contenttypes.contenttype", "pk": 104}, {"fields": {"model": "notificationtype", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 105}, {"fields": {"model": "settings", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 106}, {"fields": {"model": "subscription", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 107}, {"fields": {"model": "notification", "app_label": "django_notify"}, "model": "contenttypes.contenttype", "pk": 108}, {"fields": {"model": "logentry", "app_label": "admin"}, "model": "contenttypes.contenttype", "pk": 109}, {"fields": {"model": "role", "app_label": "django_comment_common"}, "model": "contenttypes.contenttype", "pk": 110}, {"fields": {"model": "permission", "app_label": "django_comment_common"}, "model": "contenttypes.contenttype", "pk": 111}, {"fields": {"model": "note", "app_label": "notes"}, "model": "contenttypes.contenttype", "pk": 112}, {"fields": {"model": "splashconfig", "app_label": "splash"}, "model": "contenttypes.contenttype", "pk": 113}, {"fields": {"model": "userpreference", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 114}, {"fields": {"model": "usercoursetag", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 115}, {"fields": {"model": "userorgtag", "app_label": "user_api"}, "model": "contenttypes.contenttype", "pk": 116}, {"fields": {"model": "order", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 117}, {"fields": {"model": "orderitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 118}, {"fields": {"model": "invoice", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 119}, {"fields": {"model": "invoicetransaction", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 120}, {"fields": {"model": "invoiceitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 121}, {"fields": {"model": "courseregistrationcodeinvoiceitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 122}, {"fields": {"model": "invoicehistory", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 123}, {"fields": {"model": "courseregistrationcode", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 124}, {"fields": {"model": "registrationcoderedemption", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 125}, {"fields": {"model": "coupon", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 126}, {"fields": {"model": "couponredemption", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 127}, {"fields": {"model": "paidcourseregistration", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 128}, {"fields": {"model": "courseregcodeitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 129}, {"fields": {"model": "courseregcodeitemannotation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 130}, {"fields": {"model": "paidcourseregistrationannotation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 131}, {"fields": {"model": "certificateitem", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 132}, {"fields": {"model": "donationconfiguration", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 133}, {"fields": {"model": "donation", "app_label": "shoppingcart"}, "model": "contenttypes.contenttype", "pk": 134}, {"fields": {"model": "coursemode", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 135}, {"fields": {"model": "coursemodesarchive", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 136}, {"fields": {"model": "coursemodeexpirationconfig", "app_label": "course_modes"}, "model": "contenttypes.contenttype", "pk": 137}, {"fields": {"model": "softwaresecurephotoverification", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 138}, {"fields": {"model": "historicalverificationdeadline", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 139}, {"fields": {"model": "verificationdeadline", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 140}, {"fields": {"model": "verificationcheckpoint", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 141}, {"fields": {"model": "verificationstatus", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 142}, {"fields": {"model": "incoursereverificationconfiguration", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 143}, {"fields": {"model": "icrvstatusemailsconfiguration", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 144}, {"fields": {"model": "skippedreverification", "app_label": "verify_student"}, "model": "contenttypes.contenttype", "pk": 145}, {"fields": {"model": "darklangconfig", "app_label": "dark_lang"}, "model": "contenttypes.contenttype", "pk": 146}, {"fields": {"model": "embargoedcourse", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 147}, {"fields": {"model": "embargoedstate", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 148}, {"fields": {"model": "restrictedcourse", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 149}, {"fields": {"model": "country", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 150}, {"fields": {"model": "countryaccessrule", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 151}, {"fields": {"model": "courseaccessrulehistory", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 152}, {"fields": {"model": "ipfilter", "app_label": "embargo"}, "model": "contenttypes.contenttype", "pk": 153}, {"fields": {"model": "coursererunstate", "app_label": "course_action_state"}, "model": "contenttypes.contenttype", "pk": 154}, {"fields": {"model": "mobileapiconfig", "app_label": "mobile_api"}, "model": "contenttypes.contenttype", "pk": 155}, {"fields": {"model": "usersocialauth", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 156}, {"fields": {"model": "nonce", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 157}, {"fields": {"model": "association", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 158}, {"fields": {"model": "code", "app_label": "default"}, "model": "contenttypes.contenttype", "pk": 159}, {"fields": {"model": "surveyform", "app_label": "survey"}, "model": "contenttypes.contenttype", "pk": 160}, {"fields": {"model": "surveyanswer", "app_label": "survey"}, "model": "contenttypes.contenttype", "pk": 161}, {"fields": {"model": "xblockasidesconfig", "app_label": "lms_xblock"}, "model": "contenttypes.contenttype", "pk": 162}, {"fields": {"model": "courseoverview", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 163}, {"fields": {"model": "courseoverviewtab", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 164}, {"fields": {"model": "courseoverviewimageset", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 165}, {"fields": {"model": "courseoverviewimageconfig", "app_label": "course_overviews"}, "model": "contenttypes.contenttype", "pk": 166}, {"fields": {"model": "coursestructure", "app_label": "course_structures"}, "model": "contenttypes.contenttype", "pk": 167}, {"fields": {"model": "corsmodel", "app_label": "corsheaders"}, "model": "contenttypes.contenttype", "pk": 168}, {"fields": {"model": "xdomainproxyconfiguration", "app_label": "cors_csrf"}, "model": "contenttypes.contenttype", "pk": 169}, {"fields": {"model": "creditprovider", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 170}, {"fields": {"model": "creditcourse", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 171}, {"fields": {"model": "creditrequirement", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 172}, {"fields": {"model": "historicalcreditrequirementstatus", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 173}, {"fields": {"model": "creditrequirementstatus", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 174}, {"fields": {"model": "crediteligibility", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 175}, {"fields": {"model": "historicalcreditrequest", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 176}, {"fields": {"model": "creditrequest", "app_label": "credit"}, "model": "contenttypes.contenttype", "pk": 177}, {"fields": {"model": "courseteam", "app_label": "teams"}, "model": "contenttypes.contenttype", "pk": 178}, {"fields": {"model": "courseteammembership", "app_label": "teams"}, "model": "contenttypes.contenttype", "pk": 179}, {"fields": {"model": "xblockdisableconfig", "app_label": "xblock_django"}, "model": "contenttypes.contenttype", "pk": 180}, {"fields": {"model": "bookmark", "app_label": "bookmarks"}, "model": "contenttypes.contenttype", "pk": 181}, {"fields": {"model": "xblockcache", "app_label": "bookmarks"}, "model": "contenttypes.contenttype", "pk": 182}, {"fields": {"model": "programsapiconfig", "app_label": "programs"}, "model": "contenttypes.contenttype", "pk": 183}, {"fields": {"model": "selfpacedconfiguration", "app_label": "self_paced"}, "model": "contenttypes.contenttype", "pk": 184}, {"fields": {"model": "kvstore", "app_label": "thumbnail"}, "model": "contenttypes.contenttype", "pk": 185}, {"fields": {"model": "studentitem", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 186}, {"fields": {"model": "submission", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 187}, {"fields": {"model": "score", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 188}, {"fields": {"model": "scoresummary", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 189}, {"fields": {"model": "scoreannotation", "app_label": "submissions"}, "model": "contenttypes.contenttype", "pk": 190}, {"fields": {"model": "rubric", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 191}, {"fields": {"model": "criterion", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 192}, {"fields": {"model": "criterionoption", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 193}, {"fields": {"model": "assessment", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 194}, {"fields": {"model": "assessmentpart", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 195}, {"fields": {"model": "assessmentfeedbackoption", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 196}, {"fields": {"model": "assessmentfeedback", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 197}, {"fields": {"model": "peerworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 198}, {"fields": {"model": "peerworkflowitem", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 199}, {"fields": {"model": "trainingexample", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 200}, {"fields": {"model": "studenttrainingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 201}, {"fields": {"model": "studenttrainingworkflowitem", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 202}, {"fields": {"model": "aiclassifierset", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 203}, {"fields": {"model": "aiclassifier", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 204}, {"fields": {"model": "aitrainingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 205}, {"fields": {"model": "aigradingworkflow", "app_label": "assessment"}, "model": "contenttypes.contenttype", "pk": 206}, {"fields": {"model": "assessmentworkflow", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 207}, {"fields": {"model": "assessmentworkflowstep", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 208}, {"fields": {"model": "assessmentworkflowcancellation", "app_label": "workflow"}, "model": "contenttypes.contenttype", "pk": 209}, {"fields": {"model": "profile", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 210}, {"fields": {"model": "video", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 211}, {"fields": {"model": "coursevideo", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 212}, {"fields": {"model": "encodedvideo", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 213}, {"fields": {"model": "subtitle", "app_label": "edxval"}, "model": "contenttypes.contenttype", "pk": 214}, {"fields": {"model": "milestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 215}, {"fields": {"model": "milestonerelationshiptype", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 216}, {"fields": {"model": "coursemilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 217}, {"fields": {"model": "coursecontentmilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 218}, {"fields": {"model": "usermilestone", "app_label": "milestones"}, "model": "contenttypes.contenttype", "pk": 219}, {"fields": {"model": "proctoredexam", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 220}, {"fields": {"model": "proctoredexamreviewpolicy", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 221}, {"fields": {"model": "proctoredexamreviewpolicyhistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 222}, {"fields": {"model": "proctoredexamstudentattempt", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 223}, {"fields": {"model": "proctoredexamstudentattempthistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 224}, {"fields": {"model": "proctoredexamstudentallowance", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 225}, {"fields": {"model": "proctoredexamstudentallowancehistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 226}, {"fields": {"model": "proctoredexamsoftwaresecurereview", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 227}, {"fields": {"model": "proctoredexamsoftwaresecurereviewhistory", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 228}, {"fields": {"model": "proctoredexamsoftwaresecurecomment", "app_label": "edx_proctoring"}, "model": "contenttypes.contenttype", "pk": 229}, {"fields": {"model": "organization", "app_label": "organizations"}, "model": "contenttypes.contenttype", "pk": 230}, {"fields": {"model": "organizationcourse", "app_label": "organizations"}, "model": "contenttypes.contenttype", "pk": 231}, {"fields": {"model": "videouploadconfig", "app_label": "contentstore"}, "model": "contenttypes.contenttype", "pk": 232}, {"fields": {"model": "pushnotificationconfig", "app_label": "contentstore"}, "model": "contenttypes.contenttype", "pk": 233}, {"fields": {"model": "coursecreator", "app_label": "course_creators"}, "model": "contenttypes.contenttype", "pk": 234}, {"fields": {"model": "studioconfig", "app_label": "xblock_config"}, "model": "contenttypes.contenttype", "pk": 235}, {"fields": {"domain": "example.com", "name": "example.com"}, "model": "sites.site", "pk": 1}, {"fields": {"default": false, "mode": "honor", "icon": "badges/honor_jHFWziu.png"}, "model": "certificates.badgeimageconfiguration", "pk": 1}, {"fields": {"default": false, "mode": "verified", "icon": "badges/verified_6NmkerB.png"}, "model": "certificates.badgeimageconfiguration", "pk": 2}, {"fields": {"default": false, "mode": "professional", "icon": "badges/professional_dz1wOKm.png"}, "model": "certificates.badgeimageconfiguration", "pk": 3}, {"fields": {"plain_template": "{course_title}\n\n{{message_body}}\r\n----\r\nCopyright 2013 edX, All rights reserved.\r\n----\r\nConnect with edX:\r\nFacebook (http://facebook.com/edxonline)\r\nTwitter (http://twitter.com/edxonline)\r\nGoogle+ (https://plus.google.com/108235383044095082735)\r\nMeetup (http://www.meetup.com/edX-Communities/)\r\n----\r\nThis email was automatically sent from {platform_name}.\r\nYou are receiving this email at address {email} because you are enrolled in {course_title}\r\n(URL: {course_url} ).\r\nTo stop receiving email like this, update your course email settings at {email_settings_url}.\r\n", "html_template": " Update from {course_title}
Connect with edX:
{course_title}
{{message_body}}
Copyright \u00a9 2013 edX, All rights reserved.
Our mailing address is: edX 11 Cambridge Center, Suite 101 Cambridge, MA, USA 02142
This email was automatically sent from {platform_name}. You are receiving this email at address {email} because you are enrolled in {course_title}. To stop receiving email like this, update your course email settings here.