Ticket #2359: rev5722-01-core-changes.3.diff

File rev5722-01-core-changes.3.diff, 73.0 KB (added by mir@…, 17 years ago)

updated patch for svn release 5722 (4th attempt)

  • django/oldforms/__init__.py

    From nobody Mon Sep 17 00:00:00 2001
    From: Michael Radziej <mir@noris.de>
    Date: Wed Jul 18 11:19:35 2007 +0200
    Subject: [PATCH] autoescape 1
    
    autoescape-1: Conflicts (especially, a missing import of force_unicode in django/utils/html.py
    
    Merged patch autoescape-1.
    (Base: 90c47c66000a56360a69641f6f3e4f9e96d29114)
    (Last: 147ff090a7d3d71a6e4ccf91e737f2ae7d108350)
    
    ---
    
     django/oldforms/__init__.py                   |   43 ++--
     django/template/__init__.py                   |   26 ++
     django/template/context.py                    |    4 
     django/template/defaultfilters.py             |  131 ++++++++++--
     django/template/defaulttags.py                |   41 ++++
     django/utils/encoding.py                      |    2 
     django/utils/html.py                          |   19 +-
     django/utils/safestring.py                    |  126 ++++++++++++
     docs/templates.txt                            |  118 +++++++++++
     docs/templates_python.txt                     |  116 +++++++++++
     tests/regressiontests/autoescape/__init__.py  |    0 
     tests/regressiontests/autoescape/models.py    |    0 
     tests/regressiontests/autoescape/tests.py     |  269 +++++++++++++++++++++++++
     tests/regressiontests/defaultfilters/tests.py |    4 
     tests/regressiontests/templates/tests.py      |  144 +++++++------
     15 files changed, 922 insertions(+), 121 deletions(-)
     create mode 100644 django/utils/safestring.py
     create mode 100644 tests/regressiontests/autoescape/__init__.py
     create mode 100644 tests/regressiontests/autoescape/models.py
     create mode 100644 tests/regressiontests/autoescape/tests.py
    
    base ea19ca060b42b6214085d019681cfba21b7b9b37
    last 35e6d247f711b3224d045c1f8668ed5d3302950c
    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index a0f14cdcf2b202dfe264b2cd5417575f0c97002e..30892c5cfc29dead47c82ff4d35111242aa32f75 100644
    a b  
    11from django.core import validators
    22from django.core.exceptions import PermissionDenied
    33from django.utils.html import escape
     4from django.utils.safestring import mark_safe
    45from django.conf import settings
    56from django.utils.translation import ugettext, ungettext
    67from django.utils.encoding import smart_unicode, force_unicode
    class FormFieldWrapper(object):  
    188189
    189190    def html_error_list(self):
    190191        if self.errors():
    191             return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
     192            return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
    192193        else:
    193             return ''
     194            return mark_safe('')
    194195
    195196    def get_id(self):
    196197        return self.formfield.get_id()
    class FormFieldCollection(FormFieldWrapper):  
    225226        return bool(len(self.errors()))
    226227
    227228    def html_combined_error_list(self):
    228         return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
     229        return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
    229230
    230231class InlineObjectCollection(object):
    231232    "An object that acts like a sparse list of form field collections."
    class TextField(FormField):  
    414415        maxlength = u''
    415416        if self.maxlength:
    416417            maxlength = u'maxlength="%s" ' % self.maxlength
    417         return u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
     418        return mark_safe(u'<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
    418419            (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and u' required' or '',
    419             self.field_name, self.length, escape(data), maxlength)
     420            self.field_name, self.length, escape(data), maxlength))
    420421
    421422    def html2python(data):
    422423        return data
    class LargeTextField(TextField):  
    438439    def render(self, data):
    439440        if data is None:
    440441            data = ''
    441         return u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
     442        return mark_safe(u'<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
    442443            (self.get_id(), self.__class__.__name__, self.is_required and u' required' or u'',
    443             self.field_name, self.rows, self.cols, escape(data))
     444            self.field_name, self.rows, self.cols, escape(data)))
    444445
    445446class HiddenField(FormField):
    446447    def __init__(self, field_name, is_required=False, validator_list=None):
    class HiddenField(FormField):  
    449450        self.validator_list = validator_list[:]
    450451
    451452    def render(self, data):
    452         return u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
    453             (self.get_id(), self.field_name, escape(data))
     453        return mark_safe(u'<input type="hidden" id="%s" name="%s" value="%s" />' % \
     454            (self.get_id(), self.field_name, escape(data)))
    454455
    455456class CheckboxField(FormField):
    456457    def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
    class CheckboxField(FormField):  
    464465        checked_html = ''
    465466        if data or (data is '' and self.checked_by_default):
    466467            checked_html = ' checked="checked"'
    467         return u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
     468        return mark_safe(u'<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
    468469            (self.get_id(), self.__class__.__name__,
    469             self.field_name, checked_html)
     470            self.field_name, checked_html))
    470471
    471472    def html2python(data):
    472473        "Convert value from browser ('on' or '') to a Python boolean"
    class SelectField(FormField):  
    498499                selected_html = u' selected="selected"'
    499500            output.append(u'    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
    500501        output.append(u'  </select>')
    501         return u'\n'.join(output)
     502        return mark_safe(u'\n'.join(output))
    502503
    503504    def isValidChoice(self, data, form):
    504505        str_data = smart_unicode(data)
    class RadioSelectField(FormField):  
    552553                output = [u'<ul%s>' % (self.ul_class and u' class="%s"' % self.ul_class or u'')]
    553554                output.extend([u'<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
    554555                output.append(u'</ul>')
    555                 return u''.join(output)
     556                return mark_safe(u''.join(output))
    556557            def __iter__(self):
    557558                for d in self.datalist:
    558559                    yield d
    class RadioSelectField(FormField):  
    567568            datalist.append({
    568569                'value': value,
    569570                'name': display_name,
    570                 'field': u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
    571                     (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html),
    572                 'label': u'<label for="%s">%s</label>' % \
     571                'field': mark_safe(u'<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
     572                    (self.get_id() + u'_' + unicode(i), self.field_name, value, selected_html)),
     573                'label': mark_safe(u'<label for="%s">%s</label>' % \
    573574                    (self.get_id() + u'_' + unicode(i), display_name),
    574             })
     575            )})
    575576        return RadioFieldRenderer(datalist, self.ul_class)
    576577
    577578    def isValidChoice(self, data, form):
    class SelectMultipleField(SelectField):  
    610611                selected_html = u' selected="selected"'
    611612            output.append(u'    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
    612613        output.append(u'  </select>')
    613         return u'\n'.join(output)
     614        return mark_safe(u'\n'.join(output))
    614615
    615616    def isValidChoice(self, field_data, all_data):
    616617        # data is something like ['1', '2', '3']
    class CheckboxSelectMultipleField(SelectMultipleField):  
    663664                (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
    664665                self.get_id() + escape(value), choice))
    665666        output.append(u'</ul>')
    666         return u'\n'.join(output)
     667        return mark_safe(u'\n'.join(output))
    667668
    668669####################
    669670# FILE UPLOADS     #
    class FileUploadField(FormField):  
    684685            raise validators.CriticalValidationError, ugettext("The submitted file is empty.")
    685686
    686687    def render(self, data):
    687         return u'<input type="file" id="%s" class="v%s" name="%s" />' % \
    688             (self.get_id(), self.__class__.__name__, self.field_name)
     688        return mark_safe(u'<input type="file" id="%s" class="v%s" name="%s" />' % \
     689            (self.get_id(), self.__class__.__name__, self.field_name))
    689690
    690691    def html2python(data):
    691692        if data is None:
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index 6880fd59972819ee5572641fa08ca2cbc0933a29..30a75fc3bcbc956289eb1a77a2de1a42c1a148ff 100644
    a b from django.utils.functional import curry, Promise  
    6262from django.utils.text import smart_split
    6363from django.utils.encoding import smart_unicode, force_unicode
    6464from django.utils.translation import ugettext as _
     65from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     66from django.utils.html import escape
    6567
    6668__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
    6769
    class FilterExpression(object):  
    600602                    arg_vals.append(arg)
    601603                else:
    602604                    arg_vals.append(resolve_variable(arg, context))
    603             obj = func(obj, *arg_vals)
     605            if getattr(func, 'needs_autoescape', False):
     606                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     607            else:
     608                new_obj = func(obj, *arg_vals)
     609            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     610                obj = mark_safe(new_obj)
     611            elif isinstance(obj, EscapeData):
     612                obj = mark_for_escaping(new_obj)
     613            else:
     614                obj = new_obj
     615               
    604616        return obj
    605617
    606618    def args_check(name, func, provided):
    class VariableNode(Node):  
    782794        return "<Variable Node: %s>" % self.filter_expression
    783795
    784796    def render(self, context):
    785         return self.filter_expression.resolve(context)
     797        output = self.filter_expression.resolve(context)
     798        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
     799            return escape(output)
     800        else:
     801            return output
    786802
    787803class DebugVariableNode(VariableNode):
    788804    def render(self, context):
    789805        try:
    790             return self.filter_expression.resolve(context)
     806            output = self.filter_expression.resolve(context)
    791807        except TemplateSyntaxError, e:
    792808            if not hasattr(e, 'source'):
    793809                e.source = self.source
    794810            raise
     811        if context.autoescape and not isinstance(output, SafeData):
     812            return escape(output)
     813        else:
     814            return output
    795815
    796816def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    797817    "Returns a template.Node subclass."
  • django/template/context.py

    diff --git a/django/template/context.py b/django/template/context.py
    index 59650b05fe5c1ab862fa557cf7a4642fc24c9d0e..0f229ce2ec51166719e2b3547f69cc06659d62e8 100644
    a b class ContextPopException(Exception):  
    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_]
    class RequestContext(Context):  
    98101            processors = tuple(processors)
    99102        for processor in get_standard_processors() + processors:
    100103            self.update(processor(request))
     104
  • django/template/defaultfilters.py

    diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
    index 6779b9b5f2b16b2961818a6230ff6b1caf3bb6e4..8e24f67287a883fa3297fc11f2a5fd3f31fba822 100644
    a b from django.template import resolve_variable, Library  
    44from django.conf import settings
    55from django.utils.translation import ugettext, ungettext
    66from django.utils.encoding import force_unicode, smart_str, iri_to_uri
     7from django.utils.safestring import mark_safe, SafeData
    78import re
    89import random as random_module
    910
    def addslashes(value):  
    3940    "Adds slashes - useful for passing strings to JavaScript, for example."
    4041    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
    4142addslashes = stringfilter(addslashes)
     43addslashes.is_safe = True
    4244
    4345def capfirst(value):
    4446    "Capitalizes the first character of the value"
    4547    return value and value[0].upper() + value[1:]
    4648capfirst = stringfilter(capfirst)
     49capfirst.is_safe = True
    4750
    4851def fix_ampersands(value):
    4952    "Replaces ampersands with ``&amp;`` entities"
    5053    from django.utils.html import fix_ampersands
    5154    return fix_ampersands(value)
    5255fix_ampersands = stringfilter(fix_ampersands)
     56fix_ampersands.is_safe = True
    5357
    5458def floatformat(text, arg=-1):
    5559    """
    def floatformat(text, arg=-1):  
    8387        return u'%d' % int(f)
    8488    else:
    8589        formatstr = u'%%.%df' % abs(d)
    86         return formatstr % f
     90        return mark_safe(formatstr % f)
     91floatformat.is_safe = True
    8792
    8893def iriencode(value):
    8994    "Escapes an IRI value for use in a URL"
    9095    return force_unicode(iri_to_uri(value))
    9196iriencode = stringfilter(iriencode)
    9297
    93 def linenumbers(value):
     98def linenumbers(value, autoescape = None):
    9499    "Displays text with line numbers"
    95100    from django.utils.html import escape
    96101    lines = value.split(u'\n')
    97102    # Find the maximum width of the line count, for use with zero padding string format command
    98103    width = unicode(len(unicode(len(lines))))
    99     for i, line in enumerate(lines):
    100         lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
    101     return u'\n'.join(lines)
     104    if not autoescape or isinstance(value, SafeData):
     105        for i, line in enumerate(lines):
     106            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, line)
     107    else:
     108        for i, line in enumerate(lines):
     109            lines[i] = (u"%0" + width  + u"d. %s") % (i + 1, escape(line))
     110    return mark_safe(u'\n'.join(lines))
    102111linenumbers = stringfilter(linenumbers)
     112linenumbers.is_safe = True
     113linenumbers.needs_autoescape = True
    103114
    104115def lower(value):
    105116    "Converts a string into all lowercase"
    106117    return value.lower()
    107118lower = stringfilter(lower)
     119lower.is_safe = True
    108120
    109121def make_list(value):
    110122    """
    def make_list(value):  
    113125    """
    114126    return list(value)
    115127make_list = stringfilter(make_list)
     128make_list.is_safe = False
    116129
    117130def slugify(value):
    118131    """
    def slugify(value):  
    122135    import unicodedata
    123136    value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
    124137    value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
    125     return re.sub('[-\s]+', '-', value)
     138    return mark_safe(re.sub('[-\s]+', '-', value))
    126139slugify = stringfilter(slugify)
     140slugify.is_safe = True
    127141
    128142def stringformat(value, arg):
    129143    """
    def stringformat(value, arg):  
    138152        return (u"%" + unicode(arg)) % value
    139153    except (ValueError, TypeError):
    140154        return u""
     155stringformat.is_safe = True
    141156
    142157def title(value):
    143158    "Converts a string into titlecase"
    144159    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    145160title = stringfilter(title)
     161title.is_safe = False
    146162
    147163def truncatewords(value, arg):
    148164    """
    def truncatewords(value, arg):  
    157173        return value # Fail silently.
    158174    return truncate_words(value, length)
    159175truncatewords = stringfilter(truncatewords)
     176truncatewords.is_safe = True
    160177
    161178def truncatewords_html(value, arg):
    162179    """
    def upper(value):  
    176193    "Converts a string into all uppercase"
    177194    return value.upper()
    178195upper = stringfilter(upper)
     196upper.is_safe = False
    179197
    180198def urlencode(value):
    181199    "Escapes a value for use in a URL"
    182200    from django.utils.http import urlquote
    183201    return urlquote(value)
    184202urlencode = stringfilter(urlencode)
     203urlencode.is_safe = False
    185204
    186205def urlize(value):
    187206    "Converts URLs in plain text into clickable links"
    188207    from django.utils.html import urlize
    189     return urlize(value, nofollow=True)
     208    return mark_safe(urlize(value, nofollow=True))
    190209urlize = stringfilter(urlize)
     210urlize.is_safe = True
    191211
    192212def urlizetrunc(value, limit):
    193213    """
    def urlizetrunc(value, limit):  
    197217    Argument: Length to truncate URLs to.
    198218    """
    199219    from django.utils.html import urlize
    200     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     220    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
    201221urlizetrunc = stringfilter(urlizetrunc)
     222urlize.is_safe = True
    202223
    203224def wordcount(value):
    204225    "Returns the number of words"
    205226    return len(value.split())
    206227wordcount = stringfilter(wordcount)
     228wordcount.is_safe = False
    207229
    208230def wordwrap(value, arg):
    209231    """
    def wordwrap(value, arg):  
    214236    from django.utils.text import wrap
    215237    return wrap(value, int(arg))
    216238wordwrap = stringfilter(wordwrap)
     239wordwrap.is_safe = True
    217240
    218241def ljust(value, arg):
    219242    """
    def ljust(value, arg):  
    223246    """
    224247    return value.ljust(int(arg))
    225248ljust = stringfilter(ljust)
     249ljust.is_safe = True
    226250
    227251def rjust(value, arg):
    228252    """
    def rjust(value, arg):  
    232256    """
    233257    return value.rjust(int(arg))
    234258rjust = stringfilter(rjust)
     259rjust.is_safe = True
    235260
    236261def center(value, arg):
    237262    "Centers the value in a field of a given width"
    238263    return value.center(int(arg))
    239264center = stringfilter(center)
     265center.is_safe = True
    240266
    241267def cut(value, arg):
    242268    "Removes all values of arg from the given string"
    243269    return value.replace(arg, u'')
    244270cut = stringfilter(cut)
     271cut.is_safe = False
    245272
    246273###################
    247274# HTML STRINGS    #
    248275###################
    249276
    250277def escape(value):
    251     "Escapes a string's HTML"
     278    "Marks the value as a string that should not be auto-escaped."
     279    from django.utils.safestring import mark_for_escaping
     280    return mark_for_escaping(value)
     281escape = stringfilter(escape)
     282escape.is_safe = True
     283
     284def force_escape(value):
     285    """Escapes a string's HTML. This returns a new string containing the escaped
     286    characters (as opposed to "escape", which marks the content for later
     287    possible escaping)."""
    252288    from django.utils.html import escape
    253     return escape(value)
     289    return mark_safe(escape(value))
    254290escape = stringfilter(escape)
     291force_escape.is_safe = True
    255292
    256 def linebreaks(value):
     293def linebreaks(value, autoescape = None):
    257294    "Converts newlines into <p> and <br />s"
    258295    from django.utils.html import linebreaks
    259     return linebreaks(value)
     296    autoescape = autoescape and not isinstance(value, SafeData)
     297    return mark_safe(linebreaks(value, autoescape))
    260298linebreaks = stringfilter(linebreaks)
     299linebreaks.is_safe = True
     300linebreaks.needs_autoescape = True
    261301
    262 def linebreaksbr(value):
     302def linebreaksbr(value, autoescape = None):
    263303    "Converts newlines into <br />s"
    264     return value.replace('\n', '<br />')
     304    if autoescape and not isinstance(value, SafeData):
     305        from django.utils.html import escape
     306        data = escape(value)
     307    else:
     308        data = value
     309    return mark_safe(data.replace('\n', '<br />'))
    265310linebreaksbr = stringfilter(linebreaksbr)
     311linebreaksbr.is_safe = True
     312linebreaksbr.needs_autoescape = True
     313
     314def safe(value):
     315    "Marks the value as a string that should not be auto-escaped."
     316    from django.utils.safestring import mark_safe
     317    return mark_safe(value)
     318safe = stringfilter(safe)
     319safe.is_safe = True
    266320
    267321def removetags(value, tags):
    268322    "Removes a space separated list of [X]HTML tags from the output"
    def removetags(value, tags):  
    274328    value = endtag_re.sub(u'', value)
    275329    return value
    276330removetags = stringfilter(removetags)
     331removetags.is_safe = True
    277332
    278333def striptags(value):
    279334    "Strips all [X]HTML tags"
    280335    from django.utils.html import strip_tags
    281336    return strip_tags(value)
    282337striptags = stringfilter(striptags)
     338striptags.is_safe = True
    283339
    284340###################
    285341# LISTS           #
    def dictsort(value, arg):  
    293349    decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
    294350    decorated.sort()
    295351    return [item[1] for item in decorated]
     352dictsort.is_safe = False
    296353
    297354def dictsortreversed(value, arg):
    298355    """
    def dictsortreversed(value, arg):  
    303360    decorated.sort()
    304361    decorated.reverse()
    305362    return [item[1] for item in decorated]
     363dictsortreversed.is_safe = False
    306364
    307365def first(value):
    308366    "Returns the first item in a list"
    def first(value):  
    310368        return value[0]
    311369    except IndexError:
    312370        return u''
     371first.is_safe = True
    313372
    314373def join(value, arg):
    315374    "Joins a list with a string, like Python's ``str.join(list)``"
    316375    try:
    317         return arg.join(map(force_unicode, value))
     376        data = arg.join(map(force_unicode, value))
    318377    except AttributeError: # fail silently but nicely
    319378        return value
     379    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     380    if safe_args:
     381        return mark_safe(data)
     382    else:
     383        return data
     384join.is_safe = True
    320385
    321386def length(value):
    322387    "Returns the length of the value - useful for lists"
    323388    return len(value)
     389length.is_safe = False
    324390
    325391def length_is(value, arg):
    326392    "Returns a boolean of whether the value's length is the argument"
    327393    return len(value) == int(arg)
     394length.is_safe = False
    328395
    329396def random(value):
    330397    "Returns a random item from the list"
    331398    return random_module.choice(value)
     399length.is_safe = True
    332400
    333401def slice_(value, arg):
    334402    """
    def slice_(value, arg):  
    349417
    350418    except (ValueError, TypeError):
    351419        return value # Fail silently.
     420slice_.is_safe = True
    352421
    353 def unordered_list(value):
     422def unordered_list(value, autoescape = None):
    354423    """
    355424    Recursively takes a self-nested list and returns an HTML unordered list --
    356425    WITHOUT opening and closing <ul> tags.
    def unordered_list(value):  
    371440        </ul>
    372441        </li>
    373442    """
     443    if autoescape:
     444        from django.utils.html import conditional_escape
     445        escaper = conditional_escape
     446    else:
     447        escaper = lambda x: x
     448
    374449    def _helper(value, tabs):
    375450        indent = u'\t' * tabs
    376451        if value[1]:
    377             return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
     452            return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(force_unicode(value[0])), indent,
    378453                u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    379454        else:
    380             return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
    381     return _helper(value, 1)
     455            return u'%s<li>%s</li>' % (indent, escaper(force_unicode(value[0])))
     456    return mark_safe(_helper(value, 1))
     457unordered_list.is_safe = True
     458unordered_list.needs_autoescape = True
    382459
    383460###################
    384461# INTEGERS        #
    def unordered_list(value):  
    387464def add(value, arg):
    388465    "Adds the arg to the value"
    389466    return int(value) + int(arg)
     467add.is_safe = False
    390468
    391469def get_digit(value, arg):
    392470    """
    def get_digit(value, arg):  
    406484        return int(str(value)[-arg])
    407485    except IndexError:
    408486        return 0
     487get_digit.is_safe = False
    409488
    410489###################
    411490# DATES           #
    def date(value, arg=None):  
    419498    if arg is None:
    420499        arg = settings.DATE_FORMAT
    421500    return format(value, arg)
     501date.is_safe = False
    422502
    423503def time(value, arg=None):
    424504    "Formats a time according to the given format"
    def time(value, arg=None):  
    428508    if arg is None:
    429509        arg = settings.TIME_FORMAT
    430510    return time_format(value, arg)
     511time.is_safe = False
    431512
    432513def timesince(value, arg=None):
    433514    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    def timesince(value, arg=None):  
    437518    if arg:
    438519        return timesince(arg, value)
    439520    return timesince(value)
     521timesince.is_safe = False
    440522
    441523def timeuntil(value, arg=None):
    442524    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
    def timeuntil(value, arg=None):  
    447529    if arg:
    448530        return timesince(arg, value)
    449531    return timesince(datetime.now(), value)
     532timeuntil.is_safe = False
    450533
    451534###################
    452535# LOGIC           #
    def timeuntil(value, arg=None):  
    455538def default(value, arg):
    456539    "If value is unavailable, use given default"
    457540    return value or arg
     541default.is_safe = False
    458542
    459543def default_if_none(value, arg):
    460544    "If value is None, use given default"
    461545    if value is None:
    462546        return arg
    463547    return value
     548default_if_none.is_safe = False
    464549
    465550def divisibleby(value, arg):
    466551    "Returns true if the value is devisible by the argument"
    467552    return int(value) % int(arg) == 0
     553divisibleby.is_safe = False
    468554
    469555def yesno(value, arg=None):
    470556    """
    def yesno(value, arg=None):  
    495581    if value:
    496582        return yes
    497583    return no
     584yesno.is_safe = False
    498585
    499586###################
    500587# MISC            #
    def filesizeformat(bytes):  
    517604    if bytes < 1024 * 1024 * 1024:
    518605        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
    519606    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
     607filesizeformat.is_safe = True
    520608
    521609def pluralize(value, arg=u's'):
    522610    """
    def pluralize(value, arg=u's'):  
    544632        except TypeError: # len() of unsized object
    545633            pass
    546634    return singular_suffix
     635pluralize.is_safe = False
    547636
    548637def phone2numeric(value):
    549638    "Takes a phone number and converts it in to its numerical equivalent"
    550639    from django.utils.text import phone2numeric
    551640    return phone2numeric(value)
     641phone2numeric.is_safe = True
    552642
    553643def pprint(value):
    554644    "A wrapper around pprint.pprint -- for debugging, really"
    def pprint(value):  
    557647        return pformat(value)
    558648    except Exception, e:
    559649        return u"Error in formatting: %s" % force_unicode(e, errors="replace")
     650pprint.is_safe = True
    560651
    561652# Syntax: register.filter(name of filter, callback)
    562653register.filter(add)
    register.filter(filesizeformat)  
    575666register.filter(first)
    576667register.filter(fix_ampersands)
    577668register.filter(floatformat)
     669register.filter(force_escape)
    578670register.filter(get_digit)
    579671register.filter(iriencode)
    580672register.filter(join)
    register.filter(pprint)  
    592684register.filter(removetags)
    593685register.filter(random)
    594686register.filter(rjust)
     687register.filter(safe)
    595688register.filter('slice', slice_)
    596689register.filter(slugify)
    597690register.filter(stringformat)
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 7b3a261a5b55b8875c3445913c4dfac4b1f594d0..b4b4270c45c11f4c1f39d9b66312ac82c296493f 100644
    a b from django.template import get_library, Library, InvalidTemplateLibrary  
    66from django.conf import settings
    77from django.utils.encoding import smart_str, smart_unicode
    88from django.utils.itercompat import groupby
     9from django.utils.safestring import mark_safe
    910import sys
    1011import re
    1112
    except NameError:  
    1617
    1718register = Library()
    1819
     20class AutoEscapeControlNode(Node):
     21    """Implements the actions of both the autoescape and noautescape tags."""
     22    def __init__(self, setting, nodelist):
     23        self.setting, self.nodelist = setting, nodelist
     24
     25    def render(self, context):
     26        old_setting = context.autoescape
     27        context.autoescape = self.setting
     28        output = self.nodelist.render(context)
     29        context.autoescape = old_setting
     30        if self.setting:
     31            return mark_safe(output)
     32        else:
     33            return output
     34
    1935class CommentNode(Node):
    2036    def render(self, context):
    2137        return ''
    class WithNode(Node):  
    386402        return output
    387403
    388404#@register.tag
     405def autoescape(parser, token):
     406    """
     407    Force autoescape behaviour for this block.
     408    """
     409    nodelist = parser.parse(('endautoescape',))
     410    parser.delete_first_token()
     411    return AutoEscapeControlNode(True, nodelist)
     412autoescape = register.tag(autoescape)
     413
     414#@register.tag
    389415def comment(parser, token):
    390416    """
    391417    Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
    def do_filter(parser, token):  
    488514
    489515    Sample usage::
    490516
    491         {% filter escape|lower %}
     517        {% filter force_escape|lower %}
    492518            This text will be HTML-escaped, and will appear in lowercase.
    493519        {% endfilter %}
    494520    """
    495521    _, rest = token.contents.split(None, 1)
    496522    filter_expr = parser.compile_filter("var|%s" % (rest))
     523    for func, unused in filter_expr.filters:
     524        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     525            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    497526    nodelist = parser.parse(('endfilter',))
    498527    parser.delete_first_token()
    499528    return FilterNode(filter_expr, nodelist)
    def ifchanged(parser, token):  
    746775ifchanged = register.tag(ifchanged)
    747776
    748777#@register.tag
     778def noautoescape(parser, token):
     779    """
     780    Force autoescape behaviour to be disabled for this block.
     781    """
     782    nodelist = parser.parse(('endnoautoescape',))
     783    parser.delete_first_token()
     784    return AutoEscapeControlNode(False, nodelist)
     785autoescape = register.tag(noautoescape)
     786
     787#@register.tag
    749788def ssi(parser, token):
    750789    """
    751790    Output the contents of a given file into the page.
  • django/utils/encoding.py

    diff --git a/django/utils/encoding.py b/django/utils/encoding.py
    index 7515d0c41b5dcf5a477963b7fba39e3529ff8e90..a0db1f2a6973b26d5f0f92c96d725a4b76e29cd0 100644
    a b def force_unicode(s, encoding='utf-8', strings_only=False, errors='strict'):  
    3939        else:
    4040            s = unicode(str(s), encoding, errors)
    4141    elif not isinstance(s, unicode):
    42         s = unicode(s, encoding, errors)
     42        s = s.decode(encoding, errors)
    4343    return s
    4444
    4545def smart_str(s, encoding='utf-8', strings_only=False, errors='strict'):
  • django/utils/html.py

    diff --git a/django/utils/html.py b/django/utils/html.py
    index ebd04d1b3caf45fdcda4a5fe2782d1d3ed5a6235..281184d28f7fd28116deeaf94c639f231abbdd1c 100644
    a b  
    33import re
    44import string
    55
     6from django.utils.safestring import SafeData
    67from django.utils.encoding import force_unicode
    78from django.utils.functional import allow_lazy
    89
    def escape(html):  
    3031    return force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
    3132escape = allow_lazy(escape, unicode)
    3233
    33 def linebreaks(value):
    34     "Convert newlines into <p> and <br />s."
     34def conditional_escape(html):
     35    "Similar to escape(), except that it does not operate on pre-escaped strings"
     36    if isinstance(html, SafeData):
     37        return html
     38    else:
     39        return escape(html)
     40
     41def linebreaks(value, autoescape = False):
     42    "Converts newlines into <p> and <br />s"
    3543    value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
    3644    paras = re.split('\n{2,}', value)
    37     paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
     45    if autoescape:
     46        paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
     47    else:
     48        paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
    3849    return u'\n\n'.join(paras)
    39 linebreaks = allow_lazy(linebreaks, unicode)
     50linebreaks = allow_lazy(linebreaks, unicode) 
    4051
    4152def strip_tags(value):
    4253    "Return the given HTML with all tags stripped."
  • new file django/utils/safestring.py

    diff --git a/django/utils/safestring.py b/django/utils/safestring.py
    new file mode 100644
    index 0000000000000000000000000000000000000000..18c931b56a20645660b26e1bf3e05ebb57b8b11d
    - +  
     1"""
     2Functions for working with "safe strings": strings that can be displayed safely
     3without further escaping in HTML. Here, a "safe string" means that the producer
     4of the string has already turned characters that should not be interpreted by
     5the HTML engine (e.g. '<') into the appropriate entities.
     6"""
     7from django.utils.functional import curry
     8from django.utils.encoding import force_unicode
     9
     10class EscapeData(object):
     11    pass
     12
     13class EscapeString(str, EscapeData):
     14    """
     15    A string that should be HTML-escaped when output.
     16    """
     17    pass
     18
     19class EscapeUnicode(unicode, EscapeData):
     20    """
     21    A unicode object that should be HTML-escaped when output.
     22    """
     23    pass
     24
     25class SafeData(object):
     26    pass
     27
     28class SafeString(str, SafeData):
     29    """
     30    A string subclass that has been specifically marked as "safe" for HTML
     31    output purposes.
     32    """
     33    def __add__(self, rhs):
     34        """
     35        Concatenating a safe string with another safe string or safe unicode
     36        object is safe. Otherwise, the result is no longer safe.
     37        """
     38        if isinstance(rhs, SafeUnicode):
     39            return SafeUnicode(self + rhs)
     40        elif isinstance(rhs, SafeString):
     41            return SafeString(self + rhs)
     42        else:
     43            return super(SafeString, self).__add__(rhs)
     44
     45    def __str__(self):
     46        return self
     47
     48    def _proxy_method(self, *args, **kwargs):
     49        """
     50        Wrap a call to a normal unicode method up so that we return safe
     51        results. The method that is being wrapped is passed in the 'method'
     52        argument.
     53        """
     54        method = kwargs.pop('method')
     55        data = method(self, *args, **kwargs)
     56        if isinstance(data, str):
     57            return SafeString(data)
     58        else:
     59            return SafeUnicode(data)
     60
     61    encode = curry(_proxy_method, method = str.encode)
     62    decode = curry(_proxy_method, method = str.decode)
     63
     64class SafeUnicode(unicode, SafeData):
     65    """
     66    A unicode subclass that has been specifically marked as "safe" for HTML
     67    output purposes.
     68    """
     69    def __add__(self, rhs):
     70        """
     71        Concatenating a safe unicode object with another safe string or safe
     72        unicode object is safe. Otherwise, the result is no longer safe.
     73        """
     74        if isinstance(rhs, SafeData):
     75            return SafeUnicode(self + rhs)
     76        else:
     77            return super(SafeUnicode, self).__add__(rhs)
     78
     79    def _proxy_method(self, *args, **kwargs):
     80        """
     81        Wrap a call to a normal unicode method up so that we return safe
     82        results. The method that is being wrapped is passed in the 'method'
     83        argument.
     84        """
     85        method = kwargs.pop('method')
     86        data = method(self, *args, **kwargs)
     87        if isinstance(data, str):
     88            return SafeString(data)
     89        else:
     90            return SafeUnicode(data)
     91
     92    encode = curry(_proxy_method, method = unicode.encode)
     93    decode = curry(_proxy_method, method = unicode.decode)
     94
     95
     96def mark_safe(s):
     97    """
     98    Explicitly mark a string as safe for (HTML) output purposes. The returned
     99    object can be used everywhere a string or unicode object is appropriate.
     100
     101    Can safely be called multiple times on a single string.
     102    """
     103    if isinstance(s, SafeData):
     104        return s
     105    if isinstance(s, str):
     106        return SafeString(s)
     107    if isinstance(s, unicode):
     108        return SafeUnicode(s)
     109    return SafeString(str(s))
     110
     111def mark_for_escaping(s):
     112    """
     113    Explicitly mark a string as requiring HTML escaping upon output. Has no
     114    effect on SafeData subclasses.
     115
     116    Can be safely called multiple times on a single string (the effect is only
     117    applied once).
     118    """
     119    if isinstance(s, SafeData) or isinstance(s, EscapeData):
     120        return s
     121    if isinstance(s, str):
     122        return EscapeString(s)
     123    if isinstance(s, unicode):
     124        return EscapeUnicode(s)
     125    return EscapeString(str(s))
     126
  • docs/templates.txt

    diff --git a/docs/templates.txt b/docs/templates.txt
    index 0d53c281d156c0db34a1c573149cb803527d7d81..dbce6cac71c539936adc168c2f19f987da81fb4e 100644
    a b it also defines the content that fills the hole in the *parent*. If there were  
    277277two similarly-named ``{% block %}`` tags in a template, that template's parent
    278278wouldn't know which one of the blocks' content to use.
    279279
     280Automatic HTML escaping
     281=======================
     282
     283A very real problem when creating HTML (and other) output using templates and
     284variable substitution is the possibility of accidently inserting some variable
     285value that affects the resulting HTML. For example, a template fragment like
     286
     287::
     288
     289    Hello, {{ name }}.
     290
     291seems like a harmless way to display the user's name. However, if you are
     292displaying data that the user entered directly and they entered their name as
     293
     294::
     295
     296    <script>alert('hello')</script>
     297
     298this would always display a Javascript alert box whenever the page was loaded.
     299Similarly, if you were displaying some data generated by another process and
     300it contained a '<' symbol, you couldn't just dump this straight into your
     301HTML, because it would be treated as the start of an element.  The effects of
     302these sorts of problems can vary from merely annoying to allowing exploits via
     303`Cross Site Scripting`_ (XSS) attacks.
     304
     305.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
     306
     307In order to provide some protection against these problems, Django provides an
     308auto-escaping template tag. Inside this tag, any data that comes from template
     309variables is examined to see if it contains one of the five HTML characters
     310(<, >, ', " and &) that often need escaping and those characters are converted
     311to their respective HTML entities.
     312
     313Because some variables will contain data that is *intended* to be rendered
     314as HTML, template tag and filter writers can mark their output strings as
     315requiring no further escaping. For example, the ``unordered_list`` filter is
     316designed to return raw HTML and we want the template processor to simply
     317display the results as returned, without applying any escaping. That is taken
     318care of by the filter. The template author need do nothing special in that
     319case.
     320
     321By default, auto-escaping is not in effect. To enable it inside your template,
     322wrap the affected content in the ``autoescape`` tag, like so::
     323
     324    {% autoescape %}
     325        Hello {{ name }}
     326    {% endautoescape %}
     327
     328Since the auto-escaping tag passes its effect onto templates that extend the
     329current one as well as templates included via the ``include`` tag (just like
     330all block tags), if you wrap your main HTML content in an ``autoescape`` tag,
     331you will have automatic escaping applied to all of your content.
     332
     333At times, you might want to disable auto-escaping when it would otherwise be
     334in effect. You can do this with the ``noautoescape`` tag. For example::
     335
     336    {% autoescape %}
     337        Hello {{ name }}
     338
     339        {% noautoescape %}
     340            This will not be auto-escaped: {{ data }}.
     341
     342            Nor this: {{ other_data }}
     343        {% endnoautoescape %}
     344    {% endautoescape %}
     345
     346For individual variables, the ``safe`` filter can also be used.
     347
     348Generally, you will not need to worry about auto-escaping very much. Enable it
     349in your base template once you are entering the main HTML region and then
     350write your templates normally. The view developers and custom filter authors
     351need to think about when their data should not be escaped and mark it
     352appropriately.  They are in a better position to know when that should happen
     353than the template author, so it is their responsibility. By default, when
     354auto-escaping is enabled, all output is escaped unless the template processor
     355is explicitly told otherwise.
     356
    280357Using the built-in reference
    281358============================
    282359
    available, and what they do.  
    352429Built-in tag reference
    353430----------------------
    354431
     432autoescape
     433~~~~~~~~~~
     434
     435All variables that are output inside this tag have HTML escaping applied to
     436them, as if they each had the ``escape`` filter attached to them.
     437
     438The only exceptions are variables that are already marked as 'safe' from
     439escaping, either by the code that populated the variable, or because it has
     440the ``safe`` filter applied.
     441
    355442block
    356443~~~~~
    357444
    just like in variable syntax.  
    419506
    420507Sample usage::
    421508
    422     {% filter escape|lower %}
     509    {% filter force_escape|lower %}
    423510        This text will be HTML-escaped, and will appear in all lowercase.
    424511    {% endfilter %}
    425512
    Load a custom template tag set.  
    652739
    653740See `Custom tag and filter libraries`_ for more information.
    654741
     742noautoescape
     743~~~~~~~~~~~~
     744
     745Disable the effects of the ``autoescape`` tag (if it is in effect).
     746
    655747now
    656748~~~
    657749
    Escapes a string's HTML. Specifically, it makes these replacements:  
    10401132    * ``'"'`` (double quote) to ``'&quot;'``
    10411133    * ``"'"`` (single quote) to ``'&#39;'``
    10421134
     1135The escaping is only applied when the string is output, so it does not matter
     1136where in a chained sequence of filters you put ``escape``: it will always be
     1137applied as though it were the last filter. If you want escaping to be applied
     1138immediately, use the ``force_escape`` filter.
     1139
    10431140filesizeformat
    10441141~~~~~~~~~~~~~~
    10451142
    For example:  
    10821179Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
    10831180an argument of ``-1``.
    10841181
     1182force_escape
     1183~~~~~~~~~~~~
     1184
     1185**New in Django development version**
     1186
     1187Applies HTML escaping to a string (see the ``escape`` filter for details).
     1188This filter is applied immediately and returns a new, escaped string. This is
     1189useful in the typically rare cases where you need multiple escaping or want to
     1190apply other filters to the escaped results. Normally, you want to use the
     1191``escape`` filter.
     1192
    10851193get_digit
    10861194~~~~~~~~~
    10871195
    Right-aligns the value in a field of a given width.  
    12031311
    12041312**Argument:** field size
    12051313
     1314safe
     1315~~~~
     1316
     1317Marks a string as not requiring further HTML escaping prior to output. This is
     1318only useful inside an ``autoescape`` block, when the output would otherwise be
     1319automatically escaped. Outside of an ``autoescape`` block, this filter has no
     1320effect.
     1321
    12061322slice
    12071323~~~~~
    12081324
  • docs/templates_python.txt

    diff --git a/docs/templates_python.txt b/docs/templates_python.txt
    index 117656762f9cf5d96b0818722e5929ab4f8cbe94..5b3973b879e0e3970d8b6bd28dad515d1a954309 100644
    a b an object to it's string value before being passed to your function::  
    680680    def lower(value):
    681681        return value.lower()
    682682
     683Filters and auto-escaping
     684~~~~~~~~~~~~~~~~~~~~~~~~~
     685
     686When you are writing a custom filter, you need to give some thought to how
     687this filter will work when rendered in an auto-escaping environment (inside
     688an ``autoescape`` template tag block). First, you should realise that there
     689are three types of strings that can be passed around inside the template code:
     690
     691 * raw strings are the native Python ``str`` (or ``unicode``) types. On
     692   output, they are escaped if they are inside an ``autoescape`` block.
     693 * "safe" strings are strings that are safe from further escaping at output
     694   time. Any necessary escaping has already been done. They are commonly used
     695   for output that contains raw HTML that is intended to be intrepreted on the
     696   client side.
     697
     698   Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
     699   although they share a common base class in ``SafeData``, so you can test
     700   for them using code like::
     701
     702    if isinstance(value, SafeData):
     703        # Do something with the "safe" string.
     704
     705 * strings which are marked as "need escaping" are *always* escaped on
     706   output, regardless of whether they are in an ``autoescape`` block or not.
     707   These strings are only escaped once, however, even if used inside an
     708   ``autoescaep`` block.  This type of string is internally represented by the
     709   types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to
     710   worry about these; they exist only for the implementation of the ``escape``
     711   filter.
     712
     713Inside your filter, you will need to think about three areas in order to be
     714auto-escaping compliant:
     715
     716 1. If your filter returns a string that is ready for direct output (it should
     717 be considered a "safe" string), you should call
     718 ``django.utils.safestring.mark_safe()`` on the result prior to returning.
     719 This will turn the result into the appropriate ``SafeData`` type.
     720
     721 2. If your filter is given a "safe" string, is it guaranteed to return a
     722 "safe" string? If so, set the ``is_safe`` attribute on the function to be
     723 ``True``. For example, a filter that replaced all numbers with the number
     724 spelt out in words is going to be safe-string-preserving, since it cannot
     725 introduce any of the five dangerous characters: <, >, ", ' or &. So we can
     726 write::
     727
     728    @register.filter
     729    def convert_to_words(value):
     730        # ... implementation here ...
     731        return result
     732
     733    convert_to_words.is_safe = True
     734
     735 Note that this filter does not return a universally safe result (it does not
     736 return ``mark_safe(result)``) because if it is handed a raw string such as
     737 '<a>', this will need further escaping in an auto-escape environment. The
     738 ``is_safe`` attribute only talks about the safeness of the result when a safe
     739 string is passed in to the filter.
     740
     741 3. Will your filter behave differently depending upon whether auto-escaping
     742 is currently in effect or not? For example, the ``ordered_list`` filter that
     743 ships with Django needs to know whether to escape its content or not. It will
     744 always return a safe string, since it returns raw HTML, so we cannot apply
     745 escaping to the result -- it needs to be done in-situ.
     746
     747 For these cases, the filter function needs to be told what the current
     748 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
     749 filter to ``True`` and have your function take an extra argument called
     750 ``autoescape`` with a default value of ``None``. When the filter is called,
     751 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
     752 effect. For example, the ``unordered_list`` filter is written as::
     753
     754    def unordered_list(value, autoescape = None):
     755        # ... lots of code here ...
     756
     757        return mark_safe(...)
     758
     759    unordered_list.is_safe = True
     760    unordered_list.needs_autoescape = True
     761
     762By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
     763``False``. You do not need to specify them if ``False`` is an acceptable
     764value.
     765
     766As a matter of convention, we leave ``is_safe`` as ``False`` for filters that
     767do not accept string inputs (they might take a number as an input, for
     768example) or for those that return a non-string (e.g. the ``length`` filter).
     769However, not following this convention will not cause any harm or make your
     770results any more vulnerable to cross-site scripting problems.
     771
    683772Writing custom template tags
    684773----------------------------
    685774
    Ultimately, this decoupling of compilation and rendering results in an  
    798887efficient template system, because a template can render multiple context
    799888without having to be parsed multiple times.
    800889
     890Auto-escaping considerations
     891~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     892
     893The output from template tags is not automatically run through the
     894auto-escaping filters if used inside an ``autoescape`` tag. However, there are
     895still a couple of things you should keep in mind when writing a template tag:
     896
     897If the ``render()`` function of your template stores the result in a context
     898variable (rather than returning the result in a string), it should take care
     899to call ``mark_safe()`` if appropriate. When the variable is ultimately
     900rendered, it will be affected by the auto-escape setting in effect at the
     901time, so content that should be safe from further escaping needs to be marked
     902as such.
     903
     904Also, if your template tag creates a new context for performing some
     905sub-rendering, you should be careful to set the auto-escape variable to the
     906current context's value. For example::
     907
     908    def render(self, context):
     909        # ...
     910        new_context = Context({'var': obj})
     911        new_context.autoescape = context.autoescape
     912        # ... Do something with new_context ...
     913
     914This is not a very common situation, but it is sometimes useful (see
     915``django.templates.defaulttags.FilterNode.render()`` for an example).
     916
    801917Registering the tag
    802918~~~~~~~~~~~~~~~~~~~
    803919
  • new file tests/regressiontests/autoescape/tests.py

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

    diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
    index a1efae66f66a0826cfc91318ec129ecc03504af6..a792a44f785433834ae4828f3b2485b071a441d7 100644
    a b u'a stri to be maled'  
    190190>>> cut(u'a string to be mangled', 'strings')
    191191u'a string to be mangled'
    192192
    193 >>> escape(u'<some html & special characters > here')
     193>>> force_escape(u'<some html & special characters > here')
    194194u'&lt;some html &amp; special characters &gt; here'
    195195
    196 >>> escape(u'<some html & special characters > here ĐÅ€£')
     196>>> force_escape(u'<some html & special characters > here ĐÅ€£')
    197197u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
    198198
    199199>>> linebreaks(u'line 1')
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 143d1ddaa4f842a4e35bfad0d00ae9191aa85c06..70118c66207dce05744370f08ef15a019c8ac7c6 100644
    a b class UTF8Class:  
    7676
    7777class Templates(unittest.TestCase):
    7878    def test_templates(self):
     79        TEMPLATE_TESTS = self.get_template_tests()
     80
     81        # Register our custom template loader.
     82        def test_template_loader(template_name, template_dirs=None):
     83            "A custom template loader that loads the unit-test templates."
     84            try:
     85                return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     86            except KeyError:
     87                raise template.TemplateDoesNotExist, template_name
     88
     89        old_template_loaders = loader.template_source_loaders
     90        loader.template_source_loaders = [test_template_loader]
     91
     92        failures = []
     93        tests = TEMPLATE_TESTS.items()
     94        tests.sort()
     95
     96        # Turn TEMPLATE_DEBUG off, because tests assume that.
     97        old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     98
     99        # Set TEMPLATE_STRING_IF_INVALID to a known string
     100        old_invalid = settings.TEMPLATE_STRING_IF_INVALID
     101        expected_invalid_str = 'INVALID'
     102
     103        for name, vals in tests:
     104            install()
     105
     106            if isinstance(vals[2], tuple):
     107                normal_string_result = vals[2][0]
     108                invalid_string_result = vals[2][1]
     109                if '%s' in invalid_string_result:
     110                    expected_invalid_str = 'INVALID %s'
     111                    invalid_string_result = invalid_string_result % vals[2][2]
     112                    template.invalid_var_format_string = True
     113            else:
     114                normal_string_result = vals[2]
     115                invalid_string_result = vals[2]
     116
     117            if 'LANGUAGE_CODE' in vals[1]:
     118                activate(vals[1]['LANGUAGE_CODE'])
     119            else:
     120                activate('en-us')
     121
     122            for invalid_str, result in [('', normal_string_result),
     123                                        (expected_invalid_str, invalid_string_result)]:
     124                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
     125                try:
     126                    test_template = loader.get_template(name)
     127                    output = self.render(test_template, vals)
     128                except Exception, e:
     129                    if e.__class__ != result:
     130                        failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
     131                    continue
     132                if output != result:
     133                    failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
     134
     135            if 'LANGUAGE_CODE' in vals[1]:
     136                deactivate()
     137               
     138            if template.invalid_var_format_string:
     139                expected_invalid_str = 'INVALID'
     140                template.invalid_var_format_string = False
     141
     142        loader.template_source_loaders = old_template_loaders
     143        deactivate()
     144        settings.TEMPLATE_DEBUG = old_td
     145        settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     146
     147        self.assertEqual(failures, [], '\n'.join(failures))
     148
     149    def render(self, test_template, vals):
     150        return test_template.render(template.Context(vals[1]))
     151
     152    def get_template_tests(self):
    79153        # NOW and NOW_tz are used by timesince tag tests.
    80154        NOW = datetime.now()
    81155        NOW_tz = datetime.now(LocalTimezone(datetime.now()))
    82156
    83157        # SYNTAX --
    84158        # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
    85         TEMPLATE_TESTS = {
    86 
     159        return {
    87160            ### BASIC SYNTAX ##########################################################
    88161
    89162            # Plain text should go through the template parser untouched
    class Templates(unittest.TestCase):  
    744817            'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
    745818        }
    746819
    747         # Register our custom template loader.
    748         def test_template_loader(template_name, template_dirs=None):
    749             "A custom template loader that loads the unit-test templates."
    750             try:
    751                 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
    752             except KeyError:
    753                 raise template.TemplateDoesNotExist, template_name
    754 
    755         old_template_loaders = loader.template_source_loaders
    756         loader.template_source_loaders = [test_template_loader]
    757 
    758         failures = []
    759         tests = TEMPLATE_TESTS.items()
    760         tests.sort()
    761 
    762         # Turn TEMPLATE_DEBUG off, because tests assume that.
    763         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
    764 
    765         # Set TEMPLATE_STRING_IF_INVALID to a known string
    766         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
    767         expected_invalid_str = 'INVALID'
    768 
    769         for name, vals in tests:
    770             install()
    771 
    772             if isinstance(vals[2], tuple):
    773                 normal_string_result = vals[2][0]
    774                 invalid_string_result = vals[2][1]
    775                 if '%s' in invalid_string_result:
    776                     expected_invalid_str = 'INVALID %s'
    777                     invalid_string_result = invalid_string_result % vals[2][2]
    778                     template.invalid_var_format_string = True
    779             else:
    780                 normal_string_result = vals[2]
    781                 invalid_string_result = vals[2]
    782 
    783             if 'LANGUAGE_CODE' in vals[1]:
    784                 activate(vals[1]['LANGUAGE_CODE'])
    785             else:
    786                 activate('en-us')
    787 
    788             for invalid_str, result in [('', normal_string_result),
    789                                         (expected_invalid_str, invalid_string_result)]:
    790                 settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    791                 try:
    792                     output = loader.get_template(name).render(template.Context(vals[1]))
    793                 except Exception, e:
    794                     if e.__class__ != result:
    795                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    796                     continue
    797                 if output != result:
    798                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
    799 
    800             if 'LANGUAGE_CODE' in vals[1]:
    801                 deactivate()
    802 
    803             if template.invalid_var_format_string:
    804                 expected_invalid_str = 'INVALID'
    805                 template.invalid_var_format_string = False
    806 
    807         loader.template_source_loaders = old_template_loaders
    808         deactivate()
    809         settings.TEMPLATE_DEBUG = old_td
    810         settings.TEMPLATE_STRING_IF_INVALID = old_invalid
    811 
    812         self.assertEqual(failures, [], '\n'.join(failures))
    813 
    814820if __name__ == "__main__":
    815821    unittest.main()
Back to Top