fix: deprecate enable_grading_method_in_problems (#37811)

This commit is contained in:
Irtaza Akram
2026-01-29 12:11:51 +05:00
committed by GitHub
parent 497ffae5a7
commit 8b5a22ea3a
5 changed files with 149 additions and 279 deletions

View File

@@ -1347,15 +1347,6 @@ MARK_LIBRARY_CONTENT_BLOCK_COMPLETE_ON_VIEW = False
# .. toggle_tickets: 'https://github.com/open-craft/edx-platform/pull/429'
DISABLE_UNENROLLMENT = False
# .. toggle_name: ENABLE_GRADING_METHOD_IN_PROBLEMS
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False
# .. toggle_description: Enables the grading method feature in capa problems.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2024-03-22
# .. toggle_tickets: https://github.com/openedx/edx-platform/pull/33911
ENABLE_GRADING_METHOD_IN_PROBLEMS = False
# .. toggle_name: BADGES_ENABLED
# .. toggle_implementation: DjangoSetting
# .. toggle_default: False

View File

@@ -244,15 +244,6 @@ class LoncapaProblem: # pylint: disable=too-many-public-methods,too-many-instan
if extract_tree:
self.extracted_tree = self._extract_html(self.tree)
@property
def is_grading_method_enabled(self) -> bool:
"""
Returns whether the grading method feature is enabled. If the
feature is not enabled, the grading method field will not be shown in
Studio settings and the default grading method will be used.
"""
return settings.FEATURES.get("ENABLE_GRADING_METHOD_IN_PROBLEMS", False)
def make_xml_compatible(self, tree):
"""
Adjust tree xml in-place for compatibility before creating
@@ -503,7 +494,7 @@ class LoncapaProblem: # pylint: disable=too-many-public-methods,too-many-instan
and student_answers_history). The correct map will always be updated, depending on
the student answers. The student answers will always remain the same over time.
"""
oldcmap = correct_map if self.is_grading_method_enabled else self.correct_map
oldcmap = correct_map if correct_map is not None else self.correct_map
# start new with empty CorrectMap
newcmap = CorrectMap()
@@ -524,12 +515,7 @@ class LoncapaProblem: # pylint: disable=too-many-public-methods,too-many-instan
# submission that would not exist in the persisted "student_answers".
# If grading method is enabled, we need to pass each student answers and the
# correct map in the history fields.
if (
"filesubmission" in responder.allowed_inputfields and student_answers is not None
) or self.is_grading_method_enabled:
results = responder.evaluate_answers(student_answers, oldcmap)
else:
results = responder.evaluate_answers(self.student_answers, oldcmap)
results = responder.evaluate_answers(student_answers, oldcmap)
newcmap.update(results)
return newcmap

View File

@@ -8,8 +8,6 @@ from unittest.mock import MagicMock, patch
import ddt
import pytest
from django.conf import settings
from django.test import override_settings
from lxml import etree
from markupsafe import Markup
@@ -19,9 +17,6 @@ from xmodule.capa.responsetypes import LoncapaProblemError
from xmodule.capa.tests.helpers import new_loncapa_problem
from xmodule.capa.tests.test_util import UseUnsafeCodejail
FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS = settings.FEATURES.copy()
FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS["ENABLE_GRADING_METHOD_IN_PROBLEMS"] = True
@ddt.ddt
@UseUnsafeCodejail()
@@ -752,7 +747,6 @@ class CAPAProblemReportHelpersTest(unittest.TestCase):
# function can eventualy be serialized to json without issues.
assert isinstance(problem.get_question_answers()["1_solution_1"], str)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
def test_get_grade_from_current_answers(self):
"""
Verify that `responder.evaluate_answers` is called with `student_answers`
@@ -786,7 +780,6 @@ class CAPAProblemReportHelpersTest(unittest.TestCase):
self.assertDictEqual(result.get_dict(), correct_map.get_dict())
responder_mock.evaluate_answers.assert_called_once_with(student_answers, correct_map)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
def test_get_grade_from_current_answers_without_student_answers(self):
"""
Verify that `responder.evaluate_answers` is called with appropriate arguments.
@@ -820,7 +813,6 @@ class CAPAProblemReportHelpersTest(unittest.TestCase):
self.assertDictEqual(result.get_dict(), correct_map.get_dict())
responder_mock.evaluate_answers.assert_called_once_with(None, correct_map)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
def test_get_grade_from_current_answers_with_filesubmission(self):
"""
Verify that an exception is raised when `responder.evaluate_answers` is called

View File

@@ -498,8 +498,7 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
def grading_method_display_name(self) -> str | None:
"""
If the `ENABLE_GRADING_METHOD_IN_PROBLEMS` feature flag is enabled,
return the grading method, else return None.
Return the grading method
"""
_ = self.runtime.service(self, "i18n").gettext
display_name = {
@@ -508,18 +507,7 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
GRADING_METHOD.HIGHEST_SCORE: _("Highest Score"),
GRADING_METHOD.AVERAGE_SCORE: _("Average Score"),
}
if self.is_grading_method_enabled:
return display_name[self.grading_method]
return None
@property
def is_grading_method_enabled(self) -> bool:
"""
Returns whether the grading method feature is enabled. If the
feature is not enabled, the grading method field will not be shown in
Studio settings and the default grading method will be used.
"""
return settings.FEATURES.get("ENABLE_GRADING_METHOD_IN_PROBLEMS", False)
return display_name[self.grading_method]
@property
def debug(self):
@@ -569,8 +557,6 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
ProblemBlock.matlab_api_key,
]
)
if not self.is_grading_method_enabled:
non_editable_fields.append(ProblemBlock.grading_method)
return non_editable_fields
@property
@@ -1834,8 +1820,7 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
current_score = self.score_from_lcp(self.lcp)
self.score_history.append(current_score)
if self.is_grading_method_enabled:
current_score = self.get_score_with_grading_method(current_score)
current_score = self.get_score_with_grading_method(current_score)
self.set_score(current_score)
self.set_last_submission_time()
@@ -2309,18 +2294,6 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
"""
return self.score
def update_correctness(self):
"""
Updates correct map of the LCP.
Operates by creating a new correctness map based on the current
state of the LCP, and updating the old correctness map of the LCP.
"""
# Make sure that the attempt number is always at least 1 for grading purposes,
# even if the number of attempts have been reset and this problem is regraded.
self.lcp.context["attempt"] = max(self.attempts, 1)
new_correct_map = self.lcp.get_grade_from_current_answers(None)
self.lcp.correct_map.update(new_correct_map)
def update_correctness_list(self):
"""
Updates the `correct_map_history` and the `correct_map` of the LCP.
@@ -2343,13 +2316,9 @@ class _BuiltInProblemBlock( # pylint: disable=too-many-public-methods,too-many-
"""
Returns the score calculated from the current problem state.
If the grading method is enabled, the score is calculated based on the grading method.
The score is calculated based on the grading method.
"""
if self.is_grading_method_enabled:
return self.get_rescore_with_grading_method()
self.update_correctness()
new_score = self.lcp.calculate_score()
return Score(raw_earned=new_score["score"], raw_possible=new_score["total"])
return self.get_rescore_with_grading_method()
def calculate_score_list(self):
"""

View File

@@ -17,7 +17,6 @@ import pytest
import requests
import webob
from codejail.safe_exec import SafeExecException
from django.conf import settings
from django.test import override_settings
from django.utils.encoding import smart_str
from lxml import etree
@@ -45,9 +44,6 @@ from xmodule.tests import DATA_DIR
from ..capa_block import RANDOMIZATION, SHOWANSWER
from . import get_test_system
FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS = settings.FEATURES.copy()
FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS["ENABLE_GRADING_METHOD_IN_PROBLEMS"] = True
class CapaFactory:
"""
@@ -835,16 +831,14 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
# and that this was considered attempt number 2 for grading purposes
assert block.lcp.context["attempt"] == 2
@patch("xmodule.capa_block.ProblemBlock.get_score_with_grading_method")
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_with_grading_method_disable(
self, mock_html: Mock, mock_is_correct: Mock, mock_get_score: Mock
self, mock_html: Mock, mock_is_correct: Mock
):
"""
Test that the grading method is disabled by default. Then, the
`get_score_with_grading_method` method should not be called, and
always the last attempt as the final score.
Test that without a specific grading method, the score behaves as
standard (Last Attempt).
"""
block = CapaFactory.create(attempts=0, max_attempts=3)
mock_html.return_value = "Test HTML"
@@ -858,7 +852,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.attempts == 1
assert block.lcp.context["attempt"] == 1
assert block.score == Score(raw_earned=1, raw_possible=1)
mock_get_score.assert_not_called()
# Second Attempt
mock_is_correct.return_value = False
@@ -869,7 +862,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.attempts == 2
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=0, raw_possible=1)
mock_get_score.assert_not_called()
# Third Attempt
mock_is_correct.return_value = True
@@ -880,9 +872,7 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.attempts == 3
assert block.lcp.context["attempt"] == 3
assert block.score == Score(raw_earned=1, raw_possible=1)
mock_get_score.assert_not_called()
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_with_grading_method_enable(self, mock_html: Mock, mock_is_correct: Mock):
@@ -907,126 +897,110 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_grading_method_disable_to_enable(self, mock_html: Mock, mock_is_correct: Mock):
def test_submit_problem_grading_method_always_enabled(self, mock_html: Mock, mock_is_correct: Mock):
"""
Test when the grading method is disabled and then enabled.
Test problem submission when grading method is always enabled by default.
When the grading method is disabled, the final score is always the last attempt.
When the grading method is enabled, the final score is calculated according to the grading method.
"""
block = CapaFactory.create(attempts=0, max_attempts=4)
mock_html.return_value = "Test HTML"
# Disabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=False
):
# First Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
assert block.attempts == 1
assert block.lcp.context["attempt"] == 1
assert block.score == Score(raw_earned=1, raw_possible=1)
# Second Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.50"}
block.submit_problem(get_request_dict)
assert block.attempts == 2
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=0, raw_possible=1)
# Enabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=True
):
# Third Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.96"}
block.submit_problem(get_request_dict)
assert block.attempts == 3
assert block.lcp.context["attempt"] == 3
assert block.score == Score(raw_earned=0, raw_possible=1)
# Fourth Attempt
block.grading_method = "highest_score"
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.99"}
block.submit_problem(get_request_dict)
assert block.attempts == 4
assert block.lcp.context["attempt"] == 4
assert block.score == Score(raw_earned=1, raw_possible=1)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_grading_method_enable_to_disable(self, mock_html: Mock, mock_is_correct: Mock):
"""
Test when the grading method is enabled and then disabled.
When the grading method is enabled, the final score is calculated according to the grading method.
When the grading method is disabled, the final score is always the last attempt.
The final score is calculated according to the grading method, as grading
is now always enabled.
"""
block = CapaFactory.create(attempts=0, max_attempts=4, grading_method="highest_score")
mock_html.return_value = "Test HTML"
# Enabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=True
):
# First Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
# First Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
block.submit_problem(get_request_dict)
assert block.attempts == 1
assert block.lcp.context["attempt"] == 1
assert block.score == Score(raw_earned=1, raw_possible=1)
assert block.attempts == 1
assert block.lcp.context["attempt"] == 1
assert block.score == Score(raw_earned=1, raw_possible=1)
# Second Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.50"}
# Second Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.50"}
block.submit_problem(get_request_dict)
block.submit_problem(get_request_dict)
assert block.attempts == 2
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=1, raw_possible=1)
assert block.attempts == 2
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=1, raw_possible=1)
# Disabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=False
):
# Third Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.96"}
# Third Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.96"}
block.submit_problem(get_request_dict)
block.submit_problem(get_request_dict)
assert block.attempts == 3
assert block.lcp.context["attempt"] == 3
assert block.score == Score(raw_earned=0, raw_possible=1)
assert block.attempts == 3
assert block.lcp.context["attempt"] == 3
assert block.score == Score(raw_earned=1, raw_possible=1)
# Fourth Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
# Fourth Attempt
block.grading_method = "highest_score"
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.99"}
block.submit_problem(get_request_dict)
block.submit_problem(get_request_dict)
assert block.attempts == 4
assert block.lcp.context["attempt"] == 4
assert block.score == Score(raw_earned=1, raw_possible=1)
assert block.attempts == 4
assert block.lcp.context["attempt"] == 4
assert block.score == Score(raw_earned=1, raw_possible=1)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_grading_method_always_enabled_highest_score(self, mock_html: Mock, mock_is_correct: Mock):
"""
Test problem submission when grading method is always enabled by default
with 'highest_score' grading method.
The final score is calculated according to the grading method, as grading
is now always enabled.
"""
block = CapaFactory.create(attempts=0, max_attempts=4, grading_method="highest_score")
mock_html.return_value = "Test HTML"
# First Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
assert block.attempts == 1
assert block.lcp.context["attempt"] == 1
assert block.score == Score(raw_earned=1, raw_possible=1)
# Second Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.50"}
block.submit_problem(get_request_dict)
assert block.attempts == 2
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=1, raw_possible=1)
# Third Attempt
mock_is_correct.return_value = False
get_request_dict = {CapaFactory.input_key(): "3.96"}
block.submit_problem(get_request_dict)
assert block.attempts == 3
assert block.lcp.context["attempt"] == 3
assert block.score == Score(raw_earned=1, raw_possible=1)
# Fourth Attempt
mock_is_correct.return_value = True
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
assert block.attempts == 4
assert block.lcp.context["attempt"] == 4
assert block.score == Score(raw_earned=1, raw_possible=1)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_correct_last_score(self, mock_html: Mock, mock_is_correct: Mock):
@@ -1060,7 +1034,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=0, raw_possible=1)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_correct_highest_score(self, mock_html: Mock, mock_is_correct: Mock):
@@ -1093,7 +1066,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=1, raw_possible=1)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_correct_first_score(self, mock_html: Mock, mock_is_correct: Mock):
@@ -1126,7 +1098,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
assert block.lcp.context["attempt"] == 2
assert block.score == Score(raw_earned=0, raw_possible=1)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa.correctmap.CorrectMap.is_correct")
@patch("xmodule.capa_block.ProblemBlock.get_problem_html")
def test_submit_problem_correct_average_score(self, mock_html: Mock, mock_is_correct: Mock):
@@ -1692,21 +1663,17 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
# and that this is treated as the first attempt for grading purposes
assert block.lcp.context["attempt"] == 1
@patch("xmodule.capa_block.ProblemBlock.get_rescore_with_grading_method")
def test_rescore_problem_with_grading_method_disable(self, mock_get_rescore: Mock):
def test_rescore_problem_with_grading_method_disable(self):
"""
Test the rescore method with grading method disabled.
In this case, the rescore method should not call `get_rescore_with_grading_method` method.
Test the rescore method with grading method logic enabled by default.
"""
block = CapaFactory.create(attempts=0, done=True)
block = CapaFactory.create(attempts=0, done=True, grading_method="highest_score")
block.rescore(only_if_higher=False)
assert block.attempts == 0
assert block.lcp.context["attempt"] == 1
mock_get_rescore.assert_not_called()
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
def test_rescore_problem_with_grading_method_enable(self):
"""
Test the rescore method with grading method enabled.
@@ -1725,12 +1692,12 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
mock_get_rescore.assert_called()
@patch("xmodule.capa_block.ProblemBlock.publish_grade")
def test_rescore_problem_grading_method_disable_to_enable(self, mock_publish_grade: Mock):
def test_rescore_problem_grading_method_always_enabled(self, mock_publish_grade: Mock):
"""
Test the rescore method the grading method is disabled and then enabled.
Test the rescore method when grading method is always enabled by default.
When the grading method is disabled, the final score is always the last score.
When the grading method is enabled, the final score is the score based on the grading method.
The final score is calculated according to the grading method, as grading
is now always enabled.
"""
block = CapaFactory.create(attempts=0, max_attempts=3)
@@ -1743,54 +1710,39 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
# Disabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=False
):
# Score is the last score
assert block.score == Score(raw_earned=1, raw_possible=1)
# Score is calculated according to the grading method
assert block.score == Score(raw_earned=1, raw_possible=1)
block.rescore(only_if_higher=False)
block.rescore(only_if_higher=False)
# Still Score is the last score
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
# Still Score is the last score
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
# Rescore with different grading methods
block.grading_method = "first_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=0, raw_possible=1), only_if_higher=False)
# Enabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=True
):
with patch(
"xmodule.capa.capa_problem.LoncapaProblem.is_grading_method_enabled",
new_callable=PropertyMock,
return_value=True,
):
# Change grading method to 'first_score'
block.grading_method = "first_score"
block.rescore(only_if_higher=False)
# Change grading method to 'highest_score'
block.grading_method = "highest_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=0, raw_possible=1), only_if_higher=False)
# Change grading method to 'average_score'
block.grading_method = "average_score"
block.rescore(only_if_higher=False)
# Change grading method to 'highest_score'
block.grading_method = "highest_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
# Change grading method to 'average_score'
block.grading_method = "average_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(
score=Score(raw_earned=0.33, raw_possible=1), only_if_higher=False
)
mock_publish_grade.assert_called_with(
score=Score(raw_earned=0.33, raw_possible=1), only_if_higher=False
)
@patch("xmodule.capa_block.ProblemBlock.publish_grade")
def test_rescore_problem_grading_method_enable_to_disable(self, mock_publish_grade: Mock):
def test_rescore_problem_grading_method_always_enabled_with_various_methods(self, mock_publish_grade: Mock):
"""
Test the rescore method the grading method is enabled and then disabled.
Test the rescore method when grading method is always enabled by default
with different grading methods.
When the grading method is enabled, the final score is the score based on the grading method.
When the grading method is disabled, the final score is always the last score.
The final score is calculated according to the grading method, as grading
is now always enabled.
"""
block = CapaFactory.create(attempts=0, max_attempts=3)
@@ -1803,48 +1755,28 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
get_request_dict = {CapaFactory.input_key(): "3.14"}
block.submit_problem(get_request_dict)
# Enabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=True
):
with patch(
"xmodule.capa.capa_problem.LoncapaProblem.is_grading_method_enabled",
new_callable=PropertyMock,
return_value=True,
):
# Grading method is 'last_score'
assert block.grading_method == "last_score"
assert block.score == Score(raw_earned=1, raw_possible=1)
# Grading method is 'last_score' by default
assert block.grading_method == "last_score"
assert block.score == Score(raw_earned=1, raw_possible=1)
# Change grading method to 'first_score'
block.grading_method = "first_score"
block.rescore(only_if_higher=False)
# Change grading method to 'first_score'
block.grading_method = "first_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=0, raw_possible=1), only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=0, raw_possible=1), only_if_higher=False)
# Change grading method to 'highest_score'
block.grading_method = "highest_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
# Change grading method to 'highest_score'
block.grading_method = "highest_score"
block.rescore(only_if_higher=False)
# Change grading method to 'average_score'
block.grading_method = "average_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=0.33, raw_possible=1), only_if_higher=False)
mock_publish_grade.assert_called_with(score=Score(raw_earned=1, raw_possible=1), only_if_higher=False)
block.rescore(only_if_higher=False)
assert block.score == Score(raw_earned=1, raw_possible=1)
# Change grading method to 'average_score'
block.grading_method = "average_score"
block.rescore(only_if_higher=False)
mock_publish_grade.assert_called_with(
score=Score(raw_earned=0.33, raw_possible=1), only_if_higher=False
)
# Disabled grading method
with patch(
"xmodule.capa_block.ProblemBlock.is_grading_method_enabled", new_callable=PropertyMock, return_value=False
):
block.rescore(only_if_higher=False)
# The score is the last score
assert block.score == Score(raw_earned=1, raw_possible=1)
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
@patch("xmodule.capa_block.ProblemBlock.publish_grade")
def test_rescore_problem_update_grading_method(self, mock_publish_grade: Mock):
"""
@@ -1976,7 +1908,6 @@ class ProblemBlockTest(unittest.TestCase): # pylint: disable=too-many-public-me
self.assertEqual(block.lcp.context["attempt"], 1)
block.lcp.get_grade_from_current_answers.assert_not_called()
@override_settings(FEATURES=FEATURES_WITH_GRADING_METHOD_IN_PROBLEMS)
def test_get_rescore_with_grading_method(self):
"""
Test that the `get_rescore_with_grading_method` method returns the correct score.
@@ -4046,9 +3977,10 @@ class ProblemCheckTrackingTest(unittest.TestCase):
"""Verify tracking data for file submission and custom response inputs."""
fnames = ["prog1.py", "prog2.py", "prog3.py"]
fpaths = [os.path.join(DATA_DIR, "capa", fname) for fname in fnames]
fileobjs = [open(fpath, encoding="utf-8") for fpath in fpaths] # pylint: disable=consider-using-with
for fileobj in fileobjs:
self.addCleanup(fileobj.close)
fileobjs = []
for fpath in fpaths:
with open(fpath, encoding="utf-8") as f:
fileobjs.append(f.read())
factory = CapaFactoryWithFiles
block = factory.create()
@@ -4065,7 +3997,7 @@ class ProblemCheckTrackingTest(unittest.TestCase):
assert event["submission"] == {
factory.answer_key(2): {
"question": "",
"answer": fpaths,
"answer": fileobjs,
"response_type": "coderesponse",
"input_type": "filesubmission",
"correct": False,