diff options
-rw-r--r-- | yaksh/documentation/images/float_testcase.jpg | bin | 0 -> 81100 bytes | |||
-rw-r--r-- | yaksh/documentation/images/integer_testcase.jpg | bin | 0 -> 82479 bytes | |||
-rw-r--r-- | yaksh/documentation/images/string_testcase.jpg | bin | 0 -> 84962 bytes | |||
-rw-r--r-- | yaksh/documentation/moderator_docs/creating_question.rst | 37 | ||||
-rw-r--r-- | yaksh/evaluator_tests/test_simple_question_types.py | 304 | ||||
-rw-r--r-- | yaksh/forms.py | 8 | ||||
-rw-r--r-- | yaksh/migrations/0003_auto_20170321_0917.py | 52 | ||||
-rw-r--r-- | yaksh/models.py | 83 | ||||
-rw-r--r-- | yaksh/static/yaksh/css/question.css | 5 | ||||
-rw-r--r-- | yaksh/templates/yaksh/add_question.html | 3 | ||||
-rw-r--r-- | yaksh/templates/yaksh/grade_user.html | 10 | ||||
-rw-r--r-- | yaksh/templates/yaksh/question.html | 38 | ||||
-rw-r--r-- | yaksh/templates/yaksh/user_data.html | 8 | ||||
-rw-r--r-- | yaksh/templates/yaksh/view_answerpaper.html | 7 | ||||
-rw-r--r-- | yaksh/test_models.py | 128 | ||||
-rw-r--r-- | yaksh/views.py | 32 |
16 files changed, 650 insertions, 65 deletions
diff --git a/yaksh/documentation/images/float_testcase.jpg b/yaksh/documentation/images/float_testcase.jpg Binary files differnew file mode 100644 index 0000000..2b6827c --- /dev/null +++ b/yaksh/documentation/images/float_testcase.jpg diff --git a/yaksh/documentation/images/integer_testcase.jpg b/yaksh/documentation/images/integer_testcase.jpg Binary files differnew file mode 100644 index 0000000..ca70a41 --- /dev/null +++ b/yaksh/documentation/images/integer_testcase.jpg diff --git a/yaksh/documentation/images/string_testcase.jpg b/yaksh/documentation/images/string_testcase.jpg Binary files differnew file mode 100644 index 0000000..7286eff --- /dev/null +++ b/yaksh/documentation/images/string_testcase.jpg diff --git a/yaksh/documentation/moderator_docs/creating_question.rst b/yaksh/documentation/moderator_docs/creating_question.rst index 94bb95c..69bb635 100644 --- a/yaksh/documentation/moderator_docs/creating_question.rst +++ b/yaksh/documentation/moderator_docs/creating_question.rst @@ -251,6 +251,43 @@ How to write Test cases .. image:: ../images/hook_testcase.jpg :width: 80% + * **Create Integer Based Test Case** + + Select **Answer in Integer** from Type field. + + Select Integer from Add Test Case field. + + In the Correct field, add the correct integer value for the question. + + .. image:: ../images/integer_testcase.jpg + :width: 80% + + * **Create String Based Test Case** + + Select **Answer in String** from Type field. + + Select **String** from Add Test Case field. + + In the **Correct** field, add the exact string answer for the question. + + In **String Check** field, select if the checking of the string answer + should be case sensitive or not. + + .. image:: ../images/string_testcase.jpg + :width: 80% + + * **Create Float Based Test Case** + + Select **Answer in Float** from Type field. + + Select **Float** from Add Test Case field. + + In the **Correct** field, add the correct float value for the question. + + In the **Error Margin** field, add the margin of error that will be allowed. + + .. image:: ../images/float_testcase.jpg + :width: 80% Features in Question diff --git a/yaksh/evaluator_tests/test_simple_question_types.py b/yaksh/evaluator_tests/test_simple_question_types.py new file mode 100644 index 0000000..fb1c220 --- /dev/null +++ b/yaksh/evaluator_tests/test_simple_question_types.py @@ -0,0 +1,304 @@ +import unittest +from datetime import datetime, timedelta +from django.utils import timezone +import pytz +from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ + QuestionSet, AnswerPaper, Answer, Course, IntegerTestCase, FloatTestCase,\ + StringTestCase + + +def setUpModule(): + # create user profile + user = User.objects.create_user(username='demo_user_100', + password='demo', + email='demo@test.com') + Profile.objects.create(user=user, roll_number=1, + institute='IIT', department='Aerospace', + position='Student') + + # create a course + course = Course.objects.create(name="Python Course 100", + enrollment="Enroll Request", creator=user) + + quiz = Quiz.objects.create(start_date_time=datetime(2015, 10, 9, 10, 8, 15, 0, + tzinfo=pytz.utc), + end_date_time=datetime(2199, 10, 9, 10, 8, 15, 0, + tzinfo=pytz.utc), + duration=30, active=True, attempts_allowed=1, + time_between_attempts=0, description='demo quiz 100', + pass_criteria=0,language='Python', + prerequisite=None,course=course, + instructions="Demo Instructions" + ) + question_paper = QuestionPaper.objects.create(quiz=quiz, + total_marks=1.0) + + answerpaper = AnswerPaper.objects.create(user=user, user_ip='101.0.0.1', + start_time=timezone.now(), + question_paper=question_paper, + end_time=timezone.now() + +timedelta(minutes=5), + attempt_number=1 + ) + +def tearDownModule(): + User.objects.get(username="demo_user_100").delete() + +class IntegerQuestionTestCases(unittest.TestCase): + @classmethod + def setUpClass(self): + # Creating Quiz + self.quiz = Quiz.objects.get(description="demo quiz 100") + # Creating Question paper + self.question_paper = QuestionPaper.objects.get(quiz=self.quiz) + + #Creating User + self.user = User.objects.get(username='demo_user_100') + + #Creating Question + self.question1 = Question.objects.create(summary='int1', points=1, + type='code', user=self.user) + self.question1.language = 'python' + self.question1.type = "integer" + self.question1.test_case_type = 'integertestcase' + self.question1.description = 'sum of 12+13?' + self.question1.save() + + #Creating answerpaper + self.answerpaper = AnswerPaper.objects.get(question_paper\ + =self.question_paper) + self.answerpaper.attempt_number = 1 + self.answerpaper.save() + # For question + self.integer_based_testcase = IntegerTestCase(question=self.question1, + correct=25, + type = 'integertestcase', + ) + self.integer_based_testcase.save() + + @classmethod + def tearDownClass(self): + self.question1.delete() + + def test_integer_correct_answer(self): + # Given + integer_answer = 25 + self.answer = Answer(question=self.question1, + answer=integer_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(integer_answer, + self.question1, + json_data, + ) + # Then + self.assertTrue(result['success']) + + def test_integer_incorrect_answer(self): + # Given + integer_answer = 26 + self.answer = Answer(question=self.question1, + answer=integer_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(integer_answer, + self.question1, json_data + ) + + # Then + self.assertFalse(result['success']) + + +class StringQuestionTestCases(unittest.TestCase): + @classmethod + def setUpClass(self): + # Creating Quiz + self.quiz = Quiz.objects.get(description="demo quiz 100") + # Creating Question paper + self.question_paper = QuestionPaper.objects.get(quiz=self.quiz) + #Creating User + self.user = User.objects.get(username='demo_user_100') + #Creating Question + self.question1 = Question.objects.create(summary='str1', points=1, + type='code', user=self.user) + self.question1.language = 'python' + self.question1.type = "string" + self.question1.test_case_type = 'stringtestcase' + self.question1.description = 'Write Hello, EARTH!' + self.question1.save() + + self.question2 = Question.objects.create(summary='str2', points=1, + type='code', user=self.user) + self.question2.language = 'python' + self.question2.type = "string" + self.question2.test_case_type = 'stringtestcase' + self.question2.description = 'Write Hello, EARTH!' + self.question2.save() + + #Creating answerpaper + self.answerpaper = AnswerPaper.objects.get(question_paper\ + =self.question_paper) + self.answerpaper.attempt_number = 1 + self.answerpaper.save() + + # For question + self.lower_string_testcase = StringTestCase(question=self.question1, + correct="Hello, EARTH!", + string_check="lower", + type = 'stringtestcase', + ) + self.lower_string_testcase.save() + + self.exact_string_testcase = StringTestCase(question=self.question2, + correct="Hello, EARTH!", + string_check="exact", + type = 'stringtestcase', + ) + self.exact_string_testcase.save() + + @classmethod + def tearDownClass(self): + self.question1.delete() + self.question2.delete() + + def test_case_insensitive_string_correct_answer(self): + # Given + string_answer = "hello, earth!" + answer = Answer(question=self.question1,answer=string_answer) + answer.save() + self.answerpaper.answers.add(answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(string_answer, + self.question1, json_data + ) + # Then + self.assertTrue(result['success']) + + def test_case_insensitive_string_incorrect_answer(self): + # Given + string_answer = "hello, mars!" + answer = Answer(question=self.question1,answer=string_answer) + answer.save() + self.answerpaper.answers.add(answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(string_answer, + self.question1, json_data + ) + + # Then + self.assertFalse(result['success']) + + def test_case_sensitive_string_correct_answer(self): + # Given + string_answer = "Hello, EARTH!" + answer = Answer(question=self.question2,answer=string_answer) + answer.save() + self.answerpaper.answers.add(answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(string_answer, + self.question2, json_data + ) + # Then + self.assertTrue(result['success']) + + def test_case_sensitive_string_incorrect_answer(self): + # Given + string_answer = "hello, earth!" + answer = Answer(question=self.question2,answer=string_answer) + answer.save() + self.answerpaper.answers.add(answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(string_answer, + self.question2, json_data + ) + + # Then + self.assertFalse(result['success']) + + +class FloatQuestionTestCases(unittest.TestCase): + @classmethod + def setUpClass(self): + # Creating Quiz + self.quiz = Quiz.objects.get(description="demo quiz 100") + # Creating Question paper + self.question_paper = QuestionPaper.objects.get(quiz=self.quiz) + + #Creating User + self.user = User.objects.get(username='demo_user_100') + #Creating Question + self.question1 = Question.objects.create(summary='flt1', points=1, + type='code', user=self.user) + self.question1.language = 'python' + self.question1.type = "float" + self.question1.test_case_type = 'floattestcase' + self.question1.save() + + #Creating answerpaper + self.answerpaper = AnswerPaper.objects.get(question_paper\ + =self.question_paper) + self.answerpaper.attempt_number = 1 + self.answerpaper.save() + # For question + self.float_based_testcase = FloatTestCase(question=self.question1, + correct=100, + error_margin=0.1, + type = 'floattestcase', + ) + self.float_based_testcase.save() + + @classmethod + def tearDownClass(self): + self.question1.delete() + + def test_float_correct_answer(self): + # Given + float_answer = 99.9 + self.answer = Answer(question=self.question1, + answer=float_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(float_answer, + self.question1, + json_data, + ) + # Then + self.assertTrue(result['success']) + + def test_integer_incorrect_answer(self): + # Given + float_answer = 99.8 + self.answer = Answer(question=self.question1, + answer=float_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + result = self.answerpaper.validate_answer(float_answer, + self.question1, json_data + ) + + # Then + self.assertFalse(result['success']) diff --git a/yaksh/forms.py b/yaksh/forms.py index 6ec031c..c6283c8 100644 --- a/yaksh/forms.py +++ b/yaksh/forms.py @@ -1,7 +1,7 @@ from django import forms from yaksh.models import get_model_class, Profile, Quiz, Question, TestCase, Course,\ QuestionPaper, StandardTestCase, StdIOBasedTestCase, \ - HookTestCase + HookTestCase, IntegerTestCase, StringTestCase from django.contrib.auth import authenticate from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType @@ -35,6 +35,9 @@ question_types = ( ("mcc", "Multiple Correct Choices"), ("code", "Code"), ("upload", "Assignment Upload"), + ("integer", "Answer in Integer"), + ("string", "Answer in String"), + ("float", "Answer in Float"), ) test_case_types = ( @@ -42,6 +45,9 @@ test_case_types = ( ("stdiobasedtestcase", "StdIO Based Testcase"), ("mcqtestcase", "MCQ Testcase"), ("hooktestcase", "Hook Testcase"), + ("integertestcase", "Integer Testcase"), + ("stringtestcase", "String Testcase"), + ("floattestcase", "Float Testcase"), ) UNAME_CHARS = letters + "._" + digits diff --git a/yaksh/migrations/0003_auto_20170321_0917.py b/yaksh/migrations/0003_auto_20170321_0917.py new file mode 100644 index 0000000..5a575c7 --- /dev/null +++ b/yaksh/migrations/0003_auto_20170321_0917.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.9.5 on 2017-03-21 09:17 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('yaksh', '0002_questionpaper_fixed_question_order'), + ] + + operations = [ + migrations.CreateModel( + name='FloatTestCase', + fields=[ + ('testcase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='yaksh.TestCase')), + ('correct', models.FloatField(default=None)), + ('error_margin', models.FloatField(blank=True, default=0.0, help_text='Margin of error', null=True)), + ], + bases=('yaksh.testcase',), + ), + migrations.CreateModel( + name='IntegerTestCase', + fields=[ + ('testcase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='yaksh.TestCase')), + ('correct', models.IntegerField(default=None)), + ], + bases=('yaksh.testcase',), + ), + migrations.CreateModel( + name='StringTestCase', + fields=[ + ('testcase_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='yaksh.TestCase')), + ('correct', models.TextField(default=None)), + ('string_check', models.CharField(choices=[('lower', 'Case Insensitive'), ('exact', 'Case Sensitive')], max_length=200)), + ], + bases=('yaksh.testcase',), + ), + migrations.AlterField( + model_name='question', + name='type', + field=models.CharField(choices=[('mcq', 'Single Correct Choice'), ('mcc', 'Multiple Correct Choices'), ('code', 'Code'), ('upload', 'Assignment Upload'), ('integer', 'Answer in Integer'), ('string', 'Answer in String'), ('float', 'Answer in Float')], max_length=24), + ), + migrations.AlterField( + model_name='testcase', + name='type', + field=models.CharField(choices=[('standardtestcase', 'Standard Testcase'), ('stdiobasedtestcase', 'StdIO Based Testcase'), ('mcqtestcase', 'MCQ Testcase'), ('hooktestcase', 'Hook Testcase'), ('integertestcase', 'Integer Testcase'), ('stringtestcase', 'String Testcase'), ('floattestcase', 'Float Testcase')], max_length=24, null=True), + ), + ] diff --git a/yaksh/models.py b/yaksh/models.py index b14fcf6..9e05af0 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -38,10 +38,13 @@ languages = ( ) question_types = ( - ("mcq", "Multiple Choice"), + ("mcq", "Single Correct Choice"), ("mcc", "Multiple Correct Choices"), ("code", "Code"), ("upload", "Assignment Upload"), + ("integer", "Answer in Integer"), + ("string", "Answer in String"), + ("float", "Answer in Float"), ) enrollment_methods = ( @@ -54,6 +57,14 @@ test_case_types = ( ("stdiobasedtestcase", "StdIO Based Testcase"), ("mcqtestcase", "MCQ Testcase"), ("hooktestcase", "Hook Testcase"), + ("integertestcase", "Integer Testcase"), + ("stringtestcase", "String Testcase"), + ("floattestcase", "Float Testcase"), + ) + +string_check_type = ( + ("lower", "Case Insensitive"), + ("exact", "Case Sensitive"), ) attempts = [(i, i) for i in range(1, 6)] @@ -1171,6 +1182,7 @@ class AnswerPaper(models.Model): if user_answer.strip() == 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): @@ -1178,6 +1190,39 @@ class AnswerPaper(models.Model): if set(user_answer) == set(expected_answers): result['success'] = True result['error'] = ['Correct answer'] + + elif question.type == 'integer': + expected_answers = [] + for tc in question.get_test_cases(): + expected_answers.append(int(tc.correct)) + if int(user_answer) in expected_answers: + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'string': + tc_status = [] + for tc in question.get_test_cases(): + if tc.string_check == "lower": + if tc.correct.lower().splitlines()\ + == user_answer.lower().splitlines(): + tc_status.append(True) + else: + if tc.correct.splitlines()\ + == user_answer.splitlines(): + tc_status.append(True) + if any(tc_status): + result['success'] = True + result['error'] = ['Correct answer'] + + elif question.type == 'float': + tc_status = [] + for tc in question.get_test_cases(): + if abs(tc.correct - user_answer) <= tc.error_margin: + tc_status.append(True) + if any(tc_status): + result['success'] = True + result['error'] = ['Correct answer'] + elif question.type == 'code' or question.type == "upload": user_dir = self.user.profile.get_user_dir() json_result = code_server.run_code( @@ -1326,3 +1371,39 @@ class HookTestCase(TestCase): def __str__(self): return u'Hook Testcase | Correct: {0}'.format(self.hook_code) + +class IntegerTestCase(TestCase): + correct = models.IntegerField(default=None) + + def get_field_value(self): + return {"test_case_type": "integertestcase", "correct": self.correct} + + def __str__(self): + return u'Integer Testcase | Correct: {0}'.format(self.correct) + + +class StringTestCase(TestCase): + correct = models.TextField(default=None) + string_check = models.CharField(max_length=200,choices=string_check_type) + + def get_field_value(self): + return {"test_case_type": "stringtestcase", "correct": self.correct, + "string_check":self.string_check} + + def __str__(self): + return u'String Testcase | Correct: {0}'.format(self.correct) + + +class FloatTestCase(TestCase): + correct = models.FloatField(default=None) + error_margin = models.FloatField(default=0.0, null=True, blank=True, + help_text="Margin of error") + + def get_field_value(self): + return {"test_case_type": "floattestcase", "correct": self.correct, + "error_margin":self.error_margin} + + def __str__(self): + return u'Testcase | Correct: {0} | Error Margin: +or- {1}'.format( + self.correct, self.error_margin + ) diff --git a/yaksh/static/yaksh/css/question.css b/yaksh/static/yaksh/css/question.css index 9fb2e1a..fdbe5f2 100644 --- a/yaksh/static/yaksh/css/question.css +++ b/yaksh/static/yaksh/css/question.css @@ -36,3 +36,8 @@ .lineObj{ color: grey; } + +#string{ + width: 60em; + height: 10em; +} diff --git a/yaksh/templates/yaksh/add_question.html b/yaksh/templates/yaksh/add_question.html index 0d54ef7..a33950a 100644 --- a/yaksh/templates/yaksh/add_question.html +++ b/yaksh/templates/yaksh/add_question.html @@ -58,6 +58,9 @@ <option value="stdiobasedtestcase">StdIO </option> <option value="mcqtestcase">MCQ/MCC </option> <option value="hooktestcase">Hook </option> + <option value="integertestcase">Integer </option> + <option value="stringtestcase"> String </option> + <option value="floattestcase"> Float </option> </select></p> <center> <button class="btn" type="submit" name="save_question">Save</button> diff --git a/yaksh/templates/yaksh/grade_user.html b/yaksh/templates/yaksh/grade_user.html index d20695b..1cb1f99 100644 --- a/yaksh/templates/yaksh/grade_user.html +++ b/yaksh/templates/yaksh/grade_user.html @@ -144,6 +144,16 @@ Status : <b style="color: green;"> Passed </b><br/> {{ forloop.counter }}. {{ testcase.options|safe }}</strong> {% endif %} {% endfor %} + + {% elif question.type == "integer" or "string" or "float" %} + <h5> <u>Correct Answer:</u></h5> + {% for testcase in question.get_test_cases %} + <strong>{{ testcase.correct|safe }}</strong> + {% if testcase.error_margin %} + <strong>{{ testcase.error_margin|safe }}</strong> + {% endif %} + {% endfor %} + {% else %} <h5> <u>Test cases: </u></h5> {% for testcase in question.get_test_cases %} diff --git a/yaksh/templates/yaksh/question.html b/yaksh/templates/yaksh/question.html index dc8a165..0dad59d 100644 --- a/yaksh/templates/yaksh/question.html +++ b/yaksh/templates/yaksh/question.html @@ -156,13 +156,25 @@ function call_skip(url) <h4> <u> {{ question.summary }} {% if question.type == "mcq" %} - (Single Correct Choice Questions) + (SINGLE CORRECT CHOICE) {% elif question.type == "mcc" %} - (Multiple Correct Choices) + (MULTIPLE CORRECT CHOICES) {% elif question.type == "code" %} (PROGRAMMING) {% elif question.type == "upload" %} (ASSIGNMENT UPLOAD) + {% elif question.type == "integer" %} + (FILL IN THE BLANKS WITH INTEGER ANSWER) + {% elif question.type == "string" %} + (FILL IN THE BLANKS WITH STRING ANSWER) + {% if testcase.string_check == "lower" %} + <h5>(CASE INSENSITIVE)</h5> + {% else %} + <h5>(CASE SENSITIVE)</h5> + {% endif %} + + {% elif question.type == "float" %} + (FILL IN THE BLANKS WITH FLOAT ANSWER) {% endif %} </u> <font class=pull-right>(Marks : {{ question.points }}) </font> @@ -181,6 +193,25 @@ function call_skip(url) <input name="answer" type="radio" value="{{ test_case.options }}" />{{ test_case.options|safe }} <br/> {% endfor %} {% endif %} + + {% if question.type == "integer" %} + Enter Integer:<br/> + <input name="answer" type="number" id="integer" /> + <br/><br/> + {% endif %} + + {% if question.type == "string" %} + Enter Text:<br/> + <textarea name="answer" id="string"></textarea> + <br/><br/> + {% endif %} + + {% if question.type == "float" %} + Enter Decimal Value :<br/> + <input name="answer" type="number" step="any" id="float" /> + <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 }} @@ -206,10 +237,11 @@ function call_skip(url) {% endif %} <div class="from-group"> - {% if question.type == "mcq" or question.type == "mcc"%} + {% if question.type == "mcq" or "mcc" or "integer" or "float" or "string" %} <br><button class="btn btn-primary" type="submit" name="check" id="check">Submit Answer</button> {% elif question.type == "upload" %} <br><button class="btn btn-primary" type="submit" name="check" id="check" onClick="return validate();">Upload</button> + {% else %} {% if question in paper.get_questions_unanswered %} <button class="btn btn-primary" type="submit" name="check" id="check" onClick="submitCode();">Check Answer <span class="glyphicon glyphicon-cog"></span></button> diff --git a/yaksh/templates/yaksh/user_data.html b/yaksh/templates/yaksh/user_data.html index 6679599..6e62b66 100644 --- a/yaksh/templates/yaksh/user_data.html +++ b/yaksh/templates/yaksh/user_data.html @@ -76,6 +76,14 @@ User IP address: {{ paper.user_ip }} {{ forloop.counter }}. {{ testcase.options|safe }}</strong> {% endif %} {% endfor %} + + {% elif question.type == "integer" or "string" or "float" %} + <h5> <u>Correct Answer:</u></h5> + {% for testcase in question.get_test_cases %} + <strong>{{ testcase.correct|safe }}</strong> + {% endfor %} + + {% else %} <h5> <u>Test cases: </u></h5> {% for testcase in question.get_test_cases %} diff --git a/yaksh/templates/yaksh/view_answerpaper.html b/yaksh/templates/yaksh/view_answerpaper.html index f4edf67..4520ac3 100644 --- a/yaksh/templates/yaksh/view_answerpaper.html +++ b/yaksh/templates/yaksh/view_answerpaper.html @@ -55,6 +55,13 @@ {{ forloop.counter }}. {{ testcase.options|safe }}</strong> {% endif %} {% endfor %} + + {% elif question.type == "integer" or "string" or "float" %} + <h5> <u>Correct Answer:</u></h5> + {% for testcase in question.get_test_cases %} + <strong>{{ testcase.correct|safe }}</strong> + {% endfor %} + {% else %} <h5> <u>Test cases: </u></h5> {% for testcase in question.get_test_cases %} diff --git a/yaksh/test_models.py b/yaksh/test_models.py index c732e58..f8f506a 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -44,7 +44,7 @@ def setUpModule(): tzinfo=pytz.utc), duration=30, active=True, attempts_allowed=1, time_between_attempts=0, - description='demo quiz', pass_criteria=0, + description='demo quiz 1', pass_criteria=0, language='Python', prerequisite=None, course=course, instructions="Demo Instructions") @@ -54,7 +54,7 @@ def setUpModule(): tzinfo=pytz.utc), duration=30, active=False, attempts_allowed=-1, time_between_attempts=0, - description='demo quiz', pass_criteria=40, + description='demo quiz 2', pass_criteria=40, language='Python', prerequisite=quiz, course=course, instructions="Demo Instructions") tmp_file1 = os.path.join(tempfile.gettempdir(), "test.txt") @@ -66,13 +66,21 @@ def tearDownModule(): User.objects.all().delete() Question.objects.all().delete() Quiz.objects.all().delete() + Course.objects.all().delete() + QuestionPaper.objects.all().delete() + + que_id_list = ["25", "22", "24", "27"] + for que_id in que_id_list: + dir_path = os.path.join(os.getcwd(), "yaksh", "data","question_{0}".format(que_id)) + if os.path.exists(dir_path): + shutil.rmtree(dir_path) ############################################################################### class ProfileTestCases(unittest.TestCase): def setUp(self): - self.user1 = User.objects.get(pk=1) - self.profile = Profile.objects.get(pk=1) - self.user2 = User.objects.get(pk=3) + self.user1 = User.objects.get(username="demo_user") + self.profile = Profile.objects.get(user=self.user1) + self.user2 = User.objects.get(username='demo_user3') def test_user_profile(self): """ Test user profile""" @@ -87,9 +95,9 @@ class ProfileTestCases(unittest.TestCase): class QuestionTestCases(unittest.TestCase): def setUp(self): # Single question details - self.user1 = User.objects.get(pk=1) - self.user2 = User.objects.get(pk=2) - self.question1 = Question(summary='Demo question', + self.user1 = User.objects.get(username="demo_user") + self.user2 = User.objects.get(username="demo_user2") + self.question1 = Question.objects.create(summary='Demo Python 1', language='Python', type='Code', active=True, @@ -98,9 +106,8 @@ class QuestionTestCases(unittest.TestCase): snippet='def myfunc()', user=self.user1 ) - self.question1.save() - self.question2 = Question(summary='Demo Json', + self.question2 = Question.objects.create(summary='Demo Json', language='python', type='code', active=True, @@ -109,7 +116,6 @@ class QuestionTestCases(unittest.TestCase): snippet='def fact()', user=self.user2 ) - self.question2.save() # create a temp directory and add files for loading questions test file_path = os.path.join(tempfile.gettempdir(), "test.txt") @@ -167,7 +173,7 @@ class QuestionTestCases(unittest.TestCase): def test_question(self): """ Test question """ - self.assertEqual(self.question1.summary, 'Demo question') + self.assertEqual(self.question1.summary, 'Demo Python 1') self.assertEqual(self.question1.language, 'Python') self.assertEqual(self.question1.type, 'Code') self.assertEqual(self.question1.description, 'Write a function') @@ -209,8 +215,8 @@ class QuestionTestCases(unittest.TestCase): """ Test load questions into database from json """ question = Question() result = question.load_questions(self.json_questions_data, self.user1) - question_data = Question.objects.get(pk=25) - file = FileUpload.objects.get(question=25) + question_data = Question.objects.get(summary="Json Demo") + file = FileUpload.objects.get(question=question_data) test_case = question_data.get_test_cases() self.assertEqual(question_data.summary, 'Json Demo') self.assertEqual(question_data.language, 'Python') @@ -228,10 +234,10 @@ class QuestionTestCases(unittest.TestCase): ############################################################################### class QuizTestCases(unittest.TestCase): def setUp(self): - self.creator = User.objects.get(pk=1) - self.teacher = User.objects.get(pk=2) - self.quiz1 = Quiz.objects.get(pk=1) - self.quiz2 = Quiz.objects.get(pk=2) + self.creator = User.objects.get(username="demo_user") + self.teacher = User.objects.get(username="demo_user2") + self.quiz1 = Quiz.objects.get(description='demo quiz 1') + self.quiz2 = Quiz.objects.get(description='demo quiz 2') self.trial_course = Course.objects.create_trial_course(self.creator) def test_quiz(self): @@ -242,7 +248,7 @@ class QuizTestCases(unittest.TestCase): '10:08:15') self.assertEqual(self.quiz1.duration, 30) self.assertTrue(self.quiz1.active) - self.assertEqual(self.quiz1.description, 'demo quiz') + self.assertEqual(self.quiz1.description, 'demo quiz 1') self.assertEqual(self.quiz1.language, 'Python') self.assertEqual(self.quiz1.pass_criteria, 0) self.assertEqual(self.quiz1.prerequisite, None) @@ -278,7 +284,9 @@ class QuizTestCases(unittest.TestCase): self.creator, True ) - self.assertEqual(trial_quiz.description, "Trial_orig_id_1_godmode") + self.assertEqual(trial_quiz.description, + "Trial_orig_id_{}_godmode".format(self.quiz1.id) + ) self.assertTrue(trial_quiz.is_trial) self.assertEqual(trial_quiz.duration, 1000) self.assertTrue(trial_quiz.active) @@ -293,7 +301,8 @@ class QuizTestCases(unittest.TestCase): self.creator, False ) - self.assertEqual(trial_quiz.description, "Trial_orig_id_2_usermode") + self.assertEqual(trial_quiz.description, + "Trial_orig_id_{}_usermode".format(self.quiz2.id)) self.assertTrue(trial_quiz.is_trial) self.assertEqual(trial_quiz.duration, self.quiz2.duration) self.assertEqual(trial_quiz.active, self.quiz2.active) @@ -324,7 +333,7 @@ class QuestionPaperTestCases(unittest.TestCase): def setUpClass(self): # All active questions self.questions = Question.objects.filter(active=True) - self.quiz = Quiz.objects.get(id=1) + self.quiz = Quiz.objects.get(description="demo quiz 1") # create question paper self.question_paper = QuestionPaper.objects.create(quiz=self.quiz, @@ -371,7 +380,7 @@ class QuestionPaperTestCases(unittest.TestCase): # ip address for AnswerPaper self.ip = '127.0.0.1' - self.user = User.objects.get(pk=1) + self.user = User.objects.get(username="demo_user") self.attempted_papers = AnswerPaper.objects.filter( question_paper=self.question_paper, @@ -385,7 +394,7 @@ class QuestionPaperTestCases(unittest.TestCase): def test_questionpaper(self): """ Test question paper""" - self.assertEqual(self.question_paper.quiz.description, 'demo quiz') + self.assertEqual(self.question_paper.quiz.description, 'demo quiz 1') self.assertSequenceEqual(self.question_paper.fixed_questions.all(), [self.questions[3], self.questions[5]] ) @@ -478,17 +487,17 @@ class AnswerPaperTestCases(unittest.TestCase): @classmethod def setUpClass(self): self.ip = '101.0.0.1' - self.user = User.objects.get(id=1) + self.user = User.objects.get(username='demo_user') self.profile = self.user.profile - self.quiz = Quiz.objects.get(pk=1) + self.quiz = Quiz.objects.get(description='demo quiz 1') self.question_paper = QuestionPaper(quiz=self.quiz, total_marks=3) self.question_paper.save() - self.questions = Question.objects.filter(id__in=[1,2,3]) + self.questions = Question.objects.all()[0:3] self.start_time = timezone.now() self.end_time = self.start_time + timedelta(minutes=20) - self.question1 = self.questions.get(id=1) - self.question2 = self.questions.get(id=2) - self.question3 = self.questions.get(id=3) + self.question1 = self.questions[0] + self.question2 = self.questions[1] + self.question3 = self.questions[2] # create answerpaper self.answerpaper = AnswerPaper(user=self.user, @@ -508,12 +517,12 @@ class AnswerPaperTestCases(unittest.TestCase): self.answerpaper.questions_unanswered.add(*self.questions) self.answerpaper.save() # answers for the Answer Paper - self.answer_right = Answer(question=Question.objects.get(id=1), + self.answer_right = Answer(question=self.question1, answer="Demo answer", correct=True, marks=1, error=json.dumps([]) ) - self.answer_wrong = Answer(question=Question.objects.get(id=2), + self.answer_wrong = Answer(question=self.question2, answer="My answer", correct=False, marks=0, @@ -526,14 +535,17 @@ class AnswerPaperTestCases(unittest.TestCase): self.question1.language = 'python' self.question1.test_case_type = 'standardtestcase' + self.question1.summary = "Question1" self.question1.save() self.question2.language = 'python' self.question2.type = 'mcq' self.question2.test_case_type = 'mcqtestcase' + self.question2.summary = "Question2" self.question2.save() self.question3.language = 'python' self.question3.type = 'mcc' self.question3.test_case_type = 'mcqtestcase' + self.question3.summary = "Question3" self.question3.save() self.assertion_testcase = StandardTestCase( question=self.question1, @@ -685,31 +697,32 @@ class AnswerPaperTestCases(unittest.TestCase): self.assertEqual(self.answerpaper.questions_left(), 3) # Test current_question() method of Answer Paper current_question = self.answerpaper.current_question() - self.assertEqual(current_question.id, 1) + self.assertEqual(current_question.summary, "Question1") # Test completed_question() method of Answer Paper - question = self.answerpaper.add_completed_question(1) + + question = self.answerpaper.add_completed_question(self.question1.id) self.assertEqual(self.answerpaper.questions_left(), 2) # Test next_question() method of Answer Paper current_question = self.answerpaper.current_question() - self.assertEqual(current_question.id, 2) + self.assertEqual(current_question.summary, "Question2") # When next_question_id = self.answerpaper.next_question(current_question.id) # Then self.assertTrue(next_question_id is not None) - self.assertEqual(next_question_id.id, 3) + self.assertEqual(next_question_id.summary, "Question3") # Given, here question is already answered - current_question_id = 1 + current_question_id = self.question1.id # When next_question_id = self.answerpaper.next_question(current_question_id) # Then self.assertTrue(next_question_id is not None) - self.assertEqual(next_question_id.id, 2) + self.assertEqual(next_question_id.summary, "Question2") # Given, wrong question id current_question_id = 12 @@ -719,17 +732,19 @@ class AnswerPaperTestCases(unittest.TestCase): # Then self.assertTrue(next_question_id is not None) - self.assertEqual(next_question_id.id, 1) + + self.assertEqual(next_question_id.summary, "Question1") # Given, last question in the list - current_question_id = 3 + current_question_id = self.question3.id # When next_question_id = self.answerpaper.next_question(current_question_id) # Then self.assertTrue(next_question_id is not None) - self.assertEqual(next_question_id.id, 1) + + self.assertEqual(next_question_id.summary, "Question1") # Test get_questions_answered() method # When @@ -749,14 +764,19 @@ class AnswerPaperTestCases(unittest.TestCase): # Test completed_question and next_question # When all questions are answered - current_question = self.answerpaper.add_completed_question(2) + + current_question = self.answerpaper.add_completed_question( + self.question2.id + ) # Then self.assertEqual(self.answerpaper.questions_left(), 1) - self.assertEqual(current_question.id, 3) + self.assertEqual(current_question.summary, "Question3") # When - current_question = self.answerpaper.add_completed_question(3) + current_question = self.answerpaper.add_completed_question( + self.question3.id + ) # Then self.assertEqual(self.answerpaper.questions_left(), 0) @@ -816,12 +836,12 @@ class AnswerPaperTestCases(unittest.TestCase): ############################################################################### class CourseTestCases(unittest.TestCase): def setUp(self): - self.course = Course.objects.get(pk=1) - self.creator = User.objects.get(pk=1) - self.student1 = User.objects.get(pk=2) - self.student2 = User.objects.get(pk=3) - self.quiz1 = Quiz.objects.get(pk=1) - self.quiz2 = Quiz.objects.get(pk=2) + self.course = Course.objects.get(name="Python Course") + self.creator = User.objects.get(username="demo_user") + self.student1 = User.objects.get(username="demo_user2") + self.student2 = User.objects.get(username="demo_user3") + self.quiz1 = Quiz.objects.get(description='demo quiz 1') + self.quiz2 = Quiz.objects.get(description='demo quiz 2') def test_is_creator(self): @@ -898,21 +918,19 @@ class CourseTestCases(unittest.TestCase): def test_create_trial_course(self): """Test to check if trial course is created""" - # Test for manager method create_trial_course trial_course = Course.objects.create_trial_course(self.creator) self.assertEqual(trial_course.name, "trial_course") self.assertEqual(trial_course.enrollment, "open") self.assertTrue(trial_course.active) - self.assertEqual(trial_course.students.get(user=self.creator.id), - self.creator - ) + self.assertEqual(self.creator, trial_course.creator) + self.assertIn(self.creator, trial_course.students.all()) self.assertTrue(trial_course.is_trial) ############################################################################### class TestCaseTestCases(unittest.TestCase): def setUp(self): - self.user = User.objects.get(pk=1) + self.user = User.objects.get(username="demo_user") self.question1 = Question(summary='Demo question 1', language='Python', type='Code', diff --git a/yaksh/views.py b/yaksh/views.py index de2aac6..2adc2c3 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -16,6 +16,7 @@ from django.contrib.auth.decorators import login_required from django.contrib.auth.models import Group from django.forms.models import inlineformset_factory from django.utils import timezone +from django.core.exceptions import MultipleObjectsReturned import pytz from taggit.models import Tag from itertools import chain @@ -24,8 +25,9 @@ import six # Local imports. from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUpload,\ - has_profile, StandardTestCase, McqTestCase, StdIOBasedTestCase, HookTestCase - + has_profile, StandardTestCase, McqTestCase,\ + StdIOBasedTestCase, HookTestCase, IntegerTestCase,\ + FloatTestCase, StringTestCase from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ QuestionForm, RandomQuestionForm,\ QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm,\ @@ -467,6 +469,24 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # Add the answer submitted, regardless of it being correct or not. if current_question.type == 'mcq': user_answer = request.POST.get('answer') + elif current_question.type == 'integer': + try: + user_answer = int(request.POST.get('answer')) + except ValueError: + msg = "Please enter an Integer Value" + return show_question(request, current_question, + paper, notification=msg + ) + elif current_question.type == 'float': + try: + user_answer = float(request.POST.get('answer')) + except ValueError: + msg = "Please enter a Float Value" + return show_question(request, current_question, + paper, notification=msg) + elif current_question.type == 'string': + user_answer = str(request.POST.get('answer')) + elif current_question.type == 'mcc': user_answer = request.POST.getlist('answer') elif current_question.type == 'upload': @@ -504,9 +524,11 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # questions, we obtain the results via XML-RPC with the code executed # safely in a separate process (the code_server.py) running as nobody. json_data = current_question.consolidate_answer_data(user_answer, user) \ - if current_question.type == 'code' or \ - current_question.type == 'upload' else None - result = paper.validate_answer(user_answer, current_question, json_data) + if current_question.type == 'code' or \ + current_question.type == 'upload' else None + result = paper.validate_answer(user_answer, current_question, + json_data + ) if result.get('success'): new_answer.marks = (current_question.points * result['weight'] / current_question.get_maximum_test_case_weight()) \ |