Ticket #6587: template_libs_t6587_r12288_v1.diff
File template_libs_t6587_r12288_v1.diff, 145.9 KB (added by , 15 years ago) |
---|
-
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index 4c386be..f16f739 100644
a b u'<html><h1>Hello</h1></html>' 49 49 u'<html></html>' 50 50 """ 51 51 import re 52 import imp 52 53 from inspect import getargspec 53 54 54 55 from django.conf import settings 55 56 from django.template.context import Context, RequestContext, ContextPopException 57 from django.templatetags import get_templatetags_modules 56 58 from django.utils.importlib import import_module 57 59 from django.utils.itercompat import is_iterable 58 60 from django.utils.functional import curry, Promise … … class Library(object): 965 967 self.tag(getattr(func, "_decorated_function", func).__name__, compile_func) 966 968 return func 967 969 return dec 968 969 def get_library(module_name): 970 lib = libraries.get(module_name, None) 970 def import_library(taglib_module): 971 components = taglib_module.split('.') 972 parent_components = components[:-1] 973 parent_module = '.'.join(parent_components) 974 try: 975 mod = __import__(parent_module, {}, {}, [parent_components[-1]]) 976 imp.find_module(components[-1], mod.__path__) 977 except ImportError: 978 return None 979 mod = __import__(taglib_module, {}, {}, [components[-1]]) 980 try: 981 return mod.register 982 except AttributeError: 983 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % taglib_module) 984 985 def get_library(library_name): 986 lib = libraries.get(library_name, None) 971 987 if not lib: 972 try: 973 mod = import_module(module_name) 974 except ImportError, e: 975 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) 976 try: 977 lib = mod.register 978 libraries[module_name] = lib 979 except AttributeError: 980 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 988 """ 989 If library is not already loaded loop over all templatetags modules to locate it. 990 991 {% load somelib %} and {% load someotherlib %} loops twice. 992 993 Subsequent loads eg. {% load somelib %} in the same thread will grab the cached 994 module from libraries. 995 """ 996 templatetags_modules = get_templatetags_modules() 997 tried_modules = [] 998 for module in templatetags_modules: 999 taglib_module = str('%s.%s' % (module, library_name)) 1000 tried_modules.append(taglib_module) 1001 lib = import_library(taglib_module) 1002 if lib: 1003 libraries[library_name] = lib 1004 break 1005 if not lib: 1006 raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) 981 1007 return lib 982 1008 983 def add_to_builtins(module _name):984 builtins.append( get_library(module_name))1009 def add_to_builtins(module): 1010 builtins.append(import_library(module)) 985 1011 986 add_to_builtins('django.template .defaulttags')987 add_to_builtins('django.template .defaultfilters')1012 add_to_builtins('django.templatetags.defaulttags') 1013 add_to_builtins('django.templatetags.defaultfilters') -
deleted file django/template/defaultfilters.py
diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py deleted file mode 100644 index 0ba16bc..0000000
+ - 1 """Default variable filters."""2 3 import re4 5 try:6 from decimal import Decimal, InvalidOperation, ROUND_HALF_UP7 except ImportError:8 from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP9 10 import random as random_module11 try:12 from functools import wraps13 except ImportError:14 from django.utils.functional import wraps # Python 2.3, 2.4 fallback.15 16 from django.template import Variable, Library17 from django.conf import settings18 from django.utils import formats19 from django.utils.translation import ugettext, ungettext20 from django.utils.encoding import force_unicode, iri_to_uri21 from django.utils.safestring import mark_safe, SafeData22 23 register = Library()24 25 #######################26 # STRING DECORATOR #27 #######################28 29 def stringfilter(func):30 """31 Decorator for filters which should only receive unicode objects. The object32 passed as the first positional argument will be converted to a unicode33 object.34 """35 def _dec(*args, **kwargs):36 if args:37 args = list(args)38 args[0] = force_unicode(args[0])39 if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):40 return mark_safe(func(*args, **kwargs))41 return func(*args, **kwargs)42 43 # Include a reference to the real function (used to check original44 # arguments by the template parser).45 _dec._decorated_function = getattr(func, '_decorated_function', func)46 for attr in ('is_safe', 'needs_autoescape'):47 if hasattr(func, attr):48 setattr(_dec, attr, getattr(func, attr))49 return wraps(func)(_dec)50 51 ###################52 # STRINGS #53 ###################54 55 def addslashes(value):56 """57 Adds slashes before quotes. Useful for escaping strings in CSV, for58 example. Less useful for escaping JavaScript; use the ``escapejs``59 filter instead.60 """61 return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")62 addslashes.is_safe = True63 addslashes = stringfilter(addslashes)64 65 def capfirst(value):66 """Capitalizes the first character of the value."""67 return value and value[0].upper() + value[1:]68 capfirst.is_safe=True69 capfirst = stringfilter(capfirst)70 71 _base_js_escapes = (72 ('\\', r'\x5C'),73 ('\'', r'\x27'),74 ('"', r'\x22'),75 ('>', r'\x3E'),76 ('<', r'\x3C'),77 ('&', r'\x26'),78 ('=', r'\x3D'),79 ('-', r'\x2D'),80 (';', r'\x3B'),81 (u'\u2028', r'\u2028'),82 (u'\u2029', r'\u2029')83 )84 85 # Escape every ASCII character with a value less than 32.86 _js_escapes = (_base_js_escapes +87 tuple([('%c' % z, '\\x%02X' % z) for z in range(32)]))88 89 def escapejs(value):90 """Hex encodes characters for use in JavaScript strings."""91 for bad, good in _js_escapes:92 value = value.replace(bad, good)93 return value94 escapejs = stringfilter(escapejs)95 96 def fix_ampersands(value):97 """Replaces ampersands with ``&`` entities."""98 from django.utils.html import fix_ampersands99 return fix_ampersands(value)100 fix_ampersands.is_safe=True101 fix_ampersands = stringfilter(fix_ampersands)102 103 # Values for testing floatformat input against infinity and NaN representations,104 # which differ across platforms and Python versions. Some (i.e. old Windows105 # ones) are not recognized by Decimal but we want to return them unchanged vs.106 # returning an empty string as we do for completley invalid input. Note these107 # need to be built up from values that are not inf/nan, since inf/nan values do108 # not reload properly from .pyc files on Windows prior to some level of Python 2.5109 # (see Python Issue757815 and Issue1080440).110 pos_inf = 1e200 * 1e200111 neg_inf = -1e200 * 1e200112 nan = (1e200 * 1e200) / (1e200 * 1e200)113 special_floats = [str(pos_inf), str(neg_inf), str(nan)]114 115 def floatformat(text, arg=-1):116 """117 Displays a float to a specified number of decimal places.118 119 If called without an argument, it displays the floating point number with120 one decimal place -- but only if there's a decimal place to be displayed:121 122 * num1 = 34.23234123 * num2 = 34.00000124 * num3 = 34.26000125 * {{ num1|floatformat }} displays "34.2"126 * {{ num2|floatformat }} displays "34"127 * {{ num3|floatformat }} displays "34.3"128 129 If arg is positive, it will always display exactly arg number of decimal130 places:131 132 * {{ num1|floatformat:3 }} displays "34.232"133 * {{ num2|floatformat:3 }} displays "34.000"134 * {{ num3|floatformat:3 }} displays "34.260"135 136 If arg is negative, it will display arg number of decimal places -- but137 only if there are places to be displayed:138 139 * {{ num1|floatformat:"-3" }} displays "34.232"140 * {{ num2|floatformat:"-3" }} displays "34"141 * {{ num3|floatformat:"-3" }} displays "34.260"142 143 If the input float is infinity or NaN, the (platform-dependent) string144 representation of that value will be displayed.145 """146 147 try:148 input_val = force_unicode(text)149 d = Decimal(input_val)150 except UnicodeEncodeError:151 return u''152 except InvalidOperation:153 if input_val in special_floats:154 return input_val155 try:156 d = Decimal(force_unicode(float(text)))157 except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError):158 return u''159 try:160 p = int(arg)161 except ValueError:162 return input_val163 164 try:165 m = int(d) - d166 except (ValueError, OverflowError, InvalidOperation):167 return input_val168 169 if not m and p < 0:170 return mark_safe(formats.number_format(u'%d' % (int(d)), 0))171 172 if p == 0:173 exp = Decimal(1)174 else:175 exp = Decimal('1.0') / (Decimal(10) ** abs(p))176 try:177 return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p)))178 except InvalidOperation:179 return input_val180 floatformat.is_safe = True181 182 def iriencode(value):183 """Escapes an IRI value for use in a URL."""184 return force_unicode(iri_to_uri(value))185 iriencode.is_safe = True186 iriencode = stringfilter(iriencode)187 188 def linenumbers(value, autoescape=None):189 """Displays text with line numbers."""190 from django.utils.html import escape191 lines = value.split(u'\n')192 # Find the maximum width of the line count, for use with zero padding193 # string format command194 width = unicode(len(unicode(len(lines))))195 if not autoescape or isinstance(value, SafeData):196 for i, line in enumerate(lines):197 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)198 else:199 for i, line in enumerate(lines):200 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))201 return mark_safe(u'\n'.join(lines))202 linenumbers.is_safe = True203 linenumbers.needs_autoescape = True204 linenumbers = stringfilter(linenumbers)205 206 def lower(value):207 """Converts a string into all lowercase."""208 return value.lower()209 lower.is_safe = True210 lower = stringfilter(lower)211 212 def make_list(value):213 """214 Returns the value turned into a list.215 216 For an integer, it's a list of digits.217 For a string, it's a list of characters.218 """219 return list(value)220 make_list.is_safe = False221 make_list = stringfilter(make_list)222 223 def slugify(value):224 """225 Normalizes string, converts to lowercase, removes non-alpha characters,226 and converts spaces to hyphens.227 """228 import unicodedata229 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')230 value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())231 return mark_safe(re.sub('[-\s]+', '-', value))232 slugify.is_safe = True233 slugify = stringfilter(slugify)234 235 def stringformat(value, arg):236 """237 Formats the variable according to the arg, a string formatting specifier.238 239 This specifier uses Python string formating syntax, with the exception that240 the leading "%" is dropped.241 242 See http://docs.python.org/lib/typesseq-strings.html for documentation243 of Python string formatting244 """245 try:246 return (u"%" + unicode(arg)) % value247 except (ValueError, TypeError):248 return u""249 stringformat.is_safe = True250 251 def title(value):252 """Converts a string into titlecase."""253 t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())254 return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)255 title.is_safe = True256 title = stringfilter(title)257 258 def truncatewords(value, arg):259 """260 Truncates a string after a certain number of words.261 262 Argument: Number of words to truncate after.263 """264 from django.utils.text import truncate_words265 try:266 length = int(arg)267 except ValueError: # Invalid literal for int().268 return value # Fail silently.269 return truncate_words(value, length)270 truncatewords.is_safe = True271 truncatewords = stringfilter(truncatewords)272 273 def truncatewords_html(value, arg):274 """275 Truncates HTML after a certain number of words.276 277 Argument: Number of words to truncate after.278 """279 from django.utils.text import truncate_html_words280 try:281 length = int(arg)282 except ValueError: # invalid literal for int()283 return value # Fail silently.284 return truncate_html_words(value, length)285 truncatewords_html.is_safe = True286 truncatewords_html = stringfilter(truncatewords_html)287 288 def upper(value):289 """Converts a string into all uppercase."""290 return value.upper()291 upper.is_safe = False292 upper = stringfilter(upper)293 294 def urlencode(value):295 """Escapes a value for use in a URL."""296 from django.utils.http import urlquote297 return urlquote(value)298 urlencode.is_safe = False299 urlencode = stringfilter(urlencode)300 301 def urlize(value, autoescape=None):302 """Converts URLs in plain text into clickable links."""303 from django.utils.html import urlize304 return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))305 urlize.is_safe=True306 urlize.needs_autoescape = True307 urlize = stringfilter(urlize)308 309 def urlizetrunc(value, limit, autoescape=None):310 """311 Converts URLs into clickable links, truncating URLs to the given character312 limit, and adding 'rel=nofollow' attribute to discourage spamming.313 314 Argument: Length to truncate URLs to.315 """316 from django.utils.html import urlize317 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,318 autoescape=autoescape))319 urlizetrunc.is_safe = True320 urlizetrunc.needs_autoescape = True321 urlizetrunc = stringfilter(urlizetrunc)322 323 def wordcount(value):324 """Returns the number of words."""325 return len(value.split())326 wordcount.is_safe = False327 wordcount = stringfilter(wordcount)328 329 def wordwrap(value, arg):330 """331 Wraps words at specified line length.332 333 Argument: number of characters to wrap the text at.334 """335 from django.utils.text import wrap336 return wrap(value, int(arg))337 wordwrap.is_safe = True338 wordwrap = stringfilter(wordwrap)339 340 def ljust(value, arg):341 """342 Left-aligns the value in a field of a given width.343 344 Argument: field size.345 """346 return value.ljust(int(arg))347 ljust.is_safe = True348 ljust = stringfilter(ljust)349 350 def rjust(value, arg):351 """352 Right-aligns the value in a field of a given width.353 354 Argument: field size.355 """356 return value.rjust(int(arg))357 rjust.is_safe = True358 rjust = stringfilter(rjust)359 360 def center(value, arg):361 """Centers the value in a field of a given width."""362 return value.center(int(arg))363 center.is_safe = True364 center = stringfilter(center)365 366 def cut(value, arg):367 """368 Removes all values of arg from the given string.369 """370 safe = isinstance(value, SafeData)371 value = value.replace(arg, u'')372 if safe and arg != ';':373 return mark_safe(value)374 return value375 cut = stringfilter(cut)376 377 ###################378 # HTML STRINGS #379 ###################380 381 def escape(value):382 """383 Marks the value as a string that should not be auto-escaped.384 """385 from django.utils.safestring import mark_for_escaping386 return mark_for_escaping(value)387 escape.is_safe = True388 escape = stringfilter(escape)389 390 def force_escape(value):391 """392 Escapes a string's HTML. This returns a new string containing the escaped393 characters (as opposed to "escape", which marks the content for later394 possible escaping).395 """396 from django.utils.html import escape397 return mark_safe(escape(value))398 force_escape = stringfilter(force_escape)399 force_escape.is_safe = True400 401 def linebreaks(value, autoescape=None):402 """403 Replaces line breaks in plain text with appropriate HTML; a single404 newline becomes an HTML line break (``<br />``) and a new line405 followed by a blank line becomes a paragraph break (``</p>``).406 """407 from django.utils.html import linebreaks408 autoescape = autoescape and not isinstance(value, SafeData)409 return mark_safe(linebreaks(value, autoescape))410 linebreaks.is_safe = True411 linebreaks.needs_autoescape = True412 linebreaks = stringfilter(linebreaks)413 414 def linebreaksbr(value, autoescape=None):415 """416 Converts all newlines in a piece of plain text to HTML line breaks417 (``<br />``).418 """419 if autoescape and not isinstance(value, SafeData):420 from django.utils.html import escape421 value = escape(value)422 return mark_safe(value.replace('\n', '<br />'))423 linebreaksbr.is_safe = True424 linebreaksbr.needs_autoescape = True425 linebreaksbr = stringfilter(linebreaksbr)426 427 def safe(value):428 """429 Marks the value as a string that should not be auto-escaped.430 """431 return mark_safe(value)432 safe.is_safe = True433 safe = stringfilter(safe)434 435 def safeseq(value):436 """437 A "safe" filter for sequences. Marks each element in the sequence,438 individually, as safe, after converting them to unicode. Returns a list439 with the results.440 """441 return [mark_safe(force_unicode(obj)) for obj in value]442 safeseq.is_safe = True443 444 def removetags(value, tags):445 """Removes a space separated list of [X]HTML tags from the output."""446 tags = [re.escape(tag) for tag in tags.split()]447 tags_re = u'(%s)' % u'|'.join(tags)448 starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)449 endtag_re = re.compile(u'</%s>' % tags_re)450 value = starttag_re.sub(u'', value)451 value = endtag_re.sub(u'', value)452 return value453 removetags.is_safe = True454 removetags = stringfilter(removetags)455 456 def striptags(value):457 """Strips all [X]HTML tags."""458 from django.utils.html import strip_tags459 return strip_tags(value)460 striptags.is_safe = True461 striptags = stringfilter(striptags)462 463 ###################464 # LISTS #465 ###################466 467 def dictsort(value, arg):468 """469 Takes a list of dicts, returns that list sorted by the property given in470 the argument.471 """472 var_resolve = Variable(arg).resolve473 decorated = [(var_resolve(item), item) for item in value]474 decorated.sort()475 return [item[1] for item in decorated]476 dictsort.is_safe = False477 478 def dictsortreversed(value, arg):479 """480 Takes a list of dicts, returns that list sorted in reverse order by the481 property given in the argument.482 """483 var_resolve = Variable(arg).resolve484 decorated = [(var_resolve(item), item) for item in value]485 decorated.sort()486 decorated.reverse()487 return [item[1] for item in decorated]488 dictsortreversed.is_safe = False489 490 def first(value):491 """Returns the first item in a list."""492 try:493 return value[0]494 except IndexError:495 return u''496 first.is_safe = False497 498 def join(value, arg, autoescape=None):499 """500 Joins a list with a string, like Python's ``str.join(list)``.501 """502 value = map(force_unicode, value)503 if autoescape:504 from django.utils.html import conditional_escape505 value = [conditional_escape(v) for v in value]506 try:507 data = arg.join(value)508 except AttributeError: # fail silently but nicely509 return value510 return mark_safe(data)511 join.is_safe = True512 join.needs_autoescape = True513 514 def last(value):515 "Returns the last item in a list"516 try:517 return value[-1]518 except IndexError:519 return u''520 last.is_safe = True521 522 def length(value):523 """Returns the length of the value - useful for lists."""524 try:525 return len(value)526 except (ValueError, TypeError):527 return ''528 length.is_safe = True529 530 def length_is(value, arg):531 """Returns a boolean of whether the value's length is the argument."""532 try:533 return len(value) == int(arg)534 except (ValueError, TypeError):535 return ''536 length_is.is_safe = False537 538 def random(value):539 """Returns a random item from the list."""540 return random_module.choice(value)541 random.is_safe = True542 543 def slice_(value, arg):544 """545 Returns a slice of the list.546 547 Uses the same syntax as Python's list slicing; see548 http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice549 for an introduction.550 """551 try:552 bits = []553 for x in arg.split(u':'):554 if len(x) == 0:555 bits.append(None)556 else:557 bits.append(int(x))558 return value[slice(*bits)]559 560 except (ValueError, TypeError):561 return value # Fail silently.562 slice_.is_safe = True563 564 def unordered_list(value, autoescape=None):565 """566 Recursively takes a self-nested list and returns an HTML unordered list --567 WITHOUT opening and closing <ul> tags.568 569 The list is assumed to be in the proper format. For example, if ``var``570 contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,571 then ``{{ var|unordered_list }}`` would return::572 573 <li>States574 <ul>575 <li>Kansas576 <ul>577 <li>Lawrence</li>578 <li>Topeka</li>579 </ul>580 </li>581 <li>Illinois</li>582 </ul>583 </li>584 """585 if autoescape:586 from django.utils.html import conditional_escape587 escaper = conditional_escape588 else:589 escaper = lambda x: x590 def convert_old_style_list(list_):591 """592 Converts old style lists to the new easier to understand format.593 594 The old list format looked like:595 ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]596 597 And it is converted to:598 ['Item 1', ['Item 1.1', 'Item 1.2]]599 """600 if not isinstance(list_, (tuple, list)) or len(list_) != 2:601 return list_, False602 first_item, second_item = list_603 if second_item == []:604 return [first_item], True605 old_style_list = True606 new_second_item = []607 for sublist in second_item:608 item, old_style_list = convert_old_style_list(sublist)609 if not old_style_list:610 break611 new_second_item.extend(item)612 if old_style_list:613 second_item = new_second_item614 return [first_item, second_item], old_style_list615 def _helper(list_, tabs=1):616 indent = u'\t' * tabs617 output = []618 619 list_length = len(list_)620 i = 0621 while i < list_length:622 title = list_[i]623 sublist = ''624 sublist_item = None625 if isinstance(title, (list, tuple)):626 sublist_item = title627 title = ''628 elif i < list_length - 1:629 next_item = list_[i+1]630 if next_item and isinstance(next_item, (list, tuple)):631 # The next item is a sub-list.632 sublist_item = next_item633 # We've processed the next item now too.634 i += 1635 if sublist_item:636 sublist = _helper(sublist_item, tabs+1)637 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,638 indent, indent)639 output.append('%s<li>%s%s</li>' % (indent,640 escaper(force_unicode(title)), sublist))641 i += 1642 return '\n'.join(output)643 value, converted = convert_old_style_list(value)644 return mark_safe(_helper(value))645 unordered_list.is_safe = True646 unordered_list.needs_autoescape = True647 648 ###################649 # INTEGERS #650 ###################651 652 def add(value, arg):653 """Adds the arg to the value."""654 return int(value) + int(arg)655 add.is_safe = False656 657 def get_digit(value, arg):658 """659 Given a whole number, returns the requested digit of it, where 1 is the660 right-most digit, 2 is the second-right-most digit, etc. Returns the661 original value for invalid input (if input or argument is not an integer,662 or if argument is less than 1). Otherwise, output is always an integer.663 """664 try:665 arg = int(arg)666 value = int(value)667 except ValueError:668 return value # Fail silently for an invalid argument669 if arg < 1:670 return value671 try:672 return int(str(value)[-arg])673 except IndexError:674 return 0675 get_digit.is_safe = False676 677 ###################678 # DATES #679 ###################680 681 def date(value, arg=None):682 """Formats a date according to the given format."""683 from django.utils.dateformat import format684 if not value:685 return u''686 if arg is None:687 arg = settings.DATE_FORMAT688 try:689 return formats.date_format(value, arg)690 except AttributeError:691 try:692 return format(value, arg)693 except AttributeError:694 return ''695 date.is_safe = False696 697 def time(value, arg=None):698 """Formats a time according to the given format."""699 from django.utils import dateformat700 if value in (None, u''):701 return u''702 if arg is None:703 arg = settings.TIME_FORMAT704 try:705 return formats.time_format(value, arg)706 except AttributeError:707 try:708 return dateformat.time_format(value, arg)709 except AttributeError:710 return ''711 time.is_safe = False712 713 def timesince(value, arg=None):714 """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""715 from django.utils.timesince import timesince716 if not value:717 return u''718 try:719 if arg:720 return timesince(value, arg)721 return timesince(value)722 except (ValueError, TypeError):723 return u''724 timesince.is_safe = False725 726 def timeuntil(value, arg=None):727 """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""728 from django.utils.timesince import timeuntil729 from datetime import datetime730 if not value:731 return u''732 try:733 return timeuntil(value, arg)734 except (ValueError, TypeError):735 return u''736 timeuntil.is_safe = False737 738 ###################739 # LOGIC #740 ###################741 742 def default(value, arg):743 """If value is unavailable, use given default."""744 return value or arg745 default.is_safe = False746 747 def default_if_none(value, arg):748 """If value is None, use given default."""749 if value is None:750 return arg751 return value752 default_if_none.is_safe = False753 754 def divisibleby(value, arg):755 """Returns True if the value is devisible by the argument."""756 return int(value) % int(arg) == 0757 divisibleby.is_safe = False758 759 def yesno(value, arg=None):760 """761 Given a string mapping values for true, false and (optionally) None,762 returns one of those strings accoding to the value:763 764 ========== ====================== ==================================765 Value Argument Outputs766 ========== ====================== ==================================767 ``True`` ``"yeah,no,maybe"`` ``yeah``768 ``False`` ``"yeah,no,maybe"`` ``no``769 ``None`` ``"yeah,no,maybe"`` ``maybe``770 ``None`` ``"yeah,no"`` ``"no"`` (converts None to False771 if no mapping for None is given.772 ========== ====================== ==================================773 """774 if arg is None:775 arg = ugettext('yes,no,maybe')776 bits = arg.split(u',')777 if len(bits) < 2:778 return value # Invalid arg.779 try:780 yes, no, maybe = bits781 except ValueError:782 # Unpack list of wrong size (no "maybe" value provided).783 yes, no, maybe = bits[0], bits[1], bits[1]784 if value is None:785 return maybe786 if value:787 return yes788 return no789 yesno.is_safe = False790 791 ###################792 # MISC #793 ###################794 795 def filesizeformat(bytes):796 """797 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,798 102 bytes, etc).799 """800 try:801 bytes = float(bytes)802 except TypeError:803 return u"0 bytes"804 805 if bytes < 1024:806 return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}807 if bytes < 1024 * 1024:808 return ugettext("%.1f KB") % (bytes / 1024)809 if bytes < 1024 * 1024 * 1024:810 return ugettext("%.1f MB") % (bytes / (1024 * 1024))811 return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))812 filesizeformat.is_safe = True813 814 def pluralize(value, arg=u's'):815 """816 Returns a plural suffix if the value is not 1. By default, 's' is used as817 the suffix:818 819 * If value is 0, vote{{ value|pluralize }} displays "0 votes".820 * If value is 1, vote{{ value|pluralize }} displays "1 vote".821 * If value is 2, vote{{ value|pluralize }} displays "2 votes".822 823 If an argument is provided, that string is used instead:824 825 * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".826 * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".827 * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".828 829 If the provided argument contains a comma, the text before the comma is830 used for the singular case and the text after the comma is used for the831 plural case:832 833 * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".834 * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".835 * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".836 """837 if not u',' in arg:838 arg = u',' + arg839 bits = arg.split(u',')840 if len(bits) > 2:841 return u''842 singular_suffix, plural_suffix = bits[:2]843 844 try:845 if int(value) != 1:846 return plural_suffix847 except ValueError: # Invalid string that's not a number.848 pass849 except TypeError: # Value isn't a string or a number; maybe it's a list?850 try:851 if len(value) != 1:852 return plural_suffix853 except TypeError: # len() of unsized object.854 pass855 return singular_suffix856 pluralize.is_safe = False857 858 def phone2numeric(value):859 """Takes a phone number and converts it in to its numerical equivalent."""860 from django.utils.text import phone2numeric861 return phone2numeric(value)862 phone2numeric.is_safe = True863 864 def pprint(value):865 """A wrapper around pprint.pprint -- for debugging, really."""866 from pprint import pformat867 try:868 return pformat(value)869 except Exception, e:870 return u"Error in formatting: %s" % force_unicode(e, errors="replace")871 pprint.is_safe = True872 873 # Syntax: register.filter(name of filter, callback)874 register.filter(add)875 register.filter(addslashes)876 register.filter(capfirst)877 register.filter(center)878 register.filter(cut)879 register.filter(date)880 register.filter(default)881 register.filter(default_if_none)882 register.filter(dictsort)883 register.filter(dictsortreversed)884 register.filter(divisibleby)885 register.filter(escape)886 register.filter(escapejs)887 register.filter(filesizeformat)888 register.filter(first)889 register.filter(fix_ampersands)890 register.filter(floatformat)891 register.filter(force_escape)892 register.filter(get_digit)893 register.filter(iriencode)894 register.filter(join)895 register.filter(last)896 register.filter(length)897 register.filter(length_is)898 register.filter(linebreaks)899 register.filter(linebreaksbr)900 register.filter(linenumbers)901 register.filter(ljust)902 register.filter(lower)903 register.filter(make_list)904 register.filter(phone2numeric)905 register.filter(pluralize)906 register.filter(pprint)907 register.filter(removetags)908 register.filter(random)909 register.filter(rjust)910 register.filter(safe)911 register.filter(safeseq)912 register.filter('slice', slice_)913 register.filter(slugify)914 register.filter(stringformat)915 register.filter(striptags)916 register.filter(time)917 register.filter(timesince)918 register.filter(timeuntil)919 register.filter(title)920 register.filter(truncatewords)921 register.filter(truncatewords_html)922 register.filter(unordered_list)923 register.filter(upper)924 register.filter(urlencode)925 register.filter(urlize)926 register.filter(urlizetrunc)927 register.filter(wordcount)928 register.filter(wordwrap)929 register.filter(yesno) -
deleted file django/template/defaulttags.py
diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py deleted file mode 100644 index d703c6a..0000000
+ - 1 """Default tags used by the template system, available to all templates."""2 3 import sys4 import re5 from itertools import cycle as itertools_cycle6 try:7 reversed8 except NameError:9 from django.utils.itercompat import reversed # Python 2.3 fallback10 11 from django.template import Node, NodeList, Template, Context, Variable12 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_END13 from django.template import get_library, Library, InvalidTemplateLibrary14 from django.template.smartif import IfParser, Literal15 from django.conf import settings16 from django.utils.encoding import smart_str, smart_unicode17 from django.utils.itercompat import groupby18 from django.utils.safestring import mark_safe19 20 register = Library()21 22 class AutoEscapeControlNode(Node):23 """Implements the actions of the autoescape tag."""24 def __init__(self, setting, nodelist):25 self.setting, self.nodelist = setting, nodelist26 27 def render(self, context):28 old_setting = context.autoescape29 context.autoescape = self.setting30 output = self.nodelist.render(context)31 context.autoescape = old_setting32 if self.setting:33 return mark_safe(output)34 else:35 return output36 37 class CommentNode(Node):38 def render(self, context):39 return ''40 41 class CsrfTokenNode(Node):42 def render(self, context):43 csrf_token = context.get('csrf_token', None)44 if csrf_token:45 if csrf_token == 'NOTPROVIDED':46 return mark_safe(u"")47 else:48 return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token))49 else:50 # It's very probable that the token is missing because of51 # misconfiguration, so we raise a warning52 from django.conf import settings53 if settings.DEBUG:54 import warnings55 warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.")56 return u''57 58 class CycleNode(Node):59 def __init__(self, cyclevars, variable_name=None):60 self.cyclevars = cyclevars61 self.variable_name = variable_name62 63 def render(self, context):64 if self not in context.render_context:65 context.render_context[self] = itertools_cycle(self.cyclevars)66 cycle_iter = context.render_context[self]67 value = cycle_iter.next().resolve(context)68 if self.variable_name:69 context[self.variable_name] = value70 return value71 72 class DebugNode(Node):73 def render(self, context):74 from pprint import pformat75 output = [pformat(val) for val in context]76 output.append('\n\n')77 output.append(pformat(sys.modules))78 return ''.join(output)79 80 class FilterNode(Node):81 def __init__(self, filter_expr, nodelist):82 self.filter_expr, self.nodelist = filter_expr, nodelist83 84 def render(self, context):85 output = self.nodelist.render(context)86 # Apply filters.87 context.update({'var': output})88 filtered = self.filter_expr.resolve(context)89 context.pop()90 return filtered91 92 class FirstOfNode(Node):93 def __init__(self, vars):94 self.vars = vars95 96 def render(self, context):97 for var in self.vars:98 value = var.resolve(context, True)99 if value:100 return smart_unicode(value)101 return u''102 103 class ForNode(Node):104 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):105 self.loopvars, self.sequence = loopvars, sequence106 self.is_reversed = is_reversed107 self.nodelist_loop = nodelist_loop108 if nodelist_empty is None:109 self.nodelist_empty = NodeList()110 else:111 self.nodelist_empty = nodelist_empty112 113 def __repr__(self):114 reversed_text = self.is_reversed and ' reversed' or ''115 return "<For Node: for %s in %s, tail_len: %d%s>" % \116 (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),117 reversed_text)118 119 def __iter__(self):120 for node in self.nodelist_loop:121 yield node122 for node in self.nodelist_empty:123 yield node124 125 def get_nodes_by_type(self, nodetype):126 nodes = []127 if isinstance(self, nodetype):128 nodes.append(self)129 nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))130 nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype))131 return nodes132 133 def render(self, context):134 if 'forloop' in context:135 parentloop = context['forloop']136 else:137 parentloop = {}138 context.push()139 try:140 values = self.sequence.resolve(context, True)141 except VariableDoesNotExist:142 values = []143 if values is None:144 values = []145 if not hasattr(values, '__len__'):146 values = list(values)147 len_values = len(values)148 if len_values < 1:149 context.pop()150 return self.nodelist_empty.render(context)151 nodelist = NodeList()152 if self.is_reversed:153 values = reversed(values)154 unpack = len(self.loopvars) > 1155 # Create a forloop value in the context. We'll update counters on each156 # iteration just below.157 loop_dict = context['forloop'] = {'parentloop': parentloop}158 for i, item in enumerate(values):159 # Shortcuts for current loop iteration number.160 loop_dict['counter0'] = i161 loop_dict['counter'] = i+1162 # Reverse counter iteration numbers.163 loop_dict['revcounter'] = len_values - i164 loop_dict['revcounter0'] = len_values - i - 1165 # Boolean values designating first and last times through loop.166 loop_dict['first'] = (i == 0)167 loop_dict['last'] = (i == len_values - 1)168 169 if unpack:170 # If there are multiple loop variables, unpack the item into171 # them.172 context.update(dict(zip(self.loopvars, item)))173 else:174 context[self.loopvars[0]] = item175 for node in self.nodelist_loop:176 nodelist.append(node.render(context))177 if unpack:178 # The loop variables were pushed on to the context so pop them179 # off again. This is necessary because the tag lets the length180 # of loopvars differ to the length of each set of items and we181 # don't want to leave any vars from the previous loop on the182 # context.183 context.pop()184 context.pop()185 return nodelist.render(context)186 187 class IfChangedNode(Node):188 def __init__(self, nodelist_true, nodelist_false, *varlist):189 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false190 self._last_seen = None191 self._varlist = varlist192 self._id = str(id(self))193 194 def render(self, context):195 if 'forloop' in context and self._id not in context['forloop']:196 self._last_seen = None197 context['forloop'][self._id] = 1198 try:199 if self._varlist:200 # Consider multiple parameters. This automatically behaves201 # like an OR evaluation of the multiple variables.202 compare_to = [var.resolve(context, True) for var in self._varlist]203 else:204 compare_to = self.nodelist_true.render(context)205 except VariableDoesNotExist:206 compare_to = None207 208 if compare_to != self._last_seen:209 firstloop = (self._last_seen == None)210 self._last_seen = compare_to211 content = self.nodelist_true.render(context)212 return content213 elif self.nodelist_false:214 return self.nodelist_false.render(context)215 return ''216 217 class IfEqualNode(Node):218 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):219 self.var1, self.var2 = var1, var2220 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false221 self.negate = negate222 223 def __repr__(self):224 return "<IfEqualNode>"225 226 def render(self, context):227 val1 = self.var1.resolve(context, True)228 val2 = self.var2.resolve(context, True)229 if (self.negate and val1 != val2) or (not self.negate and val1 == val2):230 return self.nodelist_true.render(context)231 return self.nodelist_false.render(context)232 233 class IfNode(Node):234 def __init__(self, var, nodelist_true, nodelist_false=None):235 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false236 self.var = var237 238 def __repr__(self):239 return "<If node>"240 241 def __iter__(self):242 for node in self.nodelist_true:243 yield node244 for node in self.nodelist_false:245 yield node246 247 def get_nodes_by_type(self, nodetype):248 nodes = []249 if isinstance(self, nodetype):250 nodes.append(self)251 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))252 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))253 return nodes254 255 def render(self, context):256 if self.var.eval(context):257 return self.nodelist_true.render(context)258 else:259 return self.nodelist_false.render(context)260 261 class RegroupNode(Node):262 def __init__(self, target, expression, var_name):263 self.target, self.expression = target, expression264 self.var_name = var_name265 266 def render(self, context):267 obj_list = self.target.resolve(context, True)268 if obj_list == None:269 # target variable wasn't found in context; fail silently.270 context[self.var_name] = []271 return ''272 # List of dictionaries in the format:273 # {'grouper': 'key', 'list': [list of contents]}.274 context[self.var_name] = [275 {'grouper': key, 'list': list(val)}276 for key, val in277 groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))278 ]279 return ''280 281 def include_is_allowed(filepath):282 for root in settings.ALLOWED_INCLUDE_ROOTS:283 if filepath.startswith(root):284 return True285 return False286 287 class SsiNode(Node):288 def __init__(self, filepath, parsed):289 self.filepath, self.parsed = filepath, parsed290 291 def render(self, context):292 if not include_is_allowed(self.filepath):293 if settings.DEBUG:294 return "[Didn't have permission to include file]"295 else:296 return '' # Fail silently for invalid includes.297 try:298 fp = open(self.filepath, 'r')299 output = fp.read()300 fp.close()301 except IOError:302 output = ''303 if self.parsed:304 try:305 t = Template(output, name=self.filepath)306 return t.render(context)307 except TemplateSyntaxError, e:308 if settings.DEBUG:309 return "[Included template had syntax error: %s]" % e310 else:311 return '' # Fail silently for invalid included templates.312 return output313 314 class LoadNode(Node):315 def render(self, context):316 return ''317 318 class NowNode(Node):319 def __init__(self, format_string):320 self.format_string = format_string321 322 def render(self, context):323 from datetime import datetime324 from django.utils.dateformat import DateFormat325 df = DateFormat(datetime.now())326 return df.format(self.format_string)327 328 class SpacelessNode(Node):329 def __init__(self, nodelist):330 self.nodelist = nodelist331 332 def render(self, context):333 from django.utils.html import strip_spaces_between_tags334 return strip_spaces_between_tags(self.nodelist.render(context).strip())335 336 class TemplateTagNode(Node):337 mapping = {'openblock': BLOCK_TAG_START,338 'closeblock': BLOCK_TAG_END,339 'openvariable': VARIABLE_TAG_START,340 'closevariable': VARIABLE_TAG_END,341 'openbrace': SINGLE_BRACE_START,342 'closebrace': SINGLE_BRACE_END,343 'opencomment': COMMENT_TAG_START,344 'closecomment': COMMENT_TAG_END,345 }346 347 def __init__(self, tagtype):348 self.tagtype = tagtype349 350 def render(self, context):351 return self.mapping.get(self.tagtype, '')352 353 class URLNode(Node):354 def __init__(self, view_name, args, kwargs, asvar):355 self.view_name = view_name356 self.args = args357 self.kwargs = kwargs358 self.asvar = asvar359 360 def render(self, context):361 from django.core.urlresolvers import reverse, NoReverseMatch362 args = [arg.resolve(context) for arg in self.args]363 kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))364 for k, v in self.kwargs.items()])365 366 # Try to look up the URL twice: once given the view name, and again367 # relative to what we guess is the "main" app. If they both fail,368 # re-raise the NoReverseMatch unless we're using the369 # {% url ... as var %} construct in which cause return nothing.370 url = ''371 try:372 url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app)373 except NoReverseMatch, e:374 if settings.SETTINGS_MODULE:375 project_name = settings.SETTINGS_MODULE.split('.')[0]376 try:377 url = reverse(project_name + '.' + self.view_name,378 args=args, kwargs=kwargs, current_app=context.current_app)379 except NoReverseMatch:380 if self.asvar is None:381 # Re-raise the original exception, not the one with382 # the path relative to the project. This makes a383 # better error message.384 raise e385 else:386 if self.asvar is None:387 raise e388 389 if self.asvar:390 context[self.asvar] = url391 return ''392 else:393 return url394 395 class WidthRatioNode(Node):396 def __init__(self, val_expr, max_expr, max_width):397 self.val_expr = val_expr398 self.max_expr = max_expr399 self.max_width = max_width400 401 def render(self, context):402 try:403 value = self.val_expr.resolve(context)404 maxvalue = self.max_expr.resolve(context)405 max_width = int(self.max_width.resolve(context))406 except VariableDoesNotExist:407 return ''408 except ValueError:409 raise TemplateSyntaxError("widthratio final argument must be an number")410 try:411 value = float(value)412 maxvalue = float(maxvalue)413 ratio = (value / maxvalue) * max_width414 except (ValueError, ZeroDivisionError):415 return ''416 return str(int(round(ratio)))417 418 class WithNode(Node):419 def __init__(self, var, name, nodelist):420 self.var = var421 self.name = name422 self.nodelist = nodelist423 424 def __repr__(self):425 return "<WithNode>"426 427 def render(self, context):428 val = self.var.resolve(context)429 context.push()430 context[self.name] = val431 output = self.nodelist.render(context)432 context.pop()433 return output434 435 #@register.tag436 def autoescape(parser, token):437 """438 Force autoescape behaviour for this block.439 """440 args = token.contents.split()441 if len(args) != 2:442 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")443 arg = args[1]444 if arg not in (u'on', u'off'):445 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")446 nodelist = parser.parse(('endautoescape',))447 parser.delete_first_token()448 return AutoEscapeControlNode((arg == 'on'), nodelist)449 autoescape = register.tag(autoescape)450 451 #@register.tag452 def comment(parser, token):453 """454 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.455 """456 parser.skip_past('endcomment')457 return CommentNode()458 comment = register.tag(comment)459 460 #@register.tag461 def cycle(parser, token):462 """463 Cycles among the given strings each time this tag is encountered.464 465 Within a loop, cycles among the given strings each time through466 the loop::467 468 {% for o in some_list %}469 <tr class="{% cycle 'row1' 'row2' %}">470 ...471 </tr>472 {% endfor %}473 474 Outside of a loop, give the values a unique name the first time you call475 it, then use that name each sucessive time through::476 477 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>478 <tr class="{% cycle rowcolors %}">...</tr>479 <tr class="{% cycle rowcolors %}">...</tr>480 481 You can use any number of values, separated by spaces. Commas can also482 be used to separate values; if a comma is used, the cycle values are483 interpreted as literal strings.484 """485 486 # Note: This returns the exact same node on each {% cycle name %} call;487 # that is, the node object returned from {% cycle a b c as name %} and the488 # one returned from {% cycle name %} are the exact same object. This489 # shouldn't cause problems (heh), but if it does, now you know.490 #491 # Ugly hack warning: This stuffs the named template dict into parser so492 # that names are only unique within each template (as opposed to using493 # a global variable, which would make cycle names have to be unique across494 # *all* templates.495 496 args = token.split_contents()497 498 if len(args) < 2:499 raise TemplateSyntaxError("'cycle' tag requires at least two arguments")500 501 if ',' in args[1]:502 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}503 # case.504 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]505 506 if len(args) == 2:507 # {% cycle foo %} case.508 name = args[1]509 if not hasattr(parser, '_namedCycleNodes'):510 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)511 if not name in parser._namedCycleNodes:512 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)513 return parser._namedCycleNodes[name]514 515 if len(args) > 4 and args[-2] == 'as':516 name = args[-1]517 values = [parser.compile_filter(arg) for arg in args[1:-2]]518 node = CycleNode(values, name)519 if not hasattr(parser, '_namedCycleNodes'):520 parser._namedCycleNodes = {}521 parser._namedCycleNodes[name] = node522 else:523 values = [parser.compile_filter(arg) for arg in args[1:]]524 node = CycleNode(values)525 return node526 cycle = register.tag(cycle)527 528 def csrf_token(parser, token):529 return CsrfTokenNode()530 register.tag(csrf_token)531 532 def debug(parser, token):533 """534 Outputs a whole load of debugging information, including the current535 context and imported modules.536 537 Sample usage::538 539 <pre>540 {% debug %}541 </pre>542 """543 return DebugNode()544 debug = register.tag(debug)545 546 #@register.tag(name="filter")547 def do_filter(parser, token):548 """549 Filters the contents of the block through variable filters.550 551 Filters can also be piped through each other, and they can have552 arguments -- just like in variable syntax.553 554 Sample usage::555 556 {% filter force_escape|lower %}557 This text will be HTML-escaped, and will appear in lowercase.558 {% endfilter %}559 """560 _, rest = token.contents.split(None, 1)561 filter_expr = parser.compile_filter("var|%s" % (rest))562 for func, unused in filter_expr.filters:563 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):564 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)565 nodelist = parser.parse(('endfilter',))566 parser.delete_first_token()567 return FilterNode(filter_expr, nodelist)568 do_filter = register.tag("filter", do_filter)569 570 #@register.tag571 def firstof(parser, token):572 """573 Outputs the first variable passed that is not False, without escaping.574 575 Outputs nothing if all the passed variables are False.576 577 Sample usage::578 579 {% firstof var1 var2 var3 %}580 581 This is equivalent to::582 583 {% if var1 %}584 {{ var1|safe }}585 {% else %}{% if var2 %}586 {{ var2|safe }}587 {% else %}{% if var3 %}588 {{ var3|safe }}589 {% endif %}{% endif %}{% endif %}590 591 but obviously much cleaner!592 593 You can also use a literal string as a fallback value in case all594 passed variables are False::595 596 {% firstof var1 var2 var3 "fallback value" %}597 598 If you want to escape the output, use a filter tag::599 600 {% filter force_escape %}601 {% firstof var1 var2 var3 "fallback value" %}602 {% endfilter %}603 604 """605 bits = token.split_contents()[1:]606 if len(bits) < 1:607 raise TemplateSyntaxError("'firstof' statement requires at least one argument")608 return FirstOfNode([parser.compile_filter(bit) for bit in bits])609 firstof = register.tag(firstof)610 611 #@register.tag(name="for")612 def do_for(parser, token):613 """614 Loops over each item in an array.615 616 For example, to display a list of athletes given ``athlete_list``::617 618 <ul>619 {% for athlete in athlete_list %}620 <li>{{ athlete.name }}</li>621 {% endfor %}622 </ul>623 624 You can loop over a list in reverse by using625 ``{% for obj in list reversed %}``.626 627 You can also unpack multiple values from a two-dimensional array::628 629 {% for key,value in dict.items %}630 {{ key }}: {{ value }}631 {% endfor %}632 633 The ``for`` tag can take an optional ``{% empty %}`` clause that will634 be displayed if the given array is empty or could not be found::635 636 <ul>637 {% for athlete in athlete_list %}638 <li>{{ athlete.name }}</li>639 {% empty %}640 <li>Sorry, no athletes in this list.</li>641 {% endfor %}642 <ul>643 644 The above is equivalent to -- but shorter, cleaner, and possibly faster645 than -- the following::646 647 <ul>648 {% if althete_list %}649 {% for athlete in athlete_list %}650 <li>{{ athlete.name }}</li>651 {% endfor %}652 {% else %}653 <li>Sorry, no athletes in this list.</li>654 {% endif %}655 </ul>656 657 The for loop sets a number of variables available within the loop:658 659 ========================== ================================================660 Variable Description661 ========================== ================================================662 ``forloop.counter`` The current iteration of the loop (1-indexed)663 ``forloop.counter0`` The current iteration of the loop (0-indexed)664 ``forloop.revcounter`` The number of iterations from the end of the665 loop (1-indexed)666 ``forloop.revcounter0`` The number of iterations from the end of the667 loop (0-indexed)668 ``forloop.first`` True if this is the first time through the loop669 ``forloop.last`` True if this is the last time through the loop670 ``forloop.parentloop`` For nested loops, this is the loop "above" the671 current one672 ========================== ================================================673 674 """675 bits = token.contents.split()676 if len(bits) < 4:677 raise TemplateSyntaxError("'for' statements should have at least four"678 " words: %s" % token.contents)679 680 is_reversed = bits[-1] == 'reversed'681 in_index = is_reversed and -3 or -2682 if bits[in_index] != 'in':683 raise TemplateSyntaxError("'for' statements should use the format"684 " 'for x in y': %s" % token.contents)685 686 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')687 for var in loopvars:688 if not var or ' ' in var:689 raise TemplateSyntaxError("'for' tag received an invalid argument:"690 " %s" % token.contents)691 692 sequence = parser.compile_filter(bits[in_index+1])693 nodelist_loop = parser.parse(('empty', 'endfor',))694 token = parser.next_token()695 if token.contents == 'empty':696 nodelist_empty = parser.parse(('endfor',))697 parser.delete_first_token()698 else:699 nodelist_empty = None700 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)701 do_for = register.tag("for", do_for)702 703 def do_ifequal(parser, token, negate):704 bits = list(token.split_contents())705 if len(bits) != 3:706 raise TemplateSyntaxError("%r takes two arguments" % bits[0])707 end_tag = 'end' + bits[0]708 nodelist_true = parser.parse(('else', end_tag))709 token = parser.next_token()710 if token.contents == 'else':711 nodelist_false = parser.parse((end_tag,))712 parser.delete_first_token()713 else:714 nodelist_false = NodeList()715 val1 = parser.compile_filter(bits[1])716 val2 = parser.compile_filter(bits[2])717 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)718 719 #@register.tag720 def ifequal(parser, token):721 """722 Outputs the contents of the block if the two arguments equal each other.723 724 Examples::725 726 {% ifequal user.id comment.user_id %}727 ...728 {% endifequal %}729 730 {% ifnotequal user.id comment.user_id %}731 ...732 {% else %}733 ...734 {% endifnotequal %}735 """736 return do_ifequal(parser, token, False)737 ifequal = register.tag(ifequal)738 739 #@register.tag740 def ifnotequal(parser, token):741 """742 Outputs the contents of the block if the two arguments are not equal.743 See ifequal.744 """745 return do_ifequal(parser, token, True)746 ifnotequal = register.tag(ifnotequal)747 748 class TemplateLiteral(Literal):749 def __init__(self, value, text):750 self.value = value751 self.text = text # for better error messages752 753 def display(self):754 return self.text755 756 def eval(self, context):757 return self.value.resolve(context, ignore_failures=True)758 759 class TemplateIfParser(IfParser):760 error_class = TemplateSyntaxError761 762 def __init__(self, parser, *args, **kwargs):763 self.template_parser = parser764 return super(TemplateIfParser, self).__init__(*args, **kwargs)765 766 def create_var(self, value):767 return TemplateLiteral(self.template_parser.compile_filter(value), value)768 769 #@register.tag(name="if")770 def do_if(parser, token):771 """772 The ``{% if %}`` tag evaluates a variable, and if that variable is "true"773 (i.e., exists, is not empty, and is not a false boolean value), the774 contents of the block are output:775 776 ::777 778 {% if athlete_list %}779 Number of athletes: {{ athlete_list|count }}780 {% else %}781 No athletes.782 {% endif %}783 784 In the above, if ``athlete_list`` is not empty, the number of athletes will785 be displayed by the ``{{ athlete_list|count }}`` variable.786 787 As you can see, the ``if`` tag can take an option ``{% else %}`` clause788 that will be displayed if the test fails.789 790 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of791 variables or to negate a given variable::792 793 {% if not athlete_list %}794 There are no athletes.795 {% endif %}796 797 {% if athlete_list or coach_list %}798 There are some athletes or some coaches.799 {% endif %}800 801 {% if athlete_list and coach_list %}802 Both atheletes and coaches are available.803 {% endif %}804 805 {% if not athlete_list or coach_list %}806 There are no athletes, or there are some coaches.807 {% endif %}808 809 {% if athlete_list and not coach_list %}810 There are some athletes and absolutely no coaches.811 {% endif %}812 813 Comparison operators are also available, and the use of filters is also814 allowed, for example:815 816 {% if articles|length >= 5 %}...{% endif %}817 818 Arguments and operators _must_ have a space between them, so819 ``{% if 1>2 %}`` is not a valid if tag.820 821 All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),822 ``!=``, ``>``, ``>=``, ``<`` and ``<=``.823 824 Operator precedence follows Python.825 """826 bits = token.split_contents()[1:]827 var = TemplateIfParser(parser, bits).parse()828 nodelist_true = parser.parse(('else', 'endif'))829 token = parser.next_token()830 if token.contents == 'else':831 nodelist_false = parser.parse(('endif',))832 parser.delete_first_token()833 else:834 nodelist_false = NodeList()835 return IfNode(var, nodelist_true, nodelist_false)836 do_if = register.tag("if", do_if)837 838 #@register.tag839 def ifchanged(parser, token):840 """841 Checks if a value has changed from the last iteration of a loop.842 843 The 'ifchanged' block tag is used within a loop. It has two possible uses.844 845 1. Checks its own rendered contents against its previous state and only846 displays the content if it has changed. For example, this displays a847 list of days, only displaying the month if it changes::848 849 <h1>Archive for {{ year }}</h1>850 851 {% for date in days %}852 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}853 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>854 {% endfor %}855 856 2. If given a variable, check whether that variable has changed.857 For example, the following shows the date every time it changes, but858 only shows the hour if both the hour and the date have changed::859 860 {% for date in days %}861 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}862 {% ifchanged date.hour date.date %}863 {{ date.hour }}864 {% endifchanged %}865 {% endfor %}866 """867 bits = token.contents.split()868 nodelist_true = parser.parse(('else', 'endifchanged'))869 token = parser.next_token()870 if token.contents == 'else':871 nodelist_false = parser.parse(('endifchanged',))872 parser.delete_first_token()873 else:874 nodelist_false = NodeList()875 values = [parser.compile_filter(bit) for bit in bits[1:]]876 return IfChangedNode(nodelist_true, nodelist_false, *values)877 ifchanged = register.tag(ifchanged)878 879 #@register.tag880 def ssi(parser, token):881 """882 Outputs the contents of a given file into the page.883 884 Like a simple "include" tag, the ``ssi`` tag includes the contents885 of another file -- which must be specified using an absolute path --886 in the current page::887 888 {% ssi /home/html/ljworld.com/includes/right_generic.html %}889 890 If the optional "parsed" parameter is given, the contents of the included891 file are evaluated as template code, with the current context::892 893 {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}894 """895 bits = token.contents.split()896 parsed = False897 if len(bits) not in (2, 3):898 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"899 " the file to be included")900 if len(bits) == 3:901 if bits[2] == 'parsed':902 parsed = True903 else:904 raise TemplateSyntaxError("Second (optional) argument to %s tag"905 " must be 'parsed'" % bits[0])906 return SsiNode(bits[1], parsed)907 ssi = register.tag(ssi)908 909 #@register.tag910 def load(parser, token):911 """912 Loads a custom template tag set.913 914 For example, to load the template tags in915 ``django/templatetags/news/photos.py``::916 917 {% load news.photos %}918 """919 bits = token.contents.split()920 for taglib in bits[1:]:921 # add the library to the parser922 try:923 lib = get_library("django.templatetags.%s" % taglib)924 parser.add_library(lib)925 except InvalidTemplateLibrary, e:926 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %927 (taglib, e))928 return LoadNode()929 load = register.tag(load)930 931 #@register.tag932 def now(parser, token):933 """934 Displays the date, formatted according to the given string.935 936 Uses the same format as PHP's ``date()`` function; see http://php.net/date937 for all the possible values.938 939 Sample usage::940 941 It is {% now "jS F Y H:i" %}942 """943 bits = token.contents.split('"')944 if len(bits) != 3:945 raise TemplateSyntaxError("'now' statement takes one argument")946 format_string = bits[1]947 return NowNode(format_string)948 now = register.tag(now)949 950 #@register.tag951 def regroup(parser, token):952 """953 Regroups a list of alike objects by a common attribute.954 955 This complex tag is best illustrated by use of an example: say that956 ``people`` is a list of ``Person`` objects that have ``first_name``,957 ``last_name``, and ``gender`` attributes, and you'd like to display a list958 that looks like:959 960 * Male:961 * George Bush962 * Bill Clinton963 * Female:964 * Margaret Thatcher965 * Colendeeza Rice966 * Unknown:967 * Pat Smith968 969 The following snippet of template code would accomplish this dubious task::970 971 {% regroup people by gender as grouped %}972 <ul>973 {% for group in grouped %}974 <li>{{ group.grouper }}975 <ul>976 {% for item in group.list %}977 <li>{{ item }}</li>978 {% endfor %}979 </ul>980 {% endfor %}981 </ul>982 983 As you can see, ``{% regroup %}`` populates a variable with a list of984 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the985 item that was grouped by; ``list`` contains the list of objects that share986 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``987 and ``Unknown``, and ``list`` is the list of people with those genders.988 989 Note that ``{% regroup %}`` does not work when the list to be grouped is not990 sorted by the key you are grouping by! This means that if your list of991 people was not sorted by gender, you'd need to make sure it is sorted992 before using it, i.e.::993 994 {% regroup people|dictsort:"gender" by gender as grouped %}995 996 """997 firstbits = token.contents.split(None, 3)998 if len(firstbits) != 4:999 raise TemplateSyntaxError("'regroup' tag takes five arguments")1000 target = parser.compile_filter(firstbits[1])1001 if firstbits[2] != 'by':1002 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")1003 lastbits_reversed = firstbits[3][::-1].split(None, 2)1004 if lastbits_reversed[1][::-1] != 'as':1005 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"1006 " be 'as'")1007 1008 expression = parser.compile_filter(lastbits_reversed[2][::-1])1009 1010 var_name = lastbits_reversed[0][::-1]1011 return RegroupNode(target, expression, var_name)1012 regroup = register.tag(regroup)1013 1014 def spaceless(parser, token):1015 """1016 Removes whitespace between HTML tags, including tab and newline characters.1017 1018 Example usage::1019 1020 {% spaceless %}1021 <p>1022 <a href="foo/">Foo</a>1023 </p>1024 {% endspaceless %}1025 1026 This example would return this HTML::1027 1028 <p><a href="foo/">Foo</a></p>1029 1030 Only space between *tags* is normalized -- not space between tags and text.1031 In this example, the space around ``Hello`` won't be stripped::1032 1033 {% spaceless %}1034 <strong>1035 Hello1036 </strong>1037 {% endspaceless %}1038 """1039 nodelist = parser.parse(('endspaceless',))1040 parser.delete_first_token()1041 return SpacelessNode(nodelist)1042 spaceless = register.tag(spaceless)1043 1044 #@register.tag1045 def templatetag(parser, token):1046 """1047 Outputs one of the bits used to compose template tags.1048 1049 Since the template system has no concept of "escaping", to display one of1050 the bits used in template tags, you must use the ``{% templatetag %}`` tag.1051 1052 The argument tells which template bit to output:1053 1054 ================== =======1055 Argument Outputs1056 ================== =======1057 ``openblock`` ``{%``1058 ``closeblock`` ``%}``1059 ``openvariable`` ``{{``1060 ``closevariable`` ``}}``1061 ``openbrace`` ``{``1062 ``closebrace`` ``}``1063 ``opencomment`` ``{#``1064 ``closecomment`` ``#}``1065 ================== =======1066 """1067 bits = token.contents.split()1068 if len(bits) != 2:1069 raise TemplateSyntaxError("'templatetag' statement takes one argument")1070 tag = bits[1]1071 if tag not in TemplateTagNode.mapping:1072 raise TemplateSyntaxError("Invalid templatetag argument: '%s'."1073 " Must be one of: %s" %1074 (tag, TemplateTagNode.mapping.keys()))1075 return TemplateTagNode(tag)1076 templatetag = register.tag(templatetag)1077 1078 def url(parser, token):1079 """1080 Returns an absolute URL matching given view with its parameters.1081 1082 This is a way to define links that aren't tied to a particular URL1083 configuration::1084 1085 {% url path.to.some_view arg1,arg2,name1=value1 %}1086 1087 The first argument is a path to a view. It can be an absolute python path1088 or just ``app_name.view_name`` without the project name if the view is1089 located inside the project. Other arguments are comma-separated values1090 that will be filled in place of positional and keyword arguments in the1091 URL. All arguments for the URL should be present.1092 1093 For example if you have a view ``app_name.client`` taking client's id and1094 the corresponding line in a URLconf looks like this::1095 1096 ('^client/(\d+)/$', 'app_name.client')1097 1098 and this app's URLconf is included into the project's URLconf under some1099 path::1100 1101 ('^clients/', include('project_name.app_name.urls'))1102 1103 then in a template you can create a link for a certain client like this::1104 1105 {% url app_name.client client.id %}1106 1107 The URL will look like ``/clients/client/123/``.1108 """1109 bits = token.split_contents()1110 if len(bits) < 2:1111 raise TemplateSyntaxError("'%s' takes at least one argument"1112 " (path to a view)" % bits[0])1113 viewname = bits[1]1114 args = []1115 kwargs = {}1116 asvar = None1117 1118 if len(bits) > 2:1119 bits = iter(bits[2:])1120 for bit in bits:1121 if bit == 'as':1122 asvar = bits.next()1123 break1124 else:1125 for arg in bit.split(","):1126 if '=' in arg:1127 k, v = arg.split('=', 1)1128 k = k.strip()1129 kwargs[k] = parser.compile_filter(v)1130 elif arg:1131 args.append(parser.compile_filter(arg))1132 return URLNode(viewname, args, kwargs, asvar)1133 url = register.tag(url)1134 1135 #@register.tag1136 def widthratio(parser, token):1137 """1138 For creating bar charts and such, this tag calculates the ratio of a given1139 value to a maximum value, and then applies that ratio to a constant.1140 1141 For example::1142 1143 <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />1144 1145 Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in1146 the above example will be 88 pixels wide (because 175/200 = .875;1147 .875 * 100 = 87.5 which is rounded up to 88).1148 """1149 bits = token.contents.split()1150 if len(bits) != 4:1151 raise TemplateSyntaxError("widthratio takes three arguments")1152 tag, this_value_expr, max_value_expr, max_width = bits1153 1154 return WidthRatioNode(parser.compile_filter(this_value_expr),1155 parser.compile_filter(max_value_expr),1156 parser.compile_filter(max_width))1157 widthratio = register.tag(widthratio)1158 1159 #@register.tag1160 def do_with(parser, token):1161 """1162 Adds a value to the context (inside of this block) for caching and easy1163 access.1164 1165 For example::1166 1167 {% with person.some_sql_method as total %}1168 {{ total }} object{{ total|pluralize }}1169 {% endwith %}1170 """1171 bits = list(token.split_contents())1172 if len(bits) != 4 or bits[2] != "as":1173 raise TemplateSyntaxError("%r expected format is 'value as name'" %1174 bits[0])1175 var = parser.compile_filter(bits[1])1176 name = bits[3]1177 nodelist = parser.parse(('endwith',))1178 parser.delete_first_token()1179 return WithNode(var, name, nodelist)1180 do_with = register.tag('with', do_with) -
django/templatetags/__init__.py
diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py index 4033e5a..8bc9454 100644
a b 1 import imp 2 1 3 from django.conf import settings 2 4 from django.utils import importlib 3 5 4 for a in settings.INSTALLED_APPS: 5 try: 6 __path__.extend(importlib.import_module('.templatetags', a).__path__) 7 except ImportError: 8 pass 6 templatetags_modules= [] 7 8 def get_templatetags_modules(): 9 if not templatetags_modules: 10 """ Populate list once per thread. """ 11 for app_module in ['django'] + list(settings.INSTALLED_APPS): 12 try: 13 components = app_module.split('.') 14 mod = __import__(app_module, {}, {}, [components[-1]]) 15 imp.find_module('templatetags', mod.__path__) 16 templatetag_module = '%s.templatetags' % app_module 17 templatetags_modules.append(templatetag_module) 18 except ImportError: 19 continue 20 return templatetags_modules -
new file django/templatetags/defaultfilters.py
diff --git a/django/templatetags/defaultfilters.py b/django/templatetags/defaultfilters.py new file mode 100644 index 0000000..0ba16bc
- + 1 """Default variable filters.""" 2 3 import re 4 5 try: 6 from decimal import Decimal, InvalidOperation, ROUND_HALF_UP 7 except ImportError: 8 from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP 9 10 import random as random_module 11 try: 12 from functools import wraps 13 except ImportError: 14 from django.utils.functional import wraps # Python 2.3, 2.4 fallback. 15 16 from django.template import Variable, Library 17 from django.conf import settings 18 from django.utils import formats 19 from django.utils.translation import ugettext, ungettext 20 from django.utils.encoding import force_unicode, iri_to_uri 21 from django.utils.safestring import mark_safe, SafeData 22 23 register = Library() 24 25 ####################### 26 # STRING DECORATOR # 27 ####################### 28 29 def stringfilter(func): 30 """ 31 Decorator for filters which should only receive unicode objects. The object 32 passed as the first positional argument will be converted to a unicode 33 object. 34 """ 35 def _dec(*args, **kwargs): 36 if args: 37 args = list(args) 38 args[0] = force_unicode(args[0]) 39 if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False): 40 return mark_safe(func(*args, **kwargs)) 41 return func(*args, **kwargs) 42 43 # Include a reference to the real function (used to check original 44 # arguments by the template parser). 45 _dec._decorated_function = getattr(func, '_decorated_function', func) 46 for attr in ('is_safe', 'needs_autoescape'): 47 if hasattr(func, attr): 48 setattr(_dec, attr, getattr(func, attr)) 49 return wraps(func)(_dec) 50 51 ################### 52 # STRINGS # 53 ################### 54 55 def addslashes(value): 56 """ 57 Adds slashes before quotes. Useful for escaping strings in CSV, for 58 example. Less useful for escaping JavaScript; use the ``escapejs`` 59 filter instead. 60 """ 61 return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") 62 addslashes.is_safe = True 63 addslashes = stringfilter(addslashes) 64 65 def capfirst(value): 66 """Capitalizes the first character of the value.""" 67 return value and value[0].upper() + value[1:] 68 capfirst.is_safe=True 69 capfirst = stringfilter(capfirst) 70 71 _base_js_escapes = ( 72 ('\\', r'\x5C'), 73 ('\'', r'\x27'), 74 ('"', r'\x22'), 75 ('>', r'\x3E'), 76 ('<', r'\x3C'), 77 ('&', r'\x26'), 78 ('=', r'\x3D'), 79 ('-', r'\x2D'), 80 (';', r'\x3B'), 81 (u'\u2028', r'\u2028'), 82 (u'\u2029', r'\u2029') 83 ) 84 85 # Escape every ASCII character with a value less than 32. 86 _js_escapes = (_base_js_escapes + 87 tuple([('%c' % z, '\\x%02X' % z) for z in range(32)])) 88 89 def escapejs(value): 90 """Hex encodes characters for use in JavaScript strings.""" 91 for bad, good in _js_escapes: 92 value = value.replace(bad, good) 93 return value 94 escapejs = stringfilter(escapejs) 95 96 def fix_ampersands(value): 97 """Replaces ampersands with ``&`` entities.""" 98 from django.utils.html import fix_ampersands 99 return fix_ampersands(value) 100 fix_ampersands.is_safe=True 101 fix_ampersands = stringfilter(fix_ampersands) 102 103 # Values for testing floatformat input against infinity and NaN representations, 104 # which differ across platforms and Python versions. Some (i.e. old Windows 105 # ones) are not recognized by Decimal but we want to return them unchanged vs. 106 # returning an empty string as we do for completley invalid input. Note these 107 # need to be built up from values that are not inf/nan, since inf/nan values do 108 # not reload properly from .pyc files on Windows prior to some level of Python 2.5 109 # (see Python Issue757815 and Issue1080440). 110 pos_inf = 1e200 * 1e200 111 neg_inf = -1e200 * 1e200 112 nan = (1e200 * 1e200) / (1e200 * 1e200) 113 special_floats = [str(pos_inf), str(neg_inf), str(nan)] 114 115 def floatformat(text, arg=-1): 116 """ 117 Displays a float to a specified number of decimal places. 118 119 If called without an argument, it displays the floating point number with 120 one decimal place -- but only if there's a decimal place to be displayed: 121 122 * num1 = 34.23234 123 * num2 = 34.00000 124 * num3 = 34.26000 125 * {{ num1|floatformat }} displays "34.2" 126 * {{ num2|floatformat }} displays "34" 127 * {{ num3|floatformat }} displays "34.3" 128 129 If arg is positive, it will always display exactly arg number of decimal 130 places: 131 132 * {{ num1|floatformat:3 }} displays "34.232" 133 * {{ num2|floatformat:3 }} displays "34.000" 134 * {{ num3|floatformat:3 }} displays "34.260" 135 136 If arg is negative, it will display arg number of decimal places -- but 137 only if there are places to be displayed: 138 139 * {{ num1|floatformat:"-3" }} displays "34.232" 140 * {{ num2|floatformat:"-3" }} displays "34" 141 * {{ num3|floatformat:"-3" }} displays "34.260" 142 143 If the input float is infinity or NaN, the (platform-dependent) string 144 representation of that value will be displayed. 145 """ 146 147 try: 148 input_val = force_unicode(text) 149 d = Decimal(input_val) 150 except UnicodeEncodeError: 151 return u'' 152 except InvalidOperation: 153 if input_val in special_floats: 154 return input_val 155 try: 156 d = Decimal(force_unicode(float(text))) 157 except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError): 158 return u'' 159 try: 160 p = int(arg) 161 except ValueError: 162 return input_val 163 164 try: 165 m = int(d) - d 166 except (ValueError, OverflowError, InvalidOperation): 167 return input_val 168 169 if not m and p < 0: 170 return mark_safe(formats.number_format(u'%d' % (int(d)), 0)) 171 172 if p == 0: 173 exp = Decimal(1) 174 else: 175 exp = Decimal('1.0') / (Decimal(10) ** abs(p)) 176 try: 177 return mark_safe(formats.number_format(u'%s' % str(d.quantize(exp, ROUND_HALF_UP)), abs(p))) 178 except InvalidOperation: 179 return input_val 180 floatformat.is_safe = True 181 182 def iriencode(value): 183 """Escapes an IRI value for use in a URL.""" 184 return force_unicode(iri_to_uri(value)) 185 iriencode.is_safe = True 186 iriencode = stringfilter(iriencode) 187 188 def linenumbers(value, autoescape=None): 189 """Displays text with line numbers.""" 190 from django.utils.html import escape 191 lines = value.split(u'\n') 192 # Find the maximum width of the line count, for use with zero padding 193 # string format command 194 width = unicode(len(unicode(len(lines)))) 195 if not autoescape or isinstance(value, SafeData): 196 for i, line in enumerate(lines): 197 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) 198 else: 199 for i, line in enumerate(lines): 200 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) 201 return mark_safe(u'\n'.join(lines)) 202 linenumbers.is_safe = True 203 linenumbers.needs_autoescape = True 204 linenumbers = stringfilter(linenumbers) 205 206 def lower(value): 207 """Converts a string into all lowercase.""" 208 return value.lower() 209 lower.is_safe = True 210 lower = stringfilter(lower) 211 212 def make_list(value): 213 """ 214 Returns the value turned into a list. 215 216 For an integer, it's a list of digits. 217 For a string, it's a list of characters. 218 """ 219 return list(value) 220 make_list.is_safe = False 221 make_list = stringfilter(make_list) 222 223 def slugify(value): 224 """ 225 Normalizes string, converts to lowercase, removes non-alpha characters, 226 and converts spaces to hyphens. 227 """ 228 import unicodedata 229 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') 230 value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) 231 return mark_safe(re.sub('[-\s]+', '-', value)) 232 slugify.is_safe = True 233 slugify = stringfilter(slugify) 234 235 def stringformat(value, arg): 236 """ 237 Formats the variable according to the arg, a string formatting specifier. 238 239 This specifier uses Python string formating syntax, with the exception that 240 the leading "%" is dropped. 241 242 See http://docs.python.org/lib/typesseq-strings.html for documentation 243 of Python string formatting 244 """ 245 try: 246 return (u"%" + unicode(arg)) % value 247 except (ValueError, TypeError): 248 return u"" 249 stringformat.is_safe = True 250 251 def title(value): 252 """Converts a string into titlecase.""" 253 t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 254 return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t) 255 title.is_safe = True 256 title = stringfilter(title) 257 258 def truncatewords(value, arg): 259 """ 260 Truncates a string after a certain number of words. 261 262 Argument: Number of words to truncate after. 263 """ 264 from django.utils.text import truncate_words 265 try: 266 length = int(arg) 267 except ValueError: # Invalid literal for int(). 268 return value # Fail silently. 269 return truncate_words(value, length) 270 truncatewords.is_safe = True 271 truncatewords = stringfilter(truncatewords) 272 273 def truncatewords_html(value, arg): 274 """ 275 Truncates HTML after a certain number of words. 276 277 Argument: Number of words to truncate after. 278 """ 279 from django.utils.text import truncate_html_words 280 try: 281 length = int(arg) 282 except ValueError: # invalid literal for int() 283 return value # Fail silently. 284 return truncate_html_words(value, length) 285 truncatewords_html.is_safe = True 286 truncatewords_html = stringfilter(truncatewords_html) 287 288 def upper(value): 289 """Converts a string into all uppercase.""" 290 return value.upper() 291 upper.is_safe = False 292 upper = stringfilter(upper) 293 294 def urlencode(value): 295 """Escapes a value for use in a URL.""" 296 from django.utils.http import urlquote 297 return urlquote(value) 298 urlencode.is_safe = False 299 urlencode = stringfilter(urlencode) 300 301 def urlize(value, autoescape=None): 302 """Converts URLs in plain text into clickable links.""" 303 from django.utils.html import urlize 304 return mark_safe(urlize(value, nofollow=True, autoescape=autoescape)) 305 urlize.is_safe=True 306 urlize.needs_autoescape = True 307 urlize = stringfilter(urlize) 308 309 def urlizetrunc(value, limit, autoescape=None): 310 """ 311 Converts URLs into clickable links, truncating URLs to the given character 312 limit, and adding 'rel=nofollow' attribute to discourage spamming. 313 314 Argument: Length to truncate URLs to. 315 """ 316 from django.utils.html import urlize 317 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True, 318 autoescape=autoescape)) 319 urlizetrunc.is_safe = True 320 urlizetrunc.needs_autoescape = True 321 urlizetrunc = stringfilter(urlizetrunc) 322 323 def wordcount(value): 324 """Returns the number of words.""" 325 return len(value.split()) 326 wordcount.is_safe = False 327 wordcount = stringfilter(wordcount) 328 329 def wordwrap(value, arg): 330 """ 331 Wraps words at specified line length. 332 333 Argument: number of characters to wrap the text at. 334 """ 335 from django.utils.text import wrap 336 return wrap(value, int(arg)) 337 wordwrap.is_safe = True 338 wordwrap = stringfilter(wordwrap) 339 340 def ljust(value, arg): 341 """ 342 Left-aligns the value in a field of a given width. 343 344 Argument: field size. 345 """ 346 return value.ljust(int(arg)) 347 ljust.is_safe = True 348 ljust = stringfilter(ljust) 349 350 def rjust(value, arg): 351 """ 352 Right-aligns the value in a field of a given width. 353 354 Argument: field size. 355 """ 356 return value.rjust(int(arg)) 357 rjust.is_safe = True 358 rjust = stringfilter(rjust) 359 360 def center(value, arg): 361 """Centers the value in a field of a given width.""" 362 return value.center(int(arg)) 363 center.is_safe = True 364 center = stringfilter(center) 365 366 def cut(value, arg): 367 """ 368 Removes all values of arg from the given string. 369 """ 370 safe = isinstance(value, SafeData) 371 value = value.replace(arg, u'') 372 if safe and arg != ';': 373 return mark_safe(value) 374 return value 375 cut = stringfilter(cut) 376 377 ################### 378 # HTML STRINGS # 379 ################### 380 381 def escape(value): 382 """ 383 Marks the value as a string that should not be auto-escaped. 384 """ 385 from django.utils.safestring import mark_for_escaping 386 return mark_for_escaping(value) 387 escape.is_safe = True 388 escape = stringfilter(escape) 389 390 def force_escape(value): 391 """ 392 Escapes a string's HTML. This returns a new string containing the escaped 393 characters (as opposed to "escape", which marks the content for later 394 possible escaping). 395 """ 396 from django.utils.html import escape 397 return mark_safe(escape(value)) 398 force_escape = stringfilter(force_escape) 399 force_escape.is_safe = True 400 401 def linebreaks(value, autoescape=None): 402 """ 403 Replaces line breaks in plain text with appropriate HTML; a single 404 newline becomes an HTML line break (``<br />``) and a new line 405 followed by a blank line becomes a paragraph break (``</p>``). 406 """ 407 from django.utils.html import linebreaks 408 autoescape = autoescape and not isinstance(value, SafeData) 409 return mark_safe(linebreaks(value, autoescape)) 410 linebreaks.is_safe = True 411 linebreaks.needs_autoescape = True 412 linebreaks = stringfilter(linebreaks) 413 414 def linebreaksbr(value, autoescape=None): 415 """ 416 Converts all newlines in a piece of plain text to HTML line breaks 417 (``<br />``). 418 """ 419 if autoescape and not isinstance(value, SafeData): 420 from django.utils.html import escape 421 value = escape(value) 422 return mark_safe(value.replace('\n', '<br />')) 423 linebreaksbr.is_safe = True 424 linebreaksbr.needs_autoescape = True 425 linebreaksbr = stringfilter(linebreaksbr) 426 427 def safe(value): 428 """ 429 Marks the value as a string that should not be auto-escaped. 430 """ 431 return mark_safe(value) 432 safe.is_safe = True 433 safe = stringfilter(safe) 434 435 def safeseq(value): 436 """ 437 A "safe" filter for sequences. Marks each element in the sequence, 438 individually, as safe, after converting them to unicode. Returns a list 439 with the results. 440 """ 441 return [mark_safe(force_unicode(obj)) for obj in value] 442 safeseq.is_safe = True 443 444 def removetags(value, tags): 445 """Removes a space separated list of [X]HTML tags from the output.""" 446 tags = [re.escape(tag) for tag in tags.split()] 447 tags_re = u'(%s)' % u'|'.join(tags) 448 starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) 449 endtag_re = re.compile(u'</%s>' % tags_re) 450 value = starttag_re.sub(u'', value) 451 value = endtag_re.sub(u'', value) 452 return value 453 removetags.is_safe = True 454 removetags = stringfilter(removetags) 455 456 def striptags(value): 457 """Strips all [X]HTML tags.""" 458 from django.utils.html import strip_tags 459 return strip_tags(value) 460 striptags.is_safe = True 461 striptags = stringfilter(striptags) 462 463 ################### 464 # LISTS # 465 ################### 466 467 def dictsort(value, arg): 468 """ 469 Takes a list of dicts, returns that list sorted by the property given in 470 the argument. 471 """ 472 var_resolve = Variable(arg).resolve 473 decorated = [(var_resolve(item), item) for item in value] 474 decorated.sort() 475 return [item[1] for item in decorated] 476 dictsort.is_safe = False 477 478 def dictsortreversed(value, arg): 479 """ 480 Takes a list of dicts, returns that list sorted in reverse order by the 481 property given in the argument. 482 """ 483 var_resolve = Variable(arg).resolve 484 decorated = [(var_resolve(item), item) for item in value] 485 decorated.sort() 486 decorated.reverse() 487 return [item[1] for item in decorated] 488 dictsortreversed.is_safe = False 489 490 def first(value): 491 """Returns the first item in a list.""" 492 try: 493 return value[0] 494 except IndexError: 495 return u'' 496 first.is_safe = False 497 498 def join(value, arg, autoescape=None): 499 """ 500 Joins a list with a string, like Python's ``str.join(list)``. 501 """ 502 value = map(force_unicode, value) 503 if autoescape: 504 from django.utils.html import conditional_escape 505 value = [conditional_escape(v) for v in value] 506 try: 507 data = arg.join(value) 508 except AttributeError: # fail silently but nicely 509 return value 510 return mark_safe(data) 511 join.is_safe = True 512 join.needs_autoescape = True 513 514 def last(value): 515 "Returns the last item in a list" 516 try: 517 return value[-1] 518 except IndexError: 519 return u'' 520 last.is_safe = True 521 522 def length(value): 523 """Returns the length of the value - useful for lists.""" 524 try: 525 return len(value) 526 except (ValueError, TypeError): 527 return '' 528 length.is_safe = True 529 530 def length_is(value, arg): 531 """Returns a boolean of whether the value's length is the argument.""" 532 try: 533 return len(value) == int(arg) 534 except (ValueError, TypeError): 535 return '' 536 length_is.is_safe = False 537 538 def random(value): 539 """Returns a random item from the list.""" 540 return random_module.choice(value) 541 random.is_safe = True 542 543 def slice_(value, arg): 544 """ 545 Returns a slice of the list. 546 547 Uses the same syntax as Python's list slicing; see 548 http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice 549 for an introduction. 550 """ 551 try: 552 bits = [] 553 for x in arg.split(u':'): 554 if len(x) == 0: 555 bits.append(None) 556 else: 557 bits.append(int(x)) 558 return value[slice(*bits)] 559 560 except (ValueError, TypeError): 561 return value # Fail silently. 562 slice_.is_safe = True 563 564 def unordered_list(value, autoescape=None): 565 """ 566 Recursively takes a self-nested list and returns an HTML unordered list -- 567 WITHOUT opening and closing <ul> tags. 568 569 The list is assumed to be in the proper format. For example, if ``var`` 570 contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, 571 then ``{{ var|unordered_list }}`` would return:: 572 573 <li>States 574 <ul> 575 <li>Kansas 576 <ul> 577 <li>Lawrence</li> 578 <li>Topeka</li> 579 </ul> 580 </li> 581 <li>Illinois</li> 582 </ul> 583 </li> 584 """ 585 if autoescape: 586 from django.utils.html import conditional_escape 587 escaper = conditional_escape 588 else: 589 escaper = lambda x: x 590 def convert_old_style_list(list_): 591 """ 592 Converts old style lists to the new easier to understand format. 593 594 The old list format looked like: 595 ['Item 1', [['Item 1.1', []], ['Item 1.2', []]] 596 597 And it is converted to: 598 ['Item 1', ['Item 1.1', 'Item 1.2]] 599 """ 600 if not isinstance(list_, (tuple, list)) or len(list_) != 2: 601 return list_, False 602 first_item, second_item = list_ 603 if second_item == []: 604 return [first_item], True 605 old_style_list = True 606 new_second_item = [] 607 for sublist in second_item: 608 item, old_style_list = convert_old_style_list(sublist) 609 if not old_style_list: 610 break 611 new_second_item.extend(item) 612 if old_style_list: 613 second_item = new_second_item 614 return [first_item, second_item], old_style_list 615 def _helper(list_, tabs=1): 616 indent = u'\t' * tabs 617 output = [] 618 619 list_length = len(list_) 620 i = 0 621 while i < list_length: 622 title = list_[i] 623 sublist = '' 624 sublist_item = None 625 if isinstance(title, (list, tuple)): 626 sublist_item = title 627 title = '' 628 elif i < list_length - 1: 629 next_item = list_[i+1] 630 if next_item and isinstance(next_item, (list, tuple)): 631 # The next item is a sub-list. 632 sublist_item = next_item 633 # We've processed the next item now too. 634 i += 1 635 if sublist_item: 636 sublist = _helper(sublist_item, tabs+1) 637 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist, 638 indent, indent) 639 output.append('%s<li>%s%s</li>' % (indent, 640 escaper(force_unicode(title)), sublist)) 641 i += 1 642 return '\n'.join(output) 643 value, converted = convert_old_style_list(value) 644 return mark_safe(_helper(value)) 645 unordered_list.is_safe = True 646 unordered_list.needs_autoescape = True 647 648 ################### 649 # INTEGERS # 650 ################### 651 652 def add(value, arg): 653 """Adds the arg to the value.""" 654 return int(value) + int(arg) 655 add.is_safe = False 656 657 def get_digit(value, arg): 658 """ 659 Given a whole number, returns the requested digit of it, where 1 is the 660 right-most digit, 2 is the second-right-most digit, etc. Returns the 661 original value for invalid input (if input or argument is not an integer, 662 or if argument is less than 1). Otherwise, output is always an integer. 663 """ 664 try: 665 arg = int(arg) 666 value = int(value) 667 except ValueError: 668 return value # Fail silently for an invalid argument 669 if arg < 1: 670 return value 671 try: 672 return int(str(value)[-arg]) 673 except IndexError: 674 return 0 675 get_digit.is_safe = False 676 677 ################### 678 # DATES # 679 ################### 680 681 def date(value, arg=None): 682 """Formats a date according to the given format.""" 683 from django.utils.dateformat import format 684 if not value: 685 return u'' 686 if arg is None: 687 arg = settings.DATE_FORMAT 688 try: 689 return formats.date_format(value, arg) 690 except AttributeError: 691 try: 692 return format(value, arg) 693 except AttributeError: 694 return '' 695 date.is_safe = False 696 697 def time(value, arg=None): 698 """Formats a time according to the given format.""" 699 from django.utils import dateformat 700 if value in (None, u''): 701 return u'' 702 if arg is None: 703 arg = settings.TIME_FORMAT 704 try: 705 return formats.time_format(value, arg) 706 except AttributeError: 707 try: 708 return dateformat.time_format(value, arg) 709 except AttributeError: 710 return '' 711 time.is_safe = False 712 713 def timesince(value, arg=None): 714 """Formats a date as the time since that date (i.e. "4 days, 6 hours").""" 715 from django.utils.timesince import timesince 716 if not value: 717 return u'' 718 try: 719 if arg: 720 return timesince(value, arg) 721 return timesince(value) 722 except (ValueError, TypeError): 723 return u'' 724 timesince.is_safe = False 725 726 def timeuntil(value, arg=None): 727 """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" 728 from django.utils.timesince import timeuntil 729 from datetime import datetime 730 if not value: 731 return u'' 732 try: 733 return timeuntil(value, arg) 734 except (ValueError, TypeError): 735 return u'' 736 timeuntil.is_safe = False 737 738 ################### 739 # LOGIC # 740 ################### 741 742 def default(value, arg): 743 """If value is unavailable, use given default.""" 744 return value or arg 745 default.is_safe = False 746 747 def default_if_none(value, arg): 748 """If value is None, use given default.""" 749 if value is None: 750 return arg 751 return value 752 default_if_none.is_safe = False 753 754 def divisibleby(value, arg): 755 """Returns True if the value is devisible by the argument.""" 756 return int(value) % int(arg) == 0 757 divisibleby.is_safe = False 758 759 def yesno(value, arg=None): 760 """ 761 Given a string mapping values for true, false and (optionally) None, 762 returns one of those strings accoding to the value: 763 764 ========== ====================== ================================== 765 Value Argument Outputs 766 ========== ====================== ================================== 767 ``True`` ``"yeah,no,maybe"`` ``yeah`` 768 ``False`` ``"yeah,no,maybe"`` ``no`` 769 ``None`` ``"yeah,no,maybe"`` ``maybe`` 770 ``None`` ``"yeah,no"`` ``"no"`` (converts None to False 771 if no mapping for None is given. 772 ========== ====================== ================================== 773 """ 774 if arg is None: 775 arg = ugettext('yes,no,maybe') 776 bits = arg.split(u',') 777 if len(bits) < 2: 778 return value # Invalid arg. 779 try: 780 yes, no, maybe = bits 781 except ValueError: 782 # Unpack list of wrong size (no "maybe" value provided). 783 yes, no, maybe = bits[0], bits[1], bits[1] 784 if value is None: 785 return maybe 786 if value: 787 return yes 788 return no 789 yesno.is_safe = False 790 791 ################### 792 # MISC # 793 ################### 794 795 def filesizeformat(bytes): 796 """ 797 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 798 102 bytes, etc). 799 """ 800 try: 801 bytes = float(bytes) 802 except TypeError: 803 return u"0 bytes" 804 805 if bytes < 1024: 806 return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} 807 if bytes < 1024 * 1024: 808 return ugettext("%.1f KB") % (bytes / 1024) 809 if bytes < 1024 * 1024 * 1024: 810 return ugettext("%.1f MB") % (bytes / (1024 * 1024)) 811 return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024)) 812 filesizeformat.is_safe = True 813 814 def pluralize(value, arg=u's'): 815 """ 816 Returns a plural suffix if the value is not 1. By default, 's' is used as 817 the suffix: 818 819 * If value is 0, vote{{ value|pluralize }} displays "0 votes". 820 * If value is 1, vote{{ value|pluralize }} displays "1 vote". 821 * If value is 2, vote{{ value|pluralize }} displays "2 votes". 822 823 If an argument is provided, that string is used instead: 824 825 * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes". 826 * If value is 1, class{{ value|pluralize:"es" }} displays "1 class". 827 * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes". 828 829 If the provided argument contains a comma, the text before the comma is 830 used for the singular case and the text after the comma is used for the 831 plural case: 832 833 * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies". 834 * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy". 835 * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies". 836 """ 837 if not u',' in arg: 838 arg = u',' + arg 839 bits = arg.split(u',') 840 if len(bits) > 2: 841 return u'' 842 singular_suffix, plural_suffix = bits[:2] 843 844 try: 845 if int(value) != 1: 846 return plural_suffix 847 except ValueError: # Invalid string that's not a number. 848 pass 849 except TypeError: # Value isn't a string or a number; maybe it's a list? 850 try: 851 if len(value) != 1: 852 return plural_suffix 853 except TypeError: # len() of unsized object. 854 pass 855 return singular_suffix 856 pluralize.is_safe = False 857 858 def phone2numeric(value): 859 """Takes a phone number and converts it in to its numerical equivalent.""" 860 from django.utils.text import phone2numeric 861 return phone2numeric(value) 862 phone2numeric.is_safe = True 863 864 def pprint(value): 865 """A wrapper around pprint.pprint -- for debugging, really.""" 866 from pprint import pformat 867 try: 868 return pformat(value) 869 except Exception, e: 870 return u"Error in formatting: %s" % force_unicode(e, errors="replace") 871 pprint.is_safe = True 872 873 # Syntax: register.filter(name of filter, callback) 874 register.filter(add) 875 register.filter(addslashes) 876 register.filter(capfirst) 877 register.filter(center) 878 register.filter(cut) 879 register.filter(date) 880 register.filter(default) 881 register.filter(default_if_none) 882 register.filter(dictsort) 883 register.filter(dictsortreversed) 884 register.filter(divisibleby) 885 register.filter(escape) 886 register.filter(escapejs) 887 register.filter(filesizeformat) 888 register.filter(first) 889 register.filter(fix_ampersands) 890 register.filter(floatformat) 891 register.filter(force_escape) 892 register.filter(get_digit) 893 register.filter(iriencode) 894 register.filter(join) 895 register.filter(last) 896 register.filter(length) 897 register.filter(length_is) 898 register.filter(linebreaks) 899 register.filter(linebreaksbr) 900 register.filter(linenumbers) 901 register.filter(ljust) 902 register.filter(lower) 903 register.filter(make_list) 904 register.filter(phone2numeric) 905 register.filter(pluralize) 906 register.filter(pprint) 907 register.filter(removetags) 908 register.filter(random) 909 register.filter(rjust) 910 register.filter(safe) 911 register.filter(safeseq) 912 register.filter('slice', slice_) 913 register.filter(slugify) 914 register.filter(stringformat) 915 register.filter(striptags) 916 register.filter(time) 917 register.filter(timesince) 918 register.filter(timeuntil) 919 register.filter(title) 920 register.filter(truncatewords) 921 register.filter(truncatewords_html) 922 register.filter(unordered_list) 923 register.filter(upper) 924 register.filter(urlencode) 925 register.filter(urlize) 926 register.filter(urlizetrunc) 927 register.filter(wordcount) 928 register.filter(wordwrap) 929 register.filter(yesno) -
new file django/templatetags/defaulttags.py
diff --git a/django/templatetags/defaulttags.py b/django/templatetags/defaulttags.py new file mode 100644 index 0000000..f1b1de7
- + 1 """Default tags used by the template system, available to all templates.""" 2 3 import sys 4 import re 5 from itertools import cycle as itertools_cycle 6 try: 7 reversed 8 except NameError: 9 from django.utils.itercompat import reversed # Python 2.3 fallback 10 11 from django.template import Node, NodeList, Template, Context, Variable 12 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 13 from django.template import get_library, Library, InvalidTemplateLibrary 14 from django.template.smartif import IfParser, Literal 15 from django.conf import settings 16 from django.utils.encoding import smart_str, smart_unicode 17 from django.utils.itercompat import groupby 18 from django.utils.safestring import mark_safe 19 20 register = Library() 21 22 class AutoEscapeControlNode(Node): 23 """Implements the actions of the autoescape tag.""" 24 def __init__(self, setting, nodelist): 25 self.setting, self.nodelist = setting, nodelist 26 27 def render(self, context): 28 old_setting = context.autoescape 29 context.autoescape = self.setting 30 output = self.nodelist.render(context) 31 context.autoescape = old_setting 32 if self.setting: 33 return mark_safe(output) 34 else: 35 return output 36 37 class CommentNode(Node): 38 def render(self, context): 39 return '' 40 41 class CsrfTokenNode(Node): 42 def render(self, context): 43 csrf_token = context.get('csrf_token', None) 44 if csrf_token: 45 if csrf_token == 'NOTPROVIDED': 46 return mark_safe(u"") 47 else: 48 return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % (csrf_token)) 49 else: 50 # It's very probable that the token is missing because of 51 # misconfiguration, so we raise a warning 52 from django.conf import settings 53 if settings.DEBUG: 54 import warnings 55 warnings.warn("A {% csrf_token %} was used in a template, but the context did not provide the value. This is usually caused by not using RequestContext.") 56 return u'' 57 58 class CycleNode(Node): 59 def __init__(self, cyclevars, variable_name=None): 60 self.cyclevars = cyclevars 61 self.variable_name = variable_name 62 63 def render(self, context): 64 if self not in context.render_context: 65 context.render_context[self] = itertools_cycle(self.cyclevars) 66 cycle_iter = context.render_context[self] 67 value = cycle_iter.next().resolve(context) 68 if self.variable_name: 69 context[self.variable_name] = value 70 return value 71 72 class DebugNode(Node): 73 def render(self, context): 74 from pprint import pformat 75 output = [pformat(val) for val in context] 76 output.append('\n\n') 77 output.append(pformat(sys.modules)) 78 return ''.join(output) 79 80 class FilterNode(Node): 81 def __init__(self, filter_expr, nodelist): 82 self.filter_expr, self.nodelist = filter_expr, nodelist 83 84 def render(self, context): 85 output = self.nodelist.render(context) 86 # Apply filters. 87 context.update({'var': output}) 88 filtered = self.filter_expr.resolve(context) 89 context.pop() 90 return filtered 91 92 class FirstOfNode(Node): 93 def __init__(self, vars): 94 self.vars = vars 95 96 def render(self, context): 97 for var in self.vars: 98 value = var.resolve(context, True) 99 if value: 100 return smart_unicode(value) 101 return u'' 102 103 class ForNode(Node): 104 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): 105 self.loopvars, self.sequence = loopvars, sequence 106 self.is_reversed = is_reversed 107 self.nodelist_loop = nodelist_loop 108 if nodelist_empty is None: 109 self.nodelist_empty = NodeList() 110 else: 111 self.nodelist_empty = nodelist_empty 112 113 def __repr__(self): 114 reversed_text = self.is_reversed and ' reversed' or '' 115 return "<For Node: for %s in %s, tail_len: %d%s>" % \ 116 (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), 117 reversed_text) 118 119 def __iter__(self): 120 for node in self.nodelist_loop: 121 yield node 122 for node in self.nodelist_empty: 123 yield node 124 125 def get_nodes_by_type(self, nodetype): 126 nodes = [] 127 if isinstance(self, nodetype): 128 nodes.append(self) 129 nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) 130 nodes.extend(self.nodelist_empty.get_nodes_by_type(nodetype)) 131 return nodes 132 133 def render(self, context): 134 if 'forloop' in context: 135 parentloop = context['forloop'] 136 else: 137 parentloop = {} 138 context.push() 139 try: 140 values = self.sequence.resolve(context, True) 141 except VariableDoesNotExist: 142 values = [] 143 if values is None: 144 values = [] 145 if not hasattr(values, '__len__'): 146 values = list(values) 147 len_values = len(values) 148 if len_values < 1: 149 context.pop() 150 return self.nodelist_empty.render(context) 151 nodelist = NodeList() 152 if self.is_reversed: 153 values = reversed(values) 154 unpack = len(self.loopvars) > 1 155 # Create a forloop value in the context. We'll update counters on each 156 # iteration just below. 157 loop_dict = context['forloop'] = {'parentloop': parentloop} 158 for i, item in enumerate(values): 159 # Shortcuts for current loop iteration number. 160 loop_dict['counter0'] = i 161 loop_dict['counter'] = i+1 162 # Reverse counter iteration numbers. 163 loop_dict['revcounter'] = len_values - i 164 loop_dict['revcounter0'] = len_values - i - 1 165 # Boolean values designating first and last times through loop. 166 loop_dict['first'] = (i == 0) 167 loop_dict['last'] = (i == len_values - 1) 168 169 if unpack: 170 # If there are multiple loop variables, unpack the item into 171 # them. 172 context.update(dict(zip(self.loopvars, item))) 173 else: 174 context[self.loopvars[0]] = item 175 for node in self.nodelist_loop: 176 nodelist.append(node.render(context)) 177 if unpack: 178 # The loop variables were pushed on to the context so pop them 179 # off again. This is necessary because the tag lets the length 180 # of loopvars differ to the length of each set of items and we 181 # don't want to leave any vars from the previous loop on the 182 # context. 183 context.pop() 184 context.pop() 185 return nodelist.render(context) 186 187 class IfChangedNode(Node): 188 def __init__(self, nodelist_true, nodelist_false, *varlist): 189 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 190 self._last_seen = None 191 self._varlist = varlist 192 self._id = str(id(self)) 193 194 def render(self, context): 195 if 'forloop' in context and self._id not in context['forloop']: 196 self._last_seen = None 197 context['forloop'][self._id] = 1 198 try: 199 if self._varlist: 200 # Consider multiple parameters. This automatically behaves 201 # like an OR evaluation of the multiple variables. 202 compare_to = [var.resolve(context, True) for var in self._varlist] 203 else: 204 compare_to = self.nodelist_true.render(context) 205 except VariableDoesNotExist: 206 compare_to = None 207 208 if compare_to != self._last_seen: 209 firstloop = (self._last_seen == None) 210 self._last_seen = compare_to 211 content = self.nodelist_true.render(context) 212 return content 213 elif self.nodelist_false: 214 return self.nodelist_false.render(context) 215 return '' 216 217 class IfEqualNode(Node): 218 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 219 self.var1, self.var2 = var1, var2 220 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 221 self.negate = negate 222 223 def __repr__(self): 224 return "<IfEqualNode>" 225 226 def render(self, context): 227 val1 = self.var1.resolve(context, True) 228 val2 = self.var2.resolve(context, True) 229 if (self.negate and val1 != val2) or (not self.negate and val1 == val2): 230 return self.nodelist_true.render(context) 231 return self.nodelist_false.render(context) 232 233 class IfNode(Node): 234 def __init__(self, var, nodelist_true, nodelist_false=None): 235 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 236 self.var = var 237 238 def __repr__(self): 239 return "<If node>" 240 241 def __iter__(self): 242 for node in self.nodelist_true: 243 yield node 244 for node in self.nodelist_false: 245 yield node 246 247 def get_nodes_by_type(self, nodetype): 248 nodes = [] 249 if isinstance(self, nodetype): 250 nodes.append(self) 251 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 252 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 253 return nodes 254 255 def render(self, context): 256 if self.var.eval(context): 257 return self.nodelist_true.render(context) 258 else: 259 return self.nodelist_false.render(context) 260 261 class RegroupNode(Node): 262 def __init__(self, target, expression, var_name): 263 self.target, self.expression = target, expression 264 self.var_name = var_name 265 266 def render(self, context): 267 obj_list = self.target.resolve(context, True) 268 if obj_list == None: 269 # target variable wasn't found in context; fail silently. 270 context[self.var_name] = [] 271 return '' 272 # List of dictionaries in the format: 273 # {'grouper': 'key', 'list': [list of contents]}. 274 context[self.var_name] = [ 275 {'grouper': key, 'list': list(val)} 276 for key, val in 277 groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) 278 ] 279 return '' 280 281 def include_is_allowed(filepath): 282 for root in settings.ALLOWED_INCLUDE_ROOTS: 283 if filepath.startswith(root): 284 return True 285 return False 286 287 class SsiNode(Node): 288 def __init__(self, filepath, parsed): 289 self.filepath, self.parsed = filepath, parsed 290 291 def render(self, context): 292 if not include_is_allowed(self.filepath): 293 if settings.DEBUG: 294 return "[Didn't have permission to include file]" 295 else: 296 return '' # Fail silently for invalid includes. 297 try: 298 fp = open(self.filepath, 'r') 299 output = fp.read() 300 fp.close() 301 except IOError: 302 output = '' 303 if self.parsed: 304 try: 305 t = Template(output, name=self.filepath) 306 return t.render(context) 307 except TemplateSyntaxError, e: 308 if settings.DEBUG: 309 return "[Included template had syntax error: %s]" % e 310 else: 311 return '' # Fail silently for invalid included templates. 312 return output 313 314 class LoadNode(Node): 315 def render(self, context): 316 return '' 317 318 class NowNode(Node): 319 def __init__(self, format_string): 320 self.format_string = format_string 321 322 def render(self, context): 323 from datetime import datetime 324 from django.utils.dateformat import DateFormat 325 df = DateFormat(datetime.now()) 326 return df.format(self.format_string) 327 328 class SpacelessNode(Node): 329 def __init__(self, nodelist): 330 self.nodelist = nodelist 331 332 def render(self, context): 333 from django.utils.html import strip_spaces_between_tags 334 return strip_spaces_between_tags(self.nodelist.render(context).strip()) 335 336 class TemplateTagNode(Node): 337 mapping = {'openblock': BLOCK_TAG_START, 338 'closeblock': BLOCK_TAG_END, 339 'openvariable': VARIABLE_TAG_START, 340 'closevariable': VARIABLE_TAG_END, 341 'openbrace': SINGLE_BRACE_START, 342 'closebrace': SINGLE_BRACE_END, 343 'opencomment': COMMENT_TAG_START, 344 'closecomment': COMMENT_TAG_END, 345 } 346 347 def __init__(self, tagtype): 348 self.tagtype = tagtype 349 350 def render(self, context): 351 return self.mapping.get(self.tagtype, '') 352 353 class URLNode(Node): 354 def __init__(self, view_name, args, kwargs, asvar): 355 self.view_name = view_name 356 self.args = args 357 self.kwargs = kwargs 358 self.asvar = asvar 359 360 def render(self, context): 361 from django.core.urlresolvers import reverse, NoReverseMatch 362 args = [arg.resolve(context) for arg in self.args] 363 kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) 364 for k, v in self.kwargs.items()]) 365 366 # Try to look up the URL twice: once given the view name, and again 367 # relative to what we guess is the "main" app. If they both fail, 368 # re-raise the NoReverseMatch unless we're using the 369 # {% url ... as var %} construct in which cause return nothing. 370 url = '' 371 try: 372 url = reverse(self.view_name, args=args, kwargs=kwargs, current_app=context.current_app) 373 except NoReverseMatch, e: 374 if settings.SETTINGS_MODULE: 375 project_name = settings.SETTINGS_MODULE.split('.')[0] 376 try: 377 url = reverse(project_name + '.' + self.view_name, 378 args=args, kwargs=kwargs, current_app=context.current_app) 379 except NoReverseMatch: 380 if self.asvar is None: 381 # Re-raise the original exception, not the one with 382 # the path relative to the project. This makes a 383 # better error message. 384 raise e 385 else: 386 if self.asvar is None: 387 raise e 388 389 if self.asvar: 390 context[self.asvar] = url 391 return '' 392 else: 393 return url 394 395 class WidthRatioNode(Node): 396 def __init__(self, val_expr, max_expr, max_width): 397 self.val_expr = val_expr 398 self.max_expr = max_expr 399 self.max_width = max_width 400 401 def render(self, context): 402 try: 403 value = self.val_expr.resolve(context) 404 maxvalue = self.max_expr.resolve(context) 405 max_width = int(self.max_width.resolve(context)) 406 except VariableDoesNotExist: 407 return '' 408 except ValueError: 409 raise TemplateSyntaxError("widthratio final argument must be an number") 410 try: 411 value = float(value) 412 maxvalue = float(maxvalue) 413 ratio = (value / maxvalue) * max_width 414 except (ValueError, ZeroDivisionError): 415 return '' 416 return str(int(round(ratio))) 417 418 class WithNode(Node): 419 def __init__(self, var, name, nodelist): 420 self.var = var 421 self.name = name 422 self.nodelist = nodelist 423 424 def __repr__(self): 425 return "<WithNode>" 426 427 def render(self, context): 428 val = self.var.resolve(context) 429 context.push() 430 context[self.name] = val 431 output = self.nodelist.render(context) 432 context.pop() 433 return output 434 435 #@register.tag 436 def autoescape(parser, token): 437 """ 438 Force autoescape behaviour for this block. 439 """ 440 args = token.contents.split() 441 if len(args) != 2: 442 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") 443 arg = args[1] 444 if arg not in (u'on', u'off'): 445 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") 446 nodelist = parser.parse(('endautoescape',)) 447 parser.delete_first_token() 448 return AutoEscapeControlNode((arg == 'on'), nodelist) 449 autoescape = register.tag(autoescape) 450 451 #@register.tag 452 def comment(parser, token): 453 """ 454 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. 455 """ 456 parser.skip_past('endcomment') 457 return CommentNode() 458 comment = register.tag(comment) 459 460 #@register.tag 461 def cycle(parser, token): 462 """ 463 Cycles among the given strings each time this tag is encountered. 464 465 Within a loop, cycles among the given strings each time through 466 the loop:: 467 468 {% for o in some_list %} 469 <tr class="{% cycle 'row1' 'row2' %}"> 470 ... 471 </tr> 472 {% endfor %} 473 474 Outside of a loop, give the values a unique name the first time you call 475 it, then use that name each sucessive time through:: 476 477 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> 478 <tr class="{% cycle rowcolors %}">...</tr> 479 <tr class="{% cycle rowcolors %}">...</tr> 480 481 You can use any number of values, separated by spaces. Commas can also 482 be used to separate values; if a comma is used, the cycle values are 483 interpreted as literal strings. 484 """ 485 486 # Note: This returns the exact same node on each {% cycle name %} call; 487 # that is, the node object returned from {% cycle a b c as name %} and the 488 # one returned from {% cycle name %} are the exact same object. This 489 # shouldn't cause problems (heh), but if it does, now you know. 490 # 491 # Ugly hack warning: This stuffs the named template dict into parser so 492 # that names are only unique within each template (as opposed to using 493 # a global variable, which would make cycle names have to be unique across 494 # *all* templates. 495 496 args = token.split_contents() 497 498 if len(args) < 2: 499 raise TemplateSyntaxError("'cycle' tag requires at least two arguments") 500 501 if ',' in args[1]: 502 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} 503 # case. 504 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] 505 506 if len(args) == 2: 507 # {% cycle foo %} case. 508 name = args[1] 509 if not hasattr(parser, '_namedCycleNodes'): 510 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) 511 if not name in parser._namedCycleNodes: 512 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) 513 return parser._namedCycleNodes[name] 514 515 if len(args) > 4 and args[-2] == 'as': 516 name = args[-1] 517 values = [parser.compile_filter(arg) for arg in args[1:-2]] 518 node = CycleNode(values, name) 519 if not hasattr(parser, '_namedCycleNodes'): 520 parser._namedCycleNodes = {} 521 parser._namedCycleNodes[name] = node 522 else: 523 values = [parser.compile_filter(arg) for arg in args[1:]] 524 node = CycleNode(values) 525 return node 526 cycle = register.tag(cycle) 527 528 def csrf_token(parser, token): 529 return CsrfTokenNode() 530 register.tag(csrf_token) 531 532 def debug(parser, token): 533 """ 534 Outputs a whole load of debugging information, including the current 535 context and imported modules. 536 537 Sample usage:: 538 539 <pre> 540 {% debug %} 541 </pre> 542 """ 543 return DebugNode() 544 debug = register.tag(debug) 545 546 #@register.tag(name="filter") 547 def do_filter(parser, token): 548 """ 549 Filters the contents of the block through variable filters. 550 551 Filters can also be piped through each other, and they can have 552 arguments -- just like in variable syntax. 553 554 Sample usage:: 555 556 {% filter force_escape|lower %} 557 This text will be HTML-escaped, and will appear in lowercase. 558 {% endfilter %} 559 """ 560 _, rest = token.contents.split(None, 1) 561 filter_expr = parser.compile_filter("var|%s" % (rest)) 562 for func, unused in filter_expr.filters: 563 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 564 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 565 nodelist = parser.parse(('endfilter',)) 566 parser.delete_first_token() 567 return FilterNode(filter_expr, nodelist) 568 do_filter = register.tag("filter", do_filter) 569 570 #@register.tag 571 def firstof(parser, token): 572 """ 573 Outputs the first variable passed that is not False, without escaping. 574 575 Outputs nothing if all the passed variables are False. 576 577 Sample usage:: 578 579 {% firstof var1 var2 var3 %} 580 581 This is equivalent to:: 582 583 {% if var1 %} 584 {{ var1|safe }} 585 {% else %}{% if var2 %} 586 {{ var2|safe }} 587 {% else %}{% if var3 %} 588 {{ var3|safe }} 589 {% endif %}{% endif %}{% endif %} 590 591 but obviously much cleaner! 592 593 You can also use a literal string as a fallback value in case all 594 passed variables are False:: 595 596 {% firstof var1 var2 var3 "fallback value" %} 597 598 If you want to escape the output, use a filter tag:: 599 600 {% filter force_escape %} 601 {% firstof var1 var2 var3 "fallback value" %} 602 {% endfilter %} 603 604 """ 605 bits = token.split_contents()[1:] 606 if len(bits) < 1: 607 raise TemplateSyntaxError("'firstof' statement requires at least one argument") 608 return FirstOfNode([parser.compile_filter(bit) for bit in bits]) 609 firstof = register.tag(firstof) 610 611 #@register.tag(name="for") 612 def do_for(parser, token): 613 """ 614 Loops over each item in an array. 615 616 For example, to display a list of athletes given ``athlete_list``:: 617 618 <ul> 619 {% for athlete in athlete_list %} 620 <li>{{ athlete.name }}</li> 621 {% endfor %} 622 </ul> 623 624 You can loop over a list in reverse by using 625 ``{% for obj in list reversed %}``. 626 627 You can also unpack multiple values from a two-dimensional array:: 628 629 {% for key,value in dict.items %} 630 {{ key }}: {{ value }} 631 {% endfor %} 632 633 The ``for`` tag can take an optional ``{% empty %}`` clause that will 634 be displayed if the given array is empty or could not be found:: 635 636 <ul> 637 {% for athlete in athlete_list %} 638 <li>{{ athlete.name }}</li> 639 {% empty %} 640 <li>Sorry, no athletes in this list.</li> 641 {% endfor %} 642 <ul> 643 644 The above is equivalent to -- but shorter, cleaner, and possibly faster 645 than -- the following:: 646 647 <ul> 648 {% if althete_list %} 649 {% for athlete in athlete_list %} 650 <li>{{ athlete.name }}</li> 651 {% endfor %} 652 {% else %} 653 <li>Sorry, no athletes in this list.</li> 654 {% endif %} 655 </ul> 656 657 The for loop sets a number of variables available within the loop: 658 659 ========================== ================================================ 660 Variable Description 661 ========================== ================================================ 662 ``forloop.counter`` The current iteration of the loop (1-indexed) 663 ``forloop.counter0`` The current iteration of the loop (0-indexed) 664 ``forloop.revcounter`` The number of iterations from the end of the 665 loop (1-indexed) 666 ``forloop.revcounter0`` The number of iterations from the end of the 667 loop (0-indexed) 668 ``forloop.first`` True if this is the first time through the loop 669 ``forloop.last`` True if this is the last time through the loop 670 ``forloop.parentloop`` For nested loops, this is the loop "above" the 671 current one 672 ========================== ================================================ 673 674 """ 675 bits = token.contents.split() 676 if len(bits) < 4: 677 raise TemplateSyntaxError("'for' statements should have at least four" 678 " words: %s" % token.contents) 679 680 is_reversed = bits[-1] == 'reversed' 681 in_index = is_reversed and -3 or -2 682 if bits[in_index] != 'in': 683 raise TemplateSyntaxError("'for' statements should use the format" 684 " 'for x in y': %s" % token.contents) 685 686 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 687 for var in loopvars: 688 if not var or ' ' in var: 689 raise TemplateSyntaxError("'for' tag received an invalid argument:" 690 " %s" % token.contents) 691 692 sequence = parser.compile_filter(bits[in_index+1]) 693 nodelist_loop = parser.parse(('empty', 'endfor',)) 694 token = parser.next_token() 695 if token.contents == 'empty': 696 nodelist_empty = parser.parse(('endfor',)) 697 parser.delete_first_token() 698 else: 699 nodelist_empty = None 700 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) 701 do_for = register.tag("for", do_for) 702 703 def do_ifequal(parser, token, negate): 704 bits = list(token.split_contents()) 705 if len(bits) != 3: 706 raise TemplateSyntaxError("%r takes two arguments" % bits[0]) 707 end_tag = 'end' + bits[0] 708 nodelist_true = parser.parse(('else', end_tag)) 709 token = parser.next_token() 710 if token.contents == 'else': 711 nodelist_false = parser.parse((end_tag,)) 712 parser.delete_first_token() 713 else: 714 nodelist_false = NodeList() 715 val1 = parser.compile_filter(bits[1]) 716 val2 = parser.compile_filter(bits[2]) 717 return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) 718 719 #@register.tag 720 def ifequal(parser, token): 721 """ 722 Outputs the contents of the block if the two arguments equal each other. 723 724 Examples:: 725 726 {% ifequal user.id comment.user_id %} 727 ... 728 {% endifequal %} 729 730 {% ifnotequal user.id comment.user_id %} 731 ... 732 {% else %} 733 ... 734 {% endifnotequal %} 735 """ 736 return do_ifequal(parser, token, False) 737 ifequal = register.tag(ifequal) 738 739 #@register.tag 740 def ifnotequal(parser, token): 741 """ 742 Outputs the contents of the block if the two arguments are not equal. 743 See ifequal. 744 """ 745 return do_ifequal(parser, token, True) 746 ifnotequal = register.tag(ifnotequal) 747 748 class TemplateLiteral(Literal): 749 def __init__(self, value, text): 750 self.value = value 751 self.text = text # for better error messages 752 753 def display(self): 754 return self.text 755 756 def eval(self, context): 757 return self.value.resolve(context, ignore_failures=True) 758 759 class TemplateIfParser(IfParser): 760 error_class = TemplateSyntaxError 761 762 def __init__(self, parser, *args, **kwargs): 763 self.template_parser = parser 764 return super(TemplateIfParser, self).__init__(*args, **kwargs) 765 766 def create_var(self, value): 767 return TemplateLiteral(self.template_parser.compile_filter(value), value) 768 769 #@register.tag(name="if") 770 def do_if(parser, token): 771 """ 772 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 773 (i.e., exists, is not empty, and is not a false boolean value), the 774 contents of the block are output: 775 776 :: 777 778 {% if athlete_list %} 779 Number of athletes: {{ athlete_list|count }} 780 {% else %} 781 No athletes. 782 {% endif %} 783 784 In the above, if ``athlete_list`` is not empty, the number of athletes will 785 be displayed by the ``{{ athlete_list|count }}`` variable. 786 787 As you can see, the ``if`` tag can take an option ``{% else %}`` clause 788 that will be displayed if the test fails. 789 790 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of 791 variables or to negate a given variable:: 792 793 {% if not athlete_list %} 794 There are no athletes. 795 {% endif %} 796 797 {% if athlete_list or coach_list %} 798 There are some athletes or some coaches. 799 {% endif %} 800 801 {% if athlete_list and coach_list %} 802 Both atheletes and coaches are available. 803 {% endif %} 804 805 {% if not athlete_list or coach_list %} 806 There are no athletes, or there are some coaches. 807 {% endif %} 808 809 {% if athlete_list and not coach_list %} 810 There are some athletes and absolutely no coaches. 811 {% endif %} 812 813 Comparison operators are also available, and the use of filters is also 814 allowed, for example: 815 816 {% if articles|length >= 5 %}...{% endif %} 817 818 Arguments and operators _must_ have a space between them, so 819 ``{% if 1>2 %}`` is not a valid if tag. 820 821 All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``), 822 ``!=``, ``>``, ``>=``, ``<`` and ``<=``. 823 824 Operator precedence follows Python. 825 """ 826 bits = token.split_contents()[1:] 827 var = TemplateIfParser(parser, bits).parse() 828 nodelist_true = parser.parse(('else', 'endif')) 829 token = parser.next_token() 830 if token.contents == 'else': 831 nodelist_false = parser.parse(('endif',)) 832 parser.delete_first_token() 833 else: 834 nodelist_false = NodeList() 835 return IfNode(var, nodelist_true, nodelist_false) 836 do_if = register.tag("if", do_if) 837 838 #@register.tag 839 def ifchanged(parser, token): 840 """ 841 Checks if a value has changed from the last iteration of a loop. 842 843 The 'ifchanged' block tag is used within a loop. It has two possible uses. 844 845 1. Checks its own rendered contents against its previous state and only 846 displays the content if it has changed. For example, this displays a 847 list of days, only displaying the month if it changes:: 848 849 <h1>Archive for {{ year }}</h1> 850 851 {% for date in days %} 852 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 853 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 854 {% endfor %} 855 856 2. If given a variable, check whether that variable has changed. 857 For example, the following shows the date every time it changes, but 858 only shows the hour if both the hour and the date have changed:: 859 860 {% for date in days %} 861 {% ifchanged date.date %} {{ date.date }} {% endifchanged %} 862 {% ifchanged date.hour date.date %} 863 {{ date.hour }} 864 {% endifchanged %} 865 {% endfor %} 866 """ 867 bits = token.contents.split() 868 nodelist_true = parser.parse(('else', 'endifchanged')) 869 token = parser.next_token() 870 if token.contents == 'else': 871 nodelist_false = parser.parse(('endifchanged',)) 872 parser.delete_first_token() 873 else: 874 nodelist_false = NodeList() 875 values = [parser.compile_filter(bit) for bit in bits[1:]] 876 return IfChangedNode(nodelist_true, nodelist_false, *values) 877 ifchanged = register.tag(ifchanged) 878 879 #@register.tag 880 def ssi(parser, token): 881 """ 882 Outputs the contents of a given file into the page. 883 884 Like a simple "include" tag, the ``ssi`` tag includes the contents 885 of another file -- which must be specified using an absolute path -- 886 in the current page:: 887 888 {% ssi /home/html/ljworld.com/includes/right_generic.html %} 889 890 If the optional "parsed" parameter is given, the contents of the included 891 file are evaluated as template code, with the current context:: 892 893 {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} 894 """ 895 bits = token.contents.split() 896 parsed = False 897 if len(bits) not in (2, 3): 898 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" 899 " the file to be included") 900 if len(bits) == 3: 901 if bits[2] == 'parsed': 902 parsed = True 903 else: 904 raise TemplateSyntaxError("Second (optional) argument to %s tag" 905 " must be 'parsed'" % bits[0]) 906 return SsiNode(bits[1], parsed) 907 ssi = register.tag(ssi) 908 909 #@register.tag 910 def load(parser, token): 911 """ 912 Loads a custom template tag set. 913 914 For example, to load the template tags in 915 ``django/templatetags/news/photos.py``:: 916 917 {% load news.photos %} 918 """ 919 bits = token.contents.split() 920 for taglib in bits[1:]: 921 # add the library to the parser 922 try: 923 lib = get_library(taglib) 924 parser.add_library(lib) 925 except InvalidTemplateLibrary, e: 926 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % 927 (taglib, e)) 928 return LoadNode() 929 load = register.tag(load) 930 931 #@register.tag 932 def now(parser, token): 933 """ 934 Displays the date, formatted according to the given string. 935 936 Uses the same format as PHP's ``date()`` function; see http://php.net/date 937 for all the possible values. 938 939 Sample usage:: 940 941 It is {% now "jS F Y H:i" %} 942 """ 943 bits = token.contents.split('"') 944 if len(bits) != 3: 945 raise TemplateSyntaxError("'now' statement takes one argument") 946 format_string = bits[1] 947 return NowNode(format_string) 948 now = register.tag(now) 949 950 #@register.tag 951 def regroup(parser, token): 952 """ 953 Regroups a list of alike objects by a common attribute. 954 955 This complex tag is best illustrated by use of an example: say that 956 ``people`` is a list of ``Person`` objects that have ``first_name``, 957 ``last_name``, and ``gender`` attributes, and you'd like to display a list 958 that looks like: 959 960 * Male: 961 * George Bush 962 * Bill Clinton 963 * Female: 964 * Margaret Thatcher 965 * Colendeeza Rice 966 * Unknown: 967 * Pat Smith 968 969 The following snippet of template code would accomplish this dubious task:: 970 971 {% regroup people by gender as grouped %} 972 <ul> 973 {% for group in grouped %} 974 <li>{{ group.grouper }} 975 <ul> 976 {% for item in group.list %} 977 <li>{{ item }}</li> 978 {% endfor %} 979 </ul> 980 {% endfor %} 981 </ul> 982 983 As you can see, ``{% regroup %}`` populates a variable with a list of 984 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the 985 item that was grouped by; ``list`` contains the list of objects that share 986 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` 987 and ``Unknown``, and ``list`` is the list of people with those genders. 988 989 Note that ``{% regroup %}`` does not work when the list to be grouped is not 990 sorted by the key you are grouping by! This means that if your list of 991 people was not sorted by gender, you'd need to make sure it is sorted 992 before using it, i.e.:: 993 994 {% regroup people|dictsort:"gender" by gender as grouped %} 995 996 """ 997 firstbits = token.contents.split(None, 3) 998 if len(firstbits) != 4: 999 raise TemplateSyntaxError("'regroup' tag takes five arguments") 1000 target = parser.compile_filter(firstbits[1]) 1001 if firstbits[2] != 'by': 1002 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 1003 lastbits_reversed = firstbits[3][::-1].split(None, 2) 1004 if lastbits_reversed[1][::-1] != 'as': 1005 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 1006 " be 'as'") 1007 1008 expression = parser.compile_filter(lastbits_reversed[2][::-1]) 1009 1010 var_name = lastbits_reversed[0][::-1] 1011 return RegroupNode(target, expression, var_name) 1012 regroup = register.tag(regroup) 1013 1014 def spaceless(parser, token): 1015 """ 1016 Removes whitespace between HTML tags, including tab and newline characters. 1017 1018 Example usage:: 1019 1020 {% spaceless %} 1021 <p> 1022 <a href="foo/">Foo</a> 1023 </p> 1024 {% endspaceless %} 1025 1026 This example would return this HTML:: 1027 1028 <p><a href="foo/">Foo</a></p> 1029 1030 Only space between *tags* is normalized -- not space between tags and text. 1031 In this example, the space around ``Hello`` won't be stripped:: 1032 1033 {% spaceless %} 1034 <strong> 1035 Hello 1036 </strong> 1037 {% endspaceless %} 1038 """ 1039 nodelist = parser.parse(('endspaceless',)) 1040 parser.delete_first_token() 1041 return SpacelessNode(nodelist) 1042 spaceless = register.tag(spaceless) 1043 1044 #@register.tag 1045 def templatetag(parser, token): 1046 """ 1047 Outputs one of the bits used to compose template tags. 1048 1049 Since the template system has no concept of "escaping", to display one of 1050 the bits used in template tags, you must use the ``{% templatetag %}`` tag. 1051 1052 The argument tells which template bit to output: 1053 1054 ================== ======= 1055 Argument Outputs 1056 ================== ======= 1057 ``openblock`` ``{%`` 1058 ``closeblock`` ``%}`` 1059 ``openvariable`` ``{{`` 1060 ``closevariable`` ``}}`` 1061 ``openbrace`` ``{`` 1062 ``closebrace`` ``}`` 1063 ``opencomment`` ``{#`` 1064 ``closecomment`` ``#}`` 1065 ================== ======= 1066 """ 1067 bits = token.contents.split() 1068 if len(bits) != 2: 1069 raise TemplateSyntaxError("'templatetag' statement takes one argument") 1070 tag = bits[1] 1071 if tag not in TemplateTagNode.mapping: 1072 raise TemplateSyntaxError("Invalid templatetag argument: '%s'." 1073 " Must be one of: %s" % 1074 (tag, TemplateTagNode.mapping.keys())) 1075 return TemplateTagNode(tag) 1076 templatetag = register.tag(templatetag) 1077 1078 def url(parser, token): 1079 """ 1080 Returns an absolute URL matching given view with its parameters. 1081 1082 This is a way to define links that aren't tied to a particular URL 1083 configuration:: 1084 1085 {% url path.to.some_view arg1,arg2,name1=value1 %} 1086 1087 The first argument is a path to a view. It can be an absolute python path 1088 or just ``app_name.view_name`` without the project name if the view is 1089 located inside the project. Other arguments are comma-separated values 1090 that will be filled in place of positional and keyword arguments in the 1091 URL. All arguments for the URL should be present. 1092 1093 For example if you have a view ``app_name.client`` taking client's id and 1094 the corresponding line in a URLconf looks like this:: 1095 1096 ('^client/(\d+)/$', 'app_name.client') 1097 1098 and this app's URLconf is included into the project's URLconf under some 1099 path:: 1100 1101 ('^clients/', include('project_name.app_name.urls')) 1102 1103 then in a template you can create a link for a certain client like this:: 1104 1105 {% url app_name.client client.id %} 1106 1107 The URL will look like ``/clients/client/123/``. 1108 """ 1109 bits = token.split_contents() 1110 if len(bits) < 2: 1111 raise TemplateSyntaxError("'%s' takes at least one argument" 1112 " (path to a view)" % bits[0]) 1113 viewname = bits[1] 1114 args = [] 1115 kwargs = {} 1116 asvar = None 1117 1118 if len(bits) > 2: 1119 bits = iter(bits[2:]) 1120 for bit in bits: 1121 if bit == 'as': 1122 asvar = bits.next() 1123 break 1124 else: 1125 for arg in bit.split(","): 1126 if '=' in arg: 1127 k, v = arg.split('=', 1) 1128 k = k.strip() 1129 kwargs[k] = parser.compile_filter(v) 1130 elif arg: 1131 args.append(parser.compile_filter(arg)) 1132 return URLNode(viewname, args, kwargs, asvar) 1133 url = register.tag(url) 1134 1135 #@register.tag 1136 def widthratio(parser, token): 1137 """ 1138 For creating bar charts and such, this tag calculates the ratio of a given 1139 value to a maximum value, and then applies that ratio to a constant. 1140 1141 For example:: 1142 1143 <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' /> 1144 1145 Above, if ``this_value`` is 175 and ``max_value`` is 200, the image in 1146 the above example will be 88 pixels wide (because 175/200 = .875; 1147 .875 * 100 = 87.5 which is rounded up to 88). 1148 """ 1149 bits = token.contents.split() 1150 if len(bits) != 4: 1151 raise TemplateSyntaxError("widthratio takes three arguments") 1152 tag, this_value_expr, max_value_expr, max_width = bits 1153 1154 return WidthRatioNode(parser.compile_filter(this_value_expr), 1155 parser.compile_filter(max_value_expr), 1156 parser.compile_filter(max_width)) 1157 widthratio = register.tag(widthratio) 1158 1159 #@register.tag 1160 def do_with(parser, token): 1161 """ 1162 Adds a value to the context (inside of this block) for caching and easy 1163 access. 1164 1165 For example:: 1166 1167 {% with person.some_sql_method as total %} 1168 {{ total }} object{{ total|pluralize }} 1169 {% endwith %} 1170 """ 1171 bits = list(token.split_contents()) 1172 if len(bits) != 4 or bits[2] != "as": 1173 raise TemplateSyntaxError("%r expected format is 'value as name'" % 1174 bits[0]) 1175 var = parser.compile_filter(bits[1]) 1176 name = bits[3] 1177 nodelist = parser.parse(('endwith',)) 1178 parser.delete_first_token() 1179 return WithNode(var, name, nodelist) 1180 do_with = register.tag('with', do_with) -
tests/regressiontests/templates/tests.py
diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py index 2946208..88afc54 100644
a b def do_echo(parser, token): 59 59 60 60 register.tag("echo", do_echo) 61 61 62 template.libraries[' django.templatetags.testtags'] = register62 template.libraries['testtags'] = register 63 63 64 64 ##################################### 65 65 # Helper objects for template tests #