fix: add resourcetemplate to xblock_mixins (#37184)

Add ResourceTemplates to XBLOCK_MIXINS so it’s applied to all CMS XBlocks at
runtime instead of being directly inherited. This keeps the Studio-only feature
in edx-platform (where it belongs), while still making it available to built-in
and extracted XBlocks.

When we extract built-in blocks from the platform, they will not be able to
inherit ResourcesTemplates directly; they will get it from XBLOCK_MIXINS. So,
we also needed to update a few template-related tests to use the mixed block
class (or an instance of it) rather than the unmixed base class, because the
unmixed base classes will soon be extracted and thus lack ResourceTemplates.

Related to https://github.com/openedx/edx-platform/issues/34827
This commit is contained in:
Irtaza Akram
2025-08-12 18:04:34 +05:00
committed by GitHub
parent 218030cfef
commit 472801b774
5 changed files with 24 additions and 14 deletions

View File

@@ -4,11 +4,10 @@
from xmodule import templates
from xmodule.capa_block import ProblemBlock
from xmodule.course_block import CourseBlock
from xmodule.html_block import HtmlBlock
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.exceptions import DuplicateCourseError
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from xmodule.modulestore.tests.factories import CourseFactory, BlockFactory
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory
from xmodule.seq_block import SequenceBlock
@@ -40,11 +39,14 @@ class TemplateTests(ModuleStoreTestCase):
self.assertRegex(dropdown['data'], r'<problem>\s*<optionresponse>\s*<p>.*dropdown problems.*')
def test_get_some_templates(self):
course = CourseFactory.create()
htmlblock = BlockFactory.create(category="html", parent_location=course.location)
self.assertEqual(len(SequenceBlock.templates()), 0)
self.assertGreater(len(HtmlBlock.templates()), 0)
self.assertGreater(len(htmlblock.templates()), 0)
self.assertIsNone(SequenceBlock.get_template('doesntexist.yaml'))
self.assertIsNone(HtmlBlock.get_template('doesntexist.yaml'))
self.assertIsNotNone(HtmlBlock.get_template('announcement.yaml'))
self.assertIsNone(htmlblock.get_template('doesntexist.yaml'))
self.assertIsNotNone(htmlblock.get_template('announcement.yaml'))
def test_factories(self):
test_course = CourseFactory.create(

View File

@@ -33,7 +33,6 @@ from xblock.fields import Scope, ScopeIds, String
from xblock.runtime import DictKeyValueStore, KvsFieldData
from xblock.test.tools import TestRuntime
from xblock.validation import ValidationMessage
from xmodule.capa_block import ProblemBlock
from xmodule.course_block import DEFAULT_START_DATE
from xmodule.modulestore import ModuleStoreEnum
from xmodule.modulestore.django import modulestore
@@ -619,7 +618,9 @@ class TestCreateItem(ItemTest):
prob_usage_key = self.response_usage_key(resp)
problem = self.get_item_from_modulestore(prob_usage_key)
# check against the template
template = ProblemBlock.get_template(template_id)
course = CourseFactory.create()
problem_block = BlockFactory.create(category="problem", parent_location=course.location)
template = problem_block.get_template(template_id)
self.assertEqual(problem.data, template["data"])
self.assertEqual(problem.display_name, template["metadata"]["display_name"])
self.assertEqual(problem.markdown, template["metadata"]["markdown"])

View File

@@ -30,9 +30,10 @@ def create_xblock(parent_locator, user, category, display_name, boilerplate=None
data = None
template_id = boilerplate
if template_id:
clz = parent.runtime.load_block_type(category)
if clz is not None:
template = clz.get_template(template_id)
base_clz = parent.runtime.load_block_type(category)
dynamic_clz = parent.runtime.mixologist.mix(base_clz)
if dynamic_clz is not None:
template = dynamic_clz.get_template(template_id)
if template is not None:
metadata = template.get('metadata', {})
data = template.get('data')

View File

@@ -828,7 +828,7 @@ P3P_HEADER = 'CP="Open EdX does not have a P3P policy."'
# Import after sys.path fixup
from xmodule.modulestore.inheritance import InheritanceMixin
from xmodule.x_module import XModuleMixin
from xmodule.x_module import XModuleMixin, ResourceTemplates
# These are the Mixins that will be added to every Blocklike upon instantiation.
# DO NOT EXPAND THIS LIST!! We want it eventually to be EMPTY. Why? Because dynamically adding functions/behaviors to
@@ -841,6 +841,7 @@ XBLOCK_MIXINS = (
# (b) refactor their functionality out of the Blocklike objects and into the edx-platform block runtimes.
LmsBlockMixin,
InheritanceMixin,
ResourceTemplates,
XModuleMixin,
EditInfoMixin,
AuthoringMixin,

View File

@@ -16,6 +16,8 @@ from collections import defaultdict
from xblock.core import XBlock
from xmodule.modulestore.tests.factories import BlockFactory, CourseFactory
log = logging.getLogger(__name__)
@@ -25,9 +27,12 @@ def all_templates():
"""
# TODO use memcache to memoize w/ expiration
templates = defaultdict(list)
for category, block in XBlock.load_classes():
if not hasattr(block, 'templates'):
course = CourseFactory.create()
for category, _ in XBlock.load_classes():
loaded_block = BlockFactory.create(category=category, parent_location=course.location)
if not hasattr(loaded_block, 'templates'):
continue
templates[category] = block.templates()
templates[category] = loaded_block.templates()
return templates