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(-)

(limited to 'yaksh')

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<course_id>\d+)/$',
         views.design_course, name="design_course"),
+    url(r'^manage/course_status/(?P<course_id>\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(-)

(limited to 'yaksh')

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 %}
 
 <div class="col-md-9 col-md-offset-2 main">
@@ -35,6 +35,10 @@
         <a href="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/">
         Send Mail</a>
         </li>
+        <li>
+        <a href="{{URL_ROOT}}/exam/manage/course_status/{{ course.id }}/">
+        View Course Status</a>
+        </li>
       </ul>
     </div>
 </div>
@@ -116,6 +120,64 @@
             {% endif %}
             </form>
         </div>
+        {% elif state == "course_status" %}
+            <div class="course_data">
+            <input type="hidden" id="course_name" value="{{course.name}}">
+            <a href="#" class="btn btn-info" id="export">Export to CSV</a>
+            <center><h2>Course Status</h2></center>
+            <table class="table table-bordered" id="course_table">
+                <tr>
+                <th>Sr No.</th>
+                <th>Students</th>
+                <th>Total</th>
+                <th colspan="{{modules|length}}">Modules</th>
+                </tr>
+                <tr>
+                    <th scope="row"></th>
+                    <th></th>
+                    <th></th>
+                    {% if modules %}
+                        {% for module in modules %}
+                            <th>
+                                {{module.name}}
+                                <br>
+                                <a data-target="tooltip" title="{% for unit in module.get_learning_units %}{% if unit.type == 'quiz' %}{{unit.quiz.description}}{% else %}{{unit.lesson.name}}{% endif %} / {% endfor %}" id="unit_status{{module.id}}" onmouseover="view_status('#unit_status{{module.id}}')">
+                                    View Units</a>
+                            </th>
+                        {% endfor %}
+                    {% else %}
+                        <th></th>
+                    {% endif %}
+                </tr>
+                {% for student in students %}
+                    <tr>
+                    <td width="5%">
+                        {{forloop.counter}}.
+                    </td>
+                    <td>
+                        {{ student.get_full_name|title }}
+                    </td>
+                    <td>
+                        {% course_completion_percent course student as c_percent %}
+                        {{c_percent}} %
+                    </td>
+                        {% if modules %}
+                            {% for module in modules %}
+                                <td>
+                                    {% module_completion_percent course module student as m_percent %}
+                                    {{m_percent}} %
+                                    <br>
+                                    <a data-target="tooltip" title="{% for unit in module.get_learning_units %}{% if unit.type == 'quiz' %}{{unit.quiz.description}}{% else %}{{unit.lesson.name}}{% endif %} - {% get_unit_status course module unit student as status %}{{status|title}} / {% endfor %}" id="unit_status{{module.id}}{{student.id}}" onmouseover="view_status('#unit_status{{module.id}}{{student.id}}')">
+                                    View Unit Status</a>
+                                </td>
+                            {% endfor %}
+                        {% else %}
+                            <td>-------</td>
+                        {% endif %}
+                    </tr>
+                {% endfor %}
+            </table>
+            </div>
         {% else %}
         <div id="student-requests">
             <center><b><u>Requests</u></b></center><br>
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 @@
         <table class="table">
     {{ form.as_table }}
     </table></center>
