paths.py 4.8 KB
Newer Older
1 2 3
"""
Set up lookup paths for mako templates.
"""
4 5

import hashlib
David Baumgold committed
6
import contextlib
7 8 9 10 11
import os
import pkg_resources

from django.conf import settings
from mako.lookup import TemplateLookup
12
from mako.exceptions import TopLevelLookupException
13 14

from . import LOOKUP
15 16 17 18 19
from openedx.core.djangoapps.theming.helpers import (
    get_template as themed_template,
    get_template_path_with_theme,
    strip_site_theme_templates_path,
)
20 21 22 23 24 25 26


class DynamicTemplateLookup(TemplateLookup):
    """
    A specialization of the standard mako `TemplateLookup` class which allows
    for adding directories progressively.
    """
27 28 29 30
    def __init__(self, *args, **kwargs):
        super(DynamicTemplateLookup, self).__init__(*args, **kwargs)
        self.__original_module_directory = self.template_args['module_directory']

David Baumgold committed
31 32 33
    def __repr__(self):
        return "<{0.__class__.__name__} {0.directories}>".format(self)

34
    def add_directory(self, directory, prepend=False):
35 36 37
        """
        Add a new directory to the template lookup path.
        """
38 39 40 41
        if prepend:
            self.directories.insert(0, os.path.normpath(directory))
        else:
            self.directories.append(os.path.normpath(directory))
42

43 44 45 46 47 48 49 50 51 52 53 54
        # 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()

55 56
    def get_template(self, uri):
        """
57 58 59 60 61
        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`
62

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
        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:
            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))

        return template
80

81

82 83 84 85 86 87 88
def clear_lookups(namespace):
    """
    Remove mako template lookups for the given namespace.
    """
    if namespace in LOOKUP:
        del LOOKUP[namespace]

89

90
def add_lookup(namespace, directory, package=None, prepend=False):
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
    """
    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)
109
    templates.add_directory(directory, prepend=prepend)
110 111 112 113 114 115 116


def lookup_template(namespace, name):
    """
    Look up a Mako template by namespace and name.
    """
    return LOOKUP[namespace].get_template(name)
David Baumgold committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139


@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)