Credit eligibility requirements display on student progress page
ECOM-1523
This commit is contained in:
@@ -42,6 +42,9 @@ from lms.envs.common import (
|
||||
# technically accessible through the CMS via legacy URLs.
|
||||
PROFILE_IMAGE_BACKEND, PROFILE_IMAGE_DEFAULT_FILENAME, PROFILE_IMAGE_DEFAULT_FILE_EXTENSION,
|
||||
PROFILE_IMAGE_SECRET_KEY, PROFILE_IMAGE_MIN_BYTES, PROFILE_IMAGE_MAX_BYTES,
|
||||
# The following setting is included as it is used to check whether to
|
||||
# display credit eligibility table on the CMS or not.
|
||||
ENABLE_CREDIT_ELIGIBILITY
|
||||
)
|
||||
from path import path
|
||||
from warnings import simplefilter
|
||||
@@ -174,7 +177,7 @@ FEATURES = {
|
||||
'SHOW_BUMPER_PERIODICITY': 7 * 24 * 3600,
|
||||
|
||||
# Enable credit eligibility feature
|
||||
'ENABLE_CREDIT_ELIGIBILITY': False,
|
||||
'ENABLE_CREDIT_ELIGIBILITY': ENABLE_CREDIT_ELIGIBILITY,
|
||||
|
||||
# Can the visibility of the discussion tab be configured on a per-course basis?
|
||||
'ALLOW_HIDING_DISCUSSION_TAB': False,
|
||||
@@ -248,7 +251,6 @@ from lms.envs.common import (
|
||||
COURSE_KEY_PATTERN, COURSE_ID_PATTERN, USAGE_KEY_PATTERN, ASSET_KEY_PATTERN
|
||||
)
|
||||
|
||||
|
||||
######################### CSRF #########################################
|
||||
|
||||
# Forwards-compatibility with Django 1.7
|
||||
|
||||
@@ -102,7 +102,7 @@ var GradingView = ValidatingView.extend({
|
||||
renderMinimumGradeCredit: function() {
|
||||
var minimum_grade_credit = this.model.get('minimum_grade_credit');
|
||||
this.$el.find('#course-minimum_grade_credit').val(
|
||||
parseFloat(minimum_grade_credit) * 100 + '%'
|
||||
Math.round(parseFloat(minimum_grade_credit) * 100) + '%'
|
||||
);
|
||||
},
|
||||
setGracePeriod : function(event) {
|
||||
|
||||
@@ -173,18 +173,18 @@ class TestFieldOverrideMongoPerformance(FieldOverridePerformanceTestCase):
|
||||
|
||||
TEST_DATA = {
|
||||
# (providers, course_width, enable_ccx): # of sql queries, # of mongo queries, # of xblocks
|
||||
('no_overrides', 1, True): (27, 7, 19),
|
||||
('no_overrides', 2, True): (135, 7, 131),
|
||||
('no_overrides', 3, True): (595, 7, 537),
|
||||
('ccx', 1, True): (27, 7, 47),
|
||||
('ccx', 2, True): (135, 7, 455),
|
||||
('ccx', 3, True): (595, 7, 2037),
|
||||
('no_overrides', 1, False): (27, 7, 19),
|
||||
('no_overrides', 2, False): (135, 7, 131),
|
||||
('no_overrides', 3, False): (595, 7, 537),
|
||||
('ccx', 1, False): (27, 7, 19),
|
||||
('ccx', 2, False): (135, 7, 131),
|
||||
('ccx', 3, False): (595, 7, 537),
|
||||
('no_overrides', 1, True): (26, 7, 19),
|
||||
('no_overrides', 2, True): (134, 7, 131),
|
||||
('no_overrides', 3, True): (594, 7, 537),
|
||||
('ccx', 1, True): (26, 7, 47),
|
||||
('ccx', 2, True): (134, 7, 455),
|
||||
('ccx', 3, True): (594, 7, 2037),
|
||||
('no_overrides', 1, False): (26, 7, 19),
|
||||
('no_overrides', 2, False): (134, 7, 131),
|
||||
('no_overrides', 3, False): (594, 7, 537),
|
||||
('ccx', 1, False): (26, 7, 19),
|
||||
('ccx', 2, False): (134, 7, 131),
|
||||
('ccx', 3, False): (594, 7, 537),
|
||||
}
|
||||
|
||||
|
||||
@@ -196,16 +196,16 @@ class TestFieldOverrideSplitPerformance(FieldOverridePerformanceTestCase):
|
||||
__test__ = True
|
||||
|
||||
TEST_DATA = {
|
||||
('no_overrides', 1, True): (27, 4, 9),
|
||||
('no_overrides', 2, True): (135, 19, 54),
|
||||
('no_overrides', 3, True): (595, 84, 215),
|
||||
('ccx', 1, True): (27, 4, 9),
|
||||
('ccx', 2, True): (135, 19, 54),
|
||||
('ccx', 3, True): (595, 84, 215),
|
||||
('no_overrides', 1, False): (27, 4, 9),
|
||||
('no_overrides', 2, False): (135, 19, 54),
|
||||
('no_overrides', 3, False): (595, 84, 215),
|
||||
('ccx', 1, False): (27, 4, 9),
|
||||
('ccx', 2, False): (135, 19, 54),
|
||||
('ccx', 3, False): (595, 84, 215),
|
||||
('no_overrides', 1, True): (26, 4, 9),
|
||||
('no_overrides', 2, True): (134, 19, 54),
|
||||
('no_overrides', 3, True): (594, 84, 215),
|
||||
('ccx', 1, True): (26, 4, 9),
|
||||
('ccx', 2, True): (134, 19, 54),
|
||||
('ccx', 3, True): (594, 84, 215),
|
||||
('no_overrides', 1, False): (26, 4, 9),
|
||||
('no_overrides', 2, False): (134, 19, 54),
|
||||
('no_overrides', 3, False): (594, 84, 215),
|
||||
('ccx', 1, False): (26, 4, 9),
|
||||
('ccx', 2, False): (134, 19, 54),
|
||||
('ccx', 3, False): (594, 84, 215),
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import urllib
|
||||
import json
|
||||
import cgi
|
||||
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from django.utils import translation
|
||||
from django.utils.translation import ugettext as _
|
||||
@@ -1065,7 +1066,9 @@ 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):
|
||||
credit_course_requirements = None
|
||||
is_course_credit = settings.FEATURES.get("ENABLE_CREDIT_ELIGIBILITY", False) and is_credit_course(course_key)
|
||||
if is_course_credit:
|
||||
requirement_statuses = get_credit_requirement_status(course_key, student.username)
|
||||
if any(requirement['status'] == 'failed' for requirement in requirement_statuses):
|
||||
eligibility_status = "not_eligible"
|
||||
@@ -1073,12 +1076,16 @@ def _progress(request, course_key, student_id):
|
||||
eligibility_status = "eligible"
|
||||
else:
|
||||
eligibility_status = "partial_eligible"
|
||||
credit_course = {
|
||||
|
||||
paired_requirements = {}
|
||||
for requirement in requirement_statuses:
|
||||
namespace = requirement.pop("namespace")
|
||||
paired_requirements.setdefault(namespace, []).append(requirement)
|
||||
|
||||
credit_course_requirements = {
|
||||
'eligibility_status': eligibility_status,
|
||||
'requirements': requirement_statuses
|
||||
'requirements': OrderedDict(sorted(paired_requirements.items(), reverse=True))
|
||||
}
|
||||
else:
|
||||
credit_course = None
|
||||
|
||||
context = {
|
||||
'course': course,
|
||||
@@ -1089,7 +1096,8 @@ def _progress(request, course_key, student_id):
|
||||
'student': student,
|
||||
'passed': is_course_passed(course, grade_summary),
|
||||
'show_generate_cert_btn': show_generate_cert_btn,
|
||||
'credit_course': credit_course
|
||||
'credit_course_requirements': credit_course_requirements,
|
||||
'is_credit_course': is_course_credit,
|
||||
}
|
||||
|
||||
if show_generate_cert_btn:
|
||||
|
||||
@@ -2021,6 +2021,10 @@ FEATURES['CLASS_DASHBOARD'] = False
|
||||
if FEATURES.get('CLASS_DASHBOARD'):
|
||||
INSTALLED_APPS += ('class_dashboard',)
|
||||
|
||||
################ Enable credit eligibility feature ####################
|
||||
ENABLE_CREDIT_ELIGIBILITY = False
|
||||
FEATURES['ENABLE_CREDIT_ELIGIBILITY'] = ENABLE_CREDIT_ELIGIBILITY
|
||||
|
||||
######################## CAS authentication ###########################
|
||||
|
||||
if FEATURES.get('AUTH_USE_CAS'):
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
$(document).ready(function() {
|
||||
var container = $('.requirement-container');
|
||||
var collapse = container.data('eligible');
|
||||
if (collapse == 'not_eligible') {
|
||||
container.addClass('is-hidden');
|
||||
$('.detail-collapse').find('.fa').toggleClass('fa-caret-up fa-caret-down');
|
||||
$('.requirement-detail').text(gettext('More'));
|
||||
}
|
||||
$('.detail-collapse').on('click', function() {
|
||||
var el = $(this);
|
||||
$('.requirement-container').toggleClass('is-hidden');
|
||||
el.find('.fa').toggleClass('fa-caret-down fa-caret-up');
|
||||
container.toggleClass('is-hidden');
|
||||
el.find('.fa').toggleClass('fa-caret-up fa-caret-down');
|
||||
el.find('.requirement-detail').text(function(i, text){
|
||||
return text === gettext('More') ? gettext('Less') : gettext('More');
|
||||
return text === gettext('Less') ? gettext('More') : gettext('Less');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -215,11 +215,11 @@
|
||||
border-bottom: 1px solid $lightGrey;
|
||||
padding: lh(0.5);
|
||||
> .requirement-name {
|
||||
width: bi-app-invert-percentage(30%);
|
||||
width: bi-app-invert-percentage(40%);
|
||||
display: inline-block;
|
||||
}
|
||||
> .requirement-status{
|
||||
width: bi-app-invert-percentage(70%);
|
||||
width: bi-app-invert-percentage(60%);
|
||||
@include float(right);
|
||||
display: inline-block;
|
||||
.fa-times{
|
||||
@@ -231,7 +231,7 @@
|
||||
color: $success-color;
|
||||
}
|
||||
> .not-achieve{
|
||||
color: $lightGrey;
|
||||
color: $darkGrey;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, DEFAULT_LONG_DATE_FORMAT
|
||||
from util.date_utils import get_time_display, DEFAULT_SHORT_DATE_FORMAT
|
||||
from django.conf import settings
|
||||
from django.utils.http import urlquote_plus
|
||||
%>
|
||||
@@ -103,26 +103,27 @@ 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:
|
||||
% if is_credit_course:
|
||||
<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.")}
|
||||
%if credit_course_requirements['eligibility_status'] == 'not_eligible':
|
||||
<span class="eligibility_msg">${student.get_full_name()}, ${_("You are no longer eligible for this course.")}</span>
|
||||
%elif credit_course_requirements['eligibility_status'] == 'eligible':
|
||||
<span class="eligibility_msg">${student.get_full_name()}, ${_("You have met the requirements for credit in this course.")}
|
||||
${_("{link} to purchase course credit.").format(link="<a href={url}>{url_name}</a>".format(url = reverse('dashboard'), url_name = _('Go to your dashboard')))}
|
||||
</span>
|
||||
%elif credit_course['eligibility_status'] == 'partial_eligible':
|
||||
<span>${student.username}, ${_("You have not yet met the requirements for credit.")}</span>
|
||||
%elif credit_course_requirements['eligibility_status'] == 'partial_eligible':
|
||||
<span>${student.get_full_name()}, ${_("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-container" data-eligible="${credit_course_requirements['eligibility_status']}">
|
||||
%for namespace in credit_course_requirements['requirements']:
|
||||
%for requirement in credit_course_requirements['requirements'][namespace]:
|
||||
<div class="requirement">
|
||||
<div class="requirement-name">${_(requirement['name'])}</div>
|
||||
<div class="requirement-name">${_(requirement['display_name'])}</div>
|
||||
<div class="requirement-status">
|
||||
%if requirement['status']:
|
||||
%if requirement['status'] == 'submitted':
|
||||
@@ -132,7 +133,7 @@ from django.utils.http import urlquote_plus
|
||||
<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>
|
||||
<span>Verified on ${get_time_display(requirement['status_date'], DEFAULT_SHORT_DATE_FORMAT, settings.TIME_ZONE)}</span>
|
||||
%endif
|
||||
%else:
|
||||
<span class="not-achieve">${_("Upcoming")}</span>
|
||||
@@ -140,9 +141,10 @@ from django.utils.http import urlquote_plus
|
||||
</div>
|
||||
</div>
|
||||
%endfor
|
||||
%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 class="detail-collapse" aria-live="polite"><i class="fa fa-caret-up"></i>
|
||||
<span class="requirement-detail">${_("Less")}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -419,26 +419,23 @@ def get_credit_requirement_status(course_key, username):
|
||||
{
|
||||
"namespace": "reverification",
|
||||
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
|
||||
"display_name": "In Course Reverification",
|
||||
"criteria": {},
|
||||
"status": "satisfied",
|
||||
},
|
||||
{
|
||||
"namespace": "reverification",
|
||||
"name": "i4x://edX/DemoX/edx-reverification-block/assessment_uuid",
|
||||
"criteria": {},
|
||||
"status": "Not satisfied",
|
||||
"status": "failed",
|
||||
},
|
||||
{
|
||||
"namespace": "proctored_exam",
|
||||
"name": "i4x://edX/DemoX/proctoring-block/final_uuid",
|
||||
"display_name": "Proctored Mid Term Exam",
|
||||
"criteria": {},
|
||||
"status": "error",
|
||||
"status": "satisfied",
|
||||
},
|
||||
{
|
||||
"namespace": "grade",
|
||||
"name": "i4x://edX/DemoX/proctoring-block/final_uuid",
|
||||
"display_name": "Minimum Passing Grade",
|
||||
"criteria": {"min_grade": 0.8},
|
||||
"status": None,
|
||||
"status": "failed",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -454,6 +451,7 @@ def get_credit_requirement_status(course_key, username):
|
||||
statuses.append({
|
||||
"namespace": requirement.namespace,
|
||||
"name": requirement.name,
|
||||
"display_name": requirement.display_name,
|
||||
"criteria": requirement.criteria,
|
||||
"status": requirement_status.status if requirement_status else None,
|
||||
"status_date": requirement_status.modified if requirement_status else None,
|
||||
@@ -475,18 +473,6 @@ def is_user_eligible_for_credit(username, course_key):
|
||||
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_credit_requirement(course_key, namespace, name):
|
||||
"""Returns the requirement of a given course, namespace and name.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user