diff options
-rw-r--r-- | yaksh/forms.py | 2 | ||||
-rw-r--r-- | yaksh/models.py | 62 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/course.js | 75 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_exercise.html | 46 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_quiz.html | 43 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_detail.html | 99 | ||||
-rw-r--r-- | yaksh/templates/yaksh/register.html | 3 | ||||
-rw-r--r-- | yaksh/templates/yaksh/show_video.html | 19 | ||||
-rw-r--r-- | yaksh/templatetags/custom_filters.py | 36 | ||||
-rw-r--r-- | yaksh/test_models.py | 42 | ||||
-rw-r--r-- | yaksh/test_views.py | 257 | ||||
-rw-r--r-- | yaksh/urls.py | 6 | ||||
-rw-r--r-- | yaksh/views.py | 108 |
13 files changed, 676 insertions, 122 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py index 8399bc9..9fd2eaa 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -176,7 +176,7 @@ class UserLoginForm(forms.Form): class ExerciseForm(forms.ModelForm): class Meta: model = Quiz - fields = ['description', 'view_answerpaper'] + fields = ['description', 'view_answerpaper', 'active'] class QuizForm(forms.ModelForm): diff --git a/yaksh/models.py b/yaksh/models.py index fea8fda..9a406c3 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1,4 +1,4 @@ -from __future__ import unicode_literals +from __future__ import unicode_literals, division from datetime import datetime, timedelta import json import random @@ -464,12 +464,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 """ @@ -492,6 +496,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.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 @@ -692,6 +707,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.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 @@ -1181,13 +1207,14 @@ class QuestionPaper(models.Model): return ans_paper - def _is_attempt_allowed(self, user): + def _is_attempt_allowed(self, user, course_id): attempts = AnswerPaper.objects.get_total_attempt(questionpaper=self, - user=user) + user=user, + course_id=course_id) return attempts != self.quiz.attempts_allowed def can_attempt_now(self, user, course_id): - if self._is_attempt_allowed(user): + if self._is_attempt_allowed(user, course_id): last_attempt = AnswerPaper.objects.get_user_last_attempt( user=user, questionpaper=self, course_id=course_id ) @@ -1265,10 +1292,11 @@ class QuestionSet(models.Model): ############################################################################### class AnswerPaperManager(models.Manager): - def get_all_questions(self, questionpaper_id, attempt_number, + def get_all_questions(self, questionpaper_id, attempt_number, course_id, status='completed'): ''' Return a dict of question id as key and count as value''' papers = self.filter(question_paper_id=questionpaper_id, + course_id=course_id, attempt_number=attempt_number, status=status) all_questions = list() questions = list() @@ -1279,9 +1307,10 @@ class AnswerPaperManager(models.Manager): return Counter(questions) def get_all_questions_answered(self, questionpaper_id, attempt_number, - status='completed'): + course_id, status='completed'): ''' Return a dict of answered question id as key and count as value''' papers = self.filter(question_paper_id=questionpaper_id, + course_id=course_id, attempt_number=attempt_number, status=status) questions_answered = list() for paper in papers: @@ -1319,15 +1348,17 @@ class AnswerPaperManager(models.Manager): ).count() def get_question_statistics(self, questionpaper_id, attempt_number, - status='completed'): + course_id, status='completed'): ''' Return dict with question object as key and list as value The list contains two value, first the number of times a question was answered correctly, and second the number of times a question appeared in a quiz''' question_stats = {} questions_answered = self.get_all_questions_answered(questionpaper_id, - attempt_number) - questions = self.get_all_questions(questionpaper_id, attempt_number) + attempt_number, + course_id) + questions = self.get_all_questions(questionpaper_id, attempt_number, + course_id) all_questions = Question.objects.filter( id__in=set(questions), active=True @@ -1376,8 +1407,9 @@ class AnswerPaperManager(models.Manager): def get_user_answerpapers(self, user): return self.filter(user=user) - def get_total_attempt(self, questionpaper, user): - return self.filter(question_paper=questionpaper, user=user).count() + def get_total_attempt(self, questionpaper, user, course_id): + return self.filter(question_paper=questionpaper, user=user, + course_id=course_id).count() def get_users_for_questionpaper(self, questionpaper_id, course_id): return self._get_answerpapers_for_quiz(questionpaper_id, course_id, diff --git a/yaksh/static/yaksh/js/course.js b/yaksh/static/yaksh/js/course.js index f0d03e2..1c64a3e 100644 --- a/yaksh/static/yaksh/js/course.js +++ b/yaksh/static/yaksh/js/course.js @@ -66,4 +66,79 @@ $("#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", '').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({});
+
+});
+
+function view_status(unit){
+ title_list = $(unit).attr("title").split("/");
+ $(unit).attr("title", title_list.join("\n"));
+}
diff --git a/yaksh/templates/yaksh/add_exercise.html b/yaksh/templates/yaksh/add_exercise.html index dac35d4..77e3ee8 100644 --- a/yaksh/templates/yaksh/add_exercise.html +++ b/yaksh/templates/yaksh/add_exercise.html @@ -14,17 +14,41 @@ {% block content %} <form name=frm id=frm action="" method="post" > - {% csrf_token %} - <center> - <table class="span1 table"> - {{ form.as_table }} - </table> - <br/><br/> - </center> + {% csrf_token %} + <center> + <table class="span1 table"> + {{ form.as_table }} + </table> + <br/><br/> + </center> + <center><button class="btn" type="submit" id="submit" name="save_exercise"> Save + </button> - <center><button class="btn" type="submit" id="submit" name="questionpaper"> Save - </button> - - <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center> + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/courses/");'>Cancel</button> </center> </form> +{% if exercise and course_id %} + {% if exercise.questionpaper_set.get.id %} + <center> + <h4>You can check the quiz by attempting it in the following modes:</h4> + <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ exercise.id }}/{{exercise.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a> + <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{exercise.id}}/{{course_id}}/");'>User Mode</button> + + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{exercise.id}}/{{course_id}}/");'> + God Mode</button> + <a data-toggle="collapse" data-target="#help"> + <span class="glyphicon glyphicon-info-sign">Help</span></a> + <div id="help" class="collapse"> + <br/> + <ul> + <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - + <ul> + <li><i>Quiz will have the same duration as that of the original quiz.</li> + <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> + <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> + </ul> + </p> + <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> + </div> + {% endif %} +{% endif %} {% endblock %} diff --git a/yaksh/templates/yaksh/add_quiz.html b/yaksh/templates/yaksh/add_quiz.html index d3705e3..684f804 100644 --- a/yaksh/templates/yaksh/add_quiz.html +++ b/yaksh/templates/yaksh/add_quiz.html @@ -35,31 +35,34 @@ </form> <br> -{% if quiz_id and course_id %} +{% if quiz and course_id %} + {% if quiz.questionpaper_set.get.id %} <center> <h4>You can check the quiz by attempting it in the following modes:</h4> - <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz_id}}/{{course_id}}/");'>User Mode</button> - - <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz_id}}/{{course_id}}/");'> - God Mode</button> + <a href="{{URL_ROOT}}/exam/manage/designquestionpaper/{{ quiz.id }}/{{quiz.questionpaper_set.get.id}}/{{course_id}}" class="btn btn-primary">View Question Paper</a> + <button class="btn" type="button" name="button" onClick='usermode("{{URL_ROOT}}/exam/manage/usermode/{{quiz.id}}/{{course_id}}/");'>User Mode</button> + + <button class="btn" type="button" name="button" onClick='location.replace("{{URL_ROOT}}/exam/manage/godmode/{{quiz.id}}/{{course_id}}/");'> + God Mode</button> <a data-toggle="collapse" data-target="#help"> <span class="glyphicon glyphicon-info-sign">Help</span></a> - <div id="help" class="collapse"> - <br/> - <ul> - <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - - <ul> - <li><i>Quiz will have the same duration as that of the original quiz.</li> - <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> - <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> - </ul> - </p> - <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> - </div> - {% endif %} - <style type="text/css"> + <div id="help" class="collapse"> + <br/> + <ul> + <li><b>User Mode:</b> Attempt quiz the way normal users will attempt i.e. - + <ul> + <li><i>Quiz will have the same duration as that of the original quiz.</li> + <li>Quiz won't start if the course is inactive or the quiz time has expired.</li> + <li>You will be notified about quiz prerequisites.(You can still attempt the quiz though)</i></li> + </ul> + </p> + <li> <b>God Mode:</b> Attempt quiz without any time or eligibilty constraints.</p> + </div> + {% endif %} +{% endif %} +<style type="text/css"> #rendered_text{ width: 550px; } - </style> +</style> {% endblock %} diff --git a/yaksh/templates/yaksh/course_detail.html b/yaksh/templates/yaksh/course_detail.html index cf0ab18..a5d10a7 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"> @@ -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 %} @@ -35,6 +36,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> @@ -84,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> @@ -92,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> @@ -103,6 +111,7 @@ <td> {{enrolled.profile.department}}</td> </tr> {% endfor %} + </tbody> </table> <br> <textarea name="subject" id="subject" placeholder="Email Subject" cols="50"></textarea> @@ -116,7 +125,72 @@ {% 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="tablesorter table table-bordered" id="course_table" data-sortlist="[0,0]"> + <thead> + <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> + ({{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 %} + <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 %} + </tbody> + </table> + </div> {% else %} + <div id="students_enrollment"> <div id="student-requests"> <center><b><u>Requests</u></b></center><br> {% if course.get_requests %} @@ -124,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> @@ -133,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> @@ -152,6 +229,7 @@ </td> </tr> {% endfor %} + </tbody> </table> <button class="btn btn-success" type="submit" name='enroll' value='enroll'>Enroll Selected</button> </div> @@ -166,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> @@ -175,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> @@ -190,6 +271,7 @@ </td> </tr> {% endfor %} + </tbody> </table> <button class="btn btn-danger" type="submit" name='reject' value='reject'> Reject Selected</button> @@ -205,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> @@ -214,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> @@ -230,6 +315,7 @@ </td> </tr> {% endfor %} + </tbody> </table> <br> <button class="btn btn-success" type="submit" name='enroll' value='enroll'> @@ -238,6 +324,7 @@ {% endif %} </form> </div> + </div> {% endif %} </div> </div> 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> <button class="btn btn-primary" type="reset">Cancel</button></center> + <center><button class="btn btn-primary" type="submit">Register</button> + <a href="{{URL_ROOT}}/exam" class="btn btn-danger">Cancel</a></center> </form> {% endblock content %} diff --git a/yaksh/templates/yaksh/show_video.html b/yaksh/templates/yaksh/show_video.html index f4b59ac..17f9d86 100644 --- a/yaksh/templates/yaksh/show_video.html +++ b/yaksh/templates/yaksh/show_video.html @@ -64,6 +64,7 @@ {% if learning_module.html_data%} <hr> {% endif %} + {% if learning_module.get_learning_units %} <center><h4>Following are the units in this modules</h4></center> <table class="table"> <tr> @@ -95,13 +96,23 @@ </tr> {% endfor %} </table> + {% else %} + <center><h3>No Lessons/Quizzes Found</h3></center> + {% endif %} </div> </div> <div style="text-align: center;"> - <a href="{{ URL_ROOT }}/exam/next_unit/{{course.id}}/{{learning_module.id}}/{{first_unit.id}}/1" class="btn btn-info">Start - <span class="glyphicon glyphicon-chevron-right"> - </span> - </a> + {% if first_unit %} + <a href="{{ URL_ROOT }}/exam/next_unit/{{course.id}}/{{learning_module.id}}/{{first_unit.id}}/1" class="btn btn-info">Start + <span class="glyphicon glyphicon-chevron-right"> + </span> + </a> + {% else %} + <a href="{{ URL_ROOT }}/exam/next_unit/{{course.id}}/{{learning_module.id}}" class="btn btn-info">Next + <span class="glyphicon glyphicon-chevron-right"> + </span> + </a> + {% endif %} </div> {% else %} <div class="panel panel-default" style="border: none; box-shadow: none;"> diff --git a/yaksh/templatetags/custom_filters.py b/yaksh/templatetags/custom_filters.py index 6ddd213..3c2c6fd 100644 --- a/yaksh/templatetags/custom_filters.py +++ b/yaksh/templatetags/custom_filters.py @@ -8,43 +8,57 @@ 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 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) diff --git a/yaksh/test_models.py b/yaksh/test_models.py index 0cd76bc..df8372c 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): @@ -1585,6 +1605,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..9be8d13 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1230,6 +1230,7 @@ class TestAddQuiz(TestCase): kwargs={'quiz_id': self.exercise.id}), data={ 'description': 'updated demo exercise', + 'active': True } ) @@ -1257,6 +1258,7 @@ class TestAddQuiz(TestCase): response = self.client.post(reverse('yaksh:add_exercise'), data={ 'description': "Demo Exercise", + 'active': True } ) quiz_list = Quiz.objects.all().order_by('-id') @@ -1916,6 +1918,25 @@ class TestAddCourse(TestCase): timezone='UTC' ) + # Create a teacher + self.teacher_plaintext_pass = 'demo_teacher' + self.teacher = User.objects.create_user( + username='demo_teacher', + password=self.teacher_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + # Create Student self.student_plaintext_pass = 'demo_student' self.student = User.objects.create_user( @@ -1928,10 +1949,13 @@ class TestAddCourse(TestCase): # Add to moderator group self.mod_group.user_set.add(self.user) + self.mod_group.user_set.add(self.teacher) self.course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user) + self.course.teachers.add(self.teacher) + self.pre_req_quiz = Quiz.objects.create( start_date_time=datetime(2014, 2, 1, 5, 8, 15, 0, tzone), end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), @@ -2021,6 +2045,33 @@ class TestAddCourse(TestCase): self.assertRedirects(response, '/exam/manage/courses', target_status_code=301) + def test_add_course_teacher_cannot_be_creator(self): + """ + Teacher editing the course should not become creator + """ + self.client.login( + username=self.teacher.username, + password=self.teacher_plaintext_pass + ) + + response = self.client.post(reverse('yaksh:edit_course', + kwargs={"course_id": self.course.id}), + data={'name': 'Teacher_course', + 'active': True, + 'enrollment': 'open', + 'start_enroll_time': '2016-01-10 09:00:15', + 'end_enroll_time': '2016-01-15 09:00:15', + } + ) + updated_course = Course.objects.get(id=self.course.id) + self.assertEqual(updated_course.name, 'Teacher_course') + self.assertEqual(updated_course.enrollment, 'open') + self.assertEqual(updated_course.active, True) + self.assertEqual(response.status_code, 302) + self.assertEqual(updated_course.creator, self.user) + self.assertRedirects(response, '/exam/manage/courses', + target_status_code=301) + class TestCourseDetail(TestCase): def setUp(self): @@ -2089,6 +2140,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 +2531,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): @@ -3960,8 +4040,27 @@ class TestQuestionPaper(TestCase): timezone='UTC' ) + self.teacher_plaintext_pass = 'demo_teacher' + self.teacher = User.objects.create_user( + username='demo_teacher', + password=self.teacher_plaintext_pass, + first_name='first_name', + last_name='last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.teacher, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + # Add to moderator group self.mod_group.user_set.add(self.user) + self.mod_group.user_set.add(self.teacher) self.course = Course.objects.create( name="Python Course", @@ -3976,6 +4075,15 @@ class TestQuestionPaper(TestCase): creator=self.user ) + self.demo_quiz = Quiz.objects.create( + start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), + end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), + duration=30, active=True, instructions="Demo Instructions", + attempts_allowed=-1, time_between_attempts=0, + description='demo quiz 2', pass_criteria=40, + creator=self.user + ) + self.learning_unit = LearningUnit.objects.create( order=1, type="quiz", quiz=self.quiz) self.learning_module = LearningModule.objects.create( @@ -3997,8 +4105,6 @@ class TestQuestionPaper(TestCase): ) self.mcq_based_testcase.save() - ordered_questions = str(self.question_mcq.id) - # Mcc Question self.question_mcc = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4012,8 +4118,6 @@ class TestQuestionPaper(TestCase): ) self.mcc_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_mcc.id) - # Integer Question self.question_int = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4026,8 +4130,6 @@ class TestQuestionPaper(TestCase): ) self.int_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_int.id) - # String Question self.question_str = Question.objects.create( summary="Test_mcc_question", description="Test MCC", @@ -4054,17 +4156,19 @@ class TestQuestionPaper(TestCase): ) self.float_based_testcase.save() - ordered_questions = ordered_questions + str(self.question_float.id) - - questions_list = [self.question_mcq, self.question_mcc, - self.question_int, self.question_str, - self.question_float] - + self.questions_list = [self.question_mcq, self.question_mcc, + self.question_int, self.question_str, + self.question_float] + questions_order = ",".join([ + str(self.question_mcq.id), str(self.question_mcc.id), + str(self.question_int.id), str(self.question_str.id), + str(self.question_float.id) + ]) self.question_paper = QuestionPaper.objects.create( quiz=self.quiz, - total_marks=5.0, fixed_question_order=ordered_questions + total_marks=5.0, fixed_question_order=questions_order ) - self.question_paper.fixed_questions.add(*questions_list) + self.question_paper.fixed_questions.add(*self.questions_list) self.answerpaper = AnswerPaper.objects.create( user=self.user, question_paper=self.question_paper, attempt_number=1, @@ -4073,12 +4177,14 @@ class TestQuestionPaper(TestCase): user_ip="127.0.0.1", status="inprogress", passed=False, percent=0, marks_obtained=0, course=self.course ) - self.answerpaper.questions.add(*questions_list) + self.answerpaper.questions.add(*self.questions_list) def tearDown(self): self.client.logout() self.user.delete() + self.teacher.delete() self.quiz.delete() + self.demo_quiz.delete() self.course.delete() self.answerpaper.delete() self.question_mcq.delete() @@ -4352,6 +4458,58 @@ class TestQuestionPaper(TestCase): wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id) self.assertEqual(wrong_answer_paper.marks_obtained, 0) + def test_design_questionpaper(self): + """ Test design Question Paper """ + + # Should fail if Question paper is not the one which is associated + # with a quiz + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.demo_quiz.id, + "questionpaper_id": self.question_paper.id})) + self.assertEqual(response.status_code, 404) + + self.client.login( + username=self.teacher.username, + password=self.teacher_plaintext_pass + ) + + # Should not allow teacher to view question paper + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "questionpaper_id": self.question_paper.id})) + + self.assertEqual(response.status_code, 404) + + # Should not allow teacher to view question paper + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "course_id": self.course.id, + "questionpaper_id": self.question_paper.id})) + + self.assertEqual(response.status_code, 404) + + # Should allow course teacher to view question paper + # Add teacher to the course + self.course.teachers.add(self.teacher) + response = self.client.get( + reverse('yaksh:designquestionpaper', + kwargs={"quiz_id": self.quiz.id, + "course_id": self.course.id, + "questionpaper_id": self.question_paper.id})) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/design_questionpaper.html') + self.assertEqual(response.context['fixed_questions'], + self.questions_list) + self.assertEqual(response.context['qpaper'], self.question_paper) + class TestLearningModule(TestCase): def setUp(self): @@ -4446,6 +4604,7 @@ class TestLearningModule(TestCase): check_prerequisite=False, creator=self.user) self.course.teachers.add(self.teacher) self.course.learning_module.add(self.learning_module) + self.course.learning_module.add(self.learning_module1) self.expected_url = "/exam/manage/courses/all_learning_module/" @@ -4710,13 +4869,35 @@ class TestLearningModule(TestCase): self.assertEqual(response.context["current_unit"].id, self.learning_unit1.id) + # Go to next module with empty module + response = self.client.get( + reverse('yaksh:next_unit', + kwargs={"module_id": self.learning_module1.id, + "course_id": self.course.id}), + follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["state"], "module") + self.assertEqual(response.context["learning_module"].id, + self.learning_module.id) + + # Go to next module from last unit of previous unit + response = self.client.get( + reverse('yaksh:next_unit', + kwargs={"module_id": self.learning_module.id, + "course_id": self.course.id, + "current_unit_id": self.learning_unit1.id}), + follow=True) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["state"], "module") + self.assertEqual(response.context["learning_module"].id, + self.learning_module1.id) + class TestLessons(TestCase): def setUp(self): self.client = Client() self.mod_group = Group.objects.create(name='moderator') - tzone = pytz.timezone('UTC') # Create Moderator with profile self.user_plaintext_pass = 'demo' self.user = User.objects.create_user( @@ -4764,25 +4945,30 @@ class TestLessons(TestCase): name="Python Course", enrollment="Open Enrollment", creator=self.user) - self.quiz = Quiz.objects.create( - start_date_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone), - end_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, tzone), - duration=30, active=True, instructions="Demo Instructions", - attempts_allowed=-1, time_between_attempts=0, - description='demo quiz', pass_criteria=40, - creator=self.user - ) - self.lesson = Lesson.objects.create( name="test lesson", description="test description", creator=self.user) + self.lesson2 = Lesson.objects.create( + name="test lesson2", description="test description2", + creator=self.user) self.learning_unit = LearningUnit.objects.create( - order=0, type="lesson", lesson=self.lesson) + order=0, type="lesson", lesson=self.lesson + ) + self.learning_unit2 = LearningUnit.objects.create( + order=0, type="lesson", lesson=self.lesson2 + ) self.learning_module = LearningModule.objects.create( order=0, name="test module", description="module", - check_prerequisite=False, creator=self.user) + check_prerequisite=False, creator=self.user + ) + self.learning_module2 = LearningModule.objects.create( + order=1, name="test module 2", description="module 2", + check_prerequisite=True, creator=self.user + ) self.learning_module.learning_unit.add(self.learning_unit.id) - self.course.learning_module.add(self.learning_module.id) + self.learning_module2.learning_unit.add(self.learning_unit2.id) + self.course.learning_module.add(*[ + self.learning_module.id, self.learning_module2.id]) self.course.teachers.add(self.teacher.id) self.expected_url = "/exam/manage/courses/" @@ -4791,11 +4977,13 @@ class TestLessons(TestCase): self.user.delete() self.student.delete() self.teacher.delete() - self.quiz.delete() self.course.delete() self.learning_unit.delete() + self.learning_unit2.delete() self.learning_module.delete() + self.learning_module2.delete() self.lesson.delete() + self.lesson2.delete() def test_edit_lesson_denies_non_moderator(self): """ Student should not be allowed to edit lesson """ @@ -4880,6 +5068,17 @@ class TestLessons(TestCase): self.assertEqual(response.context["state"], "lesson") self.assertEqual(response.context["current_unit"], self.learning_unit) + # Check unit module prerequisite completion status + response = self.client.get( + reverse('yaksh:show_lesson', + kwargs={"lesson_id": self.lesson2.id, + "module_id": self.learning_module2.id, + "course_id": self.course.id})) + err_msg = "You have not completed the module previous to {0}".format( + self.learning_module2.name) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context["msg"], err_msg) + def test_show_all_lessons(self): """ Moderator should be able to see all created lessons""" self.client.login( diff --git a/yaksh/urls.py b/yaksh/urls.py index b4bbb41..08c2091 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -40,6 +40,8 @@ urlpatterns = [ views.view_module, name='view_module'), url(r'^next_unit/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<current_unit_id>\d+)/$', views.get_next_unit, name='next_unit'), + url(r'^next_unit/(?P<course_id>\d+)/(?P<module_id>\d+)/$', + views.get_next_unit, name='next_unit'), url(r'^next_unit/(?P<course_id>\d+)/(?P<module_id>\d+)/(?P<current_unit_id>\d+)/(?P<first_unit>\d+)/$', views.get_next_unit, name='next_unit'), url(r'^course_modules/(?P<course_id>\d+)/$', @@ -76,6 +78,8 @@ urlpatterns = [ name='design_questionpaper'), url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/$', views.design_questionpaper, name='designquestionpaper'), + url(r'^manage/designquestionpaper/(?P<quiz_id>\d+)/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$', + views.design_questionpaper, name='designquestionpaper'), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<course_id>\d+)/$', views.show_statistics, name="show_statistics"), url(r'^manage/statistics/question/(?P<questionpaper_id>\d+)/(?P<attempt_number>\d+)/(?P<course_id>\d+)/$', @@ -166,4 +170,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..2bc5dfe 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -320,10 +320,9 @@ def add_quiz(request, quiz_id=None, course_id=None): return my_redirect("/exam/manage/courses/") else: - quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None form = QuizForm(instance=quiz) - context["quiz_id"] = quiz_id context["course_id"] = course_id + context["quiz"] = quiz context["form"] = form return my_render_to_response( 'yaksh/add_quiz.html', context, context_instance=ci @@ -346,7 +345,7 @@ def add_exercise(request, quiz_id=None, course_id=None): if course_id: course = get_object_or_404(Course, pk=course_id) if not course.is_creator(user) and not course.is_teacher(user): - raise Http404('This quiz does not belong to you') + raise Http404('This Course does not belong to you') context = {} if request.method == "POST": @@ -370,9 +369,8 @@ def add_exercise(request, quiz_id=None, course_id=None): return my_redirect("/exam/manage/courses/") else: - quiz = Quiz.objects.get(id=quiz_id) if quiz_id else None form = ExerciseForm(instance=quiz) - context["quiz_id"] = quiz_id + context["exercise"] = quiz context["course_id"] = course_id context["form"] = form return my_render_to_response( @@ -477,6 +475,16 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, learning_module = course.learning_module.get(id=module_id) learning_unit = learning_module.learning_unit.get(quiz=quest_paper.quiz.id) + # unit module prerequiste check + if learning_module.has_prerequisite(): + if not learning_module.is_prerequisite_passed(user, course): + msg = "You have not completed the module previous to {0}".format( + 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) @@ -931,14 +939,14 @@ def add_course(request, course_id=None): raise Http404("You are not allowed to view this course") else: course = None - if not is_moderator(user): raise Http404('You are not allowed to view this page') if request.method == 'POST': form = CourseForm(request.POST, instance=course) if form.is_valid(): new_course = form.save(commit=False) - new_course.creator = user + if course_id is None: + new_course.creator = user new_course.save() return my_redirect('/exam/manage/courses') else: @@ -1157,7 +1165,7 @@ def show_statistics(request, questionpaper_id, attempt_number=None, course_id): return my_redirect('/exam/manage/') question_stats = AnswerPaper.objects.get_question_statistics( - questionpaper_id, attempt_number + questionpaper_id, attempt_number, course_id ) context = {'question_stats': question_stats, 'quiz': quiz, 'questionpaper_id': questionpaper_id, @@ -1291,14 +1299,21 @@ def _remove_already_present(questionpaper_id, questions): @login_required @email_verified -def design_questionpaper(request, quiz_id, questionpaper_id=None): +def design_questionpaper(request, quiz_id, questionpaper_id=None, + course_id=None): user = request.user if not is_moderator(user): raise Http404('You are not allowed to view this page!') - quiz = Quiz.objects.get(id=quiz_id) - if not quiz.creator == user: - raise Http404('This course does not belong to you') + if quiz_id: + quiz = get_object_or_404(Quiz, pk=quiz_id) + if quiz.creator != user and not course_id: + raise Http404('This quiz does not belong to you') + if course_id: + 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') + filter_form = QuestionFilterForm(user=user) questions = None marks = None @@ -1306,7 +1321,8 @@ def design_questionpaper(request, quiz_id, questionpaper_id=None): if questionpaper_id is None: question_paper = QuestionPaper.objects.get_or_create(quiz_id=quiz_id)[0] else: - question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id) + question_paper = get_object_or_404(QuestionPaper, id=questionpaper_id, + quiz_id=quiz_id) qpaper_form = QuestionPaperForm(instance=question_paper) if request.method == 'POST': @@ -1523,9 +1539,9 @@ def download_quiz_csv(request, course_id, quiz_id): questions = question_paper.get_question_bank() answerpapers = AnswerPaper.objects.filter(question_paper=question_paper, - attempt_number=attempt_number) + attempt_number=attempt_number, course_id=course_id) if not answerpapers: - return monitor(request, quiz_id) + return monitor(request, quiz_id, course_id) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{0}-{1}-attempt{2}.csv"'.format( course.name.replace('.', ''), quiz.description.replace('.', ''), @@ -2332,6 +2348,15 @@ def show_lesson(request, lesson_id, module_id, course_id): learn_module = course.learning_module.get(id=module_id) learn_unit = learn_module.learning_unit.get(lesson_id=lesson_id) learning_units = learn_module.get_learning_units() + if learn_module.has_prerequisite(): + if not learn_module.is_prerequisite_passed(user, course): + msg = "You have not completed the module previous to {0}".format( + learn_module.name) + return view_module(request, module_id, course_id, msg) + + # update course status with current unit + _update_unit_status(course_id, user, learn_unit) + all_modules = course.get_learning_modules() if learn_unit.has_prerequisite(): if not learn_unit.is_prerequisite_passed(user, learn_module, course): @@ -2508,7 +2533,7 @@ def preview_html_text(request): @login_required @email_verified -def get_next_unit(request, course_id, module_id, current_unit_id, +def get_next_unit(request, course_id, module_id, current_unit_id=None, first_unit=None): user = request.user course = Course.objects.prefetch_related("learning_module").get( @@ -2517,8 +2542,14 @@ def get_next_unit(request, course_id, module_id, current_unit_id, raise Http404('You are not enrolled for this course!') learning_module = course.learning_module.prefetch_related( "learning_unit").get(id=module_id) - current_learning_unit = learning_module.learning_unit.get( - id=current_unit_id) + + if current_unit_id: + current_learning_unit = learning_module.learning_unit.get( + id=current_unit_id) + else: + next_module = course.next_module(learning_module.id) + return my_redirect("/exam/quizzes/view_module/{0}/{1}".format( + next_module.id, course_id)) if first_unit: next_unit = current_learning_unit @@ -2547,9 +2578,6 @@ def get_next_unit(request, course_id, module_id, current_unit_id, return my_redirect("/exam/quizzes/view_module/{0}/{1}/".format( next_module.id, course.id)) - # make next available unit as current unit - course_status.current_unit = next_unit - course_status.save() if next_unit.type == "quiz": return my_redirect("/exam/start/{0}/{1}/{2}".format( next_unit.quiz.questionpaper_set.get().id, module_id, course_id)) @@ -2635,13 +2663,14 @@ def view_module(request, module_id, course_id, msg=None): all_modules = course.get_learning_modules() if learning_module.has_prerequisite(): if not learning_module.is_prerequisite_passed(user, course): - msg = "You have not completed the previous learning module" + msg = "You have not completed the module previous to {0}".format( + learning_module.name) return course_modules(request, course_id, msg) learning_units = learning_module.get_learning_units() context['learning_units'] = learning_units context['learning_module'] = learning_module - context['first_unit'] = learning_units[0] + context['first_unit'] = learning_units.first() context['all_modules'] = all_modules context['user'] = user context['course'] = course @@ -2666,3 +2695,36 @@ 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) + + +def _update_unit_status(course_id, user, unit): + """ Update course status with current unit """ + course_status = CourseStatus.objects.filter( + 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.save() |