From 7fa9d73a4c5963789b8f2937097597ac92de450a Mon Sep 17 00:00:00 2001
From: Ahsan Ulhaq <ahsan.haq@arbisoft.com>
Date: Thu, 15 Oct 2015 15:22:08 +0500
Subject: [PATCH] Update Credit Eligibility Table for Skips

ECOM-2551
---
 lms/djangoapps/courseware/views.py            |  5 +-
 lms/djangoapps/verify_student/services.py     | 19 +++++
 .../verify_student/tests/test_services.py     | 80 ++++++++++++++-----
 lms/templates/courseware/progress.html        |  9 ++-
 openedx/core/djangoapps/credit/models.py      |  1 +
 .../core/djangoapps/credit/tests/test_api.py  | 15 ++++
 6 files changed, 104 insertions(+), 25 deletions(-)

diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index b3ce0215684..024a12909ff 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -1045,6 +1045,9 @@ def _credit_course_requirements(course_key, student):
     if not (settings.FEATURES.get("ENABLE_CREDIT_ELIGIBILITY", False) and is_credit_course(course_key)):
         return None
 
+    # Credit requirement statuses for which user does not remain eligible to get credit.
+    non_eligible_statuses = ['failed', 'declined']
+
     # Retrieve the status of the user for each eligibility requirement in the course.
     # For each requirement, the user's status is either "satisfied", "failed", or None.
     # In this context, `None` means that we don't know the user's status, either because
@@ -1068,7 +1071,7 @@ def _credit_course_requirements(course_key, student):
 
     # If the user has *failed* any requirements (for example, if a photo verification is denied),
     # then the user is NOT eligible for credit.
-    elif any(requirement['status'] == 'failed' for requirement in requirement_statuses):
+    elif any(requirement['status'] in non_eligible_statuses for requirement in requirement_statuses):
         eligibility_status = "not_eligible"
 
     # Otherwise, the user may be eligible for credit, but the user has not
diff --git a/lms/djangoapps/verify_student/services.py b/lms/djangoapps/verify_student/services.py
index cdf0a38df43..0269d8ed3c3 100644
--- a/lms/djangoapps/verify_student/services.py
+++ b/lms/djangoapps/verify_student/services.py
@@ -95,6 +95,7 @@ class ReverificationService(object):
             course_id=course_key,
             checkpoint_location=related_assessment_location
         )
+        user = User.objects.get(id=user_id)
 
         # user can skip a reverification attempt only if that user has not already
         # skipped an attempt
@@ -102,6 +103,24 @@ class ReverificationService(object):
             SkippedReverification.add_skipped_reverification_attempt(checkpoint, user_id, course_key)
         except IntegrityError:
             log.exception("Skipped attempt already exists for user %s: with course %s:", user_id, unicode(course_id))
+            return
+
+        try:
+            # Avoid circular import
+            from openedx.core.djangoapps.credit.api import set_credit_requirement_status
+
+            # As a user skips the reverification it declines to fulfill the requirement so
+            # requirement sets to declined.
+            set_credit_requirement_status(
+                user.username,
+                course_key,
+                'reverification',
+                checkpoint.checkpoint_location,
+                status='declined'
+            )
+
+        except Exception as err:  # pylint: disable=broad-except
+            log.error("Unable to add credit requirement status for user with id %d: %s", user_id, err)
 
     def get_attempts(self, user_id, course_id, related_assessment_location):
         """Get re-verification attempts against a user for a given 'checkpoint'
diff --git a/lms/djangoapps/verify_student/tests/test_services.py b/lms/djangoapps/verify_student/tests/test_services.py
index 85568c3d9fb..5c5a8b9dc12 100644
--- a/lms/djangoapps/verify_student/tests/test_services.py
+++ b/lms/djangoapps/verify_student/tests/test_services.py
@@ -4,6 +4,8 @@ Tests of re-verification service.
 
 import ddt
 
+from opaque_keys.edx.keys import CourseKey
+
 from course_modes.models import CourseMode
 from course_modes.tests.factories import CourseModeFactory
 from student.models import CourseEnrollment
@@ -11,6 +13,8 @@ from student.tests.factories import UserFactory
 from verify_student.models import VerificationCheckpoint, VerificationStatus, SkippedReverification
 from verify_student.services import ReverificationService
 
+from openedx.core.djangoapps.credit.api import get_credit_requirement_status, set_credit_requirements
+from openedx.core.djangoapps.credit.models import CreditCourse
 from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
 from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
 
@@ -25,20 +29,22 @@ class TestReverificationService(ModuleStoreTestCase):
         super(TestReverificationService, self).setUp()
 
         self.user = UserFactory.create(username="rusty", password="test")
-        course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
-        self.course_key = course.id
+        self.course = CourseFactory.create(org='Robot', number='999', display_name='Test Course')
+        self.course_id = self.course.id
         CourseModeFactory(
             mode_slug="verified",
-            course_id=self.course_key,
+            course_id=self.course_id,
             min_price=100,
         )
-        self.item = ItemFactory.create(parent=course, category='chapter', display_name='Test Section')
+        self.course_key = CourseKey.from_string(unicode(self.course_id))
+
+        self.item = ItemFactory.create(parent=self.course, category='chapter', display_name='Test Section')
         self.final_checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/final_uuid'.format(
-            org=self.course_key.org, course=self.course_key.course
+            org=self.course_id.org, course=self.course_id.course
         )
 
         # Enroll in a verified mode
-        self.enrollment = CourseEnrollment.enroll(self.user, self.course_key, mode=CourseMode.VERIFIED)
+        self.enrollment = CourseEnrollment.enroll(self.user, self.course_id, mode=CourseMode.VERIFIED)
 
     @ddt.data('final', 'midterm')
     def test_start_verification(self, checkpoint_name):
@@ -50,16 +56,16 @@ class TestReverificationService(ModuleStoreTestCase):
         """
         reverification_service = ReverificationService()
         checkpoint_location = u'i4x://{org}/{course}/edx-reverification-block/{checkpoint}'.format(
-            org=self.course_key.org, course=self.course_key.course, checkpoint=checkpoint_name
+            org=self.course_id.org, course=self.course_id.course, checkpoint=checkpoint_name
         )
         expected_url = (
             '/verify_student/reverify'
             '/{course_key}'
             '/{checkpoint_location}/'
-        ).format(course_key=unicode(self.course_key), checkpoint_location=checkpoint_location)
+        ).format(course_key=unicode(self.course_id), checkpoint_location=checkpoint_location)
 
         self.assertEqual(
-            reverification_service.start_verification(unicode(self.course_key), checkpoint_location),
+            reverification_service.start_verification(unicode(self.course_id), checkpoint_location),
             expected_url
         )
 
