diff options
Diffstat (limited to 'yaksh')
-rw-r--r-- | yaksh/models.py | 94 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/dashboard.css | 9 | ||||
-rw-r--r-- | yaksh/templates/base.html | 6 | ||||
-rw-r--r-- | yaksh/templates/exam.html | 10 | ||||
-rw-r--r-- | yaksh/templates/user.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 48 | ||||
-rw-r--r-- | yaksh/templates/yaksh/intro.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/moderator_dashboard.html | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 38 | ||||
-rw-r--r-- | yaksh/templates/yaksh/user_data.html | 18 | ||||
-rw-r--r-- | yaksh/templates/yaksh/view_answerpaper.html | 29 | ||||
-rw-r--r-- | yaksh/test_models.py | 61 | ||||
-rw-r--r-- | yaksh/test_views.py | 393 | ||||
-rw-r--r-- | yaksh/urls.py | 4 | ||||
-rw-r--r-- | yaksh/views.py | 28 |
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([]) ) |