Ticket #2359: 01-core-changes.diff

File 01-core-changes.diff, 60.8 KB (added by Malcolm Tredinnick, 18 years ago)

Update core changes (supercedes previous patch)

  • django/forms/__init__.py

    Core changes required to support auto-escaping tag.
    
    From:  <>
    
    * makes context auto-escaping aware
    * adds "autoescape" and "noautoescape" template tags
    * changes all existing filters to work in autoescaping environ
    * adds "force_escape" and "safe" filters.
    * adds tests for all filters in auto-escaping environ
    * fixes existing template tag tests to work with changes
    * adds documentation for template authors and template filter developers
    ---
    
     django/forms/__init__.py           |   45 +++---
     django/template/__init__.py        |   26 +++-
     django/template/context.py         |    4 +
     django/template/defaultfilters.py  |  129 ++++++++++++++++--
     django/template/defaulttags.py     |   49 +++++++
     django/utils/html.py               |   15 ++
     django/utils/safestring.py         |  106 +++++++++++++++
     docs/templates.txt                 |  116 ++++++++++++++++-
     docs/templates_python.txt          |  116 +++++++++++++++++
     tests/othertests/autoescape.py     |  252 ++++++++++++++++++++++++++++++++++++
     tests/othertests/defaultfilters.py |    2 
     11 files changed, 809 insertions(+), 51 deletions(-)
    
    diff --git a/django/forms/__init__.py b/django/forms/__init__.py
    index 730f7a5..766db8e 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 gettext, gettext_lazy, ngettext
    67
    class FormFieldWrapper(object):  
    175176
    176177    def html_error_list(self):
    177178        if self.errors():
    178             return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
     179            return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
    179180        else:
    180             return ''
     181            return mark_safe('')
    181182
    182183    def get_id(self):
    183184        return self.formfield.get_id()
    class FormFieldCollection(FormFieldWrapp  
    209210        return bool(len(self.errors()))
    210211
    211212    def html_combined_error_list(self):
    212         return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
     213        return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
    213214
    214215class InlineObjectCollection(object):
    215216    "An object that acts like a sparse list of form field collections."
    class TextField(FormField):  
    393394            maxlength = 'maxlength="%s" ' % self.maxlength
    394395        if isinstance(data, unicode):
    395396            data = data.encode(settings.DEFAULT_CHARSET)
    396         return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
     397        return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
    397398            (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    398             self.field_name, self.length, escape(data), maxlength)
     399            self.field_name, self.length, escape(data), maxlength))
    399400
    400401    def html2python(data):
    401402        return data
    class LargeTextField(TextField):  
    419420            data = ''
    420421        if isinstance(data, unicode):
    421422            data = data.encode(settings.DEFAULT_CHARSET)
    422         return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
     423        return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
    423424            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    424             self.field_name, self.rows, self.cols, escape(data))
     425            self.field_name, self.rows, self.cols, escape(data)))
    425426
    426427class HiddenField(FormField):
    427428    def __init__(self, field_name, is_required=False, validator_list=None):
    class HiddenField(FormField):  
    430431        self.validator_list = validator_list[:]
    431432
    432433    def render(self, data):
    433         return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
    434             (self.get_id(), self.field_name, escape(data))
     434        return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \
     435            (self.get_id(), self.field_name, escape(data)))
    435436
    436437class CheckboxField(FormField):
    437438    def __init__(self, field_name, checked_by_default=False, validator_list=None):
    class CheckboxField(FormField):  
    445446        checked_html = ''
    446447        if data or (data is '' and self.checked_by_default):
    447448            checked_html = ' checked="checked"'
    448         return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
     449        return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
    449450            (self.get_id(), self.__class__.__name__,
    450             self.field_name, checked_html)
     451            self.field_name, checked_html))
    451452
    452453    def html2python(data):
    453454        "Convert value from browser ('on' or '') to a Python boolean"
    class SelectField(FormField):  
    478479                selected_html = ' selected="selected"'
    479480            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
    480481        output.append('  </select>')
    481         return '\n'.join(output)
     482        return mark_safe('\n'.join(output))
    482483
    483484    def isValidChoice(self, data, form):
    484485        str_data = str(data)
    class RadioSelectField(FormField):  
    531532                output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
    532533                output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
    533534                output.append('</ul>')
    534                 return ''.join(output)
     535                return mark_safe(''.join(output))
    535536            def __iter__(self):
    536537                for d in self.datalist:
    537538                    yield d
    class RadioSelectField(FormField):  
    546547            datalist.append({
    547548                'value': value,
    548549                'name': display_name,
    549                 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
    550                     (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
    551                 'label': '<label for="%s">%s</label>' % \
     550                'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
     551                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html)),
     552                'label': mark_safe('<label for="%s">%s</label>' % \
    552553                    (self.get_id() + '_' + str(i), display_name),
    553             })
     554            )})
    554555        return RadioFieldRenderer(datalist, self.ul_class)
    555556
    556557    def isValidChoice(self, data, form):
    class SelectMultipleField(SelectField):  
    589590                selected_html = ' selected="selected"'
    590591            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
    591592        output.append('  </select>')
    592         return '\n'.join(output)
     593        return mark_safe('\n'.join(output))
    593594
    594595    def isValidChoice(self, field_data, all_data):
    595596        # data is something like ['1', '2', '3']
    class CheckboxSelectMultipleField(Select  
    640641            field_name = '%s%s' % (self.field_name, value)
    641642            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \
    642643                (self.get_id() + value , self.__class__.__name__, field_name, checked_html,
    643                 self.get_id() + value, choice))
     644                self.get_id() + value, escape(choice)))
    644645        output.append('</ul>')
    645         return '\n'.join(output)
     646        return mark_safe('\n'.join(output))
    646647
    647648####################
    648649# FILE UPLOADS     #
    class FileUploadField(FormField):  
    663664            raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    664665
    665666    def render(self, data):
    666         return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    667             (self.get_id(), self.__class__.__name__, self.field_name)
     667        return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \
     668            (self.get_id(), self.__class__.__name__, self.field_name))
    668669
    669670    def html2python(data):
    670671        if data is None:
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index ba7ca4c..99fcd72 100644
    a b from django.conf import settings  
    6060from django.template.context import Context, RequestContext, ContextPopException
    6161from django.utils.functional import curry
    6262from django.utils.text import smart_split
     63from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping
     64from django.utils.html import escape
    6365
    6466__all__ = ('Template', 'Context', 'RequestContext', 'compile_string')
    6567
    class FilterExpression(object):  
    558560                    arg_vals.append(arg)
    559561                else:
    560562                    arg_vals.append(resolve_variable(arg, context))
    561             obj = func(obj, *arg_vals)
     563            if getattr(func, 'needs_autoescape', False):
     564                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     565            else:
     566                new_obj = func(obj, *arg_vals)
     567            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     568                obj = mark_safe(new_obj)
     569            elif isinstance(obj, EscapeData):
     570                obj = mark_for_escaping(new_obj)
     571            else:
     572                obj = new_obj
     573               
    562574        return obj
    563575
    564576    def args_check(name, func, provided):
    class VariableNode(Node):  
    744756
    745757    def render(self, context):
    746758        output = self.filter_expression.resolve(context)
    747         return self.encode_output(output)
     759        encoded_output = self.encode_output(output)
     760        if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData):
     761            return escape(encoded_output)
     762        else:
     763            return encoded_output
    748764
    749765class DebugVariableNode(VariableNode):
    750766    def render(self, context):
    class DebugVariableNode(VariableNode):  
    754770            if not hasattr(e, 'source'):
    755771                e.source = self.source
    756772            raise
    757         return self.encode_output(output)
     773        encoded_output = self.encode_output(output)
     774        if context.autoescape and not isinstance(encoded_output, SafeData):
     775            return escape(encoded_output)
     776        else:
     777            return encoded_output
    758778
    759779def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    760780    "Returns a template.Node subclass."
  • django/template/context.py

    diff --git a/django/template/context.py b/django/template/context.py
    index 6d9efdc..b7466cf 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):  
    9598            processors = tuple(processors)
    9699        for processor in get_standard_processors() + processors:
    97100            self.update(processor(request))
     101
  • django/template/defaultfilters.py

    diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py
    index c75f371..ee994f3 100644
    a b  
    33from django.template import resolve_variable, Library
    44from django.conf import settings
    55from django.utils.translation import gettext
     6from django.utils.safestring import mark_safe, SafeData
    67import re
    78import random as random_module
    89
    ###################  
    1617def addslashes(value):
    1718    "Adds slashes - useful for passing strings to JavaScript, for example."
    1819    return value.replace('"', '\\"').replace("'", "\\'")
     20addslashes.is_safe = True
    1921
    2022def capfirst(value):
    2123    "Capitalizes the first character of the value"
    2224    value = str(value)
    2325    return value and value[0].upper() + value[1:]
     26capfirst.is_safe = True
    2427
    2528def fix_ampersands(value):
    2629    "Replaces ampersands with ``&amp;`` entities"
    2730    from django.utils.html import fix_ampersands
    2831    return fix_ampersands(value)
     32fix_ampersands.is_safe = True
    2933
    3034def floatformat(text):
    3135    """
    def floatformat(text):  
    4044    if m:
    4145        return '%.1f' % f
    4246    else:
    43         return '%d' % int(f)
     47        return mark_safe('%d' % int(f))
     48floatformat.is_safe = True
    4449
    45 def linenumbers(value):
     50def linenumbers(value, autoescape = None):
    4651    "Displays text with line numbers"
    4752    from django.utils.html import escape
    4853    lines = value.split('\n')
    4954    # Find the maximum width of the line count, for use with zero padding string format command
    5055    width = str(len(str(len(lines))))
    51     for i, line in enumerate(lines):
    52         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    53     return '\n'.join(lines)
     56    if not autoescape or isinstance(value, SafeData):
     57        for i, line in enumerate(lines):
     58            lines[i] = ("%0" + width  + "d. %s") % (i + 1, line)
     59    else:
     60        for i, line in enumerate(lines):
     61            lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
     62    return mark_safe('\n'.join(lines))
     63linenumbers.is_safe = True
     64linenumbers.needs_autoescape = True
    5465
    5566def lower(value):
    5667    "Converts a string into all lowercase"
    5768    return value.lower()
     69lower.is_safe = True
    5870
    5971def make_list(value):
    6072    """
    def make_list(value):  
    6274    digits. For a string, it's a list of characters.
    6375    """
    6476    return list(str(value))
     77make_list.is_safe = False
    6578
    6679def slugify(value):
    6780    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    6881    value = re.sub('[^\w\s-]', '', value).strip().lower()
    69     return re.sub('[-\s]+', '-', value)
     82    return mark_safe(re.sub('[-\s]+', '-', value))
     83slugify.is_safe = True
    7084
    7185def stringformat(value, arg):
    7286    """
    def stringformat(value, arg):  
    8195        return ("%" + arg) % value
    8296    except (ValueError, TypeError):
    8397        return ""
     98stringformat.is_safe = True
    8499
    85100def title(value):
    86101    "Converts a string into titlecase"
    87102    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
     103title.is_safe = False
    88104
    89105def truncatewords(value, arg):
    90106    """
    def truncatewords(value, arg):  
    100116    if not isinstance(value, basestring):
    101117        value = str(value)
    102118    return truncate_words(value, length)
     119truncatewords.is_safe = True
    103120
    104121def upper(value):
    105122    "Converts a string into all uppercase"
    106123    return value.upper()
     124upper.is_safe = False
    107125
    108126def urlencode(value):
    109127    "Escapes a value for use in a URL"
    110128    import urllib
    111129    return urllib.quote(value)
     130urlencode.is_safe = False
    112131
    113132def urlize(value):
    114133    "Converts URLs in plain text into clickable links"
    115134    from django.utils.html import urlize
    116     return urlize(value, nofollow=True)
     135    return mark_safe(urlize(value, nofollow=True))
     136urlize.is_safe = True
    117137
    118138def urlizetrunc(value, limit):
    119139    """
    def urlizetrunc(value, limit):  
    123143    Argument: Length to truncate URLs to.
    124144    """
    125145    from django.utils.html import urlize
    126     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     146    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
     147urlize.is_safe = True
    127148
    128149def wordcount(value):
    129150    "Returns the number of words"
    130151    return len(value.split())
     152wordcount.is_safe = False
    131153
    132154def wordwrap(value, arg):
    133155    """
    def wordwrap(value, arg):  
    137159    """
    138160    from django.utils.text import wrap
    139161    return wrap(str(value), int(arg))
     162wordwrap.is_safe = True
    140163
    141164def ljust(value, arg):
    142165    """
    def ljust(value, arg):  
    145168    Argument: field size
    146169    """
    147170    return str(value).ljust(int(arg))
     171ljust.is_safe = True
    148172
    149173def rjust(value, arg):
    150174    """
    def rjust(value, arg):  
    153177    Argument: field size
    154178    """
    155179    return str(value).rjust(int(arg))
     180rjust.is_safe = True
    156181
    157182def center(value, arg):
    158183    "Centers the value in a field of a given width"
    159184    return str(value).center(int(arg))
     185center.is_safe = True
    160186
    161187def cut(value, arg):
    162188    "Removes all values of arg from the given string"
    163189    return value.replace(arg, '')
     190cut.is_safe = False
    164191
    165192###################
    166193# HTML STRINGS    #
    167194###################
    168195
    169196def escape(value):
    170     "Escapes a string's HTML"
     197    "Marks the value as a string that should not be auto-escaped."
     198    from django.utils.safestring import mark_for_escaping
     199    return mark_for_escaping(value)
     200escape.is_safe = True
     201
     202def force_escape(value):
     203    """Escapes a string's HTML. This returns a new string containing the escaped
     204    characters (as opposed to "escape", which marks the content for later
     205    possible escaping)."""
    171206    from django.utils.html import escape
    172     return escape(value)
     207    return mark_safe(escape(value))
     208force_escape.is_safe = True
    173209
    174 def linebreaks(value):
     210def linebreaks(value, autoescape = None):
    175211    "Converts newlines into <p> and <br />s"
    176212    from django.utils.html import linebreaks
    177     return linebreaks(value)
     213    autoescape = autoescape and not isinstance(value, SafeData)
     214    return mark_safe(linebreaks(value, autoescape))
     215linebreaks.is_safe = True
     216linebreaks.needs_autoescape = True
    178217
    179 def linebreaksbr(value):
     218def linebreaksbr(value, autoescape = None):
    180219    "Converts newlines into <br />s"
    181     return value.replace('\n', '<br />')
     220    if autoescape and not isinstance(value, SafeData):
     221        from django.utils.html import escape
     222        data = escape(value)
     223    else:
     224        data = value
     225    return mark_safe(data.replace('\n', '<br />'))
     226linebreaksbr.is_safe = True
     227linebreaksbr.needs_autoescape = True
     228
     229def safe(value):
     230    "Marks the value as a string that should not be auto-escaped."
     231    from django.utils.safestring import mark_safe
     232    return mark_safe(value)
     233safe.is_safe = True
    182234
    183235def removetags(value, tags):
    184236    "Removes a space separated list of [X]HTML tags from the output"
    def removetags(value, tags):  
    189241    value = starttag_re.sub('', value)
    190242    value = endtag_re.sub('', value)
    191243    return value
     244removetags.is_safe = True
    192245
    193246def striptags(value):
    194247    "Strips all [X]HTML tags"
    def striptags(value):  
    196249    if not isinstance(value, basestring):
    197250        value = str(value)
    198251    return strip_tags(value)
     252striptags.is_safe = True
    199253
    200254###################
    201255# LISTS           #
    def dictsort(value, arg):  
    209263    decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    210264    decorated.sort()
    211265    return [item[1] for item in decorated]
     266dictsort.is_safe = False
    212267
    213268def dictsortreversed(value, arg):
    214269    """
    def dictsortreversed(value, arg):  
    219274    decorated.sort()
    220275    decorated.reverse()
    221276    return [item[1] for item in decorated]
     277dictsortreversed.is_safe = False
    222278
    223279def first(value):
    224280    "Returns the first item in a list"
    def first(value):  
    226282        return value[0]
    227283    except IndexError:
    228284        return ''
     285first.is_safe = True
    229286
    230287def join(value, arg):
    231288    "Joins a list with a string, like Python's ``str.join(list)``"
    232289    try:
    233         return arg.join(map(str, value))
     290        data = arg.join(map(str, value))
    234291    except AttributeError: # fail silently but nicely
    235292        return value
     293    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     294    if safe_args:
     295        return mark_safe(data)
     296    else:
     297        return data
     298join.is_safe = True
    236299
    237300def length(value):
    238301    "Returns the length of the value - useful for lists"
    239302    return len(value)
     303length.is_safe = False
    240304
    241305def length_is(value, arg):
    242306    "Returns a boolean of whether the value's length is the argument"
    243307    return len(value) == int(arg)
     308length.is_safe = False
    244309
    245310def random(value):
    246311    "Returns a random item from the list"
    247312    return random_module.choice(value)
     313length.is_safe = True
    248314
    249315def slice_(value, arg):
    250316    """
    def slice_(value, arg):  
    265331
    266332    except (ValueError, TypeError):
    267333        return value # Fail silently.
     334slice_.is_safe = True
    268335
    269 def unordered_list(value):
     336def unordered_list(value, autoescape = None):
    270337    """
    271338    Recursively takes a self-nested list and returns an HTML unordered list --
    272339    WITHOUT opening and closing <ul> tags.
    def unordered_list(value):  
    287354        </ul>
    288355        </li>
    289356    """
     357    if autoescape:
     358        from django.utils.html import conditional_escape
     359        escaper = conditional_escape
     360    else:
     361        escaper = lambda x: x
     362
    290363    def _helper(value, tabs):
    291364        indent = '\t' * tabs
    292365        if value[1]:
    293             return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
     366            return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent,
    294367                '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    295368        else:
    296             return '%s<li>%s</li>' % (indent, value[0])
    297     return _helper(value, 1)
     369            return '%s<li>%s</li>' % (indent, escaper(value[0]))
     370    return mark_safe(_helper(value, 1))
     371unordered_list.is_safe = True
     372unordered_list.needs_autoescape = True
    298373
    299374###################
    300375# INTEGERS        #
    ###################  
    303378def add(value, arg):
    304379    "Adds the arg to the value"
    305380    return int(value) + int(arg)
     381add.is_safe = False
    306382
    307383def get_digit(value, arg):
    308384    """
    def get_digit(value, arg):  
    322398        return int(str(value)[-arg])
    323399    except IndexError:
    324400        return 0
     401get_digit.is_safe = False
    325402
    326403###################
    327404# DATES           #
    def date(value, arg=None):  
    335412    if arg is None:
    336413        arg = settings.DATE_FORMAT
    337414    return format(value, arg)
     415date.is_safe = False
    338416
    339417def time(value, arg=None):
    340418    "Formats a time according to the given format"
    def time(value, arg=None):  
    344422    if arg is None:
    345423        arg = settings.TIME_FORMAT
    346424    return time_format(value, arg)
     425time.is_safe = False
    347426
    348427def timesince(value, arg=None):
    349428    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    def timesince(value, arg=None):  
    353432    if arg:
    354433        return timesince(arg, value)
    355434    return timesince(value)
     435timesince.is_safe = False
    356436
    357437def timeuntil(value, arg=None):
    358438    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
    def timeuntil(value, arg=None):  
    363443    if arg:
    364444        return timesince(arg, value)
    365445    return timesince(datetime.now(), value)
     446timeuntil.is_safe = False
    366447
    367448###################
    368449# LOGIC           #
    ###################  
    371452def default(value, arg):
    372453    "If value is unavailable, use given default"
    373454    return value or arg
     455default.is_safe = False
    374456
    375457def default_if_none(value, arg):
    376458    "If value is None, use given default"
    377459    if value is None:
    378460        return arg
    379461    return value
     462default_if_none.is_safe = False
    380463
    381464def divisibleby(value, arg):
    382465    "Returns true if the value is devisible by the argument"
    383466    return int(value) % int(arg) == 0
     467divisibleby.is_safe = False
    384468
    385469def yesno(value, arg=None):
    386470    """
    def yesno(value, arg=None):  
    411495    if value:
    412496        return yes
    413497    return no
     498yesno.is_safe = False
    414499
    415500###################
    416501# MISC            #
    def filesizeformat(bytes):  
    429514    if bytes < 1024 * 1024 * 1024:
    430515        return "%.1f MB" % (bytes / (1024 * 1024))
    431516    return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
     517filesizeformat.is_safe = True
    432518
    433519def pluralize(value, arg='s'):
    434520    """
    def pluralize(value, arg='s'):  
    456542        except TypeError: # len() of unsized object
    457543            pass
    458544    return singular_suffix
     545pluralize.is_safe = False
    459546
    460547def phone2numeric(value):
    461548    "Takes a phone number and converts it in to its numerical equivalent"
    462549    from django.utils.text import phone2numeric
    463550    return phone2numeric(value)
     551phone2numeric.is_safe = True
    464552
    465553def pprint(value):
    466554    "A wrapper around pprint.pprint -- for debugging, really"
    def pprint(value):  
    469557        return pformat(value)
    470558    except Exception, e:
    471559        return "Error in formatting:%s" % e
     560pprint.is_safe = True
    472561
    473562# Syntax: register.filter(name of filter, callback)
    474563register.filter(add)
    register.filter(filesizeformat)  
    487576register.filter(first)
    488577register.filter(fix_ampersands)
    489578register.filter(floatformat)
     579register.filter(force_escape)
    490580register.filter(get_digit)
    491581register.filter(join)
    492582register.filter(length)
    register.filter(pprint)  
    503593register.filter(removetags)
    504594register.filter(random)
    505595register.filter(rjust)
     596register.filter(safe)
    506597register.filter('slice', slice_)
    507598register.filter(slugify)
    508599register.filter(stringformat)
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 0a4fe33..e3d9624 100644
    a b from django.template import Node, NodeLi  
    44from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END
    55from django.template import get_library, Library, InvalidTemplateLibrary
    66from django.conf import settings
     7from django.utils.safestring import mark_safe
    78import sys
    89
    910register = Library()
    1011
     12class AutoEscapeControlNode(Node):
     13    """Implements the actions of both the autoescape and noautescape tags."""
     14    def __init__(self, setting, nodelist):
     15        self.setting, self.nodelist = setting, nodelist
     16
     17    def render(self, context):
     18        old_setting = context.autoescape
     19        context.autoescape = self.setting
     20        output = self.nodelist.render(context)
     21        context.autoescape = old_setting
     22        if self.setting:
     23            return mark_safe(output)
     24        else:
     25            return output
     26
    1127class CommentNode(Node):
    1228    def render(self, context):
    1329        return ''
    class FilterNode(Node):  
    3753    def render(self, context):
    3854        output = self.nodelist.render(context)
    3955        # apply filters
    40         return self.filter_expr.resolve(Context({'var': output}))
     56        ctxt = Context({'var': output})
     57        ctxt.autoescape = context.autoescape
     58        return self.filter_expr.resolve(ctxt)
    4159
    4260class FirstOfNode(Node):
    4361    def __init__(self, vars):
    class RegroupNode(Node):  
    218236            return ''
    219237        output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
    220238        for obj in obj_list:
    221             grouper = self.expression.resolve(Context({'var': obj}))
     239            ctxt = Context({'var': obj})
     240            ctxt.autoescape = context.autoescape
     241            grouper = self.expression.resolve(ctxt)
    222242            # TODO: Is this a sensible way to determine equality?
    223243            if output and repr(output[-1]['grouper']) == repr(grouper):
    224244                output[-1]['list'].append(obj)
    class WidthRatioNode(Node):  
    318338        return str(int(round(ratio)))
    319339
    320340#@register.tag
     341def autoescape(parser, token):
     342    """
     343    Force autoescape behaviour for this block.
     344    """
     345    nodelist = parser.parse(('endautoescape',))
     346    parser.delete_first_token()
     347    return AutoEscapeControlNode(True, nodelist)
     348autoescape = register.tag(autoescape)
     349
     350#@register.tag
    321351def comment(parser, token):
    322352    """
    323353    Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
    def do_filter(parser, token):  
    411441
    412442    Sample usage::
    413443
    414         {% filter escape|lower %}
     444        {% filter force_escape|lower %}
    415445            This text will be HTML-escaped, and will appear in lowercase.
    416446        {% endfilter %}
    417447    """
    418448    _, rest = token.contents.split(None, 1)
    419449    filter_expr = parser.compile_filter("var|%s" % (rest))
     450    for func, unused in filter_expr.filters:
     451        if func.__name__ in ('escape', 'safe'):
     452            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    420453    nodelist = parser.parse(('endfilter',))
    421454    parser.delete_first_token()
    422455    return FilterNode(filter_expr, nodelist)
    def ifchanged(parser, token):  
    646679ifchanged = register.tag(ifchanged)
    647680
    648681#@register.tag
     682def noautoescape(parser, token):
     683    """
     684    Force autoescape behaviour to be disabled for this block.
     685    """
     686    nodelist = parser.parse(('endnoautoescape',))
     687    parser.delete_first_token()
     688    return AutoEscapeControlNode(False, nodelist)
     689autoescape = register.tag(noautoescape)
     690
     691#@register.tag
    649692def ssi(parser, token):
    650693    """
    651694    Output the contents of a given file into the page.
  • django/utils/html.py

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

    diff --git a/django/utils/safestring.py b/django/utils/safestring.py
    new file mode 100644
    index 0000000..67a2e79
    - +  
     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
     8
     9class EscapeData(object):
     10    pass
     11
     12class EscapeString(str, EscapeData):
     13    """
     14    A string that should be HTML-escaped when output.
     15    """
     16    pass
     17
     18class EscapeUnicode(unicode, EscapeData):
     19    """
     20    A unicode object that should be HTML-escaped when output.
     21    """
     22    pass
     23
     24class SafeData(object):
     25    pass
     26
     27class SafeString(str, SafeData):
     28    """
     29    A string subclass that has been specifically marked as "safe" for HTML
     30    output purposes.
     31    """
     32    def __add__(self, rhs):
     33        """
     34        Concatenating a safe string with another safe string or safe unicode
     35        object is safe. Otherwise, the result is no longer safe.
     36        """
     37        if isinstance(rhs, SafeUnicode):
     38            return SafeUnicode(self + rhs)
     39        elif isinstance(rhs, SafeString):
     40            return SafeString(self, rhs)
     41        else:
     42            return super(SafeString, self).__add__(rhs)
     43
     44class SafeUnicode(unicode, SafeData):
     45    """
     46    A unicode subclass that has been specifically marked as "safe" for HTML
     47    output purposes.
     48    """
     49    def __add__(self, rhs):
     50        """
     51        Concatenating a safe unicode object with another safe string or safe
     52        unicode object is safe. Otherwise, the result is no longer safe.
     53        """
     54        if isinstance(rhs, SafeData):
     55            return SafeUnicode(self + rhs)
     56        else:
     57            return super(SafeUnicode, self).__add__(rhs)
     58
     59    def _proxy_method(self, *args, **kwargs):
     60        """
     61        Wrap a call to a normal unicode method up so that we return safe
     62        results. The method that is being wrapped is passed in the 'method'
     63        argument.
     64        """
     65        method = kwargs.pop('method')
     66        data = method(self, *args, **kwargs)
     67        if isinstance(data, str):
     68            return SafeString(data)
     69        else:
     70            return SafeUnicode(data)
     71
     72    encode = curry(_proxy_method, method = unicode.encode)
     73    decode = curry(_proxy_method, method = unicode.decode)
     74
     75
     76def mark_safe(s):
     77    """
     78    Explicitly mark a string as safe for (HTML) output purposes. The returned
     79    object can be used everywhere a string or unicode object is appropriate.
     80
     81    Can safely be called multiple times on a single string.
     82    """
     83    if isinstance(s, SafeData):
     84        return s
     85    if isinstance(s, str):
     86        return SafeString(s)
     87    if isinstance(s, unicode):
     88        return SafeUnicode(s)
     89    return SafeString(str(s))
     90
     91def mark_for_escaping(s):
     92    """
     93    Explicitly mark a string as requiring HTML escaping upon output. Has no
     94    effect on SafeData subclasses.
     95
     96    Can be safely called multiple times on a single string (the effect is only
     97    applied once).
     98    """
     99    if isinstance(s, SafeData) or isinstance(s, EscapeData):
     100        return s
     101    if isinstance(s, str):
     102        return EscapeString(s)
     103    if isinstance(s, unicode):
     104        return EscapeUnicode(s)
     105    return EscapeString(str(s))
     106
  • docs/templates.txt

    diff --git a/docs/templates.txt b/docs/templates.txt
    index 49d3001..088f5ae 100644
    a b it also defines the content that fills t  
    243243two similarly-named ``{% block %}`` tags in a template, that template's parent
    244244wouldn't know which one of the blocks' content to use.
    245245
     246Automatic HTML escaping
     247=======================
     248
     249A very real problem when creating HTML (and other) output using templates and
     250variable substitution is the possibility of accidently inserting some variable
     251value that affects the resulting HTML. For example, a template fragment like
     252
     253::
     254
     255    Hello, {{ name }}.
     256
     257seems like a harmless way to display the user's name. However, if you are
     258displaying data that the user entered directly and they entered their name as
     259
     260::
     261
     262    <script>alert('hello')</script>
     263
     264this would always display a Javascript alert box whenever the page was loaded.
     265Similarly, if you were displaying some data generated by another process and
     266it contained a '<' symbol, you couldn't just dump this straight into your
     267HTML, because it would be treated as the start of an element.  The effects of
     268these sorts of problems can vary from merely annoying to allowing exploits via
     269`Cross Site Scripting`_ (XSS) attacks.
     270
     271.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
     272
     273In order to provide some protection against these problems, Django provides an
     274auto-escaping template tag. Inside this tag, any data that comes from template
     275variables is examined to see if it contains one of the five HTML characters
     276(<, >, ', " and &) that often need escaping and those characters are converted
     277to their respective HTML entities.
     278
     279Because some variables will contain data that is *intended* to be rendered
     280as HTML, template tag and filter writers can mark their output strings as
     281requiring no further escaping. For example, the ``unordered_list`` filter is
     282designed to return raw HTML and we want the template processor to simply
     283display the results as returned, without applying any escaping. That is taken
     284care of by the filter. The template author need do nothing special in that
     285case.
     286
     287By default, auto-escaping is not in effect. To enable it inside your template,
     288wrap the affected content in the ``autoescape`` tag, like so::
     289
     290    {% autoescape %}
     291        Hello {{ name }}
     292    {% endautoescape %}
     293
     294Since the auto-escaping tag passes its effect onto templates that extend the
     295current one as well as templates included via the ``include`` tag (just like
     296all block tags), if you wrap your main HTML content in an ``autoescape`` tag,
     297you will have automatic escaping applied to all of your content.
     298
     299At times, you might want to disable auto-escaping when it would otherwise be
     300in effect. You can do this with the ``noautoescape`` tag. For example::
     301
     302    {% autoescape %}
     303        Hello {{ name }}
     304
     305        {% noautoescape %}
     306            This will not be auto-escaped: {{ data }}.
     307
     308            Nor this: {{ other_data }}
     309        {% endnoautoescape %}
     310    {% endautoescape %}
     311
     312For individual variables, the ``safe`` filter can also be used.
     313
     314Generally, you will not need to worry about auto-escaping very much. Enable it
     315in your base template once you are entering the main HTML region and then
     316write your templates normally. The view developers and custom filter authors
     317need to think about when their data should not be escaped and mark it
     318appropriately.  They are in a better position to know when that should happen
     319than the template author, so it is their responsibility. By default, when
     320auto-escaping is enabled, all output is escaped unless the template processor
     321is explicitly told otherwise.
     322
    246323Using the built-in reference
    247324============================
    248325
    available, and what they do.  
    318395Built-in tag reference
    319396----------------------
    320397
     398autoescape
     399~~~~~~~~~~
     400
     401All variables that are output inside this tag have HTML escaping applied to
     402them, as if they each had the ``escape`` filter attached to them.
     403
     404The only exceptions are variables that are already marked as 'safe' from
     405escaping, either by the code that populated the variable, or because it has
     406the ``safe`` filter applied.
     407
    321408block
    322409~~~~~
    323410
    just like in variable syntax.  
    385472
    386473Sample usage::
    387474
    388     {% filter escape|lower %}
     475    {% filter force_escape|lower %}
    389476        This text will be HTML-escaped, and will appear in all lowercase.
    390477    {% endfilter %}
    391478
    Load a custom template tag set.  
    582669
    583670See `Custom tag and filter libraries`_ for more information.
    584671
     672noautoescape
     673~~~~~~~~~~~~
     674
     675Disable the effects of the ``autoescape`` tag (if it is in effect).
     676
    585677now
    586678~~~
    587679
    Escapes a string's HTML. Specifically, i  
    866958    * ``'"'`` (double quote) to ``'&quot;'``
    867959    * ``"'"`` (single quote) to ``'&#39;'``
    868960
     961The escaping is only applied when the string is output, so it does not matter
     962where in a chained sequence of filters you put ``escape``: it will always be
     963applied as though it were the last filter. If you want escaping to be applied
     964immediately, use the ``force_escape`` filter.
     965
    869966filesizeformat
    870967~~~~~~~~~~~~~~
    871968
    decimal part to be displayed. For exampl  
    892989    * ``36.15`` gets converted to ``36.2``
    893990    * ``36`` gets converted to ``36``
    894991
     992force_escape
     993~~~~~~~~~~~~
     994
     995Applies HTML escaping to a string (see the ``escape`` filter for details).
     996This filter is applied immediately and returns a new, escaped string. This is
     997useful in the typically rare cases where you need multiple escaping or want to
     998apply other filters to the escaped results. Normally, you want to use the
     999``escape`` filter.
     1000
    8951001get_digit
    8961002~~~~~~~~~
    8971003
    Right-aligns the value in a field of a g  
    10031109
    10041110**Argument:** field size
    10051111
     1112safe
     1113~~~~
     1114
     1115Marks a string as not requiring further HTML escaping prior to output. This is
     1116only useful inside an ``autoescape`` block, when the output would otherwise be
     1117automatically escaped. Outside of an ``autoescape`` block, this filter has no
     1118effect.
     1119
    10061120slice
    10071121~~~~~
    10081122
  • docs/templates_python.txt

    diff --git a/docs/templates_python.txt b/docs/templates_python.txt
    index 95ccfb3..4a7b611 100644
    a b decorator instead::  
    593593If you leave off the ``name`` argument, as in the second example above, Django
    594594will use the function's name as the filter name.
    595595
     596Filters and auto-escaping
     597~~~~~~~~~~~~~~~~~~~~~~~~~
     598
     599When you are writing a custom filter, you need to give some thought to how
     600this filter will work when rendered in an auto-escaping environment (inside
     601an ``autoescape`` template tag block). First, you should realise that there
     602are three types of strings that can be passed around inside the template code:
     603
     604 * raw strings are the native Python ``str`` (or ``unicode``) types. On
     605   output, they are escaped if they are inside an ``autoescape`` block.
     606 * "safe" strings are strings that are safe from further escaping at output
     607   time. Any necessary escaping has already been done. They are commonly used
     608   for output that contains raw HTML that is intended to be intrepreted on the
     609   client side.
     610
     611   Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
     612   although they share a common base class in ``SafeData``, so you can test
     613   for them using code like::
     614
     615    if isinstance(value, SafeData):
     616        # Do something with the "safe" string.
     617
     618 * strings which are marked as "need escaping" are *always* escaped on
     619   output, regardless of whether they are in an ``autoescape`` block or not.
     620   These strings are only escaped once, however, even if used inside an
     621   ``autoescaep`` block.  This type of string is internally represented by the
     622   types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to
     623   worry about these; they exist only for the implementation of the ``escape``
     624   filter.
     625
     626Inside your filter, you will need to think about three areas in order to be
     627auto-escaping compliant:
     628
     629 1. If your filter returns a string that is ready for direct output (it should
     630 be considered a "safe" string), you should call
     631 ``django.utils.safestring.mark_safe()`` on the result prior to returning.
     632 This will turn the result into the appropriate ``SafeData`` type.
     633
     634 2. If your filter is given a "safe" string, is it guaranteed to return a
     635 "safe" string? If so, set the ``is_safe`` attribute on the function to be
     636 ``True``. For example, a filter that replaced all numbers with the number
     637 spelt out in words is going to be safe-string-preserving, since it cannot
     638 introduce any of the five dangerous characters: <, >, ", ' or &. So we can
     639 write::
     640
     641    @register.filter
     642    def convert_to_words(value):
     643        # ... implementation here ...
     644        return result
     645
     646    convert_to_words.is_safe = True
     647
     648 Note that this filter does not return a universally safe result (it does not
     649 return ``mark_safe(result)``) because if it is handed a raw string such as
     650 '<a>', this will need further escaping in an auto-escape environment. The
     651 ``is_safe`` attribute only talks about the safeness of the result when a safe
     652 string is passed in to the filter.
     653
     654 3. Will your filter behave differently depending upon whether auto-escaping
     655 is currently in effect or not? For example, the ``ordered_list`` filter that
     656 ships with Django needs to know whether to escape its content or not. It will
     657 always return a safe string, since it returns raw HTML, so we cannot apply
     658 escaping to the result -- it needs to be done in-situ.
     659
     660 For these cases, the filter function needs to be told what the current
     661 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
     662 filter to ``True`` and have your function take an extra argument called
     663 ``autoescape`` with a default value of ``None``. When the filter is called,
     664 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
     665 effect. For example, the ``unordered_list`` filter is written as::
     666
     667    def unordered_list(value, autoescape = None):
     668        # ... lots of code here ...
     669
     670        return mark_safe(...)
     671
     672    unordered_list.is_safe = True
     673    unordered_list.needs_autoescape = True
     674
     675By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
     676``False``. You do not need to specify them if ``False`` is an acceptable
     677value.
     678
     679As a matter of convention, we leave ``is_safe`` as ``False`` for filters that
     680do not accept string inputs (they might take a number as an input, for
     681example) or for those that return a non-string (e.g. the ``length`` filter).
     682However, not following this convention will not cause any harm or make your
     683results any more vulnerable to cross-site scripting problems.
     684
    596685Writing custom template tags
    597686----------------------------
    598687
    Ultimately, this decoupling of compilati  
    711800efficient template system, because a template can render multiple context
    712801without having to be parsed multiple times.
    713802
     803Auto-escaping considerations
     804~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     805
     806The output from template tags is not automatically run through the
     807auto-escaping filters if used inside an ``autoescape`` tag. However, there are
     808still a couple of things you should keep in mind when writing a template tag:
     809
     810If the ``render()`` function of your template stores the result in a context
     811variable (rather than returning the result in a string), it should take care
     812to call ``mark_safe()`` if appropriate. When the variable is ultimately
     813rendered, it will be affected by the auto-escape setting in effect at the
     814time, so content that should be safe from further escaping needs to be marked
     815as such.
     816
     817Also, if your template tag creates a new context for performing some
     818sub-rendering, you should be careful to set the auto-escape variable to the
     819current context's value. For example::
     820
     821    def render(self, context):
     822        # ...
     823        new_context = Context({'var': obj})
     824        new_context.autoescape = context.autoescape
     825        # ... Do something with new_context ...
     826
     827This is not a very common situation, but it is sometimes useful (see
     828``django.templates.defaulttags.FilterNode.render()`` for an example).
     829
    714830Registering the tag
    715831~~~~~~~~~~~~~~~~~~~
    716832
  • new file tests/othertests/autoescape.py

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

    diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py
    index 1636b94..1f09605 100644
    a b u'\xcb'  
    118118>>> cut('a string to be mangled', 'strings')
    119119'a string to be mangled'
    120120
    121 >>> escape('<some html & special characters > here')
     121>>> force_escape('<some html & special characters > here')
    122122'&lt;some html &amp; special characters &gt; here'
    123123
    124124>>> linebreaks('line 1')
Back to Top