From 943d2c1f6d0a1f99ffc6e48bc0c82249e2d4d08c Mon Sep 17 00:00:00 2001 From: adityacp Date: Fri, 12 Jan 2018 17:33:16 +0530 Subject: Change in models.py, views.py and urls.py - Add new model methods to calculate percent of module and course completion - Add new view function for displaying course status - Add new url to redirect to course status --- yaksh/models.py | 34 ++++++++++++++++++++++++++++++---- yaksh/urls.py | 2 ++ yaksh/views.py | 17 +++++++++++++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/yaksh/models.py b/yaksh/models.py index d4a73fa..f37df3b 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -453,12 +453,16 @@ class LearningModule(models.Model): ordered_units = learning_module.learning_unit.order_by("order") status_list = [unit.get_completion_status(user, course) for unit in ordered_units] - if all([status == "completed" for status in status_list]): - return "completed" + + if not status_list: + default_status = "no units" + elif all([status == "completed" for status in status_list]): + default_status = "completed" elif "inprogress" in status_list: - return "inprogress" + default_status = "inprogress" else: - return "not attempted" + default_status = "not attempted" + return default_status def is_prerequisite_passed(self, user, course): """ Check if prerequisite module is completed """ @@ -481,6 +485,17 @@ class LearningModule(models.Model): def has_prerequisite(self): return self.check_prerequisite + def get_module_complete_percent(self, course, user): + units = self.get_learning_units() + if not units: + percent = 0 + else: + status_list = [unit.get_completion_status(user, course) + for unit in units] + count = status_list.count("completed") + percent = round((count / len(units)) * 100) + return percent + def __str__(self): return self.name @@ -681,6 +696,17 @@ class Course(models.Model): next_index = 0 return modules.get(id=module_ids[next_index]) + def percent_completed(self, user): + modules = self.get_learning_modules() + if not modules: + percent = 0 + else: + status_list = [module.get_module_complete_percent(self, user) + for module in modules] + count = sum(status_list) + percent = round((count / len(modules))) + return percent + def __str__(self): return self.name diff --git a/yaksh/urls.py b/yaksh/urls.py index b4bbb41..d60a5d3 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -166,4 +166,6 @@ urlpatterns = [ views.design_course, name="design_course"), url(r'^manage/courses/designcourse/(?P\d+)/$', views.design_course, name="design_course"), + url(r'^manage/course_status/(?P\d+)/$', + views.course_status, name="course_status"), ] diff --git a/yaksh/views.py b/yaksh/views.py index 49249ca..7049912 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -2666,3 +2666,20 @@ def course_modules(request, course_id, msg=None): context = {"course": course, "learning_modules": learning_modules, "user": user, "msg": msg} return my_render_to_response('yaksh/course_modules.html', context) + + +@login_required +@email_verified +def course_status(request, course_id): + user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page!') + course = get_object_or_404(Course, pk=course_id) + if not course.is_creator(user) and not course.is_teacher(user): + raise Http404('This course does not belong to you') + students = course.get_only_students() + context = { + 'course': course, 'students': students, + 'state': 'course_status', 'modules': course.get_learning_modules() + } + return my_render_to_response('yaksh/course_detail.html', context) -- cgit From ae15c16a7a94e86d6debf3cc385e24fecac889e2 Mon Sep 17 00:00:00 2001 From: adityacp Date: Fri, 12 Jan 2018 17:44:01 +0530 Subject: Change in js, templates and custom_filters - Add js function to download csv data for course status - Show course status - Add new custom functions to view student status for a course --- yaksh/static/yaksh/js/course.js | 72 ++++++++++++++++++++++++++++++++ yaksh/templates/yaksh/course_detail.html | 64 +++++++++++++++++++++++++++- yaksh/templates/yaksh/register.html | 3 +- yaksh/templatetags/custom_filters.py | 41 +++++++++++++----- 4 files changed, 167 insertions(+), 13 deletions(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index f0d03e2..162e0b8 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -66,4 +66,76 @@ $("#send_mail").click(function(){ return status; }); +// Download course status as csv +function exportTableToCSV($table, filename) { + var $headers = $table.find('tr:has(th)') + ,$rows = $table.find('tr:has(td)') + + // Temporary delimiter characters unlikely to be typed by keyboard + // This is to avoid accidentally splitting the actual contents + ,tmpColDelim = String.fromCharCode(11) // vertical tab character + ,tmpRowDelim = String.fromCharCode(0) // null character + + // actual delimiter characters for CSV format + ,colDelim = '","' + ,rowDelim = '"\r\n"'; + + // Grab text from table into CSV formatted string + var csv = '"'; + csv += formatRows($headers.map(grabRow)); + csv += rowDelim; + csv += formatRows($rows.map(grabRow)) + '"'; + + // Data URI + var csvData = 'data:application/csv;charset=utf-8,' + encodeURIComponent(csv); + + // For IE (tested 10+) + if (window.navigator.msSaveOrOpenBlob) { + var blob = new Blob([decodeURIComponent(encodeURI(csv))], { + type: "text/csv;charset=utf-8;" + }); + navigator.msSaveBlob(blob, filename); + } else { + $(this) + .attr({ + 'download': filename,'href': csvData + }); + } + + function formatRows(rows){ + return rows.get().join(tmpRowDelim) + .split(tmpRowDelim).join(rowDelim) + .split(tmpColDelim).join(colDelim); + } + // Grab and format a row from the table + function grabRow(i,row){ + var $row = $(row); + var $cols = $row.find('td'); + if(!$cols.length) $cols = $row.find('th'); + + return $cols.map(grabCol) + .get().join(tmpColDelim); + } + // Grab and format a column from the table + function grabCol(j,col){ + var $col = $(col), + $text = $col.text(); + + return $text.replace('"', '""').replace("View Unit Status", ''); // escape double quotes + + } +} + + +$("#export").click(function (event) { + var outputFile = $("#course_name").val().replace(" ", "_") + '.csv'; + + exportTableToCSV.apply(this, [$('#course_table'), outputFile]); +}); + }); + +function view_status(unit){ + title_list = $(unit).attr("title").split("/"); + $(unit).attr("title", title_list.join("\n")); +} diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index cf0ab18..cd9d149 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -1,5 +1,5 @@ {% extends "manage.html" %} - +{% load custom_filters %} {% block title %} Course Details {% endblock title %}
@@ -35,6 +35,10 @@ Send Mail +
  • + + View Course Status +
  • @@ -116,6 +120,64 @@ {% endif %} + {% elif state == "course_status" %} +
    + + Export to CSV +

    Course Status

    + + + + + + + + + + + + {% if modules %} + {% for module in modules %} + + {% endfor %} + {% else %} + + {% endif %} + + {% for student in students %} + + + + + {% if modules %} + {% for module in modules %} + + {% endfor %} + {% else %} + + {% endif %} + + {% endfor %} +
    Sr No.StudentsTotalModules
    + {{module.name}} +
    + + View Units +
    + {{forloop.counter}}. + + {{ student.get_full_name|title }} + + {% course_completion_percent course student as c_percent %} + {{c_percent}} % + + {% module_completion_percent course module student as m_percent %} + {{m_percent}} % +
    + + View Unit Status +
    -------
    +
    {% else %}
    Requests

    diff --git a/yaksh/templates/yaksh/register.html b/yaksh/templates/yaksh/register.html index 7cf15a6..13cd248 100644 --- a/yaksh/templates/yaksh/register.html +++ b/yaksh/templates/yaksh/register.html @@ -12,7 +12,8 @@ {{ form.as_table }}
    -
      
    +
       + Cancel
    {% endblock content %} diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 6ddd213..0049836 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -8,43 +8,62 @@ except ImportError: register = template.Library() + @stringfilter @register.filter(name='escape_quotes') def escape_quotes(value): - value = value.decode("utf-8") - escape_single_quotes = value.replace("'", "\\'") - escape_single_and_double_quotes = escape_single_quotes.replace('"', '\\"') + value = value.decode("utf-8") + escape_single_quotes = value.replace("'", "\\'") + escape_single_and_double_quotes = escape_single_quotes.replace('"', '\\"') + + return escape_single_and_double_quotes - return escape_single_and_double_quotes @register.assignment_tag(name="completed") def completed(answerpaper): - return answerpaper.filter(status="completed").count() + return answerpaper.filter(status="completed").count() + @register.assignment_tag(name="inprogress") def inprogress(answerpaper): - return answerpaper.filter(status="inprogress").count() + return answerpaper.filter(status="inprogress").count() + @register.filter(name='zip') def zip_longest_out(a, b): - return zip_longest(a, b) + return zip_longest(a, b) @register.filter(name="file_title") def file_title(name): - return os.path.basename(name) + return os.path.basename(name) @register.simple_tag def get_unit_status(course, module, unit, user): - return course.get_unit_completion_status(module, user, unit) + return course.get_unit_completion_status(module, user, unit) @register.simple_tag def get_module_status(user, module, course): - return module.get_status(user, course) + return module.get_status(user, course) @register.simple_tag def get_course_details(course): - return course.get_quiz_details() + return course.get_quiz_details() + + +@register.simple_tag +def current_unit(course, student): + return course.get_current_unit(student) + + +@register.simple_tag +def module_completion_percent(course, module, user): + return module.get_module_complete_percent(course, user) + + +@register.simple_tag +def course_completion_percent(course, user): + return course.percent_completed(user) -- cgit From ca9cd739cd528b1fe883f25be1d76b8bbdb7bc25 Mon Sep 17 00:00:00 2001 From: adityacp Date: Fri, 12 Jan 2018 17:46:23 +0530 Subject: Add views and models tests for course status --- yaksh/models.py | 1 + yaksh/test_models.py | 42 +++++++++++++++++++++++++++++++++++++++++- yaksh/test_views.py | 29 +++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) diff --git a/yaksh/models.py b/yaksh/models.py index f37df3b..3b1e3ba 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from __future__ import division from datetime import datetime, timedelta import json import random diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 59dea5b..7086a1e 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -2,7 +2,7 @@ import unittest from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ StdIOBasedTestCase, FileUpload, McqTestCase, AssignmentUpload,\ - LearningModule, LearningUnit, Lesson, LessonFile + LearningModule, LearningUnit, Lesson, LessonFile, CourseStatus from yaksh.code_server import(ServerPool, get_result as get_result_from_code_server ) @@ -204,6 +204,26 @@ class LearningModuleTestCases(unittest.TestCase): # Then self.assertEqual(status, module_status) + def test_module_completion_percent(self): + # for module without learning units + percent = self.learning_module_two.get_module_complete_percent( + self.course, self.student + ) + self.assertEqual(percent, 0) + + # for module with learning units + lesson = Lesson.objects.get(name='L1') + self.completed_unit = LearningUnit.objects.get(lesson=lesson) + + course_status = CourseStatus.objects.create( + course=self.course, user=self.student) + course_status.completed_units.add(self.completed_unit) + + percent = self.learning_module.get_module_complete_percent( + self.course, self.student + ) + self.assertEqual(percent, 50) + class LearningUnitTestCases(unittest.TestCase): def setUp(self): @@ -1523,6 +1543,26 @@ class CourseTestCases(unittest.TestCase): """Test to check enrollment is closed for open course""" self.assertFalse(self.enroll_request_course.is_active_enrollment()) + def test_course_complete_percent(self): + # for course with no modules + self.no_module_course = Course.objects.create( + name="test_course", creator=self.creator, enrollment="open") + percent = self.course.percent_completed(self.student1) + self.assertEqual(percent, 0) + + # for course with module but zero percent completed + percent = self.course.percent_completed(self.student1) + self.assertEqual(percent, 0) + + # Add completed unit to course status and check percent + lesson = Lesson.objects.get(name='L1') + self.completed_unit = LearningUnit.objects.get(lesson=lesson) + + course_status = CourseStatus.objects.create( + course=self.course, user=self.student1) + course_status.completed_units.add(self.completed_unit) + updated_percent = self.course.percent_completed(self.student1) + self.assertEqual(updated_percent, 25) ############################################################################### diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 71d6f80..343e043 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -2089,6 +2089,11 @@ class TestCourseDetail(TestCase): self.user1_course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user1) + self.learning_module = LearningModule.objects.create( + name="test module", description="test description module", + html_data="test html description module", creator=self.user1, + order=1) + self.user1_course.learning_module.add(self.learning_module) def tearDown(self): self.client.logout() @@ -2475,6 +2480,30 @@ class TestCourseDetail(TestCase): self.assertEqual(response.get('Content-Disposition'), 'attachment; filename="sample_user_upload"') + def test_view_course_status(self): + """ Test to view course status """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + # Denies student to view course status + response = self.client.get(reverse('yaksh:course_status', + kwargs={'course_id': self.user1_course.id})) + self.assertEqual(response.status_code, 404) + + # Moderator Login + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:course_status', + kwargs={'course_id': self.user1_course.id})) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context['state'], "course_status") + self.assertEqual(response.context['course'], self.user1_course) + self.assertEqual(response.context['modules'][0], self.learning_module) + class TestEnrollRequest(TestCase): def setUp(self): -- cgit From 411a223216eb2834c6d764ac6912986894b5bdf2 Mon Sep 17 00:00:00 2001 From: adityacp Date: Sat, 13 Jan 2018 19:05:06 +0530 Subject: Remove unnecessary function from custom_filters.py --- yaksh/templatetags/custom_filters.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 0049836..3c2c6fd 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -54,11 +54,6 @@ def get_course_details(course): return course.get_quiz_details() -@register.simple_tag -def current_unit(course, student): - return course.get_current_unit(student) - - @register.simple_tag def module_completion_percent(course, module, user): return module.get_module_complete_percent(course, user) -- cgit From 5d206993d21c169cbc115733188f898d6c762e83 Mon Sep 17 00:00:00 2001 From: adityacp Date: Sat, 13 Jan 2018 19:07:18 +0530 Subject: Format column for csv download --- yaksh/static/yaksh/js/course.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 162e0b8..1ca908b 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -121,7 +121,7 @@ function exportTableToCSV($table, filename) { var $col = $(col), $text = $col.text(); - return $text.replace('"', '""').replace("View Unit Status", ''); // escape double quotes + return $text.replace('"', '""').replace("View Unit Status", '').replace("View Units", ""); // escape double quotes } } -- cgit From ef22478a11d518982f38a6a0d4d84f6f8ba5e492 Mon Sep 17 00:00:00 2001 From: adityacp Date: Sun, 14 Jan 2018 16:20:58 +0530 Subject: Change in models.py, course.js and course_detail.html - Merge future imports in models into one - Add table sorter in course.js and course_detail.html --- yaksh/models.py | 7 +++--- yaksh/static/yaksh/js/course.js | 3 +++ yaksh/templates/yaksh/course_detail.html | 37 ++++++++++++++++++++++++++------ 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/yaksh/models.py b/yaksh/models.py index 3b1e3ba..f0d1b2e 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,5 +1,4 @@ -from __future__ import unicode_literals -from __future__ import division +from __future__ import unicode_literals, division from datetime import datetime, timedelta import json import random @@ -489,7 +488,7 @@ class LearningModule(models.Model): def get_module_complete_percent(self, course, user): units = self.get_learning_units() if not units: - percent = 0 + percent = 0.0 else: status_list = [unit.get_completion_status(user, course) for unit in units] @@ -700,7 +699,7 @@ class Course(models.Model): def percent_completed(self, user): modules = self.get_learning_modules() if not modules: - percent = 0 + percent = 0.0 else: status_list = [module.get_module_complete_percent(self, user) for module in modules] diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 1ca908b..1c64a3e 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -133,6 +133,9 @@ $("#export").click(function (event) { exportTableToCSV.apply(this, [$('#course_table'), outputFile]); }); +// Table sorter for course details +$("table").tablesorter({}); + }); function view_status(unit){ diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index cd9d149..a5d10a7 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -10,6 +10,7 @@ + {% endblock %} {% block css %} @@ -20,7 +21,7 @@