From 6af5fc145296a4cd3bf1c4b93df86a84967219f3 Mon Sep 17 00:00:00 2001 From: Will Daly Date: Mon, 11 May 2015 16:01:35 +0500 Subject: [PATCH] ECOM-1339 Branding API footer Serve branded footer JSON/HTML/CSS/JS from an API endpoint in the branding app. Refactor OpenEdX and EdX.org footer templates to use the Python version of the API, ensuring that the API values are consistent with the footer included in main.html. Detailed changes: * Added footer API end-point to the branding app. * Footer API allows the language to be set with querystring parameters. * Footer API allows showing/hiding of the OpenEdX logo using querystring parameters. * Deprecate ENABLE_FOOTER_V3 in favor of the branding API configuration flag. * Move no referrer script into main.html from the edx footer template. * Rename rwd_header_footer.js to rwd_header.js * Cache API responses. Authors: Awais Qureshi, Aamir Khan, Will Daly --- .gitignore | 5 +- .../js/spec_helpers/rwd_header_footer.js | 99 ------ .../{rwd_header_footer.js => rwd_header.js} | 3 +- conf/locale/eo/LC_MESSAGES/django.mo | Bin 929914 -> 930912 bytes conf/locale/eo/LC_MESSAGES/django.po | 191 ++++++------ lms/djangoapps/branding/admin.py | 7 +- lms/djangoapps/branding/api.py | 291 ++++++++++++++++++ lms/djangoapps/branding/api_urls.py | 15 + lms/djangoapps/branding/context_processors.py | 10 + .../0002_auto__add_brandingapiconfig.py | 80 +++++ lms/djangoapps/branding/models.py | 11 + lms/djangoapps/branding/tests/test_views.py | 214 +++++++++++++ lms/djangoapps/branding/views.py | 213 ++++++++++++- .../courseware/features/homepage.feature | 2 +- lms/djangoapps/courseware/tests/test_about.py | 6 +- .../courseware/tests/test_footer.py | 16 +- lms/envs/aws.py | 7 + lms/envs/common.py | 123 +++++--- lms/static/js/footer-edx.js | 33 +- lms/static/sass/_build-footer-edx.scss | 19 ++ lms/static/sass/_build-lms.scss | 2 +- lms/static/sass/base/_variables-ltr.scss | 7 + lms/static/sass/base/_variables-rtl.scss | 7 + lms/static/sass/lms-footer-edx-rtl.scss | 10 + lms/static/sass/lms-footer-edx.scss | 10 + ...rtl.scss.mako => lms-footer-rtl.scss.mako} | 22 +- ...ter-edx.scss.mako => lms-footer.scss.mako} | 20 +- lms/static/sass/lms-main-rtl.scss.mako | 1 + lms/static/sass/lms-main.scss.mako | 1 + lms/static/sass/shared/_footer-edx.scss | 2 +- lms/static/sass/shared/_footer.scss | 4 +- lms/templates/commerce/checkout_receipt.html | 2 +- lms/templates/footer-edx-v2.html | 3 +- lms/templates/footer-edx-v3.html | 124 ++++---- lms/templates/footer.html | 131 ++++---- lms/templates/main.html | 26 +- lms/templates/main_django.html | 16 +- .../verify_student/_verification_header.html | 2 +- .../verify_student/incourse_reverify.html | 2 +- .../verify_student/pay_and_verify.html | 2 +- lms/urls.py | 2 + 41 files changed, 1254 insertions(+), 487 deletions(-) delete mode 100644 common/static/js/spec_helpers/rwd_header_footer.js rename common/static/js/utils/{rwd_header_footer.js => rwd_header.js} (93%) create mode 100644 lms/djangoapps/branding/api.py create mode 100644 lms/djangoapps/branding/api_urls.py create mode 100644 lms/djangoapps/branding/context_processors.py create mode 100644 lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py create mode 100644 lms/djangoapps/branding/tests/test_views.py create mode 100644 lms/static/sass/_build-footer-edx.scss create mode 100644 lms/static/sass/base/_variables-ltr.scss create mode 100644 lms/static/sass/base/_variables-rtl.scss create mode 100644 lms/static/sass/lms-footer-edx-rtl.scss create mode 100644 lms/static/sass/lms-footer-edx.scss rename lms/static/sass/{lms-footer-edx-rtl.scss.mako => lms-footer-rtl.scss.mako} (60%) rename lms/static/sass/{lms-footer-edx.scss.mako => lms-footer.scss.mako} (66%) diff --git a/.gitignore b/.gitignore index e1e15e6102..6769176b86 100644 --- a/.gitignore +++ b/.gitignore @@ -78,14 +78,13 @@ 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-edx.scss -lms/static/sass/lms-footer-edx-rtl.scss +lms/static/sass/lms-footer.scss +lms/static/sass/lms-footer-rtl.scss cms/static/css/ cms/static/sass/*.css cms/static/sass/*.css.map - ### Logging artifacts log/ logs diff --git a/common/static/js/spec_helpers/rwd_header_footer.js b/common/static/js/spec_helpers/rwd_header_footer.js deleted file mode 100644 index 8f737a6bbf..0000000000 --- a/common/static/js/spec_helpers/rwd_header_footer.js +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Adds rwd classes and click handlers. - */ - -(function($) { - 'use strict'; - - var rwd = (function() { - - var _fn = { - header: 'header.global-new', - - footer: '.edx-footer-new', - - resultsUrl: 'course-search', - - init: function() { - _fn.$header = $( _fn.header ); - _fn.$footer = $( _fn.footer ); - _fn.$nav = _fn.$header.find('nav'); - _fn.$globalNav = _fn.$nav.find('.nav-global'); - - _fn.add.elements(); - _fn.add.classes(); - _fn.eventHandlers.init(); - }, - - add: { - classes: function() { - // Add any RWD-specific classes - _fn.$header.addClass('rwd'); - _fn.$footer.addClass('rwd'); - }, - - elements: function() { - _fn.add.burger(); - _fn.add.registerLink(); - }, - - burger: function() { - _fn.$nav.prepend([ - '', - '', - '' - ].join('')); - }, - - registerLink: function() { - var $register = _fn.$nav.find('.cta-register'), - $li = {}, - $a = {}, - count = 0; - - // Add if register link is shown - if ( $register.length > 0 ) { - count = _fn.$globalNav.find('li').length + 1; - - // Create new li - $li = $('
  • '); - $li.addClass('desktop-hide nav-global-0' + count); - - // Clone register link and remove classes - $a = $register.clone(); - $a.removeClass(); - - // append to DOM - $a.appendTo( $li ); - _fn.$globalNav.append( $li ); - } - } - }, - - eventHandlers: { - init: function() { - _fn.eventHandlers.click(); - }, - - click: function() { - // Toggle menu - _fn.$nav.on( 'click', '.mobile-menu-button', _fn.toggleMenu ); - } - }, - - toggleMenu: function( event ) { - event.preventDefault(); - - _fn.$globalNav.toggleClass('show'); - } - }; - - return { - init: _fn.init - }; - })(); - - setTimeout( function() { - rwd.init(); - }, 100); -})(jQuery); diff --git a/common/static/js/utils/rwd_header_footer.js b/common/static/js/utils/rwd_header.js similarity index 93% rename from common/static/js/utils/rwd_header_footer.js rename to common/static/js/utils/rwd_header.js index 83ba652c8b..616f176268 100644 --- a/common/static/js/utils/rwd_header_footer.js +++ b/common/static/js/utils/rwd_header.js @@ -10,6 +10,7 @@ var _fn = { header: 'header.global-new', + // TODO (ECOM-1339): Remove this once the V3 footer is enabled permanently footer: '.edx-footer-new', resultsUrl: 'course-search', @@ -29,7 +30,7 @@ classes: function() { // Add any RWD-specific classes _fn.$header.addClass('rwd'); - _fn.$footer.addClass('rwd'); + _fn.$footer.addClass('rwd'); // TODO (ECOM-1339): remove once the V3 footer is enabled permanently }, elements: function() { diff --git a/conf/locale/eo/LC_MESSAGES/django.mo b/conf/locale/eo/LC_MESSAGES/django.mo index 338bdec0f28f4a63a3c5473e7fbeaf3ac90d7b20..0b34bcb3f0abe73fa006c0dc72511e98f801bf74 100644 GIT binary patch delta 80994 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 delta 80864 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 z�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 KcfKv|r}-Dm{&b@N diff --git a/conf/locale/eo/LC_MESSAGES/django.po b/conf/locale/eo/LC_MESSAGES/django.po index 6963d0a28c..9e5407259f 100644 --- a/conf/locale/eo/LC_MESSAGES/django.po +++ b/conf/locale/eo/LC_MESSAGES/django.po @@ -37,8 +37,8 @@ msgid "" msgstr "" "Project-Id-Version: 0.1a\n" "Report-Msgid-Bugs-To: openedx-translation@googlegroups.com\n" -"POT-Creation-Date: 2015-05-26 15:42+0000\n" -"PO-Revision-Date: 2015-05-26 15:42:32.205037\n" +"POT-Creation-Date: 2015-05-26 18:33+0000\n" +"PO-Revision-Date: 2015-05-26 18:33:10.093000\n" "Last-Translator: \n" "Language-Team: openedx-translation \n" "MIME-Version: 1.0\n" @@ -179,7 +179,7 @@ msgstr "" "Ýöü'ré énrölléd äs än hönör çödé stüdént Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, " "¢σηѕє¢тєтυя#" -#: common/djangoapps/course_modes/models.py +#: common/djangoapps/course_modes/models.py lms/djangoapps/branding/api.py #: openedx/core/djangoapps/user_api/views.py #: lms/templates/static_templates/honor.html msgid "Honor Code" @@ -3184,9 +3184,9 @@ msgstr "" msgid "Both" msgstr "Böth Ⱡ'σяєм ι#" -#: common/lib/xmodule/xmodule/course_module.py -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/about.html +#: common/lib/xmodule/xmodule/course_module.py lms/djangoapps/branding/api.py +#: lms/templates/footer-edx-v2.html lms/templates/footer.html +#: lms/templates/static_templates/about.html msgid "About" msgstr "Àßöüt Ⱡ'σяєм ιρѕ#" @@ -5141,6 +5141,80 @@ msgstr "Séärçh Ⱡ'σяєм ιρѕυ#" msgid "Copyright" msgstr "Çöpýrïght Ⱡ'σяєм ιρѕυм ∂σł#" +#: lms/djangoapps/branding/api.py +msgid "" +"© {org_name}. All rights reserved except where noted. EdX, Open edX and " +"the edX and Open EdX logos are registered trademarks or trademarks of edX " +"Inc." +msgstr "" +"© {org_name}. Àll rïghts résérvéd éxçépt whéré nötéd. ÉdX, Öpén édX änd " +"thé édX änd Öpén ÉdX lögös äré régïstéréd trädémärks ör trädémärks öf édX " +"Ìnç. Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α∂ιριѕι¢ιηg єłιт, ѕє∂ ∂σ " +"єιυѕмσ∂ тємρσя ιη¢ι∂ι∂υηт υт łαвσяє єт ∂σłσяє мαgηα αłιqυα. υт єηιм α∂ мιηιм" +" νєηιαм, qυιѕ ησѕтяυ∂ єχєя¢ιтαтιση υłłαм¢σ łαвσяιѕ ηιѕι υт αłιqυιρ єχ єα " +"¢σммσ∂σ ¢σηѕєqυαт. ∂υιѕ αυтє ιяυяє ∂σłσя ιη яєρяєнєη∂єяιт ιη νσłυρтαтє νєłιт" +" єѕѕє ¢ιłłυм ∂σłσяє єυ ƒυgιαт ηυłłα ραяιαтυя. єχ¢єρтєυя ѕιηт σ¢¢αє¢αт " +"¢υρι∂αтαт ηση ρяσι∂єηт, ѕυηт ιη ¢υłρα qυι σƒƒι¢ια ∂єѕєяυηт мσłłιт αηιм ι∂#" + +#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# +#. Translators: 'Open edX' is a brand, please keep this untranslated. +#. See http://openedx.org for more information. +#: lms/djangoapps/branding/api.py cms/templates/widgets/footer.html +#: lms/templates/footer-edx-v2.html +msgid "Powered by Open edX" +msgstr "Pöwéréd ßý Öpén édX Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/blog.html +msgid "Blog" +msgstr "Blög Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/footer-edx-v2.html +msgid "News" +msgstr "Néws Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py +msgid "FAQs" +msgstr "FÀQs Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/footer-edx-v2.html +#: lms/templates/static_templates/contact.html +msgid "Contact" +msgstr "Çöntäçt Ⱡ'σяєм ιρѕυм #" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/jobs.html +msgid "Jobs" +msgstr "Jößs Ⱡ'σяєм ι#" + +#: lms/djangoapps/branding/api.py lms/templates/static_templates/donate.html +msgid "Donate" +msgstr "Dönäté Ⱡ'σяєм ιρѕυ#" + +#: lms/djangoapps/branding/api.py +msgid "Sitemap" +msgstr "Sïtémäp Ⱡ'σяєм ιρѕυм #" + +#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# +#. Translators: This is a legal document users must agree to +#. in order to register a new account. +#: lms/djangoapps/branding/api.py openedx/core/djangoapps/user_api/views.py +#: cms/templates/widgets/footer.html lms/templates/static_templates/tos.html +msgid "Terms of Service" +msgstr "Térms öf Sérvïçé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#" + +#: lms/djangoapps/branding/api.py +msgid "Terms of Service & Honor Code" +msgstr "Térms öf Sérvïçé & Hönör Çödé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#" + +#: lms/djangoapps/branding/api.py lms/djangoapps/certificates/views.py +#: cms/templates/widgets/footer.html lms/templates/footer-edx-v2.html +#: lms/templates/static_templates/privacy.html +msgid "Privacy Policy" +msgstr "Prïväçý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" + +#: lms/djangoapps/branding/api.py +msgid "Accessibility Policy" +msgstr "Àççéssïßïlïtý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, #" + #: lms/djangoapps/ccx/views.py msgid "You must be a CCX Coach to access this view." msgstr "" @@ -5363,13 +5437,7 @@ msgstr "Wörk ät {platform_name} Ⱡ'σяєм ιρѕυм ∂σłσя #" msgid "Contact {platform_name}" msgstr "Çöntäçt {platform_name} Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/djangoapps/certificates/views.py cms/templates/widgets/footer.html -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/privacy.html -msgid "Privacy Policy" -msgstr "Prïväçý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" - -#: lms/djangoapps/certificates/views.py lms/templates/footer-edx-v3.html +#: lms/djangoapps/certificates/views.py msgid "Terms of Service & Honor Code" msgstr "" "Térms öf Sérvïçé & Hönör Çödé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢т#" @@ -8427,42 +8495,26 @@ msgstr "Çöüld nöt süßmït phötös Ⱡ'σяєм ιρѕυм ∂σłσя ѕ #. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.facebook.com. Please #. translate this the way that Facebook advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.facebook.com. Please -#. translate this the way that Facebook advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html -#: lms/templates/dashboard/_dashboard_course_listing.html +#: lms/envs/common.py lms/templates/dashboard/_dashboard_course_listing.html msgid "Facebook" msgstr "Fäçéßöök Ⱡ'σяєм ιρѕυм ∂#" #. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.twitter.com. Please #. translate this the way that Twitter advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.twitter.com. Please -#. translate this the way that Twitter advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html -#: lms/templates/dashboard/_dashboard_course_listing.html +#: lms/envs/common.py lms/templates/dashboard/_dashboard_course_listing.html msgid "Twitter" msgstr "Twïttér Ⱡ'σяєм ιρѕυм #" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.linkedin.com. Please #. translate this the way that LinkedIn advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.linked.com. Please -#. translate this the way that LinkedIn advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "LinkedIn" msgstr "LïnkédÌn Ⱡ'σяєм ιρѕυм ∂#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of plus.google.com. Please #. translate this the way that Google+ advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of plus.google.com. Please -#. translate this the way that Google+ advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "Google+" msgstr "Gööglé+ Ⱡ'σяєм ιρѕυм #" @@ -8472,13 +8524,9 @@ msgstr "Gööglé+ Ⱡ'σяєм ιρѕυм #" msgid "Tumblr" msgstr "Tümßlr Ⱡ'σяєм ιρѕυ#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# #. Translators: This is the website name of www.meetup.com. Please #. translate this the way that MeetUp advertises in your language. -#. #-#-#-#-# mako.po (0.1a) #-#-#-#-# -#. Translators: This is the website name of www.meetup.com. Please -#. translate this the way that Meetup advertises in your language. -#: lms/envs/common.py lms/templates/footer-edx-v3.html +#: lms/envs/common.py msgid "Meetup" msgstr "Méétüp Ⱡ'σяєм ιρѕυ#" @@ -9397,14 +9445,6 @@ msgstr "" "Ýöü müst ägréé tö thé {platform_name} {terms_of_service}. Ⱡ'σяєм ιρѕυм ∂σłσя" " ѕιт αмєт, ¢σηѕє¢т#" -#. #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# -#. Translators: This is a legal document users must agree to -#. in order to register a new account. -#: openedx/core/djangoapps/user_api/views.py cms/templates/widgets/footer.html -#: lms/templates/footer.html lms/templates/static_templates/tos.html -msgid "Terms of Service" -msgstr "Térms öf Sérvïçé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αм#" - #: openedx/core/djangoapps/user_api/accounts/api.py msgid "The '{field_name}' field cannot be edited." msgstr "" @@ -10081,13 +10121,6 @@ msgstr "" "Vïsït ýöür {link_start}däshßöärd{link_end} tö séé ýöür çöürsés. Ⱡ'σяєм ιρѕυм" " ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя α#" -#. Translators: 'Open edX' is a brand, please keep this untranslated. See -#. http://openedx.org for more information. -#: cms/templates/widgets/footer.html lms/templates/footer-edx-v2.html -#: lms/templates/footer-edx-v3.html lms/templates/footer.html -msgid "Powered by Open edX" -msgstr "Pöwéréd ßý Öpén édX Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт,#" - #: cms/templates/widgets/header.html lms/templates/courseware/courseware.html msgid "Course Navigation" msgstr "Çöürsé Nävïgätïön Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмє#" @@ -10499,7 +10532,7 @@ msgstr "" #. Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. #. Please do not translate any of these trademarks and company names. -#: lms/templates/footer-edx-v2.html lms/templates/footer.html +#: lms/templates/footer-edx-v2.html msgid "" "EdX, Open edX, and the edX and Open edX logos are registered trademarks or " "trademarks of {link_start}edX Inc.{link_end}" @@ -10521,18 +10554,7 @@ msgstr "(Révïséd {date}) Ⱡ'σяєм ιρѕυм ∂σłσя ѕι#" msgid "10/22/2014" msgstr "10/22/2014 Ⱡ'σяєм ιρѕυм ∂σłσ#" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html -msgid "News" -msgstr "Néws Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html -#: lms/templates/footer.html lms/templates/static_templates/contact.html -msgid "Contact" -msgstr "Çöntäçt Ⱡ'σяєм ιρѕυм #" - -#: lms/templates/footer-edx-v2.html lms/templates/footer.html -#: lms/templates/static_templates/faq.html +#: lms/templates/footer-edx-v2.html lms/templates/static_templates/faq.html msgid "FAQ" msgstr "FÀQ Ⱡ'σяєм#" @@ -10556,11 +10578,11 @@ msgstr "Föllöw Ûs Ⱡ'σяєм ιρѕυм ∂σł#" msgid "Mobile Apps" msgstr "Mößïlé Àpps Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html +#: lms/templates/footer-edx-v2.html msgid "Apple app on Apple Store" msgstr "Àpplé äpp ön Àpplé Störé Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢ση#" -#: lms/templates/footer-edx-v2.html lms/templates/footer-edx-v3.html +#: lms/templates/footer-edx-v2.html msgid "Android app on Google Play" msgstr "Àndröïd äpp ön Gööglé Pläý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕ#" @@ -10568,43 +10590,10 @@ msgstr "Àndröïd äpp ön Gööglé Pläý Ⱡ'σяєм ιρѕυм ∂σłσя msgid "Page Footer" msgstr "Pägé Föötér Ⱡ'σяєм ιρѕυм ∂σłσя #" -#: lms/templates/footer-edx-v3.html lms/templates/static_templates/blog.html -msgid "Blog" -msgstr "Blög Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html -msgid "FAQs" -msgstr "FÀQs Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html lms/templates/footer.html -#: lms/templates/static_templates/jobs.html -msgid "Jobs" -msgstr "Jößs Ⱡ'σяєм ι#" - -#: lms/templates/footer-edx-v3.html lms/templates/static_templates/donate.html -msgid "Donate" -msgstr "Dönäté Ⱡ'σяєм ιρѕυ#" - -#: lms/templates/footer-edx-v3.html -msgid "Sitemap" -msgstr "Sïtémäp Ⱡ'σяєм ιρѕυм #" - #: lms/templates/footer-edx-v3.html lms/templates/footer.html msgid "Legal" msgstr "Légäl Ⱡ'σяєм ιρѕ#" -#: lms/templates/footer-edx-v3.html -msgid "Website Accessibility Policy" -msgstr "Wéßsïté Àççéssïßïlïtý Pölïçý Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢#" - -#: lms/templates/footer.html -msgid "" -"{tos_link_start}Terms of Service{tos_link_end} and {honor_link_start}Honor " -"Code{honor_link_end}" -msgstr "" -"{tos_link_start}Térms öf Sérvïçé{tos_link_end} änd {honor_link_start}Hönör " -"Çödé{honor_link_end} Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт αмєт, ¢σηѕє¢тєтυя #" - #: lms/templates/forgot_password_modal.html msgid "Password Reset" msgstr "Pässwörd Rését Ⱡ'σяєм ιρѕυм ∂σłσя ѕιт#" diff --git a/lms/djangoapps/branding/admin.py b/lms/djangoapps/branding/admin.py index d10b2a9c61..4450174e16 100644 --- a/lms/djangoapps/branding/admin.py +++ b/lms/djangoapps/branding/admin.py @@ -1,9 +1,8 @@ -''' -Django admin pages for Video Branding Configuration. -''' +"""Django admin pages for branding configuration. """ from django.contrib import admin from config_models.admin import ConfigurationModelAdmin -from .models import BrandingInfoConfig +from .models import BrandingInfoConfig, BrandingApiConfig admin.site.register(BrandingInfoConfig, ConfigurationModelAdmin) +admin.site.register(BrandingApiConfig, ConfigurationModelAdmin) diff --git a/lms/djangoapps/branding/api.py b/lms/djangoapps/branding/api.py new file mode 100644 index 0000000000..119f3aa06b --- /dev/null +++ b/lms/djangoapps/branding/api.py @@ -0,0 +1,291 @@ +"""EdX Branding API + +Provides a way to retrieve "branded" parts of the site, +such as the site footer. + +This information exposed to: +1) Templates in the LMS. +2) Consumers of the branding API. + +This ensures that branded UI elements such as the footer +are consistent across the LMS and other sites (such as +the marketing site and blog). + +""" +import logging +import urlparse + +from django.conf import settings +from django.utils.translation import ugettext as _ +from staticfiles.storage import staticfiles_storage + +from microsite_configuration import microsite +from edxmako.shortcuts import marketing_link +from branding.models import BrandingApiConfig + + +log = logging.getLogger("edx.footer") + + +def is_enabled(): + """Check whether the branding API is enabled. """ + # TODO (ECOM-1339): Remove this comment + # Currently, the branding API configuration controls two things: + # 1) whether we're using the new version of the footer + # 2) whether we're exposing footer information through the API. + # + # Once we've enabled the new footer, the feature flag will control + # only (2), but not (1). + return BrandingApiConfig.current().enabled + + +def get_footer(is_secure=True): + """Retrieve information used to render the footer. + + This will handle both the OpenEdX and EdX.org versions + of the footer. All user-facing text is internationalized. + + Currently, this does NOT support theming. + + Keyword Arguments: + is_secure (bool): If True, use https:// in URLs. + + Returns: dict + + Example: + >>> get_footer() + { + "copyright": "(c) 2015 EdX Inc", + "logo_image": "http://www.example.com/logo.png", + "social_links": [ + { + "name": "facebook", + "title": "Facebook", + "url": "http://www.facebook.com/example", + "icon-class": "fa-facebook-square" + }, + ... + ], + "navigation_links": [ + { + "name": "about", + "title": "About", + "url": "http://www.example.com/about.html" + }, + ... + ], + "mobile_links": [ + { + "name": "apple", + "title": "Apple", + "url": "http://store.apple.com/example_app" + "image": "http://example.com/static/apple_logo.png" + }, + ... + ], + "legal_links": [ + { + "url": "http://example.com/terms-of-service.html", + "name": "terms_of_service", + "title': "Terms of Service" + }, + # ... + ], + "openedx_link": { + "url": "http://open.edx.org", + "title": "Powered by Open edX", + "image": "http://example.com/openedx.png" + } + } + + """ + return { + "copyright": _footer_copyright(), + "logo_image": _footer_logo_img(is_secure), + "social_links": _footer_social_links(), + "navigation_links": _footer_navigation_links(), + "mobile_links": _footer_mobile_links(is_secure), + "legal_links": _footer_legal_links(), + "openedx_link": _footer_openedx_link(), + } + + +def _footer_copyright(): + """Return the copyright to display in the footer. + + Returns: unicode + + """ + org_name = ( + "edX Inc" if settings.FEATURES.get('IS_EDX_DOMAIN', False) + else microsite.get_value('PLATFORM_NAME', settings.PLATFORM_NAME) + ) + + # Translators: 'EdX', 'edX', and 'Open edX' are trademarks of 'edX Inc.'. + # Please do not translate any of these trademarks and company names. + return _( + u"\u00A9 {org_name}. All rights reserved except where noted. " + u"EdX, Open edX and the edX and Open EdX logos are registered trademarks " + u"or trademarks of edX Inc." + ).format(org_name=org_name) + + +def _footer_openedx_link(): + """Return the image link for "powered by OpenEdX". + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: dict + + """ + # Translators: 'Open edX' is a brand, please keep this untranslated. + # See http://openedx.org for more information. + title = _("Powered by Open edX") + return { + "url": settings.FOOTER_OPENEDX_URL, + "title": title, + "image": settings.FOOTER_OPENEDX_LOGO_IMAGE, + } + + +def _footer_social_links(): + """Return the social media links to display in the footer. + + Returns: list + + """ + links = [] + for social_name in settings.SOCIAL_MEDIA_FOOTER_NAMES: + links.append( + { + "name": social_name, + "title": unicode(settings.SOCIAL_MEDIA_FOOTER_DISPLAY.get(social_name, {}).get("title", "")), + "url": settings.SOCIAL_MEDIA_FOOTER_URLS.get(social_name, "#"), + "icon-class": settings.SOCIAL_MEDIA_FOOTER_DISPLAY.get(social_name, {}).get("icon", ""), + } + ) + return links + + +def _footer_navigation_links(): + """Return the navigation links to display in the footer. """ + return [ + { + "name": link_name, + "title": link_title, + "url": link_url, + } + for link_name, link_url, link_title in [ + ("about", marketing_link("ABOUT"), _("About")), + ("blog", marketing_link("BLOG"), _("Blog")), + ("news", marketing_link("NEWS"), _("News")), + ("faq", marketing_link("FAQ"), _("FAQs")), + ("contact", marketing_link("CONTACT"), _("Contact")), + ("jobs", marketing_link("JOBS"), _("Jobs")), + ("donate", marketing_link("DONATE"), _("Donate")), + ("sitemap", marketing_link("SITE_MAP"), _("Sitemap")), + ] + if link_url and link_url != "#" + ] + + +def _footer_legal_links(): + """Return the legal footer links (e.g. terms of service). """ + + links = [ + ("terms_of_service_and_honor_code", marketing_link("TOS_AND_HONOR"), _("Terms of Service & Honor Code")), + ("privacy_policy", marketing_link("PRIVACY"), _("Privacy Policy")), + ("accessibility_policy", marketing_link("ACCESSIBILITY"), _("Accessibility Policy")), + ] + + # Backwards compatibility: If a combined "terms of service and honor code" + # link isn't provided, add separate TOS and honor code links. + tos_and_honor_link = marketing_link("TOS_AND_HONOR") + if not (tos_and_honor_link and tos_and_honor_link != "#"): + links.extend([ + ("terms_of_service", marketing_link("TOS"), _("Terms of Service")), + ("honor_code", marketing_link("HONOR"), _("Honor Code")), + ]) + + return [ + { + "name": link_name, + "title": link_title, + "url": link_url, + } + for link_name, link_url, link_title in links + if link_url and link_url != "#" + ] + + +def _footer_mobile_links(is_secure): + """Return the mobile app store links. + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: list + + """ + mobile_links = [] + if settings.FEATURES.get('ENABLE_FOOTER_MOBILE_APP_LINKS'): + mobile_links = [ + { + "name": "apple", + "title": "Apple", + "url": settings.MOBILE_STORE_URLS.get('apple', '#'), + "image": _absolute_url_staticfile(is_secure, 'images/app/app_store_badge_135x40.svg') + }, + { + "name": "google", + "title": "Google", + "url": settings.MOBILE_STORE_URLS.get('google', '#'), + "image": _absolute_url_staticfile(is_secure, 'images/app/google_play_badge_45.png') + } + ] + return mobile_links + + +def _footer_logo_img(is_secure): + """Return the logo used for footer about link + + Args: + is_secure (bool): Whether the request is using TLS. + + Returns: + Absolute url to logo + """ + logo_name = microsite.get_value('FOOTER_ORGANIZATION_IMAGE', settings.FOOTER_ORGANIZATION_IMAGE) + return _absolute_url_staticfile(is_secure, logo_name) + + +def _absolute_url(is_secure, url_path): + """Construct an absolute URL back to the site. + + Arguments: + is_secure (bool): If true, use HTTPS as the protocol. + url_path (unicode): The path of the URL. + + Returns: + unicode + + """ + site_name = microsite.get_value('SITE_NAME', settings.SITE_NAME) + parts = ("https" if is_secure else "http", site_name, url_path, '', '', '') + return urlparse.urlunparse(parts) + + +def _absolute_url_staticfile(is_secure, name): + """Construct an absolute URL back to a static resource on the site. + + Arguments: + is_secure (bool): If true, use HTTPS as the protocol. + name (unicode): The name of the static resource to retrieve. + + Returns: + unicode + + """ + url_path = staticfiles_storage.url(name) + return _absolute_url(is_secure, url_path) diff --git a/lms/djangoapps/branding/api_urls.py b/lms/djangoapps/branding/api_urls.py new file mode 100644 index 0000000000..d7ba50c012 --- /dev/null +++ b/lms/djangoapps/branding/api_urls.py @@ -0,0 +1,15 @@ +""" +Branding API endpoint urls. +""" + +from django.conf.urls import patterns, url + +urlpatterns = patterns( + "", + + url( + r"^footer$", + "branding.views.footer", + name="branding_footer", + ), +) diff --git a/lms/djangoapps/branding/context_processors.py b/lms/djangoapps/branding/context_processors.py new file mode 100644 index 0000000000..3aa688381d --- /dev/null +++ b/lms/djangoapps/branding/context_processors.py @@ -0,0 +1,10 @@ +"""Context processors for Django templates. """ +from branding import api as branding_api + + +# TODO (ECOM-1339): Remove this module once we permanently enable the V3 footer. +def branding_context_processor(request): # pylint: disable=unused-argument + """Add the feature flag to Django template context. """ + return { + "ENABLE_BRANDING_API": branding_api.is_enabled() + } diff --git a/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py b/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py new file mode 100644 index 0000000000..ffa45652a6 --- /dev/null +++ b/lms/djangoapps/branding/migrations/0002_auto__add_brandingapiconfig.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding model 'BrandingApiConfig' + db.create_table('branding_brandingapiconfig', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('change_date', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)), + ('changed_by', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, on_delete=models.PROTECT)), + ('enabled', self.gf('django.db.models.fields.BooleanField')(default=False)), + )) + db.send_create_signal('branding', ['BrandingApiConfig']) + + + def backwards(self, orm): + # Deleting model 'BrandingApiConfig' + db.delete_table('branding_brandingapiconfig') + + + models = { + 'auth.group': { + 'Meta': {'object_name': 'Group'}, + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + 'auth.permission': { + 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + 'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + 'branding.brandingapiconfig': { + 'Meta': {'object_name': 'BrandingApiConfig'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'branding.brandinginfoconfig': { + 'Meta': {'object_name': 'BrandingInfoConfig'}, + 'change_date': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'changed_by': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'on_delete': 'models.PROTECT'}), + 'configuration': ('django.db.models.fields.TextField', [], {}), + 'enabled': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}) + }, + 'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + } + } + + complete_apps = ['branding'] diff --git a/lms/djangoapps/branding/models.py b/lms/djangoapps/branding/models.py index 7ee3d21e99..67a51d4f26 100644 --- a/lms/djangoapps/branding/models.py +++ b/lms/djangoapps/branding/models.py @@ -44,3 +44,14 @@ class BrandingInfoConfig(ConfigurationModel): """ info = cls.current() return json.loads(info.configuration) if info.enabled else {} + + +class BrandingApiConfig(ConfigurationModel): + """Configure Branding api's + + Enable or disable api's functionality. + When this flag is disabled, the api will return 404. + + When the flag is enabled, the api will returns the valid reponse. + """ + pass diff --git a/lms/djangoapps/branding/tests/test_views.py b/lms/djangoapps/branding/tests/test_views.py new file mode 100644 index 0000000000..b11ab62518 --- /dev/null +++ b/lms/djangoapps/branding/tests/test_views.py @@ -0,0 +1,214 @@ +# encoding: utf-8 +"""Tests of Branding API views. """ +import contextlib +import json +import urllib +from django.test import TestCase +from django.core.urlresolvers import reverse +from django.conf import settings + +import mock +import ddt +from config_models.models import cache +from branding.models import BrandingApiConfig + + +@ddt.ddt +class TestFooter(TestCase): + """Test API end-point for retrieving the footer. """ + + def setUp(self): + """Clear the configuration cache. """ + super(TestFooter, self).setUp() + cache.clear() + + @ddt.data("*/*", "text/html", "application/json") + def test_feature_flag(self, accepts): + self._set_feature_flag(False) + resp = self._get_footer(accepts=accepts) + self.assertEqual(resp.status_code, 404) + + @ddt.data( + # Open source version + (False, "application/json", "application/json; charset=utf-8", "Open edX"), + (False, "text/html", "text/html; charset=utf-8", "lms-footer.css"), + (False, "text/html", "text/html; charset=utf-8", "Open edX"), + + # EdX.org version + (True, "application/json", "application/json; charset=utf-8", "edX Inc"), + (True, "text/html", "text/html; charset=utf-8", "lms-footer-edx.css"), + (True, "text/html", "text/html; charset=utf-8", "edX Inc"), + ) + @ddt.unpack + def test_footer_content_types(self, is_edx_domain, accepts, content_type, content): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer(accepts=accepts) + + self.assertEqual(resp.status_code, 200) + self.assertEqual(resp["Content-Type"], content_type) + self.assertIn(content, resp.content) + + @mock.patch.dict(settings.FEATURES, {'ENABLE_FOOTER_MOBILE_APP_LINKS': True}) + @ddt.data(True, False) + def test_footer_json(self, is_edx_domain): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer() + + self.assertEqual(resp.status_code, 200) + json_data = json.loads(resp.content) + self.assertTrue(isinstance(json_data, dict)) + + # Logo + self.assertIn("logo_image", json_data) + + # Links + self.assertIn("navigation_links", json_data) + for link in json_data["navigation_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + + # Social links + self.assertIn("social_links", json_data) + for link in json_data["social_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + self.assertIn("icon-class", link) + + # Mobile links + self.assertIn("mobile_links", json_data) + for link in json_data["mobile_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + self.assertIn("image", link) + + # Legal links + self.assertIn("legal_links", json_data) + for link in json_data["legal_links"]: + self.assertIn("name", link) + self.assertIn("title", link) + self.assertIn("url", link) + + # OpenEdX + self.assertIn("openedx_link", json_data) + self.assertIn("url", json_data["openedx_link"]) + self.assertIn("title", json_data["openedx_link"]) + self.assertIn("image", json_data["openedx_link"]) + + # Copyright + self.assertIn("copyright", json_data) + + @ddt.data( + ("en", "registered trademarks"), + ("eo", u"régïstéréd trädémärks"), # Dummy language string + ("unknown", "registered trademarks"), # default to English + ) + @ddt.unpack + def test_language_override_translation(self, language, expected_copyright): + self._set_feature_flag(True) + + # Load the footer with the specified language + resp = self._get_footer(params={'language': language}) + self.assertEqual(resp.status_code, 200) + json_data = json.loads(resp.content) + + # Verify that the translation occurred + self.assertIn(expected_copyright, json_data['copyright']) + + @ddt.data( + # OpenEdX + (False, "en", "lms-footer.css"), + (False, "ar", "lms-footer-rtl.css"), + + # EdX.org + (True, "en", "lms-footer-edx.css"), + (True, "ar", "lms-footer-edx-rtl.css"), + ) + @ddt.unpack + def test_language_rtl(self, is_edx_domain, language, static_path): + self._set_feature_flag(True) + + with self._set_is_edx_domain(is_edx_domain): + resp = self._get_footer(accepts="text/html", params={'language': language}) + + self.assertEqual(resp.status_code, 200) + self.assertIn(static_path, resp.content) + + @ddt.data( + # OpenEdX + (False, True), + (False, False), + + # EdX.org + (True, True), + (True, False), + ) + @ddt.unpack + def test_show_openedx_logo(self, is_edx_domain, show_logo): + self._set_feature_flag(True) + + with self._set_is_edx_domain(is_edx_domain): + params = {'show-openedx-logo': 1} if show_logo else {} + resp = self._get_footer(accepts="text/html", params=params) + + self.assertEqual(resp.status_code, 200) + + if show_logo: + self.assertIn(settings.FOOTER_OPENEDX_URL, resp.content) + else: + self.assertNotIn(settings.FOOTER_OPENEDX_URL, resp.content) + + @ddt.data( + # OpenEdX + (False, False), + (False, True), + + # EdX.org + (True, False), + (True, True), + ) + @ddt.unpack + def test_include_dependencies(self, is_edx_domain, include_dependencies): + self._set_feature_flag(True) + with self._set_is_edx_domain(is_edx_domain): + params = {'include-dependencies': 1} if include_dependencies else {} + resp = self._get_footer(accepts="text/html", params=params) + + self.assertEqual(resp.status_code, 200) + + if include_dependencies: + self.assertIn("vendor", resp.content) + else: + self.assertNotIn("vendor", resp.content) + + def test_no_supported_accept_type(self): + self._set_feature_flag(True) + resp = self._get_footer(accepts="application/x-shockwave-flash") + self.assertEqual(resp.status_code, 406) + + def _set_feature_flag(self, enabled): + """Enable or disable the feature flag for the branding API end-points. """ + config = BrandingApiConfig(enabled=enabled) + config.save() + + def _get_footer(self, accepts="application/json", params=None): + """Retrieve the footer. """ + url = reverse("branding_footer") + + if params is not None: + url = u"{url}?{params}".format( + url=url, + params=urllib.urlencode(params) + ) + + return self.client.get(url, HTTP_ACCEPT=accepts) + + @contextlib.contextmanager + def _set_is_edx_domain(self, is_edx_domain): + """Configure whether this an EdX-controlled domain. """ + with mock.patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': is_edx_domain}): + yield diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index b79df70f55..8e950dfb91 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -1,17 +1,29 @@ +"""Views for the branding app. """ +import logging +import urllib + from django.conf import settings from django.core.urlresolvers import reverse -from django.http import Http404 +from django.core.cache import cache +from django.views.decorators.cache import cache_control +from django.http import HttpResponse, Http404 +from django.utils import translation from django.shortcuts import redirect from django_future.csrf import ensure_csrf_cookie +from staticfiles.storage import staticfiles_storage +from edxmako.shortcuts import render_to_response import student.views from student.models import CourseEnrollment - import courseware.views - from microsite_configuration import microsite from edxmako.shortcuts import marketing_link from util.cache import cache_if_anonymous +from util.json_request import JsonResponse +import branding.api as branding_api + + +log = logging.getLogger(__name__) def get_course_enrollments(user): @@ -102,3 +114,198 @@ def courses(request): # we do not expect this case to be reached in cases where # marketing is enabled or the courses are not browsable return courseware.views.courses(request) + + +def _footer_static_url(request, name): + """Construct an absolute URL to a static asset. """ + return request.build_absolute_uri(staticfiles_storage.url(name)) + + +def _footer_css_urls(request, package_name): + """Construct absolute URLs to CSS assets in a package. """ + # We need this to work both in local development and in production. + # Unfortunately, in local development we don't run the full asset pipeline, + # so fully processed output files may not exist. + # For this reason, we use the *css package* name(s), rather than the static file name + # to identify the CSS file name(s) to include in the footer. + # We then construct an absolute URI so that external sites (such as the marketing site) + # can locate the assets. + package = settings.PIPELINE_CSS.get(package_name, {}) + paths = [package['output_filename']] if not settings.DEBUG else package['source_filenames'] + return [ + _footer_static_url(request, path) + for path in paths + ] + + +def _render_footer_html(request, show_openedx_logo, include_dependencies): + """Render the footer as HTML. + + Arguments: + show_openedx_logo (bool): If True, include the OpenEdX logo in the rendered HTML. + include_dependencies (bool): If True, include JavaScript and CSS dependencies. + + Returns: unicode + + """ + bidi = 'rtl' if translation.get_language_bidi() else 'ltr' + version = 'edx' if settings.FEATURES.get('IS_EDX_DOMAIN') else 'openedx' + css_name = settings.FOOTER_CSS[version][bidi] + + context = { + 'hide_openedx_link': not show_openedx_logo, + 'footer_js_url': _footer_static_url(request, 'js/footer-edx.js'), + 'footer_css_urls': _footer_css_urls(request, css_name), + 'bidi': bidi, + 'include_dependencies': include_dependencies, + } + + return ( + render_to_response("footer-edx-v3.html", context) + if settings.FEATURES.get("IS_EDX_DOMAIN", False) + else render_to_response("footer.html", context) + ) + + +@cache_control(must_revalidate=True, max_age=settings.FOOTER_BROWSER_CACHE_MAX_AGE) +def footer(request): + """Retrieve the branded footer. + + This end-point provides information about the site footer, + allowing for consistent display of the footer across other sites + (for example, on the marketing site and blog). + + It can be used in one of two ways: + 1) A client renders the footer from a JSON description. + 2) A browser loads an HTML representation of the footer + and injects it into the DOM. The HTML includes + CSS and JavaScript links. + + In case (2), we assume that the following dependencies + are included on the page: + a) JQuery (same version as used in edx-platform) + b) font-awesome (same version as used in edx-platform) + c) Open Sans web fonts + + Example: Retrieving the footer as JSON + + GET /api/branding/v1/footer + Accepts: application/json + + { + "navigation_links": [ + { + "url": "http://example.com/about", + "name": "about", + "title": "About" + }, + # ... + ], + "social_links": [ + { + "url": "http://example.com/social", + "name": "facebook", + "icon-class": "fa-facebook-square", + "title": "Facebook" + }, + # ... + ], + "mobile_links": [ + { + "url": "http://example.com/android", + "name": "google", + "image": "http://example.com/google.png", + "title": "Google" + }, + # ... + ], + "legal_links": [ + { + "url": "http://example.com/terms-of-service.html", + "name": "terms_of_service", + "title': "Terms of Service" + }, + # ... + ], + "openedx_link": { + "url": "http://open.edx.org", + "title": "Powered by Open edX", + "image": "http://example.com/openedx.png" + }, + "logo_image": "http://example.com/static/images/default-theme/logo.png", + "copyright": "EdX, Open edX, and the edX and Open edX logos are \ + registered trademarks or trademarks of edX Inc." + } + + + Example: Retrieving the footer as HTML + + GET /api/branding/v1/footer + Accepts: text/html + + + Example: Including the footer with the "Powered by OpenEdX" logo + + GET /api/branding/v1/footer?show-openedx-logo=1 + Accepts: text/html + + + Example: Retrieving the footer in a particular language + + GET /api/branding/v1/footer?language=en + Accepts: text/html + + Example: Retrieving the footer with all JS and CSS dependencies (for testing) + + GET /api/branding/v1/footer?include-dependencies=1 + Accepts: text/html + + """ + if not branding_api.is_enabled(): + raise Http404 + + # Use the content type to decide what representation to serve + accepts = request.META.get('HTTP_ACCEPT', '*/*') + + # Show the OpenEdX logo in the footer + show_openedx_logo = bool(request.GET.get('show-openedx-logo', False)) + + # Include JS and CSS dependencies + # This is useful for testing the end-point directly. + include_dependencies = bool(request.GET.get('include-dependencies', False)) + + # Override the language if necessary + language = request.GET.get('language', translation.get_language()) + + # Render the footer information based on the extension + if 'text/html' in accepts or '*/*' in accepts: + cache_key = u"branding.footer.{params}.html".format( + params=urllib.urlencode({ + 'language': language, + 'show_openedx_logo': show_openedx_logo, + 'include_dependencies': include_dependencies, + }) + ) + content = cache.get(cache_key) + if content is None: + with translation.override(language): + content = _render_footer_html(request, show_openedx_logo, include_dependencies) + cache.set(cache_key, content, settings.FOOTER_CACHE_TIMEOUT) + return HttpResponse(content, status=200, content_type="text/html; charset=utf-8") + + elif 'application/json' in accepts: + cache_key = u"branding.footer.{params}.json".format( + params=urllib.urlencode({ + 'language': language, + 'is_secure': request.is_secure(), + }) + ) + footer_dict = cache.get(cache_key) + if footer_dict is None: + with translation.override(language): + footer_dict = branding_api.get_footer(is_secure=request.is_secure()) + cache.set(cache_key, footer_dict, settings.FOOTER_CACHE_TIMEOUT) + return JsonResponse(footer_dict, 200, content_type="application/json; charset=utf-8") + + else: + return HttpResponse(status=406) diff --git a/lms/djangoapps/courseware/features/homepage.feature b/lms/djangoapps/courseware/features/homepage.feature index 49b3cf3e65..2cf3173713 100644 --- a/lms/djangoapps/courseware/features/homepage.feature +++ b/lms/djangoapps/courseware/features/homepage.feature @@ -18,6 +18,6 @@ Feature: LMS.Homepage for web users | id | Link | | about | About | | jobs | Jobs | - | faq | FAQ | + | faq | FAQs | | contact | Contact| | news | News | diff --git a/lms/djangoapps/courseware/tests/test_about.py b/lms/djangoapps/courseware/tests/test_about.py index 6d19bccc4a..0c75863408 100644 --- a/lms/djangoapps/courseware/tests/test_about.py +++ b/lms/djangoapps/courseware/tests/test_about.py @@ -306,7 +306,7 @@ class AboutWithInvitationOnly(ModuleStoreTestCase): url = reverse('about_course', args=[self.course.id.to_deprecated_string()]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) # Check that registration button is present self.assertIn(REG_STR, resp.content) @@ -336,7 +336,7 @@ class AboutTestCaseShibCourse(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) self.assertIn(SHIB_ERROR_STR, resp.content) self.assertIn(REG_STR, resp.content) @@ -348,7 +348,7 @@ class AboutTestCaseShibCourse(LoginEnrollmentTestCase, ModuleStoreTestCase): resp = self.client.get(url) self.assertEqual(resp.status_code, 200) self.assertIn("OOGIE BLOOGIE", resp.content) - self.assertIn(u"Register for {}".format(self.course.id.course), resp.content) + self.assertIn(u"Register for {}".format(self.course.id.course), resp.content.decode('utf-8')) self.assertIn(SHIB_ERROR_STR, resp.content) self.assertIn(REG_STR, resp.content) diff --git a/lms/djangoapps/courseware/tests/test_footer.py b/lms/djangoapps/courseware/tests/test_footer.py index edd873de54..1300f562f4 100644 --- a/lms/djangoapps/courseware/tests/test_footer.py +++ b/lms/djangoapps/courseware/tests/test_footer.py @@ -14,6 +14,17 @@ from django.test.utils import override_settings @attr('shard_1') class TestFooter(TestCase): + SOCIAL_MEDIA_NAMES = [ + "facebook", + "google_plus", + "twitter", + "linkedin", + "tumblr", + "meetup", + "reddit", + "youtube", + ] + SOCIAL_MEDIA_URLS = { "facebook": "http://www.facebook.com/", "google_plus": "https://plus.google.com/", @@ -51,7 +62,10 @@ class TestFooter(TestCase): self.assertContains(resp, 'wrapper-footer') @patch.dict(settings.FEATURES, {'IS_EDX_DOMAIN': True}) - @override_settings(SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS) + @override_settings( + SOCIAL_MEDIA_FOOTER_NAMES=SOCIAL_MEDIA_NAMES, + SOCIAL_MEDIA_FOOTER_URLS=SOCIAL_MEDIA_URLS + ) def test_edx_footer_social_links(self): resp = self.client.get('/') for name, url in self.SOCIAL_MEDIA_URLS.iteritems(): diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 69df5d519f..4c9bee97a0 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -309,6 +309,13 @@ if FEATURES.get('AUTH_USE_CAS'): # Example: {'CN': 'http://api.xuetangx.com/edx/video?s3_url='} VIDEO_CDN_URL = ENV_TOKENS.get('VIDEO_CDN_URL', {}) +# Branded footer +FOOTER_OPENEDX_URL = ENV_TOKENS.get('FOOTER_OPENEDX_URL', FOOTER_OPENEDX_URL) +FOOTER_OPENEDX_LOGO_IMAGE = ENV_TOKENS.get('FOOTER_OPENEDX_LOGO_IMAGE', FOOTER_OPENEDX_LOGO_IMAGE) +FOOTER_ORGANIZATION_IMAGE = ENV_TOKENS.get('FOOTER_ORGANIZATION_IMAGE', FOOTER_ORGANIZATION_IMAGE) +FOOTER_CACHE_TIMEOUT = ENV_TOKENS.get('FOOTER_CACHE_TIMEOUT', FOOTER_CACHE_TIMEOUT) +FOOTER_BROWSER_CACHE_MAX_AGE = ENV_TOKENS.get('FOOTER_BROWSER_CACHE_MAX_AGE', FOOTER_BROWSER_CACHE_MAX_AGE) + ############# CORS headers for cross-domain requests ################# if FEATURES.get('ENABLE_CORS_HEADERS') or FEATURES.get('ENABLE_CROSS_DOMAIN_CSRF_COOKIE'): diff --git a/lms/envs/common.py b/lms/envs/common.py index baa3b8f4c2..976eb03210 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -342,9 +342,6 @@ FEATURES = { # Show the mobile app links in the footer 'ENABLE_FOOTER_MOBILE_APP_LINKS': False, - # Use version 3 of the footer (added May 2015) - 'ENABLE_FOOTER_V3': False, - # Let students save and manage their annotations 'ENABLE_EDXNOTES': False, @@ -507,6 +504,11 @@ TEMPLATE_CONTEXT_PROCESSORS = ( # Allows the open edX footer to be leveraged in Django Templates. 'edxmako.shortcuts.open_source_footer_context_processor', + # TODO (ECOM-1339): Remove once the V3 version of the footer is enabled permanently + # This allows us to pass the appropriate feature flag to the main Django template + # that contains the footer. + 'branding.context_processors.branding_context_processor', + # Shoppingcart processor (detects if request.user has a cart) 'shoppingcart.context_processor.user_has_cart_context_processor', @@ -1039,6 +1041,48 @@ PARENTAL_CONSENT_AGE_LIMIT = 13 ################################# Jasmine ################################## JASMINE_TEST_DIRECTORY = PROJECT_ROOT + '/static/coffee' + +######################### Branded Footer ################################### +# Constants for the footer used on the site and shared with other sites +# (such as marketing and the blog) via the branding API. + +# URL for OpenEdX displayed in the footer +FOOTER_OPENEDX_URL = "http://open.edx.org" + +# URL for the OpenEdX logo image +# We use logo images served from files.edx.org so we can (roughly) track +# how many OpenEdX installations are running. +# Site operators can choose from these logo options: +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag.png +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag-light.png" +# * https://files.edx.org/openedx-logos/edx-openedx-logo-tag-dark.png +FOOTER_OPENEDX_LOGO_IMAGE = "https://files.edx.org/openedx-logos/edx-openedx-logo-tag.png" + +# This is just a placeholder image. +# Site operators can customize this with their organization's image. +FOOTER_ORGANIZATION_IMAGE = "images/default-theme/logo.png" + +# These are referred to both by the Django asset pipeline +# AND by the branding footer API, which needs to decide which +# version of the CSS to serve. +FOOTER_CSS = { + "openedx": { + "ltr": "style-lms-footer", + "rtl": "style-lms-footer-rtl", + }, + "edx": { + "ltr": "style-lms-footer-edx", + "rtl": "style-lms-footer-edx-rtl", + }, +} + +# Cache expiration for the version of the footer served +# by the branding API. +FOOTER_CACHE_TIMEOUT = 30 * 60 + +# Max age cache control header for the footer (controls browser caching). +FOOTER_BROWSER_CACHE_MAX_AGE = 5 * 60 + ################################# Deprecation warnings ##################### # Ignore deprecation warnings (so we don't clutter Jenkins builds/production) @@ -1182,8 +1226,7 @@ dashboard_js = ( ['js/search/dashboard/main.js'] ) discussion_js = sorted(rooted_glob(COMMON_ROOT / 'static', 'coffee/src/discussion/**/*.js')) -rwd_header_footer_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/common_helpers/rwd_header_footer.js')) -footer_edx_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/footer-edx.js')) +rwd_header_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/utils/rwd_header.js')) staff_grading_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/staff_grading/**/*.js')) open_ended_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/open_ended/**/*.js')) notes_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/notes/**/*.js')) @@ -1196,7 +1239,7 @@ instructor_dash_js = ( # These are not courseware, so they do not need many of the courseware-specific # JavaScript modules. student_account_js = [ - 'js/utils/rwd_header_footer.js', + 'js/utils/rwd_header.js', 'js/utils/edx.utils.validate.js', 'js/form.ext.js', 'js/my_courses_dropdown.js', @@ -1342,17 +1385,29 @@ PIPELINE_CSS = { ], 'output_filename': 'css/lms-style-xmodule-annotations.css', }, - 'style-edx-footer': { + FOOTER_CSS['openedx']['ltr']: { 'source_filenames': [ - 'sass/footer-v3.css', + 'sass/lms-footer.css', ], - 'output_filename': 'css/lms-footer-edx.css', + 'output_filename': 'css/lms-footer.css', }, - 'style-edx-footer-rtl': { + FOOTER_CSS['openedx']['rtl']: { 'source_filenames': [ - 'sass/footer-v3-rtl.css', + 'sass/lms-footer-rtl.css', ], - 'output_filename': 'css/lms-footer-edx-rtl.css', + 'output_filename': 'css/lms-footer-rtl.css' + }, + FOOTER_CSS['edx']['ltr']: { + 'source_filenames': [ + 'sass/lms-footer-edx.css', + ], + 'output_filename': 'css/lms-footer-edx.css' + }, + FOOTER_CSS['edx']['rtl']: { + 'source_filenames': [ + 'sass/lms-footer-edx-rtl.css', + ], + 'output_filename': 'css/lms-footer-edx-rtl.css' }, } @@ -1423,9 +1478,9 @@ PIPELINE_JS = { 'source_filenames': dashboard_js, 'output_filename': 'js/dashboard.js' }, - 'rwd_header_footer': { - 'source_filenames': rwd_header_footer_js, - 'output_filename': 'js/rwd_header_footer.js' + 'rwd_header': { + 'source_filenames': rwd_header_js, + 'output_filename': 'js/rwd_header.js' }, 'student_account': { 'source_filenames': student_account_js, @@ -1448,8 +1503,8 @@ PIPELINE_JS = { 'output_filename': 'js/ccx.js' }, 'footer_edx': { - 'source_filenames': footer_edx_js, - 'output_filename': 'js/footer_edx.js' + 'source_filenames': ['js/footer-edx.js'], + 'output_filename': 'js/footer-edx.js', } } @@ -1492,6 +1547,7 @@ PIPELINE_UGLIFYJS_BINARY = 'node_modules/.bin/uglifyjs' # Setting that will only affect the edX version of django-pipeline until our changes are merged upstream PIPELINE_COMPILE_INPLACE = True + ################################# CELERY ###################################### # Message configuration @@ -1797,25 +1853,14 @@ MKTG_URL_LINK_MAP = { ################# Social Media Footer Links ####################### # The names list controls the order of social media # links in the footer. -if FEATURES.get('ENABLE_FOOTER_V3'): - SOCIAL_MEDIA_FOOTER_NAMES = [ - "facebook", - "twitter", - "linkedin", - "weibo", - "vk", - ] -else: - SOCIAL_MEDIA_FOOTER_NAMES = [ - "facebook", - "twitter", - "linkedin", - "google_plus", - "tumblr", - "meetup", - "reddit", - "youtube", - ] +SOCIAL_MEDIA_FOOTER_NAMES = [ + "facebook", + "twitter", + "youtube", + "linkedin", + "google_plus", + "reddit", +] # The footer URLs dictionary maps social footer names # to URLs defined in configuration. @@ -1852,7 +1897,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.tumblr.com. Please # translate this the way that Tumblr advertises in your language. "title": _("Tumblr"), - "icon": "fa-tumblr-square" + "icon": "fa-tumblr" }, "meetup": { # Translators: This is the website name of www.meetup.com. Please @@ -1864,7 +1909,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.reddit.com. Please # translate this the way that Reddit advertises in your language. "title": _("Reddit"), - "icon": "fa-reddit-square" + "icon": "fa-reddit" }, "vk": { # Translators: This is the website name of https://vk.com. Please @@ -1882,7 +1927,7 @@ SOCIAL_MEDIA_FOOTER_DISPLAY = { # Translators: This is the website name of www.youtube.com. Please # translate this the way that YouTube advertises in your language. "title": _("Youtube"), - "icon": "fa-youtube-square" + "icon": "fa-youtube" } } diff --git a/lms/static/js/footer-edx.js b/lms/static/js/footer-edx.js index 0d589f5885..0b388d1aa6 100644 --- a/lms/static/js/footer-edx.js +++ b/lms/static/js/footer-edx.js @@ -7,18 +7,6 @@ var edx = edx || {}; var _fn = { el: '#footer-edx-v3', - init: function() { - _fn.$el = _fn.$el || $( _fn.el ); - - /** - * Only continue if the expected element - * to add footer to is in the DOM - */ - if ( _fn.$el.length > -1 ) { - _fn.footer.get(); - } - }, - analytics: { init: function() { _fn.$el = _fn.$el || $( _fn.el ); @@ -27,7 +15,7 @@ var edx = edx || {}; * Only continue if the expected element * to add footer to is in the DOM */ - if ( _fn.$el.length > -1 ) { + if ( _fn.$el.length ) { _fn.analytics.eventListener(); } }, @@ -50,31 +38,12 @@ var edx = edx || {}; } } }, - - footer: { - get: function() { - $.ajax({ - url: 'https://courses.edx.org/api/v1/branding/footer', - type: 'GET', - dataType: 'html', - success: function( data ) { - _fn.footer.render( data ); - } - }); - }, - - render: function( html ) { - _fn.$el.html( html ); - } - } }; return { - load: _fn.init, analytics: _fn.analytics.init }; })(); - // Initialize the analytics events edx.footer.analytics(); })(jQuery); diff --git a/lms/static/sass/_build-footer-edx.scss b/lms/static/sass/_build-footer-edx.scss new file mode 100644 index 0000000000..09b539b5c5 --- /dev/null +++ b/lms/static/sass/_build-footer-edx.scss @@ -0,0 +1,19 @@ +// ---------------------------------------- +// LMS edx.org Footer: Shared Build Compile + +// base - utilities +@import 'base/variables'; +@import 'base/mixins'; + +footer#footer-edx-v3 { + @import 'base/extends'; + + // base - starter + @import 'base/base'; +} + +// base - elements +@import 'elements/typography'; + +// shared - platform +@import 'shared/footer-edx'; diff --git a/lms/static/sass/_build-lms.scss b/lms/static/sass/_build-lms.scss index 3b05db8196..c5f95fe8bf 100644 --- a/lms/static/sass/_build-lms.scss +++ b/lms/static/sass/_build-lms.scss @@ -21,7 +21,7 @@ @import 'shared/fields'; @import 'shared/forms'; @import 'shared/footer'; -@import 'shared/footer-edx'; // Replaces most of the footer partial. Will update footer to remove edx specific styles once feature flag removed. +@import 'shared/footer-edx'; @import 'shared/header'; @import 'shared/course_object'; @import 'shared/course_filter'; diff --git a/lms/static/sass/base/_variables-ltr.scss b/lms/static/sass/base/_variables-ltr.scss new file mode 100644 index 0000000000..4e999af162 --- /dev/null +++ b/lms/static/sass/base/_variables-ltr.scss @@ -0,0 +1,7 @@ +// Variables for LMS (left-to-right) +// ================================== + +// Neat +// ================================== +// Sets the default layout direction of the grid. +$default-layout-direction: LTR !global; diff --git a/lms/static/sass/base/_variables-rtl.scss b/lms/static/sass/base/_variables-rtl.scss new file mode 100644 index 0000000000..b49e44b1f0 --- /dev/null +++ b/lms/static/sass/base/_variables-rtl.scss @@ -0,0 +1,7 @@ +// Variables for LMS (right-to-left) +// ================================== + +// Neat +// ================================== +// Sets the default layout direction of the grid. +$default-layout-direction: RTL !global; diff --git a/lms/static/sass/lms-footer-edx-rtl.scss b/lms/static/sass/lms-footer-edx-rtl.scss new file mode 100644 index 0000000000..e189fe9e6d --- /dev/null +++ b/lms/static/sass/lms-footer-edx-rtl.scss @@ -0,0 +1,10 @@ +// Footer for edx.org (right-to-left) +// ================================== + +// libs and resets *do not edit* +@import 'bourbon/bourbon'; // lib - bourbon +@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages +@import 'base/variables-rtl'; + +// Import shared build for the edx.org footer +@import 'build-footer-edx' diff --git a/lms/static/sass/lms-footer-edx.scss b/lms/static/sass/lms-footer-edx.scss new file mode 100644 index 0000000000..afa3d26592 --- /dev/null +++ b/lms/static/sass/lms-footer-edx.scss @@ -0,0 +1,10 @@ +// Footer for edx.org (left-to-right) +// ================================== + +// 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'; + +// Import shared build for the edx.org footer +@import 'build-footer-edx' diff --git a/lms/static/sass/lms-footer-edx-rtl.scss.mako b/lms/static/sass/lms-footer-rtl.scss.mako similarity index 60% rename from lms/static/sass/lms-footer-edx-rtl.scss.mako rename to lms/static/sass/lms-footer-rtl.scss.mako index 7c6f4466c7..cc91fab379 100644 --- a/lms/static/sass/lms-footer-edx-rtl.scss.mako +++ b/lms/static/sass/lms-footer-rtl.scss.mako @@ -1,14 +1,9 @@ -## Note: This Sass infrastructure is repeated in application-extend1 and application-extend2, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx - -// lms - css application architecture -// ==================== +// Footer for OpenEdX (right-to-left) +// ================================== // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon -@import 'vendor/bi-app/bi-app-rtl'; // set the layout for left to right languages - -// BASE *default edX offerings* -// ==================== +@import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages // base - utilities @import 'base/variables'; @@ -27,19 +22,14 @@ @import '${env.get('THEME_NAME')}'; % endif -// base - assets -@import 'base/font_face'; - -footer#footer-edx-v3 { +footer#footer-openedx { + @import 'base/reset'; @import 'base/extends'; - - // base - starter @import 'base/base'; - } // base - elements @import 'elements/typography'; // shared - platform -@import 'shared/footer-edx'; +@import 'shared/footer'; diff --git a/lms/static/sass/lms-footer-edx.scss.mako b/lms/static/sass/lms-footer.scss.mako similarity index 66% rename from lms/static/sass/lms-footer-edx.scss.mako rename to lms/static/sass/lms-footer.scss.mako index ceabdbc5de..0b4aff0381 100644 --- a/lms/static/sass/lms-footer-edx.scss.mako +++ b/lms/static/sass/lms-footer.scss.mako @@ -1,15 +1,10 @@ -## Note: This Sass infrastructure is repeated in application-extend1 and application-extend2, but needed in order to address an IE9 rule limit within CSS - http://blogs.msdn.com/b/ieinternals/archive/2011/05/14/10164546.aspx - -// lms - css application architecture -// ==================== +// Footer for OpenEdX (left-to-right) +// ================================== // 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 -// BASE *default edX offerings* -// ==================== - // base - utilities @import 'base/variables'; @import 'base/mixins'; @@ -27,19 +22,14 @@ @import '${env.get('THEME_NAME')}'; % endif -// base - assets -@import 'base/font_face'; - -footer#footer-edx-v3 { +footer#footer-openedx { + @import 'base/reset'; @import 'base/extends'; - - // base - starter @import 'base/base'; - } // base - elements @import 'elements/typography'; // shared - platform -@import 'shared/footer-edx'; +@import 'shared/footer'; diff --git a/lms/static/sass/lms-main-rtl.scss.mako b/lms/static/sass/lms-main-rtl.scss.mako index 1bac12f885..dcb06ff91a 100644 --- a/lms/static/sass/lms-main-rtl.scss.mako +++ b/lms/static/sass/lms-main-rtl.scss.mako @@ -4,6 +4,7 @@ // libs and resets *do not edit* @import 'bourbon/bourbon'; // lib - bourbon @import 'vendor/bi-app/bi-app-rtl'; // set the layout for right to left languages +@import 'base/variables-rtl'; // BASE *default edX offerings* // ==================== diff --git a/lms/static/sass/lms-main.scss.mako b/lms/static/sass/lms-main.scss.mako index 3a1a71f6e5..83d7be23d3 100644 --- a/lms/static/sass/lms-main.scss.mako +++ b/lms/static/sass/lms-main.scss.mako @@ -4,6 +4,7 @@ // 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* // ==================== diff --git a/lms/static/sass/shared/_footer-edx.scss b/lms/static/sass/shared/_footer-edx.scss index fc2babf3be..b82484c91b 100644 --- a/lms/static/sass/shared/_footer-edx.scss +++ b/lms/static/sass/shared/_footer-edx.scss @@ -80,7 +80,7 @@ footer#footer-edx-v3 { margin-bottom: 30px; } - .sm-link { + a.sm-link { @include float(left); @include margin(0, 0, 10px, 12px); @include font-size(28); diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index d38397475c..8240d7ce7b 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -10,7 +10,7 @@ background: $footer-bg; clear: both; - footer { + footer#footer-openedx { @include clearfix(); @include box-sizing(border-box); max-width: grid-width(12); @@ -286,6 +286,8 @@ $edx-footer-bg-color: rgb(252,252,252); } } +// TODO (ECOM-1339): Remove the "new" (v2) footer once the v3 footer +// is permanently enabled. .edx-footer-new { background: $edx-footer-bg-color; diff --git a/lms/templates/commerce/checkout_receipt.html b/lms/templates/commerce/checkout_receipt.html index 2989e84597..448fd6faf7 100644 --- a/lms/templates/commerce/checkout_receipt.html +++ b/lms/templates/commerce/checkout_receipt.html @@ -17,7 +17,7 @@ ${_("Receipt")} <%block name="js_extra"> -<%static:js group='rwd_header_footer'/> +<%static:js group='rwd_header'/> diff --git a/lms/templates/footer-edx-v2.html b/lms/templates/footer-edx-v2.html index 5072e66700..042c06b849 100644 --- a/lms/templates/footer-edx-v2.html +++ b/lms/templates/footer-edx-v2.html @@ -1,4 +1,5 @@ ## mako +## TODO (ECOM-1339): Delete this template once the V3 footer is enabled <%! from django.core.urlresolvers import reverse %> <%! from django.utils.translation import ugettext as _ %> <%namespace name='static' file='static_content.html'/> @@ -50,7 +51,7 @@ {% 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 and not ENABLE_FOOTER_V3 %} - {% include "footer-edx-v2.html" %} - {% elif IS_EDX_DOMAIN and ENABLE_FOOTER_V3 %} - {% include "footer-edx-v3.html" %} + {% elif IS_EDX_DOMAIN %} + {# TODO (ECOM-1339): Remove this check once we switch to the v3 footer permanently. #} + {% if ENABLE_BRANDING_API %} + {% include "footer-edx-v3.html" %} + {% else %} + {% include "footer-edx-v2.html" %} + {% endif %} {% else %} {% include "footer.html" %} {% endif %} diff --git a/lms/templates/verify_student/_verification_header.html b/lms/templates/verify_student/_verification_header.html index 519b14ae8d..db71f588ab 100644 --- a/lms/templates/verify_student/_verification_header.html +++ b/lms/templates/verify_student/_verification_header.html @@ -27,5 +27,5 @@ <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/templates/verify_student/incourse_reverify.html b/lms/templates/verify_student/incourse_reverify.html index 6a24eb072f..723e8b7363 100644 --- a/lms/templates/verify_student/incourse_reverify.html +++ b/lms/templates/verify_student/incourse_reverify.html @@ -20,7 +20,7 @@ from verify_student.views import PayAndVerifyView % endfor <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/templates/verify_student/pay_and_verify.html b/lms/templates/verify_student/pay_and_verify.html index 259f646cc3..ac99eb2a53 100644 --- a/lms/templates/verify_student/pay_and_verify.html +++ b/lms/templates/verify_student/pay_and_verify.html @@ -34,7 +34,7 @@ from verify_student.views import PayAndVerifyView % endfor <%block name="js_extra"> - <%static:js group='rwd_header_footer'/> + <%static:js group='rwd_header'/> diff --git a/lms/urls.py b/lms/urls.py index 99120f1834..52d4d561aa 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -427,6 +427,8 @@ if settings.COURSEWARE_ENABLED: # Student Notes url(r'^courses/{}/edxnotes'.format(settings.COURSE_ID_PATTERN), include('edxnotes.urls'), name="edxnotes_endpoints"), + + url(r'^api/branding/v1/', include('branding.api_urls')), ) # allow course staff to change to student view of courseware