diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index 6a5adc388e..8b1c259b48 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -943,17 +943,12 @@ class CourseEnrollmentManager(models.Manager): return is_course_full - def users_enrolled_in(self, course_id, mode=None): - """ - Returns a queryset of User for every user enrolled in the course. - - course_id (CourseKey): The key of the course associated with the enrollment. - mode (String): The enrolled mode of the users. - """ - _query = {'courseenrollment__course_id': course_id, 'courseenrollment__is_active': True} - if mode: - _query['courseenrollment__mode'] = mode - return User.objects.filter(**_query) + def users_enrolled_in(self, course_id): + """Return a queryset of User for every user enrolled in the course.""" + return User.objects.filter( + courseenrollment__course_id=course_id, + courseenrollment__is_active=True + ) def enrollment_counts(self, course_id): """ diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index f87b5c7b5d..a375b980a7 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -95,7 +95,9 @@ class CertificateStatuses(object): readable_statuses = { downloadable: "already received", notpassing: "didn't receive", - error: "error states" + error: "error states", + audit_passing: "audit passing states", + audit_notpassing: "audit not passing states", } PASSED_STATUSES = (downloadable, generating) diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py index a1d5a1acc5..14e404af84 100644 --- a/lms/djangoapps/instructor/tests/test_certificates.py +++ b/lms/djangoapps/instructor/tests/test_certificates.py @@ -13,9 +13,11 @@ from django.core.exceptions import ObjectDoesNotExist from django.test.utils import override_settings from django.conf import settings -from mock import patch, PropertyMock - from course_modes.models import CourseMode +from capa.xqueue_interface import XQueueInterface +from lms.djangoapps.grades.tests.utils import mock_passing_grade +from lms.djangoapps.verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory +from lms.djangoapps.verify_student.models import SoftwareSecurePhotoVerification from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory from config_models.models import cache @@ -351,69 +353,84 @@ class CertificatesInstructorApiTest(SharedModuleStoreTestCase): u'the "Pending Tasks" section.' ) - @patch( - 'lms.djangoapps.grades.new.course_grade.CourseGrade.summary', - PropertyMock(return_value={'grade': 'Pass', 'percent': 0.75}) - ) @override_settings(AUDIT_CERT_CUTOFF_DATE=datetime.now(pytz.UTC) - timedelta(days=1)) @ddt.data( - ('verified', 'ID Verified', True), - ('unverified', 'Not ID Verified', False) + (CertificateStatuses.generating, 'ID Verified', 'approved'), + (CertificateStatuses.unverified, 'Not ID Verified', 'denied'), ) @ddt.unpack - def test_verified_users_with_audit_certs(self, expected_cert_status, verification_output, user_verified): + def test_verified_users_with_audit_certs(self, expected_cert_status, verification_output, id_verification_status): """ - Test that `verified_users_with_audit_certs` option regenerates certificate for verified users with audit - certificates get certificate. + Test certificate regeneration for verified users with audit certificates. Scenario: - User enrolled in course as audit, - User passed the course as audit so they have `audit_passing` certificate status, + Enroll user in a course in audit mode, + User passed the course and now he has `audit_passing` certificate status, User switched to verified mode and is ID verified, - Regenerate certificates for `verified_users_with_audit_certs` is run, - Modified certificate status is `verified` if user is ID verified otherwise `unverified`. + Regenerate certificate for it, + Modified certificate status is `generating` if user is ID verified otherwise `unverified`. """ # Check that user is enrolled in audit mode. enrollment = CourseEnrollment.get_enrollment(self.user, self.course.id) - self.assertEqual(enrollment.mode, CourseMode.AUDIT) + self.assertEquals(enrollment.mode, CourseMode.AUDIT) - # Generate certificate for user and check that user has a audit passing certificate. - with patch('student.models.CourseEnrollment.refund_cutoff_date') as cutoff_date: - cutoff_date.return_value = datetime.now(pytz.UTC) - timedelta(minutes=5) - cert_status = certs_api.generate_user_certificates(student=self.user, course_key=self.course.id, course=self.course) - self.assertEqual(cert_status, CertificateStatuses.audit_passing) - - # Update user enrollment mode to verified mode. - enrollment.update_enrollment(mode='verified') - self.assertEqual(enrollment.mode, CourseMode.VERIFIED) - - with patch( - 'lms.djangoapps.verify_student.models.SoftwareSecurePhotoVerification.user_is_verified' - ) as user_verify: - user_verify.return_value = user_verified - - # Login the client and access the url with 'certificate_statuses' - self.client.login(username=self.global_staff.username, password='test') - url = reverse('start_certificate_regeneration', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.post(url, data={'certificate_statuses': ['verified_users_with_audit_certs']}) - - # Assert 200 status code in response - self.assertEqual(response.status_code, 200) - res_json = json.loads(response.content) - - # Assert request is successful - self.assertTrue(res_json['success']) - - # Assert success message - self.assertEqual( - res_json['message'], - u'Certificate regeneration task has been started. You can view the status of the generation task in ' - u'the "Pending Tasks" section.' + with mock_passing_grade(): + # Generate certificate for user and check that user has a audit passing certificate. + cert_status = certs_api.generate_user_certificates( + student=self.user, + course_key=self.course.id, + course=self.course, ) - # Check user has a not audit passing certificate now. + + # Check that certificate status is 'audit_passing'. + self.assertEquals(cert_status, CertificateStatuses.audit_passing) + + # Update user enrollment mode to verified mode. + enrollment.update_enrollment(mode=CourseMode.VERIFIED) + self.assertEquals(enrollment.mode, CourseMode.VERIFIED) + + # Create and assert user's ID verification record. + SoftwareSecurePhotoVerificationFactory.create(user=self.user, status=id_verification_status) + actual_verification_status = SoftwareSecurePhotoVerification.verification_status_for_user( + self.user, + self.course.id, + enrollment.mode, + ) + self.assertEquals(actual_verification_status, verification_output) + + # Login the client and access the url with 'audit_passing' status. + self.client.login(username=self.global_staff.username, password='test') + url = reverse( + 'start_certificate_regeneration', + kwargs={'course_id': unicode(self.course.id)} + ) + + with mock.patch.object(XQueueInterface, 'send_to_queue') as mock_send: + mock_send.return_value = (0, None) + response = self.client.post( + url, + {'certificate_statuses': [CertificateStatuses.audit_passing]} + ) + + # Assert 200 status code in response + self.assertEquals(response.status_code, 200) + res_json = json.loads(response.content) + + # Assert request is successful + self.assertTrue(res_json['success']) + + # Assert success message + self.assertEquals( + res_json['message'], + u'Certificate regeneration task has been started. ' + u'You can view the status of the generation task in ' + u'the "Pending Tasks" section.' + ) + + # Now, check whether user has audit certificate. cert = certs_api.get_certificate_for_user(self.user.username, self.course.id) - self.assertNotEqual(cert['status'], CertificateStatuses.audit_passing) - self.assertEqual(cert['status'], expected_cert_status) + self.assertNotEquals(cert['status'], CertificateStatuses.audit_passing) + self.assertEquals(cert['status'], expected_cert_status) def test_certificate_regeneration_error(self): """ diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index d4647dc810..fe3484cf9c 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -2866,8 +2866,8 @@ def start_certificate_regeneration(request, course_id): CertificateStatuses.downloadable, CertificateStatuses.error, CertificateStatuses.notpassing, - # verified users with audit passing and not passing certificate statuses. - 'verified_users_with_audit_certs' + CertificateStatuses.audit_passing, + CertificateStatuses.audit_notpassing, ] if not set(certificates_statuses).issubset(allowed_statuses): return JsonResponse( diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index 8621e7a082..8c3327f3a2 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -343,11 +343,6 @@ def _section_certificates(course): for certificate in GeneratedCertificate.get_unique_statuses(course_key=course.id) } - # Get the count of all course verified users with audit passing and audit not passing statuses. - verified_users_with_audit_certs = CourseEnrollment.objects.users_enrolled_in(course.id, mode='verified').filter( - generatedcertificate__status__in=[CertificateStatuses.audit_passing, CertificateStatuses.audit_notpassing] - ).count() - return { 'section_key': 'certificates', 'section_display_name': _('Certificates'), @@ -359,7 +354,6 @@ def _section_certificates(course): 'html_cert_enabled': html_cert_enabled, 'active_certificate': certs_api.get_active_web_certificate(course), 'certificate_statuses_with_count': certificate_statuses_with_count, - 'verified_users_with_audit_certs': verified_users_with_audit_certs, 'status': CertificateStatuses, 'certificate_generation_history': CertificateGenerationHistory.objects.filter(course_id=course.id).order_by("-created"), diff --git a/lms/djangoapps/instructor_task/api.py b/lms/djangoapps/instructor_task/api.py index 79b8ecc1bc..a412daf3b9 100644 --- a/lms/djangoapps/instructor_task/api.py +++ b/lms/djangoapps/instructor_task/api.py @@ -33,7 +33,7 @@ from lms.djangoapps.instructor_task.tasks import ( export_ora2_data, ) -from certificates.models import CertificateGenerationHistory, CertificateStatuses +from certificates.models import CertificateGenerationHistory from lms.djangoapps.instructor_task.api_helper import ( check_arguments_for_rescoring, @@ -507,11 +507,6 @@ def regenerate_certificates(request, course_key, statuses_to_regenerate): task_type = 'regenerate_certificates_all_student' task_input = {} - # Update task_input for verified users with audit passing and not passing certificate statuses. - if 'verified_users_with_audit_certs' in statuses_to_regenerate: - task_input.update({"student_set": 'verified_users_with_audit_certs'}) - statuses_to_regenerate = [CertificateStatuses.audit_passing, CertificateStatuses.audit_notpassing] - task_input.update({"statuses_to_regenerate": statuses_to_regenerate}) task_class = generate_certificates task_key = "" diff --git a/lms/djangoapps/instructor_task/tasks_helper.py b/lms/djangoapps/instructor_task/tasks_helper.py index ea3102e70e..bd05443d01 100644 --- a/lms/djangoapps/instructor_task/tasks_helper.py +++ b/lms/djangoapps/instructor_task/tasks_helper.py @@ -1429,10 +1429,6 @@ def generate_students_certificates( specific_student_id = task_input.get('specific_student_id') students_to_generate_certs_for = students_to_generate_certs_for.filter(id=specific_student_id) - # Verified users with audit passing and not passing certificate statuses. - elif student_set == "verified_users_with_audit_certs": - students_to_generate_certs_for = CourseEnrollment.objects.users_enrolled_in(course_id, mode='verified') - task_progress = TaskProgress(action_name, students_to_generate_certs_for.count(), start_time) current_step = {'step': 'Calculating students already have certificates'} diff --git a/lms/templates/instructor/instructor_dashboard_2/certificates.html b/lms/templates/instructor/instructor_dashboard_2/certificates.html index e4efe57662..ce9e955289 100644 --- a/lms/templates/instructor/instructor_dashboard_2/certificates.html +++ b/lms/templates/instructor/instructor_dashboard_2/certificates.html @@ -130,8 +130,14 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
+
+
+