From 457f959356bdb0317459290aa34d5df092da8f87 Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 5 Oct 2021 12:38:42 +1030 Subject: [PATCH 1/3] refactor: deprecates ModuleSystem.render_template in favor of the added MakoSystem render_template method. Related changes: * Adds the MakoService to the StudioEditModuleRuntime, PreviewModuleSystem, LmsModuleSystem, and XBlockRuntime * MakoService constructor takes a `namespace_prefix` string, so that the CMS PreviewModuleSystem can render to LMS templates, without needing the special render_from_lms helper method. * ModuleSystem.render_template becomes a read-only property, so the constructor calls and test module systems are updated accordingly. * Adds tests for the MakoService and module system shims. --- cms/djangoapps/contentstore/views/helpers.py | 8 --- cms/djangoapps/contentstore/views/item.py | 3 + cms/djangoapps/contentstore/views/preview.py | 5 +- .../contentstore/views/tests/test_preview.py | 31 +++++++++- common/djangoapps/edxmako/services.py | 30 ++++++++++ common/djangoapps/edxmako/tests.py | 21 +++++++ common/lib/xmodule/xmodule/tests/__init__.py | 32 +++-------- common/lib/xmodule/xmodule/tests/helpers.py | 26 +++++++++ .../xmodule/xmodule/tests/test_capa_module.py | 56 ++++++++++--------- .../tests/test_delay_between_attempts.py | 3 +- .../xmodule/tests/test_editing_module.py | 3 +- .../xmodule/xmodule/tests/test_xml_module.py | 3 +- common/lib/xmodule/xmodule/x_module.py | 24 ++++++-- common/test/templates/edxmako.html | 1 + lms/djangoapps/courseware/module_render.py | 5 +- lms/djangoapps/courseware/tests/helpers.py | 13 +++-- .../tests/test_discussion_xblock.py | 4 +- .../courseware/tests/test_module_render.py | 14 +++++ .../lms_xblock/test/test_runtime.py | 4 -- lms/envs/test.py | 14 +++++ .../core/djangoapps/xblock/runtime/runtime.py | 3 + 21 files changed, 219 insertions(+), 84 deletions(-) create mode 100644 common/djangoapps/edxmako/services.py create mode 100644 common/test/templates/edxmako.html diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index ac114bfd371c..424fe764754e 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -14,7 +14,6 @@ from xmodule.tabs import StaticTab from cms.djangoapps.models.settings.course_grading import CourseGradingModel -from common.djangoapps.edxmako.shortcuts import render_to_string from common.djangoapps.student import auth from common.djangoapps.student.roles import CourseCreatorRole, OrgContentCreatorRole from openedx.core.toggles import ENTRANCE_EXAMS @@ -43,13 +42,6 @@ def event(request): return HttpResponse(status=204) -def render_from_lms(template_name, dictionary, namespace='main'): - """ - Render a template using the LMS Mako templates - """ - return render_to_string(template_name, dictionary, namespace="lms." + namespace) - - def get_parent_xblock(xblock): """ Returns the xblock that is the parent of the specified xblock, or None if it has no parent. diff --git a/cms/djangoapps/contentstore/views/item.py b/cms/djangoapps/contentstore/views/item.py index 3ed065eb0bf5..800c47456aa1 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -32,6 +32,7 @@ from cms.djangoapps.contentstore.config.waffle import SHOW_REVIEW_RULES_FLAG from cms.djangoapps.models.settings.course_grading import CourseGradingModel from cms.lib.xblock.authoring_mixin import VISIBILITY_VIEW +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.edxmako.shortcuts import render_to_string from common.djangoapps.static_replace import replace_static_urls from common.djangoapps.student.auth import has_studio_read_access, has_studio_write_access @@ -309,6 +310,8 @@ def service(self, block, service_name): return DjangoXBlockUserService(self._user) if service_name == "studio_user_permissions": return StudioPermissionsService(self._user) + if service_name == "mako": + return MakoService() if service_name == "settings": return SettingsService() if service_name == "lti-configuration": diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 1abafe5fb9bc..92cba7a16ec6 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -19,6 +19,7 @@ from cms.lib.xblock.field_data import CmsFieldData from common.djangoapps import static_replace from common.djangoapps.edxmako.shortcuts import render_to_string +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService from lms.djangoapps.lms_xblock.field_data import LmsFieldData from openedx.core.lib.license import wrap_with_license @@ -43,7 +44,6 @@ from ..utils import get_visibility_partition_info from .access import get_user_role -from .helpers import render_from_lms from .session_kv_store import SessionKeyValueStore __all__ = ['preview_handler'] @@ -185,6 +185,7 @@ def _preview_module_system(request, descriptor, field_data): ) ] + mako_service = MakoService(namespace_prefix='lms.') if settings.FEATURES.get("LICENSING", False): # stick the license wrapper in front wrappers.insert(0, wrap_with_license) @@ -195,7 +196,6 @@ def _preview_module_system(request, descriptor, field_data): track_function=lambda event_type, event: None, filestore=descriptor.runtime.resources_fs, get_module=partial(_load_preview_module, request), - render_template=render_from_lms, debug=True, replace_urls=partial(static_replace.replace_static_urls, data_directory=None, course_id=course_id), can_execute_unsafe_code=(lambda: can_execute_unsafe_code(course_id)), @@ -213,6 +213,7 @@ def _preview_module_system(request, descriptor, field_data): services={ "field-data": field_data, "i18n": ModuleI18nService, + 'mako': mako_service, "settings": SettingsService(), "user": DjangoXBlockUserService(request.user, anonymous_user_id='student'), "partitions": StudioPartitionService(course_id=course_id), diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index 07bfc854211c..7a3bbb2ea37d 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -8,6 +8,7 @@ import ddt from django.test.client import Client, RequestFactory +from web_fragments.fragment import Fragment from xblock.core import XBlock, XBlockAside from cms.djangoapps.contentstore.utils import reverse_usage_url @@ -174,7 +175,13 @@ class PureXBlock(XBlock): """ Pure XBlock to use in tests. """ - pass # lint-amnesty, pylint: disable=unnecessary-pass + def student_view(self, context): + """ + Renders the output that a student will see. + """ + fragment = Fragment() + fragment.add_content(self.runtime.render_template('edxmako.html', context)) + return fragment @ddt.ddt @@ -206,3 +213,25 @@ def test_expected_services_exist(self, expected_service): ) service = runtime.service(descriptor, expected_service) self.assertIsNotNone(service) + + +class CmsModuleSystemShimTest(ModuleStoreTestCase): + """ + Tests that the deprecated attributes in the Module System (XBlock Runtime) return the expected values. + """ + def setUp(self): + """ + Set up the user and other fields that will be used to instantiate the runtime. + """ + super().setUp() + self.course = CourseFactory.create() + self.user = UserFactory() + self.request = RequestFactory().get('/dummy-url') + self.request.user = self.user + self.request.session = {} + + @XBlock.register_temp_plugin(PureXBlock, identifier='pure') + def test_render_template(self): + descriptor = ItemFactory(category="pure", parent=self.course) + html = get_preview_fragment(self.request, descriptor, {'element_id': 142}).content + assert '
Testing the MakoService
' in html diff --git a/common/djangoapps/edxmako/services.py b/common/djangoapps/edxmako/services.py new file mode 100644 index 000000000000..6ee2b096d63e --- /dev/null +++ b/common/djangoapps/edxmako/services.py @@ -0,0 +1,30 @@ +""" +Supports rendering an XBlock to HTML using mako templates. +""" + +from xblock.reference.plugins import Service + +from common.djangoapps.edxmako.shortcuts import render_to_string + + +class MakoService(Service): + """ + A service for rendering XBlocks to HTML using mako templates. + + Args: + namespace_prefix(string): optional prefix to the mako namespace used to find the template file. + e.g to access LMS templates from within Studio code, pass namespace_prefix='lms.' + """ + def __init__( + self, + namespace_prefix='', + **kwargs + ): + super().__init__(**kwargs) + self.namespace_prefix = namespace_prefix + + def render_template(self, template_file, dictionary, namespace='main'): + """ + Takes (template_file, dictionary) and returns rendered HTML. + """ + return render_to_string(template_file, dictionary, namespace=self.namespace_prefix + namespace) diff --git a/common/djangoapps/edxmako/tests.py b/common/djangoapps/edxmako/tests.py index 2e0bf2e48e60..69d350cf44d3 100644 --- a/common/djangoapps/edxmako/tests.py +++ b/common/djangoapps/edxmako/tests.py @@ -14,6 +14,7 @@ from common.djangoapps.edxmako import LOOKUP, add_lookup from common.djangoapps.edxmako.request_context import get_template_request_context +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.edxmako.shortcuts import ( is_any_marketing_link_set, is_marketing_link_set, @@ -208,3 +209,23 @@ def test_render_to_string_when_no_global_context_cms(self): the threadlocal REQUEST_CONTEXT.context. This is meant to run in CMS. """ assert "We're having trouble rendering your component" in render_to_string('html_error.html', None) + + +@ddt.ddt +class MakoServiceTestCase(TestCase): + """ + Tests for the MakoService + """ + @ddt.data( + (MakoService(), + '
Testing the MakoService
\n'), + (MakoService(namespace_prefix='lms.'), + '
Testing the MakoService
\n'), + ) + @ddt.unpack + def test_render_template(self, service, expected_html): + """ + Tests MakoService.render_template returns the expected rendered content. + """ + html = service.render_template('templates/edxmako.html', {'element_id': 'mako_id'}) + assert html == expected_html diff --git a/common/lib/xmodule/xmodule/tests/__init__.py b/common/lib/xmodule/xmodule/tests/__init__.py index c91da247d8da..533bff5c6952 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -11,7 +11,6 @@ import inspect import json import os -import pprint import sys import traceback import unittest @@ -34,7 +33,7 @@ from xmodule.modulestore.draft_and_published import ModuleStoreDraftAndPublished from xmodule.modulestore.inheritance import InheritanceMixin from xmodule.modulestore.xml import CourseLocationManager -from xmodule.tests.helpers import StubUserService +from xmodule.tests.helpers import mock_render_template, StubMakoService, StubUserService from xmodule.x_module import ModuleSystem, XModuleDescriptor, XModuleMixin @@ -93,18 +92,13 @@ def get_test_system( course_id=CourseKey.from_string('/'.join(['org', 'course', 'run'])), user=None, user_is_staff=False, + render_template=None, ): """ Construct a test ModuleSystem instance. - By default, the render_template() method simply returns the repr of the - context it is passed. You can override this behavior by monkey patching:: - - system = get_test_system() - system.render_template = my_render_func - - where `my_render_func` is a function of the form my_render_func(template, context). - + By default, the descriptor system's render_template() method simply returns the repr of the + context it is passed. You can override this by passing in a different render_template argument. """ if not user: user = Mock(name='get_test_system.user', is_staff=False) @@ -114,6 +108,8 @@ def get_test_system( user_is_staff=user_is_staff, ) + mako_service = StubMakoService(render_template=render_template) + descriptor_system = get_test_descriptor_system() def get_module(descriptor): @@ -136,7 +132,6 @@ def get_module(descriptor): static_url='/static', track_function=Mock(name='get_test_system.track_function'), get_module=get_module, - render_template=mock_render_template, replace_urls=str, get_real_user=lambda __: user, filestore=Mock(name='get_test_system.filestore', root_path='.'), @@ -144,6 +139,7 @@ def get_module(descriptor): hostname="edx.org", services={ 'user': user_service, + 'mako': mako_service, }, xqueue={ 'interface': None, @@ -161,7 +157,7 @@ def get_module(descriptor): ) -def get_test_descriptor_system(): +def get_test_descriptor_system(render_template=None): """ Construct a test DescriptorSystem instance. """ @@ -171,7 +167,7 @@ def get_test_descriptor_system(): load_item=Mock(name='get_test_descriptor_system.load_item'), resources_fs=Mock(name='get_test_descriptor_system.resources_fs'), error_tracker=Mock(name='get_test_descriptor_system.error_tracker'), - render_template=mock_render_template, + render_template=render_template or mock_render_template, mixins=(InheritanceMixin, XModuleMixin), field_data=field_data, services={'field-data': field_data}, @@ -180,16 +176,6 @@ def get_test_descriptor_system(): return descriptor_system -def mock_render_template(*args, **kwargs): - """ - Pretty-print the args and kwargs. - - Allows us to not depend on any actual template rendering mechanism, - while still returning a unicode object - """ - return pprint.pformat((args, kwargs)).encode().decode() - - class ModelsTest(unittest.TestCase): # lint-amnesty, pylint: disable=missing-class-docstring def test_load_class(self): diff --git a/common/lib/xmodule/xmodule/tests/helpers.py b/common/lib/xmodule/xmodule/tests/helpers.py index 4e9e9d1d14db..22dc86892431 100644 --- a/common/lib/xmodule/xmodule/tests/helpers.py +++ b/common/lib/xmodule/xmodule/tests/helpers.py @@ -5,6 +5,7 @@ import filecmp from unittest.mock import Mock +import pprint from path import Path as path from xblock.reference.user_service import UserService, XBlockUser @@ -30,6 +31,31 @@ def compare_dirs(dir1, dir2): return compare_dirs(path(directory1), path(directory2)) +def mock_render_template(*args, **kwargs): + """ + Pretty-print the args and kwargs. + + Allows us to not depend on any actual template rendering mechanism, + while still returning a unicode object + """ + return pprint.pformat((args, kwargs)).encode().decode() + + +class StubMakoService: + """ + Stub MakoService for testing modules that use mako templates. + """ + + def __init__(self, render_template=None): + self._render_template = render_template or mock_render_template + + def render_template(self, *args, **kwargs): + """ + Invokes the configured render_template method. + """ + return self._render_template(*args, **kwargs) + + class StubUserService(UserService): """ Stub UserService for testing the sequence module. diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py index 58de4adc261e..4121cac007c8 100644 --- a/common/lib/xmodule/xmodule/tests/test_capa_module.py +++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py @@ -79,7 +79,8 @@ def answer_key(cls, response_num=2, input_num=1): response_num, input_num)) @classmethod - def create(cls, attempts=None, problem_state=None, correct=False, xml=None, override_get_score=True, **kwargs): + def create(cls, attempts=None, problem_state=None, correct=False, xml=None, override_get_score=True, + render_template=None, **kwargs): """ All parameters are optional, and are added to the created problem if specified. @@ -95,6 +96,8 @@ def create(cls, attempts=None, problem_state=None, correct=False, xml=None, over module. attempts: also added to instance state. Will be converted to an int. + + render_template: pass function or Mock for testing """ location = BlockUsageLocator( CourseLocator("edX", "capa_test", "2012_Fall", deprecated=True), @@ -113,8 +116,11 @@ def create(cls, attempts=None, problem_state=None, correct=False, xml=None, over # since everything else is a string. field_data['attempts'] = int(attempts) - system = get_test_system(course_id=location.course_key, user_is_staff=kwargs.get('user_is_staff', False)) - system.render_template = Mock(return_value="
Test Template HTML
") + system = get_test_system( + course_id=location.course_key, + user_is_staff=kwargs.get('user_is_staff', False), + render_template=render_template or Mock(return_value="
Test Template HTML
"), + ) module = ProblemBlock( system, DictFieldData(field_data), @@ -1520,7 +1526,8 @@ def test_no_max_attempts(self): # assert that we got here without exploding def test_get_problem_html(self): - module = CapaFactory.create() + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(render_template=render_template) # We've tested the show/hide button logic in other tests, # so here we hard-wire the values @@ -1532,9 +1539,6 @@ def test_get_problem_html(self): module.should_show_reset_button = Mock(return_value=show_reset_button) module.should_show_save_button = Mock(return_value=show_save_button) - # Mock the system rendering function - module.system.render_template = Mock(return_value="
Test Template HTML
") - # Patch the capa problem's HTML rendering with patch('capa.capa_problem.LoncapaProblem.get_html') as mock_html: mock_html.return_value = "
Test Problem HTML
" @@ -1549,7 +1553,7 @@ def test_get_problem_html(self): assert html == '
Test Template HTML
' # Check the rendering context - render_args, _ = module.system.render_template.call_args + render_args, _ = render_template.call_args assert len(render_args) == 2 template_name = render_args[0] @@ -1584,9 +1588,10 @@ def test_get_problem_html(self): def test_demand_hint(self): # HTML generation is mocked out to be meaningless here, so instead we check # the context dict passed into HTML generation. - module = CapaFactory.create(xml=self.demand_xml) + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(xml=self.demand_xml, render_template=render_template) module.get_problem_html() # ignoring html result - context = module.system.render_template.call_args[0][1] + context = render_template.call_args[0][1] assert context['demand_hint_possible'] assert context['should_enable_next_hint'] @@ -1621,9 +1626,10 @@ def test_single_demand_hint(self): Only demand hint """ - module = CapaFactory.create(xml=test_xml) + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(xml=test_xml, render_template=render_template) module.get_problem_html() # ignoring html result - context = module.system.render_template.call_args[0][1] + context = render_template.call_args[0][1] assert context['demand_hint_possible'] assert context['should_enable_next_hint'] @@ -1652,9 +1658,10 @@ def test_image_hint(self): You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button. """ - module = CapaFactory.create(xml=test_xml) + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(xml=test_xml, render_template=render_template) module.get_problem_html() # ignoring html result - context = module.system.render_template.call_args[0][1] + context = render_template.call_args[0][1] assert context['demand_hint_possible'] assert context['should_enable_next_hint'] @@ -1696,7 +1703,8 @@ def test_get_problem_html_error(self): rendering, a "dummy" problem is created with an error message to display to the user. """ - module = CapaFactory.create() + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(render_template=render_template) # Save the original problem so we can compare it later original_problem = module.lcp @@ -1705,9 +1713,6 @@ def test_get_problem_html_error(self): # is asked to render itself as HTML module.lcp.get_html = Mock(side_effect=Exception("Test")) - # Stub out the get_test_system rendering function - module.system.render_template = Mock(return_value="
Test Template HTML
") - # Turn off DEBUG module.system.DEBUG = False @@ -1717,7 +1722,7 @@ def test_get_problem_html_error(self): assert html is not None # Check the rendering context - render_args, _ = module.system.render_template.call_args + render_args, _ = render_template.call_args context = render_args[1] assert 'error' in context['problem']['html'] @@ -1728,16 +1733,14 @@ def test_get_problem_html_error_w_debug(self): """ Test the html response when an error occurs with DEBUG on """ - module = CapaFactory.create() + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(render_template=render_template) # Simulate throwing an exception when the capa problem # is asked to render itself as HTML error_msg = "Superterrible error happened: ☠" module.lcp.get_html = Mock(side_effect=Exception(error_msg)) - # Stub out the get_test_system rendering function - module.system.render_template = Mock(return_value="
Test Template HTML
") - # Make sure DEBUG is on module.system.DEBUG = True @@ -1747,7 +1750,7 @@ def test_get_problem_html_error_w_debug(self): assert html is not None # Check the rendering context - render_args, _ = module.system.render_template.call_args + render_args, _ = render_template.call_args context = render_args[1] assert error_msg in context['problem']['html'] @@ -2111,9 +2114,10 @@ def test_problem_no_display_name(self, display_name): """ Verify that if problem display name is not provided then a default name is used. """ - module = CapaFactory.create(display_name=display_name) + render_template = Mock(return_value="
Test Template HTML
") + module = CapaFactory.create(display_name=display_name, render_template=render_template) module.get_problem_html() - render_args, _ = module.system.render_template.call_args + render_args, _ = render_template.call_args context = render_args[1] assert context['problem']['name'] == module.location.block_type diff --git a/common/lib/xmodule/xmodule/tests/test_delay_between_attempts.py b/common/lib/xmodule/xmodule/tests/test_delay_between_attempts.py index 480437a1c82f..fd1afccdb849 100644 --- a/common/lib/xmodule/xmodule/tests/test_delay_between_attempts.py +++ b/common/lib/xmodule/xmodule/tests/test_delay_between_attempts.py @@ -103,8 +103,7 @@ def create( # since everything else is a string. field_data['attempts'] = int(attempts) - system = get_test_system() - system.render_template = Mock(return_value="
Test Template HTML
") + system = get_test_system(render_template=Mock(return_value="
Test Template HTML
")) module = ProblemBlock( system, DictFieldData(field_data), diff --git a/common/lib/xmodule/xmodule/tests/test_editing_module.py b/common/lib/xmodule/xmodule/tests/test_editing_module.py index 2515adf11931..0f29595be6a0 100644 --- a/common/lib/xmodule/xmodule/tests/test_editing_module.py +++ b/common/lib/xmodule/xmodule/tests/test_editing_module.py @@ -22,8 +22,7 @@ class TabsEditingDescriptorTestCase(unittest.TestCase): def setUp(self): super().setUp() - system = get_test_descriptor_system() - system.render_template = Mock(return_value="
Test Template HTML
") + system = get_test_descriptor_system(render_template=Mock()) self.tabs = [ { 'name': "Test_css", diff --git a/common/lib/xmodule/xmodule/tests/test_xml_module.py b/common/lib/xmodule/xmodule/tests/test_xml_module.py index 2776c52a6bb0..9b4d4fc9103c 100644 --- a/common/lib/xmodule/xmodule/tests/test_xml_module.py +++ b/common/lib/xmodule/xmodule/tests/test_xml_module.py @@ -347,8 +347,7 @@ def non_editable_metadata_fields(self): non_editable_fields.append(TestModuleDescriptor.due) return non_editable_fields - system = get_test_descriptor_system() - system.render_template = Mock(return_value="
Test Template HTML
") + system = get_test_descriptor_system(render_template=Mock()) return system.construct_xblock_from_class(TestModuleDescriptor, field_data=field_data, scope_ids=Mock()) def assert_field_values(self, editable_fields, name, field, explicitly_set, value, default_value, # lint-amnesty, pylint: disable=dangerous-default-value diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 593d8f117665..53530e0e2c53 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -1748,6 +1748,7 @@ class ModuleSystemShim: """ @property +<<<<<<< HEAD def anonymous_student_id(self): """ Returns the anonymous user ID for the current user and course. @@ -1809,6 +1810,23 @@ def user_is_staff(self): return self._services['user'].get_current_user().opt_attrs.get(ATTR_KEY_USER_IS_STAFF) return None + @property + def render_template(self): + """ + Returns a function that takes (template_file, context), and returns rendered html. + + Deprecated in favor of the mako service. + """ + warnings.warn( + 'Use of runtime.render_template is deprecated. ' + 'Use MakoService.render_template or a JavaScript-based template instead.', + DeprecationWarning, stacklevel=2, + ) + render_service = self._services.get('mako') + if render_service: + return render_service.render_template + return None + class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, Runtime): """ @@ -1824,7 +1842,7 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, """ def __init__( - self, static_url, track_function, get_module, render_template, + self, static_url, track_function, get_module, replace_urls, descriptor_runtime, filestore=None, debug=False, hostname="", xqueue=None, publish=None, node_path="", course_id=None, @@ -1846,9 +1864,6 @@ def __init__( module instance object. If the current user does not have access to that location, returns None. - render_template - a function that takes (template_file, context), and - returns rendered html. - filestore - A filestore ojbect. Defaults to an instance of OSFS based at settings.DATA_DIR. @@ -1904,7 +1919,6 @@ def __init__( self.track_function = track_function self.filestore = filestore self.get_module = get_module - self.render_template = render_template self.DEBUG = self.debug = debug self.HOSTNAME = self.hostname = hostname self.replace_urls = replace_urls diff --git a/common/test/templates/edxmako.html b/common/test/templates/edxmako.html new file mode 100644 index 000000000000..fd95201801c2 --- /dev/null +++ b/common/test/templates/edxmako.html @@ -0,0 +1 @@ +<%page expression_filter="h"/>
Testing the MakoService
diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index d6ac518e9519..c409f97ff3a0 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -52,7 +52,6 @@ setup_masquerade ) from lms.djangoapps.courseware.model_data import DjangoKeyValueStore, FieldDataCache -from common.djangoapps.edxmako.shortcuts import render_to_string from lms.djangoapps.courseware.field_overrides import OverrideFieldData from lms.djangoapps.courseware.services import UserStateService from lms.djangoapps.grades.api import GradesUtilService @@ -90,6 +89,7 @@ from common.djangoapps.track import contexts from common.djangoapps.util import milestones_helpers from common.djangoapps.util.json_request import JsonResponse +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService from xmodule.contentstore.django import contentstore from xmodule.error_module import ErrorBlock, NonStaffErrorBlock @@ -703,6 +703,7 @@ def rebind_noauth_module_to_user(module, real_user): if is_masquerading_as_specific_student(user, course_id): block_wrappers.append(filter_displayed_blocks) + mako_service = MakoService() if settings.FEATURES.get("LICENSING", False): block_wrappers.append(wrap_with_license) @@ -770,7 +771,6 @@ def rebind_noauth_module_to_user(module, real_user): system = LmsModuleSystem( track_function=track_function, - render_template=render_to_string, static_url=settings.STATIC_URL, xqueue=xqueue, # TODO (cpennington): Figure out how to share info between systems @@ -810,6 +810,7 @@ def rebind_noauth_module_to_user(module, real_user): services={ 'fs': FSService(), 'field-data': field_data, + 'mako': mako_service, 'user': user_service, 'verification': XBlockVerificationService(), 'proctoring': ProctoringService(), diff --git a/lms/djangoapps/courseware/tests/helpers.py b/lms/djangoapps/courseware/tests/helpers.py index d500bf5bbf7e..83a27122bb44 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -7,6 +7,7 @@ import json from collections import OrderedDict from datetime import timedelta +from unittest.mock import Mock from django.contrib import messages from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user @@ -63,11 +64,11 @@ class BaseTestXmodule(ModuleStoreTestCase): METADATA = {} MODEL_DATA = {'data': ''} - def new_module_runtime(self): + def new_module_runtime(self, render_template=None): """ Generate a new ModuleSystem that is minimally set up for testing """ - return get_test_system(course_id=self.course.id) + return get_test_system(course_id=self.course.id, render_template=render_template) def new_descriptor_runtime(self): runtime = get_test_descriptor_system() @@ -143,12 +144,14 @@ def get_url(self, dispatch): class XModuleRenderingTestBase(BaseTestXmodule): # lint-amnesty, pylint: disable=missing-class-docstring - def new_module_runtime(self): + def new_module_runtime(self, render_template=None): """ Create a runtime that actually does html rendering """ - runtime = super().new_module_runtime() - runtime.render_template = render_to_string + if not render_template: + render_template = render_to_string + runtime = super().new_module_runtime(render_template=render_template) + runtime.modulestore = Mock() return runtime diff --git a/lms/djangoapps/courseware/tests/test_discussion_xblock.py b/lms/djangoapps/courseware/tests/test_discussion_xblock.py index e48973570a6d..b268f7c87824 100644 --- a/lms/djangoapps/courseware/tests/test_discussion_xblock.py +++ b/lms/djangoapps/courseware/tests/test_discussion_xblock.py @@ -42,7 +42,6 @@ def setUp(self): self.patchers = [] self.course_id = "test_course" self.runtime = self.new_module_runtime() - self.runtime.modulestore = mock.Mock() self.discussion_id = str(uuid.uuid4()) self.data = DictFieldData({ @@ -131,7 +130,8 @@ def setUp(self): self.template_canary = 'canary' self.render_template = mock.Mock() self.render_template.return_value = self.template_canary - self.block.runtime.render_template = self.render_template + self.runtime = self.new_module_runtime(render_template=self.render_template) + self.block.runtime = self.runtime self.has_permission_mock = mock.Mock() self.has_permission_mock.return_value = False self.block.has_permission = self.has_permission_mock diff --git a/lms/djangoapps/courseware/tests/test_module_render.py b/lms/djangoapps/courseware/tests/test_module_render.py index bc19f06e8353..1ea9b2908cc3 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -2687,3 +2687,17 @@ def test_user_service_with_anonymous_user(self): assert runtime.seed == 0 assert runtime.user_id is None assert not runtime.user_is_staff + + def test_render_template(self): + runtime, _ = render.get_module_system_for_user( + self.user, + self.student_data, + self.descriptor, + self.course.id, + self.track_function, + self.xqueue_callback_url_prefix, + self.request_token, + course=self.course, + ) + rendered = runtime.render_template('templates/edxmako.html', {'element_id': 'hi'}) # pylint: disable=not-callable + assert rendered == '
Testing the MakoService
\n' diff --git a/lms/djangoapps/lms_xblock/test/test_runtime.py b/lms/djangoapps/lms_xblock/test/test_runtime.py index 42c20de05b71..3e74f82f8c75 100644 --- a/lms/djangoapps/lms_xblock/test/test_runtime.py +++ b/lms/djangoapps/lms_xblock/test/test_runtime.py @@ -63,7 +63,6 @@ def setUp(self): static_url='/static', track_function=Mock(), get_module=Mock(), - render_template=Mock(), replace_urls=str, course_id=self.course_key, user=Mock(), @@ -130,7 +129,6 @@ def setUp(self): static_url='/static', track_function=Mock(), get_module=Mock(), - render_template=Mock(), replace_urls=str, user=self.user, course_id=self.course_id, @@ -186,7 +184,6 @@ def mock_get_real_user(_anon_id): static_url='/static', track_function=Mock(), get_module=Mock(), - render_template=Mock(), replace_urls=str, course_id=self.course_id, user=self.user, @@ -242,7 +239,6 @@ def setUp(self): static_url='/static', track_function=Mock(), get_module=Mock(), - render_template=Mock(), replace_urls=str, course_id=self.course.id, user=Mock(), diff --git a/lms/envs/test.py b/lms/envs/test.py index d43db2672cfe..43286bfb6edd 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -495,6 +495,20 @@ ACTIVATION_EMAIL_FROM_ADDRESS = 'test_activate@edx.org' TEMPLATES[0]['OPTIONS']['debug'] = True +TEMPLATES.append( + { + # This separate copy of the Mako backend is used to test rendering previews in the 'lms.main' namespace + 'NAME': 'preview', + 'BACKEND': 'common.djangoapps.edxmako.backend.Mako', + 'APP_DIRS': False, + 'DIRS': MAKO_TEMPLATE_DIRS_BASE, + 'OPTIONS': { + 'context_processors': CONTEXT_PROCESSORS, + 'debug': False, + 'namespace': 'lms.main', + } + } +) ########################## VIDEO TRANSCRIPTS STORAGE ############################ VIDEO_TRANSCRIPTS_SETTINGS = dict( diff --git a/openedx/core/djangoapps/xblock/runtime/runtime.py b/openedx/core/djangoapps/xblock/runtime/runtime.py index 558259d99842..93984037c82f 100644 --- a/openedx/core/djangoapps/xblock/runtime/runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/runtime.py @@ -19,6 +19,7 @@ from xblock.fields import Scope from xblock.runtime import KvsFieldData, MemoryIdManager, Runtime +from common.djangoapps.edxmako.services import MakoService from common.djangoapps.track import contexts as track_contexts from common.djangoapps.track import views as track_views from common.djangoapps.xblock_django.user_service import DjangoXBlockUserService @@ -235,6 +236,8 @@ def service(self, block, service_name): user_is_staff=self.user.is_staff, anonymous_user_id=self.anonymous_student_id, ) + elif service_name == "mako": + return MakoService() elif service_name == "i18n": return ModuleI18nService(block=block) # Check if the XBlockRuntimeSystem wants to handle this: From 8d62d337f5bd14de366cc3f4f315b8482cfe623e Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 5 Oct 2021 13:34:03 +1030 Subject: [PATCH 2/3] refactor: use MakoService.render_template to remove deprecation warnings from block code. --- cms/djangoapps/contentstore/views/preview.py | 2 +- cms/lib/xblock/authoring_mixin.py | 2 +- common/lib/xmodule/xmodule/annotatable_module.py | 6 ++++-- common/lib/xmodule/xmodule/capa_module.py | 11 ++++++----- common/lib/xmodule/xmodule/conditional_module.py | 9 +++++---- common/lib/xmodule/xmodule/error_module.py | 6 ++++-- common/lib/xmodule/xmodule/html_module.py | 8 ++++++-- .../lib/xmodule/xmodule/library_content_module.py | 14 ++++++++------ common/lib/xmodule/xmodule/library_root_xblock.py | 3 ++- common/lib/xmodule/xmodule/lti_module.py | 7 ++++--- common/lib/xmodule/xmodule/mako_module.py | 14 ++++++++++++++ common/lib/xmodule/xmodule/poll_module.py | 4 +++- common/lib/xmodule/xmodule/seq_module.py | 5 +++-- common/lib/xmodule/xmodule/split_test_module.py | 9 +++++---- common/lib/xmodule/xmodule/studio_editable.py | 7 ++++++- common/lib/xmodule/xmodule/template_module.py | 4 +++- .../lib/xmodule/xmodule/tests/test_html_module.py | 2 +- common/lib/xmodule/xmodule/vertical_block.py | 4 ++-- .../xmodule/xmodule/video_module/video_module.py | 5 +++-- common/lib/xmodule/xmodule/word_cloud_module.py | 5 +++-- common/lib/xmodule/xmodule/x_module.py | 1 - lms/djangoapps/courseware/module_render.py | 2 +- openedx/core/lib/license/wrapper.py | 4 ++-- .../xblock_discussion/__init__.py | 6 ++++-- 24 files changed, 91 insertions(+), 49 deletions(-) diff --git a/cms/djangoapps/contentstore/views/preview.py b/cms/djangoapps/contentstore/views/preview.py index 92cba7a16ec6..5689ef558a19 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -188,7 +188,7 @@ def _preview_module_system(request, descriptor, field_data): mako_service = MakoService(namespace_prefix='lms.') if settings.FEATURES.get("LICENSING", False): # stick the license wrapper in front - wrappers.insert(0, wrap_with_license) + wrappers.insert(0, partial(wrap_with_license, mako_service=mako_service)) return PreviewModuleSystem( static_url=settings.STATIC_URL, diff --git a/cms/lib/xblock/authoring_mixin.py b/cms/lib/xblock/authoring_mixin.py index 39f8f1062825..342c5ff05486 100644 --- a/cms/lib/xblock/authoring_mixin.py +++ b/cms/lib/xblock/authoring_mixin.py @@ -39,7 +39,7 @@ def visibility_view(self, _context=None): """ fragment = Fragment() from cms.djangoapps.contentstore.utils import reverse_course_url - fragment.add_content(self.system.render_template('visibility_editor.html', { + fragment.add_content(self.runtime.service(self, 'mako').render_template('visibility_editor.html', { 'xblock': self, 'manage_groups_url': reverse_course_url('group_configurations_list_handler', self.location.course_key), })) diff --git a/common/lib/xmodule/xmodule/annotatable_module.py b/common/lib/xmodule/xmodule/annotatable_module.py index 1c7a80122bdf..e07f81231d66 100644 --- a/common/lib/xmodule/xmodule/annotatable_module.py +++ b/common/lib/xmodule/xmodule/annotatable_module.py @@ -6,6 +6,7 @@ from lxml import etree from pkg_resources import resource_string from web_fragments.fragment import Fragment +from xblock.core import XBlock from xblock.fields import Scope, String from openedx.core.djangolib.markup import HTML, Text @@ -29,6 +30,7 @@ _ = lambda text: text +@XBlock.needs('mako') class AnnotatableBlock( RawMixin, XmlMixin, @@ -201,7 +203,7 @@ def get_html(self): 'content_html': self._render_content() } - return self.system.render_template('annotatable.html', context) + return self.runtime.service(self, 'mako').render_template('annotatable.html', context) def student_view(self, context): # lint-amnesty, pylint: disable=unused-argument """ @@ -219,7 +221,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'AnnotatableBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) diff --git a/common/lib/xmodule/xmodule/capa_module.py b/common/lib/xmodule/xmodule/capa_module.py index 25a133d19e1c..26a42d59d6a6 100644 --- a/common/lib/xmodule/xmodule/capa_module.py +++ b/common/lib/xmodule/xmodule/capa_module.py @@ -120,6 +120,7 @@ def from_json(self, value): @XBlock.needs('user') @XBlock.needs('i18n') +@XBlock.needs('mako') @XBlock.wants('call_to_action') class ProblemBlock( ScorableXBlockMixin, @@ -392,7 +393,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'ProblemBlockStudio') shim_xmodule_js(fragment, 'MarkdownEditingDescriptor') @@ -821,7 +822,7 @@ def new_lcp(self, state, text=None): filestore=self.runtime.filestore, i18n=self.runtime.service(self, "i18n"), node_path=self.runtime.node_path, - render_template=self.runtime.render_template, + render_template=self.runtime.service(self, 'mako').render_template, seed=seed, # Why do we do this if we have self.seed? STATIC_URL=self.runtime.STATIC_URL, xqueue=self.runtime.xqueue, @@ -915,7 +916,7 @@ def get_html(self): """ curr_score, total_possible = self.get_display_progress() - return self.runtime.render_template('problem_ajax.html', { + return self.runtime.service(self, 'mako').render_template('problem_ajax.html', { 'element_id': self.location.html_id(), 'id': str(self.location), 'ajax_url': self.ajax_url, @@ -1263,7 +1264,7 @@ def get_problem_html(self, encapsulate=True, submit_notification=False): 'submit_disabled_cta': submit_disabled_ctas[0] if submit_disabled_ctas else None, } - html = self.runtime.render_template('problem.html', context) + html = self.runtime.service(self, 'mako').render_template('problem.html', context) if encapsulate: html = HTML('
{html}
').format( @@ -1573,7 +1574,7 @@ def get_answer(self, _data): return { 'answers': new_answers, - 'correct_status_html': self.runtime.render_template( + 'correct_status_html': self.runtime.service(self, 'mako').render_template( 'status_span.html', {'status': Status('correct', self.runtime.service(self, "i18n").ugettext)} ) diff --git a/common/lib/xmodule/xmodule/conditional_module.py b/common/lib/xmodule/xmodule/conditional_module.py index 8a2d50c63bc1..3605f36bf828 100644 --- a/common/lib/xmodule/xmodule/conditional_module.py +++ b/common/lib/xmodule/xmodule/conditional_module.py @@ -11,6 +11,7 @@ from opaque_keys.edx.locator import BlockUsageLocator from pkg_resources import resource_string from web_fragments.fragment import Fragment +from xblock.core import XBlock from xblock.fields import ReferenceList, Scope, String from openedx.core.djangolib.markup import HTML, Text @@ -38,6 +39,7 @@ _ = lambda text: text +@XBlock.needs('mako') class ConditionalBlock( SequenceMixin, MakoTemplateBlockBase, @@ -245,7 +247,7 @@ def student_view(self, _context): def get_html(self): required_html_ids = [descriptor.location.html_id() for descriptor in self.get_required_blocks] - return self.system.render_template('conditional_ajax.html', { + return self.runtime.service(self, 'mako').render_template('conditional_ajax.html', { 'element_id': self.location.html_id(), 'ajax_url': self.ajax_url, 'depends': ';'.join(required_html_ids) @@ -271,7 +273,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'ConditionalBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) @@ -284,8 +286,7 @@ def handle_ajax(self, _dispatch, _data): if not self.is_condition_satisfied(): context = {'module': self, 'message': self.conditional_message} - html = self.system.render_template('conditional_module.html', - context) + html = self.runtime.service(self, 'mako').render_template('conditional_module.html', context) return json.dumps({'fragments': [{'content': html}], 'message': bool(self.conditional_message)}) fragments = [child.render(STUDENT_VIEW).to_dict() for child in self.get_display_items()] diff --git a/common/lib/xmodule/xmodule/error_module.py b/common/lib/xmodule/xmodule/error_module.py index 2cefcc9da9a9..a96631954e1d 100644 --- a/common/lib/xmodule/xmodule/error_module.py +++ b/common/lib/xmodule/xmodule/error_module.py @@ -11,6 +11,7 @@ from lxml import etree from web_fragments.fragment import Fragment +from xblock.core import XBlock from xblock.field_data import DictFieldData from xblock.fields import Scope, ScopeIds, String @@ -43,6 +44,7 @@ class ErrorFields: display_name = String(scope=Scope.settings) +@XBlock.needs('mako') class ErrorBlock( ErrorFields, XModuleDescriptorToXBlockMixin, @@ -62,7 +64,7 @@ def student_view(self, _context): """ Return a fragment that contains the html for the student view. """ - fragment = Fragment(self.system.render_template('module-error.html', { + fragment = Fragment(self.runtime.service(self, 'mako').render_template('module-error.html', { 'staff_access': True, 'data': self.contents, 'error': self.error_msg, @@ -209,7 +211,7 @@ def student_view(self, _context): """ Return a fragment that contains the html for the student view. """ - fragment = Fragment(self.system.render_template('module-error.html', { + fragment = Fragment(self.runtime.service(self, 'mako').render_template('module-error.html', { 'staff_access': False, 'data': '', 'error': '', diff --git a/common/lib/xmodule/xmodule/html_module.py b/common/lib/xmodule/xmodule/html_module.py index 0329bd0bc2c0..de22e1a823d6 100644 --- a/common/lib/xmodule/xmodule/html_module.py +++ b/common/lib/xmodule/xmodule/html_module.py @@ -43,6 +43,7 @@ @XBlock.needs("i18n") +@XBlock.needs("mako") @XBlock.needs("user") class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method XmlMixin, EditingMixin, @@ -131,7 +132,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'HtmlBlockStudio') shim_xmodule_js(fragment, 'HTMLEditingDescriptor') @@ -484,7 +485,10 @@ def get_html(self): 'visible_updates': course_updates[:3], 'hidden_updates': course_updates[3:], } - return self.system.render_template(f"{self.TEMPLATE_DIR}/course_updates.html", context) + return self.runtime.service(self, 'mako').render_template( + f"{self.TEMPLATE_DIR}/course_updates.html", + context, + ) @classmethod def order_updates(self, updates): # lint-amnesty, pylint: disable=bad-classmethod-argument diff --git a/common/lib/xmodule/xmodule/library_content_module.py b/common/lib/xmodule/xmodule/library_content_module.py index 43d9961703be..d81cb9804842 100644 --- a/common/lib/xmodule/xmodule/library_content_module.py +++ b/common/lib/xmodule/xmodule/library_content_module.py @@ -68,6 +68,7 @@ def _get_capa_types(): @XBlock.wants('library_tools') # Only needed in studio @XBlock.wants('studio_user_permissions') # Only available in studio @XBlock.wants('user') +@XBlock.needs('mako') class LibraryContentBlock( MakoTemplateBlockBase, XmlMixin, @@ -364,7 +365,7 @@ def student_view(self, context): # lint-amnesty, pylint: disable=missing-functi 'content': rendered_child.content, }) - fragment.add_content(self.system.render_template('vert_module.html', { + fragment.add_content(self.runtime.service(self, 'mako').render_template('vert_module.html', { 'items': contents, 'xblock_context': context, 'show_bookmark_button': False, @@ -386,10 +387,11 @@ def author_view(self, context): if is_root: # User has clicked the "View" link. Show a preview of all possible children: if self.children: # pylint: disable=no-member - fragment.add_content(self.system.render_template("library-block-author-preview-header.html", { - 'max_count': self.max_count, - 'display_name': self.display_name or self.url_name, - })) + fragment.add_content(self.runtime.service(self, 'mako').render_template( + "library-block-author-preview-header.html", { + 'max_count': self.max_count, + 'display_name': self.display_name or self.url_name, + })) context['can_edit_visibility'] = False context['can_move'] = False self.render_children(context, fragment, can_reorder=False, can_add=False) @@ -406,7 +408,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'LibraryContentBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) diff --git a/common/lib/xmodule/xmodule/library_root_xblock.py b/common/lib/xmodule/xmodule/library_root_xblock.py index ed2fc452a33e..32581330aa0b 100644 --- a/common/lib/xmodule/xmodule/library_root_xblock.py +++ b/common/lib/xmodule/xmodule/library_root_xblock.py @@ -17,6 +17,7 @@ _ = lambda text: text +@XBlock.needs('mako') class LibraryRoot(XBlock): """ The LibraryRoot is the root XBlock of a content library. All other blocks in @@ -103,7 +104,7 @@ def render_children(self, context, fragment, can_reorder=False, can_add=False): }) fragment.add_content( - self.runtime.render_template("studio_render_paged_children_view.html", { + self.runtime.service(self, 'mako').render_template("studio_render_paged_children_view.html", { 'items': contents, 'xblock_context': context, 'can_add': can_add, diff --git a/common/lib/xmodule/xmodule/lti_module.py b/common/lib/xmodule/xmodule/lti_module.py index f38cb482e8ee..29acd2b155d5 100644 --- a/common/lib/xmodule/xmodule/lti_module.py +++ b/common/lib/xmodule/xmodule/lti_module.py @@ -270,6 +270,7 @@ class LTIFields: @XBlock.needs("i18n") +@XBlock.needs("mako") @XBlock.needs("user") class LTIBlock( LTIFields, @@ -401,7 +402,7 @@ def studio_view(self, _context): # Add our specific template information (the raw data body) context.update({'data': self.data}) fragment = Fragment( - self.system.render_template(self.mako_template, context) + self.runtime.service(self, 'mako').render_template(self.mako_template, context) ) add_webpack_to_fragment(fragment, 'LTIBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) @@ -517,7 +518,7 @@ def student_view(self, _context): Return the student view. """ fragment = Fragment() - fragment.add_content(self.system.render_template('lti.html', self.get_context())) + fragment.add_content(self.runtime.service(self, 'mako').render_template('lti.html', self.get_context())) add_webpack_to_fragment(fragment, 'LTIBlockPreview') shim_xmodule_js(fragment, 'LTI') return fragment @@ -527,7 +528,7 @@ def preview_handler(self, _, __): """ This is called to get context with new oauth params to iframe. """ - template = self.system.render_template('lti_form.html', self.get_context()) + template = self.runtime.service(self, 'mako').render_template('lti_form.html', self.get_context()) return Response(template, content_type='text/html') def get_user_id(self): diff --git a/common/lib/xmodule/xmodule/mako_module.py b/common/lib/xmodule/xmodule/mako_module.py index ab1a5ed51399..b34e040798ff 100644 --- a/common/lib/xmodule/xmodule/mako_module.py +++ b/common/lib/xmodule/xmodule/mako_module.py @@ -9,11 +9,25 @@ class MakoDescriptorSystem(DescriptorSystem): # lint-amnesty, pylint: disable=abstract-method + """ + Descriptor system that renders mako templates. + """ def __init__(self, render_template, **kwargs): super().__init__(**kwargs) self.render_template = render_template + # Add the MakoService to the descriptor system. + # + # This is not needed by most XBlocks, because they are initialized with a full runtime ModuleSystem that already + # has the MakoService. + # However, there are a few cases where the XBlock only has the descriptor system instead of the full module + # runtime. Specifically: + # * in the Instructor Dashboard bulk emails tab, when rendering the HtmlBlock for its WYSIWYG editor. + # * during testing, when using the ModuleSystemTestCase to fetch factory-created blocks. + from common.djangoapps.edxmako.services import MakoService + self._services['mako'] = MakoService() + class MakoTemplateBlockBase: """ diff --git a/common/lib/xmodule/xmodule/poll_module.py b/common/lib/xmodule/xmodule/poll_module.py index 6c739f4a91f6..6ec06009e5d8 100644 --- a/common/lib/xmodule/xmodule/poll_module.py +++ b/common/lib/xmodule/xmodule/poll_module.py @@ -18,6 +18,7 @@ from lxml import etree from openedx.core.djangolib.markup import Text, HTML +from xblock.core import XBlock from xblock.fields import Boolean, Dict, List, Scope, String # lint-amnesty, pylint: disable=wrong-import-order from xmodule.mako_module import MakoTemplateBlockBase from xmodule.stringify import stringify_children @@ -36,6 +37,7 @@ _ = lambda text: text +@XBlock.needs('mako') class PollBlock( MakoTemplateBlockBase, XmlMixin, @@ -162,7 +164,7 @@ def student_view(self, _context): 'ajax_url': self.ajax_url, 'configuration_json': self.dump_poll(), } - fragment.add_content(self.system.render_template('poll.html', params)) + fragment.add_content(self.runtime.service(self, 'mako').render_template('poll.html', params)) add_webpack_to_fragment(fragment, 'PollBlockPreview') shim_xmodule_js(fragment, 'Poll') return fragment diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 6ccd4fea8a39..c71ffce6b346 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -244,6 +244,7 @@ def is_proctored_exam(self, value): @XBlock.needs('user') @XBlock.needs('bookmarks') @XBlock.needs('i18n') +@XBlock.needs('mako') @XBlock.wants('content_type_gating') class SequenceBlock( SequenceMixin, @@ -537,7 +538,7 @@ def _hidden_content_student_view(self, context): if not self._can_user_view_content(course): banner_text = self._hidden_content_banner_text(course) - hidden_content_html = self.system.render_template( + hidden_content_html = self.runtime.service(self, 'mako').render_template( 'hidden_content.html', { 'self_paced': course.self_paced, @@ -606,7 +607,7 @@ def _student_or_public_view(self, context, prereq_met, prereq_meta_info, banner_ fragment = Fragment() params = self._get_render_metadata(context, display_items, prereq_met, prereq_meta_info, banner_text, view, fragment) # lint-amnesty, pylint: disable=line-too-long - fragment.add_content(self.system.render_template("seq_module.html", params)) + fragment.add_content(self.runtime.service(self, 'mako').render_template("seq_module.html", params)) self._capture_full_seq_item_metrics(display_items) self._capture_current_unit_metrics(display_items) diff --git a/common/lib/xmodule/xmodule/split_test_module.py b/common/lib/xmodule/xmodule/split_test_module.py index 734ea19c2c18..870f6ea5e0d6 100644 --- a/common/lib/xmodule/xmodule/split_test_module.py +++ b/common/lib/xmodule/xmodule/split_test_module.py @@ -123,6 +123,7 @@ def get_split_user_partitions(user_partitions): @XBlock.needs("i18n") @XBlock.needs('user_tags') # pylint: disable=abstract-method +@XBlock.needs('mako') @XBlock.needs('partitions') @XBlock.needs('user') class SplitTestBlock( # lint-amnesty, pylint: disable=abstract-method @@ -301,7 +302,7 @@ def _staff_view(self, context): sorted_inactive_contents = sorted(inactive_contents, key=itemgetter('group_name')) # Use the new template - fragment.add_content(self.system.render_template('split_test_staff_view.html', { + fragment.add_content(self.runtime.service(self, 'mako').render_template('split_test_staff_view.html', { 'items': sorted_active_contents + sorted_inactive_contents, })) fragment.add_css('.split-test-child { display: none; }') @@ -328,7 +329,7 @@ def author_view(self, context): fragment, inactive_children, context ) - fragment.add_content(self.system.render_template('split_test_author_view.html', { + fragment.add_content(self.runtime.service(self, 'mako').render_template('split_test_author_view.html', { 'split_test': self, 'is_root': is_root, 'is_configured': self.is_configured, @@ -367,7 +368,7 @@ def studio_view(self, context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'SplitTestBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) @@ -386,7 +387,7 @@ def student_view(self, context): return self._staff_view(context) else: child_fragment = self.child.render(STUDENT_VIEW, context) - fragment = Fragment(self.system.render_template('split_test_student_view.html', { + fragment = Fragment(self.runtime.service(self, 'mako').render_template('split_test_student_view.html', { 'child_content': child_fragment.content, 'child_id': self.child.scope_ids.usage_id, })) diff --git a/common/lib/xmodule/xmodule/studio_editable.py b/common/lib/xmodule/xmodule/studio_editable.py index 8352c781d14e..cd62283bc250 100644 --- a/common/lib/xmodule/xmodule/studio_editable.py +++ b/common/lib/xmodule/xmodule/studio_editable.py @@ -1,15 +1,20 @@ """ Mixin to support editing in Studio. """ +from xblock.core import XBlock from xmodule.x_module import AUTHOR_VIEW, STUDENT_VIEW, module_attr +@XBlock.needs('mako') class StudioEditableBlock: """ Helper methods for supporting Studio editing of XBlocks. This class is only intended to be used with an XBlock! """ + # Avoids AttributeError caused by the @XBlock.needs decorator. + _services_requested = {} + has_author_view = True def render_children(self, context, fragment, can_reorder=False, can_add=False): @@ -31,7 +36,7 @@ def render_children(self, context, fragment, can_reorder=False, can_add=False): 'content': rendered_child.content }) - fragment.add_content(self.system.render_template("studio_render_children_view.html", { # pylint: disable=no-member + fragment.add_content(self.runtime.service(self, 'mako').render_template("studio_render_children_view.html", { # pylint: disable=no-member 'items': contents, 'xblock_context': context, 'can_add': can_add, diff --git a/common/lib/xmodule/xmodule/template_module.py b/common/lib/xmodule/xmodule/template_module.py index 8729190a073f..51bc6ff2b209 100644 --- a/common/lib/xmodule/xmodule/template_module.py +++ b/common/lib/xmodule/xmodule/template_module.py @@ -3,6 +3,7 @@ """ from string import Template +from xblock.core import XBlock from lxml import etree from pkg_resources import resource_string @@ -40,6 +41,7 @@ class CustomTagTemplateBlock( # pylint: disable=abstract-method """ +@XBlock.needs('mako') class CustomTagBlock(CustomTagTemplateBlock): # pylint: disable=abstract-method """ This module supports tags of the form @@ -85,7 +87,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'CustomTagBlockStudio') shim_xmodule_js(fragment, 'XMLEditingDescriptor') diff --git a/common/lib/xmodule/xmodule/tests/test_html_module.py b/common/lib/xmodule/xmodule/tests/test_html_module.py index 5695941d0b3e..13fa83d268f9 100644 --- a/common/lib/xmodule/xmodule/tests/test_html_module.py +++ b/common/lib/xmodule/xmodule/tests/test_html_module.py @@ -315,7 +315,7 @@ def test_updates_order(self): template_name = f"{info_module.TEMPLATE_DIR}/course_updates.html" info_module.get_html() # Assertion to validate that render function is called with the expected context - info_module.system.render_template.assert_called_once_with( + info_module.runtime.service(info_module, 'mako').render_template.assert_called_once_with( template_name, expected_context ) diff --git a/common/lib/xmodule/xmodule/vertical_block.py b/common/lib/xmodule/xmodule/vertical_block.py index 4d1c82477b5f..14baf8678466 100644 --- a/common/lib/xmodule/xmodule/vertical_block.py +++ b/common/lib/xmodule/xmodule/vertical_block.py @@ -48,7 +48,7 @@ class VerticalFields: ) -@XBlock.needs('user', 'bookmarks') +@XBlock.needs('user', 'bookmarks', 'mako') @XBlock.wants('completion') @XBlock.wants('call_to_action') class VerticalBlock( @@ -151,7 +151,7 @@ def _student_or_public_view(self, context, view): child_context['username'], str(self.location)), # pylint: disable=no-member }) - fragment.add_content(self.system.render_template('vert_module.html', fragment_context)) + fragment.add_content(self.runtime.service(self, 'mako').render_template('vert_module.html', fragment_context)) add_webpack_to_fragment(fragment, 'VerticalStudentView') fragment.initialize_js('VerticalStudentView') diff --git a/common/lib/xmodule/xmodule/video_module/video_module.py b/common/lib/xmodule/xmodule/video_module/video_module.py index 8bcccb630672..2216c88488ca 100644 --- a/common/lib/xmodule/xmodule/video_module/video_module.py +++ b/common/lib/xmodule/xmodule/video_module/video_module.py @@ -108,6 +108,7 @@ @XBlock.wants('settings', 'completion', 'i18n', 'request_cache') +@XBlock.needs('mako') class VideoBlock( VideoFields, VideoTranscriptsMixin, VideoStudioViewHandlers, VideoStudentViewHandlers, TabsEditingMixin, EmptyDataRawMixin, XmlMixin, EditingMixin, @@ -245,7 +246,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'VideoBlockStudio') shim_xmodule_js(fragment, 'TabsEditingDescriptor') @@ -468,7 +469,7 @@ def get_html(self, view=STUDENT_VIEW): # lint-amnesty, pylint: disable=argument 'transcript_download_formats_list': self.fields['transcript_download_format'].values, # lint-amnesty, pylint: disable=unsubscriptable-object 'license': getattr(self, "license", None), } - return self.system.render_template('video.html', context) + return self.runtime.service(self, 'mako').render_template('video.html', context) def validate(self): """ diff --git a/common/lib/xmodule/xmodule/word_cloud_module.py b/common/lib/xmodule/xmodule/word_cloud_module.py index 8ada5d143655..5fc1c737be65 100644 --- a/common/lib/xmodule/xmodule/word_cloud_module.py +++ b/common/lib/xmodule/xmodule/word_cloud_module.py @@ -44,6 +44,7 @@ def pretty_bool(value): return value in bool_dict +@XBlock.needs('mako') class WordCloudBlock( # pylint: disable=abstract-method EmptyDataRawMixin, XmlMixin, @@ -279,7 +280,7 @@ def student_view(self, context): # lint-amnesty, pylint: disable=unused-argumen Renders the output that a student will see. """ fragment = Fragment() - fragment.add_content(self.system.render_template('word_cloud.html', { + fragment.add_content(self.runtime.service(self, 'mako').render_template('word_cloud.html', { 'ajax_url': self.ajax_url, 'display_name': self.display_name, 'instructions': self.instructions, @@ -304,7 +305,7 @@ def studio_view(self, _context): Return the studio view. """ fragment = Fragment( - self.system.render_template(self.mako_template, self.get_context()) + self.runtime.service(self, 'mako').render_template(self.mako_template, self.get_context()) ) add_webpack_to_fragment(fragment, 'WordCloudBlockStudio') shim_xmodule_js(fragment, self.studio_js_module_name) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 53530e0e2c53..056528ebd4ba 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -1748,7 +1748,6 @@ class ModuleSystemShim: """ @property -<<<<<<< HEAD def anonymous_student_id(self): """ Returns the anonymous user ID for the current user and course. diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index c409f97ff3a0..b4acc4c3f680 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -705,7 +705,7 @@ def rebind_noauth_module_to_user(module, real_user): mako_service = MakoService() if settings.FEATURES.get("LICENSING", False): - block_wrappers.append(wrap_with_license) + block_wrappers.append(partial(wrap_with_license, mako_service=mako_service)) # Wrap the output display in a single div to allow for the XModule # javascript to be bound correctly diff --git a/openedx/core/lib/license/wrapper.py b/openedx/core/lib/license/wrapper.py index 2d1701540820..23fb4f04926a 100644 --- a/openedx/core/lib/license/wrapper.py +++ b/openedx/core/lib/license/wrapper.py @@ -3,12 +3,12 @@ """ -def wrap_with_license(block, view, frag, context): # pylint: disable=unused-argument +def wrap_with_license(block, view, frag, context, mako_service): # pylint: disable=unused-argument """ In the LMS, display the custom license underneath the XBlock. """ license = getattr(block, "license", None) # pylint: disable=redefined-builtin if license: context = {"license": license} - frag.content += block.runtime.render_template('license_wrapper.html', context) + frag.content += mako_service.render_template('license_wrapper.html', context) return frag diff --git a/openedx/core/lib/xblock_builtin/xblock_discussion/xblock_discussion/__init__.py b/openedx/core/lib/xblock_builtin/xblock_discussion/xblock_discussion/__init__.py index 079b1765cbb4..61ad62e25d58 100644 --- a/openedx/core/lib/xblock_builtin/xblock_discussion/xblock_discussion/__init__.py +++ b/openedx/core/lib/xblock_builtin/xblock_discussion/xblock_discussion/__init__.py @@ -32,6 +32,7 @@ def _(text): @XBlock.needs('user') # pylint: disable=abstract-method @XBlock.needs('i18n') +@XBlock.needs('mako') class DiscussionXBlock(XBlock, StudioEditableXBlockMixin, XmlParserMixin): # lint-amnesty, pylint: disable=abstract-method """ Provides a discussion forum that is inline with other content in the courseware. @@ -202,7 +203,8 @@ def student_view(self, context=None): 'login_msg': login_msg, } - fragment.add_content(self.runtime.render_template('discussion/_discussion_inline.html', context)) + fragment.add_content(self.runtime.service(self, 'mako').render_template('discussion/_discussion_inline.html', + context)) fragment.initialize_js('DiscussionInlineBlock') return fragment @@ -212,7 +214,7 @@ def author_view(self, context=None): # pylint: disable=unused-argument Renders author view for Studio. """ fragment = Fragment() - fragment.add_content(self.runtime.render_template( + fragment.add_content(self.runtime.service(self, 'mako').render_template( 'discussion/_discussion_inline_studio.html', {'discussion_id': self.discussion_id} )) From 26b43465a44b3e784f433c3c9badda16e1a131cb Mon Sep 17 00:00:00 2001 From: Jillian Vogel Date: Tue, 5 Oct 2021 14:51:45 +1030 Subject: [PATCH 3/3] refactor: use MakoService.render_template to remove deprecation warnings from test code. --- .../contentstore/views/tests/test_preview.py | 3 +- .../xmodule/xmodule/tests/test_conditional.py | 6 ++- .../courseware/tests/test_video_mongo.py | 41 ++++++++++--------- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/cms/djangoapps/contentstore/views/tests/test_preview.py b/cms/djangoapps/contentstore/views/tests/test_preview.py index 7a3bbb2ea37d..18f9da6fce83 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -169,6 +169,7 @@ def test_block_branch_not_changed_by_preview_handler(self, default_store): @XBlock.needs("field-data") @XBlock.needs("i18n") +@XBlock.needs("mako") @XBlock.needs("user") @XBlock.needs("teams_configuration") class PureXBlock(XBlock): @@ -180,7 +181,7 @@ def student_view(self, context): Renders the output that a student will see. """ fragment = Fragment() - fragment.add_content(self.runtime.render_template('edxmako.html', context)) + fragment.add_content(self.runtime.service(self, 'mako').render_template('edxmako.html', context)) return fragment diff --git a/common/lib/xmodule/xmodule/tests/test_conditional.py b/common/lib/xmodule/xmodule/tests/test_conditional.py index 13f2dc6a9504..ac862add812c 100644 --- a/common/lib/xmodule/xmodule/tests/test_conditional.py +++ b/common/lib/xmodule/xmodule/tests/test_conditional.py @@ -166,7 +166,8 @@ def test_get_html(self): # because get_test_system returns the repr of the context dict passed to render_template, # we reverse it here html = modules['cond_module'].render(STUDENT_VIEW).content - expected = modules['cond_module'].xmodule_runtime.render_template('conditional_ajax.html', { + mako_service = modules['cond_module'].xmodule_runtime.service(modules['cond_module'], 'mako') + expected = mako_service.render_template('conditional_ajax.html', { 'ajax_url': modules['cond_module'].ajax_url, 'element_id': 'i4x-edX-conditional_test-conditional-SampleConditional', 'depends': 'i4x-edX-conditional_test-problem-SampleProblem', @@ -241,7 +242,8 @@ def test_conditional_module(self, _): module = self.get_module_for_location(location) html = module.render(STUDENT_VIEW).content - html_expect = module.xmodule_runtime.render_template( + mako_service = module.xmodule_runtime.service(module, 'mako') + html_expect = mako_service.render_template( 'conditional_ajax.html', { # Test ajax url is just usage-id / handler_name diff --git a/lms/djangoapps/courseware/tests/test_video_mongo.py b/lms/djangoapps/courseware/tests/test_video_mongo.py index 8c487a1902a3..bb3a94be2976 100644 --- a/lms/djangoapps/courseware/tests/test_video_mongo.py +++ b/lms/djangoapps/courseware/tests/test_video_mongo.py @@ -130,9 +130,9 @@ def test_video_constructor(self): 'poster': 'null', } + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) class TestVideoNonYouTube(TestVideo): # pylint: disable=test-inherits-tests @@ -211,8 +211,9 @@ def test_video_constructor(self): 'poster': 'null', } + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') expected_result = get_context_dict_from_string( - self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context) + mako_service.render_template('video.html', expected_context) ) assert get_context_dict_from_string(context) == expected_result assert expected_result['download_video_link'] == 'example.mp4' @@ -383,9 +384,9 @@ def test_get_html_track(self): 'metadata': json.dumps(metadata) }) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) def test_get_html_source(self): # lint-amnesty, pylint: disable=invalid-name, redefined-outer-name @@ -491,9 +492,9 @@ def test_get_html_source(self): 'metadata': json.dumps(expected_context['metadata']) }) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) def test_get_html_with_non_existent_edx_video_id(self): """ @@ -631,9 +632,9 @@ def test_get_html_with_mocked_edx_video_id(self): 'metadata': json.dumps(expected_context['metadata']) }) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) def test_get_html_with_existing_edx_video_id(self): """ @@ -659,9 +660,9 @@ def test_get_html_with_existing_edx_video_id(self): # context returned by get_html when provided with above data # expected_context, a dict to assert with context context, expected_context = self.helper_get_html_with_edx_video_id(data) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) def test_get_html_with_existing_unstripped_edx_video_id(self): """ @@ -690,9 +691,9 @@ def test_get_html_with_existing_unstripped_edx_video_id(self): # expected_context, a dict to assert with context context, expected_context = self.helper_get_html_with_edx_video_id(data) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) def encode_and_create_video(self, edx_video_id): """ @@ -899,9 +900,9 @@ def side_effect(*args, **kwargs): # lint-amnesty, pylint: disable=unused-argume 'metadata': json.dumps(expected_context['metadata']) }) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) # pylint: disable=invalid-name def test_get_html_cdn_source_external_video(self): @@ -1001,9 +1002,9 @@ def test_get_html_cdn_source_external_video(self): 'metadata': json.dumps(expected_context['metadata']) }) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') assert get_context_dict_from_string(context) ==\ - get_context_dict_from_string(self.item_descriptor.xmodule_runtime.render_template('video.html', - expected_context)) + get_context_dict_from_string(mako_service.render_template('video.html', expected_context)) @ddt.data( (True, ['youtube', 'desktop_webm', 'desktop_mp4', 'hls']), @@ -2234,7 +2235,8 @@ def test_bumper_metadata(self, get_url_for_profiles, get_bumper_settings, is_bum })) } - expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') + expected_content = mako_service.render_template('video.html', expected_context) assert get_context_dict_from_string(content) == get_context_dict_from_string(expected_content) @@ -2325,8 +2327,9 @@ def assert_content_matches_expectations(self, autoadvanceenabled_must_be, autoad autoadvance_flag=autoadvance_must_be, ) + mako_service = self.item_descriptor.xmodule_runtime.service(self.item_descriptor, 'mako') with override_settings(FEATURES=self.FEATURES): - expected_content = self.item_descriptor.xmodule_runtime.render_template('video.html', expected_context) + expected_content = mako_service.render_template('video.html', expected_context) assert get_context_dict_from_string(content) == get_context_dict_from_string(expected_content)