@@ -69,22 +75,22 @@ class TestReverificationService(ModuleStoreTestCase):
         """
         reverification_service = ReverificationService()
         self.assertIsNone(
-            reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
+            reverification_service.get_status(self.user.id, unicode(self.course_id), self.final_checkpoint_location)
         )
 
         checkpoint_obj = VerificationCheckpoint.objects.create(
-            course_id=unicode(self.course_key),
+            course_id=unicode(self.course_id),
             checkpoint_location=self.final_checkpoint_location
         )
         VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='submitted')
         self.assertEqual(
-            reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location),
+            reverification_service.get_status(self.user.id, unicode(self.course_id), self.final_checkpoint_location),
             'submitted'
         )
 
         VerificationStatus.objects.create(checkpoint=checkpoint_obj, user=self.user, status='approved')
         self.assertEqual(
-            reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location),
+            reverification_service.get_status(self.user.id, unicode(self.course_id), self.final_checkpoint_location),
             'approved'
         )
 
@@ -94,36 +100,68 @@ class TestReverificationService(ModuleStoreTestCase):
         """
         reverification_service = ReverificationService()
         VerificationCheckpoint.objects.create(
-            course_id=unicode(self.course_key),
+            course_id=unicode(self.course_id),
             checkpoint_location=self.final_checkpoint_location
         )
 
