Commit 65997d0f by Ned Batchelder

Merge pull request #2288 from jazkarta/pluggable-mako-templates

Make mako template lookups pluggable.
parents 7fd63801 0a1ed11d
...@@ -11,5 +11,6 @@ ...@@ -11,5 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
LOOKUP = {}
lookup = None from .paths import add_lookup, lookup_template
"""
Set up lookup paths for mako templates.
"""
import os
import pkg_resources
from django.conf import settings
from mako.lookup import TemplateLookup
from . import LOOKUP
class DynamicTemplateLookup(TemplateLookup):
"""
A specialization of the standard mako `TemplateLookup` class which allows
for adding directories progressively.
"""
def add_directory(self, directory):
"""
Add a new directory to the template lookup path.
"""
self.directories.append(os.path.normpath(directory))
def add_lookup(namespace, directory, package=None):
"""
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)
def lookup_template(namespace, name):
"""
Look up a Mako template by namespace and name.
"""
return LOOKUP[namespace].get_template(name)
...@@ -18,7 +18,7 @@ import logging ...@@ -18,7 +18,7 @@ import logging
from microsite_configuration.middleware import MicrositeConfiguration from microsite_configuration.middleware import MicrositeConfiguration
import edxmako from edxmako import lookup_template
import edxmako.middleware import edxmako.middleware
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -100,7 +100,7 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'): ...@@ -100,7 +100,7 @@ def render_to_string(template_name, dictionary, context=None, namespace='main'):
if context: if context:
context_dictionary.update(context) context_dictionary.update(context)
# fetch and render template # fetch and render template
template = edxmako.lookup[namespace].get_template(template_name) template = lookup_template(namespace, template_name)
return template.render_unicode(**context_dictionary) return template.render_unicode(**context_dictionary)
......
""" """
Initialize the mako template lookup Initialize the mako template lookup
""" """
import tempdir
from django.conf import settings from django.conf import settings
from mako.lookup import TemplateLookup from . import add_lookup
import edxmako
def run(): def run():
"""Setup mako variables and lookup object""" """
# Set all mako variables based on django settings Setup mako lookup directories.
"""
template_locations = settings.MAKO_TEMPLATES template_locations = settings.MAKO_TEMPLATES
module_directory = getattr(settings, 'MAKO_MODULE_DIR', None) for namespace, directories in template_locations.items():
for directory in directories:
if module_directory is None: add_lookup(namespace, directory)
module_directory = tempdir.mkdtemp_clean()
lookup = {}
for location in template_locations:
lookup[location] = TemplateLookup(
directories=template_locations[location],
module_directory=module_directory,
output_encoding='utf-8',
input_encoding='utf-8',
default_filters=['decode.utf8'],
encoding_errors='replace',
)
edxmako.lookup = lookup
...@@ -19,7 +19,7 @@ from edxmako.shortcuts import marketing_link ...@@ -19,7 +19,7 @@ from edxmako.shortcuts import marketing_link
import edxmako import edxmako
import edxmako.middleware import edxmako.middleware
django_variables = ['lookup', 'output_encoding', 'encoding_errors'] DJANGO_VARIABLES = ['output_encoding', 'encoding_errors']
# TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate) # TODO: We should make this a Django Template subclass that simply has the MakoTemplate inside of it? (Intead of inheriting from MakoTemplate)
...@@ -34,8 +34,8 @@ class Template(MakoTemplate): ...@@ -34,8 +34,8 @@ class Template(MakoTemplate):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""Overrides base __init__ to provide django variable overrides""" """Overrides base __init__ to provide django variable overrides"""
if not kwargs.get('no_django', False): if not kwargs.get('no_django', False):
overrides = dict([(k, getattr(edxmako, k, None),) for k in django_variables]) overrides = {k: getattr(edxmako, k, None) for k in DJANGO_VARIABLES}
overrides['lookup'] = overrides['lookup']['main'] overrides['lookup'] = edxmako.LOOKUP['main']
kwargs.update(overrides) kwargs.update(overrides)
super(Template, self).__init__(*args, **kwargs) super(Template, self).__init__(*args, **kwargs)
......
from django.test import TestCase from django.test import TestCase
from django.test.utils import override_settings from django.test.utils import override_settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from edxmako import add_lookup, LOOKUP
from edxmako.shortcuts import marketing_link from edxmako.shortcuts import marketing_link
from mock import patch from mock import patch
from util.testing import UrlResetMixin from util.testing import UrlResetMixin
...@@ -24,3 +25,15 @@ class ShortcutsTests(UrlResetMixin, TestCase): ...@@ -24,3 +25,15 @@ class ShortcutsTests(UrlResetMixin, TestCase):
expected_link = reverse('login') expected_link = reverse('login')
link = marketing_link('ABOUT') link = marketing_link('ABOUT')
self.assertEquals(link, expected_link) self.assertEquals(link, expected_link)
class AddLookupTests(TestCase):
"""
Test the `add_lookup` function.
"""
@patch('edxmako.LOOKUP', {})
def test_with_package(self):
add_lookup('test', 'management', __name__)
dirs = LOOKUP['test'].directories
self.assertEqual(len(dirs), 1)
self.assertTrue(dirs[0].endswith('management'))
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.contrib.auth.models import User from django.contrib.auth.models import User
import edxmako from edxmako import lookup_template
class Command(BaseCommand): class Command(BaseCommand):
...@@ -15,8 +15,8 @@ body, and an _subject.txt for the subject. ''' ...@@ -15,8 +15,8 @@ body, and an _subject.txt for the subject. '''
#text = open(args[0]).read() #text = open(args[0]).read()
#subject = open(args[1]).read() #subject = open(args[1]).read()
users = User.objects.all() users = User.objects.all()
text = edxmako.lookup['main'].get_template('email/' + args[0] + ".txt").render() text = lookup_template('main', 'email/' + args[0] + ".txt").render()
subject = edxmako.lookup['main'].get_template('email/' + args[0] + "_subject.txt").render().strip() subject = lookup_template('main', 'email/' + args[0] + "_subject.txt").render().strip()
for user in users: for user in users:
if user.is_active: if user.is_active:
user.email_user(subject, text) user.email_user(subject, text)
...@@ -4,7 +4,7 @@ import time ...@@ -4,7 +4,7 @@ import time
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
import edxmako from edxmako import lookup_template
from django.core.mail import send_mass_mail from django.core.mail import send_mass_mail
import sys import sys
...@@ -39,8 +39,8 @@ rate -- messages per second ...@@ -39,8 +39,8 @@ rate -- messages per second
users = [u.strip() for u in open(user_file).readlines()] users = [u.strip() for u in open(user_file).readlines()]
message = edxmako.lookup['main'].get_template('emails/' + message_base + "_body.txt").render() message = lookup_template('main', 'emails/' + message_base + "_body.txt").render()
subject = edxmako.lookup['main'].get_template('emails/' + message_base + "_subject.txt").render().strip() subject = lookup_template('main', 'emails/' + message_base + "_subject.txt").render().strip()
rate = int(ratestr) rate = int(ratestr)
self.log_file = open(logfilename, "a+", buffering=0) self.log_file = open(logfilename, "a+", buffering=0)
......
"""
Test `massemail` and `massemailtxt` commands.
"""
import mock
import pkg_resources
from django.core import mail
from django.test import TestCase
from edxmako import add_lookup
from ..management.commands import massemail
from ..management.commands import massemailtxt
class TestMassEmailCommands(TestCase):
"""
Test `massemail` and `massemailtxt` commands.
"""
@mock.patch('edxmako.LOOKUP', {})
def test_massemailtxt(self):
"""
Test the `massemailtext` command.
"""
add_lookup('main', '', package=__name__)
userfile = pkg_resources.resource_filename(__name__, 'test_massemail_users.txt')
command = massemailtxt.Command()
command.handle(userfile, 'test', '/dev/null', 10)
self.assertEqual(len(mail.outbox), 2)
self.assertEqual(mail.outbox[0].to, ["Fred"])
self.assertEqual(mail.outbox[0].subject, "Test subject.")
self.assertEqual(mail.outbox[0].body.strip(), "Test body.")
self.assertEqual(mail.outbox[1].to, ["Barney"])
self.assertEqual(mail.outbox[1].subject, "Test subject.")
self.assertEqual(mail.outbox[1].body.strip(), "Test body.")
@mock.patch('edxmako.LOOKUP', {})
@mock.patch('student.management.commands.massemail.User')
def test_massemail(self, usercls):
"""
Test the `massemail` command.
"""
add_lookup('main', '', package=__name__)
fred = mock.Mock()
barney = mock.Mock()
usercls.objects.all.return_value = [fred, barney]
command = massemail.Command()
command.handle('test')
fred.email_user.assert_called_once_with('Test subject.', 'Test body.\n')
barney.email_user.assert_called_once_with('Test subject.', 'Test body.\n')
import json import json
import mock
from datetime import datetime from datetime import datetime
from pytz import UTC from pytz import UTC
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -11,6 +12,7 @@ import django_comment_client.utils as utils ...@@ -11,6 +12,7 @@ import django_comment_client.utils as utils
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE from courseware.tests.tests import TEST_DATA_MONGO_MODULESTORE
from edxmako import add_lookup
class DictionaryTestCase(TestCase): class DictionaryTestCase(TestCase):
...@@ -505,3 +507,17 @@ class JsonResponseTestCase(TestCase, UnicodeTestMixin): ...@@ -505,3 +507,17 @@ class JsonResponseTestCase(TestCase, UnicodeTestMixin):
response = utils.JsonResponse(text) response = utils.JsonResponse(text)
reparsed = json.loads(response.content) reparsed = json.loads(response.content)
self.assertEqual(reparsed, text) self.assertEqual(reparsed, text)
class RenderMustacheTests(TestCase):
"""
Test the `render_mustache` utility function.
"""
@mock.patch('edxmako.LOOKUP', {})
def test_it(self):
"""
Basic test.
"""
add_lookup('main', '', package=__name__)
self.assertEqual(utils.render_mustache('test.mustache', {}), 'Testing 1 2 3.\n')
import pytz import pytz
from collections import defaultdict from collections import defaultdict
import logging import logging
import urllib
from datetime import datetime from datetime import datetime
from django.contrib.auth.models import User from django.contrib.auth.models import User
...@@ -12,7 +11,7 @@ from django.utils import simplejson ...@@ -12,7 +11,7 @@ from django.utils import simplejson
from django_comment_common.models import Role, FORUM_ROLE_STUDENT from django_comment_common.models import Role, FORUM_ROLE_STUDENT
from django_comment_client.permissions import check_permissions_by_view from django_comment_client.permissions import check_permissions_by_view
import edxmako from edxmako import lookup_template
import pystache_custom as pystache import pystache_custom as pystache
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
...@@ -307,7 +306,7 @@ def get_metadata_for_threads(course_id, threads, user, user_info): ...@@ -307,7 +306,7 @@ def get_metadata_for_threads(course_id, threads, user, user_info):
def render_mustache(template_name, dictionary, *args, **kwargs): def render_mustache(template_name, dictionary, *args, **kwargs):
template = edxmako.lookup['main'].get_template(template_name).source template = lookup_template('main', template_name).source
return pystache.render(template, dictionary) return pystache.render(template, dictionary)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment