Commit 6ebf2515 by David Baumgold

Comprehensive theming

This is a squash of 38 commits ending with
5b080f979d692804452400ac5bed9b17c50b001e
parent 034570b3
......@@ -76,13 +76,15 @@ lms/static/certificates/css/
cms/static/css/
### Styling generated from templates
lms/static/sass/lms-main.scss
lms/static/sass/lms-main-rtl.scss
lms/static/sass/lms-course.scss
lms/static/sass/lms-course-rtl.scss
lms/static/sass/lms-footer.scss
lms/static/sass/lms-footer-rtl.scss
lms/static/sass/*.css
lms/static/sass/*.css.map
lms/static/certificates/sass/*.css
lms/static/themed_sass/
cms/static/css/
cms/static/sass/*.css
cms/static/sass/*.css.map
cms/static/themed_sass/
themes/**/css/*.css
### Logging artifacts
log/
......
......@@ -36,7 +36,7 @@ import lms.envs.common
# Although this module itself may not use these imported variables, other dependent modules may.
from lms.envs.common import (
USE_TZ, TECH_SUPPORT_EMAIL, PLATFORM_NAME, BUGS_EMAIL, DOC_STORE_CONFIG, DATA_DIR, ALL_LANGUAGES, WIKI_ENABLED,
update_module_store_settings, ASSET_IGNORE_REGEX, COPYRIGHT_YEAR, PARENTAL_CONSENT_AGE_LIMIT,
update_module_store_settings, ASSET_IGNORE_REGEX, COPYRIGHT_YEAR, PARENTAL_CONSENT_AGE_LIMIT, COMP_THEME_DIR,
# The following PROFILE_IMAGE_* settings are included as they are
# indirectly accessed through the email opt-in API, which is
# technically accessible through the CMS via legacy URLs.
......@@ -754,6 +754,9 @@ INSTALLED_APPS = (
'static_replace',
'require',
# Theming
'openedx.core.djangoapps.theming',
# comment common
'django_comment_common',
......
......@@ -11,6 +11,7 @@
// * +Timing
// * +Archetype UI
// * +Specific UI
// * +Paths
// * +Deprecated
$baseline: 20px;
......@@ -213,6 +214,10 @@ $ui-link-color-focus: $blue-s1;
$ui-notification-height: ($baseline*10);
$ui-update-color: $blue-l4;
// +Paths
// ====================
$static-path: '..' !default;
// +Deprecated
// ====================
// do not use, future clean up will use updated styles
......
......@@ -3,9 +3,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Light-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Light-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
......@@ -13,9 +13,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
......@@ -23,9 +23,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Regular-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Regular-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
......@@ -33,9 +33,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Italic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Italic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Italic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
......@@ -43,9 +43,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Semibold-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Semibold-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Semibold-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
......@@ -53,9 +53,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
......@@ -63,9 +63,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Bold-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Bold-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Bold-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
......@@ -73,9 +73,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
......@@ -7,7 +7,7 @@
width: 7px;
height: 22px;
margin-left: ($baseline/2);
background: url(../images/drag-handles.png) no-repeat;
background: url($static-path + '/images/drag-handles.png') no-repeat;
cursor: move;
}
......@@ -15,33 +15,33 @@
display: inline-block;
width: ($baseline*3);
height: ($baseline*3);
background: url(../images/large-advanced-icon.png) center no-repeat;
background: url($static-path + '/images/large-advanced-icon.png') center no-repeat;
}
.large-discussion-icon {
display: inline-block;
width: ($baseline*3);
height: ($baseline*3);
background: url(../images/large-discussion-icon.png) center no-repeat;
background: url($static-path + '/images/large-discussion-icon.png') center no-repeat;
}
.large-html-icon {
display: inline-block;
width: ($baseline*3);
height: ($baseline*3);
background: url(../images/large-html-icon.png) center no-repeat;
background: url($static-path + '/images/large-html-icon.png') center no-repeat;
}
.large-problem-icon {
display: inline-block;
width: ($baseline*3);
height: ($baseline*3);
background: url(../images/large-problem-icon.png) center no-repeat;
background: url($static-path + '/images/large-problem-icon.png') center no-repeat;
}
.large-video-icon {
display: inline-block;
width: ($baseline*3);
height: ($baseline*3);
background: url(../images/large-video-icon.png) center no-repeat;
background: url($static-path + '/images/large-video-icon.png') center no-repeat;
}
......@@ -295,12 +295,12 @@
// CASE: right to left layout
@include rtl {
background: transparent url("../images/drag-handles.png") no-repeat left center;
background: transparent url($static-path + "/images/drag-handles.png") no-repeat left center;
}
// CASE: left to right layout
@include ltr {
background: transparent url("../images/drag-handles.png") no-repeat right center;
background: transparent url($static-path + "/images/drag-handles.png") no-repeat right center;
}
}
......
@font-face {
font-family: 'CreativeCommons';
src: url('../fonts/CreativeCommons/cc.eot');
src: url('../fonts/CreativeCommons/cc.eot#iefix') format('embedded-opentype'),
url('../fonts/CreativeCommons/cc.woff') format('woff'),
url('../fonts/CreativeCommons/cc.ttf') format('truetype'),
url('../fonts/CreativeCommons/cc.svg#CreativeCommons') format('svg');
src: url($static-path + '/fonts/CreativeCommons/cc.eot');
src: url($static-path + '/fonts/CreativeCommons/cc.eot#iefix') format('embedded-opentype'),
url($static-path + '/fonts/CreativeCommons/cc.woff') format('woff'),
url($static-path + '/fonts/CreativeCommons/cc.ttf') format('truetype'),
url($static-path + '/fonts/CreativeCommons/cc.svg#CreativeCommons') format('svg');
font-weight: normal;
font-style: normal;
}
......
......@@ -456,7 +456,7 @@ input.search {
@include box-sizing(border-box);
border: 1px solid $darkGrey;
border-radius: 20px;
background: url(../images/search-icon.png) no-repeat 8px 7px #edf1f5;
background: url($static-path + '/images/search-icon.png') no-repeat 8px 7px #edf1f5;
font-family: 'Open Sans', sans-serif;
color: $baseFontColor;
outline: 0;
......
......@@ -100,7 +100,7 @@
}
&.is-locked {
background-image: url('../images/bg-micro-stripes.png');
background-image: url($static-path + '/images/bg-micro-stripes.png');
background-position: 0 0;
background-repeat: repeat;
}
......
......@@ -299,7 +299,7 @@
}
&.is-locked {
background-image: url('../images/bg-micro-stripes.png');
background-image: url($static-path + '/images/bg-micro-stripes.png');
background-position: 0 0;
background-repeat: repeat;
}
......
......@@ -197,7 +197,7 @@
}
.drag-handle {
background: url(../images/drag-handles.png) center no-repeat #fff;
background: url($static-path + '/images/drag-handles.png') center no-repeat #fff;
}
}
......@@ -210,10 +210,10 @@
width: 35px;
height: 100%;
border: none;
background: url(../images/drag-handles.png) center no-repeat #fff;
background: url($static-path + '/images/drag-handles.png') center no-repeat #fff;
&:hover {
background: url(../images/drag-handles.png) center no-repeat #fff;
background: url($static-path + '/images/drag-handles.png') center no-repeat #fff;
}
&.is-fixed {
......
......@@ -12,11 +12,7 @@
<div class="wrapper wrapper-l">
<h1 class="branding"><a href="/">
% if settings.FEATURES.get('IS_EDX_DOMAIN', False):
<img src="${static.url("images/edx-theme/edx-studio-logo.png")}" alt="${settings.STUDIO_NAME}" />
% else:
<img src="${static.url("images/default-theme/logo.png")}" alt="${settings.STUDIO_NAME}" />
% endif
<img src="${static.url("images/header-logo.png")}" alt="${settings.STUDIO_NAME}" />
</a></h1>
% if context_course:
......
......@@ -13,4 +13,4 @@
# limitations under the License.
LOOKUP = {}
from .paths import add_lookup, lookup_template, clear_lookups
from .paths import add_lookup, lookup_template, clear_lookups, save_lookups
......@@ -8,67 +8,53 @@ For this to work, assets need to be named with the appropriate
template extension (e.g., .mako for Mako templates). Currently Mako
is the only template engine supported.
"""
import os
import textwrap
from django.core.management.base import NoArgsCommand
from django.core.management.base import BaseCommand
from django.conf import settings
from mako.template import Template
import textwrap
class Command(NoArgsCommand):
class Command(BaseCommand):
"""
Basic management command to preprocess asset template files.
"""
help = "Preprocess asset template files to ready them for compilation."
def handle_noargs(self, **options):
"""
Walk over all of the static files directories specified in the
settings file, looking for asset template files (indicated by
a file extension like .mako).
"""
for staticfiles_dir in getattr(settings, "STATICFILES_DIRS", []):
# Cribbed from the django-staticfiles app at:
# https://github.com/jezdez/django-staticfiles/blob/develop/staticfiles/finders.py#L52
if isinstance(staticfiles_dir, (list, tuple)):
prefix, staticfiles_dir = staticfiles_dir
# Walk over the current static files directory tree,
# preprocessing files that have a template extension.
for root, dirs, files in os.walk(staticfiles_dir):
for filename in files:
outfile, extension = os.path.splitext(filename)
# We currently only handle Mako templates
if extension == ".mako":
self.__preprocess(os.path.join(root, filename),
os.path.join(root, outfile))
def __context(self):
"""
Return a dict that contains all of the available context
variables to the asset template.
"""
# TODO: do we need to include anything else?
# TODO: do this with the django-settings-context-processor
return {
"FEATURES": settings.FEATURES,
"THEME_NAME": getattr(settings, "THEME_NAME", None),
}
def __preprocess(self, infile, outfile):
"""
Run `infile` through the Mako template engine, storing the
result in `outfile`.
"""
with open(outfile, "w") as _outfile:
_outfile.write(textwrap.dedent("""\
/*
* This file is dynamically generated and ignored by Git.
* DO NOT MAKE CHANGES HERE. Instead, go edit its template:
* %s
*/
""" % infile))
_outfile.write(Template(filename=str(infile)).render(env=self.__context()))
def handle(self, *args, **options):
theme_name = getattr(settings, "THEME_NAME", None)
use_custom_theme = settings.FEATURES.get("USE_CUSTOM_THEME", False)
if not use_custom_theme or not theme_name:
# No custom theme, nothing to do!
return
dest_dir = args[-1]
for source_file in args[:-1]:
self.process_one_file(source_file, dest_dir, theme_name)
def process_one_file(self, source_file, dest_dir, theme_name):
"""Pre-process a .scss file to replace our markers with real code."""
with open(source_file) as fsource:
original_content = content = fsource.read()
content = content.replace(
"//<THEME-OVERRIDE>",
"@import '{}';".format(theme_name),
)
if content != original_content:
if not os.path.exists(dest_dir):
os.makedirs(dest_dir)
dest_file = os.path.join(dest_dir, os.path.basename(source_file))
with open(dest_file, "w") as fout:
fout.write(textwrap.dedent("""\
/*
* This file is dynamically generated and ignored by Git.
* DO NOT MAKE CHANGES HERE. Instead, go edit its source:
* {}
*/
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
""".format(source_file)))
fout.write(content)
......@@ -3,6 +3,7 @@ Set up lookup paths for mako templates.
"""
import hashlib
import contextlib
import os
import pkg_resources
......@@ -21,6 +22,9 @@ class DynamicTemplateLookup(TemplateLookup):
super(DynamicTemplateLookup, self).__init__(*args, **kwargs)
self.__original_module_directory = self.template_args['module_directory']
def __repr__(self):
return "<{0.__class__.__name__} {0.directories}>".format(self)
def add_directory(self, directory, prepend=False):
"""
Add a new directory to the template lookup path.
......@@ -78,3 +82,26 @@ def lookup_template(namespace, name):
Look up a Mako template by namespace and name.
"""
return LOOKUP[namespace].get_template(name)
@contextlib.contextmanager
def save_lookups():
"""
A context manager to save and restore the Mako template lookup path.
Useful for testing.
"""
# Make a copy of the list of directories for each namespace.
namespace_dirs = {namespace: list(look.directories) for namespace, look in LOOKUP.items()}
try:
yield
finally:
# Get rid of all the lookups.
LOOKUP.clear()
# Re-create the lookups from our saved list.
for namespace, directories in namespace_dirs.items():
for directory in directories:
add_lookup(namespace, directory)
......@@ -2,6 +2,9 @@
from staticfiles.storage import staticfiles_storage
from pipeline_mako import compressed_css, compressed_js
from django.utils.translation import get_language_bidi
from mako.exceptions import TemplateLookupException
from microsite_configuration import microsite
%>
<%def name='url(file, raw=False)'><%
......@@ -73,3 +76,15 @@ source, template_path = _loader.load_template_source(path)
}).call(this, require || RequireJS.require);
</script>
</%def>
<%def name="optional_include_mako(file, with_microsite=False)"><%
# http://stackoverflow.com/q/21219531
if with_microsite:
file = microsite.get_template_path(file)
try:
tmpl = self.get_template(file)
except TemplateLookupException:
pass
else:
content.write(tmpl.render_context(context))
%></%def>
......@@ -321,7 +321,7 @@ div.problem {
display: inline-block;
width: 14px;
height: 14px;
background: url('../images/unanswered-icon.png') center center no-repeat;
background: url($static-path + '/images/unanswered-icon.png') center center no-repeat;
}
}
......@@ -330,7 +330,7 @@ div.problem {
display: inline-block;
width: 25px;
height: 20px;
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
input {
......@@ -343,7 +343,7 @@ div.problem {
display: inline-block;
width: 20px;
height: 20px;
background: url('../images/spinner.gif') center center no-repeat;
background: url($static-path + '/images/spinner.gif') center center no-repeat;
}
input {
......@@ -356,7 +356,7 @@ div.problem {
display: inline-block;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
input {
......@@ -370,7 +370,7 @@ div.problem {
display: inline-block;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
input {
......@@ -448,7 +448,7 @@ div.problem {
top: 4px;
width: 14px;
height: 14px;
background: url('../images/unanswered-icon.png') center center no-repeat;
background: url($static-path + '/images/unanswered-icon.png') center center no-repeat;
}
&.processing, &.ui-icon-processing {
......@@ -457,7 +457,7 @@ div.problem {
top: 6px;
width: 25px;
height: 20px;
background: url('../images/spinner.gif') center center no-repeat;
background: url($static-path + '/images/spinner.gif') center center no-repeat;
}
&.ui-icon-check {
......@@ -466,7 +466,7 @@ div.problem {
top: 3px;
width: 25px;
height: 20px;
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
&.incomplete, &.ui-icon-close {
......@@ -475,7 +475,7 @@ div.problem {
top: 3px;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
}
......@@ -503,7 +503,7 @@ div.problem {
.grading {
margin: 0px 7px 0 0;
padding-left: 25px;
background: url('../images/info-icon.png') left center no-repeat;
background: url($static-path + '/images/info-icon.png') left center no-repeat;
text-indent: 0px;
}
......@@ -1097,7 +1097,7 @@ div.problem {
.result-errors {
margin: ($baseline/4);
padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline*2);
background: url('../images/incorrect-icon.png') center left no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center left no-repeat;
li {
color: #b00;
......@@ -1129,7 +1129,7 @@ div.problem {
}
.result-correct {
background: url('../images/correct-icon.png') left 20px no-repeat;
background: url($static-path + '/images/correct-icon.png') left 20px no-repeat;
.result-actual-output {
color: #090;
......@@ -1137,7 +1137,7 @@ div.problem {
}
.result-incorrect {
background: url('../images/incorrect-icon.png') left 20px no-repeat;
background: url($static-path + '/images/incorrect-icon.png') left 20px no-repeat;
.result-actual-output {
color: #B00;
......@@ -1348,7 +1348,7 @@ div.problem {
label.choicetextgroup_show_correct, section.choicetextgroup_show_correct {
&:after {
margin-left:15px;
content: url('../images/correct-icon.png');
content: url($static-path + '/images/correct-icon.png');
}
}
......@@ -1373,11 +1373,11 @@ div.problem .imageinput.capa_inputtype {
}
.correct {
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
.incorrect {
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
.partially-correct {
......
......@@ -178,7 +178,7 @@ section.combined-open-ended-status {
float: right;
width: 14px;
height: 14px;
background: url('../images/unanswered-icon.png') center center no-repeat;
background: url($static-path + '/images/unanswered-icon.png') center center no-repeat;
}
&.correct {
......@@ -187,7 +187,7 @@ section.combined-open-ended-status {
float: right;
width: 25px;
height: 20px;
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
&.incorrect {
......@@ -196,7 +196,7 @@ section.combined-open-ended-status {
float: right;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
}
......@@ -302,21 +302,21 @@ div.combined-rubric-container {
label.choicegroup_correct {
&:before {
margin-right: ($baseline*0.75);
content: url('../images/correct-icon.png');
content: url($static-path + '/images/correct-icon.png');
}
}
label.choicegroup_partialcorrect {
&:before {
margin-right: ($baseline*0.75);
content: url('../images/partially-correct-icon.png');
content: url($static-path + '/images/partially-correct-icon.png');
}
}
label.choicegroup_incorrect {
&:before {
margin-right: ($baseline*0.75);
content: url('../images/incorrect-icon.png');
content: url($static-path + '/images/incorrect-icon.png');
}
}
......@@ -402,7 +402,7 @@ div.result-container {
.result-errors {
margin: ($baseline/4);
padding: ($baseline/2) ($baseline/2) ($baseline/2) ($baseline*2);
background: url('../images/incorrect-icon.png') center left no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center left no-repeat;
li {
color: #B00;
......@@ -564,7 +564,7 @@ section.open-ended-child {
display: inline-block;
width: 14px;
height: 14px;
background: url('../images/unanswered-icon.png') center center no-repeat;
background: url($static-path + '/images/unanswered-icon.png') center center no-repeat;
}
}
......@@ -573,7 +573,7 @@ section.open-ended-child {
display: inline-block;
width: 25px;
height: 20px;
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
input {
......@@ -586,7 +586,7 @@ section.open-ended-child {
display: inline-block;
width: 20px;
height: 20px;
background: url('../images/spinner.gif') center center no-repeat;
background: url($static-path + '/images/spinner.gif') center center no-repeat;
}
input {
......@@ -599,7 +599,7 @@ section.open-ended-child {
display: inline-block;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
text-indent: -9999px;
}
......@@ -638,7 +638,7 @@ section.open-ended-child {
top: 4px;
width: 14px;
height: 14px;
background: url('../images/unanswered-icon.png') center center no-repeat;
background: url($static-path + '/images/unanswered-icon.png') center center no-repeat;
}
&.processing, &.ui-icon-processing {
......@@ -647,7 +647,7 @@ section.open-ended-child {
top: 6px;
width: 25px;
height: 20px;
background: url('../images/spinner.gif') center center no-repeat;
background: url($static-path + '/images/spinner.gif') center center no-repeat;
}
&.correct, &.ui-icon-check {
......@@ -656,7 +656,7 @@ section.open-ended-child {
top: 6px;
width: 25px;
height: 20px;
background: url('../images/correct-icon.png') center center no-repeat;
background: url($static-path + '/images/correct-icon.png') center center no-repeat;
}
&.incorrect, &.ui-icon-close {
......@@ -665,7 +665,7 @@ section.open-ended-child {
top: 6px;
width: 20px;
height: 20px;
background: url('../images/incorrect-icon.png') center center no-repeat;
background: url($static-path + '/images/incorrect-icon.png') center center no-repeat;
}
}
......@@ -703,7 +703,7 @@ section.open-ended-child {
.grading {
margin: 0 7px 0 0;
padding-left: 25px;
background: url('../images/info-icon.png') left center no-repeat;
background: url($static-path + '/images/info-icon.png') left center no-repeat;
text-indent: 0;
}
......
......@@ -110,7 +110,7 @@
width: 26px;
height: 21px;
vertical-align: middle;
background: url(../images/problem-editor-icons.png) no-repeat;
background: url($static-path + '/images/problem-editor-icons.png') no-repeat;
}
.problem-editor-icon.heading1 {
......
......@@ -280,7 +280,7 @@ div.video {
a.ui-slider-handle {
@extend %ui-fake-link;
@include transform(scale(.7, 1.3) translate3d(-80%, -15%, 0));
background: $pink url(../images/slider-handle.png) center center no-repeat;
background: $pink url($static-path + '/images/slider-handle.png') center center no-repeat;
background-size: 50%;
border: 1px solid darken($pink, 20%);
border-radius: 50%;
......@@ -314,7 +314,7 @@ div.video {
.video_control {
@extend %video-button;
float: left;
background-image: url('../images/vcr.png');
background-image: url($static-path + '/images/vcr.png');
background-position: 15px 15px ;
background-repeat: no-repeat;
border-left: none;
......@@ -445,7 +445,7 @@ div.video {
div.speeds {
&.is-opened {
.speed-button {
background-image: url('../images/open-arrow.png');
background-image: url($static-path + '/images/open-arrow.png');
}
}
......@@ -460,7 +460,7 @@ div.video {
.speed-button {
@extend %video-button;
@include clearfix();
background-image: url('../images/closed-arrow.png');
background-image: url($static-path + '/images/closed-arrow.png');
background-position: 10px center;
background-repeat: no-repeat;
min-width: 116px;
......@@ -515,14 +515,14 @@ div.video {
&.is-muted {
& > a {
background-image: url('../images/mute.png');
background-image: url($static-path + '/images/mute.png');
}
}
& > a {
@extend %video-button;
@include clearfix();
background-image: url('../images/volume.png');
background-image: url($static-path + '/images/volume.png');
background-position: 10px center;
background-repeat: no-repeat;
width: 30px;
......@@ -559,7 +559,7 @@ div.video {
a.ui-slider-handle {
@extend %ui-fake-link;
@include transition(height $tmg-s2 ease-in-out 0s, width $tmg-s2 ease-in-out 0s);
background: $pink url(../images/slider-handle.png) center center no-repeat;
background: $pink url($static-path + '/images/slider-handle.png') center center no-repeat;
background-size: 50%;
border: 1px solid darken($pink, 20%);
border-radius: 15px;
......@@ -578,7 +578,7 @@ div.video {
a.add-fullscreen {
@extend %video-button;
background: url(../images/fullscreen.png) center no-repeat;
background: url($static-path + '/images/fullscreen.png') center no-repeat;
border-left: none;
float: left;
padding: 0 11px;
......@@ -588,7 +588,7 @@ div.video {
a.quality-control {
@extend %video-button;
background: url(../images/hd.png) center no-repeat;
background: url($static-path + '/images/hd.png') center no-repeat;
border-left: none;
float: left;
padding: 0 11px;
......@@ -610,7 +610,7 @@ div.video {
@extend %video-button;
@include transition(none);
box-shadow: inset 1px 0 0 #555;
background: url('../images/cc.png') center no-repeat;
background: url($static-path + '/images/cc.png') center no-repeat;
border-left: none;
border-right: none;
padding: 0 11px;
......
......@@ -175,7 +175,7 @@ class LearnerProfilePage(FieldsMixin, PageObject):
"""
self.wait_for_field('image')
default_links = self.q(css='.image-frame').attrs('src')
return 'default-profile' in default_links[0] if default_links else False
return 'profiles/default' in default_links[0] if default_links else False
def mouse_hover(self, element):
"""
......
......@@ -28,3 +28,5 @@ input_encoding = utf-8
input_encoding = utf-8
[mako: themes/**.html]
input_encoding = utf-8
[mako: themes/**.html]
input_encoding = utf-8
......@@ -70,4 +70,4 @@ def get_logo_url():
elif university:
return staticfiles_storage.url('images/{uni}-on-edx-logo.png'.format(uni=university))
else:
return staticfiles_storage.url('images/default-theme/logo.png')
return staticfiles_storage.url('images/logo.png')
"""Tests of comprehensive theming."""
from django.conf import settings
from django.test import TestCase
from path import path # pylint: disable=no-name-in-module
import staticfiles
from openedx.core.djangoapps.theming.test_util import with_comp_theme
from openedx.core.lib.tempdir import mkdtemp_clean
class TestComprehensiveTheming(TestCase):
"""Test comprehensive theming."""
def setUp(self):
super(TestComprehensiveTheming, self).setUp()
# Clear the internal staticfiles caches, to get test isolation.
staticfiles.finders._finders.clear() # pylint: disable=protected-access
@with_comp_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_red_footer(self):
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
# This string comes from footer.html
self.assertContains(resp, "super-ugly")
# This string comes from header.html
self.assertContains(resp, "This file is only for demonstration, and is horrendous!")
def test_theme_outside_repo(self):
# Need to create a temporary theme, and defer decorating the function
# until it is done, which leads to this strange nested-function style
# of test.
# Make a temp directory as a theme.
tmp_theme = path(mkdtemp_clean())
template_dir = tmp_theme / "lms/templates"
template_dir.makedirs()
with open(template_dir / "footer.html", "w") as footer:
footer.write("<footer>TEMPORARY THEME</footer>")
@with_comp_theme(tmp_theme)
def do_the_test(self):
"""A function to do the work so we can use the decorator."""
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, "TEMPORARY THEME")
do_the_test(self)
def test_theme_adjusts_staticfiles_search_path(self):
# Test that a theme adds itself to the staticfiles search path.
before_finders = list(settings.STATICFILES_FINDERS)
before_dirs = list(settings.STATICFILES_DIRS)
@with_comp_theme(settings.REPO_ROOT / 'themes/red-theme')
def do_the_test(self):
"""A function to do the work so we can use the decorator."""
self.assertEqual(list(settings.STATICFILES_FINDERS), before_finders)
self.assertEqual(settings.STATICFILES_DIRS[0], settings.REPO_ROOT / 'themes/red-theme/lms/static')
self.assertEqual(settings.STATICFILES_DIRS[1:], before_dirs)
do_the_test(self)
def test_default_logo_image(self):
result = staticfiles.finders.find('images/logo.png')
self.assertEqual(result, settings.REPO_ROOT / 'lms/static/images/logo.png')
@with_comp_theme(settings.REPO_ROOT / 'themes/red-theme')
def test_overridden_logo_image(self):
result = staticfiles.finders.find('images/logo.png')
self.assertEqual(result, settings.REPO_ROOT / 'themes/red-theme/lms/static/images/logo.png')
......@@ -3,13 +3,14 @@ Tests related to the basic footer-switching based off SITE_NAME to ensure
edx.org uses an edx footer but other instances use an Open edX footer.
"""
from mock import patch
from nose.plugins.attrib import attr
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from openedx.core.djangoapps.theming.test_util import with_is_edx_domain
@attr('shard_1')
class TestFooter(TestCase):
......@@ -36,26 +37,26 @@ class TestFooter(TestCase):
"youtube": "https://www.youtube.com/"
}
@with_is_edx_domain(True)
def test_edx_footer(self):
"""
Verify that the homepage, when accessed at edx.org, has the edX footer
"""
with patch.dict('django.conf.settings.FEATURES', {"IS_EDX_DOMAIN": True}):
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-edx-v3')
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-edx-v3')
@with_is_edx_domain(False)
def test_openedx_footer(self):
"""
Verify that the homepage, when accessed at something other than
edx.org, has the Open edX footer
"""
with patch.dict('django.conf.settings.FEATURES', {"IS_EDX_DOMAIN": False}):
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-openedx')
resp = self.client.get('/')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'footer-openedx')
@patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': True})
@with_is_edx_domain(True)
@override_settings(
SOCIAL_MEDIA_FOOTER_NAMES=SOCIAL_MEDIA_NAMES,
SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS
......
......@@ -230,6 +230,7 @@ BULK_EMAIL_ROUTING_KEY_SMALL_JOBS = LOW_PRIORITY_QUEUE
# Theme overrides
THEME_NAME = ENV_TOKENS.get('THEME_NAME', None)
COMP_THEME_DIR = path(ENV_TOKENS.get('COMP_THEME_DIR', COMP_THEME_DIR))
# Marketing link overrides
MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {}))
......@@ -679,10 +680,7 @@ PROFILE_IMAGE_BACKEND = ENV_TOKENS.get('PROFILE_IMAGE_BACKEND', PROFILE_IMAGE_BA
PROFILE_IMAGE_SECRET_KEY = AUTH_TOKENS.get('PROFILE_IMAGE_SECRET_KEY', PROFILE_IMAGE_SECRET_KEY)
PROFILE_IMAGE_MAX_BYTES = ENV_TOKENS.get('PROFILE_IMAGE_MAX_BYTES', PROFILE_IMAGE_MAX_BYTES)
PROFILE_IMAGE_MIN_BYTES = ENV_TOKENS.get('PROFILE_IMAGE_MIN_BYTES', PROFILE_IMAGE_MIN_BYTES)
if FEATURES['IS_EDX_DOMAIN']:
PROFILE_IMAGE_DEFAULT_FILENAME = 'images/edx-theme/default-profile'
else:
PROFILE_IMAGE_DEFAULT_FILENAME = ENV_TOKENS.get('PROFILE_IMAGE_DEFAULT_FILENAME', PROFILE_IMAGE_DEFAULT_FILENAME)
PROFILE_IMAGE_DEFAULT_FILENAME = 'images/profiles/default'
# EdxNotes config
......
......@@ -195,7 +195,7 @@ FEATURES = {
# Enable URL that shows information about the status of variuous services
'ENABLE_SERVICE_STATUS': False,
# Toggle to indicate use of a custom theme
# Toggle to indicate use of the Stanford theming system
'USE_CUSTOM_THEME': False,
# Don't autoplay videos for students
......@@ -451,6 +451,9 @@ COURSES_ROOT = ENV_ROOT / "data"
DATA_DIR = COURSES_ROOT
# comprehensive theming system
COMP_THEME_DIR = None
# TODO: Remove the rest of the sys.path modification here and in cms/envs/common.py
sys.path.append(REPO_ROOT)
sys.path.append(PROJECT_ROOT / 'djangoapps')
......@@ -1832,6 +1835,9 @@ INSTALLED_APPS = (
'staticfiles',
'static_replace',
# Theming
'openedx.core.djangoapps.theming',
# Our courseware
'circuit',
'courseware',
......@@ -2588,7 +2594,7 @@ PROFILE_IMAGE_BACKEND = {
'base_url': os.path.join(MEDIA_URL, 'profile-images/'),
},
}
PROFILE_IMAGE_DEFAULT_FILENAME = 'images/default-theme/default-profile'
PROFILE_IMAGE_DEFAULT_FILENAME = 'images/profiles/default'
PROFILE_IMAGE_DEFAULT_FILE_EXTENSION = 'png'
# This secret key is used in generating unguessable URLs to users'
# profile images. Once it has been set, changing it will make the
......
......@@ -34,7 +34,7 @@ def run():
add_mimetypes()
if settings.FEATURES.get('USE_CUSTOM_THEME', False):
enable_theme()
enable_stanford_theme()
if settings.FEATURES.get('USE_MICROSITES', False):
enable_microsites()
......@@ -69,7 +69,7 @@ def add_mimetypes():
mimetypes.add_type('application/font-woff', '.woff')
def enable_theme():
def enable_stanford_theme():
"""
Enable the settings for a custom theme, whose files should be stored
in ENV_ROOT/themes/THEME_NAME (e.g., edx_all/themes/stanford).
......@@ -77,7 +77,7 @@ def enable_theme():
# Workaround for setting THEME_NAME to an empty
# string which is the default due to this ansible
# bug: https://github.com/ansible/ansible/issues/4812
if settings.THEME_NAME == "":
if getattr(settings, "THEME_NAME", "") == "":
settings.THEME_NAME = None
return
......
......@@ -494,7 +494,7 @@
}
.accomplishment-rendering {
background: palette(grayscale, white-t) url('../images/bg-distinguished.png') center no-repeat;
background: palette(grayscale, white-t) url($static-path + '/images/bg-distinguished.png') center no-repeat;
background-size: 65%;
.deco-corner-tl {
......
......@@ -179,7 +179,7 @@ span.edx {
width: 20px;
height: 20px;
margin-left: -($baseline/2);
background: url(../images/spinner.gif) no-repeat;
background: url($static-path + '/images/spinner.gif') no-repeat;
}
mark {
......@@ -205,7 +205,7 @@ mark {
width: 27px;
height: 24px;
margin-right: ($baseline*0.75);
background: url(../images/large-white-error-icon.png) no-repeat;
background: url($static-path + '/images/large-white-error-icon.png') no-repeat;
}
.inner-wrapper {
......
......@@ -3,9 +3,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Light-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Light-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Light-webfont.ttf') format('truetype');
font-weight: 300;
font-style: normal;
}
......@@ -13,9 +13,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-LightItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-LightItalic-webfont.ttf') format('truetype');
font-weight: 300;
font-style: italic;
}
......@@ -23,9 +23,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Regular-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Regular-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Regular-webfont.ttf') format('truetype');
font-weight: 400;
font-style: normal;
}
......@@ -33,9 +33,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Italic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Italic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Italic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Italic-webfont.ttf') format('truetype');
font-weight: 400;
font-style: italic;
}
......@@ -43,9 +43,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Semibold-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Semibold-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Semibold-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Semibold-webfont.ttf') format('truetype');
font-weight: 600;
font-style: normal;
}
......@@ -53,9 +53,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-SemiboldItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-SemiboldItalic-webfont.ttf') format('truetype');
font-weight: 600;
font-style: italic;
}
......@@ -63,9 +63,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-Bold-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-Bold-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-Bold-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-Bold-webfont.ttf') format('truetype');
font-weight: 700;
font-style: normal;
}
......@@ -73,9 +73,9 @@
@font-face {
font-family: 'Open Sans';
src:
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.woff2') format('woff2'),
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.woff') format('woff'),
url('../fonts/OpenSans/OpenSans-BoldItalic-webfont.ttf') format('truetype');
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.woff2') format('woff2'),
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.woff') format('woff'),
url($static-path + '/fonts/OpenSans/OpenSans-BoldItalic-webfont.ttf') format('truetype');
font-weight: 700;
font-style: italic;
}
......@@ -6,6 +6,11 @@ $baseline: 20px;
// ====================
// PATH
$static-path: '..' !default;
// ====================
// LAYOUT: grid
$gw-column: 80px;
$gw-gutter: 20px;
......@@ -18,10 +23,10 @@ $fg-min-width: 810px;
// ====================
// FONTS
$sans-serif: 'Open Sans', $verdana, sans-serif;
$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace;
$sans-serif: 'Open Sans', $verdana, sans-serif !default;
$monospace: Monaco, 'Bitstream Vera Sans Mono', 'Lucida Console', monospace !default;
$body-font-family: $sans-serif;
$serif: $georgia;
$serif: $georgia !default;
// FONT-WEIGHTS
$font-light: 300;
......@@ -313,13 +318,13 @@ $text-color: $dark-gray;
$dark-trans-bg: rgba(0, 0, 0, .75);
$body-bg: rgb(250,250,250);
$container-bg: $white;
$body-bg: rgb(250,250,250) !default;
$container-bg: $white !default;
$header-image: linear-gradient(-90deg, rgba(255,255,255, 1), rgba(230,230,230, 0.9));
$header-bg: $white;
$header-bg: $white !default;
$courseware-header-image: linear-gradient(top, rgb(255,255,255), rgb(238,238,238));
$courseware-header-bg: transparent;
$footer-bg: $white;
$footer-bg: $white !default;
$courseware-footer-border: none;
$courseware-footer-shadow: none;
$courseware-footer-margin: 0px;
......@@ -440,15 +445,15 @@ $dot-color: $light-gray;
$dashboard-course-cover-border: $light-gray;
// MISC: course assets
$content-wrapper-bg: $white;
$content-wrapper-bg: $white !default;
$course-bg-color: #f2f2f2;
$course-bg-image: url(../images/bg-texture.png);
$course-bg-image: url($static-path + '/images/bg-texture.png');
$account-content-wrapper-bg: shade($body-bg, 2%);
$course-profile-bg: rgb(245,245,245);
$course-header-bg: rgba(255,255,255, 0.93);
// MISC: course background texture
//$course-bg-image: url(../images/bg-texture.png);
//$course-bg-image: url($static-path + '/images/bg-texture.png');
// MISC: borders
$border-color-1: rgb(190,190,190);
......@@ -503,11 +508,11 @@ $course-title-height: ($baseline*3.6);
// IMAGES: backgrounds
$homepage-bg-image: none;
$login-banner-image: url(../images/edx-theme/edx-background-banner-account.png);
$register-banner-image: url(../images/edx-theme/edx-background-banner-account.png);
$passwordreset-banner-image: url(../images/edx-theme/edx-background-banner-account.png);
$login-banner-image: url($static-path + '/images/edx-theme/edx-background-banner-account.png');
$register-banner-image: url($static-path + '/images/edx-theme/edx-background-banner-account.png');
$passwordreset-banner-image: url($static-path + '/images/edx-theme/edx-background-banner-account.png');
$video-thumb-url: '../images/homepage-hero-video-thumb.jpg';
$video-thumb-url: $static-path + '/images/homepage-hero-video-thumb.jpg';
// ====================
......
......@@ -7,8 +7,8 @@
@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages
@import 'base/reset';
@import 'base/font_face';
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
......
......@@ -7,8 +7,8 @@
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'base/reset';
@import 'base/font_face';
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
......
......@@ -20,7 +20,7 @@ div.gradebook-wrapper {
@include box-sizing(border-box);
border-radius: 13px;
border: 1px solid $table-border-color;
background: url(../images/search-icon.png) no-repeat 9px center $gray-l6;
background: url($static-path + '/images/search-icon.png') no-repeat 9px center $gray-l6;
font-family: $sans-serif;
font-size: 11px;
box-shadow: 0 1px 4px rgba(0, 0, 0, .12) inset;
......
......@@ -33,7 +33,7 @@ div.info-wrapper {
h2 {
font-size: $body-font-size;
font-weight: bold;
background: url('../images/calendar-icon.png') 0 center no-repeat;
background: url($static-path + '/images/calendar-icon.png') 0 center no-repeat;
padding-left: $baseline;
}
......@@ -183,7 +183,7 @@ div.info-wrapper {
}
div.hitarea {
background-image: url('../images/treeview-default.gif') no-repeat;
background-image: url($static-path + '/images/treeview-default.gif') no-repeat;
display: block;
height: 100%;
margin-left: 0;
......
......@@ -72,7 +72,7 @@ div.book-wrapper {
}
div.hitarea {
background-image: url('../images/treeview-default.gif');
background-image: url($static-path + '/images/treeview-default.gif');
position: relative;
top: 4px;
......@@ -150,7 +150,7 @@ div.book-wrapper {
left: 0;
a {
background-image: url('../images/textbook/textbook-left.png');
background-image: url($static-path + '/images/textbook/textbook-left.png');
}
}
......@@ -158,7 +158,7 @@ div.book-wrapper {
right: 0;
a {
background-image: url('../images/textbook/textbook-right.png');
background-image: url($static-path + '/images/textbook/textbook-right.png');
}
}
......@@ -207,7 +207,7 @@ div.book-wrapper {
padding: 0;
a {
background-image: url('../images/slide-right-icon.png');
background-image: url($static-path + '/images/slide-right-icon.png');
}
h2 {
......
......@@ -152,7 +152,7 @@ h1.top-header {
position: relative;
a {
background: #f6f6f6 url('../images/slide-left-icon.png') center center no-repeat;
background: #f6f6f6 url($static-path + '/images/slide-left-icon.png') center center no-repeat;
border: 1px solid #D3D3D3;
border-radius: 3px 0 0 3px;
height: 16px;
......
......@@ -231,7 +231,7 @@ section.tool-wrapper {
}
.ui-slider-handle {
background: lighten( #586e75, 5% ) url('../images/amplifier-slider-handle.png') center no-repeat;
background: lighten( #586e75, 5% ) url($static-path + '/images/amplifier-slider-handle.png') center no-repeat;
border: 1px solid darken(#002b36, 8%);
box-shadow: inset 0 1px 0 lighten( #586e75, 20% );
margin-top: -.3em;
......
......@@ -667,7 +667,7 @@ div.course-wrapper {
header#open_close_accordion {
a {
background-image: url('../images/slide-right-icon.png');
background-image: url($static-path + '/images/slide-right-icon.png');
}
}
......
......@@ -52,7 +52,7 @@
margin-left: ($baseline/4);
margin-right: ($baseline/4);
position: absolute;
background-image: url(../images/wmd-buttons.png);
background-image: url($static-path + '/images/wmd-buttons.png');
background-repeat: no-repeat;
background-position: 0px 0px;
display: inline-block;
......
......@@ -17,7 +17,7 @@
.calc {
@include transition(background-color $tmg-f2 ease-in-out 0s);
background: url("../images/calc-icon.png") $black-t1 no-repeat center;
background: url($static-path + "/images/calc-icon.png") $black-t1 no-repeat center;
border-bottom: 0;
color: $white;
float: right;
......@@ -33,7 +33,7 @@
}
&.closed {
background-image: url("../images/close-calc-icon.png");
background-image: url($static-path + "/images/close-calc-icon.png");
background-color: $black;
top: -36px;
}
......@@ -155,7 +155,7 @@
height: 35px;
width: 35px;
border: none;
background: url("../images/info-icon.png") center center no-repeat;
background: url($static-path + "/images/info-icon.png") center center no-repeat;
color: $white;
&:focus {
......
......@@ -100,7 +100,7 @@ section.wiki {
width: 180px;
height: 27px;
padding: 0 15px 0 35px;
background: url(../images/search-icon.png) no-repeat 9px center $gray-l6;
background: url($static-path + '/images/search-icon.png') no-repeat 9px center $gray-l6;
border: 1px solid #c8c8c8;
border-radius: 14px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12) inset;
......
......@@ -417,7 +417,7 @@ body.discussion {
.discussion-article {
position: relative;
min-height: 500px;
background-image: url(../images/bg-texture.png);
background-image: url($static-path + '/images/bg-texture.png');
a {
word-wrap: break-word;
......@@ -598,7 +598,7 @@ body.discussion {
background: transparent;
span {
background-image: url(../images/wmd-buttons-transparent.png);
background-image: url($static-path + '/images/wmd-buttons-transparent.png');
}
}
}
......@@ -720,7 +720,7 @@ body.discussion {
}
.loading-animation {
background-image: url(../images/spinner.gif);
background-image: url($static-path + '/images/spinner.gif');
}
.discussion-show {
......@@ -742,7 +742,7 @@ body.discussion {
@include margin-right(6px);
width: 21px;
height: 19px;
background: url(../images/show-hide-discussion-icon.png) no-repeat;
background: url($static-path + '/images/show-hide-discussion-icon.png') no-repeat;
background-position: -21px 0;
}
}
......
......@@ -188,7 +188,7 @@
.post-error {
padding: ($baseline/2) $baseline 12px 45px;
border-bottom: 1px solid $red;
background: url(../images/white-error-icon.png) no-repeat 15px 14px;
background: url($static-path + '/images/white-error-icon.png') no-repeat 15px 14px;
&:last-child {
border-bottom: none;
......
@font-face {
font-family: 'CreativeCommons';
src: url('../fonts/CreativeCommons/cc.eot');
src: url('../fonts/CreativeCommons/cc.eot#iefix') format('embedded-opentype'),
url('../fonts/CreativeCommons/cc.woff') format('woff'),
url('../fonts/CreativeCommons/cc.ttf') format('truetype'),
url('../fonts/CreativeCommons/cc.svg#CreativeCommons') format('svg');
src: url($static-path + '/fonts/CreativeCommons/cc.eot');
src: url($static-path + '/fonts/CreativeCommons/cc.eot#iefix') format('embedded-opentype'),
url($static-path + '/fonts/CreativeCommons/cc.woff') format('woff'),
url($static-path + '/fonts/CreativeCommons/cc.ttf') format('truetype'),
url($static-path + '/fonts/CreativeCommons/cc.svg#CreativeCommons') format('svg');
font-weight: normal;
font-style: normal;
}
......
......@@ -2,21 +2,10 @@
@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages
@import 'base/reset';
@import 'base/font_face';
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
@import 'build-course'; // shared app style assets/rendering
......@@ -2,21 +2,10 @@
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'base/reset';
@import 'base/font_face';
@import 'base/variables';
@import 'base/font_face';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
@import 'build-course'; // shared app style assets/rendering
......@@ -9,18 +9,7 @@
@import 'base/variables';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
footer#footer-openedx {
@import 'base/reset';
......
......@@ -9,18 +9,7 @@
@import 'base/variables';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
footer#footer-openedx {
@import 'base/reset';
......
......@@ -14,17 +14,6 @@
@import 'base/variables';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
@import 'build-lms'; // shared app style assets/rendering
......@@ -4,7 +4,6 @@
// libs and resets *do not edit*
@import 'bourbon/bourbon'; // lib - bourbon
@import 'vendor/bi-app/bi-app-ltr'; // set the layout for left to right languages
@import 'base/variables-ltr';
// BASE *default edX offerings*
// ====================
......@@ -14,17 +13,6 @@
@import 'base/variables';
@import 'base/mixins';
## 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/<theme-name>/, with its base Sass file in
## themes/<theme-name>/static/sass/_<theme-name>.scss. That one entry
## point can be used to @import in as many other things as needed.
% if env["FEATURES"].get("USE_CUSTOM_THEME", False):
// import theme's Sass overrides
@import '${env.get('THEME_NAME')}';
% endif
//<THEME-OVERRIDE>
@import 'build-lms'; // shared app style assets/rendering
......@@ -405,7 +405,7 @@
.opencourseware {
text-indent: -9999px;
background: url('../images/opencourseware.png') 0 0 no-repeat;
background: url($static-path + '/images/opencourseware.png') 0 0 no-repeat;
width: 266px;
height: 31px;
margin-bottom: $baseline;
......@@ -419,7 +419,7 @@
li {
list-style: none;
padding-left: 29px;
background: url('../images/link-icon.png') left center no-repeat;
background: url($static-path + '/images/link-icon.png') left center no-repeat;
}
}
}
......
......@@ -175,7 +175,7 @@ $paleYellow: #fffcf1;
width: 263px;
height: 72px;
margin: 150px auto 50px;
background: url(../images/edx-theme/edx-edge-logo-large.png) no-repeat;
background: url($static-path + '/images/edx-theme/edx-edge-logo-large.png') no-repeat;
text-indent: -9999px;
overflow: hidden;
}
......
......@@ -1311,7 +1311,7 @@
display: block;
width: 45px;
height: 45px;
background: transparent url('../images/verified-ribbon.png') no-repeat 0 0;
background: transparent url($static-path + '/images/verified-ribbon.png') no-repeat 0 0;
}
.action-intro, .action-select {
......
When USE_CUSTOM_THEME is true, .scss files are pre-processed to add imports of
the theme settings. This directory will hold the output of the pre-processing.
<%namespace name='static' file='static_content.html'/>
<%!
from microsite_configuration import microsite
%>
<%
theme_enabled = settings.FEATURES.get("USE_CUSTOM_THEME", False)
is_microsite = microsite.is_request_in_microsite()
style_overrides_file = microsite.get_value('css_overrides_file')
%>
% if style_overrides_file:
<link rel="stylesheet" type="text/css" href="${static.url(style_overrides_file)}" />
% endif
% if theme_enabled and not is_microsite:
<%include file="theme-header.html" />
% else:
<%include file="${microsite.get_template_path('navigation.html')}" />
% endif
......@@ -4,6 +4,7 @@
from django.core.urlresolvers import reverse
from django.utils.http import urlquote_plus
from django.utils.translation import ugettext as _
from django.utils.translation import get_language_bidi
from microsite_configuration import microsite
from microsite_configuration import page_title_breadcrumbs
from branding import api as branding_api
......@@ -62,7 +63,13 @@ from branding import api as branding_api
<link rel="icon" type="image/x-icon" href="${static.url(microsite.get_value('favicon_path', settings.FAVICON_PATH))}" />
<%static:css group='style-vendor'/>
<%static:css group='style-main'/>
## route around the overwhelmingly complicated static:css nonsense
<%
application_css_path = "css/lms-main{rtl}.css".format(
rtl="-rtl" if get_language_bidi() else "",
)
%>
<link rel="stylesheet" href="${static.url(application_css_path)}" type="text/css" media="all" />
% if disable_courseware_js:
<%static:js group='base_vendor'/>
......@@ -85,30 +92,7 @@ from branding import api as branding_api
<%block name="headextra"/>
<%
if theme_enabled() and not is_microsite():
header_extra_file = 'theme-head-extra.html'
header_file = 'theme-header.html'
google_analytics_file = 'theme-google-analytics.html'
style_overrides_file = None
else:
header_extra_file = microsite.get_template_path('header_extra.html')
if settings.FEATURES['IS_EDX_DOMAIN'] and not is_microsite():
header_file = microsite.get_template_path('navigation-edx.html')
else:
header_file = microsite.get_template_path('navigation.html')
google_analytics_file = microsite.get_template_path('google_analytics.html')
style_overrides_file = microsite.get_value('css_overrides_file')
%>
% if header_extra_file:
<%include file="${header_extra_file}" />
% endif
<%static:optional_include_mako file="header-extra.html" with_microsite=True />
<%include file="widgets/optimizely.html" />
<%include file="widgets/segment-io.html" />
......@@ -116,11 +100,7 @@ from branding import api as branding_api
<meta name="path_prefix" content="${EDX_ROOT_URL}">
<meta name="google-site-verification" content="_mipQ4AtZQDNmbtOkwehQDOgCxUUV2fb_C0b6wbiRHY" />
<%include file="${google_analytics_file}" />
% if style_overrides_file:
<link rel="stylesheet" type="text/css" href="${static.url(style_overrides_file)}" />
% endif
<%static:optional_include_mako file="google_analytics.html" with_microsite=True />
</head>
......@@ -132,7 +112,7 @@ from branding import api as branding_api
<a class="nav-skip" href="<%block name="nav_skip">#content</%block>">${_("Skip to main content")}</a>
% if not disable_header:
<%include file="${header_file}" />
<%include file="header.html" />
% endif
<div class="content-wrapper" id="content">
......@@ -141,16 +121,7 @@ from branding import api as branding_api
</div>
% if not disable_footer:
<%block name="footer">
## Can be overridden by child templates wanting to hide the footer.
% if theme_enabled() and not is_microsite():
<%include file="theme-footer.html" />
% elif settings.FEATURES.get('IS_EDX_DOMAIN', False) and not is_microsite():
<%include file="footer-edx-v3.html" />
% else:
<%include file="${microsite.get_template_path('footer.html')}" />
% endif
</%block>
<%include file="themable-footer.html" />
% endif
% if not disable_window_wrap:
......
<%!
from microsite_configuration import microsite
%>
<%
theme_enabled = settings.FEATURES.get("USE_CUSTOM_THEME", False)
is_microsite = microsite.is_request_in_microsite()
%>
% if theme_enabled and not is_microsite:
<%include file="theme-footer.html" />
% else:
<%include file="${microsite.get_template_path('footer.html')}" />
% endif
"""
Core logic for Comprehensive Theming.
"""
from django.conf import settings
import edxmako
def comprehensive_theme_changes(theme_dir):
"""
Calculate the set of changes needed to enable a comprehensive theme.
Arguments:
theme_dir (path.path): the full path to the theming directory to use.
Returns:
A dict indicating the changes to make:
* 'settings': a dictionary of settings names and their new values.
* 'mako_paths': a list of directories to prepend to the edxmako
template lookup path.
"""
changes = {
'settings': {},
'mako_paths': [],
}
templates_dir = theme_dir / "lms" / "templates"
if templates_dir.isdir():
changes['settings']['TEMPLATE_DIRS'] = [templates_dir] + settings.TEMPLATE_DIRS
changes['mako_paths'].append(templates_dir)
staticfiles_dir = theme_dir / "lms" / "static"
if staticfiles_dir.isdir():
changes['settings']['STATICFILES_DIRS'] = [staticfiles_dir] + settings.STATICFILES_DIRS
locale_dir = theme_dir / "lms" / "conf" / "locale"
if locale_dir.isdir():
changes['settings']['LOCALE_PATHS'] = [locale_dir] + settings.LOCALE_PATHS
favicon = theme_dir / "lms" / "static" / "images" / "favicon.ico"
if favicon.isfile():
changes['settings']['FAVICON_PATH'] = str(favicon)
return changes
def enable_comprehensive_theme(theme_dir):
"""
Add directories to relevant paths for comprehensive theming.
"""
changes = comprehensive_theme_changes(theme_dir)
# Use the changes
for name, value in changes['settings'].iteritems():
setattr(settings, name, value)
for template_dir in changes['mako_paths']:
edxmako.paths.add_lookup('main', template_dir, prepend=True)
"""
Startup code for Comprehensive Theming
"""
from django.conf import settings
from .core import enable_comprehensive_theme
def run():
"""Enable comprehensive theming, if we should."""
if settings.COMP_THEME_DIR:
enable_comprehensive_theme(theme_dir=settings.COMP_THEME_DIR)
"""
Test helpers for Comprehensive Theming.
"""
from functools import wraps
import os
import os.path
from mock import patch
from django.conf import settings
from django.test.utils import override_settings
import edxmako
from .core import comprehensive_theme_changes
def with_comp_theme(theme_dir):
"""
A decorator to run a test with a particular comprehensive theme.
Arguments:
theme_dir (str): the full path to the theme directory to use.
This will likely use `settings.REPO_ROOT` to get the full path.
"""
# This decorator gets the settings changes needed for a theme, and applies
# them using the override_settings and edxmako.paths.add_lookup context
# managers.
changes = comprehensive_theme_changes(theme_dir)
def _decorator(func): # pylint: disable=missing-docstring
@wraps(func)
def _decorated(*args, **kwargs): # pylint: disable=missing-docstring
with override_settings(COMP_THEME_DIR=theme_dir, **changes['settings']):
with edxmako.save_lookups():
for template_dir in changes['mako_paths']:
edxmako.paths.add_lookup('main', template_dir, prepend=True)
return func(*args, **kwargs)
return _decorated
return _decorator
def with_is_edx_domain(is_edx_domain):
"""
A decorator to run a test as if IS_EDX_DOMAIN is true or false.
We are transitioning away from IS_EDX_DOMAIN and are moving toward an edX
theme. This decorator changes both settings to let tests stay isolated
from the details.
Arguments:
is_edx_domain (bool): are we an edX domain or not?
"""
# This is weird, it's a decorator that conditionally applies other
# decorators, which is confusing.
def _decorator(func): # pylint: disable=missing-docstring
if is_edx_domain:
# This applies @with_comp_theme to the func.
func = with_comp_theme(settings.REPO_ROOT / "themes" / "edx.org")(func)
# This applies @patch.dict() to the func to set IS_EDX_DOMAIN.
func = patch.dict('django.conf.settings.FEATURES', {"IS_EDX_DOMAIN": is_edx_domain})(func)
return func
return _decorator
def dump_theming_info():
"""Dump a bunch of theming information, for debugging."""
for namespace, lookup in edxmako.LOOKUP.items():
print "--- %s: %s" % (namespace, lookup.template_args['module_directory'])
for directory in lookup.directories:
print " %s" % (directory,)
print "=" * 80
for dirname, __, filenames in os.walk(settings.MAKO_MODULE_DIR):
print "%s ----------------" % (dir,)
for filename in sorted(filenames):
if filename.endswith(".pyc"):
continue
with open(os.path.join(dirname, filename)) as f:
content = len(f.read())
print " %s: %d" % (filename, content)
"""
Asset compilation and collection.
"""
from __future__ import print_function
import argparse
import glob
import traceback
from paver import tasks
from paver.easy import sh, path, task, cmdopts, needs, consume_args, call_task, no_help
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
import glob
import traceback
from .utils.envs import Env
from .utils.cmd import cmd, django_cmd
# setup baseline paths
COFFEE_DIRS = ['lms', 'cms', 'common']
SASS_DIRS = {
"lms/static/sass": "lms/static/css",
"cms/static/sass": "cms/static/css",
"common/static/sass": "common/static/css",
"lms/static/certificates/sass": "lms/static/certificates/css",
}
# A list of directories. Each will be paired with a sibling /css directory.
SASS_DIRS = [
path("lms/static/sass"),
path("lms/static/themed_sass"),
path("cms/static/sass"),
path("common/static/sass"),
path("lms/static/certificates/sass"),
]
SASS_LOAD_PATHS = ['common/static', 'common/static/sass']
SASS_CACHE_PATH = '/tmp/sass-cache'
edxapp_env = Env()
if edxapp_env.feature_flags.get('USE_CUSTOM_THEME', False):
theme_name = edxapp_env.env_tokens.get('THEME_NAME', '')
parent_dir = path(edxapp_env.REPO_ROOT).abspath().parent
theme_root = parent_dir / "themes" / theme_name
COFFEE_DIRS.append(theme_root)
SASS_DIRS[theme_root / "static" / "sass"] = None
def configure_paths():
"""Configure our paths based on settings. Called immediately."""
edxapp_env = Env()
if edxapp_env.feature_flags.get('USE_CUSTOM_THEME', False):
theme_name = edxapp_env.env_tokens.get('THEME_NAME', '')
parent_dir = path(edxapp_env.REPO_ROOT).abspath().parent
theme_root = parent_dir / "themes" / theme_name
COFFEE_DIRS.append(theme_root)
sass_dir = theme_root / "static" / "sass"
css_dir = theme_root / "static" / "css"
if sass_dir.isdir():
css_dir.mkdir_p()
SASS_DIRS.append(sass_dir)
if edxapp_env.env_tokens.get("COMP_THEME_DIR", None):
theme_dir = path(edxapp_env.env_tokens["COMP_THEME_DIR"])
lms_sass = theme_dir / "lms" / "static" / "sass"
lms_css = theme_dir / "lms" / "static" / "css"
if lms_sass.isdir():
lms_css.mkdir_p()
SASS_DIRS.append(lms_sass)
studio_sass = theme_dir / "studio" / "static" / "sass"
studio_css = theme_dir / "studio" / "static" / "css"
if studio_sass.isdir():
studio_css.mkdir_p()
SASS_DIRS.append(studio_sass)
configure_paths()
class CoffeeScriptWatcher(PatternMatchingEventHandler):
......@@ -71,7 +98,7 @@ class SassWatcher(PatternMatchingEventHandler):
"""
register files with observer
"""
for dirname in SASS_LOAD_PATHS + SASS_DIRS.keys():
for dirname in SASS_LOAD_PATHS + SASS_DIRS:
paths = []
if '*' in dirname:
paths.extend(glob.glob(dirname))
......@@ -137,7 +164,7 @@ def coffeescript_files():
"""
return find command for paths containing coffee files
"""
dirs = " ".join([Env.REPO_ROOT / coffee_dir for coffee_dir in COFFEE_DIRS])
dirs = " ".join(Env.REPO_ROOT / coffee_dir for coffee_dir in COFFEE_DIRS)
return cmd('find', dirs, '-type f', '-name \"*.coffee\"')
......@@ -176,10 +203,11 @@ def compile_sass(options):
if options.get('force'):
parts.append("--force")
parts.append("--load-path .")
for load_path in SASS_LOAD_PATHS + SASS_DIRS.keys():
for load_path in SASS_LOAD_PATHS + SASS_DIRS:
parts.append("--load-path {path}".format(path=load_path))
for sass_dir, css_dir in SASS_DIRS.items():
for sass_dir in SASS_DIRS:
css_dir = sass_dir.parent / "css"
if css_dir:
parts.append("{sass}:{css}".format(sass=sass_dir, css=css_dir))
else:
......@@ -197,7 +225,13 @@ def compile_templated_sass(systems, settings):
`settings` is the Django settings module to use.
"""
for sys in systems:
sh(django_cmd(sys, settings, 'preprocess_assets'))
if sys == "studio":
sys = "cms"
sh(django_cmd(
sys, settings, 'preprocess_assets',
'{sys}/static/sass/*.scss'.format(sys=sys),
'{sys}/static/themed_sass'.format(sys=sys)
))
print("\t\tFinished preprocessing {} assets.".format(sys))
......
......@@ -12,14 +12,23 @@ EXPECTED_COFFEE_COMMAND = (
)
EXPECTED_SASS_COMMAND = (
"sass --update --cache-location /tmp/sass-cache --default-encoding utf-8 --style compressed"
" --quiet --load-path . --load-path common/static --load-path common/static/sass"
" --load-path lms/static/sass --load-path lms/static/certificates/sass"
" --quiet"
" --load-path ."
" --load-path common/static"
" --load-path common/static/sass"
" --load-path lms/static/sass"
" --load-path lms/static/themed_sass"
" --load-path cms/static/sass --load-path common/static/sass"
" lms/static/sass:lms/static/css lms/static/certificates/sass:lms/static/certificates/css"
" cms/static/sass:cms/static/css common/static/sass:common/static/css"
" --load-path lms/static/certificates/sass"
" lms/static/sass:lms/static/css"
" lms/static/themed_sass:lms/static/css"
" cms/static/sass:cms/static/css"
" common/static/sass:common/static/css"
" lms/static/certificates/sass:lms/static/certificates/css"
)
EXPECTED_PREPROCESS_ASSETS_COMMAND = (
"python manage.py {system} --settings={asset_settings} preprocess_assets"
" {system}/static/sass/*.scss {system}/static/themed_sass"
)
EXPECTED_COLLECT_STATIC_COMMAND = (
"python manage.py {system} --settings={asset_settings} collectstatic --noinput > /dev/null"
......
......@@ -17,6 +17,8 @@
// ----------------------------
$em-base: 16; // deliberately sets bourbon-based em-base (http://bourbon.io/docs/#em-base)
$static-path: '..' !default;
// ----------------------------
// #GRID
......@@ -133,52 +135,52 @@ $palettes: (
// typography: config
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-Light-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-Light-webfont',
300,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-LightItalic-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-LightItalic-webfont',
300,
italic,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-Regular-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-Regular-webfont',
400,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-Italic-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-Italic-webfont',
400,
italic,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-Semibold-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-Semibold-webfont',
600,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-SemiboldItalic-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-SemiboldItalic-webfont',
600,
italic,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-Bold-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-Bold-webfont',
700,
$file-formats: woff woff2 ttf
);
@include font-face(
'Open Sans',
'../../fonts/OpenSans/OpenSans-BoldItalic-webfont',
$static-path + '/../fonts/OpenSans/OpenSans-BoldItalic-webfont',
700,
italic,
$file-formats: woff woff2 ttf
......
#####################
Comprehensive Theming
#####################
Comprehensive Theming lets you customize the appearance of your Open edX
installation. You can override Sass and CSS settings, images, or entire HTML
templates.
Eventually, Comprehensive Theming will obsolete existing theming mechanisms,
but for now they co-exist peacefully. This document describes how to use
Comprehensive Theming, and also the changes you'll need to make to keep other
theming mechanisms working.
Creating a theme
================
A theme is a directory of assets. You can create this directory wherever you
like, it does not have to be inside the edx-platform directory. The structure
within this directory mirrors the assets in the edx-platform repo itself.
Files you provide in your theme are used in place of the same-named files in
edx-platform. Here's a sample::
my-theme
└── lms
├── static
│   ├── images
│   │   └── logo.png
│   └── sass
│   ├── _overrides.scss
│   ├── lms-main-rtl.scss
│   └── lms-main.scss
└── templates
├── footer.html
└── header.html
The top directory is named whatever you like. This example uses "my-theme".
The files provided here override the files in edx-platform. In this case, the
``my-theme/lms/static/sass/lms-main.scss`` file is used in place of the
``edx-platform/lms/static/lms-main.scss`` file.
Images
------
Images can be substituted simply by placing the new image at the right place
in the theme directory. In our example above, the lms/static/images/logo.png
image is overridden.
Sass/CSS
--------
Most CSS styling in Open edX is done with Sass files compiled to CSS. You can
override individual settings by creating a new Sass file that uses the existing
file, and overrides the few settings you want.
For example, to change the fonts used throughout the site, you can create an
``lms/static/sass/_overrides.scss`` file with the change you want::
$sans-serif: 'Helvetica';
The variables that can currently be overridden are defined in
``lms/static/sass/base/_variables.scss``.
**Note:** We are currently in the middle of a re-engineering of the Sass
variables. They will change in the future. If you are interested, you can see
the new development in the `edX Pattern Library`__.
.. __: http://ux.edx.org/
Then create ``lms/static/sass/lms-main.scss`` to use those overrides, and also
the rest of the definitions from the original file::
// Our overrides for settings we want to change.
@import 'overrides';
// Import the original styles from edx-platform.
@import 'lms/static/sass/lms-main';
Do this for each .scss file your site needs.
HTML Templates
--------------
You can make changes to HTML templates by copying them to your theme directory
in the appropriate place, and making the changes you need. Keep in mind that
in the future if you upgrade the Open edX code, you may have to update the
copied template in your theme also.
Installing your theme
---------------------
To use your theme, follow these steps:
#. Edit /edx/app/edx_ansible/server-vars.yml to add the edxapp_comp_theme_dir
value::
edxapp_comp_theme_dir: '/full/path/to/my-theme'
#. Re-run the provisioning script::
$ sudo /edx/bin/update edx-platform HEAD
Your changes should now be visible on your site.
"Stanford" theming
==================
If you want to continue using the "Stanford" theming system, there are a few
changes you'll need to make.
Create the following new files in the ``sass`` directory of your theme:
* lms-main.scss
* lms-main-rtl.scss
* lms-course.scss
* lms-course-rtl.scss
* lms-footer.scss
* lms-footer-rtl.scss
The contents of each of these files will be very similar. Here's what
``lms-main.scss`` should look like::
$static-path: '../../../..';
@import 'lms/static/sass/lms-main';
@import '_default';
Each file should set the ``$static-path`` variable to a relative path that
points to the ``lms/static`` directory inside of ``edx-platform``. Then,
it should ``@import`` the sass file under ``lms/static/sass`` that matches
its name: ``lms-footer.scss`` should import ``lms/static/sass/lms-footer``,
for example. Finally, the file should import the ``_default`` name, which
refers to the ``_default.scss`` Sass file that should already exist in your
Stanford theme directory.
If your theme uses a different name than "default", you'll need to use that
name in the ``@import`` line.
Run the ``update_assets`` command to recompile the theme::
$ paver update_assets lms --settings=aws
## mako
<%!
from django.utils.translation import ugettext as _
from branding.api import get_footer
%>
<% footer = get_footer(is_secure=is_secure) %>
<%namespace name='static' file='static_content.html'/>
## WARNING: These files are specific to edx.org and are not used in installations outside of that domain. Open edX users will want to use the file "footer.html" for any changes or overrides.
<footer id="footer-edx-v3" role="contentinfo" aria-label="${_("Page Footer")}"
## When rendering the footer through the branding API,
## the direction may not be set on the parent element,
## so we set it here.
% if bidi:
dir=${bidi}
% endif
>
<h2 class="sr footer-about-title">${_("About edX")}</h2>
<div class="footer-content-wrapper">
<div class="footer-logo">
<img alt="edX logo" src="${static.url("images/logo.png")}">
</div>
<div class="site-details">
<nav class="site-nav" aria-label="${_("About edX")}">
% for link in footer["navigation_links"]:
<a href="${link['url']}">${link['title']}</a>
% endfor
</nav>
<nav class="legal-notices" aria-label="${_("Legal")}">
% for link in footer["legal_links"]:
<a href="${link['url']}">${link['title']}</a>
% endfor
</nav>
<p class="copyright">${footer['copyright']}</p>
## The OpenEdX link may be hidden when this view is served
## through an API to partner sites (such as marketing sites or blogs),
## which are not technically powered by OpenEdX.
% if not hide_openedx_link:
<div class="openedx-link">
<a href="${footer['openedx_link']['url']}" title="${footer['openedx_link']['title']}">
<img alt="${footer['openedx_link']['title']}" src="${footer['openedx_link']['image']}" width="140">
</a>
</div>
% endif
</div>
<div class="external-links">
<div class="social-media-links">
% for link in footer['social_links']:
<a href="${link['url']}" class="sm-link external" title="${link['title']}" rel="noreferrer">
<span class="icon fa ${link['icon-class']}" aria-hidden="true"></span>
</a>
% endfor
</div>
<div class="mobile-app-links">
% for link in footer['mobile_links']:
<a href="${link['url']}" class="app-link external">
<img alt="${link['title']}" src="${link['image']}">
</a>
% endfor
</div>
</div>
</div>
</footer>
% if include_dependencies:
<%static:js group='base_vendor'/>
<%static:css group='style-vendor'/>
<%include file="widgets/segment-io.html" />
% endif
% if footer_css_urls:
% for url in footer_css_urls:
<link rel="stylesheet" type="text/css" href="${url}"></link>
% endfor
% endif
% if footer_js_url:
<script type="text/javascript" src="${footer_js_url}"></script>
% endif
## mako
<%namespace name='static' file='static_content.html'/>
<%namespace file='main.html' import="login_query"/>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from microsite_configuration import microsite
from microsite_configuration.templatetags.microsite import platform_name
# App that handles subdomain specific branding
import branding
# app that handles site status messages
from status.status import get_site_status_msg
%>
## Provide a hook for themes to inject branding on top.
<%block name="navigation_top" />
<%block>
<%
try:
course_id = course.id.to_deprecated_string()
except:
# can't figure out a better way to get at a possibly-defined course var
course_id = None
site_status_msg = get_site_status_msg(course_id)
%>
% if site_status_msg:
<div class="site-status">
<div class="inner-wrapper">
<span class="white-error-icon"></span>
<p>${site_status_msg}</p>
</div>
</div>
% endif
</%block>
<header class="${"global slim" if course and not disable_courseware_header else "global-new"}" aria-label="Main" role="banner">
<div class="${'rwd ' if responsive else ''}wrapper-header nav-container">
<h1 class="logo" itemscope="" itemtype="http://schema.org/Organization">
<a href="${marketing_link('ROOT')}" title="Home page" itemprop="url">
<%block name="navigation_logo">
<img src="${static.url("images/logo.png")}" alt="${platform_name()}" title="${platform_name()}" itemprop="url" />
<span class="sr">${_("Home Page")}</span>
</%block>
</a>
</h1>
% if course and not disable_courseware_header:
<h2><span class="provider">${course.display_org_with_default | h}:</span> ${course.display_number_with_default | h} ${course.display_name_with_default}</h2>
% endif
% if user.is_authenticated():
% if not course or disable_courseware_header:
<nav aria-label="Main" class="nav-main">
<ul class="left nav-global authenticated">
<%block name="navigation_global_links_authenticated">
<li class="nav-global-01">
<a href="${marketing_link('HOW_IT_WORKS')}">${_("How it Works")}</a>
</li>
<li class="nav-global-02">
<a href="${marketing_link('COURSES')}">${_("Find Courses")}</a>
</li>
<li class="nav-global-03">
<a href="${marketing_link('SCHOOLS')}">${_("Schools & Partners")}</a>
</li>
</%block>
</ul>
</nav>
% endif
<ul class="user">
<li class="primary">
<a href="${reverse('dashboard')}" class="user-link">
<i class="icon fa fa-home" aria-hidden="true"></i>
<span class="sr">${_("Dashboard for:")}</span>
<div>${user.username}</div>
</a>
</li>
<li class="primary">
<a href="#" class="dropdown" aria-haspopup="true" aria-expanded="false"><span class="sr">${_("More options dropdown")}</span> &#9662;</a>
<ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" >
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
<li><a href="${reverse('learner_profile', kwargs={'username': user.username})}">${_("My Profile")}</a></li>
</%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign Out")}</a></li>
</ul>
</li>
</ul>
% if should_display_shopping_cart_func(): # see shoppingcart.context_processor.user_has_cart_context_processor
<ul class="user">
<li class="primary">
<a class="shopping-cart" href="${reverse('shoppingcart.views.show_cart')}">
<i class="icon fa fa-shopping-cart" aria-hidden="true"></i> ${_("Shopping Cart")}
</a>
</li>
</ul>
% endif
% else:
<nav aria-label="Main" class="nav-main">
<ul class="left nav-global">
<%block name="navigation_global_links">
<li class="nav-global-01">
<a href="${marketing_link('HOW_IT_WORKS')}">${_("How it Works")}</a>
</li>
<li class="nav-global-02">
<a href="${marketing_link('COURSES')}">${_("Find Courses")}</a>
</li>
<li class="nav-global-03">
<a href="${marketing_link('SCHOOLS')}">${_("Schools & Partners")}</a>
</li>
</%block>
</ul>
</nav>
<nav aria-label="Account" class="nav-account-management">
<div class="right nav-courseware">
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<div class="nav-courseware-01">
<a class="cta cta-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a>
</div>
% else:
<div class="nav-courseware-01">
<a class="cta cta-register" href="/register">${_("Register")}</a>
</div>
% endif
% endif
<div class="nav-courseware-02">
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-login nav-courseware-button" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
% else:
<a class="cta cta-login nav-courseware-button" href="/login${login_query()}">${_("Sign in")}</a>
% endif
% endif
</div>
</div>
</nav>
% endif
</div>
</header>
% if course:
<!--[if lte IE 9]>
<div class="ie-banner" aria-hidden="true">${_('<strong>Warning:</strong> Your browser is not fully supported. We strongly recommend using {chrome_link} or {ff_link}.').format(chrome_link='<a href="https://www.google.com/chrome" target="_blank">Chrome</a>', ff_link='<a href="http://www.mozilla.org/firefox" target="_blank">Firefox</a>')}</div>
<![endif]-->
% endif
%if not user.is_authenticated():
<%include file="forgot_password_modal.html" />
%endif
<%include file="help_modal.html"/>
CSS files should go in this directory. If you are using a CSS preprocessor
like Sass, you should configure it to output CSS in this directory. Running
`paver compile_sass` should do the right thing in this case.
// Theming overrides for sample theme
$header-bg: rgb(250,0,0);
$footer-bg: rgb(250,0,0);
$container-bg: rgb(250,0,0);
$content-wrapper-bg: rgb(250,0,0);
$serif: 'Comic Sans', 'Comic Sans MS';
$sans-serif: 'Comic Sans', 'Comic Sans MS';
// Theming overrides for sample theme
@import 'overrides';
// import the rest of the application
@import 'lms/static/sass/lms-main-rtl';
// Theming overrides for sample theme
@import 'overrides';
// import the rest of the application
@import 'lms/static/sass/lms-main';
## mako
<%namespace name='static' file='static_content.html'/>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
from microsite_configuration.templatetags.microsite import platform_name
%>
<div class="wrapper wrapper-footer">
<footer>
<!-- This is super-ugly, don't use it! -->
<div class="colophon">
<nav class="nav-colophon" aria-label="${_('About')}">
<ol>
<li class="nav-colophon-01">
<a id="about" href="${marketing_link('ABOUT')}">
${_("About")}
</a>
</li>
%if marketing_link('JOBS') and marketing_link('JOBS') != '#':
<li class="nav-colophon-02">
<a id="jobs" href="${marketing_link('JOBS')}">
${_("Jobs")}
</a>
</li>
%endif
%if marketing_link('NEWS') and marketing_link('NEWS') != '#':
<li class="nav-colophon-03">
<a id="news" href="${marketing_link('NEWS')}">
${_("News")}
</a>
</li>
%endif
<li class="nav-colophon-04">
<a id="faq" href="${marketing_link('FAQ')}">
${_("FAQ")}
</a>
</li>
<li class="nav-colophon-05">
<a id="contact" href="${marketing_link('CONTACT')}">
${_("Contact")}
</a>
</li>
</ol>
</nav>
<div class="wrapper-logo">
<p>
<a href="/">
## this is just a placeholder logo
## feel free to change this logo to your own by replacing "logo.png" with your own logo
<img alt="organization logo placeholder" src="${static.url("images/logo.png")}">
</a>
</p>
</div>
<p class="copyright">&copy; ${settings.COPYRIGHT_YEAR} ${settings.PLATFORM_NAME}.</p>
## Site operators: Please do not remove this paragraph! This attributes back to edX and makes your acknowledgement of edX's trademarks clear.
<p class="copyright">
## Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. Please do not translate any of these trademarks and company names.
${_("EdX, Open edX, and the edX and Open edX logos are registered trademarks or trademarks of {link_start}edX Inc.{link_end}").format(
link_start=u"<a href='https://www.edx.org/'>",
link_end=u"</a>"
)}
</p>
<nav class="nav-legal" aria-label="${_('Legal')}">
<ul>
% if marketing_link('HONOR') and marketing_link('HONOR') != '#':
<li class="nav-legal-01">
<%
tos_link = u"<a href='{}'>".format(marketing_link('TOS'))
honor_link = u"<a href='{}'>".format(marketing_link('HONOR'))
%>
${
_("{tos_link_start}Terms of Service{tos_link_end} and {honor_link_start}Honor Code{honor_link_end}").format(
tos_link_start=tos_link,
tos_link_end="</a>",
honor_link_start=honor_link,
honor_link_end="</a>",
)
}
</li>
% else:
<li class="nav-legal-01">
<a href="${marketing_link('TOS')}">${_("Terms of Service")}</a>
</li>
% endif
<li class="nav-legal-02">
<a href="${marketing_link('PRIVACY')}">${_("Privacy Policy")}</a>
</li>
</ul>
</nav>
</div>
## please leave this link and use one of the logos provided
<div class="footer-about-openedx">
<p>
<a href="http://openedx.org/">
## standard powered-by logo
## Translators: 'Open edX' is a brand, please keep this untranslated. See http://openedx.org for more information.
<img src="https://files.edx.org/openedx-logos/edx-openedx-logo-tag.png" alt="${_('Powered by Open edX')}" width="140" />
## greyscale logo for dark background
## <img src="https://files.edx.org/openedx-logos/edx-openedx-logo-tag-light.png" alt="${_('Powered by Open edX')}" width="140" />
## greyscale logo for light background
## <img src="https://files.edx.org/openedx-logos/edx-openedx-logo-tag-dark.png" alt="${_('Powered by Open edX')}" width="140" />
</a>
</p>
</div>
</footer>
</div>
## mako
<%namespace name='static' file='static_content.html'/>
<%namespace file='main.html' import="login_query, stanford_theme_enabled"/>
<%!
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext as _
# App that handles subdomain specific branding
import branding
# app that handles site status messages
from status.status import get_site_status_msg
from microsite_configuration import microsite
from microsite_configuration.templatetags.microsite import platform_name
from ccx.overrides import get_current_ccx
%>
## Provide a hook for themes to inject branding on top.
<%block name="navigation_top" />
<%block>
<%
try:
course_id = course.id.to_deprecated_string()
except:
# can't figure out a better way to get at a possibly-defined course var
course_id = None
site_status_msg = get_site_status_msg(course_id)
%>
% if site_status_msg:
<div class="site-status">
<div class="inner-wrapper">
<span class="white-error-icon"></span>
<p>${site_status_msg}</p>
</div>
</div>
% endif
</%block>
<header id="global-navigation" class="global ${"slim" if course else ""}" >
<!-- This file is only for demonstration, and is horrendous! -->
<nav aria-label="${_('Global')}">
<h1 class="logo">
<a href="${marketing_link('ROOT')}">
<%block name="navigation_logo">
<img src="${static.url("images/logo.png")}" alt="${platform_name()}"/>
</%block>
</a>
</h1>
% if course:
<h2><span class="provider">${course.display_org_with_default | h}:</span>
${course.display_number_with_default | h}
<%
display_name = course.display_name_with_default
if settings.FEATURES.get('CUSTOM_COURSES_EDX', False):
ccx = get_current_ccx()
if ccx:
display_name = ccx.display_name
%>
${display_name}</h2>
% endif
% if user.is_authenticated():
<ol class="left nav-global authenticated">
<%block name="navigation_global_links_authenticated">
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
<li class="nav-global-01">
<a href="${marketing_link('COURSES')}">${_('Find Courses')}</a>
</li>
% endif
%if settings.FEATURES.get('ENABLE_SYSADMIN_DASHBOARD','') and user.is_staff:
<li>
## Translators: This is short for "System administration".
<a href="${reverse('sysadmin')}">${_("Sysadmin")}</a>
</li>
%endif
</%block>
</ol>
<ol class="user">
<li class="primary">
<a href="${reverse('dashboard')}" class="user-link">
<i class="icon fa fa-home" aria-hidden="true"></i>
<span class="sr">${_("Dashboard for:")}</span>
<div>
${user.username}
</div>
</a>
</li>
<li class="primary">
<a href="#" class="dropdown" aria-haspopup="true" aria-expanded="false"><span class="sr">${_("More options dropdown")}</span><i class="fa fa-sort-desc" aria-hidden="true"></i></a>
<ul class="dropdown-menu" aria-label="More Options" role="menu">
<%block name="navigation_dropdown_menu_links" >
<li><a href="${reverse('account_settings')}">${_("Account Settings")}</a></li>
<li><a href="${reverse('learner_profile', kwargs={'username': user.username})}">${_("My Profile")}</a></li>
</%block>
<li><a href="${reverse('logout')}" role="menuitem">${_("Sign Out")}</a></li>
</ul>
</li>
</ol>
% if should_display_shopping_cart_func(): # see shoppingcart.context_processor.user_has_cart_context_processor
<ol class="user">
<li class="primary">
<a class="shopping-cart" href="${reverse('shoppingcart.views.show_cart')}">
<i class="icon fa fa-shopping-cart"></i> ${_("Shopping Cart")}
</a>
</li>
</ol>
% endif
% else:
<ol class="left nav-global">
<%block name="navigation_global_links">
% if microsite.get_value('ENABLE_MKTG_SITE', settings.FEATURES.get('ENABLE_MKTG_SITE', False)):
<li class="nav-global-01">
<a href="${marketing_link('HOW_IT_WORKS')}">${_("How it Works")}</a>
</li>
% if settings.FEATURES.get('COURSES_ARE_BROWSABLE'):
<li class="nav-global-02">
<a href="${marketing_link('COURSES')}">${_("Courses")}</a>
</li>
% endif
<li class="nav-global-03">
<a href="${marketing_link('SCHOOLS')}">${_("Schools")}</a>
</li>
% endif
</%block>
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<li class="nav-global-04">
<a class="cta cta-register" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register Now")}</a>
</li>
% else:
<li class="nav-global-04">
<a class="cta cta-register" href="/register">${_("Register Now")}</a>
</li>
% endif
% endif
</ol>
<ol class="right nav-courseware">
<li class="nav-courseware-01">
% if not settings.FEATURES['DISABLE_LOGIN_BUTTON']:
% if course and settings.FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD') and course.enrollment_domain:
<a class="cta cta-login" href="${reverse('course-specific-login', args=[course.id.to_deprecated_string()])}${login_query()}">${_("Sign in")}</a>
% else:
<a class="cta cta-login" href="/login${login_query()}">${_("Sign in")}</a>
% endif
% endif
</li>
</ol>
% endif
</nav>
</header>
% if course:
<!--[if lte IE 8]>
<div class="ie-banner" aria-hidden="true">${_('<strong>Warning:</strong> Your browser is not fully supported. We strongly recommend using {chrome_link} or {ff_link}.').format(chrome_link='<a href="https://www.google.com/intl/en/chrome/browser/" target="_blank">Chrome</a>', ff_link='<a href="http://www.mozilla.org/en-US/firefox/new/" target="_blank">Firefox</a>')}</div>
<![endif]-->
% endif
%if not user.is_authenticated():
<%include file="forgot_password_modal.html" />
%endif
<%include file="help_modal.html"/>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment