diff --git a/lms/djangoapps/branding/tests/test_views.py b/lms/djangoapps/branding/tests/test_views.py index 2098f654d9..0f123c2ad4 100644 --- a/lms/djangoapps/branding/tests/test_views.py +++ b/lms/djangoapps/branding/tests/test_views.py @@ -3,6 +3,7 @@ import json import urllib from django.test import TestCase +from django.contrib.auth.models import User from django.core.urlresolvers import reverse from django.conf import settings @@ -10,6 +11,8 @@ import mock import ddt from config_models.models import cache from branding.models import BrandingApiConfig +from openedx.core.djangoapps.dark_lang.models import DarkLangConfig +from openedx.core.djangoapps.lang_pref.api import released_languages from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context from student.tests.factories import UserFactory @@ -208,6 +211,38 @@ class TestFooter(TestCase): else: self.assertNotIn("vendor", resp.content) + @ddt.data( + # OpenEdX + (None, None, '1'), + (None, 'eo', '1'), + (None, None, ''), + + # EdX.org + ('edx.org', None, '1'), + ('edx.org', 'eo', '1'), + ('edx.org', None, '') + ) + @ddt.unpack + def test_include_language_selector(self, theme, language, include_language_selector): + self._set_feature_flag(True) + DarkLangConfig(released_languages='en,eo,es-419,fr', enabled=True, changed_by=User().save()).save() + + with with_comprehensive_theme_context(theme): + params = { + key: val for key, val in [ + ('language', language), ('include-language-selector', include_language_selector) + ] if val + } + resp = self._get_footer(accepts="text/html", params=params) + + self.assertEqual(resp.status_code, 200) + + if include_language_selector: + selected_language = language if language else 'en' + self._verify_language_selector(resp.content, selected_language) + else: + self.assertNotIn('footer-language-selector', resp.content) + def test_no_supported_accept_type(self): self._set_feature_flag(True) resp = self._get_footer(accepts="application/x-shockwave-flash") @@ -230,6 +265,20 @@ class TestFooter(TestCase): return self.client.get(url, HTTP_ACCEPT=accepts) + def _verify_language_selector(self, content, selected_language): + """ Verify that the language selector is present and correctly configured.""" + # Verify the selector is included + self.assertIn('footer-language-selector', content) + + # Verify the correct language is selected + self.assertIn(''.format(selected_language), content) + + # Verify the language choices + for language in released_languages(): + if language.code == selected_language: + continue + self.assertIn(''.format(language.code), content) + class TestIndex(SiteMixin, TestCase): """ Test the index view """ diff --git a/lms/djangoapps/branding/views.py b/lms/djangoapps/branding/views.py index 93d10ede5d..c4f68cc53e 100644 --- a/lms/djangoapps/branding/views.py +++ b/lms/djangoapps/branding/views.py @@ -20,6 +20,7 @@ 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 +from openedx.core.djangoapps.lang_pref.api import released_languages from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers log = logging.getLogger(__name__) @@ -122,12 +123,13 @@ def _footer_css_urls(request, package_name): ] -def _render_footer_html(request, show_openedx_logo, include_dependencies): +def _render_footer_html(request, show_openedx_logo, include_dependencies, include_language_selector): """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. + include_language_selector (bool): If True, include a language selector with all supported languages. Returns: unicode @@ -141,6 +143,7 @@ def _render_footer_html(request, show_openedx_logo, include_dependencies): 'footer_css_urls': _footer_css_urls(request, css_name), 'bidi': bidi, 'include_dependencies': include_dependencies, + 'include_language_selector': include_language_selector } return render_to_response("footer.html", context) @@ -235,6 +238,13 @@ def footer(request): GET /api/branding/v1/footer?language=en Accepts: text/html + + Example: Retrieving the footer with a language selector + + GET /api/branding/v1/footer?include-language-selector=1 + Accepts: text/html + + Example: Retrieving the footer with all JS and CSS dependencies (for testing) GET /api/branding/v1/footer?include-dependencies=1 @@ -261,19 +271,26 @@ def footer(request): except LookupError: language = settings.LANGUAGE_CODE + # Include a language selector + include_language_selector = request.GET.get('include-language-selector', '') == '1' + # 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, - }) - ) + cache_params = { + 'language': language, + 'show_openedx_logo': show_openedx_logo, + 'include_dependencies': include_dependencies + } + if include_language_selector: + cache_params['language_selector_options'] = ','.join(sorted([lang.code for lang in released_languages()])) + cache_key = u"branding.footer.{params}.html".format(params=urllib.urlencode(cache_params)) + content = cache.get(cache_key) if content is None: with translation.override(language): - content = _render_footer_html(request, show_openedx_logo, include_dependencies) + content = _render_footer_html( + request, show_openedx_logo, include_dependencies, include_language_selector + ) cache.set(cache_key, content, settings.FOOTER_CACHE_TIMEOUT) return HttpResponse(content, status=200, content_type="text/html; charset=utf-8") diff --git a/lms/static/sass/base/_base.scss b/lms/static/sass/base/_base.scss index 8c9f121484..efa59bb9f4 100644 --- a/lms/static/sass/base/_base.scss +++ b/lms/static/sass/base/_base.scss @@ -55,8 +55,8 @@ p { } span { - font: inherit; color: inherit; + font: inherit; } /* Fix for CodeMirror: prevent top-level span from affecting deeply-embedded span in CodeMirror */ diff --git a/lms/static/sass/shared/_footer-edx.scss b/lms/static/sass/shared/_footer-edx.scss index 1ef6d748d2..9da43b62ad 100644 --- a/lms/static/sass/shared/_footer-edx.scss +++ b/lms/static/sass/shared/_footer-edx.scss @@ -33,6 +33,10 @@ footer#footer-edx-v3 { font-family: $sans-serif; } + .copyright { + margin-top: 30px; + } + .site-nav, .legal-notices { li { @@ -68,7 +72,7 @@ footer#footer-edx-v3 { } .legal-notices { - margin: 20px 0 30px; + margin: 20px 0; } .openedx-link { @@ -89,7 +93,7 @@ footer#footer-edx-v3 { .social-media-links, .mobile-app-links { @extend %ui-no-list; - + .list-item { display: inline-block; } @@ -107,6 +111,12 @@ footer#footer-edx-v3 { margin-bottom: 30px; } + .icon { + font-family: 'FontAwesome'; + font-style: normal; + color: $edx-footer-link-color; + } + a.sm-link { @include float(left); @include margin(0, 0, 10px, 10px); @@ -129,11 +139,6 @@ footer#footer-edx-v3 { opacity: 0.7; border: none; } - - .icon { - font-family: 'FontAwesome'; - color: $edx-footer-link-color; - } } .app-link { @@ -208,4 +213,13 @@ footer#footer-edx-v3 { margin-bottom: 50px; } } + + .footer-language-selector { + margin: 20px 0; + + label[for=footer-language-select] { + display: inline-block; + cursor: initial; + } + } } diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index b92cdb475c..f368538651 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -37,6 +37,11 @@ } } + .icon { + font-family: 'FontAwesome'; + font-style: normal; + } + // colophon .colophon { @include span-columns(8); @@ -186,6 +191,13 @@ } } } + + .footer-language-selector { + label[for=footer-language-select] { + display: inline-block; + cursor: initial; + } + } } // edx theme overrides diff --git a/lms/templates/footer.html b/lms/templates/footer.html index 569c8d2d4e..860d1d9d2a 100644 --- a/lms/templates/footer.html +++ b/lms/templates/footer.html @@ -28,6 +28,10 @@ + % if include_language_selector: + <%include file="widgets/footer-language-selector.html"/> + % endif + diff --git a/lms/templates/widgets/footer-language-selector.html b/lms/templates/widgets/footer-language-selector.html new file mode 100644 index 0000000000..4a8b44e552 --- /dev/null +++ b/lms/templates/widgets/footer-language-selector.html @@ -0,0 +1,72 @@ +## Language-selection widget for the footer. +## +## Requires settings.LANGUAGE_COOKIE. +<%page expression_filter="h"/> +<%! + from babel import Locale + from django.conf import settings + from django.utils.translation import ugettext as _ + + from openedx.core.djangoapps.lang_pref import COOKIE_DURATION + from openedx.core.djangoapps.lang_pref.api import released_languages + from openedx.core.djangolib.js_utils import js_escaped_string + + # Make sure LANGUAGE_COOKIE is present. + if not settings.LANGUAGE_COOKIE: + raise ValueError('settings.LANGUAGE_COOKIE is required to use footer-language-selector.') +%> + + + + diff --git a/themes/edx.org/lms/templates/footer.html b/themes/edx.org/lms/templates/footer.html index fd2abc730d..a991e19337 100644 --- a/themes/edx.org/lms/templates/footer.html +++ b/themes/edx.org/lms/templates/footer.html @@ -21,7 +21,7 @@
diff --git a/lms/templates/widgets/footer-language-selector.html b/lms/templates/widgets/footer-language-selector.html new file mode 100644 index 0000000000..4a8b44e552 --- /dev/null +++ b/lms/templates/widgets/footer-language-selector.html @@ -0,0 +1,72 @@ +## Language-selection widget for the footer. +## +## Requires settings.LANGUAGE_COOKIE. +<%page expression_filter="h"/> +<%! + from babel import Locale + from django.conf import settings + from django.utils.translation import ugettext as _ + + from openedx.core.djangoapps.lang_pref import COOKIE_DURATION + from openedx.core.djangoapps.lang_pref.api import released_languages + from openedx.core.djangolib.js_utils import js_escaped_string + + # Make sure LANGUAGE_COOKIE is present. + if not settings.LANGUAGE_COOKIE: + raise ValueError('settings.LANGUAGE_COOKIE is required to use footer-language-selector.') +%> + + + + diff --git a/themes/edx.org/lms/templates/footer.html b/themes/edx.org/lms/templates/footer.html index fd2abc730d..a991e19337 100644 --- a/themes/edx.org/lms/templates/footer.html +++ b/themes/edx.org/lms/templates/footer.html @@ -21,7 +21,7 @@