Merge pull request #26652 from edx/jhynes/microba-908_cleanup_allowlist
MB-908 | Prevent ability for learner to appear on both certificate invalidation and certificate exceptions lists
This commit is contained in:
@@ -56,7 +56,11 @@ from common.djangoapps.student.tests.factories import CourseEnrollmentFactory, U
|
||||
from lms.djangoapps.bulk_email.models import BulkEmailFlag, CourseEmail, CourseEmailTemplate
|
||||
from lms.djangoapps.certificates.api import generate_user_certificates
|
||||
from lms.djangoapps.certificates.models import CertificateStatuses
|
||||
from lms.djangoapps.certificates.tests.factories import GeneratedCertificateFactory
|
||||
from lms.djangoapps.certificates.tests.factories import (
|
||||
CertificateInvalidationFactory,
|
||||
CertificateWhitelistFactory,
|
||||
GeneratedCertificateFactory
|
||||
)
|
||||
from lms.djangoapps.courseware.models import StudentModule
|
||||
from lms.djangoapps.courseware.tests.factories import (
|
||||
BetaTesterFactory,
|
||||
@@ -68,6 +72,10 @@ 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,
|
||||
common_exceptions_400,
|
||||
generate_unique_password,
|
||||
@@ -4362,3 +4370,176 @@ class TestBulkCohorting(SharedModuleStoreTestCase):
|
||||
self.verify_success_on_file_content(
|
||||
'username,email,cohort\r\nfoo_username,bar_email,baz_cohort', mock_store_upload, mock_cohort_task
|
||||
)
|
||||
|
||||
|
||||
class TestInstructorCertificateExceptions(SharedModuleStoreTestCase):
|
||||
"""
|
||||
Tests for utility functions utilized in the Instructor Dashboard Certificates app.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.global_staff = GlobalStaffFactory()
|
||||
self.course = CourseFactory.create()
|
||||
self.user = UserFactory()
|
||||
CourseEnrollment.enroll(self.user, self.course.id)
|
||||
|
||||
def test_get_student_from_request_data(self):
|
||||
"""
|
||||
Test ability to retrieve a learner record using their username and course id
|
||||
"""
|
||||
student = _get_student_from_request_data({"user": self.user.username}, self.course.id)
|
||||
|
||||
assert student.username == self.user.username
|
||||
|
||||
def test_get_student_from_request_data_empty_username(self):
|
||||
"""
|
||||
Test that we receive an expected error when no learner's username or email is entered
|
||||
"""
|
||||
with pytest.raises(ValueError) as error:
|
||||
_get_student_from_request_data({"user": ""}, self.course.id)
|
||||
|
||||
assert str(error.value) == (
|
||||
'Student username/email field is required and can not be empty. Kindly fill in username/email and then '
|
||||
'press "Invalidate Certificate" button.'
|
||||
)
|
||||
|
||||
def test_get_student_from_request_data_user_dne(self):
|
||||
"""
|
||||
Test to verify an expected error message is returned when attempting to retrieve a learner that does not exist
|
||||
in the LMS.
|
||||
"""
|
||||
with pytest.raises(ValueError) as error:
|
||||
_get_student_from_request_data({"user": "Neo"}, self.course.id)
|
||||
|
||||
assert str(error.value) == "Neo does not exist in the LMS. Please check your spelling and retry."
|
||||
|
||||
def test_get_student_from_request_data_user_not_enrolled(self):
|
||||
"""
|
||||
Test to verify an expected error message is returned when attempting to retrieve a learner that is not enrolled
|
||||
in a course-run.
|
||||
"""
|
||||
new_course = CourseFactory.create()
|
||||
|
||||
with pytest.raises(ValueError) as error:
|
||||
_get_student_from_request_data({"user": self.user.username}, new_course.id)
|
||||
|
||||
assert str(error.value) == (
|
||||
f"{self.user.username} is not enrolled in this course. Please check your spelling and retry."
|
||||
)
|
||||
|
||||
def test_get_certificate_for_user(self):
|
||||
"""
|
||||
Test that attempts to retrieve a Certificate for a learner in a course-run.
|
||||
"""
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
mode='verified',
|
||||
status=CertificateStatuses.downloadable,
|
||||
)
|
||||
|
||||
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):
|
||||
"""
|
||||
Test to verify an expected error message is returned when attempting to retrieve a certificate for a learner
|
||||
that does not exist yet.
|
||||
"""
|
||||
with pytest.raises(ValueError) as error:
|
||||
_get_certificate_for_user(self.course.id, self.user)
|
||||
|
||||
assert str(error.value) == (
|
||||
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
|
||||
|
||||
@@ -743,6 +743,40 @@ class CertificateExceptionViewInstructorApiTest(SharedModuleStoreTestCase):
|
||||
'Certificate exception (user={user}) does not exist in certificate white list.' \
|
||||
' Please refresh the page and try again.'.format(user=self.certificate_exception['user_name'])
|
||||
|
||||
def test_certificate_invalidation_already_exists(self):
|
||||
"""
|
||||
Test to confirm an error message is raised when generating a certificate exception for a learner that already
|
||||
has an active certificate invalidation.
|
||||
"""
|
||||
# generate a certificate for the test learner in our course
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.user,
|
||||
course_id=self.course.id,
|
||||
status=CertificateStatuses.downloadable,
|
||||
mode='honor',
|
||||
)
|
||||
|
||||
# create a certificate invalidation tied to the generated certificate
|
||||
CertificateInvalidationFactory.create(
|
||||
generated_certificate=generated_certificate,
|
||||
invalidated_by=self.global_staff,
|
||||
)
|
||||
|
||||
# attempt to add learner to the allowlist, expect an error
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
data=json.dumps(self.certificate_exception),
|
||||
content_type='application/json',
|
||||
REQUEST_METHOD='POST'
|
||||
)
|
||||
|
||||
res_json = json.loads(response.content.decode('utf-8'))
|
||||
assert response.status_code == 400
|
||||
assert res_json['message'] == (
|
||||
f"Student {self.user.username} is already on the certificate invalidation list and cannot be added to "
|
||||
"the certificate exception list."
|
||||
)
|
||||
|
||||
|
||||
@override_settings(CERT_QUEUE='certificates')
|
||||
@ddt.ddt
|
||||
@@ -997,6 +1031,30 @@ class TestCertificatesInstructorApiBulkWhiteListExceptions(SharedModuleStoreTest
|
||||
assert len(data['general_errors']) == 1
|
||||
assert len(data['success']) == 0
|
||||
|
||||
def test_certificate_invalidation_already_exists(self):
|
||||
"""
|
||||
Test to confirm an error message is raised when generating a certificate exception for a learner appears in the
|
||||
CSV file who has an active certificate invalidation.
|
||||
"""
|
||||
# generate a certificate for the test learner in our course
|
||||
generated_certificate = GeneratedCertificateFactory.create(
|
||||
user=self.enrolled_user_1,
|
||||
course_id=self.course.id,
|
||||
status=CertificateStatuses.downloadable,
|
||||
mode='honor',
|
||||
)
|
||||
|
||||
CertificateInvalidationFactory.create(
|
||||
generated_certificate=generated_certificate,
|
||||
invalidated_by=self.global_staff,
|
||||
)
|
||||
|
||||
# attempt to add learner to the allowlist, expect an error
|
||||
csv_content = b"test_student1@example.com,notes"
|
||||
data = self.upload_file(csv_content=csv_content)
|
||||
assert len(data['row_errors']['user_on_certificate_invalidation_list']) == 1
|
||||
assert data['row_errors']['user_on_certificate_invalidation_list'][0] == 'user "TestStudent1" in row# 1'
|
||||
|
||||
def upload_file(self, csv_content):
|
||||
"""
|
||||
Upload a csv file.
|
||||
@@ -1272,3 +1330,26 @@ class CertificateInvalidationViewTests(SharedModuleStoreTestCase):
|
||||
|
||||
# Assert Error Message
|
||||
assert res_json['message'] == 'Certificate Invalidation does not exist, Please refresh the page and try again.'
|
||||
|
||||
def test_learner_already_on_certificate_exception_list(self):
|
||||
"""
|
||||
Test to make sure we don't allow a single to learner to appear on both the certificate exception and
|
||||
invalidation lists.
|
||||
"""
|
||||
# add test learner to the allowlist
|
||||
CertificateWhitelistFactory.create(user=self.enrolled_user_1, course_id=self.course.id)
|
||||
|
||||
# now try and add them to the invalidation list, expect an error
|
||||
response = self.client.post(
|
||||
self.url,
|
||||
data=json.dumps(self.certificate_invalidation_data),
|
||||
content_type='application/json',
|
||||
)
|
||||
|
||||
res_json = json.loads(response.content.decode('utf-8'))
|
||||
assert response.status_code == 400
|
||||
assert res_json['message'] == (
|
||||
f"The student {self.enrolled_user_1.username} appears on the Certificate Exception list in course "
|
||||
f"{self.course.id}. Please remove them from the Certificate Exception list before attempting to "
|
||||
"invalidate their certificate."
|
||||
)
|
||||
|
||||
@@ -2647,13 +2647,20 @@ def certificate_exception_view(request, course_id):
|
||||
def add_certificate_exception(course_key, student, certificate_exception):
|
||||
"""
|
||||
Add a certificate exception to CertificateWhitelist table.
|
||||
Raises ValueError in case Student is already white listed.
|
||||
|
||||
Raises ValueError in case Student is already white listed or if they appear on the block list.
|
||||
|
||||
:param course_key: identifier of the course whose certificate exception will be added.
|
||||
:param student: User object whose certificate exception will be added.
|
||||
: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):
|
||||
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):
|
||||
raise ValueError(
|
||||
_("Student (username/email={user}) already in certificate exception list.").format(user=student.username)
|
||||
@@ -2667,7 +2674,8 @@ def add_certificate_exception(course_key, student, certificate_exception):
|
||||
'notes': certificate_exception.get('notes', '')
|
||||
}
|
||||
)
|
||||
log.info('%s has been added to the whitelist in course %s', student.username, course_key)
|
||||
|
||||
log.info(f"Student {student.id} has been added to the whitelist in course {course_key}")
|
||||
|
||||
generated_certificate = GeneratedCertificate.eligible_certificates.filter(
|
||||
user=student,
|
||||
@@ -2835,17 +2843,24 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
{
|
||||
general_errors: [errors related to csv file e.g. csv uploading, csv attachment, content reading etc. ],
|
||||
row_errors: {
|
||||
data_format_error: [users/data in csv file that are not well formatted],
|
||||
user_not_exist: [csv with none exiting users in LMS system],
|
||||
user_already_white_listed: [users that are already white listed],
|
||||
user_not_enrolled: [rows with not enrolled users in the given course]
|
||||
data_format_error: [users/data in csv file that are not well formatted],
|
||||
user_not_exist: [csv with none exiting users in LMS system],
|
||||
user_already_white_listed: [users that are already white listed],
|
||||
user_not_enrolled: [rows with not enrolled users in the given course],
|
||||
user_on_certificate_invalidation_list: [users that are currently have an active blocklist entry]
|
||||
},
|
||||
success: [list of successfully added users to the certificate white list model]
|
||||
}
|
||||
"""
|
||||
user_index = 0
|
||||
notes_index = 1
|
||||
row_errors_key = ['data_format_error', 'user_not_exist', 'user_already_white_listed', 'user_not_enrolled']
|
||||
row_errors_key = [
|
||||
'data_format_error',
|
||||
'user_not_exist',
|
||||
'user_already_white_listed',
|
||||
'user_not_enrolled',
|
||||
'user_on_certificate_invalidation_list'
|
||||
]
|
||||
course_key = CourseKey.from_string(course_id)
|
||||
students, general_errors, success = [], [], []
|
||||
row_errors = {key: [] for key in row_errors_key}
|
||||
@@ -2864,7 +2879,6 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
else:
|
||||
general_errors.append(_('Make sure that the file you upload is in CSV format with no '
|
||||
'extraneous characters or rows.'))
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
general_errors.append(_('Could not read uploaded file.'))
|
||||
finally:
|
||||
@@ -2878,7 +2892,7 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
if len(student) != 2:
|
||||
if student:
|
||||
build_row_errors('data_format_error', student[user_index], row_num)
|
||||
log.info('invalid data/format in csv row# %s', row_num)
|
||||
log.info(f'Invalid data/format in csv row# {row_num}')
|
||||
continue
|
||||
|
||||
user = student[user_index]
|
||||
@@ -2886,17 +2900,21 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
user = get_user_by_username_or_email(user)
|
||||
except ObjectDoesNotExist:
|
||||
build_row_errors('user_not_exist', user, row_num)
|
||||
log.info('student %s does not exist', user)
|
||||
log.info(f'Student {user} does not exist')
|
||||
else:
|
||||
if CertificateWhitelist.get_certificate_white_list(course_key, user):
|
||||
# make sure learner isn't on the blocklist
|
||||
if _check_if_learner_on_blocklist(course_key, user):
|
||||
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}')
|
||||
# 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)
|
||||
log.warning('student %s already exist.', user.username)
|
||||
|
||||
log.warning(f'Student {user.id} already on exception list in Course {course_key}.')
|
||||
# make sure user is enrolled in course
|
||||
elif not CourseEnrollment.is_enrolled(user, course_key):
|
||||
build_row_errors('user_not_enrolled', user, row_num)
|
||||
log.warning('student %s is not enrolled in course.', user.username)
|
||||
|
||||
log.warning(f'Student {user.id} is not enrolled in Course {course_key}')
|
||||
else:
|
||||
CertificateWhitelist.objects.create(
|
||||
user=user,
|
||||
@@ -2905,7 +2923,6 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
notes=student[notes_index]
|
||||
)
|
||||
success.append(_('user "{username}" in row# {row}').format(username=user.username, row=row_num))
|
||||
|
||||
else:
|
||||
general_errors.append(_('File is not attached.'))
|
||||
|
||||
@@ -2914,7 +2931,6 @@ def generate_bulk_certificate_exceptions(request, course_id):
|
||||
'row_errors': row_errors,
|
||||
'success': success
|
||||
}
|
||||
|
||||
return JsonResponse(results)
|
||||
|
||||
|
||||
@@ -2935,13 +2951,23 @@ def certificate_invalidation_view(request, course_id):
|
||||
# Validate request data and return error response in case of invalid data
|
||||
try:
|
||||
certificate_invalidation_data = parse_request_data(request)
|
||||
certificate = validate_request_data_and_get_certificate(certificate_invalidation_data, course_key)
|
||||
student = _get_student_from_request_data(certificate_invalidation_data, course_key)
|
||||
certificate = _get_certificate_for_user(course_key, student)
|
||||
except ValueError as error:
|
||||
return JsonResponse({'message': str(error)}, status=400)
|
||||
|
||||
# Invalidate certificate of the given student for the course course
|
||||
if request.method == 'POST':
|
||||
try:
|
||||
if _check_if_learner_on_allowlist(course_key, student):
|
||||
log.warning(f"Invalidating certificate for student {student.id} in course {course_key} failed. "
|
||||
"Student is currently on the allow list.")
|
||||
raise ValueError(
|
||||
_("The student {student} appears on the Certificate Exception list in course {course}. Please "
|
||||
"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)
|
||||
except ValueError as error:
|
||||
return JsonResponse({'message': str(error)}, status=400)
|
||||
@@ -3031,36 +3057,6 @@ def re_validate_certificate(request, course_key, generated_certificate):
|
||||
)
|
||||
|
||||
|
||||
def validate_request_data_and_get_certificate(certificate_invalidation, course_key):
|
||||
"""
|
||||
Fetch and return GeneratedCertificate of the student passed in request data for the given course.
|
||||
|
||||
Raises ValueError in case of missing student username/email or
|
||||
if student does not have certificate for the given course.
|
||||
|
||||
:param certificate_invalidation: dict containing certificate invalidation data
|
||||
:param course_key: CourseKey object identifying the current course.
|
||||
:return: GeneratedCertificate object of the student for the given course
|
||||
"""
|
||||
user = certificate_invalidation.get("user")
|
||||
|
||||
if not user:
|
||||
raise ValueError(
|
||||
_('Student username/email field is required and can not be empty. '
|
||||
'Kindly fill in username/email and then press "Invalidate Certificate" button.')
|
||||
)
|
||||
|
||||
student = get_student(user, course_key)
|
||||
|
||||
certificate = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
if not certificate:
|
||||
raise ValueError(_(
|
||||
"The student {student} does not have certificate for the course {course}. Kindly verify student "
|
||||
"username/email and the selected course are correct and try again."
|
||||
).format(student=student.username, course=course_key.course))
|
||||
return certificate
|
||||
|
||||
|
||||
def _get_boolean_param(request, param_name):
|
||||
"""
|
||||
Returns the value of the boolean parameter with the given
|
||||
@@ -3076,3 +3072,62 @@ def _create_error_response(request, msg):
|
||||
in JSON form.
|
||||
"""
|
||||
return JsonResponse({"error": msg}, 400)
|
||||
|
||||
|
||||
def _get_student_from_request_data(request_data, course_key):
|
||||
"""
|
||||
Attempts to retrieve the student information from the incoming request data.
|
||||
|
||||
:param request: HttpRequest object
|
||||
:param course_key: CourseKey object identifying the current course.
|
||||
"""
|
||||
user = request_data.get("user")
|
||||
if not user:
|
||||
raise ValueError(
|
||||
_('Student username/email field is required and can not be empty. '
|
||||
'Kindly fill in username/email and then press "Invalidate Certificate" button.')
|
||||
)
|
||||
|
||||
return get_student(user, course_key)
|
||||
|
||||
|
||||
def _get_certificate_for_user(course_key, student):
|
||||
"""
|
||||
Attempts to retrieve a Certificate for a learner in a given course run key.
|
||||
"""
|
||||
log.info(f"Retrieving certificate for student {student.id} in course {course_key}")
|
||||
certificate = GeneratedCertificate.certificate_for_student(student, course_key)
|
||||
if not certificate:
|
||||
raise ValueError(_(
|
||||
"The student {student} does not have certificate for the course {course}. Kindly verify student "
|
||||
"username/email and the selected course are correct and try again.").format(
|
||||
student=student.username, course=course_key.course)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
data_format_error: 'data-format-error',
|
||||
user_not_exist: 'user-not-exist',
|
||||
user_already_white_listed: 'user-already-white-listed',
|
||||
user_not_enrolled: 'user-not-enrolled'
|
||||
user_not_enrolled: 'user-not-enrolled',
|
||||
user_on_certificate_invalidation_list: 'user-on-certificate-invalidation-list'
|
||||
};
|
||||
|
||||
return Backbone.View.extend({
|
||||
@@ -43,7 +44,7 @@
|
||||
|
||||
render: function() {
|
||||
var template = this.loadTemplate('certificate-bulk-white-list');
|
||||
this.$el.html(template());
|
||||
this.$el.html(template()); // xss-lint: disable=javascript-jquery-html
|
||||
},
|
||||
|
||||
loadTemplate: function(name) {
|
||||
@@ -73,12 +74,91 @@
|
||||
},
|
||||
|
||||
display_response: function(data_from_server) {
|
||||
var UserOnCertificateInvalidationList;
|
||||
|
||||
$('.bulk-exception-results').removeClass('hidden').empty();
|
||||
|
||||
function generateDiv(group, heading, displayData) {
|
||||
// inner function generate div and display response messages.
|
||||
$('<div/>', {
|
||||
class: 'message ' + group
|
||||
}).appendTo('.bulk-exception-results').prepend( // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target,javascript-jquery-prepend
|
||||
"<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading) // eslint-disable-line max-len, xss-lint: disable=javascript-concat-html
|
||||
.append($('<ul/>', {
|
||||
class: group
|
||||
}));
|
||||
|
||||
for (var i = 0; i < displayData.length; i++) { // eslint-disable-line vars-on-top
|
||||
$('<li/>', {
|
||||
text: displayData[i]
|
||||
}).appendTo('div.message > .' + group); // eslint-disable-line max-len, xss-lint: disable=javascript-jquery-insert-into-target
|
||||
}
|
||||
$('div.message > .' + group).hide();
|
||||
}
|
||||
|
||||
function getDisplayText(qty, group) {
|
||||
// inner function to display appropriate heading text
|
||||
var text;
|
||||
|
||||
switch (group) {
|
||||
case MESSAGE_GROUP.successfully_added:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners are successfully added to exception list') :
|
||||
gettext(qty + ' learner is successfully added to the exception list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.data_format_error:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' records are not in correct format and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' record is not in correct format and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_not_exist:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners do not exist in LMS and not added to the' +
|
||||
' exception list') :
|
||||
gettext(qty + ' learner does not exist in LMS and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_already_white_listed:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners are already white listed and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' learner is already white listed and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_not_enrolled:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners are not enrolled in course and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' learner is not enrolled in course and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_on_certificate_invalidation_list:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners already appear on the certificate invalidation list') :
|
||||
gettext(qty + ' learner already appears on the certificate invalidation list');
|
||||
break;
|
||||
|
||||
default:
|
||||
text = qty > 1 ?
|
||||
gettext(qty + ' learners encountered unknown errors') :
|
||||
gettext(qty + ' learner encountered an unknown error');
|
||||
break;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
// Display general error messages
|
||||
if (data_from_server.general_errors.length) {
|
||||
var errors = data_from_server.general_errors;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.general_errors,
|
||||
gettext('Uploaded file issues. Click on "+" to view.'),
|
||||
errors
|
||||
@@ -88,9 +168,9 @@
|
||||
// Display success message
|
||||
if (data_from_server.success.length) {
|
||||
var success_data = data_from_server.success;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.successfully_added,
|
||||
get_text(success_data.length, MESSAGE_GROUP.successfully_added),
|
||||
getDisplayText(success_data.length, MESSAGE_GROUP.successfully_added),
|
||||
success_data
|
||||
);
|
||||
}
|
||||
@@ -101,93 +181,51 @@
|
||||
|
||||
if (row_errors.data_format_error.length) {
|
||||
var format_errors = row_errors.data_format_error;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.data_format_error,
|
||||
get_text(format_errors.length, MESSAGE_GROUP.data_format_error),
|
||||
getDisplayText(format_errors.length, MESSAGE_GROUP.data_format_error),
|
||||
format_errors
|
||||
);
|
||||
}
|
||||
if (row_errors.user_not_exist.length) {
|
||||
var user_not_exist = row_errors.user_not_exist;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.user_not_exist,
|
||||
get_text(user_not_exist.length, MESSAGE_GROUP.user_not_exist),
|
||||
getDisplayText(user_not_exist.length, MESSAGE_GROUP.user_not_exist),
|
||||
user_not_exist
|
||||
);
|
||||
}
|
||||
if (row_errors.user_already_white_listed.length) {
|
||||
var user_already_white_listed = row_errors.user_already_white_listed;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.user_already_white_listed,
|
||||
get_text(user_already_white_listed.length, MESSAGE_GROUP.user_already_white_listed),
|
||||
getDisplayText(
|
||||
user_already_white_listed.length,
|
||||
MESSAGE_GROUP.user_already_white_listed
|
||||
),
|
||||
user_already_white_listed
|
||||
);
|
||||
}
|
||||
if (row_errors.user_not_enrolled.length) {
|
||||
var user_not_enrolled = row_errors.user_not_enrolled;
|
||||
generate_div(
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.user_not_enrolled,
|
||||
get_text(user_not_enrolled.length, MESSAGE_GROUP.user_not_enrolled),
|
||||
getDisplayText(user_not_enrolled.length, MESSAGE_GROUP.user_not_enrolled),
|
||||
user_not_enrolled
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function generate_div(group, heading, display_data) {
|
||||
// inner function generate div and display response messages.
|
||||
$('<div/>', {
|
||||
class: 'message ' + group
|
||||
}).appendTo('.bulk-exception-results').prepend(
|
||||
"<button type='button' id= '" + group + "' class='arrow'> + </button>" + heading
|
||||
).append($('<ul/>', {
|
||||
class: group
|
||||
}));
|
||||
|
||||
for (var i = 0; i < display_data.length; i++) {
|
||||
$('<li/>', {
|
||||
text: display_data[i]
|
||||
}).appendTo('div.message > .' + group);
|
||||
if (row_errors.user_on_certificate_invalidation_list.length) {
|
||||
UserOnCertificateInvalidationList =
|
||||
row_errors.user_on_certificate_invalidation_list;
|
||||
generateDiv(
|
||||
MESSAGE_GROUP.user_on_certificate_invalidation_list,
|
||||
getDisplayText(
|
||||
UserOnCertificateInvalidationList.length,
|
||||
MESSAGE_GROUP.user_on_certificate_invalidation_list
|
||||
),
|
||||
UserOnCertificateInvalidationList
|
||||
);
|
||||
}
|
||||
$('div.message > .' + group).hide();
|
||||
}
|
||||
|
||||
function get_text(qty, group) {
|
||||
// inner function to display appropriate heading text
|
||||
var text;
|
||||
switch (group) {
|
||||
case MESSAGE_GROUP.successfully_added:
|
||||
text = qty > 1 ? gettext(qty + ' learners are successfully added to exception list') :
|
||||
gettext(qty + ' learner is successfully added to the exception list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.data_format_error:
|
||||
text = qty > 1 ? gettext(qty + ' records are not in correct format and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' record is not in correct format and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_not_exist:
|
||||
text = qty > 1 ? gettext(qty + ' learners do not exist in LMS and not added to the' +
|
||||
' exception list') :
|
||||
gettext(qty + ' learner does not exist in LMS and not added to the exception list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_already_white_listed:
|
||||
text = qty > 1 ? gettext(qty + ' learners are already white listed and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' learner is already white listed and not added to the exception ' +
|
||||
'list');
|
||||
break;
|
||||
|
||||
case MESSAGE_GROUP.user_not_enrolled:
|
||||
text = qty > 1 ? gettext(qty + ' learners are not enrolled in course and not added to' +
|
||||
' the exception list') :
|
||||
gettext(qty + ' learner is not enrolled in course and not added to the exception' +
|
||||
' list');
|
||||
break;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ define([
|
||||
data_format_error: ['user 1 in row# 1'],
|
||||
user_not_exist: ['user 2 in row# 2'],
|
||||
user_already_white_listed: ['user 3 in row# 3'],
|
||||
user_not_enrolled: ['user 4 in row# 4']
|
||||
user_not_enrolled: ['user 4 in row# 4'],
|
||||
user_on_certificate_invalidation_list: ['user 5 in row# 5']
|
||||
},
|
||||
success: []
|
||||
});
|
||||
@@ -89,6 +90,9 @@ define([
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner does not exist in LMS');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner is already white listed');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('1 learner is not enrolled in course');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain(
|
||||
'1 learner already appears on the certificate invalidation list'
|
||||
);
|
||||
});
|
||||
|
||||
it('bind the ajax call and the result will be plural form of row errors', function() {
|
||||
@@ -100,7 +104,8 @@ define([
|
||||
data_format_error: ['user 1 in row# 1', 'user 1 in row# 1'],
|
||||
user_not_exist: ['user 2 in row# 2', 'user 2 in row# 2'],
|
||||
user_already_white_listed: ['user 3 in row# 3', 'user 3 in row# 3'],
|
||||
user_not_enrolled: ['user 4 in row# 4', 'user 4 in row# 4']
|
||||
user_not_enrolled: ['user 4 in row# 4', 'user 4 in row# 4'],
|
||||
user_on_certificate_invalidation_list: ['user 5 in row# 5', 'user 5 in row# 5']
|
||||
},
|
||||
success: []
|
||||
});
|
||||
@@ -115,6 +120,9 @@ define([
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners do not exist in LMS');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners are already white listed');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain('2 learners are not enrolled in course');
|
||||
expect($(SELECTORS.bulk_exception_results).text()).toContain(
|
||||
'2 learners already appear on the certificate invalidation list'
|
||||
);
|
||||
});
|
||||
|
||||
it('toggle message details', function() {
|
||||
|
||||
Reference in New Issue
Block a user