Skip to content
Snippets Groups Projects
Unverified Commit 6b0bc638 authored by Christie Rice's avatar Christie Rice Committed by GitHub
Browse files

MICROBA-918 Update allowlist check to handle more signals (#26606)

parent d2a147bb
Branches
Tags release-2021-02-18-12.27
No related merge requests found
......@@ -19,6 +19,7 @@ from organizations.api import get_course_organization_id
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_allowlist_certificate_task as _generate_allowlist_certificate_task,
generate_user_certificates as _generate_user_certificates,
regenerate_user_certificates as _regenerate_user_certificates
)
......@@ -191,6 +192,10 @@ def regenerate_user_certificates(student, course_key, course=None,
return _regenerate_user_certificates(student, course_key, course, forced_grade, template_file, insecure)
def generate_allowlist_certificate_task(user, course_key):
return _generate_allowlist_certificate_task(user, course_key)
def certificate_downloadable_status(student, course_key):
"""
Check the student existing certificates against a given course.
......
......@@ -131,24 +131,15 @@ class Command(BaseCommand):
if not options['noop']:
# Add the certificate request to the queue
ret = generate_user_certificates(
generate_user_certificates(
student,
course_key,
course=course,
insecure=options['insecure']
)
if ret == 'generating':
LOGGER.info(
(
u"Added a certificate generation task to the XQueue "
u"for student %s in course '%s'. "
u"The new certificate status is '%s'."
),
student.id,
text_type(course_key),
ret
)
LOGGER.info(f"Added a certificate generation task to the XQueue for student {student.id} in "
f"course {course_key}.")
else:
LOGGER.info(
......
......@@ -54,10 +54,15 @@ def _listen_for_certificate_whitelist_append(sender, instance, **kwargs): # pyl
"""
Listen for a user being added to or modified on the whitelist (allowlist)
"""
if is_using_certificate_allowlist_and_is_on_allowlist(instance.user, instance.course_id):
log.info(f'{instance.course_id} is using allowlist certificates, and the user {instance.user.id} is now on '
f'its allowlist. Attempt will be made to generate an allowlist certificate.')
return generate_allowlist_certificate_task(instance.user, instance.course_id)
if not auto_certificate_generation_enabled():
return
if fire_ungenerated_certificate_task(instance.user, instance.course_id):
if _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
......@@ -70,10 +75,15 @@ def listen_for_passing_grade(sender, user, course_id, **kwargs): # pylint: disa
Listen for a learner passing a course, send cert generation task,
downstream signal from COURSE_GRADE_CHANGED
"""
if is_using_certificate_allowlist_and_is_on_allowlist(user, course_id):
log.info(f'{course_id} is using allowlist certificates, and the user {user.id} is on its allowlist. Attempt '
f'will be made to generate an allowlist certificate as a passing grade was received.')
return generate_allowlist_certificate_task(user, course_id)
if not auto_certificate_generation_enabled():
return
if fire_ungenerated_certificate_task(user, course_id):
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
......@@ -107,7 +117,7 @@ def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs): # pyli
def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylint: disable=unused-argument
"""
Catches a track change signal, determines user status,
calls fire_ungenerated_certificate_task for passing grades
calls _fire_ungenerated_certificate_task for passing grades
"""
if not auto_certificate_generation_enabled():
return
......@@ -118,8 +128,13 @@ def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylin
expected_verification_status = IDVerificationService.user_status(user)
expected_verification_status = expected_verification_status['status']
for enrollment in user_enrollments:
if grade_factory.read(user=user, course=enrollment.course_overview).passed:
if fire_ungenerated_certificate_task(user, enrollment.course_id, expected_verification_status):
if is_using_certificate_allowlist_and_is_on_allowlist(user, enrollment.course_id):
log.info(f'{enrollment.course_id} is using allowlist certificates, and the user {user.id} is on its '
f'allowlist. Attempt will be made to generate an allowlist certificate. Id verification status '
f'is {expected_verification_status}')
generate_allowlist_certificate_task(user, enrollment.course_id)
elif grade_factory.read(user=user, course=enrollment.course_overview).passed:
if _fire_ungenerated_certificate_task(user, enrollment.course_id, expected_verification_status):
message = (
u'Certificate generation task initiated for {user} : {course} via track change ' +
u'with verification status of {status}'
......@@ -131,7 +146,7 @@ def _listen_for_id_verification_status_changed(sender, user, **kwargs): # pylin
))
def fire_ungenerated_certificate_task(user, course_key, expected_verification_status=None):
def _fire_ungenerated_certificate_task(user, course_key, expected_verification_status=None):
"""
Helper function to fire certificate generation task.
Auto-generation of certificates is available for following course modes:
......@@ -156,14 +171,6 @@ def fire_ungenerated_certificate_task(user, course_key, expected_verification_st
message = u'Entered into Ungenerated Certificate task for {user} : {course}'
log.info(message.format(user=user.id, course=course_key))
if is_using_certificate_allowlist_and_is_on_allowlist(user, course_key):
log.info('{course} is using allowlist certificates, and the user {user} is on its allowlist. Attempt will be '
'made to generate an allowlist certificate.'.format(course=course_key, user=user.id))
return generate_allowlist_certificate_task(user, course_key)
log.info('{course} is not using allowlist certificates (or user {user} is not on its allowlist). The normal '
'generation logic will be followed.'.format(course=course_key, user=user.id))
allowed_enrollment_modes_list = [
CourseMode.VERIFIED,
CourseMode.CREDIT_MODE,
......
......@@ -21,7 +21,7 @@ from lms.djangoapps.certificates.models import (
CertificateStatuses,
GeneratedCertificate
)
from lms.djangoapps.certificates.signals import fire_ungenerated_certificate_task
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
......@@ -152,15 +152,14 @@ class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase):
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_generate_allowlist_task:
CertificateWhitelistFactory(
user=self.user,
course_id=self.ip_course.id,
whitelist=True
)
fire_ungenerated_certificate_task(self.user, self.ip_course.id)
mock_generate_certificate_apply_async.assert_not_called()
mock_generate_allowlist_task.assert_called_with(self.user, self.ip_course.id)
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
CertificateWhitelistFactory(
user=self.user,
course_id=self.ip_course.id,
whitelist=True
)
mock_generate_certificate_apply_async.assert_not_called()
mock_generate_allowlist_task.assert_called_with(self.user, self.ip_course.id)
def test_fire_task_allowlist_disabled(self):
"""
......@@ -174,21 +173,20 @@ class WhitelistGeneratedCertificatesTest(ModuleStoreTestCase):
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_generate_allowlist_task:
CertificateWhitelistFactory(
user=self.user,
course_id=self.ip_course.id,
whitelist=True
)
fire_ungenerated_certificate_task(self.user, self.ip_course.id)
mock_generate_certificate_apply_async.assert_called_with(
countdown=CERTIFICATE_DELAY_SECONDS,
kwargs={
'student': six.text_type(self.user.id),
'course_key': six.text_type(self.ip_course.id),
}
)
mock_generate_allowlist_task.assert_not_called()
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
CertificateWhitelistFactory(
user=self.user,
course_id=self.ip_course.id,
whitelist=True
)
mock_generate_certificate_apply_async.assert_called_with(
countdown=CERTIFICATE_DELAY_SECONDS,
kwargs={
'student': six.text_type(self.user.id),
'course_key': six.text_type(self.ip_course.id),
}
)
mock_generate_allowlist_task.assert_not_called()
class PassingGradeCertsTest(ModuleStoreTestCase):
......@@ -280,6 +278,44 @@ class PassingGradeCertsTest(ModuleStoreTestCase):
grade_factory.update(self.user, self.course)
mock_generate_certificate_apply_async.assert_not_called()
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=True)
def test_passing_grade_allowlist(self):
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
# User who is not on the allowlist
GeneratedCertificateFactory(
user=self.user,
course_id=self.course.id,
status=CertificateStatuses.error
)
with mock_passing_grade():
with mock.patch(
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_allowlist_task:
CourseGradeFactory().update(self.user, self.course)
mock_allowlist_task.assert_not_called()
# User who is on the allowlist
u = UserFactory.create()
c = CourseFactory()
course_key = c.id # pylint: disable=no-member
CertificateWhitelistFactory(
user=u,
course_id=course_key
)
GeneratedCertificateFactory(
user=u,
course_id=course_key,
status=CertificateStatuses.error
)
with mock_passing_grade():
with mock.patch(
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_allowlist_task:
CourseGradeFactory().update(u, c)
mock_allowlist_task.assert_called_with(u, course_key)
@ddt.ddt
class FailingGradeCertsTest(ModuleStoreTestCase):
......@@ -437,6 +473,47 @@ class LearnerTrackChangeCertsTest(ModuleStoreTestCase):
}
)
@override_waffle_flag(CERTIFICATES_USE_ALLOWLIST, active=True)
def test_id_verification_allowlist(self):
# User is not on the allowlist
with mock.patch(
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_allowlist_task:
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
attempt = SoftwareSecurePhotoVerification.objects.create(
user=self.user_two,
status='submitted'
)
attempt.approve()
mock_allowlist_task.assert_not_called()
# User is on the allowlist
with mock.patch(
'lms.djangoapps.certificates.signals.generate_allowlist_certificate_task',
return_value=None
) as mock_allowlist_task:
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
u = UserFactory.create()
c = CourseFactory()
course_key = c.id # pylint: disable=no-member
CourseEnrollmentFactory(
user=u,
course_id=course_key,
is_active=True,
mode='verified'
)
CertificateWhitelistFactory(
user=u,
course_id=course_key
)
attempt = SoftwareSecurePhotoVerification.objects.create(
user=u,
status='submitted'
)
attempt.approve()
mock_allowlist_task.assert_called_with(u, course_key)
@ddt.ddt
class CertificateGenerationTaskTest(ModuleStoreTestCase):
......@@ -475,6 +552,6 @@ class CertificateGenerationTaskTest(ModuleStoreTestCase):
return_value=None
) as mock_generate_certificate_apply_async:
with override_waffle_switch(AUTO_CERTIFICATE_GENERATION_SWITCH, active=True):
fire_ungenerated_certificate_task(self.user, self.course.id)
_fire_ungenerated_certificate_task(self.user, self.course.id)
task_created = mock_generate_certificate_apply_async.called
self.assertEqual(task_created, should_create)
......@@ -100,7 +100,7 @@ class TestCourseGradeFactory(GradeTestBase):
with self.assertNumQueries(3), mock_get_score(1, 2):
_assert_read(expected_pass=False, expected_percent=0) # start off with grade of 0
num_queries = 42
num_queries = 44
with self.assertNumQueries(num_queries), mock_get_score(1, 2):
grade_factory.update(self.request.user, self.course, force_update_subsections=True)
......@@ -121,7 +121,7 @@ class TestCourseGradeFactory(GradeTestBase):
with self.assertNumQueries(3):
_assert_read(expected_pass=True, expected_percent=1.0) # updated to grade of 1.0
num_queries = 30
num_queries = 28
with self.assertNumQueries(num_queries), mock_get_score(0, 0): # the subsection now is worth zero
grade_factory.update(self.request.user, self.course, force_update_subsections=True)
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment