Skip to content

Commit e46cfa6

Browse files
authored
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.
1 parent 718dac1 commit e46cfa6

6 files changed

Lines changed: 115 additions & 25 deletions

File tree

common/djangoapps/student/helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ def _cert_info(user, enrollment, cert_status):
589589
linkedin_config = LinkedInAddToProfileConfiguration.current()
590590
if linkedin_config.is_enabled():
591591
status_dict['linked_in_url'] = linkedin_config.add_to_profile_url(
592-
course_overview.display_name, cert_status.get('mode'), cert_status['download_url'],
592+
course_overview, cert_status.get('mode'), cert_status['download_url'],
593593
)
594594

595595
if status in {'generating', 'downloadable', 'notpassing', 'restricted', 'auditing', 'unverified'}:

common/djangoapps/student/models/user.py

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,21 +1375,37 @@ class LinkedInAddToProfileConfiguration(ConfigurationModel):
13751375
),
13761376
)
13771377

1378+
@property
1379+
def share_settings(self):
1380+
"""
1381+
Initialize share_settings once for reuse across methods
1382+
"""
1383+
if self._share_settings is None:
1384+
self._share_settings = configuration_helpers.get_value(
1385+
'SOCIAL_SHARING_SETTINGS',
1386+
settings.SOCIAL_SHARING_SETTINGS
1387+
)
1388+
return self._share_settings
1389+
1390+
def __init__(self, *args, **kwargs):
1391+
super().__init__(*args, **kwargs)
1392+
self._share_settings = None
1393+
13781394
def is_enabled(self, *key_fields): # pylint: disable=arguments-differ
13791395
"""
13801396
Checks both the model itself and share_settings to see if LinkedIn Add to Profile is enabled
13811397
"""
13821398
enabled = super().is_enabled(*key_fields)
1383-
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
1384-
return share_settings.get('CERTIFICATE_LINKEDIN', enabled)
1399+
return self.share_settings.get('CERTIFICATE_LINKEDIN', enabled)
1400+
1401+
def add_to_profile_url(self, course, cert_mode, cert_url, certificate=None):
13851402

1386-
def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None):
13871403
"""
13881404
Construct the URL for the "add to profile" button. This will autofill the form based on
13891405
the params provided.
13901406
13911407
Arguments:
1392-
course_name (str): The display name of the course.
1408+
course (CourseOverview): Course/CourseOverview Object.
13931409
cert_mode (str): The course mode of the user's certificate (e.g. "verified", "honor", "professional")
13941410
cert_url (str): The URL for the certificate.
13951411
@@ -1398,11 +1414,11 @@ def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None)
13981414
If provided, this function will also autofill the certId and issue date for the cert.
13991415
"""
14001416
params = {
1401-
'name': self._cert_name(course_name, cert_mode),
1417+
'name': self._cert_name(course.display_name, cert_mode),
14021418
'certUrl': cert_url,
14031419
}
14041420

1405-
params.update(self._organization_information())
1421+
params.update(self._organization_information(course))
14061422

