Display Eligibility table on progress page
1-Adding basic html for displaying eligibility table 2-Updated Eligibility Requirements Dynamically ECOM-1523
This commit is contained in:
@@ -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:
|
||||
|
||||
BIN
lms/static/images/correct-icon.png
Normal file
BIN
lms/static/images/correct-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 B |
BIN
lms/static/images/incorrect-icon.png
Normal file
BIN
lms/static/images/incorrect-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 257 B |
11
lms/static/js/courseware/credit_progress.js
Normal file
11
lms/static/js/courseware/credit_progress.js
Normal file
@@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user