summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
authorPrabhu Ramachandran2018-01-03 22:23:19 +0530
committerGitHub2018-01-03 22:23:19 +0530
commitfeb295b4107a95621e9430f5c7042cfde4674cc0 (patch)
tree098c7cdc1e97d5e7bd859e35107a4733e800a586 /yaksh
parente566d54239efcb46f253e324b7295a676378f656 (diff)
parent4310d5905a9cc702198e42830c1b670957cd7360 (diff)
downloadonline_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.py8
-rw-r--r--yaksh/models.py22
-rw-r--r--yaksh/static/yaksh/js/add_question.js4
-rw-r--r--yaksh/static/yaksh/js/requesthandler.js8
-rw-r--r--yaksh/templates/exam.html4
-rw-r--r--yaksh/templates/yaksh/add_exercise.html30
-rw-r--r--yaksh/templates/yaksh/add_question.html3
-rw-r--r--yaksh/templates/yaksh/course_modules.html16
-rw-r--r--yaksh/templates/yaksh/courses.html28
-rw-r--r--yaksh/templates/yaksh/question.html47
-rw-r--r--yaksh/templates/yaksh/quit.html10
-rw-r--r--yaksh/templates/yaksh/view_answerpaper.html10
-rw-r--r--yaksh/test_views.py100
-rw-r--r--yaksh/urls.py5
-rw-r--r--yaksh/views.py137
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>&nbsp;&nbsp;
{% 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>&nbsp;&nbsp;
{% 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 '