AA-385: Add in LinkedIn Add to Profile to courseware meta API
A major update to this function allows it to actually autofill the certificate information again! I believe LinkedIn changed their API and we never updated our end. This fixes that!
This commit is contained in:
@@ -95,6 +95,7 @@ class CourseInfoSerializer(serializers.Serializer): # pylint: disable=abstract-
|
||||
course_exit_page_is_active = serializers.BooleanField()
|
||||
certificate_data = CertificateDataSerializer()
|
||||
verify_identity_url = AbsoluteURLField()
|
||||
linkedin_add_to_profile_url = serializers.URLField()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
|
||||
@@ -3,13 +3,19 @@ Tests for courseware API
|
||||
"""
|
||||
import unittest
|
||||
from datetime import datetime
|
||||
from urllib.parse import urlencode
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from completion.test_utils import CompletionWaffleTestMixin, submit_completions_for_testing
|
||||
from django.conf import settings
|
||||
from django.test.client import RequestFactory
|
||||
from django.urls import reverse
|
||||
|
||||
from lms.djangoapps.certificates.api import get_certificate_url
|
||||
from lms.djangoapps.certificates.tests.factories import (
|
||||
GeneratedCertificateFactory, LinkedInAddToProfileConfigurationFactory
|
||||
)
|
||||
from lms.djangoapps.courseware.access_utils import ACCESS_DENIED, ACCESS_GRANTED
|
||||
from lms.djangoapps.courseware.tabs import ExternalLinkCourseTab
|
||||
from lms.djangoapps.courseware.tests.helpers import MasqueradeMixin
|
||||
@@ -73,6 +79,7 @@ class CourseApiTestViews(BaseCoursewareTests):
|
||||
ExternalLinkCourseTab.load('external_link', name='Hidden', link='http://hidden.com', is_hidden=True)
|
||||
)
|
||||
cls.store.update_item(cls.course, cls.user.id)
|
||||
LinkedInAddToProfileConfigurationFactory.create()
|
||||
|
||||
@ddt.data(
|
||||
(True, None, ACCESS_DENIED),
|
||||
@@ -82,6 +89,7 @@ class CourseApiTestViews(BaseCoursewareTests):
|
||||
(False, None, ACCESS_GRANTED),
|
||||
)
|
||||
@ddt.unpack
|
||||
@mock.patch.dict('django.conf.settings.FEATURES', {'CERTIFICATES_HTML_VIEW': True})
|
||||
@mock.patch('openedx.core.djangoapps.courseware_api.views.CoursewareMeta.is_microfrontend_enabled_for_user')
|
||||
def test_course_metadata(self, logged_in, enrollment_mode, enable_anonymous, is_microfrontend_enabled_for_user):
|
||||
is_microfrontend_enabled_for_user.return_value = True
|
||||
@@ -92,6 +100,14 @@ class CourseApiTestViews(BaseCoursewareTests):
|
||||
self.client.logout()
|
||||
if enrollment_mode:
|
||||
CourseEnrollment.enroll(self.user, self.course.id, enrollment_mode)
|
||||
if enrollment_mode == 'verified':
|
||||
cert = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
status='downloadable',
|
||||
mode='verified',
|
||||
)
|
||||
|
||||
response = self.client.get(self.url)
|
||||
assert response.status_code == 200
|
||||
if enrollment_mode:
|
||||
@@ -114,12 +130,32 @@ class CourseApiTestViews(BaseCoursewareTests):
|
||||
'The audit track does not include a certificate.')
|
||||
assert response.data['certificate_data']['msg'] == expected_audit_message
|
||||
assert response.data['verify_identity_url'] is None
|
||||
assert response.data['linkedin_add_to_profile_url'] is None
|
||||
else:
|
||||
# Not testing certificate data for verified learner here. That is tested elsewhere
|
||||
assert response.data['certificate_data'] is None
|
||||
assert response.data['certificate_data']['cert_status'] == 'earned_but_not_available'
|
||||
expected_verify_identity_url = reverse('verify_student_verify_now', args=[self.course.id])
|
||||
# The response contains an absolute URL so this is only checking the path of the final
|
||||
assert expected_verify_identity_url in response.data['verify_identity_url']
|
||||
|
||||
request = RequestFactory().request()
|
||||
cert_url = get_certificate_url(course_id=self.course.id, uuid=cert.verify_uuid)
|
||||
linkedin_url_params = {
|
||||
'name': '{platform_name} Verified Certificate for {course_name}'.format(
|
||||
platform_name=settings.PLATFORM_NAME, course_name=self.course.display_name,
|
||||
),
|
||||
'certUrl': request.build_absolute_uri(cert_url),
|
||||
# default value from the LinkedInAddToProfileConfigurationFactory company_identifier
|
||||
'organizationId': 1337,
|
||||
'certId': cert.verify_uuid,
|
||||
'issueYear': cert.created_date.year,
|
||||
'issueMonth': cert.created_date.month,
|
||||
}
|
||||
expected_linkedin_url = (
|
||||
'https://www.linkedin.com/profile/add?startTask=CERTIFICATION_NAME&{params}'.format(
|
||||
params=urlencode(linkedin_url_params)
|
||||
)
|
||||
)
|
||||
assert response.data['linkedin_add_to_profile_url'] == expected_linkedin_url
|
||||
elif enable_anonymous and not logged_in:
|
||||
# multiple checks use this handler
|
||||
check_public_access.assert_called()
|
||||
|
||||
@@ -21,6 +21,8 @@ from rest_framework.views import APIView
|
||||
|
||||
from course_modes.models import CourseMode
|
||||
from lms.djangoapps.edxnotes.helpers import is_feature_enabled
|
||||
from lms.djangoapps.certificates.api import get_certificate_url
|
||||
from lms.djangoapps.certificates.models import GeneratedCertificate
|
||||
from lms.djangoapps.course_api.api import course_detail
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.access_response import (
|
||||
@@ -41,7 +43,7 @@ from openedx.features.course_experience import DISPLAY_COURSE_SOCK_FLAG
|
||||
from openedx.features.content_type_gating.models import ContentTypeGatingConfig
|
||||
from openedx.features.course_duration_limits.access import generate_course_expired_message
|
||||
from openedx.features.discounts.utils import generate_offer_html
|
||||
from student.models import CourseEnrollment, CourseEnrollmentCelebration
|
||||
from student.models import CourseEnrollment, CourseEnrollmentCelebration, LinkedInAddToProfileConfiguration
|
||||
from xmodule.modulestore.django import modulestore
|
||||
from xmodule.modulestore.search import path_to_location
|
||||
|
||||
@@ -53,15 +55,16 @@ class CoursewareMeta:
|
||||
Encapsulates courseware and enrollment metadata.
|
||||
"""
|
||||
def __init__(self, course_key, request, username=''):
|
||||
self.request = request
|
||||
self.overview = course_detail(
|
||||
request,
|
||||
username or request.user.username,
|
||||
self.request,
|
||||
username or self.request.user.username,
|
||||
course_key,
|
||||
)
|
||||
self.original_user_is_staff = has_access(request.user, 'staff', self.overview).has_access
|
||||
self.original_user_is_staff = has_access(self.request.user, 'staff', self.overview).has_access
|
||||
self.course_key = course_key
|
||||
self.course_masquerade, self.effective_user = setup_masquerade(
|
||||
request,
|
||||
self.request,
|
||||
course_key,
|
||||
staff_access=self.original_user_is_staff,
|
||||
)
|
||||
@@ -244,6 +247,32 @@ class CoursewareMeta:
|
||||
else:
|
||||
return IDVerificationService.get_verify_location('verify_student_verify_now', self.course_key)
|
||||
|
||||
@property
|
||||
def linkedin_add_to_profile_url(self):
|
||||
"""
|
||||
Returns a URL to add a certificate to a LinkedIn profile (will autofill fields).
|
||||
|
||||
Requires LinkedIn sharing to be enabled, either via a site configuration or a
|
||||
LinkedInAddToProfileConfiguration object being enabled.
|
||||
"""
|
||||
if self.effective_user.is_anonymous:
|
||||
return
|
||||
|
||||
linkedin_config = LinkedInAddToProfileConfiguration.current()
|
||||
if linkedin_config.is_enabled():
|
||||
try:
|
||||
user_certificate = GeneratedCertificate.eligible_certificates.get(
|
||||
user=self.effective_user, course_id=self.course_key
|
||||
)
|
||||
except GeneratedCertificate.DoesNotExist:
|
||||
return
|
||||
cert_url = self.request.build_absolute_uri(
|
||||
get_certificate_url(course_id=self.course_key, uuid=user_certificate.verify_uuid)
|
||||
)
|
||||
return linkedin_config.add_to_profile_url(
|
||||
self.overview.display_name, user_certificate.mode, cert_url, certificate=user_certificate,
|
||||
)
|
||||
|
||||
|
||||
class CoursewareInformation(RetrieveAPIView):
|
||||
"""
|
||||
@@ -294,6 +323,7 @@ class CoursewareInformation(RetrieveAPIView):
|
||||
* certificate_data: data regarding the effective user's certificate for the given course
|
||||
* verify_identity_url: URL for a learner to verify their identity. Only returned for learners enrolled in a
|
||||
verified mode. Will update to reverify URL if necessary.
|
||||
* linkedin_add_to_profile_url: URL to add the effective user's certificate to a LinkedIn Profile.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
|
||||
@@ -18,9 +18,7 @@ from django.http import HttpResponse
|
||||
from django.test.client import Client
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from freezegun import freeze_time
|
||||
from mock import patch
|
||||
from six.moves import range
|
||||
|
||||
from openedx.core.djangoapps.password_policy.compliance import (
|
||||
NonCompliantPasswordException,
|
||||
|
||||
Reference in New Issue
Block a user