update the ICRV requirments status
After receiving the response from software secure update the ICRV requirement status in CreditRequirementstatus table. ECOM-1602
This commit is contained in:
@@ -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.api import get_account_settings, update_account_settings
|
||||
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 shoppingcart.models import Order, CertificateItem
|
||||
from shoppingcart.processors import (
|
||||
@@ -921,6 +922,32 @@ def _send_email(user_id, subject, message):
|
||||
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
|
||||
@csrf_exempt # SS does its own message signing, and their API won't have a cookie value
|
||||
def results_callback(request):
|
||||
@@ -974,15 +1001,19 @@ def results_callback(request):
|
||||
except SoftwareSecurePhotoVerification.DoesNotExist:
|
||||
log.error("Software Secure posted back for receipt_id %s, but not found", receipt_id)
|
||||
return HttpResponseBadRequest("edX ID {} not found".format(receipt_id))
|
||||
|
||||
if result == "PASS":
|
||||
log.debug("Approving verification for %s", receipt_id)
|
||||
attempt.approve()
|
||||
status = "approved"
|
||||
_set_user_requirement_status(attempt, 'reverification', 'satisfied')
|
||||
|
||||
elif result == "FAIL":
|
||||
log.debug("Denying verification for %s", receipt_id)
|
||||
attempt.deny(json.dumps(reason), error_code=error_code)
|
||||
status = "denied"
|
||||
_set_user_requirement_status(
|
||||
attempt, 'reverification', 'failed', json.dumps(reason)
|
||||
)
|
||||
elif result == "SYSTEM FAIL":
|
||||
log.debug("System failure for %s -- resetting to must_retry", receipt_id)
|
||||
attempt.system_error(json.dumps(reason), error_code=error_code)
|
||||
@@ -993,7 +1024,6 @@ def results_callback(request):
|
||||
return HttpResponseBadRequest(
|
||||
"Result {} not understood. Known results: PASS, FAIL, SYSTEM FAIL".format(result)
|
||||
)
|
||||
|
||||
incourse_reverify_enabled = InCourseReverificationConfiguration.current().enabled
|
||||
if incourse_reverify_enabled:
|
||||
checkpoints = VerificationCheckpoint.objects.filter(photo_verification=attempt).all()
|
||||
|
||||
@@ -29,7 +29,6 @@ from .models import (
|
||||
)
|
||||
from .signature import signature, get_shared_secret_key
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -488,6 +487,70 @@ def 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):
|
||||
"""
|
||||
Get the ids of 'CreditRequirement' entries to be disabled that are
|
||||
|
||||
@@ -9,6 +9,7 @@ successful completion of a course on EdX
|
||||
import logging
|
||||
|
||||
from django.db import models
|
||||
from django.db import transaction
|
||||
from django.core.validators import RegexValidator
|
||||
from simple_history.models import HistoricalRecords
|
||||
|
||||
@@ -208,6 +209,26 @@ class CreditRequirement(TimeStampedModel):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
@@ -257,13 +278,34 @@ class CreditRequirementStatus(TimeStampedModel):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
A record of a user's eligibility for credit from a specific credit
|
||||
provider for a specific course.
|
||||
"""
|
||||
|
||||
username = models.CharField(max_length=255, db_index=True)
|
||||
course = models.ForeignKey(CreditCourse, related_name="eligibilities")
|
||||
provider = models.ForeignKey(CreditProvider, related_name="eligibilities")
|
||||
|
||||
@@ -27,7 +27,12 @@ from openedx.core.djangoapps.credit.models import (
|
||||
CreditProvider,
|
||||
CreditRequirement,
|
||||
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):
|
||||
is_eligible = api.is_user_eligible_for_credit('abc', credit_course.course_key)
|
||||
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
|
||||
class CreditProviderIntegrationApiTests(CreditApiTestBase):
|
||||
@@ -425,6 +498,7 @@ class CreditProviderIntegrationApiTests(CreditApiTestBase):
|
||||
self.assertEqual(requests, [])
|
||||
|
||||
def _configure_credit(self):
|
||||
|
||||
"""
|
||||
Configure a credit course and its requirements.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user