summaryrefslogtreecommitdiff
path: root/yaksh
diff options
context:
space:
mode:
Diffstat (limited to 'yaksh')
-rw-r--r--yaksh/models.py94
-rw-r--r--yaksh/static/yaksh/css/dashboard.css9
-rw-r--r--yaksh/templates/base.html6
-rw-r--r--yaksh/templates/exam.html10
-rw-r--r--yaksh/templates/user.html5
-rw-r--r--yaksh/templates/yaksh/grade_user.html48
-rw-r--r--yaksh/templates/yaksh/intro.html5
-rw-r--r--yaksh/templates/yaksh/moderator_dashboard.html5
-rw-r--r--yaksh/templates/yaksh/question.html38
-rw-r--r--yaksh/templates/yaksh/user_data.html18
-rw-r--r--yaksh/templates/yaksh/view_answerpaper.html29
-rw-r--r--yaksh/test_models.py61
-rw-r--r--yaksh/test_views.py393
-rw-r--r--yaksh/urls.py4
-rw-r--r--yaksh/views.py28
15 files changed, 675 insertions, 78 deletions
diff --git a/yaksh/models.py b/yaksh/models.py
index d9e07fd..787daa6 100644
--- a/yaksh/models.py
+++ b/yaksh/models.py
@@ -1,6 +1,7 @@
from __future__ import unicode_literals
from datetime import datetime, timedelta
import json
+import random
import ruamel.yaml
from ruamel.yaml.scalarstring import PreservedScalarString
from ruamel.yaml.comments import CommentedMap
@@ -350,7 +351,7 @@ class Question(models.Model):
tags = TaggableManager(blank=True)
# Snippet of code provided to the user.
- snippet = models.CharField(max_length=256, blank=True)
+ snippet = models.TextField(blank=True)
# user for particular question
user = models.ForeignKey(User, related_name="user")
@@ -851,7 +852,11 @@ class QuestionPaper(models.Model):
questions = self.get_ordered_questions()
for question_set in self.random_questions.all():
questions += question_set.get_random_questions()
- return questions
+ if self.shuffle_questions:
+ all_questions = self.get_shuffled_questions(questions)
+ else:
+ all_questions = questions
+ return all_questions
def make_answerpaper(self, user, ip, attempt_num):
"""Creates an answer paper for the user to attempt the quiz"""
@@ -872,10 +877,11 @@ class QuestionPaper(models.Model):
ans_paper.question_paper = self
ans_paper.save()
questions = self._get_questions_for_answerpaper()
- for question in questions:
- ans_paper.questions.add(question)
- for question in questions:
- ans_paper.questions_unanswered.add(question)
+ ans_paper.questions.add(*questions)
+ question_ids = [str(que.id) for que in questions]
+ ans_paper.questions_order = ",".join(question_ids)
+ ans_paper.save()
+ ans_paper.questions_unanswered.add(*questions)
except AnswerPaper.MultipleObjectsReturned:
ans_paper = AnswerPaper.objects.get(user=user,
attempt_number=attempt_num,
@@ -918,7 +924,7 @@ class QuestionPaper(models.Model):
def create_demo_quiz_ppr(self, demo_quiz, user):
question_paper = QuestionPaper.objects.create(quiz=demo_quiz,
total_marks=6.0,
- shuffle_questions=True
+ shuffle_questions=False
)
summaries = ['Roots of quadratic equation', 'Print Output',
'Adding decimals', 'For Loop over String',
@@ -941,9 +947,19 @@ class QuestionPaper(models.Model):
for que_id in que_order:
ques.append(self.fixed_questions.get(id=que_id))
else:
- ques = self.fixed_questions.all()
+ ques = list(self.fixed_questions.all())
return ques
+ def get_shuffled_questions(self, questions):
+ """Get shuffled questions if auto suffle is enabled"""
+ random.shuffle(questions)
+ return questions
+
+ def has_questions(self):
+ questions = self.get_ordered_questions() + \
+ list(self.random_questions.all())
+ return len(questions) > 0
+
def __str__(self):
return "Question Paper for " + self.quiz.description
@@ -1164,12 +1180,27 @@ class AnswerPaper(models.Model):
default='inprogress'
)
+ # set question order
+ questions_order = models.TextField(blank=True, default='')
+
objects = AnswerPaperManager()
def current_question(self):
"""Returns the current active question to display."""
- if self.questions_unanswered.all():
- return self.questions_unanswered.all()[0]
+ unanswered_questions = self.questions_unanswered.all()
+ if unanswered_questions.exists():
+ cur_question = self.get_current_question(unanswered_questions)
+ else:
+ cur_question = self.get_current_question(self.questions.all())
+ return cur_question
+
+ def get_current_question(self, questions):
+ if self.questions_order:
+ question_id = int(self.questions_order.split(',')[0])
+ question = questions.get(id=question_id)
+ else:
+ question = questions.first()
+ return question
def questions_left(self):
"""Returns the number of questions left."""
@@ -1194,19 +1225,30 @@ class AnswerPaper(models.Model):
Skips the current question and returns the next sequentially
available question.
"""
- all_questions = self.questions.all()
- unanswered_questions = self.questions_unanswered.all()
- questions = list(all_questions.values_list('id', flat=True))
- if len(questions) == 0:
- return None
- if unanswered_questions.count() == 0:
+ if self.questions_order:
+ all_questions = [int(q_id)
+ for q_id in self.questions_order.split(',')]
+ else:
+ all_questions = list(self.questions.all().values_list(
+ 'id', flat=True))
+ if len(all_questions) == 0:
return None
try:
- index = questions.index(int(question_id))
- next_id = questions[index+1]
+ index = all_questions.index(int(question_id))
+ next_id = all_questions[index+1]
except (ValueError, IndexError):
- next_id = questions[0]
- return all_questions.get(id=next_id)
+ next_id = all_questions[0]
+ return self.questions.get(id=next_id)
+
+ def get_all_ordered_questions(self):
+ """Get all questions in a specific order for answerpaper"""
+ if self.questions_order:
+ que_ids = [int(q_id) for q_id in self.questions_order.split(',')]
+ questions = [self.questions.get(id=que_id)
+ for que_id in que_ids]
+ else:
+ questions = list(self.questions.all())
+ return questions
def time_left(self):
"""Return the time remaining for the user in seconds."""
@@ -1284,6 +1326,9 @@ class AnswerPaper(models.Model):
}]
return q_a
+ def get_latest_answer(self, question_id):
+ return self.answers.filter(question=question_id).order_by("id").last()
+
def get_questions(self):
return self.questions.filter(active=True)
@@ -1303,8 +1348,7 @@ class AnswerPaper(models.Model):
return self.time_left() > 0
def get_previous_answers(self, question):
- if question.type == 'code':
- return self.answers.filter(question=question).order_by('-id')
+ return self.answers.filter(question=question).order_by('-id')
def validate_answer(self, user_answer, question, json_data=None, uid=None):
"""
@@ -1320,15 +1364,15 @@ class AnswerPaper(models.Model):
'weight': 0.0}
if user_answer is not None:
if question.type == 'mcq':
- expected_answer = question.get_test_case(correct=True).options
- if user_answer.strip() == expected_answer.strip():
+ expected_answer = question.get_test_case(correct=True).id
+ if user_answer.strip() == str(expected_answer).strip():
result['success'] = True
result['error'] = ['Correct answer']
elif question.type == 'mcc':
expected_answers = []
for opt in question.get_test_cases(correct=True):
- expected_answers.append(opt.options)
+ expected_answers.append(str(opt.id))
if set(user_answer) == set(expected_answers):
result['success'] = True
result['error'] = ['Correct answer']
diff --git a/yaksh/static/yaksh/css/dashboard.css b/yaksh/static/yaksh/css/dashboard.css
index 28040c4..20a0339 100644
--- a/yaksh/static/yaksh/css/dashboard.css
+++ b/yaksh/static/yaksh/css/dashboard.css
@@ -87,3 +87,12 @@ body {
.sidebar-right {
float: right;
}
+
+.logged_user_info {
+ height: 70px;
+ padding:0 5%;
+ position:absolute;
+ bottom:0;
+ width: 100%;
+}
+
diff --git a/yaksh/templates/base.html b/yaksh/templates/base.html
index cbe396f..e7cc15c 100644
--- a/yaksh/templates/base.html
+++ b/yaksh/templates/base.html
@@ -52,7 +52,11 @@
{% block content %}
{% endblock %}
</div>
- <footer class="footer">
+ <footer class="footer" id="footer_div">
+ <div class="logged_user_info" align="center">
+ {% block info %}
+ {% endblock %}
+ </div>
<div class="container">
<p align="center">Developed by FOSSEE group, IIT Bombay</p>
</div>
diff --git a/yaksh/templates/exam.html b/yaksh/templates/exam.html
index 45b85f0..9596c1c 100644
--- a/yaksh/templates/exam.html
+++ b/yaksh/templates/exam.html
@@ -18,7 +18,13 @@
<form id="logout" action="{{URL_ROOT}}/exam/quit/{{ paper.attempt_number }}/{{ paper.question_paper.id }}/" method="post" class="pull-right">
{% csrf_token %}
<ul class="nav navbar-nav navbar">
- <li style="padding: 10px"><button class="btn btn-danger btn-sm" type="submit" name="quit">Quit Exam <span class="glyphicon glyphicon-off"></span></button></li>
+ <li style="padding: 10px"><button class="btn btn-danger btn-sm" type="submit" name="quit">
+ {% if paper.questions_unanswered.all %}
+ Quit Exam
+ {% else %}
+ Finish Exam
+ {% endif %}
+ <span class="glyphicon glyphicon-off"></span></button></li>
</ul>
</form>
<div class="time-div" id="time_left"></div>
@@ -40,7 +46,7 @@
<div class="col-sm-3 col-md-2 sidebar">
<p> Question Navigator </p>
<ul class="pagination pagination-sm">
- {% for qid in paper.questions.all %}
+ {% for qid in paper.get_all_ordered_questions %}
{%if paper.question_paper.quiz.allow_skip %}
{% if qid in paper.get_questions_unanswered %}
{% if qid.id == question.id %}
diff --git a/yaksh/templates/user.html b/yaksh/templates/user.html
index b068fae..83aea13 100644
--- a/yaksh/templates/user.html
+++ b/yaksh/templates/user.html
@@ -40,3 +40,8 @@
</div>
</div>
{% endblock %}
+{% if user %}
+ {% block info %}
+ <h5>{{user.get_full_name|title}}({{user.profile.roll_number}}) Logged in as {{user.username}}</h5>
+ {% endblock %}
+{% endif %}
diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html
index 9cdfb1a..37bc788 100644
--- a/yaksh/templates/yaksh/grade_user.html
+++ b/yaksh/templates/yaksh/grade_user.html
@@ -9,6 +9,14 @@
{% block script %}
<script src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-MML-AM_CHTML"></script>
+<script src="{{ URL_ROOT }}/static/yaksh/js/jquery.tablesorter.min.js"></script>
+<script type="text/javascript">
+$(document).ready(function()
+{
+ $("#marks_table").tablesorter({sortList: [[2,1]]});
+});
+
+</script>
{% endblock script %}
{% if course_details %}
@@ -108,17 +116,25 @@ Status : <b style="color: green;"> Passed </b><br/>
{% if paper.answers.count %}
<h4> Report </h4><br>
-<table class="table table-bordered">
+<table class="tablesorter table table-striped table-bordered" id ='marks_table'>
+ <thead>
+ <tr>
+ <th>Question Id</th>
<th>Questions</th>
<th>Marks Obtained</th>
+ </tr>
+ </thead>
+ <tbody>
{% for question, answers in paper.get_question_answers.items %}
{% with answers|last as answer %}
<tr>
- <td>{{ question.id }}</td>
+ <td>{{question.id}}</td>
+ <td><a href="#question_{{question.id}}">{{ question.summary }}</a></td>
<td>{{ answer.answer.marks }}</td>
</tr>
{% endwith %}
{% endfor %}
+ </tbody>
</table>
@@ -135,7 +151,7 @@ Status : <b style="color: green;"> Passed </b><br/>
{% for question, answers in paper.get_question_answers.items %}
<div class = "well well-sm">
<div class="panel panel-info">
- <div class="panel-heading">
+ <div class="panel-heading" id="question_{{question.id}}">
<strong> Details: {{forloop.counter}}. {{ question.summary }}
<span class="marks pull-right"> Mark(s): {{ question.points }} </span>
</strong>
@@ -220,7 +236,7 @@ Status : <b style="color: green;"> Passed </b><br/>
<col width="40%">
<col width="40%">
<col width="10%">
- <tr class="info">
+ <tr>
<th><center>Line No.</center></th>
<th><center>Expected Output</center></th>
<th><center>User output</center></th>
@@ -240,7 +256,7 @@ Status : <b style="color: green;"> Passed </b><br/>
</table>
<table width="100%" class='table table-bordered'>
<col width="10">
- <tr class = "danger">
+ <tr>
<td><b>Error:</b></td>
<td>{{error.error_msg}}</td>
</tr>
@@ -252,12 +268,28 @@ Status : <b style="color: green;"> Passed </b><br/>
</div>
<div class="panel-body">
- {% if question.type != "code" %}
+ {% if question.type == "code" %}
+ <pre><code>{{ ans.answer.answer.strip|safe }}</code></pre>
+ {% elif question.type == "mcc"%}
<div class="well well-sm">
- {{ ans.answer.answer.strip|safe }}
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" in ans.answer.answer.strip|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {% elif question.type == "mcq"%}
+ <div class="well well-sm">
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" == ans.answer.answer.strip|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
</div>
{% else %}
- <pre><code>{{ ans.answer.answer.strip|safe }}</code></pre>
+ <div class="well well-sm">
+ {{ ans.answer.answer.strip|safe }}
+ </div>
{% endif %}
</div>
</div>
diff --git a/yaksh/templates/yaksh/intro.html b/yaksh/templates/yaksh/intro.html
index 29723fa..3b9ba82 100644
--- a/yaksh/templates/yaksh/intro.html
+++ b/yaksh/templates/yaksh/intro.html
@@ -43,3 +43,8 @@
</div>
</div>
{% endblock content %}
+{% if user %}
+ {% block info %}
+ <h5>{{user.get_full_name|title}}({{user.profile.roll_number}}) Logged in as {{user.username}}</h5>
+ {% endblock %}
+{% endif %} \ No newline at end of file
diff --git a/yaksh/templates/yaksh/moderator_dashboard.html b/yaksh/templates/yaksh/moderator_dashboard.html
index c61675d..25bd580 100644
--- a/yaksh/templates/yaksh/moderator_dashboard.html
+++ b/yaksh/templates/yaksh/moderator_dashboard.html
@@ -79,3 +79,8 @@
</div>
{% endif %}
{% endblock %}
+{% if user %}
+ {% block info %}
+ <h5>{{user.get_full_name|title}}({{user.profile.roll_number}}) Logged in as {{user.username}}</h5>
+ {% endblock %}
+{% endif %}
diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html
index 3a3066c..1e1f38f 100644
--- a/yaksh/templates/yaksh/question.html
+++ b/yaksh/templates/yaksh/question.html
@@ -53,7 +53,7 @@ function updateClock(){
var ss = ('0' + t.seconds).slice(-2);
if(t.total<0){
-
+
document.forms["code"].submit();
clearInterval(timeinterval);
return null;
@@ -77,10 +77,7 @@ function validate(){
$("#upload_alert").modal("show");
return false;
}
- else
- {
- send_request();
- }
+ return true;
}
function call_skip(url)
@@ -91,6 +88,7 @@ function call_skip(url)
}
init_val = '{{ last_attempt|escape_quotes|safe }}';
lang = "{{ question.language }}"
+
</script>
{% endblock script %}
@@ -155,32 +153,44 @@ lang = "{{ question.language }}"
<div class="panel-body">
{% if question.type == "mcq" %}
{% for test_case in test_cases %}
- <input name="answer" type="radio" value="{{ test_case.options }}" />{{ test_case.options|safe }} <br/>
+ {% if last_attempt and last_attempt|safe == test_case.id|safe %}
+ <input name="answer" type="radio" value="{{ test_case.id }}" checked />
+ {{ test_case.options|safe }} <br/>
+ {% else %}
+ <input name="answer" type="radio" value="{{ test_case.id }}" />
+ {{ test_case.options|safe }} <br/>
+ {% endif %}
{% endfor %}
{% endif %}
{% if question.type == "integer" %}
Enter Integer:<br/>
- <input name="answer" type="number" id="integer" />
+ <input name="answer" type="number" id="integer" value={{ last_attempt|safe }} />
<br/><br/>
{% endif %}
{% if question.type == "string" %}
Enter Text:<br/>
- <textarea name="answer" id="string"></textarea>
+ <textarea name="answer" id="string">{{ last_attempt|safe }}</textarea>
<br/><br/>
{% endif %}
{% if question.type == "float" %}
Enter Decimal Value :<br/>
- <input name="answer" type="number" step="any" id="float" />
+ <input name="answer" type="number" step="any" id="float" value={{ last_attempt|safe }} />
<br/><br/>
{% endif %}
{% if question.type == "mcc" %}
{% for test_case in test_cases %}
- <input name="answer" type="checkbox" value="{{ test_case.options }}"> {{ test_case.options|safe }}
+ {% if last_attempt and test_case.id|safe in last_attempt|safe %}
+ <input name="answer" type="checkbox" value="{{ test_case.id }}" checked/> {{ test_case.options }}
<br>
+ {% else %}
+ <input name="answer" type="checkbox" value="{{ test_case.id }}">
+ {{ test_case.options}}
+ <br/>
+ {% endif %}
{% endfor %}
{% endif %}
{% if question.type == "upload" %}
@@ -241,6 +251,8 @@ lang = "{{ question.language }}"
</div>
</div>
{% endblock main %}
-
-
-
+{% if user %}
+ {% block info %}
+ <h5>{{user.get_full_name|title}}({{user.profile.roll_number}}) Logged in as {{user.username}}</h5>
+ {% endblock %}
+{% endif %}
diff --git a/yaksh/templates/yaksh/user_data.html b/yaksh/templates/yaksh/user_data.html
index a8adc22..31a023d 100644
--- a/yaksh/templates/yaksh/user_data.html
+++ b/yaksh/templates/yaksh/user_data.html
@@ -104,11 +104,29 @@ User IP address: {{ paper.user_ip }}
</div>
<div class="panel-body">
<h5><u>Student answer:</u></h5>
+ {% if question.type == "mcc"%}
+ <div class="well well-sm">
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" in answers.0.answer|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {% elif question.type == "mcq"%}
+ <div class="well well-sm">
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" == answers.0.answer|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {%else%}
<div class="well well-sm">
{{ answers.0.answer|safe }}
</div>
</div>
</div>
+ {% endif %}
{% else %}
<h5>Student answer: </h5>
{% for answer in answers %}
diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html
index 9edff5a..850d789 100644
--- a/yaksh/templates/yaksh/view_answerpaper.html
+++ b/yaksh/templates/yaksh/view_answerpaper.html
@@ -82,16 +82,35 @@
Autocheck: {{ answers.0.error_list.0 }}
</div>
<div class="panel-body">
- <h5><u>Student answer:</u></h5>
- <div class="well well-sm">
- {{ answers.0.answer|safe }}
- {% if question.type == "upload" and has_user_assignment %}
+ {% if question.type == "mcc"%}
+ <div class="well well-sm">
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" in answers.0.answer|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {% elif question.type == "mcq"%}
+ <div class="well well-sm">
+ {% for testcases in question.get_test_cases %}
+ {%if testcases.id|stringformat:"i" == answers.0.answer|safe %}
+ <li>{{ testcases.options.strip|safe }}</li>
+ {% endif %}
+ {% endfor %}
+ </div>
+ {% elif question.type == "upload" and has_user_assignment %}
<a href="{{URL_ROOT}}/exam/download/user_assignment/{{question.id}}/{{data.user.id}}/{{paper.question_paper.quiz.id}}">
+ <div class="well well-sm">
<div class="panel">
Assignment File for {{ data.user.get_full_name.title }}
</div></a>
- {% endif %}
</div>
+ {% else %}
+ <h5><u>Student answer:</u></h5>
+ <div class="well well-sm">
+ {{ answers.0.answer|safe }}
+ </div>
+ {% endif %}
</div>
</div>
{% else %}
diff --git a/yaksh/test_models.py b/yaksh/test_models.py
index bc7f114..00506cd 100644
--- a/yaksh/test_models.py
+++ b/yaksh/test_models.py
@@ -502,6 +502,7 @@ class AnswerPaperTestCases(unittest.TestCase):
def setUpClass(self):
self.ip = '101.0.0.1'
self.user = User.objects.get(username='demo_user')
+ self.user2 = User.objects.get(username='demo_user2')
self.profile = self.user.profile
self.quiz = Quiz.objects.get(description='demo quiz 1')
self.question_paper = QuestionPaper(quiz=self.quiz, total_marks=3)
@@ -524,6 +525,7 @@ class AnswerPaperTestCases(unittest.TestCase):
question_paper=self.question_paper,
user=self.user
)
+ self.question_paper.fixed_questions.add(*self.questions)
already_attempted = self.attempted_papers.count()
self.answerpaper.attempt_number = already_attempted + 1
self.answerpaper.save()
@@ -547,6 +549,12 @@ class AnswerPaperTestCases(unittest.TestCase):
self.answerpaper.answers.add(self.answer_right)
self.answerpaper.answers.add(self.answer_wrong)
+ self.answer1 = Answer.objects.create(
+ question=self.question1,
+ answer="answer1", correct=False, error=json.dumps([])
+ )
+ self.answerpaper.answers.add(self.answer1)
+
self.question1.language = 'python'
self.question1.test_case_type = 'standardtestcase'
self.question1.summary = "Question1"
@@ -584,9 +592,31 @@ class AnswerPaperTestCases(unittest.TestCase):
)
self.mcc_based_testcase.save()
+ # Setup quiz where questions are shuffled
+ # Create Quiz and Question Paper
+ self.quiz2 = Quiz.objects.get(description="demo quiz 2")
+ self.question_paper2 = QuestionPaper(
+ quiz=self.quiz2, total_marks=3, shuffle_questions=True)
+ self.question_paper2.save()
+ summary_list = ['Q%d' % (i) for i in range(1, 21)]
+ self.que_list = Question.objects.filter(summary__in=summary_list)
+ self.question_paper2.fixed_questions.add(*self.que_list)
+
+ # Create AnswerPaper for user1 and user2
+ self.user1_answerpaper = self.question_paper2.make_answerpaper(
+ self.user, self.ip, 1
+ )
+ self.user2_answerpaper = self.question_paper2.make_answerpaper(
+ self.user2, self.ip, 1
+ )
+
+ self.user2_answerpaper2 = self.question_paper.make_answerpaper(
+ self.user2, self.ip, 1
+ )
+
def test_validate_and_regrade_mcc_correct_answer(self):
# Given
- mcc_answer = ['a']
+ mcc_answer = [str(self.mcc_based_testcase.id)]
self.answer = Answer(question=self.question3,
answer=mcc_answer,
)
@@ -623,7 +653,7 @@ class AnswerPaperTestCases(unittest.TestCase):
def test_validate_and_regrade_mcq_correct_answer(self):
# Given
- mcq_answer = 'a'
+ mcq_answer = str(self.mcq_based_testcase.id)
self.answer = Answer(question=self.question2,
answer=mcq_answer,
)
@@ -657,7 +687,6 @@ class AnswerPaperTestCases(unittest.TestCase):
self.assertEqual(self.answer.marks, 0)
self.assertFalse(self.answer.correct)
-
def test_mcq_incorrect_answer(self):
# Given
mcq_answer = 'b'
@@ -794,13 +823,14 @@ class AnswerPaperTestCases(unittest.TestCase):
# Then
self.assertEqual(self.answerpaper.questions_left(), 0)
- self.assertTrue(current_question is None)
+ self.assertTrue(current_question == self.answerpaper.questions.all()[0])
# When
next_question_id = self.answerpaper.next_question(current_question_id)
# Then
- self.assertTrue(next_question_id is None)
+ all_questions = self.questions.all()
+ self.assertTrue(next_question_id == all_questions[0])
def test_update_marks(self):
""" Test update_marks method of AnswerPaper"""
@@ -834,17 +864,32 @@ class AnswerPaperTestCases(unittest.TestCase):
def test_get_previous_answers(self):
answers = self.answerpaper.get_previous_answers(self.questions[0])
- self.assertEqual(answers.count(), 1)
+ self.assertEqual(answers.count(), 2)
self.assertTrue(answers[0], self.answer_right)
answers = self.answerpaper.get_previous_answers(self.questions[1])
self.assertEqual(answers.count(), 1)
self.assertTrue(answers[0], self.answer_wrong)
- def test_set_marks (self):
+ def test_set_marks(self):
self.answer_wrong.set_marks(0.5)
self.assertEqual(self.answer_wrong.marks, 0.5)
self.answer_wrong.set_marks(10.0)
- self.assertEqual(self.answer_wrong.marks,1.0)
+ self.assertEqual(self.answer_wrong.marks, 1.0)
+
+ def test_get_latest_answer(self):
+ latest_answer = self.answerpaper.get_latest_answer(self.question1.id)
+ self.assertEqual(latest_answer.id, self.answer1.id)
+ self.assertEqual(latest_answer.answer, "answer1")
+
+ def test_shuffle_questions(self):
+ ques_set_1 = self.user1_answerpaper.get_all_ordered_questions()
+ ques_set_2 = self.user2_answerpaper.get_all_ordered_questions()
+ self.assertFalse(ques_set_1 == ques_set_2)
+
+ def test_validate_current_question(self):
+ self.user2_answerpaper2.questions_unanswered.remove(*self.questions)
+ self.assertEqual(self.user2_answerpaper2.current_question(),
+ self.question1)
###############################################################################
diff --git a/yaksh/test_views.py b/yaksh/test_views.py
index 064c39d..dc06126 100644
--- a/yaksh/test_views.py
+++ b/yaksh/test_views.py
@@ -22,7 +22,8 @@ from django.core.files.uploadedfile import SimpleUploadedFile
from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\
QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\
- AssignmentUpload, FileUpload
+ AssignmentUpload, FileUpload, McqTestCase, IntegerTestCase, StringTestCase,\
+ FloatTestCase
from yaksh.decorators import user_has_profile
@@ -2091,13 +2092,13 @@ class TestViewAnswerPaper(TestCase):
self.quiz = Quiz.objects.create(time_between_attempts=0, course=self.course,
description='demo quiz', language='Python')
-
+ self.user3 = User.objects.get(username="demo_user3")
self.question_paper = QuestionPaper.objects.create(quiz=self.quiz,
total_marks=1.0)
self.question_paper.fixed_questions.add(self.question)
self.question_paper.save()
- self.ans_paper = AnswerPaper.objects.create(user_id=3,
+ self.ans_paper = AnswerPaper.objects.create(user=self.user3,
attempt_number=1, question_paper=self.question_paper,
start_time=timezone.now(), user_ip='101.0.0.1',
end_time=timezone.now()+timezone.timedelta(minutes=20))
@@ -2381,7 +2382,7 @@ class TestGrader(TestCase):
self.question_paper.fixed_questions.add(self.question)
self.question_paper.save()
- self.answerpaper = AnswerPaper.objects.create(user_id=3,
+ self.answerpaper = AnswerPaper.objects.create(user=self.user2,
attempt_number=1, question_paper=self.question_paper,
start_time=timezone.now(), user_ip='101.0.0.1',
end_time=timezone.now()+timezone.timedelta(minutes=20))
@@ -3378,3 +3379,387 @@ class TestShowStatistics(TestCase):
[1, 1])
self.assertEqual(response.context['attempts'][0], 1)
self.assertEqual(response.context['total'], 1)
+
+
+class TestQuestionPaper(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(
+ username='demo_user',
+ password=self.user_plaintext_pass,
+ first_name='first_name',
+ last_name='last_name',
+ email='demo@test.com'
+ )
+
+ Profile.objects.create(
+ user=self.user,
+ roll_number=10,
+ institute='IIT',
+ department='Chemical',
+ position='Moderator',
+ timezone='UTC'
+ )
+
+ # Add to moderator group
+ self.mod_group.user_set.add(self.user)
+
+ self.course = Course.objects.create(
+ 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,
+ language='Python', course=self.course
+ )
+
+ # Mcq Question
+ self.question_mcq = Question.objects.create(
+ summary="Test_mcq_question", description="Test MCQ",
+ points=1.0, language="python", type="mcq", user=self.user
+ )
+ self.mcq_based_testcase = McqTestCase(
+ options="a",
+ question=self.question_mcq,
+ correct=True,
+ type='mcqtestcase'
+ )
+ 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",
+ points=1.0, language="python", type="mcq", user=self.user
+ )
+ self.mcc_based_testcase = McqTestCase(
+ options="a",
+ question=self.question_mcc,
+ correct=True,
+ type='mcqtestcase'
+ )
+ 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",
+ points=1.0, language="python", type="integer", user=self.user
+ )
+ self.int_based_testcase = IntegerTestCase(
+ correct=1,
+ question=self.question_int,
+ type='integertestcase'
+ )
+ 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",
+ points=1.0, language="python", type="string", user=self.user
+ )
+ self.str_based_testcase = StringTestCase(
+ correct="abc",
+ string_check="lower",
+ question=self.question_str,
+ type='stringtestcase'
+ )
+ self.str_based_testcase.save()
+
+ # Float Question
+ self.question_float = Question.objects.create(
+ summary="Test_mcc_question", description="Test MCC",
+ points=1.0, language="python", type="float", user=self.user
+ )
+ self.float_based_testcase = FloatTestCase(
+ correct=2.0,
+ error_margin=0,
+ question=self.question_float,
+ type='floattestcase'
+ )
+ 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.question_paper = QuestionPaper.objects.create(
+ quiz=self.quiz,
+ total_marks=5.0, fixed_question_order=ordered_questions
+ )
+ self.question_paper.fixed_questions.add(*questions_list)
+ self.answerpaper = AnswerPaper.objects.create(
+ user=self.user, question_paper=self.question_paper,
+ attempt_number=1,
+ start_time=datetime(2014, 10, 9, 10, 8, 15, 0, tzone),
+ end_time=datetime(2014, 10, 9, 10, 15, 15, 0, tzone),
+ user_ip="127.0.0.1", status="inprogress", passed=False,
+ percent=0, marks_obtained=0
+ )
+ self.answerpaper.questions.add(*questions_list)
+
+ def tearDown(self):
+ self.client.logout()
+ self.user.delete()
+ self.quiz.delete()
+ self.course.delete()
+ self.answerpaper.delete()
+ self.question_mcq.delete()
+ self.question_mcc.delete()
+ self.question_int.delete()
+ self.question_paper.delete()
+
+ def test_mcq_attempt_right_after_wrong(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same mcq question with wrong answer and then right
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Wrong Answer
+ wrong_user_answer = "25"
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+ # Given Right Answer
+ right_user_answer = str(self.mcq_based_testcase.id)
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ def test_mcq_question_attempt_wrong_after_right(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same mcq question with right answer and then wrong
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Right Answer
+ right_user_answer = str(self.mcq_based_testcase.id)
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ # Given Wrong Answer
+ wrong_user_answer = "25"
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcq.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+ def test_mcc_question_attempt_wrong_after_right(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same mcc question with right answer and then wrong
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Right Answer
+ right_user_answer = str(self.mcc_based_testcase.id)
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcc.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ # Given Wrong Answer
+ wrong_user_answer = "b"
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_mcc.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+ def test_integer_question_attempt_wrong_after_right(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same integer question with right answer and then wrong
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Right Answer
+ right_user_answer = 1
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_int.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ # Given Wrong Answer
+ wrong_user_answer = -1
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_int.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+ def test_string_question_attempt_wrong_after_right(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same string question with right answer and then wrong
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Right Answer
+ right_user_answer = "abc"
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_str.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ # Given Wrong Answer
+ wrong_user_answer = "c"
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_str.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
+
+ def test_float_question_attempt_wrong_after_right(self):
+ """ Case:- Check if answerpaper and answer marks are updated after
+ attempting same float question with right answer and then wrong
+ answer
+ """
+ self.client.login(
+ username=self.user.username,
+ password=self.user_plaintext_pass
+ )
+
+ # Given Right Answer
+ right_user_answer = 2.0
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_float.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": right_user_answer}
+ )
+
+ # Then
+ updated_answerpaper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(updated_answerpaper.marks_obtained, 1)
+
+ # Given Wrong Answer
+ wrong_user_answer = -1
+
+ # When
+ self.client.post(
+ reverse('yaksh:check',
+ kwargs={"q_id": self.question_float.id, "attempt_num": 1,
+ "questionpaper_id": self.question_paper.id}),
+ data={"answer": wrong_user_answer}
+ )
+
+ # Then
+ wrong_answer_paper = AnswerPaper.objects.get(id=self.answerpaper.id)
+ self.assertEqual(wrong_answer_paper.marks_obtained, 0)
diff --git a/yaksh/urls.py b/yaksh/urls.py
index c236640..2e25bee 100644
--- a/yaksh/urls.py
+++ b/yaksh/urls.py
@@ -19,10 +19,10 @@ urlpatterns = [
url(r'^complete/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
views.complete),
url(r'^register/$', views.user_register, name="register"),
- url(r'^(?P<q_id>\d+)/check/$', views.check),
+ url(r'^(?P<q_id>\d+)/check/$', views.check, name="check"),
url(r'^get_result/(?P<uid>\d+)/$', views.get_result),
url(r'^(?P<q_id>\d+)/check/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',\
- views.check),
+ views.check, name="check"),
url(r'^(?P<q_id>\d+)/skip/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',
views.skip),
url(r'^(?P<q_id>\d+)/skip/(?P<next_q>\d+)/(?P<attempt_num>\d+)/(?P<questionpaper_id>\d+)/$',
diff --git a/yaksh/views.py b/yaksh/views.py
index e6da2fc..b4cb844 100644
--- a/yaksh/views.py
+++ b/yaksh/views.py
@@ -392,8 +392,7 @@ def start(request, questionpaper_id=None, attempt_num=None):
msg = 'Quiz not found, please contact your '\
'instructor/administrator.'
return complete(request, msg, attempt_num, questionpaper_id=None)
- if not quest_paper.get_ordered_questions() and not \
- quest_paper.random_questions.all():
+ if not quest_paper.has_questions():
msg = 'Quiz does not have Questions, please contact your '\
'instructor/administrator.'
return complete(request, msg, attempt_num, questionpaper_id=None)
@@ -506,12 +505,15 @@ def skip(request, q_id, next_q=None, attempt_num=None, questionpaper_id=None):
question = get_object_or_404(Question, pk=q_id)
if request.method == 'POST' and question.type == 'code':
- user_code = request.POST.get('answer')
- new_answer = Answer(question=question, answer=user_code,
- correct=False, skipped=True,
- error=json.dumps([]))
- new_answer.save()
- paper.answers.add(new_answer)
+ if not paper.answers.filter(question=question, correct=True).exists():
+ user_code = request.POST.get('answer')
+ new_answer = Answer(
+ question=question, answer=user_code,
+ correct=False, skipped=True,
+ error=json.dumps([])
+ )
+ new_answer.save()
+ paper.answers.add(new_answer)
if next_q is not None:
next_q = get_object_or_404(Question, pk=next_q)
else:
@@ -594,11 +596,17 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None):
else:
user_answer = request.POST.get('answer')
if not user_answer:
- msg = ["Please submit a valid option or code"]
+ msg = "Please submit a valid answer."
return show_question(
request, current_question, paper, notification=msg
)
- new_answer = Answer(
+ if current_question in paper.get_questions_answered()\
+ and current_question.type not in ['code', 'upload']:
+ new_answer = paper.get_latest_answer(current_question.id)
+ new_answer.answer = user_answer
+ new_answer.correct = False
+ else:
+ new_answer = Answer(
question=current_question, answer=user_answer,
correct=False, error=json.dumps([])
)