Merge pull request #16856 from open-craft/mtyaka/template-block-overrides

Allow theme template block overrides.
This commit is contained in:
Matt Tuchfarber
2017-12-22 09:14:02 -05:00
committed by GitHub
6 changed files with 142 additions and 6 deletions

View File

@@ -18,6 +18,15 @@ from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme
from . import LOOKUP
class TopLevelTemplateURI(unicode):
"""
A marker class for template URIs used to signal the template lookup infrastructure that the template corresponding
to the URI should be looked up straight in the standard edx-platform location instead of trying to locate an
overridding template in the current theme first.
"""
pass
class DynamicTemplateLookup(TemplateLookup):
"""
A specialization of the standard mako `TemplateLookup` class which allows
@@ -51,6 +60,28 @@ class DynamicTemplateLookup(TemplateLookup):
self._collection.clear()
self._uri_cache.clear()
def adjust_uri(self, uri, calling_uri):
"""
This method is called by mako when including a template in another template or when inheriting an existing mako
template. The method adjusts the `uri` to make it relative to the calling template's location.
This method is overridden to detect when a template from a theme tries to override the same template from a
standard location, for example when the dashboard.html template is overridden in the theme while at the same
time inheriting from the standard LMS dashboard.html template.
When this self-inheritance is detected, the uri is wrapped in the TopLevelTemplateURI marker class to ensure
that template lookup skips the current theme and looks up the built-in template in standard locations.
"""
# Make requested uri relative to the calling uri.
relative_uri = super(DynamicTemplateLookup, self).adjust_uri(uri, calling_uri)
# Is the calling template (calling_uri) which is including or inheriting current template (uri)
# located inside a theme?
if calling_uri != strip_site_theme_templates_path(calling_uri):
# Is the calling template trying to include/inherit itself?
if calling_uri == get_template_path_with_theme(relative_uri):
return TopLevelTemplateURI(relative_uri)
return relative_uri
def get_template(self, uri):
"""
Overridden method for locating a template in either the database or the site theme.
@@ -68,15 +99,24 @@ class DynamicTemplateLookup(TemplateLookup):
# if microsite template is not present or request is not in microsite then
# let mako find and serve a template
if not template:
try:
# Try to find themed template, i.e. see if current theme overrides the template
template = super(DynamicTemplateLookup, self).get_template(get_template_path_with_theme(uri))
except TopLevelLookupException:
# strip off the prefix path to theme and look in default template dirs
template = super(DynamicTemplateLookup, self).get_template(strip_site_theme_templates_path(uri))
if isinstance(uri, TopLevelTemplateURI):
template = self._get_toplevel_template(uri)
else:
try:
# Try to find themed template, i.e. see if current theme overrides the template
template = super(DynamicTemplateLookup, self).get_template(get_template_path_with_theme(uri))
except TopLevelLookupException:
template = self._get_toplevel_template(uri)
return template
def _get_toplevel_template(self, uri):
"""
Lookup a default/toplevel template, ignoring current theme.
"""
# Strip off the prefix path to theme and look in default template dirs.
return super(DynamicTemplateLookup, self).get_template(strip_site_theme_templates_path(uri))
def clear_lookups(namespace):
"""

View File

@@ -0,0 +1,8 @@
<%page expression_filter="h"/>
# Include template which does not exist in the theme.
<%include file="/courseware/error-message.html" />
# Include template which is overriden in the theme.
<%include file="/courseware/info.html" />
# Include custom template which only exists in the theme.
<%include file="/courseware/test-theme-custom.html" />

View File

@@ -0,0 +1,2 @@
<%page expression_filter="h"/>
<p>This overrides the courseware/info.html template.</p>

View File

@@ -0,0 +1,2 @@
<%page expression_filter="h"/>
<p>This is a custom template.</p>

View File

@@ -0,0 +1,9 @@
<%page expression_filter="h"/>
<%!
from openedx.core.djangolib.markup import HTML
%>
<%inherit file="dashboard.html" />
<%block name="pagetitle">Overridden Title!</%block>
${HTML(parent.body())}
<%block name="bodyextra">Overriden Body Extra!</%block>

View File

@@ -64,6 +64,81 @@ class TestComprehensiveThemeLMS(TestCase):
result = staticfiles.finders.find('test-theme/images/logo.png')
self.assertEqual(result, settings.TEST_THEME / 'lms/static/images/logo.png')
@with_comprehensive_theme("test-theme")
def test_override_block_in_parent(self):
"""
Test that theme title is used instead of parent title.
"""
self._login()
dashboard_url = reverse('dashboard')
resp = self.client.get(dashboard_url)
self.assertEqual(resp.status_code, 200)
# This string comes from the 'pagetitle' block of the overriding theme.
self.assertContains(resp, "Overridden Title!")
@with_comprehensive_theme("test-theme")
def test_override_block_in_grandparent(self):
"""
Test that theme title is used instead of parent's parent's title.
"""
self._login()
dashboard_url = reverse('dashboard')
resp = self.client.get(dashboard_url)
self.assertEqual(resp.status_code, 200)
# This string comes from the 'bodyextra' block of the overriding theme.
self.assertContains(resp, "Overriden Body Extra!")
@with_comprehensive_theme("test-theme")
def test_parent_content_in_self_inherited_template(self):
"""
Test that parent's body is present in self inherited template.
"""
self._login()
dashboard_url = reverse('dashboard')
resp = self.client.get(dashboard_url)
self.assertEqual(resp.status_code, 200)
# This string comes from the default dashboard.html template.
self.assertContains(resp, "Explore courses")
@with_comprehensive_theme("test-theme")
def test_include_default_template(self):
"""
Test that theme template can include template which is not part of the theme.
"""
self._login()
courses_url = reverse('courses')
resp = self.client.get(courses_url)
self.assertEqual(resp.status_code, 200)
# The courses.html template includes the error-message.html template.
# Verify that the error message is included in the output.
self.assertContains(resp, "this module is temporarily unavailable")
@with_comprehensive_theme("test-theme")
def test_include_overridden_template(self):
"""
Test that theme template can include template which is overridden in the active theme.
"""
self._login()
courses_url = reverse('courses')
resp = self.client.get(courses_url)
self.assertEqual(resp.status_code, 200)
# The courses.html template includes the info.html file, which is overriden in the theme.
self.assertContains(resp, "This overrides the courseware/info.html template.")
@with_comprehensive_theme("test-theme")
def test_include_custom_template(self):
"""
Test that theme template can include template which is only present in the theme, but has no standard LMS
equivalent.
"""
self._login()
courses_url = reverse('courses')
resp = self.client.get(courses_url)
self.assertEqual(resp.status_code, 200)
# The courses.html template includes the test-theme.custom.html file.
# Verify its contents are present in the output.
self.assertContains(resp, "This is a custom template.")
@skip_unless_cms
class TestComprehensiveThemeCMS(TestCase):