diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index b98240c193161d9b2b026cd4d261c72bbf74a3de..b2a00e65434aefa6aa06d9b14df79dc0313e7565 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -350,24 +350,31 @@ class CertificateGenerationHistory(TimeStampedModel): students. """ task_input = self.instructor_task.task_input - try: - task_input_json = json.loads(task_input) - except ValueError: + if not task_input.strip(): # if task input is empty, it means certificates were generated for all learners # Translators: This string represents task was executed for all learners. return _("All learners") + task_input_json = json.loads(task_input) + # get statuses_to_regenerate from task_input convert statuses to human readable strings and return statuses = task_input_json.get('statuses_to_regenerate', None) if statuses: - return ", ".join( - [CertificateStatuses.readable_statuses.get(status, "") for status in statuses] - ) - - # If students is present in task_input then, certificate generation task was run to - # generate certificates for white listed students otherwise it is for all students. - # Translators: This string represents task was executed for students having exceptions. - return _("For exceptions") if 'students' in task_input_json else _("All learners") + readable_statuses = [ + CertificateStatuses.readable_statuses.get(status) for status in statuses + if CertificateStatuses.readable_statuses.get(status) is not None + ] + return ", ".join(readable_statuses) + + # If "student_set" is present in task_input, then this task only + # generates certificates for white listed students. Note that + # this key used to be "students", so we include that in this conditional + # for backwards compatibility. + if 'student_set' in task_input_json or 'students' in task_input_json: + # Translators: This string represents task was executed for students having exceptions. + return _("For exceptions") + else: + return _("All learners") class Meta(object): app_label = "certificates" diff --git a/lms/djangoapps/certificates/tests/test_models.py b/lms/djangoapps/certificates/tests/test_models.py index 2227f78554a6e8729af4998f6851920b0519e46b..92f62f40263b50f25247013aec2babbf87afd96a 100644 --- a/lms/djangoapps/certificates/tests/test_models.py +++ b/lms/djangoapps/certificates/tests/test_models.py @@ -1,10 +1,13 @@ """Tests for certificate Django models. """ +import ddt from django.conf import settings from django.core.exceptions import ValidationError from django.core.files.uploadedfile import SimpleUploadedFile from django.test import TestCase from django.test.utils import override_settings from nose.plugins.attrib import attr +import json +from mock import Mock from path import Path as path from certificates.models import ( @@ -14,10 +17,12 @@ from certificates.models import ( CertificateTemplateAsset, GeneratedCertificate, CertificateStatuses, + CertificateGenerationHistory, ) from certificates.tests.factories import GeneratedCertificateFactory +from instructor_task.tests.factories import InstructorTaskFactory from opaque_keys.edx.locator import CourseLocator -from student.tests.factories import UserFactory +from student.tests.factories import AdminFactory, UserFactory from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -229,3 +234,69 @@ class EligibleCertificateManagerTest(SharedModuleStoreTestCase): list(GeneratedCertificate.objects.filter(user=self.user)), # pylint: disable=no-member [self.eligible_cert, self.ineligible_cert] ) + + +@attr('shard_1') +@ddt.ddt +class TestCertificateGenerationHistory(TestCase): + """ + Test the CertificateGenerationHistory model's methods + """ + @ddt.data( + ({"student_set": "whitelisted_not_generated"}, "For exceptions", True), + ({"student_set": "whitelisted_not_generated"}, "For exceptions", False), + # check "students" key for backwards compatibility + ({"students": [1, 2, 3]}, "For exceptions", True), + ({"students": [1, 2, 3]}, "For exceptions", False), + ({}, "All learners", True), + ({}, "All learners", False), + # test single status to regenerate returns correctly + ({"statuses_to_regenerate": ['downloadable']}, 'already received', True), + ({"statuses_to_regenerate": ['downloadable']}, 'already received', False), + # test that list of > 1 statuses render correctly + ({"statuses_to_regenerate": ['downloadable', 'error']}, 'already received, error states', True), + ({"statuses_to_regenerate": ['downloadable', 'error']}, 'already received, error states', False), + # test that only "readable" statuses are returned + ({"statuses_to_regenerate": ['downloadable', 'not_readable']}, 'already received', True), + ({"statuses_to_regenerate": ['downloadable', 'not_readable']}, 'already received', False), + ) + @ddt.unpack + def test_get_certificate_generation_candidates(self, task_input, expected, is_regeneration): + staff = AdminFactory.create() + instructor_task = InstructorTaskFactory.create( + task_input=json.dumps(task_input), + requester=staff, + task_key=Mock(), + task_id=Mock(), + ) + certificate_generation_history = CertificateGenerationHistory( + course_id=instructor_task.course_id, + generated_by=staff, + instructor_task=instructor_task, + is_regeneration=is_regeneration, + ) + self.assertEqual( + certificate_generation_history.get_certificate_generation_candidates(), + expected + ) + + @ddt.data((True, "regenerated"), (False, "generated")) + @ddt.unpack + def test_get_task_name(self, is_regeneration, expected): + staff = AdminFactory.create() + instructor_task = InstructorTaskFactory.create( + task_input=json.dumps({}), + requester=staff, + task_key=Mock(), + task_id=Mock(), + ) + certificate_generation_history = CertificateGenerationHistory( + course_id=instructor_task.course_id, + generated_by=staff, + instructor_task=instructor_task, + is_regeneration=is_regeneration, + ) + self.assertEqual( + certificate_generation_history.get_task_name(), + expected + )