From c6402d31d7ed58eadb8635398808a8795a38e2f9 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Wed, 11 Feb 2015 20:12:50 +0530 Subject: [PATCH 1/8] Added Student Mastery/Class Mastery CoachReports --- .../static/css/coachreports/report_view.css | 152 ++++++++++ .../static/js/coachreports/jquery.cookie.js | 97 +++++++ .../templates/coachreports/landing_page.html | 9 + .../templates/coachreports/report_view.html | 264 ++++++++++++++++++ kalite/coachreports/urls.py | 3 + kalite/coachreports/views.py | 132 +++++++++ 6 files changed, 657 insertions(+) create mode 100644 kalite/coachreports/static/css/coachreports/report_view.css create mode 100644 kalite/coachreports/static/js/coachreports/jquery.cookie.js create mode 100644 kalite/coachreports/templates/coachreports/report_view.html diff --git a/kalite/coachreports/static/css/coachreports/report_view.css b/kalite/coachreports/static/css/coachreports/report_view.css new file mode 100644 index 0000000000..51217b651a --- /dev/null +++ b/kalite/coachreports/static/css/coachreports/report_view.css @@ -0,0 +1,152 @@ +.selection { + float: left; + padding: 0 10px 10px 0; +} + +#displaygrid { + width: 100%; + padding-top: 5px; + padding-left: 2px; +} + +#displaygrid > .users { + width: 11%; + margin-right: 1px; + float: left; +} + +.userstatus { + overflow: auto; + width: 88%; +} + +.users table { + width: 100%; +} + +#displaygrid table { + table-layout: fixed; +} + +th, td { + border: 1px solid black; + padding: 2px; + vertical-align:middle; +} + +.users th { + text-align: left; +} + +.subtitle { + font-size: 1.15em; + font-weight: bold; +} + +.status { + height: 27px; + background-color: #EEE; + /*width: 80px;*/ +} + +th.username { + height: 27px; + border: 1px solid black; + /*border-right: none;*/ + padding-left: 5px; + white-space: nowrap; + text-overflow: ellipsis; +} + +th.headrow, th.headrow div { + width: 80px; + font-weight: bold; + background-color: white; + overflow: hidden; + text-overflow: ellipsis; +} + +th.headrowuser { + font-weight: bold; +} + +.complete { + background-color: #2F942F; +} + +.partial { + background-color: #94BE48; +} + +.struggle { + background-color: #CA4D4D; +} + +#displaygrid td, #displaygrid th { + min-width: 60px; + max-width: 60px; + overflow: hidden; + /*white-space: nowrap;*/ +} + +#legend a { + height:12px; + margin:0px; + padding:0px; +} + +.student-name, .attempts, .streak_progress, .points, .total_seconds_watched { + float:left; + display:none; + overflow:hidden; + white-space:nowrap; + width:100%; + margin:0px 5px 0px 0px; + padding:0px; + text-align:right; + vertical-align:middle; +} +.student-name { + float:none; + text-align:left; + display:block; + text-overflow: ellipsis; +} +#legend { + float:right; + vertical-align:middle; + margin:12px 25px 2px 10px; +} +.legend { + float:right; + width:120px; + height:20px; + text-align:center; + vertical-align:middle; + white-space:nowrap; + overflow:hidden; + border-style:solid; + border-width: 1px; + padding:2px; + margin-right: 2px; +/*display:none;*/ + +} +.legend div { + float:left; + width:35px; + height:21px; +} +#selection-bar { + overflow:hide; +} +#disp_options { + float:right; +} + +.exercise-name { + height: 100px; + text-align: center; + vertical-align: none; + padding: 0px; +} \ No newline at end of file diff --git a/kalite/coachreports/static/js/coachreports/jquery.cookie.js b/kalite/coachreports/static/js/coachreports/jquery.cookie.js new file mode 100644 index 0000000000..a80bfa29d6 --- /dev/null +++ b/kalite/coachreports/static/js/coachreports/jquery.cookie.js @@ -0,0 +1,97 @@ +/** + * Cookie plugin + * + * Copyright (c) 2006 Klaus Hartl (stilbuero.de) + * Dual licensed under the MIT and GPL licenses: + * http://www.opensource.org/licenses/mit-license.php + * http://www.gnu.org/licenses/gpl.html + * + */ + +/** + * Create a cookie with the given name and value and other optional parameters. + * + * @example $.cookie('the_cookie', 'the_value'); + * @desc Set the value of a cookie. + * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true }); + * @desc Create a cookie with all available options. + * @example $.cookie('the_cookie', 'the_value'); + * @desc Create a session cookie. + * @example $.cookie('the_cookie', null); + * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain + * used when the cookie was set. + * + * @param String name The name of the cookie. + * @param String value The value of the cookie. + * @param Object options An object literal containing key/value pairs to provide optional cookie attributes. + * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object. + * If a negative value is specified (e.g. a date in the past), the cookie will be deleted. + * If set to null or omitted, the cookie will be a session cookie and will not be retained + * when the the browser exits. + * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie). + * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie). + * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will + * require a secure protocol (like HTTPS). + * @type undefined + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ + +/** + * Get the value of a cookie with the given name. + * + * @example $.cookie('the_cookie'); + * @desc Get the value of a cookie. + * + * @param String name The name of the cookie. + * @return The value of the cookie. + * @type String + * + * @name $.cookie + * @cat Plugins/Cookie + * @author Klaus Hartl/klaus.hartl@stilbuero.de + */ +jQuery.cookie = function(name, value, options) { + if (typeof value != 'undefined') { // name and value given, set cookie + options = options || {}; + if (value === null) { + value = ''; + options = $.extend({}, options); // clone object since it's unexpected behavior if the expired property were changed + options.expires = -1; + } + var expires = ''; + if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) { + var date; + if (typeof options.expires == 'number') { + date = new Date(); + date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000)); + } else { + date = options.expires; + } + expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE + } + // NOTE Needed to parenthesize options.path and options.domain + // in the following expressions, otherwise they evaluate to undefined + // in the packed version for some reason... + var path = options.path ? '; path=' + (options.path) : ''; + var domain = options.domain ? '; domain=' + (options.domain) : ''; + var secure = options.secure ? '; secure' : ''; + document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''); + } else { // only name given, get cookie + var cookieValue = null; + if (document.cookie && document.cookie != '') { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = jQuery.trim(cookies[i]); + // Does this cookie string begin with the name we want? + if (cookie.substring(0, name.length + 1) == (name + '=')) { + cookieValue = decodeURIComponent(cookie.substring(name.length + 1)); + break; + } + } + } + return cookieValue; + } +}; \ No newline at end of file diff --git a/kalite/coachreports/templates/coachreports/landing_page.html b/kalite/coachreports/templates/coachreports/landing_page.html index 456ecc4e1d..20ae654e7e 100644 --- a/kalite/coachreports/templates/coachreports/landing_page.html +++ b/kalite/coachreports/templates/coachreports/landing_page.html @@ -56,6 +56,15 @@