-    <center><button class="btn btn-primary" type="submit">Register</button>&nbsp;&nbsp;<button class="btn btn-primary" type="reset">Cancel</button></center>
+    <center><button class="btn btn-primary" type="submit">Register</button>&nbsp;&nbsp;
+    <a href="{{URL_ROOT}}/exam" class="btn btn-danger">Cancel</a></center>
 </form>
 
 {% 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(-)

(limited to 'yaksh')

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(-)

(limited to 'yaksh')

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(-)

(limited to 'yaksh')

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(-)

(limited to 'yaksh')

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 @@
 <script language="JavaScript" type="text/javascript" src="{{ URL_ROOT }}/static/yaksh/js/course.js"></script>
 <script type="text/javascript" src="{{ URL_ROOT }}/static/yaksh/js/tinymce/js/tinymce/tinymce.min.js"></script>
 <script src="{{ URL_ROOT }}/static/yaksh/js/jquery-ui.js"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/jquery.tablesorter.min.js"></script>
 {% endblock %}
 {% block css %}
 <link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/jquery-ui/jquery-ui.css">
@@ -20,7 +21,7 @@
 <div class="row">
     <div class="col-sm-3 col-md-2 sidebar">
       <ul class="nav nav-sidebar">
-        {% if state == 'mail'%}
+        {% if state == 'mail' or state == 'course_status' %}
             <li><a href="{{URL_ROOT}}/exam/manage/course_detail/{{course.id}}/">
             Go to Course Details</a></li>
         {% else %}
@@ -88,7 +89,8 @@
             <div id="reject">
             <form action="{{URL_ROOT}}/exam/manage/send_mail/{{ course.id }}/" method="post" id="send_mail_form">
                 {% csrf_token %}
-                <table class="table table-striped">
+                <table id="mail_table" class="tablesorter table table-striped" data-sortlist="[1,0]">
+                <thead>
                     <th></th>
                     <th></th>
                     <th>Full Name</th>
@@ -96,6 +98,8 @@
                     <th>Roll Number</th>
                     <th>Institute</th>
                     <th>Department</th>
+                </thead>
+                <tbody>
                     {% for enrolled in course.get_enrolled %}
                         <tr>
                         <td><input type="checkbox" name="check" value="{{ enrolled.id }}"></td>
@@ -107,6 +111,7 @@
                         <td> {{enrolled.profile.department}}</td>
                         </tr>
                     {% endfor %}
+                </tbody>
                 </table>
                 <br>
                 <textarea name="subject" id="subject" placeholder="Email Subject" cols="50"></textarea>
@@ -125,7 +130,8 @@
             <input type="hidden" id="course_name" value="{{course.name}}">
             <a href="#" class="btn btn-info" id="export">Export to CSV</a>
             <center><h2>Course Status</h2></center>
-            <table class="table table-bordered" id="course_table">
+            <table class="tablesorter table table-bordered" id="course_table" data-sortlist="[0,0]">
+            <thead>
                 <tr>
                 <th>Sr No.</th>
                 <th>Students</th>
@@ -141,6 +147,8 @@
                             <th>
                                 {{module.name}}
                                 <br>
+                                ({{module.get_learning_units|length}} Units)
+                                <br>
                                 <a data-target="tooltip" title="{% for unit in module.get_learning_units %}{% if unit.type == 'quiz' %}{{unit.quiz.description}}{% else %}{{unit.lesson.name}}{% endif %} / {% endfor %}" id="unit_status{{module.id}}" onmouseover="view_status('#unit_status{{module.id}}')">
                                     View Units</a>
                             </th>
@@ -149,6 +157,8 @@
                         <th></th>
                     {% endif %}
                 </tr>
+            </thead>
+            <tbody>
                 {% for student in students %}
                     <tr>
                     <td width="5%">
@@ -176,9 +186,11 @@
                         {% endif %}
                     </tr>
                 {% endfor %}
+            </tbody>
             </table>
             </div>
         {% else %}
+        <div id="students_enrollment">
         <div id="student-requests">
             <center><b><u>Requests</u></b></center><br>
             {% if course.get_requests %}
@@ -186,7 +198,8 @@
             <div id="enroll-all">
             <form action="{{URL_ROOT}}/exam/manage/enroll/{{ course.id }}/" method="post">
                 {% csrf_token %}
-                <table class="table table-striped">
+                <table id="requested_table" class="tablesorter table table-striped" data-sortlist="[1,0]">
+                <thead>
                     <th></th>
                     <th></th>
                     <th>Full Name</th>
@@ -195,6 +208,8 @@
                     <th>Institute</th>
                     <th>Department</th>
                     <th>Enroll/Reject</th>
+                </thead>
+                <tbody>
                     {% for request in course.get_requests %}
                     <tr>
                         <td><input type="checkbox" name="check" value="{{ request.id }}"></td>
@@ -214,6 +229,7 @@
                         </td>
                     </tr>
                 {% endfor %}
+                </tbody>
                 </table>
                 <button class="btn btn-success" type="submit" name='enroll' value='enroll'>Enroll Selected</button>
             </div>
@@ -228,7 +244,8 @@
             <div id="reject">
             <form action="{{URL_ROOT}}/exam/manage/enrolled/reject/{{ course.id }}/" method="post" id="reject-form">
                 {% csrf_token %}
-                <table class="table table-striped">
+                <table id="enrolled_table" class="tablesorter table table-striped" data-sortlist="[1,0]">
+                    <thead>
                     <th></th>
                     <th></th>
                     <th>Full Name</th>
@@ -237,6 +254,8 @@
                     <th>Institute</th>
                     <th>Department</th>
                     <th>Reject</th>
+                    </thead>
+                    <tbody>
                     {% for enrolled in course.get_enrolled %}
                         <tr>
                         <td><input type="checkbox" name="check" value="{{ enrolled.id }}"></td>
@@ -252,6 +271,7 @@
                         </td>
                         </tr>
                     {% endfor %}
+                    </tbody>
                 </table>
                 <button class="btn btn-danger" type="submit" name='reject' value='reject'>
                 Reject Selected</button>
@@ -267,7 +287,8 @@
             <div id="enroll">
             <form action="{{URL_ROOT}}/exam/manage/enroll/rejected/{{ course.id }}/" method="post">
                 {% csrf_token %}
-                <table class="table table-striped">
+                <table id="rejected_table" class="tablesorter table table-striped" data-sortlist="[1,0]">
+                <thead>
                     <th></th>
                     <th></th>
                     <th>Full Name</th>
@@ -276,6 +297,8 @@
                     <th>Institute</th>
                     <th>Department</th>
                     <th>Enroll</th>
+                </thead>
+                <tbody>
                     {% for rejected in course.get_rejected %}
                         <tr>
                             <td><input type="checkbox" name="check" value="{{ rejected.id }}"></td>
@@ -292,6 +315,7 @@
                             </td>
                         </tr>
                 {% endfor %}
+                </tbody>
                 </table>
                 <br>
                 <button class="btn btn-success" type="submit" name='enroll' value='enroll'>
@@ -300,6 +324,7 @@
             {% endif %}
             </form>
         </div>
+        </div>
         {% endif %}
     </div>
 </div>
-- 
cgit