Remove microsite djangoapp except migrations and configuration.

This commit is contained in:
Diana Huang
2019-08-12 17:11:49 -04:00
parent 9753eae441
commit bf7dad15e4
34 changed files with 7 additions and 2012 deletions

View File

@@ -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'

View File

@@ -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.

View File

@@ -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

View File

@@ -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.

View File

@@ -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"

View File

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

View File

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

View File

@@ -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.
"""

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -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())

View File

@@ -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

View File

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

View File

@@ -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")

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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):
"""

View File

@@ -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

View File

@@ -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 = [

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -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'