From 95ae21e2afe94e7430cd151ad0f42186e4ba541e Mon Sep 17 00:00:00 2001 From: anuragkanungo Date: Wed, 4 Feb 2015 12:08:04 +0530 Subject: [PATCH 001/140] Student should be redirected to login page if he/she gets looged out while doing playlist exercies --- kalite/distributed/static/js/distributed/exercises.js | 7 ++++++- kalite/playlist/static/js/playlist/view_playlist.js | 10 ++++++++-- kalite/playlist/views.py | 3 ++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/kalite/distributed/static/js/distributed/exercises.js b/kalite/distributed/static/js/distributed/exercises.js index bc615c4adb..ff478ceec1 100755 --- a/kalite/distributed/static/js/distributed/exercises.js +++ b/kalite/distributed/static/js/distributed/exercises.js @@ -222,7 +222,12 @@ window.AttemptLogCollection = Backbone.Collection.extend({ }, url: function() { - return "/api/attemptlog/?" + $.param(this.filters, true); + if (window.statusModel.get("is_logged_in")) { + return "/api/attemptlog/?" + $.param(this.filters, true); + } + else { + window.location = "/securesync/login"; + } }, add_new: function(attemptlog) { diff --git a/kalite/playlist/static/js/playlist/view_playlist.js b/kalite/playlist/static/js/playlist/view_playlist.js index a30660548b..be06d6492f 100644 --- a/kalite/playlist/static/js/playlist/view_playlist.js +++ b/kalite/playlist/static/js/playlist/view_playlist.js @@ -82,8 +82,14 @@ window.PlaylistContentAreaView = Backbone.View.extend({ }, render: function() { - this.$el.html(this.template(this.model.attributes)); - return this; + if (window.statusModel.get("is_logged_in")) { + this.$el.html(this.template(this.model.attributes)); + return this; + } + else + { + window.location = "/securesync/login"; + } }, show_view: function(view) { diff --git a/kalite/playlist/views.py b/kalite/playlist/views.py index ee0bb7a746..060e915b8e 100644 --- a/kalite/playlist/views.py +++ b/kalite/playlist/views.py @@ -1,6 +1,6 @@ from annoying.decorators import render_to -from kalite.shared.decorators import require_admin +from kalite.shared.decorators import require_admin, require_login @require_admin @@ -12,6 +12,7 @@ def assign_playlists(request): return context +@require_login @render_to("playlist/view_playlist.html") def view_playlist(request, playlist_id): context = { From 3daac460681b59ed2b0112ef95f8329f2a631452 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Sat, 7 Feb 2015 14:26:48 +0530 Subject: [PATCH 002/140] Added Unit Variable to ExerciseLog Export --- kalite/control_panel/api_resources.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index 8522888aa2..4ef61ada57 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -15,7 +15,7 @@ from .api_serializers import CSVSerializer from store.models import StoreItem - +from kalite.student_testing.utils import get_current_unit_settings_value class FacilityResource(ModelResource): @@ -245,6 +245,31 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): attempt_logs = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]) bundle.data["timestamp_first"] = attempt_logs.count() and attempt_logs.aggregate(Min('timestamp'))['timestamp__min'] or None bundle.data["timestamp_last"] = attempt_logs.count() and attempt_logs.aggregate(Max('timestamp'))['timestamp__max'] or None + bundle.data["unit"] = 0 + current_unit = get_current_unit_settings_value(user.facility.id) + if current_unit == 101: + bundle.data["unit"] = 101 + else: + x = bundle.data["timestamp_first"] + if x: + try: + x = datetime.strptime(str(x), '%Y-%m-%d %H:%M:%S') + except ValueError: + x = datetime.strptime(str(x), '%Y-%m-%d %H:%M:%S.%f') + + for i in xrange(101,current_unit): + y = StoreTransactionLog.objects.filter(user=user, context_id=i, context_type="unit_points_reset", item="gift_card").exclude(purchased_at__isnull=True)[0].purchased_at + if y: + try: + y = datetime.strptime(str(y), '%Y-%m-%d %H:%M:%S.%f') + except ValueError: + y = datetime.strptime(str(y), '%Y-%m-%d %H:%M:%S') + if x <= y: + bundle.data["unit"] = i + break + else: + bundle.data["unit"] = i+1 + bundle.data["part1_answered"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]).count() bundle.data["part1_correct"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], correct=True, context_type__in=["playlist", "exercise"]).count() bundle.data["part2_attempted"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["exercise_fixedblock", "playlist_fixedblock"]).count() From 30758b4832b243ac9a2996f187a36197c44bde3e Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Sat, 7 Feb 2015 22:31:58 +0530 Subject: [PATCH 003/140] Updated code for unit variable in exercise log --- kalite/control_panel/api_resources.py | 33 ++++++++------------------- 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index 4ef61ada57..f610961b37 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -15,7 +15,7 @@ from .api_serializers import CSVSerializer from store.models import StoreItem -from kalite.student_testing.utils import get_current_unit_settings_value + class FacilityResource(ModelResource): @@ -246,29 +246,14 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): bundle.data["timestamp_first"] = attempt_logs.count() and attempt_logs.aggregate(Min('timestamp'))['timestamp__min'] or None bundle.data["timestamp_last"] = attempt_logs.count() and attempt_logs.aggregate(Max('timestamp'))['timestamp__max'] or None bundle.data["unit"] = 0 - current_unit = get_current_unit_settings_value(user.facility.id) - if current_unit == 101: - bundle.data["unit"] = 101 - else: - x = bundle.data["timestamp_first"] - if x: - try: - x = datetime.strptime(str(x), '%Y-%m-%d %H:%M:%S') - except ValueError: - x = datetime.strptime(str(x), '%Y-%m-%d %H:%M:%S.%f') - - for i in xrange(101,current_unit): - y = StoreTransactionLog.objects.filter(user=user, context_id=i, context_type="unit_points_reset", item="gift_card").exclude(purchased_at__isnull=True)[0].purchased_at - if y: - try: - y = datetime.strptime(str(y), '%Y-%m-%d %H:%M:%S.%f') - except ValueError: - y = datetime.strptime(str(y), '%Y-%m-%d %H:%M:%S') - if x <= y: - bundle.data["unit"] = i - break - else: - bundle.data["unit"] = i+1 + + if bundle.data["timestamp_first"]: + for i in xrange(101,104): + if StoreTransactionLog.objects.filter(user=user, context_id=i, context_type="unit_points_reset", purchased_at__gte=bundle.data["timestamp_first"]).count() > 0: + bundle.data["unit"] = i + break + else: + bundle.data["unit"] = i + 1 bundle.data["part1_answered"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]).count() bundle.data["part1_correct"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], correct=True, context_type__in=["playlist", "exercise"]).count() From 5d6a29d682ac2f25c7a4835e91fed8969d319f38 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 10 Feb 2015 16:21:20 +0530 Subject: [PATCH 004/140] Increased height of exercise name column in exam reports --- kalite/coachreports/static/css/coachreports/test_view.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kalite/coachreports/static/css/coachreports/test_view.css b/kalite/coachreports/static/css/coachreports/test_view.css index 72479817a3..1729b27e87 100644 --- a/kalite/coachreports/static/css/coachreports/test_view.css +++ b/kalite/coachreports/static/css/coachreports/test_view.css @@ -14,7 +14,6 @@ th, td { max-width: 80px; min-width: 60px; font-weight: bold; - white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.1em; @@ -82,4 +81,7 @@ th, td { width: 15px; top: 3px; left: 10px; +} +.data-header, .student-header { + height: 80px; } \ No newline at end of file From 6d7e510675ee121dca53e492ac28bdd8623af61b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 10 Feb 2015 23:21:54 +0530 Subject: [PATCH 005/140] Added Exam Prep Playlists --- kalite/playlist/playlists.json | 781 ++++++++++++++++++ .../static/js/playlist/assign_playlists.js | 2 + .../templates/playlist/assign_playlists.html | 6 + 3 files changed, 789 insertions(+) diff --git a/kalite/playlist/playlists.json b/kalite/playlist/playlists.json index 0659210dbe..273d4cab43 100644 --- a/kalite/playlist/playlists.json +++ b/kalite/playlist/playlists.json @@ -10123,5 +10123,786 @@ "tag": "Grade 6", "title": "Playlist 2: Ratio and Proportions", "unit": 104 + }, + { + "title": + "Playlist Exam Prep : Numbers,Place Value and the 4 Operations ( +, -, x, /)", + "id": "g5_u500_ep1", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/place_value/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/addition_4/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems_1/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems_2/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/division_4/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/order_of_operations/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_sequences_1/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/rate_problems_0.5/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/prime_numbers/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/divisibility_tests/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/prime_factorization/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/composite_numbers/", + "sort_order": 12, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/least_common_multiple/", + "sort_order": 13, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Fractions", + "id": "g5_u500_ep2", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/equivalent_fractions/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/equivalent_fractions_2/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/simplifying_fractions/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_fractions_2/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/ordering_fractions/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_fractions/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_subtracting_mixed_numbers_0.5/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_subtracting_mixed_numbers_1/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_and_subtracting_fractions/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/fraction_word_problems_1/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_fractions_by_integers/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_fractions_0.5/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_fractions_0.5/", + "sort_order": 12, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_fractions/", + "sort_order": 13, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Decimals", + "id": "g5_u500_ep3", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/subtracting_decimals_0.5/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/subtracting_decimals/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_decimals/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/converting_fractions_to_decimals_0.5/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/understanding_decimals_place_value/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_decimals_1/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_decimals_2/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/understanding_moving_the_decimal/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals_2/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals_0.5/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": + "Playlist Exam Prep : Numbers,Place Value and the 4 Operations ( +, -, x, /)", + "id": "g5_u500_ep1", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/place_value/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/addition_4/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems_1/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems_2/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_word_problems/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/division_4/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/order_of_operations/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/arithmetic_sequences_1/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/rate_problems_0.5/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/prime_numbers/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/divisibility_tests/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/prime_factorization/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/composite_numbers/", + "sort_order": 12, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/least_common_multiple/", + "sort_order": 13, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Fractions", + "id": "g5_u500_ep2", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/equivalent_fractions/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/equivalent_fractions_2/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/simplifying_fractions/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_fractions_2/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/ordering_fractions/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_fractions/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_subtracting_mixed_numbers_0.5/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_subtracting_mixed_numbers_1/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_and_subtracting_fractions/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/fraction_word_problems_1/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_fractions_by_integers/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_fractions_0.5/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_fractions_0.5/", + "sort_order": 12, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_fractions/", + "sort_order": 13, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Decimals", + "id": "g5_u500_ep3", + "tag": "Grade 5", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/subtracting_decimals_0.5/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/subtracting_decimals/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/multiplying_decimals/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/converting_fractions_to_decimals_0.5/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/understanding_decimals_place_value/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_decimals_1/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_decimals_2/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/understanding_moving_the_decimal/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals_2/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_decimals_0.5/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Geometry, Exponents and Square roots", + "id": "g6_u600_ep1", + "tag": "Grade 6", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/solid_geometry/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/volume_2/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/volume_1/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/volume_with_unit_cubes/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/exponents_1/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/square_roots/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/square_roots_2/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/ratio_word_problems/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/rate_problems_0.5/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/finding_percents/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/divisibility_tests/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/order_of_operations/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/prime_factorization/", + "sort_order": 12, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": + "/e/least_common_multiple_and_greatest_common_divisor_word_problems/", + "sort_order": 13, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": + "Playlist Exam Prep: Integers, Algebraic Expressions and Equations", + "id": "g6_u600_ep2", + "tag": "Grade 6", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/adding_negative_numbers/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/adding_and_subtracting_negative_numbers/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/number_line_3/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/writing_expressions_1/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/writing_expressions_2/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/evaluating_expressions_1/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/evaluating_expressions_2/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/combining_like_terms_1/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/combining_like_terms_2/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/linear_equations_1/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/one_step_equations/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/linear_equations_2/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + } + ] + }, + { + "title": "Playlist Exam Prep: Geometry, Decimals, Symmetry", + "id": "g6_u600_ep3", + "tag": "Grade 6", + "show": true, + "unit": 100, + "entries": [ + { + "entity_id": "/e/complementary_and_supplementary_angles/", + "sort_order": 0, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/angle_addition_postulate/", + "sort_order": 1, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/vertical_angles/", + "sort_order": 2, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/parallel_lines_1/", + "sort_order": 3, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/triangle_types/", + "sort_order": 4, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/comparing_decimals_2/", + "sort_order": 5, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/decimals_on_the_number_line_3/", + "sort_order": 6, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/understanding_decimals_place_value/", + "sort_order": 7, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_decimals_0.5/", + "sort_order": 8, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_decimals_1/", + "sort_order": 9, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/dividing_decimals_2/", + "sort_order": 10, + "entity_kind": "Exercise", + "is_essential": false + }, + { + "entity_id": "/e/axis_of_symmetry/", + "sort_order": 11, + "entity_kind": "Exercise", + "is_essential": false + } + ] } ] \ No newline at end of file diff --git a/kalite/playlist/static/js/playlist/assign_playlists.js b/kalite/playlist/static/js/playlist/assign_playlists.js index ef5ce847f1..93634443b2 100644 --- a/kalite/playlist/static/js/playlist/assign_playlists.js +++ b/kalite/playlist/static/js/playlist/assign_playlists.js @@ -158,6 +158,8 @@ var AppView = Backbone.View.extend({ $("#pre-playlists").append(view.render().el); } else if (title.indexOf("Advanced") > -1){ $("#advanced-playlists").append(view.render().el); + } else if (title.indexOf("Exam Prep") > -1){ + $("#exam-prep-playlists").append(view.render().el); } }, diff --git a/kalite/playlist/templates/playlist/assign_playlists.html b/kalite/playlist/templates/playlist/assign_playlists.html index 99a72a40ef..fc5ae7c9ba 100644 --- a/kalite/playlist/templates/playlist/assign_playlists.html +++ b/kalite/playlist/templates/playlist/assign_playlists.html @@ -61,6 +61,12 @@ Advanced + + + + Exam Prep + + {% endblock content %} From 0cef1b92ebd380999d750a50eea2babf9319659b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Wed, 11 Feb 2015 01:05:10 +0530 Subject: [PATCH 006/140] Added student_testing/data/407.json --- kalite/student_testing/data/407.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 kalite/student_testing/data/407.json diff --git a/kalite/student_testing/data/407.json b/kalite/student_testing/data/407.json new file mode 100644 index 0000000000..34ddd87259 --- /dev/null +++ b/kalite/student_testing/data/407.json @@ -0,0 +1 @@ +{"playlist_ids": ["g4_p5", "g4_p6", "g4_p7"], "repeats": 1, "seed": 2388, "ids": ["place_value", "counting_1", "comparing_whole_numbers", "addition_1", "addition_2", "addition_3", "subtraction_3", "arithmetic_word_problems_1", "number_line", "multiplication_1", "arithmetic_word_problems_2", "measuring_segments", "division_0.5", "division_1.5", "division_3", "arithmetic_word_problems_2", "reading_tables_1", "reading_pictographs_1", "reading_bar_charts_1", "creating_bar_charts_1", "reading_bar_charts_2", "telling_time_0.5", "telling_time", "recognizing_fractions_0.5", "recognizing_fractions", "fractions_on_the_number_line_1", "fraction_word_problems_1", "fractions_cut_and_copy_1", "comparing_fractions_1", "adding_fractions_with_common_denominators", "comparing_improper_fractions_and_mixed_numbers", "converting_mixed_numbers_and_improper_fractions", "fractions_on_the_number_line_2", "ordering_improper_fractions_and_mixed_numbers", "measuring_angles", "angle_types", "exploring_angle_pairs_1", "conditional_statements_2", "arithmetic_word_problems_1", "radius_diameter_and_circumference"], "title": "grade4: Unit Test 2", "show": false} From 6c70ea75ff7ef5bf0495cc90ebc51f97b5292574 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Wed, 11 Feb 2015 01:36:09 +0530 Subject: [PATCH 007/140] Added 15Nov2014 RCT condition check for unit variable --- kalite/control_panel/api_resources.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index f610961b37..4272e6a867 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -1,5 +1,5 @@ from annoying.functions import get_object_or_None -from datetime import datetime +from datetime import datetime, date from django.db.models import Max, Min from django.http import HttpResponse from tastypie import fields @@ -255,6 +255,9 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): else: bundle.data["unit"] = i + 1 + if bundle.data["unit"] == 0 and bundle.data["timestamp_first"].date() > date(2014, 11, 15): + bundle.data["unit"] = 101 + bundle.data["part1_answered"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]).count() bundle.data["part1_correct"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], correct=True, context_type__in=["playlist", "exercise"]).count() bundle.data["part2_attempted"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["exercise_fixedblock", "playlist_fixedblock"]).count() From c6402d31d7ed58eadb8635398808a8795a38e2f9 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Wed, 11 Feb 2015 20:12:50 +0530 Subject: [PATCH 008/140] 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 c1abb706b07efba70ec2d5e9f007fbd0f4fcb45d Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Wed, 11 Feb 2015 23:11:29 +0530 Subject: [PATCH 009/140] RCT Unit Variable comment --- kalite/control_panel/api_resources.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index 4272e6a867..81689218cd 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -255,6 +255,7 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): else: bundle.data["unit"] = i + 1 + # Anything done after Nov 15, 2014 is in the RCT which starts from Unit 101 if bundle.data["unit"] == 0 and bundle.data["timestamp_first"].date() > date(2014, 11, 15): bundle.data["unit"] = 101 From 5105876ea4005c7f74df5ba0798c26fde6ea12ca Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Thu, 12 Feb 2015 16:38:00 +0530 Subject: [PATCH 010/140] 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 6e6c0f011148a118ffafa017b2363df89168ab0b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Thu, 12 Feb 2015 18:26:18 +0530 Subject: [PATCH 011/140] correct unit for those we are sure else unit 0 --- kalite/control_panel/api_resources.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index 81689218cd..1215058b14 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -247,17 +247,15 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): bundle.data["timestamp_last"] = attempt_logs.count() and attempt_logs.aggregate(Max('timestamp'))['timestamp__max'] or None bundle.data["unit"] = 0 - if bundle.data["timestamp_first"]: + # Anything done after Nov 15, 2014 is in the RCT which starts from Unit 101 + if StoreTransactionLog.objects.filter(user=user, context_type="unit_points_reset", purchased_at__gte=datetime(2014, 11, 15, 0, 0, 0)).count() == 0: + bundle.data["unit"] = 101 + + elif bundle.data["timestamp_first"]: for i in xrange(101,104): if StoreTransactionLog.objects.filter(user=user, context_id=i, context_type="unit_points_reset", purchased_at__gte=bundle.data["timestamp_first"]).count() > 0: bundle.data["unit"] = i break - else: - bundle.data["unit"] = i + 1 - - # Anything done after Nov 15, 2014 is in the RCT which starts from Unit 101 - if bundle.data["unit"] == 0 and bundle.data["timestamp_first"].date() > date(2014, 11, 15): - bundle.data["unit"] = 101 bundle.data["part1_answered"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]).count() bundle.data["part1_correct"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], correct=True, context_type__in=["playlist", "exercise"]).count() From 26592ee3c4e6237e92e8a4b01d1ec6af0727694a Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 01:29:30 +0530 Subject: [PATCH 012/140] Added comment why to keep unit variable as 0 for some cases --- kalite/control_panel/api_resources.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kalite/control_panel/api_resources.py b/kalite/control_panel/api_resources.py index 1215058b14..259f2f5eb6 100644 --- a/kalite/control_panel/api_resources.py +++ b/kalite/control_panel/api_resources.py @@ -257,6 +257,11 @@ def alter_list_data_to_serialize(self, request, to_be_serialized): bundle.data["unit"] = i break + # For entries we are not sure about the unit, we keep them as 0, mostly chances are that the unit would be the current_unit. + # As we can't predict the current unit on the central server, its better to have the value as 0. + # We can't predict the current unit because in some database we have unit_point_reset gift card for unit 101 whereas the current unit is also 101. + # So we can't find current_unit by saying that the first unit that doesn't have the unit_point_reset gift card is current_unit. + bundle.data["part1_answered"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["playlist", "exercise"]).count() bundle.data["part1_correct"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], correct=True, context_type__in=["playlist", "exercise"]).count() bundle.data["part2_attempted"] = AttemptLog.objects.filter(user=user, exercise_id=bundle.data["exercise_id"], context_type__in=["exercise_fixedblock", "playlist_fixedblock"]).count() From 528683397fb0dcc60baa8446661db2fa65c2726b Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Fri, 13 Feb 2015 14:47:11 +0530 Subject: [PATCH 013/140] 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 014/140] 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 015/140] 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 016/140] 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 017/140] 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 018/140] 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); - }, + } }); }); From bc21a36536e66e4dbb369a4012ff3b7b5a071371 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Mon, 23 Feb 2015 19:46:29 +0530 Subject: [PATCH 019/140] Fix 3040, Redirect to login page in case of Unauthorized --- static-libraries/js/khan-lite.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/static-libraries/js/khan-lite.js b/static-libraries/js/khan-lite.js index 01568f4640..4dc7a257e6 100644 --- a/static-libraries/js/khan-lite.js +++ b/static-libraries/js/khan-lite.js @@ -147,7 +147,10 @@ function handleFailedAPI(resp, error_prefix) { console.log(e); } break; + case 401: case 403: + // Redirect to Login Page and add the current url as next + window.location.href = USER_LOGIN_URL + "?next=" + window.location.href messages = {error: sprintf(gettext("You are not authorized to complete the request. Please login as an authorized user, then retry the request."), { login_url: USER_LOGIN_URL })}; From 3bffde53d2a8ee139864a7bc975b5a5c73b0da21 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 11:58:53 +0530 Subject: [PATCH 020/140] Used SetGetParam for url --- static-libraries/js/khan-lite.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static-libraries/js/khan-lite.js b/static-libraries/js/khan-lite.js index 4dc7a257e6..9a6d985bef 100644 --- a/static-libraries/js/khan-lite.js +++ b/static-libraries/js/khan-lite.js @@ -150,7 +150,7 @@ function handleFailedAPI(resp, error_prefix) { case 401: case 403: // Redirect to Login Page and add the current url as next - window.location.href = USER_LOGIN_URL + "?next=" + window.location.href + window.location.href = setGetParam(window.location.href, "next", USER_LOGIN_URL) messages = {error: sprintf(gettext("You are not authorized to complete the request. Please login as an authorized user, then retry the request."), { login_url: USER_LOGIN_URL })}; From 5e0b016c53d927b07578828b7881c00d75844ab1 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 12:34:20 +0530 Subject: [PATCH 021/140] Test for redirect unauthorized playlist view request to login page --- kalite/distributed/tests/browser_tests/quiz.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index e06e85c336..9b08b4bbf5 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -9,6 +9,8 @@ from kalite.testing.base import KALiteBrowserTestCase from kalite.testing.mixins import BrowserActionMixins, FacilityMixins +import urllib + PLAYLIST_ID = "g3_p1" @@ -53,3 +55,13 @@ def test_quiz_first_answer_correct_not_registered(self): self.browser.execute_script("quizlog.add_response_log_item({correct: true});") self.assertEqual(self.browser.execute_script("return quizlog.get('total_correct')"), 1) self.assertEqual(self.browser.execute_script("return quizlog._response_log_cache[0]"), 1) + + + def test_unauthorized_request_redirect_to_login(self): + + self.browser_logout_user() + self.browse_to( + self.live_server_url + + reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID})) + #Using urllib.quote to convert "/" from url to "%2F" + self.assertEqual(self.browser.current_url, self.reverse('login') + "?next=" + urllib.quote( reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID}), '')) From d4c91a2d42571bed37490fd9d9cb30a4e5beb44e Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 13:53:53 +0530 Subject: [PATCH 022/140] Added test for unauthorized request to redirect to login page --- kalite/distributed/tests/browser_tests/quiz.py | 12 ++++++++---- static-libraries/js/khan-lite.js | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index 9b08b4bbf5..c0e938a31c 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -8,10 +8,11 @@ from kalite.testing.base import KALiteBrowserTestCase from kalite.testing.mixins import BrowserActionMixins, FacilityMixins +from selenium.webdriver.common.keys import Keys import urllib -PLAYLIST_ID = "g3_p1" +PLAYLIST_ID = "g4_u400_ap1" class QuizTest(BrowserActionMixins, FacilityMixins, KALiteBrowserTestCase): @@ -59,9 +60,12 @@ def test_quiz_first_answer_correct_not_registered(self): def test_unauthorized_request_redirect_to_login(self): - self.browser_logout_user() self.browse_to( self.live_server_url + reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID})) - #Using urllib.quote to convert "/" from url to "%2F" - self.assertEqual(self.browser.current_url, self.reverse('login') + "?next=" + urllib.quote( reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID}), '')) + hash_value = urllib.urlparse(self.browser.current_url).fragment + self.browser.delete_all_cookies() + self.browser.find_element_by_id('solutionarea').find_element_by_css_selector('input[type=text]').click() + self.browser_send_keys(unicode("Anurag")) + self.browser_send_keys(Keys.RETURN) + self.assertEqual(self.browser.current_url, self.reverse('login') + "?next=" + reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID}) + "#" + hash_value) diff --git a/static-libraries/js/khan-lite.js b/static-libraries/js/khan-lite.js index 9a6d985bef..49c4e55977 100644 --- a/static-libraries/js/khan-lite.js +++ b/static-libraries/js/khan-lite.js @@ -150,7 +150,7 @@ function handleFailedAPI(resp, error_prefix) { case 401: case 403: // Redirect to Login Page and add the current url as next - window.location.href = setGetParam(window.location.href, "next", USER_LOGIN_URL) + window.location.href = setGetParam(USER_LOGIN_URL, "next", window.location.pathname + window.location.hash) messages = {error: sprintf(gettext("You are not authorized to complete the request. Please login as an authorized user, then retry the request."), { login_url: USER_LOGIN_URL })}; From cfb423d474064183e3d00b7828ef81c2a498dea6 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 18:01:24 +0530 Subject: [PATCH 023/140] Moved import to top --- kalite/distributed/tests/browser_tests/quiz.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index c0e938a31c..c01b471991 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -1,6 +1,9 @@ """ These use a web-browser, along selenium, to simulate user actions. """ + +import urllib + from django.conf import settings logging = settings.LOG from django.core.urlresolvers import reverse @@ -10,8 +13,6 @@ from kalite.testing.mixins import BrowserActionMixins, FacilityMixins from selenium.webdriver.common.keys import Keys -import urllib - PLAYLIST_ID = "g4_u400_ap1" @@ -68,4 +69,5 @@ def test_unauthorized_request_redirect_to_login(self): self.browser.find_element_by_id('solutionarea').find_element_by_css_selector('input[type=text]').click() self.browser_send_keys(unicode("Anurag")) self.browser_send_keys(Keys.RETURN) + #Not using urlencode for next param as its hassle to use escape character for params only. self.assertEqual(self.browser.current_url, self.reverse('login') + "?next=" + reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID}) + "#" + hash_value) From 14c87014c14600e1ddd60d27608e00c268db70a4 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 18:27:41 +0530 Subject: [PATCH 024/140] Added implicit wait for css selector to be found in selenium test --- kalite/distributed/tests/browser_tests/quiz.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index c01b471991..01b7800c5e 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -66,6 +66,7 @@ def test_unauthorized_request_redirect_to_login(self): reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID})) hash_value = urllib.urlparse(self.browser.current_url).fragment self.browser.delete_all_cookies() + self.browser.implicitly_wait(10) self.browser.find_element_by_id('solutionarea').find_element_by_css_selector('input[type=text]').click() self.browser_send_keys(unicode("Anurag")) self.browser_send_keys(Keys.RETURN) From b56a51410c5d05ce96914718bec91534181a6bbb Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 19:46:46 +0530 Subject: [PATCH 025/140] Updated implicit wait after selector --- kalite/distributed/tests/browser_tests/quiz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index 01b7800c5e..239b9623e5 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -66,8 +66,8 @@ def test_unauthorized_request_redirect_to_login(self): reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID})) hash_value = urllib.urlparse(self.browser.current_url).fragment self.browser.delete_all_cookies() - self.browser.implicitly_wait(10) self.browser.find_element_by_id('solutionarea').find_element_by_css_selector('input[type=text]').click() + self.browser.implicitly_wait(20) self.browser_send_keys(unicode("Anurag")) self.browser_send_keys(Keys.RETURN) #Not using urlencode for next param as its hassle to use escape character for params only. From 4ffdb35c178ac2c3e9944fbbdcd70d5da5945189 Mon Sep 17 00:00:00 2001 From: Anurag Kanungo Date: Tue, 24 Feb 2015 20:12:29 +0530 Subject: [PATCH 026/140] Increase implict wait timeout time --- kalite/distributed/tests/browser_tests/quiz.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kalite/distributed/tests/browser_tests/quiz.py b/kalite/distributed/tests/browser_tests/quiz.py index 239b9623e5..ae49de41f9 100755 --- a/kalite/distributed/tests/browser_tests/quiz.py +++ b/kalite/distributed/tests/browser_tests/quiz.py @@ -66,8 +66,8 @@ def test_unauthorized_request_redirect_to_login(self): reverse("view_playlist", kwargs={"playlist_id": PLAYLIST_ID})) hash_value = urllib.urlparse(self.browser.current_url).fragment self.browser.delete_all_cookies() + self.browser.implicitly_wait(30) self.browser.find_element_by_id('solutionarea').find_element_by_css_selector('input[type=text]').click() - self.browser.implicitly_wait(20) self.browser_send_keys(unicode("Anurag")) self.browser_send_keys(Keys.RETURN) #Not using urlencode for next param as its hassle to use escape character for params only. From b68aadfdf61c9091ff82a89476a2c9de2fd885be Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 27 Feb 2015 15:51:52 +0530 Subject: [PATCH 027/140] Fixing up loadtesting. --- .../templates/distributed/loadtesting/load_test.html | 2 +- kalite/distributed/urls.py | 2 +- kalite/testing/loadtesting/views.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kalite/distributed/templates/distributed/loadtesting/load_test.html b/kalite/distributed/templates/distributed/loadtesting/load_test.html index 566780ccbc..230347054b 100644 --- a/kalite/distributed/templates/distributed/loadtesting/load_test.html +++ b/kalite/distributed/templates/distributed/loadtesting/load_test.html @@ -278,7 +278,7 @@ var exercise_sequence = [ function(input, callback) { - this.goto("/math/arithmetic/fractions/understanding_fractions/e/recognizing_fractions_0.5/", callback); + this.goto("/learn/khan/math/arithmetic/addition-subtraction/basic_addition/addition_1/", callback); }, function(input, callback) { this.$("#solutionarea input").val(100).keypress() diff --git a/kalite/distributed/urls.py b/kalite/distributed/urls.py index 69f700560c..8802923c01 100644 --- a/kalite/distributed/urls.py +++ b/kalite/distributed/urls.py @@ -73,7 +73,7 @@ ) # Testing -if "tests.loadtesting" in settings.INSTALLED_APPS: +if "kalite.testing.loadtesting" in settings.INSTALLED_APPS: urlpatterns += patterns(__package__ + '.views', url(r'^loadtesting/', include('kalite.testing.loadtesting.urls')), ) diff --git a/kalite/testing/loadtesting/views.py b/kalite/testing/loadtesting/views.py index bd0de975b7..05ce5435d3 100644 --- a/kalite/testing/loadtesting/views.py +++ b/kalite/testing/loadtesting/views.py @@ -20,7 +20,7 @@ def load_test(request, nusers=None): # Loop over all needed students while n_users_created < int(request.GET.get("nusers", 1)): n_users_created += 1 - unpw = "s%d" % n_users_created + unpw = "sssss%d" % n_users_created (user, _) = FacilityUser.get_or_initialize(username=unpw, facility=fac) user.set_password(unpw) user.save() From 618314d084308000a8cd98c0c0d119375e83b4a6 Mon Sep 17 00:00:00 2001 From: Richard Tibbles Date: Fri, 27 Feb 2015 16:27:00 +0530 Subject: [PATCH 028/140] Final tweaks and fixes for loadtesting. --- .../distributed/loadtesting/load_test.html | 68 +++++-------------- kalite/testing/loadtesting/views.py | 4 +- 2 files changed, 18 insertions(+), 54 deletions(-) diff --git a/kalite/distributed/templates/distributed/loadtesting/load_test.html b/kalite/distributed/templates/distributed/loadtesting/load_test.html index 230347054b..39c052e38d 100644 --- a/kalite/distributed/templates/distributed/loadtesting/load_test.html +++ b/kalite/distributed/templates/distributed/loadtesting/load_test.html @@ -8,9 +8,9 @@ - - - + + +