diff --git a/lms/djangoapps/branding/api.py b/lms/djangoapps/branding/api.py
index 3f51d489fa..d590ffc264 100644
--- a/lms/djangoapps/branding/api.py
+++ b/lms/djangoapps/branding/api.py
@@ -25,6 +25,7 @@ from branding.models import BrandingApiConfig
log = logging.getLogger("edx.footer")
+EMPTY_URL = '#'
def is_enabled():
@@ -329,3 +330,64 @@ def _absolute_url_staticfile(is_secure, name):
# For local development, the returned URL will be relative,
# so we need to make it absolute.
return _absolute_url(is_secure, url_path)
+
+
+def get_microsite_url(name):
+ """
+ Look up and return the value for given url name in microsite configuration.
+ URLs are saved in "urls" dictionary inside Microsite Configuration.
+
+ Return 'EMPTY_URL' if given url name is not defined in microsite configuration urls.
+ """
+ urls = microsite.get_value("urls", default={})
+ return urls.get(name) or EMPTY_URL
+
+
+def get_url(name):
+ """
+ Lookup and return page url, lookup is performed in the following order
+
+ 1. get microsite url, If microsite URL override exists, return it
+ 2. Otherwise return the marketing URL.
+
+ :return: string containing page url.
+ """
+ # If a microsite URL override exists, return it. Otherwise return the marketing URL.
+ microsite_url = get_microsite_url(name)
+ if microsite_url != EMPTY_URL:
+ return microsite_url
+
+ # get marketing link, if marketing is disabled then platform url will be used instead.
+ url = marketing_link(name)
+
+ return url or EMPTY_URL
+
+
+def get_base_url(is_secure):
+ """
+ Return Base URL for site/microsite.
+ Arguments:
+ is_secure (bool): If true, use HTTPS as the protocol.
+ """
+ return _absolute_url(is_secure=is_secure, url_path="")
+
+
+def get_tos_and_honor_code_url():
+ """
+ Lookup and return terms of services page url
+ """
+ return get_url("TOS_AND_HONOR")
+
+
+def get_privacy_url():
+ """
+ Lookup and return privacy policies page url
+ """
+ return get_url("PRIVACY")
+
+
+def get_about_url():
+ """
+ Lookup and return About page url
+ """
+ return get_url("ABOUT")
diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py
index 6e27b96dd2..3ea089c690 100644
--- a/lms/djangoapps/certificates/api.py
+++ b/lms/djangoapps/certificates/api.py
@@ -29,7 +29,7 @@ from certificates.models import (
CertificateTemplateAsset,
)
from certificates.queue import XQueueCertInterface
-
+from branding import api as branding_api, get_logo_url
log = logging.getLogger("edx.certificate")
@@ -491,3 +491,41 @@ def get_asset_url_by_slug(asset_slug):
except CertificateTemplateAsset.DoesNotExist:
pass
return asset_url
+
+
+def get_certificate_header_context(is_secure=True):
+ """
+ Return data to be used in Certificate Header,
+ data returned should be customized according to the microsite settings
+ """
+ data = dict(
+ logo_src=get_logo_url(),
+ logo_url=branding_api.get_base_url(is_secure),
+ )
+
+ return data
+
+
+def get_certificate_footer_context():
+ """
+ Return data to be used in Certificate Footer,
+ data returned should be customized according to the microsite settings
+ """
+ data = dict()
+
+ # get Terms of Service and Honor Code page url
+ terms_of_service_and_honor_code = branding_api.get_tos_and_honor_code_url()
+ if terms_of_service_and_honor_code != branding_api.EMPTY_URL:
+ data.update({'company_tos_url': terms_of_service_and_honor_code})
+
+ # get Privacy Policy page url
+ privacy_policy = branding_api.get_privacy_url()
+ if privacy_policy != branding_api.EMPTY_URL:
+ data.update({'company_privacy_url': privacy_policy})
+
+ # get About page url
+ about = branding_api.get_about_url()
+ if about != branding_api.EMPTY_URL:
+ data.update({'company_about_url': about})
+
+ return data
diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py
index 02973fa7a1..640ab68578 100644
--- a/lms/djangoapps/certificates/tests/test_api.py
+++ b/lms/djangoapps/certificates/tests/test_api.py
@@ -1,6 +1,7 @@
"""Tests for the certificates Python API. """
from contextlib import contextmanager
import ddt
+from functools import wraps
from django.test import TestCase, RequestFactory
from django.test.utils import override_settings
@@ -29,6 +30,8 @@ from certificates.models import (
from certificates.queue import XQueueCertInterface, XQueueAddToQueueError
from certificates.tests.factories import GeneratedCertificateFactory
+from microsite_configuration import microsite
+
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
@@ -405,3 +408,94 @@ class GenerateExampleCertificatesTest(TestCase):
"""Check the example certificate status. """
actual_status = certs_api.example_certificates_status(self.COURSE_KEY)
self.assertEqual(list(expected_statuses), actual_status)
+
+
+def set_microsite(domain):
+ """
+ returns a decorator that can be used on a test_case to set a specific microsite for the current test case.
+ :param domain: Domain of the new microsite
+ """
+ def decorator(func):
+ """
+ Decorator to set current microsite according to domain
+ """
+ @wraps(func)
+ def inner(request, *args, **kwargs):
+ """
+ Execute the function after setting up the microsite.
+ """
+ microsite.set_by_domain(domain)
+ return func(request, *args, **kwargs)
+ return inner
+ return decorator
+
+
+@override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
+@attr('shard_1')
+class CertificatesBrandingTest(TestCase):
+ """Test certificates branding. """
+
+ COURSE_KEY = CourseLocator(org='test', course='test', run='test')
+
+ def setUp(self):
+ super(CertificatesBrandingTest, self).setUp()
+
+ @set_microsite(settings.MICROSITE_CONFIGURATION['test_microsite']['domain_prefix'])
+ def test_certificate_header_data(self):
+ """
+ Test that get_certificate_header_context from certificates api
+ returns data customized according to site branding.
+ """
+ # Generate certificates for the course
+ CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR)
+ data = certs_api.get_certificate_header_context(is_secure=True)
+
+ # Make sure there are not unexpected keys in dict returned by 'get_certificate_header_context'
+ self.assertItemsEqual(
+ data.keys(),
+ ['logo_src', 'logo_url']
+ )
+ self.assertIn(
+ settings.MICROSITE_CONFIGURATION['test_microsite']['logo_image_url'],
+ data['logo_src']
+ )
+
+ self.assertIn(
+ settings.MICROSITE_CONFIGURATION['test_microsite']['SITE_NAME'],
+ data['logo_url']
+ )
+
+ @set_microsite(settings.MICROSITE_CONFIGURATION['test_microsite']['domain_prefix'])
+ def test_certificate_footer_data(self):
+ """
+ Test that get_certificate_footer_context from certificates api returns
+ data customized according to site branding.
+ """
+ # Generate certificates for the course
+ CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR)
+ data = certs_api.get_certificate_footer_context()
+
+ # Make sure there are not unexpected keys in dict returned by 'get_certificate_footer_context'
+ self.assertItemsEqual(
+ data.keys(),
+ ['company_about_url', 'company_privacy_url', 'company_tos_url']
+ )
+
+ # ABOUT is present in MICROSITE_CONFIGURATION['test_microsite']["urls"] so web certificate will use that url
+ self.assertIn(
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['ABOUT'],
+ data['company_about_url']
+ )
+
+ # PRIVACY is present in MICROSITE_CONFIGURATION['test_microsite']["urls"] so web certificate will use that url
+ self.assertIn(
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['PRIVACY'],
+ data['company_privacy_url']
+ )
+
+ # TOS_AND_HONOR is present in MICROSITE_CONFIGURATION['test_microsite']["urls"],
+ # so web certificate will use that url
+ self.assertIn(
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['TOS_AND_HONOR'],
+ data['company_tos_url']
+ )
diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py
index 5fb3c5cf5b..19ad99239f 100644
--- a/lms/djangoapps/certificates/tests/test_views.py
+++ b/lms/djangoapps/certificates/tests/test_views.py
@@ -305,7 +305,9 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
self._add_course_certificates(count=1, signatory_count=2)
response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertIn('platform_microsite', response.content)
- self.assertIn('http://www.microsite.org', response.content)
+
+ # logo url is taken from microsite configuration setting
+ self.assertIn('http://test_microsite.localhost', response.content)
self.assertIn('This is special microsite aware company_about_description content', response.content)
self.assertIn('Microsite title', response.content)
diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py
index 0c8056ab4d..d3055b5e70 100644
--- a/lms/djangoapps/certificates/tests/test_webview_views.py
+++ b/lms/djangoapps/certificates/tests/test_webview_views.py
@@ -224,6 +224,21 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
"CERTIFICATE_TWITTER": True,
"CERTIFICATE_FACEBOOK": True,
})
+ @patch.dict("django.conf.settings.MICROSITE_CONFIGURATION", {
+ "test_microsite": dict(
+ settings.MICROSITE_CONFIGURATION['test_microsite'],
+ urls=dict(
+ ABOUT=None,
+ PRIVACY=None,
+ TOS_AND_HONOR=None,
+ ),
+ )
+ })
+ @patch.dict("django.conf.settings.MKTG_URL_LINK_MAP", {
+ 'ABOUT': None,
+ 'PRIVACY': None,
+ 'TOS_AND_HONOR': None,
+ })
def test_rendering_maximum_data(self):
"""
Tests at least one data item from different context update methods to
@@ -268,7 +283,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
)
# Test an item from html cert configuration
self.assertIn(
- '',
+ '',
response.content
)
# Test an item from course info
@@ -862,3 +877,138 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
settings.MEDIA_URL
)
)
+
+ @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
+ def test_certificate_branding(self):
+ """
+ Test that link urls in certificate web view are customized according to site branding and
+ microsite configuration.
+ """
+ self._add_course_certificates(count=1, signatory_count=1, is_active=True)
+
+ self.course.save()
+ self.store.update_item(self.course, self.user.id)
+
+ test_url = get_certificate_url(
+ user_id=self.user.id,
+ course_id=unicode(self.course.id)
+ )
+ response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
+ # logo_image_url Tis present in MICROSITE_CONFIGURATION['test_microsite']["urls"],
+ # so web certificate will use that.
+ self.assertContains(
+ response,
+ settings.MICROSITE_CONFIGURATION['test_microsite']['logo_image_url'],
+ )
+ # ABOUT is present in MICROSITE_CONFIGURATION['test_microsite']["urls"] so web certificate will use that url.
+ self.assertContains(
+ response,
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['ABOUT'],
+ )
+ # PRIVACY is present in MICROSITE_CONFIGURATION['test_microsite']["urls"] so web certificate will use that url.
+ self.assertContains(
+ response,
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['PRIVACY'],
+ )
+ # TOS_AND_HONOR is present in MICROSITE_CONFIGURATION['test_microsite']["urls"],
+ # so web certificate will use that url.
+ self.assertContains(
+ response,
+ settings.MICROSITE_CONFIGURATION['test_microsite']["urls"]['TOS_AND_HONOR'],
+ )
+
+ @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
+ @patch.dict("django.conf.settings.MICROSITE_CONFIGURATION", {
+ "test_microsite": dict(
+ settings.MICROSITE_CONFIGURATION['test_microsite'],
+ urls=dict(
+ ABOUT=None,
+ PRIVACY=None,
+ TOS_AND_HONOR=None,
+ ),
+ )
+ })
+ def test_certificate_branding_without_microsite_urls(self):
+ """
+ Test that links from MKTG_URL_LINK_MAP setting are used if corresponding microsite urls are not present.
+ microsite configuration.
+ """
+ self._add_course_certificates(count=1, signatory_count=1, is_active=True)
+ self.course.save()
+ self.store.update_item(self.course, self.user.id)
+ configuration = CertificateHtmlViewConfiguration.get_config()
+ test_url = get_certificate_url(
+ user_id=self.user.id,
+ course_id=unicode(self.course.id)
+ )
+ response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
+ # ABOUT is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"],
+ # so web certificate will use MKTG_URL_LINK_MAP['ABOUT'] url.
+ self.assertContains(
+ response,
+ settings.MKTG_URL_LINK_MAP['ABOUT'],
+ )
+ # PRIVACY is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"],
+ # so web certificate will use MKTG_URL_LINK_MAP['PRIVACY'] url.
+ self.assertContains(
+ response,
+ settings.MKTG_URL_LINK_MAP['PRIVACY'],
+ )
+ # TOS_AND_HONOR is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP,
+ # so web certificate will use CertificateHtmlViewConfiguration url.
+ self.assertContains(
+ response,
+ configuration['microsites']['testmicrosite']['company_tos_url'],
+ )
+
+ @override_settings(FEATURES=FEATURES_WITH_CERTS_ENABLED)
+ @patch.dict("django.conf.settings.MICROSITE_CONFIGURATION", {
+ "test_microsite": dict(
+ settings.MICROSITE_CONFIGURATION['test_microsite'],
+ urls=dict(
+ ABOUT=None,
+ PRIVACY=None,
+ TOS_AND_HONOR=None,
+ ),
+ )
+ })
+ @patch.dict("django.conf.settings.MKTG_URL_LINK_MAP", {
+ 'ABOUT': None,
+ 'PRIVACY': None,
+ 'TOS_AND_HONOR': None,
+ })
+ def test_certificate_without_branding_urls(self):
+ """
+ Test that links from CertificateHtmlViewConfiguration are used if
+ corresponding microsite or marketing urls are not present.
+ """
+ self._add_course_certificates(count=1, signatory_count=1, is_active=True)
+
+ self.course.save()
+ self.store.update_item(self.course, self.user.id)
+ configuration = CertificateHtmlViewConfiguration.get_config()
+
+ test_url = get_certificate_url(
+ user_id=self.user.id,
+ course_id=unicode(self.course.id)
+ )
+ response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
+
+ # ABOUT is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP,
+ # so web certificate will use CertificateHtmlViewConfiguration url.
+ self.assertContains(
+ response,
+ configuration['microsites']['testmicrosite']['company_about_url'],
+ )
+ # PRIVACY is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP,
+ # so web certificate will use CertificateHtmlViewConfiguration url.
+ self.assertContains(
+ response,
+ configuration['microsites']['testmicrosite']['company_privacy_url'],
+ )
+ # TOS_AND_HONOR is not present in MICROSITE_CONFIGURATION['test_microsite']["urls"] or MKTG_URL_LINK_MAP,
+ # so web certificate will use CertificateHtmlViewConfiguration url.
+ self.assertContains(
+ response,
+ configuration['microsites']['testmicrosite']['company_tos_url'],
+ )
diff --git a/lms/djangoapps/certificates/views/webview.py b/lms/djangoapps/certificates/views/webview.py
index 95a70ead7f..3053dd154b 100644
--- a/lms/djangoapps/certificates/views/webview.py
+++ b/lms/djangoapps/certificates/views/webview.py
@@ -34,7 +34,9 @@ from certificates.api import (
get_certificate_url,
emit_certificate_event,
has_html_certificates_enabled,
- get_certificate_template
+ get_certificate_template,
+ get_certificate_header_context,
+ get_certificate_footer_context,
)
from certificates.models import (
GeneratedCertificate,
@@ -540,6 +542,10 @@ def render_html_view(request, user_id, course_id):
# Append microsite overrides
_update_microsite_context(context, configuration)
+ # Add certificate header/footer data to current context
+ context.update(get_certificate_header_context(is_secure=request.is_secure()))
+ context.update(get_certificate_footer_context())
+
# Append/Override the existing view context values with any course-specific static values from Advanced Settings
context.update(course.cert_html_view_overrides)
diff --git a/lms/envs/test.py b/lms/envs/test.py
index 582e825764..9e7bb67486 100644
--- a/lms/envs/test.py
+++ b/lms/envs/test.py
@@ -446,6 +446,11 @@ MICROSITE_CONFIGURATION = {
"ENABLE_SHOPPING_CART": True,
"ENABLE_PAID_COURSE_REGISTRATION": True,
"SESSION_COOKIE_DOMAIN": "test_microsite.localhost",
+ "urls": {
+ 'ABOUT': 'testmicrosite/about',
+ 'PRIVACY': 'testmicrosite/privacy',
+ 'TOS_AND_HONOR': 'testmicrosite/tos-and-honor',
+ },
},
"microsite_with_logistration": {
"domain_prefix": "logistration",