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

File 01-core-changes.3.diff, 70.9 KB (added by Michael Radziej <mir@…>, 17 years ago)

updated patch, includes part of smurf's patch, for rev. 4659

  • django/oldforms/__init__.py

    From nobody Mon Sep 17 00:00:00 2001
    From: Michael Radziej <mir@noris.de>
    Date: Fri Mar 2 12:28:18 2007 +0100
    Subject: [PATCH] autoescape 1
    
    added formally required empty files for autoescape tests.
    
    ---
    
     django/oldforms/__init__.py                   |   43 ++--
     django/template/__init__.py                   |   26 ++
     django/template/context.py                    |    4 
     django/template/defaultfilters.py             |  133 +++++++++++--
     django/template/defaulttags.py                |   49 ++++-
     django/utils/html.py                          |   15 +
     django/utils/safestring.py                    |  109 ++++++++++
     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     |  266 +++++++++++++++++++++++++
     tests/regressiontests/defaultfilters/tests.py |    2 
     tests/regressiontests/templates/tests.py      |  126 ++++++------
     14 files changed, 896 insertions(+), 111 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 f65b09b6eeb11df88cbdcee579752eb835f25571
    last eb364cb467acd36134c65431715bcf49baad2bb4
    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index decf0f7064f01feda7e4c5d1bee84b9d60651dd1..1b3bba139a632faf48ae8e7b87d9872633b531d3 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, ngettext
    67
    class FormFieldWrapper(object):  
    181182
    182183    def html_error_list(self):
    183184        if self.errors():
    184             return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
     185            return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
    185186        else:
    186             return ''
     187            return mark_safe('')
    187188
    188189    def get_id(self):
    189190        return self.formfield.get_id()
    class FormFieldCollection(FormFieldWrapp  
    215216        return bool(len(self.errors()))
    216217
    217218    def html_combined_error_list(self):
    218         return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
     219        return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
    219220
    220221class InlineObjectCollection(object):
    221222    "An object that acts like a sparse list of form field collections."
    class TextField(FormField):  
    399400            maxlength = 'maxlength="%s" ' % self.maxlength
    400401        if isinstance(data, unicode):
    401402            data = data.encode(settings.DEFAULT_CHARSET)
    402         return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
     403        return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
    403404            (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    404             self.field_name, self.length, escape(data), maxlength)
     405            self.field_name, self.length, escape(data), maxlength))
    405406
    406407    def html2python(data):
    407408        return data
    class LargeTextField(TextField):  
    425426            data = ''
    426427        if isinstance(data, unicode):
    427428            data = data.encode(settings.DEFAULT_CHARSET)
    428         return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
     429        return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
    429430            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    430             self.field_name, self.rows, self.cols, escape(data))
     431            self.field_name, self.rows, self.cols, escape(data)))
    431432
    432433class HiddenField(FormField):
    433434    def __init__(self, field_name, is_required=False, validator_list=None):
    class HiddenField(FormField):  
    436437        self.validator_list = validator_list[:]
    437438
    438439    def render(self, data):
    439         return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
    440             (self.get_id(), self.field_name, escape(data))
     440        return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \
     441            (self.get_id(), self.field_name, escape(data)))
    441442
    442443class CheckboxField(FormField):
    443444    def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
    class CheckboxField(FormField):  
    451452        checked_html = ''
    452453        if data or (data is '' and self.checked_by_default):
    453454            checked_html = ' checked="checked"'
    454         return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
     455        return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
    455456            (self.get_id(), self.__class__.__name__,
    456             self.field_name, checked_html)
     457            self.field_name, checked_html))
    457458
    458459    def html2python(data):
    459460        "Convert value from browser ('on' or '') to a Python boolean"
    class SelectField(FormField):  
    484485                selected_html = ' selected="selected"'
    485486            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
    486487        output.append('  </select>')
    487         return '\n'.join(output)
     488        return mark_safe('\n'.join(output))
    488489
    489490    def isValidChoice(self, data, form):
    490491        str_data = str(data)
    class RadioSelectField(FormField):  
    537538                output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
    538539                output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
    539540                output.append('</ul>')
    540                 return ''.join(output)
     541                return mark_safe(''.join(output))
    541542            def __iter__(self):
    542543                for d in self.datalist:
    543544                    yield d
    class RadioSelectField(FormField):  
    552553            datalist.append({
    553554                'value': value,
    554555                'name': display_name,
    555                 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
    556                     (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
    557                 'label': '<label for="%s">%s</label>' % \
     556                'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
     557                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html)),
     558                'label': mark_safe('<label for="%s">%s</label>' % \
    558559                    (self.get_id() + '_' + str(i), display_name),
    559             })
     560            )})
    560561        return RadioFieldRenderer(datalist, self.ul_class)
    561562
    562563    def isValidChoice(self, data, form):
    class SelectMultipleField(SelectField):  
    595596                selected_html = ' selected="selected"'
    596597            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
    597598        output.append('  </select>')
    598         return '\n'.join(output)
     599        return mark_safe('\n'.join(output))
    599600
    600601    def isValidChoice(self, field_data, all_data):
    601602        # data is something like ['1', '2', '3']
    class CheckboxSelectMultipleField(Select  
    648649                (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
    649650                self.get_id() + escape(value), choice))
    650651        output.append('</ul>')
    651         return '\n'.join(output)
     652        return mark_safe('\n'.join(output))
    652653
    653654####################
    654655# FILE UPLOADS     #
    class FileUploadField(FormField):  
    669670            raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    670671
    671672    def render(self, data):
    672         return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    673             (self.get_id(), self.__class__.__name__, self.field_name)
     673        return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \
     674            (self.get_id(), self.__class__.__name__, self.field_name))
    674675
    675676    def html2python(data):
    676677        if data is None:
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index 678d19293cae45826556c830b31e07530f3640b6..b961b344c083ab876849db6539413e6f33f33798 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):  
    574576                    arg_vals.append(arg)
    575577                else:
    576578                    arg_vals.append(resolve_variable(arg, context))
    577             obj = func(obj, *arg_vals)
     579            if getattr(func, 'needs_autoescape', False):
     580                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     581            else:
     582                new_obj = func(obj, *arg_vals)
     583            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     584                obj = mark_safe(new_obj)
     585            elif isinstance(obj, EscapeData):
     586                obj = mark_for_escaping(new_obj)
     587            else:
     588                obj = new_obj
     589               
    578590        return obj
    579591
    580592    def args_check(name, func, provided):
    class VariableNode(Node):  
    766778
    767779    def render(self, context):
    768780        output = self.filter_expression.resolve(context)
    769         return self.encode_output(output)
     781        encoded_output = self.encode_output(output)
     782        if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData):
     783            return escape(encoded_output)
     784        else:
     785            return encoded_output
    770786
    771787class DebugVariableNode(VariableNode):
    772788    def render(self, context):
    class DebugVariableNode(VariableNode):  
    776792            if not hasattr(e, 'source'):
    777793                e.source = self.source
    778794            raise
    779         return self.encode_output(output)
     795        encoded_output = self.encode_output(output)
     796        if context.autoescape and not isinstance(encoded_output, SafeData):
     797            return escape(encoded_output)
     798        else:
     799            return encoded_output
    780800
    781801def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    782802    "Returns a template.Node subclass."
  • django/template/context.py

    diff --git a/django/template/context.py b/django/template/context.py
    index 25397b0e1a4a29538db23fd5440ffe2ce667f202..a93d726d24c9c69581283701756b8b1df3ae22d4 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 b53375b42d39983cfb97d6c7c659bc2cf11c546c..d34f34cd5cb098e25309cad49154284d4d8f01af 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
    def addslashes(value):  
    4950    "Adds slashes - useful for passing strings to JavaScript, for example."
    5051    return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
    5152addslashes = stringfilter(addslashes)
     53addslashes.is_safe = True
    5254
    5355def capfirst(value):
    5456    "Capitalizes the first character of the value"
    5557    return value and value[0].upper() + value[1:]
    5658capfirst = stringfilter(capfirst)
    57  
     59capfirst.is_safe = True
     60
    5861def fix_ampersands(value):
    5962    "Replaces ampersands with ``&amp;`` entities"
    6063    from django.utils.html import fix_ampersands
    6164    return fix_ampersands(value)
    6265fix_ampersands = stringfilter(fix_ampersands)
     66fix_ampersands.is_safe = True
    6367
    6468def floatformat(text, arg=-1):
    6569    """
    def floatformat(text, arg=-1):  
    9296        return '%d' % int(f)
    9397    else:
    9498        formatstr = '%%.%df' % abs(d)
    95         return formatstr % f
     99        return mark_safe(formatstr % f)
     100floatformat.is_safe = True
    96101
    97 def linenumbers(value):
     102def linenumbers(value, autoescape = None):
    98103    "Displays text with line numbers"
    99104    from django.utils.html import escape
    100105    lines = value.split('\n')
    101106    # Find the maximum width of the line count, for use with zero padding string format command
    102107    width = str(len(str(len(lines))))
    103     for i, line in enumerate(lines):
    104         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    105     return '\n'.join(lines)
     108    if not autoescape or isinstance(value, SafeData):
     109        for i, line in enumerate(lines):
     110            lines[i] = ("%0" + width  + "d. %s") % (i + 1, line)
     111    else:
     112        for i, line in enumerate(lines):
     113            lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
     114    return mark_safe('\n'.join(lines))
    106115linenumbers = stringfilter(linenumbers)
     116linenumbers.is_safe = True
     117linenumbers.needs_autoescape = True
    107118
    108119def lower(value):
    109120    "Converts a string into all lowercase"
    110121    return value.lower()
    111122lower = stringfilter(lower)
     123lower.is_safe = True
    112124
    113125def make_list(value):
    114126    """
    def make_list(value):  
    117129    """
    118130    return list(value)
    119131make_list = stringfilter(make_list)
     132make_list.is_safe = False
    120133
    121134def slugify(value):
    122135    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    123136    value = re.sub('[^\w\s-]', '', value).strip().lower()
    124     return re.sub('[-\s]+', '-', value)
     137    return mark_safe(re.sub('[-\s]+', '-', value))
    125138slugify = stringfilter(slugify)
     139slugify.is_safe = True
    126140
    127141def stringformat(value, arg):
    128142    """
    def stringformat(value, arg):  
    137151        return ("%" + str(arg)) % value
    138152    except (ValueError, TypeError):
    139153        return ""
     154stringformat.is_safe = True
    140155
    141156def title(value):
    142157    "Converts a string into titlecase"
    143158    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    144159title = stringfilter(title)
     160title.is_safe = False
    145161
    146162def truncatewords(value, arg):
    147163    """
    def truncatewords(value, arg):  
    158174        value = str(value)
    159175    return truncate_words(value, length)
    160176truncatewords = stringfilter(truncatewords)
     177truncatewords.is_safe = True
    161178
    162179def truncatewords_html(value, arg):
    163180    """
    def upper(value):  
    179196    "Converts a string into all uppercase"
    180197    return value.upper()
    181198upper = stringfilter(upper)
     199upper.is_safe = False
    182200
    183201def urlencode(value):
    184202    "Escapes a value for use in a URL"
    def urlencode(value):  
    187205        value = str(value)
    188206    return urllib.quote(value)
    189207urlencode = stringfilter(urlencode)
     208urlencode.is_safe = False
    190209
    191210def urlize(value):
    192211    "Converts URLs in plain text into clickable links"
    193212    from django.utils.html import urlize
    194     return urlize(value, nofollow=True)
     213    return mark_safe(urlize(value, nofollow=True))
    195214urlize = stringfilter(urlize)
     215urlize.is_safe = True
    196216
    197217def urlizetrunc(value, limit):
    198218    """
    def urlizetrunc(value, limit):  
    202222    Argument: Length to truncate URLs to.
    203223    """
    204224    from django.utils.html import urlize
    205     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     225    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
    206226urlizetrunc = stringfilter(urlizetrunc)
     227urlize.is_safe = True
    207228
    208229def wordcount(value):
    209230    "Returns the number of words"
    210231    return len(value.split())
    211232wordcount = stringfilter(wordcount)
     233wordcount.is_safe = False
    212234
    213235def wordwrap(value, arg):
    214236    """
    def wordwrap(value, arg):  
    219241    from django.utils.text import wrap
    220242    return wrap(value, int(arg))
    221243wordwrap = stringfilter(wordwrap)
     244wordwrap.is_safe = True
    222245
    223246def ljust(value, arg):
    224247    """
    def ljust(value, arg):  
    228251    """
    229252    return value.ljust(int(arg))
    230253ljust = stringfilter(ljust)
     254ljust.is_safe = True
    231255
    232256def rjust(value, arg):
    233257    """
    def rjust(value, arg):  
    237261    """
    238262    return value.rjust(int(arg))
    239263rjust = stringfilter(rjust)
     264rjust.is_safe = True
    240265
    241266def center(value, arg):
    242267    "Centers the value in a field of a given width"
    243268    return value.center(int(arg))
    244269center = stringfilter(center)
     270center.is_safe = True
    245271
    246272def cut(value, arg):
    247273    "Removes all values of arg from the given string"
    248274    return value.replace(arg, '')
    249275cut = stringfilter(cut)
     276cut.is_safe = False
    250277
    251278###################
    252279# HTML STRINGS    #
    253280###################
    254281
    255282def escape(value):
    256     "Escapes a string's HTML"
     283    "Marks the value as a string that should not be auto-escaped."
     284    from django.utils.safestring import mark_for_escaping
     285    return mark_for_escaping(value)
     286escape = stringfilter(escape)
     287escape.is_safe = True
     288
     289def force_escape(value):
     290    """Escapes a string's HTML. This returns a new string containing the escaped
     291    characters (as opposed to "escape", which marks the content for later
     292    possible escaping)."""
    257293    from django.utils.html import escape
    258     return escape(value)
     294    return mark_safe(escape(value))
    259295escape = stringfilter(escape)
     296force_escape.is_safe = True
    260297
    261 def linebreaks(value):
     298def linebreaks(value, autoescape = None):
    262299    "Converts newlines into <p> and <br />s"
    263300    from django.utils.html import linebreaks
    264     return linebreaks(value)
     301    autoescape = autoescape and not isinstance(value, SafeData)
     302    return mark_safe(linebreaks(value, autoescape))
    265303linebreaks = stringfilter(linebreaks)
     304linebreaks.is_safe = True
     305linebreaks.needs_autoescape = True
    266306
    267 def linebreaksbr(value):
     307def linebreaksbr(value, autoescape = None):
    268308    "Converts newlines into <br />s"
    269     return value.replace('\n', '<br />')
     309    if autoescape and not isinstance(value, SafeData):
     310        from django.utils.html import escape
     311        data = escape(value)
     312    else:
     313        data = value
     314    return mark_safe(data.replace('\n', '<br />'))
    270315linebreaksbr = stringfilter(linebreaksbr)
     316linebreaksbr.is_safe = True
     317linebreaksbr.needs_autoescape = True
     318
     319def safe(value):
     320    "Marks the value as a string that should not be auto-escaped."
     321    from django.utils.safestring import mark_safe
     322    return mark_safe(value)
     323safe = stringfilter(safe)
     324safe.is_safe = True
    271325
    272326def removetags(value, tags):
    273327    "Removes a space separated list of [X]HTML tags from the output"
    def removetags(value, tags):  
    279333    value = endtag_re.sub('', value)
    280334    return value
    281335removetags = stringfilter(removetags)
     336removetags.is_safe = True
    282337
    283338def striptags(value):
    284339    "Strips all [X]HTML tags"
    285340    from django.utils.html import strip_tags
    286341    return strip_tags(value)
    287342striptags = stringfilter(striptags)
     343striptags.is_safe = True
    288344
    289345###################
    290346# LISTS           #
    def dictsort(value, arg):  
    298354    decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    299355    decorated.sort()
    300356    return [item[1] for item in decorated]
     357dictsort.is_safe = False
    301358
    302359def dictsortreversed(value, arg):
    303360    """
    def dictsortreversed(value, arg):  
    308365    decorated.sort()
    309366    decorated.reverse()
    310367    return [item[1] for item in decorated]
     368dictsortreversed.is_safe = False
    311369
    312370def first(value):
    313371    "Returns the first item in a list"
    def first(value):  
    315373        return value[0]
    316374    except IndexError:
    317375        return ''
     376first.is_safe = True
    318377
    319378def join(value, arg):
    320379    "Joins a list with a string, like Python's ``str.join(list)``"
    321380    try:
    322         return arg.join(map(smart_string, value))
     381        data = arg.join(map(smart_string, value))
    323382    except AttributeError: # fail silently but nicely
    324383        return value
     384    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     385    if safe_args:
     386        return mark_safe(data)
     387    else:
     388        return data
     389join.is_safe = True
    325390
    326391def length(value):
    327392    "Returns the length of the value - useful for lists"
    328393    return len(value)
     394length.is_safe = False
    329395
    330396def length_is(value, arg):
    331397    "Returns a boolean of whether the value's length is the argument"
    332398    return len(value) == int(arg)
     399length.is_safe = False
    333400
    334401def random(value):
    335402    "Returns a random item from the list"
    336403    return random_module.choice(value)
     404length.is_safe = True
    337405
    338406def slice_(value, arg):
    339407    """
    def slice_(value, arg):  
    354422
    355423    except (ValueError, TypeError):
    356424        return value # Fail silently.
     425slice_.is_safe = True
    357426
    358 def unordered_list(value):
     427def unordered_list(value, autoescape = None):
    359428    """
    360429    Recursively takes a self-nested list and returns an HTML unordered list --
    361430    WITHOUT opening and closing <ul> tags.
    def unordered_list(value):  
    376445        </ul>
    377446        </li>
    378447    """
     448    if autoescape:
     449        from django.utils.html import conditional_escape
     450        escaper = conditional_escape
     451    else:
     452        escaper = lambda x: x
     453
    379454    def _helper(value, tabs):
    380455        indent = '\t' * tabs
    381456        if value[1]:
    382             return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
     457            return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent,
    383458                '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    384459        else:
    385             return '%s<li>%s</li>' % (indent, value[0])
    386     return _helper(value, 1)
     460            return '%s<li>%s</li>' % (indent, escaper(value[0]))
     461    return mark_safe(_helper(value, 1))
     462unordered_list.is_safe = True
     463unordered_list.needs_autoescape = True
    387464
    388465###################
    389466# INTEGERS        #
    ###################  
    392469def add(value, arg):
    393470    "Adds the arg to the value"
    394471    return int(value) + int(arg)
     472add.is_safe = False
    395473
    396474def get_digit(value, arg):
    397475    """
    def get_digit(value, arg):  
    411489        return int(str(value)[-arg])
    412490    except IndexError:
    413491        return 0
     492get_digit.is_safe = False
    414493
    415494###################
    416495# DATES           #
    def date(value, arg=None):  
    424503    if arg is None:
    425504        arg = settings.DATE_FORMAT
    426505    return format(value, arg)
     506date.is_safe = False
    427507
    428508def time(value, arg=None):
    429509    "Formats a time according to the given format"
    def time(value, arg=None):  
    433513    if arg is None:
    434514        arg = settings.TIME_FORMAT
    435515    return time_format(value, arg)
     516time.is_safe = False
    436517
    437518def timesince(value, arg=None):
    438519    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    def timesince(value, arg=None):  
    442523    if arg:
    443524        return timesince(arg, value)
    444525    return timesince(value)
     526timesince.is_safe = False
    445527
    446528def timeuntil(value, arg=None):
    447529    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
    def timeuntil(value, arg=None):  
    452534    if arg:
    453535        return timesince(arg, value)
    454536    return timesince(datetime.now(), value)
     537timeuntil.is_safe = False
    455538
    456539###################
    457540# LOGIC           #
    ###################  
    460543def default(value, arg):
    461544    "If value is unavailable, use given default"
    462545    return value or arg
     546default.is_safe = False
    463547
    464548def default_if_none(value, arg):
    465549    "If value is None, use given default"
    466550    if value is None:
    467551        return arg
    468552    return value
     553default_if_none.is_safe = False
    469554
    470555def divisibleby(value, arg):
    471556    "Returns true if the value is devisible by the argument"
    472557    return int(value) % int(arg) == 0
     558divisibleby.is_safe = False
    473559
    474560def yesno(value, arg=None):
    475561    """
    def yesno(value, arg=None):  
    500586    if value:
    501587        return yes
    502588    return no
     589yesno.is_safe = False
    503590
    504591###################
    505592# MISC            #
    def filesizeformat(bytes):  
    522609    if bytes < 1024 * 1024 * 1024:
    523610        return "%.1f MB" % (bytes / (1024 * 1024))
    524611    return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
     612filesizeformat.is_safe = True
    525613
    526614def pluralize(value, arg='s'):
    527615    """
    def pluralize(value, arg='s'):  
    549637        except TypeError: # len() of unsized object
    550638            pass
    551639    return singular_suffix
     640pluralize.is_safe = False
    552641
    553642def phone2numeric(value):
    554643    "Takes a phone number and converts it in to its numerical equivalent"
    555644    from django.utils.text import phone2numeric
    556645    return phone2numeric(value)
     646phone2numeric.is_safe = True
    557647
    558648def pprint(value):
    559649    "A wrapper around pprint.pprint -- for debugging, really"
    def pprint(value):  
    562652        return pformat(value)
    563653    except Exception, e:
    564654        return "Error in formatting:%s" % e
     655pprint.is_safe = True
    565656
    566657# Syntax: register.filter(name of filter, callback)
    567658register.filter(add)
    register.filter(filesizeformat)  
    580671register.filter(first)
    581672register.filter(fix_ampersands)
    582673register.filter(floatformat)
     674register.filter(force_escape)
    583675register.filter(get_digit)
    584676register.filter(join)
    585677register.filter(length)
    register.filter(pprint)  
    596688register.filter(removetags)
    597689register.filter(random)
    598690register.filter(rjust)
     691register.filter(safe)
    599692register.filter('slice', slice_)
    600693register.filter(slugify)
    601694register.filter(stringformat)
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 6aa53cfd8ba22991b8377563d8f488037f78b9bd..2fdb4ac58e497db1d46de481f86fd897c6ef02e2 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, COMMENT_TAG_START, COMMENT_TAG_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):  
    4157    def render(self, context):
    4258        output = self.nodelist.render(context)
    4359        # apply filters
    44         return self.filter_expr.resolve(Context({'var': output}))
     60        ctxt = Context({'var': output})
     61        ctxt.autoescape = context.autoescape
     62        return self.filter_expr.resolve(ctxt)
    4563
    4664class FirstOfNode(Node):
    4765    def __init__(self, vars):
    class RegroupNode(Node):  
    234252            return ''
    235253        output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]}
    236254        for obj in obj_list:
    237             grouper = self.expression.resolve(Context({'var': obj}), True)
     255            ctxt = Context({'var': obj})
     256            ctxt.autoescape = context.autoescape
     257            grouper = self.expression.resolve(ctxt, True)
    238258            # TODO: Is this a sensible way to determine equality?
    239259            if output and repr(output[-1]['grouper']) == repr(grouper):
    240260                output[-1]['list'].append(obj)
    class WidthRatioNode(Node):  
    355375        return str(int(round(ratio)))
    356376
    357377#@register.tag
     378def autoescape(parser, token):
     379    """
     380    Force autoescape behaviour for this block.
     381    """
     382    nodelist = parser.parse(('endautoescape',))
     383    parser.delete_first_token()
     384    return AutoEscapeControlNode(True, nodelist)
     385autoescape = register.tag(autoescape)
     386
     387#@register.tag
    358388def comment(parser, token):
    359389    """
    360390    Ignore everything between ``{% comment %}`` and ``{% endcomment %}``
    def do_filter(parser, token):  
    448478
    449479    Sample usage::
    450480
    451         {% filter escape|lower %}
     481        {% filter force_escape|lower %}
    452482            This text will be HTML-escaped, and will appear in lowercase.
    453483        {% endfilter %}
    454484    """
    455485    _, rest = token.contents.split(None, 1)
    456486    filter_expr = parser.compile_filter("var|%s" % (rest))
     487    for func, unused in filter_expr.filters:
     488        if func.__name__ in ('escape', 'safe'):
     489            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    457490    nodelist = parser.parse(('endfilter',))
    458491    parser.delete_first_token()
    459492    return FilterNode(filter_expr, nodelist)
    def ifchanged(parser, token):  
    694727ifchanged = register.tag(ifchanged)
    695728
    696729#@register.tag
     730def noautoescape(parser, token):
     731    """
     732    Force autoescape behaviour to be disabled for this block.
     733    """
     734    nodelist = parser.parse(('endnoautoescape',))
     735    parser.delete_first_token()
     736    return AutoEscapeControlNode(False, nodelist)
     737autoescape = register.tag(noautoescape)
     738
     739#@register.tag
    697740def ssi(parser, token):
    698741    """
    699742    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 a0d1e82dcf007822468822e3374aecd83837b006..2b314e9dabe6e22a0fea4c8020e15a5a7f739caf 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 0000000000000000000000000000000000000000..8cac661595017a5dc92a4b2dfb0e14181c1a287c
    - +  
     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
     44    def __str__(self):
     45        return self
     46
     47class SafeUnicode(unicode, SafeData):
     48    """
     49    A unicode subclass that has been specifically marked as "safe" for HTML
     50    output purposes.
     51    """
     52    def __add__(self, rhs):
     53        """
     54        Concatenating a safe unicode object with another safe string or safe
     55        unicode object is safe. Otherwise, the result is no longer safe.
     56        """
     57        if isinstance(rhs, SafeData):
     58            return SafeUnicode(self + rhs)
     59        else:
     60            return super(SafeUnicode, self).__add__(rhs)
     61
     62    def _proxy_method(self, *args, **kwargs):
     63        """
     64        Wrap a call to a normal unicode method up so that we return safe
     65        results. The method that is being wrapped is passed in the 'method'
     66        argument.
     67        """
     68        method = kwargs.pop('method')
     69        data = method(self, *args, **kwargs)
     70        if isinstance(data, str):
     71            return SafeString(data)
     72        else:
     73            return SafeUnicode(data)
     74
     75    encode = curry(_proxy_method, method = unicode.encode)
     76    decode = curry(_proxy_method, method = unicode.decode)
     77
     78
     79def mark_safe(s):
     80    """
     81    Explicitly mark a string as safe for (HTML) output purposes. The returned
     82    object can be used everywhere a string or unicode object is appropriate.
     83
     84    Can safely be called multiple times on a single string.
     85    """
     86    if isinstance(s, SafeData):
     87        return s
     88    if isinstance(s, str):
     89        return SafeString(s)
     90    if isinstance(s, unicode):
     91        return SafeUnicode(s)
     92    return SafeString(str(s))
     93
     94def mark_for_escaping(s):
     95    """
     96    Explicitly mark a string as requiring HTML escaping upon output. Has no
     97    effect on SafeData subclasses.
     98
     99    Can be safely called multiple times on a single string (the effect is only
     100    applied once).
     101    """
     102    if isinstance(s, SafeData) or isinstance(s, EscapeData):
     103        return s
     104    if isinstance(s, str):
     105        return EscapeString(s)
     106    if isinstance(s, unicode):
     107        return EscapeUnicode(s)
     108    return EscapeString(str(s))
     109
  • docs/templates.txt

    diff --git a/docs/templates.txt b/docs/templates.txt
    index d53270ac168852fe0f9336a7c4e0016c93d0526e..37f6f1c52eab98dd06aa9693b919ba0ad8cacac0 100644
    a b it also defines the content that fills t  
    270270two similarly-named ``{% block %}`` tags in a template, that template's parent
    271271wouldn't know which one of the blocks' content to use.
    272272
     273Automatic HTML escaping
     274=======================
     275
     276A very real problem when creating HTML (and other) output using templates and
     277variable substitution is the possibility of accidently inserting some variable
     278value that affects the resulting HTML. For example, a template fragment like
     279
     280::
     281
     282    Hello, {{ name }}.
     283
     284seems like a harmless way to display the user's name. However, if you are
     285displaying data that the user entered directly and they entered their name as
     286
     287::
     288
     289    <script>alert('hello')</script>
     290
     291this would always display a Javascript alert box whenever the page was loaded.
     292Similarly, if you were displaying some data generated by another process and
     293it contained a '<' symbol, you couldn't just dump this straight into your
     294HTML, because it would be treated as the start of an element.  The effects of
     295these sorts of problems can vary from merely annoying to allowing exploits via
     296`Cross Site Scripting`_ (XSS) attacks.
     297
     298.. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting
     299
     300In order to provide some protection against these problems, Django provides an
     301auto-escaping template tag. Inside this tag, any data that comes from template
     302variables is examined to see if it contains one of the five HTML characters
     303(<, >, ', " and &) that often need escaping and those characters are converted
     304to their respective HTML entities.
     305
     306Because some variables will contain data that is *intended* to be rendered
     307as HTML, template tag and filter writers can mark their output strings as
     308requiring no further escaping. For example, the ``unordered_list`` filter is
     309designed to return raw HTML and we want the template processor to simply
     310display the results as returned, without applying any escaping. That is taken
     311care of by the filter. The template author need do nothing special in that
     312case.
     313
     314By default, auto-escaping is not in effect. To enable it inside your template,
     315wrap the affected content in the ``autoescape`` tag, like so::
     316
     317    {% autoescape %}
     318        Hello {{ name }}
     319    {% endautoescape %}
     320
     321Since the auto-escaping tag passes its effect onto templates that extend the
     322current one as well as templates included via the ``include`` tag (just like
     323all block tags), if you wrap your main HTML content in an ``autoescape`` tag,
     324you will have automatic escaping applied to all of your content.
     325
     326At times, you might want to disable auto-escaping when it would otherwise be
     327in effect. You can do this with the ``noautoescape`` tag. For example::
     328
     329    {% autoescape %}
     330        Hello {{ name }}
     331
     332        {% noautoescape %}
     333            This will not be auto-escaped: {{ data }}.
     334
     335            Nor this: {{ other_data }}
     336        {% endnoautoescape %}
     337    {% endautoescape %}
     338
     339For individual variables, the ``safe`` filter can also be used.
     340
     341Generally, you will not need to worry about auto-escaping very much. Enable it
     342in your base template once you are entering the main HTML region and then
     343write your templates normally. The view developers and custom filter authors
     344need to think about when their data should not be escaped and mark it
     345appropriately.  They are in a better position to know when that should happen
     346than the template author, so it is their responsibility. By default, when
     347auto-escaping is enabled, all output is escaped unless the template processor
     348is explicitly told otherwise.
     349
    273350Using the built-in reference
    274351============================
    275352
    available, and what they do.  
    345422Built-in tag reference
    346423----------------------
    347424
     425autoescape
     426~~~~~~~~~~
     427
     428All variables that are output inside this tag have HTML escaping applied to
     429them, as if they each had the ``escape`` filter attached to them.
     430
     431The only exceptions are variables that are already marked as 'safe' from
     432escaping, either by the code that populated the variable, or because it has
     433the ``safe`` filter applied.
     434
    348435block
    349436~~~~~
    350437
    just like in variable syntax.  
    412499
    413500Sample usage::
    414501
    415     {% filter escape|lower %}
     502    {% filter force_escape|lower %}
    416503        This text will be HTML-escaped, and will appear in all lowercase.
    417504    {% endfilter %}
    418505
    Load a custom template tag set.  
    627714
    628715See `Custom tag and filter libraries`_ for more information.
    629716
     717noautoescape
     718~~~~~~~~~~~~
     719
     720Disable the effects of the ``autoescape`` tag (if it is in effect).
     721
    630722now
    631723~~~
    632724
    Escapes a string's HTML. Specifically, i  
    9501042    * ``'"'`` (double quote) to ``'&quot;'``
    9511043    * ``"'"`` (single quote) to ``'&#39;'``
    9521044
     1045The escaping is only applied when the string is output, so it does not matter
     1046where in a chained sequence of filters you put ``escape``: it will always be
     1047applied as though it were the last filter. If you want escaping to be applied
     1048immediately, use the ``force_escape`` filter.
     1049
    9531050filesizeformat
    9541051~~~~~~~~~~~~~~
    9551052
    For example:  
    9941091Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with
    9951092an argument of ``-1``.
    9961093
     1094force_escape
     1095~~~~~~~~~~~~
     1096
     1097**New in Django development version**
     1098
     1099Applies HTML escaping to a string (see the ``escape`` filter for details).
     1100This filter is applied immediately and returns a new, escaped string. This is
     1101useful in the typically rare cases where you need multiple escaping or want to
     1102apply other filters to the escaped results. Normally, you want to use the
     1103``escape`` filter.
     1104
    9971105get_digit
    9981106~~~~~~~~~
    9991107
    Right-aligns the value in a field of a g  
    11051213
    11061214**Argument:** field size
    11071215
     1216safe
     1217~~~~
     1218
     1219Marks a string as not requiring further HTML escaping prior to output. This is
     1220only useful inside an ``autoescape`` block, when the output would otherwise be
     1221automatically escaped. Outside of an ``autoescape`` block, this filter has no
     1222effect.
     1223
    11081224slice
    11091225~~~~~
    11101226
  • docs/templates_python.txt

    diff --git a/docs/templates_python.txt b/docs/templates_python.txt
    index 5dd8e4fde06ea3235ff2fa7991d6daaa053f2c8a..3c4d067ce521706d51c5de1feb3021c4b1a9385d 100644
    a b an object to it's string value before be  
    666666    def lower(value):
    667667        return value.lower()
    668668
     669Filters and auto-escaping
     670~~~~~~~~~~~~~~~~~~~~~~~~~
     671
     672When you are writing a custom filter, you need to give some thought to how
     673this filter will work when rendered in an auto-escaping environment (inside
     674an ``autoescape`` template tag block). First, you should realise that there
     675are three types of strings that can be passed around inside the template code:
     676
     677 * raw strings are the native Python ``str`` (or ``unicode``) types. On
     678   output, they are escaped if they are inside an ``autoescape`` block.
     679 * "safe" strings are strings that are safe from further escaping at output
     680   time. Any necessary escaping has already been done. They are commonly used
     681   for output that contains raw HTML that is intended to be intrepreted on the
     682   client side.
     683
     684   Internally, these strings are of type ``SafeString`` or ``SafeUnicode``,
     685   although they share a common base class in ``SafeData``, so you can test
     686   for them using code like::
     687
     688    if isinstance(value, SafeData):
     689        # Do something with the "safe" string.
     690
     691 * strings which are marked as "need escaping" are *always* escaped on
     692   output, regardless of whether they are in an ``autoescape`` block or not.
     693   These strings are only escaped once, however, even if used inside an
     694   ``autoescaep`` block.  This type of string is internally represented by the
     695   types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to
     696   worry about these; they exist only for the implementation of the ``escape``
     697   filter.
     698
     699Inside your filter, you will need to think about three areas in order to be
     700auto-escaping compliant:
     701
     702 1. If your filter returns a string that is ready for direct output (it should
     703 be considered a "safe" string), you should call
     704 ``django.utils.safestring.mark_safe()`` on the result prior to returning.
     705 This will turn the result into the appropriate ``SafeData`` type.
     706
     707 2. If your filter is given a "safe" string, is it guaranteed to return a
     708 "safe" string? If so, set the ``is_safe`` attribute on the function to be
     709 ``True``. For example, a filter that replaced all numbers with the number
     710 spelt out in words is going to be safe-string-preserving, since it cannot
     711 introduce any of the five dangerous characters: <, >, ", ' or &. So we can
     712 write::
     713
     714    @register.filter
     715    def convert_to_words(value):
     716        # ... implementation here ...
     717        return result
     718
     719    convert_to_words.is_safe = True
     720
     721 Note that this filter does not return a universally safe result (it does not
     722 return ``mark_safe(result)``) because if it is handed a raw string such as
     723 '<a>', this will need further escaping in an auto-escape environment. The
     724 ``is_safe`` attribute only talks about the safeness of the result when a safe
     725 string is passed in to the filter.
     726
     727 3. Will your filter behave differently depending upon whether auto-escaping
     728 is currently in effect or not? For example, the ``ordered_list`` filter that
     729 ships with Django needs to know whether to escape its content or not. It will
     730 always return a safe string, since it returns raw HTML, so we cannot apply
     731 escaping to the result -- it needs to be done in-situ.
     732
     733 For these cases, the filter function needs to be told what the current
     734 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the
     735 filter to ``True`` and have your function take an extra argument called
     736 ``autoescape`` with a default value of ``None``. When the filter is called,
     737 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in
     738 effect. For example, the ``unordered_list`` filter is written as::
     739
     740    def unordered_list(value, autoescape = None):
     741        # ... lots of code here ...
     742
     743        return mark_safe(...)
     744
     745    unordered_list.is_safe = True
     746    unordered_list.needs_autoescape = True
     747
     748By default, both the ``is_safe`` and ``needs_autoescape`` attributes are
     749``False``. You do not need to specify them if ``False`` is an acceptable
     750value.
     751
     752As a matter of convention, we leave ``is_safe`` as ``False`` for filters that
     753do not accept string inputs (they might take a number as an input, for
     754example) or for those that return a non-string (e.g. the ``length`` filter).
     755However, not following this convention will not cause any harm or make your
     756results any more vulnerable to cross-site scripting problems.
     757
    669758Writing custom template tags
    670759----------------------------
    671760
    Ultimately, this decoupling of compilati  
    784873efficient template system, because a template can render multiple context
    785874without having to be parsed multiple times.
    786875
     876Auto-escaping considerations
     877~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     878
     879The output from template tags is not automatically run through the
     880auto-escaping filters if used inside an ``autoescape`` tag. However, there are
     881still a couple of things you should keep in mind when writing a template tag:
     882
     883If the ``render()`` function of your template stores the result in a context
     884variable (rather than returning the result in a string), it should take care
     885to call ``mark_safe()`` if appropriate. When the variable is ultimately
     886rendered, it will be affected by the auto-escape setting in effect at the
     887time, so content that should be safe from further escaping needs to be marked
     888as such.
     889
     890Also, if your template tag creates a new context for performing some
     891sub-rendering, you should be careful to set the auto-escape variable to the
     892current context's value. For example::
     893
     894    def render(self, context):
     895        # ...
     896        new_context = Context({'var': obj})
     897        new_context.autoescape = context.autoescape
     898        # ... Do something with new_context ...
     899
     900This is not a very common situation, but it is sometimes useful (see
     901``django.templates.defaulttags.FilterNode.render()`` for an example).
     902
    787903Registering the tag
    788904~~~~~~~~~~~~~~~~~~~
    789905
  • 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..22b89e28e6bf032aa6c287f1861adedb3023bb47
    - +  
     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("&")}, "[&#39;&amp;&#39;]"),
     87            'ae-filter-make_list02': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, "['&']"),
     88       
     89            # Running slugify on a pre-escaped string leads to odd behaviour, but the
     90            # result is still safe.
     91            'ae-filter-slugify01': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a &amp; b")}, "a-b a-amp-b"),
     92           
     93            # Notice that escaping is applied *after* any filters, so the string
     94            # formatting here only needs to deal with pre-escaped characters.
     95            'ae-filter-stringformat01': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ".  a&lt;b. .  a<b."),
     96       
     97            # XXX No test for "title" filter; needs an actual object.
     98           
     99            'ae-filter-truncatewords01': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha &amp; bravo")}, "alpha &amp; ... alpha &amp; ..."),
     100       
     101            # The "upper" filter messes up entities (which are case-sensitive), so it's
     102            # not safe for non-escaping purposes.
     103            'ae-filter-upper01': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "A &amp; B A &amp;AMP; B"),
     104       
     105            'ae-filter-urlize01': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'),
     106            'ae-filter-urlize02': ('{{ a|urlize }}', {"a": mark_safe("a &amp; b")}, 'a &amp; b'),
     107       
     108            'ae-filter-urlizetrunc01': ('{{ a|urlizetrunc:"5" }} {{ b|urlizetrunc:"5" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'),
     109       
     110            'ae-filter-wordcount01': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a &amp; b")}, "3 3"),
     111       
     112            'ae-filter-wordwrap01': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &amp;\nb a &\nb"),
     113       
     114            'ae-filter-ljust01': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&amp;b  . .a&b  ."),
     115       
     116            'ae-filter-rjust01': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".  a&amp;b. .  a&b."),
     117       
     118            'ae-filter-center01': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&amp;b . . a&b ."),
     119       
     120            # Because "cut" might remove a leading ampersand, so the results are not
     121            # safe.
     122            'ae-filter-cut01': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "&amp;y &amp;amp;y"),
     123            'ae-filter-cut02': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&amp;y")}, "xy xamp;y"),
     124       
     125            # The "escape" filter works the same whether autoescape is on or off, but
     126            # it has no effect on strings already marked as safe.
     127            'ae-filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;y x&y"),
     128            'ae-filter-escape02': ('{% noautoescape %}{{ a|escape }} {{ b|escape }}{% endnoautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&amp;y x&y"),
     129       
     130            # It is only applied once, regardless of the number of times it appears in
     131            # a chain.
     132            'ae-filter-escape03': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&amp;y"),
     133       
     134            # Force_escape is applied immediately. It can be used to provide
     135            # double-escaping, for example.
     136            'ae-filter-force-escape01': ('{{ a|force_escape }}', {"a": "x&y"}, "x&amp;y"),
     137            'ae-filter-force-escape02': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&amp;amp;y"),
     138       
     139            # Because the result of force_escape is "safe", an additional escape filter
     140            # has no effect.
     141            'ae-filter-force-escape03': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&amp;y"),
     142       
     143            # The contents in "linebreaks" and "linebreaksbr" are escaped according to
     144            # the current autoescape setting.
     145            'ae-filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&amp;<br />y</p> <p>x&<br />y</p>"),
     146            'ae-filter-linebreaks02': ('{% noautoescape %}{{ a|linebreaks }} {{ b|linebreaks }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"),
     147       
     148            'ae-filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&amp;<br />y x&<br />y"),
     149            'ae-filter-linebreaksbr02': ('{% noautoescape %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"),
     150       
     151            'ae-filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "&lt;b&gt;hello&lt;/b&gt; -- <b>hello</b>"),
     152            'ae-filter-safe02': ("{% noautoescape %}{{ a }} -- {{ a|safe }}{% endnoautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"),
     153       
     154            'ae-filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x &lt;p&gt;y&lt;/p&gt; x <p>y</p>"),
     155       
     156            'ae-filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"),
     157       
     158            'ae-filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&amp;b a&b"),
     159       
     160            'ae-filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&amp;b a&b"),
     161       
     162            'ae-filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&amp;b &b"),
     163       
     164            'ae-filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x&gt;\n\t<ul>\n\t\t<li>&lt;y</li>\n\t</ul>\n\t</li>"),
     165            'ae-filter-unordered_list02': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x&gt;\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
     166            'ae-filter-unordered_list03': ('{% noautoescape %}{{ a|unordered_list }}{% endnoautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"),
     167       
     168            # If the input to "default" filter is marked as safe, then so is the
     169            # output. However, if the default arg is used, auto-escaping kicks in (if
     170            # enabled), because we cannot mark the default as safe.
     171            # Note: we have to use {"a": ""} here, otherwise the invalid template
     172            # variable string interferes with the test result.
     173            'ae-filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x&lt;"),
     174            'ae-filter-default02': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"),
     175       
     176            'ae-filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x&lt;"),
     177       
     178            'ae-filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "&lt;1-800-2255-63&gt; <1-800-2255-63>"),
     179       
     180            # Chaining a bunch of safeness-preserving filters should not alter the safe
     181            # status either way.
     182            'ae-chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A &lt; b . A < b "),
     183       
     184            # Using a filter that forces a string back to unsafe:
     185            'ae-chaining02': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A &lt; .A &lt; "),
     186       
     187            # Using a filter that forces safeness does not lead to double-escaping
     188            'ae-chaining03': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A &lt; b"),
     189       
     190            # Force to safe, then back (also showing why using force_escape too early
     191            # in a chain can lead to unexpected results).
     192            'ae-chaining04': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a &amp;lt; "),
     193            'ae-chaining05': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a &lt; "),
     194            'ae-chaining06': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "),
     195            'ae-chaining07': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a &lt; b"),
     196        })
     197
     198        return TEMPLATE_TESTS
     199
     200
     201   
     202
     203#def test_template_loader(template_name, template_dirs=None):
     204    #"A custom template loader that loads the unit-test templates."
     205    #try:
     206        #return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     207    #except KeyError:
     208        #raise template.TemplateDoesNotExist, template_name
     209
     210#def run_tests(verbosity=0, standalone=False):
     211    ## Register our custom template loader.
     212    #old_template_loaders = loader.template_source_loaders
     213    #loader.template_source_loaders = [test_template_loader]
     214
     215    #failed_tests = []
     216    #tests = TEMPLATE_TESTS.items()
     217    #tests.sort()
     218
     219    ## Turn TEMPLATE_DEBUG off, because tests assume that.
     220    #old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     221    ## Set TEMPLATE_STRING_IF_INVALID to a known string
     222    #old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID'
     223   
     224    #for name, vals in tests:
     225        #install()
     226        #if 'LANGUAGE_CODE' in vals[1]:
     227            #activate(vals[1]['LANGUAGE_CODE'])
     228        #else:
     229            #activate('en-us')
     230        #try:
     231            #ctxt = template.Context(vals[1])
     232            ## Hack for testing: force autoescaping to be in effect.
     233            #ctxt.autoescape = True
     234            #output = loader.get_template(name).render(ctxt)
     235        #except Exception, e:
     236            #if e.__class__ == vals[2]:
     237                #if verbosity:
     238                    #print "Template test: %s -- Passed" % name
     239            #else:
     240                #if verbosity:
     241                    #traceback.print_exc()
     242                    #print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e)
     243                #failed_tests.append(name)
     244            #continue
     245        #if 'LANGUAGE_CODE' in vals[1]:
     246            #deactivate()
     247        #if output == vals[2]:
     248            #if verbosity:
     249                #print "Template test: %s -- Passed" % name
     250        #else:
     251            #if verbosity:
     252                #print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output)
     253            #failed_tests.append(name)
     254    #loader.template_source_loaders = old_template_loaders
     255    #deactivate()
     256    #settings.TEMPLATE_DEBUG = old_td
     257    #settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     258
     259    #if failed_tests and not standalone:
     260        #msg = "Template tests %s failed." % failed_tests
     261        #if not verbosity:
     262            #msg += "  Re-run tests with -v1 to see actual failures"
     263        #raise Exception, msg
     264
     265if __name__ == "__main__":
     266    unittest.main()
  • tests/regressiontests/defaultfilters/tests.py

    diff --git a/tests/regressiontests/defaultfilters/tests.py b/tests/regressiontests/defaultfilters/tests.py
    index c850806052a430694ee047936b601931811c24e2..afe35394beba7ba0c1c1658ec83c3f483c560843 100644
    a b u'\xcb'  
    157157>>> cut('a string to be mangled', 'strings')
    158158'a string to be mangled'
    159159
    160 >>> escape('<some html & special characters > here')
     160>>> force_escape('<some html & special characters > here')
    161161'&lt;some html &amp; special characters &gt; here'
    162162
    163163>>> linebreaks('line 1')
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 0165824951d09a9e2bb48ce487020ce1d16f08b8..56dce95752b939c2a3dfb2be9f13a424caff613a 100644
    a b class UnicodeInStrClass:  
    7070
    7171class Templates(unittest.TestCase):
    7272    def test_templates(self):
     73        TEMPLATE_TESTS = self.get_template_tests()
     74
     75        # Register our custom template loader.
     76        def test_template_loader(template_name, template_dirs=None):
     77            "A custom template loader that loads the unit-test templates."
     78            try:
     79                return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
     80            except KeyError:
     81                raise template.TemplateDoesNotExist, template_name
     82
     83        old_template_loaders = loader.template_source_loaders
     84        loader.template_source_loaders = [test_template_loader]
     85
     86        failures = []
     87        tests = TEMPLATE_TESTS.items()
     88        tests.sort()
     89
     90        # Turn TEMPLATE_DEBUG off, because tests assume that.
     91        old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
     92
     93        # Set TEMPLATE_STRING_IF_INVALID to a known string
     94        old_invalid = settings.TEMPLATE_STRING_IF_INVALID
     95
     96        for name, vals in tests:
     97            install()
     98
     99            if isinstance(vals[2], tuple):
     100                normal_string_result = vals[2][0]
     101                invalid_string_result = vals[2][1]
     102            else:
     103                normal_string_result = vals[2]
     104                invalid_string_result = vals[2]
     105
     106            if 'LANGUAGE_CODE' in vals[1]:
     107                activate(vals[1]['LANGUAGE_CODE'])
     108            else:
     109                activate('en-us')
     110
     111            for invalid_str, result in [('', normal_string_result),
     112                                        ('INVALID', invalid_string_result)]:
     113                settings.TEMPLATE_STRING_IF_INVALID = invalid_str
     114                try:
     115                    test_template = loader.get_template(name)
     116                    output = self.render(test_template, vals)
     117                except Exception, e:
     118                    if e.__class__ != result:
     119                        failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
     120                    continue
     121                if output != result:
     122                    failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
     123
     124            if 'LANGUAGE_CODE' in vals[1]:
     125                deactivate()
     126
     127        loader.template_source_loaders = old_template_loaders
     128        deactivate()
     129        settings.TEMPLATE_DEBUG = old_td
     130        settings.TEMPLATE_STRING_IF_INVALID = old_invalid
     131
     132        self.assertEqual(failures, [], '\n'.join(failures))
     133
     134    def render(self, test_template, vals):
     135        return test_template.render(template.Context(vals[1]))
     136
     137    def get_template_tests(self):
    73138        # NOW and NOW_tz are used by timesince tag tests.
    74139        NOW = datetime.now()
    75140        NOW_tz = datetime.now(LocalTimezone(datetime.now()))
    76141
    77142        # SYNTAX --
    78143        # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class)
    79         TEMPLATE_TESTS = {
    80 
     144        return {
    81145            ### BASIC SYNTAX ##########################################################
    82146
    83147            # Plain text should go through the template parser untouched
    class Templates(unittest.TestCase):  
    681745            'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
    682746        }
    683747
    684         # Register our custom template loader.
    685         def test_template_loader(template_name, template_dirs=None):
    686             "A custom template loader that loads the unit-test templates."
    687             try:
    688                 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
    689             except KeyError:
    690                 raise template.TemplateDoesNotExist, template_name
    691 
    692         old_template_loaders = loader.template_source_loaders
    693         loader.template_source_loaders = [test_template_loader]
    694 
    695         failures = []
    696         tests = TEMPLATE_TESTS.items()
    697         tests.sort()
    698 
    699         # Turn TEMPLATE_DEBUG off, because tests assume that.
    700         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
    701 
    702         # Set TEMPLATE_STRING_IF_INVALID to a known string
    703         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
    704 
    705         for name, vals in tests:
    706             install()
    707 
    708             if isinstance(vals[2], tuple):
    709                 normal_string_result = vals[2][0]
    710                 invalid_string_result = vals[2][1]
    711             else:
    712                 normal_string_result = vals[2]
    713                 invalid_string_result = vals[2]
    714 
    715             if 'LANGUAGE_CODE' in vals[1]:
    716                 activate(vals[1]['LANGUAGE_CODE'])
    717             else:
    718                 activate('en-us')
    719 
    720             for invalid_str, result in [('', normal_string_result),
    721                                         ('INVALID', invalid_string_result)]:
    722                 settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    723                 try:
    724                     output = loader.get_template(name).render(template.Context(vals[1]))
    725                 except Exception, e:
    726                     if e.__class__ != result:
    727                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    728                     continue
    729                 if output != result:
    730                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
    731 
    732             if 'LANGUAGE_CODE' in vals[1]:
    733                 deactivate()
    734 
    735         loader.template_source_loaders = old_template_loaders
    736         deactivate()
    737         settings.TEMPLATE_DEBUG = old_td
    738         settings.TEMPLATE_STRING_IF_INVALID = old_invalid
    739 
    740         self.assertEqual(failures, [], '\n'.join(failures))
    741 
    742748if __name__ == "__main__":
    743749    unittest.main()
Back to Top