From 6e5a9f5f6fa000c45aa9636e421ec8a8407c6977 Mon Sep 17 00:00:00 2001 From: Christie Rice <8483753+crice100@users.noreply.github.com> Date: Mon, 8 Feb 2021 10:01:04 -0500 Subject: [PATCH] MICROBA-918 Move methods to separate certificate generation from celery task to schedule certificate generation. Also standardizes imports. (#26410) --- lms/djangoapps/certificates/api.py | 121 +++-------------- lms/djangoapps/certificates/generation.py | 76 ++++++++++- .../certificates/generation_handler.py | 122 +++++++++++++++++- .../commands/resubmit_error_certificates.py | 8 +- lms/djangoapps/certificates/tasks.py | 3 +- lms/djangoapps/certificates/tests/test_api.py | 100 ++++++++------ .../tests/test_cert_management.py | 2 +- .../certificates/tests/test_signals.py | 8 +- .../certificates/tests/test_support_views.py | 19 ++- lms/djangoapps/certificates/views/support.py | 12 +- lms/djangoapps/instructor/tests/test_api.py | 2 +- 11 files changed, 294 insertions(+), 179 deletions(-) diff --git a/lms/djangoapps/certificates/api.py b/lms/djangoapps/certificates/api.py index 0d05837b3e..f8ae0d7997 100644 --- a/lms/djangoapps/certificates/api.py +++ b/lms/djangoapps/certificates/api.py @@ -15,9 +15,13 @@ from django.db.models import Q from eventtracking import tracker from opaque_keys.edx.django.models import CourseKeyField 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.generation_handler import ( + is_using_certificate_allowlist_and_is_on_allowlist as _is_using_certificate_allowlist_and_is_on_allowlist, + generate_user_certificates as _generate_user_certificates, + regenerate_user_certificates as _regenerate_user_certificates +) from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateGenerationCourseSetting, @@ -30,10 +34,8 @@ 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 @@ -181,112 +183,12 @@ def get_recently_modified_certificates(course_keys=None, start_date=None, end_da def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch', forced_grade=None): - """ - It will add the add-cert request into the xqueue. - - A new record will be created to track the certificate - generation task. If an error occurs while adding the certificate - to the queue, the task will have status 'error'. It also emits - `edx.certificate.created` event for analytics. - - Args: - student (User) - course_key (CourseKey) - - Keyword Arguments: - course (Course): Optionally provide the course object; if not provided - it will be loaded. - insecure - (Boolean) - generation_mode - who has requested certificate generation. Its value should `batch` - in case of django command and `self` if student initiated the request. - forced_grade - a string indicating to replace grade parameter. if present grading - will be skipped. - """ - - if not course: - course = modulestore().get_course(course_key, depth=0) - - beta_testers_queryset = list_with_level(course, u'beta') - - if beta_testers_queryset.filter(username=student.username): - message = u'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.' - log.info(message.format(student.username, course_key)) - return - - xqueue = XQueueCertInterface() - if insecure: - xqueue.use_https = False - - generate_pdf = not has_html_certificates_enabled(course) - - cert = xqueue.add_cert( - student, - course_key, - course=course, - generate_pdf=generate_pdf, - forced_grade=forced_grade - ) - - message = u'Queued Certificate Generation task for {user} : {course}' - log.info(message.format(user=student.id, course=course_key)) - - # If cert_status is not present in certificate valid_statuses (for example unverified) then - # add_cert returns None and raises AttributeError while accessing cert attributes. - if cert is None: - return - - if CertificateStatuses.is_passing_status(cert.status): - _emit_certificate_event('created', student, course_key, course, { - 'user_id': student.id, - 'course_id': six.text_type(course_key), - 'certificate_id': cert.verify_uuid, - 'enrollment_mode': cert.mode, - 'generation_mode': generation_mode - }) - return cert.status + return _generate_user_certificates(student, course_key, course, insecure, generation_mode, forced_grade) def regenerate_user_certificates(student, course_key, course=None, forced_grade=None, template_file=None, insecure=False): - """ - It will add the regen-cert request into the xqueue. - - A new record will be created to track the certificate - generation task. If an error occurs while adding the certificate - to the queue, the task will have status 'error'. - - Args: - student (User) - course_key (CourseKey) - - Keyword Arguments: - course (Course): Optionally provide the course object; if not provided - it will be loaded. - grade_value - The grade string, such as "Distinction" - template_file - The template file used to render this certificate - insecure - (Boolean) - """ - xqueue = XQueueCertInterface() - if insecure: - xqueue.use_https = False - - if not course: - course = modulestore().get_course(course_key, depth=0) - - generate_pdf = not has_html_certificates_enabled(course) - log.info( - u"Started regenerating certificates for user %s in course %s with generate_pdf status: %s", - student.username, six.text_type(course_key), generate_pdf - ) - - return xqueue.regen_cert( - student, - course_key, - course=course, - forced_grade=forced_grade, - template_file=template_file, - generate_pdf=generate_pdf - ) + return _regenerate_user_certificates(student, course_key, course, forced_grade, template_file, insecure) def certificate_downloadable_status(student, course_key): @@ -634,3 +536,12 @@ def get_certificate_footer_context(): data.update({'company_about_url': about}) return data + + +def is_using_certificate_allowlist_and_is_on_allowlist(user, course_key): + """ + Return True if both: + 1) the course run is using the allowlist, and + 2) if the user is on the allowlist for this course run + """ + return _is_using_certificate_allowlist_and_is_on_allowlist(user, course_key) diff --git a/lms/djangoapps/certificates/generation.py b/lms/djangoapps/certificates/generation.py index 1749886d0c..8569b591ca 100644 --- a/lms/djangoapps/certificates/generation.py +++ b/lms/djangoapps/certificates/generation.py @@ -6,7 +6,7 @@ existing cert if it does already exist). For now, these methods deal primarily with allowlist certificates, and are part of the V2 certificates revamp. -These method should be called from tasks. +These methods should be called from tasks. """ import logging @@ -18,8 +18,11 @@ from xmodule.modulestore.django import modulestore from common.djangoapps.student.models import CourseEnrollment, UserProfile from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate +from lms.djangoapps.certificates.queue import XQueueCertInterface from lms.djangoapps.certificates.utils import emit_certificate_event +from lms.djangoapps.certificates.utils import has_html_certificates_enabled from lms.djangoapps.grades.api import CourseGradeFactory +from lms.djangoapps.instructor.access import list_with_level log = logging.getLogger(__name__) @@ -86,3 +89,74 @@ def _generate_certificate(user, course_id): created_msg=created_msg )) return cert + + +def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch', + forced_grade=None): + """ + It will add the add-cert request into the xqueue. + + A new record will be created to track the certificate + generation task. If an error occurs while adding the certificate + to the queue, the task will have status 'error'. It also emits + `edx.certificate.created` event for analytics. + + This method has not yet been updated (it predates the certificates revamp). If modifying this method, + see also generate_user_certificates() in generation_handler.py (which is very similar but is not called from a + celery task). In the future these methods will be unified. + + Args: + student (User) + course_key (CourseKey) + + Keyword Arguments: + course (Course): Optionally provide the course object; if not provided + it will be loaded. + insecure - (Boolean) + generation_mode - who has requested certificate generation. Its value should `batch` + in case of django command and `self` if student initiated the request. + forced_grade - a string indicating to replace grade parameter. if present grading + will be skipped. + """ + + if not course: + course = modulestore().get_course(course_key, depth=0) + + beta_testers_queryset = list_with_level(course, 'beta') + + if beta_testers_queryset.filter(username=student.username): + message = 'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.' + log.info(message.format(student.username, course_key)) + return + + xqueue = XQueueCertInterface() + if insecure: + xqueue.use_https = False + + generate_pdf = not has_html_certificates_enabled(course) + + cert = xqueue.add_cert( + student, + course_key, + course=course, + generate_pdf=generate_pdf, + forced_grade=forced_grade + ) + + message = 'Queued Certificate Generation task for {user} : {course}' + log.info(message.format(user=student.id, course=course_key)) + + # If cert_status is not present in certificate valid_statuses (for example unverified) then + # add_cert returns None and raises AttributeError while accessing cert attributes. + if cert is None: + return + + if CertificateStatuses.is_passing_status(cert.status): + emit_certificate_event('created', student, course_key, course, { + 'user_id': student.id, + 'course_id': str(course_key), + 'certificate_id': cert.verify_uuid, + 'enrollment_mode': cert.mode, + 'generation_mode': generation_mode + }) + return cert.status diff --git a/lms/djangoapps/certificates/generation_handler.py b/lms/djangoapps/certificates/generation_handler.py index f62ff82207..4a9fa7d54f 100644 --- a/lms/djangoapps/certificates/generation_handler.py +++ b/lms/djangoapps/certificates/generation_handler.py @@ -4,13 +4,12 @@ Course certificate generation handler. These methods check to see if a certificate can be generated (created if it does not already exist, or updated if it exists but its state can be altered). If so, a celery task is launched to do the generation. If the certificate cannot be generated, a message is logged and no further action is taken. - -For now, these methods deal primarily with allowlist certificates, and are part of the V2 certificates revamp. """ import logging from edx_toggles.toggles import LegacyWaffleFlagNamespace +from xmodule.modulestore.django import modulestore from common.djangoapps.student.models import CourseEnrollment from lms.djangoapps.certificates.models import ( @@ -19,7 +18,10 @@ from lms.djangoapps.certificates.models import ( CertificateWhitelist, GeneratedCertificate ) +from lms.djangoapps.certificates.queue import XQueueCertInterface from lms.djangoapps.certificates.tasks import CERTIFICATE_DELAY_SECONDS, generate_certificate +from lms.djangoapps.certificates.utils import emit_certificate_event, has_html_certificates_enabled +from lms.djangoapps.instructor.access import list_with_level from lms.djangoapps.verify_student.services import IDVerificationService from openedx.core.djangoapps.certificates.api import auto_certificate_generation_enabled from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag @@ -162,3 +164,119 @@ def _can_generate_allowlist_certificate_for_status(cert): 'generation' .format(status=cert.status, user=cert.user.id, course=cert.course_id)) return True + + +def generate_user_certificates(student, course_key, course=None, insecure=False, generation_mode='batch', + forced_grade=None): + """ + It will add the add-cert request into the xqueue. + + A new record will be created to track the certificate + generation task. If an error occurs while adding the certificate + to the queue, the task will have status 'error'. It also emits + `edx.certificate.created` event for analytics. + + This method has not yet been updated (it predates the certificates revamp). If modifying this method, + see also generate_user_certificates() in generation.py (which is very similar but is called from a celery task). + In the future these methods will be unified. + + Args: + student (User) + course_key (CourseKey) + + Keyword Arguments: + course (Course): Optionally provide the course object; if not provided + it will be loaded. + insecure - (Boolean) + generation_mode - who has requested certificate generation. Its value should `batch` + in case of django command and `self` if student initiated the request. + forced_grade - a string indicating to replace grade parameter. if present grading + will be skipped. + """ + + if not course: + course = modulestore().get_course(course_key, depth=0) + + beta_testers_queryset = list_with_level(course, 'beta') + + if beta_testers_queryset.filter(username=student.username): + message = 'Cancelling course certificate generation for user [{}] against course [{}], user is a Beta Tester.' + log.info(message.format(student.username, course_key)) + return + + xqueue = XQueueCertInterface() + if insecure: + xqueue.use_https = False + + generate_pdf = not has_html_certificates_enabled(course) + + cert = xqueue.add_cert( + student, + course_key, + course=course, + generate_pdf=generate_pdf, + forced_grade=forced_grade + ) + + message = 'Queued Certificate Generation task for {user} : {course}' + log.info(message.format(user=student.id, course=course_key)) + + # If cert_status is not present in certificate valid_statuses (for example unverified) then + # add_cert returns None and raises AttributeError while accessing cert attributes. + if cert is None: + return + + if CertificateStatuses.is_passing_status(cert.status): + emit_certificate_event('created', student, course_key, course, { + 'user_id': student.id, + 'course_id': str(course_key), + 'certificate_id': cert.verify_uuid, + 'enrollment_mode': cert.mode, + 'generation_mode': generation_mode + }) + return cert.status + + +def regenerate_user_certificates(student, course_key, course=None, + forced_grade=None, template_file=None, insecure=False): + """ + Add the regen-cert request into the xqueue. + + A new record will be created to track the certificate + generation task. If an error occurs while adding the certificate + to the queue, the task will have status 'error'. + + This method has not yet been updated (it predates the certificates revamp). + + Args: + student (User) + course_key (CourseKey) + + Keyword Arguments: + course (Course): Optionally provide the course object; if not provided + it will be loaded. + grade_value - The grade string, such as "Distinction" + template_file - The template file used to render this certificate + insecure - (Boolean) + """ + xqueue = XQueueCertInterface() + if insecure: + xqueue.use_https = False + + if not course: + course = modulestore().get_course(course_key, depth=0) + + generate_pdf = not has_html_certificates_enabled(course) + log.info( + "Started regenerating certificates for user %s in course %s with generate_pdf status: %s", + student.username, str(course_key), generate_pdf + ) + + return xqueue.regen_cert( + student, + course_key, + course=course, + forced_grade=forced_grade, + template_file=template_file, + generate_pdf=generate_pdf + ) diff --git a/lms/djangoapps/certificates/management/commands/resubmit_error_certificates.py b/lms/djangoapps/certificates/management/commands/resubmit_error_certificates.py index 0f95a4e4fb..37bf08a136 100644 --- a/lms/djangoapps/certificates/management/commands/resubmit_error_certificates.py +++ b/lms/djangoapps/certificates/management/commands/resubmit_error_certificates.py @@ -25,11 +25,11 @@ from django.core.management.base import BaseCommand, CommandError from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey from six import text_type - -from lms.djangoapps.certificates import api as certs_api -from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate from xmodule.modulestore.django import modulestore +from lms.djangoapps.certificates.api import generate_user_certificates +from lms.djangoapps.certificates.models import CertificateStatuses, GeneratedCertificate + LOGGER = logging.getLogger(__name__) @@ -96,7 +96,7 @@ class Command(BaseCommand): course = self._load_course_with_cache(course_key, course_cache) if course is not None: - certs_api.generate_user_certificates(user, course_key, course=course) + generate_user_certificates(user, course_key, course=course) resubmit_count += 1 LOGGER.info( ( diff --git a/lms/djangoapps/certificates/tasks.py b/lms/djangoapps/certificates/tasks.py index 72247085f1..54cf401099 100644 --- a/lms/djangoapps/certificates/tasks.py +++ b/lms/djangoapps/certificates/tasks.py @@ -10,8 +10,7 @@ from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imp from edx_django_utils.monitoring import set_code_owner_attribute from opaque_keys.edx.keys import CourseKey -from lms.djangoapps.certificates.api import generate_user_certificates -from lms.djangoapps.certificates.generation import generate_allowlist_certificate +from lms.djangoapps.certificates.generation import generate_allowlist_certificate, generate_user_certificates from lms.djangoapps.verify_student.services import IDVerificationService logger = getLogger(__name__) diff --git a/lms/djangoapps/certificates/tests/test_api.py b/lms/djangoapps/certificates/tests/test_api.py index 2042e6668f..bfc0223e2f 100644 --- a/lms/djangoapps/certificates/tests/test_api.py +++ b/lms/djangoapps/certificates/tests/test_api.py @@ -18,10 +18,29 @@ from freezegun import freeze_time from mock import patch from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.locator import CourseLocator +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory from common.djangoapps.course_modes.models import CourseMode from common.djangoapps.course_modes.tests.factories import CourseModeFactory -from lms.djangoapps.certificates import api as certs_api +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.tests.factories import UserFactory +from common.djangoapps.util.testing import EventTestMixin +from lms.djangoapps.certificates.api import ( + cert_generation_enabled, + certificate_downloadable_status, + example_certificates_status, + generate_example_certificates, + generate_user_certificates, + get_certificate_for_user, + get_certificates_for_user, + get_certificates_for_user_by_course_keys, + get_certificate_footer_context, + get_certificate_header_context, + get_certificate_url, + is_certificate_invalid, + set_cert_generation_enabled +) from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, @@ -34,11 +53,6 @@ from lms.djangoapps.certificates.tests.factories import CertificateInvalidationF from lms.djangoapps.courseware.tests.factories import GlobalStaffFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from openedx.core.djangoapps.site_configuration.tests.test_util import with_site_configuration -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.tests.factories import UserFactory -from common.djangoapps.util.testing import EventTestMixin -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase, SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy() FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True @@ -112,7 +126,7 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes mode='verified' ) self.assertEqual( - certs_api.certificate_downloadable_status(self.student, self.course.id), + certificate_downloadable_status(self.student, self.course.id), { 'is_downloadable': False, 'is_generating': True, @@ -131,7 +145,7 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes ) self.assertEqual( - certs_api.certificate_downloadable_status(self.student, self.course.id), + certificate_downloadable_status(self.student, self.course.id), { 'is_downloadable': False, 'is_generating': True, @@ -143,7 +157,7 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes def test_without_cert(self): self.assertEqual( - certs_api.certificate_downloadable_status(self.student_no_cert, self.course.id), + certificate_downloadable_status(self.student_no_cert, self.course.id), { 'is_downloadable': False, 'is_generating': False, @@ -167,7 +181,7 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes ) self.assertEqual( - certs_api.certificate_downloadable_status(self.student, self.course.id), + certificate_downloadable_status(self.student, self.course.id), { 'is_downloadable': True, 'is_generating': False, @@ -190,11 +204,11 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes CourseEnrollment.enroll(self.student, self.course.id, mode='honor') self._setup_course_certificate() with mock_passing_grade(): - certs_api.generate_user_certificates(self.student, self.course.id) + generate_user_certificates(self.student, self.course.id) cert_status = certificate_status_for_student(self.student, self.course.id) self.assertEqual( - certs_api.certificate_downloadable_status(self.student, self.course.id), + certificate_downloadable_status(self.student, self.course.id), { 'is_downloadable': True, 'is_generating': False, @@ -224,9 +238,9 @@ class CertificateDownloadableStatusTests(WebCertificateTestMixin, ModuleStoreTes CourseEnrollment.enroll(self.student, self.course.id, mode='honor') self._setup_course_certificate() with mock_passing_grade(): - certs_api.generate_user_certificates(self.student, self.course.id) + generate_user_certificates(self.student, self.course.id) - downloadable_status = certs_api.certificate_downloadable_status(self.student, self.course.id) + downloadable_status = certificate_downloadable_status(self.student, self.course.id) self.assertEqual(downloadable_status['is_downloadable'], cert_downloadable_status) self.assertEqual(downloadable_status.get('earned_but_not_available'), earned_but_not_available) @@ -257,7 +271,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase): # Also check query count for 'is_certificate_invalid' method. with self.assertNumQueries(1): self.assertFalse( - certs_api.is_certificate_invalid(self.student, course.id) + is_certificate_invalid(self.student, course.id) ) @ddt.data( @@ -275,7 +289,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase): generated_cert = self._generate_cert(status) self._invalidate_certificate(generated_cert, True) self.assertTrue( - certs_api.is_certificate_invalid(self.student, self.course.id) + is_certificate_invalid(self.student, self.course.id) ) @ddt.data( @@ -293,7 +307,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase): generated_cert = self._generate_cert(status) self._invalidate_certificate(generated_cert, False) self.assertFalse( - certs_api.is_certificate_invalid(self.student, self.course.id) + is_certificate_invalid(self.student, self.course.id) ) @ddt.data( @@ -318,7 +332,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase): # Also check query count for 'is_certificate_invalid' method. with self.assertNumQueries(2): self.assertTrue( - certs_api.is_certificate_invalid(self.student, self.course.id) + is_certificate_invalid(self.student, self.course.id) ) def _invalidate_certificate(self, certificate, active): @@ -409,7 +423,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): """ Test to get a certificate for a user for a specific course. """ - cert = certs_api.get_certificate_for_user(self.student.username, self.web_cert_course.id) + cert = get_certificate_for_user(self.student.username, self.web_cert_course.id) self.assertEqual(cert['username'], self.student.username) self.assertEqual(cert['course_key'], self.web_cert_course.id) @@ -424,7 +438,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): """ Test to get all the certificates for a user """ - certs = certs_api.get_certificates_for_user(self.student.username) + certs = get_certificates_for_user(self.student.username) self.assertEqual(len(certs), 2) self.assertEqual(certs[0]['username'], self.student.username) self.assertEqual(certs[1]['username'], self.student.username) @@ -448,7 +462,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): Test to get certificates for a user for certain course keys, in a dictionary indexed by those course keys. """ - certs = certs_api.get_certificates_for_user_by_course_keys( + certs = get_certificates_for_user_by_course_keys( user=self.student, course_keys={self.web_cert_course.id, self.no_cert_course.id}, ) @@ -463,7 +477,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): Test the case when there is no certificate for a user for a specific course. """ self.assertIsNone( - certs_api.get_certificate_for_user(self.student_no_cert.username, self.web_cert_course.id) + get_certificate_for_user(self.student_no_cert.username, self.web_cert_course.id) ) def test_no_certificates_for_user(self): @@ -471,7 +485,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): Test the case when there are no certificates for a user. """ self.assertEqual( - certs_api.get_certificates_for_user(self.student_no_cert.username), + get_certificates_for_user(self.student_no_cert.username), [] ) @@ -484,7 +498,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): 'certificates:render_cert_by_uuid', kwargs=dict(certificate_uuid=self.uuid) ) - cert_url = certs_api.get_certificate_url( + cert_url = get_certificate_url( user_id=self.student.id, course_id=self.web_cert_course.id, uuid=self.uuid @@ -496,7 +510,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): kwargs=dict(certificate_uuid=self.uuid) ) - cert_url = certs_api.get_certificate_url( + cert_url = get_certificate_url( user_id=self.student.id, course_id=self.web_cert_course.id, uuid=self.uuid @@ -508,7 +522,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): """ Test the get_certificate_url with a pdf cert course """ - cert_url = certs_api.get_certificate_url( + cert_url = get_certificate_url( user_id=self.student.id, course_id=self.pdf_cert_course.id, uuid=self.uuid @@ -520,7 +534,7 @@ class CertificateGetTests(SharedModuleStoreTestCase): Test the case when there is a certificate but the course was deleted. """ self.assertIsNone( - certs_api.get_certificate_for_user( + get_certificate_for_user( self.student.username, self.nonexistent_course_id ) @@ -555,7 +569,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu def test_new_cert_requests_into_xqueue_returns_generating(self): with mock_passing_grade(): with self._mock_queue(): - certs_api.generate_user_certificates(self.student, self.course.id) + generate_user_certificates(self.student, self.course.id) # Verify that the certificate has status 'generating' cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id) @@ -564,7 +578,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu 'edx.certificate.created', user_id=self.student.id, course_id=six.text_type(self.course.id), - certificate_url=certs_api.get_certificate_url(self.student.id, self.course.id), + certificate_url=get_certificate_url(self.student.id, self.course.id), certificate_id=cert.verify_uuid, enrollment_mode=cert.mode, generation_mode='batch' @@ -573,7 +587,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu def test_xqueue_submit_task_error(self): with mock_passing_grade(): with self._mock_queue(is_successful=False): - certs_api.generate_user_certificates(self.student, self.course.id) + generate_user_certificates(self.student, self.course.id) # Verify that the certificate has been marked with status error cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id) @@ -597,7 +611,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu with mock_passing_grade(): with self._mock_queue(): - status = certs_api.generate_user_certificates(self.student, self.course.id) + status = generate_user_certificates(self.student, self.course.id) self.assertEqual(status, 'generating') @patch.dict(settings.FEATURES, {'CERTIFICATES_HTML_VIEW': True}) @@ -607,7 +621,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu """ self._setup_course_certificate() with mock_passing_grade(): - certs_api.generate_user_certificates(self.student, self.course.id) + generate_user_certificates(self.student, self.course.id) # Verify that the certificate has status 'downloadable' cert = GeneratedCertificate.eligible_certificates.get(user=self.student, course_id=self.course.id) @@ -618,7 +632,7 @@ class GenerateUserCertificatesTest(EventTestMixin, WebCertificateTestMixin, Modu """ Test certificate url is empty if html view is not enabled and certificate is not yet generated """ - url = certs_api.get_certificate_url(self.student.id, self.course.id) + url = get_certificate_url(self.student.id, self.course.id) self.assertEqual(url, "") @@ -649,7 +663,7 @@ class CertificateGenerationEnabledTest(EventTestMixin, TestCase): CertificateGenerationConfiguration.objects.create(enabled=is_feature_enabled) if is_course_enabled is not None: - certs_api.set_cert_generation_enabled(self.COURSE_KEY, is_course_enabled) + set_cert_generation_enabled(self.COURSE_KEY, is_course_enabled) cert_event_type = 'enabled' if is_course_enabled else 'disabled' event_name = '.'.join(['edx', 'certificate', 'generation', cert_event_type]) self.assert_event_emitted( @@ -664,11 +678,11 @@ class CertificateGenerationEnabledTest(EventTestMixin, TestCase): CertificateGenerationConfiguration.objects.create(enabled=True) # Enable for the course - certs_api.set_cert_generation_enabled(self.COURSE_KEY, True) + set_cert_generation_enabled(self.COURSE_KEY, True) self._assert_enabled_for_course(self.COURSE_KEY, True) # Disable for the course - certs_api.set_cert_generation_enabled(self.COURSE_KEY, False) + set_cert_generation_enabled(self.COURSE_KEY, False) self._assert_enabled_for_course(self.COURSE_KEY, False) def test_setting_is_course_specific(self): @@ -676,7 +690,7 @@ class CertificateGenerationEnabledTest(EventTestMixin, TestCase): CertificateGenerationConfiguration.objects.create(enabled=True) # Enable for one course - certs_api.set_cert_generation_enabled(self.COURSE_KEY, True) + set_cert_generation_enabled(self.COURSE_KEY, True) self._assert_enabled_for_course(self.COURSE_KEY, True) # Should be disabled for another course @@ -685,7 +699,7 @@ class CertificateGenerationEnabledTest(EventTestMixin, TestCase): def _assert_enabled_for_course(self, course_key, expect_enabled): """Check that self-generated certificates are enabled or disabled for the course. """ - actual_enabled = certs_api.cert_generation_enabled(course_key) + actual_enabled = cert_generation_enabled(course_key) self.assertEqual(expect_enabled, actual_enabled) @@ -698,7 +712,7 @@ class GenerateExampleCertificatesTest(ModuleStoreTestCase): # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) with self._mock_xqueue() as mock_queue: - certs_api.generate_example_certificates(self.COURSE_KEY) + generate_example_certificates(self.COURSE_KEY) # Verify that the appropriate certs were added to the queue self._assert_certs_in_queue(mock_queue, 1) @@ -716,7 +730,7 @@ class GenerateExampleCertificatesTest(ModuleStoreTestCase): # Generate certificates for the course with self._mock_xqueue() as mock_queue: - certs_api.generate_example_certificates(self.COURSE_KEY) + generate_example_certificates(self.COURSE_KEY) # Verify that the appropriate certs were added to the queue self._assert_certs_in_queue(mock_queue, 2) @@ -748,7 +762,7 @@ class GenerateExampleCertificatesTest(ModuleStoreTestCase): def _assert_cert_status(self, *expected_statuses): """Check the example certificate status. """ - actual_status = certs_api.example_certificates_status(self.COURSE_KEY) + actual_status = example_certificates_status(self.COURSE_KEY) self.assertEqual(list(expected_statuses), actual_status) @@ -775,7 +789,7 @@ class CertificatesBrandingTest(ModuleStoreTestCase): """ # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) - data = certs_api.get_certificate_header_context(is_secure=True) + data = get_certificate_header_context(is_secure=True) # Make sure there are not unexpected keys in dict returned by 'get_certificate_header_context' six.assertCountEqual( @@ -801,7 +815,7 @@ class CertificatesBrandingTest(ModuleStoreTestCase): """ # Generate certificates for the course CourseModeFactory.create(course_id=self.COURSE_KEY, mode_slug=CourseMode.HONOR) - data = certs_api.get_certificate_footer_context() + data = get_certificate_footer_context() # Make sure there are not unexpected keys in dict returned by 'get_certificate_footer_context' six.assertCountEqual( diff --git a/lms/djangoapps/certificates/tests/test_cert_management.py b/lms/djangoapps/certificates/tests/test_cert_management.py index fd2c015a24..dedf063e3b 100644 --- a/lms/djangoapps/certificates/tests/test_cert_management.py +++ b/lms/djangoapps/certificates/tests/test_cert_management.py @@ -167,7 +167,7 @@ class RegenerateCertificatesTest(CertificateManagementTest): @ddt.data(True, False) @override_settings(CERT_QUEUE='test-queue') @patch.dict('django.conf.settings.FEATURES', {'ENABLE_OPENBADGES': True}) - @patch('lms.djangoapps.certificates.api.XQueueCertInterface', spec=True) + @patch('lms.djangoapps.certificates.generation_handler.XQueueCertInterface', spec=True) def test_clear_badge(self, issue_badges, xqueue): """ Given that I have a user with a badge diff --git a/lms/djangoapps/certificates/tests/test_signals.py b/lms/djangoapps/certificates/tests/test_signals.py index 523cc3e13c..60906e0653 100644 --- a/lms/djangoapps/certificates/tests/test_signals.py +++ b/lms/djangoapps/certificates/tests/test_signals.py @@ -14,7 +14,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, UserFactory -from lms.djangoapps.certificates import api as certs_api +from lms.djangoapps.certificates.api import cert_generation_enabled from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_ALLOWLIST from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, @@ -49,15 +49,15 @@ class SelfGeneratedCertsSignalTest(ModuleStoreTestCase): according to course-pacing. """ self.course = CourseFactory.create(self_paced=False, emit_signals=True) # lint-amnesty, pylint: disable=attribute-defined-outside-init - self.assertFalse(certs_api.cert_generation_enabled(self.course.id)) + self.assertFalse(cert_generation_enabled(self.course.id)) self.course.self_paced = True self.store.update_item(self.course, self.user.id) - self.assertTrue(certs_api.cert_generation_enabled(self.course.id)) + self.assertTrue(cert_generation_enabled(self.course.id)) self.course.self_paced = False self.store.update_item(self.course, self.user.id) - self.assertFalse(certs_api.cert_generation_enabled(self.course.id)) + self.assertFalse(cert_generation_enabled(self.course.id)) class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase): diff --git a/lms/djangoapps/certificates/tests/test_support_views.py b/lms/djangoapps/certificates/tests/test_support_views.py index ab0c260819..2445716e5b 100644 --- a/lms/djangoapps/certificates/tests/test_support_views.py +++ b/lms/djangoapps/certificates/tests/test_support_views.py @@ -8,23 +8,22 @@ from uuid import uuid4 import ddt import six - from django.conf import settings from django.test.utils import override_settings from django.urls import reverse from mock import patch from opaque_keys.edx.keys import CourseKey +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory -from lms.djangoapps.certificates import api +from common.djangoapps.student.models import CourseEnrollment +from common.djangoapps.student.roles import GlobalStaff, SupportStaffRole +from common.djangoapps.student.tests.factories import UserFactory +from lms.djangoapps.certificates.api import regenerate_user_certificates from lms.djangoapps.certificates.models import CertificateInvalidation, CertificateStatuses, GeneratedCertificate from lms.djangoapps.certificates.tests.factories import CertificateInvalidationFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory -from common.djangoapps.student.models import CourseEnrollment -from common.djangoapps.student.roles import GlobalStaff, SupportStaffRole -from common.djangoapps.student.tests.factories import UserFactory -from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory FEATURES_WITH_CERTS_ENABLED = settings.FEATURES.copy() FEATURES_WITH_CERTS_ENABLED['CERTIFICATES_HTML_VIEW'] = True @@ -113,7 +112,7 @@ class CertificateSearchTests(CertificateSupportTestCase): ) self.course.cert_html_view_enabled = True - #course certificate configurations + # Course certificate configurations certificates = [ { 'id': 1, @@ -316,8 +315,8 @@ class CertificateRegenerateTests(CertificateSupportTestCase): with mock_passing_grade(percent=0.75): with patch('common.djangoapps.course_modes.models.CourseMode.mode_for_course') as mock_mode_for_course: mock_mode_for_course.return_value = 'honor' - api.regenerate_user_certificates(self.student, self.course.id, # lint-amnesty, pylint: disable=no-member - course=self.course) + regenerate_user_certificates(self.student, self.course.id, # lint-amnesty, pylint: disable=no-member + course=self.course) mock_generate_cert.assert_called() diff --git a/lms/djangoapps/certificates/views/support.py b/lms/djangoapps/certificates/views/support.py index c8a3538001..439183ea05 100644 --- a/lms/djangoapps/certificates/views/support.py +++ b/lms/djangoapps/certificates/views/support.py @@ -18,15 +18,15 @@ from django.utils.translation import ugettext as _ from django.views.decorators.http import require_GET, require_POST from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey +from xmodule.modulestore.django import modulestore -from lms.djangoapps.certificates import api +from common.djangoapps.student.models import CourseEnrollment, User +from common.djangoapps.util.json_request import JsonResponse +from lms.djangoapps.certificates.api import get_certificates_for_user, regenerate_user_certificates from lms.djangoapps.certificates.models import CertificateInvalidation from lms.djangoapps.certificates.permissions import GENERATE_ALL_CERTIFICATES, VIEW_ALL_CERTIFICATES from lms.djangoapps.instructor_task.api import generate_certificates_for_students from openedx.core.djangoapps.content.course_overviews.models import CourseOverview -from common.djangoapps.student.models import CourseEnrollment, User -from common.djangoapps.util.json_request import JsonResponse -from xmodule.modulestore.django import modulestore log = logging.getLogger(__name__) @@ -100,7 +100,7 @@ def search_certificates(request): except User.DoesNotExist: return HttpResponseBadRequest(_(u"user '{user}' does not exist").format(user=user_filter)) - certificates = api.get_certificates_for_user(user.username) + certificates = get_certificates_for_user(user.username) for cert in certificates: cert["course_key"] = six.text_type(cert["course_key"]) cert["created"] = cert["created"].isoformat() @@ -201,7 +201,7 @@ def regenerate_certificate_for_user(request): # Attempt to regenerate certificates try: - certificate = api.regenerate_user_certificates(params["user"], params["course_key"], course=course) + certificate = regenerate_user_certificates(params["user"], params["course_key"], course=course) except: # pylint: disable=bare-except # We are pessimistic about the kinds of errors that might get thrown by the # certificates API. This may be overkill, but we're logging everything so we can diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index a61012473d..2e49b2c996 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -1970,7 +1970,7 @@ class TestInstructorAPIBulkBetaEnrollment(SharedModuleStoreTestCase, LoginEnroll message = message.format(self.beta_tester.username, self.course.id) generate_user_certificates(self.beta_tester, self.course.id, self.course) - capture.check_present(('edx.certificate', 'INFO', message)) + capture.check_present(('lms.djangoapps.certificates.generation_handler', 'INFO', message)) def test_missing_params(self): """ Test missing all query parameters. """