2015-02-18 21:51:05 +00:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
from __future__ import unicode_literals
|
2008-05-29 13:11:23 +00:00
|
|
|
|
2008-07-29 06:05:15 +00:00
|
|
|
import os.path
|
2013-07-01 12:22:27 +00:00
|
|
|
import sys
|
2015-03-02 19:31:14 +00:00
|
|
|
import tempfile
|
2014-01-19 06:58:22 +00:00
|
|
|
import types
|
2013-07-01 12:22:27 +00:00
|
|
|
import unittest
|
2015-02-18 21:51:05 +00:00
|
|
|
from contextlib import contextmanager
|
2008-05-29 13:11:23 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
from django.template import Context, TemplateDoesNotExist
|
2014-11-19 22:23:58 +00:00
|
|
|
from django.template.engine import Engine
|
2015-02-18 21:51:05 +00:00
|
|
|
from django.test import SimpleTestCase, override_settings
|
2013-07-01 12:22:27 +00:00
|
|
|
from django.utils import six
|
2015-02-18 21:51:05 +00:00
|
|
|
|
|
|
|
from .utils import TEMPLATE_DIR
|
2010-10-11 12:55:17 +00:00
|
|
|
|
2015-01-28 12:35:27 +00:00
|
|
|
try:
|
|
|
|
import pkg_resources
|
|
|
|
except ImportError:
|
|
|
|
pkg_resources = None
|
2008-05-29 13:11:23 +00:00
|
|
|
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
class CachedLoaderTests(SimpleTestCase):
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def create_engine(self, **kwargs):
|
|
|
|
return Engine(
|
|
|
|
loaders=[
|
|
|
|
('django.template.loaders.cached.Loader', [
|
|
|
|
'django.template.loaders.filesystem.Loader',
|
|
|
|
]),
|
|
|
|
],
|
|
|
|
)
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def test_templatedir_caching(self):
|
|
|
|
"""
|
|
|
|
#13573 -- Template directories should be part of the cache key.
|
|
|
|
"""
|
|
|
|
engine = self.create_engine()
|
2008-05-29 13:11:23 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
# Retrieve a template specifying a template directory to check
|
|
|
|
t1, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'first'),))
|
|
|
|
# Now retrieve the same template name, but from a different directory
|
|
|
|
t2, name = engine.find_template('test.html', (os.path.join(TEMPLATE_DIR, 'second'),))
|
2013-11-02 21:34:05 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
# The two templates should not have the same content
|
|
|
|
self.assertNotEqual(t1.render(Context({})), t2.render(Context({})))
|
2008-06-26 04:22:12 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def test_missing_template_is_cached(self):
|
|
|
|
"""
|
|
|
|
#19949 -- TemplateDoesNotExist exceptions should be cached.
|
|
|
|
"""
|
|
|
|
engine = self.create_engine()
|
|
|
|
loader = engine.template_loaders[0]
|
|
|
|
|
|
|
|
self.assertFalse('missing.html' in loader.template_cache)
|
|
|
|
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
loader.load_template("missing.html")
|
|
|
|
|
|
|
|
self.assertEqual(
|
|
|
|
loader.template_cache["missing.html"],
|
|
|
|
TemplateDoesNotExist,
|
|
|
|
"Cached loader failed to cache the TemplateDoesNotExist exception",
|
|
|
|
)
|
|
|
|
|
|
|
|
def test_debug_nodelist_name(self):
|
|
|
|
template_name = 'index.html'
|
|
|
|
engine = Engine(dirs=[TEMPLATE_DIR], debug=True)
|
|
|
|
|
|
|
|
template = engine.get_template(template_name)
|
|
|
|
name = template.nodelist[0].source[0].name
|
|
|
|
self.assertTrue(
|
|
|
|
name.endswith(template_name),
|
|
|
|
'Template loaded through cached loader has incorrect name for debug page: %s' % template_name,
|
|
|
|
)
|
|
|
|
|
|
|
|
template = engine.get_template(template_name)
|
|
|
|
name = template.nodelist[0].source[0].name
|
|
|
|
self.assertTrue(
|
|
|
|
name.endswith(template_name),
|
|
|
|
'Cached template loaded through cached loader has incorrect name for debug page: %s' % template_name,
|
|
|
|
)
|
2008-05-29 13:11:23 +00:00
|
|
|
|
2010-09-12 21:53:35 +00:00
|
|
|
|
2013-07-25 22:17:40 +00:00
|
|
|
@unittest.skipUnless(pkg_resources, 'setuptools is not installed')
|
2015-02-18 21:51:05 +00:00
|
|
|
class EggLoaderTests(SimpleTestCase):
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
@contextmanager
|
|
|
|
def create_egg(self, name, resources):
|
|
|
|
"""
|
|
|
|
Creates a mock egg with a list of resources.
|
|
|
|
|
|
|
|
name: The name of the module.
|
|
|
|
resources: A dictionary of template names mapped to file-like objects.
|
|
|
|
"""
|
|
|
|
|
|
|
|
if six.PY2:
|
|
|
|
name = name.encode('utf-8')
|
|
|
|
|
|
|
|
class MockLoader(object):
|
|
|
|
pass
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2013-07-25 22:17:40 +00:00
|
|
|
class MockProvider(pkg_resources.NullProvider):
|
|
|
|
def __init__(self, module):
|
|
|
|
pkg_resources.NullProvider.__init__(self, module)
|
|
|
|
self.module = module
|
|
|
|
|
|
|
|
def _has(self, path):
|
|
|
|
return path in self.module._resources
|
|
|
|
|
|
|
|
def _isdir(self, path):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_resource_stream(self, manager, resource_name):
|
|
|
|
return self.module._resources[resource_name]
|
|
|
|
|
|
|
|
def _get(self, path):
|
|
|
|
return self.module._resources[path].read()
|
|
|
|
|
2014-01-27 20:28:53 +00:00
|
|
|
def _fn(self, base, resource_name):
|
2014-02-04 20:14:41 +00:00
|
|
|
return os.path.normcase(resource_name)
|
2014-01-27 20:28:53 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
egg = types.ModuleType(name)
|
|
|
|
egg.__loader__ = MockLoader()
|
|
|
|
egg.__path__ = ['/some/bogus/path/']
|
|
|
|
egg.__file__ = '/some/bogus/path/__init__.pyc'
|
|
|
|
egg._resources = resources
|
|
|
|
sys.modules[name] = egg
|
2008-05-29 13:11:23 +00:00
|
|
|
pkg_resources._provider_factories[MockLoader] = MockProvider
|
2008-06-26 04:22:12 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
del sys.modules[name]
|
|
|
|
del pkg_resources._provider_factories[MockLoader]
|
2014-12-17 21:10:57 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
2015-02-18 21:51:05 +00:00
|
|
|
engine = Engine(loaders=[
|
|
|
|
'django.template.loaders.eggs.Loader',
|
2014-12-17 21:10:57 +00:00
|
|
|
])
|
2015-02-18 21:51:05 +00:00
|
|
|
self.loader = engine.template_loaders[0]
|
2014-12-17 21:10:57 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def test_existing(self):
|
|
|
|
templates = {
|
|
|
|
os.path.normcase('templates/y.html'): six.StringIO("y"),
|
|
|
|
}
|
2010-05-21 08:54:15 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
with self.create_egg('egg', templates):
|
|
|
|
with override_settings(INSTALLED_APPS=['egg']):
|
|
|
|
contents, template_name = self.loader.load_template_source("y.html")
|
|
|
|
self.assertEqual(contents, "y")
|
|
|
|
self.assertEqual(template_name, "egg:egg:templates/y.html")
|
2010-05-21 08:54:15 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def test_non_existing(self):
|
2014-02-22 21:28:27 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
Template loading fails if the template is not in the egg.
|
2014-02-22 21:28:27 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
with self.create_egg('egg', {}):
|
|
|
|
with override_settings(INSTALLED_APPS=['egg']):
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
self.loader.load_template_source("not-existing.html")
|
2014-02-22 21:28:27 +00:00
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
def test_not_installed(self):
|
2013-11-19 16:49:16 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
Template loading fails if the egg is not in INSTALLED_APPS.
|
2013-11-19 16:49:16 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
templates = {
|
|
|
|
os.path.normcase('templates/y.html'): six.StringIO("y"),
|
|
|
|
}
|
|
|
|
|
|
|
|
with self.create_egg('egg', templates):
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
self.loader.load_template_source("y.html")
|
|
|
|
|
|
|
|
|
|
|
|
class FileSystemLoaderTests(SimpleTestCase):
|
|
|
|
|
|
|
|
def setUp(self):
|
2015-03-02 19:31:14 +00:00
|
|
|
self.engine = Engine(dirs=[TEMPLATE_DIR])
|
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def set_dirs(self, dirs):
|
|
|
|
original_dirs = self.engine.dirs
|
|
|
|
self.engine.dirs = dirs
|
|
|
|
try:
|
|
|
|
yield
|
|
|
|
finally:
|
|
|
|
self.engine.dirs = original_dirs
|
2015-02-18 21:51:05 +00:00
|
|
|
|
|
|
|
@contextmanager
|
|
|
|
def source_checker(self, dirs):
|
|
|
|
loader = self.engine.template_loaders[0]
|
|
|
|
|
|
|
|
def check_sources(path, expected_sources):
|
|
|
|
expected_sources = [os.path.abspath(s) for s in expected_sources]
|
|
|
|
self.assertEqual(
|
2015-02-24 15:17:17 +00:00
|
|
|
list(loader.get_template_sources(path)),
|
2015-02-18 21:51:05 +00:00
|
|
|
expected_sources,
|
|
|
|
)
|
|
|
|
|
2015-03-02 19:31:14 +00:00
|
|
|
with self.set_dirs(dirs):
|
2015-02-24 15:17:17 +00:00
|
|
|
yield check_sources
|
2015-02-18 21:51:05 +00:00
|
|
|
|
|
|
|
def test_directory_security(self):
|
|
|
|
with self.source_checker(['/dir1', '/dir2']) as check_sources:
|
|
|
|
check_sources('index.html', ['/dir1/index.html', '/dir2/index.html'])
|
|
|
|
check_sources('/etc/passwd', [])
|
|
|
|
check_sources('etc/passwd', ['/dir1/etc/passwd', '/dir2/etc/passwd'])
|
|
|
|
check_sources('../etc/passwd', [])
|
|
|
|
check_sources('../../../etc/passwd', [])
|
|
|
|
check_sources('/dir1/index.html', ['/dir1/index.html'])
|
|
|
|
check_sources('../dir2/index.html', ['/dir2/index.html'])
|
|
|
|
check_sources('/dir1blah', [])
|
|
|
|
check_sources('../dir1blah', [])
|
|
|
|
|
|
|
|
def test_unicode_template_name(self):
|
|
|
|
with self.source_checker(['/dir1', '/dir2']) as check_sources:
|
|
|
|
# UTF-8 bytestrings are permitted.
|
|
|
|
check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/dir1/Ångström', '/dir2/Ångström'])
|
|
|
|
# Unicode strings are permitted.
|
|
|
|
check_sources('Ångström', ['/dir1/Ångström', '/dir2/Ångström'])
|
|
|
|
|
|
|
|
def test_utf8_bytestring(self):
|
2013-11-19 16:49:16 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
Invalid UTF-8 encoding in bytestrings should raise a useful error
|
2013-11-19 16:49:16 +00:00
|
|
|
"""
|
2015-02-18 21:51:05 +00:00
|
|
|
engine = Engine()
|
|
|
|
loader = engine.template_loaders[0]
|
|
|
|
with self.assertRaises(UnicodeDecodeError):
|
|
|
|
list(loader.get_template_sources(b'\xc3\xc3', ['/dir1']))
|
|
|
|
|
|
|
|
def test_unicode_dir_name(self):
|
|
|
|
with self.source_checker([b'/Stra\xc3\x9fe']) as check_sources:
|
|
|
|
check_sources('Ångström', ['/Straße/Ångström'])
|
|
|
|
check_sources(b'\xc3\x85ngstr\xc3\xb6m', ['/Straße/Ångström'])
|
|
|
|
|
|
|
|
@unittest.skipUnless(
|
|
|
|
os.path.normcase('/TEST') == os.path.normpath('/test'),
|
|
|
|
"This test only runs on case-sensitive file systems.",
|
|
|
|
)
|
|
|
|
def test_case_sensitivity(self):
|
|
|
|
with self.source_checker(['/dir1', '/DIR2']) as check_sources:
|
|
|
|
check_sources('index.html', ['/dir1/index.html', '/DIR2/index.html'])
|
|
|
|
check_sources('/DIR1/index.HTML', ['/DIR1/index.HTML'])
|
|
|
|
|
2015-03-02 19:31:14 +00:00
|
|
|
def test_file_does_not_exist(self):
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
self.engine.get_template('doesnotexist.html')
|
|
|
|
|
|
|
|
@unittest.skipIf(
|
|
|
|
sys.platform == 'win32',
|
|
|
|
"Python on Windows doesn't have working os.chmod().",
|
|
|
|
)
|
|
|
|
def test_permissions_error(self):
|
|
|
|
with tempfile.NamedTemporaryFile() as tmpfile:
|
|
|
|
tmpdir = os.path.dirname(tmpfile.name)
|
|
|
|
tmppath = os.path.join(tmpdir, tmpfile.name)
|
|
|
|
os.chmod(tmppath, 0o0222)
|
|
|
|
with self.set_dirs([tmpdir]):
|
|
|
|
with self.assertRaisesMessage(IOError, 'Permission denied'):
|
|
|
|
self.engine.get_template(tmpfile.name)
|
|
|
|
|
|
|
|
def test_notafile_error(self):
|
2015-03-05 15:06:01 +00:00
|
|
|
with self.assertRaises(IOError):
|
2015-03-02 19:31:14 +00:00
|
|
|
self.engine.get_template('first')
|
|
|
|
|
2015-02-18 21:51:05 +00:00
|
|
|
|
2015-02-24 15:17:17 +00:00
|
|
|
class AppDirectoriesLoaderTest(SimpleTestCase):
|
2015-02-18 21:51:05 +00:00
|
|
|
|
|
|
|
def setUp(self):
|
|
|
|
self.engine = Engine(
|
|
|
|
loaders=['django.template.loaders.app_directories.Loader'],
|
|
|
|
)
|
2015-02-24 15:17:17 +00:00
|
|
|
|
|
|
|
@override_settings(INSTALLED_APPS=['template_tests'])
|
|
|
|
def test_load_template(self):
|
|
|
|
self.engine.get_template('index.html')
|
|
|
|
|
|
|
|
@override_settings(INSTALLED_APPS=[])
|
|
|
|
def test_not_installed(self):
|
|
|
|
with self.assertRaises(TemplateDoesNotExist):
|
|
|
|
self.engine.get_template('index.html')
|