feat: Certificate sharing to linkedin (optionally) consider course level organization name (#37331)
By adjusting social media sharing settings(specifically linkedin) certificate parameters are autopopulated to LinkedIn API. Additional setting parameters(such as CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME) are introduced to override existing(platform level parameter for organization name) parameters for an operator to configure course level organization name. This will enable learners to share certificate in to LinkedIn with an option for course associated organization to be autopopulated.
This commit is contained in:
@@ -589,7 +589,7 @@ def _cert_info(user, enrollment, cert_status):
|
||||
linkedin_config = LinkedInAddToProfileConfiguration.current()
|
||||
if linkedin_config.is_enabled():
|
||||
status_dict['linked_in_url'] = linkedin_config.add_to_profile_url(
|
||||
course_overview.display_name, cert_status.get('mode'), cert_status['download_url'],
|
||||
course_overview, cert_status.get('mode'), cert_status['download_url'],
|
||||
)
|
||||
|
||||
if status in {'generating', 'downloadable', 'notpassing', 'restricted', 'auditing', 'unverified'}:
|
||||
|
||||
@@ -1375,21 +1375,37 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
),
|
||||
)
|
||||
|
||||
@property
|
||||
def share_settings(self):
|
||||
"""
|
||||
Initialize share_settings once for reuse across methods
|
||||
"""
|
||||
if self._share_settings is None:
|
||||
self._share_settings = configuration_helpers.get_value(
|
||||
'SOCIAL_SHARING_SETTINGS',
|
||||
settings.SOCIAL_SHARING_SETTINGS
|
||||
)
|
||||
return self._share_settings
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self._share_settings = None
|
||||
|
||||
def is_enabled(self, *key_fields): # pylint: disable=arguments-differ
|
||||
"""
|
||||
Checks both the model itself and share_settings to see if LinkedIn Add to Profile is enabled
|
||||
"""
|
||||
enabled = super().is_enabled(*key_fields)
|
||||
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
|
||||
return share_settings.get('CERTIFICATE_LINKEDIN', enabled)
|
||||
return self.share_settings.get('CERTIFICATE_LINKEDIN', enabled)
|
||||
|
||||
def add_to_profile_url(self, course, cert_mode, cert_url, certificate=None):
|
||||
|
||||
def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None):
|
||||
"""
|
||||
Construct the URL for the "add to profile" button. This will autofill the form based on
|
||||
the params provided.
|
||||
|
||||
Arguments:
|
||||
course_name (str): The display name of the course.
|
||||
course (CourseOverview): Course/CourseOverview Object.
|
||||
cert_mode (str): The course mode of the user's certificate (e.g. "verified", "honor", "professional")
|
||||
cert_url (str): The URL for the certificate.
|
||||
|
||||
@@ -1398,11 +1414,11 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
If provided, this function will also autofill the certId and issue date for the cert.
|
||||
"""
|
||||
params = {
|
||||
'name': self._cert_name(course_name, cert_mode),
|
||||
'name': self._cert_name(course.display_name, cert_mode),
|
||||
'certUrl': cert_url,
|
||||
}
|
||||
|
||||
params.update(self._organization_information())
|
||||
params.update(self._organization_information(course))
|
||||
|
||||
if certificate:
|
||||
params.update({
|
||||
@@ -1426,28 +1442,45 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
|
||||
Returns:
|
||||
str: The formatted string to display for the name field on the LinkedIn Add to Profile dialog.
|
||||
"""
|
||||
default_cert_name = self.MODE_TO_CERT_NAME.get(cert_mode, _('{platform_name} Certificate for {course_name}'))
|
||||
default_cert_name = self.MODE_TO_CERT_NAME.get(
|
||||
cert_mode, _('{platform_name} Certificate for {course_name}')
|
||||
)
|
||||
# Look for an override of the certificate name in the SOCIAL_SHARING_SETTINGS setting
|
||||
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
|
||||
cert_name = share_settings.get('CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}).get(cert_mode, default_cert_name)
|
||||
cert_name = self.share_settings.get(
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}
|
||||
).get(cert_mode, default_cert_name)
|
||||
|
||||
return cert_name.format(
|
||||
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
|
||||
course_name=course_name
|
||||
)
|
||||
|
||||
def _organization_information(self):
|
||||
def _organization_information(self, course=None):
|
||||
"""
|
||||
Returns organization information for use in the URL parameters for add to profile.
|
||||
Returns organization information for use in the URL parameters for add to
|
||||
profile. By default when sharing to LinkedIn, Platform Name and/or Platform
|
||||
LINKEDIN_COMPANY_ID will be used. If Course specific Organization Name is
|
||||
prefered when sharing Certificate to linkedIn the flag for that
|
||||
CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME should be set
|
||||
to True alongside other LinkedIn settings
|
||||
|
||||
Returns:
|
||||
dict: Either the organization ID on LinkedIn or the organization's name
|
||||
dict: Either the organization ID on LinkedIn, the organization's name or
|
||||
organization name associated to a specific course
|
||||
Will be used to prefill the organization on the add to profile action.
|
||||
"""
|
||||
org_id = configuration_helpers.get_value('LINKEDIN_COMPANY_ID', self.company_identifier)
|
||||
prefer_course_organization_name = self.share_settings.get(
|
||||
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME', False
|
||||
)
|
||||
if (prefer_course_organization_name and course):
|
||||
return {"organizationName": course.display_organization}
|
||||
|
||||
org_id = configuration_helpers.get_value(
|
||||
"LINKEDIN_COMPANY_ID", self.company_identifier
|
||||
)
|
||||
# Prefer organization ID per documentation at https://addtoprofile.linkedin.com/
|
||||
if org_id:
|
||||
return {'organizationId': org_id}
|
||||
return {"organizationId": org_id}
|
||||
return {'organizationName': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)}
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
"""Tests for LinkedIn Add to Profile configuration. """
|
||||
|
||||
|
||||
from types import SimpleNamespace
|
||||
from urllib.parse import quote
|
||||
import ddt
|
||||
|
||||
from django.conf import settings
|
||||
from django.test import TestCase
|
||||
|
||||
@@ -17,6 +16,7 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
|
||||
COURSE_NAME = 'Test Course ☃'
|
||||
CERT_URL = 'http://s3.edx/cert'
|
||||
COURSE_ORGANIZATION = 'TEST+ORGANIZATION'
|
||||
SITE_CONFIGURATION = {
|
||||
'SOCIAL_SHARING_SETTINGS': {
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
|
||||
@@ -27,6 +27,17 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
}
|
||||
}
|
||||
}
|
||||
SITE_CONFIGURATION_COURSE_LEVEL_ORG = {
|
||||
'SOCIAL_SHARING_SETTINGS': {
|
||||
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME': True,
|
||||
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
|
||||
'honor': '{platform_name} Honor Code Credential for {course_name}',
|
||||
'verified': '{platform_name} Verified Credential for {course_name}',
|
||||
'professional': '{platform_name} Professional Credential for {course_name}',
|
||||
'no-id-professional': '{platform_name} Professional Credential for {course_name}',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ddt.data(
|
||||
('honor', 'Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
|
||||
@@ -49,7 +60,13 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
company_identifier=config.company_identifier,
|
||||
)
|
||||
|
||||
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME, display_organization=self.COURSE_ORGANIZATION
|
||||
)
|
||||
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@@ -74,8 +91,49 @@ class LinkedInAddToProfileUrlTests(TestCase):
|
||||
cert_url=quote(self.CERT_URL, safe=''),
|
||||
company_identifier=config.company_identifier,
|
||||
)
|
||||
|
||||
with with_site_configuration_context(configuration=self.SITE_CONFIGURATION):
|
||||
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME,
|
||||
display_organization=self.COURSE_ORGANIZATION,
|
||||
)
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@ddt.data(
|
||||
('honor', 'Honor+Code+Credential+for+Test+Course+%E2%98%83'),
|
||||
('verified', 'Verified+Credential+for+Test+Course+%E2%98%83'),
|
||||
('professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
|
||||
('no-id-professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
|
||||
('default_mode', 'Certificate+for+Test+Course+%E2%98%83')
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_linked_in_url_with_course_org_name_override(
|
||||
self, cert_mode, expected_cert_name
|
||||
):
|
||||
config = LinkedInAddToProfileConfigurationFactory()
|
||||
|
||||
expected_url = (
|
||||
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
|
||||
'name={platform}+{cert_name}&certUrl={cert_url}&'
|
||||
'organizationName={course_organization_name}'
|
||||
).format(
|
||||
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
|
||||
cert_name=expected_cert_name,
|
||||
cert_url=quote(self.CERT_URL, safe=''),
|
||||
course_organization_name=quote(self.COURSE_ORGANIZATION.encode('utf-8')),
|
||||
)
|
||||
|
||||
with with_site_configuration_context(
|
||||
configuration=self.SITE_CONFIGURATION_COURSE_LEVEL_ORG
|
||||
):
|
||||
course_mock_object = SimpleNamespace(
|
||||
display_name=self.COURSE_NAME,
|
||||
display_organization=self.COURSE_ORGANIZATION,
|
||||
)
|
||||
actual_url = config.add_to_profile_url(
|
||||
course_mock_object, cert_mode, self.CERT_URL
|
||||
)
|
||||
|
||||
self.assertEqual(actual_url, expected_url)
|
||||
|
||||
@@ -51,8 +51,6 @@ from openedx.features.course_experience.url_helpers import make_learning_mfe_cou
|
||||
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
|
||||
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BETA_TESTER_METHOD = 'common.djangoapps.student.helpers.access.is_beta_tester'
|
||||
@@ -426,6 +424,7 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
|
||||
self.course.end = datetime.now(pytz.UTC) - timedelta(days=1)
|
||||
self.course.display_name = 'Omega'
|
||||
self.course.course_organization = 'Omega Org'
|
||||
self.course = self.update_course(self.course, self.user.id)
|
||||
|
||||
cert = GeneratedCertificateFactory.create(
|
||||
@@ -449,7 +448,7 @@ class DashboardTest(ModuleStoreTestCase, TestVerificationBase):
|
||||
).format(
|
||||
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
|
||||
cert_url=quote(cert.download_url, safe=''),
|
||||
company_identifier=linkedin_config.company_identifier
|
||||
company_identifier=linkedin_config.company_identifier,
|
||||
)
|
||||
|
||||
# Single assertion for the expected LinkedIn URL
|
||||
|
||||
@@ -305,7 +305,7 @@ def _update_social_context(request, context, course, user_certificate, platform_
|
||||
linkedin_config = LinkedInAddToProfileConfiguration.current()
|
||||
if linkedin_config.is_enabled():
|
||||
context['linked_in_url'] = linkedin_config.add_to_profile_url(
|
||||
course.display_name, user_certificate.mode, smart_str(share_url), certificate=user_certificate
|
||||
course, user_certificate.mode, smart_str(share_url), certificate=user_certificate
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -288,7 +288,7 @@ class CoursewareMeta:
|
||||
get_certificate_url(course_id=self.course_key, uuid=user_certificate.verify_uuid)
|
||||
)
|
||||
return linkedin_config.add_to_profile_url(
|
||||
self.course_overview.display_name, user_certificate.mode, cert_url, certificate=user_certificate,
|
||||
self.course_overview, user_certificate.mode, cert_url, certificate=user_certificate,
|
||||
)
|
||||
|
||||
@property
|
||||
|
||||
Reference in New Issue
Block a user