diff --git a/common/djangoapps/course_modes/models.py b/common/djangoapps/course_modes/models.py index e92657f956..ccff710372 100644 --- a/common/djangoapps/course_modes/models.py +++ b/common/djangoapps/course_modes/models.py @@ -106,6 +106,9 @@ class CourseMode(models.Model): # Modes that allow a student to earn credit with a university partner CREDIT_MODES = [CREDIT_MODE] + # Modes that are allowed to upsell + UPSELL_TO_VERIFIED_MODES = [HONOR] + class Meta(object): """ meta attributes of this model """ unique_together = ('course_id', 'mode_slug', 'currency') diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index 05ab444bf8..480138a2c9 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -537,6 +537,32 @@ class DashboardTest(ModuleStoreTestCase): self.assertNotContains(response, "How it Works") self.assertNotContains(response, "Schools & Partners") + def test_course_mode_info_with_honor_enrollment(self): + """It will be true only if enrollment mode is honor and course has verified mode.""" + course_mode_info = self._enrollment_with_complete_course('honor') + self.assertTrue(course_mode_info['show_upsell']) + self.assertEquals(course_mode_info['days_for_upsell'], 1) + + @ddt.data('verified', 'credit') + def test_course_mode_info_with_different_enrollments(self, enrollment_mode): + """If user enrollment mode is either verified or credit then show_upsell + will be always false. + """ + course_mode_info = self._enrollment_with_complete_course(enrollment_mode) + self.assertFalse(course_mode_info['show_upsell']) + self.assertIsNone(course_mode_info['days_for_upsell']) + + def _enrollment_with_complete_course(self, enrollment_mode): + """"Dry method for course enrollment.""" + CourseModeFactory.create( + course_id=self.course.id, + mode_slug='verified', + mode_display_name='Verified', + expiration_datetime=datetime.now(pytz.UTC) + timedelta(days=1) + ) + enrollment = CourseEnrollment.enroll(self.user, self.course.id, mode=enrollment_mode) + return complete_course_mode_info(self.course.id, enrollment) + class UserSettingsEventTestMixin(EventTestMixin): """ diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 62aee8270b..9ff10cf49e 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -469,9 +469,9 @@ def complete_course_mode_info(course_id, enrollment, modes=None): modes = CourseMode.modes_for_course_dict(course_id) mode_info = {'show_upsell': False, 'days_for_upsell': None} - # we want to know if the user is already verified and if verified is an - # option - if 'verified' in modes and enrollment.mode != 'verified': + # we want to know if the user is already enrolled as verified or credit and + # if verified is an option. + if CourseMode.VERIFIED in modes and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES: mode_info['show_upsell'] = True # if there is an expiration date, find out how long from now it is if modes['verified'].expiration_datetime: diff --git a/lms/djangoapps/certificates/models.py b/lms/djangoapps/certificates/models.py index d02cf75e93..5e2e8f416e 100644 --- a/lms/djangoapps/certificates/models.py +++ b/lms/djangoapps/certificates/models.py @@ -111,6 +111,8 @@ class GeneratedCertificate(models.Model): MODES = Choices('verified', 'honor', 'audit') + VERIFIED_CERTS_MODES = [CourseMode.VERIFIED, CourseMode.CREDIT_MODE] + user = models.ForeignKey(User) course_id = CourseKeyField(max_length=255, blank=True, default=None) verify_uuid = models.CharField(max_length=32, blank=True, default='') diff --git a/lms/djangoapps/certificates/queue.py b/lms/djangoapps/certificates/queue.py index ea005ddf3c..1279bcdd09 100644 --- a/lms/djangoapps/certificates/queue.py +++ b/lms/djangoapps/certificates/queue.py @@ -15,6 +15,7 @@ from courseware import grades from xmodule.modulestore.django import modulestore from capa.xqueue_interface import XQueueInterface from capa.xqueue_interface import make_xheader, make_hashkey +from course_modes.models import CourseMode from student.models import UserProfile, CourseEnrollment from verify_student.models import SoftwareSecurePhotoVerification @@ -256,9 +257,14 @@ class XQueueCertInterface(object): is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() grade = grades.grade(student, self.request, course) enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id) - mode_is_verified = (enrollment_mode == GeneratedCertificate.MODES.verified) + mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student) cert_mode = enrollment_mode + + # For credit mode generate verified certificate + if cert_mode == CourseMode.CREDIT_MODE: + cert_mode = CourseMode.VERIFIED + if mode_is_verified and user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id) elif mode_is_verified and not user_is_verified: diff --git a/lms/djangoapps/certificates/tests/test_queue.py b/lms/djangoapps/certificates/tests/test_queue.py index dcd3a41b9f..1060580a79 100644 --- a/lms/djangoapps/certificates/tests/test_queue.py +++ b/lms/djangoapps/certificates/tests/test_queue.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- """Tests for the XQueue certificates interface. """ from contextlib import contextmanager +import ddt import json from mock import patch, Mock from nose.plugins.attrib import attr @@ -28,8 +29,10 @@ from certificates.models import ( GeneratedCertificate, CertificateStatuses, ) +from verify_student.tests.factories import SoftwareSecurePhotoVerificationFactory +@ddt.ddt @attr('shard_1') @override_settings(CERT_QUEUE='certificates') class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): @@ -46,6 +49,8 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): mode="honor", ) self.xqueue = XQueueCertInterface() + self.user_2 = UserFactory.create() + SoftwareSecurePhotoVerificationFactory.create(user=self.user_2, status='approved') def test_add_cert_callback_url(self): with patch('courseware.grades.grade', Mock(return_value={'grade': 'Pass', 'percent': 0.75})): @@ -73,6 +78,50 @@ class XQueueCertInterfaceAddCertificateTest(ModuleStoreTestCase): self.assertEqual(certificate.status, CertificateStatuses.downloadable) self.assertIsNotNone(certificate.verify_uuid) + @ddt.data('honor', 'audit') + def test_add_cert_with_honor_certificates(self, mode): + """Test certificates generations for honor and audit modes.""" + template_name = 'certificate-template-{id.org}-{id.course}.pdf'.format( + id=self.course.id + ) + self.assert_queue_response(mode, mode, template_name) + + @ddt.data('credit', 'verified') + def test_add_cert_with_verified_certificates(self, mode): + """Test if enrollment mode is verified or credit along with valid + software-secure verification than verified certificate should be generated. + """ + template_name = 'certificate-template-{id.org}-{id.course}-verified.pdf'.format( + id=self.course.id + ) + + self.assert_queue_response(mode, 'verified', template_name) + + def assert_queue_response(self, mode, expected_mode, expected_template_name): + """Dry method for course enrollment and adding request to queue.""" + CourseEnrollmentFactory( + user=self.user_2, + course_id=self.course.id, + is_active=True, + mode=mode, + ) + with patch('courseware.grades.grade', Mock(return_value={'grade': 'Pass', 'percent': 0.75})): + with patch.object(XQueueInterface, 'send_to_queue') as mock_send: + mock_send.return_value = (0, None) + self.xqueue.add_cert(self.user_2, self.course.id) + + # Verify that the task was sent to the queue with the correct callback URL + self.assertTrue(mock_send.called) + __, kwargs = mock_send.call_args_list[0] + + actual_header = json.loads(kwargs['header']) + self.assertIn('https://edx.org/update_certificate?key=', actual_header['lms_callback_url']) + certificate = GeneratedCertificate.objects.get(user=self.user_2, course_id=self.course.id) + self.assertEqual(certificate.mode, expected_mode) + + body = json.loads(kwargs['body']) + self.assertIn(expected_template_name, body['template_pdf']) + @attr('shard_1') @override_settings(CERT_QUEUE='certificates')