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:
Dillon Dumesnil
2020-10-09 15:16:56 -04:00
parent 5a66ed1c41
commit 084ab4c10d
12 changed files with 316 additions and 301 deletions

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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:**

View File

@@ -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,