AA-274: sending credit requirement information and added tests

This commit is contained in:
Daphne Li-Chen
2020-07-02 09:53:51 -04:00
parent 0192de00b2
commit f557e395de
5 changed files with 132 additions and 42 deletions

View File

@@ -66,18 +66,43 @@ class CertificateDataSerializer(serializers.Serializer):
return cert_data.cert_status == CertificateStatuses.requesting and cert_data.request_cert_url is not None
class CreditCourseRequirementsSerializer(serializers.Serializer):
class CreditRequirementSerializer(serializers.Serializer):
"""
Serializer for credit_course_requirements
Serializer for credit requirement objects
"""
display_name = serializers.CharField()
namespace = serializers.CharField()
min_grade = serializers.SerializerMethodField()
status = serializers.CharField()
status_date = serializers.DateTimeField()
def get_min_grade(self, req):
return req['criteria']['min_grade'] * 100
def get_min_grade(self, requirement):
if requirement['namespace'] == 'grade':
return requirement['criteria']['min_grade'] * 100
else:
return None
class CreditCourseRequirementsSerializer(serializers.Serializer):
"""
Serializer for credit_course_requirements
"""
dashboard_url = serializers.SerializerMethodField()
eligibility_status = serializers.CharField()
requirements = CreditRequirementSerializer(many=True)
def get_dashboard_url(self, _):
relative_path = reverse('dashboard')
request = self.context['request']
return request.build_absolute_uri(relative_path)
class VerificationDataSerializer(serializers.Serializer):
"""
Serializer for verification data object
"""
link = serializers.URLField()
status = serializers.CharField()
status_date = serializers.DateTimeField()
class ProgressTabSerializer(serializers.Serializer):
@@ -85,7 +110,10 @@ class ProgressTabSerializer(serializers.Serializer):
Serializer for progress tab
"""
certificate_data = CertificateDataSerializer()
credit_course_requirements = CreditCourseRequirementsSerializer()
credit_support_url = serializers.URLField()
courseware_summary = ChapterSerializer(many=True)
enrollment_mode = serializers.CharField()
studio_url = serializers.CharField()
user_timezone = serializers.CharField()
verification_data = VerificationDataSerializer()

View File

@@ -9,11 +9,14 @@ from django.urls import reverse
from course_modes.models import CourseMode
from lms.djangoapps.course_home_api.tests.utils import BaseCourseHomeTests
from lms.djangoapps.course_home_api.toggles import COURSE_HOME_MICROFRONTEND
from lms.djangoapps.verify_student.models import ManualVerification
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
from student.models import CourseEnrollment
from student.tests.factories import UserFactory
CREDIT_SUPPORT_URL = 'https://support.edx.org/hc/en-us/sections/115004154688-Purchasing-Academic-Credit'
@override_waffle_flag(COURSE_HOME_MICROFRONTEND, active=True)
@ddt.ddt
@@ -36,6 +39,13 @@ class ProgressTabTestViews(BaseCourseHomeTests):
for chapter in response.data['courseware_summary']:
self.assertIsNotNone(chapter)
self.assertIn('settings/grading/' + str(self.course.id), response.data['studio_url'])
self.assertEqual(response.data['credit_support_url'], CREDIT_SUPPORT_URL)
self.assertIsNotNone(response.data['verification_data'])
self.assertEqual(response.data['verification_data']['status'], 'none')
if enrollment_mode == CourseMode.VERIFIED:
ManualVerification.objects.create(user=self.user, status='approved')
response = self.client.get(self.url)
self.assertEqual(response.data['verification_data']['status'], 'approved')
def test_get_authenticated_user_not_enrolled(self):
response = self.client.get(self.url)

View File

@@ -17,13 +17,15 @@ from lms.djangoapps.courseware.context_processor import user_timezone_locale_pre
from lms.djangoapps.courseware.courses import get_course_with_access, get_studio_url
from lms.djangoapps.courseware.masquerade import setup_masquerade
from lms.djangoapps.courseware.access import has_access
from xmodule.modulestore.django import modulestore
import lms.djangoapps.course_blocks.api as course_blocks_api
from lms.djangoapps.courseware.views.views import credit_course_requirements, get_cert_data
from lms.djangoapps.grades.api import CourseGradeFactory
from lms.djangoapps.verify_student.services import IDVerificationService
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
CREDIT_SUPPORT_URL = 'https://support.edx.org/hc/en-us/sections/115004154688-Purchasing-Academic-Credit'
class ProgressTabView(RetrieveAPIView):
"""
@@ -46,6 +48,16 @@ class ProgressTabView(RetrieveAPIView):
is_requestable: (bool) true if status is requesting and request_cert_url is not None
msg: (str) message for the certificate status
title: (str) title of the certificate status
credit_course_requirements: An object containing the following fields
dashboard_url: (str) the url to the user's dashboard
eligibility_status: (str) the user's eligibility to receive a course credit
requirements: object containing the following fields
display_name: (str) the name of the requirement that should be displayed
namespace: (str) the type that the requirement is
min_grade: (float) the percentage formatted minimum grade needed for this requirement
status: (str) the status of the requirement
status_date: (str) the date the status was set
credit_support_url: (str) the url to the support docs for purchasing a credit
courseware_summary: List of serialized Chapters. each Chapter has the following fields:
display_name: (str) a str of what the name of the Chapter is for displaying on the site
subsections: List of serialized Subsections, each has the following fields:
@@ -63,6 +75,10 @@ class ProgressTabView(RetrieveAPIView):
enrollment_mode: (str) a str representing the enrollment the user has ('audit', 'verified', ...)
studio_url: (str) a str of the link to the grading in studio for the course
user_timezone: (str) The user's preferred timezone
verification_data: an object containing
link: (str) the link to either start or retry verification
status: (str) the status of the verification
status_date: (str) the date time string of when the verification status was set
@@ -80,7 +96,6 @@ class ProgressTabView(RetrieveAPIView):
def get(self, request, *args, **kwargs):
course_key_string = kwargs.get('course_key_string')
course_key = CourseKey.from_string(course_key_string)
course_usage_key = modulestore().make_course_usage_key(course_key)
# Enable NR tracing for this view based on course
monitoring_utils.set_custom_metric('course_id', course_key_string)
@@ -109,12 +124,29 @@ class ProgressTabView(RetrieveAPIView):
course_grade = CourseGradeFactory().read(request.user, course)
courseware_summary = course_grade.chapter_grades.values()
verification_status = IDVerificationService.user_status(request.user)
verification_link = None
if verification_status['status'] is None or verification_status['status'] == 'expired':
verification_link = IDVerificationService.get_verify_location('verify_student_verify_now',
course_id=course_key)
elif verification_status['status'] == 'must_reverify':
verification_link = IDVerificationService.get_verify_location('verify_student_reverify',
course_id=course_key)
verification_data = {
'link': verification_link,
'status': verification_status['status'],
'status_date': verification_status['status_date'],
}
data = {
'certificate_data': get_cert_data(request.user, course, enrollment_mode, course_grade),
'courseware_summary': courseware_summary,
'credit_course_requirements': credit_course_requirements(course_key, request.user),
'credit_support_url': CREDIT_SUPPORT_URL,
'enrollment_mode': enrollment_mode,
'studio_url': get_studio_url(course, 'settings/grading'),
'user_timezone': user_timezone,
'verification_data': verification_data,
}
context = self.get_serializer_context()
context['staff_access'] = bool(has_access(request.user, 'staff', course))

