Skip to content

Commit ea4686b

Browse files
committed
feat: Certificate sharing to linkedin (optionally) consider course level organization name
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 0c77083 commit ea4686b

File tree

7 files changed

+150
-33
lines changed

7 files changed

+150
-33
lines changed

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: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,13 +1383,14 @@ def is_enabled(self, *key_fields): # pylint: disable=arguments-differ
13831383
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
13841384
return share_settings.get('CERTIFICATE_LINKEDIN', enabled)
13851385

1386-
def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None):
1386+
def add_to_profile_url(self, course, cert_mode, cert_url, certificate=None):
1387+
13871388
"""
13881389
Construct the URL for the "add to profile" button. This will autofill the form based on
13891390
the params provided.
13901391
13911392
Arguments:
1392-
course_name (str): The display name of the course.
1393+
course (CourseOverview): Course/CourseOverview Object.
13931394
cert_mode (str): The course mode of the user's certificate (e.g. "verified", "honor", "professional")
13941395
cert_url (str): The URL for the certificate.
13951396
@@ -1398,11 +1399,11 @@ def add_to_profile_url(self, course_name, cert_mode, cert_url, certificate=None)
13981399
If provided, this function will also autofill the certId and issue date for the cert.
13991400
"""
14001401
params = {
1401-
'name': self._cert_name(course_name, cert_mode),
1402+
'name': self._cert_name(course.display_name, cert_mode),
14021403
'certUrl': cert_url,
14031404
}
14041405

1405-
params.update(self._organization_information())
1406+
params.update(self._organization_information(course))
14061407

14071408
if certificate:
14081409
params.update({
@@ -1436,19 +1437,34 @@ def _cert_name(self, course_name, cert_mode):
14361437
course_name=course_name
14371438
)
14381439

1439-
def _organization_information(self):
1440+
def _organization_information(self, course=False):
14401441
"""
1441-
Returns organization information for use in the URL parameters for add to profile.
1442+
Returns organization information for use in the URL parameters for add to
1443+
profile. By default when sharing to LinkedIn, Platform Name and/or Platform
1444+
LINKEDIN_COMPANY_ID will be used. If Course specific Organization Name is
1445+
prefered when sharing Certificate to linkedIn the flag for that
1446+
CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME should be set
1447+
to True alongside other LinkedIn settings
14421448
14431449
Returns:
1444-
dict: Either the organization ID on LinkedIn or the organization's name
1450+
dict: Either the organization ID on LinkedIn, the organization's name or
1451+
organization name associated to a specific course
14451452
Will be used to prefill the organization on the add to profile action.
14461453
"""
1447-
org_id = configuration_helpers.get_value('LINKEDIN_COMPANY_ID', self.company_identifier)
1448-
# Prefer organization ID per documentation at https://addtoprofile.linkedin.com/
1449-
if org_id:
1450-
return {'organizationId': org_id}
1451-
return {'organizationName': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)}
1454+
share_settings = configuration_helpers.get_value('SOCIAL_SHARING_SETTINGS', settings.SOCIAL_SHARING_SETTINGS)
1455+
prefer_course_organization_name = share_settings.get(
1456+
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME', False
1457+
)
1458+
if (prefer_course_organization_name and (course is not False)):
1459+
return {"organizationName": course.display_organization}
1460+
else:
1461+
org_id = configuration_helpers.get_value(
1462+
"LINKEDIN_COMPANY_ID", self.company_identifier
1463+
)
1464+
# Prefer organization ID per documentation at https://addtoprofile.linkedin.com/
1465+
if org_id:
1466+
return {"organizationId": org_id}
1467+
return {'organizationName': configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME)}
14521468

14531469

14541470
class EntranceExamConfiguration(models.Model):

common/djangoapps/student/tests/test_certificates.py

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
GeneratedCertificateFactory,
2222
LinkedInAddToProfileConfigurationFactory
2323
)
24+
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
2425

2526
# pylint: disable=no-member
2627

@@ -156,23 +157,52 @@ def test_unverified_certificate_message(self, enrollment_mode):
156157
.format(platform_name=settings.PLATFORM_NAME))
157158

158159
def test_post_to_linkedin_visibility(self):
160+
"""
161+
Verify the LinkedIn "Add to Profile" button visibility based on configuration.
162+
163+
Tests that:
164+
1. When CERTIFICATE_LINKEDIN is False, the LinkedIn button is not visible
165+
2. When CERTIFICATE_LINKEDIN is True, the LinkedIn button appears as expected
166+
"""
167+
self._create_certificate('honor')
168+
169+
# LinkedIn sharing disabled
170+
# When CERTIFICATE_LINKEDIN is set to False in site configuration,
171+
# the LinkedIn "Add to Profile" button should not be visible to users
172+
SITE_CONFIGURATION = {
173+
'SOCIAL_SHARING_SETTINGS': {
174+
'CERTIFICATE_LINKEDIN': False
175+
}
176+
}
177+
with with_site_configuration_context(configuration=SITE_CONFIGURATION):
178+
# button should not be visible
179+
self._check_linkedin_visibility(False)
180+
181+
# LinkedIn sharing enabled
182+
# When CERTIFICATE_LINKEDIN is set to True in site configuration,
183+
# the LinkedIn "Add to Profile" button should be visible to users
184+
# allowing them to share their certificate on LinkedIn
185+
SITE_CONFIGURATION = {
186+
'SOCIAL_SHARING_SETTINGS': {
187+
'CERTIFICATE_LINKEDIN': True
188+
}
189+
}
190+
with with_site_configuration_context(configuration=SITE_CONFIGURATION):
191+
# now we should see it
192+
self._check_linkedin_visibility(True)
193+
194+
def test_post_to_linkedin_is_not_default(self):
159195
"""
160196
Verifies that the post certificate to linked button
161197
does not appear by default (when config is not set)
162-
Then Verifies that the post certificate to linked button appears
163-
as expected once a config is set
164198
"""
165199
self._create_certificate('honor')
166200

