This allows an overridding template from a theme to inherit from the same corresponding standard template. This is useful when you only want to override one or more named blocks, but otherwise make no modifications to the standard template.
180 lines
6.9 KiB
Python
180 lines
6.9 KiB
Python
"""
|
|
Set up lookup paths for mako templates.
|
|
"""
|
|
|
|
import contextlib
|
|
import hashlib
|
|
import os
|
|
|
|
import pkg_resources
|
|
from django.conf import settings
|
|
from mako.exceptions import TopLevelLookupException
|
|
from mako.lookup import TemplateLookup
|
|
|
|
from request_cache.middleware import request_cached
|
|
from openedx.core.djangoapps.theming.helpers import get_template as themed_template
|
|
from openedx.core.djangoapps.theming.helpers import get_template_path_with_theme, strip_site_theme_templates_path
|
|
|
|
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
|
|
for adding directories progressively.
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super(DynamicTemplateLookup, self).__init__(*args, **kwargs)
|
|
self.__original_module_directory = self.template_args['module_directory']
|
|
|
|
def __repr__(self):
|
|
return "<{0.__class__.__name__} {0.directories}>".format(self)
|
|
|
|
def add_directory(self, directory, prepend=False):
|
|
"""
|
|
Add a new directory to the template lookup path.
|
|
"""
|
|
if prepend:
|
|
self.directories.insert(0, os.path.normpath(directory))
|
|
else:
|
|
self.directories.append(os.path.normpath(directory))
|
|
|
|
# Since the lookup path has changed, the compiled modules might be
|
|
# wrong because now "foo.html" might be a completely different template,
|
|
# and "foo.html.py" in the module directory has no way to know that.
|
|
# Update the module_directory argument to point to a directory
|
|
# specifically for this lookup path.
|
|
unique = hashlib.md5(":".join(str(d) for d in self.directories)).hexdigest()
|
|
self.template_args['module_directory'] = os.path.join(self.__original_module_directory, unique)
|
|
|
|
# Also clear the internal caches. Ick.
|
|
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.
|
|
|
|
If not found, template lookup will be done in comprehensive theme for current site
|
|
by prefixing path to theme.
|
|
e.g if uri is `main.html` then new uri would be something like this `/red-theme/lms/static/main.html`
|
|
|
|
If still unable to find a template, it will fallback to the default template directories after stripping off
|
|
the prefix path to theme.
|
|
"""
|
|
# try to get template for the given file from microsite
|
|
template = themed_template(uri)
|
|
|
|
# if microsite template is not present or request is not in microsite then
|
|
# let mako find and serve a template
|
|
if not template:
|
|
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):
|
|
"""
|
|
Remove mako template lookups for the given namespace.
|
|
"""
|
|
if namespace in LOOKUP:
|
|
del LOOKUP[namespace]
|
|
|
|
|
|
def add_lookup(namespace, directory, package=None, prepend=False):
|
|
"""
|
|
Adds a new mako template lookup directory to the given namespace.
|
|
|
|
If `package` is specified, `pkg_resources` is used to look up the directory
|
|
inside the given package. Otherwise `directory` is assumed to be a path
|
|
in the filesystem.
|
|
"""
|
|
templates = LOOKUP.get(namespace)
|
|
if not templates:
|
|
LOOKUP[namespace] = templates = DynamicTemplateLookup(
|
|
module_directory=settings.MAKO_MODULE_DIR,
|
|
output_encoding='utf-8',
|
|
input_encoding='utf-8',
|
|
default_filters=['decode.utf8'],
|
|
encoding_errors='replace',
|
|
)
|
|
if package:
|
|
directory = pkg_resources.resource_filename(package, directory)
|
|
templates.add_directory(directory, prepend=prepend)
|
|
|
|
|
|
@request_cached
|
|
def lookup_template(namespace, name):
|
|
"""
|
|
Look up a Mako template by namespace and name.
|
|
"""
|
|
return LOOKUP[namespace].get_template(name)
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def save_lookups():
|
|
"""
|
|
A context manager to save and restore the Mako template lookup path.
|
|
|
|
Useful for testing.
|
|
|
|
"""
|
|
# Make a copy of the list of directories for each namespace.
|
|
namespace_dirs = {namespace: list(look.directories) for namespace, look in LOOKUP.items()}
|
|
|
|
try:
|
|
yield
|
|
finally:
|
|
# Get rid of all the lookups.
|
|
LOOKUP.clear()
|
|
|
|
# Re-create the lookups from our saved list.
|
|
for namespace, directories in namespace_dirs.items():
|
|
for directory in directories:
|
|
add_lookup(namespace, directory)
|