14071423
if certificate:
14081424
params.update({
@@ -1426,28 +1442,45 @@ def _cert_name(self, course_name, cert_mode):
14261442
Returns:
14271443
str: The formatted string to display for the name field on the LinkedIn Add to Profile dialog.
14281444
"""
1429-
default_cert_name = self.MODE_TO_CERT_NAME.get(cert_mode, _('{platform_name} Certificate for {course_name}'))
1445+
default_cert_name = self.MODE_TO_CERT_NAME.get(
1446+
cert_mode, _('{platform_name} Certificate for {course_name}')
1447+
)
14301448
# Look for an override of the certificate name in the SOCIAL_SHARING_SETTINGS setting
1431-
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
1432-
cert_name = share_settings.get('CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}).get(cert_mode, default_cert_name)
1449+
cert_name = self.share_settings.get(
1450+
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME', {}
1451+
).get(cert_mode, default_cert_name)
14331452

14341453
return cert_name.format(
14351454
platform_name=configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME),
14361455
course_name=course_name
14371456
)
14381457

1439-
def _organization_information(self):
1458+
def _organization_information(self, course=None):
14401459
"""
1441-
Returns organization information for use in the URL parameters for add to profile.
1460+
Returns organization information for use in the URL parameters for add to
1461+
profile. By default when sharing to LinkedIn, Platform Name and/or Platform
1462+
LINKEDIN_COMPANY_ID will be used. If Course specific Organization Name is
1463+
prefered when sharing Certificate to linkedIn the flag for that
1464+
CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME should be set
1465+
to True alongside other LinkedIn settings
14421466
14431467
Returns:
1444-
dict: Either the organization ID on LinkedIn or the organization's name
1468+
dict: Either the organization ID on LinkedIn, the organization's name or
1469+
organization name associated to a specific course
14451470
Will be used to prefill the organization on the add to profile action.
14461471
"""
1447-
org_id = configuration_helpers.get_value('LINKEDIN_COMPANY_ID', self.company_identifier)
1472+
prefer_course_organization_name = self.share_settings.get(
1473+
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME', False
1474+
)
1475+
if (prefer_course_organization_name and course):
1476+
return {"organizationName": course.display_organization}
1477+
1478+
org_id = configuration_helpers.get_value(
1479+
"LINKEDIN_COMPANY_ID", self.company_identifier
1480+
)
14481481
# Prefer organization ID per documentation at https://addtoprofile.linkedin.com/
14491482
if org_id:
1450-
return {'organizationId': org_id}
1483+
return {"organizationId": org_id}
14511484
return {'organizationName': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)}
14521485

14531486

common/djangoapps/student/tests/test_linkedin.py

Lines changed: 63 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
"""Tests for LinkedIn Add to Profile configuration. """
22

3-
3+
from types import SimpleNamespace
44
from urllib.parse import quote
55
import ddt
6-
76
from django.conf import settings
87
from django.test import TestCase
98

@@ -17,6 +16,7 @@ class LinkedInAddToProfileUrlTests(TestCase):
1716

1817
COURSE_NAME = 'Test Course ☃'
1918
CERT_URL = 'http://s3.edx/cert'
19+
COURSE_ORGANIZATION = 'TEST+ORGANIZATION'
2020
SITE_CONFIGURATION = {
2121
'SOCIAL_SHARING_SETTINGS': {
2222
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
@@ -27,6 +27,17 @@ class LinkedInAddToProfileUrlTests(TestCase):
2727
}
2828
}
2929
}
30+
SITE_CONFIGURATION_COURSE_LEVEL_ORG = {
31+
'SOCIAL_SHARING_SETTINGS': {
32+
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME': True,
33+
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
34+
'honor': '{platform_name} Honor Code Credential for {course_name}',
35+
'verified': '{platform_name} Verified Credential for {course_name}',
36+
'professional': '{platform_name} Professional Credential for {course_name}',
37+
'no-id-professional': '{platform_name} Professional Credential for {course_name}',
38+
}
39+
}
40+
}
3041

3142
@ddt.data(
3243
('honor', 'Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
@@ -49,7 +60,13 @@ def test_linked_in_url(self, cert_mode, expected_cert_name):
4960
company_identifier=config.company_identifier,
5061
)
5162

52-
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
63+
course_mock_object = SimpleNamespace(
64+
display_name=self.COURSE_NAME, display_organization=self.COURSE_ORGANIZATION
65+
)
66+
67+
actual_url = config.add_to_profile_url(
68+
course_mock_object, cert_mode, self.CERT_URL
69+
)
5370

5471
self.assertEqual(actual_url, expected_url)
5572

@@ -74,8 +91,49 @@ def test_linked_in_url_with_cert_name_override(self, cert_mode, expected_cert_na
7491
cert_url=quote(self.CERT_URL, safe=''),
7592
company_identifier=config.company_identifier,
7693
)
77-
7894
with with_site_configuration_context(configuration=self.SITE_CONFIGURATION):
79-
actual_url = config.add_to_profile_url(self.COURSE_NAME, cert_mode, self.CERT_URL)
95+
course_mock_object = SimpleNamespace(
96+
display_name=self.COURSE_NAME,
97+
display_organization=self.COURSE_ORGANIZATION,
98+
)
99+
actual_url = config.add_to_profile_url(
100+
course_mock_object, cert_mode, self.CERT_URL
101+
)
102+
self.assertEqual(actual_url, expected_url)
103+
104+
@ddt.data(
105+
('honor', 'Honor+Code+Credential+for+Test+Course+%E2%98%83'),
106+
('verified', 'Verified+Credential+for+Test+Course+%E2%98%83'),
107+
('professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
108+
('no-id-professional', 'Professional+Credential+for+Test+Course+%E2%98%83'),
109+
('default_mode', 'Certificate+for+Test+Course+%E2%98%83')
110+
)
111+
@ddt.unpack
112+
def test_linked_in_url_with_course_org_name_override(
113+
self, cert_mode, expected_cert_name
114+
):
115+
config = LinkedInAddToProfileConfigurationFactory()
116+
117+
expected_url = (
118+
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&'
119+
'name={platform}+{cert_name}&certUrl={cert_url}&'
120+
'organizationName={course_organization_name}'
121+
).format(
122+
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
123+
cert_name=expected_cert_name,
124+
cert_url=quote(self.CERT_URL, safe=''),
125+
course_organization_name=quote(self.COURSE_ORGANIZATION.encode('utf-8')),
126+
)
127+
128+
with with_site_configuration_context(
129+
configuration=self.SITE_CONFIGURATION_COURSE_LEVEL_ORG
130+
):
131+
course_mock_object = SimpleNamespace(
132+
display_name=self.COURSE_NAME,
133+
display_organization=self.COURSE_ORGANIZATION,
134+
)
135+
actual_url = config.add_to_profile_url(
136+
course_mock_object, cert_mode, self.CERT_URL
137+
)
80138

81139
self.assertEqual(actual_url, expected_url)

common/djangoapps/student/tests/tests.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,6 @@
5151
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase # lint-amnesty, pylint: disable=wrong-import-order
5252
from xmodule.modulestore.tests.factories import CourseFactory, check_mongo_calls # lint-amnesty, pylint: disable=wrong-import-order
5353
from xmodule.data import CertificatesDisplayBehaviors # lint-amnesty, pylint: disable=wrong-import-order
54-
55-
5654
log = logging.getLogger(__name__)
5755

5856
BETA_TESTER_METHOD = 'common.djangoapps.student.helpers.access.is_beta_tester'
@@ -426,6 +424,7 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
426424
self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
427425
self.course.end = datetime.now(pytz.UTC) - timedelta(days=1)
428426
self.course.display_name = 'Omega'
427+
self.course.course_organization = 'Omega Org'
429428
self.course = self.update_course(self.course, self.user.id)
430429

431430
cert = GeneratedCertificateFactory.create(
@@ -449,7 +448,7 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
449448
).format(
450449
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
451450
cert_url=quote(cert.download_url, safe=''),
452-
company_identifier=linkedin_config.company_identifier
451+
company_identifier=linkedin_config.company_identifier,
453452
)
454453

455454
# Single assertion for the expected LinkedIn URL

lms/djangoapps/certificates/views/webview.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ def _update_social_context(request, context, course, user_certificate, platform_
305305
linkedin_config = LinkedInAddToProfileConfiguration.current()
306306
if linkedin_config.is_enabled():
307307
context['linked_in_url'] = linkedin_config.add_to_profile_url(
308-
course.display_name, user_certificate.mode, smart_str(share_url), certificate=user_certificate
308+
course, user_certificate.mode, smart_str(share_url), certificate=user_certificate
309309
)
310310

311311

openedx/core/djangoapps/courseware_api/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ def linkedin_add_to_profile_url(self):
288288
get_certificate_url(course_id=self.course_key, uuid=user_certificate.verify_uuid)
289289
)
290290
return linkedin_config.add_to_profile_url(
291-
self.course_overview.display_name, user_certificate.mode, cert_url, certificate=user_certificate,
291+
self.course_overview, user_certificate.mode, cert_url, certificate=user_certificate,
292292
)
293293

294294
@property

0 commit comments

Comments
 (0)