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

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

updated patch, see comment

  • django/oldforms/__init__.py

    From nobody Mon Sep 17 00:00:00 2001
    From: Michael Radziej <mir@noris.de>
    Date: Mon Mar 19 13:34:53 2007 +0100
    Subject: [PATCH] autoescape 1
    
    Extended the check for 'escape' or 'safe' in `do_filter` so
    that it can deal with decorators like `stringfilter`.
    to
    
    ---
    
     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 c52c291f3430b51fc122a093ebbff8f619a7a23e
    last 339180bd0aa87b4026c063e53726a2b0267f73cf
    diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py
    index 56101984f5332e8832ac14c49d7a7a1488a90ef0..e9a21ce7f5d5f37c8c36a88076ec4088e5dfd846 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):  
    183184
    184185    def html_error_list(self):
    185186        if self.errors():
    186             return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])
     187            return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()]))
    187188        else:
    188             return ''
     189            return mark_safe('')
    189190
    190191    def get_id(self):
    191192        return self.formfield.get_id()
    class FormFieldCollection(FormFieldWrapp  
    217218        return bool(len(self.errors()))
    218219
    219220    def html_combined_error_list(self):
    220         return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])
     221        return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]))
    221222
    222223class InlineObjectCollection(object):
    223224    "An object that acts like a sparse list of form field collections."
    class TextField(FormField):  
    404405            maxlength = 'maxlength="%s" ' % self.maxlength
    405406        if isinstance(data, unicode):
    406407            data = data.encode(settings.DEFAULT_CHARSET)
    407         return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
     408        return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \
    408409            (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    409             self.field_name, self.length, escape(data), maxlength)
     410            self.field_name, self.length, escape(data), maxlength))
    410411
    411412    def html2python(data):
    412413        return data
    class LargeTextField(TextField):  
    430431            data = ''
    431432        if isinstance(data, unicode):
    432433            data = data.encode(settings.DEFAULT_CHARSET)
    433         return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
     434        return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \
    434435            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '',
    435             self.field_name, self.rows, self.cols, escape(data))
     436            self.field_name, self.rows, self.cols, escape(data)))
    436437
    437438class HiddenField(FormField):
    438439    def __init__(self, field_name, is_required=False, validator_list=None):
    class HiddenField(FormField):  
    441442        self.validator_list = validator_list[:]
    442443
    443444    def render(self, data):
    444         return '<input type="hidden" id="%s" name="%s" value="%s" />' % \
    445             (self.get_id(), self.field_name, escape(data))
     445        return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \
     446            (self.get_id(), self.field_name, escape(data)))
    446447
    447448class CheckboxField(FormField):
    448449    def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False):
    class CheckboxField(FormField):  
    456457        checked_html = ''
    457458        if data or (data is '' and self.checked_by_default):
    458459            checked_html = ' checked="checked"'
    459         return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
     460        return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \
    460461            (self.get_id(), self.__class__.__name__,
    461             self.field_name, checked_html)
     462            self.field_name, checked_html))
    462463
    463464    def html2python(data):
    464465        "Convert value from browser ('on' or '') to a Python boolean"
    class SelectField(FormField):  
    489490                selected_html = ' selected="selected"'
    490491            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name)))
    491492        output.append('  </select>')
    492         return '\n'.join(output)
     493        return mark_safe('\n'.join(output))
    493494
    494495    def isValidChoice(self, data, form):
    495496        str_data = str(data)
    class RadioSelectField(FormField):  
    542543                output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')]
    543544                output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist])
    544545                output.append('</ul>')
    545                 return ''.join(output)
     546                return mark_safe(''.join(output))
    546547            def __iter__(self):
    547548                for d in self.datalist:
    548549                    yield d
    class RadioSelectField(FormField):  
    557558            datalist.append({
    558559                'value': value,
    559560                'name': display_name,
    560                 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
    561                     (self.get_id() + '_' + str(i), self.field_name, value, selected_html),
    562                 'label': '<label for="%s">%s</label>' % \
     561                'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \
     562                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html)),
     563                'label': mark_safe('<label for="%s">%s</label>' % \
    563564                    (self.get_id() + '_' + str(i), display_name),
    564             })
     565            )})
    565566        return RadioFieldRenderer(datalist, self.ul_class)
    566567
    567568    def isValidChoice(self, data, form):
    class SelectMultipleField(SelectField):  
    600601                selected_html = ' selected="selected"'
    601602            output.append('    <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice)))
    602603        output.append('  </select>')
    603         return '\n'.join(output)
     604        return mark_safe('\n'.join(output))
    604605
    605606    def isValidChoice(self, field_data, all_data):
    606607        # data is something like ['1', '2', '3']
    class CheckboxSelectMultipleField(Select  
    653654                (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html,
    654655                self.get_id() + escape(value), choice))
    655656        output.append('</ul>')
    656         return '\n'.join(output)
     657        return mark_safe('\n'.join(output))
    657658
    658659####################
    659660# FILE UPLOADS     #
    class FileUploadField(FormField):  
    674675            raise validators.CriticalValidationError, gettext("The submitted file is empty.")
    675676
    676677    def render(self, data):
    677         return '<input type="file" id="%s" class="v%s" name="%s" />' % \
    678             (self.get_id(), self.__class__.__name__, self.field_name)
     678        return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \
     679            (self.get_id(), self.__class__.__name__, self.field_name))
    679680
    680681    def html2python(data):
    681682        if data is None:
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index 0d8990a42b61273588ff318667c2ea1a701fe573..53201c225138497be2848a593f36d81a4d3242e2 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):  
    576578                    arg_vals.append(arg)
    577579                else:
    578580                    arg_vals.append(resolve_variable(arg, context))
    579             obj = func(obj, *arg_vals)
     581            if getattr(func, 'needs_autoescape', False):
     582                new_obj = func(obj, autoescape = context.autoescape, *arg_vals)
     583            else:
     584                new_obj = func(obj, *arg_vals)
     585            if getattr(func, 'is_safe', False) and isinstance(obj, SafeData):
     586                obj = mark_safe(new_obj)
     587            elif isinstance(obj, EscapeData):
     588                obj = mark_for_escaping(new_obj)
     589            else:
     590                obj = new_obj
     591               
    580592        return obj
    581593
    582594    def args_check(name, func, provided):
    class VariableNode(Node):  
    765777
    766778    def render(self, context):
    767779        output = self.filter_expression.resolve(context)
    768         return self.encode_output(output)
     780        encoded_output = self.encode_output(output)
     781        if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData):
     782            return escape(encoded_output)
     783        else:
     784            return encoded_output
    769785
    770786class DebugVariableNode(VariableNode):
    771787    def render(self, context):
    class DebugVariableNode(VariableNode):  
    775791            if not hasattr(e, 'source'):
    776792                e.source = self.source
    777793            raise
    778         return self.encode_output(output)
     794        encoded_output = self.encode_output(output)
     795        if context.autoescape and not isinstance(encoded_output, SafeData):
     796            return escape(encoded_output)
     797        else:
     798            return encoded_output
    779799
    780800def generic_tag_compiler(params, defaults, name, node_class, parser, token):
    781801    "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 a025365c908eec6c7962a9cca28665c87e30386e..a0165b3dad0d7f82b181873809d4e07102565301 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):  
    9397        return '%d' % int(f)
    9498    else:
    9599        formatstr = '%%.%df' % abs(d)
    96         return formatstr % f
     100        return mark_safe(formatstr % f)
     101floatformat.is_safe = True
    97102
    98 def linenumbers(value):
     103def linenumbers(value, autoescape = None):
    99104    "Displays text with line numbers"
    100105    from django.utils.html import escape
    101106    lines = value.split('\n')
    102107    # Find the maximum width of the line count, for use with zero padding string format command
    103108    width = str(len(str(len(lines))))
    104     for i, line in enumerate(lines):
    105         lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
    106     return '\n'.join(lines)
     109    if not autoescape or isinstance(value, SafeData):
     110        for i, line in enumerate(lines):
     111            lines[i] = ("%0" + width  + "d. %s") % (i + 1, line)
     112    else:
     113        for i, line in enumerate(lines):
     114            lines[i] = ("%0" + width  + "d. %s") % (i + 1, escape(line))
     115    return mark_safe('\n'.join(lines))
    107116linenumbers = stringfilter(linenumbers)
     117linenumbers.is_safe = True
     118linenumbers.needs_autoescape = True
    108119
    109120def lower(value):
    110121    "Converts a string into all lowercase"
    111122    return value.lower()
    112123lower = stringfilter(lower)
     124lower.is_safe = True
    113125
    114126def make_list(value):
    115127    """
    def make_list(value):  
    118130    """
    119131    return list(value)
    120132make_list = stringfilter(make_list)
     133make_list.is_safe = False
    121134
    122135def slugify(value):
    123136    "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
    124137    value = re.sub('[^\w\s-]', '', value).strip().lower()
    125     return re.sub('[-\s]+', '-', value)
     138    return mark_safe(re.sub('[-\s]+', '-', value))
    126139slugify = stringfilter(slugify)
     140slugify.is_safe = True
    127141
    128142def stringformat(value, arg):
    129143    """
    def stringformat(value, arg):  
    138152        return ("%" + str(arg)) % value
    139153    except (ValueError, TypeError):
    140154        return ""
     155stringformat.is_safe = True
    141156
    142157def title(value):
    143158    "Converts a string into titlecase"
    144159    return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
    145160title = stringfilter(title)
     161title.is_safe = False
    146162
    147163def truncatewords(value, arg):
    148164    """
    def truncatewords(value, arg):  
    159175        value = str(value)
    160176    return truncate_words(value, length)
    161177truncatewords = stringfilter(truncatewords)
     178truncatewords.is_safe = True
    162179
    163180def truncatewords_html(value, arg):
    164181    """
    def upper(value):  
    180197    "Converts a string into all uppercase"
    181198    return value.upper()
    182199upper = stringfilter(upper)
     200upper.is_safe = False
    183201
    184202def urlencode(value):
    185203    "Escapes a value for use in a URL"
    def urlencode(value):  
    188206        value = str(value)
    189207    return urllib.quote(value)
    190208urlencode = stringfilter(urlencode)
     209urlencode.is_safe = False
    191210
    192211def urlize(value):
    193212    "Converts URLs in plain text into clickable links"
    194213    from django.utils.html import urlize
    195     return urlize(value, nofollow=True)
     214    return mark_safe(urlize(value, nofollow=True))
    196215urlize = stringfilter(urlize)
     216urlize.is_safe = True
    197217
    198218def urlizetrunc(value, limit):
    199219    """
    def urlizetrunc(value, limit):  
    203223    Argument: Length to truncate URLs to.
    204224    """
    205225    from django.utils.html import urlize
    206     return urlize(value, trim_url_limit=int(limit), nofollow=True)
     226    return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True))
    207227urlizetrunc = stringfilter(urlizetrunc)
     228urlize.is_safe = True
    208229
    209230def wordcount(value):
    210231    "Returns the number of words"
    211232    return len(value.split())
    212233wordcount = stringfilter(wordcount)
     234wordcount.is_safe = False
    213235
    214236def wordwrap(value, arg):
    215237    """
    def wordwrap(value, arg):  
    220242    from django.utils.text import wrap
    221243    return wrap(value, int(arg))
    222244wordwrap = stringfilter(wordwrap)
     245wordwrap.is_safe = True
    223246
    224247def ljust(value, arg):
    225248    """
    def ljust(value, arg):  
    229252    """
    230253    return value.ljust(int(arg))
    231254ljust = stringfilter(ljust)
     255ljust.is_safe = True
    232256
    233257def rjust(value, arg):
    234258    """
    def rjust(value, arg):  
    238262    """
    239263    return value.rjust(int(arg))
    240264rjust = stringfilter(rjust)
     265rjust.is_safe = True
    241266
    242267def center(value, arg):
    243268    "Centers the value in a field of a given width"
    244269    return value.center(int(arg))
    245270center = stringfilter(center)
     271center.is_safe = True
    246272
    247273def cut(value, arg):
    248274    "Removes all values of arg from the given string"
    249275    return value.replace(arg, '')
    250276cut = stringfilter(cut)
     277cut.is_safe = False
    251278
    252279###################
    253280# HTML STRINGS    #
    254281###################
    255282
    256283def escape(value):
    257     "Escapes a string's HTML"
     284    "Marks the value as a string that should not be auto-escaped."
     285    from django.utils.safestring import mark_for_escaping
     286    return mark_for_escaping(value)
     287escape = stringfilter(escape)
     288escape.is_safe = True
     289
     290def force_escape(value):
     291    """Escapes a string's HTML. This returns a new string containing the escaped
     292    characters (as opposed to "escape", which marks the content for later
     293    possible escaping)."""
    258294    from django.utils.html import escape
    259     return escape(value)
     295    return mark_safe(escape(value))
    260296escape = stringfilter(escape)
     297force_escape.is_safe = True
    261298
    262 def linebreaks(value):
     299def linebreaks(value, autoescape = None):
    263300    "Converts newlines into <p> and <br />s"
    264301    from django.utils.html import linebreaks
    265     return linebreaks(value)
     302    autoescape = autoescape and not isinstance(value, SafeData)
     303    return mark_safe(linebreaks(value, autoescape))
    266304linebreaks = stringfilter(linebreaks)
     305linebreaks.is_safe = True
     306linebreaks.needs_autoescape = True
    267307
    268 def linebreaksbr(value):
     308def linebreaksbr(value, autoescape = None):
    269309    "Converts newlines into <br />s"
    270     return value.replace('\n', '<br />')
     310    if autoescape and not isinstance(value, SafeData):
     311        from django.utils.html import escape
     312        data = escape(value)
     313    else:
     314        data = value
     315    return mark_safe(data.replace('\n', '<br />'))
    271316linebreaksbr = stringfilter(linebreaksbr)
     317linebreaksbr.is_safe = True
     318linebreaksbr.needs_autoescape = True
     319
     320def safe(value):
     321    "Marks the value as a string that should not be auto-escaped."
     322    from django.utils.safestring import mark_safe
     323    return mark_safe(value)
     324safe = stringfilter(safe)
     325safe.is_safe = True
    272326
    273327def removetags(value, tags):
    274328    "Removes a space separated list of [X]HTML tags from the output"
    def removetags(value, tags):  
    280334    value = endtag_re.sub('', value)
    281335    return value
    282336removetags = stringfilter(removetags)
     337removetags.is_safe = True
    283338
    284339def striptags(value):
    285340    "Strips all [X]HTML tags"
    286341    from django.utils.html import strip_tags
    287342    return strip_tags(value)
    288343striptags = stringfilter(striptags)
     344striptags.is_safe = True
    289345
    290346###################
    291347# LISTS           #
    def dictsort(value, arg):  
    299355    decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
    300356    decorated.sort()
    301357    return [item[1] for item in decorated]
     358dictsort.is_safe = False
    302359
    303360def dictsortreversed(value, arg):
    304361    """
    def dictsortreversed(value, arg):  
    309366    decorated.sort()
    310367    decorated.reverse()
    311368    return [item[1] for item in decorated]
     369dictsortreversed.is_safe = False
    312370
    313371def first(value):
    314372    "Returns the first item in a list"
    def first(value):  
    316374        return value[0]
    317375    except IndexError:
    318376        return ''
     377first.is_safe = True
    319378
    320379def join(value, arg):
    321380    "Joins a list with a string, like Python's ``str.join(list)``"
    322381    try:
    323         return arg.join(map(smart_string, value))
     382        data = arg.join(map(smart_string, value))
    324383    except AttributeError: # fail silently but nicely
    325384        return value
     385    safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True)
     386    if safe_args:
     387        return mark_safe(data)
     388    else:
     389        return data
     390join.is_safe = True
    326391
    327392def length(value):
    328393    "Returns the length of the value - useful for lists"
    329394    return len(value)
     395length.is_safe = False
    330396
    331397def length_is(value, arg):
    332398    "Returns a boolean of whether the value's length is the argument"
    333399    return len(value) == int(arg)
     400length.is_safe = False
    334401
    335402def random(value):
    336403    "Returns a random item from the list"
    337404    return random_module.choice(value)
     405length.is_safe = True
    338406
    339407def slice_(value, arg):
    340408    """
    def slice_(value, arg):  
    355423
    356424    except (ValueError, TypeError):
    357425        return value # Fail silently.
     426slice_.is_safe = True
    358427
    359 def unordered_list(value):
     428def unordered_list(value, autoescape = None):
    360429    """
    361430    Recursively takes a self-nested list and returns an HTML unordered list --
    362431    WITHOUT opening and closing <ul> tags.
    def unordered_list(value):  
    377446        </ul>
    378447        </li>
    379448    """
     449    if autoescape:
     450        from django.utils.html import conditional_escape
     451        escaper = conditional_escape
     452    else:
     453        escaper = lambda x: x
     454
    380455    def _helper(value, tabs):
    381456        indent = '\t' * tabs
    382457        if value[1]:
    383             return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
     458            return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent,
    384459                '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
    385460        else:
    386             return '%s<li>%s</li>' % (indent, value[0])
    387     return _helper(value, 1)
     461            return '%s<li>%s</li>' % (indent, escaper(value[0]))
     462    return mark_safe(_helper(value, 1))
     463unordered_list.is_safe = True
     464unordered_list.needs_autoescape = True
    388465
    389466###################
    390467# INTEGERS        #
    ###################  
    393470def add(value, arg):
    394471    "Adds the arg to the value"
    395472    return int(value) + int(arg)
     473add.is_safe = False
    396474
    397475def get_digit(value, arg):
    398476    """
    def get_digit(value, arg):  
    412490        return int(str(value)[-arg])
    413491    except IndexError:
    414492        return 0
     493get_digit.is_safe = False
    415494
    416495###################
    417496# DATES           #
    def date(value, arg=None):  
    425504    if arg is None:
    426505        arg = settings.DATE_FORMAT
    427506    return format(value, arg)
     507date.is_safe = False
    428508
    429509def time(value, arg=None):
    430510    "Formats a time according to the given format"
    def time(value, arg=None):  
    434514    if arg is None:
    435515        arg = settings.TIME_FORMAT
    436516    return time_format(value, arg)
     517time.is_safe = False
    437518
    438519def timesince(value, arg=None):
    439520    'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
    def timesince(value, arg=None):  
    443524    if arg:
    444525        return timesince(arg, value)
    445526    return timesince(value)
     527timesince.is_safe = False
    446528
    447529def timeuntil(value, arg=None):
    448530    'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
    def timeuntil(value, arg=None):  
    453535    if arg:
    454536        return timesince(arg, value)
    455537    return timesince(datetime.now(), value)
     538timeuntil.is_safe = False
    456539
    457540###################
    458541# LOGIC           #
    ###################  
    461544def default(value, arg):
    462545    "If value is unavailable, use given default"
    463546    return value or arg
     547default.is_safe = False
    464548
    465549def default_if_none(value, arg):
    466550    "If value is None, use given default"
    467551    if value is None:
    468552        return arg
    469553    return value
     554default_if_none.is_safe = False
    470555
    471556def divisibleby(value, arg):
    472557    "Returns true if the value is devisible by the argument"
    473558    return int(value) % int(arg) == 0
     559divisibleby.is_safe = False
    474560
    475561def yesno(value, arg=None):
    476562    """
    def yesno(value, arg=None):  
    501587    if value:
    502588        return yes
    503589    return no
     590yesno.is_safe = False
    504591
    505592###################
    506593# MISC            #
    def filesizeformat(bytes):  
    523610    if bytes < 1024 * 1024 * 1024:
    524611        return "%.1f MB" % (bytes / (1024 * 1024))
    525612    return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
     613filesizeformat.is_safe = True
    526614
    527615def pluralize(value, arg='s'):
    528616    """
    def pluralize(value, arg='s'):  
    550638        except TypeError: # len() of unsized object
    551639            pass
    552640    return singular_suffix
     641pluralize.is_safe = False
    553642
    554643def phone2numeric(value):
    555644    "Takes a phone number and converts it in to its numerical equivalent"
    556645    from django.utils.text import phone2numeric
    557646    return phone2numeric(value)
     647phone2numeric.is_safe = True
    558648
    559649def pprint(value):
    560650    "A wrapper around pprint.pprint -- for debugging, really"
    def pprint(value):  
    563653        return pformat(value)
    564654    except Exception, e:
    565655        return "Error in formatting:%s" % e
     656pprint.is_safe = True
    566657
    567658# Syntax: register.filter(name of filter, callback)
    568659register.filter(add)
    register.filter(filesizeformat)  
    581672register.filter(first)
    582673register.filter(fix_ampersands)
    583674register.filter(floatformat)
     675register.filter(force_escape)
    584676register.filter(get_digit)
    585677register.filter(join)
    586678register.filter(length)
    register.filter(pprint)  
    597689register.filter(removetags)
    598690register.filter(random)
    599691register.filter(rjust)
     692register.filter(safe)
    600693register.filter('slice', slice_)
    601694register.filter(slugify)
    602695register.filter(stringformat)
  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index ed870047b1305916e4f546c98c6b95cad5b1cce2..152aeef4b2ca62285a9e42937cf3a28b70132332 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):  
    457487
    458488    Sample usage::
    459489
    460         {% filter escape|lower %}
     490        {% filter force_escape|lower %}
    461491            This text will be HTML-escaped, and will appear in lowercase.
    462492        {% endfilter %}
    463493    """
    464494    _, rest = token.contents.split(None, 1)
    465495    filter_expr = parser.compile_filter("var|%s" % (rest))
     496    for func, unused in filter_expr.filters:
     497        if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     498            raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    466499    nodelist = parser.parse(('endfilter',))
    467500    parser.delete_first_token()
    468501    return FilterNode(filter_expr, nodelist)
    def ifchanged(parser, token):  
    704737ifchanged = register.tag(ifchanged)
    705738
    706739#@register.tag
     740def noautoescape(parser, token):
     741    """
     742    Force autoescape behaviour to be disabled for this block.
     743    """
     744    nodelist = parser.parse(('endnoautoescape',))
     745    parser.delete_first_token()
     746    return AutoEscapeControlNode(False, nodelist)
     747autoescape = register.tag(noautoescape)
     748
     749#@register.tag
    707750def ssi(parser, token):
    708751    """
    709752    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 375fd361961514278ab715823dbda41844b4b7e4..7843ba369898ffc7f00c2a2238fe9736d1567ec1 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):  
    695759            'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''),
    696760        }
    697761
    698         # Register our custom template loader.
    699         def test_template_loader(template_name, template_dirs=None):
    700             "A custom template loader that loads the unit-test templates."
    701             try:
    702                 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)
    703             except KeyError:
    704                 raise template.TemplateDoesNotExist, template_name
    705 
    706         old_template_loaders = loader.template_source_loaders
    707         loader.template_source_loaders = [test_template_loader]
    708 
    709         failures = []
    710         tests = TEMPLATE_TESTS.items()
    711         tests.sort()
    712 
    713         # Turn TEMPLATE_DEBUG off, because tests assume that.
    714         old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False
    715 
    716         # Set TEMPLATE_STRING_IF_INVALID to a known string
    717         old_invalid = settings.TEMPLATE_STRING_IF_INVALID
    718 
    719         for name, vals in tests:
    720             install()
    721 
    722             if isinstance(vals[2], tuple):
    723                 normal_string_result = vals[2][0]
    724                 invalid_string_result = vals[2][1]
    725             else:
    726                 normal_string_result = vals[2]
    727                 invalid_string_result = vals[2]
    728 
    729             if 'LANGUAGE_CODE' in vals[1]:
    730                 activate(vals[1]['LANGUAGE_CODE'])
    731             else:
    732                 activate('en-us')
    733 
    734             for invalid_str, result in [('', normal_string_result),
    735                                         ('INVALID', invalid_string_result)]:
    736                 settings.TEMPLATE_STRING_IF_INVALID = invalid_str
    737                 try:
    738                     output = loader.get_template(name).render(template.Context(vals[1]))
    739                 except Exception, e:
    740                     if e.__class__ != result:
    741                         failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))
    742                     continue
    743                 if output != result:
    744                     failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))
    745 
    746             if 'LANGUAGE_CODE' in vals[1]:
    747                 deactivate()
    748 
    749         loader.template_source_loaders = old_template_loaders
    750         deactivate()
    751         settings.TEMPLATE_DEBUG = old_td
    752         settings.TEMPLATE_STRING_IF_INVALID = old_invalid
    753 
    754         self.assertEqual(failures, [], '\n'.join(failures))
    755 
    756762if __name__ == "__main__":
    757763    unittest.main()
Back to Top