summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/models.py25
-rw-r--r--yaksh/static/yaksh/js/course.js97
-rw-r--r--yaksh/templates/yaksh/complete.html9
-rw-r--r--yaksh/templates/yaksh/course_detail.html75
-rw-r--r--yaksh/templates/yaksh/course_modules.html202
-rw-r--r--yaksh/templates/yaksh/show_video.html6
-rw-r--r--yaksh/templates/yaksh/user_status.html44
-rw-r--r--yaksh/templatetags/custom_filters.py6
-rw-r--r--yaksh/test_models.py7
-rw-r--r--yaksh/test_views.py89
-rw-r--r--yaksh/urls.py2
-rw-r--r--yaksh/views.py88
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)