Commit dacfcc98 by Mushtaq Ali Committed by Qubad786

Generate certificates for verified users with audit certificate statues - ECOM-5012

parent ada4e082
...@@ -943,12 +943,17 @@ class CourseEnrollmentManager(models.Manager): ...@@ -943,12 +943,17 @@ class CourseEnrollmentManager(models.Manager):
return is_course_full return is_course_full
def users_enrolled_in(self, course_id): def users_enrolled_in(self, course_id, mode=None):
"""Return a queryset of User for every user enrolled in the course.""" """
return User.objects.filter( Returns a queryset of User for every user enrolled in the course.
courseenrollment__course_id=course_id,
courseenrollment__is_active=True 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): def enrollment_counts(self, course_id):
""" """
......
...@@ -3,12 +3,19 @@ import contextlib ...@@ -3,12 +3,19 @@ import contextlib
import ddt import ddt
import mock import mock
import json import json
import pytz
from datetime import datetime, timedelta
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.test.utils import override_settings from django.test.utils import override_settings
from django.conf import 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.django_utils import SharedModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory from xmodule.modulestore.tests.factories import CourseFactory
from config_models.models import cache from config_models.models import cache
...@@ -344,6 +351,70 @@ class CertificatesInstructorApiTest(SharedModuleStoreTestCase): ...@@ -344,6 +351,70 @@ class CertificatesInstructorApiTest(SharedModuleStoreTestCase):
u'the "Pending Tasks" section.' 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): def test_certificate_regeneration_error(self):
""" """
Test certificate regeneration errors out when accessed with either empty list of 'certificate_statuses' or Test certificate regeneration errors out when accessed with either empty list of 'certificate_statuses' or
......
...@@ -2862,7 +2862,13 @@ def start_certificate_regeneration(request, course_id): ...@@ -2862,7 +2862,13 @@ def start_certificate_regeneration(request, course_id):
) )
# Check if the selected statuses are allowed # 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): if not set(certificates_statuses).issubset(allowed_statuses):
return JsonResponse( return JsonResponse(
{'message': _('Please select certificate statuses from the list only.')}, {'message': _('Please select certificate statuses from the list only.')},
......
...@@ -343,6 +343,11 @@ def _section_certificates(course): ...@@ -343,6 +343,11 @@ def _section_certificates(course):
for certificate in GeneratedCertificate.get_unique_statuses(course_key=course.id) 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 { return {
'section_key': 'certificates', 'section_key': 'certificates',
'section_display_name': _('Certificates'), 'section_display_name': _('Certificates'),
...@@ -354,6 +359,7 @@ def _section_certificates(course): ...@@ -354,6 +359,7 @@ def _section_certificates(course):
'html_cert_enabled': html_cert_enabled, 'html_cert_enabled': html_cert_enabled,
'active_certificate': certs_api.get_active_web_certificate(course), 'active_certificate': certs_api.get_active_web_certificate(course),
'certificate_statuses_with_count': certificate_statuses_with_count, 'certificate_statuses_with_count': certificate_statuses_with_count,
'verified_users_with_audit_certs': verified_users_with_audit_certs,
'status': CertificateStatuses, 'status': CertificateStatuses,
'certificate_generation_history': 'certificate_generation_history':
CertificateGenerationHistory.objects.filter(course_id=course.id).order_by("-created"), CertificateGenerationHistory.objects.filter(course_id=course.id).order_by("-created"),
......
...@@ -33,7 +33,7 @@ from lms.djangoapps.instructor_task.tasks import ( ...@@ -33,7 +33,7 @@ from lms.djangoapps.instructor_task.tasks import (
export_ora2_data, export_ora2_data,
) )
from certificates.models import CertificateGenerationHistory from certificates.models import CertificateGenerationHistory, CertificateStatuses
from lms.djangoapps.instructor_task.api_helper import ( from lms.djangoapps.instructor_task.api_helper import (
check_arguments_for_rescoring, check_arguments_for_rescoring,
...@@ -507,6 +507,11 @@ def regenerate_certificates(request, course_key, statuses_to_regenerate): ...@@ -507,6 +507,11 @@ def regenerate_certificates(request, course_key, statuses_to_regenerate):
task_type = 'regenerate_certificates_all_student' task_type = 'regenerate_certificates_all_student'
task_input = {} 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_input.update({"statuses_to_regenerate": statuses_to_regenerate})
task_class = generate_certificates task_class = generate_certificates
task_key = "" task_key = ""
......
...@@ -1429,6 +1429,10 @@ def generate_students_certificates( ...@@ -1429,6 +1429,10 @@ def generate_students_certificates(
specific_student_id = task_input.get('specific_student_id') specific_student_id = task_input.get('specific_student_id')
students_to_generate_certs_for = students_to_generate_certs_for.filter(id=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) task_progress = TaskProgress(action_name, students_to_generate_certs_for.count(), start_time)
current_step = {'step': 'Calculating students already have certificates'} current_step = {'step': 'Calculating students already have certificates'}
......
...@@ -130,6 +130,12 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str ...@@ -130,6 +130,12 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str
</div> </div>
<div> <div>
<label> <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}"> <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))} ${_("Regenerate for learners in an error state. ({count})").format(count=section_data['certificate_statuses_with_count'].get(section_data['status'].error, 0))}
</label> </label>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment