diff --git a/cms/djangoapps/contentstore/views/helpers.py b/cms/djangoapps/contentstore/views/helpers.py index ac114bfd37..424fe76475 100644 --- a/cms/djangoapps/contentstore/views/helpers.py +++ b/cms/djangoapps/contentstore/views/helpers.py @@ -14,7 +14,6 @@ from xmodule.modulestore.django import modulestore 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 3ed065eb0b..800c47456a 100644 --- a/cms/djangoapps/contentstore/views/item.py +++ b/cms/djangoapps/contentstore/views/item.py @@ -32,6 +32,7 @@ from xblock.fields import Scope 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 @@ class StudioEditModuleRuntime: 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 1abafe5fb9..92cba7a16e 100644 --- a/cms/djangoapps/contentstore/views/preview.py +++ b/cms/djangoapps/contentstore/views/preview.py @@ -19,6 +19,7 @@ from cms.djangoapps.xblock_config.models import StudioConfig 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 xmodule.x_module import AUTHOR_VIEW, PREVIEW_VIEWS, STUDENT_VIEW, ModuleSys 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 07bfc85421..7a3bbb2ea3 100644 --- a/cms/djangoapps/contentstore/views/tests/test_preview.py +++ b/cms/djangoapps/contentstore/views/tests/test_preview.py @@ -8,6 +8,7 @@ from unittest import mock 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 @@ class StudioXBlockServiceBindingTest(ModuleStoreTestCase): ) 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 0000000000..6ee2b096d6 --- /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 2e0bf2e48e..69d350cf44 100644 --- a/common/djangoapps/edxmako/tests.py +++ b/common/djangoapps/edxmako/tests.py @@ -14,6 +14,7 @@ from edx_django_utils.cache import RequestCache 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 @@ class MakoRequestContextTest(TestCase): 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 c91da247d8..533bff5c69 100644 --- a/common/lib/xmodule/xmodule/tests/__init__.py +++ b/common/lib/xmodule/xmodule/tests/__init__.py @@ -11,7 +11,6 @@ Run like this: import inspect import json import os -import pprint import sys import traceback import unittest @@ -34,7 +33,7 @@ from xmodule.modulestore import ModuleStoreEnum 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_test_system( 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_test_system( hostname="edx.org", services={ 'user': user_service, + 'mako': mako_service, }, xqueue={ 'interface': None, @@ -161,7 +157,7 @@ def get_test_system( ) -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 4e9e9d1d14..22dc868924 100644 --- a/common/lib/xmodule/xmodule/tests/helpers.py +++ b/common/lib/xmodule/xmodule/tests/helpers.py @@ -5,6 +5,7 @@ Utility methods for unit tests. 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 directories_equal(directory1, directory2): 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 58de4adc26..4121cac007 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 @@ class CapaFactory: 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 @@ class CapaFactory: 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 @@ class CapaFactory: # 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss # 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss # 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss """ 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss 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 @@ class ProblemBlockTest(unittest.TestCase): # lint-amnesty, pylint: disable=miss """ 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 480437a1c8..fd1afccdb8 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 @@ class CapaFactoryWithDelay: # 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 2515adf119..0f29595be6 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 2776c52a6b..9b4d4fc910 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 @@ class EditableMetadataFieldsTest(unittest.TestCase): 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 593d8f1176..53530e0e2c 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 @@ class ModuleSystemShim: 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 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, 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 @@ class ModuleSystem(MetricsMixin, ConfigurableFragmentWrapper, ModuleSystemShim, 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 0000000000..fd95201801 --- /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 d6ac518e95..c409f97ff3 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -52,7 +52,6 @@ from lms.djangoapps.courseware.masquerade import ( 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.student.roles import CourseBetaTesterRole 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 get_module_system_for_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 get_module_system_for_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 get_module_system_for_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 d500bf5bbf..83a27122bb 100644 --- a/lms/djangoapps/courseware/tests/helpers.py +++ b/lms/djangoapps/courseware/tests/helpers.py @@ -7,6 +7,7 @@ import ast 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 @@ class BaseTestXmodule(ModuleStoreTestCase): 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 e48973570a..b268f7c878 100644 --- a/lms/djangoapps/courseware/tests/test_discussion_xblock.py +++ b/lms/djangoapps/courseware/tests/test_discussion_xblock.py @@ -42,7 +42,6 @@ class TestDiscussionXBlock(XModuleRenderingTestBase): 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 @@ class TestViews(TestDiscussionXBlock): 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 bc19f06e83..1ea9b2908c 100644 --- a/lms/djangoapps/courseware/tests/test_module_render.py +++ b/lms/djangoapps/courseware/tests/test_module_render.py @@ -2687,3 +2687,17 @@ class LmsModuleSystemShimTest(SharedModuleStoreTestCase): 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 42c20de05b..3e74f82f8c 100644 --- a/lms/djangoapps/lms_xblock/test/test_runtime.py +++ b/lms/djangoapps/lms_xblock/test/test_runtime.py @@ -63,7 +63,6 @@ class TestHandlerUrl(TestCase): 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 @@ class TestUserServiceAPI(TestCase): 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 @@ class TestBadgingService(ModuleStoreTestCase): 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 @@ class TestI18nService(ModuleStoreTestCase): 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 d43db2672c..43286bfb6e 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -495,6 +495,20 @@ ENTERPRISE_CONSENT_API_URL = 'http://enterprise.example.com/consent/api/v1/' 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 558259d998..93984037c8 100644 --- a/openedx/core/djangoapps/xblock/runtime/runtime.py +++ b/openedx/core/djangoapps/xblock/runtime/runtime.py @@ -19,6 +19,7 @@ from xblock.field_data import SplitFieldData 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 @@ class XBlockRuntime(RuntimeShim, Runtime): 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: