diff options
-rw-r--r-- | yaksh/models.py | 25 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/course.js | 97 | ||||
-rw-r--r-- | yaksh/templates/yaksh/complete.html | 9 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_detail.html | 75 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_modules.html | 202 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_video.html | 6 | ||||
-rw-r--r-- | yaksh/templates/yaksh/user_status.html | 44 | ||||
-rw-r--r-- | yaksh/templatetags/custom_filters.py | 6 | ||||
-rw-r--r-- | yaksh/test_models.py | 7 | ||||
-rw-r--r-- | yaksh/test_views.py | 89 | ||||
-rw-r--r-- | yaksh/urls.py | 2 | ||||
-rw-r--r-- | yaksh/views.py | 88 |
12 files changed, 402 insertions, 248 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index 1ca293b..50a5cc2 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -567,7 +567,7 @@ class LearningModule(models.Model): status_list = [unit.get_completion_status(user, course) for unit in units] count = status_list.count("completed") - percent = round((count / len(units)) * 100) + percent = round((count / units.count()) * 100) return percent def _create_module_copy(self, user, module_name): @@ -783,15 +783,14 @@ 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() + def percent_completed(self, user, modules): if not modules: percent = 0.0 else: status_list = [module.get_module_complete_percent(self, user) for module in modules] count = sum(status_list) - percent = round((count / len(modules))) + percent = round((count / modules.count())) return percent def get_grade(self, user): @@ -802,6 +801,11 @@ class Course(models.Model): grade = "NA" return grade + def get_current_unit(self, user): + course_status = CourseStatus.objects.filter(course=self, user=user) + if course_status.exists(): + return course_status.first().current_unit + def days_before_start(self): """ Get the days remaining for the start of the course """ if timezone.now() < self.start_enroll_time: @@ -810,6 +814,14 @@ class Course(models.Model): remaining_days = 0 return remaining_days + def get_completion_percent(self, user): + course_status = CourseStatus.objects.filter(course=self, user=user) + if course_status.exists(): + percentage = course_status.first().percent_completed + else: + percentage = 0 + return percentage + def __str__(self): return self.name @@ -824,6 +836,7 @@ class CourseStatus(models.Model): user = models.ForeignKey(User) grade = models.CharField(max_length=255, null=True, blank=True) percentage = models.FloatField(default=0.0) + percent_completed = models.IntegerField(default=0) def get_grade(self): return self.grade @@ -862,6 +875,10 @@ class CourseStatus(models.Model): break return complete + def set_current_unit(self, unit): + self.current_unit = unit + self.save() + ############################################################################### class ConcurrentUser(models.Model): diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index 1c64a3e..bd197a8 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -42,7 +42,7 @@ $(function() { max_height: 200,
height: 200
});
- });
+});
$("#send_mail").click(function(){
var subject = $("#subject").val();
@@ -66,79 +66,38 @@ $("#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);
+// Table sorter for course details
+$("table").tablesorter({});
- // For IE (tested 10+)
- if (window.navigator.msSaveOrOpenBlob) {
- var blob = new Blob([decodeURIComponent(encodeURI(csv))], {
- type: "text/csv;charset=utf-8;"
+// Get user course completion status
+$('.user_data').click(function() {
+ var data = $(this).data('item-id');
+ course_id = data.split("+")[0];
+ student_id = data.split("+")[1];
+ var status_div = $("#show_status_"+course_id+"_"+student_id);
+ if(!status_div.is(":visible")){
+ var get_url = window.location.protocol + "//" + window.location.host +
+ "/exam/manage/get_user_status/" + course_id + "/" + student_id;
+ $.ajax({
+ url: get_url,
+ timeout: 8000,
+ type: "GET",
+ dataType: "json",
+ contentType: 'application/json; charset=utf-8',
+ success: function(data) {
+ status_div.toggle();
+ status_div.html(data.user_data);
+ },
+ error: function(jqXHR, textStatus) {
+ alert("Unable to get user data. Please Try again later.");
+ }
});
- 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);
+ status_div.toggle();
}
- // 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", '').replace("View Units", ""); // escape double quotes
-
- }
-}
-
-
-$("#export").click(function (event) {
- var outputFile = $("#course_name").val().replace(" ", "_") + '.csv';
-
- exportTableToCSV.apply(this, [$('#course_table'), outputFile]);
});
-// Table sorter for course details
-$("table").tablesorter({});
-
-});
+$('[data-toggle="tooltip"]').tooltip();
-function view_status(unit){
- title_list = $(unit).attr("title").split("/");
- $(unit).attr("title", title_list.join("\n"));
-}
+}); // end document ready
diff --git a/yaksh/templates/yaksh/complete.html b/yaksh/templates/yaksh/complete.html index 0881bfe..a3627d0 100644 --- a/yaksh/templates/yaksh/complete.html +++ b/yaksh/templates/yaksh/complete.html @@ -3,6 +3,13 @@ {% block pagetitle %}<img src="{{ URL_ROOT }}/static/yaksh/images/yaksh_text.png" width="80" alt="YAKSH"></img>{% endblock %} {% block content %} +{% if module_id and not paper.question_paper.quiz.is_trial %} +<center> + <div class="alert alert-info"> + Note:- Please Click on the Next button to submit the quiz. Please do not close the browser without clicking Next. + </div> +</center> +{% endif %} {% csrf_token %} {% if paper.questions_answered.all or paper.questions_unanswered.all %} <center><table class="table table-bordered" > @@ -33,7 +40,7 @@ width="80" alt="YAKSH"></img>{% endblock %} <center><h3>{{message}}</h3></center> <center> <br> - {% if module_id and not user == "moderator" %} + {% if module_id and not paper.question_paper.quiz.is_trial %} {% if first_unit %} <a href="{{URL_ROOT}}/exam/next_unit/{{course_id}}/{{module_id}}/{{learning_unit.id}}/1" class="btn btn-info" id="Next"> Next <span class="glyphicon glyphicon-chevron-right"> diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index 9fcae68..2bf725c 100644 --- a/yaksh/templates/yaksh/course_detail.html +++ b/yaksh/templates/yaksh/course_detail.html @@ -2,7 +2,7 @@ {% load custom_filters %} {% block title %} Course Details {% endblock title %} -<div class="col-md-9 col-md-offset-2 main"> +<div class="col-md-9 col-md-offset-6 main"> {% block pagetitle %} Course Details for {{ course.name|title }} {% endblock %} </div> @@ -14,6 +14,12 @@ {% endblock %} {% block css %} <link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/jquery-ui/jquery-ui.css"> +<style> + .user_data + .tooltip.top > .tooltip-inner { + padding: 12px; + font-size: 10px; + } +</style> {% endblock %} {% block content %} <br/> @@ -128,68 +134,51 @@ {% 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="tablesorter table table-bordered" id="course_table" data-sortlist="[0,0]"> <thead> <tr> <th>Sr No.</th> - <th>Students</th> - <th>Total</th> + <th>Email</th> + <th>Current Unit</th> + <th>Course Completion Percentage</th> <th>Grade</th> - <th colspan="{{modules|length}}">Modules</th> - </tr> - <tr> - <th scope="row"></th> - <th></th> - <th></th> - <th></th> - {% if modules %} - {% for module in modules %} - <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> - {% endfor %} - {% else %} - <th></th> - {% endif %} </tr> </thead> <tbody> - {% for student in students %} + {% for student, grade, percent, unit in student_details %} <tr> <td width="5%"> {{forloop.counter}}. </td> + <td width="50%"> + <a class="user_data" data-item-id="{{course.id}}+{{student.id}}" data-toggle="tooltip" title="Click to view Overall Course progress" data-placement="top"> + {% if student.email %} + {{ student.email }} + {% else %} + {{ student.get_full_name|title}} + {% endif %} + </a> + <div id="show_status_{{course.id}}_{{student.id}}" style="display: None;"> + </div> + </td> <td> - {{ student.get_full_name|title }} + {% if unit %} + {% if unit.type == 'quiz' %} + {{unit.quiz.description}} + {% else %} + {{unit.lesson.name}} + {% endif %} + {% else %} + NA + {% endif%} </td> <td> - {% course_completion_percent course student as c_percent %} - {{c_percent}} % + {{percent}}% </td> <td> - {% course_grade course student as grade %} {{grade}} </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 %} </tbody> diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html index 6c93e97..5baa781 100644 --- a/yaksh/templates/yaksh/course_modules.html +++ b/yaksh/templates/yaksh/course_modules.html @@ -18,101 +18,117 @@ </div> {% endif %} <b>Grade: {% if grade %} {{ grade }} {% else %} Will be available once the course is complete {% endif %}</b> -{% if learning_modules %} - <table class="table"> - {% for module in learning_modules %} - <tr> - <td> - <a href="{{URL_ROOT}}/exam/quizzes/view_module/{{module.id}}/{{course.id}}"> - {{module.name|title}}</a> - </td> - <td> - <span class="glyphicon glyphicon-chevron-down" id="learning_units{{module.id}}{{course.id}}_down"> - </span> - <span class="glyphicon glyphicon-chevron-up" id="learning_units{{module.id}}{{course.id}}_up" style="display: none;"> - </span> - <a data-toggle="collapse" data-target="#learning_units{{module.id}}{{course.id}}" onclick="view_unit('learning_units{{module.id}}{{course.id}}');"> - View Lessons/Quizzes/Exercises</a> - <div id="learning_units{{module.id}}{{course.id}}" class="collapse"> - <table class="table"> - <tr> - <th>Lesson/Quiz/Exercise</th> - <th>Status</th> - <th>Type</th> - <th>View AnswerPaper</th> - </tr> - {% for unit in module.get_learning_units %} - <tr> - <td> - {% if unit.type == "quiz" %} - {{unit.quiz.description}} - {% else %} - {{unit.lesson.name}} - {% endif %} - </td> - <td> - {% get_unit_status course module unit user as status %} - {% if status == "completed" %} - <span class="label label-success">{{status|title}} - </span> - {% elif status == "inprogress" %} - <span class="label label-info">{{status|title}} - </span> - {% else %} - <span class="label label-warning">{{status|title}} - </span> - {% endif %} - </td> - <td> - {% if unit.type == "quiz" %} - {% if unit.quiz.is_exercise %} - Exercise - {% else %} - Quiz - {% endif %} - {% else %} - Lesson - {% endif %} - </td> - <td> - {% if unit.type == "quiz" %} - {% if unit.quiz.view_answerpaper %} - <a href="{{ URL_ROOT }}/exam/view_answerpaper/{{ unit.quiz.questionpaper_set.get.id }}/{{course.id}}"> - <i class="fa fa-eye" aria-hidden="true"></i> Can View </a> +{% if modules %} + <br><br> + <strong>Overall Course Progress</strong> + <div class="progress"> + {% if course_percentage <= 50 %} + <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% elif course_percentage <= 75 %} + <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% else %} + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% endif %} + <b style="color: black;">{{course_percentage}}% Completed</b> + </div> + </div> + <div class="panel panel-default"> + <div class="panel panel-body"> + <table class="table"> + {% for module, percent in modules %} + <tr> + <td width="25%"> + <a href="{{URL_ROOT}}/exam/quizzes/view_module/{{module.id}}/{{course.id}}"> + {{module.name|title}}</a> + </td> + <td> + <span class="glyphicon glyphicon-chevron-down" id="learning_units{{module.id}}{{course.id}}_down"> + </span> + <span class="glyphicon glyphicon-chevron-up" id="learning_units{{module.id}}{{course.id}}_up" style="display: none;"> + </span> + <a data-toggle="collapse" data-target="#learning_units{{module.id}}{{course.id}}" onclick="view_unit('learning_units{{module.id}}{{course.id}}');"> + View Lessons/Quizzes/Exercises</a> + <div id="learning_units{{module.id}}{{course.id}}" class="collapse"> + <table class="table table-bordered"> + <tr> + <th>Lesson/Quiz/Exercise</th> + <th>Status</th> + <th>Type</th> + <th>View AnswerPaper</th> + </tr> + {% for unit in module.get_learning_units %} + <tr> + <td> + {% if unit.type == "quiz" %} + {{unit.quiz.description}} + {% else %} + {{unit.lesson.name}} + {% endif %} + </td> + <td> + {% get_unit_status course module unit user as status %} + {% if status == "completed" %} + <span class="label label-success">{{status|title}} + </span> + {% elif status == "inprogress" %} + <span class="label label-info">{{status|title}} + </span> + {% else %} + <span class="label label-warning">{{status|title}} + </span> + {% endif %} + </td> + <td> + {% if unit.type == "quiz" %} + {% if unit.quiz.is_exercise %} + Exercise + {% else %} + Quiz + {% endif %} + {% else %} + Lesson + {% endif %} + </td> + <td> + {% if unit.type == "quiz" %} + {% if unit.quiz.view_answerpaper %} + <a href="{{ URL_ROOT }}/exam/view_answerpaper/{{ unit.quiz.questionpaper_set.get.id }}/{{course.id}}"> + <i class="fa fa-eye" aria-hidden="true"></i> Can View </a> + {% else %} + <a> + <i class="fa fa-eye-slash" aria-hidden="true"> + </i> Cannot view now </a> + {% endif %} + {% else %} + ------ + {% endif %} + </td> + </tr> + {% endfor %} + </table> + </div> + </td> + <td> + <div class="progress"> + {% if percent <= 50 %} + <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> + {% elif percent <= 75 %} + <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> {% else %} - <a> - <i class="fa fa-eye-slash" aria-hidden="true"> - </i> Cannot view now </a> + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> {% endif %} - {% else %} - ------ - {% endif %} - </td> - </tr> - {% endfor %} - </table> - </div> - </td> - <td> - {% get_module_status user module course as module_status %} - Status: - {% if module_status == "completed" %} - <span class="label label-success"> - {{module_status|title}} - </span> - {% elif module_status == "inprogress" %} - <span class="label label-info"> - {{module_status|title}} - </span> - {% else %} - <span class="label label-warning"> - {{module_status|title}} - </span> - {% endif %} - </td> - </tr> - {% endfor %} - </table> + <b style="color: black;">{{percent}}% Completed</b> + </div> + </div> + </td> + </tr> + {% endfor %} + </table> + </div> + </div> {% else %} <h3> No lectures found </h3> {% endif %} diff --git a/yaksh/templates/yaksh/show_video.html b/yaksh/templates/yaksh/show_video.html index 17f9d86..eae3762 100644 --- a/yaksh/templates/yaksh/show_video.html +++ b/yaksh/templates/yaksh/show_video.html @@ -11,7 +11,11 @@ {% block main %} <div class="col-sm-3 col-md-2 sidebar"> - <center><h4>{{course.name}}</h4></center> + <center> + <a href="{{URL_ROOT}}/exam/course_modules/{{course.id}}"> + <h4>{{course.name}}</h4> + </a> + </center> <br> {% for module in all_modules %} {% if module.id == learning_module.id %} diff --git a/yaksh/templates/yaksh/user_status.html b/yaksh/templates/yaksh/user_status.html new file mode 100644 index 0000000..5f006c9 --- /dev/null +++ b/yaksh/templates/yaksh/user_status.html @@ -0,0 +1,44 @@ +{% if status %} + <strong>Student Name: {{student.get_full_name|title}}</strong> + <br> + <strong>Overall Course Progress:</strong> + <div class="progress"> + {% if course_percentage <= 50 %} + <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% elif course_percentage <= 75 %} + <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% else %} + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" + aria-valuemin="0" aria-valuemax="100" style="width:{{course_percentage}}%"> + {% endif %} + <b style="color: black;">{{course_percentage}}% Completed</b> + </div> + </div> + <br> + <strong>Per Module Progress:</strong> + <br> + <table class="table"> + {% for module, percent in modules %} + <tr> + <td width="30%">{{ module.name }}</td> + <td> + <div class="progress"> + {% if percent <= 50 %} + <div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> + {% elif percent <= 75 %} + <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> + {% else %} + <div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width:{{percent}}%"> + {% endif %} + <b style="color: black;">{{percent}}% Completed</b> + </div> + </div> + </td> + </tr> + {% endfor %} + </table> +{% else %} + {{ msg }} +{% endif %}
\ No newline at end of file diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 0c5eb5a..ee0d51a 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -66,14 +66,10 @@ def course_completion_percent(course, user): @register.simple_tag -def course_grade(course, user): - return course.get_grade(user) - - -@register.simple_tag def get_ordered_testcases(question, answerpaper): return question.get_ordered_test_cases(answerpaper) + @register.simple_tag def get_answer_for_arrange_options(ans, question): if type(ans) == bytes: diff --git a/yaksh/test_models.py b/yaksh/test_models.py index bcd0434..f97b7b2 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1729,11 +1729,12 @@ class CourseTestCases(unittest.TestCase): # 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) + modules = self.course.get_learning_modules() + percent = self.course.percent_completed(self.student1, modules) self.assertEqual(percent, 0) self.quiz1.questionpaper_set.all().delete() # for course with module but zero percent completed - percent = self.course.percent_completed(self.student1) + percent = self.course.percent_completed(self.student1, modules) self.assertEqual(percent, 0) # Add completed unit to course status and check percent @@ -1743,7 +1744,7 @@ class CourseTestCases(unittest.TestCase): 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) + updated_percent = self.course.percent_completed(self.student1, modules) self.assertEqual(updated_percent, 25) def test_course_time_remaining_to_start(self): diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 514f1cd..33747c8 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -26,7 +26,7 @@ from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ AssignmentUpload, FileUpload, McqTestCase, IntegerTestCase, StringTestCase,\ FloatTestCase, FIXTURES_DIR_PATH, LearningModule, LearningUnit, Lesson,\ - LessonFile + LessonFile, CourseStatus from yaksh.decorators import user_has_profile @@ -1856,8 +1856,9 @@ class TestCourses(TestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "yaksh/course_modules.html") self.assertEqual(response.context['course'], self.user1_course) - self.assertEqual(response.context['learning_modules'][0], - self.learning_module) + module, percent = response.context['modules'][0] + self.assertEqual(module, self.learning_module) + self.assertEqual(percent, 0.0) def test_duplicate_course(self): """ Test To clone/duplicate course and link modules""" @@ -2198,13 +2199,20 @@ class TestCourseDetail(TestCase): self.mod_group.user_set.add(self.user1) self.mod_group.user_set.add(self.user2) - self.user1_course = Course.objects.create(name="Python Course", - enrollment="Enroll Request", creator=self.user1) + 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) + self.lesson = Lesson.objects.create( + name="test lesson", description="test description", + creator=self.user1) + self.learning_unit1 = LearningUnit.objects.create( + order=1, type="lesson", lesson=self.lesson) + self.learning_module.learning_unit.add(self.learning_unit1) def tearDown(self): self.client.logout() @@ -2608,12 +2616,81 @@ class TestCourseDetail(TestCase): username=self.user1.username, password=self.user1_plaintext_pass ) + self.user1_course.students.add(self.student) + + # Check student details when course is not started + 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) + student_details = response.context['student_details'][0] + student, grade, percent, current_unit = student_details + self.assertEqual(student.username, "demo_student") + self.assertEqual(grade, "NA") + self.assertEqual(percent, 0.0) + self.assertIsNone(current_unit) + + # Check student details when student starts the course + self.course_status = CourseStatus.objects.create( + course=self.user1_course, user=self.student) 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) + student_details = response.context['student_details'][0] + student, grade, percent, current_unit = student_details + self.assertEqual(student.username, "demo_student") + self.assertIsNone(grade) + self.assertEqual(percent, 0) + self.assertIsNone(current_unit) + + self.user1_course.students.remove(self.student) + + def test_course_status_per_user(self): + """ Test course status for a particular student""" + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + # Denies student to view course status + response = self.client.get(reverse('yaksh:get_user_data', + kwargs={'course_id': self.user1_course.id, + 'student_id': self.student.id})) + err_msg = response.json()['user_data'].replace("\n", "").strip() + self.assertEqual(response.status_code, 200) + self.assertEqual(err_msg, "You are not a moderator") + + # Other Moderator Login + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + response = self.client.get(reverse('yaksh:get_user_data', + kwargs={'course_id': self.user1_course.id, + 'student_id': self.student.id})) + err_msg = response.json()['user_data'].replace("\n", "").strip() + actual_err = dedent("""\ + You are neither course creator nor course teacher for {0}""".format( + self.user1_course.name)) + self.assertEqual(response.status_code, 200) + self.assertEqual(err_msg, actual_err) + + # Actual course creator login + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + response = self.client.get(reverse('yaksh:get_user_data', + kwargs={'course_id': self.user1_course.id, + 'student_id': self.student.id})) + self.assertEqual(response.status_code, 200) + data = response.json()['user_data'] + self.assertIn("Student_First_Name Student_Last_Name", data) + self.assertIn("Overall Course Progress", data) + self.assertIn("Per Module Progress", data) class TestEnrollRequest(TestCase): diff --git a/yaksh/urls.py b/yaksh/urls.py index 3611573..2af1e70 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -174,4 +174,6 @@ urlpatterns = [ views.course_status, name="course_status"), url(r'^manage/preview_questionpaper/(?P<questionpaper_id>\d+)/$', views.preview_questionpaper, name="preview_questionpaper"), + url(r'^manage/get_user_status/(?P<course_id>\d+)/(?P<student_id>\d+)/$', + views.get_user_data, name="get_user_data") ] diff --git a/yaksh/views.py b/yaksh/views.py index e1c1889..d8d630b 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -475,9 +475,6 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, learning_module.name) return course_modules(request, course_id, msg) - # update course status with current unit - _update_unit_status(course_id, user, learning_unit) - # is user enrolled in the course if not course.is_enrolled(user): msg = 'You are not enrolled in {0} course'.format(course.name) @@ -511,6 +508,9 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, return view_module(request, module_id=module_id, course_id=course_id, msg=msg) + # update course status with current unit + _update_unit_status(course_id, user, learning_unit) + # if any previous attempt last_attempt = AnswerPaper.objects.get_user_last_attempt( quest_paper, user, course_id) @@ -894,7 +894,7 @@ def quit(request, reason=None, attempt_num=None, questionpaper_id=None, @email_verified def complete(request, reason=None, attempt_num=None, questionpaper_id=None, course_id=None, module_id=None): - """Show a page to inform user that the quiz has been compeleted.""" + """Show a page to inform user that the quiz has been completed.""" user = request.user if questionpaper_id is None: message = reason or "An Unexpected Error occurred. Please contact your '\ @@ -2541,20 +2541,17 @@ def get_next_unit(request, course_id, module_id, current_unit_id=None, else: next_unit = learning_module.get_next_unit(current_learning_unit.id) - course_status = CourseStatus.objects.filter( + course_status, created = CourseStatus.objects.get_or_create( user=user, course_id=course_id, ) - if not course_status.exists(): - course_status = CourseStatus.objects.create( - user=user, course_id=course_id - ) - else: - course_status = course_status.first() # Add learning unit to completed units list if not first_unit: course_status.completed_units.add(current_learning_unit.id) + # Update course completion percentage + _update_course_percent(course, user) + # if last unit of current module go to next module is_last_unit = course.is_last_unit(learning_module, current_learning_unit.id) @@ -2681,9 +2678,11 @@ def course_modules(request, course_id, msg=None): msg = "{0} is either expired or not active".format(course.name) return quizlist_user(request, msg=msg) learning_modules = course.get_learning_modules() - context = {"course": course, "learning_modules": learning_modules, - "user": user, "msg": msg} + context = {"course": course, "user": user, "msg": msg} course_status = CourseStatus.objects.filter(course=course, user=user) + context['course_percentage'] = course.get_completion_percent(user) + context['modules'] = [(module, module.get_module_complete_percent(course, user)) + for module in learning_modules] if course_status.exists(): course_status = course_status.first() if not course_status.grade: @@ -2702,26 +2701,32 @@ def course_status(request, 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() + stud_details = [(student, course.get_grade(student), + course.get_completion_percent(student), + course.get_current_unit(student)) for student in students] context = { - 'course': course, 'students': students, - 'state': 'course_status', 'modules': course.get_learning_modules() + 'course': course, 'student_details': stud_details, + 'state': 'course_status' } return my_render_to_response(request, 'yaksh/course_detail.html', context) def _update_unit_status(course_id, user, unit): """ Update course status with current unit """ - course_status = CourseStatus.objects.filter( + course_status, created = CourseStatus.objects.get_or_create( user=user, course_id=course_id, ) - if not course_status.exists(): - course_status = CourseStatus.objects.create( - user=user, course_id=course_id - ) - else: - course_status = course_status.first() # make next available unit as current unit - course_status.current_unit = unit + course_status.set_current_unit(unit) + + +def _update_course_percent(course, user): + course_status, created = CourseStatus.objects.get_or_create( + user=user, course=course, + ) + # Update course completion percent + modules = course.get_learning_modules() + course_status.percent_completed = course.percent_completed(user, modules) course_status.save() @@ -2742,3 +2747,40 @@ def preview_questionpaper(request, questionpaper_id): return my_render_to_response( request, 'yaksh/preview_questionpaper.html', context ) + + +@login_required +@email_verified +def get_user_data(request, course_id, student_id): + user = request.user + data = {} + response_kwargs = {} + response_kwargs['content_type'] = 'application/json' + course = Course.objects.get(id=course_id) + if not is_moderator(user): + data['msg'] = 'You are not a moderator' + data['status'] = False + elif not course.is_creator(user) and not course.is_teacher(user): + msg = 'You are neither course creator nor course teacher for {0}'.format( + course.name) + data['msg'] = msg + data['status'] = False + else: + student = User.objects.get(id=student_id) + data['status'] = True + modules = course.get_learning_modules() + module_percent = [(module, module.get_module_complete_percent(course, student)) + for module in modules] + data['modules'] = module_percent + _update_course_percent(course, student) + data['course_percentage'] = course.get_completion_percent(student) + data['student'] = student + template_path = os.path.join( + os.path.dirname(__file__), "templates", "yaksh", "user_status.html" + ) + with open(template_path) as f: + template_data = f.read() + template = Template(template_data) + context = Context(data) + data = template.render(context) + return HttpResponse(json.dumps({"user_data": data}), **response_kwargs) |