Merge remote-tracking branch 'edx/master' into opaque-keys-merge-master
Conflicts: cms/djangoapps/contentstore/tests/utils.py cms/djangoapps/contentstore/views/import_export.py cms/djangoapps/contentstore/views/tests/test_import_export.py common/djangoapps/student/views.py lms/djangoapps/class_dashboard/dashboard_data.py lms/djangoapps/instructor/views/instructor_dashboard.py lms/static/js/staff_debug_actions.js lms/templates/notes.html lms/templates/staff_problem_info.html
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
Computes the data to display on the Instructor Dashboard
|
||||
"""
|
||||
from util.json_request import JsonResponse
|
||||
import json
|
||||
|
||||
from courseware import models
|
||||
from django.db.models import Count
|
||||
@@ -22,9 +23,12 @@ def get_problem_grade_distribution(course_id):
|
||||
|
||||
`course_id` the course ID for the course interested in
|
||||
|
||||
Output is a dict, where the key is the problem 'module_id' and the value is a dict with:
|
||||
Output is 2 dicts:
|
||||
'prob-grade_distrib' where the key is the problem 'module_id' and the value is a dict with:
|
||||
'max_grade' - max grade for this problem
|
||||
'grade_distrib' - array of tuples (`grade`,`count`).
|
||||
'total_student_count' where the key is problem 'module_id' and the value is number of students
|
||||
attempting the problem
|
||||
"""
|
||||
|
||||
# Aggregate query on studentmodule table for grade data for all problems in course
|
||||
@@ -35,6 +39,7 @@ def get_problem_grade_distribution(course_id):
|
||||
).values('module_state_key', 'grade', 'max_grade').annotate(count_grade=Count('grade'))
|
||||
|
||||
prob_grade_distrib = {}
|
||||
total_student_count = {}
|
||||
|
||||
# Loop through resultset building data for each problem
|
||||
for row in db_query:
|
||||
@@ -54,7 +59,10 @@ def get_problem_grade_distribution(course_id):
|
||||
'grade_distrib': [(row['grade'], row['count_grade'])]
|
||||
}
|
||||
|
||||
return prob_grade_distrib
|
||||
# Build set of total students attempting each problem
|
||||
total_student_count[curr_problem] = total_student_count.get(curr_problem, 0) + row['count_grade']
|
||||
|
||||
return prob_grade_distrib, total_student_count
|
||||
|
||||
|
||||
def get_sequential_open_distrib(course_id):
|
||||
@@ -139,7 +147,7 @@ def get_d3_problem_grade_distrib(course_id):
|
||||
'data' - data for the d3_stacked_bar_graph function of the grade distribution for that problem
|
||||
"""
|
||||
|
||||
prob_grade_distrib = get_problem_grade_distribution(course_id)
|
||||
prob_grade_distrib, total_student_count = get_problem_grade_distribution(course_id)
|
||||
d3_data = []
|
||||
|
||||
# Retrieve course object down to problems
|
||||
@@ -181,19 +189,24 @@ def get_d3_problem_grade_distrib(course_id):
|
||||
for (grade, count_grade) in problem_info['grade_distrib']:
|
||||
percent = 0.0
|
||||
if max_grade > 0:
|
||||
percent = (grade * 100.0) / max_grade
|
||||
percent = round((grade * 100.0) / max_grade, 1)
|
||||
|
||||
# Construct tooltip for problem in grade distibution view
|
||||
tooltip = _("{label} {problem_name} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})").format(
|
||||
label=label,
|
||||
problem_name=problem_name,
|
||||
count_grade=count_grade,
|
||||
students=_("students"),
|
||||
percent=percent,
|
||||
grade=grade,
|
||||
max_grade=max_grade,
|
||||
questions=_("questions"),
|
||||
)
|
||||
# Compute percent of students with this grade
|
||||
student_count_percent = 0
|
||||
if total_student_count.get(child.location.url(), 0) > 0:
|
||||
student_count_percent = count_grade * 100 / total_student_count[child.location.url()]
|
||||
|
||||
# Tooltip parameters for problem in grade distribution view
|
||||
tooltip = {
|
||||
'type': 'problem',
|
||||
'label': label,
|
||||
'problem_name': problem_name,
|
||||
'count_grade': count_grade,
|
||||
'percent': percent,
|
||||
'grade': grade,
|
||||
'max_grade': max_grade,
|
||||
'student_count_percent': student_count_percent,
|
||||
}
|
||||
|
||||
# Construct data to be sent to d3
|
||||
stack_data.append({
|
||||
@@ -249,11 +262,14 @@ def get_d3_sequential_open_distrib(course_id):
|
||||
num_students = sequential_open_distrib[subsection.location]
|
||||
|
||||
stack_data = []
|
||||
tooltip = _("{num_students} student(s) opened Subsection {subsection_num}: {subsection_name}").format(
|
||||
num_students=num_students,
|
||||
subsection_num=c_subsection,
|
||||
subsection_name=subsection_name,
|
||||
)
|
||||
|
||||
# Tooltip parameters for subsection in open_distribution view
|
||||
tooltip = {
|
||||
'type': 'subsection',
|
||||
'num_students': num_students,
|
||||
'subsection_num': c_subsection,
|
||||
'subsection_name': subsection_name
|
||||
}
|
||||
|
||||
stack_data.append({
|
||||
'color': 0,
|
||||
@@ -332,19 +348,18 @@ def get_d3_section_grade_distrib(course_id, section):
|
||||
for (grade, count_grade) in grade_distrib[problem]['grade_distrib']:
|
||||
percent = 0.0
|
||||
if max_grade > 0:
|
||||
percent = (grade * 100.0) / max_grade
|
||||
percent = round((grade * 100.0) / max_grade, 1)
|
||||
|
||||
# Construct tooltip for problem in grade distibution view
|
||||
tooltip = _("{problem_info_x} {problem_info_n} - {count_grade} {students} ({percent:.0f}%: {grade:.0f}/{max_grade:.0f} {questions})").format(
|
||||
problem_info_x=problem_info[problem]['x_value'],
|
||||
count_grade=count_grade,
|
||||
students=_("students"),
|
||||
percent=percent,
|
||||
problem_info_n=problem_info[problem]['display_name'],
|
||||
grade=grade,
|
||||
max_grade=max_grade,
|
||||
questions=_("questions"),
|
||||
)
|
||||
tooltip = {
|
||||
'type': 'problem',
|
||||
'problem_info_x': problem_info[problem]['x_value'],
|
||||
'count_grade': count_grade,
|
||||
'percent': percent,
|
||||
'problem_info_n': problem_info[problem]['display_name'],
|
||||
'grade': grade,
|
||||
'max_grade': max_grade,
|
||||
}
|
||||
|
||||
stack_data.append({
|
||||
'color': percent,
|
||||
@@ -418,7 +433,12 @@ def get_students_opened_subsection(request, csv=False):
|
||||
If 'csv' is True, returns a header array, and an array of arrays in the format:
|
||||
student names, usernames for CSV download.
|
||||
"""
|
||||
<<<<<<< HEAD
|
||||
module_state_key = Location.from_deprecated_string(request.GET.get('module_id'))
|
||||
=======
|
||||
|
||||
module_id = request.GET.get('module_id')
|
||||
>>>>>>> edx/master
|
||||
csv = request.GET.get('csv')
|
||||
|
||||
# Query for "opened a subsection" students
|
||||
@@ -450,9 +470,11 @@ def get_students_opened_subsection(request, csv=False):
|
||||
return JsonResponse(response_payload)
|
||||
else:
|
||||
tooltip = request.GET.get('tooltip')
|
||||
filename = sanitize_filename(tooltip[tooltip.index('S'):])
|
||||
|
||||
header = ['Name', 'Username']
|
||||
# Subsection name is everything after 3rd space in tooltip
|
||||
filename = sanitize_filename(' '.join(tooltip.split(' ')[3:]))
|
||||
|
||||
header = [_("Name").encode('utf-8'), _("Username").encode('utf-8')]
|
||||
for student in students:
|
||||
results.append([student['student__profile__name'], student['student__username']])
|
||||
|
||||
@@ -510,7 +532,7 @@ def get_students_problem_grades(request, csv=False):
|
||||
tooltip = request.GET.get('tooltip')
|
||||
filename = sanitize_filename(tooltip[:tooltip.rfind(' - ')])
|
||||
|
||||
header = ['Name', 'Username', 'Grade', 'Percent']
|
||||
header = [_("Name").encode('utf-8'), _("Username").encode('utf-8'), _("Grade").encode('utf-8'), _("Percent").encode('utf-8')]
|
||||
for student in students:
|
||||
|
||||
percent = 0
|
||||
@@ -522,11 +544,60 @@ def get_students_problem_grades(request, csv=False):
|
||||
return response
|
||||
|
||||
|
||||
def post_metrics_data_csv(request):
|
||||
"""
|
||||
Generate a list of opened subsections or problems for the entire course for CSV download.
|
||||
Returns a header array, and an array of arrays in the format:
|
||||
section, subsection, count of students for subsections
|
||||
or section, problem, name, count of students, percent of students, score for problems.
|
||||
"""
|
||||
|
||||
data = json.loads(request.POST['data'])
|
||||
sections = json.loads(data['sections'])
|
||||
tooltips = json.loads(data['tooltips'])
|
||||
course_id = data['course_id']
|
||||
data_type = data['data_type']
|
||||
|
||||
results = []
|
||||
if data_type == 'subsection':
|
||||
header = [_("Section").encode('utf-8'), _("Subsection").encode('utf-8'), _("Opened by this number of students").encode('utf-8')]
|
||||
filename = sanitize_filename(_('subsections') + '_' + course_id)
|
||||
elif data_type == 'problem':
|
||||
header = [_("Section").encode('utf-8'), _("Problem").encode('utf-8'), _("Name").encode('utf-8'), _("Count of Students").encode('utf-8'), _("% of Students").encode('utf-8'), _("Score").encode('utf-8')]
|
||||
filename = sanitize_filename(_('problems') + '_' + course_id)
|
||||
|
||||
for index, section in enumerate(sections):
|
||||
results.append([section])
|
||||
|
||||
# tooltips array is array of dicts for subsections and
|
||||
# array of array of dicts for problems.
|
||||
if data_type == 'subsection':
|
||||
for tooltip_dict in tooltips[index]:
|
||||
num_students = tooltip_dict['num_students']
|
||||
subsection = tooltip_dict['subsection_name']
|
||||
# Append to results offsetting 1 column to the right.
|
||||
results.append(['', subsection, num_students])
|
||||
|
||||
elif data_type == 'problem':
|
||||
for tooltip in tooltips[index]:
|
||||
for tooltip_dict in tooltip:
|
||||
label = tooltip_dict['label']
|
||||
problem_name = tooltip_dict['problem_name']
|
||||
count_grade = tooltip_dict['count_grade']
|
||||
student_count_percent = tooltip_dict['student_count_percent']
|
||||
percent = tooltip_dict['percent']
|
||||
# Append to results offsetting 1 column to the right.
|
||||
results.append(['', label, problem_name, count_grade, student_count_percent, percent])
|
||||
|
||||
response = create_csv_response(filename, header, results)
|
||||
return response
|
||||
|
||||
|
||||
def sanitize_filename(filename):
|
||||
"""
|
||||
Utility function
|
||||
"""
|
||||
filename = filename.replace(" ", "_")
|
||||
filename = filename.encode('ascii')
|
||||
filename = filename.encode('utf-8')
|
||||
filename = filename[0:25] + '.csv'
|
||||
return filename
|
||||
|
||||
@@ -94,12 +94,15 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
|
||||
|
||||
def test_get_problem_grade_distribution(self):
|
||||
|
||||
prob_grade_distrib = get_problem_grade_distribution(self.course.id)
|
||||
prob_grade_distrib, total_student_count = get_problem_grade_distribution(self.course.id)
|
||||
|
||||
for problem in prob_grade_distrib:
|
||||
max_grade = prob_grade_distrib[problem]['max_grade']
|
||||
self.assertEquals(1, max_grade)
|
||||
|
||||
for val in total_student_count.values():
|
||||
self.assertEquals(USER_COUNT, val)
|
||||
|
||||
def test_get_sequential_open_distibution(self):
|
||||
|
||||
sequential_open_distrib = get_sequential_open_distrib(self.course.id)
|
||||
@@ -242,6 +245,61 @@ class TestGetProblemGradeDistribution(ModuleStoreTestCase):
|
||||
# Check response contains 1 line for each user +1 for the header
|
||||
self.assertEquals(USER_COUNT + 1, len(response.content.splitlines()))
|
||||
|
||||
def test_post_metrics_data_subsections_csv(self):
|
||||
|
||||
url = reverse('post_metrics_data_csv')
|
||||
|
||||
sections = json.dumps(["Introduction"])
|
||||
tooltips = json.dumps([[{"subsection_name": "Pre-Course Survey", "subsection_num": 1, "type": "subsection", "num_students": 18963}]])
|
||||
course_id = self.course.id
|
||||
data_type = 'subsection'
|
||||
|
||||
data = json.dumps({'sections': sections,
|
||||
'tooltips': tooltips,
|
||||
'course_id': course_id,
|
||||
'data_type': data_type,
|
||||
})
|
||||
|
||||
response = self.client.post(url, {'data': data})
|
||||
# Check response contains 1 line for header, 1 line for Section and 1 line for Subsection
|
||||
self.assertEquals(3, len(response.content.splitlines()))
|
||||
|
||||
def test_post_metrics_data_problems_csv(self):
|
||||
|
||||
url = reverse('post_metrics_data_csv')
|
||||
|
||||
sections = json.dumps(["Introduction"])
|
||||
tooltips = json.dumps([[[
|
||||
{'student_count_percent': 0,
|
||||
'problem_name': 'Q1',
|
||||
'grade': 0,
|
||||
'percent': 0,
|
||||
'label': 'P1.2.1',
|
||||
'max_grade': 1,
|
||||
'count_grade': 26,
|
||||
'type': u'problem'},
|
||||
{'student_count_percent': 99,
|
||||
'problem_name': 'Q1',
|
||||
'grade': 1,
|
||||
'percent': 100,
|
||||
'label': 'P1.2.1',
|
||||
'max_grade': 1,
|
||||
'count_grade': 4763,
|
||||
'type': 'problem'},
|
||||
]]])
|
||||
course_id = self.course.id
|
||||
data_type = 'problem'
|
||||
|
||||
data = json.dumps({'sections': sections,
|
||||
'tooltips': tooltips,
|
||||
'course_id': course_id,
|
||||
'data_type': data_type,
|
||||
})
|
||||
|
||||
response = self.client.post(url, {'data': data})
|
||||
# Check response contains 1 line for header, 1 line for Sections and 2 lines for problems
|
||||
self.assertEquals(4, len(response.content.splitlines()))
|
||||
|
||||
def test_get_section_display_name(self):
|
||||
|
||||
section_display_name = get_section_display_name(self.course.id)
|
||||
|
||||
30
lms/djangoapps/class_dashboard/urls.py
Normal file
30
lms/djangoapps/class_dashboard/urls.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""
|
||||
Class Dashboard API endpoint urls.
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
urlpatterns = patterns('', # nopep8
|
||||
# Json request data for metrics for entire course
|
||||
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/all_sequential_open_distrib$',
|
||||
'class_dashboard.views.all_sequential_open_distrib', name="all_sequential_open_distrib"),
|
||||
|
||||
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/all_problem_grade_distribution$',
|
||||
'class_dashboard.views.all_problem_grade_distribution', name="all_problem_grade_distribution"),
|
||||
|
||||
# Json request data for metrics for particular section
|
||||
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/problem_grade_distribution/(?P<section>\d+)$',
|
||||
'class_dashboard.views.section_problem_grade_distrib', name="section_problem_grade_distrib"),
|
||||
|
||||
# For listing students that opened a sub-section
|
||||
url(r'^get_students_opened_subsection$',
|
||||
'class_dashboard.dashboard_data.get_students_opened_subsection', name="get_students_opened_subsection"),
|
||||
|
||||
# For listing of students' grade per problem
|
||||
url(r'^get_students_problem_grades$',
|
||||
'class_dashboard.dashboard_data.get_students_problem_grades', name="get_students_problem_grades"),
|
||||
|
||||
# For generating metrics data as a csv
|
||||
url(r'^post_metrics_data_csv_url',
|
||||
'class_dashboard.dashboard_data.post_metrics_data_csv', name="post_metrics_data_csv"),
|
||||
)
|
||||
@@ -255,10 +255,17 @@ def _section_metrics(course_key, access):
|
||||
'section_key': 'metrics',
|
||||
'section_display_name': ('Metrics'),
|
||||
'access': access,
|
||||
<<<<<<< HEAD
|
||||
'sub_section_display_name': get_section_display_name(course_key),
|
||||
'section_has_problem': get_array_section_has_problem(course_key),
|
||||
=======
|
||||
'course_id': course_id,
|
||||
'sub_section_display_name': get_section_display_name(course_id),
|
||||
'section_has_problem': get_array_section_has_problem(course_id),
|
||||
>>>>>>> edx/master
|
||||
'get_students_opened_subsection_url': reverse('get_students_opened_subsection'),
|
||||
'get_students_problem_grades_url': reverse('get_students_problem_grades'),
|
||||
'post_metrics_data_csv_url': reverse('post_metrics_data_csv'),
|
||||
}
|
||||
return section_data
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ from edxmako.shortcuts import render_to_response
|
||||
from courseware.courses import get_course_with_access
|
||||
from notes.models import Note
|
||||
from notes.utils import notes_enabled_for_course
|
||||
from xmodule.annotator_token import retrieve_token
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -23,8 +22,7 @@ def notes(request, course_id):
|
||||
'course': course,
|
||||
'notes': notes,
|
||||
'student': student,
|
||||
'storage': storage,
|
||||
'token': retrieve_token(student.email, course.annotation_token_secret),
|
||||
'storage': storage
|
||||
}
|
||||
|
||||
return render_to_response('notes.html', context)
|
||||
|
||||
@@ -828,7 +828,6 @@ main_vendor_js = [
|
||||
'js/vendor/swfobject/swfobject.js',
|
||||
'js/vendor/jquery.ba-bbq.min.js',
|
||||
'js/vendor/ova/annotator-full.js',
|
||||
'js/vendor/ova/annotator-full-firebase-auth.js',
|
||||
'js/vendor/ova/video.dev.js',
|
||||
'js/vendor/ova/vjs.youtube.js',
|
||||
'js/vendor/ova/rangeslider.js',
|
||||
|
||||
@@ -114,6 +114,7 @@ var StaffDebug = (function(){
|
||||
|
||||
// Register click handlers
|
||||
$(document).ready(function() {
|
||||
<<<<<<< HEAD
|
||||
$('#staff-debug-reset').click(function() {
|
||||
StaffDebug.reset($(this).parent().data('location-name'), $(this).parent().data('location'));
|
||||
return false;
|
||||
@@ -124,6 +125,18 @@ $(document).ready(function() {
|
||||
});
|
||||
$('#staff-debug-rescore').click(function() {
|
||||
StaffDebug.rescore($(this).parent().data('location-name'), $(this).parent().data('location'));
|
||||
=======
|
||||
$('.staff-debug-reset').click(function() {
|
||||
StaffDebug.reset($(this).data('location'));
|
||||
return false;
|
||||
});
|
||||
$('.staff-debug-sdelete').click(function() {
|
||||
StaffDebug.sdelete($(this).data('location'));
|
||||
return false;
|
||||
});
|
||||
$('.staff-debug-rescore').click(function() {
|
||||
StaffDebug.rescore($(this).data('location'));
|
||||
>>>>>>> edx/master
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -591,17 +591,16 @@ section.instructor-dashboard-content-2 {
|
||||
|
||||
.instructor-dashboard-wrapper-2 section.idash-section#metrics {
|
||||
|
||||
.metrics-container {
|
||||
.metrics-container, .metrics-header-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
float: left;
|
||||
clear: both;
|
||||
margin-top: 25px;
|
||||
|
||||
.metrics-left {
|
||||
|
||||
.metrics-left, .metrics-left-header {
|
||||
position: relative;
|
||||
width: 30%;
|
||||
height: 640px;
|
||||
float: left;
|
||||
margin-right: 2.5%;
|
||||
|
||||
@@ -609,10 +608,13 @@ section.instructor-dashboard-content-2 {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.metrics-right {
|
||||
.metrics-section.metrics-left {
|
||||
height: 640px;
|
||||
}
|
||||
|
||||
.metrics-right, .metrics-right-header {
|
||||
position: relative;
|
||||
width: 65%;
|
||||
height: 295px;
|
||||
float: left;
|
||||
margin-left: 2.5%;
|
||||
margin-bottom: 25px;
|
||||
@@ -622,6 +624,10 @@ section.instructor-dashboard-content-2 {
|
||||
}
|
||||
}
|
||||
|
||||
.metrics-section.metrics-right {
|
||||
height: 295px;
|
||||
}
|
||||
|
||||
svg {
|
||||
.stacked-bar {
|
||||
cursor: pointer;
|
||||
@@ -718,10 +724,6 @@ section.instructor-dashboard-content-2 {
|
||||
border-radius: 5px;
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
input#graph_reload {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%page args="id_opened_prefix, id_grade_prefix, id_attempt_prefix, id_tooltip_prefix, course_id, **kwargs"/>
|
||||
<%page args="id_opened_prefix, id_grade_prefix, id_attempt_prefix, id_tooltip_prefix, course_id, allSubsectionTooltipArr, allProblemTooltipArr, **kwargs"/>
|
||||
<%!
|
||||
import json
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -30,6 +30,13 @@ $(function () {
|
||||
margin: {left:0},
|
||||
};
|
||||
|
||||
// Construct array of tooltips for all sections for the "Download Subsection Data" button.
|
||||
var sectionTooltipArr = new Array();
|
||||
paramOpened.data.forEach( function(element, index, array) {
|
||||
sectionTooltipArr[index] = element.stackData[0].tooltip;
|
||||
});
|
||||
allSubsectionTooltipArr[i] = sectionTooltipArr;
|
||||
|
||||
barGraphOpened = edx_d3CreateStackedBarGraph(paramOpened, d3.select(curr_id).append("svg"),
|
||||
d3.select("#${id_tooltip_prefix}"+i));
|
||||
barGraphOpened.scale.stackColor.range(["#555555","#555555"]);
|
||||
@@ -68,6 +75,17 @@ $(function () {
|
||||
bVerticalXAxisLabel : true,
|
||||
};
|
||||
|
||||
// Construct array of tooltips for all sections for the "Download Problem Data" button.
|
||||
var sectionTooltipArr = new Array();
|
||||
paramGrade.data.forEach( function(element, index, array) {
|
||||
var stackDataArr = new Array();
|
||||
for (var j = 0; j < element.stackData.length; j++) {
|
||||
stackDataArr[j] = element.stackData[j].tooltip
|
||||
}
|
||||
sectionTooltipArr[index] = stackDataArr;
|
||||
});
|
||||
allProblemTooltipArr[i] = sectionTooltipArr;
|
||||
|
||||
barGraphGrade = edx_d3CreateStackedBarGraph(paramGrade, d3.select(curr_id).append("svg"),
|
||||
d3.select("#${id_tooltip_prefix}"+i));
|
||||
barGraphGrade.scale.stackColor.domain([0,50,100]).range(["#e13f29","#cccccc","#17a74d"]);
|
||||
@@ -83,6 +101,7 @@ $(function () {
|
||||
|
||||
i+=1;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -349,8 +349,20 @@ edx_d3CreateStackedBarGraph = function(parameters, svg, divTooltip) {
|
||||
var top = pos[1]-10;
|
||||
var width = $('#'+graph.divTooltip.attr("id")).width();
|
||||
|
||||
// Construct the tooltip
|
||||
if (d.tooltip['type'] == 'subsection') {
|
||||
tooltip_str = d.tooltip['num_students'] + ' ' + gettext('student(s) opened Subsection') + ' ' \
|
||||
+ d.tooltip['subsection_num'] + ': ' + d.tooltip['subsection_name']
|
||||
}else if (d.tooltip['type'] == 'problem') {
|
||||
tooltip_str = d.tooltip['label'] + ' ' + d.tooltip['problem_name'] + ' - ' \
|
||||
+ d.tooltip['count_grade'] + ' ' + gettext('students') + ' (' \
|
||||
+ d.tooltip['student_count_percent'] + '%) (' + \
|
||||
+ d.tooltip['percent'] + '%: ' + \
|
||||
+ d.tooltip['grade'] +'/' + d.tooltip['max_grade'] + ' '
|
||||
+ gettext('questions') + ')'
|
||||
}
|
||||
graph.divTooltip.style("visibility", "visible")
|
||||
.text(d.tooltip);
|
||||
.text(tooltip_str);
|
||||
|
||||
if ((left+width+30) > $("#"+graph.divTooltip.node().parentNode.id).width())
|
||||
left -= (width+30);
|
||||
|
||||
@@ -17,6 +17,13 @@
|
||||
<%static:css group='style-vendor-tinymce-skin'/>
|
||||
<%static:css group='style-course'/>
|
||||
|
||||
<script type="text/javascript">
|
||||
// This is a hack to get tinymce to work correctly in Firefox until the annotator tool is refactored to not include
|
||||
// tinymce globally.
|
||||
if(typeof window.Range.prototype === "undefined") {
|
||||
window.Range.prototype = { };
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.axislabels.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/jquery-jvectormap-1.1.1/jquery-jvectormap-1.1.1.min.js')}"></script>
|
||||
@@ -715,7 +722,9 @@ function goto( mode)
|
||||
</div>
|
||||
%endfor
|
||||
<script>
|
||||
${all_section_metrics.body("metric_opened_","metric_grade_","metric_attempts_","metric_tooltip_",course.id)}
|
||||
var allSubsectionTooltipArr = new Array();
|
||||
var allProblemTooltipArr = new Array();
|
||||
${all_section_metrics.body("metric_opened_","metric_grade_","metric_attempts_","metric_tooltip_",course.id, allSubsectionTooltipArr, allProblemTooltipArr)}
|
||||
</script>
|
||||
|
||||
%endif
|
||||
|
||||
@@ -25,6 +25,13 @@
|
||||
<%static:css group='style-vendor-tinymce-content'/>
|
||||
<%static:css group='style-vendor-tinymce-skin'/>
|
||||
<%static:css group='style-course'/>
|
||||
<script type="text/javascript">
|
||||
// This is a hack to get tinymce to work correctly in Firefox until the annotator tool is refactored to not include
|
||||
// tinymce globally.
|
||||
if(typeof window.Range.prototype === "undefined") {
|
||||
window.Range.prototype = { };
|
||||
}
|
||||
</script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/underscore-min.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/mustache.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('js/vendor/flot/jquery.flot.js')}"></script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<%page args="section_data"/>
|
||||
|
||||
@@ -11,19 +11,35 @@
|
||||
%else:
|
||||
<%namespace name="d3_stacked_bar_graph" file="/class_dashboard/d3_stacked_bar_graph.js"/>
|
||||
<%namespace name="all_section_metrics" file="/class_dashboard/all_section_metrics.js"/>
|
||||
|
||||
<h3 class="attention" id="graph_load">${_("Loading the latest graphs for you; depending on your class size, this may take a few minutes.")}</h3>
|
||||
<input type="button" id="graph_reload" value="${_("Reload Graphs")}" />
|
||||
<div id="graph_reload">
|
||||
<p>${_("Use Reload Graphs to refresh the graphs.")}</p>
|
||||
<p><input type="button" value="${_("Reload Graphs")}"/></p>
|
||||
</div>
|
||||
<div class="metrics-header-container">
|
||||
<div class="metrics-left-header">
|
||||
<h2>${_("Subsection Data")}</h2>
|
||||
<p>${_("Each bar shows the number of students that opened the subsection.")}</p>
|
||||
<p>${_("You can click on any of the bars to list the students that opened the subsection.")}</p>
|
||||
<p>${_("You can also download this data as a CSV file.")}</p>
|
||||
<p><input type="button" id="download_subsection_data" value="${_("Download Subsection Data for all Subsections as a CSV")}" /></p>
|
||||
</div>
|
||||
<div class="metrics-right-header">
|
||||
<h2>${_("Grade Distribution Data")}</h2>
|
||||
<p>${_("Each bar shows the grade distribution for that problem.")}</p>
|
||||
<p>${_("You can click on any of the bars to list the students that attempted the problem, along with the grades they received.")}</p>
|
||||
<p>${_("You can also download this data as a CSV file.")}</p>
|
||||
<p><input type="button" id="download_problem_data" value="${_("Download Problem Data for all Problems as a CSV")}" /></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- For each section with data, create the divs for displaying the graphs
|
||||
and the popup window for listing the students
|
||||
-->
|
||||
%for i in range(0, len(section_data['sub_section_display_name'])):
|
||||
<div class="metrics-container" id="metrics_section_${i}">
|
||||
<h2>${_("Section:")} ${section_data['sub_section_display_name'][i]}</h2>
|
||||
<h2>${_("Section")}: ${section_data['sub_section_display_name'][i]}</h2>
|
||||
<div class="metrics-tooltip" id="metric_tooltip_${i}"></div>
|
||||
<div class="metrics-section metrics-left" id="metric_opened_${i}">
|
||||
<h3>${_("Count of Students Opened a Subsection")}</h3>
|
||||
</div>
|
||||
<div class="metrics-section metrics-right" id="metric_grade_${i}" data-section-has-problem=${section_data['section_has_problem'][i]}>
|
||||
<h3>${_("Grade Distribution per Problem")}</h3>
|
||||
@@ -46,11 +62,91 @@
|
||||
<script>
|
||||
$(function () {
|
||||
var firstLoad = true;
|
||||
var allSubsectionTooltipArr = new Array();
|
||||
var allProblemTooltipArr = new Array();
|
||||
|
||||
// Click handler for left bars
|
||||
$('.metrics-container').on("click", '.metrics-left .stacked-bar', function () {
|
||||
var module_id = $('rect', this).attr('id');
|
||||
var metrics_overlay = $(this).closest('.metrics-left').siblings('.metrics-overlay');
|
||||
|
||||
// Set module_id attribute on metrics_overlay
|
||||
metrics_overlay.data("module-id", module_id);
|
||||
|
||||
var header = $(this).closest('.metrics-left').siblings('.metrics-tooltip').text();
|
||||
var overlay_content = '<h3 class="metrics-overlay-title">' + header + '</h3>';
|
||||
$('.metrics-overlay-content', metrics_overlay).before(overlay_content);
|
||||
|
||||
$.ajax({
|
||||
url: "${section_data['get_students_opened_subsection_url']}",
|
||||
type: "GET",
|
||||
data: {module_id: module_id},
|
||||
dataType: "json",
|
||||
|
||||
success: function(response) {
|
||||
overlay_content = "<tr class='header'><th>${_('Name')}</th><th>${_('Username')}</th></tr>";
|
||||
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
|
||||
|
||||
$.each(response.results, function(index, value ){
|
||||
overlay_content = '<tr><td>' + value['name'] + "</td><td>" + value['username'] + '</td></tr>';
|
||||
$('.metrics-overlay-content tbody', metrics_overlay).append(overlay_content);
|
||||
});
|
||||
// If student list too long, append message to screen.
|
||||
if (response.max_exceeded) {
|
||||
overlay_content = "<p class='overflow-message'>${_('This is a partial list, to view all students download as a csv.')}</p>";
|
||||
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
|
||||
}
|
||||
}
|
||||
})
|
||||
metrics_overlay.find('.metrics-student-opened').show();
|
||||
metrics_overlay.show();
|
||||
});
|
||||
|
||||
// Click handler for right bars
|
||||
$('.metrics-container').on("click", '.metrics-right .stacked-bar', function () {
|
||||
var module_id = $('rect', this).attr('id');
|
||||
var metrics_overlay = $(this).closest('.metrics-right').siblings('.metrics-overlay');
|
||||
|
||||
//Set module_id attribute on metrics_overlay
|
||||
metrics_overlay.data("module-id", module_id);
|
||||
|
||||
var header = $(this).closest('.metrics-right').siblings('.metrics-tooltip').text();
|
||||
var far_index = header.indexOf(' - ');
|
||||
var title = header.substring(0, far_index);
|
||||
|
||||
var overlay_content = '<h3 class="metrics-overlay-title">' + title + '</h3>';
|
||||
$('.metrics-overlay-content', metrics_overlay).before(overlay_content);
|
||||
|
||||
$.ajax({
|
||||
url: "${section_data['get_students_problem_grades_url']}",
|
||||
type: "GET",
|
||||
data: {module_id: module_id},
|
||||
dataType: "json",
|
||||
|
||||
success: function(response) {
|
||||
overlay_content = "<tr class='header'><th>${_('Name')}</th><th>${_('Username')}</th><th>${_('Grade')}</th><th>${_('Percent')}</th></tr>";
|
||||
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
|
||||
|
||||
$.each(response.results, function(index, value ){
|
||||
overlay_content = '<tr><td>' + value['name'] + "</td><td>" + value['username'] + "</td><td>" + value['grade'] + "</td><td>" + value['percent'] + '</td></tr>';
|
||||
$('.metrics-overlay-content tbody', metrics_overlay).append(overlay_content);
|
||||
});
|
||||
// If student list too long, append message to screen.
|
||||
if (response.max_exceeded) {
|
||||
overlay_content = "<p class='overflow-message'>${_('This is a partial list, to view all students download as a csv.')}</p>";
|
||||
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
|
||||
}
|
||||
},
|
||||
})
|
||||
metrics_overlay.find('.metrics-student-grades').show();
|
||||
metrics_overlay.show();
|
||||
});
|
||||
|
||||
loadGraphs = function() {
|
||||
$('#graph_load').show();
|
||||
$('#graph_reload').hide();
|
||||
$('.metrics-header-container').hide();
|
||||
$('.loading').remove();
|
||||
|
||||
|
||||
var nothingText = "${_('There are no problems in this section.')}";
|
||||
var loadingText = "${_('Loading...')}";
|
||||
@@ -71,103 +167,87 @@
|
||||
});
|
||||
$('.metrics-left svg, .metrics-right svg').remove();
|
||||
|
||||
${all_section_metrics.body("metric_opened_", "metric_grade_", "metric_attempts_", "metric_tooltip_", course.id)}
|
||||
|
||||
setTimeout(function() {
|
||||
$('#graph_load, #graph_reload').toggle();
|
||||
$('.metrics-left .stacked-bar').on("click", function () {
|
||||
var module_id = $('rect', this).attr('id');
|
||||
var metrics_overlay = $(this).closest('.metrics-left').siblings('.metrics-overlay');
|
||||
|
||||
// Set module_id attribute on metrics_overlay
|
||||
metrics_overlay.data("module-id", module_id);
|
||||
|
||||
var header = $(this).closest('.metrics-left').siblings('.metrics-tooltip').text();
|
||||
var overlay_content = '<h3 class="metrics-overlay-title">' + header + '</h3>';
|
||||
$('.metrics-overlay-content', metrics_overlay).before(overlay_content);
|
||||
${all_section_metrics.body("metric_opened_", "metric_grade_", "metric_attempts_", "metric_tooltip_", course.id, allSubsectionTooltipArr, allProblemTooltipArr)}
|
||||
}
|
||||
|
||||
// For downloading subsection and problem data as csv
|
||||
download_csv_data = function(event) {
|
||||
|
||||
$.ajax({
|
||||
url: "${section_data['get_students_opened_subsection_url']}",
|
||||
type: "GET",
|
||||
data: {module_id: module_id},
|
||||
dataType: "json",
|
||||
var allSectionArr = []
|
||||
var allTooltipArr = []
|
||||
if (event.type == 'subsection') {
|
||||
allTooltipArr = allSubsectionTooltipArr;
|
||||
} else if (event.type == 'problem') {
|
||||
allTooltipArr = allProblemTooltipArr;
|
||||
}
|
||||
allTooltipArr.forEach( function(element, index, array) {
|
||||
|
||||
success: function(response) {
|
||||
overlay_content = '<tr class="header"><th>${_("Name")}</th><th>${_("Username")}</th></tr>';
|
||||
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
|
||||
|
||||
$.each(response.results, function(index, value ){
|
||||
overlay_content = '<tr><td>' + value['name'] + "</td><td>" + value['username'] + '</td></tr>';
|
||||
$('.metrics-overlay-content tbody', metrics_overlay).append(overlay_content);
|
||||
});
|
||||
// If student list too long, append message to screen.
|
||||
if (response.max_exceeded) {
|
||||
overlay_content = '<p class="overflow-message">${_("This is a partial list, to view all students download as a csv.")}</p>';
|
||||
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
|
||||
}
|
||||
}
|
||||
})
|
||||
metrics_overlay.find('.metrics-student-opened').show();
|
||||
metrics_overlay.show();
|
||||
});
|
||||
|
||||
$('.metrics-right .stacked-bar').on("click",function () {
|
||||
var module_id = $('rect', this).attr('id');
|
||||
var metrics_overlay = $(this).closest('.metrics-right').siblings('.metrics-overlay');
|
||||
|
||||
//Set module_id attribute on metrics_overlay
|
||||
metrics_overlay.data("module-id", module_id);
|
||||
|
||||
var header = $(this).closest('.metrics-right').siblings('.metrics-tooltip').text();
|
||||
var far_index = header.indexOf(' students (');
|
||||
var near_index = header.substr(0, far_index).lastIndexOf(' ') + 1;
|
||||
var title = header.substring(0, near_index -3);
|
||||
|
||||
var overlay_content = '<h3 class="metrics-overlay-title">' + title + '</h3>';
|
||||
$('.metrics-overlay-content', metrics_overlay).before(overlay_content);
|
||||
var metrics_section = 'metrics_section' + '_' + index
|
||||
// Get Section heading which is everything after first ': '
|
||||
var heading = $('#' + metrics_section).children('h2').text();
|
||||
allSectionArr[index] = heading.substr(heading.indexOf(': ') +2)
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
url: "${section_data['get_students_problem_grades_url']}",
|
||||
type: "GET",
|
||||
data: {module_id: module_id},
|
||||
dataType: "json",
|
||||
|
||||
success: function(response) {
|
||||
overlay_content = '<tr class="header"><th>${_("Name")}</th><th>${_("Username")}</th><th>${_("Grade")}</th><th>${_("Percent")}</th></tr>';
|
||||
$('.metrics-overlay-content thead', metrics_overlay).append(overlay_content);
|
||||
|
||||
$.each(response.results, function(index, value ){
|
||||
overlay_content = '<tr><td>' + value['name'] + "</td><td>" + value['username'] + "</td><td>" + value['grade'] + "</td><td>" + value['percent'] + '</td></tr>';
|
||||
$('.metrics-overlay-content tbody', metrics_overlay).append(overlay_content);
|
||||
});
|
||||
// If student list too long, append message to screen.
|
||||
if (response.max_exceeded) {
|
||||
overlay_content = '<p class="overflow-message">${_("This is a partial list, to view all students download as a csv.")}</p>';
|
||||
$('.metrics-overlay-content', metrics_overlay).after(overlay_content);
|
||||
}
|
||||
},
|
||||
})
|
||||
metrics_overlay.find('.metrics-student-grades').show();
|
||||
metrics_overlay.show();
|
||||
});
|
||||
|
||||
}, 5000);
|
||||
var data = {}
|
||||
data['sections'] = JSON.stringify(allSectionArr);
|
||||
data['tooltips'] = JSON.stringify(allTooltipArr);
|
||||
data['course_id'] = "${section_data['course_id']}";
|
||||
data['data_type'] = event.type;
|
||||
|
||||
var input_data = document.createElement("input");
|
||||
input_data.name = 'data';
|
||||
input_data.value = JSON.stringify(data);
|
||||
|
||||
var csrf_token_input = document.createElement("input");
|
||||
csrf_token_input.name = 'csrfmiddlewaretoken';
|
||||
csrf_token_input.value = "${ csrf_token }"
|
||||
|
||||
// Send data as a POST so it doesn't create a huge url
|
||||
var form = document.createElement("form");
|
||||
form.action = "${section_data['post_metrics_data_csv_url']}";
|
||||
form.method = 'post'
|
||||
|
||||
form.appendChild(input_data);
|
||||
form.appendChild(csrf_token_input)
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}
|
||||
|
||||
$('.instructor-nav a').click(function () {
|
||||
if ($(this).data('section') === "metrics" && firstLoad) {
|
||||
loadGraphs();
|
||||
firstLoad = false;
|
||||
$('#graph_reload').show();
|
||||
$('.metrics-header-container').show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#graph_reload').click(function () {
|
||||
loadGraphs();
|
||||
$('#graph_reload').show();
|
||||
$('.metrics-header-container').show();
|
||||
});
|
||||
|
||||
$('#download_subsection_data').click(function() {
|
||||
download_csv_data({'type': 'subsection'});
|
||||
});
|
||||
|
||||
$('#download_problem_data').click(function() {
|
||||
download_csv_data({'type': 'problem'});
|
||||
});
|
||||
|
||||
if (window.location.hash === "#view-metrics") {
|
||||
$('.instructor-nav a[data-section="metrics"]').click();
|
||||
$('#graph_reload').hide();
|
||||
$('.metrics-header-container').hide();
|
||||
}
|
||||
|
||||
$(document).ajaxStop(function() {
|
||||
$('#graph_reload').show();
|
||||
$('.metrics-header-container').show();
|
||||
});
|
||||
|
||||
});
|
||||
$('.metrics-overlay .close-button').click(function(event) {
|
||||
event.preventDefault();
|
||||
@@ -179,13 +259,14 @@
|
||||
});
|
||||
$('.metrics-overlay .download-csv').click(function(event) {
|
||||
|
||||
var module_id = $(this).closest('.metrics-overlay').data("module-id");
|
||||
var module_id = $(this).closest('.metrics-overlay').data("module-id");
|
||||
var tooltip = $(this).closest('.metrics-container').children('.metrics-tooltip').text();
|
||||
var attributes = '?module_id=' + module_id + '&tooltip=' + tooltip + '&csv=true';
|
||||
var attributes = '?module_id=' + module_id + '&csv=true' + '&tooltip=' + tooltip;
|
||||
var url = $(this).data("endpoint");
|
||||
url += attributes;
|
||||
|
||||
return location.href = url;
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</p>
|
||||
<br>
|
||||
<p>
|
||||
<a href="${ section_data['spoc_gradebook_url'] }" class="progress-link"> ${_("View Gradebook")} </a>
|
||||
<a href="${ section_data['spoc_gradebook_url'] }" class="gradebook-link"> ${_("View Gradebook")} </a>
|
||||
</p>
|
||||
<hr>
|
||||
%endif
|
||||
|
||||
@@ -66,10 +66,19 @@
|
||||
</section>
|
||||
<script>
|
||||
|
||||
<<<<<<< HEAD
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '';
|
||||
for (var index = 0; index <= 6; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
=======
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '',
|
||||
courseid;
|
||||
for (var index = 0; index <= 6; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
|
||||
>>>>>>> edx/master
|
||||
var pagination = 100,
|
||||
is_staff = false,
|
||||
options = {
|
||||
@@ -168,7 +177,7 @@
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
token: "${token}"
|
||||
tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid
|
||||
},
|
||||
store: {
|
||||
// The endpoint of the store on your server.
|
||||
|
||||
@@ -63,12 +63,21 @@ ${block_content}
|
||||
</div>
|
||||
<div data-location="${unicode(location)}" data-location-name="${location.name}">
|
||||
[
|
||||
<<<<<<< HEAD
|
||||
<a href="#" id="staff-debug-reset">${_('Reset Student Attempts')}</a>
|
||||
|
|
||||
<a href="#" id="staff-debug-sdelete">${_('Delete Student State')}</a>
|
||||
|
|
||||
<a href="#" id="staff-debug-rescore">${_('Rescore Student Submission')}</a>
|
||||
|
||||
=======
|
||||
<a href="#" id="staff-debug-reset" class="staff-debug-reset" data-location="${location.name}">${_('Reset Student Attempts')}</a>
|
||||
|
|
||||
<a href="#" id="staff-debug-sdelete" class="staff-debug-sdelete" data-location="${location.name}">${_('Delete Student State')}</a>
|
||||
|
|
||||
<a href="#" id="staff-debug-rescore" class="staff-debug-rescore" data-location="${location.name}">${_('Rescore Student Submission')}</a>
|
||||
|
||||
>>>>>>> edx/master
|
||||
]
|
||||
</div>
|
||||
<div id="result_${location.name}"/>
|
||||
|
||||
@@ -1,63 +1,64 @@
|
||||
<%! from django.utils.translation import ugettext as _ %>
|
||||
|
||||
<div class="annotatable-wrapper">
|
||||
<div class="annotatable-header">
|
||||
% if display_name is not UNDEFINED and display_name is not None:
|
||||
<div class="annotatable-title">${display_name}</div>
|
||||
% endif
|
||||
</div>
|
||||
% if instructions_html is not UNDEFINED and instructions_html is not None:
|
||||
<div class="annotatable-section shaded">
|
||||
<div class="annotatable-section-title">
|
||||
${_('Instructions')}
|
||||
<a class="annotatable-toggle annotatable-toggle-instructions expanded" href="javascript:void(0)">${_('Collapse Instructions')}</a>
|
||||
</div>
|
||||
<div class="annotatable-section-body annotatable-instructions">
|
||||
${instructions_html}
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="annotatable-section">
|
||||
<div class="annotatable-content">
|
||||
<div id="textHolder">${content_html}</div>
|
||||
<div id="sourceCitation">${_('Source:')} ${source}</div>
|
||||
<div id="catchDIV">
|
||||
<div class="annotationListContainer">${_('You do not have any notes.')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="annotatable-header">
|
||||
% if display_name is not UNDEFINED and display_name is not None:
|
||||
<div class="annotatable-title">${display_name}</div>
|
||||
% endif
|
||||
</div>
|
||||
% if instructions_html is not UNDEFINED and instructions_html is not None:
|
||||
<div class="annotatable-section shaded">
|
||||
<div class="annotatable-section-title">
|
||||
${_('Instructions')}
|
||||
<a class="annotatable-toggle annotatable-toggle-instructions expanded" href="javascript:void(0)">${_('Collapse Instructions')}</a>
|
||||
</div>
|
||||
<div class="annotatable-section-body annotatable-instructions">
|
||||
${instructions_html}
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
<div class="annotatable-section">
|
||||
<div class="annotatable-content">
|
||||
<div id="textHolder">${content_html}</div>
|
||||
<div id="sourceCitation">${_('Source:')} ${source}</div>
|
||||
<div id="catchDIV">
|
||||
<div class="annotationListContainer">${_('You do not have any notes.')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script>
|
||||
function onClickHideInstructions(){
|
||||
//Reset function if there is more than one event handler
|
||||
$(this).off();
|
||||
$(this).on('click',onClickHideInstructions);
|
||||
var hide = $(this).html()=='Collapse Instructions'?true:false,
|
||||
cls, txt,slideMethod;
|
||||
txt = (hide ? 'Expand' : 'Collapse') + ' Instructions';
|
||||
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
|
||||
slideMethod = (hide ? 'slideUp' : 'slideDown');
|
||||
$(this).text(txt).removeClass(cls[0]).addClass(cls[1]);
|
||||
$(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod]();
|
||||
}
|
||||
$('.annotatable-toggle-instructions').on('click', onClickHideInstructions);
|
||||
|
||||
//Grab uri of the course
|
||||
function onClickHideInstructions(){
|
||||
//Reset function if there is more than one event handler
|
||||
$(this).off();
|
||||
$(this).on('click',onClickHideInstructions);
|
||||
var hide = $(this).html()=='Collapse Instructions'?true:false,
|
||||
cls, txt,slideMethod;
|
||||
txt = (hide ? 'Expand' : 'Collapse') + ' Instructions';
|
||||
cls = (hide ? ['expanded', 'collapsed'] : ['collapsed', 'expanded']);
|
||||
slideMethod = (hide ? 'slideUp' : 'slideDown');
|
||||
$(this).text(txt).removeClass(cls[0]).addClass(cls[1]);
|
||||
$(this).parents('.annotatable-section:first').find('.annotatable-instructions')[slideMethod]();
|
||||
}
|
||||
$('.annotatable-toggle-instructions').on('click', onClickHideInstructions);
|
||||
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '';
|
||||
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
//Change uri in cms
|
||||
var lms_location = $('.sidebar .preview-button').attr('href');
|
||||
if (typeof lms_location!='undefined'){
|
||||
uri = window.location.protocol;
|
||||
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
|
||||
}
|
||||
var unit_id = $('#sequence-list').find('.active').attr("data-element");
|
||||
uri += unit_id;
|
||||
var pagination = 100,
|
||||
is_staff = !('${user.is_staff}'=='False'),
|
||||
uri = '',
|
||||
courseid;
|
||||
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
|
||||
//Change uri in cms
|
||||
var lms_location = $('.sidebar .preview-button').attr('href');
|
||||
if (typeof lms_location!='undefined'){
|
||||
courseid = parts[4].split(".").join("/");
|
||||
uri = window.location.protocol;
|
||||
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
|
||||
}
|
||||
var pagination = 100,
|
||||
is_staff = !('${user.is_staff}'=='False'),
|
||||
options = {
|
||||
optionsAnnotator: {
|
||||
permissions:{
|
||||
@@ -88,7 +89,7 @@
|
||||
if (annotation.permissions) {
|
||||
tokens = annotation.permissions[action] || [];
|
||||
if (is_staff){
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
if (tokens.length === 0) {
|
||||
return true;
|
||||
@@ -114,7 +115,7 @@
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
token: "${token}"
|
||||
tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid
|
||||
},
|
||||
store: {
|
||||
// The endpoint of the store on your server.
|
||||
@@ -139,14 +140,11 @@
|
||||
offset:0,
|
||||
uri:uri,
|
||||
media:'text',
|
||||
userid:'${user.email}',
|
||||
userid:'${user.email}',
|
||||
}
|
||||
},
|
||||
highlightTags:{
|
||||
tag: "${tag}",
|
||||
},
|
||||
diacriticMarks:{
|
||||
diacritics: "${diacritic_marks}"
|
||||
}
|
||||
},
|
||||
optionsVideoJS: {techOrder: ["html5","flash","youtube"]},
|
||||
@@ -163,11 +161,12 @@
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
|
||||
tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova";
|
||||
|
||||
//remove old instances
|
||||
var imgURLRoot = "${settings.STATIC_URL}" + "js/vendor/ova/catch/img/";
|
||||
tinyMCE.baseURL = "${settings.STATIC_URL}" + "js/vendor/ova";
|
||||
|
||||
//remove old instances
|
||||
if (Annotator._instances.length !== 0) {
|
||||
$('#textHolder').annotator("destroy");
|
||||
}
|
||||
@@ -175,6 +174,7 @@
|
||||
//Load the plugin Video/Text Annotation
|
||||
var ova = new OpenVideoAnnotation.Annotator($('#textHolder'),options);
|
||||
|
||||
|
||||
//Catch
|
||||
var annotator = ova.annotator,
|
||||
catchOptions = {
|
||||
@@ -183,7 +183,7 @@
|
||||
imageUrlRoot:imgURLRoot,
|
||||
showMediaSelector: false,
|
||||
showPublicPrivate: true,
|
||||
userId:'${user.email}',
|
||||
userId:'${user.email}',
|
||||
pagination:pagination,//Number of Annotations per load in the pagination,
|
||||
flags:is_staff
|
||||
},
|
||||
|
||||
@@ -49,16 +49,18 @@
|
||||
|
||||
//Grab uri of the course
|
||||
var parts = window.location.href.split("/"),
|
||||
uri = '';
|
||||
uri = '',
|
||||
courseid;
|
||||
for (var index = 0; index <= 9; index += 1) uri += parts[index]+"/"; //Get the unit url
|
||||
courseid = parts[4] + "/" + parts[5] + "/" + parts[6];
|
||||
//Change uri in cms
|
||||
var lms_location = $('.sidebar .preview-button').attr('href');
|
||||
if (typeof lms_location!='undefined'){
|
||||
courseid = parts[4].split(".").join("/");
|
||||
uri = window.location.protocol;
|
||||
for (var index = 0; index <= 9; index += 1) uri += lms_location.split("/")[index]+"/"; //Get the unit url
|
||||
}
|
||||
var unit_id = $('#sequence-list').find('.active').attr("data-element");
|
||||
uri += unit_id;
|
||||
|
||||
var pagination = 100,
|
||||
is_staff = !('${user.is_staff}'=='False'),
|
||||
options = {
|
||||
@@ -117,7 +119,7 @@
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
token: "${token}"
|
||||
tokenUrl: location.protocol+'//'+location.host+"/token?course_id="+courseid
|
||||
},
|
||||
store: {
|
||||
// The endpoint of the store on your server.
|
||||
@@ -173,6 +175,8 @@
|
||||
var ova = new OpenVideoAnnotation.Annotator($('#videoHolder'),options);
|
||||
|
||||
ova.annotator.addPlugin('Tags');
|
||||
|
||||
|
||||
|
||||
//Catch
|
||||
var annotator = ova.annotator,
|
||||
@@ -182,7 +186,7 @@
|
||||
imageUrlRoot:imgURLRoot,
|
||||
showMediaSelector: false,
|
||||
showPublicPrivate: true,
|
||||
userId:'${user.email}',
|
||||
userId:'${user.email}',
|
||||
pagination:pagination,//Number of Annotations per load in the pagination,
|
||||
flags:is_staff
|
||||
},
|
||||
|
||||
19
lms/urls.py
19
lms/urls.py
@@ -15,6 +15,7 @@ urlpatterns = ('', # nopep8
|
||||
url(r'^request_certificate$', 'certificates.views.request_certificate'),
|
||||
url(r'^$', 'branding.views.index', name="root"), # Main marketing page, or redirect to courseware
|
||||
url(r'^dashboard$', 'student.views.dashboard', name="dashboard"),
|
||||
url(r'^token$', 'student.views.token', name="token"),
|
||||
url(r'^login$', 'student.views.signin_user', name="signin_user"),
|
||||
url(r'^register$', 'student.views.register_user', name="register_user"),
|
||||
|
||||
@@ -377,23 +378,7 @@ if settings.COURSEWARE_ENABLED and settings.FEATURES.get('ENABLE_INSTRUCTOR_LEGA
|
||||
|
||||
if settings.FEATURES.get('CLASS_DASHBOARD'):
|
||||
urlpatterns += (
|
||||
# Json request data for metrics for entire course
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/all_sequential_open_distrib$',
|
||||
'class_dashboard.views.all_sequential_open_distrib', name="all_sequential_open_distrib"),
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/all_problem_grade_distribution$',
|
||||
'class_dashboard.views.all_problem_grade_distribution', name="all_problem_grade_distribution"),
|
||||
|
||||
# Json request data for metrics for particular section
|
||||
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/problem_grade_distribution/(?P<section>\d+)$',
|
||||
'class_dashboard.views.section_problem_grade_distrib', name="section_problem_grade_distrib"),
|
||||
|
||||
# For listing students that opened a sub-section
|
||||
url(r'^get_students_opened_subsection$',
|
||||
'class_dashboard.dashboard_data.get_students_opened_subsection', name="get_students_opened_subsection"),
|
||||
|
||||
# For listing of students' grade per problem
|
||||
url(r'^get_students_problem_grades$',
|
||||
'class_dashboard.dashboard_data.get_students_problem_grades', name="get_students_problem_grades"),
|
||||
url(r'^class_dashboard/', include('class_dashboard.urls')),
|
||||
)
|
||||
|
||||
if settings.DEBUG or settings.FEATURES.get('ENABLE_DJANGO_ADMIN_SITE'):
|
||||
|
||||
Reference in New Issue
Block a user