MICROBA-1024 | Move the allowlist and blocklist checks to certificates app
[MICROBA-1024] - Move some of the recently added logic from the instructor app to the certificates app - Attempt to not use other certificate models directly in the code I am touching, moving this logic to certificates as well.
This commit is contained in:
@@ -125,14 +125,16 @@ def get_certificates_for_user(username):
|
||||
return certs
|
||||
|
||||
|
||||
def get_certificate_for_user(username, course_key):
|
||||
def get_certificate_for_user(username, course_key, format_results=True):
|
||||
"""
|
||||
Retrieve certificate information for a particular user for a specific course.
|
||||
|
||||
Arguments:
|
||||
username (unicode): The identifier of the user.
|
||||
course_key (CourseKey): A Course Key.
|
||||
Returns: dict
|
||||
Returns:
|
||||
A dict containing information about the certificate or, optionally,
|
||||
the GeneratedCertificate object itself.
|
||||
"""
|
||||
try:
|
||||
cert = GeneratedCertificate.eligible_certificates.get(
|
||||
@@ -141,7 +143,11 @@ def get_certificate_for_user(username, course_key):
|
||||
)
|
||||
except GeneratedCertificate.DoesNotExist:
|
||||
return None
|
||||
return format_certificate_for_user(username, cert)
|
||||
|
||||
if format_results:
|
||||
return format_certificate_for_user(username, cert)
|
||||
else:
|
||||
return cert
|
||||
|
||||
|
||||
def get_certificates_for_user_by_course_keys(user, course_keys):
|
||||
@@ -283,24 +289,21 @@ def set_cert_generation_enabled(course_key, is_enabled):
|
||||
log.info("Disabled self-generated certificates for course '%s'.", str(course_key))
|
||||
|
||||
|
||||
def is_certificate_invalid(student, course_key):
|
||||
"""Check that whether the student in the course has been invalidated
|
||||
for receiving certificates.
|
||||
def is_certificate_invalidated(student, course_key):
|
||||
"""Check whether the certificate belonging to the given student (in given course) has been invalidated.
|
||||
|
||||
Arguments:
|
||||
student (user object): logged-in user
|
||||
course_key (CourseKey): The course identifier.
|
||||
|
||||
Returns:
|
||||
Boolean denoting whether the student in the course is invalidated
|
||||
to receive certificates
|
||||
Boolean denoting whether the certificate has been invalidated for this learner.
|
||||
"""
|
||||
is_invalid = False
|
||||
certificate = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
if certificate is not None:
|
||||
is_invalid = CertificateInvalidation.has_certificate_invalidation(student, course_key)
|
||||
if certificate:
|
||||
return CertificateInvalidation.has_certificate_invalidation(student, course_key)
|
||||
|
||||
return is_invalid
|
||||
return False
|
||||
|
||||
|
||||
def cert_generation_enabled(course_key):
|
||||
@@ -569,3 +572,56 @@ def get_allowlisted_users(course_key):
|
||||
return User.objects.none()
|
||||
|
||||
return User.objects.filter(certificatewhitelist__course_id=course_key, certificatewhitelist__whitelist=True)
|
||||
|
||||
|
||||
def create_certificate_allowlist_entry(user, course_key, notes):
|
||||
"""
|
||||
Creates a certificate exception for a given learner in a given course-run.
|
||||
"""
|
||||
certificate_allowlist, __ = CertificateWhitelist.objects.get_or_create(
|
||||
user=user,
|
||||
course_id=course_key,
|
||||
defaults={
|
||||
'whitelist': True,
|
||||
'notes': notes,
|
||||
}
|
||||
)
|
||||
|
||||
return certificate_allowlist
|
||||
|
||||
|
||||
def create_certificate_invalidation_entry(certificate, user_requesting_invalidation, notes):
|
||||
"""
|
||||
Invalidates a certificate with the given certificate id.
|
||||
"""
|
||||
certificate_invalidation, __ = CertificateInvalidation.objects.update_or_create(
|
||||
generated_certificate=certificate,
|
||||
defaults={
|
||||
'active': True,
|
||||
'invalidated_by': user_requesting_invalidation,
|
||||
'notes': notes,
|
||||
}
|
||||
)
|
||||
|
||||
return certificate_invalidation
|
||||
|
||||
|
||||
def get_allowlist_entry(user, course_key):
|
||||
"""
|
||||
Retrieves and returns an allowlist entry for a given learner and course-run.
|
||||
"""
|
||||
return CertificateWhitelist.objects.get(user=user, course_id=course_key)
|
||||
|
||||
|
||||
def get_certificate_invalidation_entry(certificate):
|
||||
"""
|
||||
Retrieves and returns an certificate invalidation entry for a given certificate id.
|
||||
"""
|
||||
return CertificateInvalidation.objects.get(generated_certificate=certificate)
|
||||
|
||||
|
||||
def is_on_allowlist(user, course_key):
|
||||
"""
|
||||
Determines if a learner appears on the allowlist for a given course-run.
|
||||
"""
|
||||
return CertificateWhitelist.objects.filter(user=user, course_id=course_key, whitelist=True).exists()
|
||||
|
||||
@@ -33,17 +33,22 @@ from common.djangoapps.util.testing import EventTestMixin
|
||||
from lms.djangoapps.certificates.api import (
|
||||
cert_generation_enabled,
|
||||
certificate_downloadable_status,
|
||||
create_certificate_allowlist_entry,
|
||||
create_certificate_invalidation_entry,
|
||||
example_certificates_status,
|
||||
generate_example_certificates,
|
||||
generate_user_certificates,
|
||||
get_allowlist_entry,
|
||||
get_allowlisted_users,
|
||||
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_invalidation_entry,
|
||||
get_certificate_url,
|
||||
is_certificate_invalid,
|
||||
is_certificate_invalidated,
|
||||
is_on_allowlist,
|
||||
set_cert_generation_enabled
|
||||
)
|
||||
from lms.djangoapps.certificates.generation_handler import CERTIFICATES_USE_ALLOWLIST
|
||||
@@ -260,7 +265,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
|
||||
)
|
||||
# Also check query count for 'is_certificate_invalid' method.
|
||||
with self.assertNumQueries(1):
|
||||
assert not is_certificate_invalid(self.student, course.id)
|
||||
assert not is_certificate_invalidated(self.student, course.id)
|
||||
|
||||
@ddt.data(
|
||||
CertificateStatuses.generating,
|
||||
@@ -276,7 +281,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
|
||||
True. """
|
||||
generated_cert = self._generate_cert(status)
|
||||
self._invalidate_certificate(generated_cert, True)
|
||||
assert is_certificate_invalid(self.student, self.course.id)
|
||||
assert is_certificate_invalidated(self.student, self.course.id)
|
||||
|
||||
@ddt.data(
|
||||
CertificateStatuses.generating,
|
||||
@@ -292,7 +297,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
|
||||
false than method will return false. """
|
||||
generated_cert = self._generate_cert(status)
|
||||
self._invalidate_certificate(generated_cert, False)
|
||||
assert not is_certificate_invalid(self.student, self.course.id)
|
||||
assert not is_certificate_invalidated(self.student, self.course.id)
|
||||
|
||||
@ddt.data(
|
||||
CertificateStatuses.generating,
|
||||
@@ -315,7 +320,7 @@ class CertificateIsInvalid(WebCertificateTestMixin, ModuleStoreTestCase):
|
||||
)
|
||||
# Also check query count for 'is_certificate_invalid' method.
|
||||
with self.assertNumQueries(2):
|
||||
assert is_certificate_invalid(self.student, self.course.id)
|
||||
assert is_certificate_invalidated(self.student, self.course.id)
|
||||
|
||||
def _invalidate_certificate(self, certificate, active):
|
||||
""" Dry method to mark certificate as invalid. """
|
||||
@@ -876,3 +881,113 @@ class AllowlistTests(ModuleStoreTestCase):
|
||||
|
||||
users = get_allowlisted_users(self.third_course_run_key)
|
||||
assert 0 == users.count()
|
||||
|
||||
|
||||
class InstructorDashboardFunctionalityTests(ModuleStoreTestCase):
|
||||
"""
|
||||
Tests for some functionality that the Instructor Dashboard django app relies on.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.global_staff = GlobalStaffFactory()
|
||||
self.user = UserFactory()
|
||||
self.course_run = CourseFactory()
|
||||
self.course_run_key = self.course_run.id # pylint: disable=no-member
|
||||
|
||||
CourseEnrollmentFactory(
|
||||
user=self.user,
|
||||
course_id=self.course_run_key,
|
||||
is_active=True,
|
||||
mode="verified",
|
||||
)
|
||||
|
||||
def test_create_certificate_invalidation_entry(self):
|
||||
"""
|
||||
Test to verify that we can use the functionality defined in the Certificates api.py to create certificate
|
||||
invalidation entries. This is functionality the Instructor Dashboard django app relies on.
|
||||
"""
|
||||
certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course_run_key,
|
||||
status=CertificateStatuses.unavailable,
|
||||
mode='verified'
|
||||
)
|
||||
|
||||
result = create_certificate_invalidation_entry(certificate, self.global_staff, "Test!")
|
||||
|
||||
assert result.generated_certificate == certificate
|
||||
assert result.active is True
|
||||
assert result.notes == "Test!"
|
||||
|
||||
def test_create_certificate_allowlist_entry(self):
|
||||
"""
|
||||
Test to verify that we can use the functionality defined in the Certificates api.py to create allowlist
|
||||
entries. This is functionality the Instructor Dashboard django app relies on.
|
||||
"""
|
||||
result = create_certificate_allowlist_entry(self.user, self.course_run_key, "Testing!")
|
||||
|
||||
assert result.course_id == self.course_run_key
|
||||
assert result.user == self.user
|
||||
assert result.notes == "Testing!"
|
||||
|
||||
def test_get_allowlist_entry(self):
|
||||
"""
|
||||
Test to verify that we can retrieve an allowlist entry for a learner.
|
||||
"""
|
||||
allowlist_entry = CertificateWhitelistFactory.create(course_id=self.course_run_key, user=self.user)
|
||||
|
||||
retrieved_entry = get_allowlist_entry(self.user, self.course_run_key)
|
||||
|
||||
assert retrieved_entry.id == allowlist_entry.id
|
||||
assert retrieved_entry.course_id == allowlist_entry.course_id
|
||||
assert retrieved_entry.user == allowlist_entry.user
|
||||
|
||||
def test_get_certificate_invalidation_entry(self):
|
||||
"""
|
||||
Test to verify that we can retrieve a certificate invalidation entry for a learner.
|
||||
"""
|
||||
certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course_run_key,
|
||||
status=CertificateStatuses.unavailable,
|
||||
mode='verified'
|
||||
)
|
||||
|
||||
invalidation = CertificateInvalidationFactory.create(
|
||||
generated_certificate=certificate,
|
||||
invalidated_by=self.global_staff,
|
||||
active=True
|
||||
)
|
||||
|
||||
retrieved_invalidation = get_certificate_invalidation_entry(certificate)
|
||||
|
||||
assert retrieved_invalidation.id == invalidation.id
|
||||
assert retrieved_invalidation.generated_certificate == certificate
|
||||
assert retrieved_invalidation.active == invalidation.active
|
||||
|
||||
def test_is_on_allowlist(self):
|
||||
"""
|
||||
Test to verify that we return True when an allowlist entry exists.
|
||||
"""
|
||||
CertificateWhitelistFactory.create(course_id=self.course_run_key, user=self.user)
|
||||
|
||||
result = is_on_allowlist(self.user, self.course_run_key)
|
||||
assert result
|
||||
|
||||
def test_is_on_allowlist_expect_false(self):
|
||||
"""
|
||||
Test to verify that we will not return False when no allowlist entry exists.
|
||||
"""
|
||||
result = is_on_allowlist(self.user, self.course_run_key)
|
||||
assert not result
|
||||
|
||||
def test_is_on_allowlist_entry_disabled(self):
|
||||
"""
|
||||
Test to verify that we will return False when the allowlist entry if it is disabled.
|
||||
"""
|
||||
CertificateWhitelistFactory.create(course_id=self.course_run_key, user=self.user, whitelist=False)
|
||||
|
||||
result = is_on_allowlist(self.user, self.course_run_key)
|
||||
assert not result
|
||||
|
||||
@@ -1204,7 +1204,7 @@ def _missing_required_verification(student, enrollment_mode):
|
||||
|
||||
|
||||
def _certificate_message(student, course, enrollment_mode): # lint-amnesty, pylint: disable=missing-function-docstring
|
||||
if certs_api.is_certificate_invalid(student, course.id):
|
||||
if certs_api.is_certificate_invalidated(student, course.id):
|
||||
return INVALID_CERT_DATA
|
||||
|
||||
cert_downloadable_status = certs_api.certificate_downloadable_status(student, course.id)
|
||||
|
||||
@@ -57,8 +57,6 @@ from lms.djangoapps.bulk_email.models import BulkEmailFlag, CourseEmail, CourseE
|
||||
from lms.djangoapps.certificates.api import generate_user_certificates
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses
|
||||
from lms.djangoapps.certificates.tests.factories import (
|
||||
CertificateInvalidationFactory,
|
||||
CertificateWhitelistFactory,
|
||||
GeneratedCertificateFactory
|
||||
)
|
||||
from lms.djangoapps.courseware.models import StudentModule
|
||||
@@ -72,8 +70,6 @@ from lms.djangoapps.courseware.tests.helpers import LoginEnrollmentTestCase
|
||||
from lms.djangoapps.experiments.testutils import override_experiment_waffle_flag
|
||||
from lms.djangoapps.instructor.tests.utils import FakeContentTask, FakeEmail, FakeEmailInfo
|
||||
from lms.djangoapps.instructor.views.api import (
|
||||
_check_if_learner_on_allowlist,
|
||||
_check_if_learner_on_blocklist,
|
||||
_get_certificate_for_user,
|
||||
_get_student_from_request_data,
|
||||
_split_input_list,
|
||||
@@ -4440,10 +4436,7 @@ class TestInstructorCertificateExceptions(SharedModuleStoreTestCase):
|
||||
)
|
||||
|
||||
retrieved_certificate = _get_certificate_for_user(self.course.id, self.user)
|
||||
|
||||
assert retrieved_certificate.id == generated_certificate.id
|
||||
assert retrieved_certificate.user == self.user
|
||||
assert retrieved_certificate.course_id == self.course.id
|
||||
|
||||
def test_get_certificate_for_user_no_certificate(self):
|
||||
"""
|
||||
@@ -4457,89 +4450,3 @@ class TestInstructorCertificateExceptions(SharedModuleStoreTestCase):
|
||||
f"The student {self.user} does not have certificate for the course {self.course.id.course}. Kindly "
|
||||
"verify student username/email and the selected course are correct and try again."
|
||||
)
|
||||
|
||||
def test_check_if_learner_on_allowlist(self):
|
||||
"""
|
||||
Test to verify that no learner is returned if the learner does not have an active entry on the allowlist.
|
||||
"""
|
||||
result = _check_if_learner_on_allowlist(self.course.id, self.user)
|
||||
|
||||
assert not result
|
||||
|
||||
def test_check_if_learner_on_allowlist_allowlist_entry_exists(self):
|
||||
"""
|
||||
Test that verifies the correct result is returned if a learner has an active entry on the allowlist.
|
||||
"""
|
||||
CertificateWhitelistFactory.create(
|
||||
course_id=self.course.id,
|
||||
user=self.user
|
||||
)
|
||||
|
||||
result = _check_if_learner_on_allowlist(self.course.id, self.user)
|
||||
|
||||
assert result
|
||||
|
||||
def test_check_if_learner_on_blocklist_no_cert(self):
|
||||
"""
|
||||
Test to verify that the correct result is returned if a learner does not currently have a certificate in a
|
||||
course-run.
|
||||
"""
|
||||
result = _check_if_learner_on_blocklist(self.course.id, self.user)
|
||||
|
||||
assert not result
|
||||
|
||||
def test_check_if_learner_on_blocklist_with_cert(self):
|
||||
"""
|
||||
Test to verify that the correct result is returned if a learner has a certificate but does not have an active
|
||||
entry on the blocklist.
|
||||
"""
|
||||
GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode='verified',
|
||||
status=CertificateStatuses.downloadable,
|
||||
)
|
||||
|
||||
result = _check_if_learner_on_blocklist(self.course.id, self.user)
|
||||
assert not result
|
||||
|
||||
def test_check_if_learner_on_blocklist_blocklist_entry_exists(self):
|
||||
"""
|
||||
Test to verify that the correct result is returned if a learner has a Certificate and an active entry on
|
||||
the blocklist.
|
||||
"""
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode='verified',
|
||||
status=CertificateStatuses.downloadable,
|
||||
)
|
||||
|
||||
CertificateInvalidationFactory.create(
|
||||
generated_certificate=generated_certificate,
|
||||
invalidated_by=self.global_staff,
|
||||
active=True
|
||||
)
|
||||
|
||||
result = _check_if_learner_on_blocklist(self.course.id, self.user)
|
||||
assert result
|
||||
|
||||
def test_check_if_learner_on_blocklist_inactive_blocklist_entry_exists(self):
|
||||
"""
|
||||
Test to verify that the correct result is returned if a learner has an inactive entry on the blocklist.
|
||||
"""
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode='verified',
|
||||
status=CertificateStatuses.downloadable,
|
||||
)
|
||||
|
||||
CertificateInvalidationFactory.create(
|
||||
generated_certificate=generated_certificate,
|
||||
invalidated_by=self.global_staff,
|
||||
active=False
|
||||
)
|
||||
|
||||
result = _check_if_learner_on_blocklist(self.course.id, self.user)
|
||||
assert not result
|
||||
|
||||
@@ -73,10 +73,8 @@ from lms.djangoapps.bulk_email.api import is_bulk_email_feature_enabled
|
||||
from lms.djangoapps.bulk_email.models import CourseEmail
|
||||
from lms.djangoapps.certificates import api as certs_api
|
||||
from lms.djangoapps.certificates.models import (
|
||||
CertificateInvalidation,
|
||||
CertificateStatuses,
|
||||
CertificateWhitelist,
|
||||
GeneratedCertificate
|
||||
CertificateWhitelist
|
||||
)
|
||||
from lms.djangoapps.courseware.access import has_access
|
||||
from lms.djangoapps.courseware.courses import get_course_by_id, get_course_with_access
|
||||
@@ -2655,41 +2653,35 @@ def add_certificate_exception(course_key, student, certificate_exception):
|
||||
:param certificate_exception: A dict object containing certificate exception info.
|
||||
:return: CertificateWhitelist item in dict format containing certificate exception info.
|
||||
"""
|
||||
if _check_if_learner_on_blocklist(course_key, student):
|
||||
# Check if the learner is blocked from receiving certificates in this course run.
|
||||
if certs_api.is_certificate_invalidated(student, course_key):
|
||||
raise ValueError(
|
||||
_("Student {user} is already on the certificate invalidation list and cannot be added to the certificate "
|
||||
"exception list.").format(user=student.username)
|
||||
)
|
||||
|
||||
if CertificateWhitelist.get_certificate_white_list(course_key, student):
|
||||
if certs_api.is_on_allowlist(student, course_key):
|
||||
raise ValueError(
|
||||
_("Student (username/email={user}) already in certificate exception list.").format(user=student.username)
|
||||
)
|
||||
|
||||
certificate_white_list, __ = CertificateWhitelist.objects.get_or_create(
|
||||
user=student,
|
||||
course_id=course_key,
|
||||
defaults={
|
||||
'whitelist': True,
|
||||
'notes': certificate_exception.get('notes', '')
|
||||
}
|
||||
certificate_allowlist_entry = certs_api.create_certificate_allowlist_entry(
|
||||
student,
|
||||
course_key,
|
||||
certificate_exception.get("notes", "")
|
||||
)
|
||||
|
||||
log.info(f"Student {student.id} has been added to the whitelist in course {course_key}")
|
||||
|
||||
generated_certificate = GeneratedCertificate.eligible_certificates.filter(
|
||||
user=student,
|
||||
course_id=course_key,
|
||||
status=CertificateStatuses.downloadable,
|
||||
).first()
|
||||
generated_certificate = certs_api.get_certificate_for_user(student.username, course_key, False)
|
||||
|
||||
exception = dict({
|
||||
'id': certificate_white_list.id,
|
||||
'id': certificate_allowlist_entry.id,
|
||||
'user_email': student.email,
|
||||
'user_name': student.username,
|
||||
'user_id': student.id,
|
||||
'certificate_generated': generated_certificate and generated_certificate.created_date.strftime("%B %d, %Y"),
|
||||
'created': certificate_white_list.created.strftime("%A, %B %d, %Y"),
|
||||
'created': certificate_allowlist_entry.created.strftime("%A, %B %d, %Y"),
|
||||
})
|
||||
|
||||
return exception
|
||||
@@ -2706,7 +2698,7 @@ def remove_certificate_exception(course_key, student):
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
certificate_exception = CertificateWhitelist.objects.get(user=student, course_id=course_key)
|
||||
certificate_exception = certs_api.get_allowlist_entry(student, course_key)
|
||||
except ObjectDoesNotExist:
|
||||
raise ValueError( # lint-amnesty, pylint: disable=raise-missing-from
|
||||
_('Certificate exception (user={user}) does not exist in certificate white list. '
|
||||
@@ -2714,20 +2706,17 @@ def remove_certificate_exception(course_key, student):
|
||||
)
|
||||
|
||||
try:
|
||||
generated_certificate = GeneratedCertificate.objects.get(
|
||||
user=student,
|
||||
course_id=course_key
|
||||
)
|
||||
generated_certificate.invalidate()
|
||||
certificate = certs_api.get_certificate_for_user(student.username, course_key, False)
|
||||
log.info(
|
||||
'Certificate invalidated for %s in course %s when removed from certificate exception list',
|
||||
student.username,
|
||||
course_key
|
||||
f"Invalidating certificate for student {student.id} in course {course_key} before removing them from the "
|
||||
"allowlist."
|
||||
)
|
||||
certificate.invalidate()
|
||||
except ObjectDoesNotExist:
|
||||
# Certificate has not been generated yet, so just remove the certificate exception from white list
|
||||
pass
|
||||
log.info('%s has been removed from the whitelist in course %s', student.username, course_key)
|
||||
|
||||
log.info(f"Removing student {student.id} from the allowlist in course {course_key}.")
|
||||
certificate_exception.delete()
|
||||
|
||||
|
||||
@@ -2903,10 +2892,9 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
log.info(f'Student {user} does not exist')
|
||||
else:
|
||||
# make sure learner isn't on the blocklist
|
||||
if _check_if_learner_on_blocklist(course_key, user):
|
||||
if certs_api.is_certificate_invalidated(user, course_key):
|
||||
build_row_errors('user_on_certificate_invalidation_list', user, row_num)
|
||||
log.warning(f'Student {user.id} is blocked from receiving a Certificate in Course '
|
||||
f'{course_key}')
|
||||
log.warning(f'Student {user.id} is blocked from receiving a Certificate in Course {course_key}')
|
||||
# make sure user isn't already on the exception list
|
||||
elif CertificateWhitelist.get_certificate_white_list(course_key, user):
|
||||
build_row_errors('user_already_white_listed', user, row_num)
|
||||
@@ -2959,7 +2947,7 @@ def certificate_invalidation_view(request, course_id):
|
||||
# Invalidate certificate of the given student for the course course
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
if _check_if_learner_on_allowlist(course_key, student):
|
||||
if certs_api.is_on_allowlist(student, course_key):
|
||||
log.warning(f"Invalidating certificate for student {student.id} in course {course_key} failed. "
|
||||
"Student is currently on the allow list.")
|
||||
raise ValueError(
|
||||
@@ -2967,8 +2955,12 @@ def certificate_invalidation_view(request, course_id):
|
||||
"remove them from the Certificate Exception list before attempting to invalidate their "
|
||||
"certificate.").format(student=student, course=course_key)
|
||||
)
|
||||
|
||||
certificate_invalidation = invalidate_certificate(request, certificate, certificate_invalidation_data)
|
||||
certificate_invalidation = invalidate_certificate(
|
||||
request,
|
||||
certificate,
|
||||
certificate_invalidation_data,
|
||||
student
|
||||
)
|
||||
except ValueError as error:
|
||||
return JsonResponse({'message': str(error)}, status=400)
|
||||
return JsonResponse(certificate_invalidation)
|
||||
@@ -2976,54 +2968,51 @@ def certificate_invalidation_view(request, course_id):
|
||||
# Re-Validate student certificate for the course course
|
||||
elif request.method == 'DELETE':
|
||||
try:
|
||||
re_validate_certificate(request, course_key, certificate)
|
||||
re_validate_certificate(request, course_key, certificate, student)
|
||||
except ValueError as error:
|
||||
return JsonResponse({'message': str(error)}, status=400)
|
||||
|
||||
return JsonResponse({}, status=204)
|
||||
|
||||
|
||||
def invalidate_certificate(request, generated_certificate, certificate_invalidation_data):
|
||||
def invalidate_certificate(request, generated_certificate, certificate_invalidation_data, student):
|
||||
"""
|
||||
Invalidate given GeneratedCertificate and add CertificateInvalidation record for future reference or re-validation.
|
||||
|
||||
:param request: HttpRequest object
|
||||
:param generated_certificate: GeneratedCertificate object, the certificate we want to invalidate
|
||||
:param certificate_invalidation_data: dict object containing data for CertificateInvalidation.
|
||||
:param student: User object, this user is tied to the generated_certificate we are going to invalidate
|
||||
:return: dict object containing updated certificate invalidation data.
|
||||
"""
|
||||
if CertificateInvalidation.get_certificate_invalidations(
|
||||
generated_certificate.course_id,
|
||||
generated_certificate.user,
|
||||
):
|
||||
# Check if the learner already appears on the certificate invalidation list
|
||||
if certs_api.is_certificate_invalidated(student, generated_certificate.course_id):
|
||||
raise ValueError(
|
||||
_("Certificate of {user} has already been invalidated. Please check your spelling and retry.").format(
|
||||
user=generated_certificate.user.username,
|
||||
user=student.username,
|
||||
)
|
||||
)
|
||||
|
||||
# Verify that certificate user wants to invalidate is a valid one.
|
||||
# Verify that the learner's certificate is valid before invalidating
|
||||
if not generated_certificate.is_valid():
|
||||
raise ValueError(
|
||||
_("Certificate for student {user} is already invalid, kindly verify that certificate was generated "
|
||||
"for this student and then proceed.").format(user=generated_certificate.user.username)
|
||||
"for this student and then proceed.").format(user=student.username)
|
||||
)
|
||||
|
||||
# Add CertificateInvalidation record for future reference or re-validation
|
||||
certificate_invalidation, __ = CertificateInvalidation.objects.update_or_create(
|
||||
generated_certificate=generated_certificate,
|
||||
defaults={
|
||||
'invalidated_by': request.user,
|
||||
'notes': certificate_invalidation_data.get("notes", ""),
|
||||
'active': True,
|
||||
}
|
||||
certificate_invalidation = certs_api.create_certificate_invalidation_entry(
|
||||
generated_certificate,
|
||||
request.user,
|
||||
certificate_invalidation_data.get("notes", ""),
|
||||
)
|
||||
|
||||
# Invalidate GeneratedCertificate
|
||||
# Invalidate the certificate
|
||||
generated_certificate.invalidate()
|
||||
|
||||
return {
|
||||
'id': certificate_invalidation.id,
|
||||
'user': certificate_invalidation.generated_certificate.user.username,
|
||||
'user': student.username,
|
||||
'invalidated_by': certificate_invalidation.invalidated_by.username,
|
||||
'created': certificate_invalidation.created.strftime("%B %d, %Y"),
|
||||
'notes': certificate_invalidation.notes,
|
||||
@@ -3031,7 +3020,7 @@ def invalidate_certificate(request, generated_certificate, certificate_invalidat
|
||||
|
||||
|
||||
@common_exceptions_400
|
||||
def re_validate_certificate(request, course_key, generated_certificate):
|
||||
def re_validate_certificate(request, course_key, generated_certificate, student):
|
||||
"""
|
||||
Remove certificate invalidation from db and start certificate generation task for this student.
|
||||
Raises ValueError if certificate invalidation is present.
|
||||
@@ -3041,16 +3030,10 @@ def re_validate_certificate(request, course_key, generated_certificate):
|
||||
:param generated_certificate: GeneratedCertificate object of the student for the given course
|
||||
"""
|
||||
try:
|
||||
# Fetch CertificateInvalidation object
|
||||
certificate_invalidation = CertificateInvalidation.objects.get(generated_certificate=generated_certificate)
|
||||
certificate_invalidation = certs_api.get_certificate_invalidation_entry(generated_certificate)
|
||||
certificate_invalidation.deactivate()
|
||||
except ObjectDoesNotExist:
|
||||
raise ValueError(_("Certificate Invalidation does not exist, Please refresh the page and try again.")) # lint-amnesty, pylint: disable=raise-missing-from
|
||||
else:
|
||||
# Deactivate certificate invalidation if it was fetched successfully.
|
||||
certificate_invalidation.deactivate()
|
||||
|
||||
# We need to generate certificate only for a single student here
|
||||
student = certificate_invalidation.generated_certificate.user
|
||||
|
||||
task_api.generate_certificates_for_students(
|
||||
request, course_key, student_set="specific_student", specific_student_id=student.id
|
||||
@@ -3093,10 +3076,13 @@ def _get_student_from_request_data(request_data, course_key):
|
||||
|
||||
def _get_certificate_for_user(course_key, student):
|
||||
"""
|
||||
Attempts to retrieve a Certificate for a learner in a given course run key.
|
||||
Attempt to retrieve certificate information for a learner in a given course-run.
|
||||
|
||||
Raises a ValueError if a certificate cannot be retrieved for the learner. This will prompt an informative message
|
||||
to be displayed on the instructor dashboard.
|
||||
"""
|
||||
log.info(f"Retrieving certificate for student {student.id} in course {course_key}")
|
||||
certificate = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
certificate = certs_api.get_certificate_for_user(student.username, course_key, False)
|
||||
if not certificate:
|
||||
raise ValueError(_(
|
||||
"The student {student} does not have certificate for the course {course}. Kindly verify student "
|
||||
@@ -3105,29 +3091,3 @@ def _get_certificate_for_user(course_key, student):
|
||||
)
|
||||
|
||||
return certificate
|
||||
|
||||
|
||||
def _check_if_learner_on_allowlist(course_key, student):
|
||||
"""
|
||||
Utility method that will try to determine if the learner is currently on the allowlist. This is a check that
|
||||
occurs as part of adding a learner to the CertificateInvalidation list.
|
||||
"""
|
||||
log.info(f"Checking if student {student.id} is currently on the allowlist of course {course_key}")
|
||||
return CertificateWhitelist.objects.filter(user=student, course_id=course_key, whitelist=True).exists()
|
||||
|
||||
|
||||
def _check_if_learner_on_blocklist(course_key, student):
|
||||
"""
|
||||
Utility method that will try to determine if the learner is currently on the block list. This is a check that
|
||||
occurs as part of adding a learner to the Allow list.
|
||||
|
||||
The CertificateInvalidation model does not store a username or user id, just a reference to the id of the
|
||||
invalidated Certificate. We check if the learner has a Certificate in the Course-Run and then use that to check if
|
||||
the learner has an active entry on the block list.
|
||||
"""
|
||||
log.info(f"Checking if student {student.id} is currently on the blocklist of course {course_key}")
|
||||
cert = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
if cert:
|
||||
return CertificateInvalidation.objects.filter(generated_certificate_id=cert.id, active=True).exists()
|
||||
|
||||
return False
|
||||
|
||||
Reference in New Issue
Block a user