Remove microsite djangoapp except migrations and configuration.
This commit is contained in:
@@ -206,9 +206,6 @@ FEATURES = {
|
||||
# based on their location.
|
||||
'EMBARGO': False,
|
||||
|
||||
# Turn on/off Microsites feature
|
||||
'USE_MICROSITES': False,
|
||||
|
||||
# Allow creating courses with non-ascii characters in the course id
|
||||
'ALLOW_UNICODE_COURSE_ID': False,
|
||||
|
||||
@@ -1680,21 +1677,6 @@ DEFAULT_SITE_THEME = None
|
||||
|
||||
ENABLE_COMPREHENSIVE_THEMING = False
|
||||
|
||||
################################ Settings for Microsites ################################
|
||||
|
||||
### Select an implementation for the microsite backend
|
||||
# for MICROSITE_BACKEND possible choices are
|
||||
# 1. microsite_configuration.backends.filebased.FilebasedMicrositeBackend
|
||||
# 2. microsite_configuration.backends.database.DatabaseMicrositeBackend
|
||||
MICROSITE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend'
|
||||
# for MICROSITE_TEMPLATE_BACKEND possible choices are
|
||||
# 1. microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend
|
||||
# 2. microsite_configuration.backends.database.DatabaseMicrositeTemplateBackend
|
||||
MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend'
|
||||
# TTL for microsite database template cache
|
||||
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
|
||||
|
||||
|
||||
############################ Global Database Configuration #####################
|
||||
|
||||
DATABASE_ROUTERS = [
|
||||
@@ -1970,10 +1952,6 @@ BULK_EMAIL_DEFAULT_FROM_EMAIL = 'no-reply@example.com'
|
||||
# a bulk email message.
|
||||
BULK_EMAIL_LOG_SENT_EMAILS = False
|
||||
|
||||
################################ Settings for Microsites ################################
|
||||
MICROSITE_ROOT_DIR = '/edx/app/edxapp/edx-microsite'
|
||||
MICROSITE_CONFIGURATION = {}
|
||||
|
||||
############### Settings for django file storage ##################
|
||||
DEFAULT_FILE_STORAGE = 'django.core.files.storage.FileSystemStorage'
|
||||
|
||||
|
||||
@@ -514,19 +514,6 @@ XBLOCK_SETTINGS = ENV_TOKENS.get('XBLOCK_SETTINGS', {})
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})["licensing_enabled"] = FEATURES.get("LICENSING", False)
|
||||
XBLOCK_SETTINGS.setdefault("VideoBlock", {})['YOUTUBE_API_KEY'] = AUTH_TOKENS.get('YOUTUBE_API_KEY', YOUTUBE_API_KEY)
|
||||
|
||||
################# MICROSITE ####################
|
||||
# microsite specific configurations.
|
||||
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
|
||||
MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', ''))
|
||||
# this setting specify which backend to be used when pulling microsite specific configuration
|
||||
MICROSITE_BACKEND = ENV_TOKENS.get("MICROSITE_BACKEND", MICROSITE_BACKEND)
|
||||
# this setting specify which backend to be used when loading microsite specific templates
|
||||
MICROSITE_TEMPLATE_BACKEND = ENV_TOKENS.get("MICROSITE_TEMPLATE_BACKEND", MICROSITE_TEMPLATE_BACKEND)
|
||||
# TTL for microsite database template cache
|
||||
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = ENV_TOKENS.get(
|
||||
"MICROSITE_DATABASE_TEMPLATE_CACHE_TTL", MICROSITE_DATABASE_TEMPLATE_CACHE_TTL
|
||||
)
|
||||
|
||||
############################ OAUTH2 Provider ###################################
|
||||
|
||||
# OpenID Connect issuer ID. Normally the URL of the authentication endpoint.
|
||||
|
||||
@@ -218,72 +218,6 @@ FEATURES['EMBARGO'] = True
|
||||
|
||||
FEATURES['ENABLE_COMBINED_LOGIN_REGISTRATION'] = True
|
||||
|
||||
# set up some testing for microsites
|
||||
FEATURES['USE_MICROSITES'] = True
|
||||
MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_sites'
|
||||
MICROSITE_CONFIGURATION = {
|
||||
"test_site": {
|
||||
"domain_prefix": "test-site",
|
||||
"university": "test_site",
|
||||
"platform_name": "Test Site",
|
||||
"logo_image_url": "test_site/images/header-logo.png",
|
||||
"email_from_address": "test_site@edx.org",
|
||||
"payment_support_email": "test_site@edx.org",
|
||||
"ENABLE_MKTG_SITE": False,
|
||||
"SITE_NAME": "test_site.localhost",
|
||||
"course_org_filter": "TestSiteX",
|
||||
"course_about_show_social_links": False,
|
||||
"css_overrides_file": "test_site/css/test_site.css",
|
||||
"show_partners": False,
|
||||
"show_homepage_promo_video": False,
|
||||
"course_index_overlay_text": "This is a Test Site Overlay Text.",
|
||||
"course_index_overlay_logo_file": "test_site/images/header-logo.png",
|
||||
"homepage_overlay_html": "<h1>This is a Test Site Overlay HTML</h1>",
|
||||
"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_site.localhost",
|
||||
"urls": {
|
||||
'ABOUT': 'test-site/about',
|
||||
'PRIVACY': 'test-site/privacy',
|
||||
'TOS_AND_HONOR': 'test-site/tos-and-honor',
|
||||
},
|
||||
},
|
||||
"site_with_logistration": {
|
||||
"domain_prefix": "logistration",
|
||||
"university": "logistration",
|
||||
"platform_name": "Test logistration",
|
||||
"logo_image_url": "test_site/images/header-logo.png",
|
||||
"email_from_address": "test_site@edx.org",
|
||||
"payment_support_email": "test_site@edx.org",
|
||||
"ENABLE_MKTG_SITE": False,
|
||||
"ENABLE_COMBINED_LOGIN_REGISTRATION": True,
|
||||
"SITE_NAME": "test_site.localhost",
|
||||
"course_org_filter": "LogistrationX",
|
||||
"course_about_show_social_links": False,
|
||||
"css_overrides_file": "test_site/css/test_site.css",
|
||||
"show_partners": False,
|
||||
"show_homepage_promo_video": False,
|
||||
"course_index_overlay_text": "Logistration.",
|
||||
"course_index_overlay_logo_file": "test_site/images/header-logo.png",
|
||||
"homepage_overlay_html": "<h1>This is a Logistration HTML</h1>",
|
||||
"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_TEST_HOSTNAME = 'test-site.testserver'
|
||||
MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
|
||||
|
||||
TEST_THEME = COMMON_ROOT / "test" / "test-theme"
|
||||
|
||||
# For consistency in user-experience, keep the value of this setting in sync with
|
||||
|
||||
@@ -129,17 +129,6 @@ def marketing_link_context_processor(request):
|
||||
)
|
||||
|
||||
|
||||
def footer_context_processor(request): # pylint: disable=unused-argument
|
||||
"""
|
||||
Checks the site name to determine whether to use the edX.org footer or the Open Source Footer.
|
||||
"""
|
||||
return dict(
|
||||
[
|
||||
("IS_REQUEST_IN_MICROSITE", is_request_in_themed_site())
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary, namespace='main', request=None):
|
||||
"""
|
||||
Render a Mako template to as a string.
|
||||
|
||||
@@ -1,33 +1,6 @@
|
||||
"""
|
||||
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 __future__ import absolute_import
|
||||
|
||||
from django.conf import settings as base_settings
|
||||
|
||||
from microsite_configuration import microsite
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
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
|
||||
class MicrositeConfigurationConfig(AppConfig):
|
||||
name = 'microsite_configuration'
|
||||
verbose_name = "Microsite Configuration"
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
"""
|
||||
Django admin page for microsite models
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
|
||||
from util.organizations_helpers import get_organizations
|
||||
|
||||
from .models import Microsite, MicrositeHistory, MicrositeOrganizationMapping, MicrositeTemplate
|
||||
|
||||
|
||||
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)
|
||||
@@ -1,19 +0,0 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.apps import AppConfig
|
||||
|
||||
from .microsite import enable_microsites
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MicrositeConfigurationConfig(AppConfig):
|
||||
name = 'microsite_configuration'
|
||||
verbose_name = "Microsite Configuration"
|
||||
|
||||
def ready(self):
|
||||
# Mako requires the directories to be added after the django setup.
|
||||
enable_microsites(log)
|
||||
@@ -1,7 +0,0 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
@@ -1,325 +0,0 @@
|
||||
"""
|
||||
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 os.path
|
||||
import six
|
||||
import threading
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from util.url import strip_port_from_host
|
||||
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
class AbstractBaseMicrositeBackend(six.with_metaclass(abc.ABCMeta, object)):
|
||||
"""
|
||||
Abstract Base Class for the microsite backends.
|
||||
"""
|
||||
|
||||
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 six.iteritems(settings.MICROSITE_CONFIGURATION):
|
||||
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 six.itervalues(settings.MICROSITE_CONFIGURATION):
|
||||
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 six.itervalues(settings.MICROSITE_CONFIGURATION):
|
||||
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):
|
||||
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
|
||||
)
|
||||
|
||||
|
||||
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, template_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',
|
||||
])
|
||||
|
||||
relative_path = template_path[1:] if template_path.startswith('/') else template_path
|
||||
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 template_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
|
||||
@@ -1,205 +0,0 @@
|
||||
"""
|
||||
Microsite backend that reads the configuration from the database
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models.signals import post_save
|
||||
from django.dispatch import receiver
|
||||
from mako.template import Template
|
||||
|
||||
from microsite_configuration.backends.base import BaseMicrositeBackend, BaseMicrositeTemplateBackend
|
||||
from microsite_configuration.microsite import get_value as microsite_get_value
|
||||
from microsite_configuration.models import Microsite, MicrositeOrganizationMapping, MicrositeTemplate
|
||||
from util.cache import cache
|
||||
from util.memcache import fasthash
|
||||
from util.url import strip_port_from_host
|
||||
|
||||
|
||||
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 = <html><body>Template from DB</body></html>
|
||||
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
"""
|
||||
Microsite backend that reads the configuration from a file
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
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
|
||||
@@ -1,36 +0,0 @@
|
||||
"""
|
||||
Command to delete all rows from these tables:
|
||||
microsite_configuration_historicalmicrositeorganizationmapping
|
||||
microsite_configuration_historicalmicrositetemplate
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
from microsite_configuration.models import MicrositeOrganizationMapping, MicrositeTemplate
|
||||
from openedx.core.djangoapps.util.row_delete import delete_rows, BaseDeletionCommand
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Command(BaseDeletionCommand):
|
||||
"""
|
||||
Example usage: ./manage.py lms --settings=devstack delete_historical_microsite_data
|
||||
"""
|
||||
help = 'Deletes all historical MicrositeOrganizationMapping and MicrositeTemplate rows (in chunks).'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Deletes rows, chunking the deletes to avoid long table/row locks.
|
||||
"""
|
||||
chunk_size, sleep_between = super(Command, self).handle(*args, **options)
|
||||
delete_rows(
|
||||
MicrositeOrganizationMapping.objects,
|
||||
'microsite_configuration_historicalmicrositeorganizationmapping',
|
||||
'history_id',
|
||||
chunk_size, sleep_between
|
||||
)
|
||||
delete_rows(
|
||||
MicrositeTemplate.objects,
|
||||
'microsite_configuration_historicalmicrositetemplate',
|
||||
'history_id',
|
||||
chunk_size, sleep_between
|
||||
)
|
||||
@@ -1,164 +0,0 @@
|
||||
"""
|
||||
This file implements the Microsite support for the Open edX platform.
|
||||
A microsite enables the following features:
|
||||
|
||||
1) Mapping of sub-domain name to a 'brand', e.g. foo-university.edx.org
|
||||
2) Present a landing page with a listing of courses that are specific to the 'brand'
|
||||
3) Ability to swap out some branding elements in the website
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import inspect
|
||||
from importlib import import_module
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from microsite_configuration.backends.base import BaseMicrositeBackend, BaseMicrositeTemplateBackend
|
||||
|
||||
__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',
|
||||
]
|
||||
|
||||
BACKEND = None
|
||||
TEMPLATES_BACKEND = None
|
||||
|
||||
|
||||
def is_feature_enabled():
|
||||
"""
|
||||
Returns whether the feature flag to enable microsite has been set
|
||||
"""
|
||||
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 BACKEND.is_request_in_microsite()
|
||||
|
||||
|
||||
def get_value(val_name, default=None, **kwargs):
|
||||
"""
|
||||
Returns a value associated with the request's microsite, if present
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Returns True/False whether a Microsite has a definition for the
|
||||
specified named value
|
||||
"""
|
||||
return BACKEND.has_override_value(val_name)
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
return BACKEND.get_value_for_org(org, val_name, default)
|
||||
|
||||
|
||||
def get_all_orgs():
|
||||
"""
|
||||
This returns a set of orgs that are considered within a microsite. This can be used,
|
||||
for example, to do filtering
|
||||
"""
|
||||
return BACKEND.get_all_orgs()
|
||||
|
||||
|
||||
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
|
||||
"""
|
||||
BACKEND.clear()
|
||||
|
||||
|
||||
def set_by_domain(domain):
|
||||
"""
|
||||
For a given request domain, find a match in our microsite configuration
|
||||
and make it available to the complete django request process
|
||||
"""
|
||||
BACKEND.set_config_by_domain(domain)
|
||||
|
||||
|
||||
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
|
||||
|
||||
return TEMPLATES_BACKEND.get_template(uri)
|
||||
|
||||
|
||||
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)
|
||||
@@ -1,39 +0,0 @@
|
||||
"""
|
||||
This file implements the Middleware support for the Open edX platform.
|
||||
A microsite enables the following features:
|
||||
|
||||
1) Mapping of sub-domain name to a 'brand', e.g. foo-university.edx.org
|
||||
2) Present a landing page with a listing of courses that are specific to the 'brand'
|
||||
3) Ability to swap out some branding elements in the website
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from microsite_configuration import microsite
|
||||
|
||||
|
||||
class MicrositeMiddleware(object):
|
||||
"""
|
||||
Middleware class which will bind configuration information regarding 'microsites' on a per request basis.
|
||||
The actual configuration information is taken from Django settings information
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Middleware entry point on every request processing. This will associate a request's domain name
|
||||
with a 'University' and any corresponding microsite configuration information
|
||||
"""
|
||||
microsite.clear()
|
||||
|
||||
domain = request.META.get('HTTP_HOST', None)
|
||||
|
||||
microsite.set_by_domain(domain)
|
||||
|
||||
return None
|
||||
|
||||
def process_response(self, request, response):
|
||||
"""
|
||||
Middleware entry point for request completion.
|
||||
"""
|
||||
microsite.clear()
|
||||
return response
|
||||
@@ -1,183 +0,0 @@
|
||||
"""
|
||||
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.
|
||||
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import collections
|
||||
|
||||
from django.contrib.sites.models import Site
|
||||
from django.db import models
|
||||
from django.db.models.base import ObjectDoesNotExist
|
||||
from django.db.models.signals import pre_delete, pre_save
|
||||
from django.dispatch import receiver
|
||||
from jsonfield.fields import JSONField
|
||||
from model_utils.models import TimeStampedModel
|
||||
|
||||
|
||||
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.
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.OneToOneField(Site, related_name='microsite', on_delete=models.CASCADE)
|
||||
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
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
site = models.ForeignKey(Site, related_name='microsite_history', on_delete=models.CASCADE)
|
||||
key = models.CharField(max_length=63, db_index=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
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
organization = models.CharField(max_length=63, db_index=True, unique=True)
|
||||
microsite = models.ForeignKey(Microsite, db_index=True, on_delete=models.CASCADE)
|
||||
|
||||
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
|
||||
|
||||
.. no_pii:
|
||||
"""
|
||||
|
||||
microsite = models.ForeignKey(Microsite, db_index=True, on_delete=models.CASCADE)
|
||||
template_uri = models.CharField(max_length=255, db_index=True)
|
||||
template = models.TextField()
|
||||
|
||||
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
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
"""
|
||||
Test Microsite base backends.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from microsite_configuration.backends.base import AbstractBaseMicrositeBackend
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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()
|
||||
@@ -1,211 +0,0 @@
|
||||
"""
|
||||
Test Microsite database backends.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
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()
|
||||
self.addCleanup(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'))
|
||||
|
||||
@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)
|
||||
|
||||
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(list(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')
|
||||
|
||||
def test_has_configuration_set(self):
|
||||
"""
|
||||
Tests microsite.has_configuration_set works as expected on this backend.
|
||||
"""
|
||||
self.assertTrue(microsite.BACKEND.has_configuration_set())
|
||||
|
||||
Microsite.objects.all().delete()
|
||||
self.assertFalse(microsite.BACKEND.has_configuration_set())
|
||||
|
||||
|
||||
@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="""
|
||||
<html>
|
||||
<body>
|
||||
About this microsite.
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
)
|
||||
|
||||
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())
|
||||
@@ -1,77 +0,0 @@
|
||||
"""
|
||||
Factories module to hold microsite factories
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import factory
|
||||
from django.contrib.sites.models import Site
|
||||
from factory.django import DjangoModelFactory
|
||||
|
||||
from microsite_configuration.models import Microsite, MicrositeOrganizationMapping, MicrositeTemplate
|
||||
|
||||
|
||||
class SiteFactory(DjangoModelFactory):
|
||||
"""
|
||||
Factory for django.contrib.sites.models.Site
|
||||
"""
|
||||
class Meta(object):
|
||||
model = Site
|
||||
django_get_or_create = ('domain',)
|
||||
|
||||
name = "test microsite"
|
||||
domain = "test-site.testserver"
|
||||
|
||||
|
||||
class MicrositeFactory(DjangoModelFactory):
|
||||
"""
|
||||
Factory for Microsite
|
||||
"""
|
||||
class Meta(object):
|
||||
model = Microsite
|
||||
|
||||
key = "test_site"
|
||||
site = factory.SubFactory(SiteFactory)
|
||||
values = {
|
||||
"domain_prefix": "test-site",
|
||||
"university": "test_site",
|
||||
"platform_name": "Test Site DB",
|
||||
"logo_image_url": "test_site/images/header-logo.png",
|
||||
"email_from_address": "test_site@edx.org",
|
||||
"payment_support_email": "test_site_dbe@edx.org",
|
||||
"ENABLE_MKTG_SITE": False,
|
||||
"SITE_NAME": "test_site.localhost",
|
||||
"course_org_filter": "TestSiteX",
|
||||
"course_about_show_social_links": False,
|
||||
"css_overrides_file": "test_site/css/test_site.css",
|
||||
"show_partners": False,
|
||||
"show_homepage_promo_video": False,
|
||||
"course_index_overlay_text": "This is a Test Site Overlay Text.",
|
||||
"course_index_overlay_logo_file": "test_site/images/header-logo.png",
|
||||
"homepage_overlay_html": "<h1>This is a Test Site Overlay HTML</h1>",
|
||||
"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_site.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
|
||||
@@ -1,51 +0,0 @@
|
||||
"""
|
||||
Tests for microsite admin
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.http import HttpRequest
|
||||
|
||||
from microsite_configuration.admin import MicrositeAdmin
|
||||
from microsite_configuration.models import Microsite
|
||||
from microsite_configuration.tests.tests import DatabaseMicrositeTestCase
|
||||
|
||||
|
||||
class MicrositeAdminTests(DatabaseMicrositeTestCase):
|
||||
"""
|
||||
Test class for MicrositeAdmin
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(MicrositeAdminTests, self).setUp()
|
||||
self.adminsite = AdminSite()
|
||||
self.microsite_admin = MicrositeAdmin(Microsite, self.adminsite)
|
||||
self.request = HttpRequest()
|
||||
|
||||
def test_fields_in_admin_form(self):
|
||||
"""
|
||||
Tests presence of form fields for Microsite.
|
||||
"""
|
||||
microsite_form = self.microsite_admin.get_form(self.request, self.microsite)
|
||||
self.assertEqual(
|
||||
list(microsite_form.base_fields),
|
||||
["site", "key", "values"]
|
||||
)
|
||||
|
||||
def test_save_action_admin_form(self):
|
||||
"""
|
||||
Tests save action for Microsite model form.
|
||||
"""
|
||||
new_values = {
|
||||
"domain_prefix": "test-site-new",
|
||||
"platform_name": "Test Site New"
|
||||
}
|
||||
microsite_form = self.microsite_admin.get_form(self.request)(instance=self.microsite, data={
|
||||
"key": self.microsite.key,
|
||||
"site": self.microsite.site.id,
|
||||
"values": new_values,
|
||||
})
|
||||
self.assertTrue(microsite_form.is_valid())
|
||||
microsite_form.save()
|
||||
new_microsite = Microsite.objects.get(key=self.microsite.key)
|
||||
self.assertEqual(new_microsite.values, new_values)
|
||||
@@ -1,39 +0,0 @@
|
||||
"""
|
||||
Some additional unit tests for Microsite logic. The LMS covers some of the Microsite testing, this adds
|
||||
some additional coverage
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import ddt
|
||||
from mock import patch
|
||||
|
||||
from microsite_configuration.backends.base import BaseMicrositeBackend
|
||||
from microsite_configuration.microsite import get_backend, get_value_for_org
|
||||
from microsite_configuration.tests.tests import MICROSITE_BACKENDS, DatabaseMicrositeTestCase
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestMicrosites(DatabaseMicrositeTestCase):
|
||||
"""
|
||||
Run through some Microsite logic
|
||||
"""
|
||||
|
||||
@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")
|
||||
|
||||
@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("TestSiteX", "university", "default_value")
|
||||
self.assertEquals(value, "test_site")
|
||||
@@ -1,84 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Tests configuration templatetags and helper functions.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from microsite_configuration import microsite
|
||||
from microsite_configuration.backends.base import BaseMicrositeBackend
|
||||
from microsite_configuration.backends.database import DatabaseMicrositeBackend
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.site_configuration.templatetags import configuration as configuration_tags
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
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 | {}'.format(settings.PLATFORM_NAME)
|
||||
title = configuration_helpers.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 | 驴 | {}'.format(settings.PLATFORM_NAME)
|
||||
title = configuration_helpers.page_title_breadcrumbs(*crumbs)
|
||||
self.assertEqual(expected, title)
|
||||
|
||||
def test_platform_name(self):
|
||||
pname = configuration_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 | {}'.format(settings.PLATFORM_NAME)
|
||||
title = configuration_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 = configuration_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
|
||||
)
|
||||
@@ -1,40 +0,0 @@
|
||||
"""
|
||||
Holds base classes for microsite tests
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.test import TestCase
|
||||
from mock import DEFAULT
|
||||
|
||||
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='TestSiteX')
|
||||
|
||||
|
||||
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
|
||||
@@ -388,7 +388,7 @@ def generate_activation_email_context(user, registration):
|
||||
|
||||
def create_or_set_user_attribute_created_on_site(user, site):
|
||||
"""
|
||||
Create or Set UserAttribute indicating the microsite site the user account was created on.
|
||||
Create or Set UserAttribute indicating the site the user account was created on.
|
||||
User maybe created on 'courses.edx.org', or a white-label site. Due to the very high
|
||||
traffic on this table we now ignore the default site (eg. 'courses.edx.org') and
|
||||
code which comsumes this attribute should assume a 'created_on_site' which doesn't exist
|
||||
|
||||
@@ -222,6 +222,7 @@ class MigrationTests(TestCase):
|
||||
"""
|
||||
Tests for migrations.
|
||||
"""
|
||||
@unittest.skip("Migration will delete several models. Need to ship not referencing it first. DEPR-12.")
|
||||
@override_settings(MIGRATION_MODULES={})
|
||||
def test_migrations_are_in_sync(self):
|
||||
"""
|
||||
|
||||
@@ -255,9 +255,6 @@ FEATURES = {
|
||||
# defaults, so that we maintain current behavior
|
||||
'ALLOW_WIKI_ROOT_ACCESS': True,
|
||||
|
||||
# Turn on/off Microsites feature
|
||||
'USE_MICROSITES': False,
|
||||
|
||||
# Turn on third-party auth. Disabled for now because full implementations are not yet available. Remember to run
|
||||
# migrations if you enable this; we don't create tables by default.
|
||||
'ENABLE_THIRD_PARTY_AUTH': False,
|
||||
@@ -646,8 +643,6 @@ def _make_mako_template_dirs(settings):
|
||||
for theme in get_themes_unchecked(themes_dirs, settings.PROJECT_ROOT):
|
||||
if theme.themes_base_dir not in settings.MAKO_TEMPLATE_DIRS_BASE:
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, theme.themes_base_dir)
|
||||
if settings.FEATURES.get('USE_MICROSITES', False) and getattr(settings, "MICROSITE_CONFIGURATION", False):
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, settings.MICROSITE_ROOT_DIR)
|
||||
return settings.MAKO_TEMPLATE_DIRS_BASE
|
||||
|
||||
|
||||
@@ -673,9 +668,6 @@ CONTEXT_PROCESSORS = [
|
||||
# Timezone processor (sends language and time_zone preference)
|
||||
'courseware.context_processor.user_timezone_locale_prefs',
|
||||
|
||||
# Allows the open edX footer to be leveraged in Django Templates.
|
||||
'edxmako.shortcuts.footer_context_processor',
|
||||
|
||||
# Online contextual help
|
||||
'help_tokens.context_processor',
|
||||
'openedx.core.djangoapps.site_configuration.context_processors.configuration_context',
|
||||
@@ -732,19 +724,6 @@ derived_collection_entry('TEMPLATES', 1, 'DIRS')
|
||||
DEFAULT_TEMPLATE_ENGINE = TEMPLATES[0]
|
||||
DEFAULT_TEMPLATE_ENGINE_DIRS = DEFAULT_TEMPLATE_ENGINE['DIRS'][:]
|
||||
|
||||
|
||||
def _add_microsite_dirs_to_default_template_engine(settings):
|
||||
"""
|
||||
Derives the final DEFAULT_TEMPLATE_ENGINE['DIRS'] setting from other settings.
|
||||
"""
|
||||
if settings.FEATURES.get('USE_MICROSITES', False) and getattr(settings, "MICROSITE_CONFIGURATION", False):
|
||||
DEFAULT_TEMPLATE_ENGINE_DIRS.append(settings.MICROSITE_ROOT_DIR)
|
||||
return DEFAULT_TEMPLATE_ENGINE_DIRS
|
||||
|
||||
|
||||
DEFAULT_TEMPLATE_ENGINE['DIRS'] = _add_microsite_dirs_to_default_template_engine
|
||||
derived_collection_entry('DEFAULT_TEMPLATE_ENGINE', 'DIRS')
|
||||
|
||||
###############################################################################################
|
||||
|
||||
AUTHENTICATION_BACKENDS = [
|
||||
@@ -1450,7 +1429,6 @@ MIDDLEWARE_CLASSES = [
|
||||
|
||||
'mobile_api.middleware.AppVersionUpgrade',
|
||||
'openedx.core.djangoapps.header_control.middleware.HeaderControlMiddleware',
|
||||
'microsite_configuration.middleware.MicrositeMiddleware',
|
||||
'lms.djangoapps.discussion.django_comment_client.middleware.AjaxExceptionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
||||
@@ -2383,7 +2361,7 @@ INSTALLED_APPS = [
|
||||
'openedx.core.djangoapps.dark_lang',
|
||||
|
||||
# Microsite configuration
|
||||
'microsite_configuration.apps.MicrositeConfigurationConfig',
|
||||
'microsite_configuration.MicrositeConfigurationConfig',
|
||||
|
||||
# RSS Proxy
|
||||
'rss_proxy',
|
||||
@@ -3441,23 +3419,6 @@ EDX_DRF_EXTENSIONS = {
|
||||
'JWT_PAYLOAD_USER_ATTRIBUTE_MAPPING': {},
|
||||
}
|
||||
|
||||
################################ Settings for Microsites ################################
|
||||
|
||||
### Select an implementation for the microsite backend
|
||||
# for MICROSITE_BACKEND possible choices are
|
||||
# 1. microsite_configuration.backends.filebased.FilebasedMicrositeBackend
|
||||
# 2. microsite_configuration.backends.database.DatabaseMicrositeBackend
|
||||
MICROSITE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend'
|
||||
# for MICROSITE_TEMPLATE_BACKEND possible choices are
|
||||
# 1. microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend
|
||||
# 2. microsite_configuration.backends.database.DatabaseMicrositeTemplateBackend
|
||||
MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend'
|
||||
# TTL for microsite database template cache
|
||||
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = 5 * 60
|
||||
|
||||
MICROSITE_ROOT_DIR = '/edx/app/edxapp/edx-microsite'
|
||||
MICROSITE_CONFIGURATION = {}
|
||||
|
||||
################################ Settings for rss_proxy ################################
|
||||
|
||||
RSS_PROXY_CACHE_TIMEOUT = 3600 # The length of time we cache RSS retrieved from remote URLs in seconds
|
||||
|
||||
@@ -81,8 +81,6 @@ when nested within each other::
|
||||
for theme in get_themes_unchecked(themes_dirs, settings.PROJECT_ROOT):
|
||||
if theme.themes_base_dir not in settings.MAKO_TEMPLATE_DIRS_BASE:
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, theme.themes_base_dir)
|
||||
if settings.FEATURES.get('USE_MICROSITES', False) and getattr(settings, "MICROSITE_CONFIGURATION", False):
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, settings.MICROSITE_ROOT_DIR)
|
||||
return settings.MAKO_TEMPLATE_DIRS_BASE
|
||||
|
||||
TEMPLATES = [
|
||||
|
||||
@@ -898,18 +898,6 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U
|
||||
JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {}))
|
||||
JWT_AUTH.update(AUTH_TOKENS.get('JWT_AUTH', {}))
|
||||
|
||||
################# MICROSITE ####################
|
||||
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {})
|
||||
MICROSITE_ROOT_DIR = path(ENV_TOKENS.get('MICROSITE_ROOT_DIR', ''))
|
||||
# this setting specify which backend to be used when pulling microsite specific configuration
|
||||
MICROSITE_BACKEND = ENV_TOKENS.get("MICROSITE_BACKEND", MICROSITE_BACKEND)
|
||||
# this setting specify which backend to be used when loading microsite specific templates
|
||||
MICROSITE_TEMPLATE_BACKEND = ENV_TOKENS.get("MICROSITE_TEMPLATE_BACKEND", MICROSITE_TEMPLATE_BACKEND)
|
||||
# TTL for microsite database template cache
|
||||
MICROSITE_DATABASE_TEMPLATE_CACHE_TTL = ENV_TOKENS.get(
|
||||
"MICROSITE_DATABASE_TEMPLATE_CACHE_TTL", MICROSITE_DATABASE_TEMPLATE_CACHE_TTL
|
||||
)
|
||||
|
||||
# Offset for pk of courseware.StudentModuleHistoryExtended
|
||||
STUDENTMODULEHISTORYEXTENDED_OFFSET = ENV_TOKENS.get(
|
||||
'STUDENTMODULEHISTORYEXTENDED_OFFSET', STUDENTMODULEHISTORYEXTENDED_OFFSET
|
||||
|
||||
@@ -417,77 +417,6 @@ PLATFORM_DESCRIPTION = ugettext_lazy(u"Open édX Platform")
|
||||
|
||||
SITE_NAME = "edx.org"
|
||||
|
||||
# set up some testing for microsites
|
||||
FEATURES['USE_MICROSITES'] = True
|
||||
MICROSITE_ROOT_DIR = COMMON_ROOT / 'test' / 'test_sites'
|
||||
MICROSITE_CONFIGURATION = {
|
||||
"test_site": {
|
||||
"domain_prefix": "test-site",
|
||||
"university": "test_site",
|
||||
"platform_name": "Test Site",
|
||||
"logo_image_url": "test_site/images/header-logo.png",
|
||||
"email_from_address": "test_site@edx.org",
|
||||
"ACTIVATION_EMAIL_FROM_ADDRESS": "test_activate@edx.org",
|
||||
"payment_support_email": "test_site@edx.org",
|
||||
"ENABLE_MKTG_SITE": False,
|
||||
"SITE_NAME": "test_site.localhost",
|
||||
"course_org_filter": "TestSiteX",
|
||||
"course_about_show_social_links": False,
|
||||
"css_overrides_file": "test_site/css/test_site.css",
|
||||
"show_partners": False,
|
||||
"show_homepage_promo_video": False,
|
||||
"course_index_overlay_text": "This is a Test Site Overlay Text.",
|
||||
"course_index_overlay_logo_file": "test_site/images/header-logo.png",
|
||||
"homepage_overlay_html": "<h1>This is a Test Site Overlay HTML</h1>",
|
||||
"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_site.localhost",
|
||||
"LINKEDIN_COMPANY_ID": "test",
|
||||
"FACEBOOK_APP_ID": "12345678908",
|
||||
"urls": {
|
||||
'ABOUT': 'test-site/about',
|
||||
'PRIVACY': 'test-site/privacy',
|
||||
'TOS_AND_HONOR': 'test-site/tos-and-honor',
|
||||
},
|
||||
},
|
||||
"site_with_logistration": {
|
||||
"domain_prefix": "logistration",
|
||||
"university": "logistration",
|
||||
"platform_name": "Test logistration",
|
||||
"logo_image_url": "test_site/images/header-logo.png",
|
||||
"email_from_address": "test_site@edx.org",
|
||||
"ACTIVATION_EMAIL_FROM_ADDRESS": "test_activate@edx.org",
|
||||
"payment_support_email": "test_site@edx.org",
|
||||
"ENABLE_MKTG_SITE": False,
|
||||
"ENABLE_COMBINED_LOGIN_REGISTRATION": True,
|
||||
"SITE_NAME": "test_site.localhost",
|
||||
"course_org_filter": "LogistrationX",
|
||||
"course_about_show_social_links": False,
|
||||
"css_overrides_file": "test_site/css/test_site.css",
|
||||
"show_partners": False,
|
||||
"show_homepage_promo_video": False,
|
||||
"course_index_overlay_text": "Logistration.",
|
||||
"course_index_overlay_logo_file": "test_site/images/header-logo.png",
|
||||
"homepage_overlay_html": "<h1>This is a Logistration HTML</h1>",
|
||||
"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_TEST_HOSTNAME = 'test-site.testserver'
|
||||
MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
|
||||
|
||||
TEST_THEME = COMMON_ROOT / "test" / "test-theme"
|
||||
|
||||
# add extra template directory for test-only templates
|
||||
|
||||
19
lms/tests.py
19
lms/tests.py
@@ -9,7 +9,6 @@ from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
from edxmako import LOOKUP, add_lookup
|
||||
from microsite_configuration import microsite
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,21 +31,3 @@ class LmsModuleTests(TestCase):
|
||||
assert settings.FEATURES['ENABLE_API_DOCS']
|
||||
response = self.client.get('/api-docs/')
|
||||
self.assertEqual(200, response.status_code)
|
||||
|
||||
|
||||
class TemplateLookupTests(TestCase):
|
||||
"""
|
||||
Tests for TemplateLookup.
|
||||
"""
|
||||
|
||||
def test_add_lookup_to_main(self):
|
||||
"""Test that any template directories added are not cleared when microsites are enabled."""
|
||||
|
||||
add_lookup('main', 'external_module', __name__)
|
||||
directories = LOOKUP['main'].directories
|
||||
self.assertEqual(len([directory for directory in directories if 'external_module' in directory]), 1)
|
||||
|
||||
# This should not clear the directories list
|
||||
microsite.enable_microsites(log)
|
||||
directories = LOOKUP['main'].directories
|
||||
self.assertEqual(len([directory for directory in directories if 'external_module' in directory]), 1)
|
||||
|
||||
@@ -101,9 +101,6 @@ LMS_ROOT_URL = "http://localhost:8000"
|
||||
|
||||
MEDIA_ROOT = tempfile.mkdtemp()
|
||||
|
||||
MICROSITE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeBackend'
|
||||
MICROSITE_TEMPLATE_BACKEND = 'microsite_configuration.backends.filebased.FilebasedMicrositeTemplateBackend'
|
||||
|
||||
RECALCULATE_GRADES_ROUTING_KEY = 'edx.core.default'
|
||||
POLICY_CHANGE_GRADES_ROUTING_KEY = 'edx.core.default'
|
||||
POLICY_CHANGE_TASK_RATE_LIMIT = '300/h'
|
||||
|
||||
Reference in New Issue
Block a user