mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
Implemented auto-escaping of variable output in templates. Fully controllable by template authors and it's possible to write filters and templates that simulataneously work in both auto-escaped and non-auto-escaped environments if you need to. Fixed #2359
See documentation in templates.txt and templates_python.txt for how everything works. Backwards incompatible if you're inserting raw HTML output via template variables. Based on an original design from Simon Willison and with debugging help from Michael Radziej. git-svn-id: http://code.djangoproject.com/svn/django/trunk@6671 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -14,9 +14,11 @@ from django import template
|
||||
from django.template import loader
|
||||
from django.template.loaders import app_directories, filesystem
|
||||
from django.utils.translation import activate, deactivate, ugettext as _
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.tzinfo import LocalTimezone
|
||||
|
||||
from unicode import unicode_tests
|
||||
import filters
|
||||
|
||||
# Some other tests we would like to run
|
||||
__test__ = {
|
||||
@@ -120,20 +122,97 @@ class Templates(unittest.TestCase):
|
||||
['/dir1/index.html'])
|
||||
|
||||
def test_templates(self):
|
||||
# NOW and NOW_tz are used by timesince tag tests.
|
||||
NOW = datetime.now()
|
||||
NOW_tz = datetime.now(LocalTimezone(datetime.now()))
|
||||
template_tests = self.get_template_tests()
|
||||
filter_tests = filters.get_filter_tests()
|
||||
|
||||
# Quickly check that we aren't accidentally using a name in both
|
||||
# template and filter tests.
|
||||
overlapping_names = [name for name in filter_tests if name in
|
||||
template_tests]
|
||||
assert not overlapping_names, 'Duplicate test name(s): %s' % ', '.join(overlapping_names)
|
||||
|
||||
template_tests.update(filter_tests)
|
||||
|
||||
# Register our custom template loader.
|
||||
def test_template_loader(template_name, template_dirs=None):
|
||||
"A custom template loader that loads the unit-test templates."
|
||||
try:
|
||||
return (template_tests[template_name][0] , "test:%s" % template_name)
|
||||
except KeyError:
|
||||
raise template.TemplateDoesNotExist, template_name
|
||||
|
||||
old_template_loaders = loader.template_source_loaders
|
||||
loader.template_source_loaders = [test_template_loader]
|
||||
|
||||
failures = []
|
||||
tests = template_tests.items()
|
||||
tests.sort()
|
||||
|
||||
# Turn TEMPLATE_DEBUG off, because tests assume that.
|
||||
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
|
||||
|
||||
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
||||
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
||||
expected_invalid_str = 'INVALID'
|
||||
|
||||
for name, vals in tests:
|
||||
if isinstance(vals[2], tuple):
|
||||
normal_string_result = vals[2][0]
|
||||
invalid_string_result = vals[2][1]
|
||||
if '%s' in invalid_string_result:
|
||||
expected_invalid_str = 'INVALID %s'
|
||||
invalid_string_result = invalid_string_result % vals[2][2]
|
||||
template.invalid_var_format_string = True
|
||||
else:
|
||||
normal_string_result = vals[2]
|
||||
invalid_string_result = vals[2]
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
activate(vals[1]['LANGUAGE_CODE'])
|
||||
else:
|
||||
activate('en-us')
|
||||
|
||||
for invalid_str, result in [('', normal_string_result),
|
||||
(expected_invalid_str, invalid_string_result)]:
|
||||
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
||||
try:
|
||||
test_template = loader.get_template(name)
|
||||
output = self.render(test_template, vals)
|
||||
except Exception, e:
|
||||
if e.__class__ != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
|
||||
continue
|
||||
if output != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
deactivate()
|
||||
|
||||
if template.invalid_var_format_string:
|
||||
expected_invalid_str = 'INVALID'
|
||||
template.invalid_var_format_string = False
|
||||
|
||||
loader.template_source_loaders = old_template_loaders
|
||||
deactivate()
|
||||
settings.TEMPLATE_DEBUG = old_td
|
||||
settings.TEMPLATE_STRING_IF_INVALID = old_invalid
|
||||
|
||||
self.assertEqual(failures, [], '\n'.join(failures))
|
||||
|
||||
def render(self, test_template, vals):
|
||||
return test_template.render(template.Context(vals[1]))
|
||||
|
||||
def get_template_tests(self):
|
||||
# SYNTAX --
|
||||
# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
|
||||
TEMPLATE_TESTS = {
|
||||
|
||||
### BASIC SYNTAX ##########################################################
|
||||
return {
|
||||
### BASIC SYNTAX ################################################
|
||||
|
||||
# Plain text should go through the template parser untouched
|
||||
'basic-syntax01': ("something cool", {}, "something cool"),
|
||||
|
||||
# Variables should be replaced with their value in the current context
|
||||
# Variables should be replaced with their value in the current
|
||||
# context
|
||||
'basic-syntax02': ("{{ headline }}", {'headline':'Success'}, "Success"),
|
||||
|
||||
# More than one replacement variable is allowed in a template
|
||||
@@ -240,7 +319,8 @@ class Templates(unittest.TestCase):
|
||||
'filter-syntax09': ('{{ var|removetags:"b i"|upper|lower }}', {"var": "<b><i>Yes</i></b>"}, "yes"),
|
||||
|
||||
# Escaped string as argument
|
||||
'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'),
|
||||
'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}',
|
||||
{"var": None}, ' endquote" hah'),
|
||||
|
||||
# Variable as argument
|
||||
'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
|
||||
@@ -760,38 +840,6 @@ class Templates(unittest.TestCase):
|
||||
# 'now03' : ('{% now "j \"n\" Y"%}', {}, str(datetime.now().day) + '"' + str(datetime.now().month) + '"' + str(datetime.now().year)),
|
||||
# 'now04' : ('{% now "j \nn\n Y"%}', {}, str(datetime.now().day) + '\n' + str(datetime.now().month) + '\n' + str(datetime.now().year))
|
||||
|
||||
### TIMESINCE TAG ##################################################
|
||||
# Default compare with datetime.now()
|
||||
'timesince01' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'),
|
||||
'timesince02' : ('{{ a|timesince }}', {'a':(datetime.now() - timedelta(days=1, minutes = 1))}, '1 day'),
|
||||
'timesince03' : ('{{ a|timesince }}', {'a':(datetime.now() -
|
||||
timedelta(hours=1, minutes=25, seconds = 10))}, '1 hour, 25 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'timesince04' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2), 'b':NOW + timedelta(days=1)}, '1 day'),
|
||||
'timesince05' : ('{{ a|timesince:b }}', {'a':NOW + timedelta(days=2, minutes=1), 'b':NOW + timedelta(days=2)}, '1 minute'),
|
||||
|
||||
# Check that timezone is respected
|
||||
'timesince06' : ('{{ a|timesince:b }}', {'a':NOW_tz + timedelta(hours=8), 'b':NOW_tz}, '8 hours'),
|
||||
|
||||
# Check times in the future.
|
||||
'timesince07' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(minutes=1, seconds=10)}, '0 minutes'),
|
||||
'timesince08' : ('{{ a|timesince }}', {'a':datetime.now() + timedelta(days=1, minutes=1)}, '0 minutes'),
|
||||
|
||||
### TIMEUNTIL TAG ##################################################
|
||||
# Default compare with datetime.now()
|
||||
'timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'),
|
||||
'timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'),
|
||||
'timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'),
|
||||
|
||||
# Compare to a given parameter
|
||||
'timeuntil04' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=1), 'b':NOW - timedelta(days=2)}, '1 day'),
|
||||
'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'),
|
||||
|
||||
# Check times in the past.
|
||||
'timeuntil07' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(minutes=1, seconds=10)}, '0 minutes'),
|
||||
'timeuntil08' : ('{{ a|timeuntil }}', {'a':datetime.now() - timedelta(days=1, minutes=1)}, '0 minutes'),
|
||||
|
||||
### URL TAG ########################################################
|
||||
# Successes
|
||||
'url01' : ('{% url regressiontests.templates.views.client client.id %}', {'client': {'id': 1}}, '/url_tag/client/1/'),
|
||||
@@ -819,72 +867,31 @@ class Templates(unittest.TestCase):
|
||||
'cache08' : ('{% load cache %}{% cache %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
'cache09' : ('{% load cache %}{% cache 1 %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
'cache10' : ('{% load cache %}{% cache foo bar %}{% endcache %}', {}, template.TemplateSyntaxError),
|
||||
|
||||
### AUTOESCAPE TAG ##############################################
|
||||
'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
|
||||
'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
|
||||
'autoescape-tag03': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
|
||||
|
||||
# Autoescape disabling and enabling nest in a predictable way.
|
||||
'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": "<a>"}, "<a> <a>"),
|
||||
|
||||
'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>first</b>"}, "<b>first</b>"),
|
||||
|
||||
# Strings (ASCII or unicode) already marked as "safe" are not
|
||||
# auto-escaped
|
||||
'autoescape-tag06': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"),
|
||||
'autoescape-tag07': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"),
|
||||
|
||||
# String arguments to filters, if used in the result, are escaped,
|
||||
# too.
|
||||
'basic-syntax08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'),
|
||||
|
||||
# The "safe" and "escape" filters cannot work due to internal
|
||||
# implementation details (fortunately, the (no)autoescape block
|
||||
# tags can be used in those cases)
|
||||
'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
|
||||
}
|
||||
|
||||
# Register our custom template loader.
|
||||
def test_template_loader(template_name, template_dirs=None):
|
||||
"A custom template loader that loads the unit-test templates."
|
||||
try:
|
||||
return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
|
||||
except KeyError:
|
||||
raise template.TemplateDoesNotExist, template_name
|
||||
|
||||
old_template_loaders = loader.template_source_loaders
|
||||
loader.template_source_loaders = [test_template_loader]
|
||||
|
||||
failures = []
|
||||
tests = TEMPLATE_TESTS.items()
|
||||
tests.sort()
|
||||
|
||||
# Turn TEMPLATE_DEBUG off, because tests assume that.
|
||||
old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
|
||||
|
||||
# Set TEMPLATE_STRING_IF_INVALID to a known string
|
||||
old_invalid = settings.TEMPLATE_STRING_IF_INVALID
|
||||
expected_invalid_str = 'INVALID'
|
||||
|
||||
for name, vals in tests:
|
||||
if isinstance(vals[2], tuple):
|
||||
normal_string_result = vals[2][0]
|
||||
invalid_string_result = vals[2][1]
|
||||
if '%s' in invalid_string_result:
|
||||
expected_invalid_str = 'INVALID %s'
|
||||
invalid_string_result = invalid_string_result % vals[2][2]
|
||||
template.invalid_var_format_string = True
|
||||
else:
|
||||
normal_string_result = vals[2]
|
||||
invalid_string_result = vals[2]
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
activate(vals[1]['LANGUAGE_CODE'])
|
||||
else:
|
||||
activate('en-us')
|
||||
|
||||
for invalid_str, result in [('', normal_string_result),
|
||||
(expected_invalid_str, invalid_string_result)]:
|
||||
settings.TEMPLATE_STRING_IF_INVALID = invalid_str
|
||||
try:
|
||||
output = loader.get_template(name).render(template.Context(vals[1]))
|
||||
except Exception, e:
|
||||
if e.__class__ != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
|
||||
continue
|
||||
if output != result:
|
||||
failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
|
||||
|
||||
if 'LANGUAGE_CODE' in vals[1]:
|
||||
deactivate()
|
||||
|
||||
if template.invalid_var_format_string:
|
||||
expected_invalid_str = 'INVALID'
|
||||
template.invalid_var_format_string = False
|
||||
|
||||
loader.template_source_loaders = old_template_loaders
|
||||
deactivate()
|
||||
settings.TEMPLATE_DEBUG = old_td
|
||||
settings.TEMPLATE_STRING_IF_INVALID = old_invalid
|
||||
|
||||
self.assertEqual(failures, [], '\n'.join(failures))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user