Ticket #2359: 01-core-changes.2.diff
File 01-core-changes.2.diff, 69.7 KB (added by , 18 years ago) |
---|
-
django/oldforms/__init__.py
From nobody Mon Sep 17 00:00:00 2001 From: Michael Radziej <mir@noris.de> Date: Tue Feb 6 12:56:18 2007 +0100 Subject: [PATCH] autoescape 1 SafeString needs to explicitly return self for __str__() --- django/oldforms/__init__.py | 43 ++-- 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 | 109 ++++++++++ docs/templates.txt | 118 +++++++++++ docs/templates_python.txt | 116 +++++++++++ tests/regressiontests/autoescape/tests.py | 266 +++++++++++++++++++++++++ tests/regressiontests/defaultfilters/tests.py | 2 tests/regressiontests/templates/tests.py | 126 ++++++------ 12 files changed, 893 insertions(+), 110 deletions(-) create mode 100644 django/utils/safestring.py create mode 100644 tests/regressiontests/autoescape/tests.py base 86e42e8abea4d494f2276f082933aa6773e82b88 last e2bddd7b0de6f643f7702089680ca1c38494f5c7 diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index decf0f7064f01feda7e4c5d1bee84b9d60651dd1..1b3bba139a632faf48ae8e7b87d9872633b531d3 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, ngettext 6 7 … … class FormFieldWrapper(object): 181 182 182 183 def html_error_list(self): 183 184 if self.errors(): 184 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])185 return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])) 185 186 else: 186 return ''187 return mark_safe('') 187 188 188 189 def get_id(self): 189 190 return self.formfield.get_id() … … class FormFieldCollection(FormFieldWrapp 215 216 return bool(len(self.errors())) 216 217 217 218 def html_combined_error_list(self): 218 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])219 return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) 219 220 220 221 class InlineObjectCollection(object): 221 222 "An object that acts like a sparse list of form field collections." … … class TextField(FormField): 399 400 maxlength = 'maxlength="%s" ' % self.maxlength 400 401 if isinstance(data, unicode): 401 402 data = data.encode(settings.DEFAULT_CHARSET) 402 return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \403 return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 403 404 (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 404 self.field_name, self.length, escape(data), maxlength) 405 self.field_name, self.length, escape(data), maxlength)) 405 406 406 407 def html2python(data): 407 408 return data … … class LargeTextField(TextField): 425 426 data = '' 426 427 if isinstance(data, unicode): 427 428 data = data.encode(settings.DEFAULT_CHARSET) 428 return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \429 return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ 429 430 (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 430 self.field_name, self.rows, self.cols, escape(data)) 431 self.field_name, self.rows, self.cols, escape(data))) 431 432 432 433 class HiddenField(FormField): 433 434 def __init__(self, field_name, is_required=False, validator_list=None): … … class HiddenField(FormField): 436 437 self.validator_list = validator_list[:] 437 438 438 439 def render(self, data): 439 return '<input type="hidden" id="%s" name="%s" value="%s" />' % \440 (self.get_id(), self.field_name, escape(data)) 440 return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \ 441 (self.get_id(), self.field_name, escape(data))) 441 442 442 443 class CheckboxField(FormField): 443 444 def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): … … class CheckboxField(FormField): 451 452 checked_html = '' 452 453 if data or (data is '' and self.checked_by_default): 453 454 checked_html = ' checked="checked"' 454 return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \455 return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ 455 456 (self.get_id(), self.__class__.__name__, 456 self.field_name, checked_html) 457 self.field_name, checked_html)) 457 458 458 459 def html2python(data): 459 460 "Convert value from browser ('on' or '') to a Python boolean" … … class SelectField(FormField): 484 485 selected_html = ' selected="selected"' 485 486 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name))) 486 487 output.append(' </select>') 487 return '\n'.join(output)488 return mark_safe('\n'.join(output)) 488 489 489 490 def isValidChoice(self, data, form): 490 491 str_data = str(data) … … class RadioSelectField(FormField): 537 538 output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')] 538 539 output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist]) 539 540 output.append('</ul>') 540 return ''.join(output)541 return mark_safe(''.join(output)) 541 542 def __iter__(self): 542 543 for d in self.datalist: 543 544 yield d … … class RadioSelectField(FormField): 552 553 datalist.append({ 553 554 'value': value, 554 555 'name': display_name, 555 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \556 (self.get_id() + '_' + str(i), self.field_name, value, selected_html) ,557 'label': '<label for="%s">%s</label>' % \556 'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ 557 (self.get_id() + '_' + str(i), self.field_name, value, selected_html)), 558 'label': mark_safe('<label for="%s">%s</label>' % \ 558 559 (self.get_id() + '_' + str(i), display_name), 559 })560 )}) 560 561 return RadioFieldRenderer(datalist, self.ul_class) 561 562 562 563 def isValidChoice(self, data, form): … … class SelectMultipleField(SelectField): 595 596 selected_html = ' selected="selected"' 596 597 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice))) 597 598 output.append(' </select>') 598 return '\n'.join(output)599 return mark_safe('\n'.join(output)) 599 600 600 601 def isValidChoice(self, field_data, all_data): 601 602 # data is something like ['1', '2', '3'] … … class CheckboxSelectMultipleField(Select 648 649 (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, 649 650 self.get_id() + escape(value), choice)) 650 651 output.append('</ul>') 651 return '\n'.join(output)652 return mark_safe('\n'.join(output)) 652 653 653 654 #################### 654 655 # FILE UPLOADS # … … class FileUploadField(FormField): 669 670 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 670 671 671 672 def render(self, data): 672 return '<input type="file" id="%s" class="v%s" name="%s" />' % \673 (self.get_id(), self.__class__.__name__, self.field_name) 673 return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \ 674 (self.get_id(), self.__class__.__name__, self.field_name)) 674 675 675 676 def html2python(data): 676 677 if data is None: -
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index 771880168445cfa207dfe3a5b04f1fa62a3c7266..05678572108396fcd897499487db1da9c49e73f1 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): 568 570 arg_vals.append(arg) 569 571 else: 570 572 arg_vals.append(resolve_variable(arg, context)) 571 obj = func(obj, *arg_vals) 573 if getattr(func, 'needs_autoescape', False): 574 new_obj = func(obj, autoescape = context.autoescape, *arg_vals) 575 else: 576 new_obj = func(obj, *arg_vals) 577 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 578 obj = mark_safe(new_obj) 579 elif isinstance(obj, EscapeData): 580 obj = mark_for_escaping(new_obj) 581 else: 582 obj = new_obj 583 572 584 return obj 573 585 574 586 def args_check(name, func, provided): … … class VariableNode(Node): 754 766 755 767 def render(self, context): 756 768 output = self.filter_expression.resolve(context) 757 return self.encode_output(output) 769 encoded_output = self.encode_output(output) 770 if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData): 771 return escape(encoded_output) 772 else: 773 return encoded_output 758 774 759 775 class DebugVariableNode(VariableNode): 760 776 def render(self, context): … … class DebugVariableNode(VariableNode): 764 780 if not hasattr(e, 'source'): 765 781 e.source = self.source 766 782 raise 767 return self.encode_output(output) 783 encoded_output = self.encode_output(output) 784 if context.autoescape and not isinstance(encoded_output, SafeData): 785 return escape(encoded_output) 786 else: 787 return encoded_output 768 788 769 789 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 770 790 "Returns a template.Node subclass." -
django/template/context.py
diff --git a/django/template/context.py b/django/template/context.py index ba23e95ab7dc4e7ffbd939e869b397fd431f9159..fdd2c919ce2d7bd1afb9844f08ef5c016ebf32a7 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 1d0f78ce12259aa5de58197bfc1b339f3bf25f84..5616f03266502787fe656272263757b3bbcd4aae 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('"', '\\"').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, arg=-1): 31 35 """ … … def floatformat(text, arg=-1): 58 62 return '%d' % int(f) 59 63 else: 60 64 formatstr = '%%.%df' % abs(d) 61 return formatstr % f 65 return mark_safe(formatstr % f) 66 floatformat.is_safe = True 62 67 63 def linenumbers(value ):68 def linenumbers(value, autoescape = None): 64 69 "Displays text with line numbers" 65 70 from django.utils.html import escape 66 71 lines = value.split('\n') 67 72 # Find the maximum width of the line count, for use with zero padding string format command 68 73 width = str(len(str(len(lines)))) 69 for i, line in enumerate(lines): 70 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 71 return '\n'.join(lines) 74 if not autoescape or isinstance(value, SafeData): 75 for i, line in enumerate(lines): 76 lines[i] = ("%0" + width + "d. %s") % (i + 1, line) 77 else: 78 for i, line in enumerate(lines): 79 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 80 return mark_safe('\n'.join(lines)) 81 linenumbers.is_safe = True 82 linenumbers.needs_autoescape = True 72 83 73 84 def lower(value): 74 85 "Converts a string into all lowercase" 75 86 return value.lower() 87 lower.is_safe = True 76 88 77 89 def make_list(value): 78 90 """ … … def make_list(value): 80 92 digits. For a string, it's a list of characters. 81 93 """ 82 94 return list(str(value)) 95 make_list.is_safe = False 83 96 84 97 def slugify(value): 85 98 "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens" 86 99 value = re.sub('[^\w\s-]', '', value).strip().lower() 87 return re.sub('[-\s]+', '-', value) 100 return mark_safe(re.sub('[-\s]+', '-', value)) 101 slugify.is_safe = True 88 102 89 103 def stringformat(value, arg): 90 104 """ … … def stringformat(value, arg): 99 113 return ("%" + arg) % value 100 114 except (ValueError, TypeError): 101 115 return "" 116 stringformat.is_safe = True 102 117 103 118 def title(value): 104 119 "Converts a string into titlecase" 105 120 return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 121 title.is_safe = False 106 122 107 123 def truncatewords(value, arg): 108 124 """ … … def truncatewords(value, arg): 118 134 if not isinstance(value, basestring): 119 135 value = str(value) 120 136 return truncate_words(value, length) 137 truncatewords.is_safe = True 121 138 122 139 def upper(value): 123 140 "Converts a string into all uppercase" 124 141 return value.upper() 142 upper.is_safe = False 125 143 126 144 def urlencode(value): 127 145 "Escapes a value for use in a URL" 128 146 import urllib 129 147 return urllib.quote(value) 148 urlencode.is_safe = False 130 149 131 150 def urlize(value): 132 151 "Converts URLs in plain text into clickable links" 133 152 from django.utils.html import urlize 134 return urlize(value, nofollow=True) 153 return mark_safe(urlize(value, nofollow=True)) 154 urlize.is_safe = True 135 155 136 156 def urlizetrunc(value, limit): 137 157 """ … … def urlizetrunc(value, limit): 141 161 Argument: Length to truncate URLs to. 142 162 """ 143 163 from django.utils.html import urlize 144 return urlize(value, trim_url_limit=int(limit), nofollow=True) 164 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True)) 165 urlize.is_safe = True 145 166 146 167 def wordcount(value): 147 168 "Returns the number of words" 148 169 return len(value.split()) 170 wordcount.is_safe = False 149 171 150 172 def wordwrap(value, arg): 151 173 """ … … def wordwrap(value, arg): 155 177 """ 156 178 from django.utils.text import wrap 157 179 return wrap(str(value), int(arg)) 180 wordwrap.is_safe = True 158 181 159 182 def ljust(value, arg): 160 183 """ … … def ljust(value, arg): 163 186 Argument: field size 164 187 """ 165 188 return str(value).ljust(int(arg)) 189 ljust.is_safe = True 166 190 167 191 def rjust(value, arg): 168 192 """ … … def rjust(value, arg): 171 195 Argument: field size 172 196 """ 173 197 return str(value).rjust(int(arg)) 198 rjust.is_safe = True 174 199 175 200 def center(value, arg): 176 201 "Centers the value in a field of a given width" 177 202 return str(value).center(int(arg)) 203 center.is_safe = True 178 204 179 205 def cut(value, arg): 180 206 "Removes all values of arg from the given string" 181 207 return value.replace(arg, '') 208 cut.is_safe = False 182 209 183 210 ################### 184 211 # HTML STRINGS # 185 212 ################### 186 213 187 214 def escape(value): 188 "Escapes a string's HTML" 215 "Marks the value as a string that should not be auto-escaped." 216 from django.utils.safestring import mark_for_escaping 217 return mark_for_escaping(value) 218 escape.is_safe = True 219 220 def force_escape(value): 221 """Escapes a string's HTML. This returns a new string containing the escaped 222 characters (as opposed to "escape", which marks the content for later 223 possible escaping).""" 189 224 from django.utils.html import escape 190 return escape(value) 225 return mark_safe(escape(value)) 226 force_escape.is_safe = True 191 227 192 def linebreaks(value ):228 def linebreaks(value, autoescape = None): 193 229 "Converts newlines into <p> and <br />s" 194 230 from django.utils.html import linebreaks 195 return linebreaks(value) 231 autoescape = autoescape and not isinstance(value, SafeData) 232 return mark_safe(linebreaks(value, autoescape)) 233 linebreaks.is_safe = True 234 linebreaks.needs_autoescape = True 196 235 197 def linebreaksbr(value ):236 def linebreaksbr(value, autoescape = None): 198 237 "Converts newlines into <br />s" 199 return value.replace('\n', '<br />') 238 if autoescape and not isinstance(value, SafeData): 239 from django.utils.html import escape 240 data = escape(value) 241 else: 242 data = value 243 return mark_safe(data.replace('\n', '<br />')) 244 linebreaksbr.is_safe = True 245 linebreaksbr.needs_autoescape = True 246 247 def safe(value): 248 "Marks the value as a string that should not be auto-escaped." 249 from django.utils.safestring import mark_safe 250 return mark_safe(value) 251 safe.is_safe = True 200 252 201 253 def removetags(value, tags): 202 254 "Removes a space separated list of [X]HTML tags from the output" … … def removetags(value, tags): 207 259 value = starttag_re.sub('', value) 208 260 value = endtag_re.sub('', value) 209 261 return value 262 removetags.is_safe = True 210 263 211 264 def striptags(value): 212 265 "Strips all [X]HTML tags" … … def striptags(value): 214 267 if not isinstance(value, basestring): 215 268 value = str(value) 216 269 return strip_tags(value) 270 striptags.is_safe = True 217 271 218 272 ################### 219 273 # LISTS # … … def dictsort(value, arg): 227 281 decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value] 228 282 decorated.sort() 229 283 return [item[1] for item in decorated] 284 dictsort.is_safe = False 230 285 231 286 def dictsortreversed(value, arg): 232 287 """ … … def dictsortreversed(value, arg): 237 292 decorated.sort() 238 293 decorated.reverse() 239 294 return [item[1] for item in decorated] 295 dictsortreversed.is_safe = False 240 296 241 297 def first(value): 242 298 "Returns the first item in a list" … … def first(value): 244 300 return value[0] 245 301 except IndexError: 246 302 return '' 303 first.is_safe = True 247 304 248 305 def join(value, arg): 249 306 "Joins a list with a string, like Python's ``str.join(list)``" 250 307 try: 251 returnarg.join(map(str, value))308 data = arg.join(map(str, value)) 252 309 except AttributeError: # fail silently but nicely 253 310 return value 311 safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True) 312 if safe_args: 313 return mark_safe(data) 314 else: 315 return data 316 join.is_safe = True 254 317 255 318 def length(value): 256 319 "Returns the length of the value - useful for lists" 257 320 return len(value) 321 length.is_safe = False 258 322 259 323 def length_is(value, arg): 260 324 "Returns a boolean of whether the value's length is the argument" 261 325 return len(value) == int(arg) 326 length.is_safe = False 262 327 263 328 def random(value): 264 329 "Returns a random item from the list" 265 330 return random_module.choice(value) 331 length.is_safe = True 266 332 267 333 def slice_(value, arg): 268 334 """ … … def slice_(value, arg): 283 349 284 350 except (ValueError, TypeError): 285 351 return value # Fail silently. 352 slice_.is_safe = True 286 353 287 def unordered_list(value ):354 def unordered_list(value, autoescape = None): 288 355 """ 289 356 Recursively takes a self-nested list and returns an HTML unordered list -- 290 357 WITHOUT opening and closing <ul> tags. … … def unordered_list(value): 305 372 </ul> 306 373 </li> 307 374 """ 375 if autoescape: 376 from django.utils.html import conditional_escape 377 escaper = conditional_escape 378 else: 379 escaper = lambda x: x 380 308 381 def _helper(value, tabs): 309 382 indent = '\t' * tabs 310 383 if value[1]: 311 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,384 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent, 312 385 '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent) 313 386 else: 314 return '%s<li>%s</li>' % (indent, value[0]) 315 return _helper(value, 1) 387 return '%s<li>%s</li>' % (indent, escaper(value[0])) 388 return mark_safe(_helper(value, 1)) 389 unordered_list.is_safe = True 390 unordered_list.needs_autoescape = True 316 391 317 392 ################### 318 393 # INTEGERS # … … ################### 321 396 def add(value, arg): 322 397 "Adds the arg to the value" 323 398 return int(value) + int(arg) 399 add.is_safe = False 324 400 325 401 def get_digit(value, arg): 326 402 """ … … def get_digit(value, arg): 340 416 return int(str(value)[-arg]) 341 417 except IndexError: 342 418 return 0 419 get_digit.is_safe = False 343 420 344 421 ################### 345 422 # DATES # … … def date(value, arg=None): 353 430 if arg is None: 354 431 arg = settings.DATE_FORMAT 355 432 return format(value, arg) 433 date.is_safe = False 356 434 357 435 def time(value, arg=None): 358 436 "Formats a time according to the given format" … … def time(value, arg=None): 362 440 if arg is None: 363 441 arg = settings.TIME_FORMAT 364 442 return time_format(value, arg) 443 time.is_safe = False 365 444 366 445 def timesince(value, arg=None): 367 446 'Formats a date as the time since that date (i.e. "4 days, 6 hours")' … … def timesince(value, arg=None): 371 450 if arg: 372 451 return timesince(arg, value) 373 452 return timesince(value) 453 timesince.is_safe = False 374 454 375 455 def timeuntil(value, arg=None): 376 456 'Formats a date as the time until that date (i.e. "4 days, 6 hours")' … … def timeuntil(value, arg=None): 381 461 if arg: 382 462 return timesince(arg, value) 383 463 return timesince(datetime.now(), value) 464 timeuntil.is_safe = False 384 465 385 466 ################### 386 467 # LOGIC # … … ################### 389 470 def default(value, arg): 390 471 "If value is unavailable, use given default" 391 472 return value or arg 473 default.is_safe = False 392 474 393 475 def default_if_none(value, arg): 394 476 "If value is None, use given default" 395 477 if value is None: 396 478 return arg 397 479 return value 480 default_if_none.is_safe = False 398 481 399 482 def divisibleby(value, arg): 400 483 "Returns true if the value is devisible by the argument" 401 484 return int(value) % int(arg) == 0 485 divisibleby.is_safe = False 402 486 403 487 def yesno(value, arg=None): 404 488 """ … … def yesno(value, arg=None): 429 513 if value: 430 514 return yes 431 515 return no 516 yesno.is_safe = False 432 517 433 518 ################### 434 519 # MISC # … … def filesizeformat(bytes): 451 536 if bytes < 1024 * 1024 * 1024: 452 537 return "%.1f MB" % (bytes / (1024 * 1024)) 453 538 return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) 539 filesizeformat.is_safe = True 454 540 455 541 def pluralize(value, arg='s'): 456 542 """ … … def pluralize(value, arg='s'): 478 564 except TypeError: # len() of unsized object 479 565 pass 480 566 return singular_suffix 567 pluralize.is_safe = False 481 568 482 569 def phone2numeric(value): 483 570 "Takes a phone number and converts it in to its numerical equivalent" 484 571 from django.utils.text import phone2numeric 485 572 return phone2numeric(value) 573 phone2numeric.is_safe = True 486 574 487 575 def pprint(value): 488 576 "A wrapper around pprint.pprint -- for debugging, really" … … def pprint(value): 491 579 return pformat(value) 492 580 except Exception, e: 493 581 return "Error in formatting:%s" % e 582 pprint.is_safe = True 494 583 495 584 # Syntax: register.filter(name of filter, callback) 496 585 register.filter(add) … … register.filter(filesizeformat) 509 598 register.filter(first) 510 599 register.filter(fix_ampersands) 511 600 register.filter(floatformat) 601 register.filter(force_escape) 512 602 register.filter(get_digit) 513 603 register.filter(join) 514 604 register.filter(length) … … register.filter(pprint) 525 615 register.filter(removetags) 526 616 register.filter(random) 527 617 register.filter(rjust) 618 register.filter(safe) 528 619 register.filter('slice', slice_) 529 620 register.filter(slugify) 530 621 register.filter(stringformat) -
django/template/defaulttags.py
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index 3f3f4bda5657e6cc46ed5696410e1441fd33cfcd..9eb4490259af1b8da35abf2268aa850c1ae8406b 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, COMMENT_TAG_START, COMMENT_TAG_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): 41 57 def render(self, context): 42 58 output = self.nodelist.render(context) 43 59 # 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) 45 63 46 64 class FirstOfNode(Node): 47 65 def __init__(self, vars): … … class RegroupNode(Node): 234 252 return '' 235 253 output = [] # list of dictionaries in the format {'grouper': 'key', 'list': [list of contents]} 236 254 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) 238 258 # TODO: Is this a sensible way to determine equality? 239 259 if output and repr(output[-1]['grouper']) == repr(grouper): 240 260 output[-1]['list'].append(obj) … … class WidthRatioNode(Node): 336 356 return str(int(round(ratio))) 337 357 338 358 #@register.tag 359 def autoescape(parser, token): 360 """ 361 Force autoescape behaviour for this block. 362 """ 363 nodelist = parser.parse(('endautoescape',)) 364 parser.delete_first_token() 365 return AutoEscapeControlNode(True, nodelist) 366 autoescape = register.tag(autoescape) 367 368 #@register.tag 339 369 def comment(parser, token): 340 370 """ 341 371 Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` … … def do_filter(parser, token): 429 459 430 460 Sample usage:: 431 461 432 {% filter escape|lower %}462 {% filter force_escape|lower %} 433 463 This text will be HTML-escaped, and will appear in lowercase. 434 464 {% endfilter %} 435 465 """ 436 466 _, rest = token.contents.split(None, 1) 437 467 filter_expr = parser.compile_filter("var|%s" % (rest)) 468 for func, unused in filter_expr.filters: 469 if func.__name__ in ('escape', 'safe'): 470 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 438 471 nodelist = parser.parse(('endfilter',)) 439 472 parser.delete_first_token() 440 473 return FilterNode(filter_expr, nodelist) … … def ifchanged(parser, token): 675 708 ifchanged = register.tag(ifchanged) 676 709 677 710 #@register.tag 711 def noautoescape(parser, token): 712 """ 713 Force autoescape behaviour to be disabled for this block. 714 """ 715 nodelist = parser.parse(('endnoautoescape',)) 716 parser.delete_first_token() 717 return AutoEscapeControlNode(False, nodelist) 718 autoescape = register.tag(noautoescape) 719 720 #@register.tag 678 721 def ssi(parser, token): 679 722 """ 680 723 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 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 0000000000000000000000000000000000000000..eac37c686ea6c5ebbedb34c6c3738854a8ad90ff
- + 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 def __str__(self): 45 return self 46 47 class 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 79 def 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 94 def 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 9f8fe446b499142f0000140e3a1ac4f7dbe947c2..1365bb0593db7b3ac3a03353979db973cbf45528 100644
a b it also defines the content that fills t 260 260 two similarly-named ``{% block %}`` tags in a template, that template's parent 261 261 wouldn't know which one of the blocks' content to use. 262 262 263 Automatic HTML escaping 264 ======================= 265 266 A very real problem when creating HTML (and other) output using templates and 267 variable substitution is the possibility of accidently inserting some variable 268 value that affects the resulting HTML. For example, a template fragment like 269 270 :: 271 272 Hello, {{ name }}. 273 274 seems like a harmless way to display the user's name. However, if you are 275 displaying data that the user entered directly and they entered their name as 276 277 :: 278 279 <script>alert('hello')</script> 280 281 this would always display a Javascript alert box whenever the page was loaded. 282 Similarly, if you were displaying some data generated by another process and 283 it contained a '<' symbol, you couldn't just dump this straight into your 284 HTML, because it would be treated as the start of an element. The effects of 285 these sorts of problems can vary from merely annoying to allowing exploits via 286 `Cross Site Scripting`_ (XSS) attacks. 287 288 .. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting 289 290 In order to provide some protection against these problems, Django provides an 291 auto-escaping template tag. Inside this tag, any data that comes from template 292 variables is examined to see if it contains one of the five HTML characters 293 (<, >, ', " and &) that often need escaping and those characters are converted 294 to their respective HTML entities. 295 296 Because some variables will contain data that is *intended* to be rendered 297 as HTML, template tag and filter writers can mark their output strings as 298 requiring no further escaping. For example, the ``unordered_list`` filter is 299 designed to return raw HTML and we want the template processor to simply 300 display the results as returned, without applying any escaping. That is taken 301 care of by the filter. The template author need do nothing special in that 302 case. 303 304 By default, auto-escaping is not in effect. To enable it inside your template, 305 wrap the affected content in the ``autoescape`` tag, like so:: 306 307 {% autoescape %} 308 Hello {{ name }} 309 {% endautoescape %} 310 311 Since the auto-escaping tag passes its effect onto templates that extend the 312 current one as well as templates included via the ``include`` tag (just like 313 all block tags), if you wrap your main HTML content in an ``autoescape`` tag, 314 you will have automatic escaping applied to all of your content. 315 316 At times, you might want to disable auto-escaping when it would otherwise be 317 in effect. You can do this with the ``noautoescape`` tag. For example:: 318 319 {% autoescape %} 320 Hello {{ name }} 321 322 {% noautoescape %} 323 This will not be auto-escaped: {{ data }}. 324 325 Nor this: {{ other_data }} 326 {% endnoautoescape %} 327 {% endautoescape %} 328 329 For individual variables, the ``safe`` filter can also be used. 330 331 Generally, you will not need to worry about auto-escaping very much. Enable it 332 in your base template once you are entering the main HTML region and then 333 write your templates normally. The view developers and custom filter authors 334 need to think about when their data should not be escaped and mark it 335 appropriately. They are in a better position to know when that should happen 336 than the template author, so it is their responsibility. By default, when 337 auto-escaping is enabled, all output is escaped unless the template processor 338 is explicitly told otherwise. 339 263 340 Using the built-in reference 264 341 ============================ 265 342 … … available, and what they do. 335 412 Built-in tag reference 336 413 ---------------------- 337 414 415 autoescape 416 ~~~~~~~~~~ 417 418 All variables that are output inside this tag have HTML escaping applied to 419 them, as if they each had the ``escape`` filter attached to them. 420 421 The only exceptions are variables that are already marked as 'safe' from 422 escaping, either by the code that populated the variable, or because it has 423 the ``safe`` filter applied. 424 338 425 block 339 426 ~~~~~ 340 427 … … just like in variable syntax. 402 489 403 490 Sample usage:: 404 491 405 {% filter escape|lower %}492 {% filter force_escape|lower %} 406 493 This text will be HTML-escaped, and will appear in all lowercase. 407 494 {% endfilter %} 408 495 … … Load a custom template tag set. 617 704 618 705 See `Custom tag and filter libraries`_ for more information. 619 706 707 noautoescape 708 ~~~~~~~~~~~~ 709 710 Disable the effects of the ``autoescape`` tag (if it is in effect). 711 620 712 now 621 713 ~~~ 622 714 … … Escapes a string's HTML. Specifically, i 905 997 * ``'"'`` (double quote) to ``'"'`` 906 998 * ``"'"`` (single quote) to ``'''`` 907 999 1000 The escaping is only applied when the string is output, so it does not matter 1001 where in a chained sequence of filters you put ``escape``: it will always be 1002 applied as though it were the last filter. If you want escaping to be applied 1003 immediately, use the ``force_escape`` filter. 1004 908 1005 filesizeformat 909 1006 ~~~~~~~~~~~~~~ 910 1007 … … For example: 949 1046 Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with 950 1047 an argument of ``-1``. 951 1048 1049 force_escape 1050 ~~~~~~~~~~~~ 1051 1052 **New in Django development version** 1053 1054 Applies HTML escaping to a string (see the ``escape`` filter for details). 1055 This filter is applied immediately and returns a new, escaped string. This is 1056 useful in the typically rare cases where you need multiple escaping or want to 1057 apply other filters to the escaped results. Normally, you want to use the 1058 ``escape`` filter. 1059 952 1060 get_digit 953 1061 ~~~~~~~~~ 954 1062 … … Right-aligns the value in a field of a g 1060 1168 1061 1169 **Argument:** field size 1062 1170 1171 safe 1172 ~~~~ 1173 1174 Marks a string as not requiring further HTML escaping prior to output. This is 1175 only useful inside an ``autoescape`` block, when the output would otherwise be 1176 automatically escaped. Outside of an ``autoescape`` block, this filter has no 1177 effect. 1178 1063 1179 slice 1064 1180 ~~~~~ 1065 1181 -
docs/templates_python.txt
diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 5f9c5bde4327b142187132226a90a6c960d55b55..68877d444227d561fa0550320ff9faf134867c90 100644
a b decorator instead:: 654 654 If you leave off the ``name`` argument, as in the second example above, Django 655 655 will use the function's name as the filter name. 656 656 657 Filters and auto-escaping 658 ~~~~~~~~~~~~~~~~~~~~~~~~~ 659 660 When you are writing a custom filter, you need to give some thought to how 661 this filter will work when rendered in an auto-escaping environment (inside 662 an ``autoescape`` template tag block). First, you should realise that there 663 are three types of strings that can be passed around inside the template code: 664 665 * raw strings are the native Python ``str`` (or ``unicode``) types. On 666 output, they are escaped if they are inside an ``autoescape`` block. 667 * "safe" strings are strings that are safe from further escaping at output 668 time. Any necessary escaping has already been done. They are commonly used 669 for output that contains raw HTML that is intended to be intrepreted on the 670 client side. 671 672 Internally, these strings are of type ``SafeString`` or ``SafeUnicode``, 673 although they share a common base class in ``SafeData``, so you can test 674 for them using code like:: 675 676 if isinstance(value, SafeData): 677 # Do something with the "safe" string. 678 679 * strings which are marked as "need escaping" are *always* escaped on 680 output, regardless of whether they are in an ``autoescape`` block or not. 681 These strings are only escaped once, however, even if used inside an 682 ``autoescaep`` block. This type of string is internally represented by the 683 types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to 684 worry about these; they exist only for the implementation of the ``escape`` 685 filter. 686 687 Inside your filter, you will need to think about three areas in order to be 688 auto-escaping compliant: 689 690 1. If your filter returns a string that is ready for direct output (it should 691 be considered a "safe" string), you should call 692 ``django.utils.safestring.mark_safe()`` on the result prior to returning. 693 This will turn the result into the appropriate ``SafeData`` type. 694 695 2. If your filter is given a "safe" string, is it guaranteed to return a 696 "safe" string? If so, set the ``is_safe`` attribute on the function to be 697 ``True``. For example, a filter that replaced all numbers with the number 698 spelt out in words is going to be safe-string-preserving, since it cannot 699 introduce any of the five dangerous characters: <, >, ", ' or &. So we can 700 write:: 701 702 @register.filter 703 def convert_to_words(value): 704 # ... implementation here ... 705 return result 706 707 convert_to_words.is_safe = True 708 709 Note that this filter does not return a universally safe result (it does not 710 return ``mark_safe(result)``) because if it is handed a raw string such as 711 '<a>', this will need further escaping in an auto-escape environment. The 712 ``is_safe`` attribute only talks about the safeness of the result when a safe 713 string is passed in to the filter. 714 715 3. Will your filter behave differently depending upon whether auto-escaping 716 is currently in effect or not? For example, the ``ordered_list`` filter that 717 ships with Django needs to know whether to escape its content or not. It will 718 always return a safe string, since it returns raw HTML, so we cannot apply 719 escaping to the result -- it needs to be done in-situ. 720 721 For these cases, the filter function needs to be told what the current 722 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the 723 filter to ``True`` and have your function take an extra argument called 724 ``autoescape`` with a default value of ``None``. When the filter is called, 725 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in 726 effect. For example, the ``unordered_list`` filter is written as:: 727 728 def unordered_list(value, autoescape = None): 729 # ... lots of code here ... 730 731 return mark_safe(...) 732 733 unordered_list.is_safe = True 734 unordered_list.needs_autoescape = True 735 736 By default, both the ``is_safe`` and ``needs_autoescape`` attributes are 737 ``False``. You do not need to specify them if ``False`` is an acceptable 738 value. 739 740 As a matter of convention, we leave ``is_safe`` as ``False`` for filters that 741 do not accept string inputs (they might take a number as an input, for 742 example) or for those that return a non-string (e.g. the ``length`` filter). 743 However, not following this convention will not cause any harm or make your 744 results any more vulnerable to cross-site scripting problems. 745 657 746 Writing custom template tags 658 747 ---------------------------- 659 748 … … Ultimately, this decoupling of compilati 772 861 efficient template system, because a template can render multiple context 773 862 without having to be parsed multiple times. 774 863 864 Auto-escaping considerations 865 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 866 867 The output from template tags is not automatically run through the 868 auto-escaping filters if used inside an ``autoescape`` tag. However, there are 869 still a couple of things you should keep in mind when writing a template tag: 870 871 If the ``render()`` function of your template stores the result in a context 872 variable (rather than returning the result in a string), it should take care 873 to call ``mark_safe()`` if appropriate. When the variable is ultimately 874 rendered, it will be affected by the auto-escape setting in effect at the 875 time, so content that should be safe from further escaping needs to be marked 876 as such. 877 878 Also, if your template tag creates a new context for performing some 879 sub-rendering, you should be careful to set the auto-escape variable to the 880 current context's value. For example:: 881 882 def render(self, context): 883 # ... 884 new_context = Context({'var': obj}) 885 new_context.autoescape = context.autoescape 886 # ... Do something with new_context ... 887 888 This is not a very common situation, but it is sometimes useful (see 889 ``django.templates.defaulttags.FilterNode.render()`` for an example). 890 775 891 Registering the tag 776 892 ~~~~~~~~~~~~~~~~~~~ 777 893 -
new file tests/regressiontests/autoescape/tests.py
diff --git a/tests/regressiontests/autoescape/tests.py b/tests/regressiontests/autoescape/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..22b89e28e6bf032aa6c287f1861adedb3023bb47
- + 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 from regressiontests.templates.tests 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 unittest 16 17 class 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" 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>"}, "<b>first</b>"), 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>"}, "<b>hello</b>"), 53 54 # Noautoescape and autoescape nest in a predictable way. 55 'autoescape-tag04': ("{% noautoescape %}{{ first }} {% autoescape %}{{ first }}{% endautoescape %}{% endnoautoescape %}", {"first": "<a>"}, "<a> <a>"), 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"<a>\' <a>\'"), 68 69 'ae-filter-capfirst01': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), 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;b a&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<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 80 '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"), 81 82 'ae-filter-lower01': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & 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("&")}, "['&']"), 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 & 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<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 & bravo")}, "alpha & ... alpha & ..."), 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 & b")}, "A & B A &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 & b")}, 'a & 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 & b")}, "3 3"), 111 112 'ae-filter-wordwrap01': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 113 114 'ae-filter-ljust01': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 115 116 'ae-filter-rjust01': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 117 118 'ae-filter-center01': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&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&y")}, "&y &amp;y"), 123 'ae-filter-cut02': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&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&y x&y"), 128 'ae-filter-escape02': ('{% noautoescape %}{{ a|escape }} {{ b|escape }}{% endnoautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&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&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&y"), 137 'ae-filter-force-escape02': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&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&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&<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&<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>"}, "<b>hello</b> -- <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 <p>y</p> 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&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&b a&b"), 161 162 'ae-filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 163 164 '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>"), 165 '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>"), 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<"), 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<"), 177 178 '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>"), 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 < 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 < .A < "), 186 187 # Using a filter that forces safeness does not lead to double-escaping 188 'ae-chaining03': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < 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 &lt; "), 193 'ae-chaining05': ('{{ a|cut:"b"|force_escape }}', {"a": "a < b"}, "a < "), 194 'ae-chaining06': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), 195 'ae-chaining07': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < 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 265 if __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 439a40c31bc4afcc0f1c55fc2bd85d67d19af91e..75c1afa4dc2f7fa37b26258c2de096edbfd4608a 100644
a b u'\xcb' 141 141 >>> cut('a string to be mangled', 'strings') 142 142 'a string to be mangled' 143 143 144 >>> escape('<some html & special characters > here')144 >>> force_escape('<some html & special characters > here') 145 145 '<some html & special characters > here' 146 146 147 147 >>> linebreaks('line 1') -
tests/regressiontests/templates/tests.py
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 0a41f5b5b7b605da059dc5008b9386dfd74ffdb2..c22151012be44abd19d9e19de9e54fa66ba9dd89 100644
a b class UnicodeInStrClass: 70 70 71 71 class Templates(unittest.TestCase): 72 72 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): 73 138 # NOW and NOW_tz are used by timesince tag tests. 74 139 NOW = datetime.now() 75 140 NOW_tz = datetime.now(LocalTimezone(datetime.now())) 76 141 77 142 # SYNTAX -- 78 143 # 'template_name': ('template contents', 'context dict', 'expected string output' or Exception class) 79 TEMPLATE_TESTS = { 80 144 return { 81 145 ### BASIC SYNTAX ########################################################## 82 146 83 147 # Plain text should go through the template parser untouched … … class Templates(unittest.TestCase): 632 696 'timeuntil05' : ('{{ a|timeuntil:b }}', {'a':NOW - timedelta(days=2), 'b':NOW - timedelta(days=2, minutes=1)}, '1 minute'), 633 697 } 634 698 635 # Register our custom template loader.636 def test_template_loader(template_name, template_dirs=None):637 "A custom template loader that loads the unit-test templates."638 try:639 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)640 except KeyError:641 raise template.TemplateDoesNotExist, template_name642 643 old_template_loaders = loader.template_source_loaders644 loader.template_source_loaders = [test_template_loader]645 646 failures = []647 tests = TEMPLATE_TESTS.items()648 tests.sort()649 650 # Turn TEMPLATE_DEBUG off, because tests assume that.651 old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False652 653 # Set TEMPLATE_STRING_IF_INVALID to a known string654 old_invalid = settings.TEMPLATE_STRING_IF_INVALID655 656 for name, vals in tests:657 install()658 659 if isinstance(vals[2], tuple):660 normal_string_result = vals[2][0]661 invalid_string_result = vals[2][1]662 else:663 normal_string_result = vals[2]664 invalid_string_result = vals[2]665 666 if 'LANGUAGE_CODE' in vals[1]:667 activate(vals[1]['LANGUAGE_CODE'])668 else:669 activate('en-us')670 671 for invalid_str, result in [('', normal_string_result),672 ('INVALID', invalid_string_result)]:673 settings.TEMPLATE_STRING_IF_INVALID = invalid_str674 try:675 output = loader.get_template(name).render(template.Context(vals[1]))676 except Exception, e:677 if e.__class__ != result:678 failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))679 continue680 if output != result:681 failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))682 683 if 'LANGUAGE_CODE' in vals[1]:684 deactivate()685 686 loader.template_source_loaders = old_template_loaders687 deactivate()688 settings.TEMPLATE_DEBUG = old_td689 settings.TEMPLATE_STRING_IF_INVALID = old_invalid690 691 self.assertEqual(failures, [], '\n'.join(failures))692 693 699 if __name__ == "__main__": 694 700 unittest.main()