diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
--- a/django/oldforms/__init__.py
+++ b/django/oldforms/__init__.py
@@ -1,6 +1,7 @@
 from django.core import validators
 from django.core.exceptions import PermissionDenied
 from django.utils.html import escape
+from django.utils.safestring import mark_safe
 from django.conf import settings
 from django.utils.translation import ugettext, ungettext
 from django.utils.encoding import smart_unicode, smart_str
@@ -188,9 +189,9 @@ class FormFieldWrapper(object):
 
     def html_error_list(self):
         if self.errors():
-            return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
+            return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
         else:
-            return ''
+            return mark_safe('')
 
     def get_id(self):
         return self.formfield.get_id()
@@ -225,7 +226,7 @@ class FormFieldCollection(FormFieldWrapp
         return bool(len(self.errors()))
 
     def html_combined_error_list(self):
-        return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
+        return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
 
 class InlineObjectCollection(object):
     "An object that acts like a sparse list of form field collections."
@@ -414,9 +415,9 @@ class TextField(FormField):
         maxlength = u''
         if self.maxlength:
             maxlength = u'maxlength="%s" ' % self.maxlength
-        return u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
+        return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
             (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
-            self.field_name, self.length, escape(data), maxlength)
+            self.field_name, self.length, escape(data), maxlength))
 
     def html2python(data):
         return data
@@ -438,9 +439,9 @@ class LargeTextField(TextField):
     def render(self, data):
         if data is None:
             data = ''
-        return u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
+        return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
             (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
-            self.field_name, self.rows, self.cols, escape(data))
+            self.field_name, self.rows, self.cols, escape(data)))
 
 class HiddenField(FormField):
     def __init__(self, field_name, is_required=False, validator_list=None):
@@ -449,8 +450,8 @@ class HiddenField(FormField):
         self.validator_list = validator_list[:]
 
     def render(self, data):
-        return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
-            (self.get_id(), self.field_name, escape(data))
+        return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
+            (self.get_id(), self.field_name, escape(data)))
 
 class CheckboxField(FormField):
     def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
@@ -464,9 +465,9 @@ class CheckboxField(FormField):
         checked_html = ''
         if data or (data is '' and self.checked_by_default):
             checked_html = ' checked="checked"'
-        return u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
+        return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
             (self.get_id(), self.__class__.__name__,
-            self.field_name, checked_html)
+            self.field_name, checked_html))
 
     def html2python(data):
         "Convert value from browser ('on' or '') to a Python boolean"
@@ -498,7 +499,7 @@ class SelectField(FormField):
                 selected_html = u' selected="selected"'
             output.append(u'    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
         output.append(u'  </select>')
-        return u'\n'.join(output)
+        return mark_safe(u'\n'.join(output))
 
     def isValidChoice(self, data, form):
         str_data = smart_unicode(data)
@@ -552,7 +553,7 @@ class RadioSelectField(FormField):
                 output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
                 output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
                 output.append(u'</ul>')
-                return u''.join(output)
+                return mark_safe(u''.join(output))
             def __iter__(self):
                 for d in self.datalist:
                     yield d
@@ -567,11 +568,11 @@ class RadioSelectField(FormField):
             datalist.append({
                 'value': value,
                 'name': display_name,
-                'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
-                    (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html),
-                'label': u'<label for="%s">%s</label>' % \
+                'field': mark_safe(u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
+                    (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)),
+                'label': mark_safe(u'<label for="%s">%s</label>' % \
                     (self.get_id() + u'_' + unicode(i), display_name),
-            })
+            )})
         return RadioFieldRenderer(datalist, self.ul_class)
 
     def isValidChoice(self, data, form):
@@ -610,7 +611,7 @@ class SelectMultipleField(SelectField):
                 selected_html = u' selected="selected"'
             output.append(u'    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
         output.append(u'  </select>')
-        return u'\n'.join(output)
+        return mark_safe(u'\n'.join(output))
 
     def isValidChoice(self, field_data, all_data):
         # data is something like ['1', '2', '3']
@@ -663,7 +664,7 @@ class CheckboxSelectMultipleField(Select
                 (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
                 self.get_id() + escape(value), choice))
         output.append(u'</ul>')
-        return u'\n'.join(output)
+        return mark_safe(u'\n'.join(output))
 
 ####################
 # FILE UPLOADS     #
@@ -684,8 +685,8 @@ class FileUploadField(FormField):
             raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
 
     def render(self, data):
-        return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
-            (self.get_id(), self.__class__.__name__, self.field_name)
+        return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
+            (self.get_id(), self.__class__.__name__, self.field_name))
 
     def html2python(data):
         if data is None:
diff --git a/django/template/__init__.py b/django/template/__init__.py
index 5b8cdc3b1f1950efa082b098cd351b283bd22834..3f3c611b2ab1909f2357351349c6f89757dfab0a 100644
--- a/django/template/__init__.py
+++ b/django/template/__init__.py
@@ -62,6 +62,8 @@ from django.utils.functional import curr
 from django.utils.text import smart_split
 from django.utils.encoding import smart_unicode, smart_str, force_unicode
 from django.utils.translation import ugettext as _
+from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
+from django.utils.html import escape
 
 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
 
@@ -603,7 +605,17 @@ class FilterExpression(object):
                     arg_vals.append(arg)
                 else:
                     arg_vals.append(resolve_variable(arg, context))
-            obj = func(obj, *arg_vals)
+            if getattr(func, 'needs_autoescape', False):
+                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
+            else:
+                new_obj = func(obj, *arg_vals)
+            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
+                obj = mark_safe(new_obj)
+            elif isinstance(obj, EscapeData):
+                obj = mark_for_escaping(new_obj)
+            else:
+                obj = new_obj
+                
         return obj
 
     def args_check(name, func, provided):
@@ -792,16 +804,24 @@ class VariableNode(Node):
         return "<Variable Node: %s>" % self.filter_expression
 
     def render(self, context):
-        return self.filter_expression.resolve(context)
+        output = self.filter_expression.resolve(context)
+        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
+            return escape(output)
+        else:
+            return output
 
 class DebugVariableNode(VariableNode):
     def render(self, context):
         try:
-            return self.filter_expression.resolve(context)
+            output = self.filter_expression.resolve(context)
         except TemplateSyntaxError, e:
             if not hasattr(e, 'source'):
                 e.source = self.source
             raise
+        if context.autoescape and not isinstance(output, SafeData):
+            return escape(output)
+        else:
+            return output
 
 def generic_tag_compiler(params, defaults, name, node_class, parser, token):
     "Returns a template.Node subclass."
diff --git a/django/template/context.py b/django/template/context.py
index 59650b05fe5c1ab862fa557cf7a4642fc24c9d0e..0f229ce2ec51166719e2b3547f69cc06659d62e8 100644
--- a/django/template/context.py
+++ b/django/template/context.py
@@ -9,6 +9,9 @@ class ContextPopException(Exception):
 
 class Context(object):
     "A stack container for variable context"
