Ticket #2359: 01-core-changes.diff
File 01-core-changes.diff, 60.8 KB (added by , 18 years ago) |
---|
-
django/forms/__init__.py
Core changes required to support auto-escaping tag. From: <> * makes context auto-escaping aware * adds "autoescape" and "noautoescape" template tags * changes all existing filters to work in autoescaping environ * adds "force_escape" and "safe" filters. * adds tests for all filters in auto-escaping environ * fixes existing template tag tests to work with changes * adds documentation for template authors and template filter developers --- django/forms/__init__.py | 45 +++--- django/template/__init__.py | 26 +++- django/template/context.py | 4 + django/template/defaultfilters.py | 129 ++++++++++++++++-- django/template/defaulttags.py | 49 +++++++ django/utils/html.py | 15 ++ django/utils/safestring.py | 106 +++++++++++++++ docs/templates.txt | 116 ++++++++++++++++- docs/templates_python.txt | 116 +++++++++++++++++ tests/othertests/autoescape.py | 252 ++++++++++++++++++++++++++++++++++++ tests/othertests/defaultfilters.py | 2 11 files changed, 809 insertions(+), 51 deletions(-) diff --git a/django/forms/__init__.py b/django/forms/__init__.py index 730f7a5..766db8e 100644
a b 1 1 from django.core import validators 2 2 from django.core.exceptions import PermissionDenied 3 3 from django.utils.html import escape 4 from django.utils.safestring import mark_safe 4 5 from django.conf import settings 5 6 from django.utils.translation import gettext, gettext_lazy, ngettext 6 7 … … class FormFieldWrapper(object): 175 176 176 177 def html_error_list(self): 177 178 if self.errors(): 178 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])179 return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])) 179 180 else: 180 return ''181 return mark_safe('') 181 182 182 183 def get_id(self): 183 184 return self.formfield.get_id() … … class FormFieldCollection(FormFieldWrapp 209 210 return bool(len(self.errors())) 210 211 211 212 def html_combined_error_list(self): 212 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])213 return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) 213 214 214 215 class InlineObjectCollection(object): 215 216 "An object that acts like a sparse list of form field collections." … … class TextField(FormField): 393 394 maxlength = 'maxlength="%s" ' % self.maxlength 394 395 if isinstance(data, unicode): 395 396 data = data.encode(settings.DEFAULT_CHARSET) 396 return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \397 return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 397 398 (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 398 self.field_name, self.length, escape(data), maxlength) 399 self.field_name, self.length, escape(data), maxlength)) 399 400 400 401 def html2python(data): 401 402 return data … … class LargeTextField(TextField): 419 420 data = '' 420 421 if isinstance(data, unicode): 421 422 data = data.encode(settings.DEFAULT_CHARSET) 422 return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \423 return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ 423 424 (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 424 self.field_name, self.rows, self.cols, escape(data)) 425 self.field_name, self.rows, self.cols, escape(data))) 425 426 426 427 class HiddenField(FormField): 427 428 def __init__(self, field_name, is_required=False, validator_list=None): … … class HiddenField(FormField): 430 431 self.validator_list = validator_list[:] 431 432 432 433 def render(self, data): 433 return '<input type="hidden" id="%s" name="%s" value="%s" />' % \434 (self.get_id(), self.field_name, escape(data)) 434 return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \ 435 (self.get_id(), self.field_name, escape(data))) 435 436 436 437 class CheckboxField(FormField): 437 438 def __init__(self, field_name, checked_by_default=False, validator_list=None): … … class CheckboxField(FormField): 445 446 checked_html = '' 446 447 if data or (data is '' and self.checked_by_default): 447 448 checked_html = ' checked="checked"' 448 return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \449 return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ 449 450 (self.get_id(), self.__class__.__name__, 450 self.field_name, checked_html) 451 self.field_name, checked_html)) 451 452 452 453 def html2python(data): 453 454 "Convert value from browser ('on' or '') to a Python boolean" … … class SelectField(FormField): 478 479 selected_html = ' selected="selected"' 479 480 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name))) 480 481 output.append(' </select>') 481 return '\n'.join(output)482 return mark_safe('\n'.join(output)) 482 483 483 484 def isValidChoice(self, data, form): 484 485 str_data = str(data) … … class RadioSelectField(FormField): 531 532 output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')] 532 533 output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist]) 533 534 output.append('</ul>') 534 return ''.join(output)535 return mark_safe(''.join(output)) 535 536 def __iter__(self): 536 537 for d in self.datalist: 537 538 yield d … … class RadioSelectField(FormField): 546 547 datalist.append({ 547 548 'value': value, 548 549 'name': display_name, 549 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \550 (self.get_id() + '_' + str(i), self.field_name, value, selected_html) ,551 'label': '<label for="%s">%s</label>' % \550 'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ 551 (self.get_id() + '_' + str(i), self.field_name, value, selected_html)), 552 'label': mark_safe('<label for="%s">%s</label>' % \ 552 553 (self.get_id() + '_' + str(i), display_name), 553 })554 )}) 554 555 return RadioFieldRenderer(datalist, self.ul_class) 555 556 556 557 def isValidChoice(self, data, form): … … class SelectMultipleField(SelectField): 589 590 selected_html = ' selected="selected"' 590 591 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice))) 591 592 output.append(' </select>') 592 return '\n'.join(output)593 return mark_safe('\n'.join(output)) 593 594 594 595 def isValidChoice(self, field_data, all_data): 595 596 # data is something like ['1', '2', '3'] … … class CheckboxSelectMultipleField(Select 640 641 field_name = '%s%s' % (self.field_name, value) 641 642 output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \ 642 643 (self.get_id() + value , self.__class__.__name__, field_name, checked_html, 643 self.get_id() + value, choice))644 self.get_id() + value, escape(choice))) 644 645 output.append('</ul>') 645 return '\n'.join(output)646 return mark_safe('\n'.join(output)) 646 647 647 648 #################### 648 649 # FILE UPLOADS # … … class FileUploadField(FormField): 663 664 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 664 665 665 666 def render(self, data): 666 return '<input type="file" id="%s" class="v%s" name="%s" />' % \667 (self.get_id(), self.__class__.__name__, self.field_name) 667 return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \ 668 (self.get_id(), self.__class__.__name__, self.field_name)) 668 669 669 670 def html2python(data): 670 671 if data is None: -
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index ba7ca4c..99fcd72 100644
a b from django.conf import settings 60 60 from django.template.context import Context, RequestContext, ContextPopException 61 61 from django.utils.functional import curry 62 62 from django.utils.text import smart_split 63 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 64 from django.utils.html import escape 63 65 64 66 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string') 65 67 … … class FilterExpression(object): 558 560 arg_vals.append(arg) 559 561 else: 560 562 arg_vals.append(resolve_variable(arg, context)) 561 obj = func(obj, *arg_vals) 563 if getattr(func, 'needs_autoescape', False): 564 new_obj = func(obj, autoescape = context.autoescape, *arg_vals) 565 else: 566 new_obj = func(obj, *arg_vals) 567 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 568 obj = mark_safe(new_obj) 569 elif isinstance(obj, EscapeData): 570 obj = mark_for_escaping(new_obj) 571 else: 572 obj = new_obj 573 562 574 return obj 563 575 564 576 def args_check(name, func, provided): … … class VariableNode(Node): 744 756 745 757 def render(self, context): 746 758 output = self.filter_expression.resolve(context) 747 return self.encode_output(output) 759 encoded_output = self.encode_output(output) 760 if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData): 761 return escape(encoded_output) 762 else: 763 return encoded_output 748 764 749 765 class DebugVariableNode(VariableNode): 750 766 def render(self, context): … … class DebugVariableNode(VariableNode): 754 770 if not hasattr(e, 'source'): 755 771 e.source = self.source 756 772 raise 757 return self.encode_output(output) 773 encoded_output = self.encode_output(output) 774 if context.autoescape and not isinstance(encoded_output, SafeData): 775 return escape(encoded_output) 776 else: 777 return encoded_output 758 778 759 779 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 760 780 "Returns a template.Node subclass." -
django/template/context.py
diff --git a/django/template/context.py b/django/template/context.py index 6d9efdc..b7466cf 100644
a b class ContextPopException(Exception): 9 9 10 10 class Context(object): 11 11 "A stack container for variable context" 12 13 autoescape = False 14 12 15 def __init__(self, dict_=None): 13 16 dict_ = dict_ or {} 14 17 self.dicts = [dict_] … … class RequestContext(Context): 95 98 processors = tuple(processors) 96 99 for processor in get_standard_processors() + processors: 97 100 self.update(processor(request)) 101 -
django/template/defaultfilters.py
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index c75f371..ee994f3 100644
a b 3 3 from django.template import resolve_variable, Library 4 4 from django.conf import settings 5 5 from django.utils.translation import gettext 6 from django.utils.safestring import mark_safe, SafeData 6 7 import re 7 8 import random as random_module 8 9 … … ################### 16 17 def addslashes(value): 17 18 "Adds slashes - useful for passing strings to JavaScript, for example." 18 19 return value.replace('"', '\\"').replace("'", "\\'") 20 addslashes.is_safe = True 19 21 20 22 def capfirst(value): 21 23 "Capitalizes the first character of the value" 22 24 value = str(value) 23 25 return value and value[0].upper() + value[1:] 26 capfirst.is_safe = True 24 27 25 28 def fix_ampersands(value): 26 29 "Replaces ampersands with ``&`` entities" 27 30 from django.utils.html import fix_ampersands 28 31 return fix_ampersands(value) 32 fix_ampersands.is_safe = True 29 33 30 34 def floatformat(text): 31 35 """ … … def floatformat(text): 40 44 if m: 41 45 return '%.1f' % f 42 46 else: 43 return '%d' % int(f) 47 return mark_safe('%d' % int(f)) 48 floatformat.is_safe = True 44 49 45 def linenumbers(value ):50 def linenumbers(value, autoescape = None): 46 51 "Displays text with line numbers" 47 52 from django.utils.html import escape 48 53 lines = value.split('\n') 49 54 # Find the maximum width of the line count, for use with zero padding string format command 50 55 width = str(len(str(len(lines)))) 51 for i, line in enumerate(lines): 52 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 53 return '\n'.join(lines) 56 if not autoescape or isinstance(value, SafeData): 57 for i, line in enumerate(lines): 58 lines[i] = ("%0" + width + "d. %s") % (i + 1, line) 59 else: 60 for i, line in enumerate(lines): 61 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 62 return mark_safe('\n'.join(lines)) 63 linenumbers.is_safe = True 64 linenumbers.needs_autoescape = True 54 65 55 66 def lower(value): 56 67 "Converts a string into all lowercase" 57 68 return value.lower() 69 lower.is_safe = True 58 70 59 71 def make_list(value): 60 72 """ … … def make_list(value): 62 74 digits. For a string, it's a list of characters. 63 75 """ 64 76 return list(str(value)) 77 make_list.is_safe = False 65 78 66 79 def slugify(value): 67 80 "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens" 68 81 value = re.sub('[^\w\s-]', '', value).strip().lower() 69 return re.sub('[-\s]+', '-', value) 82 return mark_safe(re.sub('[-\s]+', '-', value)) 83 slugify.is_safe = True 70 84 71 85 def stringformat(value, arg): 72 86 """ … … def stringformat(value, arg): 81 95 return ("%" + arg) % value 82 96 except (ValueError, TypeError): 83 97 return "" 98 stringformat.is_safe = True 84 99 85 100 def title(value): 86 101 "Converts a string into titlecase" 87 102 return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 103 title.is_safe = False 88 104 89 105 def truncatewords(value, arg): 90 106 """ … … def truncatewords(value, arg): 100 116 if not isinstance(value, basestring): 101 117 value = str(value) 102 118 return truncate_words(value, length) 119 truncatewords.is_safe = True 103 120 104 121 def upper(value): 105 122 "Converts a string into all uppercase" 106 123 return value.upper() 124 upper.is_safe = False 107 125 108 126 def urlencode(value): 109 127 "Escapes a value for use in a URL" 110 128 import urllib 111 129 return urllib.quote(value) 130 urlencode.is_safe = False 112 131 113 132 def urlize(value): 114 133 "Converts URLs in plain text into clickable links" 115 134 from django.utils.html import urlize 116 return urlize(value, nofollow=True) 135 return mark_safe(urlize(value, nofollow=True)) 136 urlize.is_safe = True 117 137 118 138 def urlizetrunc(value, limit): 119 139 """ … … def urlizetrunc(value, limit): 123 143 Argument: Length to truncate URLs to. 124 144 """ 125 145 from django.utils.html import urlize 126 return urlize(value, trim_url_limit=int(limit), nofollow=True) 146 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True)) 147 urlize.is_safe = True 127 148 128 149 def wordcount(value): 129 150 "Returns the number of words" 130 151 return len(value.split()) 152 wordcount.is_safe = False 131 153 132 154 def wordwrap(value, arg): 133 155 """ … … def wordwrap(value, arg): 137 159 """ 138 160 from django.utils.text import wrap 139 161 return wrap(str(value), int(arg)) 162 wordwrap.is_safe = True 140 163 141 164 def ljust(value, arg): 142 165 """ … … def ljust(value, arg): 145 168 Argument: field size 146 169 """ 147 170 return str(value).ljust(int(arg)) 171 ljust.is_safe = True 148 172 149 173 def rjust(value, arg): 150 174 """ … … def rjust(value, arg): 153 177 Argument: field size 154 178 """ 155 179 return str(value).rjust(int(arg)) 180 rjust.is_safe = True 156 181 157 182 def center(value, arg): 158 183 "Centers the value in a field of a given width" 159 184 return str(value).center(int(arg)) 185 center.is_safe = True 160 186 161 187 def cut(value, arg): 162 188 "Removes all values of arg from the given string" 163 189 return value.replace(arg, '') 190 cut.is_safe = False 164 191 165 192 ################### 166 193 # HTML STRINGS # 167 194 ################### 168 195 169 196 def escape(value): 170 "Escapes a string's HTML" 197 "Marks the value as a string that should not be auto-escaped." 198 from django.utils.safestring import mark_for_escaping 199 return mark_for_escaping(value) 200 escape.is_safe = True 201 202 def force_escape(value): 203 """Escapes a string's HTML. This returns a new string containing the escaped 204 characters (as opposed to "escape", which marks the content for later 205 possible escaping).""" 171 206 from django.utils.html import escape 172 return escape(value) 207 return mark_safe(escape(value)) 208 force_escape.is_safe = True 173 209 174 def linebreaks(value ):210 def linebreaks(value, autoescape = None): 175 211 "Converts newlines into <p> and <br />s" 176 212 from django.utils.html import linebreaks 177 return linebreaks(value) 213 autoescape = autoescape and not isinstance(value, SafeData) 214 return mark_safe(linebreaks(value, autoescape)) 215 linebreaks.is_safe = True 216 linebreaks.needs_autoescape = True 178 217 179 def linebreaksbr(value ):218 def linebreaksbr(value, autoescape = None): 180 219 "Converts newlines into <br />s" 181 return value.replace('\n', '<br />') 220 if autoescape and not isinstance(value, SafeData): 221 from django.utils.html import escape 222 data = escape(value) 223 else: 224 data = value 225 return mark_safe(data.replace('\n', '<br />')) 226 linebreaksbr.is_safe = True 227 linebreaksbr.needs_autoescape = True 228 229 def safe(value): 230 "Marks the value as a string that should not be auto-escaped." 231 from django.utils.safestring import mark_safe 232 return mark_safe(value) 233 safe.is_safe = True 182 234 183 235 def removetags(value, tags): 184 236 "Removes a space separated list of [X]HTML tags from the output" … … def removetags(value, tags): 189 241 value = starttag_re.sub('', value) 190 242 value = endtag_re.sub('', value) 191 243 return value 244 removetags.is_safe = True 192 245 193 246 def striptags(value): 194 247 "Strips all [X]HTML tags" … … def striptags(value): 196 249 if not isinstance(value, basestring): 197 250 value = str(value) 198 251 return strip_tags(value) 252 striptags.is_safe = True 199 253 200 254 ################### 201 255 # LISTS # … … def dictsort(value, arg): 209 263 decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value] 210 264 decorated.sort() 211 265 return [item[1] for item in decorated] 266 dictsort.is_safe = False 212 267 213 268 def dictsortreversed(value, arg): 214 269 """ … … def dictsortreversed(value, arg): 219 274 decorated.sort() 220 275 decorated.reverse() 221 276 return [item[1] for item in decorated] 277 dictsortreversed.is_safe = False 222 278 223 279 def first(value): 224 280 "Returns the first item in a list" … … def first(value): 226 282 return value[0] 227 283 except IndexError: 228 284 return '' 285 first.is_safe = True 229 286 230 287 def join(value, arg): 231 288 "Joins a list with a string, like Python's ``str.join(list)``" 232 289 try: 233 returnarg.join(map(str, value))290 data = arg.join(map(str, value)) 234 291 except AttributeError: # fail silently but nicely 235 292 return value 293 safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True) 294 if safe_args: 295 return mark_safe(data) 296 else: 297 return data 298 join.is_safe = True 236 299 237 300 def length(value): 238 301 "Returns the length of the value - useful for lists" 239 302 return len(value) 303 length.is_safe = False 240 304 241 305 def length_is(value, arg): 242 306 "Returns a boolean of whether the value's length is the argument" 243 307 return len(value) == int(arg) 308 length.is_safe = False 244 309 245 310 def random(value): 246 311 "Returns a random item from the list" 247 312 return random_module.choice(value) 313 length.is_safe = True 248 314 249 315 def slice_(value, arg): 250 316 """ … … def slice_(value, arg): 265 331 266 332 except (ValueError, TypeError): 267 333 return value # Fail silently. 334 slice_.is_safe = True 268 335 269 def unordered_list(value ):336 def unordered_list(value, autoescape = None): 270 337 """ 271 338 Recursively takes a self-nested list and returns an HTML unordered list -- 272 339 WITHOUT opening and closing <ul> tags. … … def unordered_list(value): 287 354 </ul> 288 355 </li> 289 356 """ 357 if autoescape: 358 from django.utils.html import conditional_escape 359 escaper = conditional_escape 360 else: 361 escaper = lambda x: x 362 290 363 def _helper(value, tabs): 291 364 indent = '\t' * tabs 292 365 if value[1]: 293 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,366 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent, 294 367 '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent) 295 368 else: 296 return '%s<li>%s</li>' % (indent, value[0]) 297 return _helper(value, 1) 369 return '%s<li>%s</li>' % (indent, escaper(value[0])) 370 return mark_safe(_helper(value, 1)) 371 unordered_list.is_safe = True 372 unordered_list.needs_autoescape = True 298 373 299 374 ################### 300 375 # INTEGERS # … … ################### 303 378 def add(value, arg): 304 379 "Adds the arg to the value" 305 380 return int(value) + int(arg) 381 add.is_safe = False 306 382 307 383 def get_digit(value, arg): 308 384 """ … … def get_digit(value, arg): 322 398 return int(str(value)[-arg]) 323 399 except IndexError: 324 400 return 0 401 get_digit.is_safe = False 325 402 326 403 ################### 327 404 # DATES # … … def date(value, arg=None): 335 412 if arg is None: 336 413 arg = settings.DATE_FORMAT 337 414 return format(value, arg) 415 date.is_safe = False 338 416 339 417 def time(value, arg=None): 340 418 "Formats a time according to the given format" … … def time(value, arg=None): 344 422 if arg is None: 345 423 arg = settings.TIME_FORMAT 346 424 return time_format(value, arg) 425 time.is_safe = False 347 426 348 427 def timesince(value, arg=None): 349 428 'Formats a date as the time since that date (i.e. "4 days, 6 hours")' … … def timesince(value, arg=None): 353 432 if arg: 354 433 return timesince(arg, value) 355 434 return timesince(value) 435 timesince.is_safe = False 356 436 357 437 def timeuntil(value, arg=None): 358 438 'Formats a date as the time until that date (i.e. "4 days, 6 hours")' … … def timeuntil(value, arg=None): 363 443 if arg: 364 444 return timesince(arg, value) 365 445 return timesince(datetime.now(), value) 446 timeuntil.is_safe = False 366 447 367 448 ################### 368 449 # LOGIC # … … ################### 371 452 def default(value, arg): 372 453 "If value is unavailable, use given default" 373 454 return value or arg 455 default.is_safe = False 374 456 375 457 def default_if_none(value, arg): 376 458 "If value is None, use given default" 377 459 if value is None: 378 460 return arg 379 461 return value 462 default_if_none.is_safe = False 380 463 381 464 def divisibleby(value, arg): 382 465 "Returns true if the value is devisible by the argument" 383 466 return int(value) % int(arg) == 0 467 divisibleby.is_safe = False 384 468 385 469 def yesno(value, arg=None): 386 470 """ … … def yesno(value, arg=None): 411 495 if value: 412 496 return yes 413 497 return no 498 yesno.is_safe = False 414 499 415 500 ################### 416 501 # MISC # … … def filesizeformat(bytes): 429 514 if bytes < 1024 * 1024 * 1024: 430 515 return "%.1f MB" % (bytes / (1024 * 1024)) 431 516 return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) 517 filesizeformat.is_safe = True 432 518 433 519 def pluralize(value, arg='s'): 434 520 """ … … def pluralize(value, arg='s'): 456 542 except TypeError: # len() of unsized object 457 543 pass 458 544 return singular_suffix 545 pluralize.is_safe = False 459 546 460 547 def phone2numeric(value): 461 548 "Takes a phone number and converts it in to its numerical equivalent" 462 549 from django.utils.text import phone2numeric 463 550 return phone2numeric(value) 551 phone2numeric.is_safe = True 464 552 465 553 def pprint(value): 466 554 "A wrapper around pprint.pprint -- for debugging, really" … … def pprint(value): 469 557 return pformat(value) 470 558 except Exception, e: 471 559 return "Error in formatting:%s" % e 560 pprint.is_safe = True 472 561 473 562 # Syntax: register.filter(name of filter, callback) 474 563 register.filter(add) … … register.filter(filesizeformat) 487 576 register.filter(first) 488 577 register.filter(fix_ampersands) 489 578 register.filter(floatformat) 579 register.filter(force_escape) 490 580 register.filter(get_digit) 491 581 register.filter(join) 492 582 register.filter(length) … … register.filter(pprint) 503 593 register.filter(removetags) 504 594 register.filter(random) 505 595 register.filter(rjust) 596 register.filter(safe) 506 597 register.filter('slice', slice_) 507 598 register.filter(slugify) 508 599 register.filter(stringformat) -
django/template/defaulttags.py
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 0a4fe33..e3d9624 100644
a b from django.template import Node, NodeLi 4 4 from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END 5 5 from django.template import get_library, Library, InvalidTemplateLibrary 6 6 from django.conf import settings 7 from django.utils.safestring import mark_safe 7 8 import sys 8 9 9 10 register = Library() 10 11 12 class 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 11 27 class CommentNode(Node): 12 28 def render(self, context): 13 29 return '' … … class FilterNode(Node): 37 53 def render(self, context): 38 54 output = self.nodelist.render(context) 39 55 # apply filters 40 return self.filter_expr.resolve(Context({'var': output})) 56 ctxt = Context({'var': output}) 57 ctxt.autoescape = context.autoescape 58 return self.filter_expr.resolve(ctxt) 41 59 42 60 class FirstOfNode(Node): 43 61 def __init__(self, vars): … … class RegroupNode(Node): 218 236 return '' 219 237 output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]} 220 238 for obj in obj_list: 221 grouper = self.expression.resolve(Context({'var': obj})) 239 ctxt = Context({'var': obj}) 240 ctxt.autoescape = context.autoescape 241 grouper = self.expression.resolve(ctxt) 222 242 # TODO: Is this a sensible way to determine equality? 223 243 if output and repr(output[-1]['grouper']) == repr(grouper): 224 244 output[-1]['list'].append(obj) … … class WidthRatioNode(Node): 318 338 return str(int(round(ratio))) 319 339 320 340 #@register.tag 341 def autoescape(parser, token): 342 """ 343 Force autoescape behaviour for this block. 344 """ 345 nodelist = parser.parse(('endautoescape',)) 346 parser.delete_first_token() 347 return AutoEscapeControlNode(True, nodelist) 348 autoescape = register.tag(autoescape) 349 350 #@register.tag 321 351 def comment(parser, token): 322 352 """ 323 353 Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` … … def do_filter(parser, token): 411 441 412 442 Sample usage:: 413 443 414 {% filter escape|lower %}444 {% filter force_escape|lower %} 415 445 This text will be HTML-escaped, and will appear in lowercase. 416 446 {% endfilter %} 417 447 """ 418 448 _, rest = token.contents.split(None, 1) 419 449 filter_expr = parser.compile_filter("var|%s" % (rest)) 450 for func, unused in filter_expr.filters: 451 if func.__name__ in ('escape', 'safe'): 452 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 420 453 nodelist = parser.parse(('endfilter',)) 421 454 parser.delete_first_token() 422 455 return FilterNode(filter_expr, nodelist) … … def ifchanged(parser, token): 646 679 ifchanged = register.tag(ifchanged) 647 680 648 681 #@register.tag 682 def noautoescape(parser, token): 683 """ 684 Force autoescape behaviour to be disabled for this block. 685 """ 686 nodelist = parser.parse(('endnoautoescape',)) 687 parser.delete_first_token() 688 return AutoEscapeControlNode(False, nodelist) 689 autoescape = register.tag(noautoescape) 690 691 #@register.tag 649 692 def ssi(parser, token): 650 693 """ 651 694 Output the contents of a given file into the page. -
django/utils/html.py
diff --git a/django/utils/html.py b/django/utils/html.py index a0d1e82..2b314e9 100644
a b 1 1 "HTML utilities suitable for global use." 2 2 3 3 import re, string 4 from django.utils.safestring import SafeData 4 5 5 6 # Configuration for urlize() function 6 7 LEADING_PUNCTUATION = ['(', '<', '<'] … … def escape(html): 27 28 html = str(html) 28 29 return html.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"').replace("'", ''') 29 30 30 def linebreaks(value): 31 def 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 38 def linebreaks(value, autoescape = False): 31 39 "Converts newlines into <p> and <br />s" 32 40 value = re.sub(r'\r\n|\r|\n', '\n', value) # normalize newlines 33 41 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] 35 46 return '\n\n'.join(paras) 36 47 37 48 def strip_tags(value): -
new file django/utils/safestring.py
diff --git a/django/utils/safestring.py b/django/utils/safestring.py new file mode 100644 index 0000000..67a2e79
- + 1 """ 2 Functions for working with "safe strings": strings that can be displayed safely 3 without further escaping in HTML. Here, a "safe string" means that the producer 4 of the string has already turned characters that should not be interpreted by 5 the HTML engine (e.g. '<') into the appropriate entities. 6 """ 7 from django.utils.functional import curry 8 9 class EscapeData(object): 10 pass 11 12 class EscapeString(str, EscapeData): 13 """ 14 A string that should be HTML-escaped when output. 15 """ 16 pass 17 18 class EscapeUnicode(unicode, EscapeData): 19 """ 20 A unicode object that should be HTML-escaped when output. 21 """ 22 pass 23 24 class SafeData(object): 25 pass 26 27 class 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 class SafeUnicode(unicode, SafeData): 45 """ 46 A unicode subclass that has been specifically marked as "safe" for HTML 47 output purposes. 48 """ 49 def __add__(self, rhs): 50 """ 51 Concatenating a safe unicode object with another safe string or safe 52 unicode object is safe. Otherwise, the result is no longer safe. 53 """ 54 if isinstance(rhs, SafeData): 55 return SafeUnicode(self + rhs) 56 else: 57 return super(SafeUnicode, self).__add__(rhs) 58 59 def _proxy_method(self, *args, **kwargs): 60 """ 61 Wrap a call to a normal unicode method up so that we return safe 62 results. The method that is being wrapped is passed in the 'method' 63 argument. 64 """ 65 method = kwargs.pop('method') 66 data = method(self, *args, **kwargs) 67 if isinstance(data, str): 68 return SafeString(data) 69 else: 70 return SafeUnicode(data) 71 72 encode = curry(_proxy_method, method = unicode.encode) 73 decode = curry(_proxy_method, method = unicode.decode) 74 75 76 def mark_safe(s): 77 """ 78 Explicitly mark a string as safe for (HTML) output purposes. The returned 79 object can be used everywhere a string or unicode object is appropriate. 80 81 Can safely be called multiple times on a single string. 82 """ 83 if isinstance(s, SafeData): 84 return s 85 if isinstance(s, str): 86 return SafeString(s) 87 if isinstance(s, unicode): 88 return SafeUnicode(s) 89 return SafeString(str(s)) 90 91 def mark_for_escaping(s): 92 """ 93 Explicitly mark a string as requiring HTML escaping upon output. Has no 94 effect on SafeData subclasses. 95 96 Can be safely called multiple times on a single string (the effect is only 97 applied once). 98 """ 99 if isinstance(s, SafeData) or isinstance(s, EscapeData): 100 return s 101 if isinstance(s, str): 102 return EscapeString(s) 103 if isinstance(s, unicode): 104 return EscapeUnicode(s) 105 return EscapeString(str(s)) 106 -
docs/templates.txt
diff --git a/docs/templates.txt b/docs/templates.txt index 49d3001..088f5ae 100644
a b it also defines the content that fills t 243 243 two similarly-named ``{% block %}`` tags in a template, that template's parent 244 244 wouldn't know which one of the blocks' content to use. 245 245 246 Automatic HTML escaping 247 ======================= 248 249 A very real problem when creating HTML (and other) output using templates and 250 variable substitution is the possibility of accidently inserting some variable 251 value that affects the resulting HTML. For example, a template fragment like 252 253 :: 254 255 Hello, {{ name }}. 256 257 seems like a harmless way to display the user's name. However, if you are 258 displaying data that the user entered directly and they entered their name as 259 260 :: 261 262 <script>alert('hello')</script> 263 264 this would always display a Javascript alert box whenever the page was loaded. 265 Similarly, if you were displaying some data generated by another process and 266 it contained a '<' symbol, you couldn't just dump this straight into your 267 HTML, because it would be treated as the start of an element. The effects of 268 these sorts of problems can vary from merely annoying to allowing exploits via 269 `Cross Site Scripting`_ (XSS) attacks. 270 271 .. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting 272 273 In order to provide some protection against these problems, Django provides an 274 auto-escaping template tag. Inside this tag, any data that comes from template 275 variables is examined to see if it contains one of the five HTML characters 276 (<, >, ', " and &) that often need escaping and those characters are converted 277 to their respective HTML entities. 278 279 Because some variables will contain data that is *intended* to be rendered 280 as HTML, template tag and filter writers can mark their output strings as 281 requiring no further escaping. For example, the ``unordered_list`` filter is 282 designed to return raw HTML and we want the template processor to simply 283 display the results as returned, without applying any escaping. That is taken 284 care of by the filter. The template author need do nothing special in that 285 case. 286 287 By default, auto-escaping is not in effect. To enable it inside your template, 288 wrap the affected content in the ``autoescape`` tag, like so:: 289 290 {% autoescape %} 291 Hello {{ name }} 292 {% endautoescape %} 293 294 Since the auto-escaping tag passes its effect onto templates that extend the 295 current one as well as templates included via the ``include`` tag (just like 296 all block tags), if you wrap your main HTML content in an ``autoescape`` tag, 297 you will have automatic escaping applied to all of your content. 298 299 At times, you might want to disable auto-escaping when it would otherwise be 300 in effect. You can do this with the ``noautoescape`` tag. For example:: 301 302 {% autoescape %} 303 Hello {{ name }} 304 305 {% noautoescape %} 306 This will not be auto-escaped: {{ data }}. 307 308 Nor this: {{ other_data }} 309 {% endnoautoescape %} 310 {% endautoescape %} 311 312 For individual variables, the ``safe`` filter can also be used. 313 314 Generally, you will not need to worry about auto-escaping very much. Enable it 315 in your base template once you are entering the main HTML region and then 316 write your templates normally. The view developers and custom filter authors 317 need to think about when their data should not be escaped and mark it 318 appropriately. They are in a better position to know when that should happen 319 than the template author, so it is their responsibility. By default, when 320 auto-escaping is enabled, all output is escaped unless the template processor 321 is explicitly told otherwise. 322 246 323 Using the built-in reference 247 324 ============================ 248 325 … … available, and what they do. 318 395 Built-in tag reference 319 396 ---------------------- 320 397 398 autoescape 399 ~~~~~~~~~~ 400 401 All variables that are output inside this tag have HTML escaping applied to 402 them, as if they each had the ``escape`` filter attached to them. 403 404 The only exceptions are variables that are already marked as 'safe' from 405 escaping, either by the code that populated the variable, or because it has 406 the ``safe`` filter applied. 407 321 408 block 322 409 ~~~~~ 323 410 … … just like in variable syntax. 385 472 386 473 Sample usage:: 387 474 388 {% filter escape|lower %}475 {% filter force_escape|lower %} 389 476 This text will be HTML-escaped, and will appear in all lowercase. 390 477 {% endfilter %} 391 478 … … Load a custom template tag set. 582 669 583 670 See `Custom tag and filter libraries`_ for more information. 584 671 672 noautoescape 673 ~~~~~~~~~~~~ 674 675 Disable the effects of the ``autoescape`` tag (if it is in effect). 676 585 677 now 586 678 ~~~ 587 679 … … Escapes a string's HTML. Specifically, i 866 958 * ``'"'`` (double quote) to ``'"'`` 867 959 * ``"'"`` (single quote) to ``'''`` 868 960 961 The escaping is only applied when the string is output, so it does not matter 962 where in a chained sequence of filters you put ``escape``: it will always be 963 applied as though it were the last filter. If you want escaping to be applied 964 immediately, use the ``force_escape`` filter. 965 869 966 filesizeformat 870 967 ~~~~~~~~~~~~~~ 871 968 … … decimal part to be displayed. For exampl 892 989 * ``36.15`` gets converted to ``36.2`` 893 990 * ``36`` gets converted to ``36`` 894 991 992 force_escape 993 ~~~~~~~~~~~~ 994 995 Applies HTML escaping to a string (see the ``escape`` filter for details). 996 This filter is applied immediately and returns a new, escaped string. This is 997 useful in the typically rare cases where you need multiple escaping or want to 998 apply other filters to the escaped results. Normally, you want to use the 999 ``escape`` filter. 1000 895 1001 get_digit 896 1002 ~~~~~~~~~ 897 1003 … … Right-aligns the value in a field of a g 1003 1109 1004 1110 **Argument:** field size 1005 1111 1112 safe 1113 ~~~~ 1114 1115 Marks a string as not requiring further HTML escaping prior to output. This is 1116 only useful inside an ``autoescape`` block, when the output would otherwise be 1117 automatically escaped. Outside of an ``autoescape`` block, this filter has no 1118 effect. 1119 1006 1120 slice 1007 1121 ~~~~~ 1008 1122 -
docs/templates_python.txt
diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 95ccfb3..4a7b611 100644
a b decorator instead:: 593 593 If you leave off the ``name`` argument, as in the second example above, Django 594 594 will use the function's name as the filter name. 595 595 596 Filters and auto-escaping 597 ~~~~~~~~~~~~~~~~~~~~~~~~~ 598 599 When you are writing a custom filter, you need to give some thought to how 600 this filter will work when rendered in an auto-escaping environment (inside 601 an ``autoescape`` template tag block). First, you should realise that there 602 are three types of strings that can be passed around inside the template code: 603 604 * raw strings are the native Python ``str`` (or ``unicode``) types. On 605 output, they are escaped if they are inside an ``autoescape`` block. 606 * "safe" strings are strings that are safe from further escaping at output 607 time. Any necessary escaping has already been done. They are commonly used 608 for output that contains raw HTML that is intended to be intrepreted on the 609 client side. 610 611 Internally, these strings are of type ``SafeString`` or ``SafeUnicode``, 612 although they share a common base class in ``SafeData``, so you can test 613 for them using code like:: 614 615 if isinstance(value, SafeData): 616 # Do something with the "safe" string. 617 618 * strings which are marked as "need escaping" are *always* escaped on 619 output, regardless of whether they are in an ``autoescape`` block or not. 620 These strings are only escaped once, however, even if used inside an 621 ``autoescaep`` block. This type of string is internally represented by the 622 types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to 623 worry about these; they exist only for the implementation of the ``escape`` 624 filter. 625 626 Inside your filter, you will need to think about three areas in order to be 627 auto-escaping compliant: 628 629 1. If your filter returns a string that is ready for direct output (it should 630 be considered a "safe" string), you should call 631 ``django.utils.safestring.mark_safe()`` on the result prior to returning. 632 This will turn the result into the appropriate ``SafeData`` type. 633 634 2. If your filter is given a "safe" string, is it guaranteed to return a 635 "safe" string? If so, set the ``is_safe`` attribute on the function to be 636 ``True``. For example, a filter that replaced all numbers with the number 637 spelt out in words is going to be safe-string-preserving, since it cannot 638 introduce any of the five dangerous characters: <, >, ", ' or &. So we can 639 write:: 640 641 @register.filter 642 def convert_to_words(value): 643 # ... implementation here ... 644 return result 645 646 convert_to_words.is_safe = True 647 648 Note that this filter does not return a universally safe result (it does not 649 return ``mark_safe(result)``) because if it is handed a raw string such as 650 '<a>', this will need further escaping in an auto-escape environment. The 651 ``is_safe`` attribute only talks about the safeness of the result when a safe 652 string is passed in to the filter. 653 654 3. Will your filter behave differently depending upon whether auto-escaping 655 is currently in effect or not? For example, the ``ordered_list`` filter that 656 ships with Django needs to know whether to escape its content or not. It will 657 always return a safe string, since it returns raw HTML, so we cannot apply 658 escaping to the result -- it needs to be done in-situ. 659 660 For these cases, the filter function needs to be told what the current 661 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the 662 filter to ``True`` and have your function take an extra argument called 663 ``autoescape`` with a default value of ``None``. When the filter is called, 664 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in 665 effect. For example, the ``unordered_list`` filter is written as:: 666 667 def unordered_list(value, autoescape = None): 668 # ... lots of code here ... 669 670 return mark_safe(...) 671 672 unordered_list.is_safe = True 673 unordered_list.needs_autoescape = True 674 675 By default, both the ``is_safe`` and ``needs_autoescape`` attributes are 676 ``False``. You do not need to specify them if ``False`` is an acceptable 677 value. 678 679 As a matter of convention, we leave ``is_safe`` as ``False`` for filters that 680 do not accept string inputs (they might take a number as an input, for 681 example) or for those that return a non-string (e.g. the ``length`` filter). 682 However, not following this convention will not cause any harm or make your 683 results any more vulnerable to cross-site scripting problems. 684 596 685 Writing custom template tags 597 686 ---------------------------- 598 687 … … Ultimately, this decoupling of compilati 711 800 efficient template system, because a template can render multiple context 712 801 without having to be parsed multiple times. 713 802 803 Auto-escaping considerations 804 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 805 806 The output from template tags is not automatically run through the 807 auto-escaping filters if used inside an ``autoescape`` tag. However, there are 808 still a couple of things you should keep in mind when writing a template tag: 809 810 If the ``render()`` function of your template stores the result in a context 811 variable (rather than returning the result in a string), it should take care 812 to call ``mark_safe()`` if appropriate. When the variable is ultimately 813 rendered, it will be affected by the auto-escape setting in effect at the 814 time, so content that should be safe from further escaping needs to be marked 815 as such. 816 817 Also, if your template tag creates a new context for performing some 818 sub-rendering, you should be careful to set the auto-escape variable to the 819 current context's value. For example:: 820 821 def render(self, context): 822 # ... 823 new_context = Context({'var': obj}) 824 new_context.autoescape = context.autoescape 825 # ... Do something with new_context ... 826 827 This is not a very common situation, but it is sometimes useful (see 828 ``django.templates.defaulttags.FilterNode.render()`` for an example). 829 714 830 Registering the tag 715 831 ~~~~~~~~~~~~~~~~~~~ 716 832 -
new file tests/othertests/autoescape.py
diff --git a/tests/othertests/autoescape.py b/tests/othertests/autoescape.py new file mode 100644 index 0000000..07a08a1
- + 1 from django.conf import settings 2 3 if __name__ == '__main__': 4 # When running this file in isolation, we need to set up the configuration 5 # before importing 'template'. 6 settings.configure() 7 8 import templates 9 from django import template 10 from django.template import loader 11 from django.utils.translation import activate, deactivate, install 12 from django.utils.tzinfo import LocalTimezone 13 from django.utils.safestring import mark_safe 14 from datetime import datetime, timedelta 15 import traceback 16 17 # We want to check all the normal template tests work when autoescaping is 18 # engaged. We just update results that would change with autoescaping and add 19 # in new tests. 20 TEMPLATE_TESTS = templates.TEMPLATE_TESTS.copy() 21 22 # SYNTAX -- 23 # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class) 24 TEMPLATE_TESTS.update({ 25 26 ### BASIC SYNTAX ########################################################## 27 28 # Escaped string as argument (this test replaces the non-escaped version) 29 'basic-syntax30': (r'{{ var|default_if_none:" endquote\" hah" }}', {"var": None}, ' endquote" hah'), 30 31 # We are simulating being in a block that has inherited auto-escaping, so 32 # it is applied by default in all these tests. 33 'autoescape-basic01': ("{{ first }}", {"first": "<b>first</b>"}, "<b>first</b>"), 34 35 # Strings (ASCII or unicode) already marked as "safe" are not auto-escaped 36 'autoescape-basic02': ("{{ first }}", {"first": mark_safe("<b>first</b>")}, "<b>first</b>"), 37 'autoescape-basic03': ("{{ first }}", {"first": mark_safe(u"<b>Apple</b>")}, u"<b>Apple</b>"), 38 39 40 ### (NO)AUTOESCAPE TAG ################################################### 41 'autoescape-tag01': ("{% noautoescape %}hello{% endnoautoescape %}", {}, "hello"), 42 'autoescape-tag02': ("{% noautoescape %}{{ first }}{% endnoautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"), 43 'autoescape-tag03': ("{% autoescape %}{{ first }}{% endautoescape %}", {"first": "<b>hello</b>"}, "<b>hello</b>"), 44 45 # Noautoescape and autoescape nest in a predictable way. 46 'autoescape-tag04': ("{% noautoescape %}{{ first }} {% autoescape %}{{ first }}{% endautoescape %}{% endnoautoescape %}", {"first": "<a>"}, "<a> <a>"), 47 48 ### FILTER TAG ############################################################ 49 50 # The "safe" and "escape" filters cannot work due to internal 51 # implementation details (fortunately, the (no)autoescape block tags can be 52 # used in those cases) 53 'autoescape-filtertag01': ("{{ first }}{% filter safe %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError), 54 'autoescape-filtertag02': ("{% filter escape %}{{ first }} x<y{% endfilter %}", {"first": "<a>"}, template.TemplateSyntaxError), 55 56 ### FILTER TESTS ########################################################## 57 58 'ae-filter-addslash01': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"), 59 60 'ae-filter-capfirst01': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), 61 62 # Note that applying fix_ampsersands in autoescape mode leads to double 63 # escaping. 64 'ae-filter-fix_ampersands01': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, "a&amp;b a&b"), 65 66 'ae-filter-floatformat01': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"), 67 68 # The contents of "linenumbers" is escaped according to the current 69 # autoescape setting. 70 'ae-filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 71 'ae-filter-linenumbers02': ("{% noautoescape %}{{ a|linenumbers }} {{ b|linenumbers }}{% endnoautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 72 73 'ae-filter-lower01': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana"), 74 75 # The make_list filter can destroy # existing encoding, so the results are 76 # escaped. 77 'ae-filter-make_list01': ("{{ a|make_list }}", {"a": mark_safe("&")}, "['&']"), 78 'ae-filter-make_list02': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, "['&']"), 79 80 # Running slugify on a pre-escaped string leads to odd behaviour, but the 81 # result is still safe. 82 'ae-filter-slugify01': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b"), 83 84 # Notice that escaping is applied *after* any filters, so the string 85 # formatting here only needs to deal with pre-escaped characters. 86 'ae-filter-stringformat01': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b."), 87 88 # XXX No test for "title" filter; needs an actual object. 89 90 'ae-filter-truncatewords01': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ..."), 91 92 # The "upper" filter messes up entities (which are case-sensitive), so it's 93 # not safe for non-escaping purposes. 94 'ae-filter-upper01': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a & b")}, "A & B A &AMP; B"), 95 96 'ae-filter-urlize01': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/x=&y=" rel="nofollow">http://example.com/x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), 97 'ae-filter-urlize02': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'), 98 99 'ae-filter-urlizetrunc01': ('{{ a|urlizetrunc:"5" }} {{ b|urlizetrunc:"5" }}', {"a": "http://example.com/x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/x=&y=" rel="nofollow">http:...</a> <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 100 101 'ae-filter-wordcount01': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 102 103 'ae-filter-wordwrap01': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 104 105 'ae-filter-ljust01': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 106 107 'ae-filter-rjust01': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 108 109 'ae-filter-center01': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."), 110 111 # Because "cut" might remove a leading ampersand, so the results are not 112 # safe. 113 'ae-filter-cut01': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &amp;y"), 114 'ae-filter-cut02': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), 115 116 # The "escape" filter works the same whether autoescape is on or off, but 117 # it has no effect on strings already marked as safe. 118 'ae-filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 119 'ae-filter-escape02': ('{% noautoescape %}{{ a|escape }} {{ b|escape }}{% endnoautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 120 121 # It is only applied once, regardless of the number of times it appears in 122 # a chain. 123 'ae-filter-escape03': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&y"), 124 125 # Force_escape is applied immediately. It can be used to provide 126 # double-escaping, for example. 127 'ae-filter-force-escape01': ('{{ a|force_escape }}', {"a": "x&y"}, "x&y"), 128 'ae-filter-force-escape02': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&amp;y"), 129 130 # Because the result of force_escape is "safe", an additional escape filter 131 # has no effect. 132 'ae-filter-force-escape03': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&y"), 133 134 # The contents in "linebreaks" and "linebreaksbr" are escaped according to 135 # the current autoescape setting. 136 'ae-filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 137 'ae-filter-linebreaks02': ('{% noautoescape %}{{ a|linebreaks }} {{ b|linebreaks }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 138 139 'ae-filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 140 'ae-filter-linebreaksbr02': ('{% noautoescape %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endnoautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 141 142 'ae-filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 143 'ae-filter-safe02': ("{% noautoescape %}{{ a }} -- {{ a|safe }}{% endnoautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 144 145 'ae-filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>"), 146 147 'ae-filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), 148 149 'ae-filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), 150 151 'ae-filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), 152 153 'ae-filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 154 155 'ae-filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 156 'ae-filter-unordered_list02': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 157 'ae-filter-unordered_list03': ('{% noautoescape %}{{ a|unordered_list }}{% endnoautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 158 159 # If the input to "default" filter is marked as safe, then so is the 160 # output. However, if the default arg is used, auto-escaping kicks in (if 161 # enabled), because we cannot mark the default as safe. 162 # Note: we have to use {"a": ""} here, otherwise the invalid template 163 # variable string interferes with the test result. 164 'ae-filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"), 165 'ae-filter-default02': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), 166 167 'ae-filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"), 168 169 'ae-filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), 170 171 # Chaining a bunch of safeness-preserving filters should not alter the safe 172 # status either way. 173 'ae-chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), 174 175 # Using a filter that forces a string back to unsafe: 176 'ae-chaining02': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), 177 178 # Using a filter that forces safeness does not lead to double-escaping 179 'ae-chaining03': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"), 180 181 # Force to safe, then back (also showing why using force_escape too early 182 # in a chain can lead to unexpected results). 183 'ae-chaining04': ('{{ a|force_escape|cut:"b" }}', {"a": "a < b"}, "a &lt; "), 184 'ae-chaining05': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a < "), 185 'ae-chaining06': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), 186 'ae-chaining07': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"), 187 }) 188 189 def test_template_loader(template_name, template_dirs=None): 190 "A custom template loader that loads the unit-test templates." 191 try: 192 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name) 193 except KeyError: 194 raise template.TemplateDoesNotExist, template_name 195 196 def run_tests(verbosity=0, standalone=False): 197 # Register our custom template loader. 198 old_template_loaders = loader.template_source_loaders 199 loader.template_source_loaders = [test_template_loader] 200 201 failed_tests = [] 202 tests = TEMPLATE_TESTS.items() 203 tests.sort() 204 205 # Turn TEMPLATE_DEBUG off, because tests assume that. 206 old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False 207 # Set TEMPLATE_STRING_IF_INVALID to a known string 208 old_invalid, settings.TEMPLATE_STRING_IF_INVALID = settings.TEMPLATE_STRING_IF_INVALID, 'INVALID' 209 210 for name, vals in tests: 211 install() 212 if 'LANGUAGE_CODE' in vals[1]: 213 activate(vals[1]['LANGUAGE_CODE']) 214 else: 215 activate('en-us') 216 try: 217 ctxt = template.Context(vals[1]) 218 # Hack for testing: force autoescaping to be in effect. 219 ctxt.autoescape = True 220 output = loader.get_template(name).render(ctxt) 221 except Exception, e: 222 if e.__class__ == vals[2]: 223 if verbosity: 224 print "Template test: %s -- Passed" % name 225 else: 226 if verbosity: 227 traceback.print_exc() 228 print "Template test: %s -- FAILED. Got %s, exception: %s" % (name, e.__class__, e) 229 failed_tests.append(name) 230 continue 231 if 'LANGUAGE_CODE' in vals[1]: 232 deactivate() 233 if output == vals[2]: 234 if verbosity: 235 print "Template test: %s -- Passed" % name 236 else: 237 if verbosity: 238 print "Template test: %s -- FAILED. Expected %r, got %r" % (name, vals[2], output) 239 failed_tests.append(name) 240 loader.template_source_loaders = old_template_loaders 241 deactivate() 242 settings.TEMPLATE_DEBUG = old_td 243 settings.TEMPLATE_STRING_IF_INVALID = old_invalid 244 245 if failed_tests and not standalone: 246 msg = "Template tests %s failed." % failed_tests 247 if not verbosity: 248 msg += " Re-run tests with -v1 to see actual failures" 249 raise Exception, msg 250 251 if __name__ == "__main__": 252 run_tests(1, True) -
tests/othertests/defaultfilters.py
diff --git a/tests/othertests/defaultfilters.py b/tests/othertests/defaultfilters.py index 1636b94..1f09605 100644
a b u'\xcb' 118 118 >>> cut('a string to be mangled', 'strings') 119 119 'a string to be mangled' 120 120 121 >>> escape('<some html & special characters > here')121 >>> force_escape('<some html & special characters > here') 122 122 '<some html & special characters > here' 123 123 124 124 >>> linebreaks('line 1')