Ticket #2359: rev5774-01-core-changes.diff

File rev5774-01-core-changes.diff, 73.1 KB (added by mir@…, 17 years ago)

updated patch

  • django/oldforms/__init__.py

    From nobody Mon Sep 17 00:00:00 2001
    From: Michael Radziej <mir@noris.de>
    Date: Mon Jul 30 12:44:06 2007 +0200
    Subject: [PATCH] autoescape 1
    
    Refreshed patch autoescape-1.
    (Base: 34b59671f77f8e7e9b839bae4959592448e59b5e)
    (Last: d477ce3e5467f90b1145c5dbe26537617ddb4e7e)
    
    ---
    
     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                    |  131 ++++++++++++
     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, 927 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 8a9d2313722ddf02d5e1153eeb49d74ee55c4cd7
    last f85821b7ba98b464a787255deeebcbe82c81fc71
    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..aa6beda948a4702b8b1f719d7230445a23b8e0e5
    - +  
     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, Promise
     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, Promise):
     106        if s._delegate_str:
     107            return SafeString(s)
     108        else:
     109            return SafeUnicode(s)
     110    if isinstance(s, str):
     111        return SafeString(s)
     112    if isinstance(s, unicode):
     113        return SafeUnicode(s)
     114    return SafeString(str(s))
     115
     116def mark_for_escaping(s):
     117    """
     118    Explicitly mark a string as requiring HTML escaping upon output. Has no
     119    effect on SafeData subclasses.
     120
     121    Can be safely called multiple times on a single string (the effect is only
     122    applied once).
     123    """
     124    if isinstance(s, SafeData) or isinstance(s, EscapeData):
     125        return s
     126    if isinstance(s, str):
     127        return EscapeString(s)
     128    if isinstance(s, unicode):
     129        return EscapeUnicode(s)
     130    return EscapeString(str(s))
     131
  • 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 0f7ac2f352b9e0833f61a1e9f4c4acd849a8dd30..94edbc8a12297d693bb20c5566be578832359b45 100644
    a b class Templates(unittest.TestCase):  
    120120                                  ['/dir1/index.html'])
    121121
    122122    def test_templates(self):
     123        TEMPLATE_TESTS = self.get_template_tests()
     124
     125        # Register our custom template loader.
     126        def test_template_loader(template_name, template_dirs=None):
     127            "A custom template loader that loads the unit-test templates."
     128            try:
     129                return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     130            except KeyError:
     131                raise template.TemplateDoesNotExist, template_name
     132
     133        old_template_loaders = loader.template_source_loaders
     134        loader.template_source_loaders = [test_template_loader]
     135
     136        failures = []
     137        tests = TEMPLATE_TESTS.items()
     138        tests.sort()
     139
     140        # Turn TEMPLATE_DEBUG off, because tests assume that.
     141        old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     142
     143        # Set TEMPLATE_STRING_IF_INVALID to a known string
     144        old_invalid = settings.TEMPLATE_STRING_IF_INVALID
     145        expected_invalid_str = 'INVALID'
     146
     147        for name, vals in tests:
     148            install()
     149
     150            if isinstance(vals[2], tuple):
     151                normal_string_result = vals[2][0]
     152                invalid_string_result = vals[2][1]
     153                if '%s' in invalid_string_result:
     154                    expected_invalid_str = 'INVALID %s'
     155                    invalid_string_result = invalid_string_result % vals[2][2]
     156                    template.invalid_var_format_string = True
     157            else:
     158                normal_string_result = vals[2]
     159                invalid_string_result = vals[2]
     160
     161            if 'LANGUAGE_CODE' in vals[1]:
     162                activate(vals[1]['LANGUAGE_CODE'])
     163            else:
     164                activate('en-us')
     165
     166            for invalid_str, result in [('', normal_string_result),
     167                                        (expected_invalid_str, invalid_string_result)]:
     168                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
     169                try:
     170                    test_template = loader.get_template(name)
     171                    output = self.render(test_template, vals)
     172                except Exception, e:
     173                    if e.__class__ != result:
     174                        failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
     175                    continue
     176                if output != result:
     177                    failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
     178
     179            if 'LANGUAGE_CODE' in vals[1]:
     180                deactivate()
     181               
     182            if template.invalid_var_format_string:
     183                expected_invalid_str = 'INVALID'
     184                template.invalid_var_format_string = False
     185
     186        loader.template_source_loaders = old_template_loaders
     187        deactivate()
     188        settings.TEMPLATE_DEBUG = old_td
     189        settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     190
     191        self.assertEqual(failures, [], '\n'.join(failures))
     192
     193    def render(self, test_template, vals):
     194        return test_template.render(template.Context(vals[1]))
     195
     196    def get_template_tests(self):
    123197        # NOW and NOW_tz are used by timesince tag tests.
    124198        NOW = datetime.now()
    125199        NOW_tz = datetime.now(LocalTimezone(datetime.now()))
    126200
    127201        # SYNTAX --
    128202        # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
    129         TEMPLATE_TESTS = {
    130 
     203        return {
    131204            ### BASIC SYNTAX ##########################################################
    132205
    133206            # Plain text should go through the template parser untouched
    class Templates(unittest.TestCase):  
    788861            'url-fail03' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
    789862        }
    790863
    791         # Register our custom template loader.
    792         def test_template_loader(template_name, template_dirs=None):
    793             "A custom template loader that loads the unit-test templates."
    794             try:
    795                 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
    796             except KeyError:
    797                 raise template.TemplateDoesNotExist, template_name
    798 
    799         old_template_loaders = loader.template_source_loaders
    800         loader.template_source_loaders = [test_template_loader]
    801 
    802         failures = []
    803         tests = TEMPLATE_TESTS.items()
    804         tests.sort()
    805 
    806         # Turn TEMPLATE_DEBUG off, because tests assume that.
    807         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
    808 
    809         # Set TEMPLATE_STRING_IF_INVALID to a known string
    810         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
    811         expected_invalid_str = 'INVALID'
    812 
    813         for name, vals in tests:
    814             install()
    815 
    816             if isinstance(vals[2], tuple):
    817                 normal_string_result = vals[2][0]
    818                 invalid_string_result = vals[2][1]
    819                 if '%s' in invalid_string_result:
    820                     expected_invalid_str = 'INVALID %s'
    821                     invalid_string_result = invalid_string_result % vals[2][2]
    822                     template.invalid_var_format_string = True
    823             else:
    824                 normal_string_result = vals[2]
    825                 invalid_string_result = vals[2]
    826 
    827             if 'LANGUAGE_CODE' in vals[1]:
    828                 activate(vals[1]['LANGUAGE_CODE'])
    829             else:
    830                 activate('en-us')
    831 
    832             for invalid_str, result in [('', normal_string_result),
    833                                         (expected_invalid_str, invalid_string_result)]:
    834                 settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    835                 try:
    836                     output = loader.get_template(name).render(template.Context(vals[1]))
    837                 except Exception, e:
    838                     if e.__class__ != result:
    839                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    840                     continue
    841                 if output != result:
    842                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
    843 
    844             if 'LANGUAGE_CODE' in vals[1]:
    845                 deactivate()
    846 
    847             if template.invalid_var_format_string:
    848                 expected_invalid_str = 'INVALID'
    849                 template.invalid_var_format_string = False
    850 
    851         loader.template_source_loaders = old_template_loaders
    852         deactivate()
    853         settings.TEMPLATE_DEBUG = old_td
    854         settings.TEMPLATE_STRING_IF_INVALID = old_invalid
    855 
    856         self.assertEqual(failures, [], '\n'.join(failures))
    857 
    858864if __name__ == "__main__":
    859865    unittest.main()
Back to Top