diff options
-rw-r--r-- | yaksh/models.py | 6 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 22 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 32 | ||||
-rw-r--r-- | yaksh/test_models.py | 17 | ||||
-rw-r--r-- | yaksh/test_views.py | 387 | ||||
-rw-r--r-- | yaksh/urls.py | 4 | ||||
-rw-r--r-- | yaksh/views.py | 25 |
7 files changed, 461 insertions, 32 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index d9e07fd..68bde48 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -1284,6 +1284,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 +1306,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): """ diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index 9cdfb1a..2038210 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> diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index 3a3066c..eb6eb3d 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) @@ -155,32 +152,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.options|safe %} + <input name="answer" type="radio" value="{{ test_case.options }}" checked /> + {{ test_case.options|safe }} <br/> + {% else %} + <input name="answer" type="radio" value="{{ test_case.options }}" /> + {{ 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.options|safe in last_attempt|safe %} + <input name="answer" type="checkbox" value="{{ test_case.options }}" checked/> {{ test_case.options }} <br> + {% else %} + <input name="answer" type="checkbox" value="{{ test_case.options }}"> + {{ test_case.options}} + <br/> + {% endif %} {% endfor %} {% endif %} {% if question.type == "upload" %} @@ -241,6 +250,3 @@ lang = "{{ question.language }}" </div> </div> {% endblock main %} - - - diff --git a/yaksh/test_models.py b/yaksh/test_models.py index bc7f114..fd31ca2 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -547,6 +547,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" @@ -834,17 +840,22 @@ 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") ############################################################################### diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 064c39d..5ef97d6 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 @@ -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 = "b" + + # 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 = "a" + + # 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 = "a" + + # 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 = "b" + + # 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 = "a" + + # 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..6abfa2b 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -506,12 +506,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 +597,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([]) ) |