Ticket #2359: unicode-autoescape-1.diff

File unicode-autoescape-1.diff, 73.6 KB (added by mir@…, 17 years ago)

core changes ported for the unicode branch

  • django/oldforms/__init__.py

    From nobody Mon Sep 17 00:00:00 2001
    From: Michael Radziej <mir@noris.de>
    Date: Fri May 25 14:32:37 2007 +0200
    Subject: [PATCH] autoescape 1
    
    Refreshed patch autoescape-1.
    (Base: 1385b65efac6593b1152308f341a15016f6b29d3)
    (Last: b573a42a7e03c1e50b48d9e8d688db49e8d56f82)
    
    ---
    
     django/oldforms/__init__.py                   |   43 ++--
     django/template/__init__.py                   |   26 ++
     django/template/context.py                    |    4 
     django/template/defaultfilters.py             |  131 ++++++++++--
     django/template/defaulttags.py                |   45 ++++
     django/utils/encoding.py                      |    2 
     django/utils/html.py                          |   16 +
     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     |  270 +++++++++++++++++++++++++
     tests/regressiontests/defaultfilters/tests.py |    4 
     tests/regressiontests/templates/tests.py      |  144 +++++++------
     15 files changed, 925 insertions(+), 120 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 fb9e634ca37d3deb02ab7185a7d85ad0398d8356
    last 4fc620a019aa2eb53633490b4a883fb2c8577890
    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index bc787f860cd71b006b1f3619839c7d4e22e18587..1999b2041e13688eb52a5365d7c9215e3812974f 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, smart_str
    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(FormFieldWrapp  
    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(Select  
    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 5b8cdc3b1f1950efa082b098cd351b283bd22834..3f3c611b2ab1909f2357351349c6f89757dfab0a 100644
    a b from django.utils.functional import curr  
    6262from django.utils.text import smart_split
    6363from django.utils.encoding import smart_unicode, smart_str, 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):  
    603605                    arg_vals.append(arg)
    604606                else:
    605607                    arg_vals.append(resolve_variable(arg, context))
    606             obj = func(obj, *arg_vals)
     608            if getattr(func, 'needs_autoescape', False):
     609                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     610            else:
     611                new_obj = func(obj, *arg_vals)
     612            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     613                obj = mark_safe(new_obj)
     614            elif isinstance(obj, EscapeData):
     615                obj = mark_for_escaping(new_obj)
     616            else:
     617                obj = new_obj
     618               
    607619        return obj
    608620
    609621    def args_check(name, func, provided):
    class VariableNode(Node):  
    792804        return "<Variable Node: %s>" % self.filter_expression
    793805
    794806    def render(self, context):
    795         return self.filter_expression.resolve(context)
     807        output = self.filter_expression.resolve(context)
     808        if (context.autoescape and not isinstance(output, SafeData)) or isinstance(output, EscapeData):
     809            return escape(output)
     810        else:
     811            return output
    796812
    797813class DebugVariableNode(VariableNode):
    798814    def render(self, context):
    799815        try:
    800             return self.filter_expression.resolve(context)
     816            output = self.filter_expression.resolve(context)
    801817        except TemplateSyntaxError, e:
    802818            if not hasattr(e, 'source'):
    803819                e.source = self.source
    804820            raise
     821        if context.autoescape and not isinstance(output, SafeData):
     822            return escape(output)
     823        else:
     824            return output
    805825
    806826def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    807827    "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 27a0b07df038f36db04dcb251495da37cf775cee..1b5c08812ae8cdae088db28409d5801f192d52ec 100644
    a b from django.template import resolve_vari  
    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    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    119132    # Don't compile patterns as unicode because \w then would mean any letter. Slugify is effectively an asciiization.
    120133    value = re.sub('[^\w\s-]', '', value).strip().lower()
    121     return re.sub('[-\s]+', '-', value)
     134    return mark_safe(re.sub('[-\s]+', '-', value))
    122135slugify = stringfilter(slugify)
     136slugify.is_safe = True
    123137
    124138def stringformat(value, arg):
    125139    """
    def stringformat(value, arg):  
    134148        return (u"%" + unicode(arg)) % value
    135149    except (ValueError, TypeError):
    136150        return u""
     151stringformat.is_safe = True
    137152
    138153def title(value):
    139154    "Converts a string into titlecase"
    140155    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    141156title = stringfilter(title)
     157title.is_safe = False
    142158
    143159def truncatewords(value, arg):
    144160    """
    def truncatewords(value, arg):  
    153169        return value # Fail silently.
    154170    return truncate_words(value, length)
    155171truncatewords = stringfilter(truncatewords)
     172truncatewords.is_safe = True
    156173
    157174def truncatewords_html(value, arg):
    158175    """
    def upper(value):  
    172189    "Converts a string into all uppercase"
    173190    return value.upper()
    174191upper = stringfilter(upper)
     192upper.is_safe = False
    175193
    176194def urlencode(value):
    177195    "Escapes a value for use in a URL"
    178196    import urllib
    179197    return force_unicode(urllib.quote(value))
    180198urlencode = stringfilter(urlencode)
     199urlencode.is_safe = False
    181200
    182201def urlize(value):
    183202    "Converts URLs in plain text into clickable links"
    184203    from django.utils.html import urlize
    185     return urlize(value, nofollow=True)
     204    return mark_safe(urlize(value, nofollow=True))
    186205urlize = stringfilter(urlize)
     206urlize.is_safe = True
    187207
    188208def urlizetrunc(value, limit):
    189209    """
    def urlizetrunc(value, limit):  
    193213    Argument: Length to truncate URLs to.
    194214    """
    195215    from django.utils.html import urlize
    196     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     216    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
    197217urlizetrunc = stringfilter(urlizetrunc)
     218urlize.is_safe = True
    198219
    199220def wordcount(value):
    200221    "Returns the number of words"
    201222    return len(value.split())
    202223wordcount = stringfilter(wordcount)
     224wordcount.is_safe = False
    203225
    204226def wordwrap(value, arg):
    205227    """
    def wordwrap(value, arg):  
    210232    from django.utils.text import wrap
    211233    return wrap(value, int(arg))
    212234wordwrap = stringfilter(wordwrap)
     235wordwrap.is_safe = True
    213236
    214237def ljust(value, arg):
    215238    """
    def ljust(value, arg):  
    219242    """
    220243    return value.ljust(int(arg))
    221244ljust = stringfilter(ljust)
     245ljust.is_safe = True
    222246
    223247def rjust(value, arg):
    224248    """
    def rjust(value, arg):  
    228252    """
    229253    return value.rjust(int(arg))
    230254rjust = stringfilter(rjust)
     255rjust.is_safe = True
    231256
    232257def center(value, arg):
    233258    "Centers the value in a field of a given width"
    234259    return value.center(int(arg))
    235260center = stringfilter(center)
     261center.is_safe = True
    236262
    237263def cut(value, arg):
    238264    "Removes all values of arg from the given string"
    239265    return value.replace(arg, u'')
    240266cut = stringfilter(cut)
     267cut.is_safe = False
    241268
    242269###################
    243270# HTML STRINGS    #
    244271###################
    245272
    246273def escape(value):
    247     "Escapes a string's HTML"
     274    "Marks the value as a string that should not be auto-escaped."
     275    from django.utils.safestring import mark_for_escaping
     276    return mark_for_escaping(value)
     277escape = stringfilter(escape)
     278escape.is_safe = True
     279
     280def force_escape(value):
     281    """Escapes a string's HTML. This returns a new string containing the escaped
     282    characters (as opposed to "escape", which marks the content for later
     283    possible escaping)."""
    248284    from django.utils.html import escape
    249     return escape(value)
     285    return mark_safe(escape(value))
    250286escape = stringfilter(escape)
     287force_escape.is_safe = True
    251288
    252 def linebreaks(value):
     289def linebreaks(value, autoescape = None):
    253290    "Converts newlines into <p> and <br />s"
    254291    from django.utils.html import linebreaks
    255     return linebreaks(value)
     292    autoescape = autoescape and not isinstance(value, SafeData)
     293    return mark_safe(linebreaks(value, autoescape))
    256294linebreaks = stringfilter(linebreaks)
     295linebreaks.is_safe = True
     296linebreaks.needs_autoescape = True
    257297
    258 def linebreaksbr(value):
     298def linebreaksbr(value, autoescape = None):
    259299    "Converts newlines into <br />s"
    260     return value.replace('\n', '<br />')
     300    if autoescape and not isinstance(value, SafeData):
     301        from django.utils.html import escape
     302        data = escape(value)
     303    else:
     304        data = value
     305    return mark_safe(data.replace('\n', '<br />'))
    261306linebreaksbr = stringfilter(linebreaksbr)
     307linebreaksbr.is_safe = True
     308linebreaksbr.needs_autoescape = True
     309
     310def safe(value):
     311    "Marks the value as a string that should not be auto-escaped."
     312    from django.utils.safestring import mark_safe
     313    return mark_safe(value)
     314safe = stringfilter(safe)
     315safe.is_safe = True
    262316
    263317def removetags(value, tags):
    264318    "Removes a space separated list of [X]HTML tags from the output"
    def removetags(value, tags):  
    270324    value = endtag_re.sub(u'', value)
    271325    return value
    272326removetags = stringfilter(removetags)
     327removetags.is_safe = True
    273328
    274329def striptags(value):
    275330    "Strips all [X]HTML tags"
    276331    from django.utils.html import strip_tags
    277332    return strip_tags(value)
    278333striptags = stringfilter(striptags)
     334striptags.is_safe = True
    279335
    280336###################
    281337# LISTS           #
    def dictsort(value, arg):  
    289345    decorated = [(resolve_variable(u'var.' + arg, {u'var' : item}), item) for item in value]
    290346    decorated.sort()
    291347    return [item[1] for item in decorated]
     348dictsort.is_safe = False
    292349
    293350def dictsortreversed(value, arg):
    294351    """
    def dictsortreversed(value, arg):  
    299356    decorated.sort()
    300357    decorated.reverse()
    301358    return [item[1] for item in decorated]
     359dictsortreversed.is_safe = False
    302360
    303361def first(value):
    304362    "Returns the first item in a list"
    def first(value):  
    306364        return value[0]
    307365    except IndexError:
    308366        return u''
     367first.is_safe = True
    309368
    310369def join(value, arg):
    311370    "Joins a list with a string, like Python's ``str.join(list)``"
    312371    try:
    313         return arg.join(map(force_unicode, value))
     372        data = arg.join(map(force_unicode, value))
    314373    except AttributeError: # fail silently but nicely
    315374        return value
     375    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     376    if safe_args:
     377        return mark_safe(data)
     378    else:
     379        return data
     380join.is_safe = True
    316381
    317382def length(value):
    318383    "Returns the length of the value - useful for lists"
    319384    return len(value)
     385length.is_safe = False
    320386
    321387def length_is(value, arg):
    322388    "Returns a boolean of whether the value's length is the argument"
    323389    return len(value) == int(arg)
     390length.is_safe = False
    324391
    325392def random(value):
    326393    "Returns a random item from the list"
    327394    return random_module.choice(value)
     395length.is_safe = True
    328396
    329397def slice_(value, arg):
    330398    """
    def slice_(value, arg):  
    345413
    346414    except (ValueError, TypeError):
    347415        return value # Fail silently.
     416slice_.is_safe = True
    348417
    349 def unordered_list(value):
     418def unordered_list(value, autoescape = None):
    350419    """
    351420    Recursively takes a self-nested list and returns an HTML unordered list --
    352421    WITHOUT opening and closing <ul> tags.
    def unordered_list(value):  
    367436        </ul>
    368437        </li>
    369438    """
     439    if autoescape:
     440        from django.utils.html import conditional_escape
     441        escaper = conditional_escape
     442    else:
     443        escaper = lambda x: x
     444
    370445    def _helper(value, tabs):
    371446        indent = u'\t' * tabs
    372447        if value[1]:
    373             return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, force_unicode(value[0]), indent,
     448            return u'%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(force_unicode(value[0])), indent,
    374449                u'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    375450        else:
    376             return u'%s<li>%s</li>' % (indent, force_unicode(value[0]))
    377     return _helper(value, 1)
     451            return u'%s<li>%s</li>' % (indent, escaper(force_unicode(value[0])))
     452    return mark_safe(_helper(value, 1))
     453unordered_list.is_safe = True
     454unordered_list.needs_autoescape = True
    378455
    379456###################
    380457# INTEGERS        #
    ###################  
    383460def add(value, arg):
    384461    "Adds the arg to the value"
    385462    return int(value) + int(arg)
     463add.is_safe = False
    386464
    387465def get_digit(value, arg):
    388466    """
    def get_digit(value, arg):  
    402480        return int(str(value)[-arg])
    403481    except IndexError:
    404482        return 0
     483get_digit.is_safe = False
    405484
    406485###################
    407486# DATES           #
    def date(value, arg=None):  
    415494    if arg is None:
    416495        arg = settings.DATE_FORMAT
    417496    return format(value, arg)
     497date.is_safe = False
    418498
    419499def time(value, arg=None):
    420500    "Formats a time according to the given format"
    def time(value, arg=None):  
    424504    if arg is None:
    425505        arg = settings.TIME_FORMAT
    426506    return time_format(value, arg)
     507time.is_safe = False
    427508
    428509def timesince(value, arg=None):
    429510    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    def timesince(value, arg=None):  
    433514    if arg:
    434515        return timesince(arg, value)
    435516    return timesince(value)
     517timesince.is_safe = False
    436518
    437519def timeuntil(value, arg=None):
    438520    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
    def timeuntil(value, arg=None):  
    443525    if arg:
    444526        return timesince(arg, value)
    445527    return timesince(datetime.now(), value)
     528timeuntil.is_safe = False
    446529
    447530###################
    448531# LOGIC           #
    ###################  
    451534def default(value, arg):
    452535    "If value is unavailable, use given default"
    453536    return value or arg
     537default.is_safe = False
    454538
    455539def default_if_none(value, arg):
    456540    "If value is None, use given default"
    457541    if value is None:
    458542        return arg
    459543    return value
     544default_if_none.is_safe = False
    460545
    461546def divisibleby(value, arg):
    462547    "Returns true if the value is devisible by the argument"
    463548    return int(value) % int(arg) == 0
     549divisibleby.is_safe = False
    464550
    465551def yesno(value, arg=None):
    466552    """
    def yesno(value, arg=None):  
    491577    if value:
    492578        return yes
    493579    return no
     580yesno.is_safe = False
    494581
    495582###################
    496583# MISC            #
    def filesizeformat(bytes):  
    513600    if bytes < 1024 * 1024 * 1024:
    514601        return ugettext("%.1f MB") % (bytes / (1024 * 1024))
    515602    return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
     603filesizeformat.is_safe = True
    516604
    517605def pluralize(value, arg=u's'):
    518606    """
    def pluralize(value, arg=u's'):  
    540628        except TypeError: # len() of unsized object
    541629            pass
    542630    return singular_suffix
     631pluralize.is_safe = False
    543632
    544633def phone2numeric(value):
    545634    "Takes a phone number and converts it in to its numerical equivalent"
    546635    from django.utils.text import phone2numeric
    547636    return phone2numeric(value)
     637phone2numeric.is_safe = True
    548638
    549639def pprint(value):
    550640    "A wrapper around pprint.pprint -- for debugging, really"
    def pprint(value):  
    553643        return pformat(value)
    554644    except Exception, e:
    555645        return u"Error in formatting:%s" % force_unicode(e)
     646pprint.is_safe = True
    556647
    557648# Syntax: register.filter(name of filter, callback)
    558649register.filter(add)
    register.filter(filesizeformat)  
    571662register.filter(first)
    572663register.filter(fix_ampersands)
    573664register.filter(floatformat)
     665register.filter(force_escape)
    574666register.filter(get_digit)
    575667register.filter(iriencode)
    576668register.filter(join)
    register.filter(pprint)  
    588680register.filter(removetags)
    589681register.filter(random)
    590682register.filter(rjust)
     683register.filter(safe)
    591684register.filter('slice', slice_)
    592685register.filter(slugify)
    593686register.filter(stringformat)
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index f2453dff4ac7b556dbe8b5b73dc1b9d42dd82f04..83ee973213ebd85e3db054e56a403d7ee1c8c667 100644
    a b from django.template import TemplateSynt  
    55from django.template import get_library, Library, InvalidTemplateLibrary
    66from django.conf import settings
    77from django.utils.encoding import smart_str
     8from django.utils.safestring import mark_safe
    89import sys
    910
    1011register = Library()
    1112
     13class AutoEscapeControlNode(Node):
     14    """Implements the actions of both the autoescape and noautescape tags."""
     15    def __init__(self, setting, nodelist):
     16        self.setting, self.nodelist = setting, nodelist
     17
     18    def render(self, context):
     19        old_setting = context.autoescape
     20        context.autoescape = self.setting
     21        output = self.nodelist.render(context)
     22        context.autoescape = old_setting
     23        if self.setting:
     24            return mark_safe(output)
     25        else:
     26            return output
     27
    1228class CommentNode(Node):
    1329    def render(self, context):
    1430        return ''
    class RegroupNode(Node):  
    238254            return ''
    239255        output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
    240256        for obj in obj_list:
    241             grouper = self.expression.resolve(Context({'var': obj}), True)
     257            ctxt = Context({'var': obj})
     258            ctxt.autoescape = context.autoescape
     259            grouper = self.expression.resolve(ctxt, True)
    242260            # TODO: Is this a sensible way to determine equality?
    243261            if output and repr(output[-1]['grouper']) == repr(grouper):
    244262                output[-1]['list'].append(obj)
    class WithNode(Node):  
    376394        return output
    377395
    378396#@register.tag
     397def autoescape(parser, token):
     398    """
     399    Force autoescape behaviour for this block.
     400    """
     401    nodelist = parser.parse(('endautoescape',))
     402    parser.delete_first_token()
     403    return AutoEscapeControlNode(True, nodelist)
     404autoescape = register.tag(autoescape)
     405
     406#@register.tag
    379407def comment(parser, token):
    380408    """
    381409    Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
    def do_filter(parser, token):  
    478506
    479507    Sample usage::
    480508
    481         {% filter escape|lower %}
     509        {% filter force_escape|lower %}
    482510            This text will be HTML-escaped, and will appear in lowercase.
    483511        {% endfilter %}
    484512    """
    485513    _, rest = token.contents.split(None, 1)
    486514    filter_expr = parser.compile_filter("var|%s" % (rest))
     515    for func, unused in filter_expr.filters:
     516        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     517            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    487518    nodelist = parser.parse(('endfilter',))
    488519    parser.delete_first_token()
    489520    return FilterNode(filter_expr, nodelist)
    def ifchanged(parser, token):  
    725756ifchanged = register.tag(ifchanged)
    726757
    727758#@register.tag
     759def noautoescape(parser, token):
     760    """
     761    Force autoescape behaviour to be disabled for this block.
     762    """
     763    nodelist = parser.parse(('endnoautoescape',))
     764    parser.delete_first_token()
     765    return AutoEscapeControlNode(False, nodelist)
     766autoescape = register.tag(noautoescape)
     767
     768#@register.tag
    728769def ssi(parser, token):
    729770    """
    730771    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 8601dc44f3b60aacef2bbf5b209d60f32808e4df..d88d69931caabd8555fea15a784a751f0010943b 100644
    a b def force_unicode(s, encoding='utf-8', e  
    3333        else:
    3434            s = unicode(str(s), encoding, errors)
    3535    elif not isinstance(s, unicode):
    36         s = unicode(s, encoding, errors)
     36        s = s.decode(encoding, errors)
    3737    return s
    3838
    3939def 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 1dde298c297a8d4bbd24d8c53334a587dd789c54..2fb8298e458364f8b3f2614df6966d328f2e4c7c 100644
    a b  
    33import re
    44import string
    55import urllib
     6from django.utils.encoding import smart_unicode
     7from django.utils.safestring import SafeData
    68from django.utils.encoding import force_unicode, smart_str
    79from django.utils.functional import allow_lazy
    810
    def escape(html):  
    3032    return force_unicode(html).replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;').replace("'", '&#39;')
    3133escape = allow_lazy(escape, unicode)
    3234
    33 def linebreaks(value):
     35def conditional_escape(html):
     36    "Similar to escape(), except that it does not operate on pre-escaped strings"
     37    if isinstance(html, SafeData):
     38        return html
     39    else:
     40        return escape(html)
     41
     42def linebreaks(value, autoescape = False):
    3443    "Converts newlines into <p> and <br />s"
    3544    value = re.sub(r'\r\n|\r|\n', '\n', force_unicode(value)) # normalize newlines
    3645    paras = re.split('\n{2,}', value)
    37     paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
     46    if autoescape:
     47        paras = [u'<p>%s</p>' % escape(p.strip()).replace('\n', '<br />') for p in paras]
     48    else:
     49        paras = [u'<p>%s</p>' % p.strip().replace('\n', '<br />') for p in paras]
    3850    return u'\n\n'.join(paras)
    3951linebreaks = allow_lazy(linebreaks, unicode)
    4052
  • 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 8fe4c82d43460be2e347eaa878c0cbbbaaad6b49..36888bd60f9cc37646e0f38bf2af1a25a0918576 100644
    a b it also defines the content that fills t  
    268268two similarly-named ``{% block %}`` tags in a template, that template's parent
    269269wouldn't know which one of the blocks' content to use.
    270270
     271Automatic HTML escaping
     272=======================
     273
     274A very real problem when creating HTML (and other) output using templates and
     275variable substitution is the possibility of accidently inserting some variable
     276value that affects the resulting HTML. For example, a template fragment like
     277
     278::
     279
     280    Hello, {{ name }}.
     281
     282seems like a harmless way to display the user's name. However, if you are
     283displaying data that the user entered directly and they entered their name as
     284
     285::
     286
     287    <script>alert('hello')</script>
     288
     289this would always display a Javascript alert box whenever the page was loaded.
     290Similarly, if you were displaying some data generated by another process and
     291it contained a '<' symbol, you couldn't just dump this straight into your
     292HTML, because it would be treated as the start of an element.  The effects of
     293these sorts of problems can vary from merely annoying to allowing exploits via
     294`Cross Site Scripting`_ (XSS) attacks.
     295
     296.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
     297
     298In order to provide some protection against these problems, Django provides an
     299auto-escaping template tag. Inside this tag, any data that comes from template
     300variables is examined to see if it contains one of the five HTML characters
     301(<, >, ', " and &) that often need escaping and those characters are converted
     302to their respective HTML entities.
     303
     304Because some variables will contain data that is *intended* to be rendered
     305as HTML, template tag and filter writers can mark their output strings as
     306requiring no further escaping. For example, the ``unordered_list`` filter is
     307designed to return raw HTML and we want the template processor to simply
     308display the results as returned, without applying any escaping. That is taken
     309care of by the filter. The template author need do nothing special in that
     310case.
     311
     312By default, auto-escaping is not in effect. To enable it inside your template,
     313wrap the affected content in the ``autoescape`` tag, like so::
     314
     315    {% autoescape %}
     316        Hello {{ name }}
     317    {% endautoescape %}
     318
     319Since the auto-escaping tag passes its effect onto templates that extend the
     320current one as well as templates included via the ``include`` tag (just like
     321all block tags), if you wrap your main HTML content in an ``autoescape`` tag,
     322you will have automatic escaping applied to all of your content.
     323
     324At times, you might want to disable auto-escaping when it would otherwise be
     325in effect. You can do this with the ``noautoescape`` tag. For example::
     326
     327    {% autoescape %}
     328        Hello {{ name }}
     329
     330        {% noautoescape %}
     331            This will not be auto-escaped: {{ data }}.
     332
     333            Nor this: {{ other_data }}
     334        {% endnoautoescape %}
     335    {% endautoescape %}
     336
     337For individual variables, the ``safe`` filter can also be used.
     338
     339Generally, you will not need to worry about auto-escaping very much. Enable it
     340in your base template once you are entering the main HTML region and then
     341write your templates normally. The view developers and custom filter authors
     342need to think about when their data should not be escaped and mark it
     343appropriately.  They are in a better position to know when that should happen
     344than the template author, so it is their responsibility. By default, when
     345auto-escaping is enabled, all output is escaped unless the template processor
     346is explicitly told otherwise.
     347
    271348Using the built-in reference
    272349============================
    273350
    available, and what they do.  
    343420Built-in tag reference
    344421----------------------
    345422
     423autoescape
     424~~~~~~~~~~
     425
     426All variables that are output inside this tag have HTML escaping applied to
     427them, as if they each had the ``escape`` filter attached to them.
     428
     429The only exceptions are variables that are already marked as 'safe' from
     430escaping, either by the code that populated the variable, or because it has
     431the ``safe`` filter applied.
     432
    346433block
    347434~~~~~
    348435
    just like in variable syntax.  
    410497
    411498Sample usage::
    412499
    413     {% filter escape|lower %}
     500    {% filter force_escape|lower %}
    414501        This text will be HTML-escaped, and will appear in all lowercase.
    415502    {% endfilter %}
    416503
    Load a custom template tag set.  
    625712
    626713See `Custom tag and filter libraries`_ for more information.
    627714
     715noautoescape
     716~~~~~~~~~~~~
     717
     718Disable the effects of the ``autoescape`` tag (if it is in effect).
     719
    628720now
    629721~~~
    630722
    Escapes a string's HTML. Specifically, i  
    9621054    * ``'"'`` (double quote) to ``'&quot;'``
    9631055    * ``"'"`` (single quote) to ``'&#39;'``
    9641056
     1057The escaping is only applied when the string is output, so it does not matter
     1058where in a chained sequence of filters you put ``escape``: it will always be
     1059applied as though it were the last filter. If you want escaping to be applied
     1060immediately, use the ``force_escape`` filter.
     1061
    9651062filesizeformat
    9661063~~~~~~~~~~~~~~
    9671064
    For example:  
    10041101Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
    10051102an argument of ``-1``.
    10061103
     1104force_escape
     1105~~~~~~~~~~~~
     1106
     1107**New in Django development version**
     1108
     1109Applies HTML escaping to a string (see the ``escape`` filter for details).
     1110This filter is applied immediately and returns a new, escaped string. This is
     1111useful in the typically rare cases where you need multiple escaping or want to
     1112apply other filters to the escaped results. Normally, you want to use the
     1113``escape`` filter.
     1114
    10071115get_digit
    10081116~~~~~~~~~
    10091117
    Right-aligns the value in a field of a g  
    11251233
    11261234**Argument:** field size
    11271235
     1236safe
     1237~~~~
     1238
     1239Marks a string as not requiring further HTML escaping prior to output. This is
     1240only useful inside an ``autoescape`` block, when the output would otherwise be
     1241automatically escaped. Outside of an ``autoescape`` block, this filter has no
     1242effect.
     1243
    11281244slice
    11291245~~~~~
    11301246
  • docs/templates_python.txt

    diff --git a/docs/templates_python.txt b/docs/templates_python.txt
    index 08a287f572d40cb0a628e144c2824a8137f4d6f9..f3181e6c688e1e5a0b7d4be0ac46ad95b9aa8df4 100644
    a b an object to it's string value before be  
    669669    def lower(value):
    670670        return value.lower()
    671671
     672Filters and auto-escaping
     673~~~~~~~~~~~~~~~~~~~~~~~~~
     674
     675When you are writing a custom filter, you need to give some thought to how
     676this filter will work when rendered in an auto-escaping environment (inside
     677an ``autoescape`` template tag block). First, you should realise that there
     678are three types of strings that can be passed around inside the template code:
     679
     680 * raw strings are the native Python ``str`` (or ``unicode``) types. On
     681   output, they are escaped if they are inside an ``autoescape`` block.
     682 * "safe" strings are strings that are safe from further escaping at output
     683   time. Any necessary escaping has already been done. They are commonly used
     684   for output that contains raw HTML that is intended to be intrepreted on the
     685   client side.
     686
     687   Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
     688   although they share a common base class in ``SafeData``, so you can test
     689   for them using code like::
     690
     691    if isinstance(value, SafeData):
     692        # Do something with the "safe" string.
     693
     694 * strings which are marked as "need escaping" are *always* escaped on
     695   output, regardless of whether they are in an ``autoescape`` block or not.
     696   These strings are only escaped once, however, even if used inside an
     697   ``autoescaep`` block.  This type of string is internally represented by the
     698   types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to
     699   worry about these; they exist only for the implementation of the ``escape``
     700   filter.
     701
     702Inside your filter, you will need to think about three areas in order to be
     703auto-escaping compliant:
     704
     705 1. If your filter returns a string that is ready for direct output (it should
     706 be considered a "safe" string), you should call
     707 ``django.utils.safestring.mark_safe()`` on the result prior to returning.
     708 This will turn the result into the appropriate ``SafeData`` type.
     709
     710 2. If your filter is given a "safe" string, is it guaranteed to return a
     711 "safe" string? If so, set the ``is_safe`` attribute on the function to be
     712 ``True``. For example, a filter that replaced all numbers with the number
     713 spelt out in words is going to be safe-string-preserving, since it cannot
     714 introduce any of the five dangerous characters: <, >, ", ' or &. So we can
     715 write::
     716
     717    @register.filter
     718    def convert_to_words(value):
     719        # ... implementation here ...
     720        return result
     721
     722    convert_to_words.is_safe = True
     723
     724 Note that this filter does not return a universally safe result (it does not
     725 return ``mark_safe(result)``) because if it is handed a raw string such as
     726 '<a>', this will need further escaping in an auto-escape environment. The
     727 ``is_safe`` attribute only talks about the safeness of the result when a safe
     728 string is passed in to the filter.
     729
     730 3. Will your filter behave differently depending upon whether auto-escaping
     731 is currently in effect or not? For example, the ``ordered_list`` filter that
     732 ships with Django needs to know whether to escape its content or not. It will
     733 always return a safe string, since it returns raw HTML, so we cannot apply
     734 escaping to the result -- it needs to be done in-situ.
     735
     736 For these cases, the filter function needs to be told what the current
     737 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
     738 filter to ``True`` and have your function take an extra argument called
     739 ``autoescape`` with a default value of ``None``. When the filter is called,
     740 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
     741 effect. For example, the ``unordered_list`` filter is written as::
     742
     743    def unordered_list(value, autoescape = None):
     744        # ... lots of code here ...
     745
     746        return mark_safe(...)
     747
     748    unordered_list.is_safe = True
     749    unordered_list.needs_autoescape = True
     750
     751By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
     752``False``. You do not need to specify them if ``False`` is an acceptable
     753value.
     754
     755As a matter of convention, we leave ``is_safe`` as ``False`` for filters that
     756do not accept string inputs (they might take a number as an input, for
     757example) or for those that return a non-string (e.g. the ``length`` filter).
     758However, not following this convention will not cause any harm or make your
     759results any more vulnerable to cross-site scripting problems.
     760
    672761Writing custom template tags
    673762----------------------------
    674763
    Ultimately, this decoupling of compilati  
    787876efficient template system, because a template can render multiple context
    788877without having to be parsed multiple times.
    789878
     879Auto-escaping considerations
     880~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     881
     882The output from template tags is not automatically run through the
     883auto-escaping filters if used inside an ``autoescape`` tag. However, there are
     884still a couple of things you should keep in mind when writing a template tag:
     885
     886If the ``render()`` function of your template stores the result in a context
     887variable (rather than returning the result in a string), it should take care
     888to call ``mark_safe()`` if appropriate. When the variable is ultimately
     889rendered, it will be affected by the auto-escape setting in effect at the
     890time, so content that should be safe from further escaping needs to be marked
     891as such.
     892
     893Also, if your template tag creates a new context for performing some
     894sub-rendering, you should be careful to set the auto-escape variable to the
     895current context's value. For example::
     896
     897    def render(self, context):
     898        # ...
     899        new_context = Context({'var': obj})
     900        new_context.autoescape = context.autoescape
     901        # ... Do something with new_context ...
     902
     903This is not a very common situation, but it is sometimes useful (see
     904``django.templates.defaulttags.FilterNode.render()`` for an example).
     905
    790906Registering the tag
    791907~~~~~~~~~~~~~~~~~~~
    792908
  • 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..d8fff6800c81bed694debbd5c97efb03d8e7109c
    - +  
     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:"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>'),
     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            # Escaped string as argument
     198            'filter-syntax10': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote&quot; hah'),
     199
     200        })
     201
     202        return TEMPLATE_TESTS
     203
     204
     205   
     206
     207#def test_template_loader(template_name, template_dirs=None):
     208    #"A custom template loader that loads the unit-test templates."
     209    #try:
     210        #return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     211    #except KeyError:
     212        #raise template.TemplateDoesNotExist, template_name
     213
     214#def run_tests(verbosity=0, standalone=False):
     215    ## Register our custom template loader.
     216    #old_template_loaders = loader.template_source_loaders
     217    #loader.template_source_loaders = [test_template_loader]
     218
     219    #failed_tests = []
     220    #tests = TEMPLATE_TESTS.items()
     221    #tests.sort()
     222
     223    ## Turn TEMPLATE_DEBUG off, because tests assume that.
     224    #old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     225    ## Set TEMPLATE_STRING_IF_INVALID to a known string
     226    #old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
     227   
     228    #for name, vals in tests:
     229        #install()
     230        #if 'LANGUAGE_CODE' in vals[1]:
     231            #activate(vals[1]['LANGUAGE_CODE'])
     232        #else:
     233            #activate('en-us')
     234        #try:
     235            #ctxt = template.Context(vals[1])
     236            ## Hack for testing: force autoescaping to be in effect.
     237            #ctxt.autoescape = True
     238            #output = loader.get_template(name).render(ctxt)
     239        #except Exception, e:
     240            #if e.__class__ == vals[2]:
     241                #if verbosity:
     242                    #print "Template test: %s -- Passed" % name
     243            #else:
     244                #if verbosity:
     245                    #traceback.print_exc()
     246                    #print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
     247                #failed_tests.append(name)
     248            #continue
     249        #if 'LANGUAGE_CODE' in vals[1]:
     250            #deactivate()
     251        #if output == vals[2]:
     252            #if verbosity:
     253                #print "Template test: %s -- Passed" % name
     254        #else:
     255            #if verbosity:
     256                #print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
     257            #failed_tests.append(name)
     258    #loader.template_source_loaders = old_template_loaders
     259    #deactivate()
     260    #settings.TEMPLATE_DEBUG = old_td
     261    #settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     262
     263    #if failed_tests and not standalone:
     264        #msg = "Template tests %s failed." % failed_tests
     265        #if not verbosity:
     266            #msg += "  Re-run tests with -v1 to see actual failures"
     267        #raise Exception, msg
     268
     269if __name__ == "__main__":
     270    unittest.main()
  • tests/regressiontests/defaultfilters/tests.py

    diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
    index d2335dd87b727dd68bf17dc970596eb104d75938..47e8e24ddd76e41d5e408c7b29cebd17c9d22c96 100644
    a b u'a stri to be maled'  
    168168>>> cut(u'a string to be mangled', 'strings')
    169169u'a string to be mangled'
    170170
    171 >>> escape(u'<some html & special characters > here')
     171>>> force_escape(u'<some html & special characters > here')
    172172u'&lt;some html &amp; special characters &gt; here'
    173173
    174 >>> escape(u'<some html & special characters > here ĐÅ€£')
     174>>> force_escape(u'<some html & special characters > here ĐÅ€£')
    175175u'&lt;some html &amp; special characters &gt; here \xc4\x90\xc3\x85\xe2\x82\xac\xc2\xa3'
    176176
    177177>>> linebreaks(u'line 1')
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 141e5124099c198f99dd2d6de75ffd841349b0cb..1f9552e1cd03c8c119997e3408ea3033cdfe93e6 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):  
    725798            'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
    726799        }
    727800
    728         # Register our custom template loader.
    729         def test_template_loader(template_name, template_dirs=None):
    730             "A custom template loader that loads the unit-test templates."
    731             try:
    732                 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
    733             except KeyError:
    734                 raise template.TemplateDoesNotExist, template_name
    735 
    736         old_template_loaders = loader.template_source_loaders
    737         loader.template_source_loaders = [test_template_loader]
    738 
    739         failures = []
    740         tests = TEMPLATE_TESTS.items()
    741         tests.sort()
    742 
    743         # Turn TEMPLATE_DEBUG off, because tests assume that.
    744         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
    745 
    746         # Set TEMPLATE_STRING_IF_INVALID to a known string
    747         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
    748         expected_invalid_str = 'INVALID'
    749 
    750         for name, vals in tests:
    751             install()
    752 
    753             if isinstance(vals[2], tuple):
    754                 normal_string_result = vals[2][0]
    755                 invalid_string_result = vals[2][1]
    756                 if '%s' in invalid_string_result:
    757                     expected_invalid_str = 'INVALID %s'
    758                     invalid_string_result = invalid_string_result % vals[2][2]
    759                     template.invalid_var_format_string = True
    760             else:
    761                 normal_string_result = vals[2]
    762                 invalid_string_result = vals[2]
    763 
    764             if 'LANGUAGE_CODE' in vals[1]:
    765                 activate(vals[1]['LANGUAGE_CODE'])
    766             else:
    767                 activate('en-us')
    768 
    769             for invalid_str, result in [('', normal_string_result),
    770                                         (expected_invalid_str, invalid_string_result)]:
    771                 settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    772                 try:
    773                     output = loader.get_template(name).render(template.Context(vals[1]))
    774                 except Exception, e:
    775                     if e.__class__ != result:
    776                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    777                     continue
    778                 if output != result:
    779                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
    780 
    781             if 'LANGUAGE_CODE' in vals[1]:
    782                 deactivate()
    783 
    784             if template.invalid_var_format_string:
    785                 expected_invalid_str = 'INVALID'
    786                 template.invalid_var_format_string = False
    787 
    788         loader.template_source_loaders = old_template_loaders
    789         deactivate()
    790         settings.TEMPLATE_DEBUG = old_td
    791         settings.TEMPLATE_STRING_IF_INVALID = old_invalid
    792 
    793         self.assertEqual(failures, [], '\n'.join(failures))
    794 
    795801if __name__ == "__main__":
    796802    unittest.main()
Back to Top