summaryrefslogtreecommitdiff
path: root/tests/regressiontests
diff options
context:
space:
mode:
authorJoseph Kocherhans <joseph@jkocherhans.com>2007-11-30 06:23:24 +0000
committerJoseph Kocherhans <joseph@jkocherhans.com>2007-11-30 06:23:24 +0000
commitc81b01e060ad80a5c4df579a96ff737df2d336b3 (patch)
treed34a873e7b5e19f0e91424d4ab28e2f968233d63 /tests/regressiontests
parentf88babafc58eafece72d3f2f7444336c69196808 (diff)
newforms-admin: Merged from trunk up to [6775].
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@6777 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'tests/regressiontests')
-rw-r--r--tests/regressiontests/cache/tests.py30
-rw-r--r--tests/regressiontests/datastructures/tests.py12
-rw-r--r--tests/regressiontests/dateformat/tests.py4
-rw-r--r--tests/regressiontests/defaultfilters/tests.py2
-rw-r--r--tests/regressiontests/forms/error_messages.py45
-rw-r--r--tests/regressiontests/forms/fields.py14
-rw-r--r--tests/regressiontests/forms/localflavor/ca.py4
-rw-r--r--tests/regressiontests/forms/models.py4
-rw-r--r--tests/regressiontests/forms/widgets.py61
-rw-r--r--tests/regressiontests/i18n/tests.py14
-rw-r--r--tests/regressiontests/templates/filters.py44
-rw-r--r--tests/regressiontests/templates/tests.py59
-rw-r--r--tests/regressiontests/utils/datastructures.py51
-rw-r--r--tests/regressiontests/utils/tests.py13
-rw-r--r--tests/regressiontests/utils/timesince.py2
-rw-r--r--tests/regressiontests/views/tests/static.py10
16 files changed, 331 insertions, 38 deletions
diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
index 3879da7703..e94ea33139 100644
--- a/tests/regressiontests/cache/tests.py
+++ b/tests/regressiontests/cache/tests.py
@@ -3,9 +3,12 @@
# Unit tests for cache framework
# Uses whatever cache backend is set in the test settings file.
-from django.core.cache import cache
import time, unittest
+from django.core.cache import cache
+from django.utils.cache import patch_vary_headers
+from django.http import HttpResponse
+
# functions/classes for complex data type tests
def f():
return 42
@@ -87,5 +90,30 @@ class Cache(unittest.TestCase):
cache.set(key, value)
self.assertEqual(cache.get(key), value)
+
+class CacheUtils(unittest.TestCase):
+ """TestCase for django.utils.cache functions."""
+
+ def test_patch_vary_headers(self):
+ headers = (
+ # Initial vary, new headers, resulting vary.
+ (None, ('Accept-Encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'),
+ ('Accept-Encoding', ('ACCEPT-ENCODING',), 'Accept-Encoding'),
+ ('Cookie', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding',), 'Cookie, Accept-Encoding'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ (None, ('Accept-Encoding', 'COOKIE'), 'Accept-Encoding, COOKIE'),
+ ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ ('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'),
+ )
+ for initial_vary, newheaders, resulting_vary in headers:
+ response = HttpResponse()
+ if initial_vary is not None:
+ response['Vary'] = initial_vary
+ patch_vary_headers(response, newheaders)
+ self.assertEqual(response['Vary'], resulting_vary)
+
+
if __name__ == '__main__':
unittest.main()
diff --git a/tests/regressiontests/datastructures/tests.py b/tests/regressiontests/datastructures/tests.py
index d1e21e673c..3b0ccde257 100644
--- a/tests/regressiontests/datastructures/tests.py
+++ b/tests/regressiontests/datastructures/tests.py
@@ -25,11 +25,23 @@
>>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
>>> d['name']
'Simon'
+>>> d.get('name')
+'Simon'
>>> d.getlist('name')
['Adrian', 'Simon']
+>>> d['lastname']
+Traceback (most recent call last):
+...
+MultiValueDictKeyError: "Key 'lastname' not found in <MultiValueDict: {'position': ['Developer'], 'name': ['Adrian', 'Simon']}>"
+>>> d.get('lastname')
+
>>> d.get('lastname', 'nonexistent')
'nonexistent'
+>>> d.getlist('lastname')
+[]
>>> d.setlist('lastname', ['Holovaty', 'Willison'])
+>>> d.getlist('lastname')
+['Holovaty', 'Willison']
### SortedDict #################################################################
diff --git a/tests/regressiontests/dateformat/tests.py b/tests/regressiontests/dateformat/tests.py
index 30c9a4e6dd..481e36a7dd 100644
--- a/tests/regressiontests/dateformat/tests.py
+++ b/tests/regressiontests/dateformat/tests.py
@@ -66,6 +66,9 @@ u'1979 189 CET'
>>> format(my_birthday, r'jS o\f F')
u'8th of July'
+
+>>> format(the_future, r'Y')
+u'2100'
"""
from django.utils import dateformat, translation
@@ -84,3 +87,4 @@ except AttributeError:
my_birthday = datetime.datetime(1979, 7, 8, 22, 00)
summertime = datetime.datetime(2005, 10, 30, 1, 00)
wintertime = datetime.datetime(2005, 10, 30, 4, 00)
+the_future = datetime.datetime(2100, 10, 25, 0, 00)
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index 26d448900d..bfa03cd6e1 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -37,6 +37,8 @@ u''
u'13.1031'
>>> floatformat(u'foo', u'bar')
u''
+>>> floatformat(None)
+u''
>>> addslashes(u'"double quotes" and \'single quotes\'')
u'\\"double quotes\\" and \\\'single quotes\\\''
diff --git a/tests/regressiontests/forms/error_messages.py b/tests/regressiontests/forms/error_messages.py
index ff7e110f6f..9f972f5b90 100644
--- a/tests/regressiontests/forms/error_messages.py
+++ b/tests/regressiontests/forms/error_messages.py
@@ -312,4 +312,49 @@ ValidationError: [u'REQUIRED']
Traceback (most recent call last):
...
ValidationError: [u'INVALID IP ADDRESS']
+
+###############################################################################
+
+# Create choices for the model choice field tests below.
+
+>>> from regressiontests.forms.models import ChoiceModel
+>>> ChoiceModel.objects.create(pk=1, name='a')
+<ChoiceModel: ChoiceModel object>
+>>> ChoiceModel.objects.create(pk=2, name='b')
+<ChoiceModel: ChoiceModel object>
+>>> ChoiceModel.objects.create(pk=3, name='c')
+<ChoiceModel: ChoiceModel object>
+
+# ModelChoiceField ############################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = 'INVALID CHOICE'
+>>> f = ModelChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('4')
+Traceback (most recent call last):
+...
+ValidationError: [u'INVALID CHOICE']
+
+# ModelMultipleChoiceField ####################################################
+
+>>> e = {'required': 'REQUIRED'}
+>>> e['invalid_choice'] = '%s IS INVALID CHOICE'
+>>> e['list'] = 'NOT A LIST OF VALUES'
+>>> f = ModelMultipleChoiceField(queryset=ChoiceModel.objects.all(), error_messages=e)
+>>> f.clean('')
+Traceback (most recent call last):
+...
+ValidationError: [u'REQUIRED']
+>>> f.clean('3')
+Traceback (most recent call last):
+...
+ValidationError: [u'NOT A LIST OF VALUES']
+>>> f.clean(['4'])
+Traceback (most recent call last):
+...
+ValidationError: [u'4 IS INVALID CHOICE']
"""
diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py
index 3b93d70338..cff5db6fca 100644
--- a/tests/regressiontests/forms/fields.py
+++ b/tests/regressiontests/forms/fields.py
@@ -323,6 +323,10 @@ Decimal("3.14")
Traceback (most recent call last):
...
ValidationError: [u'Enter a number.']
+>>> f.clean(u'łąść')
+Traceback (most recent call last):
+...
+ValidationError: [u'Enter a number.']
>>> f.clean('1.0 ')
Decimal("1.0")
>>> f.clean(' 1.0')
@@ -914,6 +918,11 @@ False
>>> f.clean('Django rocks')
True
+>>> f.clean('True')
+True
+>>> f.clean('False')
+False
+
>>> f = BooleanField(required=False)
>>> f.clean('')
False
@@ -930,6 +939,11 @@ False
>>> f.clean('Django rocks')
True
+A form's BooleanField with a hidden widget will output the string 'False', so
+that should clean to the boolean value False:
+>>> f.clean('False')
+False
+
# ChoiceField #################################################################
>>> f = ChoiceField(choices=[('1', '1'), ('2', '2')])
diff --git a/tests/regressiontests/forms/localflavor/ca.py b/tests/regressiontests/forms/localflavor/ca.py
index baeb2ad9a8..a13a6de65f 100644
--- a/tests/regressiontests/forms/localflavor/ca.py
+++ b/tests/regressiontests/forms/localflavor/ca.py
@@ -147,6 +147,10 @@ u'BC'
u'NS'
>>> f.clean(' manitoba ')
u'MB'
+>>> f.clean(' new brunswick ')
+u'NB'
+>>> f.clean('NB')
+u'NB'
>>> f.clean('T2S 2H7')
Traceback (most recent call last):
...
diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
index 1a6f566b6b..c7ce128560 100644
--- a/tests/regressiontests/forms/models.py
+++ b/tests/regressiontests/forms/models.py
@@ -10,6 +10,10 @@ class Defaults(models.Model):
def_date = models.DateField(default = datetime.date(1980, 1, 1))
value = models.IntegerField(default=42)
+class ChoiceModel(models.Model):
+ """For ModelChoiceField and ModelMultipleChoiceField tests."""
+ name = models.CharField(max_length=10)
+
__test__ = {'API_TESTS': """
>>> from django.newforms import form_for_model, form_for_instance
diff --git a/tests/regressiontests/forms/widgets.py b/tests/regressiontests/forms/widgets.py
index cb1d084631..0e69602103 100644
--- a/tests/regressiontests/forms/widgets.py
+++ b/tests/regressiontests/forms/widgets.py
@@ -2,6 +2,7 @@
tests = r"""
>>> from django.newforms import *
>>> from django.newforms.widgets import RadioFieldRenderer
+>>> from django.utils.safestring import mark_safe
>>> import datetime
>>> import time
>>> import re
@@ -128,6 +129,13 @@ u'<input type="hidden" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u
>>> w.render('email', '', attrs={'class': 'special'})
u'<input type="hidden" class="special" name="email" />'
+Boolean values are rendered to their string forms ("True" and "False").
+>>> w = HiddenInput()
+>>> w.render('get_spam', False)
+u'<input type="hidden" name="get_spam" value="False" />'
+>>> w.render('get_spam', True)
+u'<input type="hidden" name="get_spam" value="True" />'
+
# MultipleHiddenInput Widget ##################################################
>>> w = MultipleHiddenInput()
@@ -205,6 +213,8 @@ u'<textarea rows="10" cols="40" name="msg"></textarea>'
u'<textarea rows="10" cols="40" name="msg">value</textarea>'
>>> w.render('msg', 'some "quoted" & ampersanded value')
u'<textarea rows="10" cols="40" name="msg">some &quot;quoted&quot; &amp; ampersanded value</textarea>'
+>>> w.render('msg', mark_safe('pre &quot;quoted&quot; value'))
+u'<textarea rows="10" cols="40" name="msg">pre &quot;quoted&quot; value</textarea>'
>>> w.render('msg', 'value', attrs={'class': 'pretty', 'rows': 20})
u'<textarea class="pretty" rows="20" cols="40" name="msg">value</textarea>'
@@ -375,6 +385,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<select name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select name="email">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
@@ -538,6 +559,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<option value="5">5</option>
</select>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<select multiple="multiple" name="escape">
+<option value="1">1</option>
+<option value="2">2</option>
+<option value="3">3</option>
+<option value="bad">you &amp; me</option>
+<option value="good">you &gt; me</option>
+</select>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<select multiple="multiple" name="nums">\n<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" selected="selected">\u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</option>\n<option value="\u0107\u017e\u0161\u0111">abc\u0107\u017e\u0161\u0111</option>\n</select>'
@@ -663,6 +695,16 @@ You can create your own custom renderers for RadioSelect to use.
<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
<label><input type="radio" name="beatle" value="R" /> Ringo</label>
+Or you can use custom RadioSelect fields that use your custom renderer.
+>>> class CustomRadioSelect(RadioSelect):
+... renderer = MyRenderer
+>>> w = CustomRadioSelect()
+>>> print w.render('beatle', 'G', choices=(('J', 'John'), ('P', 'Paul'), ('G', 'George'), ('R', 'Ringo')))
+<label><input type="radio" name="beatle" value="J" /> John</label><br />
+<label><input type="radio" name="beatle" value="P" /> Paul</label><br />
+<label><input checked="checked" type="radio" name="beatle" value="G" /> George</label><br />
+<label><input type="radio" name="beatle" value="R" /> Ringo</label>
+
A RadioFieldRenderer object also allows index access to individual RadioInput
objects.
>>> w = RadioSelect()
@@ -682,6 +724,14 @@ Traceback (most recent call last):
...
IndexError: list index out of range
+# Choices are escaped correctly
+>>> w = RadioSelect()
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<ul>
+<li><label><input type="radio" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="radio" name="escape" value="good" /> you &gt; me</label></li>
+</ul>
+
# Unicode choices are correctly rendered as HTML
>>> w = RadioSelect()
>>> unicode(w.render('email', 'ŠĐĆŽćžšđ', choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')]))
@@ -811,6 +861,17 @@ If 'choices' is passed to both the constructor and render(), then they'll both b
<li><label><input type="checkbox" name="nums" value="5" /> 5</label></li>
</ul>
+# Choices are escaped correctly
+>>> print w.render('escape', None, choices=(('bad', 'you & me'), ('good', mark_safe('you &gt; me'))))
+<ul>
+<li><label><input type="checkbox" name="escape" value="1" /> 1</label></li>
+<li><label><input type="checkbox" name="escape" value="2" /> 2</label></li>
+<li><label><input type="checkbox" name="escape" value="3" /> 3</label></li>
+<li><label><input type="checkbox" name="escape" value="bad" /> you &amp; me</label></li>
+<li><label><input type="checkbox" name="escape" value="good" /> you &gt; me</label></li>
+</ul>
+
+# Unicode choices are correctly rendered as HTML
>>> w.render('nums', ['ŠĐĆŽćžšđ'], choices=[('ŠĐĆŽćžšđ', 'ŠĐabcĆŽćžšđ'), ('ćžšđ', 'abcćžšđ')])
u'<ul>\n<li><label><input type="checkbox" name="nums" value="1" /> 1</label></li>\n<li><label><input type="checkbox" name="nums" value="2" /> 2</label></li>\n<li><label><input type="checkbox" name="nums" value="3" /> 3</label></li>\n<li><label><input checked="checked" type="checkbox" name="nums" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" /> \u0160\u0110abc\u0106\u017d\u0107\u017e\u0161\u0111</label></li>\n<li><label><input type="checkbox" name="nums" value="\u0107\u017e\u0161\u0111" /> abc\u0107\u017e\u0161\u0111</label></li>\n</ul>'
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
index 5d7129480c..4175bdbe5f 100644
--- a/tests/regressiontests/templates/filters.py
+++ b/tests/regressiontests/templates/filters.py
@@ -12,6 +12,15 @@ from datetime import datetime, timedelta
from django.utils.tzinfo import LocalTimezone
from django.utils.safestring import mark_safe
+# These two classes are used to test auto-escaping of __unicode__ output.
+class UnsafeClass:
+ def __unicode__(self):
+ return u'you & me'
+
+class SafeClass:
+ def __unicode__(self):
+ return mark_safe(u'you &gt; me')
+
# RESULT SYNTAX --
# 'template_name': ('template contents', 'context dict',
# 'expected string output' or Exception class)
@@ -94,6 +103,11 @@ def get_filter_tests():
'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>'),
@@ -177,23 +191,28 @@ def get_filter_tests():
'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.
+ # 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&lt;"),
+ '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&lt;"),
+ '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>"),
+ # Ensure iriencode keeps safe strings:
+ 'filter-iriencode01': ('{{ url|iriencode }}', {'url': '?test=1&me=2'}, '?test=1&amp;me=2'),
+ 'filter-iriencode02': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': '?test=1&me=2'}, '?test=1&me=2'),
+ 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+ 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'),
+
# 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 "),
@@ -209,12 +228,19 @@ def get_filter_tests():
# 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 &lt; "),
- 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:"b" }}{% endautoescape %}', {"a": "a < b"}, "a &lt; "),
- 'chaining09': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a &lt; "),
- 'chaining10': ('{% autoescape off %}{{ a|cut:"b"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a &lt; "),
+ '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"),
+
+ # Filters decorated with stringfilter still respect is_safe.
+ 'autoescape-stringfilter01': (r'{{ unsafe|capfirst }}', {'unsafe': UnsafeClass()}, 'You &amp; me'),
+ 'autoescape-stringfilter02': (r'{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}', {'unsafe': UnsafeClass()}, 'You & me'),
+ 'autoescape-stringfilter03': (r'{{ safe|capfirst }}', {'safe': SafeClass()}, 'You &gt; me'),
+ 'autoescape-stringfilter04': (r'{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}', {'safe': SafeClass()}, 'You &gt; me'),
}
+
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
index d52e8f0abf..cbbd88b06c 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -268,6 +268,12 @@ class Templates(unittest.TestCase):
# Embedded newlines make it not-a-tag.
'basic-syntax24': ("{{ moo\n }}", {}, "{{ moo\n }}"),
+ # Literal strings are permitted inside variables, mostly for i18n
+ # purposes.
+ 'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
+ 'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
+ 'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
+
# List-index syntax allows a template to access a certain item of a subscriptable object.
'list-index01': ("{{ var.1 }}", {"var": ["first item", "second item"]}, "second item"),
@@ -318,9 +324,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
+ # Literal string as argument is always "safe" from auto-escaping..
'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}',
- {"var": None}, ' endquote&quot; hah'),
+ {"var": None}, ' endquote" hah'),
# Variable as argument
'filter-syntax11': (r'{{ var|default_if_none:var2 }}', {"var": None, "var2": "happy"}, 'happy'),
@@ -617,7 +623,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'),
@@ -626,7 +632,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'),
@@ -635,16 +641,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'),
@@ -656,7 +662,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_'),
@@ -674,16 +680,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'),
@@ -705,10 +711,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"),
@@ -717,10 +723,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"),
@@ -734,8 +740,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 ###################################
@@ -883,9 +897,14 @@ class Templates(unittest.TestCase):
'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&quot; hah'),
+ # Literal string arguments to filters, if used in the result, are
+ # safe.
+ 'autoescape-tag08': (r'{% autoescape on %}{{ var|default_if_none:" endquote\" hah" }}{% endautoescape %}', {"var": None}, ' endquote" hah'),
+
+ # Objects which return safe strings as their __unicode__ method
+ # won't get double-escaped.
+ 'autoescape-tag09': (r'{{ unsafe }}', {'unsafe': filters.UnsafeClass()}, 'you &amp; me'),
+ 'autoescape-tag10': (r'{{ safe }}', {'safe': filters.SafeClass()}, 'you &gt; me'),
# The "safe" and "escape" filters cannot work due to internal
# implementation details (fortunately, the (no)autoescape block
diff --git a/tests/regressiontests/utils/datastructures.py b/tests/regressiontests/utils/datastructures.py
new file mode 100644
index 0000000000..52bf81a9e0
--- /dev/null
+++ b/tests/regressiontests/utils/datastructures.py
@@ -0,0 +1,51 @@
+"""
+>>> from django.utils.datastructures import SortedDict
+
+>>> d = SortedDict()
+>>> d[7] = 'seven'
+>>> d[1] = 'one'
+>>> d[9] = 'nine'
+>>> d.keys()
+[7, 1, 9]
+>>> d.values()
+['seven', 'one', 'nine']
+>>> d.items()
+[(7, 'seven'), (1, 'one'), (9, 'nine')]
+
+# Overwriting an item keeps it's place.
+>>> d[1] = 'ONE'
+>>> d.values()
+['seven', 'ONE', 'nine']
+
+# New items go to the end.
+>>> d[0] = 'nil'
+>>> d.keys()
+[7, 1, 9, 0]
+
+# Deleting an item, then inserting the same key again will place it at the end.
+>>> del d[7]
+>>> d.keys()
+[1, 9, 0]
+>>> d[7] = 'lucky number 7'
+>>> d.keys()
+[1, 9, 0, 7]
+
+# Changing the keys won't do anything, it's only a copy of the keys dict.
+>>> k = d.keys()
+>>> k.remove(9)
+>>> d.keys()
+[1, 9, 0, 7]
+
+# Initialising a SortedDict with two keys will just take the first one. A real
+# dict will actually take the second value so we will too, but we'll keep the
+# ordering from the first key found.
+>>> tuples = ((2, 'two'), (1, 'one'), (2, 'second-two'))
+>>> d = SortedDict(tuples)
+>>> d.keys()
+[2, 1]
+>>> real_dict = dict(tuples)
+>>> real_dict.values()
+['one', 'second-two']
+>>> d.values()
+['second-two', 'one']
+""" \ No newline at end of file
diff --git a/tests/regressiontests/utils/tests.py b/tests/regressiontests/utils/tests.py
index eb3a722888..abcd7212d8 100644
--- a/tests/regressiontests/utils/tests.py
+++ b/tests/regressiontests/utils/tests.py
@@ -6,7 +6,14 @@ from unittest import TestCase
from django.utils import html, checksums
-from timesince import timesince_tests
+import timesince
+import datastructures
+
+# Extra tests
+__test__ = {
+ 'timesince': timesince,
+ 'datastructures': datastructures,
+}
class TestUtilsHtml(TestCase):
@@ -142,10 +149,6 @@ class TestUtilsChecksums(TestCase):
for value, output in items:
self.check_output(f, value, output)
-__test__ = {
- 'timesince_tests': timesince_tests,
-}
-
if __name__ == "__main__":
import doctest
doctest.testmod()
diff --git a/tests/regressiontests/utils/timesince.py b/tests/regressiontests/utils/timesince.py
index 30200be6e9..4f304ec7f7 100644
--- a/tests/regressiontests/utils/timesince.py
+++ b/tests/regressiontests/utils/timesince.py
@@ -1,4 +1,4 @@
-timesince_tests = """
+"""
>>> from datetime import datetime, timedelta
>>> from django.utils.timesince import timesince
diff --git a/tests/regressiontests/views/tests/static.py b/tests/regressiontests/views/tests/static.py
index 0a67cf543e..c731b249e8 100644
--- a/tests/regressiontests/views/tests/static.py
+++ b/tests/regressiontests/views/tests/static.py
@@ -12,4 +12,12 @@ class StaticTests(TestCase):
for filename in media_files:
response = self.client.get('/views/site_media/%s' % filename)
file = open(path.join(media_dir, filename))
- self.assertEquals(file.read(), response.content) \ No newline at end of file
+ self.assertEquals(file.read(), response.content)
+
+ def test_copes_with_empty_path_component(self):
+ file_name = 'file.txt'
+ response = self.client.get('/views/site_media//%s' % file_name)
+ file = open(path.join(media_dir, file_name))
+ self.assertEquals(file.read(), response.content)
+
+