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: