From 58daa365cd18a30f37a2567a3edb405ad07a5248 Mon Sep 17 00:00:00 2001 From: David Baumgold <david@davidbaumgold.com> Date: Tue, 6 Oct 2015 16:00:58 -0400 Subject: [PATCH] re-enable edx.org comprehensive theme --- cms/envs/aws.py | 1 + cms/envs/common.py | 7 +- cms/envs/devstack.py | 3 +- .../logo.png => studio-logo.png} | Bin cms/templates/widgets/header.html | 6 +- .../acceptance/pages/lms/learner_profile.py | 2 +- .../test_microsite/templates/head-extra.html | 7 + lms/djangoapps/branding/__init__.py | 2 +- lms/djangoapps/branding/views.py | 2 +- ...eming.py => test_comprehensive_theming.py} | 13 +- lms/envs/aws.py | 7 +- lms/envs/common.py | 12 +- lms/envs/devstack.py | 4 +- .../images/{default-theme => }/logo-large.png | Bin .../images/{default-theme => }/logo.png | Bin .../default_120.png} | Bin .../default_30.png} | Bin .../default_50.png} | Bin .../default_500.png} | Bin lms/templates/courseware/course_about.html | 12 -- lms/templates/google_analytics.html | 13 -- lms/templates/google_tag_manager.html | 4 - lms/templates/header.html | 6 +- lms/templates/main.html | 71 ++++----- lms/templates/main_django.html | 20 +-- lms/templates/themable-footer.html | 20 +++ openedx/core/djangoapps/theming/core.py | 14 +- openedx/core/djangoapps/theming/finders.py | 79 ++++++++++ openedx/core/djangoapps/theming/startup.py | 4 +- openedx/core/djangoapps/theming/storage.py | 88 +++++++++++ .../theming/templatetags/__init__.py | 0 .../theming/templatetags/optional_include.py | 77 ++++++++++ openedx/core/djangoapps/theming/test_util.py | 8 +- openedx/core/storage.py | 35 +++++ pavelib/assets.py | 14 +- themes/README.rst | 86 ++++++++++- .../edx.org/cms/static/images/studio-logo.png | Bin .../edx.org/lms/static/images/logo-large.png | Bin .../edx.org/lms/static/images/logo.png | Bin .../static/images/profiles}/default_120.png | Bin .../static/images/profiles}/default_30.png | Bin .../static/images/profiles}/default_50.png | Bin .../static/images/profiles}/default_500.png | Bin themes/edx.org/lms/templates/footer.html | 83 ++++++++++ themes/edx.org/lms/templates/header.html | 142 ++++++++++++++++++ 45 files changed, 697 insertions(+), 145 deletions(-) rename cms/static/images/{default-theme/logo.png => studio-logo.png} (100%) create mode 100644 common/test/test_microsites/test_microsite/templates/head-extra.html rename lms/djangoapps/courseware/tests/{test_comp_theming.py => test_comprehensive_theming.py} (85%) rename lms/static/images/{default-theme => }/logo-large.png (100%) rename lms/static/images/{default-theme => }/logo.png (100%) rename lms/static/images/{default-theme/default-profile_120.png => profiles/default_120.png} (100%) rename lms/static/images/{default-theme/default-profile_30.png => profiles/default_30.png} (100%) rename lms/static/images/{default-theme/default-profile_50.png => profiles/default_50.png} (100%) rename lms/static/images/{default-theme/default-profile_500.png => profiles/default_500.png} (100%) delete mode 100644 lms/templates/google_analytics.html delete mode 100644 lms/templates/google_tag_manager.html create mode 100644 lms/templates/themable-footer.html create mode 100644 openedx/core/djangoapps/theming/finders.py create mode 100644 openedx/core/djangoapps/theming/storage.py create mode 100644 openedx/core/djangoapps/theming/templatetags/__init__.py create mode 100644 openedx/core/djangoapps/theming/templatetags/optional_include.py create mode 100644 openedx/core/storage.py rename cms/static/images/edx-theme/edx-studio-logo.png => themes/edx.org/cms/static/images/studio-logo.png (100%) rename lms/static/images/edx-theme/edx-logo-bw.png => themes/edx.org/lms/static/images/logo-large.png (100%) rename lms/static/images/edx-theme/edx-header-logo.png => themes/edx.org/lms/static/images/logo.png (100%) rename {lms/static/images/edx-theme => themes/edx.org/lms/static/images/profiles}/default_120.png (100%) rename {lms/static/images/edx-theme => themes/edx.org/lms/static/images/profiles}/default_30.png (100%) rename {lms/static/images/edx-theme => themes/edx.org/lms/static/images/profiles}/default_50.png (100%) rename {lms/static/images/edx-theme => themes/edx.org/lms/static/images/profiles}/default_500.png (100%) create mode 100644 themes/edx.org/lms/templates/footer.html create mode 100644 themes/edx.org/lms/templates/header.html diff --git a/cms/envs/aws.py b/cms/envs/aws.py index 3d7093431d4..bdb670eb66d 100644 --- a/cms/envs/aws.py +++ b/cms/envs/aws.py @@ -177,6 +177,7 @@ ASSET_IGNORE_REGEX = ENV_TOKENS.get('ASSET_IGNORE_REGEX', ASSET_IGNORE_REGEX) # Theme overrides THEME_NAME = ENV_TOKENS.get('THEME_NAME', None) +COMPREHENSIVE_THEME_DIR = path(ENV_TOKENS.get('COMPREHENSIVE_THEME_DIR', COMPREHENSIVE_THEME_DIR)) #Timezone overrides TIME_ZONE = ENV_TOKENS.get('TIME_ZONE', TIME_ZONE) diff --git a/cms/envs/common.py b/cms/envs/common.py index 6cfcede38d2..71493e06ec4 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -31,7 +31,8 @@ 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, COMP_THEME_DIR, + update_module_store_settings, ASSET_IGNORE_REGEX, COPYRIGHT_YEAR, + PARENTAL_CONSENT_AGE_LIMIT, COMPREHENSIVE_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. @@ -467,12 +468,12 @@ EMBARGO_SITE_REDIRECT_URL = None PIPELINE_ENABLED = True -# Process static files using RequireJS Optimizer -STATICFILES_STORAGE = 'openedx.core.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage' +STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage' # List of finder classes that know how to find static files in various locations. # Note: the pipeline finder is included to be able to discover optimized files STATICFILES_FINDERS = [ + 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'pipeline.finders.PipelineFinder', diff --git a/cms/envs/devstack.py b/cms/envs/devstack.py index 127eb8a3a1f..c19db5e9b01 100644 --- a/cms/envs/devstack.py +++ b/cms/envs/devstack.py @@ -37,10 +37,11 @@ FEATURES['PREVIEW_LMS_BASE'] = "preview." + LMS_BASE # Skip packaging and optimization in development PIPELINE_ENABLED = False -STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage' +STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage' # Revert to the default set of finders as we don't want the production pipeline STATICFILES_FINDERS = [ + 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ] diff --git a/cms/static/images/default-theme/logo.png b/cms/static/images/studio-logo.png similarity index 100% rename from cms/static/images/default-theme/logo.png rename to cms/static/images/studio-logo.png diff --git a/cms/templates/widgets/header.html b/cms/templates/widgets/header.html index 4014139a702..37270f3af52 100644 --- a/cms/templates/widgets/header.html +++ b/cms/templates/widgets/header.html @@ -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/studio-logo.png")}" alt="${settings.STUDIO_NAME}" /> </a></h1> % if context_course: diff --git a/common/test/acceptance/pages/lms/learner_profile.py b/common/test/acceptance/pages/lms/learner_profile.py index f71562de3a7..dec050f022a 100644 --- a/common/test/acceptance/pages/lms/learner_profile.py +++ b/common/test/acceptance/pages/lms/learner_profile.py @@ -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): """ diff --git a/common/test/test_microsites/test_microsite/templates/head-extra.html b/common/test/test_microsites/test_microsite/templates/head-extra.html new file mode 100644 index 00000000000..82d60e754c4 --- /dev/null +++ b/common/test/test_microsites/test_microsite/templates/head-extra.html @@ -0,0 +1,7 @@ +<%namespace name='static' file='../../static_content.html'/> +<%! from microsite_configuration import 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 diff --git a/lms/djangoapps/branding/__init__.py b/lms/djangoapps/branding/__init__.py index 96287a29f94..a7f8c512c17 100644 --- a/lms/djangoapps/branding/__init__.py +++ b/lms/djangoapps/branding/__init__.py @@ -81,4 +81,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') diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index e94ffded33d..85d7f9a85dd 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -232,7 +232,7 @@ def footer(request): "title": "Powered by Open edX", "image": "http://example.com/openedx.png" }, - "logo_image": "http://example.com/static/images/default-theme/logo.png", + "logo_image": "http://example.com/static/images/logo.png", "copyright": "EdX, Open edX, and the edX and Open edX logos are \ registered trademarks or trademarks of edX Inc." } diff --git a/lms/djangoapps/courseware/tests/test_comp_theming.py b/lms/djangoapps/courseware/tests/test_comprehensive_theming.py similarity index 85% rename from lms/djangoapps/courseware/tests/test_comp_theming.py rename to lms/djangoapps/courseware/tests/test_comprehensive_theming.py index 65803687b41..ec734fec1dd 100644 --- a/lms/djangoapps/courseware/tests/test_comp_theming.py +++ b/lms/djangoapps/courseware/tests/test_comprehensive_theming.py @@ -1,13 +1,12 @@ """Tests of comprehensive theming.""" -import unittest from django.conf import settings from django.test import TestCase from path import path # pylint: disable=no-name-in-module from django.contrib import staticfiles -from openedx.core.djangoapps.theming.test_util import with_comp_theme +from openedx.core.djangoapps.theming.test_util import with_comprehensive_theme from openedx.core.lib.tempdir import mkdtemp_clean @@ -20,8 +19,7 @@ class TestComprehensiveTheming(TestCase): # Clear the internal staticfiles caches, to get test isolation. staticfiles.finders.get_finder.cache_clear() - @with_comp_theme(settings.REPO_ROOT / 'themes/red-theme') - @unittest.skip("Disabled until we can release theming to production") + @with_comprehensive_theme(settings.REPO_ROOT / 'themes/red-theme') def test_red_footer(self): resp = self.client.get('/') self.assertEqual(resp.status_code, 200) @@ -42,7 +40,7 @@ class TestComprehensiveTheming(TestCase): with open(template_dir / "footer.html", "w") as footer: footer.write("<footer>TEMPORARY THEME</footer>") - @with_comp_theme(tmp_theme) + @with_comprehensive_theme(tmp_theme) def do_the_test(self): """A function to do the work so we can use the decorator.""" resp = self.client.get('/') @@ -56,7 +54,7 @@ class TestComprehensiveTheming(TestCase): before_finders = list(settings.STATICFILES_FINDERS) before_dirs = list(settings.STATICFILES_DIRS) - @with_comp_theme(settings.REPO_ROOT / 'themes/red-theme') + @with_comprehensive_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) @@ -65,12 +63,11 @@ class TestComprehensiveTheming(TestCase): do_the_test(self) - @unittest.skip("Disabled until we can release theming to production") 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') + @with_comprehensive_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') diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 45a614f255f..5fcaa2f4f1d 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -237,7 +237,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)) +COMPREHENSIVE_THEME_DIR = path(ENV_TOKENS.get('COMPREHENSIVE_THEME_DIR', COMPREHENSIVE_THEME_DIR)) # Marketing link overrides MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {})) @@ -701,10 +701,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' -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 diff --git a/lms/envs/common.py b/lms/envs/common.py index 176659cf0a4..e805a586ca4 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -426,7 +426,7 @@ COURSES_ROOT = ENV_ROOT / "data" DATA_DIR = COURSES_ROOT # comprehensive theming system -COMP_THEME_DIR = "" +COMPREHENSIVE_THEME_DIR = "" # TODO: Remove the rest of the sys.path modification here and in cms/envs/common.py sys.path.append(REPO_ROOT) @@ -1087,7 +1087,7 @@ FOOTER_OPENEDX_LOGO_IMAGE = "https://files.edx.org/openedx-logos/edx-openedx-log # This is just a placeholder image. # Site operators can customize this with their organization's image. -FOOTER_ORGANIZATION_IMAGE = "images/default-theme/logo.png" +FOOTER_ORGANIZATION_IMAGE = "images/logo.png" # These are referred to both by the Django asset pipeline # AND by the branding footer API, which needs to decide which @@ -1197,12 +1197,12 @@ X_FRAME_OPTIONS = 'ALLOW' PIPELINE_ENABLED = True -# Process static files using RequireJS Optimizer -STATICFILES_STORAGE = 'openedx.core.lib.django_require.staticstorage.OptimizedCachedRequireJsStorage' +STATICFILES_STORAGE = 'openedx.core.storage.ProductionStorage' # List of finder classes that know how to find static files in various locations. # Note: the pipeline finder is included to be able to discover optimized files STATICFILES_FINDERS = [ + 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', 'pipeline.finders.PipelineFinder', @@ -2550,7 +2550,7 @@ PDF_RECEIPT_TAX_ID_LABEL = 'Tax ID' PDF_RECEIPT_LOGO_PATH = PROJECT_ROOT + '/static/images/openedx-logo-tag.png' # Height of the Logo in mm PDF_RECEIPT_LOGO_HEIGHT_MM = 12 -PDF_RECEIPT_COBRAND_LOGO_PATH = PROJECT_ROOT + '/static/images/default-theme/logo.png' +PDF_RECEIPT_COBRAND_LOGO_PATH = PROJECT_ROOT + '/static/images/logo.png' # Height of the Co-brand Logo in mm PDF_RECEIPT_COBRAND_LOGO_HEIGHT_MM = 12 @@ -2647,7 +2647,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 diff --git a/lms/envs/devstack.py b/lms/envs/devstack.py index 897768757b5..154e000656e 100644 --- a/lms/envs/devstack.py +++ b/lms/envs/devstack.py @@ -86,12 +86,12 @@ def should_show_debug_toolbar(_): ########################### PIPELINE ################################# -# Skip packaging and optimization in development PIPELINE_ENABLED = False -STATICFILES_STORAGE = 'pipeline.storage.NonPackagingPipelineStorage' +STATICFILES_STORAGE = 'openedx.core.storage.DevelopmentStorage' # Revert to the default set of finders as we don't want the production pipeline STATICFILES_FINDERS = [ + 'openedx.core.djangoapps.theming.finders.ComprehensiveThemeFinder', 'django.contrib.staticfiles.finders.FileSystemFinder', 'django.contrib.staticfiles.finders.AppDirectoriesFinder', ] diff --git a/lms/static/images/default-theme/logo-large.png b/lms/static/images/logo-large.png similarity index 100% rename from lms/static/images/default-theme/logo-large.png rename to lms/static/images/logo-large.png diff --git a/lms/static/images/default-theme/logo.png b/lms/static/images/logo.png similarity index 100% rename from lms/static/images/default-theme/logo.png rename to lms/static/images/logo.png diff --git a/lms/static/images/default-theme/default-profile_120.png b/lms/static/images/profiles/default_120.png similarity index 100% rename from lms/static/images/default-theme/default-profile_120.png rename to lms/static/images/profiles/default_120.png diff --git a/lms/static/images/default-theme/default-profile_30.png b/lms/static/images/profiles/default_30.png similarity index 100% rename from lms/static/images/default-theme/default-profile_30.png rename to lms/static/images/profiles/default_30.png diff --git a/lms/static/images/default-theme/default-profile_50.png b/lms/static/images/profiles/default_50.png similarity index 100% rename from lms/static/images/default-theme/default-profile_50.png rename to lms/static/images/profiles/default_50.png diff --git a/lms/static/images/default-theme/default-profile_500.png b/lms/static/images/profiles/default_500.png similarity index 100% rename from lms/static/images/default-theme/default-profile_500.png rename to lms/static/images/profiles/default_500.png diff --git a/lms/templates/courseware/course_about.html b/lms/templates/courseware/course_about.html index a41bd48446d..60b862199a6 100644 --- a/lms/templates/courseware/course_about.html +++ b/lms/templates/courseware/course_about.html @@ -10,18 +10,6 @@ from edxmako.shortcuts import marketing_link <%inherit file="../main.html" /> <%block name="headextra"> - - <% - if self.theme_enabled(): - google_analytics_file = u'../{ga}'.format( - ga=microsite.get_value('google_analytics_file', 'theme-google-analytics.html') - ) - else: - google_analytics_file = '../google_analytics.html' - %> - - <%include file="${google_analytics_file}" /> - ## OG (Open Graph) title and description added below to give social media info to display ## (https://developers.facebook.com/docs/opengraph/howtos/maximizing-distribution-media-content#tags) <meta property="og:title" content="${get_course_about_section(request, course, 'title')}" /> diff --git a/lms/templates/google_analytics.html b/lms/templates/google_analytics.html deleted file mode 100644 index 44ef9e76d63..00000000000 --- a/lms/templates/google_analytics.html +++ /dev/null @@ -1,13 +0,0 @@ -% if settings.GOOGLE_ANALYTICS_ACCOUNT: - <script type="text/javascript"> - var _gaq = _gaq || []; - _gaq.push(['_setAccount', '${settings.GOOGLE_ANALYTICS_ACCOUNT}']); - _gaq.push(['_trackPageview']); - - (function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); - })(); - </script> -% endif diff --git a/lms/templates/google_tag_manager.html b/lms/templates/google_tag_manager.html deleted file mode 100644 index b3c6892d0ee..00000000000 --- a/lms/templates/google_tag_manager.html +++ /dev/null @@ -1,4 +0,0 @@ -<%doc> - Yet, installing google tag manager for microsite(s). - So intentionally left it blank -</%doc> diff --git a/lms/templates/header.html b/lms/templates/header.html index b3732c39128..00526f0ef6d 100644 --- a/lms/templates/header.html +++ b/lms/templates/header.html @@ -1,3 +1,4 @@ +## mako <%namespace name='static' file='static_content.html'/> <%! from microsite_configuration import microsite @@ -5,13 +6,8 @@ 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: diff --git a/lms/templates/main.html b/lms/templates/main.html index 1e450583ece..4af999ae834 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -61,7 +61,17 @@ 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'/> + ## We could do <%static:css group='style-main'/>, but that's only useful + ## if the group contains multiple files, and the 'style-main' group doesn't. + ## Instead, we'll construct this <link> element manually, to improve clarity. + ## When nothing in the system is referencing the 'style-main' group, it can + ## be removed from the environment file. + <% + 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'/> @@ -84,31 +94,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') - google_tag_manager_file = microsite.get_template_path('google_tag_manager.html') -%> - - % if header_extra_file: - <%include file="${header_extra_file}" /> - % endif + <%static:optional_include_mako file="head-extra.html" with_microsite="True" /> <%include file="widgets/optimizely.html" /> <%include file="widgets/segment-io.html" /> @@ -116,16 +102,25 @@ 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)}" /> +<% ga_acct = microsite.get_value("GOOGLE_ANALYTICS_ACCOUNT", settings.GOOGLE_ANALYTICS_ACCOUNT) %> +% if ga_acct: + <script type="text/javascript"> + var _gaq = _gaq || []; + _gaq.push(['_setAccount', '${ga_acct}']); + _gaq.push(['_trackPageview']); + + (function() { + var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; + ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; + var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); + })(); + </script> % endif </head> <body class="${static.dir_rtl()} <%block name='bodyclass'/> lang_${LANGUAGE_CODE}"> -<%include file="${google_tag_manager_file}" /> +<%static:optional_include_mako file="body-initial.html" with_microsite="True" /> <div id="page-prompt"></div> % if not disable_window_wrap: <div class="window-wrap" dir="${static.dir_rtl()}"> @@ -133,7 +128,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="${microsite.get_template_path('header.html')}" /> % endif <div class="content-wrapper" id="content"> @@ -142,16 +137,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: @@ -162,6 +148,7 @@ from branding import api as branding_api <%include file="widgets/segment-io-footer.html" /> <script type="text/javascript" src="${static.url('js/vendor/noreferrer.js')}" charset="utf-8"></script> <script type="text/javascript" src="${static.url('js/utils/navigation.js')}" charset="utf-8"></script> + <%static:optional_include_mako file="body-extra.html" with_microsite="True" /> </body> </html> diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html index c8d2aac2d2d..a7245f0cef2 100644 --- a/lms/templates/main_django.html +++ b/lms/templates/main_django.html @@ -1,8 +1,6 @@ <!DOCTYPE html> -{% load pipeline %} -{% load sekizai_tags i18n microsite %} +{% load sekizai_tags i18n microsite pipeline optional_include %} {% load url from future %} -{% load staticfiles %} <html lang="{{LANGUAGE_CODE}}"> <head> <meta charset="UTF-8"> @@ -21,7 +19,7 @@ {% block headextra %}{% endblock %} {% render_block "css" %} - {% microsite_css_overrides_file %} + {% optional_include "head-extra.html" %} <meta name="path_prefix" content="{{EDX_ROOT_URL}}"> </head> @@ -30,23 +28,15 @@ <div class="window-wrap" dir="${static.dir_rtl()}"> <a class="nav-skip" href="{% block nav_skip %}#content{% endblock %}">{% trans "Skip to main content" %}</a> {% with course=request.course %} - {% if IS_EDX_DOMAIN %} - {% include "navigation-edx.html" %} - {% else %} - {% include "navigation.html" %} - {% endif %} + {% include "header.html" %} {% endwith %} <div class="content-wrapper" id="content"> {% block body %}{% endblock %} {% block bodyextra %}{% endblock %} </div> - {% if IS_REQUEST_IN_MICROSITE %} - {# For now we don't support overriden Django templates in microsites. Leave footer blank for now which is better than saying Edx.#} - {% elif IS_EDX_DOMAIN %} - {% include "footer-edx-v3.html" %} - {% else %} + {% with course=request.course %} {% include "footer.html" %} - {% endif %} + {% endwith %} </div> diff --git a/lms/templates/themable-footer.html b/lms/templates/themable-footer.html new file mode 100644 index 00000000000..09893ad4e80 --- /dev/null +++ b/lms/templates/themable-footer.html @@ -0,0 +1,20 @@ +## mako +<%! +from microsite_configuration import microsite +%> +<% +theme_enabled = settings.FEATURES.get("USE_CUSTOM_THEME", False) +is_microsite = microsite.is_request_in_microsite() +%> + +## This file only exists as an additional layer of indirection to preserve +## backwards compatibility with microsites and Stanford theming +## (as much as possible). If you are writing your own theme using the +## "comprehensive theming" system, do NOT override this file. You should +## override "footer.html" instead. + +% if theme_enabled and not is_microsite: + <%include file="theme-footer.html" /> +% else: + <%include file="${microsite.get_template_path('footer.html')}" /> +% endif diff --git a/openedx/core/djangoapps/theming/core.py b/openedx/core/djangoapps/theming/core.py index 45b8e33c1a1..bd2e4b5d0d6 100644 --- a/openedx/core/djangoapps/theming/core.py +++ b/openedx/core/djangoapps/theming/core.py @@ -1,6 +1,7 @@ """ Core logic for Comprehensive Theming. """ +from path import Path from django.conf import settings @@ -28,21 +29,26 @@ def comprehensive_theme_changes(theme_dir): 'settings': {}, 'mako_paths': [], } + root = Path(settings.PROJECT_ROOT) + if root.name == "": + root = root.parent - templates_dir = theme_dir / "lms" / "templates" + component_dir = theme_dir / root.name + + templates_dir = component_dir / "templates" if templates_dir.isdir(): changes['settings']['TEMPLATE_DIRS'] = [templates_dir] + settings.DEFAULT_TEMPLATE_ENGINE['DIRS'] changes['mako_paths'].append(templates_dir) - staticfiles_dir = theme_dir / "lms" / "static" + staticfiles_dir = component_dir / "static" if staticfiles_dir.isdir(): changes['settings']['STATICFILES_DIRS'] = [staticfiles_dir] + settings.STATICFILES_DIRS - locale_dir = theme_dir / "lms" / "conf" / "locale" + locale_dir = component_dir / "conf" / "locale" if locale_dir.isdir(): changes['settings']['LOCALE_PATHS'] = [locale_dir] + settings.LOCALE_PATHS - favicon = theme_dir / "lms" / "static" / "images" / "favicon.ico" + favicon = component_dir / "static" / "images" / "favicon.ico" if favicon.isfile(): changes['settings']['FAVICON_PATH'] = str(favicon) diff --git a/openedx/core/djangoapps/theming/finders.py b/openedx/core/djangoapps/theming/finders.py new file mode 100644 index 00000000000..cbf4366f5a6 --- /dev/null +++ b/openedx/core/djangoapps/theming/finders.py @@ -0,0 +1,79 @@ +""" +Static file finders for Django. +https://docs.djangoproject.com/en/1.8/ref/settings/#std:setting-STATICFILES_FINDERS +Yes, this interface is private and undocumented, but we need to access it anyway. + +In order to deploy Open edX in production, it's important to be able to collect +and process static assets: images, CSS, JS, fonts, etc. Django's collectstatic +system is the accepted way to do that in Django-based projects, but that system +doesn't handle every kind of collection and processing that web developers need. +Other open source projects like `Django-Pipeline`_ and `Django-Require`_ hook +into Django's collectstatic system to provide features like minification, +compression, Sass pre-processing, and require.js optimization for assets before +they are pushed to production. To make sure that themed assets are collected +and served by the system (in addition to core assets), we need to extend this +interface, as well. + +.. _Django-Pipeline: http://django-pipeline.readthedocs.org/ +.. _Django-Require: https://github.com/etianen/django-require +""" +from path import Path +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.contrib.staticfiles import utils +from django.contrib.staticfiles.finders import BaseFinder +from openedx.core.djangoapps.theming.storage import CachedComprehensiveThemingStorage + + +class ComprehensiveThemeFinder(BaseFinder): + """ + A static files finder that searches the active comprehensive theme + for static files. If the ``COMPREHENSIVE_THEME_DIR`` setting is unset, + or the ``COMPREHENSIVE_THEME_DIR`` does not exist on the file system, + this finder will never find any files. + """ + def __init__(self, *args, **kwargs): + super(ComprehensiveThemeFinder, self).__init__(*args, **kwargs) + + theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "") + if not theme_dir: + self.storage = None + return + + if not isinstance(theme_dir, basestring): + raise ImproperlyConfigured("Your COMPREHENSIVE_THEME_DIR setting must be a string") + + root = Path(settings.PROJECT_ROOT) + if root.name == "": + root = root.parent + + component_dir = Path(theme_dir) / root.name + static_dir = component_dir / "static" + self.storage = CachedComprehensiveThemingStorage(location=static_dir) + + def find(self, path, all=False): # pylint: disable=redefined-builtin + """ + Looks for files in the default file storage, if it's local. + """ + if not self.storage: + return [] + + if path.startswith(self.storage.prefix): + # strip the prefix + path = path[len(self.storage.prefix):] + + if self.storage.exists(path): + match = self.storage.path(path) + if all: + match = [match] + return match + + return [] + + def list(self, ignore_patterns): + """ + List all files of the storage. + """ + if self.storage and self.storage.exists(''): + for path in utils.get_files(self.storage, ignore_patterns): + yield path, self.storage diff --git a/openedx/core/djangoapps/theming/startup.py b/openedx/core/djangoapps/theming/startup.py index ae217804b73..8642a765552 100644 --- a/openedx/core/djangoapps/theming/startup.py +++ b/openedx/core/djangoapps/theming/startup.py @@ -10,5 +10,5 @@ from .core import enable_comprehensive_theme def run(): """Enable comprehensive theming, if we should.""" - if settings.COMP_THEME_DIR: - enable_comprehensive_theme(theme_dir=path(settings.COMP_THEME_DIR)) + if settings.COMPREHENSIVE_THEME_DIR: + enable_comprehensive_theme(theme_dir=path(settings.COMPREHENSIVE_THEME_DIR)) diff --git a/openedx/core/djangoapps/theming/storage.py b/openedx/core/djangoapps/theming/storage.py new file mode 100644 index 00000000000..3fb5311b5a6 --- /dev/null +++ b/openedx/core/djangoapps/theming/storage.py @@ -0,0 +1,88 @@ +""" +Comprehensive Theming support for Django's collectstatic functionality. +See https://docs.djangoproject.com/en/1.8/ref/contrib/staticfiles/ +""" +from path import Path +import os.path +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.contrib.staticfiles.storage import StaticFilesStorage, CachedFilesMixin +from django.utils._os import safe_join + + +class ComprehensiveThemingAwareMixin(object): + """ + Mixin for Django storage system to make it aware of the currently-active + comprehensive theme, so that it can generate theme-scoped URLs for themed + static assets. + """ + def __init__(self, *args, **kwargs): + super(ComprehensiveThemingAwareMixin, self).__init__(*args, **kwargs) + theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "") + if not theme_dir: + self.theme_location = None + return + + if not isinstance(theme_dir, basestring): + raise ImproperlyConfigured("Your COMPREHENSIVE_THEME_DIR setting must be a string") + + root = Path(settings.PROJECT_ROOT) + if root.name == "": + root = root.parent + + component_dir = Path(theme_dir) / root.name + self.theme_location = component_dir / "static" + + @property + def prefix(self): + """ + This is used by the ComprehensiveThemeFinder in the collection step. + """ + theme_dir = getattr(settings, "COMPREHENSIVE_THEME_DIR", "") + if not theme_dir: + return None + theme_name = os.path.basename(os.path.normpath(theme_dir)) + return "themes/{name}/".format(name=theme_name) + + def themed(self, name): + """ + Given a name, return a boolean indicating whether that name exists + as a themed asset in the comprehensive theme. + """ + # Nothing can be themed if we don't have a theme location. + if not self.theme_location: + return False + + path = safe_join(self.theme_location, name) + return os.path.exists(path) + + def path(self, name): + """ + Get the path to the real asset on disk + """ + if self.themed(name): + base = self.theme_location + else: + base = self.location + path = safe_join(base, name) + return os.path.normpath(path) + + def url(self, name, *args, **kwargs): + """ + Add the theme prefix to the asset URL + """ + if self.themed(name): + name = self.prefix + name + return super(ComprehensiveThemingAwareMixin, self).url(name, *args, **kwargs) + + +class CachedComprehensiveThemingStorage( + ComprehensiveThemingAwareMixin, + CachedFilesMixin, + StaticFilesStorage +): + """ + Used by the ComprehensiveThemeFinder class. Mixes in support for cached + files and comprehensive theming in static files. + """ + pass diff --git a/openedx/core/djangoapps/theming/templatetags/__init__.py b/openedx/core/djangoapps/theming/templatetags/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/openedx/core/djangoapps/theming/templatetags/optional_include.py b/openedx/core/djangoapps/theming/templatetags/optional_include.py new file mode 100644 index 00000000000..77e6ae2ffb8 --- /dev/null +++ b/openedx/core/djangoapps/theming/templatetags/optional_include.py @@ -0,0 +1,77 @@ +""" +The functions in this module are based on the contents of +https://github.com/django/django/blob/1.8.5/django/template/loader_tags.py -- +specifically, the do_include function. It has been modified as little as +possible, in order to match the behavior of the {% include %} template tag, +except for making it optional. +""" +# Because we want to match the original loader_tags.py file as closely as +# possible, we should disable pylint so it doesn't complain about the violations +# that are already in that file +# pylint: skip-file +from django.template.base import ( + TemplateSyntaxError, Library, token_kwargs, TemplateDoesNotExist +) +from django.template.loader_tags import IncludeNode + +register = Library() + + +class OptionalIncludeNode(IncludeNode): + def render(self, context): + try: + return super(OptionalIncludeNode, self).render(context) + except TemplateDoesNotExist: + return '' + + +@register.tag('optional_include') +def do_include(parser, token): + """ + Loads a template and renders it with the current context, if it exists. + You can pass additional context using keyword arguments. + + Example:: + + {% optional_include "foo/some_include" %} + {% optional_include "foo/some_include" with bar="BAZZ!" baz="BING!" %} + + Use the ``only`` argument to exclude the current context when rendering + the included template:: + + {% optional_include "foo/some_include" only %} + {% optional_include "foo/some_include" with bar="1" only %} + """ + bits = token.split_contents() + if len(bits) < 2: + msg = ( + "%r tag takes at least one argument: the name of the template " + "to be optionally included." + ) % bits[0] + raise TemplateSyntaxError(msg) + options = {} + remaining_bits = bits[2:] + while remaining_bits: + option = remaining_bits.pop(0) + if option in options: + raise TemplateSyntaxError('The %r option was specified more ' + 'than once.' % option) + if option == 'with': + value = token_kwargs(remaining_bits, parser, support_legacy=False) + if not value: + raise TemplateSyntaxError('"with" in %r tag needs at least ' + 'one keyword argument.' % bits[0]) + elif option == 'only': + value = True + else: + raise TemplateSyntaxError('Unknown argument for %r tag: %r.' % + (bits[0], option)) + options[option] = value + isolated_context = options.get('only', False) + namemap = options.get('with', {}) + node = OptionalIncludeNode( + parser.compile_filter(bits[1]), + extra_context=namemap, + isolated_context=isolated_context, + ) + return node diff --git a/openedx/core/djangoapps/theming/test_util.py b/openedx/core/djangoapps/theming/test_util.py index 3a8dc011acb..63220f94a74 100644 --- a/openedx/core/djangoapps/theming/test_util.py +++ b/openedx/core/djangoapps/theming/test_util.py @@ -16,7 +16,7 @@ import edxmako from .core import comprehensive_theme_changes -def with_comp_theme(theme_dir): +def with_comprehensive_theme(theme_dir): """ A decorator to run a test with a particular comprehensive theme. @@ -34,7 +34,7 @@ def with_comp_theme(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 override_settings(COMPREHENSIVE_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) @@ -60,8 +60,8 @@ def with_is_edx_domain(is_edx_domain): # 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 @with_comprehensive_theme to the func. + func = with_comprehensive_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) diff --git a/openedx/core/storage.py b/openedx/core/storage.py new file mode 100644 index 00000000000..6607b27c0c0 --- /dev/null +++ b/openedx/core/storage.py @@ -0,0 +1,35 @@ +""" +Django storage backends for Open edX. +""" +from django.contrib.staticfiles.storage import StaticFilesStorage, CachedFilesMixin +from pipeline.storage import PipelineMixin, NonPackagingMixin +from require.storage import OptimizedFilesMixin +from openedx.core.djangoapps.theming.storage import ComprehensiveThemingAwareMixin + + +class ProductionStorage( + ComprehensiveThemingAwareMixin, + OptimizedFilesMixin, + PipelineMixin, + CachedFilesMixin, + StaticFilesStorage +): + """ + This class combines Django's StaticFilesStorage class with several mixins + that provide additional functionality. We use this version on production. + """ + pass + + +class DevelopmentStorage( + ComprehensiveThemingAwareMixin, + NonPackagingMixin, + PipelineMixin, + StaticFilesStorage +): + """ + This class combines Django's StaticFilesStorage class with several mixins + that provide additional functionality. We use this version for development, + so that we can skip packaging and optimization. + """ + pass diff --git a/pavelib/assets.py b/pavelib/assets.py index eac7756b31f..21947628dcb 100644 --- a/pavelib/assets.py +++ b/pavelib/assets.py @@ -45,18 +45,18 @@ def configure_paths(): css_dir.mkdir_p() SASS_DIRS.append(sass_dir) - if edxapp_env.env_tokens.get("COMP_THEME_DIR", ""): - theme_dir = path(edxapp_env.env_tokens["COMP_THEME_DIR"]) + if edxapp_env.env_tokens.get("COMPREHENSIVE_THEME_DIR", ""): + theme_dir = path(edxapp_env.env_tokens["COMPREHENSIVE_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) + cms_sass = theme_dir / "cms" / "static" / "sass" + cms_css = theme_dir / "cms" / "static" / "css" + if cms_sass.isdir(): + cms_css.mkdir_p() + SASS_DIRS.append(cms_sass) configure_paths() diff --git a/themes/README.rst b/themes/README.rst index 88fe30dc179..90f42b62154 100644 --- a/themes/README.rst +++ b/themes/README.rst @@ -90,6 +90,28 @@ 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. +Template Names +============== + +Here are the list of template names that you *should* use in your comprehensive +theme (so far): + +* ``header.html`` +* ``footer.html`` + +You should **not** use the following names in your comprehensive theme: + +* ``themable-footer.html`` + +If you look at the ``main.html`` template file, you will notice that it includes +``header.html`` and ``themable-footer.html``, rather than ``footer.html``. +You might be inclined to override ``themable-footer.html`` as a result. DO NOT +DO THIS. ``themable-footer.html`` is an additional layer of indirection that +is necessary to avoid breaking microsites, which also refers to a file named +``footer.html``. The goal is to eventually make comprehensive theming do +everything that microsites does now, and then deprecate and remove microsites +from the codebase. At that point, the ``themable-footer.html`` file will go +away, since the additional layer of indirection will no longer be necessary. Installing your theme --------------------- @@ -101,9 +123,9 @@ directory. There are two ways to do this. #. As the vagrant user, edit (or create) /edx/app/edx_ansible/server-vars.yml to add the - ``edxapp_comp_theme_dir`` value:: + ``edxapp_comprehensive_theme_dir`` value:: - edxapp_comp_theme_dir: '/full/path/to/my-theme' + edxapp_comprehensive_theme_dir: '/full/path/to/my-theme' #. Run the update script:: @@ -111,13 +133,18 @@ directory. There are two ways to do this. $ sudo /edx/bin/update edx-platform HEAD #. Otherwise, edit the /edx/app/edxapp/lms.env.json file to add the - ``COMP_THEME_DIR`` value:: + ``COMPREHENSIVE_THEME_DIR`` value:: - "COMP_THEME_DIR": "/full/path/to/my-theme", + "COMPREHENSIVE_THEME_DIR": "/full/path/to/my-theme", Restart your site. Your changes should now be visible. +Comprehensive Theming +===================== +* The ``PROFILE_IMAGE_DEFAULT_FILENAME`` Django setting is now ignored. + + "Stanford" theming ================== @@ -154,3 +181,54 @@ name in the ``@import`` line. Run the ``update_assets`` command to recompile the theme:: $ paver update_assets lms --settings=aws + +Microsites +========== + +If you want to continue using the "Microsites" theming system, there are a few +changes you'll need to make. A few templates have been renamed, or folded into +other templates: + +* ``header_extra.html`` has been renamed to ``head-extra.html``. This file + was always inserted into the ``<head>`` element of the page, rather than + the header of the ``<body>`` element, so this change makes the name more + accurate. + +* ``google_analytics.html`` has been removed. The contents of this template + can and should be added to the ``head-extra.html`` template. + +* ``google_tag_manager.html`` has been renamed to ``body-initial.html``. + +In addition, there are some other changes you'll need to make: + +* The ``google_analytics_file`` config value is now ignored. If your Open edX + installation has a Google Analytics account ID set, the Google Analytics + JavaScript will be included automatically on your site using that account ID. + You can set this account ID either using the "GOOGLE_ANALYTICS_ACCOUNT" value + in the Django settings, or by setting the newly-added "GOOGLE_ANALYTICS_ACCOUNT" + config value in your microsite configuration. + +* If you don't want the Google Analytics JavaScript to be output at all in your + microsite, set the "GOOGLE_ANALYTICS_ACCOUNT" config value to the empty string. + If you want to customize the way that Google Analytics is loaded, set the + "GOOGLE_ANALYTICS_ACCOUNT" config value to the empty string, and then load + Google Analytics yourself (with whatever customizations you want) in your + ``head-extra.html`` template. + +* The ``css_overrides_file`` config value is now ignored. To add a CSS override + file to your microsite, create a ``head-extra.html`` template with the + following content: + + .. code-block:: mako + + <%namespace name='static' file='../../static_content.html'/> + <%! from microsite_configuration import 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 you already have a ``head-extra.html`` template, you can modify it to + output this ``<link rel="stylesheet">`` tag, in addition to whatever else you + already have in that template. diff --git a/cms/static/images/edx-theme/edx-studio-logo.png b/themes/edx.org/cms/static/images/studio-logo.png similarity index 100% rename from cms/static/images/edx-theme/edx-studio-logo.png rename to themes/edx.org/cms/static/images/studio-logo.png diff --git a/lms/static/images/edx-theme/edx-logo-bw.png b/themes/edx.org/lms/static/images/logo-large.png similarity index 100% rename from lms/static/images/edx-theme/edx-logo-bw.png rename to themes/edx.org/lms/static/images/logo-large.png diff --git a/lms/static/images/edx-theme/edx-header-logo.png b/themes/edx.org/lms/static/images/logo.png similarity index 100% rename from lms/static/images/edx-theme/edx-header-logo.png rename to themes/edx.org/lms/static/images/logo.png diff --git a/lms/static/images/edx-theme/default_120.png b/themes/edx.org/lms/static/images/profiles/default_120.png similarity index 100% rename from lms/static/images/edx-theme/default_120.png rename to themes/edx.org/lms/static/images/profiles/default_120.png diff --git a/lms/static/images/edx-theme/default_30.png b/themes/edx.org/lms/static/images/profiles/default_30.png similarity index 100% rename from lms/static/images/edx-theme/default_30.png rename to themes/edx.org/lms/static/images/profiles/default_30.png diff --git a/lms/static/images/edx-theme/default_50.png b/themes/edx.org/lms/static/images/profiles/default_50.png similarity index 100% rename from lms/static/images/edx-theme/default_50.png rename to themes/edx.org/lms/static/images/profiles/default_50.png diff --git a/lms/static/images/edx-theme/default_500.png b/themes/edx.org/lms/static/images/profiles/default_500.png similarity index 100% rename from lms/static/images/edx-theme/default_500.png rename to themes/edx.org/lms/static/images/profiles/default_500.png diff --git a/themes/edx.org/lms/templates/footer.html b/themes/edx.org/lms/templates/footer.html new file mode 100644 index 00000000000..992d43744e6 --- /dev/null +++ b/themes/edx.org/lms/templates/footer.html @@ -0,0 +1,83 @@ +## 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="${footer['logo_image']}"> + </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> + <span class="sr">${link['action']}</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" /> + <%include file="widgets/segment-io-footer.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 + diff --git a/themes/edx.org/lms/templates/header.html b/themes/edx.org/lms/templates/header.html new file mode 100644 index 00000000000..f5790b4319a --- /dev/null +++ b/themes/edx.org/lms/templates/header.html @@ -0,0 +1,142 @@ +## 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 +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')}" itemprop="url"> + <%block name="navigation_logo"> + <img src="${static.url("images/logo.png")}" alt="${_("{platform_name} Home Page").format(platform_name=platform_name())}" itemprop="logo" /> + </%block> + </a> + </h1> + + % if course and not disable_courseware_header: + <h2 class="course-header"> + <span class="provider">${course.display_org_with_default | h}:</span> + <span class="course-number">${course.display_number_with_default | h}</span> + <span class="course-name">${course.display_name_with_default}</span> + </h2> + % endif + + % if user.is_authenticated(): + % if not course or disable_courseware_header: + % if not nav_hidden: + <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 + % endif + + <ul class="user"> + <li class="primary"> + <a href="${reverse('dashboard')}" class="user-link"> + <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> ▾</a> + <ul class="dropdown-menu" aria-label="More Options" role="menu"> + <%block name="navigation_dropdown_menu_links" > + <li><a href="${reverse('dashboard')}">${_("Dashboard")}</a></li> + <li><a href="${reverse('learner_profile', kwargs={'username': user.username})}">${_("Profile")}</a></li> + <li><a href="${reverse('account_settings')}">${_("Account")}</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="Account" class="nav-account-management"> + <div class="right nav-courseware"> + <div 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 + </div> + % 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-02"> + <a class="cta cta-register nav-courseware-button" href="${reverse('course-specific-register', args=[course.id.to_deprecated_string()])}">${_("Register")}</a> + </div> + % else: + <div class="nav-courseware-02"> + <a class="cta cta-register nav-courseware-button" href="/register">${_("Register")}</a> + </div> + % endif + % endif + </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 + +<%include file="help_modal.html"/> -- GitLab