# -*- coding: utf-8 -*- # # Copyright (C) 2014-2015 Harvard, edX, OpenCraft # # This software's license gives you freedom; you can copy, convey, # propagate, redistribute and/or modify this program under the terms of # the GNU Affero General Public License (AGPL) as published by the Free # Software Foundation (FSF), either version 3 of the License, or (at your # option) any later version of the AGPL published by the FSF. # # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero # General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program in a file in the toplevel directory called # "AGPLv3". If not, see <http://www.gnu.org/licenses/>. # """ Helper class (ResourceLoader) for loading resources used by an XBlock """ import os import sys import warnings import pkg_resources import django from django.template import Context, Template, Engine, base as TemplateBase from mako.template import Template as MakoTemplate from mako.lookup import TemplateLookup as MakoTemplateLookup class ResourceLoader(object): """Loads resources relative to the module named by the module_name parameter.""" def __init__(self, module_name): self.module_name = module_name def load_unicode(self, resource_path): """ Gets the content of a resource """ resource_content = pkg_resources.resource_string(self.module_name, resource_path) return resource_content.decode('utf-8') def render_django_template(self, template_path, context=None, i18n_service=None): """ Evaluate a django template by resource path, applying the provided context. """ context = context or {} context['_i18n_service'] = i18n_service libraries = { 'i18n': 'xblockutils.templatetags.i18n', } # For django 1.8, we have to load the libraries manually, and restore them once the template is rendered. _libraries = None if django.VERSION[0] == 1 and django.VERSION[1] == 8: _libraries = TemplateBase.libraries.copy() for library_name in libraries: library = TemplateBase.import_library(libraries[library_name]) # pylint: disable=no-member if library: TemplateBase.libraries[library_name] = library engine = Engine() else: # Django>1.8 Engine can load the extra templatetag libraries itself # but we have to override the default installed libraries. from django.template.backends.django import get_installed_libraries installed_libraries = get_installed_libraries() installed_libraries.update(libraries) engine = Engine(libraries=installed_libraries) template_str = self.load_unicode(template_path) template = Template(template_str, engine=engine) rendered = template.render(Context(context)) # Restore the original TemplateBase.libraries if _libraries is not None: TemplateBase.libraries = _libraries return rendered def render_mako_template(self, template_path, context=None): """ Evaluate a mako template by resource path, applying the provided context """ context = context or {} template_str = self.load_unicode(template_path) lookup = MakoTemplateLookup(directories=[pkg_resources.resource_filename(self.module_name, '')]) template = MakoTemplate(template_str, lookup=lookup) return template.render(**context) def render_template(self, template_path, context=None): """ This function has been deprecated. It calls render_django_template to support backwards compatibility. """ warnings.warn( "ResourceLoader.render_template has been deprecated in favor of ResourceLoader.render_django_template" ) return self.render_django_template(template_path, context) def render_js_template(self, template_path, element_id, context=None): """ Render a js template. """ context = context or {} return u"<script type='text/template' id='{}'>\n{}\n</script>".format( element_id, self.render_django_template(template_path, context) ) def load_scenarios_from_path(self, relative_scenario_dir, include_identifier=False): """ Returns an array of (title, xmlcontent) from files contained in a specified directory, formatted as expected for the return value of the workbench_scenarios() method. If `include_identifier` is True, returns an array of (identifier, title, xmlcontent). """ base_dir = os.path.dirname(os.path.realpath(sys.modules[self.module_name].__file__)) scenario_dir = os.path.join(base_dir, relative_scenario_dir) scenarios = [] if os.path.isdir(scenario_dir): for template in sorted(os.listdir(scenario_dir)): if not template.endswith('.xml'): continue identifier = template[:-4] title = identifier.replace('_', ' ').title() template_path = os.path.join(relative_scenario_dir, template) scenario = str(self.render_django_template(template_path, {"url_name": identifier})) if not include_identifier: scenarios.append((title, scenario)) else: scenarios.append((identifier, title, scenario)) return scenarios