def test_cert_generation_on_whitelist_append_self_paced(self): """ Verify that signal is sent, received, and fires task based on 'AUTO_CERTIFICATE_GENERATION' flag """ with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None ) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=False): CertificateWhitelist.objects.create( user=self.user, course_id=self.course.id ) mock_generate_certificate_apply_async.assert_not_called() with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): CertificateWhitelist.objects.create( user=self.user, course_id=self.course.id ) mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': unicode(self.user.id), 'course_key': unicode(self.course.id), } )
def configure_waffle_namespace(feature_enabled): namespace = certs_waffle.waffle() auto_certificate_generation_switch = LegacyWaffleSwitch( namespace, certs_waffle.AUTO_CERTIFICATE_GENERATION) with override_waffle_switch(auto_certificate_generation_switch, active=feature_enabled): yield
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pylint: disable=unused-argument switches = waffle.waffle() # All flags enabled if (not switches.is_enabled(waffle.SELF_PACED_ONLY) and not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY)): return # Only SELF_PACED_ONLY flag enabled if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY): if not courses.get_course_by_id(instance.course_id, depth=0).self_paced: return # Only INSTRUCTOR_PACED_ONLY flag enabled if not switches.is_enabled(waffle.SELF_PACED_ONLY): if courses.get_course_by_id(instance.course_id, depth=0).self_paced: return generate_certificate.apply_async( student=instance.user, course_key=instance.course_id, ) log.info( u'Certificate generation task initiated for {user} : {course} via whitelist' .format(user=instance.user.id, course=instance.course_id))
def test_cert_generation_on_photo_verification_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): mock_generate_certificate_apply_async.assert_not_called() attempt = SoftwareSecurePhotoVerification.objects.create( user=self.user_two, status='submitted') attempt.approve() expected_verification_status = { 'status': 'approved', 'error': '', 'should_display': True, } mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': unicode(self.user_two.id), 'course_key': unicode(self.course_two.id), 'expected_verification_status': unicode(expected_verification_status), })
def _listen_for_track_change(sender, user, **kwargs): # pylint: disable=unused-argument """ Catches a track change signal, determines user status, calls fire_ungenerated_certificate_task for passing grades """ if (not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY) and not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY)): return user_enrollments = CourseEnrollment.enrollments_for_user(user=user) grade_factory = CourseGradeFactory() for enrollment in user_enrollments: if grade_factory.read(user=user, course=enrollment.course).passed: if fire_ungenerated_certificate_task(user, enrollment.course.id): log.info( u'Certificate generation task initiated for {user} : {course} via track change' .format(user=user.id, course=enrollment.course.id))
def configure_waffle_namespace(self_paced_enabled, instructor_paced_enabled): namespace = certs_waffle.waffle() with namespace.override(certs_waffle.SELF_PACED_ONLY, active=self_paced_enabled): with namespace.override(certs_waffle.INSTRUCTOR_PACED_ONLY, active=instructor_paced_enabled): yield
def configure_waffle_namespace(feature_enabled): """ Context manager to configure the certs flags """ namespace = certs_waffle.waffle() auto_certificate_generation_switch = LegacyWaffleSwitch(namespace, certs_waffle.AUTO_CERTIFICATE_GENERATION) # pylint: disable=toggle-missing-annotation with override_waffle_switch(auto_certificate_generation_switch, active=feature_enabled): yield
def test_cert_generation_on_whitelist_append_instructor_paced(self): """ Verify that signal is sent, received, and fires task based on 'INSTRUCTOR_PACED_ONLY' flag """ with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.INSTRUCTOR_PACED_ONLY, active=False): CertificateWhitelist.objects.create( user=self.user, course_id=self.ip_course.id) mock_generate_certificate_apply_async.assert_not_called() with waffle.waffle().override(waffle.INSTRUCTOR_PACED_ONLY, active=True): CertificateWhitelist.objects.create( user=self.user, course_id=self.ip_course.id) mock_generate_certificate_apply_async.assert_called_with( student=self.user, course_key=self.ip_course.id)
def _listen_for_passing_grade(sender, user, course_id, **kwargs): # pylint: disable=unused-argument """ Listen for a learner passing a course, send cert generation task, downstream signal from COURSE_GRADE_CHANGED """ # No flags enabled if (not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY) and not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY)): return if courses.get_course_by_id(course_id, depth=0).self_paced: if not waffle.waffle().is_enabled(waffle.SELF_PACED_ONLY): return else: if not waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY): return if fire_ungenerated_certificate_task(user, course_id): log.info( u'Certificate generation task initiated for {user} : {course} via passing grade' .format(user=user.id, course=course_id))
def test_cert_generation_on_passing_self_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.SELF_PACED_ONLY, active=True): grade_factory = CourseGradeFactory() # Not passing grade_factory.update(self.user, self.course) mock_generate_certificate_apply_async.assert_not_called() # Certs fired after passing with mock_passing_grade(): grade_factory.update(self.user, self.course) mock_generate_certificate_apply_async.assert_called_with( student=self.user, course_key=self.course.id)
def test_cert_generation_on_photo_verification_self_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.SELF_PACED_ONLY, active=True): mock_generate_certificate_apply_async.assert_not_called() attempt = SoftwareSecurePhotoVerification.objects.create( user=self.user_one, status='submitted') attempt.approve() mock_generate_certificate_apply_async.assert_called_with( kwargs={ 'student': unicode(self.user_one.id), 'course_key': unicode(self.course_one.id), })
def test_cert_generation_on_photo_verification_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with mock_passing_grade(): grade_factory = CourseGradeFactory() grade_factory.update(self.user_two, self.course_two) with waffle.waffle().override(waffle.INSTRUCTOR_PACED_ONLY, active=True): mock_generate_certificate_apply_async.assert_not_called() attempt = SoftwareSecurePhotoVerification.objects.create( user=self.user_two, status='submitted') attempt.approve() mock_generate_certificate_apply_async.assert_called_with( student=self.user_two, course_key=self.course_two.id)
def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pylint: disable=unused-argument switches = waffle.waffle() # No flags enabled if (not switches.is_enabled(waffle.SELF_PACED_ONLY) and not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY)): return if courses.get_course_by_id(instance.course_id, depth=0).self_paced: if not switches.is_enabled(waffle.SELF_PACED_ONLY): return else: if not switches.is_enabled(waffle.INSTRUCTOR_PACED_ONLY): return fire_ungenerated_certificate_task(instance.user, instance.course_id) log.info( u'Certificate generation task initiated for {user} : {course} via whitelist' .format(user=instance.user.id, course=instance.course_id))
def test_cert_generation_on_passing_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.INSTRUCTOR_PACED_ONLY, active=True): grade_factory = CourseGradeFactory() # Not passing grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_not_called() # Certs fired after passing with mock_passing_grade(): grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_called_with( kwargs={ 'student': unicode(self.user.id), 'course_key': unicode(self.ip_course.id), })
def test_fire_ungenerated_certificate_task_allowed_modes( self, enrollment_mode, should_create): """ Test that certificate generation task is fired for only modes that are allowed to generate certificates automatically. """ self.user = UserFactory.create() self.enrollment = CourseEnrollmentFactory(user=self.user, course_id=self.course.id, is_active=True, mode=enrollment_mode) with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): fire_ungenerated_certificate_task(self.user, self.course.id) task_created = mock_generate_certificate_apply_async.called self.assertEqual(task_created, should_create)
def test_cert_generation_on_passing_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): grade_factory = CourseGradeFactory() # Not passing grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_not_called() # Certs fired after passing with mock_passing_grade(): grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': unicode(self.user.id), 'course_key': unicode(self.ip_course.id), })
def test_cert_generation_on_photo_verification_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None ) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): mock_generate_certificate_apply_async.assert_not_called() attempt = SoftwareSecurePhotoVerification.objects.create( user=self.user_two, status='submitted' ) attempt.approve() mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': unicode(self.user_two.id), 'course_key': unicode(self.course_two.id), 'expected_verification_status': IDVerificationAttempt.STATUS.approved, } )
def test_cert_generation_on_photo_verification_self_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): mock_generate_certificate_apply_async.assert_not_called() attempt = SoftwareSecurePhotoVerification.objects.create( user=self.user_one, status='submitted') attempt.approve() mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': six.text_type(self.user_one.id), 'course_key': six.text_type(self.course_one.id), 'expected_verification_status': IDVerificationAttempt.STATUS.approved, })
def test_fire_ungenerated_certificate_task_allowed_modes(self, enrollment_mode, should_create): """ Test that certificate generation task is fired for only modes that are allowed to generate certificates automatically. """ self.user = UserFactory.create() self.enrollment = CourseEnrollmentFactory( user=self.user, course_id=self.course.id, is_active=True, mode=enrollment_mode ) with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None ) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): fire_ungenerated_certificate_task(self.user, self.course.id) task_created = mock_generate_certificate_apply_async.called self.assertEqual(task_created, should_create)
def test_cert_generation_on_passing_instructor_paced(self): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate.apply_async', return_value=None ) as mock_generate_certificate_apply_async: with waffle.waffle().override(waffle.AUTO_CERTIFICATE_GENERATION, active=True): grade_factory = CourseGradeFactory() # Not passing grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_not_called() # Certs fired after passing with mock_passing_grade(): grade_factory.update(self.user, self.ip_course) mock_generate_certificate_apply_async.assert_called_with( countdown=CERTIFICATE_DELAY_SECONDS, kwargs={ 'student': unicode(self.user.id), 'course_key': unicode(self.ip_course.id), } )
from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_UPDATED from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, GeneratedCertificate) from lms.djangoapps.certificates.signals import _fire_ungenerated_certificate_task from lms.djangoapps.certificates.tasks import CERTIFICATE_DELAY_SECONDS from lms.djangoapps.certificates.tests.factories import CertificateAllowlistFactory, GeneratedCertificateFactory from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import IDVerificationAttempt, SoftwareSecurePhotoVerification from openedx.core.djangoapps.certificates.config import waffle from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory AUTO_CERTIFICATE_GENERATION_SWITCH = LegacyWaffleSwitch( waffle.waffle(), waffle.AUTO_CERTIFICATE_GENERATION) # lint-amnesty, pylint: disable=toggle-missing-annotation class SelfGeneratedCertsSignalTest(ModuleStoreTestCase): """ Tests for enabling/disabling self-generated certificates according to course-pacing. """ ENABLED_SIGNALS = ['course_published'] def setUp(self): super().setUp() CertificateGenerationConfiguration.objects.create(enabled=True) def test_cert_generation_flag_on_pacing_toggle(self): """ Verify that signal enables or disables self-generated certificates
def configure_waffle_namespace(feature_enabled): namespace = certs_waffle.waffle() with namespace.override(certs_waffle.AUTO_CERTIFICATE_GENERATION, active=feature_enabled): yield
""" The public API for certificates. """ from datetime import datetime from pytz import UTC from openedx.core.djangoapps.certificates.config import waffle SWITCHES = waffle.waffle() def auto_certificate_generation_enabled(): return SWITCHES.is_enabled(waffle.AUTO_CERTIFICATE_GENERATION) def _enabled_and_instructor_paced(course): if auto_certificate_generation_enabled(): return not course.self_paced return False def can_show_certificate_available_date_field(course): return _enabled_and_instructor_paced(course) def display_date_for_certificate(course, certificate): if ( auto_certificate_generation_enabled() and not course.self_paced and course.certificate_available_date and course.certificate_available_date < datetime.now(UTC)
def configure_waffle_namespace(feature_enabled): namespace = certs_waffle.waffle() with namespace.override(certs_waffle.AUTO_CERTIFICATE_GENERATION, active=feature_enabled): yield
def is_enabled(self): return ( self.date is not None and datetime.datetime.now(utc) <= self.date and not self.course.self_paced and waffle.waffle().is_enabled(waffle.INSTRUCTOR_PACED_ONLY) )
from lms.djangoapps.certificates import api as certs_api from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_ALLOWLIST from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, GeneratedCertificate) from lms.djangoapps.certificates.signals import fire_ungenerated_certificate_task from lms.djangoapps.certificates.tasks import CERTIFICATE_DELAY_SECONDS from lms.djangoapps.certificates.tests.factories import CertificateWhitelistFactory from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import IDVerificationAttempt, SoftwareSecurePhotoVerification from openedx.core.djangoapps.certificates.config import waffle AUTO_CERTIFICATE_GENERATION_SWITCH = LegacyWaffleSwitch( waffle.waffle(), waffle.AUTO_CERTIFICATE_GENERATION) class SelfGeneratedCertsSignalTest(ModuleStoreTestCase): """ Tests for enabling/disabling self-generated certificates according to course-pacing. """ ENABLED_SIGNALS = ['course_published'] def setUp(self): super(SelfGeneratedCertsSignalTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments CertificateGenerationConfiguration.objects.create(enabled=True) def test_cert_generation_flag_on_pacing_toggle(self): """ Verify that signal enables or disables self-generated certificates
from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_UPDATED from lms.djangoapps.certificates.data import CertificateStatuses from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, GeneratedCertificate ) from lms.djangoapps.certificates.signals import _fire_ungenerated_certificate_task from lms.djangoapps.certificates.tests.factories import CertificateAllowlistFactory, GeneratedCertificateFactory from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from openedx.core.djangoapps.certificates.config import waffle from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory AUTO_CERTIFICATE_GENERATION_SWITCH = LegacyWaffleSwitch(waffle.waffle(), waffle.AUTO_CERTIFICATE_GENERATION) # lint-amnesty, pylint: disable=toggle-missing-annotation class SelfGeneratedCertsSignalTest(ModuleStoreTestCase): """ Tests for enabling/disabling self-generated certificates according to course-pacing. """ ENABLED_SIGNALS = ['course_published'] def setUp(self): super().setUp() CertificateGenerationConfiguration.objects.create(enabled=True) def test_cert_generation_flag_on_pacing_toggle(self): """ Verify that signal enables or disables self-generated certificates
""" The public API for certificates. """ import logging from datetime import datetime from pytz import UTC from lms.djangoapps.certificates.models import CertificateWhitelist from openedx.core.djangoapps.certificates.config import waffle from student.models import CourseEnrollment log = logging.getLogger(__name__) SWITCHES = waffle.waffle() def auto_certificate_generation_enabled(): return SWITCHES.is_enabled(waffle.AUTO_CERTIFICATE_GENERATION) def _enabled_and_instructor_paced(course): if auto_certificate_generation_enabled(): return not course.self_paced return False def certificates_viewable_for_course(course): """ Returns True if certificates are viewable for any student enrolled in the course, False otherwise. """
from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_ALLOWLIST from lms.djangoapps.certificates.models import ( CertificateGenerationConfiguration, CertificateStatuses, GeneratedCertificate ) from lms.djangoapps.certificates.signals import fire_ungenerated_certificate_task from lms.djangoapps.certificates.tasks import CERTIFICATE_DELAY_SECONDS from lms.djangoapps.certificates.tests.factories import CertificateWhitelistFactory from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from lms.djangoapps.grades.tests.utils import mock_passing_grade from lms.djangoapps.verify_student.models import IDVerificationAttempt, SoftwareSecurePhotoVerification from openedx.core.djangoapps.certificates.config import waffle AUTO_CERTIFICATE_GENERATION_SWITCH = LegacyWaffleSwitch(waffle.waffle(), waffle.AUTO_CERTIFICATE_GENERATION) class SelfGeneratedCertsSignalTest(ModuleStoreTestCase): """ Tests for enabling/disabling self-generated certificates according to course-pacing. """ ENABLED_SIGNALS = ['course_published'] def setUp(self): super(SelfGeneratedCertsSignalTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments CertificateGenerationConfiguration.objects.create(enabled=True) def test_cert_generation_flag_on_pacing_toggle(self): """ Verify that signal enables or disables self-generated certificates