From ea3506d2b983e59afbef723bc98780907fd6cdc1 Mon Sep 17 00:00:00 2001 From: Nate Hardison Date: Fri, 17 May 2013 14:39:59 -0700 Subject: [PATCH] Add general theming capabilities This commit adds the requisite settings and startup features to enable integration of themes into the edX platform. It does not yet provide hooks in any of the templates, but it does cause the main `lms/static/sass/application.scss` file to `@import` a theme's base Sass. Template hooks will come down the road. CHANGELOG --------- Define a new `MITX_FEATURE`, `USE_CUSTOM_THEME`, that when enabled, can be used in templates to determine whether or not custom theme templates should be used instead of the defaults. Also define a new setting, `THEME_NAME`, which will be used to locate theme-specific files. Establish the convention that themes will be stored outside of the `REPO_ROOT`, inside the `ENV_ROOT`, in a directory named `themes/`. `themes/` will store the files for a particular theme. Provide a function, `enable_theme`, that modifies the template and static asset load paths appropriately to include the theme's files. Move the main LMS Sass file to a Mako template that conditionally `@import`s the theme's base Sass file when a theme is enabled. Add logic to the assets Rakefile to properly preprocess any Sass/ Mako templates before compiling them. --- .gitignore | 1 + lms/envs/aws.py | 5 ++ lms/envs/common.py | 35 +++++++++++++- ...application.scss => application.scss.mako} | 13 +++++ rakefile | 8 ++++ rakefiles/assets.rake | 47 ++++++++++++++++++- 6 files changed, 105 insertions(+), 4 deletions(-) rename lms/static/sass/{application.scss => application.scss.mako} (63%) diff --git a/.gitignore b/.gitignore index 9c82bb8ea9..05e76c4caa 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ Gemfile.lock conf/locale/en/LC_MESSAGES/*.po !messages.po lms/static/sass/*.css +lms/static/sass/application.scss cms/static/sass/*.css lms/lib/comment_client/python nosetests.xml diff --git a/lms/envs/aws.py b/lms/envs/aws.py index df80d5a924..a3d5cb653f 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -114,6 +114,11 @@ DEFAULT_FEEDBACK_EMAIL = ENV_TOKENS.get('DEFAULT_FEEDBACK_EMAIL', DEFAULT_FEEDBA ADMINS = ENV_TOKENS.get('ADMINS', ADMINS) SERVER_EMAIL = ENV_TOKENS.get('SERVER_EMAIL', SERVER_EMAIL) +#Theme overrides +THEME_NAME = ENV_TOKENS.get('THEME_NAME', None) +if not THEME_NAME is None: + enable_theme(THEME_NAME) + #Timezone overrides TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE) diff --git a/lms/envs/common.py b/lms/envs/common.py index 3c133a1e6b..64012d1f53 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -110,6 +110,9 @@ MITX_FEATURES = { # Enable URL that shows information about the status of variuous services 'ENABLE_SERVICE_STATUS': False, + + # Toggle to indicate use of a custom theme + 'USE_CUSTOM_THEME': False } # Used for A/B testing @@ -167,12 +170,12 @@ MAKO_TEMPLATES['main'] = [PROJECT_ROOT / 'templates', # This is where Django Template lookup is defined. There are a few of these # still left lying around. -TEMPLATE_DIRS = ( +TEMPLATE_DIRS = [ PROJECT_ROOT / "templates", COMMON_ROOT / 'templates', COMMON_ROOT / 'lib' / 'capa' / 'capa' / 'templates', COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates', -) +] TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.request', @@ -714,3 +717,31 @@ MKTG_URL_LINK_MAP = { 'HONOR': 'honor', 'PRIVACY': 'privacy_edx', } + +############################### THEME ################################ +def enable_theme(theme_name): + """ + Enable the settings for a custom theme, whose files should be stored + in ENV_ROOT/themes/THEME_NAME (e.g., edx_all/themes/stanford). + + The THEME_NAME setting should be configured separately since it can't + be set here (this function closes too early). An idiom for doing this + is: + + THEME_NAME = "stanford" + enable_theme(THEME_NAME) + """ + MITX_FEATURES['USE_CUSTOM_THEME'] = True + + # Calculate the location of the theme's files + theme_root = ENV_ROOT / "themes" / theme_name + + # Include the theme's templates in the template search paths + TEMPLATE_DIRS.append(theme_root / 'templates') + MAKO_TEMPLATES['main'].append(theme_root / 'templates') + + # Namespace the theme's static files to 'themes/' to + # avoid collisions with default edX static files + STATICFILES_DIRS.append((u'themes/%s' % theme_name, + theme_root / 'static')) + diff --git a/lms/static/sass/application.scss b/lms/static/sass/application.scss.mako similarity index 63% rename from lms/static/sass/application.scss rename to lms/static/sass/application.scss.mako index 6a1ef8743e..434475c6ac 100644 --- a/lms/static/sass/application.scss +++ b/lms/static/sass/application.scss.mako @@ -36,3 +36,16 @@ @import 'news'; @import 'shame'; + +## THEMING +## ------- +## Set up this file to import an edX theme library if the environment +## indicates that a theme should be used. The assumption is that the +## theme resides outside of this main edX repository, in a directory +## called themes//, with its base Sass file in +## themes//static/sass/_.scss. That one entry +## point can be used to @import in as many other things as needed. +% if not env['THEME_NAME'] is None: + // import theme's Sass overrides + @import '${env['THEME_NAME']}' +% endif diff --git a/rakefile b/rakefile index cef93e67eb..20101a14db 100644 --- a/rakefile +++ b/rakefile @@ -1,3 +1,4 @@ +require 'json' require 'rake/clean' require './rakefiles/helpers.rb' @@ -7,6 +8,13 @@ end # Build Constants REPO_ROOT = File.dirname(__FILE__) +ENV_ROOT = File.dirname(REPO_ROOT) REPORT_DIR = File.join(REPO_ROOT, "reports") +# Environment constants +SERVICE_VARIANT = ENV['SERVICE_VARIANT'] +CONFIG_PREFIX = SERVICE_VARIANT ? SERVICE_VARIANT + "." : "" +ENV_FILE = File.join(ENV_ROOT, CONFIG_PREFIX + "env.json") +ENV_TOKENS = File.exists?(ENV_FILE) ? JSON.parse(File.read(ENV_FILE)) : {} + task :default => [:test, :pep8, :pylint] diff --git a/rakefiles/assets.rake b/rakefiles/assets.rake index 0369968fdf..ef77d4fea7 100644 --- a/rakefiles/assets.rake +++ b/rakefiles/assets.rake @@ -1,3 +1,36 @@ +# Theming constants +THEME_NAME = ENV_TOKENS['THEME_NAME'] +USE_CUSTOM_THEME = !(THEME_NAME.nil? || THEME_NAME.empty?) +if USE_CUSTOM_THEME + THEME_ROOT = File.join(ENV_ROOT, "themes", THEME_NAME) + THEME_SASS = File.join(THEME_ROOT, "static", "sass") +end + +# Run the specified file through the Mako templating engine, providing +# the ENV_TOKENS to the templating context. +def preprocess_with_mako(filename) + # simple command-line invocation of Mako engine + mako = "from mako.template import Template;" + + "print Template(filename=\"#{filename}\")" + + # Total hack. It works because a Python dict literal has + # the same format as a JSON object. + ".render(env=#{ENV_TOKENS.to_json});" + + # strip off the .mako extension + output_filename = filename.chomp(File.extname(filename)) + + # just pipe from stdout into the new file + File.open(output_filename, 'w') do |file| + file.write(`python -c '#{mako}'`) + end +end + +# Preprocess all static assets that have the .mako extension by +# running them through the Mako templating engine. Right now we +# just hardcode the asset filenames. +def preprocess_assets + preprocess_with_mako("lms/static/sass/application.scss.mako") +end def xmodule_cmd(watch=false, debug=false) xmodule_cmd = 'xmodule_assets common/static/xmodule' @@ -32,10 +65,20 @@ def coffee_cmd(watch=false, debug=false) end def sass_cmd(watch=false, debug=false) + # Make sure to preprocess templatized Sass first + preprocess_assets() + + sass_load_paths = ["./common/static/sass"] + sass_watch_paths = ["*/static"] + if USE_CUSTOM_THEME + sass_load_paths << THEME_SASS + sass_watch_paths << THEME_SASS + end + "sass #{debug ? '--debug-info' : '--style compressed'} " + - "--load-path ./common/static/sass " + + "--load-path #{sass_load_paths.join(' ')} " + "--require ./common/static/sass/bourbon/lib/bourbon.rb " + - "#{watch ? '--watch' : '--update'} */static" + "#{watch ? '--watch' : '--update'} #{sass_watch_paths.join(' ')}" end desc "Compile all assets"