-        reverification_service.skip_verification(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
+        reverification_service.skip_verification(self.user.id, unicode(self.course_id), self.final_checkpoint_location)
         self.assertEqual(
-            SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count(),
+            SkippedReverification.objects.filter(user=self.user, course_id=self.course_id).count(),
             1
         )
 
         # now test that a user can have only one entry for a skipped
         # reverification for a course
-        reverification_service.skip_verification(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
+        reverification_service.skip_verification(self.user.id, unicode(self.course_id), self.final_checkpoint_location)
         self.assertEqual(
-            SkippedReverification.objects.filter(user=self.user, course_id=self.course_key).count(),
+            SkippedReverification.objects.filter(user=self.user, course_id=self.course_id).count(),
             1
         )
 
         # testing service for skipped attempt.
         self.assertEqual(
-            reverification_service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location),
+            reverification_service.get_status(self.user.id, unicode(self.course_id), self.final_checkpoint_location),
             'skipped'
         )
 
+    def test_declined_verification_on_skip(self):
+        """Test that status with value 'declined' is added in credit
+        requirement status model when a user skip's an ICRV.
+        """
+        reverification_service = ReverificationService()
+        checkpoint = VerificationCheckpoint.objects.create(
+            course_id=unicode(self.course_id),
+            checkpoint_location=self.final_checkpoint_location
+        )
+        # Create credit course and set credit requirements.
+        CreditCourse.objects.create(course_key=self.course_key, enabled=True)
+        set_credit_requirements(
+            self.course_key,
+            [
+                {
+                    "namespace": "reverification",
+                    "name": checkpoint.checkpoint_location,
+                    "display_name": "Assessment 1",
+                    "criteria": {},
+                }
+            ]
+        )
+
+        reverification_service.skip_verification(self.user.id, unicode(self.course_id), self.final_checkpoint_location)
+        requirement_status = get_credit_requirement_status(
+            self.course_key, self.user.username, 'reverification', checkpoint.checkpoint_location
+        )
+        self.assertEqual(SkippedReverification.objects.filter(user=self.user, course_id=self.course_id).count(), 1)
+        self.assertEqual(len(requirement_status), 1)
+        self.assertEqual(requirement_status[0].get('name'), checkpoint.checkpoint_location)
+        self.assertEqual(requirement_status[0].get('status'), 'declined')
+
     def test_get_attempts(self):
         """Check verification attempts count against a user for a given
         'checkpoint' and 'course_id'.
         """
         reverification_service = ReverificationService()
-        course_id = unicode(self.course_key)
+        course_id = unicode(self.course_id)
         self.assertEqual(
             reverification_service.get_attempts(self.user.id, course_id, self.final_checkpoint_location),
             0
@@ -147,5 +185,5 @@ class TestReverificationService(ModuleStoreTestCase):
 
         # Should be marked as "skipped" (opted out)
         service = ReverificationService()
-        status = service.get_status(self.user.id, unicode(self.course_key), self.final_checkpoint_location)
+        status = service.get_status(self.user.id, unicode(self.course_id), self.final_checkpoint_location)
         self.assertEqual(status, service.NON_VERIFIED_TRACK)
diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html
index 9483e8efea3..8666bd941fc 100644
--- a/lms/templates/courseware/progress.html
+++ b/lms/templates/courseware/progress.html
@@ -132,10 +132,13 @@ from django.utils.http import urlquote_plus
                                 %if requirement['status'] == 'submitted':
                                     <span class="requirement-submitted">${_("Verification Submitted")}</span>
                                 %elif requirement['status'] == 'failed':
-                                    <i class="fa fa-times"></i>
+                                    <i class="fa fa-times" aria-hidden="true"></i>
                                     <span>${_("Verification Failed" )}</span>
+                                %elif requirement['status'] == 'declined':
+                                    <i class="fa fa-times" aria-hidden="true"></i>
+                                    <span>${_("Verification Declined" )}</span>
                                 %elif requirement['status'] == 'satisfied':
-                                    <i class="fa fa-check"></i>
+                                    <i class="fa fa-check" aria-hidden="true"></i>
                                     % if requirement['namespace'] == 'grade' and 'final_grade' in requirement['reason']:
                                     <span>${int(requirement['reason']['final_grade'] * 100)}%</span>
                                     % else:
@@ -149,7 +152,7 @@ from django.utils.http import urlquote_plus
                     </div>
                 %endfor
                  </div>
-                <button class="detail-collapse" aria-live="polite"><i class="fa fa-caret-up"></i>
+                <button class="detail-collapse" aria-live="polite"><i class="fa fa-caret-up" aria-hidden="true"></i>
                     <span class="requirement-detail">${_("Less")}</span>
                 </button>
             </div>
diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py
index 1bc67198e62..895faaa2df8 100644
--- a/openedx/core/djangoapps/credit/models.py
+++ b/openedx/core/djangoapps/credit/models.py
@@ -411,6 +411,7 @@ class CreditRequirementStatus(TimeStampedModel):
     REQUIREMENT_STATUS_CHOICES = (
         ("satisfied", "satisfied"),
         ("failed", "failed"),
+        ("declined", "declined"),
     )
 
     username = models.CharField(max_length=255, db_index=True)
diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py
index 11ea560876b..17508938408 100644
--- a/openedx/core/djangoapps/credit/tests/test_api.py
+++ b/openedx/core/djangoapps/credit/tests/test_api.py
@@ -315,6 +315,21 @@ class CreditRequirementApiTests(CreditApiTestBase):
         req_status = api.get_credit_requirement_status(self.course_key, "staff", namespace="grade", name="grade")
         self.assertEqual(req_status[0]["status"], "failed")
 
+        # Set the requirement to "declined" and check that it's actually set
+        api.set_credit_requirement_status(
+            "staff", self.course_key,
+            "reverification",
+            "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
+            status="declined"
+        )
+        req_status = api.get_credit_requirement_status(
+            self.course_key,
+            "staff",
+            namespace="reverification",
+            name="i4x://edX/DemoX/edx-reverification-block/assessment_uuid"
+        )
+        self.assertEqual(req_status[0]["status"], "declined")
+
     def test_remove_credit_requirement_status(self):
         self.add_credit_course()
         requirements = [
-- 
GitLab