{% trans "Student Spending" %}

+
+ +
diff --git a/kalite/coachreports/templates/coachreports/report_view.html b/kalite/coachreports/templates/coachreports/report_view.html new file mode 100644 index 0000000000..0761a2e140 --- /dev/null +++ b/kalite/coachreports/templates/coachreports/report_view.html @@ -0,0 +1,264 @@ +{% extends "coachreports/base.html" %} + +{% load i18n %} +{% load kalite_staticfiles %} +{% load my_filters %} + +{% block coachreports_active %}active{% endblock coachreports_active %} +{% block title %}{% trans "Progress by topic" %} {{ block.super }}{% endblock title %} + +{% block headcss %}{{ block.super }} + + + + +{% endblock headcss %} + + + +{% block headjs %}{{ block.super }} + + + + + + + + +{% endblock headjs %} + +{% block content %} + {% block navbar_title %}{{ block.super }}{% endblock navbar_title %} + +
+
+ {# Select the unit number #} + {% if unit_types %} +
{% trans "Select Unit" %}
+ + {% else %} +
{% trans "No units available." %}
+ {% endif %} +
+ + + + {% if groups %} + {% if playlists %} +
+
{% trans "Select Playlist" %}
+ {% endif %} + {% endif %} +
+ +
+
    + {% for playlist in playlists %} +
  • {% trans playlist.title %} +
      + {% for exercise in playlist.exercises %} +
    • {{ exercise.title }}
    • + {% endfor %} +
    +
  • + {% endfor %} + +
+
+ + + +
+
+
{% trans "In Progress" %}
+
{% trans "Completed" %}
+ {% if request_report_type != "video" %} +
{% trans "Struggling" %}
+ {% endif %} +
+
+
+ + + {% if not students %} +

+ {% if not groups.0.groups and not groups.1 %} + {% comment %}No groups available: then "ungrouped" is selected, and "no students" returned.{% endcomment %} + {% trans "No student accounts have been created." %} + {% elif request.GET.playlist %} + {% trans "Please select a playlist above, but not both." %} + {% elif not request.GET.playlist %} + {% comment %}Group was selected, but data not queried because a playlist was not selected + (NOTE: this required knowledge of how the view queries data){% endcomment %} +
+ {% if playlists %} + {% trans "Please select a playlist above." %} + {% endif %} +
+ {% else %} + {% comment %}Everything specified, but no users fit the query.{% endcomment %} + {% trans "No student accounts in this group have been created." %} + {% endif %} +

+ + {% else %} + {% block students_header %} +
+
+
+ + + + + + {% for student in students %} + + + + {% endfor %} + +
+ {% trans "Student" %} +
+ + + +
+
+
+
+ {% endblock students_header %} + + {% if request_report_type == "exercise" and exercises %} + {% block exercise_data %} +
+ + + + + {% for exercise in exercises %} + + + + {% for student in students %} + + + {% for exercise in exercises %} + {% if not student.exercise_logs|get_item:exercise.slug %} + + {% elif student.exercise_logs|get_item:exercise.slug|get_item:"complete" %} + + {% else %} + + {% endif %} + + {% endfor %} + + {% endfor %} + +
+ Student Mastery + + +
+ Mastered: {{ exercise_stats|get_item:exercise.id|get_item:"mastered" }} + Progress: {{ exercise_stats|get_item:exercise.id|get_item:"progress" }} + Struggling: {{ exercise_stats|get_item:exercise.id|get_item:"struggling" }} + Mastery: {{ exercise_stats|get_item:exercise.id|get_item:"mastery" }} % +
+ {% endfor %} +
+ {{ student.mastery }} % + + {% elif student.exercise_logs|get_item:exercise.slug|get_item:"struggling" %} + {{ student.exercise_logs|get_item:exercise.slug|get_item:"streak_progress" }} / {{ student.exercise_logs|get_item:exercise.slug|get_item:"attempts" }} {{ student.exercise_logs|get_item:exercise.slug|get_item:"streak_progress" }} / {{ student.exercise_logs|get_item:exercise.slug|get_item:"attempts" }} {{ student.exercise_logs|get_item:exercise.slug|get_item:"streak_progress" }} / {{ student.exercise_logs|get_item:exercise.slug|get_item:"attempts" }}
+
+ {% endblock exercise_data %} + {% endif %} + {% endif %} +{% endblock content %} diff --git a/kalite/coachreports/urls.py b/kalite/coachreports/urls.py index 03708c12d9..94d6b6743b 100644 --- a/kalite/coachreports/urls.py +++ b/kalite/coachreports/urls.py @@ -18,6 +18,9 @@ url(r'^table/$', 'tabular_view', {}, 'tabular_view'), url(r'^table/(?P\w+)/$', 'tabular_view', {}, 'tabular_view'), + url(r'^report/$', 'report_view', {}, 'report_view'), + url(r'^report/(?P\w+)/$', 'report_view', {}, 'report_view'), + url(r'^test/$', 'test_view', {}, 'test_view'), url(r'^test/(?P\w+)/$', 'test_detail_view', {}, 'test_detail_view'), diff --git a/kalite/coachreports/views.py b/kalite/coachreports/views.py index 66eeedaec9..e91db6d8e7 100755 --- a/kalite/coachreports/views.py +++ b/kalite/coachreports/views.py @@ -35,6 +35,9 @@ from kalite.student_testing.api_resources import TestResource from kalite.student_testing.models import TestLog from kalite.topic_tools import get_topic_exercises, get_topic_videos, get_knowledgemap_topics, get_node_cache, get_topic_tree, get_flat_topic_tree, get_live_topics, get_id2slug_map, get_slug2id_map, convert_leaf_url_to_id +from kalite.playlist import UNITS +from kalite.student_testing.utils import get_current_unit_settings_value +from kalite.ab_testing.data.groups import get_grade_by_facility # shared by test_view and test_detail view SUMMARY_STATS = [ugettext_lazy('Max'), ugettext_lazy('Min'), ugettext_lazy('Average'), ugettext_lazy('Std Dev')] @@ -269,6 +272,135 @@ def tabular_view(request, facility, report_type="exercise"): return context +@require_authorized_admin +@facility_required +@render_to("coachreports/report_view.html") +def report_view(request, facility, unit_type=100): + """Tabular view also gets data server-side.""" + student_ordering = ["last_name", "first_name", "username"] + unit_list = (unit for unit in UNITS if unit > 100) + if unit_type == 100: + unit_type = get_current_unit_settings_value(facility.id) + + grade = "Grade " + str(get_grade_by_facility(facility)) + playlists = (filter(lambda p: (p.unit==int(unit_type) or p.unit==100) and p.tag==grade, Playlist.all()) or [None]) + context = plotting_metadata_context(request, facility=facility) + context.update({ + # For translators: the following two translations are nouns + "unit_types": unit_list, + "request_unit_type": int(unit_type), + "request_report_type": "exercise", + "playlists": [{"id": p.id, "title": p.title, "tag": p.tag, "exercises": p.get_playlist_entries("Exercise") } for p in playlists if p], + }) + + # get querystring info + exercises = str(request.GET.get("playlist", "")) + # No valid data; just show generic + # Exactly one of topic_id or playlist_id should be present + if not exercises: + return context + + exercises = exercises.split(',') + group_id = request.GET.get("group", "") + users = get_user_queryset(request, facility, group_id) + + temp = [] + for p in playlists: + for ex in p.get_playlist_entries("Exercise"): + if ex['id'] in exercises: + temp.append(ex) + + exercises = sorted(temp, key=lambda e: (e["h_position"], e["v_position"])) + context["exercises"] = exercises + exercise_count = len(exercises) + + # More code, but much faster + exercise_names = [ex["name"] for ex in context["exercises"]] + # Get students + context["students"] = [] + exlogs = ExerciseLog.objects \ + .filter(user__in=users, exercise_id__in=exercise_names) \ + .order_by(*["user__%s" % field for field in student_ordering]) \ + .values("user__id", "struggling", "complete", "exercise_id", "attempts", "streak_progress") + exlogs = list(exlogs) # force the query to be evaluated + + exercise_ids = [ex["id"] for ex in context["exercises"]] + user_count = len(users) + + exercise_stats = {} + for ex_id in exercise_ids: + exercise_stats[ex_id] = { "struggling" :0 , "mastered": 0, "progress": 0, "mastery": 0} + + for ex in exlogs: + if ex["complete"] == True: + exercise_stats[ex["exercise_id"]]["mastered"] = exercise_stats[ex["exercise_id"]]["mastered"] + 1 + elif ex["struggling"] == True: + exercise_stats[ex["exercise_id"]]["struggling"] = exercise_stats[ex["exercise_id"]]["struggling"] + 1 + elif ex["attempts"] > 0: + exercise_stats[ex["exercise_id"]]["progress"] = exercise_stats[ex["exercise_id"]]["progress"] + 1 + + exercise_stats[ex["exercise_id"]]["mastery"] = exercise_stats[ex["exercise_id"]]["mastered"] * 100 / user_count + + context["exercise_stats"] = exercise_stats + context["exercise_count"] = exercise_count + + exlog_idx = 0 + for user in users: + log_table = {} + while exlog_idx < len(exlogs) and exlogs[exlog_idx]["user__id"] == user.id: + exlogs[exlog_idx]["streak_progress"] = exlogs[exlog_idx]["streak_progress"] / 12 + log_table[exlogs[exlog_idx]["exercise_id"]] = exlogs[exlog_idx] + exlog_idx += 1 + + progress = 0 + mastered = 0 + struggling = 0 + + for ex in log_table: + if log_table[ex]["complete"] == True: + mastered = mastered + 1 + elif log_table[ex]["struggling"] == True: + struggling = struggling + 1 + elif log_table[ex]["attempts"] > 0: + progress = progress + 1 + + mastery = (mastered * 100)/exercise_count + + context["students"].append({ # this could be DRYer + "first_name": user.first_name, + "last_name": user.last_name, + "username": user.username, + "name": user.get_name(), + "id": user.id, + "exercise_logs": log_table, + "progress": progress, + "mastered": mastered, + "struggling": struggling, + "mastery" : mastery, + }) + + + progress = 0 + mastered = 0 + struggling = 0 + for student in context["students"]: + for ex in student["exercise_logs"]: + if student["exercise_logs"][ex]["complete"] == True: + mastered = mastered + 1 + elif student["exercise_logs"][ex]["struggling"] == True: + struggling = struggling + 1 + elif student["exercise_logs"][ex]["attempts"] > 0: + progress = progress + 1 + + context["progress"] = progress + context["mastered"] = mastered + context["struggling"] = struggling + + log_coach_report_view(request) + + return context + + @require_authorized_admin @facility_required @render_to("coachreports/test_view.html") From 5105876ea4005c7f74df5ba0798c26fde6ea12ca Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Thu, 12 Feb 2015 16:38:00 +0530 Subject: [PATCH 2/8] Removed Unit Selection Box and Added Class Level Mastery --- .../static/css/coachreports/report_view.css | 7 ++--- .../templates/coachreports/report_view.html | 27 +++---------------- kalite/coachreports/views.py | 17 +++++------- 3 files changed, 14 insertions(+), 37 deletions(-) diff --git a/kalite/coachreports/static/css/coachreports/report_view.css b/kalite/coachreports/static/css/coachreports/report_view.css index 51217b651a..773739a434 100644 --- a/kalite/coachreports/static/css/coachreports/report_view.css +++ b/kalite/coachreports/static/css/coachreports/report_view.css @@ -113,13 +113,13 @@ th.headrowuser { text-overflow: ellipsis; } #legend { - float:right; + float:left; vertical-align:middle; margin:12px 25px 2px 10px; } .legend { float:right; - width:120px; + width:140px; height:20px; text-align:center; vertical-align:middle; @@ -128,7 +128,8 @@ th.headrowuser { border-style:solid; border-width: 1px; padding:2px; - margin-right: 2px; + margin-right: 10px; + font-weight: bold; /*display:none;*/ } diff --git a/kalite/coachreports/templates/coachreports/report_view.html b/kalite/coachreports/templates/coachreports/report_view.html index 0761a2e140..c0af1d7d21 100644 --- a/kalite/coachreports/templates/coachreports/report_view.html +++ b/kalite/coachreports/templates/coachreports/report_view.html @@ -28,10 +28,6 @@ // As such, changing any of the values of the items requires a change of URL and subsequent // navigation event in order to produce the new report. - $("#unit_type").change(function(){ - window.location.href="{% url 'report_view' %}"+$("#unit_type option:selected").val()+ "/" + window.location.search; - }); - $("#student").change(function(){ window.location.href = setGetParam(window.location.href, "user", $("#student option:selected").val()); }); @@ -112,21 +108,6 @@ {% block navbar_title %}{{ block.super }}{% endblock navbar_title %}
-
- {# Select the unit number #} - {% if unit_types %} -
{% trans "Select Unit" %}
- - {% else %} -
{% trans "No units available." %}
- {% endif %} -
- - {% if groups %} {% if playlists %} @@ -139,7 +120,7 @@
    {% for playlist in playlists %} -
  • {% trans playlist.title %} +
  • {% trans "Unit" %} {{ playlist.unit }} {% trans playlist.title %}
      {% for exercise in playlist.exercises %}
    • {{ exercise.title }}
    • @@ -155,10 +136,10 @@
      -
      {% trans "In Progress" %}
      -
      {% trans "Completed" %}
      +
      {% trans "In Progress" %} {{ progress }} %
      +
      {% trans "Completed" %} {{ mastered }} %
      {% if request_report_type != "video" %} -
      {% trans "Struggling" %}
      +
      {% trans "Struggling" %} {{ struggling }} %
      {% endif %}
      diff --git a/kalite/coachreports/views.py b/kalite/coachreports/views.py index e91db6d8e7..e1b81a2f65 100755 --- a/kalite/coachreports/views.py +++ b/kalite/coachreports/views.py @@ -275,22 +275,17 @@ def tabular_view(request, facility, report_type="exercise"): @require_authorized_admin @facility_required @render_to("coachreports/report_view.html") -def report_view(request, facility, unit_type=100): +def report_view(request, facility): """Tabular view also gets data server-side.""" student_ordering = ["last_name", "first_name", "username"] - unit_list = (unit for unit in UNITS if unit > 100) - if unit_type == 100: - unit_type = get_current_unit_settings_value(facility.id) grade = "Grade " + str(get_grade_by_facility(facility)) - playlists = (filter(lambda p: (p.unit==int(unit_type) or p.unit==100) and p.tag==grade, Playlist.all()) or [None]) + playlists = (filter(lambda p: p.unit >= 100 and p.unit <= 104 and p.tag==grade, Playlist.all()) or [None]) context = plotting_metadata_context(request, facility=facility) context.update({ # For translators: the following two translations are nouns - "unit_types": unit_list, - "request_unit_type": int(unit_type), "request_report_type": "exercise", - "playlists": [{"id": p.id, "title": p.title, "tag": p.tag, "exercises": p.get_playlist_entries("Exercise") } for p in playlists if p], + "playlists": [{"id": p.id, "title": p.title, "tag": p.tag, "exercises": p.get_playlist_entries("Exercise"), "unit": p.unit } for p in playlists if p], }) # get querystring info @@ -392,9 +387,9 @@ def report_view(request, facility, unit_type=100): elif student["exercise_logs"][ex]["attempts"] > 0: progress = progress + 1 - context["progress"] = progress - context["mastered"] = mastered - context["struggling"] = struggling + context["progress"] = (100/exercise_count) * progress/user_count + context["mastered"] = (100/exercise_count) * mastered/user_count + context["struggling"] = (100/exercise_count) * struggling/user_count log_coach_report_view(request) From 528683397fb0dcc60baa8446661db2fa65c2726b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 14:47:11 +0530 Subject: [PATCH 3/8] Code cleanup and renamed report view to exercise_mastery_view --- ...ort_view.css => exercise_mastery_view.css} | 13 +- .../js/coachreports/exercise_mastery_view.js | 73 +++++++++ ...t_view.html => exercise_mastery_view.html} | 138 ++++-------------- .../templates/coachreports/landing_page.html | 4 +- kalite/coachreports/urls.py | 3 +- kalite/coachreports/views.py | 17 +-- 6 files changed, 117 insertions(+), 131 deletions(-) rename kalite/coachreports/static/css/coachreports/{report_view.css => exercise_mastery_view.css} (95%) create mode 100644 kalite/coachreports/static/js/coachreports/exercise_mastery_view.js rename kalite/coachreports/templates/coachreports/{report_view.html => exercise_mastery_view.html} (56%) diff --git a/kalite/coachreports/static/css/coachreports/report_view.css b/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css similarity index 95% rename from kalite/coachreports/static/css/coachreports/report_view.css rename to kalite/coachreports/static/css/coachreports/exercise_mastery_view.css index 773739a434..303cfd8eba 100644 --- a/kalite/coachreports/static/css/coachreports/report_view.css +++ b/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css @@ -46,13 +46,11 @@ th, td { .status { height: 27px; background-color: #EEE; - /*width: 80px;*/ } th.username { height: 27px; border: 1px solid black; - /*border-right: none;*/ padding-left: 5px; white-space: nowrap; text-overflow: ellipsis; @@ -86,7 +84,6 @@ th.headrowuser { min-width: 60px; max-width: 60px; overflow: hidden; - /*white-space: nowrap;*/ } #legend a { @@ -106,17 +103,20 @@ th.headrowuser { text-align:right; vertical-align:middle; } + .student-name { float:none; text-align:left; display:block; text-overflow: ellipsis; } + #legend { float:left; vertical-align:middle; margin:12px 25px 2px 10px; } + .legend { float:right; width:140px; @@ -130,17 +130,18 @@ th.headrowuser { padding:2px; margin-right: 10px; font-weight: bold; -/*display:none;*/ - } + .legend div { float:left; width:35px; height:21px; } + #selection-bar { overflow:hide; } + #disp_options { float:right; } @@ -150,4 +151,4 @@ th.headrowuser { text-align: center; vertical-align: none; padding: 0px; -} \ No newline at end of file +} diff --git a/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js new file mode 100644 index 0000000000..40d24792cf --- /dev/null +++ b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js @@ -0,0 +1,73 @@ +$(function() { + + $("#student").change(function() { + window.location.href = setGetParam(window.location.href, "user", $("#student option:selected").val()); + }); + + $("#playlist").change(function() { + window.location.href = setGetParam(window.location.href, "playlist", $("#playlist option:selected").val()); + }); + + $("#facility").change(function() { + window.location.href = setGetParamDict(window.location.href, { + "facility": $("#facility option:selected").val(), + "group": $("#" + $("#facility option:selected").val() + "_group_select").val() + }); + }); + + $(".group_select").change(function(event) { + window.location.href = setGetParam(window.location.href, "group", $(event.target).val()); + }); + + // Selector to toggle visible elements is stored in each option value + cell_height = 27; + $("#disp_options").change(function() { + selector = $("#disp_options option:selected").val(); + + // adjust the cell height + cell_height += 50 * Math.pow(-1, 0 + $(selector).is(":visible")); + + // adjust view in data cells + $(selector).each(function() { + $(this).toggle() + }); + $(selector).each(function() { + $(this).height(20); + $(this).parent().height(cell_height); + }); + + // Adjust student name cell heights + $("th.username").each(function() { + $(this).height(cell_height); + }); + }); + $(window).resize(function() { + $('.headrowuser').height($('.headrow.data').height()); + }).resize(); +}); + +$(function(){ + $("#tree").dynatree({ + persist: true, + expand: false, + checkbox: true, + selectMode: 3, + cookieId: "exercises", + children: null, + + onPostInit: function(isReloading, isError) { + if (window.location.href.indexOf("&playlist=") == -1) { + $("#tree").dynatree("getTree").visit(function(node){ + node.select(false); + node.expand(false); + }); + } + }, + onSelect: function(select, dtnode) { + var selKeys = $.map(dtnode.tree.getSelectedNodes(), function(dtnode){ + return dtnode.data.key; + }); + window.location.href = setGetParam(window.location.href, "playlist", selKeys); + }, + }); +}); diff --git a/kalite/coachreports/templates/coachreports/report_view.html b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html similarity index 56% rename from kalite/coachreports/templates/coachreports/report_view.html rename to kalite/coachreports/templates/coachreports/exercise_mastery_view.html index c0af1d7d21..b405443736 100644 --- a/kalite/coachreports/templates/coachreports/report_view.html +++ b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html @@ -8,9 +8,9 @@ {% block title %}{% trans "Progress by topic" %} {{ block.super }}{% endblock title %} {% block headcss %}{{ block.super }} - - - + + + {% endblock headcss %} @@ -20,87 +20,7 @@ - - - - + {% endblock headjs %} @@ -119,14 +39,15 @@
        + {% for playlist in playlists %} -
      • {% trans "Unit" %} {{ playlist.unit }} {% trans playlist.title %} +
      • {% trans "Unit" %} {{ playlist.unit }} {% trans playlist.title %}
          {% for exercise in playlist.exercises %}
        • {{ exercise.title }}
        • {% endfor %}
        -
      • + {% endfor %}
      @@ -137,35 +58,34 @@
      {% trans "In Progress" %} {{ progress }} %
      -
      {% trans "Completed" %} {{ mastered }} %
      - {% if request_report_type != "video" %} +
      {% trans "Mastered" %} {{ mastered }} %
      {% trans "Struggling" %} {{ struggling }} %
      - {% endif %}
      {% if not students %} -

      - {% if not groups.0.groups and not groups.1 %} - {% comment %}No groups available: then "ungrouped" is selected, and "no students" returned.{% endcomment %} - {% trans "No student accounts have been created." %} - {% elif request.GET.playlist %} - {% trans "Please select a playlist above, but not both." %} - {% elif not request.GET.playlist %} - {% comment %}Group was selected, but data not queried because a playlist was not selected - (NOTE: this required knowledge of how the view queries data){% endcomment %} -
      - {% if playlists %} - {% trans "Please select a playlist above." %} - {% endif %} +

      +

      + {% if not groups.0.groups and not groups.1 %} + {% comment %}No groups available: then "ungrouped" is selected, and "no students" returned.{% endcomment %} + {% trans "No student accounts have been created." %} + {% endif %} + {% if not request.GET.playlist %} + {% comment %}Group was selected, but data not queried because a playlist was not selected + (NOTE: this required knowledge of how the view queries data){% endcomment %} +
      + {% if playlists %} + {% trans "Please select a playlist above." %} + {% endif %} +
      + {% else %} + {% comment %}Everything specified, but no users fit the query.{% endcomment %} + {% trans "No student accounts in this group have been created." %} + {% endif %}
      - {% else %} - {% comment %}Everything specified, but no users fit the query.{% endcomment %} - {% trans "No student accounts in this group have been created." %} - {% endif %} -

      +

      {% else %} {% block students_header %} @@ -195,13 +115,12 @@
      {% endblock students_header %} - {% if request_report_type == "exercise" and exercises %} {% block exercise_data %}
      - {% for exercise in exercises %} @@ -240,6 +159,5 @@
      + Student Mastery
      {% endblock exercise_data %} - {% endif %} {% endif %} {% endblock content %} diff --git a/kalite/coachreports/templates/coachreports/landing_page.html b/kalite/coachreports/templates/coachreports/landing_page.html index 20ae654e7e..dd5dc9ed15 100644 --- a/kalite/coachreports/templates/coachreports/landing_page.html +++ b/kalite/coachreports/templates/coachreports/landing_page.html @@ -58,10 +58,10 @@

      {% trans "Student Spending" %}

      diff --git a/kalite/coachreports/urls.py b/kalite/coachreports/urls.py index 94d6b6743b..1b3f3e7dd4 100644 --- a/kalite/coachreports/urls.py +++ b/kalite/coachreports/urls.py @@ -18,8 +18,7 @@ url(r'^table/$', 'tabular_view', {}, 'tabular_view'), url(r'^table/(?P\w+)/$', 'tabular_view', {}, 'tabular_view'), - url(r'^report/$', 'report_view', {}, 'report_view'), - url(r'^report/(?P\w+)/$', 'report_view', {}, 'report_view'), + url(r'^report/$', 'exercise_mastery_view', {}, 'exercise_mastery_view'), url(r'^test/$', 'test_view', {}, 'test_view'), url(r'^test/(?P\w+)/$', 'test_detail_view', {}, 'test_detail_view'), diff --git a/kalite/coachreports/views.py b/kalite/coachreports/views.py index e1b81a2f65..163c8e6d84 100755 --- a/kalite/coachreports/views.py +++ b/kalite/coachreports/views.py @@ -274,24 +274,20 @@ def tabular_view(request, facility, report_type="exercise"): @require_authorized_admin @facility_required -@render_to("coachreports/report_view.html") -def report_view(request, facility): - """Tabular view also gets data server-side.""" +@render_to("coachreports/exercise_mastery_view.html") +def exercise_mastery_view(request, facility): + student_ordering = ["last_name", "first_name", "username"] grade = "Grade " + str(get_grade_by_facility(facility)) playlists = (filter(lambda p: p.unit >= 100 and p.unit <= 104 and p.tag==grade, Playlist.all()) or [None]) context = plotting_metadata_context(request, facility=facility) context.update({ - # For translators: the following two translations are nouns - "request_report_type": "exercise", "playlists": [{"id": p.id, "title": p.title, "tag": p.tag, "exercises": p.get_playlist_entries("Exercise"), "unit": p.unit } for p in playlists if p], }) - # get querystring info exercises = str(request.GET.get("playlist", "")) - # No valid data; just show generic - # Exactly one of topic_id or playlist_id should be present + if not exercises: return context @@ -309,15 +305,14 @@ def report_view(request, facility): context["exercises"] = exercises exercise_count = len(exercises) - # More code, but much faster exercise_names = [ex["name"] for ex in context["exercises"]] - # Get students + context["students"] = [] exlogs = ExerciseLog.objects \ .filter(user__in=users, exercise_id__in=exercise_names) \ .order_by(*["user__%s" % field for field in student_ordering]) \ .values("user__id", "struggling", "complete", "exercise_id", "attempts", "streak_progress") - exlogs = list(exlogs) # force the query to be evaluated + exlogs = list(exlogs) exercise_ids = [ex["id"] for ex in context["exercises"]] user_count = len(users) From af8d0169e6ee6002ac51725858ea0eb3428a5962 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 14:58:56 +0530 Subject: [PATCH 4/8] On Facility Switch remove playlist values from url, to avoid get exercise error --- .../static/js/coachreports/exercise_mastery_view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js index 40d24792cf..3370db353e 100644 --- a/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js +++ b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js @@ -11,7 +11,8 @@ $(function() { $("#facility").change(function() { window.location.href = setGetParamDict(window.location.href, { "facility": $("#facility option:selected").val(), - "group": $("#" + $("#facility option:selected").val() + "_group_select").val() + "group": $("#" + $("#facility option:selected").val() + "_group_select").val(), + "playlist": "" }); }); From 350a7f5eb61443edd15d72d84f8afab1473ad51a Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 15:08:03 +0530 Subject: [PATCH 5/8] Fixed minor bugs related to division by zero and exercise selection --- .../coachreports/exercise_mastery_view.html | 17 +++++------------ kalite/coachreports/views.py | 12 +++++++++--- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html index b405443736..5e658e4f39 100644 --- a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html +++ b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html @@ -68,21 +68,14 @@ {% if not students %}

      - {% if not groups.0.groups and not groups.1 %} - {% comment %}No groups available: then "ungrouped" is selected, and "no students" returned.{% endcomment %} - {% trans "No student accounts have been created." %} - {% endif %} {% if not request.GET.playlist %} - {% comment %}Group was selected, but data not queried because a playlist was not selected - (NOTE: this required knowledge of how the view queries data){% endcomment %}
      - {% if playlists %} - {% trans "Please select a playlist above." %} - {% endif %} + {% if playlists %} + {% trans "Please select a playlist above." %} + {% else %} + {% trans "No student accounts in this group have been created." %} + {% endif %}
      - {% else %} - {% comment %}Everything specified, but no users fit the query.{% endcomment %} - {% trans "No student accounts in this group have been created." %} {% endif %}

      diff --git a/kalite/coachreports/views.py b/kalite/coachreports/views.py index 163c8e6d84..83cf6e54ae 100755 --- a/kalite/coachreports/views.py +++ b/kalite/coachreports/views.py @@ -382,9 +382,15 @@ def exercise_mastery_view(request, facility): elif student["exercise_logs"][ex]["attempts"] > 0: progress = progress + 1 - context["progress"] = (100/exercise_count) * progress/user_count - context["mastered"] = (100/exercise_count) * mastered/user_count - context["struggling"] = (100/exercise_count) * struggling/user_count + + context["progress"] = 0 + context["mastered"] = 0 + context["struggling"] = 0 + + if user_count > 0 and exercise_count > 0: + context["progress"] = (100/exercise_count) * progress/user_count + context["mastered"] = (100/exercise_count) * mastered/user_count + context["struggling"] = (100/exercise_count) * struggling/user_count log_coach_report_view(request) From 1c582c2cd390dc98878936838913f18fe41058e8 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 15:14:57 +0530 Subject: [PATCH 6/8] Moved jquery.js required for dyna-tree persistance to static-libraries --- .../templates/coachreports/exercise_mastery_view.html | 2 +- .../js/coachreports => static-libraries/js}/jquery.cookie.js | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename {kalite/coachreports/static/js/coachreports => static-libraries/js}/jquery.cookie.js (100%) diff --git a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html index 5e658e4f39..d2db678d5a 100644 --- a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html +++ b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html @@ -17,7 +17,7 @@ {% block headjs %}{{ block.super }} - + diff --git a/kalite/coachreports/static/js/coachreports/jquery.cookie.js b/static-libraries/js/jquery.cookie.js similarity index 100% rename from kalite/coachreports/static/js/coachreports/jquery.cookie.js rename to static-libraries/js/jquery.cookie.js From 66a0248bd9c43a4201670a7445557cbb36fe5f65 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 15:30:58 +0530 Subject: [PATCH 7/8] Refactoring and percentage in float --- .../coachreports/exercise_mastery_view.css | 6 ++++- .../coachreports/exercise_mastery_view.html | 2 +- kalite/coachreports/views.py | 23 +++++++++++-------- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css b/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css index 303cfd8eba..75365e0e0f 100644 --- a/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css +++ b/kalite/coachreports/static/css/coachreports/exercise_mastery_view.css @@ -119,7 +119,7 @@ th.headrowuser { .legend { float:right; - width:140px; + width:160px; height:20px; text-align:center; vertical-align:middle; @@ -152,3 +152,7 @@ th.headrowuser { vertical-align: none; padding: 0px; } + +#exercise-mastery { + font-size: 10px +} \ No newline at end of file diff --git a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html index d2db678d5a..094b0c59a2 100644 --- a/kalite/coachreports/templates/coachreports/exercise_mastery_view.html +++ b/kalite/coachreports/templates/coachreports/exercise_mastery_view.html @@ -119,7 +119,7 @@ {% for exercise in exercises %} -
      +
      Mastered: {{ exercise_stats|get_item:exercise.id|get_item:"mastered" }} Progress: {{ exercise_stats|get_item:exercise.id|get_item:"progress" }} Struggling: {{ exercise_stats|get_item:exercise.id|get_item:"struggling" }} diff --git a/kalite/coachreports/views.py b/kalite/coachreports/views.py index 83cf6e54ae..3c55833ef7 100755 --- a/kalite/coachreports/views.py +++ b/kalite/coachreports/views.py @@ -329,7 +329,8 @@ def exercise_mastery_view(request, facility): elif ex["attempts"] > 0: exercise_stats[ex["exercise_id"]]["progress"] = exercise_stats[ex["exercise_id"]]["progress"] + 1 - exercise_stats[ex["exercise_id"]]["mastery"] = exercise_stats[ex["exercise_id"]]["mastered"] * 100 / user_count + if user_count: + exercise_stats[ex["exercise_id"]]["mastery"] = "{0:.2f}".format( exercise_stats[ex["exercise_id"]]["mastered"] * 100.0 / user_count ) context["exercise_stats"] = exercise_stats context["exercise_count"] = exercise_count @@ -345,6 +346,7 @@ def exercise_mastery_view(request, facility): progress = 0 mastered = 0 struggling = 0 + mastery = 0 for ex in log_table: if log_table[ex]["complete"] == True: @@ -354,9 +356,10 @@ def exercise_mastery_view(request, facility): elif log_table[ex]["attempts"] > 0: progress = progress + 1 - mastery = (mastered * 100)/exercise_count + if exercise_count: + mastery = "{0:.2f}".format( (mastered * 100.0)/exercise_count ) - context["students"].append({ # this could be DRYer + context["students"].append({ "first_name": user.first_name, "last_name": user.last_name, "username": user.username, @@ -374,12 +377,12 @@ def exercise_mastery_view(request, facility): mastered = 0 struggling = 0 for student in context["students"]: - for ex in student["exercise_logs"]: - if student["exercise_logs"][ex]["complete"] == True: + for ex in student["exercise_logs"].values(): + if ex["complete"] == True: mastered = mastered + 1 - elif student["exercise_logs"][ex]["struggling"] == True: + elif ex["struggling"] == True: struggling = struggling + 1 - elif student["exercise_logs"][ex]["attempts"] > 0: + elif ex["attempts"] > 0: progress = progress + 1 @@ -388,9 +391,9 @@ def exercise_mastery_view(request, facility): context["struggling"] = 0 if user_count > 0 and exercise_count > 0: - context["progress"] = (100/exercise_count) * progress/user_count - context["mastered"] = (100/exercise_count) * mastered/user_count - context["struggling"] = (100/exercise_count) * struggling/user_count + context["progress"] = "{0:.2f}".format( (100.0/exercise_count) * progress/user_count ) + context["mastered"] = "{0:.2f}".format( (100.0/exercise_count) * mastered/user_count ) + context["struggling"] = "{0:.2f}".format( (100.0/exercise_count) * struggling/user_count ) log_coach_report_view(request) From c106ca1a8d2377ba9711f63bd9cab6ebbd4bac6b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 15:34:33 +0530 Subject: [PATCH 8/8] removed extra comma, tests was failing --- .../static/js/coachreports/exercise_mastery_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js index 3370db353e..96b4c1d0c3 100644 --- a/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js +++ b/kalite/coachreports/static/js/coachreports/exercise_mastery_view.js @@ -69,6 +69,6 @@ $(function(){ return dtnode.data.key; }); window.location.href = setGetParam(window.location.href, "playlist", selKeys); - }, + } }); });