Ticket #2359: autoescape.diff

File autoescape.diff, 44.4 KB (added by Malcolm Tredinnick, 18 years ago)

Core changes and test suite

  • django/contrib/markup/templatetags/markup.py

    diff -urN --exclude-from=django.exclue django_src_current/django/contrib/markup/templatetags/markup.py django_autoescape/django/contrib/markup/templatetags/markup.py
    old new  
    1616
    1717from django import template
    1818from django.conf import settings
     19from django.utils.safestring import mark_safe
    1920
    2021register = template.Library()
    2122
     
    2526    except ImportError:
    2627        if settings.DEBUG:
    2728            raise template.TemplateSyntaxError, "Error in {% textile %} filter: The Python textile library isn't installed."
    28         return value
     29        return mark_safe(value)
    2930    else:
    30         return textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET)
     31        return mark_safe(textile.textile(value, encoding=settings.DEFAULT_CHARSET, output=settings.DEFAULT_CHARSET))
     32textile.is_safe = True
    3133
    3234def markdown(value):
    3335    try:
     
    3537    except ImportError:
    3638        if settings.DEBUG:
    3739            raise template.TemplateSyntaxError, "Error in {% markdown %} filter: The Python markdown library isn't installed."
    38         return value
     40        return mark_safe(value)
    3941    else:
    40         return markdown.markdown(value)
     42        return mark_safe(markdown.markdown(value))
     43markdown.is_safe = True
    4144
    4245def restructuredtext(value):
    4346    try:
     
    4548    except ImportError:
    4649        if settings.DEBUG:
    4750            raise template.TemplateSyntaxError, "Error in {% restructuredtext %} filter: The Python docutils library isn't installed."
    48         return value
     51        return mark_safe(value)
    4952    else:
    5053        docutils_settings = getattr(settings, "RESTRUCTUREDTEXT_FILTER_SETTINGS", {})
    5154        parts = publish_parts(source=value, writer_name="html4css1", settings_overrides=docutils_settings)
    52         return parts["fragment"]
     55        return mark_safe(parts["fragment"])
     56restructuredtext.is_safe = True
    5357
    5458register.filter(textile)
    5559register.filter(markdown)
  • django/template/context.py

    diff -urN --exclude-from=django.exclue django_src_current/django/template/context.py django_autoescape/django/template/context.py
    old new  
    99
    1010class Context(object):
    1111    "A stack container for variable context"
     12
     13    autoescape = False
     14
    1215    def __init__(self, dict_=None):
    1316        dict_ = dict_ or {}
    1417        self.dicts = [dict_]
     
    9598            processors = tuple(processors)
    9699        for processor in get_standard_processors() + processors:
    97100            self.update(processor(request))
     101
     102class SafeContext(Context):
     103    "A Context derivative that autoescapes by default."
     104    autoescape = True
     105
     106class SafeRequestContext(RequestContext):
     107    autoescape = True
  • django/template/defaultfilters.py

    diff -urN --exclude-from=django.exclue django_src_current/django/template/defaultfilters.py django_autoescape/django/template/defaultfilters.py
    old new  
    33from django.template import resolve_variable, Library
    44from django.conf import settings
    55from django.utils.translation import gettext
     6from django.utils.safestring import mark_safe, SafeData
    67import re
    78import random as random_module
    89
     
    1617def addslashes(value):
    1718    "Adds slashes - useful for passing strings to JavaScript, for example."
    1819    return value.replace('"', '\\"').replace("'", "\\'")
     20addslashes.is_safe = True
    1921
    2022def capfirst(value):
    2123    "Capitalizes the first character of the value"
    2224    value = str(value)
    2325    return value and value[0].upper() + value[1:]
     26capfirst.is_safe = True
    2427
    2528def fix_ampersands(value):
    2629    "Replaces ampersands with ``&`` entities"
    2730    from django.utils.html import fix_ampersands
    2831    return fix_ampersands(value)
     32fix_ampersands.is_safe = True
    2933
    3034def floatformat(text):
    3135    """
     
    4044    if m:
    4145        return '%.1f' % f
    4246    else:
    43         return '%d' % int(f)
     47        return mark_safe('%d' % int(f))
     48floatformat.is_safe = True
    4449
    45 def linenumbers(value):
     50def linenumbers(value, autoescape = None):
    4651    "Displays text with line numbers"
    4752    from django.utils.html import escape
    4853    lines = value.split('\n')
    4954    # Find the maximum width of the line count, for use with zero padding string format command
    5055    width = str(len(str(len(lines))))
    51     for i, line in enumerate(lines):
    52         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    53     return '\n'.join(lines)
     56    if not autoescape or isinstance(value, SafeData):
     57        for i, line in enumerate(lines):
     58            lines[i] = ("%0" + width  + "d. %s") % (i + 1, line)
     59    else:
     60        for i, line in enumerate(lines):
     61            lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
     62    return mark_safe('\n'.join(lines))
     63linenumbers.is_safe = True
     64linenumbers.needs_autoescape = True
    5465
    5566def lower(value):
    5667    "Converts a string into all lowercase"
    5768    return value.lower()
     69lower.is_safe = True
    5870
    5971def make_list(value):
    6072    """
     
    6274    digits. For a string, it's a list of characters.
    6375    """
    6476    return list(str(value))
     77make_list.is_safe = False
    6578
    6679def slugify(value):
    6780    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    6881    value = re.sub('[^\w\s-]', '', value).strip().lower()
    69     return re.sub('[-\s]+', '-', value)
     82    return mark_safe(re.sub('[-\s]+', '-', value))
     83slugify.is_safe = True
    7084
    7185def stringformat(value, arg):
    7286    """
     
    8195        return ("%" + arg) % value
    8296    except (ValueError, TypeError):
    8397        return ""
     98stringformat.is_safe = True
    8499
    85100def title(value):
    86101    "Converts a string into titlecase"
    87102    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
     103title.is_safe = False
    88104
    89105def truncatewords(value, arg):
    90106    """
     
    100116    if not isinstance(value, basestring):
    101117        value = str(value)
    102118    return truncate_words(value, length)
     119truncatewords.is_safe = True
    103120
    104121def upper(value):
    105122    "Converts a string into all uppercase"
    106123    return value.upper()
     124upper.is_safe = False
    107125
    108126def urlencode(value):
    109127    "Escapes a value for use in a URL"
    110128    import urllib
    111129    return urllib.quote(value)
     130urlencode.is_safe = False
    112131
    113132def urlize(value):
    114133    "Converts URLs in plain text into clickable links"
    115134    from django.utils.html import urlize
    116     return urlize(value, nofollow=True)
     135    return mark_safe(urlize(value, nofollow=True))
     136urlize.is_safe = True
    117137
    118138def urlizetrunc(value, limit):
    119139    """
     
    123143    Argument: Length to truncate URLs to.
    124144    """
    125145    from django.utils.html import urlize
    126     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     146    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
     147urlize.is_safe = True
    127148
    128149def wordcount(value):
    129150    "Returns the number of words"
    130151    return len(value.split())
     152wordcount.is_safe = False
    131153
    132154def wordwrap(value, arg):
    133155    """
     
    137159    """
    138160    from django.utils.text import wrap
    139161    return wrap(str(value), int(arg))
     162wordwrap.is_safe = True
    140163
    141164def ljust(value, arg):
    142165    """
     
    145168    Argument: field size
    146169    """
    147170    return str(value).ljust(int(arg))
     171ljust.is_safe = True
    148172
    149173def rjust(value, arg):
    150174    """
     
    153177    Argument: field size
    154178    """
    155179    return str(value).rjust(int(arg))
     180rjust.is_safe = True
    156181
    157182def center(value, arg):
    158183    "Centers the value in a field of a given width"
    159184    return str(value).center(int(arg))
     185center.is_safe = True
    160186
    161187def cut(value, arg):
    162188    "Removes all values of arg from the given string"
    163189    return value.replace(arg, '')
     190cut.is_safe = False
    164191
    165192###################
    166193# HTML STRINGS    #
     
    168195
    169196def escape(value):
    170197    "Escapes a string's HTML"
    171     from django.utils.html import escape
    172     return escape(value)
     198    if not isinstance(value, SafeData):
     199        from django.utils.html import escape
     200        return mark_safe(escape(value))
     201    else:
     202        return value
     203escape.is_safe = True
    173204
    174 def linebreaks(value):
     205def linebreaks(value, autoescape = None):
    175206    "Converts newlines into <p> and <br />s"
    176207    from django.utils.html import linebreaks
    177     return linebreaks(value)
     208    autoescape = autoescape and not isinstance(value, SafeData)
     209    return mark_safe(linebreaks(value, autoescape))
     210linebreaks.is_safe = True
     211linebreaks.needs_autoescape = True
    178212
    179 def linebreaksbr(value):
     213def linebreaksbr(value, autoescape = None):
    180214    "Converts newlines into <br />s"
    181     return value.replace('\n', '<br />')
     215    if autoescape and not isinstance(value, SafeData):
     216        from django.utils.html import escape
     217        data = escape(value)
     218    else:
     219        data = value
     220    return mark_safe(data.replace('\n', '<br />'))
     221linebreaksbr.is_safe = True
     222linebreaksbr.needs_autoescape = True
     223
     224def noescape(value):
     225    "Marks the value as a string that should not be auto-escaped."
     226    from django.utils.safestring import mark_safe
     227    return mark_safe(value)
     228noescape.is_safe = True
    182229
    183230def removetags(value, tags):
    184231    "Removes a space separated list of [X]HTML tags from the output"
     
    189236    value = starttag_re.sub('', value)
    190237    value = endtag_re.sub('', value)
    191238    return value
     239removetags.is_safe = True
    192240
    193241def striptags(value):
    194242    "Strips all [X]HTML tags"
     
    196244    if not isinstance(value, basestring):
    197245        value = str(value)
    198246    return strip_tags(value)
     247striptags.is_safe = True
    199248
    200249###################
    201250# LISTS           #
     
    209258    decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    210259    decorated.sort()
    211260    return [item[1] for item in decorated]
     261dictsort.is_safe = False
    212262
    213263def dictsortreversed(value, arg):
    214264    """
     
    219269    decorated.sort()
    220270    decorated.reverse()
    221271    return [item[1] for item in decorated]
     272dictsortreversed.is_safe = False
    222273
    223274def first(value):
    224275    "Returns the first item in a list"
     
    226277        return value[0]
    227278    except IndexError:
    228279        return ''
     280first.is_safe = True
    229281
    230282def join(value, arg):
    231283    "Joins a list with a string, like Python's ``str.join(list)``"
    232284    try:
    233         return arg.join(map(str, value))
     285        data = arg.join(map(str, value))
    234286    except AttributeError: # fail silently but nicely
    235287        return value
     288    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     289    if safe_args:
     290        return mark_safe(data)
     291    else:
     292        return data
     293join.is_safe = True
    236294
    237295def length(value):
    238296    "Returns the length of the value - useful for lists"
    239297    return len(value)
     298length.is_safe = False
    240299
    241300def length_is(value, arg):
    242301    "Returns a boolean of whether the value's length is the argument"
    243302    return len(value) == int(arg)
     303length.is_safe = False
    244304
    245305def random(value):
    246306    "Returns a random item from the list"
    247307    return random_module.choice(value)
     308length.is_safe = True
    248309
    249310def slice_(value, arg):
    250311    """
     
    265326
    266327    except (ValueError, TypeError):
    267328        return value # Fail silently.
     329slice_.is_safe = True
    268330
    269 def unordered_list(value):
     331def unordered_list(value, autoescape = None):
    270332    """
    271333    Recursively takes a self-nested list and returns an HTML unordered list --
    272334    WITHOUT opening and closing <ul> tags.
     
    287349        </ul>
    288350        </li>
    289351    """
     352    if autoescape:
     353        from django.utils.html import conditional_escape
     354        escaper = conditional_escape
     355    else:
     356        escaper = lambda x: x
     357
    290358    def _helper(value, tabs):
    291359        indent = '\t' * tabs
    292360        if value[1]:
    293             return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
     361            return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent,
    294362                '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    295363        else:
    296             return '%s<li>%s</li>' % (indent, value[0])
    297     return _helper(value, 1)
     364            return '%s<li>%s</li>' % (indent, escaper(value[0]))
     365    return mark_safe(_helper(value, 1))
     366unordered_list.is_safe = True
     367unordered_list.needs_autoescape = True
    298368
    299369###################
    300370# INTEGERS        #
     
    303373def add(value, arg):
    304374    "Adds the arg to the value"
    305375    return int(value) + int(arg)
     376add.is_safe = False
    306377
    307378def get_digit(value, arg):
    308379    """
     
    322393        return int(str(value)[-arg])
    323394    except IndexError:
    324395        return 0
     396get_digit.is_safe = False
    325397
    326398###################
    327399# DATES           #
     
    335407    if arg is None:
    336408        arg = settings.DATE_FORMAT
    337409    return format(value, arg)
     410date.is_safe = False
    338411
    339412def time(value, arg=None):
    340413    "Formats a time according to the given format"
     
    344417    if arg is None:
    345418        arg = settings.TIME_FORMAT
    346419    return time_format(value, arg)
     420time.is_safe = False
    347421
    348422def timesince(value, arg=None):
    349423    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
     
    353427    if arg:
    354428        return timesince(arg, value)
    355429    return timesince(value)
     430timesince.is_safe = False
    356431
    357432def timeuntil(value, arg=None):
    358433    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
     
    363438    if arg:
    364439        return timesince(arg, value)
    365440    return timesince(datetime.now(), value)
     441timeuntil.is_safe = False
    366442
    367443###################
    368444# LOGIC           #
     
    371447def default(value, arg):
    372448    "If value is unavailable, use given default"
    373449    return value or arg
     450default.is_safe = False
    374451
    375452def default_if_none(value, arg):
    376453    "If value is None, use given default"
    377454    if value is None:
    378455        return arg
    379456    return value
     457default_if_none.is_safe = False
    380458
    381459def divisibleby(value, arg):
    382460    "Returns true if the value is devisible by the argument"
    383461    return int(value) % int(arg) == 0
     462divisibleby.is_safe = False
    384463
    385464def yesno(value, arg=None):
    386465    """
     
    411490    if value:
    412491        return yes
    413492    return no
     493yesno.is_safe = False
    414494
    415495###################
    416496# MISC            #
     
    429509    if bytes < 1024 * 1024 * 1024:
    430510        return "%.1f MB" % (bytes / (1024 * 1024))
    431511    return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
     512filesizeformat.is_safe = True
    432513
    433514def pluralize(value, arg='s'):
    434515    """
     
    456537        except TypeError: # len() of unsized object
    457538            pass
    458539    return singular_suffix
     540pluralize.is_safe = False
    459541
    460542def phone2numeric(value):
    461543    "Takes a phone number and converts it in to its numerical equivalent"
    462544    from django.utils.text import phone2numeric
    463545    return phone2numeric(value)
     546phone2numeric.is_safe = True
    464547
    465548def pprint(value):
    466549    "A wrapper around pprint.pprint -- for debugging, really"
     
    469552        return pformat(value)
    470553    except Exception, e:
    471554        return "Error in formatting:%s" % e
     555pprint.is_safe = True
    472556
    473557# Syntax: register.filter(name of filter, callback)
    474558register.filter(add)
     
    497581register.filter(ljust)
    498582register.filter(lower)
    499583register.filter(make_list)
     584register.filter(noescape)
    500585register.filter(phone2numeric)
    501586register.filter(pluralize)
    502587register.filter(pprint)
  • django/template/defaulttags.py

    diff -urN --exclude-from=django.exclue django_src_current/django/template/defaulttags.py django_autoescape/django/template/defaulttags.py
    old new  
    44from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END
    55from django.template import get_library, Library, InvalidTemplateLibrary
    66from django.conf import settings
     7from django.utils.safestring import mark_safe
    78import sys
    89
    910register = Library()
    1011
     12class AutoEscapeNode(Node):
     13    def __init__(self, setting, nodelist):
     14        self.setting, self.nodelist = setting, nodelist
     15
     16    def render(self, context):
     17        old_setting = context.autoescape
     18        context.autoescape = self.setting
     19        output = self.nodelist.render(context)
     20        context.autoescape = old_setting
     21        if self.setting:
     22            return mark_safe(output)
     23        else:
     24            return output
     25
    1126class CommentNode(Node):
    1227    def render(self, context):
    1328        return ''
     
    318333        return str(int(round(ratio)))
    319334
    320335#@register.tag
     336def autoescape(parser, token):
     337    """
     338    Set autoescape behaviour for this block. Possible values are "on" and "off".
     339    """
     340    unused, rest = token.contents.split(None, 1)
     341    if rest not in ('on', 'off'):
     342        raise TemplateSyntaxError("autoescape argument must be 'on' or 'off'")
     343    setting = (rest == 'on')
     344    nodelist = parser.parse(('endautoescape',))
     345    parser.delete_first_token()
     346    return AutoEscapeNode(setting, nodelist)
     347autoescape = register.tag(autoescape)
     348
     349#@register.tag
    321350def comment(parser, token):
    322351    """
    323352    Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
     
    417446    """
    418447    _, rest = token.contents.split(None, 1)
    419448    filter_expr = parser.compile_filter("var|%s" % (rest))
     449    for func, unused in filter_expr.filters:
     450        if func.__name__ == 'noescape':
     451            raise TemplateSyntaxError('"filter noescape" is not permitted.  Use the "autoescape" tag instead.')
    420452    nodelist = parser.parse(('endfilter',))
    421453    parser.delete_first_token()
    422454    return FilterNode(filter_expr, nodelist)
  • django/template/__init__.py

    diff -urN --exclude-from=django.exclue django_src_current/django/template/__init__.py django_autoescape/django/template/__init__.py
    old new  
    5757import re
    5858from inspect import getargspec
    5959from django.conf import settings
    60 from django.template.context import Context, RequestContext, ContextPopException
     60from django.template.context import Context, SafeContext, RequestContext, ContextPopException
    6161from django.utils.functional import curry
    6262from django.utils.text import smart_split
     63from django.utils.safestring import SafeData, mark_safe
     64from django.utils.html import escape
    6365
    64 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
     66__all__ = ('Template', 'Context', 'SafeContext', 'RequestContext', 'compile_string')
    6567
    6668TOKEN_TEXT = 0
    6769TOKEN_VAR = 1
     
    558560                    arg_vals.append(arg)
    559561                else:
    560562                    arg_vals.append(resolve_variable(arg, context))
    561             obj = func(obj, *arg_vals)
     563            if getattr(func, 'needs_autoescape', False):
     564                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     565            else:
     566                new_obj = func(obj, *arg_vals)
     567            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     568                obj = mark_safe(new_obj)
     569            else:
     570                obj = new_obj
     571               
    562572        return obj
    563573
    564574    def args_check(name, func, provided):
     
    744754
    745755    def render(self, context):
    746756        output = self.filter_expression.resolve(context)
    747         return self.encode_output(output)
     757        encoded_output = self.encode_output(output)
     758        if context.autoescape and not isinstance(encoded_output, SafeData):
     759            return escape(encoded_output)
     760        else:
     761            return encoded_output
    748762
    749763class DebugVariableNode(VariableNode):
    750764    def render(self, context):
     
    754768            if not hasattr(e, 'source'):
    755769                e.source = self.source
    756770            raise
    757         return self.encode_output(output)
     771        encoded_output = self.encode_output(output)
     772        if context.autoescape and not isinstance(encoded_output, SafeData):
     773            return escape(encoded_output)
     774        else:
     775            return encoded_output
    758776
    759777def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    760778    "Returns a template.Node subclass."
  • django/utils/html.py

    diff -urN --exclude-from=django.exclue django_src_current/django/utils/html.py django_autoescape/django/utils/html.py
    old new  
    11"HTML utilities suitable for global use."
    22
    33import re, string
     4from django.utils.safestring import SafeData
    45
    56# Configuration for urlize() function
    67LEADING_PUNCTUATION  = ['(', '<', '&lt;']
     
    2728        html = str(html)
    2829    return html.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
    2930
    30 def linebreaks(value):
     31def conditional_escape(html):
     32    "Similar to escape(), except that it does not operate on pre-escaped strings"
     33    if isinstance(html, SafeData):
     34        return html
     35    else:
     36        return escape(html)
     37
     38def linebreaks(value, autoescape = False):
    3139    "Converts newlines into <p> and <br />s"
    3240    value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines
    3341    paras = re.split('\n{2,}', value)
    34     paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
     42    if autoescape:
     43        paras = ['<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
     44    else:
     45        paras = ['<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
    3546    return '\n\n'.join(paras)
    3647
    3748def strip_tags(value):
  • django/utils/safestring.py

    diff -urN --exclude-from=django.exclue django_src_current/django/utils/safestring.py django_autoescape/django/utils/safestring.py
    old new  
     1"""
     2Functions for working with "safe strings": strings that can be display safely
     3without further modification in HTML.
     4"""
     5from django.utils.functional import curry
     6
     7class SafeData(object):
     8    pass
     9
     10class SafeString(str, SafeData):
     11    """
     12    A string subclass that has been specifically marked as "safe" for output
     13    purposes.
     14    """
     15    def __add__(self, rhs):
     16        """
     17        Concatenating a safe string with another safe string or safe unicode
     18        object is safe. Otherwise, the result is no longer safe.
     19        """
     20        if isinstance(rhs, SafeUnicode):
     21            return SafeUnicode(self + rhs)
     22        elif isinstance(rhs, SafeString):
     23            return SafeString(self, rhs)
     24        else:
     25            return super(SafeString, self).__add__(rhs)
     26
     27class SafeUnicode(unicode, SafeData):
     28    """
     29    A unicode subclass that has been specifically marked as "safe" for output
     30    purposes.
     31    """
     32    def __add__(self, rhs):
     33        """
     34        Concatenating a safe unicode object with another safe string or safe
     35        unicode object is safe. Otherwise, the result is no longer safe.
     36        """
     37        if isinstance(rhs, SafeData):
     38            return SafeUnicode(self + rhs)
     39        else:
     40            return super(SafeUnicode, self).__add__(rhs)
     41
     42    def _proxy_method(self, *args, **kwargs):
     43        """
     44        Wrap a call to a normal unicode method up so that we return safe
     45        results. The method that is being wrapped is passed in the 'method'
     46        argument.
     47        """
     48        method = kwargs.pop('method')
     49        data = method(self, *args, **kwargs)
     50        if isinstance(data, str):
     51            return SafeString(data)
     52        else:
     53            return SafeUnicode(data)
     54
     55    encode = curry(_proxy_method, method = unicode.encode)
     56    decode = curry(_proxy_method, method = unicode.decode)
     57
     58
     59def mark_safe(s):
     60    """
     61    Explicitly mark a string as safe for output purposes. The returned object
     62    can be used everywhere a string or unicode object is appropriate.
     63
     64    Can safely be called multiple times on a single string.
     65    """
     66    if isinstance(s, SafeData):
     67        return s
     68    if isinstance(s, str):
     69        return SafeString(s)
     70    if isinstance(s, unicode):
     71        return SafeUnicode(s)
     72    raise TypeError, "Input must be str or unicode (got %s)" % type(s)
     73
  • django/views/debug.py

    diff -urN --exclude-from=django.exclue django_src_current/django/views/debug.py django_autoescape/django/views/debug.py
    old new  
    11from django.conf import settings
    2 from django.template import Template, Context, TemplateDoesNotExist
     2from django.template import Template, Context, SafeContext, TemplateDoesNotExist
    33from django.utils.html import escape
    44from django.http import HttpResponseServerError, HttpResponseNotFound
    55import os, re
     
    118118            'lineno': '?',
    119119        }]
    120120    t = Template(TECHNICAL_500_TEMPLATE)
    121     c = Context({
     121    c = SafeContext({
    122122        'exception_type': exc_type.__name__,
    123123        'exception_value': exc_value,
    124124        'frames': frames,
     
    157157def empty_urlconf(request):
    158158    "Create an empty URLconf 404 error response."
    159159    t = Template(EMPTY_URLCONF_TEMPLATE)
    160     c = Context({
     160    c = SafeContext({
    161161        'project_name': settings.SETTINGS_MODULE.split('.')[0]
    162162    })
    163163    return HttpResponseNotFound(t.render(c), mimetype='text/html')
     
    295295
    296296<div id="summary">
    297297  <h1>{{ exception_type }} at {{ request.path }}</h1>
    298   <h2>{{ exception_value|escape }}</h2>
     298  <h2>{{ exception_value }}</h2>
    299299  <table class="meta">
    300300    <tr>
    301301      <th>Request Method:</th>
     
    340340<div id="template">
    341341   <h2>Template error</h2>
    342342   <p>In template <code>{{ template_info.name }}</code>, error at line <strong>{{ template_info.line }}</strong></p>
    343    <h3>{{ template_info.message|escape }}</h3>
     343   <h3>{{ template_info.message }}</h3>
    344344   <table class="source{% if template_info.top %} cut-top{% endif %}{% ifnotequal template_info.bottom template_info.total %} cut-bottom{% endifnotequal %}">
    345345   {% for source_line in template_info.source_lines %}
    346346   {% ifequal source_line.0 template_info.line %}
     
    367367          {% if frame.context_line %}
    368368            <div class="context" id="c{{ frame.id }}">
    369369              {% if frame.pre_context %}
    370                 <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
     370                <ol start="{{ frame.pre_context_lineno }}" class="pre-context" id="pre{{ frame.id }}">{% for line in frame.pre_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
    371371              {% endif %}
    372               <ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line|escape }} <span>...</span></li></ol>
     372              <ol start="{{ frame.lineno }}" class="context-line"><li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ frame.context_line }} <span>...</span></li></ol>
    373373              {% if frame.post_context %}
    374                 <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line|escape }}</li>{% endfor %}</ol>
     374                <ol start='{{ frame.lineno|add:"1" }}' class="post-context" id="post{{ frame.id }}">{% for line in frame.post_context %}<li onclick="toggle('pre{{ frame.id }}', 'post{{ frame.id }}')">{{ line }}</li>{% endfor %}</ol>
    375375              {% endif %}
    376376            </div>
    377377          {% endif %}
     
    391391                {% for var in frame.vars|dictsort:"0" %}
    392392                  <tr>
    393393                    <td>{{ var.0 }}</td>
    394                     <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     394                    <td class="code"><div>{{ var.1|pprint }}</div></td>
    395395                  </tr>
    396396                {% endfor %}
    397397              </tbody>
     
    411411{% for frame in frames %}
    412412  File "{{ frame.filename }}" in {{ frame.function }}<br/>
    413413  {% if frame.context_line %}
    414     &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line|escape }}<br/>
     414    &nbsp;&nbsp;{{ frame.lineno }}. {{ frame.context_line }}<br/>
    415415  {% endif %}
    416416{% endfor %}<br/>
    417417&nbsp;&nbsp;{{ exception_type }} at {{ request.path }}<br/>
    418 &nbsp;&nbsp;{{ exception_value|escape }}</code>
     418&nbsp;&nbsp;{{ exception_value }}</code>
    419419          </td>
    420420        </tr>
    421421      </tbody>
     
    439439        {% for var in request.GET.items %}
    440440          <tr>
    441441            <td>{{ var.0 }}</td>
    442             <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     442            <td class="code"><div>{{ var.1|pprint }}</div></td>
    443443          </tr>
    444444        {% endfor %}
    445445      </tbody>
     
    461461        {% for var in request.POST.items %}
    462462          <tr>
    463463            <td>{{ var.0 }}</td>
    464             <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     464            <td class="code"><div>{{ var.1|pprint }}</div></td>
    465465          </tr>
    466466        {% endfor %}
    467467      </tbody>
     
    483483        {% for var in request.COOKIES.items %}
    484484          <tr>
    485485            <td>{{ var.0 }}</td>
    486             <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     486            <td class="code"><div>{{ var.1|pprint }}</div></td>
    487487          </tr>
    488488        {% endfor %}
    489489      </tbody>
     
    504504      {% for var in request.META.items|dictsort:"0" %}
    505505        <tr>
    506506          <td>{{ var.0 }}</td>
    507           <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     507          <td class="code"><div>{{ var.1|pprint }}</div></td>
    508508        </tr>
    509509      {% endfor %}
    510510    </tbody>
     
    523523      {% for var in settings.items|dictsort:"0" %}
    524524        <tr>
    525525          <td>{{ var.0 }}</td>
    526           <td class="code"><div>{{ var.1|pprint|escape }}</div></td>
     526          <td class="code"><div>{{ var.1|pprint }}</div></td>
    527527        </tr>
    528528      {% endfor %}
    529529    </tbody>
     
    590590      </p>
    591591      <ol>
    592592        {% for pattern in urlpatterns %}
    593           <li>{{ pattern|escape }}</li>
     593          <li>{{ pattern }}</li>
    594594        {% endfor %}
    595595      </ol>
    596596      <p>The current URL, <code>{{ request.path }}</code>, didn't match any of these.</p>
    597597    {% else %}
    598       <p>{{ reason|escape }}</p>
     598      <p>{{ reason }}</p>
    599599    {% endif %}
    600600  </div>
    601601
  • tests/othertests/autoescape.py

    diff -urN --exclude-from=django.exclue django_src_current/tests/othertests/autoescape.py django_autoescape/tests/othertests/autoescape.py
    old new  
     1from django.conf import settings
     2
     3if __name__ == '__main__':
     4    # When running this file in isolation, we need to set up the configuration
     5    # before importing 'template'.
     6    settings.configure()
     7
     8import templates
     9from django import template
     10from django.template import loader
     11from django.utils.translation import activate, deactivate, install
     12from django.utils.tzinfo import LocalTimezone
     13from django.utils.safestring import mark_safe
     14from datetime import datetime, timedelta
     15import traceback
     16
     17# We want to check all the normal template tests against SafeContext by
     18# default. We just update results that would change with autoescaping and add
     19# in new tests.
     20TEMPLATE_TESTS = templates.TEMPLATE_TESTS.copy()
     21
     22# SYNTAX --
     23# 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
     24TEMPLATE_TESTS.update({
     25
     26    ### BASIC SYNTAX ##########################################################
     27
     28    # Escaped string as argument (this replaces the non-escaped version)
     29    'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote&quot; hah'),
     30
     31    # Autoescaping is applied by default
     32    'autoescape-basic01': ("{{ first }}", {"first": "<b>first</b>"}, "&lt;b&gt;first&lt;/b&gt;"),
     33
     34    # Strings (ASCII or unicode) already marked as "safe" are not auto-escaped
     35    'autoescape-basic02': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"),
     36    'autoescape-basic03': ("{{ first }}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"),
     37
     38
     39    ### AUTOESCAPE TAG ########################################################
     40    'autoescape-tag01': ("{% autoescape off %}hello{% endautoescape %}", {}, "hello"),
     41    'autoescape-tag02': ("{% autoescape off %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"),
     42    'autoescape-tag03': ("{% autoescape on %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt;"),
     43
     44    ### FILTER TAG ############################################################
     45
     46    # The "noescape" filter cannot work due to internal implementation details
     47    # (fortunately, it is equivalent to using the autoescape tag in these
     48    # cases).
     49    'autoescape-filtertag01': ("{{ first }}{% filter noescape %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError),
     50
     51    ### FILTER TESTS ##########################################################
     52
     53    'ae-filter-addslash01': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"&lt;a&gt;\&#39; <a>\'"),
     54
     55    'ae-filter-capfirst01': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred&gt;")}, "Fred&gt; Fred&gt;"),
     56
     57    # Note that applying fix_ampsersands in autoescape mode leads to double
     58    # escaping.
     59    '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"),
     60
     61    'ae-filter-floatformat01': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"),
     62
     63    # The contents of "linenumbers" is escaped according to the current
     64    # autoescape setting.
     65    '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"),
     66    'ae-filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"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"),
     67
     68    'ae-filter-lower01': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple &amp; banana")}, "apple &amp; banana apple &amp; banana"),
     69
     70    # The make_list filter can destroy # existing encoding, so the results are
     71    # escaped.
     72    'ae-filter-make_list01': ("{{ a|make_list }}", {"a": mark_safe("&")}, "[&#39;&amp;&#39;]"),
     73    'ae-filter-make_list02': ('{{ a|make_list|stringformat:"s"|noescape }}', {"a": mark_safe("&")}, "['&']"),
     74
     75    # Running slugify on a pre-escaped string leads to odd behaviour, but the
     76    # result is still safe.
     77    'ae-filter-slugify01': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a &amp; b")}, "a-b a-amp-b"),
     78   
     79    # Notice that escaping is applied *after* any filters, so the string
     80    # formatting here only needs to deal with pre-escaped characters.
     81    'ae-filter-stringformat01': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ".  a&lt;b. .  a<b."),
     82
     83    # XXX No test for "title" filter; needs an actual object.
     84   
     85    'ae-filter-truncatewords01': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, "alpha &amp; ... alpha &amp; ..."),
     86
     87    # The "upper" filter messes up entities (which are case-sensitive), so it's
     88    # not safe for non-escaping purposes.
     89    'ae-filter-upper01': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "A &amp; B A &amp;AMP; B"),
     90
     91    '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>'),
     92    'ae-filter-urlize02': ('{{ a|urlize }}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
     93
     94    '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>'),
     95
     96    'ae-filter-wordcount01': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
     97
     98    'ae-filter-wordwrap01': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &amp;\nb a &\nb"),
     99
     100    'ae-filter-ljust01': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&amp;b  . .a&b  ."),
     101
     102    'ae-filter-rjust01': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".  a&amp;b. .  a&b."),
     103
     104    'ae-filter-center01': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&amp;b . . a&b ."),
     105
     106    # Because "cut" might remove a leading ampersand, so the results are not
     107    # safe.
     108    'ae-filter-cut01': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "&amp;y &amp;amp;y"),
     109    'ae-filter-cut02': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "xy xamp;y"),
     110
     111    # The "escape" filter works the same whether autoescape is on or off, but
     112    # it has no effect on strings already marked as safe.
     113    'ae-filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;y x&y"),
     114    'ae-filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;y x&y"),
     115    'ae-filter-escape03': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&amp;y"),
     116
     117    # The contents in "linebreaks" and "linebreaksbr" are escaped according to
     118    # the current autoescape setting.
     119    '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>"),
     120    'ae-filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"),
     121
     122    'ae-filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&amp;<br />y x&<br />y"),
     123    'ae-filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"),
     124
     125    'ae-filter-noescape01': ("{{ a }} -- {{ a|noescape }}", {"a": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>"),
     126    'ae-filter-noescape02': ("{% autoescape off %}{{ a }} -- {{ a|noescape }}{% endautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"),
     127
     128    '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>"),
     129
     130    '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"),
     131
     132    'ae-filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&amp;b a&b"),
     133
     134    '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"),
     135
     136    'ae-filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&amp;b &b"),
     137
     138    '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>"),
     139    '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>"),
     140    'ae-filter-unordered_list03': ('{% 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>"),
     141
     142    # If the input to "default" filter is marked as safe, then so is the
     143    # output. However, if the default arg is used, auto-escaping kicks in (if
     144    # enabled), because we cannot mark the default as safe.
     145    # Note: we have to use {"a": ""} here, otherwise the invalid template
     146    # variable string interferes with the test result.
     147    'ae-filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x&lt;"),
     148    'ae-filter-default02': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"),
     149
     150    'ae-filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x&lt;"),
     151
     152    '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>"),
     153
     154    # Chaining a bunch of safeness-preserving filters should not alter the safe
     155    # status either way.
     156    'ae-chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A &lt; b . A < b "),
     157
     158    # Using a filter that forces a string back to unsafe:
     159    'ae-chaining02': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A &lt; .A &lt; "),
     160
     161    # Using a filter that forces safeness does not lead to double-escaping
     162    'ae-chaining03': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A &lt; b"),
     163
     164    # Force to safe, then back (also showing why using escape too early in a
     165    # chain can lead to unexpected results).
     166    'ae-chaining04': ('{{ a|escape|cut:"b" }}', {"a": "a < b"}, "a &amp;lt; "),
     167    'ae-chaining05': ('{{ a|cut:"b"|escape }}', {"a": "a < b"}, "a &lt; "),
     168    'ae-chaining06': ('{{ a|cut:"b"|noescape }}', {"a": "a < b"}, "a < "),
     169    'ae-chaining07': ('{{ a|noescape|escape }}', {"a": "a < b"}, "a < b"),
     170})
     171
     172def test_template_loader(template_name, template_dirs=None):
     173    "A custom template loader that loads the unit-test templates."
     174    try:
     175        return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     176    except KeyError:
     177        raise template.TemplateDoesNotExist, template_name
     178
     179def run_tests(verbosity=0, standalone=False):
     180    # Register our custom template loader.
     181    old_template_loaders = loader.template_source_loaders
     182    loader.template_source_loaders = [test_template_loader]
     183
     184    failed_tests = []
     185    tests = TEMPLATE_TESTS.items()
     186    tests.sort()
     187
     188    # Turn TEMPLATE_DEBUG off, because tests assume that.
     189    old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     190    # Set TEMPLATE_STRING_IF_INVALID to a known string
     191    old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
     192   
     193    for name, vals in tests:
     194        install()
     195        if 'LANGUAGE_CODE' in vals[1]:
     196            activate(vals[1]['LANGUAGE_CODE'])
     197        else:
     198            activate('en-us')
     199        try:
     200            output = loader.get_template(name).render(template.SafeContext(vals[1]))
     201        except Exception, e:
     202            if e.__class__ == vals[2]:
     203                if verbosity:
     204                    print "Template test: %s -- Passed" % name
     205            else:
     206                if verbosity:
     207                    traceback.print_exc()
     208                    print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
     209                failed_tests.append(name)
     210            continue
     211        if 'LANGUAGE_CODE' in vals[1]:
     212            deactivate()
     213        if output == vals[2]:
     214            if verbosity:
     215                print "Template test: %s -- Passed" % name
     216        else:
     217            if verbosity:
     218                print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
     219            failed_tests.append(name)
     220    loader.template_source_loaders = old_template_loaders
     221    deactivate()
     222    settings.TEMPLATE_DEBUG = old_td
     223    settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     224
     225    if failed_tests and not standalone:
     226        msg = "Template tests %s failed." % failed_tests
     227        if not verbosity:
     228            msg += "  Re-run tests with -v1 to see actual failures"
     229        raise Exception, msg
     230
     231if __name__ == "__main__":
     232    run_tests(1, True)
Back to Top