Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow negative marks for a question #706

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions yaksh/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,14 @@ class Question(models.Model):
# The type of question.
type = models.CharField(max_length=24, choices=question_types)

# Negative marks for the question
negative_marks = models.FloatField(
default=0.0,
help_text='In Percentage eg: 25, '
'means 0.25 will be negative marks if question is for 1 mark'
)


# Is this question active or not. If it is inactive it will not be used
# when creating a QuestionPaper.
active = models.BooleanField(default=True)
Expand Down Expand Up @@ -1732,10 +1740,16 @@ class Answer(models.Model):
comment = models.TextField(null=True, blank=True)

def set_marks(self, marks):
if marks > self.question.points:
self.marks = self.question.points
else:
self.marks = marks
points = self.question.points
qtype = self.question.type
if marks > points:
marks = points
if qtype == 'mcq' or qtype == 'mcc':
wrong_fraction = points - marks
negative_marks = wrong_fraction * (self.question.negative_marks/100)
marks = marks - negative_marks
self.marks = marks
self.save()

def set_comment(self, comments):
self.comment = comments
Expand Down Expand Up @@ -2629,16 +2643,16 @@ def regrade(self, question_id, server_port=SERVER_POOL_PORT):
if question.partial_grading and question.type == 'code':
max_weight = question.get_maximum_test_case_weight()
factor = result['weight']/max_weight
user_answer.marks = question.points * factor
user_answer.set_marks(question.points * factor)
else:
user_answer.marks = question.points
user_answer.set_marks(question.points)
else:
if question.partial_grading and question.type == 'code':
max_weight = question.get_maximum_test_case_weight()
factor = result['weight']/max_weight
user_answer.marks = question.points * factor
user_answer.set_marks(question.points * factor)
else:
user_answer.marks = 0
user_answer.set_marks(0)
user_answer.save()
self.update_marks('completed')
return True, msg
Expand Down
14 changes: 12 additions & 2 deletions yaksh/static/yaksh/js/add_question.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,11 @@ function autosubmit()
}

$(document).ready(() => {
$("#id_negative_marks").closest("tr").hide()
let qtype = $("#id_type").val();
if (qtype === "mcq" || qtype === "mcc") {
$("#id_negative_marks").closest("tr").show()
}
let option = $('#id_language').val();
if(option === 'other') {
$('#id_topic').closest('tr').show();
Expand Down Expand Up @@ -210,5 +215,10 @@ $(document).ready(() => {
} else {
$('#id_language').children("option[value='other']").show();
}
})
});
if (value === "mcq" || value === "mcc") {
$("#id_negative_marks").closest("tr").show()
} else {
$("#id_negative_marks").closest("tr").hide()
}
});
});
111 changes: 111 additions & 0 deletions yaksh/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -1790,6 +1790,62 @@ def test_update_marks(self):
self.assertTrue(self.answerpaper.passed)
self.assertFalse(self.answerpaper.is_attempt_inprogress())

def test_update_marks_with_negative_marks(self):
# Given
question = self.answer_wrong.question
question.negative_marks = 25
question.save()

# When
self.answer_wrong.set_marks(1)
self.answerpaper.update_marks()

# Then
self.assertEqual(self.answerpaper.marks_obtained, 2)
self.assertEqual(self.answerpaper.percent, 66.67)

# When
answers = self.answerpaper.answers.filter(question=question)
for answer in answers:
answer.set_marks(0)
self.answerpaper.update_marks()

# Then
self.assertEqual(self.answerpaper.marks_obtained, 0.75)
self.assertEqual(self.answerpaper.percent, 25)

# When
for answer in answers:
self.answer_wrong.set_marks(-1)
self.answer_wrong.set_marks(0)
self.answerpaper.update_marks()

# Then
self.assertEqual(self.answerpaper.marks_obtained, 0.75)
self.assertEqual(self.answerpaper.percent, 25)

# When
for answer in answers:
self.answer_wrong.set_marks(0.5)
self.answerpaper.update_marks()

# Then
self.assertEqual(self.answerpaper.marks_obtained, 1.375)
self.assertEqual(self.answerpaper.percent, 45.83)

# Given
question.negative_marks = 0
question.save()

# When
for answer in answers:
self.answer_wrong.set_marks(0)
self.answerpaper.update_marks()

# Then
self.assertEqual(self.answerpaper.marks_obtained, 1)
self.assertEqual(self.answerpaper.percent, 33.33)

def test_set_end_time(self):
current_time = timezone.now()
self.answerpaper.set_end_time(current_time)
Expand Down Expand Up @@ -1818,6 +1874,61 @@ def test_set_marks(self):
self.assertEqual(self.answer_wrong.marks, 0.5)
self.answer_wrong.set_marks(10.0)
self.assertEqual(self.answer_wrong.marks, 1.0)
self.answer_wrong.set_marks(0)
self.assertEqual(self.answer_wrong.marks, 0)

def test_set_marks_with_negative_marks(self):
# Given
question = self.answer_wrong.question
question.negative_marks = 25
question.save()

# When
self.answer_wrong.set_marks(1)
# Then
self.assertEqual(self.answer_wrong.marks, 1)

# When
self.answer_wrong.set_marks(0)
# Then
self.assertEqual(self.answer_wrong.marks, -0.25)

# When
self.answer_wrong.set_marks(0.5)
# Then
self.assertEqual(self.answer_wrong.marks, 0.375)

# Given
question.negative_marks = 0
# When
self.answer_wrong.set_marks(1)
# Then
self.assertEqual(self.answer_wrong.marks, 1.0)

# When
self.answer_wrong.set_marks(0)
# Then
self.assertEqual(self.answer_wrong.marks, 0)

def test_no_negative_marks_except_mcq_mcc(self):
# Given
code_question = self.answer1.question
marks = self.answer1.marks
code_question.negative_marks = 25
code_question.save()

# When
self.answer1.set_marks(1)
# Then
self.assertEqual(self.answer1.marks, 1)

# When
self.answer1.set_marks(0)
# Then
self.assertEqual(self.answer1.marks, 0)

self.answer1.marks = marks
self.answer1.save()

def test_get_latest_answer(self):
latest_answer = self.answerpaper.get_latest_answer(self.question1.id)
Expand Down
10 changes: 6 additions & 4 deletions yaksh/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -987,22 +987,24 @@ def _update_paper(request, uid, result):
paper = new_answer.answerpaper_set.first()

if result.get('success'):
new_answer.marks = (current_question.points * result['weight'] /
current_question.get_maximum_test_case_weight()) \
marks = (current_question.points * result['weight'] /
current_question.get_maximum_test_case_weight()) \
if current_question.partial_grading and \
current_question.type == 'code' or \
current_question.type == 'upload' else current_question.points
new_answer.set_marks(marks)
new_answer.correct = result.get('success')
error_message = None
new_answer.error = json.dumps(result.get('error'))
next_question = paper.add_completed_question(current_question.id)
else:
new_answer.marks = (current_question.points * result['weight'] /
current_question.get_maximum_test_case_weight()) \
marks = (current_question.points * result['weight'] /
current_question.get_maximum_test_case_weight()) \
if current_question.partial_grading and \
current_question.type == 'code' or \
current_question.type == 'upload' \
else 0
new_answer.set_marks(marks)
error_message = result.get('error') \
if current_question.type == 'code' or \
current_question.type == 'upload' \
Expand Down