Commit ca2b8c27 by Matt Drayer

Merge pull request #10988 from edx/saleem-latif/SOL-1493

SOL-1493: Web Certificates: Platform Branding integration
parents 05b96f8c 3967d809
...@@ -64,28 +64,3 @@ def get_university_for_request(): ...@@ -64,28 +64,3 @@ def get_university_for_request():
if no university was specified if no university was specified
""" """
return microsite.get_value('university') return microsite.get_value('university')
def get_logo_url():
"""
Return the url for the branded logo image to be used
"""
# if the MicrositeConfiguration has a value for the logo_image_url
# let's use that
image_url = microsite.get_value('logo_image_url')
if image_url:
return '{static_url}{image_url}'.format(
static_url=settings.STATIC_URL,
image_url=image_url
)
# otherwise, use the legacy means to configure this
university = microsite.get_value('university')
if university is None and settings.FEATURES.get('IS_EDX_DOMAIN', False):
return staticfiles_storage.url('images/edx-theme/edx-logo-77x36.png')
elif university:
return staticfiles_storage.url('images/{uni}-on-edx-logo.png'.format(uni=university))
else:
return staticfiles_storage.url('images/logo.png')
...@@ -25,6 +25,7 @@ from branding.models import BrandingApiConfig ...@@ -25,6 +25,7 @@ from branding.models import BrandingApiConfig
log = logging.getLogger("edx.footer") log = logging.getLogger("edx.footer")
EMPTY_URL = '#'
def is_enabled(): def is_enabled():
...@@ -329,3 +330,89 @@ def _absolute_url_staticfile(is_secure, name): ...@@ -329,3 +330,89 @@ def _absolute_url_staticfile(is_secure, name):
# For local development, the returned URL will be relative, # For local development, the returned URL will be relative,
# so we need to make it absolute. # so we need to make it absolute.
return _absolute_url(is_secure, url_path) 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_logo_url():
"""
Return the url for the branded logo image to be used
"""
# if the MicrositeConfiguration has a value for the logo_image_url
# let's use that
image_url = microsite.get_value('logo_image_url')
if image_url:
return '{static_url}{image_url}'.format(
static_url=settings.STATIC_URL,
image_url=image_url
)
# otherwise, use the legacy means to configure this
university = microsite.get_value('university')
if university is None and settings.FEATURES.get('IS_EDX_DOMAIN', False):
return staticfiles_storage.url('images/edx-theme/edx-logo-77x36.png')
elif university:
return staticfiles_storage.url('images/{uni}-on-edx-logo.png'.format(uni=university))
else:
return staticfiles_storage.url('images/logo.png')
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")
...@@ -29,7 +29,7 @@ from certificates.models import ( ...@@ -29,7 +29,7 @@ from certificates.models import (
CertificateTemplateAsset, CertificateTemplateAsset,
) )
from certificates.queue import XQueueCertInterface from certificates.queue import XQueueCertInterface
from branding import api as branding_api
log = logging.getLogger("edx.certificate") log = logging.getLogger("edx.certificate")
...@@ -491,3 +491,41 @@ def get_asset_url_by_slug(asset_slug): ...@@ -491,3 +491,41 @@ def get_asset_url_by_slug(asset_slug):
except CertificateTemplateAsset.DoesNotExist: except CertificateTemplateAsset.DoesNotExist:
pass pass
return asset_url 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=branding_api.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
"""Tests for the certificates Python API. """ """Tests for the certificates Python API. """
from contextlib import contextmanager from contextlib import contextmanager
import ddt import ddt
from functools import wraps
from django.test import TestCase, RequestFactory from django.test import TestCase, RequestFactory
from django.test.utils import override_settings from django.test.utils import override_settings
...@@ -29,6 +30,8 @@ from certificates.models import ( ...@@ -29,6 +30,8 @@ from certificates.models import (
from certificates.queue import XQueueCertInterface, XQueueAddToQueueError from certificates.queue import XQueueCertInterface, XQueueAddToQueueError
from certificates.tests.factories import GeneratedCertificateFactory from certificates.tests.factories import GeneratedCertificateFactory
from microsite_configuration import microsite
FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy() FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy()
FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True
...@@ -405,3 +408,94 @@ class GenerateExampleCertificatesTest(TestCase): ...@@ -405,3 +408,94 @@ class GenerateExampleCertificatesTest(TestCase):
"""Check the example certificate status. """ """Check the example certificate status. """
actual_status = certs_api.example_certificates_status(self.COURSE_KEY) actual_status = certs_api.example_certificates_status(self.COURSE_KEY)
self.assertEqual(list(expected_statuses), actual_status) 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']
)
...@@ -305,7 +305,9 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase): ...@@ -305,7 +305,9 @@ class MicrositeCertificatesViewsTests(ModuleStoreTestCase):
self._add_course_certificates(count=1, signatory_count=2) self._add_course_certificates(count=1, signatory_count=2)
response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME) response = self.client.get(test_url, HTTP_HOST=settings.MICROSITE_TEST_HOSTNAME)
self.assertIn('platform_microsite', response.content) 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('This is special microsite aware company_about_description content', response.content)
self.assertIn('Microsite title', response.content) self.assertIn('Microsite title', response.content)
......
...@@ -224,6 +224,21 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -224,6 +224,21 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
"CERTIFICATE_TWITTER": True, "CERTIFICATE_TWITTER": True,
"CERTIFICATE_FACEBOOK": 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): def test_rendering_maximum_data(self):
""" """
Tests at least one data item from different context update methods to Tests at least one data item from different context update methods to
...@@ -268,7 +283,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -268,7 +283,7 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
) )
# Test an item from html cert configuration # Test an item from html cert configuration
self.assertIn( self.assertIn(
'<a class="logo" href="http://www.edx.org/honor_logo.png">', '<a class="logo" href="http://test_microsite.localhost">',
response.content response.content
) )
# Test an item from course info # Test an item from course info
...@@ -862,3 +877,138 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase): ...@@ -862,3 +877,138 @@ class CertificatesViewsTests(ModuleStoreTestCase, EventTrackingTestCase):
settings.MEDIA_URL 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'],
)
...@@ -34,7 +34,9 @@ from certificates.api import ( ...@@ -34,7 +34,9 @@ from certificates.api import (
get_certificate_url, get_certificate_url,
emit_certificate_event, emit_certificate_event,
has_html_certificates_enabled, has_html_certificates_enabled,
get_certificate_template get_certificate_template,
get_certificate_header_context,
get_certificate_footer_context,
) )
from certificates.models import ( from certificates.models import (
GeneratedCertificate, GeneratedCertificate,
...@@ -540,6 +542,10 @@ def render_html_view(request, user_id, course_id): ...@@ -540,6 +542,10 @@ def render_html_view(request, user_id, course_id):
# Append microsite overrides # Append microsite overrides
_update_microsite_context(context, configuration) _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 # Append/Override the existing view context values with any course-specific static values from Advanced Settings
context.update(course.cert_html_view_overrides) context.update(course.cert_html_view_overrides)
......
...@@ -446,6 +446,11 @@ MICROSITE_CONFIGURATION = { ...@@ -446,6 +446,11 @@ MICROSITE_CONFIGURATION = {
"ENABLE_SHOPPING_CART": True, "ENABLE_SHOPPING_CART": True,
"ENABLE_PAID_COURSE_REGISTRATION": True, "ENABLE_PAID_COURSE_REGISTRATION": True,
"SESSION_COOKIE_DOMAIN": "test_microsite.localhost", "SESSION_COOKIE_DOMAIN": "test_microsite.localhost",
"urls": {
'ABOUT': 'testmicrosite/about',
'PRIVACY': 'testmicrosite/privacy',
'TOS_AND_HONOR': 'testmicrosite/tos-and-honor',
},
}, },
"microsite_with_logistration": { "microsite_with_logistration": {
"domain_prefix": "logistration", "domain_prefix": "logistration",
......
...@@ -9,7 +9,7 @@ from microsite_configuration import microsite ...@@ -9,7 +9,7 @@ from microsite_configuration import microsite
from microsite_configuration.templatetags.microsite import platform_name from microsite_configuration.templatetags.microsite import platform_name
# App that handles subdomain specific branding # App that handles subdomain specific branding
import branding from branding import api as branding_api
# app that handles site status messages # app that handles site status messages
from status.status import get_site_status_msg from status.status import get_site_status_msg
%> %>
...@@ -41,7 +41,7 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -41,7 +41,7 @@ site_status_msg = get_site_status_msg(course_id)
<h1 class="logo" itemscope="" itemtype="http://schema.org/Organization"> <h1 class="logo" itemscope="" itemtype="http://schema.org/Organization">
<a href="${marketing_link('ROOT')}" itemprop="url"> <a href="${marketing_link('ROOT')}" itemprop="url">
<%block name="navigation_logo"> <%block name="navigation_logo">
<img src="${static.url(branding.get_logo_url())}" alt="${_("{platform_name} Home Page").format(platform_name=platform_name())}" itemprop="logo" /> <img src="${static.url(branding_api.get_logo_url())}" alt="${_("{platform_name} Home Page").format(platform_name=platform_name())}" itemprop="logo" />
</%block> </%block>
</a> </a>
</h1> </h1>
......
...@@ -10,7 +10,7 @@ from microsite_configuration.templatetags.microsite import platform_name ...@@ -10,7 +10,7 @@ from microsite_configuration.templatetags.microsite import platform_name
from lms.djangoapps.ccx.overrides import get_current_ccx from lms.djangoapps.ccx.overrides import get_current_ccx
# App that handles subdomain specific branding # App that handles subdomain specific branding
import branding from branding import api as branding_api
# app that handles site status messages # app that handles site status messages
from status.status import get_site_status_msg from status.status import get_site_status_msg
%> %>
...@@ -42,7 +42,7 @@ site_status_msg = get_site_status_msg(course_id) ...@@ -42,7 +42,7 @@ site_status_msg = get_site_status_msg(course_id)
<h1 class="logo"> <h1 class="logo">
<a href="${marketing_link('ROOT')}"> <a href="${marketing_link('ROOT')}">
<%block name="navigation_logo"> <%block name="navigation_logo">
<img src="${static.url(branding.get_logo_url())}" alt="${_("{platform_name} Home Page").format(platform_name=platform_name())}"/> <img src="${static.url(branding_api.get_logo_url())}" alt="${_("{platform_name} Home Page").format(platform_name=platform_name())}"/>
</%block> </%block>
</a> </a>
</h1> </h1>
......
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