From 77eb6e8254d4f5cf7e1f66940a7dd283bf1be1be Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Fri, 9 May 2025 14:29:27 -0600 Subject: [PATCH 1/7] fix: use geom_type instead of type for Shapely objects --- xmodule/capa/responsetypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xmodule/capa/responsetypes.py b/xmodule/capa/responsetypes.py index 6155fe926e..7521adf0cf 100644 --- a/xmodule/capa/responsetypes.py +++ b/xmodule/capa/responsetypes.py @@ -3399,7 +3399,7 @@ class ImageResponse(LoncapaResponse): parsed_region = [parsed_region] for region in parsed_region: polygon = MultiPoint(region).convex_hull - if (polygon.type == 'Polygon' and + if (polygon.geom_type == 'Polygon' and polygon.contains(Point(ans_x, ans_y))): correct_map.set(aid, 'correct') break From 204a1eb438d3042d70fbdb79fbc59361e42d982f Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Fri, 9 May 2025 16:03:30 -0600 Subject: [PATCH 2/7] test: test_capa_system helper function replaced by mock_capa_system --- xmodule/capa/tests/helpers.py | 5 +- xmodule/capa/tests/test_answer_pool.py | 4 +- xmodule/capa/tests/test_customrender.py | 8 +-- xmodule/capa/tests/test_html_render.py | 6 +- xmodule/capa/tests/test_inputtypes.py | 64 ++++++++++---------- xmodule/capa/tests/test_responsetypes.py | 4 +- xmodule/capa/tests/test_shuffle.py | 4 +- xmodule/capa/tests/test_targeted_feedback.py | 4 +- xmodule/capa/tests/test_util.py | 4 +- 9 files changed, 51 insertions(+), 52 deletions(-) diff --git a/xmodule/capa/tests/helpers.py b/xmodule/capa/tests/helpers.py index 540d459c5a..9708f42625 100644 --- a/xmodule/capa/tests/helpers.py +++ b/xmodule/capa/tests/helpers.py @@ -58,10 +58,9 @@ class StubXQueueService: return dispatch -def test_capa_system(render_template=None): +def mock_capa_system(render_template=None): """ Construct a mock LoncapaSystem instance. - """ the_system = Mock( spec=LoncapaSystem, @@ -102,7 +101,7 @@ def mock_capa_block(): def new_loncapa_problem(xml, problem_id='1', capa_system=None, seed=723, use_capa_render_template=False): """Construct a `LoncapaProblem` suitable for unit tests.""" render_template = capa_render_template if use_capa_render_template else None - return LoncapaProblem(xml, id=problem_id, seed=seed, capa_system=capa_system or test_capa_system(render_template), + return LoncapaProblem(xml, id=problem_id, seed=seed, capa_system=capa_system or mock_capa_system(render_template), capa_block=mock_capa_block()) diff --git a/xmodule/capa/tests/test_answer_pool.py b/xmodule/capa/tests/test_answer_pool.py index d11df3a203..28de918580 100644 --- a/xmodule/capa/tests/test_answer_pool.py +++ b/xmodule/capa/tests/test_answer_pool.py @@ -8,7 +8,7 @@ import textwrap import unittest from xmodule.capa.responsetypes import LoncapaProblemError -from xmodule.capa.tests.helpers import new_loncapa_problem, test_capa_system +from xmodule.capa.tests.helpers import new_loncapa_problem, mock_capa_system class CapaAnswerPoolTest(unittest.TestCase): @@ -16,7 +16,7 @@ class CapaAnswerPoolTest(unittest.TestCase): def setUp(self): super(CapaAnswerPoolTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.system = test_capa_system() + self.system = mock_capa_system() # XML problem setup used by a few tests. common_question_xml = textwrap.dedent(""" diff --git a/xmodule/capa/tests/test_customrender.py b/xmodule/capa/tests/test_customrender.py index 95dc7a68a0..0a16f764fe 100644 --- a/xmodule/capa/tests/test_customrender.py +++ b/xmodule/capa/tests/test_customrender.py @@ -6,7 +6,7 @@ import xml.sax.saxutils as saxutils from lxml import etree from xmodule.capa import customrender -from xmodule.capa.tests.helpers import test_capa_system +from xmodule.capa.tests.helpers import mock_capa_system # just a handy shortcut lookup_tag = customrender.registry.get_class_for_tag @@ -30,7 +30,7 @@ class HelperTest(unittest.TestCase): ''' def check(self, d): - xml = etree.XML(test_capa_system().render_template('blah', d)) + xml = etree.XML(mock_capa_system().render_template('blah', d)) assert d == extract_context(xml) def test_extract_context(self): @@ -50,7 +50,7 @@ class SolutionRenderTest(unittest.TestCase): xml_str = """{s}""".format(s=solution) element = etree.fromstring(xml_str) - renderer = lookup_tag('solution')(test_capa_system(), element) + renderer = lookup_tag('solution')(mock_capa_system(), element) assert renderer.id == 'solution_12' @@ -69,7 +69,7 @@ class MathRenderTest(unittest.TestCase): xml_str = """{tex}""".format(tex=latex_in) element = etree.fromstring(xml_str) - renderer = lookup_tag('math')(test_capa_system(), element) + renderer = lookup_tag('math')(mock_capa_system(), element) assert renderer.mathstr == mathjax_out diff --git a/xmodule/capa/tests/test_html_render.py b/xmodule/capa/tests/test_html_render.py index 2a9a786771..0af5f1198e 100644 --- a/xmodule/capa/tests/test_html_render.py +++ b/xmodule/capa/tests/test_html_render.py @@ -10,7 +10,7 @@ from unittest import mock import ddt from lxml import etree -from xmodule.capa.tests.helpers import new_loncapa_problem, test_capa_system +from xmodule.capa.tests.helpers import new_loncapa_problem, mock_capa_system from openedx.core.djangolib.markup import HTML from .response_xml_factory import CustomResponseXMLFactory, StringResponseXMLFactory @@ -24,7 +24,7 @@ class CapaHtmlRenderTest(unittest.TestCase): def setUp(self): super(CapaHtmlRenderTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.capa_system = test_capa_system() + self.capa_system = mock_capa_system() def test_blank_problem(self): """ @@ -148,7 +148,7 @@ class CapaHtmlRenderTest(unittest.TestCase): xml_str = StringResponseXMLFactory().build_xml(**kwargs) # Mock out the template renderer - the_system = test_capa_system() + the_system = mock_capa_system() the_system.render_template = mock.Mock() the_system.render_template.return_value = "
Input Template Render
" diff --git a/xmodule/capa/tests/test_inputtypes.py b/xmodule/capa/tests/test_inputtypes.py index 4e14bc42b7..523f303b45 100644 --- a/xmodule/capa/tests/test_inputtypes.py +++ b/xmodule/capa/tests/test_inputtypes.py @@ -34,7 +34,7 @@ from six.moves import zip from xmodule.capa import inputtypes from xmodule.capa.checker import DemoSystem -from xmodule.capa.tests.helpers import test_capa_system +from xmodule.capa.tests.helpers import mock_capa_system from xmodule.capa.xqueue_interface import XQUEUE_TIMEOUT from openedx.core.djangolib.markup import HTML @@ -72,7 +72,7 @@ class OptionInputTest(unittest.TestCase): 'default_option_text': 'Select an option', 'response_data': RESPONSE_DATA } - option_input = lookup_tag('optioninput')(test_capa_system(), element, state) + option_input = lookup_tag('optioninput')(mock_capa_system(), element, state) context = option_input._get_render_context() # pylint: disable=protected-access prob_id = 'sky_input' @@ -138,7 +138,7 @@ class ChoiceGroupTest(unittest.TestCase): 'response_data': RESPONSE_DATA } - the_input = lookup_tag(tag)(test_capa_system(), element, state) + the_input = lookup_tag(tag)(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access @@ -233,7 +233,7 @@ class JSInputTest(unittest.TestCase): 'value': 103, 'response_data': RESPONSE_DATA } - the_input = lookup_tag('jsinput')(test_capa_system(), element, state) + the_input = lookup_tag('jsinput')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access @@ -270,7 +270,7 @@ class TextLineTest(unittest.TestCase): 'value': 'BumbleBee', 'response_data': RESPONSE_DATA } - the_input = lookup_tag('textline')(test_capa_system(), element, state) + the_input = lookup_tag('textline')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -306,7 +306,7 @@ class TextLineTest(unittest.TestCase): 'value': 'BumbleBee', 'response_data': RESPONSE_DATA } - the_input = lookup_tag('textline')(test_capa_system(), element, state) + the_input = lookup_tag('textline')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -354,7 +354,7 @@ class TextLineTest(unittest.TestCase): 'value': 'BumbleBee', 'response_data': RESPONSE_DATA } - the_input = lookup_tag('textline')(test_capa_system(), element, state) + the_input = lookup_tag('textline')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -400,7 +400,7 @@ class FileSubmissionTest(unittest.TestCase): 'response_data': RESPONSE_DATA } input_class = lookup_tag('filesubmission') - the_input = input_class(test_capa_system(), element, state) + the_input = input_class(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -450,7 +450,7 @@ class CodeInputTest(unittest.TestCase): } input_class = lookup_tag('codeinput') - the_input = input_class(test_capa_system(), element, state) + the_input = input_class(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -510,7 +510,7 @@ class MatlabTest(unittest.TestCase): } self.input_class = lookup_tag('matlabinput') - self.the_input = self.input_class(test_capa_system(), elt, state) + self.the_input = self.input_class(mock_capa_system(), elt, state) def test_rendering(self): context = self.the_input._get_render_context() # pylint: disable=protected-access @@ -547,7 +547,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' expected = { @@ -582,7 +582,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) prob_id = 'prob_1_2' - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=protected-access expected = { 'STATIC_URL': '/dummy-static/', @@ -616,7 +616,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) prob_id = 'prob_1_2' - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=protected-access expected = { 'STATIC_URL': '/dummy-static/', @@ -668,7 +668,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) inner_msg = 'hello!' queue_msg = json.dumps({'msg': inner_msg}) @@ -687,7 +687,7 @@ class MatlabTest(unittest.TestCase): 'feedback': {'message': '3'}, } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) inner_msg = 'hello!' queue_msg = json.dumps({'msg': inner_msg}) @@ -702,7 +702,7 @@ class MatlabTest(unittest.TestCase): state = {'input_state': {'queuestate': 'queued', 'queuetime': 5}} elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) assert the_input.status == 'queued' @patch('xmodule.capa.inputtypes.time.time', return_value=45) @@ -711,7 +711,7 @@ class MatlabTest(unittest.TestCase): state = {'input_state': {'queuestate': 'queued', 'queuetime': 5}} elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) assert the_input.status == 'unsubmitted' assert the_input.msg == 'No response from Xqueue within {} seconds. Aborted.'.format(XQUEUE_TIMEOUT) @@ -723,7 +723,7 @@ class MatlabTest(unittest.TestCase): state = {'input_state': {'queuestate': 'queued'}} elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) assert the_input.status == 'unsubmitted' def test_matlab_api_key(self): @@ -731,7 +731,7 @@ class MatlabTest(unittest.TestCase): Test that api_key ends up in the xqueue payload """ elt = etree.fromstring(self.xml) - system = test_capa_system() + system = mock_capa_system() system.matlab_api_key = 'test_api_key' the_input = lookup_tag('matlabinput')(system, elt, {}) @@ -852,7 +852,7 @@ class MatlabTest(unittest.TestCase): } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) context = the_input._get_render_context() # pylint: disable=protected-access self.maxDiff = None expected = fromstring('\n
if Conditionally execute statements.\nThe general form of the if statement is\n\n if expression\n statements\n ELSEIF expression\n statements\n ELSE\n statements\n END\n\nThe statements are executed if the real part of the expression \nhas all non-zero elements. The ELSE and ELSEIF parts are optional.\nZero or more ELSEIF parts can be used as well as nested if\'s.\nThe expression is usually of the form expr rop expr where \nrop is ==, <, >, <=, >=, or ~=.\n\n\nExample\n if I == J\n A(I,J) = 2;\n elseif abs(I-J) == 1\n A(I,J) = -1;\n else\n A(I,J) = 0;\n end\n\nSee also relop, else, elseif, end, for, while, switch.\n\nReference page in Help browser\n doc if\n\n
    \n') # lint-amnesty, pylint: disable=line-too-long @@ -901,7 +901,7 @@ class MatlabTest(unittest.TestCase): 'status': 'queued', } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) assert the_input.queue_msg == queue_msg def test_matlab_queue_message_not_allowed_tag(self): @@ -915,7 +915,7 @@ class MatlabTest(unittest.TestCase): 'status': 'queued', } elt = etree.fromstring(self.xml) - the_input = self.input_class(test_capa_system(), elt, state) + the_input = self.input_class(mock_capa_system(), elt, state) expected = "" assert the_input.queue_msg == expected @@ -974,7 +974,7 @@ class SchematicTest(unittest.TestCase): 'response_data': RESPONSE_DATA } - the_input = lookup_tag('schematic')(test_capa_system(), element, state) + the_input = lookup_tag('schematic')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -1021,7 +1021,7 @@ class ImageInputTest(unittest.TestCase): 'response_data': RESPONSE_DATA } - the_input = lookup_tag('imageinput')(test_capa_system(), element, state) + the_input = lookup_tag('imageinput')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -1079,7 +1079,7 @@ class CrystallographyTest(unittest.TestCase): 'response_data': RESPONSE_DATA } - the_input = lookup_tag('crystallography')(test_capa_system(), element, state) + the_input = lookup_tag('crystallography')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -1124,7 +1124,7 @@ class VseprTest(unittest.TestCase): 'response_data': RESPONSE_DATA } - the_input = lookup_tag('vsepr_input')(test_capa_system(), element, state) + the_input = lookup_tag('vsepr_input')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -1160,7 +1160,7 @@ class ChemicalEquationTest(unittest.TestCase): 'value': 'H2OYeah', 'response_data': RESPONSE_DATA } - self.the_input = lookup_tag('chemicalequationinput')(test_capa_system(), element, state) + self.the_input = lookup_tag('chemicalequationinput')(mock_capa_system(), element, state) def test_rendering(self): """ @@ -1255,7 +1255,7 @@ class FormulaEquationTest(unittest.TestCase): 'value': 'x^2+1/2', 'response_data': RESPONSE_DATA } - self.the_input = lookup_tag('formulaequationinput')(test_capa_system(), element, state) + self.the_input = lookup_tag('formulaequationinput')(mock_capa_system(), element, state) def test_rendering(self): """ @@ -1305,7 +1305,7 @@ class FormulaEquationTest(unittest.TestCase): 'value': 'x^2+1/2', 'response_data': RESPONSE_DATA } - the_input = lookup_tag('formulaequationinput')(test_capa_system(), element, state) + the_input = lookup_tag('formulaequationinput')(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'prob_1_2' @@ -1440,7 +1440,7 @@ class DragAndDropTest(unittest.TestCase): ] } - the_input = lookup_tag('drag_and_drop_input')(test_capa_system(), element, state) + the_input = lookup_tag('drag_and_drop_input')(mock_capa_system(), element, state) prob_id = 'prob_1_2' context = the_input._get_render_context() # pylint: disable=protected-access expected = { @@ -1494,7 +1494,7 @@ class AnnotationInputTest(unittest.TestCase): tag = 'annotationinput' - the_input = lookup_tag(tag)(test_capa_system(), element, state) + the_input = lookup_tag(tag)(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access prob_id = 'annotation_input' @@ -1588,7 +1588,7 @@ class TestChoiceText(unittest.TestCase): 'describedby_html': DESCRIBEDBY.format(status_id=prob_id) } expected.update(state) - the_input = lookup_tag(tag)(test_capa_system(), element, state) + the_input = lookup_tag(tag)(mock_capa_system(), element, state) context = the_input._get_render_context() # pylint: disable=protected-access assert context == expected diff --git a/xmodule/capa/tests/test_responsetypes.py b/xmodule/capa/tests/test_responsetypes.py index e8df8894c7..fa0c97fb15 100644 --- a/xmodule/capa/tests/test_responsetypes.py +++ b/xmodule/capa/tests/test_responsetypes.py @@ -20,7 +20,7 @@ from pytz import UTC from xmodule.capa.correctmap import CorrectMap from xmodule.capa.responsetypes import LoncapaProblemError, ResponseError, StudentInputError -from xmodule.capa.tests.helpers import load_fixture, new_loncapa_problem, test_capa_system +from xmodule.capa.tests.helpers import load_fixture, new_loncapa_problem, mock_capa_system from xmodule.capa.tests.response_xml_factory import ( AnnotationResponseXMLFactory, ChoiceResponseXMLFactory, @@ -2326,7 +2326,7 @@ class CustomResponseTest(ResponseTest): # pylint: disable=missing-class-docstri import my_helper num = my_helper.seventeen() """) - capa_system = test_capa_system() + capa_system = mock_capa_system() capa_system.get_python_lib_zip = lambda: zipstring.getvalue() # lint-amnesty, pylint: disable=unnecessary-lambda problem = self.build_problem(script=script, capa_system=capa_system) assert problem.context['num'] == 17 diff --git a/xmodule/capa/tests/test_shuffle.py b/xmodule/capa/tests/test_shuffle.py index cc0242826a..d7ce39f008 100644 --- a/xmodule/capa/tests/test_shuffle.py +++ b/xmodule/capa/tests/test_shuffle.py @@ -5,7 +5,7 @@ import textwrap import unittest from xmodule.capa.responsetypes import LoncapaProblemError -from xmodule.capa.tests.helpers import new_loncapa_problem, test_capa_system +from xmodule.capa.tests.helpers import new_loncapa_problem, mock_capa_system class CapaShuffleTest(unittest.TestCase): @@ -13,7 +13,7 @@ class CapaShuffleTest(unittest.TestCase): def setUp(self): super(CapaShuffleTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.system = test_capa_system() + self.system = mock_capa_system() def test_shuffle_4_choices(self): xml_str = textwrap.dedent(""" diff --git a/xmodule/capa/tests/test_targeted_feedback.py b/xmodule/capa/tests/test_targeted_feedback.py index cae4763123..0ec13b4962 100644 --- a/xmodule/capa/tests/test_targeted_feedback.py +++ b/xmodule/capa/tests/test_targeted_feedback.py @@ -6,7 +6,7 @@ i.e. those with the element import textwrap import unittest -from xmodule.capa.tests.helpers import load_fixture, new_loncapa_problem, test_capa_system +from xmodule.capa.tests.helpers import load_fixture, new_loncapa_problem, mock_capa_system class CapaTargetedFeedbackTest(unittest.TestCase): @@ -16,7 +16,7 @@ class CapaTargetedFeedbackTest(unittest.TestCase): def setUp(self): super(CapaTargetedFeedbackTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.system = test_capa_system() + self.system = mock_capa_system() def test_no_targeted_feedback(self): xml_str = textwrap.dedent(""" diff --git a/xmodule/capa/tests/test_util.py b/xmodule/capa/tests/test_util.py index dc356d39db..92bc039cfa 100644 --- a/xmodule/capa/tests/test_util.py +++ b/xmodule/capa/tests/test_util.py @@ -9,7 +9,7 @@ import unittest import ddt from lxml import etree -from xmodule.capa.tests.helpers import test_capa_system +from xmodule.capa.tests.helpers import mock_capa_system from xmodule.capa.util import ( compare_with_tolerance, contextualize_text, @@ -25,7 +25,7 @@ class UtilTest(unittest.TestCase): def setUp(self): super(UtilTest, self).setUp() # lint-amnesty, pylint: disable=super-with-arguments - self.system = test_capa_system() + self.system = mock_capa_system() def test_compare_with_tolerance(self): # lint-amnesty, pylint: disable=too-many-statements # Test default tolerance '0.001%' (it is relative) From 9f244292c2b8ef4868f7836e4e6b156f42ef4610 Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Fri, 9 May 2025 16:49:27 -0600 Subject: [PATCH 3/7] fix: handled deprecation warnings in xblock_django app --- common/djangoapps/xblock_django/tests/test_commands.py | 2 +- common/djangoapps/xblock_django/translation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/common/djangoapps/xblock_django/tests/test_commands.py b/common/djangoapps/xblock_django/tests/test_commands.py index 322ee9384f..8a558b6beb 100644 --- a/common/djangoapps/xblock_django/tests/test_commands.py +++ b/common/djangoapps/xblock_django/tests/test_commands.py @@ -64,7 +64,7 @@ def test_compile_xblock_translations(tmp_translations_dir): 'msgfmt', '--check-format', '-o', str(po_file.with_suffix('.mo')), str(po_file), ], 'Compiles the .po files' - js_file_text = get_javascript_i18n_file_path('done', 'tr').text() + js_file_text = get_javascript_i18n_file_path('done', 'tr').read_text() assert 'Merhaba' in js_file_text, 'Ensures the JavaScript catalog is compiled' assert 'TestingDoneXBlockI18n' in js_file_text, 'Ensures the namespace is used' assert 'gettext' in js_file_text, 'Ensures the gettext function is defined' diff --git a/common/djangoapps/xblock_django/translation.py b/common/djangoapps/xblock_django/translation.py index 72726221aa..66f9642682 100644 --- a/common/djangoapps/xblock_django/translation.py +++ b/common/djangoapps/xblock_django/translation.py @@ -101,7 +101,7 @@ def compile_xblock_js_messages(): xblock_conf_locale_dir = xmodule_api.get_python_locale_root() / xblock_module i18n_js_namespace = xblock_class.get_i18n_js_namespace() - for locale_dir in xblock_conf_locale_dir.listdir(): + for locale_dir in xblock_conf_locale_dir.iterdir(): locale_code = str(locale_dir.basename()) locale_messages_dir = locale_dir / 'LC_MESSAGES' js_translations_domain = None From a51f87be43832d772756c7c21d1e7f77c526f81b Mon Sep 17 00:00:00 2001 From: Daniel Wong Date: Tue, 13 May 2025 15:35:28 -0600 Subject: [PATCH 4/7] test: removing unused tests related to masking --- xmodule/tests/test_capa_block.py | 42 -------------------------------- 1 file changed, 42 deletions(-) diff --git a/xmodule/tests/test_capa_block.py b/xmodule/tests/test_capa_block.py index 2801430320..b48aa10ef8 100644 --- a/xmodule/tests/test_capa_block.py +++ b/xmodule/tests/test_capa_block.py @@ -2798,48 +2798,6 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss ('shuffle', ['choice_3', 'choice_1', 'choice_2', 'choice_0']) assert event_info['success'] == 'correct' - @unittest.skip("masking temporarily disabled") - def test_save_unmask(self): - """On problem save, unmasked data should appear on publish.""" - block = CapaFactory.create(xml=self.common_shuffle_xml) - with patch.object(block.runtime, 'publish') as mock_publish: - get_request_dict = {CapaFactory.input_key(): 'mask_0'} - block.save_problem(get_request_dict) - mock_call = mock_publish.mock_calls[0] - event_info = mock_call[1][1] - assert event_info['answers'][CapaFactory.answer_key()] == 'choice_2' - assert event_info['permutation'][CapaFactory.answer_key()] is not None - - @unittest.skip("masking temporarily disabled") - def test_reset_unmask(self): - """On problem reset, unmask names should appear publish.""" - block = CapaFactory.create(xml=self.common_shuffle_xml) - get_request_dict = {CapaFactory.input_key(): 'mask_0'} - block.submit_problem(get_request_dict) - # On reset, 'old_state' should use unmasked names - with patch.object(block.runtime, 'publish') as mock_publish: - block.reset_problem(None) - mock_call = mock_publish.mock_calls[0] - event_info = mock_call[1][1] - assert mock_call[1][0] == 'reset_problem' - assert event_info['old_state']['student_answers'][CapaFactory.answer_key()] == 'choice_2' - assert event_info['permutation'][CapaFactory.answer_key()] is not None - - @unittest.skip("masking temporarily disabled") - def test_rescore_unmask(self): - """On problem rescore, unmasked names should appear on publish.""" - block = CapaFactory.create(xml=self.common_shuffle_xml) - get_request_dict = {CapaFactory.input_key(): 'mask_0'} - block.submit_problem(get_request_dict) - # On rescore, state/student_answers should use unmasked names - with patch.object(block.runtime, 'publish') as mock_publish: - block.rescore_problem(only_if_higher=False) # lint-amnesty, pylint: disable=no-member - mock_call = mock_publish.mock_calls[0] - event_info = mock_call[1][1] - assert mock_call[1][0] == 'problem_rescore' - assert event_info['state']['student_answers'][CapaFactory.answer_key()] == 'choice_2' - assert event_info['permutation'][CapaFactory.answer_key()] is not None - def test_check_unmask_answerpool(self): """Check answer-pool question publish uses unmasked names""" xml = textwrap.dedent(""" From 1679e1ae4d153f5526f40862e4f9882dacfbc2a7 Mon Sep 17 00:00:00 2001 From: Ram Chandra Bhavirisetty Date: Fri, 16 May 2025 06:18:28 +0000 Subject: [PATCH 5/7] chore: update event-tracking constraint to allow newer versions --- requirements/constraints.txt | 7 ------- requirements/edx/base.txt | 3 +-- requirements/edx/development.txt | 3 +-- requirements/edx/doc.txt | 3 +-- requirements/edx/testing.txt | 3 +-- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/requirements/constraints.txt b/requirements/constraints.txt index 393e8a1482..6959d84815 100644 --- a/requirements/constraints.txt +++ b/requirements/constraints.txt @@ -57,13 +57,6 @@ django-stubs<6 # for them. edx-enterprise==5.13.7 -# Date: 2024-07-26 -# To override the constraint of edx-lint -# This can be removed once https://github.com/openedx/edx-platform/issues/34586 is resolved -# and the upstream constraint in edx-lint has been removed. -# Issue for unpinning: https://github.com/openedx/edx-platform/issues/35273 -event-tracking==3.0.0 - # Date: 2023-07-26 # Our legacy Sass code is incompatible with anything except this ancient libsass version. # Here is a ticket to upgrade, but it's of debatable importance given that we are rapidly moving diff --git a/requirements/edx/base.txt b/requirements/edx/base.txt index f5b94a806b..c17f7b8c5f 100644 --- a/requirements/edx/base.txt +++ b/requirements/edx/base.txt @@ -546,9 +546,8 @@ enmerkar==0.7.1 # via enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/kernel.in -event-tracking==3.0.0 +event-tracking==3.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/kernel.in # edx-completion # edx-proctoring diff --git a/requirements/edx/development.txt b/requirements/edx/development.txt index b64266f5ff..0919fc4c29 100644 --- a/requirements/edx/development.txt +++ b/requirements/edx/development.txt @@ -860,9 +860,8 @@ enmerkar-underscore==2.4.0 # via # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt -event-tracking==3.0.0 +event-tracking==3.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/doc.txt # -r requirements/edx/testing.txt # edx-completion diff --git a/requirements/edx/doc.txt b/requirements/edx/doc.txt index d3083fc7ad..c76d1061da 100644 --- a/requirements/edx/doc.txt +++ b/requirements/edx/doc.txt @@ -637,9 +637,8 @@ enmerkar==0.7.1 # enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/base.txt -event-tracking==3.0.0 +event-tracking==3.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-completion # edx-proctoring diff --git a/requirements/edx/testing.txt b/requirements/edx/testing.txt index cec7ca7d63..17214368b0 100644 --- a/requirements/edx/testing.txt +++ b/requirements/edx/testing.txt @@ -664,9 +664,8 @@ enmerkar==0.7.1 # enmerkar-underscore enmerkar-underscore==2.4.0 # via -r requirements/edx/base.txt -event-tracking==3.0.0 +event-tracking==3.3.0 # via - # -c requirements/edx/../constraints.txt # -r requirements/edx/base.txt # edx-completion # edx-proctoring From 5e4ad1906cf2170e5508f833d7abc0713b891f34 Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 19 May 2025 09:15:26 -0400 Subject: [PATCH 6/7] fix: typo in static-assets.rst (#36698) --- docs/references/static-assets.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/references/static-assets.rst b/docs/references/static-assets.rst index b1db36b7f1..9a3e3c25b6 100644 --- a/docs/references/static-assets.rst +++ b/docs/references/static-assets.rst @@ -11,7 +11,7 @@ which communicate with edx-platform over AJAX, but are built and deployed independently. Eventually, we expect that MFEs will replace all edx-platform frontend pages, except perhaps XBlock views.* -Configuraiton +Configuration ************* To customize the static assets build, set some or all of these variable in your From 27c4ea44f22959c3849cd2cc08908cbbf63cb6d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Chris=20Ch=C3=A1vez?= Date: Mon, 19 May 2025 12:34:39 -0500 Subject: [PATCH 7/7] feat: Add units dict to index [FC-0083] (#36650) * Adds the units dict to the component search documents. * Send CONTENT_OBJECT_ASSOCIATIONS_CHANGED signal when add/remove components in units. --- openedx/core/djangoapps/content/search/api.py | 11 +++ .../djangoapps/content/search/documents.py | 63 +++++++++++++++ .../djangoapps/content/search/handlers.py | 3 + .../content/search/tests/test_api.py | 60 ++++++++++++--- .../content_libraries/api/blocks.py | 4 +- .../content_libraries/api/containers.py | 8 ++ .../content_libraries/tests/test_api.py | 77 +++++++++++++++++++ 7 files changed, 212 insertions(+), 14 deletions(-) diff --git a/openedx/core/djangoapps/content/search/api.py b/openedx/core/djangoapps/content/search/api.py index b866f13dc4..d81128f659 100644 --- a/openedx/core/djangoapps/content/search/api.py +++ b/openedx/core/djangoapps/content/search/api.py @@ -52,6 +52,7 @@ from .documents import ( searchable_doc_for_key, searchable_doc_tags, searchable_doc_tags_for_collection, + searchable_doc_units, ) log = logging.getLogger(__name__) @@ -451,6 +452,7 @@ def rebuild_index(status_cb: Callable[[str], None] | None = None, incremental=Fa doc.update(searchable_doc_for_library_block(metadata)) doc.update(searchable_doc_tags(metadata.usage_key)) doc.update(searchable_doc_collections(metadata.usage_key)) + doc.update(searchable_doc_units(metadata.usage_key)) docs.append(doc) except Exception as err: # pylint: disable=broad-except status_cb(f"Error indexing library component {component}: {err}") @@ -853,6 +855,15 @@ def upsert_item_collections_index_docs(opaque_key: OpaqueKey): _update_index_docs([doc]) +def upsert_item_units_index_docs(opaque_key: OpaqueKey): + """ + Updates the units data in documents for the given Course/Library block + """ + doc = {Fields.id: meili_id_from_opaque_key(opaque_key)} + doc.update(searchable_doc_units(opaque_key)) + _update_index_docs([doc]) + + def upsert_collection_tags_index_docs(collection_key: LibraryCollectionLocator): """ Updates the tags data in documents for the given library collection diff --git a/openedx/core/djangoapps/content/search/documents.py b/openedx/core/djangoapps/content/search/documents.py index 28b5e74450..36698da6a0 100644 --- a/openedx/core/djangoapps/content/search/documents.py +++ b/openedx/core/djangoapps/content/search/documents.py @@ -67,6 +67,10 @@ class Fields: collections = "collections" collections_display_name = "display_name" collections_key = "key" + # Units (dictionary) that this object belongs to. + units = "units" + units_display_name = "display_name" + units_key = "key" # The "content" field is a dictionary of arbitrary data, depending on the block_type. # It comes from each XBlock's index_dictionary() method (if present) plus some processing. @@ -369,6 +373,54 @@ def _collections_for_content_object(object_id: OpaqueKey) -> dict: return result +def _units_for_content_object(object_id: OpaqueKey) -> dict: + """ + Given an XBlock, course, library, etc., get the units for its index doc. + + e.g. for something in Units "UNIT_A" and "UNIT_B", this would return: + { + "units": { + "display_name": ["Unit A", "Unit B"], + "key": ["UNIT_A", "UNIT_B"], + } + } + + If the object is in no collections, returns: + { + "collections": { + "display_name": [], + "key": [], + }, + } + """ + result = { + Fields.units: { + Fields.units_display_name: [], + Fields.units_key: [], + } + } + + # Gather the units associated with this object + units = None + try: + if isinstance(object_id, UsageKey): + units = lib_api.get_containers_contains_component(object_id) + else: + log.warning(f"Unexpected key type for {object_id}") + + except ObjectDoesNotExist: + log.warning(f"No library item found for {object_id}") + + if not units: + return result + + for unit in units: + result[Fields.units][Fields.units_display_name].append(unit.display_name) + result[Fields.units][Fields.units_key].append(str(unit.container_key)) + + return result + + def _published_data_from_block(block_published) -> dict: """ Given an library block get the published data. @@ -460,6 +512,17 @@ def searchable_doc_collections(opaque_key: OpaqueKey) -> dict: return doc +def searchable_doc_units(opaque_key: OpaqueKey) -> dict: + """ + Generate a dictionary document suitable for ingestion into a search engine + like Meilisearch or Elasticsearch, with the units data for the given content object. + """ + doc = searchable_doc_for_key(opaque_key) + doc.update(_units_for_content_object(opaque_key)) + + return doc + + def searchable_doc_tags_for_collection( collection_key: LibraryCollectionLocator ) -> dict: diff --git a/openedx/core/djangoapps/content/search/handlers.py b/openedx/core/djangoapps/content/search/handlers.py index 315d3cde53..fdc52a968e 100644 --- a/openedx/core/djangoapps/content/search/handlers.py +++ b/openedx/core/djangoapps/content/search/handlers.py @@ -46,6 +46,7 @@ from .api import ( upsert_content_object_tags_index_doc, upsert_collection_tags_index_docs, upsert_item_collections_index_docs, + upsert_item_units_index_docs, ) from .tasks import ( delete_library_block_index_doc, @@ -263,6 +264,8 @@ def content_object_associations_changed_handler(**kwargs) -> None: upsert_content_object_tags_index_doc(opaque_key) if not content_object.changes or "collections" in content_object.changes: upsert_item_collections_index_docs(opaque_key) + if not content_object.changes or "units" in content_object.changes: + upsert_item_units_index_docs(opaque_key) @receiver(LIBRARY_CONTAINER_CREATED) diff --git a/openedx/core/djangoapps/content/search/tests/test_api.py b/openedx/core/djangoapps/content/search/tests/test_api.py index 90b6a407e7..2093b3dc85 100644 --- a/openedx/core/djangoapps/content/search/tests/test_api.py +++ b/openedx/core/djangoapps/content/search/tests/test_api.py @@ -8,7 +8,7 @@ import copy from datetime import datetime, timezone from unittest.mock import MagicMock, Mock, call, patch from opaque_keys.edx.keys import UsageKey -from opaque_keys.edx.locator import LibraryCollectionLocator +from opaque_keys.edx.locator import LibraryCollectionLocator, LibraryContainerLocator import ddt import pytest @@ -134,8 +134,8 @@ class TestSearchApi(ModuleStoreTestCase): lib_access, _ = SearchAccess.objects.get_or_create(context_key=self.library.key) # Populate it with 2 problems, freezing the date so we can verify created date serializes correctly. - created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) - with freeze_time(created_date): + self.created_date = datetime(2023, 4, 5, 6, 7, 8, tzinfo=timezone.utc) + with freeze_time(self.created_date): self.problem1 = library_api.create_library_block(self.library.key, "problem", "p1") self.problem2 = library_api.create_library_block(self.library.key, "problem", "p2") # Update problem1, freezing the date so we can verify modified date serializes correctly. @@ -155,7 +155,7 @@ class TestSearchApi(ModuleStoreTestCase): "type": "library_block", "access_id": lib_access.id, "last_published": None, - "created": created_date.timestamp(), + "created": self.created_date.timestamp(), "modified": modified_date.timestamp(), "publish_status": "never", } @@ -172,8 +172,8 @@ class TestSearchApi(ModuleStoreTestCase): "type": "library_block", "access_id": lib_access.id, "last_published": None, - "created": created_date.timestamp(), - "modified": created_date.timestamp(), + "created": self.created_date.timestamp(), + "modified": self.created_date.timestamp(), "publish_status": "never", } @@ -189,7 +189,7 @@ class TestSearchApi(ModuleStoreTestCase): # Create a collection: self.learning_package = authoring_api.get_learning_package_by_key(self.library.key) - with freeze_time(created_date): + with freeze_time(self.created_date): self.collection = authoring_api.create_collection( learning_package_id=self.learning_package.id, key="MYCOL", @@ -210,8 +210,8 @@ class TestSearchApi(ModuleStoreTestCase): "num_children": 0, "context_key": "lib:org1:lib", "org": "org1", - "created": created_date.timestamp(), - "modified": created_date.timestamp(), + "created": self.created_date.timestamp(), + "modified": self.created_date.timestamp(), "access_id": lib_access.id, "published": { "num_children": 0 @@ -220,7 +220,7 @@ class TestSearchApi(ModuleStoreTestCase): } # Create a unit: - with freeze_time(created_date): + with freeze_time(self.created_date): self.unit = library_api.create_container( library_key=self.library.key, container_type=library_api.ContainerType.Unit, @@ -242,8 +242,8 @@ class TestSearchApi(ModuleStoreTestCase): "publish_status": "never", "context_key": "lib:org1:lib", "org": "org1", - "created": created_date.timestamp(), - "modified": created_date.timestamp(), + "created": self.created_date.timestamp(), + "modified": self.created_date.timestamp(), "last_published": None, "access_id": lib_access.id, "breadcrumbs": [{"display_name": "Library"}], @@ -268,9 +268,11 @@ class TestSearchApi(ModuleStoreTestCase): doc_problem1 = copy.deepcopy(self.doc_problem1) doc_problem1["tags"] = {} doc_problem1["collections"] = {'display_name': [], 'key': []} + doc_problem1["units"] = {'display_name': [], 'key': []} doc_problem2 = copy.deepcopy(self.doc_problem2) doc_problem2["tags"] = {} doc_problem2["collections"] = {'display_name': [], 'key': []} + doc_problem2["units"] = {'display_name': [], 'key': []} doc_collection = copy.deepcopy(self.collection_dict) doc_collection["tags"] = {} doc_unit = copy.deepcopy(self.unit_dict) @@ -300,9 +302,11 @@ class TestSearchApi(ModuleStoreTestCase): doc_problem1 = copy.deepcopy(self.doc_problem1) doc_problem1["tags"] = {} doc_problem1["collections"] = {"display_name": [], "key": []} + doc_problem1["units"] = {'display_name': [], 'key': []} doc_problem2 = copy.deepcopy(self.doc_problem2) doc_problem2["tags"] = {} doc_problem2["collections"] = {"display_name": [], "key": []} + doc_problem2["units"] = {'display_name': [], 'key': []} doc_collection = copy.deepcopy(self.collection_dict) doc_collection["tags"] = {} doc_unit = copy.deepcopy(self.unit_dict) @@ -417,6 +421,7 @@ class TestSearchApi(ModuleStoreTestCase): doc_problem2 = copy.deepcopy(self.doc_problem2) doc_problem2["tags"] = {} doc_problem2["collections"] = {'display_name': [], 'key': []} + doc_problem2["units"] = {'display_name': [], 'key': []} orig_from_component = library_api.LibraryXBlockMetadata.from_component @@ -939,3 +944,34 @@ class TestSearchApi(ModuleStoreTestCase): ], any_order=True, ) + + @override_settings(MEILISEARCH_ENABLED=True) + def test_block_in_units(self, mock_meilisearch): + with freeze_time(self.created_date): + library_api.update_container_children( + LibraryContainerLocator.from_string(self.unit_key), + [self.problem1.usage_key], + None, + ) + + doc_block_with_units = { + "id": self.doc_problem1["id"], + "units": { + "display_name": [self.unit.display_name], + "key": [self.unit_key], + }, + } + new_unit_dict = { + **self.unit_dict, + "num_children": 1, + 'content': {'child_usage_keys': [self.doc_problem1["usage_key"]]} + } + + assert mock_meilisearch.return_value.index.return_value.update_documents.call_count == 2 + mock_meilisearch.return_value.index.return_value.update_documents.assert_has_calls( + [ + call([doc_block_with_units]), + call([new_unit_dict]), + ], + any_order=True, + ) diff --git a/openedx/core/djangoapps/content_libraries/api/blocks.py b/openedx/core/djangoapps/content_libraries/api/blocks.py index d693ff30d7..dd67095597 100644 --- a/openedx/core/djangoapps/content_libraries/api/blocks.py +++ b/openedx/core/djangoapps/content_libraries/api/blocks.py @@ -646,11 +646,11 @@ def restore_library_block(usage_key: LibraryUsageLocatorV2, user_id: int | None ) ) - # Add tags and collections back to index + # Add tags, collections and units back to index CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event( content_object=ContentObjectChangedData( object_id=str(usage_key), - changes=["collections", "tags"], + changes=["collections", "tags", "units"], ), ) diff --git a/openedx/core/djangoapps/content_libraries/api/containers.py b/openedx/core/djangoapps/content_libraries/api/containers.py index d97a6100a6..01974a441e 100644 --- a/openedx/core/djangoapps/content_libraries/api/containers.py +++ b/openedx/core/djangoapps/content_libraries/api/containers.py @@ -429,6 +429,14 @@ def update_container_children( created_by=user_id, entities_action=entities_action, ) + + for key in children_ids: + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.send_event( + content_object=ContentObjectChangedData( + object_id=str(key), + changes=["units"], + ), + ) case _: raise ValueError(f"Invalid container type: {container_type}") diff --git a/openedx/core/djangoapps/content_libraries/tests/test_api.py b/openedx/core/djangoapps/content_libraries/tests/test_api.py index 3a1121da38..085f69d0a3 100644 --- a/openedx/core/djangoapps/content_libraries/tests/test_api.py +++ b/openedx/core/djangoapps/content_libraries/tests/test_api.py @@ -839,6 +839,83 @@ class ContentLibraryContainersTest(ContentLibrariesRestApiTest): self._set_library_block_fields(self.html_block_usage_key, {"data": block_olx, "metadata": {}}) self._validate_calls_of_html_block(container_update_event_receiver) + def test_call_object_changed_signal_when_remove_component(self): + html_block_1 = self._add_block_to_library( + self.lib1.library_key, "html", "html3", + ) + api.update_container_children( + self.unit2.container_key, + [UsageKey.from_string(html_block_1["id"])], + None, + entities_action=authoring_api.ChildrenEntitiesAction.APPEND, + ) + + event_reciver = mock.Mock() + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.connect(event_reciver) + api.update_container_children( + self.unit2.container_key, + [UsageKey.from_string(html_block_1["id"])], + None, + entities_action=authoring_api.ChildrenEntitiesAction.REMOVE, + ) + + assert event_reciver.call_count == 1 + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=html_block_1["id"], + changes=["units"], + ), + }, + event_reciver.call_args_list[0].kwargs, + ) + + def test_call_object_changed_signal_when_add_component(self): + event_reciver = mock.Mock() + CONTENT_OBJECT_ASSOCIATIONS_CHANGED.connect(event_reciver) + html_block_1 = self._add_block_to_library( + self.lib1.library_key, "html", "html4", + ) + html_block_2 = self._add_block_to_library( + self.lib1.library_key, "html", "html5", + ) + + api.update_container_children( + self.unit2.container_key, + [ + UsageKey.from_string(html_block_1["id"]), + UsageKey.from_string(html_block_2["id"]) + ], + None, + entities_action=authoring_api.ChildrenEntitiesAction.APPEND, + ) + + assert event_reciver.call_count == 2 + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=html_block_1["id"], + changes=["units"], + ), + }, + event_reciver.call_args_list[0].kwargs, + ) + self.assertDictContainsSubset( + { + "signal": CONTENT_OBJECT_ASSOCIATIONS_CHANGED, + "sender": None, + "content_object": ContentObjectChangedData( + object_id=html_block_2["id"], + changes=["units"], + ), + }, + event_reciver.call_args_list[1].kwargs, + ) + def test_delete_component_and_revert(self): """ When a component is deleted and then the delete is reverted, signals