+
+    autoescape = False
+
     def __init__(self, dict_=None):
         dict_ = dict_ or {}
         self.dicts = [dict_]
@@ -98,3 +101,4 @@ class RequestContext(Context):
             processors = tuple(processors)
         for processor in get_standard_processors() + processors:
             self.update(processor(request))
+
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
index 27a0b07df038f36db04dcb251495da37cf775cee..1b5c08812ae8cdae088db28409d5801f192d52ec 100644
--- a/django/template/defaultfilters.py
+++ b/django/template/defaultfilters.py
@@ -4,6 +4,7 @@ from django.template import resolve_vari
 from django.conf import settings
 from django.utils.translation import ugettext, ungettext
 from django.utils.encoding import force_unicode, smart_str, iri_to_uri
+from django.utils.safestring import mark_safe, SafeData
 import re
 import random as random_module
 
@@ -39,17 +40,20 @@ def addslashes(value):
     "Adds slashes - useful for passing strings to JavaScript, for example."
     return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
 addslashes = stringfilter(addslashes)
+addslashes.is_safe = True
 
 def capfirst(value):
     "Capitalizes the first character of the value"
     return value and value[0].upper() + value[1:]
 capfirst = stringfilter(capfirst)
+capfirst.is_safe = True
 
 def fix_ampersands(value):
     "Replaces ampersands with ``&amp;`` entities"
     from django.utils.html import fix_ampersands
     return fix_ampersands(value)
 fix_ampersands = stringfilter(fix_ampersands)
+fix_ampersands.is_safe = True
 
 def floatformat(text, arg=-1):
     """
@@ -83,28 +87,36 @@ def floatformat(text, arg=-1):
         return u'%d' % int(f)
     else:
         formatstr = u'%%.%df' % abs(d)
-        return formatstr % f
+        return mark_safe(formatstr % f)
+floatformat.is_safe = True
 
 def iriencode(value):
     "Escapes an IRI value for use in a URL"
     return force_unicode(iri_to_uri(value))
 iriencode = stringfilter(iriencode)
 
-def linenumbers(value):
+def linenumbers(value, autoescape = None):
     "Displays text with line numbers"
     from django.utils.html import escape
     lines = value.split(u'\n')
     # Find the maximum width of the line count, for use with zero padding string format command
     width = unicode(len(unicode(len(lines))))
-    for i, line in enumerate(lines):
-        lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
-    return u'\n'.join(lines)
+    if not autoescape or isinstance(value, SafeData):
+        for i, line in enumerate(lines):
+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
+    else:
+        for i, line in enumerate(lines):
+            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
+    return mark_safe(u'\n'.join(lines))
 linenumbers = stringfilter(linenumbers)
+linenumbers.is_safe = True
+linenumbers.needs_autoescape = True
 
 def lower(value):
     "Converts a string into all lowercase"
     return value.lower()
 lower = stringfilter(lower)
+lower.is_safe = True
 
 def make_list(value):
     """
@@ -113,13 +125,15 @@ def make_list(value):
     """
     return list(value)
 make_list = stringfilter(make_list)
+make_list.is_safe = False
 
 def slugify(value):
     "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
     # Don't compile patterns as unicode because \w then would mean any letter. Slugify is effectively an asciiization.
     value = re.sub('[^\w\s-]', '', value).strip().lower()
-    return re.sub('[-\s]+', '-', value)
+    return mark_safe(re.sub('[-\s]+', '-', value))
 slugify = stringfilter(slugify)
+slugify.is_safe = True
 
 def stringformat(value, arg):
     """
@@ -134,11 +148,13 @@ def stringformat(value, arg):
         return (u"%" + unicode(arg)) % value
     except (ValueError, TypeError):
         return u""
+stringformat.is_safe = True
 
 def title(value):
     "Converts a string into titlecase"
     return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
 title = stringfilter(title)
+title.is_safe = False
 
 def truncatewords(value, arg):
     """
@@ -153,6 +169,7 @@ def truncatewords(value, arg):
         return value # Fail silently.
     return truncate_words(value, length)
 truncatewords = stringfilter(truncatewords)
+truncatewords.is_safe = True
 
 def truncatewords_html(value, arg):
     """
@@ -172,18 +189,21 @@ def upper(value):
     "Converts a string into all uppercase"
     return value.upper()
 upper = stringfilter(upper)
+upper.is_safe = False
 
 def urlencode(value):
     "Escapes a value for use in a URL"
     import urllib
     return force_unicode(urllib.quote(value))
 urlencode = stringfilter(urlencode)
+urlencode.is_safe = False
 
 def urlize(value):
     "Converts URLs in plain text into clickable links"
     from django.utils.html import urlize
-    return urlize(value, nofollow=True)
+    return mark_safe(urlize(value, nofollow=True))
 urlize = stringfilter(urlize)
+urlize.is_safe = True
 
 def urlizetrunc(value, limit):
     """
@@ -193,13 +213,15 @@ def urlizetrunc(value, limit):
     Argument: Length to truncate URLs to.
     """
     from django.utils.html import urlize
-    return urlize(value, trim_url_limit=int(limit), nofollow=True)
+    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
 urlizetrunc = stringfilter(urlizetrunc)
+urlize.is_safe = True
 
 def wordcount(value):
     "Returns the number of words"
     return len(value.split())
 wordcount = stringfilter(wordcount)
+wordcount.is_safe = False
 
 def wordwrap(value, arg):
     """
@@ -210,6 +232,7 @@ def wordwrap(value, arg):
     from django.utils.text import wrap
     return wrap(value, int(arg))
 wordwrap = stringfilter(wordwrap)
+wordwrap.is_safe = True
 
 def ljust(value, arg):
     """
@@ -219,6 +242,7 @@ def ljust(value, arg):
     """
     return value.ljust(int(arg))
 ljust = stringfilter(ljust)
+ljust.is_safe = True
 
 def rjust(value, arg):
     """
@@ -228,37 +252,67 @@ def rjust(value, arg):
     """
     return value.rjust(int(arg))
 rjust = stringfilter(rjust)
+rjust.is_safe = True
 
 def center(value, arg):
     "Centers the value in a field of a given width"
     return value.center(int(arg))
 center = stringfilter(center)
+center.is_safe = True
 
 def cut(value, arg):
     "Removes all values of arg from the given string"
     return value.replace(arg, u'')
 cut = stringfilter(cut)
+cut.is_safe = False
 
 ###################
 # HTML STRINGS    #
 ###################
 
 def escape(value):
-    "Escapes a string's HTML"
+    "Marks the value as a string that should not be auto-escaped."
+    from django.utils.safestring import mark_for_escaping
+    return mark_for_escaping(value)
+escape = stringfilter(escape)
+escape.is_safe = True
+
+def force_escape(value):
+    """Escapes a string's HTML. This returns a new string containing the escaped
+    characters (as opposed to "escape", which marks the content for later
+    possible escaping)."""
     from django.utils.html import escape
-    return escape(value)
+    return mark_safe(escape(value))
 escape = stringfilter(escape)
+force_escape.is_safe = True
 
-def linebreaks(value):
+def linebreaks(value, autoescape = None):
     "Converts newlines into <p> and <br />s"
     from django.utils.html import linebreaks
-    return linebreaks(value)
+    autoescape = autoescape and not isinstance(value, SafeData)
+    return mark_safe(linebreaks(value, autoescape))
 linebreaks = stringfilter(linebreaks)
+linebreaks.is_safe = True
+linebreaks.needs_autoescape = True
 
-def linebreaksbr(value):
+def linebreaksbr(value, autoescape = None):
     "Converts newlines into <br />s"
-    return value.replace('\n', '<br />')
+    if autoescape and not isinstance(value, SafeData):
+        from django.utils.html import escape
+        data = escape(value)
+    else:
+        data = value
+    return mark_safe(data.replace('\n', '<br />'))
 linebreaksbr = stringfilter(linebreaksbr)
+linebreaksbr.is_safe = True
+linebreaksbr.needs_autoescape = True
+
+def safe(value):
+    "Marks the value as a string that should not be auto-escaped."
+    from django.utils.safestring import mark_safe
+    return mark_safe(value)
+safe = stringfilter(safe)
+safe.is_safe = True
 
 def removetags(value, tags):
     "Removes a space separated list of [X]HTML tags from the output"
@@ -270,12 +324,14 @@ def removetags(value, tags):
     value = endtag_re.sub(u'', value)
     return value
 removetags = stringfilter(removetags)
+removetags.is_safe = True
 
 def striptags(value):
     "Strips all [X]HTML tags"
     from django.utils.html import strip_tags
     return strip_tags(value)
 striptags = stringfilter(striptags)
+striptags.is_safe = True
 
 ###################
 # LISTS           #
@@ -289,6 +345,7 @@ def dictsort(value, arg):
     decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
     decorated.sort()
     return [item[1] for item in decorated]
+dictsort.is_safe = False
 
 def dictsortreversed(value, arg):
     """
@@ -299,6 +356,7 @@ def dictsortreversed(value, arg):
     decorated.sort()
     decorated.reverse()
     return [item[1] for item in decorated]
+dictsortreversed.is_safe = False
 
 def first(value):
     "Returns the first item in a list"
@@ -306,25 +364,35 @@ def first(value):
         return value[0]
     except IndexError:
         return u''
+first.is_safe = True
 
 def join(value, arg):
     "Joins a list with a string, like Python's ``str.join(list)``"
     try:
-        return arg.join(map(force_unicode, value))
+        data = arg.join(map(force_unicode, value))
     except AttributeError: # fail silently but nicely
         return value
+    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
+    if safe_args:
+        return mark_safe(data)
+    else:
+        return data
+join.is_safe = True
 
 def length(value):
     "Returns the length of the value - useful for lists"
     return len(value)
+length.is_safe = False
 
 def length_is(value, arg):
     "Returns a boolean of whether the value's length is the argument"
     return len(value) == int(arg)
+length.is_safe = False
 
 def random(value):
     "Returns a random item from the list"
     return random_module.choice(value)
+length.is_safe = True
 
 def slice_(value, arg):
     """
@@ -345,8 +413,9 @@ def slice_(value, arg):
 
     except (ValueError, TypeError):
         return value # Fail silently.
+slice_.is_safe = True
 
-def unordered_list(value):
+def unordered_list(value, autoescape = None):
     """
     Recursively takes a self-nested list and returns an HTML unordered list --
     WITHOUT opening and closing <ul> tags.
@@ -367,14 +436,22 @@ def unordered_list(value):
         </ul>
         </li>
     """
+    if autoescape:
+        from django.utils.html import conditional_escape
+        escaper = conditional_escape
+    else:
+        escaper = lambda x: x
+
     def _helper(value, tabs):
         indent = u'\t' * tabs
         if value[1]:
-            return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
+            return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(force_unicode(value[0])), indent,
                 u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
         else:
-            return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
-    return _helper(value, 1)
+            return u'%s<li>%s</li>' % (indent, escaper(force_unicode(value[0])))
+    return mark_safe(_helper(value, 1))
+unordered_list.is_safe = True
+unordered_list.needs_autoescape = True
 
 ###################
 # INTEGERS        #
@@ -383,6 +460,7 @@ ###################
 def add(value, arg):
     "Adds the arg to the value"
     return int(value) + int(arg)
+add.is_safe = False
 
 def get_digit(value, arg):
     """
@@ -402,6 +480,7 @@ def get_digit(value, arg):
         return int(str(value)[-arg])
     except IndexError:
         return 0
+get_digit.is_safe = False
 
 ###################
 # DATES           #
@@ -415,6 +494,7 @@ def date(value, arg=None):
     if arg is None:
         arg = settings.DATE_FORMAT
     return format(value, arg)
+date.is_safe = False
 
 def time(value, arg=None):
     "Formats a time according to the given format"
@@ -424,6 +504,7 @@ def time(value, arg=None):
     if arg is None:
         arg = settings.TIME_FORMAT
     return time_format(value, arg)
+time.is_safe = False
 
 def timesince(value, arg=None):
     'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
@@ -433,6 +514,7 @@ def timesince(value, arg=None):
     if arg:
         return timesince(arg, value)
     return timesince(value)
+timesince.is_safe = False
 
 def timeuntil(value, arg=None):
     'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
@@ -443,6 +525,7 @@ def timeuntil(value, arg=None):
     if arg:
         return timesince(arg, value)
     return timesince(datetime.now(), value)
+timeuntil.is_safe = False
 
 ###################
 # LOGIC           #
@@ -451,16 +534,19 @@ ###################
 def default(value, arg):
     "If value is unavailable, use given default"
     return value or arg
+default.is_safe = False
 
 def default_if_none(value, arg):
     "If value is None, use given default"
     if value is None:
         return arg
     return value
+default_if_none.is_safe = False
 
 def divisibleby(value, arg):
     "Returns true if the value is devisible by the argument"
     return int(value) % int(arg) == 0
+divisibleby.is_safe = False
 
 def yesno(value, arg=None):
     """
@@ -491,6 +577,7 @@ def yesno(value, arg=None):
     if value:
         return yes
     return no
+yesno.is_safe = False
 
 ###################
 # MISC            #
@@ -513,6 +600,7 @@ def filesizeformat(bytes):
     if bytes < 1024 * 1024 * 1024:
         return ugettext("%.1f MB") % (bytes / (1024 * 1024))
     return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
+filesizeformat.is_safe = True
 
 def pluralize(value, arg=u's'):
     """
@@ -540,11 +628,13 @@ def pluralize(value, arg=u's'):
         except TypeError: # len() of unsized object
             pass
     return singular_suffix
+pluralize.is_safe = False
 
 def phone2numeric(value):
     "Takes a phone number and converts it in to its numerical equivalent"
     from django.utils.text import phone2numeric
     return phone2numeric(value)
+phone2numeric.is_safe = True
 
 def pprint(value):
     "A wrapper around pprint.pprint -- for debugging, really"
@@ -553,6 +643,7 @@ def pprint(value):
         return pformat(value)
     except Exception, e:
         return u"Error in formatting:%s" % force_unicode(e)
+pprint.is_safe = True
 
 # Syntax: register.filter(name of filter, callback)
 register.filter(add)
@@ -571,6 +662,7 @@ register.filter(filesizeformat)
 register.filter(first)
 register.filter(fix_ampersands)
 register.filter(floatformat)
+register.filter(force_escape)
 register.filter(get_digit)
 register.filter(iriencode)
 register.filter(join)
@@ -588,6 +680,7 @@ register.filter(pprint)
 register.filter(removetags)
 register.filter(random)
 register.filter(rjust)
+register.filter(safe)
 register.filter('slice', slice_)
 register.filter(slugify)
 register.filter(stringformat)
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
index f2453dff4ac7b556dbe8b5b73dc1b9d42dd82f04..83ee973213ebd85e3db054e56a403d7ee1c8c667 100644
--- a/django/template/defaulttags.py
+++ b/django/template/defaulttags.py
@@ -5,10 +5,26 @@ from django.template import TemplateSynt
 from django.template import get_library, Library, InvalidTemplateLibrary
 from django.conf import settings
 from django.utils.encoding import smart_str
+from django.utils.safestring import mark_safe
 import sys
 
 register = Library()
 
+class AutoEscapeControlNode(Node):
+    """Implements the actions of both the autoescape and noautescape tags."""
+    def __init__(self, setting, nodelist):
+        self.setting, self.nodelist = setting, nodelist
+
+    def render(self, context):
+        old_setting = context.autoescape
+        context.autoescape = self.setting
+        output = self.nodelist.render(context)
+        context.autoescape = old_setting
+        if self.setting:
+            return mark_safe(output)
+        else:
+            return output
+
 class CommentNode(Node):
     def render(self, context):
         return ''
@@ -238,7 +254,9 @@ class RegroupNode(Node):
             return ''
         output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
         for obj in obj_list:
-            grouper = self.expression.resolve(Context({'var': obj}), True)
+            ctxt = Context({'var': obj})
+            ctxt.autoescape = context.autoescape
+            grouper = self.expression.resolve(ctxt, True)
             # TODO: Is this a sensible way to determine equality?
             if output and repr(output[-1]['grouper']) == repr(grouper):
                 output[-1]['list'].append(obj)
@@ -376,6 +394,16 @@ class WithNode(Node):
         return output
 
 #@register.tag
+def autoescape(parser, token):
+    """
+    Force autoescape behaviour for this block.
+    """
+    nodelist = parser.parse(('endautoescape',))
+    parser.delete_first_token()
+    return AutoEscapeControlNode(True, nodelist)
+autoescape = register.tag(autoescape)
+
+#@register.tag
 def comment(parser, token):
     """
     Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
@@ -478,12 +506,15 @@ def do_filter(parser, token):
 
     Sample usage::
 
-        {% filter escape|lower %}
+        {% filter force_escape|lower %}
             This text will be HTML-escaped, and will appear in lowercase.
         {% endfilter %}
     """
     _, rest = token.contents.split(None, 1)
     filter_expr = parser.compile_filter("var|%s" % (rest))
+    for func, unused in filter_expr.filters:
+        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
+            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
     nodelist = parser.parse(('endfilter',))
     parser.delete_first_token()
     return FilterNode(filter_expr, nodelist)
@@ -725,6 +756,16 @@ def ifchanged(parser, token):
 ifchanged = register.tag(ifchanged)
 
 #@register.tag
