Merge pull request #16710 from edx/jmbowman/PLAT-1419
PLAT-1419 Make edxmako a proper template backend
This commit is contained in:
@@ -44,11 +44,11 @@ def event(request):
|
||||
return HttpResponse(status=204)
|
||||
|
||||
|
||||
def render_from_lms(template_name, dictionary, context=None, namespace='main'):
|
||||
def render_from_lms(template_name, dictionary, namespace='main'):
|
||||
"""
|
||||
Render a template using the LMS MAKO_TEMPLATES
|
||||
Render a template using the LMS Mako templates
|
||||
"""
|
||||
return render_to_string(template_name, dictionary, context, namespace="lms." + namespace)
|
||||
return render_to_string(template_name, dictionary, namespace="lms." + namespace)
|
||||
|
||||
|
||||
def get_parent_xblock(xblock):
|
||||
|
||||
@@ -119,7 +119,7 @@ from lms.envs.common import (
|
||||
VIDEO_TRANSCRIPTS_SETTINGS,
|
||||
|
||||
# Methods to derive settings
|
||||
_make_main_mako_templates,
|
||||
_make_mako_template_dirs,
|
||||
_make_locale_paths,
|
||||
)
|
||||
from path import Path as path
|
||||
@@ -134,7 +134,7 @@ from openedx.core.djangoapps.theming.helpers_dirs import (
|
||||
get_theme_base_dirs_from_settings
|
||||
)
|
||||
from openedx.core.lib.license import LicenseMixin
|
||||
from openedx.core.lib.derived import derived, derived_dict_entry
|
||||
from openedx.core.lib.derived import derived, derived_collection_entry
|
||||
from openedx.core.release import doc_version
|
||||
|
||||
############################ FEATURE CONFIGURATION #############################
|
||||
@@ -310,11 +310,9 @@ GEOIPV6_PATH = REPO_ROOT / "common/static/data/geoip/GeoIPv6.dat"
|
||||
|
||||
############################# TEMPLATE CONFIGURATION #############################
|
||||
# Mako templating
|
||||
# TODO: Move the Mako templating into a different engine in TEMPLATES below.
|
||||
import tempfile
|
||||
MAKO_MODULE_DIR = os.path.join(tempfile.gettempdir(), 'mako_cms')
|
||||
MAKO_TEMPLATES = {}
|
||||
MAIN_MAKO_TEMPLATES_BASE = [
|
||||
MAKO_TEMPLATE_DIRS_BASE = [
|
||||
PROJECT_ROOT / 'templates',
|
||||
COMMON_ROOT / 'templates',
|
||||
COMMON_ROOT / 'djangoapps' / 'pipeline_mako' / 'templates',
|
||||
@@ -324,19 +322,27 @@ MAIN_MAKO_TEMPLATES_BASE = [
|
||||
OPENEDX_ROOT / 'core' / 'lib' / 'license' / 'templates',
|
||||
CMS_ROOT / 'djangoapps' / 'pipeline_js' / 'templates',
|
||||
]
|
||||
MAKO_TEMPLATES['lms.main'] = lms.envs.common.MAIN_MAKO_TEMPLATES_BASE
|
||||
|
||||
MAKO_TEMPLATES['main'] = _make_main_mako_templates
|
||||
derived_dict_entry('MAKO_TEMPLATES', 'main')
|
||||
CONTEXT_PROCESSORS = (
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.static',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.contrib.auth.context_processors.auth', # this is required for admin
|
||||
'django.template.context_processors.csrf',
|
||||
'dealer.contrib.django.staff.context_processor', # access git revision
|
||||
'help_tokens.context_processor',
|
||||
)
|
||||
|
||||
# Django templating
|
||||
TEMPLATES = [
|
||||
{
|
||||
'NAME': 'django',
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
# Don't look for template source files inside installed applications.
|
||||
'APP_DIRS': False,
|
||||
# Instead, look for template source files in these dirs.
|
||||
'DIRS': MAIN_MAKO_TEMPLATES_BASE,
|
||||
'DIRS': _make_mako_template_dirs,
|
||||
# Options specific to this backend.
|
||||
'OPTIONS': {
|
||||
'loaders': (
|
||||
@@ -346,21 +352,36 @@ TEMPLATES = [
|
||||
'edxmako.makoloader.MakoFilesystemLoader',
|
||||
'edxmako.makoloader.MakoAppDirectoriesLoader',
|
||||
),
|
||||
'context_processors': (
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.static',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.contrib.auth.context_processors.auth', # this is required for admin
|
||||
'django.template.context_processors.csrf',
|
||||
'dealer.contrib.django.staff.context_processor', # access git revision
|
||||
'help_tokens.context_processor',
|
||||
),
|
||||
'context_processors': CONTEXT_PROCESSORS,
|
||||
# Change 'debug' in your environment settings files - not here.
|
||||
'debug': False
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'NAME': 'mako',
|
||||
'BACKEND': 'edxmako.backend.Mako',
|
||||
'APP_DIRS': False,
|
||||
'DIRS': _make_mako_template_dirs,
|
||||
'OPTIONS': {
|
||||
'context_processors': CONTEXT_PROCESSORS,
|
||||
'debug': False,
|
||||
}
|
||||
},
|
||||
{
|
||||
# This separate copy of the Mako backend is used to render previews using the LMS templates
|
||||
'NAME': 'preview',
|
||||
'BACKEND': 'edxmako.backend.Mako',
|
||||
'APP_DIRS': False,
|
||||
'DIRS': lms.envs.common.MAKO_TEMPLATE_DIRS_BASE,
|
||||
'OPTIONS': {
|
||||
'context_processors': CONTEXT_PROCESSORS,
|
||||
'debug': False,
|
||||
'namespace': 'lms.main',
|
||||
}
|
||||
},
|
||||
]
|
||||
derived_collection_entry('TEMPLATES', 0, 'DIRS')
|
||||
derived_collection_entry('TEMPLATES', 1, 'DIRS')
|
||||
DEFAULT_TEMPLATE_ENGINE = TEMPLATES[0]
|
||||
|
||||
##############################################################################
|
||||
|
||||
@@ -14,3 +14,13 @@
|
||||
LOOKUP = {}
|
||||
|
||||
from .paths import add_lookup, lookup_template, clear_lookups, save_lookups
|
||||
|
||||
|
||||
class Engines(object):
|
||||
"""
|
||||
Aliases for the available template engines.
|
||||
Note that the preview engine is only configured for cms.
|
||||
"""
|
||||
DJANGO = 'django'
|
||||
MAKO = 'mako'
|
||||
PREVIEW = 'preview'
|
||||
|
||||
@@ -20,8 +20,11 @@ class EdxMakoConfig(AppConfig):
|
||||
IMPORTANT: This method can be called multiple times during application startup. Any changes to this method
|
||||
must be safe for multiple callers during startup phase.
|
||||
"""
|
||||
template_locations = settings.MAKO_TEMPLATES
|
||||
for namespace, directories in template_locations.items():
|
||||
for backend in settings.TEMPLATES:
|
||||
if 'edxmako' not in backend['BACKEND']:
|
||||
continue
|
||||
namespace = backend['OPTIONS'].get('namespace', 'main')
|
||||
directories = backend['DIRS']
|
||||
clear_lookups(namespace)
|
||||
for directory in directories:
|
||||
add_lookup(namespace, directory)
|
||||
|
||||
67
common/djangoapps/edxmako/backend.py
Normal file
67
common/djangoapps/edxmako/backend.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""
|
||||
Django template system engine for Mako templates.
|
||||
"""
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from django.template import TemplateDoesNotExist, TemplateSyntaxError
|
||||
from django.template.backends.base import BaseEngine
|
||||
from django.template.context import _builtin_context_processors
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.module_loading import import_string
|
||||
from mako.exceptions import MakoException, TopLevelLookupException, text_error_template
|
||||
|
||||
from openedx.core.djangoapps.theming.helpers import get_template_path
|
||||
|
||||
from .paths import lookup_template
|
||||
from .template import Template
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Mako(BaseEngine):
|
||||
"""
|
||||
A Mako template engine to be added to the ``TEMPLATES`` Django setting.
|
||||
"""
|
||||
app_dirname = 'templates'
|
||||
|
||||
def __init__(self, params):
|
||||
"""
|
||||
Fetches template options, initializing BaseEngine properties,
|
||||
and assigning our Mako default settings.
|
||||
Note that OPTIONS contains backend-specific settings.
|
||||
:param params: This is simply the template dict you
|
||||
define in your settings file.
|
||||
"""
|
||||
params = params.copy()
|
||||
options = params.pop('OPTIONS').copy()
|
||||
super(Mako, self).__init__(params)
|
||||
self.context_processors = options.pop('context_processors', [])
|
||||
self.namespace = options.pop('namespace', 'main')
|
||||
|
||||
def from_string(self, template_code):
|
||||
try:
|
||||
return Template(template_code)
|
||||
except MakoException:
|
||||
message = text_error_template().render()
|
||||
raise TemplateSyntaxError(message)
|
||||
|
||||
def get_template(self, template_name):
|
||||
"""
|
||||
Loads and returns a template for the given name.
|
||||
"""
|
||||
template_name = get_template_path(template_name)
|
||||
try:
|
||||
return Template(lookup_template(self.namespace, template_name), engine=self)
|
||||
except TopLevelLookupException:
|
||||
raise TemplateDoesNotExist(template_name)
|
||||
|
||||
@cached_property
|
||||
def template_context_processors(self):
|
||||
"""
|
||||
Collect and cache the active context processors.
|
||||
"""
|
||||
context_processors = _builtin_context_processors
|
||||
context_processors += tuple(self.context_processors)
|
||||
return tuple(import_string(path) for path in context_processors)
|
||||
@@ -2,7 +2,7 @@ import logging
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.template import Engine
|
||||
from django.template import Engine, engines
|
||||
from django.template.base import TemplateDoesNotExist
|
||||
from django.template.loaders.app_directories import Loader as AppDirectoriesLoader
|
||||
from django.template.loaders.filesystem import Loader as FilesystemLoader
|
||||
@@ -16,8 +16,8 @@ log = logging.getLogger(__name__)
|
||||
class MakoLoader(object):
|
||||
"""
|
||||
This is a Django loader object which will load the template as a
|
||||
Mako template if the first line is "## mako". It is based off BaseLoader
|
||||
in django.template.loader.
|
||||
Mako template if the first line is "## mako". It is based off Loader
|
||||
in django.template.loaders.base.
|
||||
We need this in order to be able to include mako templates inside main_django.html.
|
||||
"""
|
||||
|
||||
@@ -53,7 +53,8 @@ class MakoLoader(object):
|
||||
output_encoding='utf-8',
|
||||
default_filters=['decode.utf8'],
|
||||
encoding_errors='replace',
|
||||
uri=template_name)
|
||||
uri=template_name,
|
||||
engine=engines['mako'])
|
||||
return template, None
|
||||
else:
|
||||
# This is a regular template
|
||||
|
||||
@@ -20,24 +20,12 @@ Methods for creating RequestContext for using with Mako templates.
|
||||
|
||||
|
||||
from crum import get_current_request
|
||||
from django.conf import settings
|
||||
from django.template import RequestContext
|
||||
from django.template.context import _builtin_context_processors
|
||||
from django.utils.module_loading import import_string
|
||||
|
||||
import request_cache
|
||||
from util.request import safe_get_host
|
||||
|
||||
|
||||
def get_template_context_processors():
|
||||
"""
|
||||
Returns the context processors defined in settings.TEMPLATES.
|
||||
"""
|
||||
context_processors = _builtin_context_processors
|
||||
context_processors += tuple(settings.DEFAULT_TEMPLATE_ENGINE['OPTIONS']['context_processors'])
|
||||
return tuple(import_string(path) for path in context_processors)
|
||||
|
||||
|
||||
def get_template_request_context(request=None):
|
||||
"""
|
||||
Returns the template processing context to use for the current request,
|
||||
@@ -60,13 +48,6 @@ def get_template_request_context(request=None):
|
||||
context['is_secure'] = request.is_secure()
|
||||
context['site'] = safe_get_host(request)
|
||||
|
||||
# This used to happen when a RequestContext object was initialized but was
|
||||
# moved to a different part of the logic when template engines were introduced.
|
||||
# Since we are not using template engines we do this here.
|
||||
# https://github.com/django/django/commit/37505b6397058bcc3460f23d48a7de9641cd6ef0
|
||||
for processor in get_template_context_processors():
|
||||
context.update(processor(request))
|
||||
|
||||
request_cache_dict[cache_key] = context
|
||||
|
||||
return context
|
||||
|
||||
@@ -18,12 +18,12 @@ from urlparse import urljoin
|
||||
from django.conf import settings
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.http import HttpResponse
|
||||
from django.template import Context
|
||||
from django.template import engines
|
||||
|
||||
from edxmako import lookup_template
|
||||
from edxmako.request_context import get_template_request_context
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming.helpers import get_template_path, is_request_in_themed_site
|
||||
from openedx.core.djangoapps.theming.helpers import is_request_in_themed_site
|
||||
|
||||
from . import Engines
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@@ -131,7 +131,7 @@ def footer_context_processor(request): # pylint: disable=unused-argument
|
||||
)
|
||||
|
||||
|
||||
def render_to_string(template_name, dictionary, context=None, namespace='main', request=None):
|
||||
def render_to_string(template_name, dictionary, namespace='main', request=None):
|
||||
"""
|
||||
Render a Mako template to as a string.
|
||||
|
||||
@@ -147,52 +147,23 @@ def render_to_string(template_name, dictionary, context=None, namespace='main',
|
||||
from the template paths specified in configuration.
|
||||
dictionary: A dictionary of variables to insert into the template during
|
||||
rendering.
|
||||
context: A :class:`~django.template.Context` with values to make
|
||||
available to the template.
|
||||
namespace: The Mako namespace to find the named template in.
|
||||
request: The request to use to construct the RequestContext for rendering
|
||||
this template. If not supplied, the current request will be used.
|
||||
"""
|
||||
|
||||
template_name = get_template_path(template_name)
|
||||
|
||||
context_instance = Context(dictionary)
|
||||
# add dictionary to context_instance
|
||||
context_instance.update(dictionary or {})
|
||||
# collapse context_instance to a single dictionary for mako
|
||||
context_dictionary = {}
|
||||
context_instance['settings'] = settings
|
||||
context_instance['EDX_ROOT_URL'] = settings.EDX_ROOT_URL
|
||||
context_instance['marketing_link'] = marketing_link
|
||||
context_instance['is_any_marketing_link_set'] = is_any_marketing_link_set
|
||||
context_instance['is_marketing_link_set'] = is_marketing_link_set
|
||||
|
||||
# In various testing contexts, there might not be a current request context.
|
||||
request_context = get_template_request_context(request)
|
||||
if request_context:
|
||||
for item in request_context:
|
||||
context_dictionary.update(item)
|
||||
for item in context_instance:
|
||||
context_dictionary.update(item)
|
||||
if context:
|
||||
context_dictionary.update(context)
|
||||
|
||||
# "Fix" CSRF token by evaluating the lazy object
|
||||
KEY_CSRF_TOKENS = ('csrf_token', 'csrf')
|
||||
for key in KEY_CSRF_TOKENS:
|
||||
if key in context_dictionary:
|
||||
context_dictionary[key] = unicode(context_dictionary[key])
|
||||
|
||||
# fetch and render template
|
||||
template = lookup_template(namespace, template_name)
|
||||
return template.render_unicode(**context_dictionary)
|
||||
if namespace == 'lms.main':
|
||||
engine = engines[Engines.PREVIEW]
|
||||
else:
|
||||
engine = engines[Engines.MAKO]
|
||||
template = engine.get_template(template_name)
|
||||
return template.render(dictionary, request)
|
||||
|
||||
|
||||
def render_to_response(template_name, dictionary=None, context_instance=None, namespace='main', request=None, **kwargs):
|
||||
def render_to_response(template_name, dictionary=None, namespace='main', request=None, **kwargs):
|
||||
"""
|
||||
Returns a HttpResponse whose content is filled with the result of calling
|
||||
lookup.get_template(args[0]).render with the passed arguments.
|
||||
"""
|
||||
|
||||
dictionary = dictionary or {}
|
||||
return HttpResponse(render_to_string(template_name, dictionary, context_instance, namespace, request), **kwargs)
|
||||
return HttpResponse(render_to_string(template_name, dictionary, namespace, request), **kwargs)
|
||||
|
||||
@@ -13,47 +13,89 @@
|
||||
# limitations under the License.
|
||||
|
||||
from django.conf import settings
|
||||
from django.template import Context, engines
|
||||
from mako.template import Template as MakoTemplate
|
||||
from six import text_type
|
||||
|
||||
import edxmako
|
||||
from edxmako.request_context import get_template_request_context
|
||||
from edxmako.shortcuts import marketing_link
|
||||
from . import Engines, LOOKUP
|
||||
from .request_context import get_template_request_context
|
||||
from .shortcuts import is_any_marketing_link_set, is_marketing_link_set, marketing_link
|
||||
|
||||
KEY_CSRF_TOKENS = ('csrf_token', 'csrf')
|
||||
|
||||
|
||||
# TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate)
|
||||
|
||||
|
||||
class Template(MakoTemplate):
|
||||
class Template(object):
|
||||
"""
|
||||
This bridges the gap between a Mako template and a djano template. It can
|
||||
be rendered like it is a django template because the arguments are transformed
|
||||
This bridges the gap between a Mako template and a Django template. It can
|
||||
be rendered like it is a Django template because the arguments are transformed
|
||||
in a way that MakoTemplate can understand.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Overrides base __init__ to provide django variable overrides"""
|
||||
if not kwargs.get('no_django', False):
|
||||
kwargs['lookup'] = edxmako.LOOKUP['main']
|
||||
super(Template, self).__init__(*args, **kwargs)
|
||||
self.engine = kwargs.pop('engine', engines[Engines.MAKO])
|
||||
if len(args) and isinstance(args[0], MakoTemplate):
|
||||
self.mako_template = args[0]
|
||||
else:
|
||||
kwargs['lookup'] = LOOKUP['main']
|
||||
self.mako_template = MakoTemplate(*args, **kwargs)
|
||||
|
||||
def render(self, context_instance):
|
||||
def render(self, context=None, request=None):
|
||||
"""
|
||||
This takes a render call with a context (from Django) and translates
|
||||
it to a render call on the mako template.
|
||||
"""
|
||||
# collapse context_instance to a single dictionary for mako
|
||||
context_dictionary = {}
|
||||
context_object = self._get_context_object(request)
|
||||
context_dictionary = self._get_context_processors_output_dict(context_object)
|
||||
|
||||
# In various testing contexts, there might not be a current request context.
|
||||
request_context = get_template_request_context()
|
||||
if request_context:
|
||||
for item in request_context:
|
||||
context_dictionary.update(item)
|
||||
for item in context_instance:
|
||||
context_dictionary.update(item)
|
||||
if isinstance(context, Context):
|
||||
context_dictionary.update(context.flatten())
|
||||
elif context is not None:
|
||||
context_dictionary.update(context)
|
||||
|
||||
self._add_core_context(context_dictionary)
|
||||
self._evaluate_lazy_csrf_tokens(context_dictionary)
|
||||
|
||||
return self.mako_template.render_unicode(**context_dictionary)
|
||||
|
||||
@staticmethod
|
||||
def _get_context_object(request):
|
||||
"""
|
||||
Get a Django RequestContext or Context, as appropriate for the situation.
|
||||
In some tests, there might not be a current request.
|
||||
"""
|
||||
request_context = get_template_request_context(request)
|
||||
if request_context is not None:
|
||||
return request_context
|
||||
else:
|
||||
return Context({})
|
||||
|
||||
def _get_context_processors_output_dict(self, context_object):
|
||||
"""
|
||||
Run the context processors for the given context and get the output as a new dictionary.
|
||||
"""
|
||||
with context_object.bind_template(self):
|
||||
return context_object.flatten()
|
||||
|
||||
@staticmethod
|
||||
def _add_core_context(context_dictionary):
|
||||
"""
|
||||
Add to the given dictionary context variables which should always be
|
||||
present, even when context processors aren't run during tests. Using
|
||||
a context processor should almost always be preferred to adding more
|
||||
variables here.
|
||||
"""
|
||||
context_dictionary['settings'] = settings
|
||||
context_dictionary['EDX_ROOT_URL'] = settings.EDX_ROOT_URL
|
||||
context_dictionary['django_context'] = context_instance
|
||||
context_dictionary['marketing_link'] = marketing_link
|
||||
context_dictionary['is_any_marketing_link_set'] = is_any_marketing_link_set
|
||||
context_dictionary['is_marketing_link_set'] = is_marketing_link_set
|
||||
|
||||
return super(Template, self).render_unicode(**context_dictionary)
|
||||
@staticmethod
|
||||
def _evaluate_lazy_csrf_tokens(context_dictionary):
|
||||
"""
|
||||
Evaluate any lazily-evaluated CSRF tokens in the given context.
|
||||
"""
|
||||
for key in KEY_CSRF_TOKENS:
|
||||
if key in context_dictionary:
|
||||
context_dictionary[key] = text_type(context_dictionary[key])
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
from django.template import loader
|
||||
from django.template.base import Context, Template
|
||||
from django.template.loader import get_template, select_template
|
||||
|
||||
|
||||
def django_template_include(file_name, mako_context):
|
||||
"""
|
||||
This can be used within a mako template to include a django template
|
||||
in the way that a django-style {% include %} does. Pass it context
|
||||
which can be the mako context ('context') or a dictionary.
|
||||
"""
|
||||
|
||||
dictionary = dict(mako_context)
|
||||
return loader.render_to_string(file_name, dictionary=dictionary)
|
||||
|
||||
|
||||
def render_inclusion(func, file_name, takes_context, django_context, *args, **kwargs):
|
||||
"""
|
||||
This allows a mako template to call a template tag function (written
|
||||
for django templates) that is an "inclusion tag". These functions are
|
||||
decorated with @register.inclusion_tag.
|
||||
|
||||
-func: This is the function that is registered as an inclusion tag.
|
||||
You must import it directly using a python import statement.
|
||||
-file_name: This is the filename of the template, passed into the
|
||||
@register.inclusion_tag statement.
|
||||
-takes_context: This is a parameter of the @register.inclusion_tag.
|
||||
-django_context: This is an instance of the django context. If this
|
||||
is a mako template rendered through the regular django rendering calls,
|
||||
a copy of the django context is available as 'django_context'.
|
||||
-*args and **kwargs are the arguments to func.
|
||||
"""
|
||||
|
||||
if takes_context:
|
||||
args = [django_context] + list(args)
|
||||
|
||||
_dict = func(*args, **kwargs)
|
||||
if isinstance(file_name, Template):
|
||||
t = file_name
|
||||
elif not isinstance(file_name, basestring) and is_iterable(file_name):
|
||||
t = select_template(file_name)
|
||||
else:
|
||||
t = get_template(file_name)
|
||||
|
||||
nodelist = t.nodelist
|
||||
|
||||
new_context = Context(_dict)
|
||||
csrf_token = django_context.get('csrf_token', None)
|
||||
if csrf_token is not None:
|
||||
new_context['csrf_token'] = csrf_token
|
||||
|
||||
return nodelist.render(new_context)
|
||||
@@ -24,14 +24,11 @@ class DemoSystem(object):
|
||||
self.lookup = TemplateLookup(directories=[path(__file__).dirname() / 'templates'])
|
||||
self.DEBUG = True
|
||||
|
||||
def render_template(self, template_filename, dictionary, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
context_dict = {}
|
||||
context_dict.update(dictionary)
|
||||
context_dict.update(context)
|
||||
return self.lookup.get_template(template_filename).render(**context_dict)
|
||||
def render_template(self, template_filename, dictionary):
|
||||
"""
|
||||
Render the specified template with the given dictionary of context data.
|
||||
"""
|
||||
return self.lookup.get_template(template_filename).render(**dictionary)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -8,8 +8,8 @@ import mimetypes
|
||||
from django.conf import settings
|
||||
from django.http import Http404, HttpResponseNotFound, HttpResponseServerError
|
||||
from django.shortcuts import redirect
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from mako.exceptions import TopLevelLookupException
|
||||
|
||||
from edxmako.shortcuts import render_to_response, render_to_string
|
||||
from util.cache import cache_if_anonymous
|
||||
@@ -51,7 +51,7 @@ def render(request, template):
|
||||
if template == 'honor.html':
|
||||
context['allow_iframing'] = True
|
||||
return render_to_response('static_templates/' + template, context, content_type=content_type)
|
||||
except TopLevelLookupException:
|
||||
except TemplateDoesNotExist:
|
||||
raise Http404
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def render_press_release(request, slug):
|
||||
template = slug.lower().replace('-', '_') + ".html"
|
||||
try:
|
||||
resp = render_to_response('static_templates/press_releases/' + template, {})
|
||||
except TopLevelLookupException:
|
||||
except TemplateDoesNotExist:
|
||||
raise Http404
|
||||
else:
|
||||
return resp
|
||||
|
||||
@@ -43,7 +43,7 @@ from openedx.core.djangoapps.theming.helpers_dirs import (
|
||||
get_themes_unchecked,
|
||||
get_theme_base_dirs_from_settings
|
||||
)
|
||||
from openedx.core.lib.derived import derived, derived_dict_entry
|
||||
from openedx.core.lib.derived import derived, derived_collection_entry
|
||||
from openedx.core.release import doc_version
|
||||
from xmodule.modulestore.modulestore_settings import update_module_store_settings
|
||||
from xmodule.modulestore.edit_info import EditInfoMixin
|
||||
@@ -535,11 +535,9 @@ OAUTH2_PROVIDER_APPLICATION_MODEL = 'oauth2_provider.Application'
|
||||
|
||||
################################## TEMPLATE CONFIGURATION #####################################
|
||||
# Mako templating
|
||||
# TODO: Move the Mako templating into a different engine in TEMPLATES below.
|
||||
import tempfile
|
||||
MAKO_MODULE_DIR = os.path.join(tempfile.gettempdir(), 'mako_lms')
|
||||
MAKO_TEMPLATES = {}
|
||||
MAIN_MAKO_TEMPLATES_BASE = [
|
||||
MAKO_TEMPLATE_DIRS_BASE = [
|
||||
PROJECT_ROOT / 'templates',
|
||||
COMMON_ROOT / 'templates',
|
||||
COMMON_ROOT / 'lib' / 'capa' / 'capa' / 'templates',
|
||||
@@ -550,24 +548,55 @@ MAIN_MAKO_TEMPLATES_BASE = [
|
||||
]
|
||||
|
||||
|
||||
def _make_main_mako_templates(settings):
|
||||
def _make_mako_template_dirs(settings):
|
||||
"""
|
||||
Derives the final MAKO_TEMPLATES['main'] setting from other settings.
|
||||
Derives the final Mako template directories list from other settings.
|
||||
"""
|
||||
if settings.ENABLE_COMPREHENSIVE_THEMING:
|
||||
themes_dirs = get_theme_base_dirs_from_settings(settings.COMPREHENSIVE_THEME_DIRS)
|
||||
for theme in get_themes_unchecked(themes_dirs, PROJECT_ROOT):
|
||||
if theme.themes_base_dir not in settings.MAIN_MAKO_TEMPLATES_BASE:
|
||||
settings.MAIN_MAKO_TEMPLATES_BASE.insert(0, theme.themes_base_dir)
|
||||
for theme in get_themes_unchecked(themes_dirs, settings.PROJECT_ROOT):
|
||||
if theme.themes_base_dir not in settings.MAKO_TEMPLATE_DIRS_BASE:
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, theme.themes_base_dir)
|
||||
if settings.FEATURES.get('USE_MICROSITES', False) and getattr(settings, "MICROSITE_CONFIGURATION", False):
|
||||
settings.MAIN_MAKO_TEMPLATES_BASE.insert(0, settings.MICROSITE_ROOT_DIR)
|
||||
return settings.MAIN_MAKO_TEMPLATES_BASE
|
||||
MAKO_TEMPLATES['main'] = _make_main_mako_templates
|
||||
derived_dict_entry('MAKO_TEMPLATES', 'main')
|
||||
settings.MAKO_TEMPLATE_DIRS_BASE.insert(0, settings.MICROSITE_ROOT_DIR)
|
||||
return settings.MAKO_TEMPLATE_DIRS_BASE
|
||||
|
||||
|
||||
CONTEXT_PROCESSORS = [
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.static',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.contrib.auth.context_processors.auth', # this is required for admin
|
||||
'django.template.context_processors.csrf',
|
||||
|
||||
# Added for django-wiki
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'sekizai.context_processors.sekizai',
|
||||
|
||||
# Hack to get required link URLs to password reset templates
|
||||
'edxmako.shortcuts.marketing_link_context_processor',
|
||||
|
||||
# Shoppingcart processor (detects if request.user has a cart)
|
||||
'shoppingcart.context_processor.user_has_cart_context_processor',
|
||||
|
||||
# Timezone processor (sends language and time_zone preference)
|
||||
'courseware.context_processor.user_timezone_locale_prefs',
|
||||
|
||||
# Allows the open edX footer to be leveraged in Django Templates.
|
||||
'edxmako.shortcuts.footer_context_processor',
|
||||
|
||||
# Online contextual help
|
||||
'help_tokens.context_processor',
|
||||
'openedx.core.djangoapps.site_configuration.context_processors.configuration_context'
|
||||
]
|
||||
|
||||
# Django templating
|
||||
TEMPLATES = [
|
||||
{
|
||||
'NAME': 'django',
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
# Don't look for template source files inside installed applications.
|
||||
'APP_DIRS': False,
|
||||
@@ -588,41 +617,27 @@ TEMPLATES = [
|
||||
'edxmako.makoloader.MakoFilesystemLoader',
|
||||
'edxmako.makoloader.MakoAppDirectoriesLoader',
|
||||
],
|
||||
'context_processors': [
|
||||
'django.template.context_processors.request',
|
||||
'django.template.context_processors.static',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.contrib.auth.context_processors.auth', # this is required for admin
|
||||
'django.template.context_processors.csrf',
|
||||
|
||||
# Added for django-wiki
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.tz',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'sekizai.context_processors.sekizai',
|
||||
|
||||
# Hack to get required link URLs to password reset templates
|
||||
'edxmako.shortcuts.marketing_link_context_processor',
|
||||
|
||||
# Shoppingcart processor (detects if request.user has a cart)
|
||||
'shoppingcart.context_processor.user_has_cart_context_processor',
|
||||
|
||||
# Timezone processor (sends language and time_zone preference)
|
||||
'courseware.context_processor.user_timezone_locale_prefs',
|
||||
|
||||
# Allows the open edX footer to be leveraged in Django Templates.
|
||||
'edxmako.shortcuts.footer_context_processor',
|
||||
|
||||
# Online contextual help
|
||||
'help_tokens.context_processor',
|
||||
'openedx.core.djangoapps.site_configuration.context_processors.configuration_context'
|
||||
],
|
||||
'context_processors': CONTEXT_PROCESSORS,
|
||||
# Change 'debug' in your environment settings files - not here.
|
||||
'debug': False
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
'NAME': 'mako',
|
||||
'BACKEND': 'edxmako.backend.Mako',
|
||||
# Don't look for template source files inside installed applications.
|
||||
'APP_DIRS': False,
|
||||
# Instead, look for template source files in these dirs.
|
||||
'DIRS': _make_mako_template_dirs,
|
||||
# Options specific to this backend.
|
||||
'OPTIONS': {
|
||||
'context_processors': CONTEXT_PROCESSORS,
|
||||
# Change 'debug' in your environment settings files - not here.
|
||||
'debug': False,
|
||||
}
|
||||
},
|
||||
]
|
||||
derived_collection_entry('TEMPLATES', 1, 'DIRS')
|
||||
DEFAULT_TEMPLATE_ENGINE = TEMPLATES[0]
|
||||
DEFAULT_TEMPLATE_ENGINE_DIRS = DEFAULT_TEMPLATE_ENGINE['DIRS'][:]
|
||||
|
||||
@@ -634,8 +649,10 @@ def _add_microsite_dirs_to_default_template_engine(settings):
|
||||
if settings.FEATURES.get('USE_MICROSITES', False) and getattr(settings, "MICROSITE_CONFIGURATION", False):
|
||||
DEFAULT_TEMPLATE_ENGINE_DIRS.append(settings.MICROSITE_ROOT_DIR)
|
||||
return DEFAULT_TEMPLATE_ENGINE_DIRS
|
||||
|
||||
|
||||
DEFAULT_TEMPLATE_ENGINE['DIRS'] = _add_microsite_dirs_to_default_template_engine
|
||||
derived_dict_entry('DEFAULT_TEMPLATE_ENGINE', 'DIRS')
|
||||
derived_collection_entry('DEFAULT_TEMPLATE_ENGINE', 'DIRS')
|
||||
|
||||
###############################################################################################
|
||||
|
||||
|
||||
@@ -491,7 +491,7 @@ MICROSITE_LOGISTRATION_HOSTNAME = 'logistration.testserver'
|
||||
TEST_THEME = COMMON_ROOT / "test" / "test-theme"
|
||||
|
||||
# add extra template directory for test-only templates
|
||||
MAIN_MAKO_TEMPLATES_BASE.extend([
|
||||
MAKO_TEMPLATE_DIRS_BASE.extend([
|
||||
COMMON_ROOT / 'test' / 'templates',
|
||||
COMMON_ROOT / 'test' / 'test_sites',
|
||||
REPO_ROOT / 'openedx' / 'core' / 'djangolib' / 'tests' / 'templates',
|
||||
|
||||
@@ -113,7 +113,7 @@ def send_credit_notifications(username, course_key):
|
||||
else:
|
||||
email_body_content = ''
|
||||
|
||||
email_body = Template(email_body_content).render([context])
|
||||
email_body = Template(email_body_content).render(context)
|
||||
msg_alternative.attach(SafeMIMEText(email_body, _subtype='html', _charset='utf-8'))
|
||||
|
||||
# attach logo image
|
||||
|
||||
@@ -4,9 +4,9 @@ These views will NOT be shown on production: trying to access them will result
|
||||
in a 404 error.
|
||||
"""
|
||||
from django.http import HttpResponseNotFound
|
||||
from django.template import TemplateDoesNotExist
|
||||
from django.utils.translation import ugettext as _
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from mako.exceptions import TopLevelLookupException
|
||||
from openedx.core.djangoapps.util.user_messages import PageLevelMessages
|
||||
|
||||
|
||||
@@ -51,5 +51,5 @@ def show_reference_template(request, template):
|
||||
PageLevelMessages.register_error_message(request, _('This is a test error'))
|
||||
|
||||
return render_to_response(template, context)
|
||||
except TopLevelLookupException:
|
||||
except TemplateDoesNotExist:
|
||||
return HttpResponseNotFound('Missing template {template}'.format(template=template))
|
||||
|
||||
@@ -17,21 +17,23 @@ def derived(*settings):
|
||||
Can be called multiple times to add more derived settings.
|
||||
|
||||
Args:
|
||||
settings (list): List of setting names to register.
|
||||
settings (str): Setting names to register.
|
||||
"""
|
||||
__DERIVED.extend(settings)
|
||||
|
||||
|
||||
def derived_dict_entry(setting_dict, key):
|
||||
def derived_collection_entry(collection_name, *accessors):
|
||||
"""
|
||||
Registers a setting which is a dictionary and needs a derived value for a particular key.
|
||||
Registers a setting which is a dictionary or list and needs a derived value for a particular entry.
|
||||
Can be called multiple times to add more derived settings.
|
||||
|
||||
Args:
|
||||
setting_dict (str): Name of setting which contains a dictionary.
|
||||
key (str): Name of key in the setting dictionary which will be derived.
|
||||
collection_name (str): Name of setting which contains a dictionary or list.
|
||||
accessors (int|str): Sequence of dictionary keys and list indices in the collection (and
|
||||
collections within it) leading to the value which will be derived.
|
||||
For example: 0, 'DIRS'.
|
||||
"""
|
||||
__DERIVED.append((setting_dict, key))
|
||||
__DERIVED.append((collection_name, accessors))
|
||||
|
||||
|
||||
def derive_settings(module_name):
|
||||
@@ -52,13 +54,16 @@ def derive_settings(module_name):
|
||||
elif isinstance(derived, tuple):
|
||||
# If a tuple, two elements are expected - else ignore.
|
||||
if len(derived) == 2:
|
||||
# Both elements are expected to be strings.
|
||||
# The first string is the attribute which is expected to be a dictionary.
|
||||
# The second string is a key in that dictionary containing a derived setting.
|
||||
setting = getattr(module, derived[0])[derived[1]]
|
||||
# The first element is the name of the attribute which is expected to be a dictionary or list.
|
||||
# The second element is a list of string keys in that dictionary leading to a derived setting.
|
||||
collection = getattr(module, derived[0])
|
||||
accessors = derived[1]
|
||||
for accessor in accessors[:-1]:
|
||||
collection = collection[accessor]
|
||||
setting = collection[accessors[-1]]
|
||||
if callable(setting):
|
||||
setting_val = setting(module)
|
||||
getattr(module, derived[0]).update({derived[1]: setting_val})
|
||||
collection[accessors[-1]] = setting_val
|
||||
|
||||
|
||||
def clear_for_tests():
|
||||
|
||||
@@ -4,7 +4,7 @@ Tests for derived.py
|
||||
|
||||
import sys
|
||||
from unittest import TestCase
|
||||
from openedx.core.lib.derived import derived, derive_settings, clear_for_tests
|
||||
from openedx.core.lib.derived import derived, derived_collection_entry, derive_settings, clear_for_tests
|
||||
|
||||
|
||||
class TestDerivedSettings(TestCase):
|
||||
@@ -22,7 +22,9 @@ class TestDerivedSettings(TestCase):
|
||||
derived('DERIVED_VALUE', 'ANOTHER_DERIVED_VALUE')
|
||||
self.module.DICT_VALUE = {}
|
||||
self.module.DICT_VALUE['test_key'] = lambda settings: settings.DERIVED_VALUE * 3
|
||||
derived(('DICT_VALUE', 'test_key'))
|
||||
derived_collection_entry('DICT_VALUE', 'test_key')
|
||||
self.module.DICT_VALUE['list_key'] = ['not derived', lambda settings: settings.DERIVED_VALUE]
|
||||
derived_collection_entry('DICT_VALUE', 'list_key', 1)
|
||||
|
||||
def test_derived_settings_are_derived(self):
|
||||
derive_settings(__name__)
|
||||
@@ -42,3 +44,7 @@ class TestDerivedSettings(TestCase):
|
||||
def test_derived_dict_settings(self):
|
||||
derive_settings(__name__)
|
||||
self.assertEqual(self.module.DICT_VALUE['test_key'], 'mutter paneermutter paneermutter paneer')
|
||||
|
||||
def test_derived_nested_settings(self):
|
||||
derive_settings(__name__)
|
||||
self.assertEqual(self.module.DICT_VALUE['list_key'][1], 'mutter paneer')
|
||||
|
||||
Reference in New Issue
Block a user