Ticket #2359: 01-core-changes.4.diff
File 01-core-changes.4.diff, 71.0 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: Mon Mar 19 13:34:53 2007 +0100 Subject: [PATCH] autoescape 1 Extended the check for 'escape' or 'safe' in `do_filter` so that it can deal with decorators like `stringfilter`. to --- django/oldforms/__init__.py | 43 ++-- django/template/__init__.py | 26 ++ django/template/context.py | 4 django/template/defaultfilters.py | 133 +++++++++++-- django/template/defaulttags.py | 49 ++++- django/utils/html.py | 15 + django/utils/safestring.py | 109 ++++++++++ docs/templates.txt | 118 +++++++++++ docs/templates_python.txt | 116 +++++++++++ tests/regressiontests/autoescape/__init__.py | 0 tests/regressiontests/autoescape/models.py | 0 tests/regressiontests/autoescape/tests.py | 266 +++++++++++++++++++++++++ tests/regressiontests/defaultfilters/tests.py | 2 tests/regressiontests/templates/tests.py | 126 ++++++------ 14 files changed, 896 insertions(+), 111 deletions(-) create mode 100644 django/utils/safestring.py create mode 100644 tests/regressiontests/autoescape/__init__.py create mode 100644 tests/regressiontests/autoescape/models.py create mode 100644 tests/regressiontests/autoescape/tests.py base c52c291f3430b51fc122a093ebbff8f619a7a23e last 339180bd0aa87b4026c063e53726a2b0267f73cf diff --git a/django/oldforms/__init__.py b/django/oldforms/__init__.py index 56101984f5332e8832ac14c49d7a7a1488a90ef0..e9a21ce7f5d5f37c8c36a88076ec4088e5dfd846 100644
a b 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): 183 184 184 185 def html_error_list(self): 185 186 if self.errors(): 186 return '<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])187 return mark_safe('<ul class="errorlist"><li>%s</li></ul>' % '</li><li>'.join([escape(e) for e in self.errors()])) 187 188 else: 188 return ''189 return mark_safe('') 189 190 190 191 def get_id(self): 191 192 return self.formfield.get_id() … … class FormFieldCollection(FormFieldWrapp 217 218 return bool(len(self.errors())) 218 219 219 220 def html_combined_error_list(self): 220 return ''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])221 return mark_safe(''.join([field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')])) 221 222 222 223 class InlineObjectCollection(object): 223 224 "An object that acts like a sparse list of form field collections." … … class TextField(FormField): 404 405 maxlength = 'maxlength="%s" ' % self.maxlength 405 406 if isinstance(data, unicode): 406 407 data = data.encode(settings.DEFAULT_CHARSET) 407 return '<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \408 return mark_safe('<input type="%s" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 408 409 (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 409 self.field_name, self.length, escape(data), maxlength) 410 self.field_name, self.length, escape(data), maxlength)) 410 411 411 412 def html2python(data): 412 413 return data … … class LargeTextField(TextField): 430 431 data = '' 431 432 if isinstance(data, unicode): 432 433 data = data.encode(settings.DEFAULT_CHARSET) 433 return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \434 return mark_safe('<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ 434 435 (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 435 self.field_name, self.rows, self.cols, escape(data)) 436 self.field_name, self.rows, self.cols, escape(data))) 436 437 437 438 class HiddenField(FormField): 438 439 def __init__(self, field_name, is_required=False, validator_list=None): … … class HiddenField(FormField): 441 442 self.validator_list = validator_list[:] 442 443 443 444 def render(self, data): 444 return '<input type="hidden" id="%s" name="%s" value="%s" />' % \445 (self.get_id(), self.field_name, escape(data)) 445 return mark_safe('<input type="hidden" id="%s" name="%s" value="%s" />' % \ 446 (self.get_id(), self.field_name, escape(data))) 446 447 447 448 class CheckboxField(FormField): 448 449 def __init__(self, field_name, checked_by_default=False, validator_list=None, is_required=False): … … class CheckboxField(FormField): 456 457 checked_html = '' 457 458 if data or (data is '' and self.checked_by_default): 458 459 checked_html = ' checked="checked"' 459 return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \460 return mark_safe('<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ 460 461 (self.get_id(), self.__class__.__name__, 461 self.field_name, checked_html) 462 self.field_name, checked_html)) 462 463 463 464 def html2python(data): 464 465 "Convert value from browser ('on' or '') to a Python boolean" … … class SelectField(FormField): 489 490 selected_html = ' selected="selected"' 490 491 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(display_name))) 491 492 output.append(' </select>') 492 return '\n'.join(output)493 return mark_safe('\n'.join(output)) 493 494 494 495 def isValidChoice(self, data, form): 495 496 str_data = str(data) … … class RadioSelectField(FormField): 542 543 output = ['<ul%s>' % (self.ul_class and ' class="%s"' % self.ul_class or '')] 543 544 output.extend(['<li>%s %s</li>' % (d['field'], d['label']) for d in self.datalist]) 544 545 output.append('</ul>') 545 return ''.join(output)546 return mark_safe(''.join(output)) 546 547 def __iter__(self): 547 548 for d in self.datalist: 548 549 yield d … … class RadioSelectField(FormField): 557 558 datalist.append({ 558 559 'value': value, 559 560 'name': display_name, 560 'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \561 (self.get_id() + '_' + str(i), self.field_name, value, selected_html) ,562 'label': '<label for="%s">%s</label>' % \561 'field': mark_safe('<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ 562 (self.get_id() + '_' + str(i), self.field_name, value, selected_html)), 563 'label': mark_safe('<label for="%s">%s</label>' % \ 563 564 (self.get_id() + '_' + str(i), display_name), 564 })565 )}) 565 566 return RadioFieldRenderer(datalist, self.ul_class) 566 567 567 568 def isValidChoice(self, data, form): … … class SelectMultipleField(SelectField): 600 601 selected_html = ' selected="selected"' 601 602 output.append(' <option value="%s"%s>%s</option>' % (escape(value), selected_html, escape(choice))) 602 603 output.append(' </select>') 603 return '\n'.join(output)604 return mark_safe('\n'.join(output)) 604 605 605 606 def isValidChoice(self, field_data, all_data): 606 607 # data is something like ['1', '2', '3'] … … class CheckboxSelectMultipleField(Select 653 654 (self.get_id() + escape(value), self.__class__.__name__, field_name, checked_html, 654 655 self.get_id() + escape(value), choice)) 655 656 output.append('</ul>') 656 return '\n'.join(output)657 return mark_safe('\n'.join(output)) 657 658 658 659 #################### 659 660 # FILE UPLOADS # … … class FileUploadField(FormField): 674 675 raise validators.CriticalValidationError, gettext("The submitted file is empty.") 675 676 676 677 def render(self, data): 677 return '<input type="file" id="%s" class="v%s" name="%s" />' % \678 (self.get_id(), self.__class__.__name__, self.field_name) 678 return mark_safe('<input type="file" id="%s" class="v%s" name="%s" />' % \ 679 (self.get_id(), self.__class__.__name__, self.field_name)) 679 680 680 681 def html2python(data): 681 682 if data is None: -
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index 0d8990a42b61273588ff318667c2ea1a701fe573..53201c225138497be2848a593f36d81a4d3242e2 100644
a b from django.conf import settings 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): 576 578 arg_vals.append(arg) 577 579 else: 578 580 arg_vals.append(resolve_variable(arg, context)) 579 obj = func(obj, *arg_vals) 581 if getattr(func, 'needs_autoescape', False): 582 new_obj = func(obj, autoescape = context.autoescape, *arg_vals) 583 else: 584 new_obj = func(obj, *arg_vals) 585 if getattr(func, 'is_safe', False) and isinstance(obj, SafeData): 586 obj = mark_safe(new_obj) 587 elif isinstance(obj, EscapeData): 588 obj = mark_for_escaping(new_obj) 589 else: 590 obj = new_obj 591 580 592 return obj 581 593 582 594 def args_check(name, func, provided): … … class VariableNode(Node): 765 777 766 778 def render(self, context): 767 779 output = self.filter_expression.resolve(context) 768 return self.encode_output(output) 780 encoded_output = self.encode_output(output) 781 if (context.autoescape and not isinstance(encoded_output, SafeData)) or isinstance(encoded_output, EscapeData): 782 return escape(encoded_output) 783 else: 784 return encoded_output 769 785 770 786 class DebugVariableNode(VariableNode): 771 787 def render(self, context): … … class DebugVariableNode(VariableNode): 775 791 if not hasattr(e, 'source'): 776 792 e.source = self.source 777 793 raise 778 return self.encode_output(output) 794 encoded_output = self.encode_output(output) 795 if context.autoescape and not isinstance(encoded_output, SafeData): 796 return escape(encoded_output) 797 else: 798 return encoded_output 779 799 780 800 def generic_tag_compiler(params, defaults, name, node_class, parser, token): 781 801 "Returns a template.Node subclass." -
django/template/context.py
diff --git a/django/template/context.py b/django/template/context.py index 25397b0e1a4a29538db23fd5440ffe2ce667f202..a93d726d24c9c69581283701756b8b1df3ae22d4 100644
a b class ContextPopException(Exception): 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): 98 101 processors = tuple(processors) 99 102 for processor in get_standard_processors() + processors: 100 103 self.update(processor(request)) 104 -
django/template/defaultfilters.py
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index a025365c908eec6c7962a9cca28665c87e30386e..a0165b3dad0d7f82b181873809d4e07102565301 100644
a b 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 … … def addslashes(value): 49 50 "Adds slashes - useful for passing strings to JavaScript, for example." 50 51 return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") 51 52 addslashes = stringfilter(addslashes) 53 addslashes.is_safe = True 52 54 53 55 def capfirst(value): 54 56 "Capitalizes the first character of the value" 55 57 return value and value[0].upper() + value[1:] 56 58 capfirst = stringfilter(capfirst) 57 59 capfirst.is_safe = True 60 58 61 def fix_ampersands(value): 59 62 "Replaces ampersands with ``&`` entities" 60 63 from django.utils.html import fix_ampersands 61 64 return fix_ampersands(value) 62 65 fix_ampersands = stringfilter(fix_ampersands) 66 fix_ampersands.is_safe = True 63 67 64 68 def floatformat(text, arg=-1): 65 69 """ … … def floatformat(text, arg=-1): 93 97 return '%d' % int(f) 94 98 else: 95 99 formatstr = '%%.%df' % abs(d) 96 return formatstr % f 100 return mark_safe(formatstr % f) 101 floatformat.is_safe = True 97 102 98 def linenumbers(value ):103 def linenumbers(value, autoescape = None): 99 104 "Displays text with line numbers" 100 105 from django.utils.html import escape 101 106 lines = value.split('\n') 102 107 # Find the maximum width of the line count, for use with zero padding string format command 103 108 width = str(len(str(len(lines)))) 104 for i, line in enumerate(lines): 105 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 106 return '\n'.join(lines) 109 if not autoescape or isinstance(value, SafeData): 110 for i, line in enumerate(lines): 111 lines[i] = ("%0" + width + "d. %s") % (i + 1, line) 112 else: 113 for i, line in enumerate(lines): 114 lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line)) 115 return mark_safe('\n'.join(lines)) 107 116 linenumbers = stringfilter(linenumbers) 117 linenumbers.is_safe = True 118 linenumbers.needs_autoescape = True 108 119 109 120 def lower(value): 110 121 "Converts a string into all lowercase" 111 122 return value.lower() 112 123 lower = stringfilter(lower) 124 lower.is_safe = True 113 125 114 126 def make_list(value): 115 127 """ … … def make_list(value): 118 130 """ 119 131 return list(value) 120 132 make_list = stringfilter(make_list) 133 make_list.is_safe = False 121 134 122 135 def slugify(value): 123 136 "Converts to lowercase, removes non-alpha chars and converts spaces to hyphens" 124 137 value = re.sub('[^\w\s-]', '', value).strip().lower() 125 return re.sub('[-\s]+', '-', value)138 return mark_safe(re.sub('[-\s]+', '-', value)) 126 139 slugify = stringfilter(slugify) 140 slugify.is_safe = True 127 141 128 142 def stringformat(value, arg): 129 143 """ … … def stringformat(value, arg): 138 152 return ("%" + str(arg)) % value 139 153 except (ValueError, TypeError): 140 154 return "" 155 stringformat.is_safe = True 141 156 142 157 def title(value): 143 158 "Converts a string into titlecase" 144 159 return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 145 160 title = stringfilter(title) 161 title.is_safe = False 146 162 147 163 def truncatewords(value, arg): 148 164 """ … … def truncatewords(value, arg): 159 175 value = str(value) 160 176 return truncate_words(value, length) 161 177 truncatewords = stringfilter(truncatewords) 178 truncatewords.is_safe = True 162 179 163 180 def truncatewords_html(value, arg): 164 181 """ … … def upper(value): 180 197 "Converts a string into all uppercase" 181 198 return value.upper() 182 199 upper = stringfilter(upper) 200 upper.is_safe = False 183 201 184 202 def urlencode(value): 185 203 "Escapes a value for use in a URL" … … def urlencode(value): 188 206 value = str(value) 189 207 return urllib.quote(value) 190 208 urlencode = stringfilter(urlencode) 209 urlencode.is_safe = False 191 210 192 211 def urlize(value): 193 212 "Converts URLs in plain text into clickable links" 194 213 from django.utils.html import urlize 195 return urlize(value, nofollow=True)214 return mark_safe(urlize(value, nofollow=True)) 196 215 urlize = stringfilter(urlize) 216 urlize.is_safe = True 197 217 198 218 def urlizetrunc(value, limit): 199 219 """ … … def urlizetrunc(value, limit): 203 223 Argument: Length to truncate URLs to. 204 224 """ 205 225 from django.utils.html import urlize 206 return urlize(value, trim_url_limit=int(limit), nofollow=True)226 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True)) 207 227 urlizetrunc = stringfilter(urlizetrunc) 228 urlize.is_safe = True 208 229 209 230 def wordcount(value): 210 231 "Returns the number of words" 211 232 return len(value.split()) 212 233 wordcount = stringfilter(wordcount) 234 wordcount.is_safe = False 213 235 214 236 def wordwrap(value, arg): 215 237 """ … … def wordwrap(value, arg): 220 242 from django.utils.text import wrap 221 243 return wrap(value, int(arg)) 222 244 wordwrap = stringfilter(wordwrap) 245 wordwrap.is_safe = True 223 246 224 247 def ljust(value, arg): 225 248 """ … … def ljust(value, arg): 229 252 """ 230 253 return value.ljust(int(arg)) 231 254 ljust = stringfilter(ljust) 255 ljust.is_safe = True 232 256 233 257 def rjust(value, arg): 234 258 """ … … def rjust(value, arg): 238 262 """ 239 263 return value.rjust(int(arg)) 240 264 rjust = stringfilter(rjust) 265 rjust.is_safe = True 241 266 242 267 def center(value, arg): 243 268 "Centers the value in a field of a given width" 244 269 return value.center(int(arg)) 245 270 center = stringfilter(center) 271 center.is_safe = True 246 272 247 273 def cut(value, arg): 248 274 "Removes all values of arg from the given string" 249 275 return value.replace(arg, '') 250 276 cut = stringfilter(cut) 277 cut.is_safe = False 251 278 252 279 ################### 253 280 # HTML STRINGS # 254 281 ################### 255 282 256 283 def escape(value): 257 "Escapes a string's HTML" 284 "Marks the value as a string that should not be auto-escaped." 285 from django.utils.safestring import mark_for_escaping 286 return mark_for_escaping(value) 287 escape = stringfilter(escape) 288 escape.is_safe = True 289 290 def force_escape(value): 291 """Escapes a string's HTML. This returns a new string containing the escaped 292 characters (as opposed to "escape", which marks the content for later 293 possible escaping).""" 258 294 from django.utils.html import escape 259 return escape(value)295 return mark_safe(escape(value)) 260 296 escape = stringfilter(escape) 297 force_escape.is_safe = True 261 298 262 def linebreaks(value ):299 def linebreaks(value, autoescape = None): 263 300 "Converts newlines into <p> and <br />s" 264 301 from django.utils.html import linebreaks 265 return linebreaks(value) 302 autoescape = autoescape and not isinstance(value, SafeData) 303 return mark_safe(linebreaks(value, autoescape)) 266 304 linebreaks = stringfilter(linebreaks) 305 linebreaks.is_safe = True 306 linebreaks.needs_autoescape = True 267 307 268 def linebreaksbr(value ):308 def linebreaksbr(value, autoescape = None): 269 309 "Converts newlines into <br />s" 270 return value.replace('\n', '<br />') 310 if autoescape and not isinstance(value, SafeData): 311 from django.utils.html import escape 312 data = escape(value) 313 else: 314 data = value 315 return mark_safe(data.replace('\n', '<br />')) 271 316 linebreaksbr = stringfilter(linebreaksbr) 317 linebreaksbr.is_safe = True 318 linebreaksbr.needs_autoescape = True 319 320 def safe(value): 321 "Marks the value as a string that should not be auto-escaped." 322 from django.utils.safestring import mark_safe 323 return mark_safe(value) 324 safe = stringfilter(safe) 325 safe.is_safe = True 272 326 273 327 def removetags(value, tags): 274 328 "Removes a space separated list of [X]HTML tags from the output" … … def removetags(value, tags): 280 334 value = endtag_re.sub('', value) 281 335 return value 282 336 removetags = stringfilter(removetags) 337 removetags.is_safe = True 283 338 284 339 def striptags(value): 285 340 "Strips all [X]HTML tags" 286 341 from django.utils.html import strip_tags 287 342 return strip_tags(value) 288 343 striptags = stringfilter(striptags) 344 striptags.is_safe = True 289 345 290 346 ################### 291 347 # LISTS # … … def dictsort(value, arg): 299 355 decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value] 300 356 decorated.sort() 301 357 return [item[1] for item in decorated] 358 dictsort.is_safe = False 302 359 303 360 def dictsortreversed(value, arg): 304 361 """ … … def dictsortreversed(value, arg): 309 366 decorated.sort() 310 367 decorated.reverse() 311 368 return [item[1] for item in decorated] 369 dictsortreversed.is_safe = False 312 370 313 371 def first(value): 314 372 "Returns the first item in a list" … … def first(value): 316 374 return value[0] 317 375 except IndexError: 318 376 return '' 377 first.is_safe = True 319 378 320 379 def join(value, arg): 321 380 "Joins a list with a string, like Python's ``str.join(list)``" 322 381 try: 323 returnarg.join(map(smart_string, value))382 data = arg.join(map(smart_string, value)) 324 383 except AttributeError: # fail silently but nicely 325 384 return value 385 safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), value, True) 386 if safe_args: 387 return mark_safe(data) 388 else: 389 return data 390 join.is_safe = True 326 391 327 392 def length(value): 328 393 "Returns the length of the value - useful for lists" 329 394 return len(value) 395 length.is_safe = False 330 396 331 397 def length_is(value, arg): 332 398 "Returns a boolean of whether the value's length is the argument" 333 399 return len(value) == int(arg) 400 length.is_safe = False 334 401 335 402 def random(value): 336 403 "Returns a random item from the list" 337 404 return random_module.choice(value) 405 length.is_safe = True 338 406 339 407 def slice_(value, arg): 340 408 """ … … def slice_(value, arg): 355 423 356 424 except (ValueError, TypeError): 357 425 return value # Fail silently. 426 slice_.is_safe = True 358 427 359 def unordered_list(value ):428 def unordered_list(value, autoescape = None): 360 429 """ 361 430 Recursively takes a self-nested list and returns an HTML unordered list -- 362 431 WITHOUT opening and closing <ul> tags. … … def unordered_list(value): 377 446 </ul> 378 447 </li> 379 448 """ 449 if autoescape: 450 from django.utils.html import conditional_escape 451 escaper = conditional_escape 452 else: 453 escaper = lambda x: x 454 380 455 def _helper(value, tabs): 381 456 indent = '\t' * tabs 382 457 if value[1]: 383 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,458 return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, escaper(value[0]), indent, 384 459 '\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent) 385 460 else: 386 return '%s<li>%s</li>' % (indent, value[0]) 387 return _helper(value, 1) 461 return '%s<li>%s</li>' % (indent, escaper(value[0])) 462 return mark_safe(_helper(value, 1)) 463 unordered_list.is_safe = True 464 unordered_list.needs_autoescape = True 388 465 389 466 ################### 390 467 # INTEGERS # … … ################### 393 470 def add(value, arg): 394 471 "Adds the arg to the value" 395 472 return int(value) + int(arg) 473 add.is_safe = False 396 474 397 475 def get_digit(value, arg): 398 476 """ … … def get_digit(value, arg): 412 490 return int(str(value)[-arg]) 413 491 except IndexError: 414 492 return 0 493 get_digit.is_safe = False 415 494 416 495 ################### 417 496 # DATES # … … def date(value, arg=None): 425 504 if arg is None: 426 505 arg = settings.DATE_FORMAT 427 506 return format(value, arg) 507 date.is_safe = False 428 508 429 509 def time(value, arg=None): 430 510 "Formats a time according to the given format" … … def time(value, arg=None): 434 514 if arg is None: 435 515 arg = settings.TIME_FORMAT 436 516 return time_format(value, arg) 517 time.is_safe = False 437 518 438 519 def timesince(value, arg=None): 439 520 'Formats a date as the time since that date (i.e. "4 days, 6 hours")' … … def timesince(value, arg=None): 443 524 if arg: 444 525 return timesince(arg, value) 445 526 return timesince(value) 527 timesince.is_safe = False 446 528 447 529 def timeuntil(value, arg=None): 448 530 'Formats a date as the time until that date (i.e. "4 days, 6 hours")' … … def timeuntil(value, arg=None): 453 535 if arg: 454 536 return timesince(arg, value) 455 537 return timesince(datetime.now(), value) 538 timeuntil.is_safe = False 456 539 457 540 ################### 458 541 # LOGIC # … … ################### 461 544 def default(value, arg): 462 545 "If value is unavailable, use given default" 463 546 return value or arg 547 default.is_safe = False 464 548 465 549 def default_if_none(value, arg): 466 550 "If value is None, use given default" 467 551 if value is None: 468 552 return arg 469 553 return value 554 default_if_none.is_safe = False 470 555 471 556 def divisibleby(value, arg): 472 557 "Returns true if the value is devisible by the argument" 473 558 return int(value) % int(arg) == 0 559 divisibleby.is_safe = False 474 560 475 561 def yesno(value, arg=None): 476 562 """ … … def yesno(value, arg=None): 501 587 if value: 502 588 return yes 503 589 return no 590 yesno.is_safe = False 504 591 505 592 ################### 506 593 # MISC # … … def filesizeformat(bytes): 523 610 if bytes < 1024 * 1024 * 1024: 524 611 return "%.1f MB" % (bytes / (1024 * 1024)) 525 612 return "%.1f GB" % (bytes / (1024 * 1024 * 1024)) 613 filesizeformat.is_safe = True 526 614 527 615 def pluralize(value, arg='s'): 528 616 """ … … def pluralize(value, arg='s'): 550 638 except TypeError: # len() of unsized object 551 639 pass 552 640 return singular_suffix 641 pluralize.is_safe = False 553 642 554 643 def phone2numeric(value): 555 644 "Takes a phone number and converts it in to its numerical equivalent" 556 645 from django.utils.text import phone2numeric 557 646 return phone2numeric(value) 647 phone2numeric.is_safe = True 558 648 559 649 def pprint(value): 560 650 "A wrapper around pprint.pprint -- for debugging, really" … … def pprint(value): 563 653 return pformat(value) 564 654 except Exception, e: 565 655 return "Error in formatting:%s" % e 656 pprint.is_safe = True 566 657 567 658 # Syntax: register.filter(name of filter, callback) 568 659 register.filter(add) … … register.filter(filesizeformat) 581 672 register.filter(first) 582 673 register.filter(fix_ampersands) 583 674 register.filter(floatformat) 675 register.filter(force_escape) 584 676 register.filter(get_digit) 585 677 register.filter(join) 586 678 register.filter(length) … … register.filter(pprint) 597 689 register.filter(removetags) 598 690 register.filter(random) 599 691 register.filter(rjust) 692 register.filter(safe) 600 693 register.filter('slice', slice_) 601 694 register.filter(slugify) 602 695 register.filter(stringformat) -
django/template/defaulttags.py
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py index ed870047b1305916e4f546c98c6b95cad5b1cce2..152aeef4b2ca62285a9e42937cf3a28b70132332 100644
a b from django.template import Node, NodeLi 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): 355 375 return str(int(round(ratio))) 356 376 357 377 #@register.tag 378 def autoescape(parser, token): 379 """ 380 Force autoescape behaviour for this block. 381 """ 382 nodelist = parser.parse(('endautoescape',)) 383 parser.delete_first_token() 384 return AutoEscapeControlNode(True, nodelist) 385 autoescape = register.tag(autoescape) 386 387 #@register.tag 358 388 def comment(parser, token): 359 389 """ 360 390 Ignore everything between ``{% comment %}`` and ``{% endcomment %}`` … … def do_filter(parser, token): 457 487 458 488 Sample usage:: 459 489 460 {% filter escape|lower %}490 {% filter force_escape|lower %} 461 491 This text will be HTML-escaped, and will appear in lowercase. 462 492 {% endfilter %} 463 493 """ 464 494 _, rest = token.contents.split(None, 1) 465 495 filter_expr = parser.compile_filter("var|%s" % (rest)) 496 for func, unused in filter_expr.filters: 497 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 498 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 466 499 nodelist = parser.parse(('endfilter',)) 467 500 parser.delete_first_token() 468 501 return FilterNode(filter_expr, nodelist) … … def ifchanged(parser, token): 704 737 ifchanged = register.tag(ifchanged) 705 738 706 739 #@register.tag 740 def noautoescape(parser, token): 741 """ 742 Force autoescape behaviour to be disabled for this block. 743 """ 744 nodelist = parser.parse(('endnoautoescape',)) 745 parser.delete_first_token() 746 return AutoEscapeControlNode(False, nodelist) 747 autoescape = register.tag(noautoescape) 748 749 #@register.tag 707 750 def ssi(parser, token): 708 751 """ 709 752 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..8cac661595017a5dc92a4b2dfb0e14181c1a287c
- + 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 d53270ac168852fe0f9336a7c4e0016c93d0526e..37f6f1c52eab98dd06aa9693b919ba0ad8cacac0 100644
a b it also defines the content that fills t 270 270 two similarly-named ``{% block %}`` tags in a template, that template's parent 271 271 wouldn't know which one of the blocks' content to use. 272 272 273 Automatic HTML escaping 274 ======================= 275 276 A very real problem when creating HTML (and other) output using templates and 277 variable substitution is the possibility of accidently inserting some variable 278 value that affects the resulting HTML. For example, a template fragment like 279 280 :: 281 282 Hello, {{ name }}. 283 284 seems like a harmless way to display the user's name. However, if you are 285 displaying data that the user entered directly and they entered their name as 286 287 :: 288 289 <script>alert('hello')</script> 290 291 this would always display a Javascript alert box whenever the page was loaded. 292 Similarly, if you were displaying some data generated by another process and 293 it contained a '<' symbol, you couldn't just dump this straight into your 294 HTML, because it would be treated as the start of an element. The effects of 295 these sorts of problems can vary from merely annoying to allowing exploits via 296 `Cross Site Scripting`_ (XSS) attacks. 297 298 .. _Cross Site Scripting: http://en.wikipedia.org/wiki/Cross-site_scripting 299 300 In order to provide some protection against these problems, Django provides an 301 auto-escaping template tag. Inside this tag, any data that comes from template 302 variables is examined to see if it contains one of the five HTML characters 303 (<, >, ', " and &) that often need escaping and those characters are converted 304 to their respective HTML entities. 305 306 Because some variables will contain data that is *intended* to be rendered 307 as HTML, template tag and filter writers can mark their output strings as 308 requiring no further escaping. For example, the ``unordered_list`` filter is 309 designed to return raw HTML and we want the template processor to simply 310 display the results as returned, without applying any escaping. That is taken 311 care of by the filter. The template author need do nothing special in that 312 case. 313 314 By default, auto-escaping is not in effect. To enable it inside your template, 315 wrap the affected content in the ``autoescape`` tag, like so:: 316 317 {% autoescape %} 318 Hello {{ name }} 319 {% endautoescape %} 320 321 Since the auto-escaping tag passes its effect onto templates that extend the 322 current one as well as templates included via the ``include`` tag (just like 323 all block tags), if you wrap your main HTML content in an ``autoescape`` tag, 324 you will have automatic escaping applied to all of your content. 325 326 At times, you might want to disable auto-escaping when it would otherwise be 327 in effect. You can do this with the ``noautoescape`` tag. For example:: 328 329 {% autoescape %} 330 Hello {{ name }} 331 332 {% noautoescape %} 333 This will not be auto-escaped: {{ data }}. 334 335 Nor this: {{ other_data }} 336 {% endnoautoescape %} 337 {% endautoescape %} 338 339 For individual variables, the ``safe`` filter can also be used. 340 341 Generally, you will not need to worry about auto-escaping very much. Enable it 342 in your base template once you are entering the main HTML region and then 343 write your templates normally. The view developers and custom filter authors 344 need to think about when their data should not be escaped and mark it 345 appropriately. They are in a better position to know when that should happen 346 than the template author, so it is their responsibility. By default, when 347 auto-escaping is enabled, all output is escaped unless the template processor 348 is explicitly told otherwise. 349 273 350 Using the built-in reference 274 351 ============================ 275 352 … … available, and what they do. 345 422 Built-in tag reference 346 423 ---------------------- 347 424 425 autoescape 426 ~~~~~~~~~~ 427 428 All variables that are output inside this tag have HTML escaping applied to 429 them, as if they each had the ``escape`` filter attached to them. 430 431 The only exceptions are variables that are already marked as 'safe' from 432 escaping, either by the code that populated the variable, or because it has 433 the ``safe`` filter applied. 434 348 435 block 349 436 ~~~~~ 350 437 … … just like in variable syntax. 412 499 413 500 Sample usage:: 414 501 415 {% filter escape|lower %}502 {% filter force_escape|lower %} 416 503 This text will be HTML-escaped, and will appear in all lowercase. 417 504 {% endfilter %} 418 505 … … Load a custom template tag set. 627 714 628 715 See `Custom tag and filter libraries`_ for more information. 629 716 717 noautoescape 718 ~~~~~~~~~~~~ 719 720 Disable the effects of the ``autoescape`` tag (if it is in effect). 721 630 722 now 631 723 ~~~ 632 724 … … Escapes a string's HTML. Specifically, i 950 1042 * ``'"'`` (double quote) to ``'"'`` 951 1043 * ``"'"`` (single quote) to ``'''`` 952 1044 1045 The escaping is only applied when the string is output, so it does not matter 1046 where in a chained sequence of filters you put ``escape``: it will always be 1047 applied as though it were the last filter. If you want escaping to be applied 1048 immediately, use the ``force_escape`` filter. 1049 953 1050 filesizeformat 954 1051 ~~~~~~~~~~~~~~ 955 1052 … … For example: 994 1091 Using ``floatformat`` with no argument is equivalent to using ``floatformat`` with 995 1092 an argument of ``-1``. 996 1093 1094 force_escape 1095 ~~~~~~~~~~~~ 1096 1097 **New in Django development version** 1098 1099 Applies HTML escaping to a string (see the ``escape`` filter for details). 1100 This filter is applied immediately and returns a new, escaped string. This is 1101 useful in the typically rare cases where you need multiple escaping or want to 1102 apply other filters to the escaped results. Normally, you want to use the 1103 ``escape`` filter. 1104 997 1105 get_digit 998 1106 ~~~~~~~~~ 999 1107 … … Right-aligns the value in a field of a g 1105 1213 1106 1214 **Argument:** field size 1107 1215 1216 safe 1217 ~~~~ 1218 1219 Marks a string as not requiring further HTML escaping prior to output. This is 1220 only useful inside an ``autoescape`` block, when the output would otherwise be 1221 automatically escaped. Outside of an ``autoescape`` block, this filter has no 1222 effect. 1223 1108 1224 slice 1109 1225 ~~~~~ 1110 1226 -
docs/templates_python.txt
diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 5dd8e4fde06ea3235ff2fa7991d6daaa053f2c8a..3c4d067ce521706d51c5de1feb3021c4b1a9385d 100644
a b an object to it's string value before be 666 666 def lower(value): 667 667 return value.lower() 668 668 669 Filters and auto-escaping 670 ~~~~~~~~~~~~~~~~~~~~~~~~~ 671 672 When you are writing a custom filter, you need to give some thought to how 673 this filter will work when rendered in an auto-escaping environment (inside 674 an ``autoescape`` template tag block). First, you should realise that there 675 are three types of strings that can be passed around inside the template code: 676 677 * raw strings are the native Python ``str`` (or ``unicode``) types. On 678 output, they are escaped if they are inside an ``autoescape`` block. 679 * "safe" strings are strings that are safe from further escaping at output 680 time. Any necessary escaping has already been done. They are commonly used 681 for output that contains raw HTML that is intended to be intrepreted on the 682 client side. 683 684 Internally, these strings are of type ``SafeString`` or ``SafeUnicode``, 685 although they share a common base class in ``SafeData``, so you can test 686 for them using code like:: 687 688 if isinstance(value, SafeData): 689 # Do something with the "safe" string. 690 691 * strings which are marked as "need escaping" are *always* escaped on 692 output, regardless of whether they are in an ``autoescape`` block or not. 693 These strings are only escaped once, however, even if used inside an 694 ``autoescaep`` block. This type of string is internally represented by the 695 types ``EscapeString`` and ``EscapeUnicode``. You will not normally need to 696 worry about these; they exist only for the implementation of the ``escape`` 697 filter. 698 699 Inside your filter, you will need to think about three areas in order to be 700 auto-escaping compliant: 701 702 1. If your filter returns a string that is ready for direct output (it should 703 be considered a "safe" string), you should call 704 ``django.utils.safestring.mark_safe()`` on the result prior to returning. 705 This will turn the result into the appropriate ``SafeData`` type. 706 707 2. If your filter is given a "safe" string, is it guaranteed to return a 708 "safe" string? If so, set the ``is_safe`` attribute on the function to be 709 ``True``. For example, a filter that replaced all numbers with the number 710 spelt out in words is going to be safe-string-preserving, since it cannot 711 introduce any of the five dangerous characters: <, >, ", ' or &. So we can 712 write:: 713 714 @register.filter 715 def convert_to_words(value): 716 # ... implementation here ... 717 return result 718 719 convert_to_words.is_safe = True 720 721 Note that this filter does not return a universally safe result (it does not 722 return ``mark_safe(result)``) because if it is handed a raw string such as 723 '<a>', this will need further escaping in an auto-escape environment. The 724 ``is_safe`` attribute only talks about the safeness of the result when a safe 725 string is passed in to the filter. 726 727 3. Will your filter behave differently depending upon whether auto-escaping 728 is currently in effect or not? For example, the ``ordered_list`` filter that 729 ships with Django needs to know whether to escape its content or not. It will 730 always return a safe string, since it returns raw HTML, so we cannot apply 731 escaping to the result -- it needs to be done in-situ. 732 733 For these cases, the filter function needs to be told what the current 734 auto-escaping setting is. Set the ``needs_autoescape`` attribute on the 735 filter to ``True`` and have your function take an extra argument called 736 ``autoescape`` with a default value of ``None``. When the filter is called, 737 the ``autoescape`` keyword argument will be ``True`` if auto-escaping is in 738 effect. For example, the ``unordered_list`` filter is written as:: 739 740 def unordered_list(value, autoescape = None): 741 # ... lots of code here ... 742 743 return mark_safe(...) 744 745 unordered_list.is_safe = True 746 unordered_list.needs_autoescape = True 747 748 By default, both the ``is_safe`` and ``needs_autoescape`` attributes are 749 ``False``. You do not need to specify them if ``False`` is an acceptable 750 value. 751 752 As a matter of convention, we leave ``is_safe`` as ``False`` for filters that 753 do not accept string inputs (they might take a number as an input, for 754 example) or for those that return a non-string (e.g. the ``length`` filter). 755 However, not following this convention will not cause any harm or make your 756 results any more vulnerable to cross-site scripting problems. 757 669 758 Writing custom template tags 670 759 ---------------------------- 671 760 … … Ultimately, this decoupling of compilati 784 873 efficient template system, because a template can render multiple context 785 874 without having to be parsed multiple times. 786 875 876 Auto-escaping considerations 877 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 878 879 The output from template tags is not automatically run through the 880 auto-escaping filters if used inside an ``autoescape`` tag. However, there are 881 still a couple of things you should keep in mind when writing a template tag: 882 883 If the ``render()`` function of your template stores the result in a context 884 variable (rather than returning the result in a string), it should take care 885 to call ``mark_safe()`` if appropriate. When the variable is ultimately 886 rendered, it will be affected by the auto-escape setting in effect at the 887 time, so content that should be safe from further escaping needs to be marked 888 as such. 889 890 Also, if your template tag creates a new context for performing some 891 sub-rendering, you should be careful to set the auto-escape variable to the 892 current context's value. For example:: 893 894 def render(self, context): 895 # ... 896 new_context = Context({'var': obj}) 897 new_context.autoescape = context.autoescape 898 # ... Do something with new_context ... 899 900 This is not a very common situation, but it is sometimes useful (see 901 ``django.templates.defaulttags.FilterNode.render()`` for an example). 902 787 903 Registering the tag 788 904 ~~~~~~~~~~~~~~~~~~~ 789 905 -
new file tests/regressiontests/autoescape/tests.py
diff --git a/tests/regressiontests/autoescape/__init__.py b/tests/regressiontests/autoescape/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/regressiontests/autoescape/models.py b/tests/regressiontests/autoescape/models.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/tests/regressiontests/autoescape/tests.py b/tests/regressiontests/autoescape/tests.py new file mode 100644 index 0000000000000000000000000000000000000000..22b89e28e6bf032aa6c287f1861adedb3023bb47
- + 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 c850806052a430694ee047936b601931811c24e2..afe35394beba7ba0c1c1658ec83c3f483c560843 100644
a b u'\xcb' 157 157 >>> cut('a string to be mangled', 'strings') 158 158 'a string to be mangled' 159 159 160 >>> escape('<some html & special characters > here')160 >>> force_escape('<some html & special characters > here') 161 161 '<some html & special characters > here' 162 162 163 163 >>> linebreaks('line 1') -
tests/regressiontests/templates/tests.py
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 375fd361961514278ab715823dbda41844b4b7e4..7843ba369898ffc7f00c2a2238fe9736d1567ec1 100644
a b class UnicodeInStrClass: 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): 695 759 'url06' : ('{% url regressiontests.templates.views.client no_such_param="value" %}', {}, ''), 696 760 } 697 761 698 # Register our custom template loader.699 def test_template_loader(template_name, template_dirs=None):700 "A custom template loader that loads the unit-test templates."701 try:702 return (TEMPLATE_TESTS[template_name][0] , "test:%s" % template_name)703 except KeyError:704 raise template.TemplateDoesNotExist, template_name705 706 old_template_loaders = loader.template_source_loaders707 loader.template_source_loaders = [test_template_loader]708 709 failures = []710 tests = TEMPLATE_TESTS.items()711 tests.sort()712 713 # Turn TEMPLATE_DEBUG off, because tests assume that.714 old_td, settings.TEMPLATE_DEBUG = settings.TEMPLATE_DEBUG, False715 716 # Set TEMPLATE_STRING_IF_INVALID to a known string717 old_invalid = settings.TEMPLATE_STRING_IF_INVALID718 719 for name, vals in tests:720 install()721 722 if isinstance(vals[2], tuple):723 normal_string_result = vals[2][0]724 invalid_string_result = vals[2][1]725 else:726 normal_string_result = vals[2]727 invalid_string_result = vals[2]728 729 if 'LANGUAGE_CODE' in vals[1]:730 activate(vals[1]['LANGUAGE_CODE'])731 else:732 activate('en-us')733 734 for invalid_str, result in [('', normal_string_result),735 ('INVALID', invalid_string_result)]:736 settings.TEMPLATE_STRING_IF_INVALID = invalid_str737 try:738 output = loader.get_template(name).render(template.Context(vals[1]))739 except Exception, e:740 if e.__class__ != result:741 failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Got %s, exception: %s" % (invalid_str, name, e.__class__, e))742 continue743 if output != result:744 failures.append("Template test (TEMPLATE_STRING_IF_INVALID='%s'): %s -- FAILED. Expected %r, got %r" % (invalid_str, name, result, output))745 746 if 'LANGUAGE_CODE' in vals[1]:747 deactivate()748 749 loader.template_source_loaders = old_template_loaders750 deactivate()751 settings.TEMPLATE_DEBUG = old_td752 settings.TEMPLATE_STRING_IF_INVALID = old_invalid753 754 self.assertEqual(failures, [], '\n'.join(failures))755 756 762 if __name__ == "__main__": 757 763 unittest.main()