Commit 7d117683 by Ahsan Ulhaq

update the ICRV requirments status

After receiving the response from software secure update the ICRV
requirement status in CreditRequirementstatus table.

ECOM-1602
parent 0d961d5c
...@@ -42,6 +42,7 @@ from microsite_configuration import microsite ...@@ -42,6 +42,7 @@ from microsite_configuration import microsite
from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH from openedx.core.djangoapps.user_api.accounts import NAME_MIN_LENGTH
from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings from openedx.core.djangoapps.user_api.accounts.api import get_account_settings, update_account_settings
from openedx.core.djangoapps.user_api.errors import UserNotFound, AccountValidationError from openedx.core.djangoapps.user_api.errors import UserNotFound, AccountValidationError
from openedx.core.djangoapps.credit.api import get_credit_requirement, set_credit_requirement_status
from student.models import CourseEnrollment from student.models import CourseEnrollment
from shoppingcart.models import Order, CertificateItem from shoppingcart.models import Order, CertificateItem
from shoppingcart.processors import ( from shoppingcart.processors import (
...@@ -921,6 +922,32 @@ def _send_email(user_id, subject, message): ...@@ -921,6 +922,32 @@ def _send_email(user_id, subject, message):
user.email_user(subject, message, from_address) user.email_user(subject, message, from_address)
def _set_user_requirement_status(attempt, namespace, status, reason=None):
"""Sets the status of a credit requirement for the user,
based on a verification checkpoint.
"""
checkpoint = None
try:
checkpoint = VerificationCheckpoint.objects.get(photo_verification=attempt)
except VerificationCheckpoint.DoesNotExist:
log.error("Unable to find checkpoint for user with id %d", attempt.user.id)
if checkpoint is not None:
course_key = checkpoint.course_id
credit_requirement = get_credit_requirement(
course_key, namespace, checkpoint.checkpoint_location
)
if credit_requirement is not None:
try:
set_credit_requirement_status(
attempt.user.username, credit_requirement, status, reason
)
except Exception: # pylint: disable=broad-except
# Catch exception if unable to add credit requirement
# status for user
log.error("Unable to add Credit requirement status for user with id %d", attempt.user.id)
@require_POST @require_POST
@csrf_exempt # SS does its own message signing, and their API won't have a cookie value @csrf_exempt # SS does its own message signing, and their API won't have a cookie value
def results_callback(request): def results_callback(request):
...@@ -974,15 +1001,19 @@ def results_callback(request): ...@@ -974,15 +1001,19 @@ def results_callback(request):
except SoftwareSecurePhotoVerification.DoesNotExist: except SoftwareSecurePhotoVerification.DoesNotExist:
log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id) log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
return HttpResponseBadRequest("edX ID {} not found".format(receipt_id)) return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
if result == "PASS": if result == "PASS":
log.debug("Approving verification for %s", receipt_id) log.debug("Approving verification for %s", receipt_id)
attempt.approve() attempt.approve()
status = "approved" status = "approved"
_set_user_requirement_status(attempt, 'reverification', 'satisfied')
elif result == "FAIL": elif result == "FAIL":
log.debug("Denying verification for %s", receipt_id) log.debug("Denying verification for %s", receipt_id)
attempt.deny(json.dumps(reason), error_code=error_code) attempt.deny(json.dumps(reason), error_code=error_code)
status = "denied" status = "denied"
_set_user_requirement_status(
attempt, 'reverification', 'failed', json.dumps(reason)
)
elif result == "SYSTEM FAIL": elif result == "SYSTEM FAIL":
log.debug("System failure for %s -- resetting to must_retry", receipt_id) log.debug("System failure for %s -- resetting to must_retry", receipt_id)
attempt.system_error(json.dumps(reason), error_code=error_code) attempt.system_error(json.dumps(reason), error_code=error_code)
...@@ -993,7 +1024,6 @@ def results_callback(request): ...@@ -993,7 +1024,6 @@ def results_callback(request):
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result) "Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
) )
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
if incourse_reverify_enabled: if incourse_reverify_enabled:
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all() checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
......
...@@ -29,7 +29,6 @@ from .models import ( ...@@ -29,7 +29,6 @@ from .models import (
) )
from .signature import signature, get_shared_secret_key from .signature import signature, get_shared_secret_key
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
...@@ -488,6 +487,70 @@ def is_credit_course(course_key): ...@@ -488,6 +487,70 @@ def is_credit_course(course_key):
return CreditCourse.is_credit_course(course_key) return CreditCourse.is_credit_course(course_key)
def get_credit_requirement(course_key, namespace, name):
"""Returns the requirement of a given course, namespace and name.
Args:
course_key(CourseKey): The identifier for course
namespace(str): Namespace of requirement
name(str): Name of the requirement
Returns: dict
Example:
>>> get_credit_requirement_status(
"course-v1-edX-DemoX-1T2015", "proctored_exam", "i4x://edX/DemoX/proctoring-block/final_uuid"
)
{
"course_key": "course-v1-edX-DemoX-1T2015"
"namespace": "reverification",
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
"display_name": "reverification"
"criteria": {},
}
"""
requirement = CreditRequirement.get_course_requirement(course_key, namespace, name)
return {
"course_key": requirement.course.course_key,
"namespace": requirement.namespace,
"name": requirement.name,
"display_name": requirement.display_name,
"criteria": requirement.criteria
} if requirement else None
def set_credit_requirement_status(username, requirement, status="satisfied", reason=None):
"""Update Credit Requirement Status for given username and requirement
if exists else add new.
Args:
username(str): Username of the user
requirement(dict): requirement dict
status(str): Status of the requirement
reason(dict): Reason of the status
Example:
>>> set_credit_requirement_status(
"staff",
{
"course_key": "course-v1-edX-DemoX-1T2015"
"namespace": "reverification",
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
},
"satisfied",
{}
)
"""
credit_requirement = CreditRequirement.get_course_requirement(
requirement['course_key'], requirement['namespace'], requirement['name']
)
CreditRequirementStatus.add_or_update_requirement_status(
username, credit_requirement, status, reason
)
def _get_requirements_to_disable(old_requirements, new_requirements): def _get_requirements_to_disable(old_requirements, new_requirements):
""" """
Get the ids of 'CreditRequirement' entries to be disabled that are Get the ids of 'CreditRequirement' entries to be disabled that are
......
...@@ -9,6 +9,7 @@ successful completion of a course on EdX ...@@ -9,6 +9,7 @@ successful completion of a course on EdX
import logging import logging
from django.db import models from django.db import models
from django.db import transaction
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
...@@ -208,6 +209,26 @@ class CreditRequirement(TimeStampedModel): ...@@ -208,6 +209,26 @@ class CreditRequirement(TimeStampedModel):
""" """
cls.objects.filter(id__in=requirement_ids).update(active=False) cls.objects.filter(id__in=requirement_ids).update(active=False)
@classmethod
def get_course_requirement(cls, course_key, namespace, name):
"""Get credit requirement of a given course.
Args:
course_key(CourseKey): The identifier for a course
namespace(str): Namespace of credit course requirements
name(str): Name of credit course requirement
Returns:
CreditRequirement object if exists
"""
try:
return cls.objects.get(
course__course_key=course_key, active=True, namespace=namespace, name=name
)
except cls.DoesNotExist:
return None
class CreditRequirementStatus(TimeStampedModel): class CreditRequirementStatus(TimeStampedModel):
""" """
...@@ -257,13 +278,34 @@ class CreditRequirementStatus(TimeStampedModel): ...@@ -257,13 +278,34 @@ class CreditRequirementStatus(TimeStampedModel):
""" """
return cls.objects.filter(requirement__in=requirements, username=username) return cls.objects.filter(requirement__in=requirements, username=username)
@classmethod
@transaction.commit_on_success
def add_or_update_requirement_status(cls, username, requirement, status="satisfied", reason=None):
"""Add credit requirement status for given username.
Args:
username(str): Username of the user
requirement(CreditRequirement): 'CreditRequirement' object
status(str): Status of the requirement
reason(dict): Reason of the status
"""
requirement_status, created = cls.objects.get_or_create(
username=username,
requirement=requirement,
defaults={"reason": reason, "status": status}
)
if not created:
requirement_status.status = status
requirement_status.reason = reason if reason else {}
requirement_status.save()
class CreditEligibility(TimeStampedModel): class CreditEligibility(TimeStampedModel):
""" """
A record of a user's eligibility for credit from a specific credit A record of a user's eligibility for credit from a specific credit
provider for a specific course. provider for a specific course.
""" """
username = models.CharField(max_length=255, db_index=True) username = models.CharField(max_length=255, db_index=True)
course = models.ForeignKey(CreditCourse, related_name="eligibilities") course = models.ForeignKey(CreditCourse, related_name="eligibilities")
provider = models.ForeignKey(CreditProvider, related_name="eligibilities") provider = models.ForeignKey(CreditProvider, related_name="eligibilities")
......
...@@ -27,7 +27,12 @@ from openedx.core.djangoapps.credit.models import ( ...@@ -27,7 +27,12 @@ from openedx.core.djangoapps.credit.models import (
CreditProvider, CreditProvider,
CreditRequirement, CreditRequirement,
CreditRequirementStatus, CreditRequirementStatus,
CreditEligibility, CreditEligibility
)
from openedx.core.djangoapps.credit.api import (
set_credit_requirements,
set_credit_requirement_status,
get_credit_requirement
) )
...@@ -215,6 +220,74 @@ class CreditRequirementApiTests(CreditApiTestBase): ...@@ -215,6 +220,74 @@ class CreditRequirementApiTests(CreditApiTestBase):
is_eligible = api.is_user_eligible_for_credit('abc', credit_course.course_key) is_eligible = api.is_user_eligible_for_credit('abc', credit_course.course_key)
self.assertFalse(is_eligible) self.assertFalse(is_eligible)
def test_get_credit_requirement(self):
self.add_credit_course()
requirements = [
{
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {
"min_grade": 0.8
}
}
]
requirement = get_credit_requirement(self.course_key, "grade", "grade")
self.assertIsNone(requirement)
expected_requirement = {
"course_key": self.course_key,
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {
"min_grade": 0.8
}
}
set_credit_requirements(self.course_key, requirements)
requirement = get_credit_requirement(self.course_key, "grade", "grade")
self.assertIsNotNone(requirement)
self.assertEqual(requirement, expected_requirement)
def test_set_credit_requirement_status(self):
self.add_credit_course()
requirements = [
{
"namespace": "grade",
"name": "grade",
"display_name": "Grade",
"criteria": {
"min_grade": 0.8
}
},
{
"namespace": "reverification",
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
"display_name": "Assessment 1",
"criteria": {}
}
]
set_credit_requirements(self.course_key, requirements)
course_requirements = CreditRequirement.get_course_requirements(self.course_key)
self.assertEqual(len(course_requirements), 2)
requirement = get_credit_requirement(self.course_key, "grade", "grade")
set_credit_requirement_status("staff", requirement, 'satisfied', {})
course_requirement = CreditRequirement.get_course_requirement(
requirement['course_key'], requirement['namespace'], requirement['name']
)
status = CreditRequirementStatus.objects.get(username="staff", requirement=course_requirement)
self.assertEqual(status.requirement.namespace, requirement['namespace'])
self.assertEqual(status.status, "satisfied")
set_credit_requirement_status(
"staff", requirement, 'failed', {'failure_reason': "requirements not satisfied"}
)
status = CreditRequirementStatus.objects.get(username="staff", requirement=course_requirement)
self.assertEqual(status.requirement.namespace, requirement['namespace'])
self.assertEqual(status.status, "failed")
@ddt.ddt @ddt.ddt
class CreditProviderIntegrationApiTests(CreditApiTestBase): class CreditProviderIntegrationApiTests(CreditApiTestBase):
...@@ -425,6 +498,7 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase): ...@@ -425,6 +498,7 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
self.assertEqual(requests, []) self.assertEqual(requests, [])
def _configure_credit(self): def _configure_credit(self):
""" """
Configure a credit course and its requirements. Configure a credit course and its requirements.
......
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