+def noautoescape(parser, token):
+    """
+    Force autoescape behaviour to be disabled for this block.
+    """
+    nodelist = parser.parse(('endnoautoescape',))
+    parser.delete_first_token()
+    return AutoEscapeControlNode(False, nodelist)
+autoescape = register.tag(noautoescape)
+
+#@register.tag
 def ssi(parser, token):
     """
     Output the contents of a given file into the page.
diff --git a/django/utils/encoding.py b/django/utils/encoding.py
index 8601dc44f3b60aacef2bbf5b209d60f32808e4df..d88d69931caabd8555fea15a784a751f0010943b 100644
--- a/django/utils/encoding.py
+++ b/django/utils/encoding.py
@@ -33,7 +33,7 @@ def force_unicode(s, encoding='utf-8', e
         else:
             s = unicode(str(s), encoding, errors)
     elif not isinstance(s, unicode):
-        s = unicode(s, encoding, errors)
+        s = s.decode(encoding, errors)
     return s
 
 def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
diff --git a/django/utils/html.py b/django/utils/html.py
index 1dde298c297a8d4bbd24d8c53334a587dd789c54..2fb8298e458364f8b3f2614df6966d328f2e4c7c 100644
--- a/django/utils/html.py
+++ b/django/utils/html.py
@@ -3,6 +3,8 @@
 import re
 import string
 import urllib
+from django.utils.encoding import smart_unicode
+from django.utils.safestring import SafeData
 from django.utils.encoding import force_unicode, smart_str
 from django.utils.functional import allow_lazy
 
@@ -30,11 +32,21 @@ def escape(html):
     return force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
 escape = allow_lazy(escape, unicode)
 
-def linebreaks(value):
+def conditional_escape(html):
+    "Similar to escape(), except that it does not operate on pre-escaped strings"
+    if isinstance(html, SafeData):
+        return html
+    else:
+        return escape(html)
+
+def linebreaks(value, autoescape = False):
     "Converts newlines into <p> and <br />s"
     value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
     paras = re.split('\n{2,}', value)
-    paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
+    if autoescape:
+        paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
+    else:
+        paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
     return u'\n\n'.join(paras)
 linebreaks = allow_lazy(linebreaks, unicode)
 
diff --git a/django/utils/safestring.py b/django/utils/safestring.py
new file mode 100644
index 0000000000000000000000000000000000000000..18c931b56a20645660b26e1bf3e05ebb57b8b11d
--- /dev/null
+++ b/django/utils/safestring.py
@@ -0,0 +1,126 @@
+"""
+Functions for working with "safe strings": strings that can be displayed safely
+without further escaping in HTML. Here, a "safe string" means that the producer
+of the string has already turned characters that should not be interpreted by
+the HTML engine (e.g. '<') into the appropriate entities.
+"""
+from django.utils.functional import curry
+from django.utils.encoding import force_unicode
+
+class EscapeData(object):
+    pass
+
+class EscapeString(str, EscapeData):
+    """
+    A string that should be HTML-escaped when output.
+    """
+    pass
+
+class EscapeUnicode(unicode, EscapeData):
+    """
+    A unicode object that should be HTML-escaped when output.
+    """
+    pass
+
+class SafeData(object):
+    pass
+
+class SafeString(str, SafeData):
+    """
+    A string subclass that has been specifically marked as "safe" for HTML
+    output purposes.
+    """
+    def __add__(self, rhs):
+        """
+        Concatenating a safe string with another safe string or safe unicode
+        object is safe. Otherwise, the result is no longer safe.
+        """
+        if isinstance(rhs, SafeUnicode):
+            return SafeUnicode(self + rhs)
+        elif isinstance(rhs, SafeString):
+            return SafeString(self + rhs)
+        else:
+            return super(SafeString, self).__add__(rhs)
+
+    def __str__(self):
+        return self
+
+    def _proxy_method(self, *args, **kwargs):
+        """
+        Wrap a call to a normal unicode method up so that we return safe
+        results. The method that is being wrapped is passed in the 'method'
+        argument.
+        """
+        method = kwargs.pop('method')
+        data = method(self, *args, **kwargs)
+        if isinstance(data, str):
+            return SafeString(data)
+        else:
+            return SafeUnicode(data)
+
+    encode = curry(_proxy_method, method = str.encode)
+    decode = curry(_proxy_method, method = str.decode)
+
+class SafeUnicode(unicode, SafeData):
+    """
+    A unicode subclass that has been specifically marked as "safe" for HTML
+    output purposes.
+    """
+    def __add__(self, rhs):
+        """
+        Concatenating a safe unicode object with another safe string or safe
+        unicode object is safe. Otherwise, the result is no longer safe.
+        """
+        if isinstance(rhs, SafeData):
+            return SafeUnicode(self + rhs)
+        else:
+            return super(SafeUnicode, self).__add__(rhs)
+
+    def _proxy_method(self, *args, **kwargs):
+        """
+        Wrap a call to a normal unicode method up so that we return safe
+        results. The method that is being wrapped is passed in the 'method'
+        argument.
+        """
+        method = kwargs.pop('method')
+        data = method(self, *args, **kwargs)
+        if isinstance(data, str):
+            return SafeString(data)
+        else:
+            return SafeUnicode(data)
+
+    encode = curry(_proxy_method, method = unicode.encode)
+    decode = curry(_proxy_method, method = unicode.decode)
+
+
+def mark_safe(s):
+    """
+    Explicitly mark a string as safe for (HTML) output purposes. The returned
+    object can be used everywhere a string or unicode object is appropriate.
+
+    Can safely be called multiple times on a single string.
+    """
+    if isinstance(s, SafeData):
+        return s
+    if isinstance(s, str):
+        return SafeString(s)
+    if isinstance(s, unicode):
+        return SafeUnicode(s)
+    return SafeString(str(s))
+
+def mark_for_escaping(s):
+    """
+    Explicitly mark a string as requiring HTML escaping upon output. Has no
+    effect on SafeData subclasses.
+
+    Can be safely called multiple times on a single string (the effect is only
+    applied once).
+    """
+    if isinstance(s, SafeData) or isinstance(s, EscapeData):
+        return s
+    if isinstance(s, str):
+        return EscapeString(s)
+    if isinstance(s, unicode):
+        return EscapeUnicode(s)
+    return EscapeString(str(s))
+
diff --git a/docs/templates.txt b/docs/templates.txt
index 8fe4c82d43460be2e347eaa878c0cbbbaaad6b49..36888bd60f9cc37646e0f38bf2af1a25a0918576 100644
--- a/docs/templates.txt
+++ b/docs/templates.txt
@@ -268,6 +268,83 @@ it also defines the content that fills t
 two similarly-named ``{% block %}`` tags in a template, that template's parent
 wouldn't know which one of the blocks' content to use.
 
+Automatic HTML escaping
+=======================
+
+A very real problem when creating HTML (and other) output using templates and
+variable substitution is the possibility of accidently inserting some variable
+value that affects the resulting HTML. For example, a template fragment like
+
+::
+
+    Hello, {{ name }}.
+
+seems like a harmless way to display the user's name. However, if you are
+displaying data that the user entered directly and they entered their name as
+
+::
+
+    <script>alert('hello')</script>
+
+this would always display a Javascript alert box whenever the page was loaded.
+Similarly, if you were displaying some data generated by another process and
+it contained a '<' symbol, you couldn't just dump this straight into your
+HTML, because it would be treated as the start of an element.  The effects of
+these sorts of problems can vary from merely annoying to allowing exploits via
+`Cross Site Scripting`_ (XSS) attacks.
+
+.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
+
+In order to provide some protection against these problems, Django provides an
+auto-escaping template tag. Inside this tag, any data that comes from template
+variables is examined to see if it contains one of the five HTML characters
+(<, >, ', " and &) that often need escaping and those characters are converted
+to their respective HTML entities.
+
+Because some variables will contain data that is *intended* to be rendered
+as HTML, template tag and filter writers can mark their output strings as
+requiring no further escaping. For example, the ``unordered_list`` filter is
+designed to return raw HTML and we want the template processor to simply
+display the results as returned, without applying any escaping. That is taken
+care of by the filter. The template author need do nothing special in that
+case.
+
+By default, auto-escaping is not in effect. To enable it inside your template,
+wrap the affected content in the ``autoescape`` tag, like so::
+
+    {% autoescape %}
+        Hello {{ name }}
+    {% endautoescape %}
+
+Since the auto-escaping tag passes its effect onto templates that extend the
+current one as well as templates included via the ``include`` tag (just like
+all block tags), if you wrap your main HTML content in an ``autoescape`` tag,
+you will have automatic escaping applied to all of your content.
+
+At times, you might want to disable auto-escaping when it would otherwise be
+in effect. You can do this with the ``noautoescape`` tag. For example::
+
+    {% autoescape %}
+        Hello {{ name }}
+
+        {% noautoescape %}
+            This will not be auto-escaped: {{ data }}.
+
+            Nor this: {{ other_data }}
+        {% endnoautoescape %}
+    {% endautoescape %}
+
+For individual variables, the ``safe`` filter can also be used.
+
+Generally, you will not need to worry about auto-escaping very much. Enable it
+in your base template once you are entering the main HTML region and then
+write your templates normally. The view developers and custom filter authors
+need to think about when their data should not be escaped and mark it
+appropriately.  They are in a better position to know when that should happen
+than the template author, so it is their responsibility. By default, when
+auto-escaping is enabled, all output is escaped unless the template processor
+is explicitly told otherwise.
+
 Using the built-in reference
 ============================
 
@@ -343,6 +420,16 @@ available, and what they do.
 Built-in tag reference
 ----------------------
 
+autoescape
+~~~~~~~~~~
+
+All variables that are output inside this tag have HTML escaping applied to
+them, as if they each had the ``escape`` filter attached to them.
+
+The only exceptions are variables that are already marked as 'safe' from
+escaping, either by the code that populated the variable, or because it has
+the ``safe`` filter applied.
+
 block
 ~~~~~
 
@@ -410,7 +497,7 @@ just like in variable syntax.
 
 Sample usage::
 
-    {% filter escape|lower %}
+    {% filter force_escape|lower %}
         This text will be HTML-escaped, and will appear in all lowercase.
     {% endfilter %}
 
@@ -625,6 +712,11 @@ Load a custom template tag set.
 
 See `Custom tag and filter libraries`_ for more information.
 
+noautoescape
+~~~~~~~~~~~~
+
+Disable the effects of the ``autoescape`` tag (if it is in effect).
+
 now
 ~~~
 
@@ -962,6 +1054,11 @@ Escapes a string's HTML. Specifically, i
     * ``'"'`` (double quote) to ``'&quot;'``
     * ``"'"`` (single quote) to ``'&#39;'``
 
+The escaping is only applied when the string is output, so it does not matter
+where in a chained sequence of filters you put ``escape``: it will always be
+applied as though it were the last filter. If you want escaping to be applied
+immediately, use the ``force_escape`` filter.
+
 filesizeformat
 ~~~~~~~~~~~~~~
 
@@ -1004,6 +1101,17 @@ For example:
 Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
 an argument of ``-1``.
 
+force_escape
+~~~~~~~~~~~~
+
+**New in Django development version**
+
+Applies HTML escaping to a string (see the ``escape`` filter for details).
+This filter is applied immediately and returns a new, escaped string. This is
+useful in the typically rare cases where you need multiple escaping or want to
+apply other filters to the escaped results. Normally, you want to use the
+``escape`` filter.
+
 get_digit
 ~~~~~~~~~
 
@@ -1125,6 +1233,14 @@ Right-aligns the value in a field of a g
 
 **Argument:** field size
 
+safe
+~~~~
+
+Marks a string as not requiring further HTML escaping prior to output. This is
+only useful inside an ``autoescape`` block, when the output would otherwise be
+automatically escaped. Outside of an ``autoescape`` block, this filter has no
+effect.
+
 slice
 ~~~~~
 
diff --git a/docs/templates_python.txt b/docs/templates_python.txt
index 08a287f572d40cb0a628e144c2824a8137f4d6f9..f3181e6c688e1e5a0b7d4be0ac46ad95b9aa8df4 100644
--- a/docs/templates_python.txt
+++ b/docs/templates_python.txt
@@ -669,6 +669,95 @@ an object to it's string value before be
     def lower(value):
     	return value.lower()
 
+Filters and auto-escaping
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When you are writing a custom filter, you need to give some thought to how
+this filter will work when rendered in an auto-escaping environment (inside
+an ``autoescape`` template tag block). First, you should realise that there
+are three types of strings that can be passed around inside the template code:
+
+ * raw strings are the native Python ``str`` (or ``unicode``) types. On
+   output, they are escaped if they are inside an ``autoescape`` block.
+ * "safe" strings are strings that are safe from further escaping at output
+   time. Any necessary escaping has already been done. They are commonly used
+   for output that contains raw HTML that is intended to be intrepreted on the
+   client side.
+
+   Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
+   although they share a common base class in ``SafeData``, so you can test
+   for them using code like::
+
+    if isinstance(value, SafeData):
+        # Do something with the "safe" string.
+
+ * strings which are marked as "need escaping" are *always* escaped on
+   output, regardless of whether they are in an ``autoescape`` block or not.
+   These strings are only escaped once, however, even if used inside an
+   ``autoescaep`` block.  This type of string is internally represented by the
+   types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to
+   worry about these; they exist only for the implementation of the ``escape``
+   filter.
+
+Inside your filter, you will need to think about three areas in order to be
+auto-escaping compliant:
+
+ 1. If your filter returns a string that is ready for direct output (it should
+ be considered a "safe" string), you should call
+ ``django.utils.safestring.mark_safe()`` on the result prior to returning.
+ This will turn the result into the appropriate ``SafeData`` type.
+
+ 2. If your filter is given a "safe" string, is it guaranteed to return a
+ "safe" string? If so, set the ``is_safe`` attribute on the function to be
+ ``True``. For example, a filter that replaced all numbers with the number
+ spelt out in words is going to be safe-string-preserving, since it cannot
+ introduce any of the five dangerous characters: <, >, ", ' or &. So we can
+ write::
+
+    @register.filter
+    def convert_to_words(value):
+        # ... implementation here ...
+        return result
+
+    convert_to_words.is_safe = True
+
+ Note that this filter does not return a universally safe result (it does not
+ return ``mark_safe(result)``) because if it is handed a raw string such as
+ '<a>', this will need further escaping in an auto-escape environment. The
+ ``is_safe`` attribute only talks about the safeness of the result when a safe
+ string is passed in to the filter.
+
+ 3. Will your filter behave differently depending upon whether auto-escaping
+ is currently in effect or not? For example, the ``ordered_list`` filter that
+ ships with Django needs to know whether to escape its content or not. It will
+ always return a safe string, since it returns raw HTML, so we cannot apply
+ escaping to the result -- it needs to be done in-situ.
+
+ For these cases, the filter function needs to be told what the current
+ auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
+ filter to ``True`` and have your function take an extra argument called
+ ``autoescape`` with a default value of ``None``. When the filter is called,
+ the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
+ effect. For example, the ``unordered_list`` filter is written as::
+
+    def unordered_list(value, autoescape = None):
+        # ... lots of code here ...
+
+        return mark_safe(...)
+
+    unordered_list.is_safe = True
+    unordered_list.needs_autoescape = True
+
+By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
+``False``. You do not need to specify them if ``False`` is an acceptable
+value.
+
+As a matter of convention, we leave ``is_safe`` as ``False`` for filters that
+do not accept string inputs (they might take a number as an input, for
+example) or for those that return a non-string (e.g. the ``length`` filter).
+However, not following this convention will not cause any harm or make your
+results any more vulnerable to cross-site scripting problems.
+
 Writing custom template tags
 ----------------------------
 
@@ -787,6 +876,33 @@ Ultimately, this decoupling of compilati
 efficient template system, because a template can render multiple context
 without having to be parsed multiple times.
 
+Auto-escaping considerations
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The output from template tags is not automatically run through the
+auto-escaping filters if used inside an ``autoescape`` tag. However, there are
+still a couple of things you should keep in mind when writing a template tag:
+
+If the ``render()`` function of your template stores the result in a context
+variable (rather than returning the result in a string), it should take care
+to call ``mark_safe()`` if appropriate. When the variable is ultimately
+rendered, it will be affected by the auto-escape setting in effect at the
+time, so content that should be safe from further escaping needs to be marked
+as such.
+
+Also, if your template tag creates a new context for performing some
+sub-rendering, you should be careful to set the auto-escape variable to the
+current context's value. For example::
+
+    def render(self, context):
+        # ...
+        new_context = Context({'var': obj})
+        new_context.autoescape = context.autoescape
+        # ... Do something with new_context ...
+
+This is not a very common situation, but it is sometimes useful (see
+``django.templates.defaulttags.FilterNode.render()`` for an example).
+
 Registering the tag
 ~~~~~~~~~~~~~~~~~~~
 
diff --git a/tests/regressiontests/autoescape/__init__.py b/tests/regressiontests/autoescape/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/regressiontests/autoescape/models.py b/tests/regressiontests/autoescape/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/regressiontests/autoescape/tests.py b/tests/regressiontests/autoescape/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..d8fff6800c81bed694debbd5c97efb03d8e7109c
--- /dev/null
+++ b/tests/regressiontests/autoescape/tests.py
@@ -0,0 +1,270 @@
+from django.conf import settings
+
+if __name__ == '__main__':
+    # When running this file in isolation, we need to set up the configuration
+    # before importing 'template'.
+    settings.configure()
+
+from regressiontests.templates.tests import Templates
+from django import template
+from django.template import loader
+from django.utils.translation import activate, deactivate, install
+from django.utils.tzinfo import LocalTimezone
+from django.utils.safestring import mark_safe
+from datetime import datetime, timedelta
+import unittest
+
+class AutoescapeTemplates(Templates):
+    def render(self, test_template, vals):
+        ctxt = template.Context(vals[1])
+        # Hack for testing: force autoescaping to be in effect.
+        ctxt.autoescape = True
+        return test_template.render(ctxt)
+
+    def get_template_tests(self):
+        # We want to check all the normal template tests work when autoescaping is
+        # engaged. We just update results that would change with autoescaping and add
+        # in new tests.
+
+        TEMPLATE_TESTS = super(AutoescapeTemplates, self).get_template_tests()
+
+        # SYNTAX --
+        # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
+        TEMPLATE_TESTS.update({
+        
+            ### BASIC SYNTAX ##########################################################
+        
+            # Escaped string as argument (this test replaces the non-escaped version)
+            'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote&quot; hah'),
+        
+            # We are simulating being in a block that has inherited auto-escaping, so
+            # it is applied by default in all these tests.
+            'autoescape-basic01': ("{{ first }}", {"first": "<b>first</b>"}, "&lt;b&gt;first&lt;/b&gt;"),
+        
+            # Strings (ASCII or unicode) already marked as "safe" are not auto-escaped
+            'autoescape-basic02': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"),
+            'autoescape-basic03': ("{{ first }}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"),
+        
+        
+            ### (NO)AUTOESCAPE TAG ###################################################
+            'autoescape-tag01': ("{% noautoescape %}hello{% endnoautoescape %}", {}, "hello"),
+            'autoescape-tag02': ("{% noautoescape %}{{ first }}{% endnoautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
+            'autoescape-tag03': ("{% autoescape %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt;"),
+        
+            # Noautoescape and autoescape nest in a predictable way.
+            'autoescape-tag04': ("{% noautoescape %}{{ first }} {% autoescape %}{{ first }}{% endautoescape %}{% endnoautoescape %}", {"first": "<a>"}, "<a> &lt;a&gt;"),
+        
+            ### FILTER TAG ############################################################
+        
+            # 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),
+            'autoescape-filtertag02': ("{% filter escape %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
+        
+            ### FILTER TESTS ##########################################################
+        
+            'ae-filter-addslash01': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"&lt;a&gt;\&#39; <a>\'"),
+        
+            'ae-filter-capfirst01': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred&gt;")}, "Fred&gt; Fred&gt;"),
+        
+            # Note that applying fix_ampsersands in autoescape mode leads to double
+            # escaping.
+            'ae-filter-fix_ampersands01': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, "a&amp;amp;b a&amp;b"),
+        
+            'ae-filter-floatformat01': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"),
+        
+            # The contents of "linenumbers" is escaped according to the current
+            # autoescape setting.
+            'ae-filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n&lt;two&gt;\nthree")}, "1. one\n2. &lt;two&gt;\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+            'ae-filter-linenumbers02': ("{% noautoescape %}{{ a|linenumbers }} {{ b|linenumbers }}{% endnoautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n&lt;two&gt;\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. &lt;two&gt;\n3. three"),
+        
+            'ae-filter-lower01': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, "apple &amp; banana apple &amp; banana"),
+        
+            # The make_list filter can destroy # existing encoding, so the results are
+            # escaped.
+            'ae-filter-make_list01': ("{{ a|make_list }}", {"a": mark_safe("&")}, "[u&#39;&amp;&#39;]"),
+            'ae-filter-make_list02': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, "[u'&']"),
+        
+            # Running slugify on a pre-escaped string leads to odd behaviour, but the
+            # result is still safe.
+            'ae-filter-slugify01': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a &amp; b")}, "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.
+            'ae-filter-stringformat01': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ".  a&lt;b. .  a<b."),
+        
+            # XXX No test for "title" filter; needs an actual object.
+            
+            'ae-filter-truncatewords01': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, "alpha &amp; ... alpha &amp; ..."),
+        
+            # The "upper" filter messes up entities (which are case-sensitive), so it's
+            # not safe for non-escaping purposes.
+            'ae-filter-upper01': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "A &amp; B A &amp;AMP; B"),
+        
+            'ae-filter-urlize01': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<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>'),
+            'ae-filter-urlize02': ('{{ a|urlize }}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
+        
+            'ae-filter-urlizetrunc01': ('{{ a|urlizetrunc:"5" }} {{ b|urlizetrunc:"5" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
+        
+            'ae-filter-wordcount01': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
+        
+            'ae-filter-wordwrap01': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &amp;\nb a &\nb"),
+        
+            'ae-filter-ljust01': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&amp;b  . .a&b  ."),
+        
+            'ae-filter-rjust01': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".  a&amp;b. .  a&b."),
+        
+            'ae-filter-center01': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&amp;b . . a&b ."),
+        
+            # Because "cut" might remove a leading ampersand, so the results are not
+            # safe.
+            'ae-filter-cut01': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "&amp;y &amp;amp;y"),
+            'ae-filter-cut02': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "xy xamp;y"),
+        
+            # The "escape" filter works the same whether autoescape is on or off, but
+            # it has no effect on strings already marked as safe.
+            'ae-filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;y x&y"),
+            'ae-filter-escape02': ('{% noautoescape %}{{ a|escape }} {{ b|escape }}{% endnoautoescape %}', {"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.
+            'ae-filter-escape03': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&amp;y"),
+        
+            # Force_escape is applied immediately. It can be used to provide
+            # double-escaping, for example.
+            'ae-filter-force-escape01': ('{{ a|force_escape }}', {"a": "x&y"}, "x&amp;y"),
+            'ae-filter-force-escape02': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&amp;amp;y"),
+        
+            # Because the result of force_escape is "safe", an additional escape filter
+            # has no effect.
+            'ae-filter-force-escape03': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&amp;y"),
+        
+            # The contents in "linebreaks" and "linebreaksbr" are escaped according to
+            # the current autoescape setting.
+            'ae-filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&amp;<br />y</p> <p>x&<br />y</p>"),
+            'ae-filter-linebreaks02': ('{% noautoescape %}{{ a|linebreaks }} {{ b|linebreaks }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"),
+        
+            'ae-filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&amp;<br />y x&<br />y"),
+            'ae-filter-linebreaksbr02': ('{% noautoescape %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"),
+        
+            'ae-filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>"),
+            'ae-filter-safe02': ("{% noautoescape %}{{ a }} -- {{ a|safe }}{% endnoautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"),
+        
+            'ae-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>")}, "x &lt;p&gt;y&lt;/p&gt; x <p>y</p>"),
+        
+            'ae-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"),
+        
+            'ae-filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&amp;b a&b"),
+        
+            'ae-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"),
+        
+            'ae-filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&amp;b &b"),
+        
+            'ae-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>"),
+            'ae-filter-unordered_list02': ('{{ 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>"),
+            'ae-filter-unordered_list03': ('{% noautoescape %}{{ a|unordered_list }}{% endnoautoescape %}', {"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.
+            'ae-filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x&lt;"),
+            'ae-filter-default02': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"),
+        
+            'ae-filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x&lt;"),
+        
+            'ae-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>"),
+        
+            # Chaining a bunch of safeness-preserving filters should not alter the safe
+            # status either way.
+            'ae-chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A &lt; b . A < b "),
+        
+            # Using a filter that forces a string back to unsafe:
+            'ae-chaining02': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A &lt; .A &lt; "),
+        
+            # Using a filter that forces safeness does not lead to double-escaping
+            'ae-chaining03': ('{{ a|escape|capfirst }}', {"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).
+            'ae-chaining04': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a &amp;lt; "),
+            'ae-chaining05': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a &lt; "),
+            'ae-chaining06': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "),
+            'ae-chaining07': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a &lt; b"),
+
+            # Escaped string as argument
+            'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote&quot; hah'),
+
+        })
+
+        return TEMPLATE_TESTS
+
+
+    
+
+#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
+
+#def run_tests(verbosity=0, standalone=False):
+    ## Register our custom template loader.
+    #old_template_loaders = loader.template_source_loaders
+    #loader.template_source_loaders = [test_template_loader]
+
+    #failed_tests = []
+    #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 = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
+    
+    #for name, vals in tests:
+        #install()
+        #if 'LANGUAGE_CODE' in vals[1]:
+            #activate(vals[1]['LANGUAGE_CODE'])
+        #else:
+            #activate('en-us')
+        #try:
+            #ctxt = template.Context(vals[1])
+            ## Hack for testing: force autoescaping to be in effect.
+            #ctxt.autoescape = True
+            #output = loader.get_template(name).render(ctxt)
+        #except Exception, e:
+            #if e.__class__ == vals[2]:
+                #if verbosity:
+                    #print "Template test: %s -- Passed" % name
+            #else:
+                #if verbosity:
+                    #traceback.print_exc()
+                    #print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
+                #failed_tests.append(name)
+            #continue
+        #if 'LANGUAGE_CODE' in vals[1]:
+            #deactivate()
+        #if output == vals[2]:
+            #if verbosity:
+                #print "Template test: %s -- Passed" % name
+        #else:
+            #if verbosity:
+                #print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
+            #failed_tests.append(name)
+    #loader.template_source_loaders = old_template_loaders
+    #deactivate()
+    #settings.TEMPLATE_DEBUG = old_td
+    #settings.TEMPLATE_STRING_IF_INVALID = old_invalid
+
+    #if failed_tests and not standalone:
+        #msg = "Template tests %s failed." % failed_tests
+        #if not verbosity:
+            #msg += "  Re-run tests with -v1 to see actual failures"
+        #raise Exception, msg
+
+if __name__ == "__main__":
+    unittest.main()
diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
index d2335dd87b727dd68bf17dc970596eb104d75938..47e8e24ddd76e41d5e408c7b29cebd17c9d22c96 100644
--- a/tests/regressiontests/defaultfilters/tests.py
+++ b/tests/regressiontests/defaultfilters/tests.py
@@ -168,10 +168,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/templates/tests.py b/tests/regressiontests/templates/tests.py
index 141e5124099c198f99dd2d6de75ffd841349b0cb..1f9552e1cd03c8c119997e3408ea3033cdfe93e6 100644
--- a/tests/regressiontests/templates/tests.py
+++ b/tests/regressiontests/templates/tests.py
@@ -76,14 +76,87 @@ class UTF8Class:
 
 class Templates(unittest.TestCase):
     def test_templates(self):
+        TEMPLATE_TESTS = self.get_template_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:
+            install()
+
+            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):
         # NOW and NOW_tz are used by timesince tag tests.
         NOW = datetime.now()
         NOW_tz = datetime.now(LocalTimezone(datetime.now()))
 
         # SYNTAX --
         # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
-        TEMPLATE_TESTS = {
-
+        return {
             ### BASIC SYNTAX ##########################################################
 
             # Plain text should go through the template parser untouched
@@ -725,72 +798,5 @@ class Templates(unittest.TestCase):
             'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
         }
 
-        # 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:
-            install()
-
-            if isinstance(vals[2], tuple):
-                normal_string_result = vals[2][0]
-                invalid_string_result = vals[2][1]
-                if '%s' in invalid_string_result:
-                    expected_invalid_str = 'INVALID %s'
-                    invalid_string_result = invalid_string_result % vals[2][2]
-                    template.invalid_var_format_string = True
-            else:
-                normal_string_result = vals[2]
-                invalid_string_result = vals[2]
-
-            if 'LANGUAGE_CODE' in vals[1]:
-                activate(vals[1]['LANGUAGE_CODE'])
-            else:
-                activate('en-us')
-
-            for invalid_str, result in [('', normal_string_result),
-                                        (expected_invalid_str, invalid_string_result)]:
-                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
-                try:
-                    output = loader.get_template(name).render(template.Context(vals[1]))
-                except Exception, e:
-                    if e.__class__ != result:
-                        failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
-                    continue
-                if output != result:
-                    failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
-
-            if 'LANGUAGE_CODE' in vals[1]:
-                deactivate()
-
-            if template.invalid_var_format_string:
-                expected_invalid_str = 'INVALID'
-                template.invalid_var_format_string = False
-
-        loader.template_source_loaders = old_template_loaders
-        deactivate()
-        settings.TEMPLATE_DEBUG = old_td
-        settings.TEMPLATE_STRING_IF_INVALID = old_invalid
-
-        self.assertEqual(failures, [], '\n'.join(failures))
-
 if __name__ == "__main__":
     unittest.main()
-- 
1.4.GIT-dirty

