Commit 0d961d5c by Ahsan Ulhaq

Merge pull request #8362 from edx/ahsan/ECOM-1523-Credit-Eligibility-Progress-page-display

Credit Eligibility Progress page display
parents cc007d65 d35b6528
......@@ -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)
]
}
......@@ -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:
......
$(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');
});
});
});
......@@ -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();
......
......@@ -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
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.stack.js')}"></script>
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.symbol.js')}"></script>
<script type="text/javascript" src="${static.url('js/courseware/certificates_api.js')}"></script>
<script type="text/javascript" src="${static.url('js/courseware/credit_progress.js')}"></script>
<script>
${progress_graph.body(grade_summary, course.grade_cutoffs, "grade-detail-graph", not course.no_grade, not course.no_grade)}
</script>
......@@ -104,6 +105,51 @@ from django.utils.http import urlquote_plus
<div class="grade-detail-graph" id="grade-detail-graph" aria-hidden="true"></div>
%endif
%if credit_course is not None:
<section class="credit-eligibility">
<div class="credit-eligibility-container">
<div class="eligibility-heading">
<h2>${_("Requirements for Course Credit")}</h2>
</div>
%if credit_course['eligibility_status'] == 'not_eligible':
<span class="eligibility_msg">${student.username}, ${_("You are no longer eligible for this course.")}</span>
%elif credit_course['eligibility_status'] == 'eligible':
<span class="eligibility_msg">${student.username}, ${_("You have met the requirements for credit in this course.")}
<a href="#">${_("Go to your dashboard")}</a> ${_("to purchase course credit.")}
</span>
%elif credit_course['eligibility_status'] == 'partial_eligible':
<span>${student.username}, ${_("You have not yet met the requirements for credit.")}</span>
%endif
<button class="credit-help"><i class="fa fa-question"></i><span class="sr">Help regarding credit requirement</span></button><br>
<div class="requirement-container is-hidden">
%for requirement in credit_course['requirements']:
<div class="requirement">
<div class="requirement-name">${_(requirement['name'])}</div>
<div class="requirement-status">
%if requirement['status']:
%if requirement['status'] == 'submitted':
<span class="requirement-submitted">${_("Verification Submitted")}</span>
%elif requirement['status'] == 'failed':
<i class="fa fa-times"></i>
<span>${_("Verification Failed" )}</span>
%elif requirement['status'] == 'satisfied':
<i class="fa fa-check"></i>
<span>Verified on ${get_time_display(requirement['status_date'], DEFAULT_LONG_DATE_FORMAT, settings.TIME_ZONE)}</span>
%endif
%else:
<span class="not-achieve">${_("Upcoming")}</span>
%endif
</div>
</div>
%endfor
</div>
<button class="detail-collapse" aria-live="polite"><i class="fa fa-caret-down"></i>
<span class="requirement-detail">${_("More")}</span><span class="sr"> detail</span>
</button>
</div>
</section>
%endif
<div class="chapters">
%for chapter in courseware_summary:
%if not chapter['display_name'] == "hidden":
......
......@@ -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
......
......@@ -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,
......
......@@ -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):
......
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