diff --git a/lms/djangoapps/ccx/tests/test_field_override_performance.py b/lms/djangoapps/ccx/tests/test_field_override_performance.py
index 88f81a42b1..82fe0b8097 100644
--- a/lms/djangoapps/ccx/tests/test_field_override_performance.py
+++ b/lms/djangoapps/ccx/tests/test_field_override_performance.py
@@ -167,10 +167,10 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
TEST_DATA = {
'no_overrides': [
- (26, 7, 19), (134, 7, 131), (594, 7, 537)
+ (27, 7, 19), (135, 7, 131), (595, 7, 537)
],
'ccx': [
- (26, 7, 47), (134, 7, 455), (594, 7, 2037)
+ (27, 7, 47), (135, 7, 455), (595, 7, 2037)
],
}
@@ -184,9 +184,9 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
TEST_DATA = {
'no_overrides': [
- (26, 4, 9), (134, 19, 54), (594, 84, 215)
+ (27, 4, 9), (135, 19, 54), (595, 84, 215)
],
'ccx': [
- (26, 4, 9), (134, 19, 54), (594, 84, 215)
+ (27, 4, 9), (135, 19, 54), (595, 84, 215)
]
}
diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py
index 7bf6a2e503..7d0dcdb329 100644
--- a/lms/djangoapps/courseware/views.py
+++ b/lms/djangoapps/courseware/views.py
@@ -38,6 +38,11 @@ from courseware.courses import (
sort_by_start_date,
)
from courseware.masquerade import setup_masquerade
+from openedx.core.djangoapps.credit.api import (
+ get_credit_requirement_status,
+ is_user_eligible_for_credit,
+ is_credit_course
+)
from courseware.model_data import FieldDataCache
from .module_render import toc_for_course, get_module_for_descriptor, get_module, get_module_by_usage_id
from .entrance_exams import (
@@ -1050,6 +1055,21 @@ def _progress(request, course_key, student_id):
# checking certificate generation configuration
show_generate_cert_btn = certs_api.cert_generation_enabled(course_key)
+ if is_credit_course(course_key):
+ requirement_statuses = get_credit_requirement_status(course_key, student.username)
+ if any(requirement['status'] == 'failed' for requirement in requirement_statuses):
+ eligibility_status = "not_eligible"
+ elif is_user_eligible_for_credit(student.username, course_key):
+ eligibility_status = "eligible"
+ else:
+ eligibility_status = "partial_eligible"
+ credit_course = {
+ 'eligibility_status': eligibility_status,
+ 'requirements': requirement_statuses
+ }
+ else:
+ credit_course = None
+
context = {
'course': course,
'courseware_summary': courseware_summary,
@@ -1058,7 +1078,8 @@ def _progress(request, course_key, student_id):
'staff_access': staff_access,
'student': student,
'passed': is_course_passed(course, grade_summary),
- 'show_generate_cert_btn': show_generate_cert_btn
+ 'show_generate_cert_btn': show_generate_cert_btn,
+ 'credit_course': credit_course
}
if show_generate_cert_btn:
diff --git a/lms/static/images/correct-icon.png b/lms/static/images/correct-icon.png
new file mode 100644
index 0000000000..5ae7de2a14
Binary files /dev/null and b/lms/static/images/correct-icon.png differ
diff --git a/lms/static/images/incorrect-icon.png b/lms/static/images/incorrect-icon.png
new file mode 100644
index 0000000000..0926486ec9
Binary files /dev/null and b/lms/static/images/incorrect-icon.png differ
diff --git a/lms/static/js/courseware/credit_progress.js b/lms/static/js/courseware/credit_progress.js
new file mode 100644
index 0000000000..d0600b85c0
--- /dev/null
+++ b/lms/static/js/courseware/credit_progress.js
@@ -0,0 +1,11 @@
+$(document).ready(function() {
+ $('.detail-collapse').on('click', function() {
+ var el = $(this);
+ $('.requirement-container').toggleClass('is-hidden');
+ el.find('.fa').toggleClass('fa-caret-down fa-caret-up');
+ el.find('.requirement-detail').text(function(i, text){
+ return text === gettext('More') ? gettext('Less') : gettext('More');
+ });
+ });
+
+});
diff --git a/lms/static/sass/course/_profile.scss b/lms/static/sass/course/_profile.scss
index 783e2dcd1b..32ff94b641 100644
--- a/lms/static/sass/course/_profile.scss
+++ b/lms/static/sass/course/_profile.scss
@@ -179,6 +179,66 @@
width: 100%;
}
+ > .credit-eligibility{
+ border-top: 1px solid $lightGrey;
+ margin-top: lh();
+ @include padding-left(0);
+
+ > .credit-eligibility-container {
+ padding: lh();
+ > .credit-help {
+ background: $blue;
+ color: $white;
+ width: lh();
+ margin: 0;
+ padding: 0;
+ border-radius: lh(0.9);
+ border-color: $white;
+ text-shadow: None;
+ }
+ > .detail-collapse{
+ border: none;
+ box-shadow: none;
+ background: $white;
+ padding: 0;
+ color: $blue;
+ > i {
+ padding: lh(0.25);
+ }
+ > span{
+ color: inherit;
+ }
+ }
+ > .requirement-container{
+ padding: lh();
+ > .requirement{
+ border-bottom: 1px solid $lightGrey;
+ padding: lh(0.5);
+ > .requirement-name {
+ width: bi-app-invert-percentage(30%);
+ display: inline-block;
+ }
+ > .requirement-status{
+ width: bi-app-invert-percentage(70%);
+ @include float(right);
+ display: inline-block;
+ .fa-times{
+ @extend %t-icon6;
+ color: $alert-color;
+ }
+ .fa-check{
+ @extend %t-icon6;
+ color: $success-color;
+ }
+ > .not-achieve{
+ color: $lightGrey;
+ }
+ }
+ }
+ }
+ }
+ }
+
> .chapters {
border-top: 1px solid #e3e3e3;
margin-top: lh();
diff --git a/lms/templates/courseware/progress.html b/lms/templates/courseware/progress.html
index 019c0aa399..975756d823 100644
--- a/lms/templates/courseware/progress.html
+++ b/lms/templates/courseware/progress.html
@@ -3,7 +3,7 @@
<%!
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from util.date_utils import get_time_display
+from util.date_utils import get_time_display, DEFAULT_LONG_DATE_FORMAT
from django.conf import settings
from django.utils.http import urlquote_plus
%>
@@ -24,6 +24,7 @@ from django.utils.http import urlquote_plus
+
@@ -104,6 +105,51 @@ from django.utils.http import urlquote_plus
%endif
+ %if credit_course is not None:
+
+
+
+
${_("Requirements for Course Credit")}
+
+ %if credit_course['eligibility_status'] == 'not_eligible':
+
${student.username}, ${_("You are no longer eligible for this course.")}
+ %elif credit_course['eligibility_status'] == 'eligible':
+
${student.username}, ${_("You have met the requirements for credit in this course.")}
+ ${_("Go to your dashboard")} ${_("to purchase course credit.")}
+
+ %elif credit_course['eligibility_status'] == 'partial_eligible':
+
${student.username}, ${_("You have not yet met the requirements for credit.")}
+ %endif
+
Help regarding credit requirement
+
+ %for requirement in credit_course['requirements']:
+
+
${_(requirement['name'])}
+
+ %if requirement['status']:
+ %if requirement['status'] == 'submitted':
+ ${_("Verification Submitted")}
+ %elif requirement['status'] == 'failed':
+
+ ${_("Verification Failed" )}
+ %elif requirement['status'] == 'satisfied':
+
+ Verified on ${get_time_display(requirement['status_date'], DEFAULT_LONG_DATE_FORMAT, settings.TIME_ZONE)}
+ %endif
+ %else:
+ ${_("Upcoming")}
+ %endif
+
+
+ %endfor
+
+
+ ${_("More")} detail
+
+
+
+ %endif
+
%for chapter in courseware_summary:
%if not chapter['display_name'] == "hidden":
diff --git a/openedx/core/djangoapps/credit/api.py b/openedx/core/djangoapps/credit/api.py
index 4fa4e55bd3..b54eb5504c 100644
--- a/openedx/core/djangoapps/credit/api.py
+++ b/openedx/core/djangoapps/credit/api.py
@@ -406,6 +406,88 @@ def get_credit_requests_for_user(username):
return CreditRequest.credit_requests_for_user(username)
+def get_credit_requirement_status(course_key, username):
+ """ Retrieve the user's status for each credit requirement in the course.
+
+ Args:
+ course_key (CourseKey): The identifier for course
+ username (str): The identifier of the user
+
+ Example:
+ >>> get_credit_requirement_status("course-v1-edX-DemoX-1T2015", "john")
+
+ [
+ {
+ "namespace": "reverification",
+ "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
+ "criteria": {},
+ "status": "satisfied",
+ },
+ {
+ "namespace": "reverification",
+ "name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
+ "criteria": {},
+ "status": "Not satisfied",
+ },
+ {
+ "namespace": "proctored_exam",
+ "name": "i4x://edX/DemoX/proctoring-block/final_uuid",
+ "criteria": {},
+ "status": "error",
+ },
+ {
+ "namespace": "grade",
+ "name": "i4x://edX/DemoX/proctoring-block/final_uuid",
+ "criteria": {"min_grade": 0.8},
+ "status": None,
+ },
+ ]
+
+ Returns:
+ list of requirement statuses
+ """
+ requirements = CreditRequirement.get_course_requirements(course_key)
+ requirement_statuses = CreditRequirementStatus.get_statuses(requirements, username)
+ requirement_statuses = dict((o.requirement, o) for o in requirement_statuses)
+ statuses = []
+ for requirement in requirements:
+ requirement_status = requirement_statuses.get(requirement)
+ statuses.append({
+ "namespace": requirement.namespace,
+ "name": requirement.name,
+ "criteria": requirement.criteria,
+ "status": requirement_status.status if requirement_status else None,
+ "status_date": requirement_status.modified if requirement_status else None,
+ })
+ return statuses
+
+
+def is_user_eligible_for_credit(username, course_key):
+ """Returns a boolean indicating if the user is eligible for credit for
+ the given course
+
+ Args:
+ username(str): The identifier for user
+ course_key (CourseKey): The identifier for course
+
+ Returns:
+ True if user is eligible for the course else False
+ """
+ return CreditEligibility.is_user_eligible_for_credit(course_key, username)
+
+
+def is_credit_course(course_key):
+ """Check if the given course is a credit course
+
+ Arg:
+ course_key (CourseKey): The identifier for course
+
+ Returns:
+ True if course is credit course else False
+ """
+ return CreditCourse.is_credit_course(course_key)
+
+
def _get_requirements_to_disable(old_requirements, new_requirements):
"""
Get the ids of 'CreditRequirement' entries to be disabled that are
diff --git a/openedx/core/djangoapps/credit/models.py b/openedx/core/djangoapps/credit/models.py
index 8aff5cfcf1..eb632a5253 100644
--- a/openedx/core/djangoapps/credit/models.py
+++ b/openedx/core/djangoapps/credit/models.py
@@ -244,6 +244,19 @@ class CreditRequirementStatus(TimeStampedModel):
class Meta(object): # pylint: disable=missing-docstring
get_latest_by = "created"
+ @classmethod
+ def get_statuses(cls, requirements, username):
+ """ Get credit requirement statuses of given requirement and username
+
+ Args:
+ requirement(CreditRequirement): The identifier for a requirement
+ username(str): username of the user
+
+ Returns:
+ Queryset 'CreditRequirementStatus' objects
+ """
+ return cls.objects.filter(requirement__in=requirements, username=username)
+
class CreditEligibility(TimeStampedModel):
"""
@@ -258,6 +271,19 @@ class CreditEligibility(TimeStampedModel):
class Meta(object): # pylint: disable=missing-docstring
unique_together = ('username', 'course')
+ @classmethod
+ def is_user_eligible_for_credit(cls, course_key, username):
+ """Check if the given user is eligible for the provided credit course
+
+ Args:
+ course_key(CourseKey): The course identifier
+ username(str): The username of the user
+
+ Returns:
+ Bool True if the user eligible for credit course else False
+ """
+ return cls.objects.filter(course__course_key=course_key, username=username).exists()
+
class CreditRequest(TimeStampedModel):
"""
@@ -321,6 +347,7 @@ class CreditRequest(TimeStampedModel):
]
"""
+
return [
{
"uuid": request.uuid,
diff --git a/openedx/core/djangoapps/credit/tests/test_api.py b/openedx/core/djangoapps/credit/tests/test_api.py
index 57474f485b..6e5845fd98 100644
--- a/openedx/core/djangoapps/credit/tests/test_api.py
+++ b/openedx/core/djangoapps/credit/tests/test_api.py
@@ -204,6 +204,17 @@ class CreditRequirementApiTests(CreditApiTestBase):
self.assertEqual(len(grade_req), 1)
self.assertEqual(grade_req[0].active, False)
+ def test_is_user_eligible_for_credit(self):
+ credit_course = self.add_credit_course()
+ CreditEligibility.objects.create(
+ course=credit_course, username="staff", provider=CreditProvider.objects.get(provider_id=self.PROVIDER_ID)
+ )
+ is_eligible = api.is_user_eligible_for_credit('staff', credit_course.course_key)
+ self.assertTrue(is_eligible)
+
+ is_eligible = api.is_user_eligible_for_credit('abc', credit_course.course_key)
+ self.assertFalse(is_eligible)
+
@ddt.ddt
class CreditProviderIntegrationApiTests(CreditApiTestBase):