Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cms/djangoapps/contentstore/views/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
61 changes: 41 additions & 20 deletions cms/envs/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
VIDEO_TRANSCRIPTS_SETTINGS,

# Methods to derive settings
_make_main_mako_templates,
_make_mako_template_dirs,
_make_locale_paths,
)
from path import Path as path
Expand All @@ -134,7 +134,7 @@
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 #############################
Expand Down Expand Up @@ -310,11 +310,9 @@

############################# 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',
Expand All @@ -324,19 +322,27 @@
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': (
Expand All @@ -346,21 +352,36 @@
'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]

##############################################################################
Expand Down
10 changes: 10 additions & 0 deletions common/djangoapps/edxmako/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
7 changes: 5 additions & 2 deletions common/djangoapps/edxmako/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ def ready(self):
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 changes: 67 additions & 0 deletions common/djangoapps/edxmako/backend.py
Original file line number Diff line number Diff line change
@@ -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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What uses this property? I couldn't find any usages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used by Django's RequestContext class in bind_template: https://github.com/django/django/blob/stable/1.8.x/django/template/context.py#L237

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course!

"""
Collect and cache the active context processors.
"""
context_processors = _builtin_context_processors
context_processors += tuple(self.context_processors)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so weird and seemingly unnecessary that they made _builtin_context_processors a tuple, but the conf option a list.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting; the original TEMPLATE_CONTEXT_PROCESSORS setting was a tuple, but the new context_processors option under TEMPLATES is a list. Maybe that's just because everything else under TEMPLATES was already a list for ease of customization, so it made sense to have them consistent? I definitely think using a tuple internally is the right call, to avoid accidental modifications.

return tuple(import_string(path) for path in context_processors)
9 changes: 5 additions & 4 deletions common/djangoapps/edxmako/makoloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

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
Expand All @@ -16,8 +16,8 @@
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.
"""

Expand Down Expand Up @@ -53,7 +53,8 @@ def load_template(self, template_name, template_dirs=None):
output_encoding='utf-8',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since load_template has been deprecated in the base Loader class since Django 1.9, should we also add a deprecation warning (perhaps shimmed) at the beginning of this method?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll need to refactor this to switch to the new methods before we upgrade to 2.0 or above, but we have the whole Python 3 upgrade to finish before that. I created PLAT-1820 to track this, but don't think we want to spam Splunk with lots of deprecation warnings about it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - ticket over spam.

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
Expand Down
Empty file.
Empty file.
19 changes: 0 additions & 19 deletions common/djangoapps/edxmako/request_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,12 @@


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,
Expand All @@ -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
57 changes: 14 additions & 43 deletions common/djangoapps/edxmako/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@
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__)

Expand Down Expand Up @@ -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.

Expand All @@ -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.
"""
if namespace == 'lms.main':
engine = engines[Engines.PREVIEW]
else:
engine = engines[Engines.MAKO]
template = engine.get_template(template_name)
return template.render(dictionary, request)


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)


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