summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--yaksh/models.py84
-rw-r--r--yaksh/templates/manage.html1
-rw-r--r--yaksh/templates/yaksh/regrade.html165
-rw-r--r--yaksh/test_models.py148
-rw-r--r--yaksh/test_views.py197
-rw-r--r--yaksh/urls.py11
-rw-r--r--yaksh/views.py93
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})