From b1e5695acfd121a60c9383b8cf005a3a90bdec11 Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Tue, 26 Jan 2021 14:41:34 -0500 Subject: [PATCH] MICROBA-921 Move emit_certificate_event() to utils (#26133) --- lms/djangoapps/certificates/api.py | 91 ++------------ lms/djangoapps/certificates/tests/test_api.py | 2 +- .../certificates/tests/test_views.py | 2 +- .../certificates/tests/test_webview_views.py | 91 +++++++------- lms/djangoapps/certificates/utils.py | 117 ++++++++++++++++++ lms/djangoapps/certificates/views/webview.py | 7 +- .../djangoapps/programs/tests/test_utils.py | 3 +- 7 files changed, 176 insertions(+), 137 deletions(-) create mode 100644 lms/djangoapps/certificates/utils.py diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 0f7a969050..fb968f9f27 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -9,13 +9,11 @@ rather than importing Django models directly. import logging import six -from django.conf import settings from django.db.models import Q -from django.urls import reverse from eventtracking import tracker from opaque_keys.edx.django.models import CourseKeyField -from opaque_keys.edx.keys import CourseKey from organizations.api import get_course_organization_id +from xmodule.modulestore.django import modulestore from lms.djangoapps.branding import api as branding_api from lms.djangoapps.certificates.models import ( @@ -30,10 +28,12 @@ from lms.djangoapps.certificates.models import ( certificate_status_for_student ) from lms.djangoapps.certificates.queue import XQueueCertInterface +from lms.djangoapps.certificates.utils import emit_certificate_event as _emit_certificate_event +from lms.djangoapps.certificates.utils import get_certificate_url as _get_certificate_url +from lms.djangoapps.certificates.utils import has_html_certificates_enabled as _has_html_certificates_enabled from lms.djangoapps.instructor.access import list_with_level from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from xmodule.modulestore.django import modulestore log = logging.getLogger("edx.certificate") MODES = GeneratedCertificate.MODES @@ -234,7 +234,7 @@ def generate_user_certificates(student, course_key, course=None, insecure=False, return if CertificateStatuses.is_passing_status(cert.status): - emit_certificate_event('created', student, course_key, course, { + _emit_certificate_event('created', student, course_key, course, { 'user_id': student.id, 'course_id': six.text_type(course_key), 'certificate_id': cert.verify_uuid, @@ -458,7 +458,7 @@ def example_certificates_status(course_key): Example Usage: >>> from lms.djangoapps.certificates import api as certs_api - >>> certs_api.example_certificate_status(course_key) + >>> certs_api.example_certificates_status(course_key) [ { 'description': 'honor', @@ -476,63 +476,12 @@ def example_certificates_status(course_key): return ExampleCertificateSet.latest_status(course_key) -def _safe_course_key(course_key): - if not isinstance(course_key, CourseKey): - return CourseKey.from_string(course_key) - return course_key - - -def _course_from_key(course_key): - return CourseOverview.get_from_id(_safe_course_key(course_key)) - - -def _certificate_html_url(uuid): - """ - Returns uuid based certificate URL. - """ - return reverse( - 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': uuid} - ) if uuid else '' - - -def _certificate_download_url(user_id, course_id, user_certificate=None): - if not user_certificate: - try: - user_certificate = GeneratedCertificate.eligible_certificates.get( - user=user_id, - course_id=_safe_course_key(course_id) - ) - except GeneratedCertificate.DoesNotExist: - log.critical( - u'Unable to lookup certificate\n' - u'user id: %s\n' - u'course: %s', six.text_type(user_id), six.text_type(course_id) - ) - - if user_certificate: - return user_certificate.download_url - - return '' - - def has_html_certificates_enabled(course): - if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): - return False - return course.cert_html_view_enabled + return _has_html_certificates_enabled(course) def get_certificate_url(user_id=None, course_id=None, uuid=None, user_certificate=None): - url = '' - - course = _course_from_key(course_id) - if not course: - return url - - if has_html_certificates_enabled(course): - url = _certificate_html_url(uuid) - else: - url = _certificate_download_url(user_id, course_id, user_certificate=user_certificate) - return url + return _get_certificate_url(user_id, course_id, uuid, user_certificate) def get_active_web_certificate(course, is_preview_mode=None): @@ -634,30 +583,6 @@ def _get_two_letter_language_code(language_code): return language_code[:2] -def emit_certificate_event(event_name, user, course_id, course=None, event_data=None): - """ - Emits certificate event. - """ - event_name = '.'.join(['edx', 'certificate', event_name]) - if course is None: - course = modulestore().get_course(course_id, depth=0) - context = { - 'org_id': course.org, - 'course_id': six.text_type(course_id) - } - - data = { - 'user_id': user.id, - 'course_id': six.text_type(course_id), - 'certificate_url': get_certificate_url(user.id, course_id, uuid=event_data['certificate_id']) - } - event_data = event_data or {} - event_data.update(data) - - with tracker.get_tracker().context(event_name, context): - tracker.emit(event_name, event_data) - - def get_asset_url_by_slug(asset_slug): """ Returns certificate template asset url for given asset_slug. diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index 227d4cf408..61e368e6cc 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -535,7 +535,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu ENABLED_SIGNALS = ['course_published'] def setUp(self): # pylint: disable=arguments-differ - super(GenerateUserCertificatesTest, self).setUp('lms.djangoapps.certificates.api.tracker') + super().setUp('lms.djangoapps.certificates.utils.tracker') self.student = UserFactory.create( email='joe_user@edx.org', diff --git a/lms/djangoapps/certificates/tests/test_views.py b/lms/djangoapps/certificates/tests/test_views.py index 563c9a5687..980daede09 100644 --- a/lms/djangoapps/certificates/tests/test_views.py +++ b/lms/djangoapps/certificates/tests/test_views.py @@ -15,13 +15,13 @@ from django.urls import reverse from opaque_keys.edx.locator import CourseLocator from six.moves import range -from lms.djangoapps.certificates.api import get_certificate_url from lms.djangoapps.certificates.models import ( CertificateHtmlViewConfiguration, ExampleCertificate, ExampleCertificateSet, GeneratedCertificate ) +from lms.djangoapps.certificates.utils import get_certificate_url from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration from openedx.core.djangolib.testing.utils import CacheIsolationTestCase from common.djangoapps.student.tests.factories import UserFactory diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py index f4dc26bdbf..55ec89e4d1 100644 --- a/lms/djangoapps/certificates/tests/test_webview_views.py +++ b/lms/djangoapps/certificates/tests/test_webview_views.py @@ -4,7 +4,6 @@ import datetime import json -from collections import OrderedDict from urllib.parse import urlencode from uuid import uuid4 @@ -26,7 +25,6 @@ from lms.djangoapps.badges.tests.factories import ( BadgeClassFactory, CourseCompleteImageConfigurationFactory ) -from lms.djangoapps.certificates.api import get_certificate_url from lms.djangoapps.certificates.models import ( CertificateGenerationCourseSetting, CertificateHtmlViewConfiguration, @@ -36,6 +34,7 @@ from lms.djangoapps.certificates.models import ( CertificateTemplateAsset, GeneratedCertificate ) +from lms.djangoapps.certificates.utils import get_certificate_url from lms.djangoapps.certificates.tests.factories import ( CertificateHtmlViewConfigurationFactory, GeneratedCertificateFactory, @@ -1005,7 +1004,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) response_json = json.loads(response.content.decode('utf-8')) self.assertEqual(CertificateStatuses.generating, response_json['add_status']) - #TEMPLATES WITHOUT LANGUAGE TESTS + # TEMPLATES WITHOUT LANGUAGE TESTS @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(LANGUAGE_CODE='fr') @patch('lms.djangoapps.certificates.views.webview.get_course_run_details') @@ -1126,8 +1125,8 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) self.assertContains(response, u'mode: {}'.format(mode)) self.assertContains(response, 'course name: test_template_1_course') - ## Templates With Language tests - #1 + # Templates With Language tests + # 1 @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @override_settings(LANGUAGE_CODE='fr') @patch('lms.djangoapps.certificates.views.webview.get_course_run_details') @@ -1164,16 +1163,16 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_id=six.text_type(self.course.id), uuid=self.cert.verify_uuid ) - #create a org_mode_and_coursekey template language=null + # Create an org_mode_and_coursekey template language=null self._create_custom_named_template( 'test_null_lang_template', org_id=1, mode='honor', course_key=six.text_type(self.course.id), language=None, ) - #verify return template lang = null + # Verify return template lang = null response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a org_mode_and_coursekey template language=wrong_language + # Create an org_mode_and_coursekey template language=wrong_language self._create_custom_named_template( 'test_wrong_lang_template', org_id=1, @@ -1181,12 +1180,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_key=six.text_type(self.course.id), language=wrong_language, ) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create an org_mode_and_coursekey template language='' + # Create an org_mode_and_coursekey template language='' self._create_custom_named_template( 'test_all_languages_template', org_id=1, @@ -1194,12 +1193,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_key=six.text_type(self.course.id), language='', ) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_all_languages_template') - #create a org_mode_and_coursekey template language=lang + # Create a org_mode_and_coursekey template language=lang self._create_custom_named_template( 'test_right_lang_template', org_id=1, @@ -1212,7 +1211,7 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_right_lang_template') - #2 + # 2 @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @patch('lms.djangoapps.certificates.views.webview.get_course_run_details') @patch('lms.djangoapps.certificates.api.get_course_organization_id') @@ -1244,35 +1243,35 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_id=six.text_type(self.course.id), uuid=self.cert.verify_uuid ) - #create a org and mode template language=null + # Create a org and mode template language=null self._create_custom_named_template('test_null_lang_template', org_id=1, mode='honor', language=None) - #verify return template lang = null + # Verify return template lang = null response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a org and mode template language=wrong_language + # Create a org and mode template language=wrong_language self._create_custom_named_template('test_wrong_lang_template', org_id=1, mode='honor', language=wrong_language) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create an org and mode template language='' + # Create an org and mode template language='' self._create_custom_named_template('test_all_languages_template', org_id=1, mode='honor', language='') - #verify returns All Languages template + # Verify returns All Languages template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_all_languages_template') - #create a org and mode template language=lang + # Create a org and mode template language=lang self._create_custom_named_template('test_right_lang_template', org_id=1, mode='honor', language=right_language) - # verify return right_language template + # Verify return right_language template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_right_lang_template') - #3 + # 3 @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @patch('lms.djangoapps.certificates.views.webview.get_course_run_details') @patch('lms.djangoapps.certificates.api.get_course_organization_id') @@ -1302,35 +1301,35 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_id=six.text_type(self.course.id), uuid=self.cert.verify_uuid ) - #create a org template language=null + # Create a org template language=null self._create_custom_named_template('test_null_lang_template', org_id=1, language=None) - #verify return template lang = null + # Verify return template lang = null response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a org template language=wrong_language + # Create a org template language=wrong_language self._create_custom_named_template('test_wrong_lang_template', org_id=1, language=wrong_language) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create an org template language='' + # Create an org template language='' self._create_custom_named_template('test_all_languages_template', org_id=1, language='') - #verify returns All Languages template + # Verify returns All Languages template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_all_languages_template') - #create a org template language=lang + # Create a org template language=lang self._create_custom_named_template('test_right_lang_template', org_id=1, language=right_language) - # verify return right_language template + # Verify return right_language template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_right_lang_template') - #4 + # 4 @override_settings(FEATURES=FEATURES_WITH_CUSTOM_CERTS_ENABLED) @patch('lms.djangoapps.certificates.views.webview.get_course_run_details') @patch('lms.djangoapps.certificates.api.get_course_organization_id') @@ -1361,30 +1360,30 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_id=six.text_type(self.course.id), uuid=self.cert.verify_uuid ) - #create a mode template language=null + # Create a mode template language=null self._create_custom_named_template('test_null_lang_template', mode='honor', language=None) - #verify return template with lang = null + # Verify return template with lang = null response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a mode template language=wrong_language + # Create a mode template language=wrong_language self._create_custom_named_template('test_wrong_lang_template', mode='honor', language=wrong_language) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a mode template language='' + # Create a mode template language='' self._create_custom_named_template('test_all_languages_template', mode='honor', language='') - #verify returns All Languages template + # Verify returns All Languages template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_all_languages_template') - #create a mode template language=lang + # Create a mode template language=lang self._create_custom_named_template('test_right_lang_template', mode='honor', language=right_language) - # verify return right_language template + # Verify return right_language template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_right_lang_template') @@ -1423,28 +1422,28 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) course_id=six.text_type(self.course.id), uuid=self.cert.verify_uuid ) - #create a mode template language=null + # Create a mode template language=null self._create_custom_named_template('test_null_lang_template', org_id=1, mode='honor', language=None) - #verify return template with lang = null + # Verify return template with lang = null response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a mode template language=wrong_language + # Create a mode template language=wrong_language self._create_custom_named_template('test_wrong_lang_template', org_id=1, mode='honor', language=wrong_language) - #verify returns null lang template + # Verify returns null lang template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_null_lang_template') - #create a mode template language='' + # Create a mode template language='' self._create_custom_named_template('test_all_languages_template', org_id=1, mode='honor', language='') - #verify returns All Languages template + # Verify returns All Languages template response = self.client.get(test_url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'course name: test_all_languages_template') - #create a mode template language=lang + # Create a mode template language=lang self._create_custom_named_template('test_right_lang_template', org_id=1, mode='honor', language=right_language) # verify return right_language template response = self.client.get(test_url) diff --git a/lms/djangoapps/certificates/utils.py b/lms/djangoapps/certificates/utils.py new file mode 100644 index 0000000000..d4063348c8 --- /dev/null +++ b/lms/djangoapps/certificates/utils.py @@ -0,0 +1,117 @@ +""" +Certificates utilities +""" + +import logging + +import six +from django.conf import settings +from django.urls import reverse +from eventtracking import tracker +from opaque_keys.edx.keys import CourseKey +from xmodule.modulestore.django import modulestore + +from lms.djangoapps.certificates.models import ( + GeneratedCertificate +) +from openedx.core.djangoapps.content.course_overviews.models import CourseOverview + +log = logging.getLogger(__name__) + + +def emit_certificate_event(event_name, user, course_id, course=None, event_data=None): + """ + Emits certificate event. + """ + event_name = '.'.join(['edx', 'certificate', event_name]) + if course is None: + course = modulestore().get_course(course_id, depth=0) + context = { + 'org_id': course.org, + 'course_id': six.text_type(course_id) + } + + data = { + 'user_id': user.id, + 'course_id': six.text_type(course_id), + 'certificate_url': get_certificate_url(user.id, course_id, uuid=event_data['certificate_id']) + } + event_data = event_data or {} + event_data.update(data) + + with tracker.get_tracker().context(event_name, context): + tracker.emit(event_name, event_data) + + +def get_certificate_url(user_id=None, course_id=None, uuid=None, user_certificate=None): + """ + Returns the certificate URL + """ + url = '' + + course = _course_from_key(course_id) + if not course: + return url + + if has_html_certificates_enabled(course): + url = _certificate_html_url(uuid) + else: + url = _certificate_download_url(user_id, course_id, user_certificate=user_certificate) + return url + + +def has_html_certificates_enabled(course): + """ + Returns True if HTML certificates are enabled + """ + if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False): + return False + return course.cert_html_view_enabled + + +def _certificate_html_url(uuid): + """ + Returns uuid based certificate URL. + """ + return reverse( + 'certificates:render_cert_by_uuid', kwargs={'certificate_uuid': uuid} + ) if uuid else '' + + +def _certificate_download_url(user_id, course_id, user_certificate=None): + """ + Returns the certificate download URL + """ + if not user_certificate: + try: + user_certificate = GeneratedCertificate.eligible_certificates.get( + user=user_id, + course_id=_safe_course_key(course_id) + ) + except GeneratedCertificate.DoesNotExist: + log.critical( + u'Unable to lookup certificate\n' + u'user id: %s\n' + u'course: %s', six.text_type(user_id), six.text_type(course_id) + ) + + if user_certificate: + return user_certificate.download_url + + return '' + + +def _course_from_key(course_key): + """ + Returns the course overview + """ + return CourseOverview.get_from_id(_safe_course_key(course_key)) + + +def _safe_course_key(course_key): + """ + Returns the course key + """ + if not isinstance(course_key, CourseKey): + return CourseKey.from_string(course_key) + return course_key diff --git a/lms/djangoapps/certificates/views/webview.py b/lms/djangoapps/certificates/views/webview.py index 363007e4b1..76cae2abdd 100644 --- a/lms/djangoapps/certificates/views/webview.py +++ b/lms/djangoapps/certificates/views/webview.py @@ -27,12 +27,10 @@ from lms.djangoapps.badges.utils import badges_enabled from common.djangoapps.edxmako.shortcuts import render_to_response from common.djangoapps.edxmako.template import Template from lms.djangoapps.certificates.api import ( - emit_certificate_event, get_active_web_certificate, get_certificate_footer_context, get_certificate_header_context, - get_certificate_template, - get_certificate_url + get_certificate_template ) from lms.djangoapps.certificates.models import ( CertificateGenerationCourseSetting, @@ -42,6 +40,7 @@ from lms.djangoapps.certificates.models import ( GeneratedCertificate ) from lms.djangoapps.certificates.permissions import PREVIEW_CERTIFICATES +from lms.djangoapps.certificates.utils import emit_certificate_event, get_certificate_url from lms.djangoapps.courseware.courses import get_course_by_id from openedx.core.djangoapps.catalog.utils import get_course_run_details from openedx.core.djangoapps.certificates.api import certificates_viewable_for_course, display_date_for_certificate @@ -430,7 +429,7 @@ def _update_organization_context(context, course): partner_short_name = course.display_organization if course.display_organization else course.org organizations = organizations_api.get_course_organizations(course_key=course.id) if organizations: - #TODO Need to add support for multiple organizations, Currently we are interested in the first one. + # TODO Need to add support for multiple organizations, Currently we are interested in the first one. organization = organizations[0] partner_long_name = organization.get('name', partner_long_name) partner_short_name = organization.get('short_name', partner_short_name) diff --git a/openedx/core/djangoapps/programs/tests/test_utils.py b/openedx/core/djangoapps/programs/tests/test_utils.py index bc75946421..98c3c45b90 100644 --- a/openedx/core/djangoapps/programs/tests/test_utils.py +++ b/openedx/core/djangoapps/programs/tests/test_utils.py @@ -57,7 +57,6 @@ from xmodule.modulestore.tests.django_utils import ( ) from xmodule.modulestore.tests.factories import CourseFactory as ModuleStoreCourseFactory -CERTIFICATES_API_MODULE = 'lms.djangoapps.certificates.api' ECOMMERCE_URL_ROOT = 'https://ecommerce.example.com' UTILS_MODULE = 'openedx.core.djangoapps.programs.utils' LOGGER_NAME = 'openedx.core.djangoapps.programs.utils' @@ -932,7 +931,7 @@ class TestProgramDataExtender(ModuleStoreTestCase): @ddt.data(True, False) @mock.patch(UTILS_MODULE + '.certificate_api.certificate_downloadable_status') - @mock.patch(CERTIFICATES_API_MODULE + '.has_html_certificates_enabled') + @mock.patch('lms.djangoapps.certificates.utils.has_html_certificates_enabled') def test_certificate_url_retrieval(self, is_uuid_available, mock_html_certs_enabled, mock_get_cert_data): """ Verify that the student's run mode certificate is included,