From dacfcc985feac678f06656cad2bcde3a1ba45b5c Mon Sep 17 00:00:00 2001
From: Mushtaq Ali <mushtaque@edx.org>
Date: Wed, 31 Aug 2016 16:55:07 +0500
Subject: [PATCH] Generate certificates for verified users with audit certificate statues - ECOM-5012

---
 common/djangoapps/student/models.py                               | 17 +++++++++++------
 lms/djangoapps/instructor/tests/test_certificates.py              | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 lms/djangoapps/instructor/views/api.py                            |  8 +++++++-
 lms/djangoapps/instructor/views/instructor_dashboard.py           |  6 ++++++
 lms/djangoapps/instructor_task/api.py                             |  7 ++++++-
 lms/djangoapps/instructor_task/tasks_helper.py                    |  4 ++++
 lms/templates/instructor/instructor_dashboard_2/certificates.html |  6 ++++++
 7 files changed, 111 insertions(+), 8 deletions(-)

diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py
index 8b1c259..6a5adc3 100644
--- a/common/djangoapps/student/models.py
+++ b/common/djangoapps/student/models.py
@@ -943,12 +943,17 @@ class CourseEnrollmentManager(models.Manager):
 
         return is_course_full
 
-    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 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 enrollment_counts(self, course_id):
         """
diff --git a/lms/djangoapps/instructor/tests/test_certificates.py b/lms/djangoapps/instructor/tests/test_certificates.py
index 82ffb70..a1d5a1a 100644
--- a/lms/djangoapps/instructor/tests/test_certificates.py
+++ b/lms/djangoapps/instructor/tests/test_certificates.py
@@ -3,12 +3,19 @@ import contextlib
 import ddt
 import mock
 import json
+import pytz
+
+from datetime import datetime, timedelta
 
 from nose.plugins.attrib import attr
 from django.core.urlresolvers import reverse
 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 xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory
 from config_models.models import cache
@@ -344,6 +351,70 @@ 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)
+    )
+    @ddt.unpack
+    def test_verified_users_with_audit_certs(self, expected_cert_status, verification_output, user_verified):
+        """
+        Test that `verified_users_with_audit_certs` option regenerates certificate for verified users with audit
+        certificates get certificate.
+
+        Scenario:
+            User enrolled in course as audit,
+            User passed the course as audit so they have `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`.
+        """
+        # Check that user is enrolled in audit mode.
+        enrollment = CourseEnrollment.get_enrollment(self.user, self.course.id)
+        self.assertEqual(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.'
+            )
+            # Check user has a not audit passing certificate now.
+            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)
+
     def test_certificate_regeneration_error(self):
         """
         Test certificate regeneration errors out when accessed with either empty list of 'certificate_statuses' or
diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py
index 8b27e24..d4647dc 100644
--- a/lms/djangoapps/instructor/views/api.py
+++ b/lms/djangoapps/instructor/views/api.py
@@ -2862,7 +2862,13 @@ def start_certificate_regeneration(request, course_id):
         )
 
     # Check if the selected statuses are allowed
-    allowed_statuses = [CertificateStatuses.downloadable, CertificateStatuses.error, CertificateStatuses.notpassing]
+    allowed_statuses = [
+        CertificateStatuses.downloadable,
+        CertificateStatuses.error,
+        CertificateStatuses.notpassing,
+        # verified users with audit passing and not passing certificate statuses.
+        'verified_users_with_audit_certs'
+    ]
     if not set(certificates_statuses).issubset(allowed_statuses):
         return JsonResponse(
             {'message': _('Please select certificate statuses from the list only.')},
diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py
index 8c3327f..8621e7a 100644
--- a/lms/djangoapps/instructor/views/instructor_dashboard.py
+++ b/lms/djangoapps/instructor/views/instructor_dashboard.py
@@ -343,6 +343,11 @@ 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'),
@@ -354,6 +359,7 @@ 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 a412daf..79b8ecc 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
+from certificates.models import CertificateGenerationHistory, CertificateStatuses
 
 from lms.djangoapps.instructor_task.api_helper import (
     check_arguments_for_rescoring,
@@ -507,6 +507,11 @@ 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 bd05443..ea3102e 100644
--- a/lms/djangoapps/instructor_task/tasks_helper.py
+++ b/lms/djangoapps/instructor_task/tasks_helper.py
@@ -1429,6 +1429,10 @@ 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 e84c47a..e4efe57 100644
--- a/lms/templates/instructor/instructor_dashboard_2/certificates.html
+++ b/lms/templates/instructor/instructor_dashboard_2/certificates.html
@@ -130,6 +130,12 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
                 </div>
                 <div>
                     <label>
+                        <input id="certificate_status_verified_users_with_audit_certs}" type="checkbox" name="certificate_statuses" value="verified_users_with_audit_certs">
+                        ${_("Regenerate for verified learners with audit certificates. ({count})").format(count=section_data['verified_users_with_audit_certs'])}
+                    </label>
+                </div>
+                <div>
+                    <label>
                         <input id="certificate_status_${section_data['status'].error}" type="checkbox" name="certificate_statuses" value="${section_data['status'].error}">
                         ${_("Regenerate for learners in an error state. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].error, 0))}
                     </label>
--
libgit2 0.26.0