-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/cdodge/alpha microsite2 #2061
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ | |
import json | ||
|
||
from .common import * | ||
|
||
from logsettings import get_logger_config | ||
import os | ||
|
||
|
@@ -145,7 +146,6 @@ | |
#Timezone overrides | ||
TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE) | ||
|
||
|
||
ENV_FEATURES = ENV_TOKENS.get('FEATURES', ENV_TOKENS.get('MITX_FEATURES', {})) | ||
for feature, value in ENV_FEATURES.items(): | ||
FEATURES[feature] = value | ||
|
@@ -213,3 +213,16 @@ | |
|
||
# Event tracking | ||
TRACKING_BACKENDS.update(AUTH_TOKENS.get("TRACKING_BACKENDS", {})) | ||
|
||
SUBDOMAIN_BRANDING = ENV_TOKENS.get('SUBDOMAIN_BRANDING', {}) | ||
VIRTUAL_UNIVERSITIES = ENV_TOKENS.get('VIRTUAL_UNIVERSITIES', []) | ||
|
||
MICROSITE_CONFIGURATION = ENV_TOKENS.get('MICROSITE_CONFIGURATION', {}) | ||
MICROSITE_ROOT_DIR = ENV_TOKENS.get('MICROSITE_ROOT_DIR') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @chrisndodge - I believe this should have a default value, otherwise it could throw a key error if the configuration script does not define a value for this key. |
||
if len(MICROSITE_CONFIGURATION.keys()) > 0: | ||
enable_microsites( | ||
MICROSITE_CONFIGURATION, | ||
SUBDOMAIN_BRANDING, | ||
VIRTUAL_UNIVERSITIES, | ||
microsites_root=path(MICROSITE_ROOT_DIR) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
""" | ||
This is a localdev test for the Microsite processing pipeline | ||
""" | ||
# We intentionally define lots of variables that aren't used, and | ||
# want to import all variables from base settings files | ||
# pylint: disable=W0401, W0614 | ||
|
||
from .dev import * | ||
from .dev import SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES | ||
|
||
MICROSITE_NAMES = ['openedx'] | ||
MICROSITE_CONFIGURATION = {} | ||
|
||
if MICROSITE_NAMES and len(MICROSITE_NAMES) > 0: | ||
enable_microsites(MICROSITE_NAMES, MICROSITE_CONFIGURATION, SUBDOMAIN_BRANDING, VIRTUAL_UNIVERSITIES) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,183 @@ | ||
""" | ||
This file implements the initial Microsite support for the Open edX platform. | ||
A microsite enables the following features: | ||
|
||
1) Mapping of sub-domain name to a 'brand', e.g. foo-university.edx.org | ||
2) Present a landing page with a listing of courses that are specific to the 'brand' | ||
3) Ability to swap out some branding elements in the website | ||
""" | ||
import threading | ||
import os.path | ||
|
||
from django.conf import settings | ||
|
||
_microsite_configuration_threadlocal = threading.local() | ||
_microsite_configuration_threadlocal.data = {} | ||
|
||
|
||
def has_microsite_configuration_set(): | ||
""" | ||
Returns whether the MICROSITE_CONFIGURATION has been set in the configuration files | ||
""" | ||
return getattr(settings, "MICROSITE_CONFIGURATION", False) | ||
|
||
|
||
class MicrositeConfiguration(object): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This has a lot of stuff besides the middleware. Can't we have a MicrositeMiddleware class with just the process_request and process_response methods, and then another class to do the rest? It's odd to have a middleware class that is also referenced throughout the rest of the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can I defer this to a post-release cleanup activity? My concern is if we put the static methods elsewhere, I'll have to change a lot of call sites which is more code churn than is probably wise at this point in time. |
||
""" | ||
Middleware class which will bind configuration information regarding 'microsites' on a per request basis. | ||
The actual configuration information is taken from Django settings information | ||
""" | ||
|
||
@classmethod | ||
def is_request_in_microsite(cls): | ||
""" | ||
This will return if current request is a request within a microsite | ||
""" | ||
return cls.get_microsite_configuration() | ||
|
||
@classmethod | ||
def get_microsite_configuration(cls): | ||
""" | ||
Returns the current request's microsite configuration | ||
""" | ||
if not hasattr(_microsite_configuration_threadlocal, 'data'): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The threadlocal has the data attribute, you set it at module level. Why check here that it exists? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do remember there was a specific reason but I can't remember the details now. I think it might have had to do if the classmethod was called outside of a HTTP request. |
||
return {} | ||
|
||
return _microsite_configuration_threadlocal.data | ||
|
||
@classmethod | ||
def get_microsite_configuration_value(cls, val_name, default=None): | ||
""" | ||
Returns a value associated with the request's microsite, if present | ||
""" | ||
configuration = cls.get_microsite_configuration() | ||
return configuration.get(val_name, default) | ||
|
||
@classmethod | ||
def get_microsite_template_path(cls, relative_path): | ||
""" | ||
Returns a path to a Mako template, which can either be in | ||
a microsite directory (as an override) or will just return what is passed in | ||
""" | ||
|
||
if not cls.is_request_in_microsite(): | ||
return relative_path | ||
|
||
microsite_template_path = cls.get_microsite_configuration_value('template_dir') | ||
|
||
if microsite_template_path: | ||
search_path = microsite_template_path / relative_path | ||
|
||
if os.path.isfile(search_path): | ||
path = '{0}/templates/{1}'.format( | ||
cls.get_microsite_configuration_value('microsite_name'), | ||
relative_path | ||
) | ||
return path | ||
|
||
return relative_path | ||
|
||
@classmethod | ||
def get_microsite_configuration_value_for_org(cls, org, val_name, default=None): | ||
""" | ||
This returns a configuration value for a microsite which has an org_filter that matches | ||
what is passed in | ||
""" | ||
if not has_microsite_configuration_set(): | ||
return default | ||
|
||
for key in settings.MICROSITE_CONFIGURATION.keys(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Iterating a dict gives you its keys. |
||
org_filter = settings.MICROSITE_CONFIGURATION[key].get('course_org_filter', None) | ||
if org_filter == org: | ||
return settings.MICROSITE_CONFIGURATION[key].get(val_name, default) | ||
return default | ||
|
||
@classmethod | ||
def get_all_microsite_orgs(cls): | ||
""" | ||
This returns a set of orgs that are considered within a Microsite. This can be used, | ||
for example, to do filtering | ||
""" | ||
org_filter_set = [] | ||
if not has_microsite_configuration_set(): | ||
return org_filter_set | ||
|
||
for key in settings.MICROSITE_CONFIGURATION: | ||
org_filter = settings.MICROSITE_CONFIGURATION[key].get('course_org_filter') | ||
if org_filter: | ||
org_filter_set.append(org_filter) | ||
|
||
return org_filter_set | ||
|
||
def clear_microsite_configuration(self): | ||
""" | ||
Clears out any microsite configuration from the current request/thread | ||
""" | ||
_microsite_configuration_threadlocal.data = {} | ||
|
||
def process_request(self, request): | ||
""" | ||
Middleware entry point on every request processing. This will associate a request's domain name | ||
with a 'University' and any corresponding microsite configuration information | ||
""" | ||
self.clear_microsite_configuration() | ||
|
||
domain = request.META.get('HTTP_HOST', None) | ||
|
||
if domain: | ||
subdomain = MicrositeConfiguration.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys()) | ||
university = MicrositeConfiguration.match_university(subdomain) | ||
microsite_configuration = self.get_microsite_configuration_for_university(university) | ||
if microsite_configuration: | ||
microsite_configuration['university'] = university | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is modifying a dictionary from settings, which seems very unusual. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cdodge have we figured out another way to do this? |
||
microsite_configuration['subdomain'] = subdomain | ||
microsite_configuration['site_domain'] = domain | ||
_microsite_configuration_threadlocal.data = microsite_configuration | ||
|
||
# also put the configuration on the request itself to make it easier to dereference | ||
request.microsite_configuration = _microsite_configuration_threadlocal.data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not add it to the session? Why can't one of these mechanisms for passing this around be sufficient? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't look like |
||
return None | ||
|
||
def process_response(self, request, response): | ||
""" | ||
Middleware entry point for request completion. | ||
""" | ||
self.clear_microsite_configuration() | ||
return response | ||
|
||
def get_microsite_configuration_for_university(self, university): | ||
""" | ||
For a given university, return the microsite configuration which | ||
is in the Django settings | ||
""" | ||
if not university: | ||
return None | ||
|
||
if not has_microsite_configuration_set(): | ||
return None | ||
|
||
configuration = settings.MICROSITE_CONFIGURATION.get(university, None) | ||
return configuration | ||
|
||
@classmethod | ||
def match_university(cls, domain): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function name sounds like you give it a university. |
||
""" | ||
Return the university name specified for the domain, or None | ||
if no university was specified | ||
""" | ||
if not settings.FEATURES['SUBDOMAIN_BRANDING'] or domain is None: | ||
return None | ||
|
||
subdomain = cls.pick_subdomain(domain, settings.SUBDOMAIN_BRANDING.keys()) | ||
return settings.SUBDOMAIN_BRANDING.get(subdomain) | ||
|
||
@classmethod | ||
def pick_subdomain(cls, domain, options, default='default'): | ||
""" | ||
Attempt to match the incoming request's HOST domain with a configuration map | ||
to see what subdomains are supported in Microsites. | ||
""" | ||
for option in options: | ||
if domain.startswith(option): | ||
return option | ||
return default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"microsite_configuration" is pretty long, wouldn't "microsite" be enough?