From 76057fc4d27898fe9140f330f4d71dca9d178fd7 Mon Sep 17 00:00:00 2001 From: Syed Hassan Raza Date: Fri, 1 Apr 2016 15:44:55 +0500 Subject: [PATCH] semantic task_input for certificate generation ECOM-3505 --- lms/djangoapps/certificates/views/support.py | 7 +- .../instructor/tests/test_certificates.py | 40 ++- lms/djangoapps/instructor/views/api.py | 41 +-- lms/djangoapps/instructor_task/api.py | 52 ++- .../instructor_task/tasks_helper.py | 44 ++- .../instructor_task/tests/test_api.py | 13 + .../tests/test_tasks_helper.py | 316 ++++++++++++------ .../certificate-white-list.underscore | 3 +- 8 files changed, 346 insertions(+), 170 deletions(-) diff --git a/lms/djangoapps/certificates/views/support.py b/lms/djangoapps/certificates/views/support.py index 65d92adacf..89af213b74 100644 --- a/lms/djangoapps/certificates/views/support.py +++ b/lms/djangoapps/certificates/views/support.py @@ -260,5 +260,10 @@ def generate_certificate_for_user(request): return HttpResponseBadRequest(msg) # Attempt to generate certificate - generate_certificates_for_students(request, params["course_key"], students=[params["user"]]) + generate_certificates_for_students( + request, + params["course_key"], + student_set="specific_student", + specific_student_id=params["user"].id + ) return HttpResponse(200) diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index 5367e871c7..b8e8e0317c 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -720,7 +720,6 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase): response = self.client.post( url, - data=json.dumps([self.certificate_exception]), content_type='application/json' ) # Assert Success @@ -736,24 +735,49 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase): u"Certificate generation started for white listed students." ) - def test_generate_certificate_exceptions_invalid_user_list_error(self): + def test_generate_certificate_exceptions_whitelist_not_generated(self): """ - Test generate certificates exceptions api endpoint returns error - when called with certificate exceptions with empty 'user_id' field + Test generate certificates exceptions api endpoint returns success + when calling with new certificate exception. """ url = reverse( 'generate_certificate_exceptions', kwargs={'course_id': unicode(self.course.id), 'generate_for': 'new'} ) - # assign empty user_id - self.certificate_exception.update({'user_id': ''}) + response = self.client.post( + url, + content_type='application/json' + ) + + # Assert Success + self.assertEqual(response.status_code, 200) + + res_json = json.loads(response.content) + + # Assert Request is successful + self.assertTrue(res_json['success']) + # Assert Message + self.assertEqual( + res_json['message'], + u"Certificate generation started for white listed students." + ) + + def test_generate_certificate_exceptions_generate_for_incorrect_value(self): + """ + Test generate certificates exceptions api endpoint returns error + when calling with generate_for without 'new' or 'all' value. + """ + url = reverse( + 'generate_certificate_exceptions', + kwargs={'course_id': unicode(self.course.id), 'generate_for': ''} + ) response = self.client.post( url, - data=json.dumps([self.certificate_exception]), content_type='application/json' ) + # Assert Failure self.assertEqual(response.status_code, 400) @@ -764,7 +788,7 @@ class GenerateCertificatesInstructorApiTest(SharedModuleStoreTestCase): # Assert Message self.assertEqual( res_json['message'], - u"Invalid data, user_id must be present for all certificate exceptions." + u'Invalid data, generate_for must be "new" or "all".' ) diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index 9cd7fb2e71..cca2c8ca1c 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -3032,41 +3032,24 @@ def generate_certificate_exceptions(request, course_id, generate_for=None): """ course_key = CourseKey.from_string(course_id) - try: - certificate_white_list = json.loads(request.body) - except ValueError: - return JsonResponse({ - 'success': False, - 'message': _('Invalid Json data, Please refresh the page and then try again.') - }, status=400) - - users = [exception.get('user_id', False) for exception in certificate_white_list] - if generate_for == 'all': # Generate Certificates for all white listed students - students = User.objects.filter( - certificatewhitelist__course_id=course_key, - certificatewhitelist__whitelist=True - ) - elif not all(users): - # Invalid data, user_id must be present for all certificate exceptions + students = 'all_whitelisted' + + elif generate_for == 'new': + students = 'whitelisted_not_generated' + + else: + # Invalid data, generate_for must be present for all certificate exceptions return JsonResponse( { 'success': False, - 'message': _('Invalid data, user_id must be present for all certificate exceptions.'), + 'message': _('Invalid data, generate_for must be "new" or "all".'), }, status=400 ) - else: - students = User.objects.filter( - id__in=users, - certificatewhitelist__course_id=course_key, - certificatewhitelist__whitelist=True - ) - if students: - # generate certificates for students if 'students' list is not empty - instructor_task.api.generate_certificates_for_students(request, course_key, students=students) + instructor_task.api.generate_certificates_for_students(request, course_key, student_set=students) response_payload = { 'success': True, @@ -3275,8 +3258,10 @@ def re_validate_certificate(request, course_key, generated_certificate): certificate_invalidation.deactivate() # We need to generate certificate only for a single student here - students = [certificate_invalidation.generated_certificate.user] - instructor_task.api.generate_certificates_for_students(request, course_key, students=students) + student = certificate_invalidation.generated_certificate.user + instructor_task.api.generate_certificates_for_students( + request, course_key, student_set="specific_student", specific_student_id=student.id + ) def validate_request_data_and_get_certificate(certificate_invalidation, course_key): diff --git a/lms/djangoapps/instructor_task/api.py b/lms/djangoapps/instructor_task/api.py index 1f7b50847f..10939226f4 100644 --- a/lms/djangoapps/instructor_task/api.py +++ b/lms/djangoapps/instructor_task/api.py @@ -45,6 +45,13 @@ from bulk_email.models import CourseEmail from util import milestones_helpers +class SpecificStudentIdMissingError(Exception): + """ + Exception indicating that a student id was not provided when generating a certificate for a specific student. + """ + pass + + def get_running_instructor_tasks(course_id): """ Returns a query of InstructorTask objects of running tasks for a given course. @@ -437,17 +444,34 @@ def submit_export_ora2_data(request, course_key): return submit_task(request, task_type, task_class, course_key, task_input, task_key) -def generate_certificates_for_students(request, course_key, students=None): # pylint: disable=invalid-name +def generate_certificates_for_students(request, course_key, student_set=None, specific_student_id=None): # pylint: disable=invalid-name """ - Submits a task to generate certificates for given students enrolled in the course or - all students if argument 'students' is None + Submits a task to generate certificates for given students enrolled in the course. + + Arguments: + course_key : Course Key + student_set : Semantic for student collection for certificate generation. + Options are: + 'all_whitelisted': All Whitelisted students. + 'whitelisted_not_generated': Whitelisted students which does not got certificates yet. + 'specific_student': Single student for certificate generation. + specific_student_id : Student ID when student_set is 'specific_student' Raises AlreadyRunningError if certificates are currently being generated. + Raises SpecificStudentIdMissingError if student_set is 'specific_student' and specific_student_id is 'None' """ - if students: - task_type = 'generate_certificates_certain_student' - students = [student.id for student in students] - task_input = {'students': students} + if student_set: + task_type = 'generate_certificates_student_set' + task_input = {'student_set': student_set} + + if student_set == 'specific_student': + task_type = 'generate_certificates_certain_student' + if specific_student_id is None: + raise SpecificStudentIdMissingError( + "Attempted to generate certificate for a single student, " + "but no specific student id provided" + ) + task_input.update({'specific_student_id': specific_student_id}) else: task_type = 'generate_certificates_all_student' task_input = {} @@ -466,22 +490,16 @@ def generate_certificates_for_students(request, course_key, students=None): # p return instructor_task -def regenerate_certificates(request, course_key, statuses_to_regenerate, students=None): +def regenerate_certificates(request, course_key, statuses_to_regenerate): """ - Submits a task to regenerate certificates for given students enrolled in the course or - all students if argument 'students' is None. + Submits a task to regenerate certificates for given students enrolled in the course. Regenerate Certificate only if the status of the existing generated certificate is in 'statuses_to_regenerate' list passed in the arguments. Raises AlreadyRunningError if certificates are currently being generated. """ - if students: - task_type = 'regenerate_certificates_certain_student' - students = [student.id for student in students] - task_input = {'students': students} - else: - task_type = 'regenerate_certificates_all_student' - task_input = {} + task_type = 'regenerate_certificates_all_student' + task_input = {} task_input.update({"statuses_to_regenerate": statuses_to_regenerate}) task_class = generate_certificates diff --git a/lms/djangoapps/instructor_task/tasks_helper.py b/lms/djangoapps/instructor_task/tasks_helper.py index a4f50953ad..bd32135472 100644 --- a/lms/djangoapps/instructor_task/tasks_helper.py +++ b/lms/djangoapps/instructor_task/tasks_helper.py @@ -1409,30 +1409,56 @@ def generate_students_certificates( json column, otherwise generate certificates for all enrolled students. """ start_time = time() - enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id) + students_to_generate_certs_for = CourseEnrollment.objects.users_enrolled_in(course_id) - students = task_input.get('students', None) + student_set = task_input.get('student_set') + if student_set == 'all_whitelisted': + # Generate Certificates for all white listed students. + students_to_generate_certs_for = students_to_generate_certs_for.filter( + certificatewhitelist__course_id=course_id, + certificatewhitelist__whitelist=True + ) - if students is not None: - enrolled_students = enrolled_students.filter(id__in=students) + elif student_set == 'whitelisted_not_generated': + # All Whitelisted students + students_to_generate_certs_for = students_to_generate_certs_for.filter( + certificatewhitelist__course_id=course_id, + certificatewhitelist__whitelist=True + ) - task_progress = TaskProgress(action_name, enrolled_students.count(), start_time) + # Whitelisted students which got certificates already. + certificate_generated_students = GeneratedCertificate.objects.filter( # pylint: disable=no-member + course_id=course_id, + ) + certificate_generated_students_ids = set(certificate_generated_students.values_list('user_id', flat=True)) + + students_to_generate_certs_for = students_to_generate_certs_for.exclude( + id__in=certificate_generated_students_ids + ) + + elif student_set == "specific_student": + specific_student_id = task_input.get('specific_student_id') + students_to_generate_certs_for = students_to_generate_certs_for.filter(id=specific_student_id) + + task_progress = TaskProgress(action_name, students_to_generate_certs_for.count(), start_time) current_step = {'step': 'Calculating students already have certificates'} task_progress.update_task_state(extra_meta=current_step) statuses_to_regenerate = task_input.get('statuses_to_regenerate', []) - if students is not None and not statuses_to_regenerate: + if student_set is not None and not statuses_to_regenerate: # We want to skip 'filtering students' only when students are given and statuses to regenerate are not - students_require_certs = enrolled_students + students_require_certs = students_to_generate_certs_for else: - students_require_certs = students_require_certificate(course_id, enrolled_students, statuses_to_regenerate) + students_require_certs = students_require_certificate( + course_id, students_to_generate_certs_for, statuses_to_regenerate + ) if statuses_to_regenerate: # Mark existing generated certificates as 'unavailable' before regenerating # We need to call this method after "students_require_certificate" otherwise "students_require_certificate" # would return no results. - invalidate_generated_certificates(course_id, enrolled_students, statuses_to_regenerate) + invalidate_generated_certificates(course_id, students_to_generate_certs_for, statuses_to_regenerate) task_progress.skipped = task_progress.total - len(students_require_certs) diff --git a/lms/djangoapps/instructor_task/tests/test_api.py b/lms/djangoapps/instructor_task/tests/test_api.py index 5dcd09769a..2e1b3648dd 100644 --- a/lms/djangoapps/instructor_task/tests/test_api.py +++ b/lms/djangoapps/instructor_task/tests/test_api.py @@ -24,6 +24,7 @@ from instructor_task.api import ( generate_certificates_for_students, regenerate_certificates, submit_export_ora2_data, + SpecificStudentIdMissingError, ) from instructor_task.api_helper import AlreadyRunningError @@ -295,6 +296,18 @@ class InstructorTaskCourseSubmitTest(TestReportMixin, InstructorTaskCourseTestCa ) self._test_resubmission(api_call) + def test_certificate_generation_no_specific_student_id(self): + """ + Raises ValueError when student_set is 'specific_student' and 'specific_student_id' is None. + """ + with self.assertRaises(SpecificStudentIdMissingError): + generate_certificates_for_students( + self.create_task_request(self.instructor), + self.course.id, + student_set='specific_student', + specific_student_id=None + ) + def test_certificate_generation_history(self): """ Tests that a new record is added whenever certificate generation/regeneration task is submitted. diff --git a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py index 72a354652d..1d05a83dec 100644 --- a/lms/djangoapps/instructor_task/tests/test_tasks_helper.py +++ b/lms/djangoapps/instructor_task/tests/test_tasks_helper.py @@ -1628,8 +1628,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): Verify that certificates generated for all eligible students enrolled in a course. """ # create 10 students - students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i)) - for i in xrange(1, 11)] + students = self._create_students(10) # mark 2 students to have certificates generated already for student in students[:2]: @@ -1644,40 +1643,157 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): for student in students[2:7]: CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) - current_task = Mock() - current_task.update_state = Mock() - instructor_task = Mock() - instructor_task.task_input = json.dumps({'students': None}) + task_input = {'student_set': None} + expected_results = { + 'action_name': 'certificates generated', + 'total': 10, + 'attempted': 8, + 'succeeded': 5, + 'failed': 3, + 'skipped': 2 + } + with self.assertNumQueries(214): - with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task: - mock_current_task.return_value = current_task - with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue: - mock_queue.return_value = (0, "Successfully queued") - with patch('instructor_task.models.InstructorTask.objects.get') as instructor_task_object: - instructor_task_object.return_value = instructor_task - result = generate_students_certificates( - None, None, self.course.id, {}, 'certificates generated' - ) - self.assertDictContainsSubset( - { - 'action_name': 'certificates generated', - 'total': 10, - 'attempted': 8, - 'succeeded': 5, - 'failed': 3, - 'skipped': 2 - }, - result + self.assertCertificatesGenerated(task_input, expected_results) + + def test_certificate_generation_all_whitelisted(self): + """ + Verify that certificates generated for all white-listed students when using semantic task_input as + `all_whitelisted`. + """ + # create 5 students + students = self._create_students(5) + + # white-list 5 students + for student in students: + CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) + + task_input = {'student_set': 'all_whitelisted'} + expected_results = { + 'action_name': 'certificates generated', + 'total': 5, + 'attempted': 5, + 'succeeded': 5, + 'failed': 0, + 'skipped': 0 + } + self.assertCertificatesGenerated(task_input, expected_results) + + def test_certificate_generation_whitelist_already_generated(self): + """ + Verify that certificates generated for all white-listed students having certifcates already when using + semantic task_input as `all_whitelisted`. + """ + # create 5 students + students = self._create_students(5) + + # white-list 5 students + for student in students: + CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) + + # mark 5 students to have certificates generated already + for student in students: + GeneratedCertificateFactory.create( + user=student, + course_id=self.course.id, + status=CertificateStatuses.downloadable, + mode='honor' + ) + + task_input = {'student_set': 'all_whitelisted'} + expected_results = { + 'action_name': 'certificates generated', + 'total': 5, + 'attempted': 5, + 'succeeded': 5, + 'failed': 0, + 'skipped': 0 + } + self.assertCertificatesGenerated(task_input, expected_results) + + def test_certificate_generation_whitelisted_not_generated(self): + """ + Verify that certificates only generated for those students which does not have certificates yet when + using semantic task_input as `whitelisted_not_generated`. + """ + # create 5 students + students = self._create_students(5) + + # mark 2 students to have certificates generated already + for student in students[:2]: + GeneratedCertificateFactory.create( + user=student, + course_id=self.course.id, + status=CertificateStatuses.downloadable, + mode='honor' + ) + + # white-list 5 students + for student in students: + CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) + + task_input = {'student_set': 'whitelisted_not_generated'} + + expected_results = { + 'action_name': 'certificates generated', + 'total': 3, + 'attempted': 3, + 'succeeded': 3, + 'failed': 0, + 'skipped': 0 + } + self.assertCertificatesGenerated( + task_input, + expected_results ) + def test_certificate_generation_specific_student(self): + """ + Tests generating a certificate for a specific student. + """ + student = self.create_student(username="Hamnet", email="ham@ardenforest.co.uk") + CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) + task_input = { + 'student_set': 'specific_student', + 'specific_student_id': student.id + } + expected_results = { + 'action_name': 'certificates generated', + 'total': 1, + 'attempted': 1, + 'succeeded': 1, + 'failed': 0, + 'skipped': 0, + } + self.assertCertificatesGenerated(task_input, expected_results) + + def test_specific_student_not_enrolled(self): + """ + Tests generating a certificate for a specific student if that student + is not enrolled in the course. + """ + student = self.create_student(username="jacques", email="antlers@ardenforest.co.uk") + task_input = { + 'student_set': 'specific_student', + 'specific_student_id': student.id + } + expected_results = { + 'action_name': 'certificates generated', + 'total': 1, + 'attempted': 1, + 'succeeded': 0, + 'failed': 1, + 'skipped': 0, + } + self.assertCertificatesGenerated(task_input, expected_results) + def test_certificate_regeneration_for_statuses_to_regenerate(self): """ Verify that certificates are regenerated for all eligible students enrolled in a course whose generated certificate statuses lies in the list 'statuses_to_regenerate' given in task_input. """ # create 10 students - students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i)) - for i in xrange(1, 11)] + students = self._create_students(10) # mark 2 students to have certificates generated already for student in students[:2]: @@ -1710,31 +1826,22 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): for student in students[:7]: CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) - current_task = Mock() - current_task.update_state = Mock() - # Certificates should be regenerated for students having generated certificates with status # 'downloadable' or 'error' which are total of 5 students in this test case task_input = {'statuses_to_regenerate': [CertificateStatuses.downloadable, CertificateStatuses.error]} - with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task: - mock_current_task.return_value = current_task - with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue: - mock_queue.return_value = (0, "Successfully queued") - result = generate_students_certificates( - None, None, self.course.id, task_input, 'certificates generated' - ) + expected_results = { + 'action_name': 'certificates generated', + 'total': 10, + 'attempted': 5, + 'succeeded': 5, + 'failed': 0, + 'skipped': 5 + } - self.assertDictContainsSubset( - { - 'action_name': 'certificates generated', - 'total': 10, - 'attempted': 5, - 'succeeded': 5, - 'failed': 0, - 'skipped': 5 - }, - result + self.assertCertificatesGenerated( + task_input, + expected_results ) def test_certificate_regeneration_with_expected_failures(self): @@ -1746,8 +1853,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): default_grade = '-1' # create 10 students - students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i)) - for i in xrange(1, 11)] + students = self._create_students(10) # mark 2 students to have certificates generated already for student in students[:2]: @@ -1796,32 +1902,21 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): for student in students[:7]: CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) - current_task = Mock() - current_task.update_state = Mock() - # Regenerated certificates for students having generated certificates with status # 'deleted' or 'generating' task_input = {'statuses_to_regenerate': [CertificateStatuses.deleted, CertificateStatuses.generating]} - with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task: - mock_current_task.return_value = current_task - with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue: - mock_queue.return_value = (0, "Successfully queued") - result = generate_students_certificates( - None, None, self.course.id, task_input, 'certificates generated' - ) + expected_results = { + 'action_name': 'certificates generated', + 'total': 10, + 'attempted': 5, + 'succeeded': 2, + 'failed': 3, + 'skipped': 5 + } + + self.assertCertificatesGenerated(task_input, expected_results) - self.assertDictContainsSubset( - { - 'action_name': 'certificates generated', - 'total': 10, - 'attempted': 5, - 'succeeded': 2, - 'failed': 3, - 'skipped': 5 - }, - result - ) generated_certificates = GeneratedCertificate.eligible_certificates.filter( user__in=students, course_id=self.course.id, @@ -1852,8 +1947,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): default_grade = '-1' # create 10 students - students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i)) - for i in xrange(1, 11)] + students = self._create_students(10) # mark 2 students to have certificates generated already for student in students[:2]: @@ -1899,9 +1993,6 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): for student in students[:]: CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) - current_task = Mock() - current_task.update_state = Mock() - # Regenerated certificates for students having generated certificates with status # 'downloadable', 'error' or 'generating' task_input = { @@ -1912,24 +2003,18 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): ] } - with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task: - mock_current_task.return_value = current_task - with patch('capa.xqueue_interface.XQueueInterface.send_to_queue') as mock_queue: - mock_queue.return_value = (0, "Successfully queued") - result = generate_students_certificates( - None, None, self.course.id, task_input, 'certificates generated' - ) + expected_results = { + 'action_name': 'certificates generated', + 'total': 10, + 'attempted': 8, + 'succeeded': 8, + 'failed': 0, + 'skipped': 2 + } - self.assertDictContainsSubset( - { - 'action_name': 'certificates generated', - 'total': 10, - 'attempted': 8, - 'succeeded': 8, - 'failed': 0, - 'skipped': 2 - }, - result + self.assertCertificatesGenerated( + task_input, + expected_results ) generated_certificates = GeneratedCertificate.eligible_certificates.filter( @@ -1963,8 +2048,7 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): Verify that certificates are regenerated for all students passed in task_input. """ # create 10 students - students = [self.create_student(username='student_{}'.format(i), email='student_{}@example.com'.format(i)) - for i in xrange(1, 11)] + students = self._create_students(10) # mark 2 students to have certificates generated already for student in students[:2]: @@ -2006,12 +2090,27 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): for student in students[:7]: CertificateWhitelistFactory.create(user=student, course_id=self.course.id, whitelist=True) - current_task = Mock() - current_task.update_state = Mock() - # Certificates should be regenerated for students having generated certificates with status # 'downloadable' or 'error' which are total of 5 students in this test case - task_input = {'students': [student.id for student in students]} + task_input = {'student_set': "all_whitelisted"} + + expected_results = { + 'action_name': 'certificates generated', + 'total': 7, + 'attempted': 7, + 'succeeded': 7, + 'failed': 0, + 'skipped': 0, + } + + self.assertCertificatesGenerated(task_input, expected_results) + + def assertCertificatesGenerated(self, task_input, expected_results): + """ + Generate certificates for the given task_input and compare with expected_results. + """ + current_task = Mock() + current_task.update_state = Mock() with patch('instructor_task.tasks_helper._get_current_task') as mock_current_task: mock_current_task.return_value = current_task @@ -2022,17 +2121,22 @@ class TestCertificateGeneration(InstructorTaskModuleTestCase): ) self.assertDictContainsSubset( - { - 'action_name': 'certificates generated', - 'total': 10, - 'attempted': 10, - 'succeeded': 7, - 'failed': 3, - 'skipped': 0, - }, + expected_results, result ) + def _create_students(self, number_of_students): + """ + Create Students for course. + """ + return [ + self.create_student( + username='student_{}'.format(index), + email='student_{}@example.com'.format(index) + ) + for index in xrange(number_of_students) + ] + class TestInstructorOra2Report(SharedModuleStoreTestCase): """ diff --git a/lms/templates/instructor/instructor_dashboard_2/certificate-white-list.underscore b/lms/templates/instructor/instructor_dashboard_2/certificate-white-list.underscore index 581c3ecfd5..0e97cc3af9 100644 --- a/lms/templates/instructor/instructor_dashboard_2/certificate-white-list.underscore +++ b/lms/templates/instructor/instructor_dashboard_2/certificate-white-list.underscore @@ -2,7 +2,8 @@