summaryrefslogtreecommitdiff
path: root/tests/regressiontests
diff options
context:
space:
mode:
authorMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-11-18 05:48:24 +0000
committerMalcolm Tredinnick <malcolm.tredinnick@gmail.com>2007-11-18 05:48:24 +0000
commit3d07f94d68ae7ef69c336c36ee119ef49c1e6028 (patch)
tree5ddb605fbe53758ed57b876d73791691d8ebf7c6 /tests/regressiontests
parent44df4e390fc9e037a3ab2775ffd695c80831f0ee (diff)
queryset-refactor: Merged from trunk up to [6689].
git-svn-id: http://code.djangoproject.com/svn/django/branches/queryset-refactor@6690 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/regressiontests')
-rw-r--r--tests/regressiontests/defaultfilters/tests.py8
-rw-r--r--tests/regressiontests/forms/extra.py3
-rw-r--r--tests/regressiontests/forms/forms.py2
-rw-r--r--tests/regressiontests/forms/tests.py2
-rw-r--r--tests/regressiontests/humanize/tests.py3
-rw-r--r--tests/regressiontests/i18n/tests.py14
-rw-r--r--tests/regressiontests/templates/filters.py224
-rw-r--r--tests/regressiontests/templates/tests.py247
-rw-r--r--tests/regressiontests/test_client_regress/models.py52
9 files changed, 406 insertions, 149 deletions
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index 9482f1cc9f..26d448900d 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -17,6 +17,10 @@ u'0'
u'7.700'
>>> floatformat(6.000000,3)
u'6.000'
+>>> floatformat(6.200000, 3)
+u'6.200'
+>>> floatformat(6.200000, -3)
+u'6.200'
>>> floatformat(13.1031,-3)
u'13.103'
>>> floatformat(11.1197, -2)
@@ -190,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'&lt;some html &amp; special characters &gt; here'
->>> escape(u'<some html & special characters > here ĐÅ€£')
+>>> force_escape(u'<some html & special characters > here ĐÅ€£')
u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
>>> linebreaks(u'line 1')
diff --git a/tests/regressiontests/forms/extra.py b/tests/regressiontests/forms/extra.py
index 7f6175f649..9dff4071f1 100644
--- a/tests/regressiontests/forms/extra.py
+++ b/tests/regressiontests/forms/extra.py
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
tests = r"""
>>> from django.newforms import *
+>>> from django.utils.encoding import force_unicode
>>> import datetime
>>> import time
>>> import re
@@ -362,7 +363,7 @@ u'sirrobin'
... return self.as_divs()
... def as_divs(self):
... if not self: return u''
-... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % e for e in self])
+... return u'<div class="errorlist">%s</div>' % ''.join([u'<div class="error">%s</div>' % force_unicode(e) for e in self])
>>> class CommentForm(Form):
... name = CharField(max_length=50, required=False)
... email = EmailField()
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&#39;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 aa33386d09..e646ce8f82 100644
--- a/tests/regressiontests/forms/tests.py
+++ b/tests/regressiontests/forms/tests.py
@@ -48,7 +48,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,
'util_tests': util_tests,
'widgets_tests': widgets_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/i18n/tests.py b/tests/regressiontests/i18n/tests.py
index 0a18e8bea5..2ffc62f90d 100644
--- a/tests/regressiontests/i18n/tests.py
+++ b/tests/regressiontests/i18n/tests.py
@@ -4,7 +4,7 @@ import misc
regressions = ur"""
Format string interpolation should work with *_lazy objects.
->>> from django.utils.translation import ugettext_lazy, activate, deactivate, gettext_lazy
+>>> from django.utils.translation import ugettext, ugettext_lazy, activate, deactivate, gettext_lazy
>>> s = ugettext_lazy('Add %(name)s')
>>> d = {'name': 'Ringo'}
>>> s % d
@@ -39,6 +39,18 @@ unicode(string_concat(...)) should not raise a TypeError - #4796
<module 'django.utils.translation' from ...>
>>> unicode(django.utils.translation.string_concat("dja", "ngo"))
u'django'
+
+Translating a string requiring no auto-escaping shouldn't change the "safe"
+status.
+
+>>> from django.utils.safestring import mark_safe
+>>> s = mark_safe('Password')
+>>> type(s)
+<class 'django.utils.safestring.SafeString'>
+>>> activate('de')
+>>> type(ugettext(s))
+<class 'django.utils.safestring.SafeUnicode'>
+>>> deactivate()
"""
__test__ = {
diff --git a/tests/regressiontests/templates/filters.py b/tests/regressiontests/templates/filters.py
new file mode 100644
index 0000000000..27b24cb169
--- /dev/null
+++ b/tests/regressiontests/templates/filters.py
@@ -0,0 +1,224 @@
+# 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"&lt;a&gt;\&#39; <a>\'"),
+
+ 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred&gt;")}, u"Fred> Fred&gt;"),
+ 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred&gt;")}, u"Fred&gt; Fred&gt;"),
+
+ # 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&amp;b a&amp;b"),
+ 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, u"a&amp;amp;b a&amp;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&lt;two&gt;\nthree")}, u"1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+ 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n&lt;two&gt;\nthree")}, u"1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+
+ 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, u"apple & banana apple &amp; banana"),
+ 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, u"apple &amp; banana apple &amp; 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&#39;&amp;&#39;]"),
+ '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 &amp; b")}, u"a-b a-amp-b"),
+ 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a &amp; 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&lt;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 &amp; bravo")}, u"alpha & ... alpha &amp; ..."),
+ 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, u"alpha &amp; ... alpha &amp; ..."),
+
+ # 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 &amp; b")}, u"A & B A &AMP; B"),
+ 'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, u"A &amp; B A &amp;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=&amp;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 &amp; b")}, 'a &amp; b'),
+ 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
+
+ # This will lead to a nonsense result, but at least it won't be
+ # exploitable for XSS purposes when auto-escaping is on.
+ 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"),
+ 'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '&lt;script&gt;alert(&#39;foo&#39;)&lt;/script&gt;'),
+
+ '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 &amp; b")}, "3 3"),
+ 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; 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 &amp;\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&amp;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&amp;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&amp;b . . a&b ."),
+
+ 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"&y &amp;y"),
+ 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"&amp;y &amp;y"),
+ 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"xy xamp;y"),
+ 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&amp;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&amp;y")}, u"x&y x&ampy"),
+ 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, u"x&amp;y x&amp;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&amp;y x&y"),
+ 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;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&amp;y"),
+ 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, u"x&amp;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&amp;y"),
+ 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;amp;y"),
+ 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, u"x&amp;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&amp;y"),
+ 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, u"x&amp;y"),
+ 'filter-force-escape07': ('{{ a|escape|force_escape }}', {"a": "x&y"}, u"x&amp;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&amp;<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&amp;<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>"}, "&lt;b&gt;hello&lt;/b&gt; -- <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 &lt;p&gt;y&lt;/p&gt; 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&amp;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&amp;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")}, "&amp;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&gt;\n\t<ul>\n\t\t<li>&lt;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&gt;\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>"),
+
+ # Literal string arguments to the default filter are always treated as
+ # safe strings, regardless of the auto-escaping state.
+ #
+ # 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>") }, "&lt;1-800-2255-63&gt; <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 &lt; 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 &lt; .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 &lt; b"),
+ 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A &lt; 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:";" }}', {"a": "a < b"}, "a &amp;lt b"),
+ 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:";" }}{% endautoescape %}', {"a": "a < b"}, "a &lt b"),
+ 'chaining09': ('{{ a|cut:";"|force_escape }}', {"a": "a < b"}, "a &lt; b"),
+ 'chaining10': ('{% autoescape off %}{{ a|cut:";"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a &lt; b"),
+ '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 &lt; b"),
+ 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a &lt; b"),
+ }
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index 60f7d54145..5c3a0a9081 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
@@ -239,8 +318,9 @@ class Templates(unittest.TestCase):
# Chained filters, with an argument to the first one
'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'),
+ # Literal string as argument is always "safe" from auto-escaping..
+ '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'),
@@ -537,7 +617,7 @@ class Templates(unittest.TestCase):
### INHERITANCE ###########################################################
# Standard template with no inheritance
- 'inheritance01': ("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}", {}, '1_3_'),
+ 'inheritance01': ("1{% block first %}&{% endblock %}3{% block second %}_{% endblock %}", {}, '1&3_'),
# Standard two-level inheritance
'inheritance02': ("{% extends 'inheritance01' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
@@ -546,7 +626,7 @@ class Templates(unittest.TestCase):
'inheritance03': ("{% extends 'inheritance02' %}", {}, '1234'),
# Two-level with no redefinitions on second level
- 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1_3_'),
+ 'inheritance04': ("{% extends 'inheritance01' %}", {}, '1&3_'),
# Two-level with double quotes instead of single quotes
'inheritance05': ('{% extends "inheritance02" %}', {}, '1234'),
@@ -555,16 +635,16 @@ class Templates(unittest.TestCase):
'inheritance06': ("{% extends foo %}", {'foo': 'inheritance02'}, '1234'),
# Two-level with one block defined, one block not defined
- 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1_35'),
+ 'inheritance07': ("{% extends 'inheritance01' %}{% block second %}5{% endblock %}", {}, '1&35'),
# Three-level with one block defined on this level, two blocks defined next level
'inheritance08': ("{% extends 'inheritance02' %}{% block second %}5{% endblock %}", {}, '1235'),
# Three-level with second and third levels blank
- 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1_3_'),
+ 'inheritance09': ("{% extends 'inheritance04' %}", {}, '1&3_'),
# Three-level with space NOT in a block -- should be ignored
- 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1_3_'),
+ 'inheritance10': ("{% extends 'inheritance04' %} ", {}, '1&3_'),
# Three-level with both blocks defined on this level, but none on second level
'inheritance11': ("{% extends 'inheritance04' %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {}, '1234'),
@@ -576,7 +656,7 @@ class Templates(unittest.TestCase):
'inheritance13': ("{% extends 'inheritance02' %}{% block first %}a{% endblock %}{% block second %}b{% endblock %}", {}, '1a3b'),
# A block defined only in a child template shouldn't be displayed
- 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1_3_'),
+ 'inheritance14': ("{% extends 'inheritance01' %}{% block newblock %}NO DISPLAY{% endblock %}", {}, '1&3_'),
# A block within another block
'inheritance15': ("{% extends 'inheritance01' %}{% block first %}2{% block inner %}inner{% endblock %}{% endblock %}", {}, '12inner3_'),
@@ -594,16 +674,16 @@ class Templates(unittest.TestCase):
'inheritance19': ("{% extends 'inheritance01' %}{% block first %}{% load testtags %}{% echo 400 %}5678{% endblock %}", {}, '140056783_'),
# Two-level inheritance with {{ block.super }}
- 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+ 'inheritance20': ("{% extends 'inheritance01' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1&a3_'),
# Three-level inheritance with {{ block.super }} from parent
'inheritance21': ("{% extends 'inheritance02' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '12a34'),
# Three-level inheritance with {{ block.super }} from grandparent
- 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1_a3_'),
+ 'inheritance22': ("{% extends 'inheritance04' %}{% block first %}{{ block.super }}a{% endblock %}", {}, '1&a3_'),
# Three-level inheritance with {{ block.super }} from parent and grandparent
- 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1_ab3_'),
+ 'inheritance23': ("{% extends 'inheritance20' %}{% block first %}{{ block.super }}b{% endblock %}", {}, '1&ab3_'),
# Inheritance from local context without use of template loader
'inheritance24': ("{% extends context_template %}{% block first %}2{% endblock %}{% block second %}4{% endblock %}", {'context_template': template.Template("1{% block first %}_{% endblock %}3{% block second %}_{% endblock %}")}, '1234'),
@@ -625,10 +705,10 @@ class Templates(unittest.TestCase):
'i18n02': ('{% load i18n %}{% trans "xxxyyyxxx" %}', {}, "xxxyyyxxx"),
# simple translation of a variable
- 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': 'xxxyyyxxx'}, "xxxyyyxxx"),
+ 'i18n03': ('{% load i18n %}{% blocktrans %}{{ anton }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u"Å"),
# simple translation of a variable and filter
- 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'XXXYYYXXX'}, "xxxyyyxxx"),
+ 'i18n04': ('{% load i18n %}{% blocktrans with anton|lower as berta %}{{ berta }}{% endblocktrans %}', {'anton': '\xc3\x85'}, u'å'),
# simple translation of a string with interpolation
'i18n05': ('{% load i18n %}{% blocktrans %}xxx{{ anton }}xxx{% endblocktrans %}', {'anton': 'yyy'}, "xxxyyyxxx"),
@@ -637,10 +717,10 @@ class Templates(unittest.TestCase):
'i18n06': ('{% load i18n %}{% trans "Page not found" %}', {'LANGUAGE_CODE': 'de'}, "Seite nicht gefunden"),
# translation of singular form
- 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 1}, "singular"),
+ 'i18n07': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 1}, "singular"),
# translation of plural form
- 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}plural{% endblocktrans %}', {'number': 2}, "plural"),
+ 'i18n08': ('{% load i18n %}{% blocktrans count number as counter %}singular{% plural %}{{ counter }} plural{% endblocktrans %}', {'number': 2}, "2 plural"),
# simple non-translation (only marking) of a string to german
'i18n09': ('{% load i18n %}{% trans "Page not found" noop %}', {'LANGUAGE_CODE': 'de'}, "Page not found"),
@@ -654,8 +734,16 @@ class Templates(unittest.TestCase):
# usage of the get_available_languages tag
'i18n12': ('{% load i18n %}{% get_available_languages as langs %}{% for lang in langs %}{% ifequal lang.0 "de" %}{{ lang.0 }}{% endifequal %}{% endfor %}', {}, 'de'),
- # translation of a constant string
- 'i18n13': ('{{ _("Page not found") }}', {'LANGUAGE_CODE': 'de'}, 'Seite nicht gefunden'),
+ # translation of constant strings
+ 'i18n13': ('{{ _("Password") }}', {'LANGUAGE_CODE': 'de'}, 'Passwort'),
+ 'i18n14': ('{% cycle "foo" _("Password") _(\'Password\') as c %} {% cycle c %} {% cycle c %}', {'LANGUAGE_CODE': 'de'}, 'foo Passwort Passwort'),
+ 'i18n15': ('{{ absent|default:_("Password") }}', {'LANGUAGE_CODE': 'de', 'absent': ""}, 'Passwort'),
+ 'i18n16': ('{{ _("<") }}', {'LANGUAGE_CODE': 'de'}, '<'),
+
+ # Escaping inside blocktrans works as if it was directly in the
+ # template.
+ 'i18n17': ('{% load i18n %}{% blocktrans with anton|escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
+ 'i18n18': ('{% load i18n %}{% blocktrans with anton|force_escape as berta %}{{ berta }}{% endblocktrans %}', {'anton': 'α & β'}, u'α &amp; β'),
### HANDLING OF TEMPLATE_STRING_IF_INVALID ###################################
@@ -760,38 +848,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 +875,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
-
- 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')
+ ### 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>"}, "&lt;b&gt;hello&lt;/b&gt;"),
- 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))
+ # Autoescape disabling and enabling nest in a predictable way.
+ 'autoescape-tag04': ("{% autoescape off %}{{ first }} {% autoescape on%}{{ first }}{% endautoescape %}{% endautoescape %}", {"first": "<a>"}, "<a> &lt;a&gt;"),
- if 'LANGUAGE_CODE' in vals[1]:
- deactivate()
+ 'autoescape-tag05': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>first</b>"}, "&lt;b&gt;first&lt;/b&gt;"),
- if template.invalid_var_format_string:
- expected_invalid_str = 'INVALID'
- template.invalid_var_format_string = 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>"),
- loader.template_source_loaders = old_template_loaders
- deactivate()
- settings.TEMPLATE_DEBUG = old_td
- settings.TEMPLATE_STRING_IF_INVALID = old_invalid
+ # Literal string arguments to filters, if used in the result, are
+ # safe.
+ 'basic-syntax08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'),
- 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()
diff --git a/tests/regressiontests/test_client_regress/models.py b/tests/regressiontests/test_client_regress/models.py
index 60fd909f43..b5d9ae63b9 100644
--- a/tests/regressiontests/test_client_regress/models.py
+++ b/tests/regressiontests/test_client_regress/models.py
@@ -31,12 +31,12 @@ class AssertContainsTests(TestCase):
self.assertContains(response, 'once', 2)
except AssertionError, e:
self.assertEquals(str(e), "Found 1 instances of 'once' in response (expected 2)")
-
+
try:
self.assertContains(response, 'twice', 1)
except AssertionError, e:
self.assertEquals(str(e), "Found 2 instances of 'twice' in response (expected 1)")
-
+
try:
self.assertContains(response, 'thrice')
except AssertionError, e:
@@ -46,37 +46,37 @@ class AssertContainsTests(TestCase):
self.assertContains(response, 'thrice', 3)
except AssertionError, e:
self.assertEquals(str(e), "Found 0 instances of 'thrice' in response (expected 3)")
-
+
class AssertTemplateUsedTests(TestCase):
fixtures = ['testdata.json']
-
+
def test_no_context(self):
"Template usage assertions work then templates aren't in use"
response = self.client.get('/test_client_regress/no_template_view/')
# Check that the no template case doesn't mess with the template assertions
self.assertTemplateNotUsed(response, 'GET Template')
-
+
try:
self.assertTemplateUsed(response, 'GET Template')
except AssertionError, e:
self.assertEquals(str(e), "No templates used to render the response")
- def test_single_context(self):
+ def test_single_context(self):
"Template assertions work when there is a single context"
response = self.client.get('/test_client/post_view/', {})
- #
+ #
try:
self.assertTemplateNotUsed(response, 'Empty GET Template')
except AssertionError, e:
self.assertEquals(str(e), "Template 'Empty GET Template' was used unexpectedly in rendering the response")
-
+
try:
- self.assertTemplateUsed(response, 'Empty POST Template')
+ self.assertTemplateUsed(response, 'Empty POST Template')
except AssertionError, e:
self.assertEquals(str(e), "Template 'Empty POST Template' was not a template used to render the response. Actual template(s) used: Empty GET Template")
-
+
def test_multiple_context(self):
"Template assertions work when there are multiple contexts"
post_data = {
@@ -99,37 +99,37 @@ class AssertTemplateUsedTests(TestCase):
self.assertEquals(str(e), "Template 'base.html' was used unexpectedly in rendering the response")
try:
- self.assertTemplateUsed(response, "Valid POST Template")
+ self.assertTemplateUsed(response, "Valid POST Template")
except AssertionError, e:
self.assertEquals(str(e), "Template 'Valid POST Template' was not a template used to render the response. Actual template(s) used: form_view.html, base.html")
class AssertRedirectsTests(TestCase):
def test_redirect_page(self):
- "An assertion is raised if the original page couldn't be retrieved as expected"
+ "An assertion is raised if the original page couldn't be retrieved as expected"
# This page will redirect with code 301, not 302
- response = self.client.get('/test_client/permanent_redirect_view/')
+ response = self.client.get('/test_client/permanent_redirect_view/')
try:
self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e:
self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
-
+
def test_lost_query(self):
"An assertion is raised if the redirect location doesn't preserve GET parameters"
response = self.client.get('/test_client/redirect_view/', {'var': 'value'})
try:
self.assertRedirects(response, '/test_client/get_view/')
except AssertionError, e:
- self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected '/test_client/get_view/'")
+ self.assertEquals(str(e), "Response redirected to 'http://testserver/test_client/get_view/?var=value', expected 'http://testserver/test_client/get_view/'")
def test_incorrect_target(self):
"An assertion is raised if the response redirects to another target"
- response = self.client.get('/test_client/permanent_redirect_view/')
+ response = self.client.get('/test_client/permanent_redirect_view/')
try:
# Should redirect to get_view
self.assertRedirects(response, '/test_client/some_view/')
except AssertionError, e:
self.assertEquals(str(e), "Response didn't redirect as expected: Response code was 301 (expected 302)")
-
+
def test_target_page(self):
"An assertion is raised if the response redirect target cannot be retrieved as expected"
response = self.client.get('/test_client/double_redirect_view/')
@@ -138,7 +138,7 @@ class AssertRedirectsTests(TestCase):
self.assertRedirects(response, 'http://testserver/test_client/permanent_redirect_view/')
except AssertionError, e:
self.assertEquals(str(e), "Couldn't retrieve redirection page '/test_client/permanent_redirect_view/': response code was 301 (expected 200)")
-
+
class AssertFormErrorTests(TestCase):
def test_unknown_form(self):
"An assertion is raised if the form name is unknown"
@@ -157,7 +157,7 @@ class AssertFormErrorTests(TestCase):
self.assertFormError(response, 'wrong_form', 'some_field', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The form 'wrong_form' was not used to render the response")
-
+
def test_unknown_field(self):
"An assertion is raised if the field name is unknown"
post_data = {
@@ -175,7 +175,7 @@ class AssertFormErrorTests(TestCase):
self.assertFormError(response, 'form', 'some_field', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The form 'form' in context 0 does not contain the field 'some_field'")
-
+
def test_noerror_field(self):
"An assertion is raised if the field doesn't have any errors"
post_data = {
@@ -193,7 +193,7 @@ class AssertFormErrorTests(TestCase):
self.assertFormError(response, 'form', 'value', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The field 'value' on form 'form' in context 0 contains no errors")
-
+
def test_unknown_error(self):
"An assertion is raised if the field doesn't contain the provided error"
post_data = {
@@ -211,7 +211,7 @@ class AssertFormErrorTests(TestCase):
self.assertFormError(response, 'form', 'email', 'Some error.')
except AssertionError, e:
self.assertEqual(str(e), "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [u'Enter a valid e-mail address.'])")
-
+
def test_unknown_nonfield_error(self):
"""
Checks that an assertion is raised if the form's non field errors
@@ -231,7 +231,7 @@ class AssertFormErrorTests(TestCase):
try:
self.assertFormError(response, 'form', None, 'Some error.')
except AssertionError, e:
- self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
+ self.assertEqual(str(e), "The form 'form' in context 0 does not contain the non-field error 'Some error.' (actual errors: )")
class FileUploadTests(TestCase):
def test_simple_upload(self):
@@ -256,8 +256,8 @@ class LoginTests(TestCase):
# Get a redirection page with the second client.
response = c.get("/test_client_regress/login_protected_redirect_view/")
-
- # At this points, the self.client isn't logged in.
- # Check that assertRedirects uses the original client, not the
+
+ # At this points, the self.client isn't logged in.
+ # Check that assertRedirects uses the original client, not the
# default client.
self.assertRedirects(response, "http://testserver/test_client_regress/get_view/")