View File

@@ -173,6 +173,7 @@ class IDVerificationService(object):
'status': 'none',
'error': '',
'should_display': True,
'status_date': '',
'verification_expiry': '',
}
@@ -188,6 +189,7 @@ class IDVerificationService(object):
manual_id_verifications,
'updated_at'
)
except IndexError:
# The user has no verification attempts, return the default set of data.
return user_status
@@ -218,6 +220,7 @@ class IDVerificationService(object):
expiration_datetime = cls.get_expiration_datetime(user, ['approved'])
if getattr(attempt, 'expiry_date', None) and is_verification_expiring_soon(expiration_datetime):
user_status['verification_expiry'] = attempt.expiry_date.date().strftime("%m/%d/%Y")
user_status['status_date'] = attempt.status_changed
elif attempt.status in ['submitted', 'approved', 'must_retry']:
# user_has_valid_or_pending does include 'approved', but if we are

View File

@@ -3,10 +3,13 @@
Tests for the service classes in verify_student.
"""
from datetime import datetime
import ddt
from django.conf import settings
from mock import patch
from freezegun import freeze_time
from pytz import utc
from lms.djangoapps.verify_student.models import ManualVerification, SoftwareSecurePhotoVerification, SSOVerification
from lms.djangoapps.verify_student.services import IDVerificationService
@@ -66,47 +69,62 @@ class TestIDVerificationService(ModuleStoreTestCase):
self.assertTrue(IDVerificationService.user_has_valid_or_pending(user), status)
def test_user_status(self):
# each part of the test is in it's own 'frozen time' block because the status is dependent on recency of
# verifications and in order to control the recency, we just put everything inside of a frozen time
# test for correct status when no error returned
user = UserFactory.create()
status = IDVerificationService.user_status(user)
expected_status = {'status': 'none', 'error': '', 'should_display': True, 'verification_expiry': ''}
self.assertDictEqual(status, expected_status)
with freeze_time('2014-12-12'):
status = IDVerificationService.user_status(user)
expected_status = {'status': 'none', 'error': '', 'should_display': True, 'verification_expiry': '',
'status_date': ''}
self.assertDictEqual(status, expected_status)
# test for when photo verification has been created
SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': ''}
self.assertDictEqual(status, expected_status)
with freeze_time('2015-01-02'):
# test for when photo verification has been created
SoftwareSecurePhotoVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': True, 'verification_expiry': '',
'status_date': datetime.now(utc)}
self.assertDictEqual(status, expected_status)
# create another photo verification for the same user, make sure the denial
# is handled properly
SoftwareSecurePhotoVerification.objects.create(
user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
)
status = IDVerificationService.user_status(user)
expected_status = {
'status': 'must_reverify', 'error': ['id_image_missing'], 'should_display': True, 'verification_expiry': ''
}
self.assertDictEqual(status, expected_status)
with freeze_time('2015-02-02'):
# create another photo verification for the same user, make sure the denial
# is handled properly
SoftwareSecurePhotoVerification.objects.create(
user=user, status='denied', error_msg='[{"photoIdReasons": ["Not provided"]}]'
)
status = IDVerificationService.user_status(user)
expected_status = {
'status': 'must_reverify', 'error': ['id_image_missing'], 'should_display': True, 'verification_expiry': '',
'status_date': '',
}
self.assertDictEqual(status, expected_status)
# test for when sso verification has been created
SSOVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': ''}
self.assertDictEqual(status, expected_status)
with freeze_time('2015-03-02'):
# test for when sso verification has been created
SSOVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '',
'status_date': datetime.now(utc)}
self.assertDictEqual(status, expected_status)
# create another sso verification for the same user, make sure the denial
# is handled properly
SSOVerification.objects.create(user=user, status='denied')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'must_reverify', 'error': '', 'should_display': False, 'verification_expiry': ''}
self.assertDictEqual(status, expected_status)
with freeze_time('2015-04-02'):
# create another sso verification for the same user, make sure the denial
# is handled properly
SSOVerification.objects.create(user=user, status='denied')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'must_reverify', 'error': '', 'should_display': False, 'verification_expiry': '',
'status_date': ''}
self.assertDictEqual(status, expected_status)
# test for when manual verification has been created
ManualVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': ''}
self.assertDictEqual(status, expected_status)
with freeze_time('2015-05-02'):
# test for when manual verification has been created
ManualVerification.objects.create(user=user, status='approved')
status = IDVerificationService.user_status(user)
expected_status = {'status': 'approved', 'error': '', 'should_display': False, 'verification_expiry': '',
'status_date': datetime.now(utc)}
self.assertDictEqual(status, expected_status)
@ddt.unpack
@ddt.data(
@@ -125,7 +143,6 @@ class TestIDVerificationService(ModuleStoreTestCase):
with patch(
'lms.djangoapps.verify_student.services.IDVerificationService.user_is_verified'
) as mock_verification:
mock_verification.return_value = status
status = IDVerificationService.verification_status_for_user(user, enrollment_mode)