201+
# button should not be visible
167202
# until we set up the configuration, the LinkedIn action
168203
# button should not be visible
169204
self._check_linkedin_visibility(False)
170205

171-
LinkedInAddToProfileConfigurationFactory()
172-
# now we should see it
173-
self._check_linkedin_visibility(True)
174-
175-
176206
@ddt.ddt
177207
@skip_unless_lms
178208
class CertificateDisplayTestLinkedHtmlView(CertificateDisplayTestBase):

common/djangoapps/student/tests/test_linkedin.py

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,21 @@
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

109
from lms.djangoapps.certificates.tests.factories import LinkedInAddToProfileConfigurationFactory
1110
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
1211

13-
1412
@ddt.ddt
1513
class LinkedInAddToProfileUrlTests(TestCase):
1614
"""Tests for URL generation of LinkedInAddToProfileConfig. """
1715

1816
COURSE_NAME = 'Test Course ☃'
1917
CERT_URL = 'http://s3.edx/cert'
18+
COURSE_ORGANIZATION = 'TEST+ORGANIZATION'
2019
SITE_CONFIGURATION = {
2120
'SOCIAL_SHARING_SETTINGS': {
2221
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
@@ -27,6 +26,17 @@ class LinkedInAddToProfileUrlTests(TestCase):
2726
}
2827
}
2928
}
29+
SITE_CONFIGURATION_COURSE_LEVEL_ORG = {
30+
'SOCIAL_SHARING_SETTINGS': {
31+
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME': True,
32+
'CERTIFICATE_LINKEDIN_MODE_TO_CERT_NAME': {
33+
'honor': '{platform_name} Honor Code Credential for {course_name}',
34+
'verified': '{platform_name} Verified Credential for {course_name}',
35+
'professional': '{platform_name} Professional Credential for {course_name}',
36+
'no-id-professional': '{platform_name} Professional Credential for {course_name}',
37+
}
38+
}
39+
}
3040

3141
@ddt.data(
3242
('honor', 'Honor+Code+Certificate+for+Test+Course+%E2%98%83'),
@@ -49,7 +59,13 @@ def test_linked_in_url(self, cert_mode, expected_cert_name):
4959
company_identifier=config.company_identifier,
5060
)
5161

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

5470
self.assertEqual(actual_url, expected_url)
5571

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

81138
self.assertEqual(actual_url, expected_url)

common/djangoapps/student/tests/tests.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,7 @@
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-
54+
from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration_context
5655
log = logging.getLogger(__name__)
5756

5857
BETA_TESTER_METHOD = 'common.djangoapps.student.helpers.access.is_beta_tester'
@@ -399,7 +398,10 @@ def test_linked_in_add_to_profile_btn_not_appearing_without_config(self):
399398
grade='67',
400399
download_url=download_url
401400
)
402-
response = self.client.get(reverse('dashboard'))
401+
# LinkedIn sharing config is not set
402+
SITE_CONFIGURATION = {'SOCIAL_SHARING_SETTINGS': {}}
403+
with with_site_configuration_context(configuration=SITE_CONFIGURATION):
404+
response = self.client.get(reverse('dashboard'))
403405

404406
assert response.status_code == 200
405407
self.assertNotContains(response, 'Add Certificate to LinkedIn')
@@ -426,6 +428,7 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
426428
self.course.start = datetime.now(pytz.UTC) - timedelta(days=2)
427429
self.course.end = datetime.now(pytz.UTC) - timedelta(days=1)
428430
self.course.display_name = 'Omega'
431+
self.course.course_organization = 'Omega Org'
429432
self.course = self.update_course(self.course, self.user.id)
430433

431434
cert = GeneratedCertificateFactory.create(
@@ -436,7 +439,18 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
436439
grade='67',
437440
download_url='https://www.edx.org'
438441
)
439-
response = self.client.get(reverse('dashboard'))
442+
# LinkedIn sharing enabled
443+
# When CERTIFICATE_LINKEDIN is set to True in site configuration,
444+
# the LinkedIn "Add to Profile" button should be visible to users
445+
# allowing them to share their certificate on LinkedIn
446+
SITE_CONFIGURATION = {
447+
'SOCIAL_SHARING_SETTINGS': {
448+
'CERTIFICATE_LINKEDIN': True,
449+
'CERTIFICATE_LINKEDIN_DEFAULTS_TO_COURSE_ORGANIZATION_NAME': False
450+
}
451+
}
452+
with with_site_configuration_context(configuration=SITE_CONFIGURATION):
453+
response = self.client.get(reverse('dashboard'))
440454

441455
assert response.status_code == 200
442456
self.assertContains(response, 'Add Certificate to LinkedIn')
@@ -449,7 +463,7 @@ def test_linked_in_add_to_profile_btn_with_certificate(self):
449463
).format(
450464
platform=quote(settings.PLATFORM_NAME.encode('utf-8')),
451465
cert_url=quote(cert.download_url, safe=''),
452-
company_identifier=linkedin_config.company_identifier
466+
company_identifier=linkedin_config.company_identifier,
453467
)
454468

455469
# 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)