diff options
author | Prabhu Ramachandran | 2018-01-03 22:23:19 +0530 |
---|---|---|
committer | GitHub | 2018-01-03 22:23:19 +0530 |
commit | feb295b4107a95621e9430f5c7042cfde4674cc0 (patch) | |
tree | 098c7cdc1e97d5e7bd859e35107a4733e800a586 /yaksh | |
parent | e566d54239efcb46f253e324b7295a676378f656 (diff) | |
parent | 4310d5905a9cc702198e42830c1b670957cd7360 (diff) | |
download | online_test-feb295b4107a95621e9430f5c7042cfde4674cc0.tar.gz online_test-feb295b4107a95621e9430f5c7042cfde4674cc0.tar.bz2 online_test-feb295b4107a95621e9430f5c7042cfde4674cc0.zip |
Merge pull request #408 from prathamesh920/exercise
Exercise feature in video lessons
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/forms.py | 8 | ||||
-rw-r--r-- | yaksh/models.py | 22 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/add_question.js | 4 | ||||
-rw-r--r-- | yaksh/static/yaksh/js/requesthandler.js | 8 | ||||
-rw-r--r-- | yaksh/templates/exam.html | 4 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_exercise.html | 30 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_question.html | 3 | ||||
-rw-r--r-- | yaksh/templates/yaksh/course_modules.html | 16 | ||||
-rw-r--r-- | yaksh/templates/yaksh/courses.html | 28 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 47 | ||||
-rw-r--r-- | yaksh/templates/yaksh/quit.html | 10 | ||||
-rw-r--r-- | yaksh/templates/yaksh/view_answerpaper.html | 10 | ||||
-rw-r--r-- | yaksh/test_views.py | 100 | ||||
-rw-r--r-- | yaksh/urls.py | 5 | ||||
-rw-r--r-- | yaksh/views.py | 137 |
15 files changed, 379 insertions, 53 deletions
diff --git a/yaksh/forms.py b/yaksh/forms.py index 52e6a12..8399bc9 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -173,6 +173,12 @@ class UserLoginForm(forms.Form): return user +class ExerciseForm(forms.ModelForm): + class Meta: + model = Quiz + fields = ['description', 'view_answerpaper'] + + class QuizForm(forms.ModelForm): """Creates a form to add or edit a Quiz. It has the related fields and functions required.""" @@ -209,7 +215,7 @@ class QuizForm(forms.ModelForm): class Meta: model = Quiz - exclude = ["is_trial", "creator"] + exclude = ["is_trial", "creator", "is_exercise"] class QuestionForm(forms.ModelForm): diff --git a/yaksh/models.py b/yaksh/models.py index 5eca3d1..1d24bda 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -311,6 +311,8 @@ class Quiz(models.Model): weightage = models.FloatField(default=1.0) + is_exercise = models.BooleanField(default=False) + creator = models.ForeignKey(User, null=True) objects = QuizManager() @@ -757,6 +759,10 @@ class Question(models.Model): # Check assignment upload based question grade_assignment_upload = models.BooleanField(default=False) + min_time = models.IntegerField("time in minutes", default=0) + + solution = models.TextField(blank=True) + def consolidate_answer_data(self, user_answer, user=None): question_data = {} metadata = {} @@ -1527,15 +1533,25 @@ class AnswerPaper(models.Model): def time_left(self): """Return the time remaining for the user in seconds.""" + secs = self._get_total_seconds() + total = self.question_paper.quiz.duration*60.0 + remain = max(total - secs, 0) + return int(remain) + + def time_left_on_question(self, question): + secs = self._get_total_seconds() + total = question.min_time*60.0 + remain = max(total - secs, 0) + return int(remain) + + def _get_total_seconds(self): dt = timezone.now() - self.start_time try: secs = dt.total_seconds() except AttributeError: # total_seconds is new in Python 2.7. :( secs = dt.seconds + dt.days*24*3600 - total = self.question_paper.quiz.duration*60.0 - remain = max(total - secs, 0) - return int(remain) + return secs def _update_marks_obtained(self): """Updates the total marks earned by student for this paper.""" diff --git a/yaksh/static/yaksh/js/add_question.js b/yaksh/static/yaksh/js/add_question.js index 5bec8c6..346991a 100644 --- a/yaksh/static/yaksh/js/add_question.js +++ b/yaksh/static/yaksh/js/add_question.js @@ -110,6 +110,9 @@ function textareaformat() document.getElementById('my').innerHTML = document.getElementById('id_description').value ; }); + $('#id_solution').bind('keypress', function (event){ + document.getElementById('rend_solution').innerHTML = document.getElementById('id_solution').value ; + }); $('#id_type').bind('focus', function(event){ var type = document.getElementById('id_type'); @@ -121,6 +124,7 @@ function textareaformat() language.style.border = '1px solid #ccc'; }); document.getElementById('my').innerHTML = document.getElementById('id_description').value ; + document.getElementById('rend_solution').innerHTML = document.getElementById('id_solution').value ; if (document.getElementById('id_grade_assignment_upload').checked || document.getElementById('id_type').value == 'upload'){ diff --git a/yaksh/static/yaksh/js/requesthandler.js b/yaksh/static/yaksh/js/requesthandler.js index 639dc81..f98ab12 100644 --- a/yaksh/static/yaksh/js/requesthandler.js +++ b/yaksh/static/yaksh/js/requesthandler.js @@ -36,6 +36,11 @@ function unlock_screen() { document.getElementById("ontop").style.display = "none"; } +function show_solution() { + document.getElementById("solution").style.display = "block"; + document.getElementById("skip_ex").style.visibility = "visible"; +} + function get_result(uid){ var url = "/exam/get_result/" + uid + "/" + course_id + "/" + module_id + "/"; ajax_check_code(url, "GET", "html", null, uid) @@ -119,6 +124,9 @@ function ajax_check_code(url, method_type, data_type, data, uid) var global_editor = {}; var csrftoken = jQuery("[name=csrfmiddlewaretoken]").val(); $(document).ready(function(){ + if(is_exercise == "True" && can_skip == "False"){ + setTimeout(function() {show_solution();}, delay_time*1000); + } // Codemirror object, language modes and initial content // Get the textarea node diff --git a/yaksh/templates/exam.html b/yaksh/templates/exam.html index 03c9ff8..ce1d3b8 100644 --- a/yaksh/templates/exam.html +++ b/yaksh/templates/exam.html @@ -23,9 +23,9 @@ <ul class="nav navbar-nav navbar"> <li style="padding: 10px"><button class="btn btn-danger btn-sm" type="submit" name="quit"> {% if paper.questions_unanswered.all %} - Quit Exam + Quit {{ quiz_type }} {% else %} - Finish Exam + Finish {{ quiz_type }} {% endif %} <span class="glyphicon glyphicon-off"></span></button></li> </ul> diff --git a/yaksh/templates/yaksh/add_exercise.html b/yaksh/templates/yaksh/add_exercise.html new file mode 100644 index 0000000..dac35d4 --- /dev/null +++ b/yaksh/templates/yaksh/add_exercise.html @@ -0,0 +1,30 @@ +{% extends "manage.html" %} + + +{% block subtitle %}Add Exercise{% endblock %} + +{% block css %} +<link rel="stylesheet" href="{{ URL_ROOT }}/static/yaksh/css/question_quiz.css" type="text/css" /> +{% endblock %} +{% block script %} +<script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.9.1.min.js"></script> +<script src="{{ URL_ROOT }}/static/yaksh/js/add_quiz.js"></script> +{% endblock %} +{% block onload %} onload="javascript:test();" {% endblock %} +{% block content %} + +<form name=frm id=frm action="" method="post" > + {% csrf_token %} + <center> + <table class="span1 table"> + {{ form.as_table }} + </table> + <br/><br/> + </center> + + <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> +</form> +{% endblock %} diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html index fb8cb95..ed69657 100644 --- a/yaksh/templates/yaksh/add_question.html +++ b/yaksh/templates/yaksh/add_question.html @@ -24,7 +24,10 @@ <tr><td><strong>Rendered: </strong><td><p id='my'></p> <tr><td>Description: <td>{{ qform.description}} {{qform.description.errors}} <tr><td>Tags: <td>{{ qform.tags }} + <tr><td><strong>Rendered Solution: </strong><td><p id='rend_solution'></p> + <tr><td>Solution: <td>{{ qform.solution }} <tr><td>Snippet: <td>{{ qform.snippet }} + <tr><td>Minimum Time(in minutes):<td> {{ qform.min_time }} <tr><td>Partial Grading: <td>{{ qform.partial_grading }} <tr><td>Grade Assignment Upload:<td> {{ qform.grade_assignment_upload }} <tr><td> File: <td> {{ fileform.file_field }}{{ fileform.file_field.errors }} diff --git a/yaksh/templates/yaksh/course_modules.html b/yaksh/templates/yaksh/course_modules.html index 8e6f5a6..fad1be0 100644 --- a/yaksh/templates/yaksh/course_modules.html +++ b/yaksh/templates/yaksh/course_modules.html @@ -31,7 +31,7 @@ </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</a> + View Lessons/Quizzes/Exercises</a> </td> <td> {% get_module_status user module course as module_status %} @@ -56,7 +56,7 @@ <div id="learning_units{{module.id}}{{course.id}}" class="collapse"> <table class="table"> <tr> - <th>Lesson/Quiz</th> + <th>Lesson/Quiz/Exercise</th> <th>Status</th> <th>Type</th> <th>View AnswerPaper</th> @@ -85,7 +85,15 @@ {% endif %} </td> <td> - {{unit.type|title}} + {% if unit.type == "quiz" %} + {% if unit.quiz.is_exercise %} + Exercise + {% else %} + Quiz + {% endif %} + {% else %} + Lesson + {% endif %} </td> <td> {% if unit.type == "quiz" %} @@ -109,4 +117,4 @@ {% else %} <h3> No lectures found </h3> {% endif %} -{% endblock %}
\ No newline at end of file +{% endblock %} diff --git a/yaksh/templates/yaksh/courses.html b/yaksh/templates/yaksh/courses.html index 0efa72a..b6b9f7e 100644 --- a/yaksh/templates/yaksh/courses.html +++ b/yaksh/templates/yaksh/courses.html @@ -152,8 +152,13 @@ <ul class="inputs-list"> <li> {% if unit.type == "quiz" %} - <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}"> - {{unit.quiz.description}}</a> + {% if unit.quiz.is_exercise %} + <a href="{{URL_ROOT}}/exam/manage/add_exercise/{{unit.quiz.id}}/{{course.id}}"> + {{unit.quiz.description}}</a> + {% else %} + <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}"> + {{unit.quiz.description}}</a> + {% endif %} {% else %} <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{unit.lesson.id}}/{{course.id}}"> {{unit.lesson.name}}</a> @@ -283,8 +288,13 @@ <ul class="inputs-list"> <li> {% if unit.type == "quiz" %} - <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}"> - {{unit.quiz.description}}</a> + {% if unit.quiz.is_exercise %} + <a href="{{URL_ROOT}}/exam/manage/add_exercise/{{unit.quiz.id}}/{{course.id}}"> + {{unit.quiz.description}}</a> + {% else %} + <a href="{{URL_ROOT}}/exam/manage/addquiz/{{unit.quiz.id}}/{{course.id}}"> + {{unit.quiz.description}}</a> + {% endif %} {% else %} <a href="{{URL_ROOT}}/exam/manage/courses/edit_lesson/{{unit.lesson.id}}/{{course.id}}"> {{unit.lesson.name}}</a> @@ -313,7 +323,8 @@ <div id="all_quizzes" > <div class="col-md-offset-2 main"> {% if type == "quiz" %} - <a href="{{URL_ROOT}}/exam/manage/addquiz/" class="btn btn-primary">Add new Quiz</a> + <a href="{{URL_ROOT}}/exam/manage/addquiz/" class="btn btn-primary">Add New Quiz</a> + <a href="{{URL_ROOT}}/exam/manage/add_exercise/" class="btn btn-primary">Add New Exercise</a> {% if not quizzes %} <center><h4> No new Quiz Added</h4></center> <br><br> @@ -331,8 +342,11 @@ <td>{{forloop.counter}}</td> <td width="30%"> <ul class="list-group"> - <a href="{{URL_ROOT}}/exam/manage/addquiz/{{quiz.id}}/">{{ quiz.description }} - </a> + {% if quiz.is_exercise %} + <a href="{{URL_ROOT}}/exam/manage/add_exercise/{{quiz.id}}/">{{ quiz.description }}</a> + {% else %} + <a href="{{URL_ROOT}}/exam/manage/addquiz/{{quiz.id}}/">{{ quiz.description }}</a> + {% endif %} {% if quiz.active %} <span class="label label-success">Active</span> {% else %} diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index d65e513..b7251ad 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -22,7 +22,18 @@ <script src="{{ URL_ROOT }}/static/yaksh/js/codemirror/mode/shell/shell.js"></script> <script src="{{ URL_ROOT }}/static/yaksh/js/mathjax/MathJax.js?config=TeX-MML-AM_CHTML"></script> <script> +init_val = '{{ last_attempt|escape_quotes|safe }}'; +lang = "{{ question.language }}" +course_id = "{{course.id}}" +module_id = "{{module.id}}" +is_exercise = "{{ quiz.is_exercise }}" +can_skip = "{{ can_skip }}" +delay_time = new Number("{{ delay_time }}") + var time_left = {{ paper.time_left }} +{% if quiz.is_exercise %} + time_left = {{ delay_time }} +{% endif %} function getTimeRemaining(endtime){ var t = Date.parse(endtime) - Date.parse(new Date()); @@ -51,13 +62,15 @@ function updateClock(){ var mm = ('0' + t.minutes).slice(-2); var ss = ('0' + t.seconds).slice(-2); - if(t.total<0){ - + if(t.total<0 && is_exercise=="False"){ document.forms["code"].submit(); clearInterval(timeinterval); return null; } - if (t.total<=300000){ + if(t.total<=0 && is_exercise=="True"){ + clearInterval(timeinterval); + } + if (t.total<=300000 && t.total > 0){ clock.innerHTML = "<blink><center><span style='color:red'><strong>" + hh + ":" + mm + ":" + ss + "</strong></center></span></blink>"; } if (t.total>=300000) { @@ -67,6 +80,7 @@ function updateClock(){ var clock = document.getElementById("time_left"); updateClock(); var timeinterval = setInterval(updateClock,1000); + } function validate(){ @@ -91,7 +105,6 @@ module_id = "{{module.id}}" question_type = "{{ question.type }}" </script> - {% endblock script %} {% block onload %} onload="updateTime();" {% endblock %} @@ -184,6 +197,21 @@ question_type = "{{ question.type }}" <h5><a href="{{f_name.file.url}}">{{f_name.file.name}}</a></h5> {% endfor %} {% endif %} + + {% if quiz.is_exercise %} + {% if can_skip %} + <div id="solution"> + {% else %} + <div id="solution" style="display:none"> + {% endif %} + {% if question.solution %} + <h4><u> Solution by teacher</u></h4> + {% else %} + <h4><u> No solution provided by teacher </u></h4> + {% endif%} + <font size=3 face=arial> {{ question.solution|safe }} </font> + </div> + {% endif %} </div> <div class="panel-body"> {% if question.type == "mcq" %} @@ -253,16 +281,23 @@ question_type = "{{ question.type }}" <br><button class="btn btn-primary" type="submit" name="check" id="check" onClick="return validate();">Upload</button> {% else %} - {% if question in paper.get_questions_unanswered %} + {% if question in paper.get_questions_unanswered or quiz.is_exercise %} <button class="btn btn-primary" type="submit" name="check" id="check" >Check Answer <span class="glyphicon glyphicon-cog"></span></button> {% endif %} {% endif %} + {% if quiz.is_exercise %} + {% if can_skip %} + <button id="skip_ex" class="btn btn-primary" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')" name="skip"> Next <span class="glyphicon glyphicon-arrow-right"></span></button> + {% else %} + <button id="skip_ex" class="btn btn-primary" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')" name="skip" style="visibility:hidden"> Next <span class="glyphicon glyphicon-arrow-right"></span></button> + {% endif %} + {% endif %} {% if paper.question_paper.quiz.allow_skip and not paper.get_questions_unanswered|length_is:"1" %} {% if question in paper.get_questions_unanswered %} <button class="btn btn-primary" onclick="call_skip('{{ URL_ROOT }}/exam/{{ question.id }}/skip/{{ paper.attempt_number }}/{{ module.id }}/{{ paper.question_paper.id }}/{{course.id}}/')" name="skip" id="skip">Attempt Later <span class="glyphicon glyphicon-arrow-right"></span></button> {% endif %} - {% endif %} + {% endif %} </div> </form> </div> diff --git a/yaksh/templates/yaksh/quit.html b/yaksh/templates/yaksh/quit.html index 78a9b47..ee72026 100644 --- a/yaksh/templates/yaksh/quit.html +++ b/yaksh/templates/yaksh/quit.html @@ -28,9 +28,13 @@ width="80" alt="YAKSH"></img> {% endblock %} {% endfor %} </table></center> - <center><h4>Your current answers are saved.</h4></center> - <center><h4> Are you sure you wish to quit the exam?</h4></center> - <center><h4> Be sure, as you won't be able to restart this exam.</h4></center> + {% if paper.question_paper.quiz.is_exercise %} + <center><h4> Are you sure you wish to quit the Exercise?</h4></center> + {% else %} + <center><h4>Your current answers are saved.</h4></center> + <center><h4> Are you sure you wish to quit the exam?</h4></center> + <center><h4> Be sure, as you won't be able to restart this exam.</h4></center> + {% endif %} <form action="{{URL_ROOT}}/exam/complete/{{ paper.attempt_number }}/{{module_id}}/{{ paper.question_paper.id }}/{{course_id}}/" method="post"> {% csrf_token %} <center> diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html index 5f899e3..410b578 100644 --- a/yaksh/templates/yaksh/view_answerpaper.html +++ b/yaksh/templates/yaksh/view_answerpaper.html @@ -11,13 +11,21 @@ {% block main %} {% if not data.papers %} + {% if quiz.is_exercise %} + <p><b> You have not attempted the Exercise {{ quiz.description }} </b></p> + {% else %} <p><b> You have not attempted the quiz {{ quiz.description }} </b></p> + {% endif %} {% else %} {% for paper in data.papers %} {% if forloop.counter == 2 and data.questionpaperid %} <U><h2> Previous attempts </h2></U> {% endif %} - <h2> Quiz: {{ paper.question_paper.quiz.description }} </h2> + {% if quiz.is_exercise %} + <h2> Exercise: {{ paper.question_paper.quiz.description }} </h2> + {% else %} + <h2> Quiz: {{ paper.question_paper.quiz.description }} </h2> + {% endif %} <p> Attempt Number: {{ paper.attempt_number }}<br/> diff --git a/yaksh/test_views.py b/yaksh/test_views.py index ad5b714..71d6f80 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1042,11 +1042,19 @@ class TestAddQuiz(TestCase): creator=self.user ) + self.exercise = 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), + attempts_allowed=-1, time_between_attempts=0, + is_exercise=True, description='demo exercise', creator=self.user + ) + def tearDown(self): self.client.logout() self.user.delete() self.student.delete() self.quiz.delete() + self.exercise.delete() self.course.delete() def test_add_quiz_denies_anonymous(self): @@ -1171,6 +1179,98 @@ class TestAddQuiz(TestCase): self.assertEqual(response.status_code, 302) self.assertRedirects(response, '/exam/manage/courses/all_quizzes/') + def test_add_exercise_denies_anonymous(self): + """ + If not logged in redirect to login page + """ + response = self.client.get(reverse('yaksh:add_exercise'), + follow=True + ) + redirect_destination = '/exam/login/?next=/exam/manage/add_exercise/' + self.assertRedirects(response, redirect_destination) + + def test_add_exercise_denies_non_moderator(self): + """ + If not moderator in redirect to login page + """ + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + course_id = self.course.id + response = self.client.get(reverse('yaksh:add_exercise'), + follow=True + ) + self.assertEqual(response.status_code, 404) + + def test_add_exercise_get(self): + """ + GET request to add exercise should display add exercise form + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + response = self.client.get(reverse('yaksh:add_exercise') + ) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'yaksh/add_exercise.html') + self.assertIsNotNone(response.context['form']) + + def test_add_exercise_post_existing_exercise(self): + """ + POST request to edit exercise + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + tzone = pytz.timezone('UTC') + response = self.client.post(reverse('yaksh:edit_exercise', + kwargs={'quiz_id': self.exercise.id}), + data={ + 'description': 'updated demo exercise', + } + ) + + updated_exercise = Quiz.objects.get(id=self.exercise.id) + self.assertEqual(updated_exercise.active, True) + self.assertEqual(updated_exercise.duration, 1000) + self.assertEqual(updated_exercise.attempts_allowed, -1) + self.assertEqual(updated_exercise.time_between_attempts, 0) + self.assertEqual(updated_exercise.description, 'updated demo exercise') + self.assertEqual(updated_exercise.pass_criteria, 0) + self.assertTrue(updated_exercise.is_exercise) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/exam/manage/courses/all_quizzes/') + + def test_add_exercise_post_new_exercise(self): + """ + POST request to add new exercise + """ + self.client.login( + username=self.user.username, + password=self.user_plaintext_pass + ) + + tzone = pytz.timezone('UTC') + response = self.client.post(reverse('yaksh:add_exercise'), + data={ + 'description': "Demo Exercise", + } + ) + quiz_list = Quiz.objects.all().order_by('-id') + new_exercise = quiz_list[0] + self.assertEqual(new_exercise.active, True) + self.assertEqual(new_exercise.duration, 1000) + self.assertEqual(new_exercise.attempts_allowed, -1) + self.assertEqual(new_exercise.time_between_attempts, 0) + self.assertEqual(new_exercise.description, 'Demo Exercise') + self.assertEqual(new_exercise.pass_criteria, 0) + self.assertTrue(new_exercise.is_exercise) + self.assertEqual(response.status_code, 302) + self.assertRedirects(response, '/exam/manage/courses/all_quizzes/') + def test_show_all_quizzes(self): self.client.login( username=self.user.username, diff --git a/yaksh/urls.py b/yaksh/urls.py index 716a7d0..b4bbb41 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -49,6 +49,11 @@ urlpatterns = [ url(r'^manage/addquestion/(?P<question_id>\d+)/$', views.add_question, name="add_question"), url(r'^manage/addquiz/$', views.add_quiz, name='add_quiz'), + url(r'^manage/add_exercise/$', views.add_exercise, name='add_exercise'), + url(r'^manage/add_exercise/(?P<quiz_id>\d+)/$', views.add_exercise, + name='edit_exercise'), + url(r'^manage/add_exercise/(?P<quiz_id>\d+)/(?P<course_id>\d+)/$', + views.add_exercise, name='edit_exercise'), url(r'^manage/addquiz/(?P<quiz_id>\d+)/$', views.add_quiz, name='edit_quiz'), url(r'^manage/addquiz/(?P<quiz_id>\d+)/(?P<course_id>\d+)$', diff --git a/yaksh/views.py b/yaksh/views.py index 295b983..a4d9e78 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -49,7 +49,7 @@ from yaksh.forms import ( UserRegisterForm, UserLoginForm, QuizForm, QuestionForm, RandomQuestionForm, QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm, get_object_form, FileForm, QuestionPaperForm, LessonForm, - LessonFileForm, LearningModuleForm + LessonFileForm, LearningModuleForm, ExerciseForm ) from .settings import URL_ROOT from .file_utils import extract_files, is_csv @@ -330,6 +330,56 @@ def add_quiz(request, quiz_id=None, course_id=None): @login_required +@email_verified +def add_exercise(request, quiz_id=None, course_id=None): + user = request.user + ci = RequestContext(request) + if not is_moderator(user): + raise Http404('You are not allowed to view this course !') + 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') + else: + quiz = 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') + + context = {} + if request.method == "POST": + form = ExerciseForm(request.POST, instance=quiz) + if form.is_valid(): + if quiz is None: + form.instance.creator = user + quiz = form.save(commit=False) + quiz.is_exercise = True + quiz.time_between_attempts = 0 + quiz.weightage = 0 + quiz.allow_skip = False + quiz.attempts_allowed = -1 + quiz.duration = 1000 + quiz.pass_criteria = 0 + quiz.save() + + if not course_id: + return my_redirect("/exam/manage/courses/all_quizzes/") + else: + 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["course_id"] = course_id + context["form"] = form + return my_render_to_response( + 'yaksh/add_exercise.html', context, context_instance=ci + ) + + +@login_required @has_profile @email_verified def prof_manage(request, msg=None): @@ -465,7 +515,8 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, if last_attempt and last_attempt.is_attempt_inprogress(): return show_question( request, last_attempt.current_question(), last_attempt, - course_id=course_id, module_id=module_id + course_id=course_id, module_id=module_id, + previous_question=last_attempt.current_question() ) # allowed to start if not quest_paper.can_attempt_now(user, course_id): @@ -479,7 +530,7 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, attempt_number = 1 else: attempt_number = last_attempt.attempt_number + 1 - if attempt_num is None: + if attempt_num is None and not quest_paper.quiz.is_exercise: context = { 'user': user, 'questionpaper': quest_paper, @@ -501,7 +552,7 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, if new_paper.status == 'inprogress': return show_question(request, new_paper.current_question(), new_paper, course_id=course_id, - module_id=module_id + module_id=module_id, previous_question=None ) else: msg = 'You have already finished the quiz!' @@ -510,27 +561,42 @@ def start(request, questionpaper_id=None, attempt_num=None, course_id=None, @login_required @email_verified def show_question(request, question, paper, error_message=None, notification=None, - course_id=None, module_id=None): + course_id=None, module_id=None, previous_question=None): """Show a question if possible.""" user = request.user + quiz = paper.question_paper.quiz + quiz_type = 'Exam' + can_skip = False + if previous_question: + delay_time = paper.time_left_on_question(previous_question) + else: + delay_time = paper.time_left_on_question(question) + + if previous_question and quiz.is_exercise: + if delay_time <= 0 or previous_question in paper.questions_answered.all(): + can_skip = True + question = previous_question if not question: msg = 'Congratulations! You have successfully completed the quiz.' return complete( request, msg, paper.attempt_number, paper.question_paper.id, course_id=course_id, module_id=module_id ) - if not paper.question_paper.quiz.active: + if not quiz.active: reason = 'The quiz has been deactivated!' return complete( request, reason, paper.attempt_number, paper.question_paper.id, - module_id=module_id - ) - if paper.time_left() <= 0: - reason = 'Your time is up!' - return complete( - request, reason, paper.attempt_number, paper.question_paper.id, - course_id, module_id=module_id + course_id=course_id, module_id=module_id ) + if not quiz.is_exercise: + if paper.time_left() <= 0: + reason = 'Your time is up!' + return complete( + request, reason, paper.attempt_number, paper.question_paper.id, + course_id, module_id=module_id + ) + else: + quiz_type = 'Exercise' if question in paper.questions_answered.all(): notification = ( 'You have already attempted this question successfully' @@ -544,13 +610,17 @@ def show_question(request, question, paper, error_message=None, notification=Non context = { 'question': question, 'paper': paper, + 'quiz': quiz, 'error_message': error_message, 'test_cases': test_cases, 'files': files, 'notification': notification, 'last_attempt': question.snippet.encode('unicode-escape'), 'course': course, - 'module': module + 'module': module, + 'can_skip': can_skip, + 'delay_time': delay_time, + 'quiz_type': quiz_type } answers = paper.get_previous_answers(question) if answers: @@ -573,6 +643,10 @@ def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None, ) question = get_object_or_404(Question, pk=q_id) + if paper.question_paper.quiz.is_exercise: + paper.start_time = timezone.now() + paper.save() + if request.method == 'POST' and question.type == 'code': if not paper.answers.filter(question=question, correct=True).exists(): user_code = request.POST.get('answer') @@ -617,7 +691,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, msg = "Please enter an Integer Value" return show_question( request, current_question, paper, notification=msg, - course_id=course_id, module_id=module_id + course_id=course_id, module_id=module_id, + previous_question=current_question ) elif current_question.type == 'float': try: @@ -626,7 +701,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, msg = "Please enter a Float Value" return show_question(request, current_question, paper, notification=msg, - course_id=course_id, module_id=module_id) + course_id=course_id, module_id=module_id, + previous_question=current_question) elif current_question.type == 'string': user_answer = str(request.POST.get('answer')) @@ -640,7 +716,8 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, msg = "Please upload assignment file" return show_question( request, current_question, paper, notification=msg, - course_id=course_id, module_id=module_id + course_id=course_id, module_id=module_id, + previous_question=current_question ) for fname in assignment_filename: fname._name = fname._name.replace(" ","_") @@ -670,14 +747,16 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, paper.answers.add(new_answer) next_q = paper.add_completed_question(current_question.id) return show_question(request, next_q, paper, - course_id=course_id, module_id=module_id) + course_id=course_id, module_id=module_id, + previous_question=current_question) else: user_answer = request.POST.get('answer') if not user_answer: msg = "Please submit a valid answer." return show_question( request, current_question, paper, notification=msg, - course_id=course_id, module_id=module_id + course_id=course_id, module_id=module_id, + previous_question=current_question ) if current_question in paper.get_questions_answered()\ and current_question.type not in ['code', 'upload']: @@ -702,7 +781,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, user_answer, current_question, json_data, uid ) if current_question.type in ['code', 'upload']: - if paper.time_left() <= 0: + if paper.time_left() <= 0 and not paper.question_paper.quiz.is_exercise: url = 'http://localhost:%s' % SERVER_POOL_PORT result_details = get_result_from_code_server(url, uid, block=True) result = json.loads(result_details.get('result')) @@ -710,16 +789,19 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None, result) return show_question(request, next_question, paper, error_message, course_id=course_id, - module_id=module_id) + module_id=module_id, + previous_question=current_question) else: return JsonResponse(result) else: next_question, error_message, paper = _update_paper(request, uid, result) return show_question(request, next_question, paper, error_message, - course_id=course_id, module_id=module_id) + course_id=course_id, module_id=module_id, + previous_question=current_question) else: return show_question(request, current_question, paper, - course_id=course_id, module_id=module_id) + course_id=course_id, module_id=module_id, + previous_question=current_question) @csrf_exempt @@ -738,9 +820,12 @@ def get_result(request, uid, course_id, module_id): next_question, error_message, paper = _update_paper(request, uid, result ) + answer = Answer.objects.get(id=uid) + current_question = answer.question if result.get('success'): return show_question(request, next_question, paper, error_message, - course_id=course_id, module_id=module_id) + course_id=course_id, module_id=module_id, + previous_question=current_question) else: with open(template_path) as f: template_data = f.read() @@ -870,7 +955,7 @@ def enroll_request(request, course_id): user = request.user ci = RequestContext(request) course = get_object_or_404(Course, pk=course_id) - if not course.is_active_enrollment and course.hidden: + if not course.is_active_enrollment() and course.hidden: msg = ( 'Unable to add enrollments for this course, please contact your ' 'instructor/administrator.' @@ -943,7 +1028,7 @@ def enroll(request, course_id, user_id=None, was_rejected=False): raise Http404('You are not allowed to view this page') course = get_object_or_404(Course, pk=course_id) - if not course.is_active_enrollment: + if not course.is_active_enrollment(): msg = ( 'Enrollment for this course has been closed,' ' please contact your ' |