From 03e54d7f6a42c0d9207db554abba9da854d494e4 Mon Sep 17 00:00:00 2001 From: prathamesh Date: Fri, 23 Sep 2016 13:02:27 +0530 Subject: Regrade Feature A question can be regraded for a given quiz. Whole quiz can be regraded. Particular user paper can be regraded. Moved validate_answer from views to models. regrade method also updates the answer i.e. marks and correctness. Also, the answer paper percentage is updated. --- yaksh/models.py | 84 ++++++++++++++++ yaksh/templates/manage.html | 1 + yaksh/templates/yaksh/regrade.html | 165 +++++++++++++++++++++++++++++++ yaksh/test_models.py | 148 +++++++++++++++++++++++++++- yaksh/test_views.py | 197 ++++++++++++++++++++++++++++++++++++- yaksh/urls.py | 11 ++- yaksh/views.py | 93 +++++++---------- 7 files changed, 639 insertions(+), 60 deletions(-) create mode 100644 yaksh/templates/yaksh/regrade.html 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 @@
  • Courses
  • My Profile
  • Change Password
  • +
  • Grader