diff options
-rw-r--r-- | yaksh/models.py | 84 | ||||
-rw-r--r-- | yaksh/templates/manage.html | 1 | ||||
-rw-r--r-- | yaksh/templates/yaksh/regrade.html | 165 | ||||
-rw-r--r-- | yaksh/test_models.py | 148 | ||||
-rw-r--r-- | yaksh/test_views.py | 197 | ||||
-rw-r--r-- | yaksh/urls.py | 11 | ||||
-rw-r--r-- | yaksh/views.py | 93 |
7 files changed, 639 insertions, 60 deletions
diff --git a/yaksh/models.py b/yaksh/models.py index 8e8a0d3..d14943a 100644 --- a/yaksh/models.py +++ b/yaksh/models.py @@ -12,7 +12,14 @@ from taggit.managers import TaggableManager from django.utils import timezone import pytz import os +import stat +from os.path import join, abspath, dirname, exists import shutil +from yaksh.xmlrpc_clients import code_server + + +# The directory where user data can be saved. +OUTPUT_DIR = abspath(join(dirname(__file__), 'output')) languages = ( ("python", "Python"), @@ -172,6 +179,18 @@ class Profile(models.Model): choices=[(tz, tz) for tz in pytz.common_timezones] ) + def get_user_dir(self): + """Return the output directory for the user.""" + + user_dir = join(OUTPUT_DIR, str(self.user.username)) + if not exists(user_dir): + os.mkdir(user_dir) + # Make it rwx by others. + os.chmod(user_dir, stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH + | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR + | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP) + return user_dir + ############################################################################### class Question(models.Model): @@ -902,6 +921,71 @@ class AnswerPaper(models.Model): if question.type == 'code': return self.answers.filter(question=question).order_by('-id') + def validate_answer(self, user_answer, question, json_data=None): + """ + Checks whether the answer submitted by the user is right or wrong. + If right then returns correct = True, success and + message = Correct answer. + success is True for MCQ's and multiple correct choices because + only one attempt are allowed for them. + For code questions success is True only if the answer is correct. + """ + + result = {'success': True, 'error': 'Incorrect answer'} + correct = False + 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(): + correct = 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) + if set(user_answer) == set(expected_answers): + result['error'] = 'Correct answer' + correct = True + elif question.type == 'code': + user_dir = self.user.profile.get_user_dir() + json_result = code_server.run_code(question.language, + question.test_case_type, json_data, user_dir) + result = json.loads(json_result) + if result.get('success'): + correct = True + return correct, result + + def regrade(self, question_id): + try: + question = self.questions.get(id=question_id) + msg = 'User: {0}; Quiz: {1}; Question: {2}.\n'.format( + self.user, self.question_paper.quiz.description, question) + except Question.DoesNotExist: + msg = 'User: {0}; Quiz: {1} Question id: {2}.\n'.format( + self.user, self.question_paper.quiz.description, question_id) + return False, msg + 'Question not in the answer paper.' + user_answer = self.answers.filter(question=question).last() + if not user_answer: + return False, msg + 'Did not answer.' + if question.type == 'mcc': + try: + answer = eval(user_answer.answer) + if type(answer) is not list: + return False, msg + 'MCC answer not a list.' + except Exception as e: + return False, msg + 'MCC answer submission error' + else: + answer = user_answer.answer + json_data = question.consolidate_answer_data(answer) \ + if question.type == 'code' else None + correct, result = self.validate_answer(answer, question, json_data) + user_answer.marks = question.points if correct else 0.0 + user_answer.correct = correct + user_answer.error = result.get('error') + user_answer.save() + self.update_marks('complete') + return True, msg + def __unicode__(self): u = self.user q = self.question_paper.quiz diff --git a/yaksh/templates/manage.html b/yaksh/templates/manage.html index b628a44..9e004e6 100644 --- a/yaksh/templates/manage.html +++ b/yaksh/templates/manage.html @@ -33,6 +33,7 @@ <li><a href="{{ URL_ROOT }}/exam/manage/courses">Courses</a></li> <li><a href="{{ URL_ROOT }}/exam/viewprofile">My Profile</a></li> <li><a href="{{ URL_ROOT }}/exam/changepassword">Change Password</a></li> +<li><a href="{{ URL_ROOT }}/exam/manage/grader"> Grader </a></li> </ul> <ul style="float:right;"> <li><strong><a style='cursor:pointer' onClick='location.replace("{{URL_ROOT}}/exam/complete/");'>Log out</a></strong></li> diff --git a/yaksh/templates/yaksh/regrade.html b/yaksh/templates/yaksh/regrade.html new file mode 100644 index 0000000..77d8ec4 --- /dev/null +++ b/yaksh/templates/yaksh/regrade.html @@ -0,0 +1,165 @@ +{% extends "manage.html" %} + +{% block title %} Grader {% endblock title %} + +{% block css %} +<link rel="stylesheet" media="all" type="text/css" href="{{ URL_ROOT }}/static/yaksh/css/bootstrap.css" /> +{% endblock %} +{% block subtitle %} + Grader + {% endblock %} + {% block script %} + <script src="{{ URL_ROOT }}/static/yaksh/js/jquery-1.9.1.min.js"></script> + <script src="{{ URL_ROOT }}/static/yaksh/js/bootstrap.min.js"></script> + {% endblock %} + {% block manage %} + +<div class="row"> + <div class="col-md-3"> + <ul class="nav nav-pills nav-stacked"> + <li class="active"><a href="#intro" data-toggle="pill" > Intro </a></li> + <li><a href="#questions" data-toggle="pill" > Question-wise regrade </a></li> + <li><a href="#quizzes" data-toggle="pill" > Quiz-wise regrade </a></li> + <li><a href="#users" data-toggle="pill" > User-wise regrade </a></li> + </ul> + </div><!--span2--> + <div class="col-md-9"> + <div class="tab-content"> + <div id="intro" class="tab-pane fade in active"> + <h3> Regrade </h3> + <dl> + <dt> Question wise regrade </dt> + <dd> You can regrade a question for all answerpapers for a given quiz. </dd> + <dt> Quiz wise regrade <dt> + <dd> You can regrade an answerpaper for a quiz or a question for the same. </dd> + <dt> User wise regrade </dt> + <dd> You can regrade an answerpaper for an user or a question for the same. </dd> + </dl> + </div> + <div id="questions" class="tab-pane fade"> + {% for course in courses %} + <div class="well"> + <h4><span class="label label-info"> + <a href="#questions_quizzes{{ course.id }}" data-toggle="collapse">Course: {{ course }}</a> + </span></h4> + <div id="questions_quizzes{{ course.id }}" class="collapse"> + {% for quiz in course.quiz_set.all %} + <p><a href="#questions_questions{{ quiz.id }}" data-toggle="collapse">Quiz: {{ quiz }}</a></p> + <div id="questions_questions{{ quiz.id }}" class="collapse"> + {% with questionpaper=quiz.questionpaper_set.get %} + <p class="bg-info"> Questions: </p> + <ol class="list-group"> + {% for question in questionpaper.fixed_questions.all %} + <li class="list-group">{{ question.summary }} + <a href="{{ URL_ROOT }}/exam/manage/regrade/questionpaper/{{ course.id }}/{{ question.id }}/{{ questionpaper.id }}/" + class="btn btn-default btn-sm pull-right"><span class="glyphicon glyphicon-repeat"></span> Regrade </a> + </li> + {% endfor %} + {% for random_set in questionpaper.random_questions.all %} + {% for question in random_set.questions.all %} + <li class="list-group"> {{ question.summary }} + <a href="{{ URL_ROOT }}/exam/manage/regrade/questionpaper/{{ course.id}}/{{ question.id }}/{{ questionpaper.id }}/" + class="btn btn-default btn-sm pull-right"><span class="glyphicon glyphicon-repeat"></span> Regrade </a> + </li> + {% endfor %} + {% endfor %} + </ol> + {% endwith %}<br /><br /> + </div> + {% endfor %} + </div> + </div><!--well--> + {% endfor %} + </div> + + <div id="quizzes" class="tab-pane fade"> + {% for course in courses %} + <div class="well"> + <h4><span class="label label-info"> + <a href="#quizzes_quizzes{{ course.id }}" data-toggle="collapse">Course: {{ course }}</a> + </span></h4> + <div id="quizzes_quizzes{{ course.id }}" class="collapse"> + {% for quiz in course.quiz_set.all %} + <p><a href="#quizzes_papers{{ quiz.id }}" data-toggle="collapse">Quiz: {{ quiz }}</a></p> + <div id="quizzes_papers{{ quiz.id }}" class="collapse"> + <ol class="list-group"> + {% for answerpaper in quiz.questionpaper_set.get.answerpaper_set.all %} + <li class="list-group"> + Username: {{ answerpaper.user.username }}; Name: {{ answerpaper.user.get_full_name }}; Attempt Number: {{ answerpaper.attempt_number}} + <a href="{{ URL_ROOT }}/exam/manage/regrade/paper/{{ course.id }}/{{ answerpaper.id }}/" + class="btn btn-default btn-sm pull-right"><span class="glyphicon glyphicon-repeat"></span> Regrade whole paper </a> + </li> + <ol class="list-group"> + {% for question in answerpaper.questions.all %} + <li class="list-group"> {{ question.summary }} + <a href="{{ URL_ROOT }}/exam/manage/regrade/answerpaper/{{ course.id }}/{{ question.id }}/{{ answerpaper.id }}/" + class="btn btn-default btn-sm pull-right"><span class="glyphicon glyphicon-repeat"></span> Regrade </a> + </li> + {% endfor %} + </ol> + {% endfor %} + </ol> + </div> + {% endfor %} + </div> + </div><!--well--> + {% endfor %} + </div> + + <div id="users" class="tab-pane fade"> + {% for course in courses %} + <div class="well"> + <h4><span class="label label-info"> + <a href="#users_users{{ course.id }}" data-toggle="collapse">Course: {{ course }}</a> + </span></h4> + <div id="users_users{{ course.id }}" class="collapse"> + {% for user in course.students.all %} + <p><a href="#users_papers{{ user.id }}" data-toggle="collapse"> Answer Papers for {{ user.get_full_name }}</a></p> + <div id="users_papers{{ user.id }}" class="collapse"> + <ol class="list-group"> + {% for answerpaper in user.answerpaper_set.all %} + <li class="list-group"> Quiz: {{answerpaper.question_paper.quiz.description }}; Attempt Number: {{ answerpaper.attempt_number }} + <a href="{{ URL_ROOT }}/exam/manage/regrade/paper/{{ course.id }}/{{ answerpaper.id }}/" + class="btn btn-default btn-sm pull-right" ><span class="glyphicon glyphicon-repeat"></span> Regrade whole paper </a> + </li> + <ol class="list-group"> + {% for question in answerpaper.questions.all %} + <li class="list-group"> {{ question.summary }} + <a href="{{ URL_ROOT }}/exam/manage/regrade/answerpaper/{{ course.id }}/{{ question.id }}/{{ answerpaper.id }}/" + class="btn btn-default btn-sm pull-right"><span class="glyphicon glyphicon-repeat"></span> Regrade </a> + </li> + {% endfor %} + </ol> + {% endfor %} + </ol> + </div> + {% endfor %} + </div> + </div><!--well--> + {% endfor %} + </div> + </div> + </div><!--span10--> +</div><!--row--> + +{% if details %} +<div> + <table class="table"> + <tbody> + {% for detail in details %} + {% if detail.0 %} + <tr class="success"> + <td> Graded Successfully </td> + {% else%} + <tr class="danger"> + <td> Did not Grade </td> + {% endif %} + <td> {{ detail.1|linebreaks }} </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> + {% endif %} + + {% endblock %} diff --git a/yaksh/test_models.py b/yaksh/test_models.py index ca64f8c..31513ad 100644 --- a/yaksh/test_models.py +++ b/yaksh/test_models.py @@ -1,7 +1,7 @@ import unittest from yaksh.models import User, Profile, Question, Quiz, QuestionPaper,\ QuestionSet, AnswerPaper, Answer, Course, StandardTestCase,\ - StdioBasedTestCase + StdioBasedTestCase, McqTestCase import json from datetime import datetime, timedelta from django.utils import timezone @@ -416,6 +416,9 @@ class AnswerPaperTestCases(unittest.TestCase): self.questions = Question.objects.filter(id__in=[1,2,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) # create answerpaper self.answerpaper = AnswerPaper(user=self.user, @@ -449,6 +452,147 @@ class AnswerPaperTestCases(unittest.TestCase): self.answerpaper.answers.add(self.answer_right) self.answerpaper.answers.add(self.answer_wrong) + self.question1.language = 'python' + self.question1.test_case_type = 'standardtestcase' + self.question1.save() + self.question2.language = 'python' + self.question2.type = 'mcq' + self.question2.test_case_type = 'mcqtestcase' + self.question2.save() + self.question3.language = 'python' + self.question3.type = 'mcc' + self.question3.test_case_type = 'mcqtestcase' + self.question3.save() + self.assertion_testcase = StandardTestCase( + question=self.question1, + test_case='assert add(1, 3) == 4' + ) + self.assertion_testcase.save() + self.mcq_based_testcase = McqTestCase( + options = 'a', + question=self.question2, + correct = True + ) + self.mcq_based_testcase.save() + self.mcc_based_testcase = McqTestCase( + question=self.question3, + options = 'a', + correct = True + ) + self.mcc_based_testcase.save() + + def test_validate_and_regrade_mcc_question(self): + # Given + mcc_answer = ['a'] + self.answer = Answer(question=self.question3, + answer=mcc_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + correct, result = self.answerpaper.validate_answer(mcc_answer, + self.question3, json_data) + + # Then + self.assertTrue(correct) + self.assertTrue(result['success']) + self.assertEqual(result['error'], 'Correct answer') + self.answer.correct = True + self.answer.marks = 1 + + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = ['a', 'b'] + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question3.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question3).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 0) + self.assertFalse(self.answer.correct) + + def test_validate_and_regrade_mcq_question(self): + # Given + mcq_answer = 'a' + self.answer = Answer(question=self.question2, + answer=mcq_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = None + correct, result = self.answerpaper.validate_answer(mcq_answer, + self.question2, json_data) + + # Then + self.assertTrue(correct) + self.assertTrue(result['success']) + self.assertEqual(result['error'], 'Correct answer') + self.answer.correct = True + self.answer.marks = 1 + + # Given + self.answer.correct = True + self.answer.marks = 1 + + self.answer.answer = 'b' + self.answer.save() + + # When + details = self.answerpaper.regrade(self.question2.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question2).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 0) + self.assertFalse(self.answer.correct) + + def test_validate_and_regrade_code_question(self): + # Given + code_answer = 'def add(a, b):\n return a' + self.answer = Answer(question=self.question1, + answer=code_answer, + ) + self.answer.save() + self.answerpaper.answers.add(self.answer) + + # When + json_data = self.question1.consolidate_answer_data(code_answer) + correct, result = self.answerpaper.validate_answer(code_answer, + self.question1, json_data) + + # Then + self.assertFalse(correct) + self.assertFalse(result['success']) + self.assertTrue('AssertionError' in result['error']) + self.assertFalse(self.answer.correct) + self.assertEqual(self.answer.marks, 0) + + # Given + self.answer.answer = 'def add(a, b):\n return a+b' + self.answer.save() + self.answerpaper.percent = None + self.answerpaper.save() + + # When + details = self.answerpaper.regrade(self.question1.id) + + # Then + self.answer = self.answerpaper.answers.filter(question=self.question1).last() + self.assertTrue(details[0]) + self.assertEqual(self.answer.marks, 1) + self.assertTrue(self.answerpaper.percent is not None) + self.assertTrue(self.answer.correct) + + def test_answerpaper(self): """ Test Answer Paper""" self.assertEqual(self.answerpaper.user.username, 'demo_user') @@ -709,7 +853,7 @@ class TestCaseTestCases(unittest.TestCase): active=True, description='Write to standard output', points=1.0, - test_case_type="stdiobasedtestcase", + test_case_type="stdiobasedtestcase", user=self.user, snippet='def myfunc()' ) diff --git a/yaksh/test_views.py b/yaksh/test_views.py index 7d23ce9..b5830ec 100644 --- a/yaksh/test_views.py +++ b/yaksh/test_views.py @@ -1132,7 +1132,7 @@ class TestViewAnswerPaper(TestCase): self.assertRedirects(response, '/exam/quizzes/') -class TestSelfEnroll(TestCase): +class TestGrader(TestCase): def setUp(self): self.client = Client() @@ -1192,6 +1192,201 @@ class TestSelfEnroll(TestCase): self.course = Course.objects.create(name="Python Course", enrollment="Enroll Request", creator=self.user1) + self.question = Question.objects.create(summary='Dummy', points=1, + type='code', user=self.user1) + + self.quiz = Quiz.objects.create(time_between_attempts=0, course=self.course, + description='demo quiz', language='Python') + + 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.answerpaper = AnswerPaper.objects.create(user_id=3, + 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)) + + def tearDown(self): + self.client.logout() + self.user1.delete() + self.user2.delete() + self.student.delete() + self.course.delete() + + def test_grader_denies_anonymous(self): + # Given + redirect_destination = ('/exam/login/?next=/exam/manage/grader/') + + # When + response = self.client.get(reverse('yaksh:grader'), follow=True) + + # Then + self.assertRedirects(response, redirect_destination) + + + def test_grader_denies_students(self): + # Given + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + # When + response = self.client.get(reverse('yaksh:grader'), follow=True) + + # Then + self.assertEqual(response.status_code, 404) + + + def test_regrade_denies_anonymous(self): + # Given + redirect_destination = ('/exam/login/?next=/exam/manage/regrade/answerpaper/1/1/1/') + + # When + response = self.client.get(reverse('yaksh:regrade', + kwargs={'course_id': self.course.id, + 'question_id': self.question.id, + 'answerpaper_id': self.answerpaper.id}), + follow=True) + + # Then + self.assertRedirects(response, redirect_destination) + + + def test_regrade_denies_students(self): + # Given + self.client.login( + username=self.student.username, + password=self.student_plaintext_pass + ) + + # When + response = self.client.get(reverse('yaksh:regrade', + kwargs={'course_id': self.course.id, + 'question_id': self.question.id, + 'answerpaper_id': self.answerpaper.id}), + follow=True) + + # Then + self.assertEqual(response.status_code, 404) + + + def test_grader_by_moderator(self): + # Given + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + + # When + response = self.client.get(reverse('yaksh:grader'), + follow=True) + + # Then + self.assertEqual(response.status_code, 200) + self.assertTrue('courses' in response.context) + self.assertTemplateUsed(response, 'yaksh/regrade.html') + + + def test_regrade_by_moderator(self): + # Given + self.client.login( + username=self.user1.username, + password=self.user1_plaintext_pass + ) + + # When + response = self.client.get(reverse('yaksh:regrade', + kwargs={'course_id': self.course.id, + 'question_id': self.question.id, + 'answerpaper_id': self.answerpaper.id}), + follow=True) + + # Then + self.assertEqual(response.status_code, 200) + self.assertTrue('courses' in response.context) + self.assertTrue('details' in response.context) + self.assertTemplateUsed(response, 'yaksh/regrade.html') + + + def test_regrade_denies_moderator_not_in_course(self): + # Given + self.client.login( + username=self.user2.username, + password=self.user2_plaintext_pass + ) + + # When + response = self.client.get(reverse('yaksh:regrade', + kwargs={'course_id': self.course.id, + 'question_id': self.question.id, + 'answerpaper_id': self.answerpaper.id}), + follow=True) + + # Then + self.assertEqual(response.status_code, 404) + +class TestSelfEnroll(TestCase): + def setUp(self): + self.client = Client() + self.mod_group = Group.objects.create(name='moderator') + # Create Moderator with profile + self.user1_plaintext_pass = 'demo1' + self.user1 = User.objects.create_user( + username='demo_user1', + password=self.user1_plaintext_pass, + first_name='user1_first_name', + last_name='user1_last_name', + email='demo@test.com' + ) + + Profile.objects.create( + user=self.user1, + roll_number=10, + institute='IIT', + department='Chemical', + position='Moderator', + timezone='UTC' + ) + + self.user2_plaintext_pass = 'demo2' + self.user2 = User.objects.create_user( + username='demo_user2', + password=self.user2_plaintext_pass, + first_name='user2_first_name', + last_name='user2_last_name', + email='demo2@test.com' + ) + + Profile.objects.create( + user=self.user2, + roll_number=10, + institute='IIT', + department='Aeronautical', + position='Moderator', + timezone='UTC' + ) + + # Create Student + self.student_plaintext_pass = 'demo_student' + self.student = User.objects.create_user( + username='demo_student', + password=self.student_plaintext_pass, + first_name='student_first_name', + last_name='student_last_name', + email='demo_student@test.com' + ) + + # Add to moderator group + self.mod_group.user_set.add(self.user1) + self.mod_group.user_set.add(self.user2) + + self.course = Course.objects.create(name="Python Course", + enrollment="Enroll Request", creator=self.user1) + def tearDown(self): self.client.logout() self.user1.delete() diff --git a/yaksh/urls.py b/yaksh/urls.py index daa6008..bbc5bdc 100644 --- a/yaksh/urls.py +++ b/yaksh/urls.py @@ -101,5 +101,14 @@ urlpatterns += [ url(r'^manage/remove_teachers/(?P<course_id>\d+)/$', views.remove_teachers, name='remove_teacher'), url(r'^manage/download_questions/$', views.show_all_questions), url(r'^manage/upload_questions/$', views.show_all_questions), - url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz) + url(r'^manage/grader/$', views.grader, name='grader'), + url(r'^manage/regrade/question/(?P<course_id>\d+)/(?P<question_id>\d+)/$', + views.regrade, name='regrade'), + url(r'^manage/regrade/questionpaper/(?P<course_id>\d+)/(?P<question_id>\d+)/(?P<questionpaper_id>\d+)/$', + views.regrade, name='regrade'), + url(r'^manage/regrade/answerpaper/(?P<course_id>\d+)/(?P<question_id>\d+)/(?P<answerpaper_id>\d+)/$', + views.regrade, name='regrade'), + url(r'^manage/regrade/paper/(?P<course_id>\d+)/(?P<answerpaper_id>\d+)/$', + views.regrade, name='regrade'), + url(r'^manage/(?P<mode>[\w\-]+)/(?P<quiz_id>\d+)/$', views.test_quiz), ] diff --git a/yaksh/views.py b/yaksh/views.py index 9f7c7a9..44caef9 100644 --- a/yaksh/views.py +++ b/yaksh/views.py @@ -1,8 +1,6 @@ import random import string import os -import stat -from os.path import dirname, pardir, abspath, join, exists from datetime import datetime import collections import csv @@ -22,7 +20,6 @@ import pytz from taggit.models import Tag from itertools import chain import json - # Local imports. from yaksh.models import get_model_class, Quiz, Question, QuestionPaper, QuestionSet, Course from yaksh.models import Profile, Answer, AnswerPaper, User, TestCase, FileUpload,\ @@ -31,12 +28,9 @@ from yaksh.forms import UserRegisterForm, UserLoginForm, QuizForm,\ QuestionForm, RandomQuestionForm,\ QuestionFilterForm, CourseForm, ProfileForm, UploadFileForm,\ get_object_form, FileForm -from yaksh.xmlrpc_clients import code_server from settings import URL_ROOT from yaksh.models import AssignmentUpload -# The directory where user data can be saved. -OUTPUT_DIR = abspath(join(dirname(__file__), 'output')) def my_redirect(url): @@ -55,19 +49,6 @@ def my_render_to_response(template, context=None, **kwargs): return render_to_response(template, context, **kwargs) -def get_user_dir(user): - """Return the output directory for the user.""" - - user_dir = join(OUTPUT_DIR, str(user.username)) - if not exists(user_dir): - os.mkdir(user_dir) - # Make it rwx by others. - os.chmod(user_dir, stat.S_IROTH | stat.S_IWOTH | stat.S_IXOTH - | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR - | stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP) - return user_dir - - def is_moderator(user): """Check if the user is having moderator rights""" if user.groups.filter(name='moderator').exists(): @@ -430,7 +411,7 @@ def start(request, questionpaper_id=None, attempt_num=None): raise Http404(msg) new_paper = quest_paper.make_answerpaper(user, ip, attempt_num) # Make user directory. - user_dir = get_user_dir(user) + user_dir = new_paper.user.profile.get_user_dir() return show_question(request, new_paper.current_question(), new_paper) @@ -531,7 +512,7 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): # safely in a separate process (the code_server.py) running as nobody. json_data = question.consolidate_answer_data(user_answer) \ if question.type == 'code' else None - correct, result = validate_answer(user, user_answer, question, json_data) + correct, result = paper.validate_answer(user_answer, question, json_data) if correct: new_answer.correct = correct new_answer.marks = question.points @@ -552,40 +533,6 @@ def check(request, q_id, attempt_num=None, questionpaper_id=None): return show_question(request, question, paper) -def validate_answer(user, user_answer, question, json_data=None): - """ - Checks whether the answer submitted by the user is right or wrong. - If right then returns correct = True, success and - message = Correct answer. - success is True for MCQ's and multiple correct choices because - only one attempt are allowed for them. - For code questions success is True only if the answer is correct. - """ - - result = {'success': True, 'error': 'Incorrect answer'} - correct = False - - 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(): - correct = 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) - if set(user_answer) == set(expected_answers): - result['error'] = 'Correct answer' - correct = True - elif question.type == 'code': - user_dir = get_user_dir(user) - json_result = code_server.run_code(question.language, question.test_case_type, json_data, user_dir) - result = json.loads(json_result) - if result.get('success'): - correct = True - return correct, result - def quit(request, reason=None, attempt_num=None, questionpaper_id=None): """Show the quit page when the user logs out.""" @@ -1229,7 +1176,6 @@ def add_teacher(request, course_id): context_instance=ci) - @login_required def remove_teachers(request, course_id): """ remove user from a course """ @@ -1286,3 +1232,38 @@ def view_answerpaper(request, questionpaper_id): return my_render_to_response('yaksh/view_answerpaper.html', context) else: return my_redirect('/exam/quizzes/') + + +@login_required +def grader(request, extra_context=None): + user = request.user + if not is_moderator(user): + raise Http404('You are not allowed to view this page!') + courses = Course.objects.filter(is_trial=False) + user_courses = list(courses.filter(creator=user)) + list(courses.filter(teachers=user)) + context = {'courses': user_courses} + if extra_context: + context.update(extra_context) + return my_render_to_response('yaksh/regrade.html', context) + + +@login_required +def regrade(request, course_id, question_id=None, answerpaper_id=None, questionpaper_id=None): + user = request.user + course = get_object_or_404(Course, pk=course_id) + if not is_moderator(user) or (user != course.creator and user not in course.teachers.all()): + raise Http404('You are not allowed to view this page!') + details = [] + if answerpaper_id is not None and question_id is None: + answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id) + for question in answerpaper.questions.all(): + details.append(answerpaper.regrade(question.id)) + if questionpaper_id is not None and question_id is not None: + answerpapers = AnswerPaper.objects.filter(questions=question_id, + question_paper_id=questionpaper_id) + for answerpaper in answerpapers: + details.append(answerpaper.regrade(question_id)) + if answerpaper_id is not None and question_id is not None: + answerpaper = get_object_or_404(AnswerPaper, pk=answerpaper_id) + details.append(answerpaper.regrade(question_id)) + return grader(request, extra_context={'details': details}) |