Commit 03db7a7f by Ahsan Ulhaq

Merge pull request #8228 from edx/ahsan/ECOM-1602-update-ICRV-requirments-status-new

update the ICRV requirments status
parents 905512b6 7d117683
......@@ -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.
......
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