Commit 70d8c5c2 by Anthony Mangano

update footer endpoint in branding api to support inclusion of language selector

parent aa4eba33
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
import json import json
import urllib import urllib
from django.test import TestCase from django.test import TestCase
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
...@@ -10,6 +11,8 @@ import mock ...@@ -10,6 +11,8 @@ import mock
import ddt import ddt
from config_models.models import cache from config_models.models import cache
from branding.models import BrandingApiConfig 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.site_configuration.tests.mixins import SiteMixin
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
...@@ -208,6 +211,38 @@ class TestFooter(TestCase): ...@@ -208,6 +211,38 @@ class TestFooter(TestCase):
else: else:
self.assertNotIn("vendor", resp.content) 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): def test_no_supported_accept_type(self):
self._set_feature_flag(True) self._set_feature_flag(True)
resp = self._get_footer(accepts="application/x-shockwave-flash") resp = self._get_footer(accepts="application/x-shockwave-flash")
...@@ -230,6 +265,20 @@ class TestFooter(TestCase): ...@@ -230,6 +265,20 @@ class TestFooter(TestCase):
return self.client.get(url, HTTP_ACCEPT=accepts) 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('<option value="{}" selected="selected">'.format(selected_language), content)
# Verify the language choices
for language in released_languages():
if language.code == selected_language:
continue
self.assertIn('<option value="{}">'.format(language.code), content)
class TestIndex(SiteMixin, TestCase): class TestIndex(SiteMixin, TestCase):
""" Test the index view """ """ Test the index view """
......
...@@ -20,6 +20,7 @@ from edxmako.shortcuts import marketing_link ...@@ -20,6 +20,7 @@ from edxmako.shortcuts import marketing_link
from util.cache import cache_if_anonymous from util.cache import cache_if_anonymous
from util.json_request import JsonResponse from util.json_request import JsonResponse
import branding.api as branding_api 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 from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -122,12 +123,13 @@ def _footer_css_urls(request, package_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. """Render the footer as HTML.
Arguments: Arguments:
show_openedx_logo (bool): If True, include the OpenEdX logo in the rendered HTML. 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_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 Returns: unicode
...@@ -141,6 +143,7 @@ def _render_footer_html(request, show_openedx_logo, include_dependencies): ...@@ -141,6 +143,7 @@ def _render_footer_html(request, show_openedx_logo, include_dependencies):
'footer_css_urls': _footer_css_urls(request, css_name), 'footer_css_urls': _footer_css_urls(request, css_name),
'bidi': bidi, 'bidi': bidi,
'include_dependencies': include_dependencies, 'include_dependencies': include_dependencies,
'include_language_selector': include_language_selector
} }
return render_to_response("footer.html", context) return render_to_response("footer.html", context)
...@@ -235,6 +238,13 @@ def footer(request): ...@@ -235,6 +238,13 @@ def footer(request):
GET /api/branding/v1/footer?language=en GET /api/branding/v1/footer?language=en
Accepts: text/html 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) Example: Retrieving the footer with all JS and CSS dependencies (for testing)
GET /api/branding/v1/footer?include-dependencies=1 GET /api/branding/v1/footer?include-dependencies=1
...@@ -261,19 +271,26 @@ def footer(request): ...@@ -261,19 +271,26 @@ def footer(request):
except LookupError: except LookupError:
language = settings.LANGUAGE_CODE 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 # Render the footer information based on the extension
if 'text/html' in accepts or '*/*' in accepts: if 'text/html' in accepts or '*/*' in accepts:
cache_key = u"branding.footer.{params}.html".format( cache_params = {
params=urllib.urlencode({ 'language': language,
'language': language, 'show_openedx_logo': show_openedx_logo,
'show_openedx_logo': show_openedx_logo, 'include_dependencies': include_dependencies
'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) content = cache.get(cache_key)
if content is None: if content is None:
with translation.override(language): 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) cache.set(cache_key, content, settings.FOOTER_CACHE_TIMEOUT)
return HttpResponse(content, status=200, content_type="text/html; charset=utf-8") return HttpResponse(content, status=200, content_type="text/html; charset=utf-8")
......
...@@ -55,8 +55,8 @@ p { ...@@ -55,8 +55,8 @@ p {
} }
span { span {
font: inherit;
color: inherit; color: inherit;
font: inherit;
} }
/* Fix for CodeMirror: prevent top-level span from affecting deeply-embedded span in CodeMirror */ /* Fix for CodeMirror: prevent top-level span from affecting deeply-embedded span in CodeMirror */
......
...@@ -33,6 +33,10 @@ footer#footer-edx-v3 { ...@@ -33,6 +33,10 @@ footer#footer-edx-v3 {
font-family: $sans-serif; font-family: $sans-serif;
} }
.copyright {
margin-top: 30px;
}
.site-nav, .site-nav,
.legal-notices { .legal-notices {
li { li {
...@@ -68,7 +72,7 @@ footer#footer-edx-v3 { ...@@ -68,7 +72,7 @@ footer#footer-edx-v3 {
} }
.legal-notices { .legal-notices {
margin: 20px 0 30px; margin: 20px 0;
} }
.openedx-link { .openedx-link {
...@@ -89,7 +93,7 @@ footer#footer-edx-v3 { ...@@ -89,7 +93,7 @@ footer#footer-edx-v3 {
.social-media-links, .social-media-links,
.mobile-app-links { .mobile-app-links {
@extend %ui-no-list; @extend %ui-no-list;
.list-item { .list-item {
display: inline-block; display: inline-block;
} }
...@@ -107,6 +111,12 @@ footer#footer-edx-v3 { ...@@ -107,6 +111,12 @@ footer#footer-edx-v3 {
margin-bottom: 30px; margin-bottom: 30px;
} }
.icon {
font-family: 'FontAwesome';
font-style: normal;
color: $edx-footer-link-color;
}
a.sm-link { a.sm-link {
@include float(left); @include float(left);
@include margin(0, 0, 10px, 10px); @include margin(0, 0, 10px, 10px);
...@@ -129,11 +139,6 @@ footer#footer-edx-v3 { ...@@ -129,11 +139,6 @@ footer#footer-edx-v3 {
opacity: 0.7; opacity: 0.7;
border: none; border: none;
} }
.icon {
font-family: 'FontAwesome';
color: $edx-footer-link-color;
}
} }
.app-link { .app-link {
...@@ -208,4 +213,13 @@ footer#footer-edx-v3 { ...@@ -208,4 +213,13 @@ footer#footer-edx-v3 {
margin-bottom: 50px; margin-bottom: 50px;
} }
} }
.footer-language-selector {
margin: 20px 0;
label[for=footer-language-select] {
display: inline-block;
cursor: initial;
}
}
} }
...@@ -37,6 +37,11 @@ ...@@ -37,6 +37,11 @@
} }
} }
.icon {
font-family: 'FontAwesome';
font-style: normal;
}
// colophon // colophon
.colophon { .colophon {
@include span-columns(8); @include span-columns(8);
...@@ -186,6 +191,13 @@ ...@@ -186,6 +191,13 @@
} }
} }
} }
.footer-language-selector {
label[for=footer-language-select] {
display: inline-block;
cursor: initial;
}
}
} }
// edx theme overrides // edx theme overrides
......
...@@ -28,6 +28,10 @@ ...@@ -28,6 +28,10 @@
</ol> </ol>
</nav> </nav>
% if include_language_selector:
<%include file="widgets/footer-language-selector.html"/>
% endif
<div class="wrapper-logo"> <div class="wrapper-logo">
<p> <p>
<a href="/"> <a href="/">
......
## 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.')
%>
<div class="footer-language-selector">
<label for="footer-language-select">
<span class="icon fa fa-globe" aria-hidden="true"></span>
<span class="sr">${_("Choose Language")}</span>
</label>
<select id="footer-language-select" name="language" onchange="footerLanguageSelector.handleSelection(this)">
% for language in sorted(released_languages(), key=lambda x: x.code):
<% language_name = Locale.parse(language.code.replace('_', '-'), sep='-').language_name %>
% if language.code == LANGUAGE_CODE:
<option value="${language.code}" selected="selected">${language_name}</option>
% else:
<option value="${language.code}">${language_name}</option>
% endif
% endfor
</select>
</div>
<script type="text/javascript">
window.footerLanguageSelector = {
##
## Set the language cookie using the same settings as
## https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/lang_pref/views.py#L26
## and
## https://github.com/edx/edx-platform/blob/master/openedx/core/djangoapps/lang_pref/middleware.py#63
## Then refresh the page so that the language negotiation middleware can read it and re-render everything
## in the selected language.
##
## NOTE: For logged-in users, the LMS language negotiation middleware should persist the selected language
## preference to the user's profile. This effect may be delayed on pages that do not use the LMS language
## selection middleware.
##
handleSelection: function($select) {
this.setLanguageCookie($select.value, this.refreshPage);
},
setLanguageCookie: function(value, callback) {
var cookie = '${settings.LANGUAGE_COOKIE | n, js_escaped_string}=' + value + ';path=/';
% if settings.SESSION_COOKIE_DOMAIN:
cookie += ';domain=${settings.SESSION_COOKIE_DOMAIN | n, js_escaped_string}';
% endif
% if COOKIE_DURATION:
cookie += ';max-age=${COOKIE_DURATION | n, js_escaped_string}';
% endif
document.cookie = cookie;
callback();
},
refreshPage: function() {
window.location.reload();
}
};
</script>
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
<div class="footer-content-wrapper"> <div class="footer-content-wrapper">
<div class="footer-logo"> <div class="footer-logo">
<a href="${marketing_link('ROOT')}"> <a href="${marketing_link('ROOT')}">
<img alt="edX Home Page" src="${footer['logo_image']}"> <img alt="${_('edX Home Page')}" src="${footer['logo_image']}">
</a> </a>
</div> </div>
...@@ -44,6 +44,11 @@ ...@@ -44,6 +44,11 @@
% endfor % endfor
</ul> </ul>
</nav> </nav>
% if include_language_selector:
<%include file="widgets/footer-language-selector.html"/>
% endif
<p class="copyright">${_( <p class="copyright">${_(
u"\u00A9 2012-{year} edX Inc. All rights reserved except where noted. " u"\u00A9 2012-{year} edX Inc. All rights reserved except where noted. "
u"EdX, Open edX and the edX and Open EdX logos are registered trademarks " u"EdX, Open edX and the edX and Open EdX logos are registered trademarks "
......
...@@ -43,6 +43,10 @@ from django.utils.translation import ugettext as _ ...@@ -43,6 +43,10 @@ from django.utils.translation import ugettext as _
</ol> </ol>
</nav> </nav>
% if include_language_selector:
<%include file='widgets/footer-language-selector.html'/>
% endif
<div class="wrapper-logo"> <div class="wrapper-logo">
<p> <p>
<a href="/"> <a href="/">
......
...@@ -21,6 +21,11 @@ ...@@ -21,6 +21,11 @@
<li><a href="${reverse('tos')}#copyright">${_("Copyright")}</a></li> <li><a href="${reverse('tos')}#copyright">${_("Copyright")}</a></li>
</ol> </ol>
</nav> </nav>
% if include_language_selector:
<%include file='widgets/footer-language-selector.html'/>
% endif
</div> </div>
<div class="references"> <div class="references">
<span>Built on <a href="http://open.edx.org">OpenEdX</a>.</span> <span>Built on <a href="http://open.edx.org">OpenEdX</a>.</span>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment