diff options
| author | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-11-30 05:50:44 +0000 |
|---|---|---|
| committer | Joseph Kocherhans <joseph@jkocherhans.com> | 2007-11-30 05:50:44 +0000 |
| commit | f88babafc58eafece72d3f2f7444336c69196808 (patch) | |
| tree | 0f52526851e157511d3c94140b979c0c2a8af079 /tests | |
| parent | c82b2becf2bffe8ad18827b80a6c12433f799660 (diff) | |
newforms-admin: Merged trunk revision [6671].
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6776 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/regressiontests/defaultfilters/tests.py | 4 | ||||
| -rw-r--r-- | tests/regressiontests/forms/forms.py | 2 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests.py | 2 | ||||
| -rw-r--r-- | tests/regressiontests/humanize/tests.py | 3 | ||||
| -rw-r--r-- | tests/regressiontests/templates/filters.py | 220 | ||||
| -rw-r--r-- | tests/regressiontests/templates/tests.py | 207 |
6 files changed, 333 insertions, 105 deletions
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py index 270642d4a0..26d448900d 100644 --- a/tests/regressiontests/defaultfilters/tests.py +++ b/tests/regressiontests/defaultfilters/tests.py @@ -194,10 +194,10 @@ u'a stri to be maled' >>> cut(u'a string to be mangled', 'strings') u'a string to be mangled' ->>> escape(u'<some html & special characters > here') +>>> force_escape(u'<some html & special characters > here') u'<some html & special characters > here' ->>> escape(u'<some html & special characters > here ĐÅ€£') +>>> force_escape(u'<some html & special characters > here ĐÅ€£') u'<some html & special characters > here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3' >>> linebreaks(u'line 1') diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py index ed88e3a6bb..7c0cf8abf3 100644 --- a/tests/regressiontests/forms/forms.py +++ b/tests/regressiontests/forms/forms.py @@ -1554,7 +1554,7 @@ does not have help text, nothing will be output. ... </form>''') >>> print t.render(Context({'form': UserRegistration(auto_id=False)})) <form action=""> -<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p> +<p>Username: <input type="text" name="username" maxlength="10" /><br />Good luck picking a username that doesn't already exist.</p> <p>Password1: <input type="password" name="password1" /></p> <p>Password2: <input type="password" name="password2" /></p> <input type="submit" /> diff --git a/tests/regressiontests/forms/tests.py b/tests/regressiontests/forms/tests.py index 333b928700..53c3fc4b17 100644 --- a/tests/regressiontests/forms/tests.py +++ b/tests/regressiontests/forms/tests.py @@ -52,7 +52,7 @@ __test__ = { 'localflavor_sk_tests': localflavor_sk_tests, 'localflavor_uk_tests': localflavor_uk_tests, 'localflavor_us_tests': localflavor_us_tests, - 'regressions_tests': regression_tests, + 'regression_tests': regression_tests, 'formset_tests': formset_tests, 'media_tests': media_tests, 'util_tests': util_tests, diff --git a/tests/regressiontests/humanize/tests.py b/tests/regressiontests/humanize/tests.py index 196488ba6e..6f60c6d6f9 100644 --- a/tests/regressiontests/humanize/tests.py +++ b/tests/regressiontests/humanize/tests.py @@ -3,6 +3,7 @@ from datetime import timedelta, date from django.template import Template, Context, add_to_builtins from django.utils.dateformat import DateFormat from django.utils.translation import ugettext as _ +from django.utils.html import escape add_to_builtins('django.contrib.humanize.templatetags.humanize') @@ -15,7 +16,7 @@ class HumanizeTests(unittest.TestCase): test_content = test_list[index] t = Template('{{ test_content|%s }}' % method) rendered = t.render(Context(locals())).strip() - self.assertEqual(rendered, result_list[index], + self.assertEqual(rendered, escape(result_list[index]), msg="%s test failed, produced %s, should've produced %s" % (method, rendered, result_list[index])) def test_ordinal(self): diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py new file mode 100644 index 0000000000..5d7129480c --- /dev/null +++ b/tests/regressiontests/templates/filters.py @@ -0,0 +1,220 @@ +# coding: utf-8 +""" +Tests for template filters (as opposed to template tags). + +The tests are hidden inside a function so that things like timestamps and +timezones are only evaluated at the moment of execution and will therefore be +consistent. +""" + +from datetime import datetime, timedelta + +from django.utils.tzinfo import LocalTimezone +from django.utils.safestring import mark_safe + +# RESULT SYNTAX -- +# 'template_name': ('template contents', 'context dict', +# 'expected string output' or Exception class) +def get_filter_tests(): + now = datetime.now() + now_tz = datetime.now(LocalTimezone(now)) + return { + # Default compare with datetime.now() + 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), + 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'), + 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'), + + # Compare to a given parameter + 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2), 'b':now + timedelta(days=1)}, '1 day'), + 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now + timedelta(days=2, minutes=1), 'b':now + timedelta(days=2)}, '1 minute'), + + # Check that timezone is respected + 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz + timedelta(hours=8), 'b':now_tz}, '8 hours'), + + # Default compare with datetime.now() + 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), + 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), + 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), + + # Compare to a given parameter + 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'), + 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'), + + 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"), + 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, ur"<a>\' <a>\'"), + + 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"), + 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, u"Fred> Fred>"), + + # Note that applying fix_ampsersands in autoescape mode leads to + # double escaping. + 'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&b a&b"), + 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;b a&b"), + + 'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"), + 'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, u"1.4 1.4"), + + # The contents of "linenumbers" is escaped according to the current + # autoescape setting. + 'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), + 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), + + 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"), + 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, u"apple & banana apple & banana"), + + # The make_list filter can destroy existing escaping, so the results are + # escaped. + 'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, u"[u'&']"), + 'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, u"[u'&']"), + + # Running slugify on a pre-escaped string leads to odd behaviour, + # but the result is still safe. + 'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"), + 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, u"a-b a-amp-b"), + + # Notice that escaping is applied *after* any filters, so the string + # formatting here only needs to deal with pre-escaped characters. + 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), + 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, u". a<b. . a<b."), + + # XXX No test for "title" filter; needs an actual object. + + 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), + 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, u"alpha & ... alpha & ..."), + + # The "upper" filter messes up entities (which are case-sensitive), + # so it's not safe for non-escaping purposes. + 'filter-upper01': ('{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"A & B A & B"), + 'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a & b")}, u"A & B A &AMP; B"), + + 'filter-urlize01': ('{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), + 'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), + 'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a & b")}, 'a & b'), + 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'), + + 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), + 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, u'<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), + + 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), + 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), + + 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"), + 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, u"a &\nb a &\nb"), + + 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."), + 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u".a&b . .a&b ."), + + 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."), + 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b. . a&b."), + + 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."), + 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, u". a&b . . a&b ."), + + 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"), + 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"&y &y"), + 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"), + 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"xy xamp;y"), + # Passing ';' to cut can break existing HTML entities, so those strings + # are auto-escaped. + 'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"), + 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&ampy"), + + # The "escape" filter works the same whether autoescape is on or off, + # but it has no effect on strings already marked as safe. + 'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, u"x&y x&y"), + 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), + + # It is only applied once, regardless of the number of times it + # appears in a chain. + 'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, u"x&y"), + + # Force_escape is applied immediately. It can be used to provide + # double-escaping, for example. + 'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"), + 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, u"x&amp;y"), + + # Because the result of force_escape is "safe", an additional + # escape filter has no effect. + 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&y"), + 'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&y"), + + # The contents in "linebreaks" and "linebreaksbr" are escaped + # according to the current autoescape setting. + 'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"<p>x&<br />y</p> <p>x&<br />y</p>"), + 'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"<p>x&<br />y</p> <p>x&<br />y</p>"), + + 'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&<br />y x&<br />y"), + 'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, u"x&<br />y x&<br />y"), + + 'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": u"<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), + 'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, u"<b>hello</b> -- <b>hello</b>"), + + 'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x <p>y</p> x <p>y</p>"), + 'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, u"x <p>y</p> x <p>y</p>"), + + 'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), + 'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), + + 'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), + 'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), + + 'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), + 'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), + + 'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), + 'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), + + 'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), + 'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), + 'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), + 'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), + 'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), + + # If the input to "default" filter is marked as safe, then so is the + # output. However, if the default arg is used, auto-escaping kicks in + # (if enabled), because we cannot mark the default as safe. + # + # Note: we have to use {"a": ""} here, otherwise the invalid template + # variable string interferes with the test result. + 'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"), + 'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"), + 'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), + 'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"), + + 'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"), + 'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"), + + 'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), + 'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), + + # Chaining a bunch of safeness-preserving filters should not alter + # the safe status either way. + 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), + 'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), + + # Using a filter that forces a string back to unsafe: + 'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), + 'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), + + # Using a filter that forces safeness does not lead to double-escaping + 'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"), + 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A < b"), + + # Force to safe, then back (also showing why using force_escape too + # early in a chain can lead to unexpected results). + 'chaining07': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a < "), + 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:"b" }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining09': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a < "), + 'chaining10': ('{% autoescape off %}{{ a|cut:"b"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), + 'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "), + 'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"), + 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), + } diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 60f7d54145..d52e8f0abf 100644 --- a/tests/regressiontests/templates/tests.py +++ b/tests/regressiontests/templates/tests.py @@ -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), - } - # 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 + ### 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>"), - old_template_loaders = loader.template_source_loaders - loader.template_source_loaders = [test_template_loader] + # Autoescape disabling and enabling nest in a predictable way. + 'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": "<a>"}, "<a> <a>"), - failures = [] - tests = TEMPLATE_TESTS.items() - tests.sort() + 'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>first</b>"}, "<b>first</b>"), - # Turn TEMPLATE_DEBUG off, because tests assume that. - old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False + # 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>"), - # Set TEMPLATE_STRING_IF_INVALID to a known string - old_invalid = settings.TEMPLATE_STRING_IF_INVALID - expected_invalid_str = 'INVALID' + # 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'), - 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)) + # 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), + } if __name__ == "__main__": unittest.main() |
