Ticket #6587: new_template_lib_loader_12.diff
File new_template_lib_loader_12.diff, 135.7 KB (added by , 16 years ago) |
---|
-
django/template/__init__.py
diff --git a/django/template/__init__.py b/django/template/__init__.py index 5c4ab30..4ee3174 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 from django.conf import settings 54 55 from django.template.context import Context, RequestContext, ContextPopException … … from django.utils.encoding import smart_unicode, force_unicode 59 60 from django.utils.translation import ugettext as _ 60 61 from django.utils.safestring import SafeData, EscapeData, mark_safe, mark_for_escaping 61 62 from django.utils.html import escape 63 from django.templatetags import get_templatetags_modules 62 64 63 65 __all__ = ('Template', 'Context', 'RequestContext', 'compile_string') 64 66 … … class Library(object): 913 915 return func 914 916 return dec 915 917 916 def get_library(module_name): 917 lib = libraries.get(module_name, None) 918 def import_library(templatetag_module, library_name): 919 try: 920 components = templatetag_module.split('.') 921 mod = __import__(templatetag_module) 922 for comp in components[1:]: 923 mod = getattr(mod, comp) 924 imp.find_module(library_name, mod.__path__) 925 except ImportError, AttributeError: 926 return None 927 library_module = '%s.%s' % (templatetag_module, library_name) 928 components = library_module.split('.') 929 mod = __import__(library_module) 930 for comp in components[1:]: 931 mod = getattr(mod, comp) 932 try: 933 return mod.register 934 except AttributeError: 935 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 936 937 def get_library(library_name): 938 lib = libraries.get(library_name, None) 918 939 if not lib: 919 try: 920 mod = __import__(module_name, {}, {}, ['']) 921 except ImportError, e: 922 raise InvalidTemplateLibrary("Could not load template library from %s, %s" % (module_name, e)) 923 try: 924 lib = mod.register 925 libraries[module_name] = lib 926 except AttributeError: 927 raise InvalidTemplateLibrary("Template library %s does not have a variable named 'register'" % module_name) 940 941 """ 942 If library is not already loaded loop over all templatetags modules to locate it. 943 944 {% load somelib %} and {% load someotherlib %} loops twice. 945 946 Subsequent loads eg. {% load somelib %} in the same thread will grab the cached 947 module from libraries. 948 """ 949 templatetags_modules = get_templatetags_modules() 950 tried_modules = [] 951 for module in templatetags_modules: 952 taglib_module = '%s.%s' % (module, library_name) 953 tried_modules.append(taglib_module) 954 lib = import_library(module, library_name) 955 if lib: 956 libraries[library_name] = lib 957 break 958 if not lib: 959 raise InvalidTemplateLibrary("Template library %s not found, tried %s" % (library_name, ','.join(tried_modules))) 928 960 return lib 929 961 930 def add_to_builtins(module _name):931 builtins.append( get_library(module_name))962 def add_to_builtins(module, library_name): 963 builtins.append(import_library(module, library_name)) 932 964 933 add_to_builtins('django.template .defaulttags')934 add_to_builtins('django.template .defaultfilters')965 add_to_builtins('django.templatetags', 'defaulttags') 966 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 cef3143..0000000
+ - 1 """Default variable filters."""2 3 import re4 import random as random_module5 try:6 from functools import wraps7 except ImportError:8 from django.utils.functional import wraps # Python 2.3, 2.4 fallback.9 10 from django.template import Variable, Library11 from django.conf import settings12 from django.utils.translation import ugettext, ungettext13 from django.utils.encoding import force_unicode, iri_to_uri14 from django.utils.safestring import mark_safe, SafeData15 16 register = Library()17 18 #######################19 # STRING DECORATOR #20 #######################21 22 def stringfilter(func):23 """24 Decorator for filters which should only receive unicode objects. The object25 passed as the first positional argument will be converted to a unicode26 object.27 """28 def _dec(*args, **kwargs):29 if args:30 args = list(args)31 args[0] = force_unicode(args[0])32 if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False):33 return mark_safe(func(*args, **kwargs))34 return func(*args, **kwargs)35 36 # Include a reference to the real function (used to check original37 # arguments by the template parser).38 _dec._decorated_function = getattr(func, '_decorated_function', func)39 for attr in ('is_safe', 'needs_autoescape'):40 if hasattr(func, attr):41 setattr(_dec, attr, getattr(func, attr))42 return wraps(func)(_dec)43 44 ###################45 # STRINGS #46 ###################47 48 49 def addslashes(value):50 """51 Adds slashes before quotes. Useful for escaping strings in CSV, for52 example. Less useful for escaping JavaScript; use the ``escapejs``53 filter instead.54 """55 return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")56 addslashes.is_safe = True57 addslashes = stringfilter(addslashes)58 59 def capfirst(value):60 """Capitalizes the first character of the value."""61 return value and value[0].upper() + value[1:]62 capfirst.is_safe=True63 capfirst = stringfilter(capfirst)64 65 _js_escapes = (66 ('\\', '\\\\'),67 ('"', '\\"'),68 ("'", "\\'"),69 ('\n', '\\n'),70 ('\r', '\\r'),71 ('\b', '\\b'),72 ('\f', '\\f'),73 ('\t', '\\t'),74 ('\v', '\\v'),75 ('</', '<\\/'),76 )77 def escapejs(value):78 """Backslash-escapes characters for use in JavaScript strings."""79 for bad, good in _js_escapes:80 value = value.replace(bad, good)81 return value82 escapejs = stringfilter(escapejs)83 84 def fix_ampersands(value):85 """Replaces ampersands with ``&`` entities."""86 from django.utils.html import fix_ampersands87 return fix_ampersands(value)88 fix_ampersands.is_safe=True89 fix_ampersands = stringfilter(fix_ampersands)90 91 def floatformat(text, arg=-1):92 """93 Displays a float to a specified number of decimal places.94 95 If called without an argument, it displays the floating point number with96 one decimal place -- but only if there's a decimal place to be displayed:97 98 * num1 = 34.2323499 * num2 = 34.00000100 * num3 = 34.26000101 * {{ num1|floatformat }} displays "34.2"102 * {{ num2|floatformat }} displays "34"103 * {{ num3|floatformat }} displays "34.3"104 105 If arg is positive, it will always display exactly arg number of decimal106 places:107 108 * {{ num1|floatformat:3 }} displays "34.232"109 * {{ num2|floatformat:3 }} displays "34.000"110 * {{ num3|floatformat:3 }} displays "34.260"111 112 If arg is negative, it will display arg number of decimal places -- but113 only if there are places to be displayed:114 115 * {{ num1|floatformat:"-3" }} displays "34.232"116 * {{ num2|floatformat:"-3" }} displays "34"117 * {{ num3|floatformat:"-3" }} displays "34.260"118 """119 try:120 f = float(text)121 except (ValueError, TypeError):122 return u''123 try:124 d = int(arg)125 except ValueError:126 return force_unicode(f)127 try:128 m = f - int(f)129 except OverflowError:130 return force_unicode(f)131 if not m and d < 0:132 return mark_safe(u'%d' % int(f))133 else:134 formatstr = u'%%.%df' % abs(d)135 return mark_safe(formatstr % f)136 floatformat.is_safe = True137 138 def iriencode(value):139 """Escapes an IRI value for use in a URL."""140 return force_unicode(iri_to_uri(value))141 iriencode.is_safe = True142 iriencode = stringfilter(iriencode)143 144 def linenumbers(value, autoescape=None):145 """Displays text with line numbers."""146 from django.utils.html import escape147 lines = value.split(u'\n')148 # Find the maximum width of the line count, for use with zero padding149 # string format command150 width = unicode(len(unicode(len(lines))))151 if not autoescape or isinstance(value, SafeData):152 for i, line in enumerate(lines):153 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)154 else:155 for i, line in enumerate(lines):156 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))157 return mark_safe(u'\n'.join(lines))158 linenumbers.is_safe = True159 linenumbers.needs_autoescape = True160 linenumbers = stringfilter(linenumbers)161 162 def lower(value):163 """Converts a string into all lowercase."""164 return value.lower()165 lower.is_safe = True166 lower = stringfilter(lower)167 168 def make_list(value):169 """170 Returns the value turned into a list.171 172 For an integer, it's a list of digits.173 For a string, it's a list of characters.174 """175 return list(value)176 make_list.is_safe = False177 make_list = stringfilter(make_list)178 179 def slugify(value):180 """181 Normalizes string, converts to lowercase, removes non-alpha characters,182 and converts spaces to hyphens.183 """184 import unicodedata185 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')186 value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())187 return mark_safe(re.sub('[-\s]+', '-', value))188 slugify.is_safe = True189 slugify = stringfilter(slugify)190 191 def stringformat(value, arg):192 """193 Formats the variable according to the arg, a string formatting specifier.194 195 This specifier uses Python string formating syntax, with the exception that196 the leading "%" is dropped.197 198 See http://docs.python.org/lib/typesseq-strings.html for documentation199 of Python string formatting200 """201 try:202 return (u"%" + unicode(arg)) % value203 except (ValueError, TypeError):204 return u""205 stringformat.is_safe = True206 207 def title(value):208 """Converts a string into titlecase."""209 return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())210 title.is_safe = True211 title = stringfilter(title)212 213 def truncatewords(value, arg):214 """215 Truncates a string after a certain number of words.216 217 Argument: Number of words to truncate after.218 """219 from django.utils.text import truncate_words220 try:221 length = int(arg)222 except ValueError: # Invalid literal for int().223 return value # Fail silently.224 return truncate_words(value, length)225 truncatewords.is_safe = True226 truncatewords = stringfilter(truncatewords)227 228 def truncatewords_html(value, arg):229 """230 Truncates HTML after a certain number of words.231 232 Argument: Number of words to truncate after.233 """234 from django.utils.text import truncate_html_words235 try:236 length = int(arg)237 except ValueError: # invalid literal for int()238 return value # Fail silently.239 return truncate_html_words(value, length)240 truncatewords_html.is_safe = True241 truncatewords_html = stringfilter(truncatewords_html)242 243 def upper(value):244 """Converts a string into all uppercase."""245 return value.upper()246 upper.is_safe = False247 upper = stringfilter(upper)248 249 def urlencode(value):250 """Escapes a value for use in a URL."""251 from django.utils.http import urlquote252 return urlquote(value)253 urlencode.is_safe = False254 urlencode = stringfilter(urlencode)255 256 def urlize(value, autoescape=None):257 """Converts URLs in plain text into clickable links."""258 from django.utils.html import urlize259 return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))260 urlize.is_safe=True261 urlize.needs_autoescape = True262 urlize = stringfilter(urlize)263 264 def urlizetrunc(value, limit, autoescape=None):265 """266 Converts URLs into clickable links, truncating URLs to the given character267 limit, and adding 'rel=nofollow' attribute to discourage spamming.268 269 Argument: Length to truncate URLs to.270 """271 from django.utils.html import urlize272 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,273 autoescape=autoescape))274 urlizetrunc.is_safe = True275 urlizetrunc.needs_autoescape = True276 urlizetrunc = stringfilter(urlizetrunc)277 278 def wordcount(value):279 """Returns the number of words."""280 return len(value.split())281 wordcount.is_safe = False282 wordcount = stringfilter(wordcount)283 284 def wordwrap(value, arg):285 """286 Wraps words at specified line length.287 288 Argument: number of characters to wrap the text at.289 """290 from django.utils.text import wrap291 return wrap(value, int(arg))292 wordwrap.is_safe = True293 wordwrap = stringfilter(wordwrap)294 295 def ljust(value, arg):296 """297 Left-aligns the value in a field of a given width.298 299 Argument: field size.300 """301 return value.ljust(int(arg))302 ljust.is_safe = True303 ljust = stringfilter(ljust)304 305 def rjust(value, arg):306 """307 Right-aligns the value in a field of a given width.308 309 Argument: field size.310 """311 return value.rjust(int(arg))312 rjust.is_safe = True313 rjust = stringfilter(rjust)314 315 def center(value, arg):316 """Centers the value in a field of a given width."""317 return value.center(int(arg))318 center.is_safe = True319 center = stringfilter(center)320 321 def cut(value, arg):322 """323 Removes all values of arg from the given string.324 """325 safe = isinstance(value, SafeData)326 value = value.replace(arg, u'')327 if safe and arg != ';':328 return mark_safe(value)329 return value330 cut = stringfilter(cut)331 332 ###################333 # HTML STRINGS #334 ###################335 336 def escape(value):337 """338 Marks the value as a string that should not be auto-escaped.339 """340 from django.utils.safestring import mark_for_escaping341 return mark_for_escaping(value)342 escape.is_safe = True343 escape = stringfilter(escape)344 345 def force_escape(value):346 """347 Escapes a string's HTML. This returns a new string containing the escaped348 characters (as opposed to "escape", which marks the content for later349 possible escaping).350 """351 from django.utils.html import escape352 return mark_safe(escape(value))353 force_escape = stringfilter(force_escape)354 force_escape.is_safe = True355 356 def linebreaks(value, autoescape=None):357 """358 Replaces line breaks in plain text with appropriate HTML; a single359 newline becomes an HTML line break (``<br />``) and a new line360 followed by a blank line becomes a paragraph break (``</p>``).361 """362 from django.utils.html import linebreaks363 autoescape = autoescape and not isinstance(value, SafeData)364 return mark_safe(linebreaks(value, autoescape))365 linebreaks.is_safe = True366 linebreaks.needs_autoescape = True367 linebreaks = stringfilter(linebreaks)368 369 def linebreaksbr(value, autoescape=None):370 """371 Converts all newlines in a piece of plain text to HTML line breaks372 (``<br />``).373 """374 if autoescape and not isinstance(value, SafeData):375 from django.utils.html import escape376 value = escape(value)377 return mark_safe(value.replace('\n', '<br />'))378 linebreaksbr.is_safe = True379 linebreaksbr.needs_autoescape = True380 linebreaksbr = stringfilter(linebreaksbr)381 382 def safe(value):383 """384 Marks the value as a string that should not be auto-escaped.385 """386 from django.utils.safestring import mark_safe387 return mark_safe(value)388 safe.is_safe = True389 safe = stringfilter(safe)390 391 def removetags(value, tags):392 """Removes a space separated list of [X]HTML tags from the output."""393 tags = [re.escape(tag) for tag in tags.split()]394 tags_re = u'(%s)' % u'|'.join(tags)395 starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)396 endtag_re = re.compile(u'</%s>' % tags_re)397 value = starttag_re.sub(u'', value)398 value = endtag_re.sub(u'', value)399 return value400 removetags.is_safe = True401 removetags = stringfilter(removetags)402 403 def striptags(value):404 """Strips all [X]HTML tags."""405 from django.utils.html import strip_tags406 return strip_tags(value)407 striptags.is_safe = True408 striptags = stringfilter(striptags)409 410 ###################411 # LISTS #412 ###################413 414 def dictsort(value, arg):415 """416 Takes a list of dicts, returns that list sorted by the property given in417 the argument.418 """419 var_resolve = Variable(arg).resolve420 decorated = [(var_resolve(item), item) for item in value]421 decorated.sort()422 return [item[1] for item in decorated]423 dictsort.is_safe = False424 425 def dictsortreversed(value, arg):426 """427 Takes a list of dicts, returns that list sorted in reverse order by the428 property given in the argument.429 """430 var_resolve = Variable(arg).resolve431 decorated = [(var_resolve(item), item) for item in value]432 decorated.sort()433 decorated.reverse()434 return [item[1] for item in decorated]435 dictsortreversed.is_safe = False436 437 def first(value):438 """Returns the first item in a list."""439 try:440 return value[0]441 except IndexError:442 return u''443 first.is_safe = False444 445 def join(value, arg):446 """Joins a list with a string, like Python's ``str.join(list)``."""447 try:448 data = arg.join(map(force_unicode, value))449 except AttributeError: # fail silently but nicely450 return value451 safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),452 value, True)453 if safe_args:454 return mark_safe(data)455 else:456 return data457 join.is_safe = True458 459 def last(value):460 "Returns the last item in a list"461 try:462 return value[-1]463 except IndexError:464 return u''465 last.is_safe = True466 467 def length(value):468 """Returns the length of the value - useful for lists."""469 return len(value)470 length.is_safe = True471 472 def length_is(value, arg):473 """Returns a boolean of whether the value's length is the argument."""474 return len(value) == int(arg)475 length_is.is_safe = True476 477 def random(value):478 """Returns a random item from the list."""479 return random_module.choice(value)480 random.is_safe = True481 482 def slice_(value, arg):483 """484 Returns a slice of the list.485 486 Uses the same syntax as Python's list slicing; see487 http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice488 for an introduction.489 """490 try:491 bits = []492 for x in arg.split(u':'):493 if len(x) == 0:494 bits.append(None)495 else:496 bits.append(int(x))497 return value[slice(*bits)]498 499 except (ValueError, TypeError):500 return value # Fail silently.501 slice_.is_safe = True502 503 def unordered_list(value, autoescape=None):504 """505 Recursively takes a self-nested list and returns an HTML unordered list --506 WITHOUT opening and closing <ul> tags.507 508 The list is assumed to be in the proper format. For example, if ``var``509 contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,510 then ``{{ var|unordered_list }}`` would return::511 512 <li>States513 <ul>514 <li>Kansas515 <ul>516 <li>Lawrence</li>517 <li>Topeka</li>518 </ul>519 </li>520 <li>Illinois</li>521 </ul>522 </li>523 """524 if autoescape:525 from django.utils.html import conditional_escape526 escaper = conditional_escape527 else:528 escaper = lambda x: x529 def convert_old_style_list(list_):530 """531 Converts old style lists to the new easier to understand format.532 533 The old list format looked like:534 ['Item 1', [['Item 1.1', []], ['Item 1.2', []]]535 536 And it is converted to:537 ['Item 1', ['Item 1.1', 'Item 1.2]]538 """539 if not isinstance(list_, (tuple, list)) or len(list_) != 2:540 return list_, False541 first_item, second_item = list_542 if second_item == []:543 return [first_item], True544 old_style_list = True545 new_second_item = []546 for sublist in second_item:547 item, old_style_list = convert_old_style_list(sublist)548 if not old_style_list:549 break550 new_second_item.extend(item)551 if old_style_list:552 second_item = new_second_item553 return [first_item, second_item], old_style_list554 def _helper(list_, tabs=1):555 indent = u'\t' * tabs556 output = []557 558 list_length = len(list_)559 i = 0560 while i < list_length:561 title = list_[i]562 sublist = ''563 sublist_item = None564 if isinstance(title, (list, tuple)):565 sublist_item = title566 title = ''567 elif i < list_length - 1:568 next_item = list_[i+1]569 if next_item and isinstance(next_item, (list, tuple)):570 # The next item is a sub-list.571 sublist_item = next_item572 # We've processed the next item now too.573 i += 1574 if sublist_item:575 sublist = _helper(sublist_item, tabs+1)576 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,577 indent, indent)578 output.append('%s<li>%s%s</li>' % (indent,579 escaper(force_unicode(title)), sublist))580 i += 1581 return '\n'.join(output)582 value, converted = convert_old_style_list(value)583 return mark_safe(_helper(value))584 unordered_list.is_safe = True585 unordered_list.needs_autoescape = True586 587 ###################588 # INTEGERS #589 ###################590 591 def add(value, arg):592 """Adds the arg to the value."""593 return int(value) + int(arg)594 add.is_safe = False595 596 def get_digit(value, arg):597 """598 Given a whole number, returns the requested digit of it, where 1 is the599 right-most digit, 2 is the second-right-most digit, etc. Returns the600 original value for invalid input (if input or argument is not an integer,601 or if argument is less than 1). Otherwise, output is always an integer.602 """603 try:604 arg = int(arg)605 value = int(value)606 except ValueError:607 return value # Fail silently for an invalid argument608 if arg < 1:609 return value610 try:611 return int(str(value)[-arg])612 except IndexError:613 return 0614 get_digit.is_safe = False615 616 ###################617 # DATES #618 ###################619 620 def date(value, arg=None):621 """Formats a date according to the given format."""622 from django.utils.dateformat import format623 if not value:624 return u''625 if arg is None:626 arg = settings.DATE_FORMAT627 return format(value, arg)628 date.is_safe = False629 630 def time(value, arg=None):631 """Formats a time according to the given format."""632 from django.utils.dateformat import time_format633 if value in (None, u''):634 return u''635 if arg is None:636 arg = settings.TIME_FORMAT637 return time_format(value, arg)638 time.is_safe = False639 640 def timesince(value, arg=None):641 """Formats a date as the time since that date (i.e. "4 days, 6 hours")."""642 from django.utils.timesince import timesince643 if not value:644 return u''645 if arg:646 return timesince(arg, value)647 return timesince(value)648 timesince.is_safe = False649 650 def timeuntil(value, arg=None):651 """Formats a date as the time until that date (i.e. "4 days, 6 hours")."""652 from django.utils.timesince import timesince653 from datetime import datetime654 if not value:655 return u''656 if arg:657 return timesince(arg, value)658 return timesince(datetime.now(), value)659 timeuntil.is_safe = False660 661 ###################662 # LOGIC #663 ###################664 665 def default(value, arg):666 """If value is unavailable, use given default."""667 return value or arg668 default.is_safe = False669 670 def default_if_none(value, arg):671 """If value is None, use given default."""672 if value is None:673 return arg674 return value675 default_if_none.is_safe = False676 677 def divisibleby(value, arg):678 """Returns True if the value is devisible by the argument."""679 return int(value) % int(arg) == 0680 divisibleby.is_safe = False681 682 def yesno(value, arg=None):683 """684 Given a string mapping values for true, false and (optionally) None,685 returns one of those strings accoding to the value:686 687 ========== ====================== ==================================688 Value Argument Outputs689 ========== ====================== ==================================690 ``True`` ``"yeah,no,maybe"`` ``yeah``691 ``False`` ``"yeah,no,maybe"`` ``no``692 ``None`` ``"yeah,no,maybe"`` ``maybe``693 ``None`` ``"yeah,no"`` ``"no"`` (converts None to False694 if no mapping for None is given.695 ========== ====================== ==================================696 """697 if arg is None:698 arg = ugettext('yes,no,maybe')699 bits = arg.split(u',')700 if len(bits) < 2:701 return value # Invalid arg.702 try:703 yes, no, maybe = bits704 except ValueError:705 # Unpack list of wrong size (no "maybe" value provided).706 yes, no, maybe = bits[0], bits[1], bits[1]707 if value is None:708 return maybe709 if value:710 return yes711 return no712 yesno.is_safe = False713 714 ###################715 # MISC #716 ###################717 718 def filesizeformat(bytes):719 """720 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,721 102 bytes, etc).722 """723 try:724 bytes = float(bytes)725 except TypeError:726 return u"0 bytes"727 728 if bytes < 1024:729 return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}730 if bytes < 1024 * 1024:731 return ugettext("%.1f KB") % (bytes / 1024)732 if bytes < 1024 * 1024 * 1024:733 return ugettext("%.1f MB") % (bytes / (1024 * 1024))734 return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))735 filesizeformat.is_safe = True736 737 def pluralize(value, arg=u's'):738 """739 Returns a plural suffix if the value is not 1. By default, 's' is used as740 the suffix:741 742 * If value is 0, vote{{ value|pluralize }} displays "0 votes".743 * If value is 1, vote{{ value|pluralize }} displays "1 vote".744 * If value is 2, vote{{ value|pluralize }} displays "2 votes".745 746 If an argument is provided, that string is used instead:747 748 * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".749 * If value is 1, class{{ value|pluralize:"es" }} displays "1 class".750 * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".751 752 If the provided argument contains a comma, the text before the comma is753 used for the singular case and the text after the comma is used for the754 plural case:755 756 * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".757 * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".758 * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".759 """760 if not u',' in arg:761 arg = u',' + arg762 bits = arg.split(u',')763 if len(bits) > 2:764 return u''765 singular_suffix, plural_suffix = bits[:2]766 767 try:768 if int(value) != 1:769 return plural_suffix770 except ValueError: # Invalid string that's not a number.771 pass772 except TypeError: # Value isn't a string or a number; maybe it's a list?773 try:774 if len(value) != 1:775 return plural_suffix776 except TypeError: # len() of unsized object.777 pass778 return singular_suffix779 pluralize.is_safe = False780 781 def phone2numeric(value):782 """Takes a phone number and converts it in to its numerical equivalent."""783 from django.utils.text import phone2numeric784 return phone2numeric(value)785 phone2numeric.is_safe = True786 787 def pprint(value):788 """A wrapper around pprint.pprint -- for debugging, really."""789 from pprint import pformat790 try:791 return pformat(value)792 except Exception, e:793 return u"Error in formatting: %s" % force_unicode(e, errors="replace")794 pprint.is_safe = True795 796 # Syntax: register.filter(name of filter, callback)797 register.filter(add)798 register.filter(addslashes)799 register.filter(capfirst)800 register.filter(center)801 register.filter(cut)802 register.filter(date)803 register.filter(default)804 register.filter(default_if_none)805 register.filter(dictsort)806 register.filter(dictsortreversed)807 register.filter(divisibleby)808 register.filter(escape)809 register.filter(escapejs)810 register.filter(filesizeformat)811 register.filter(first)812 register.filter(fix_ampersands)813 register.filter(floatformat)814 register.filter(force_escape)815 register.filter(get_digit)816 register.filter(iriencode)817 register.filter(join)818 register.filter(last)819 register.filter(length)820 register.filter(length_is)821 register.filter(linebreaks)822 register.filter(linebreaksbr)823 register.filter(linenumbers)824 register.filter(ljust)825 register.filter(lower)826 register.filter(make_list)827 register.filter(phone2numeric)828 register.filter(pluralize)829 register.filter(pprint)830 register.filter(removetags)831 register.filter(random)832 register.filter(rjust)833 register.filter(safe)834 register.filter('slice', slice_)835 register.filter(slugify)836 register.filter(stringformat)837 register.filter(striptags)838 register.filter(time)839 register.filter(timesince)840 register.filter(timeuntil)841 register.filter(title)842 register.filter(truncatewords)843 register.filter(truncatewords_html)844 register.filter(unordered_list)845 register.filter(upper)846 register.filter(urlencode)847 register.filter(urlize)848 register.filter(urlizetrunc)849 register.filter(wordcount)850 register.filter(wordwrap)851 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 01c43ee..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.conf import settings15 from django.utils.encoding import smart_str, smart_unicode16 from django.utils.itercompat import groupby17 from django.utils.safestring import mark_safe18 19 register = Library()20 21 class AutoEscapeControlNode(Node):22 """Implements the actions of the autoescape tag."""23 def __init__(self, setting, nodelist):24 self.setting, self.nodelist = setting, nodelist25 26 def render(self, context):27 old_setting = context.autoescape28 context.autoescape = self.setting29 output = self.nodelist.render(context)30 context.autoescape = old_setting31 if self.setting:32 return mark_safe(output)33 else:34 return output35 36 class CommentNode(Node):37 def render(self, context):38 return ''39 40 class CycleNode(Node):41 def __init__(self, cyclevars, variable_name=None):42 self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars])43 self.variable_name = variable_name44 45 def render(self, context):46 value = self.cycle_iter.next().resolve(context)47 if self.variable_name:48 context[self.variable_name] = value49 return value50 51 class DebugNode(Node):52 def render(self, context):53 from pprint import pformat54 output = [pformat(val) for val in context]55 output.append('\n\n')56 output.append(pformat(sys.modules))57 return ''.join(output)58 59 class FilterNode(Node):60 def __init__(self, filter_expr, nodelist):61 self.filter_expr, self.nodelist = filter_expr, nodelist62 63 def render(self, context):64 output = self.nodelist.render(context)65 # Apply filters.66 context.update({'var': output})67 filtered = self.filter_expr.resolve(context)68 context.pop()69 return filtered70 71 class FirstOfNode(Node):72 def __init__(self, vars):73 self.vars = map(Variable, vars)74 75 def render(self, context):76 for var in self.vars:77 try:78 value = var.resolve(context)79 except VariableDoesNotExist:80 continue81 if value:82 return smart_unicode(value)83 return u''84 85 class ForNode(Node):86 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):87 self.loopvars, self.sequence = loopvars, sequence88 self.is_reversed = is_reversed89 self.nodelist_loop = nodelist_loop90 91 def __repr__(self):92 reversed_text = self.is_reversed and ' reversed' or ''93 return "<For Node: for %s in %s, tail_len: %d%s>" % \94 (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),95 reversed_text)96 97 def __iter__(self):98 for node in self.nodelist_loop:99 yield node100 101 def get_nodes_by_type(self, nodetype):102 nodes = []103 if isinstance(self, nodetype):104 nodes.append(self)105 nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))106 return nodes107 108 def render(self, context):109 nodelist = NodeList()110 if 'forloop' in context:111 parentloop = context['forloop']112 else:113 parentloop = {}114 context.push()115 try:116 values = self.sequence.resolve(context, True)117 except VariableDoesNotExist:118 values = []119 if values is None:120 values = []121 if not hasattr(values, '__len__'):122 values = list(values)123 len_values = len(values)124 if self.is_reversed:125 values = reversed(values)126 unpack = len(self.loopvars) > 1127 # Create a forloop value in the context. We'll update counters on each128 # iteration just below.129 loop_dict = context['forloop'] = {'parentloop': parentloop}130 for i, item in enumerate(values):131 # Shortcuts for current loop iteration number.132 loop_dict['counter0'] = i133 loop_dict['counter'] = i+1134 # Reverse counter iteration numbers.135 loop_dict['revcounter'] = len_values - i136 loop_dict['revcounter0'] = len_values - i - 1137 # Boolean values designating first and last times through loop.138 loop_dict['first'] = (i == 0)139 loop_dict['last'] = (i == len_values - 1)140 141 if unpack:142 # If there are multiple loop variables, unpack the item into143 # them.144 context.update(dict(zip(self.loopvars, item)))145 else:146 context[self.loopvars[0]] = item147 for node in self.nodelist_loop:148 nodelist.append(node.render(context))149 if unpack:150 # The loop variables were pushed on to the context so pop them151 # off again. This is necessary because the tag lets the length152 # of loopvars differ to the length of each set of items and we153 # don't want to leave any vars from the previous loop on the154 # context.155 context.pop()156 context.pop()157 return nodelist.render(context)158 159 class IfChangedNode(Node):160 def __init__(self, nodelist, *varlist):161 self.nodelist = nodelist162 self._last_seen = None163 self._varlist = map(Variable, varlist)164 self._id = str(id(self))165 166 def render(self, context):167 if 'forloop' in context and self._id not in context['forloop']:168 self._last_seen = None169 context['forloop'][self._id] = 1170 try:171 if self._varlist:172 # Consider multiple parameters. This automatically behaves173 # like an OR evaluation of the multiple variables.174 compare_to = [var.resolve(context) for var in self._varlist]175 else:176 compare_to = self.nodelist.render(context)177 except VariableDoesNotExist:178 compare_to = None179 180 if compare_to != self._last_seen:181 firstloop = (self._last_seen == None)182 self._last_seen = compare_to183 context.push()184 context['ifchanged'] = {'firstloop': firstloop}185 content = self.nodelist.render(context)186 context.pop()187 return content188 else:189 return ''190 191 class IfEqualNode(Node):192 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):193 self.var1, self.var2 = Variable(var1), Variable(var2)194 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false195 self.negate = negate196 197 def __repr__(self):198 return "<IfEqualNode>"199 200 def render(self, context):201 try:202 val1 = self.var1.resolve(context)203 except VariableDoesNotExist:204 val1 = None205 try:206 val2 = self.var2.resolve(context)207 except VariableDoesNotExist:208 val2 = None209 if (self.negate and val1 != val2) or (not self.negate and val1 == val2):210 return self.nodelist_true.render(context)211 return self.nodelist_false.render(context)212 213 class IfNode(Node):214 def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):215 self.bool_exprs = bool_exprs216 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false217 self.link_type = link_type218 219 def __repr__(self):220 return "<If node>"221 222 def __iter__(self):223 for node in self.nodelist_true:224 yield node225 for node in self.nodelist_false:226 yield node227 228 def get_nodes_by_type(self, nodetype):229 nodes = []230 if isinstance(self, nodetype):231 nodes.append(self)232 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))233 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))234 return nodes235 236 def render(self, context):237 if self.link_type == IfNode.LinkTypes.or_:238 for ifnot, bool_expr in self.bool_exprs:239 try:240 value = bool_expr.resolve(context, True)241 except VariableDoesNotExist:242 value = None243 if (value and not ifnot) or (ifnot and not value):244 return self.nodelist_true.render(context)245 return self.nodelist_false.render(context)246 else:247 for ifnot, bool_expr in self.bool_exprs:248 try:249 value = bool_expr.resolve(context, True)250 except VariableDoesNotExist:251 value = None252 if not ((value and not ifnot) or (ifnot and not value)):253 return self.nodelist_false.render(context)254 return self.nodelist_true.render(context)255 256 class LinkTypes:257 and_ = 0,258 or_ = 1259 260 class RegroupNode(Node):261 def __init__(self, target, expression, var_name):262 self.target, self.expression = target, expression263 self.var_name = var_name264 265 def render(self, context):266 obj_list = self.target.resolve(context, True)267 if obj_list == None:268 # target variable wasn't found in context; fail silently.269 context[self.var_name] = []270 return ''271 # List of dictionaries in the format:272 # {'grouper': 'key', 'list': [list of contents]}.273 context[self.var_name] = [274 {'grouper': key, 'list': list(val)}275 for key, val in276 groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))277 ]278 return ''279 280 def include_is_allowed(filepath):281 for root in settings.ALLOWED_INCLUDE_ROOTS:282 if filepath.startswith(root):283 return True284 return False285 286 class SsiNode(Node):287 def __init__(self, filepath, parsed):288 self.filepath, self.parsed = filepath, parsed289 290 def render(self, context):291 if not include_is_allowed(self.filepath):292 if settings.DEBUG:293 return "[Didn't have permission to include file]"294 else:295 return '' # Fail silently for invalid includes.296 try:297 fp = open(self.filepath, 'r')298 output = fp.read()299 fp.close()300 except IOError:301 output = ''302 if self.parsed:303 try:304 t = Template(output, name=self.filepath)305 return t.render(context)306 except TemplateSyntaxError, e:307 if settings.DEBUG:308 return "[Included template had syntax error: %s]" % e309 else:310 return '' # Fail silently for invalid included templates.311 return output312 313 class LoadNode(Node):314 def render(self, context):315 return ''316 317 class NowNode(Node):318 def __init__(self, format_string):319 self.format_string = format_string320 321 def render(self, context):322 from datetime import datetime323 from django.utils.dateformat import DateFormat324 df = DateFormat(datetime.now())325 return df.format(self.format_string)326 327 class SpacelessNode(Node):328 def __init__(self, nodelist):329 self.nodelist = nodelist330 331 def render(self, context):332 from django.utils.html import strip_spaces_between_tags333 return strip_spaces_between_tags(self.nodelist.render(context).strip())334 335 class TemplateTagNode(Node):336 mapping = {'openblock': BLOCK_TAG_START,337 'closeblock': BLOCK_TAG_END,338 'openvariable': VARIABLE_TAG_START,339 'closevariable': VARIABLE_TAG_END,340 'openbrace': SINGLE_BRACE_START,341 'closebrace': SINGLE_BRACE_END,342 'opencomment': COMMENT_TAG_START,343 'closecomment': COMMENT_TAG_END,344 }345 346 def __init__(self, tagtype):347 self.tagtype = tagtype348 349 def render(self, context):350 return self.mapping.get(self.tagtype, '')351 352 class URLNode(Node):353 def __init__(self, view_name, args, kwargs):354 self.view_name = view_name355 self.args = args356 self.kwargs = kwargs357 358 def render(self, context):359 from django.core.urlresolvers import reverse, NoReverseMatch360 args = [arg.resolve(context) for arg in self.args]361 kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))362 for k, v in self.kwargs.items()])363 try:364 return reverse(self.view_name, args=args, kwargs=kwargs)365 except NoReverseMatch:366 try:367 project_name = settings.SETTINGS_MODULE.split('.')[0]368 return reverse(project_name + '.' + self.view_name,369 args=args, kwargs=kwargs)370 except NoReverseMatch:371 return ''372 373 class WidthRatioNode(Node):374 def __init__(self, val_expr, max_expr, max_width):375 self.val_expr = val_expr376 self.max_expr = max_expr377 self.max_width = max_width378 379 def render(self, context):380 try:381 value = self.val_expr.resolve(context)382 maxvalue = self.max_expr.resolve(context)383 except VariableDoesNotExist:384 return ''385 try:386 value = float(value)387 maxvalue = float(maxvalue)388 ratio = (value / maxvalue) * int(self.max_width)389 except (ValueError, ZeroDivisionError):390 return ''391 return str(int(round(ratio)))392 393 class WithNode(Node):394 def __init__(self, var, name, nodelist):395 self.var = var396 self.name = name397 self.nodelist = nodelist398 399 def __repr__(self):400 return "<WithNode>"401 402 def render(self, context):403 val = self.var.resolve(context)404 context.push()405 context[self.name] = val406 output = self.nodelist.render(context)407 context.pop()408 return output409 410 #@register.tag411 def autoescape(parser, token):412 """413 Force autoescape behaviour for this block.414 """415 args = token.contents.split()416 if len(args) != 2:417 raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")418 arg = args[1]419 if arg not in (u'on', u'off'):420 raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")421 nodelist = parser.parse(('endautoescape',))422 parser.delete_first_token()423 return AutoEscapeControlNode((arg == 'on'), nodelist)424 autoescape = register.tag(autoescape)425 426 #@register.tag427 def comment(parser, token):428 """429 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.430 """431 parser.skip_past('endcomment')432 return CommentNode()433 comment = register.tag(comment)434 435 #@register.tag436 def cycle(parser, token):437 """438 Cycles among the given strings each time this tag is encountered.439 440 Within a loop, cycles among the given strings each time through441 the loop::442 443 {% for o in some_list %}444 <tr class="{% cycle 'row1' 'row2' %}">445 ...446 </tr>447 {% endfor %}448 449 Outside of a loop, give the values a unique name the first time you call450 it, then use that name each sucessive time through::451 452 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>453 <tr class="{% cycle rowcolors %}">...</tr>454 <tr class="{% cycle rowcolors %}">...</tr>455 456 You can use any number of values, separated by spaces. Commas can also457 be used to separate values; if a comma is used, the cycle values are458 interpreted as literal strings.459 """460 461 # Note: This returns the exact same node on each {% cycle name %} call;462 # that is, the node object returned from {% cycle a b c as name %} and the463 # one returned from {% cycle name %} are the exact same object. This464 # shouldn't cause problems (heh), but if it does, now you know.465 #466 # Ugly hack warning: This stuffs the named template dict into parser so467 # that names are only unique within each template (as opposed to using468 # a global variable, which would make cycle names have to be unique across469 # *all* templates.470 471 args = token.split_contents()472 473 if len(args) < 2:474 raise TemplateSyntaxError("'cycle' tag requires at least two arguments")475 476 if ',' in args[1]:477 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}478 # case.479 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]480 481 if len(args) == 2:482 # {% cycle foo %} case.483 name = args[1]484 if not hasattr(parser, '_namedCycleNodes'):485 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)486 if not name in parser._namedCycleNodes:487 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)488 return parser._namedCycleNodes[name]489 490 if len(args) > 4 and args[-2] == 'as':491 name = args[-1]492 node = CycleNode(args[1:-2], name)493 if not hasattr(parser, '_namedCycleNodes'):494 parser._namedCycleNodes = {}495 parser._namedCycleNodes[name] = node496 else:497 node = CycleNode(args[1:])498 return node499 cycle = register.tag(cycle)500 501 def debug(parser, token):502 """503 Outputs a whole load of debugging information, including the current504 context and imported modules.505 506 Sample usage::507 508 <pre>509 {% debug %}510 </pre>511 """512 return DebugNode()513 debug = register.tag(debug)514 515 #@register.tag(name="filter")516 def do_filter(parser, token):517 """518 Filters the contents of the block through variable filters.519 520 Filters can also be piped through each other, and they can have521 arguments -- just like in variable syntax.522 523 Sample usage::524 525 {% filter force_escape|lower %}526 This text will be HTML-escaped, and will appear in lowercase.527 {% endfilter %}528 """529 _, rest = token.contents.split(None, 1)530 filter_expr = parser.compile_filter("var|%s" % (rest))531 for func, unused in filter_expr.filters:532 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):533 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)534 nodelist = parser.parse(('endfilter',))535 parser.delete_first_token()536 return FilterNode(filter_expr, nodelist)537 do_filter = register.tag("filter", do_filter)538 539 #@register.tag540 def firstof(parser, token):541 """542 Outputs the first variable passed that is not False.543 544 Outputs nothing if all the passed variables are False.545 546 Sample usage::547 548 {% firstof var1 var2 var3 %}549 550 This is equivalent to::551 552 {% if var1 %}553 {{ var1 }}554 {% else %}{% if var2 %}555 {{ var2 }}556 {% else %}{% if var3 %}557 {{ var3 }}558 {% endif %}{% endif %}{% endif %}559 560 but obviously much cleaner!561 562 You can also use a literal string as a fallback value in case all563 passed variables are False::564 565 {% firstof var1 var2 var3 "fallback value" %}566 567 """568 bits = token.split_contents()[1:]569 if len(bits) < 1:570 raise TemplateSyntaxError("'firstof' statement requires at least one"571 " argument")572 return FirstOfNode(bits)573 firstof = register.tag(firstof)574 575 #@register.tag(name="for")576 def do_for(parser, token):577 """578 Loops over each item in an array.579 580 For example, to display a list of athletes given ``athlete_list``::581 582 <ul>583 {% for athlete in athlete_list %}584 <li>{{ athlete.name }}</li>585 {% endfor %}586 </ul>587 588 You can loop over a list in reverse by using589 ``{% for obj in list reversed %}``.590 591 You can also unpack multiple values from a two-dimensional array::592 593 {% for key,value in dict.items %}594 {{ key }}: {{ value }}595 {% endfor %}596 597 The for loop sets a number of variables available within the loop:598 599 ========================== ================================================600 Variable Description601 ========================== ================================================602 ``forloop.counter`` The current iteration of the loop (1-indexed)603 ``forloop.counter0`` The current iteration of the loop (0-indexed)604 ``forloop.revcounter`` The number of iterations from the end of the605 loop (1-indexed)606 ``forloop.revcounter0`` The number of iterations from the end of the607 loop (0-indexed)608 ``forloop.first`` True if this is the first time through the loop609 ``forloop.last`` True if this is the last time through the loop610 ``forloop.parentloop`` For nested loops, this is the loop "above" the611 current one612 ========================== ================================================613 614 """615 bits = token.contents.split()616 if len(bits) < 4:617 raise TemplateSyntaxError("'for' statements should have at least four"618 " words: %s" % token.contents)619 620 is_reversed = bits[-1] == 'reversed'621 in_index = is_reversed and -3 or -2622 if bits[in_index] != 'in':623 raise TemplateSyntaxError("'for' statements should use the format"624 " 'for x in y': %s" % token.contents)625 626 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')627 for var in loopvars:628 if not var or ' ' in var:629 raise TemplateSyntaxError("'for' tag received an invalid argument:"630 " %s" % token.contents)631 632 sequence = parser.compile_filter(bits[in_index+1])633 nodelist_loop = parser.parse(('endfor',))634 parser.delete_first_token()635 return ForNode(loopvars, sequence, is_reversed, nodelist_loop)636 do_for = register.tag("for", do_for)637 638 def do_ifequal(parser, token, negate):639 bits = list(token.split_contents())640 if len(bits) != 3:641 raise TemplateSyntaxError, "%r takes two arguments" % bits[0]642 end_tag = 'end' + bits[0]643 nodelist_true = parser.parse(('else', end_tag))644 token = parser.next_token()645 if token.contents == 'else':646 nodelist_false = parser.parse((end_tag,))647 parser.delete_first_token()648 else:649 nodelist_false = NodeList()650 return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)651 652 #@register.tag653 def ifequal(parser, token):654 """655 Outputs the contents of the block if the two arguments equal each other.656 657 Examples::658 659 {% ifequal user.id comment.user_id %}660 ...661 {% endifequal %}662 663 {% ifnotequal user.id comment.user_id %}664 ...665 {% else %}666 ...667 {% endifnotequal %}668 """669 return do_ifequal(parser, token, False)670 ifequal = register.tag(ifequal)671 672 #@register.tag673 def ifnotequal(parser, token):674 """675 Outputs the contents of the block if the two arguments are not equal.676 See ifequal.677 """678 return do_ifequal(parser, token, True)679 ifnotequal = register.tag(ifnotequal)680 681 #@register.tag(name="if")682 def do_if(parser, token):683 """684 The ``{% if %}`` tag evaluates a variable, and if that variable is "true"685 (i.e., exists, is not empty, and is not a false boolean value), the686 contents of the block are output:687 688 ::689 690 {% if athlete_list %}691 Number of athletes: {{ athlete_list|count }}692 {% else %}693 No athletes.694 {% endif %}695 696 In the above, if ``athlete_list`` is not empty, the number of athletes will697 be displayed by the ``{{ athlete_list|count }}`` variable.698 699 As you can see, the ``if`` tag can take an option ``{% else %}`` clause700 that will be displayed if the test fails.701 702 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of703 variables or to negate a given variable::704 705 {% if not athlete_list %}706 There are no athletes.707 {% endif %}708 709 {% if athlete_list or coach_list %}710 There are some athletes or some coaches.711 {% endif %}712 713 {% if athlete_list and coach_list %}714 Both atheletes and coaches are available.715 {% endif %}716 717 {% if not athlete_list or coach_list %}718 There are no athletes, or there are some coaches.719 {% endif %}720 721 {% if athlete_list and not coach_list %}722 There are some athletes and absolutely no coaches.723 {% endif %}724 725 ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,726 because the order of logic would be ambigous. For example, this is727 invalid::728 729 {% if athlete_list and coach_list or cheerleader_list %}730 731 If you need to combine ``and`` and ``or`` to do advanced logic, just use732 nested if tags. For example::733 734 {% if athlete_list %}735 {% if coach_list or cheerleader_list %}736 We have athletes, and either coaches or cheerleaders!737 {% endif %}738 {% endif %}739 """740 bits = token.contents.split()741 del bits[0]742 if not bits:743 raise TemplateSyntaxError("'if' statement requires at least one argument")744 # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']745 bitstr = ' '.join(bits)746 boolpairs = bitstr.split(' and ')747 boolvars = []748 if len(boolpairs) == 1:749 link_type = IfNode.LinkTypes.or_750 boolpairs = bitstr.split(' or ')751 else:752 link_type = IfNode.LinkTypes.and_753 if ' or ' in bitstr:754 raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"755 for boolpair in boolpairs:756 if ' ' in boolpair:757 try:758 not_, boolvar = boolpair.split()759 except ValueError:760 raise TemplateSyntaxError, "'if' statement improperly formatted"761 if not_ != 'not':762 raise TemplateSyntaxError, "Expected 'not' in if statement"763 boolvars.append((True, parser.compile_filter(boolvar)))764 else:765 boolvars.append((False, parser.compile_filter(boolpair)))766 nodelist_true = parser.parse(('else', 'endif'))767 token = parser.next_token()768 if token.contents == 'else':769 nodelist_false = parser.parse(('endif',))770 parser.delete_first_token()771 else:772 nodelist_false = NodeList()773 return IfNode(boolvars, nodelist_true, nodelist_false, link_type)774 do_if = register.tag("if", do_if)775 776 #@register.tag777 def ifchanged(parser, token):778 """779 Checks if a value has changed from the last iteration of a loop.780 781 The 'ifchanged' block tag is used within a loop. It has two possible uses.782 783 1. Checks its own rendered contents against its previous state and only784 displays the content if it has changed. For example, this displays a785 list of days, only displaying the month if it changes::786 787 <h1>Archive for {{ year }}</h1>788 789 {% for date in days %}790 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}791 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>792 {% endfor %}793 794 2. If given a variable, check whether that variable has changed.795 For example, the following shows the date every time it changes, but796 only shows the hour if both the hour and the date have changed::797 798 {% for date in days %}799 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}800 {% ifchanged date.hour date.date %}801 {{ date.hour }}802 {% endifchanged %}803 {% endfor %}804 """805 bits = token.contents.split()806 nodelist = parser.parse(('endifchanged',))807 parser.delete_first_token()808 return IfChangedNode(nodelist, *bits[1:])809 ifchanged = register.tag(ifchanged)810 811 #@register.tag812 def ssi(parser, token):813 """814 Outputs the contents of a given file into the page.815 816 Like a simple "include" tag, the ``ssi`` tag includes the contents817 of another file -- which must be specified using an absolute path --818 in the current page::819 820 {% ssi /home/html/ljworld.com/includes/right_generic.html %}821 822 If the optional "parsed" parameter is given, the contents of the included823 file are evaluated as template code, with the current context::824 825 {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}826 """827 bits = token.contents.split()828 parsed = False829 if len(bits) not in (2, 3):830 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"831 " the file to be included")832 if len(bits) == 3:833 if bits[2] == 'parsed':834 parsed = True835 else:836 raise TemplateSyntaxError("Second (optional) argument to %s tag"837 " must be 'parsed'" % bits[0])838 return SsiNode(bits[1], parsed)839 ssi = register.tag(ssi)840 841 #@register.tag842 def load(parser, token):843 """844 Loads a custom template tag set.845 846 For example, to load the template tags in847 ``django/templatetags/news/photos.py``::848 849 {% load news.photos %}850 """851 bits = token.contents.split()852 for taglib in bits[1:]:853 # add the library to the parser854 try:855 lib = get_library("django.templatetags.%s" % taglib)856 parser.add_library(lib)857 except InvalidTemplateLibrary, e:858 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %859 (taglib, e))860 return LoadNode()861 load = register.tag(load)862 863 #@register.tag864 def now(parser, token):865 """866 Displays the date, formatted according to the given string.867 868 Uses the same format as PHP's ``date()`` function; see http://php.net/date869 for all the possible values.870 871 Sample usage::872 873 It is {% now "jS F Y H:i" %}874 """875 bits = token.contents.split('"')876 if len(bits) != 3:877 raise TemplateSyntaxError, "'now' statement takes one argument"878 format_string = bits[1]879 return NowNode(format_string)880 now = register.tag(now)881 882 #@register.tag883 def regroup(parser, token):884 """885 Regroups a list of alike objects by a common attribute.886 887 This complex tag is best illustrated by use of an example: say that888 ``people`` is a list of ``Person`` objects that have ``first_name``,889 ``last_name``, and ``gender`` attributes, and you'd like to display a list890 that looks like:891 892 * Male:893 * George Bush894 * Bill Clinton895 * Female:896 * Margaret Thatcher897 * Colendeeza Rice898 * Unknown:899 * Pat Smith900 901 The following snippet of template code would accomplish this dubious task::902 903 {% regroup people by gender as grouped %}904 <ul>905 {% for group in grouped %}906 <li>{{ group.grouper }}907 <ul>908 {% for item in group.list %}909 <li>{{ item }}</li>910 {% endfor %}911 </ul>912 {% endfor %}913 </ul>914 915 As you can see, ``{% regroup %}`` populates a variable with a list of916 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the917 item that was grouped by; ``list`` contains the list of objects that share918 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female``919 and ``Unknown``, and ``list`` is the list of people with those genders.920 921 Note that `{% regroup %}`` does not work when the list to be grouped is not922 sorted by the key you are grouping by! This means that if your list of923 people was not sorted by gender, you'd need to make sure it is sorted924 before using it, i.e.::925 926 {% regroup people|dictsort:"gender" by gender as grouped %}927 928 """929 firstbits = token.contents.split(None, 3)930 if len(firstbits) != 4:931 raise TemplateSyntaxError, "'regroup' tag takes five arguments"932 target = parser.compile_filter(firstbits[1])933 if firstbits[2] != 'by':934 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")935 lastbits_reversed = firstbits[3][::-1].split(None, 2)936 if lastbits_reversed[1][::-1] != 'as':937 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"938 " be 'as'")939 940 expression = parser.compile_filter(lastbits_reversed[2][::-1])941 942 var_name = lastbits_reversed[0][::-1]943 return RegroupNode(target, expression, var_name)944 regroup = register.tag(regroup)945 946 def spaceless(parser, token):947 """948 Removes whitespace between HTML tags, including tab and newline characters.949 950 Example usage::951 952 {% spaceless %}953 <p>954 <a href="foo/">Foo</a>955 </p>956 {% endspaceless %}957 958 This example would return this HTML::959 960 <p><a href="foo/">Foo</a></p>961 962 Only space between *tags* is normalized -- not space between tags and text.963 In this example, the space around ``Hello`` won't be stripped::964 965 {% spaceless %}966 <strong>967 Hello968 </strong>969 {% endspaceless %}970 """971 nodelist = parser.parse(('endspaceless',))972 parser.delete_first_token()973 return SpacelessNode(nodelist)974 spaceless = register.tag(spaceless)975 976 #@register.tag977 def templatetag(parser, token):978 """979 Outputs one of the bits used to compose template tags.980 981 Since the template system has no concept of "escaping", to display one of982 the bits used in template tags, you must use the ``{% templatetag %}`` tag.983 984 The argument tells which template bit to output:985 986 ================== =======987 Argument Outputs988 ================== =======989 ``openblock`` ``{%``990 ``closeblock`` ``%}``991 ``openvariable`` ``{{``992 ``closevariable`` ``}}``993 ``openbrace`` ``{``994 ``closebrace`` ``}``995 ``opencomment`` ``{#``996 ``closecomment`` ``#}``997 ================== =======998 """999 bits = token.contents.split()1000 if len(bits) != 2:1001 raise TemplateSyntaxError, "'templatetag' statement takes one argument"1002 tag = bits[1]1003 if tag not in TemplateTagNode.mapping:1004 raise TemplateSyntaxError("Invalid templatetag argument: '%s'."1005 " Must be one of: %s" %1006 (tag, TemplateTagNode.mapping.keys()))1007 return TemplateTagNode(tag)1008 templatetag = register.tag(templatetag)1009 1010 def url(parser, token):1011 """1012 Returns an absolute URL matching given view with its parameters.1013 1014 This is a way to define links that aren't tied to a particular URL1015 configuration::1016 1017 {% url path.to.some_view arg1,arg2,name1=value1 %}1018 1019 The first argument is a path to a view. It can be an absolute python path1020 or just ``app_name.view_name`` without the project name if the view is1021 located inside the project. Other arguments are comma-separated values1022 that will be filled in place of positional and keyword arguments in the1023 URL. All arguments for the URL should be present.1024 1025 For example if you have a view ``app_name.client`` taking client's id and1026 the corresponding line in a URLconf looks like this::1027 1028 ('^client/(\d+)/$', 'app_name.client')1029 1030 and this app's URLconf is included into the project's URLconf under some1031 path::1032 1033 ('^clients/', include('project_name.app_name.urls'))1034 1035 then in a template you can create a link for a certain client like this::1036 1037 {% url app_name.client client.id %}1038 1039 The URL will look like ``/clients/client/123/``.1040 """1041 bits = token.contents.split(' ', 2)1042 if len(bits) < 2:1043 raise TemplateSyntaxError("'%s' takes at least one argument"1044 " (path to a view)" % bits[0])1045 args = []1046 kwargs = {}1047 if len(bits) > 2:1048 for arg in bits[2].split(','):1049 if '=' in arg:1050 k, v = arg.split('=', 1)1051 k = k.strip()1052 kwargs[k] = parser.compile_filter(v)1053 else:1054 args.append(parser.compile_filter(arg))1055 return URLNode(bits[1], args, kwargs)1056 url = register.tag(url)1057 1058 #@register.tag1059 def widthratio(parser, token):1060 """1061 For creating bar charts and such, this tag calculates the ratio of a given1062 value to a maximum value, and then applies that ratio to a constant.1063 1064 For example::1065 1066 <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />1067 1068 Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in1069 the above example will be 88 pixels wide (because 175/200 = .875;1070 .875 * 100 = 87.5 which is rounded up to 88).1071 """1072 bits = token.contents.split()1073 if len(bits) != 4:1074 raise TemplateSyntaxError("widthratio takes three arguments")1075 tag, this_value_expr, max_value_expr, max_width = bits1076 try:1077 max_width = int(max_width)1078 except ValueError:1079 raise TemplateSyntaxError("widthratio final argument must be an integer")1080 return WidthRatioNode(parser.compile_filter(this_value_expr),1081 parser.compile_filter(max_value_expr), max_width)1082 widthratio = register.tag(widthratio)1083 1084 #@register.tag1085 def do_with(parser, token):1086 """1087 Adds a value to the context (inside of this block) for caching and easy1088 access.1089 1090 For example::1091 1092 {% with person.some_sql_method as total %}1093 {{ total }} object{{ total|pluralize }}1094 {% endwith %}1095 """1096 bits = list(token.split_contents())1097 if len(bits) != 4 or bits[2] != "as":1098 raise TemplateSyntaxError("%r expected format is 'value as name'" %1099 bits[0])1100 var = parser.compile_filter(bits[1])1101 name = bits[3]1102 nodelist = parser.parse(('endwith',))1103 parser.delete_first_token()1104 return WithNode(var, name, nodelist)1105 do_with = register.tag('with', do_with) -
django/template/loader.py
diff --git a/django/template/loader.py b/django/template/loader.py index 1d7d945..8f0d55a 100644
a b def select_template(template_name_list): 116 116 # If we get here, none of the templates could be loaded 117 117 raise TemplateDoesNotExist, ', '.join(template_name_list) 118 118 119 add_to_builtins('django.template .loader_tags')119 add_to_builtins('django.template', 'loader_tags') -
django/templatetags/__init__.py
diff --git a/django/templatetags/__init__.py b/django/templatetags/__init__.py index 9204535..ef550c6 100644
a b 1 import imp 1 2 from django.conf import settings 2 3 3 for a in settings.INSTALLED_APPS: 4 try: 5 __path__.extend(__import__(a + '.templatetags', {}, {}, ['']).__path__) 6 except ImportError: 7 pass 4 templatetags_modules= [] 5 6 def get_templatetags_modules(): 7 if not templatetags_modules: 8 """ Populate list once per thread. """ 9 for app_module in ['django'] + list(settings.INSTALLED_APPS): 10 try: 11 components = app_module.split('.') 12 mod = __import__(app_module) 13 for comp in components[1:]: 14 mod = getattr(mod, comp) 15 imp.find_module('templatetags', mod.__path__) 16 templatetags_modules.append('%s.templatetags' % app_module) 17 except ImportError, AttributeError: 18 pass 19 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..cef3143
- + 1 """Default variable filters.""" 2 3 import re 4 import random as random_module 5 try: 6 from functools import wraps 7 except ImportError: 8 from django.utils.functional import wraps # Python 2.3, 2.4 fallback. 9 10 from django.template import Variable, Library 11 from django.conf import settings 12 from django.utils.translation import ugettext, ungettext 13 from django.utils.encoding import force_unicode, iri_to_uri 14 from django.utils.safestring import mark_safe, SafeData 15 16 register = Library() 17 18 ####################### 19 # STRING DECORATOR # 20 ####################### 21 22 def stringfilter(func): 23 """ 24 Decorator for filters which should only receive unicode objects. The object 25 passed as the first positional argument will be converted to a unicode 26 object. 27 """ 28 def _dec(*args, **kwargs): 29 if args: 30 args = list(args) 31 args[0] = force_unicode(args[0]) 32 if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False): 33 return mark_safe(func(*args, **kwargs)) 34 return func(*args, **kwargs) 35 36 # Include a reference to the real function (used to check original 37 # arguments by the template parser). 38 _dec._decorated_function = getattr(func, '_decorated_function', func) 39 for attr in ('is_safe', 'needs_autoescape'): 40 if hasattr(func, attr): 41 setattr(_dec, attr, getattr(func, attr)) 42 return wraps(func)(_dec) 43 44 ################### 45 # STRINGS # 46 ################### 47 48 49 def addslashes(value): 50 """ 51 Adds slashes before quotes. Useful for escaping strings in CSV, for 52 example. Less useful for escaping JavaScript; use the ``escapejs`` 53 filter instead. 54 """ 55 return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") 56 addslashes.is_safe = True 57 addslashes = stringfilter(addslashes) 58 59 def capfirst(value): 60 """Capitalizes the first character of the value.""" 61 return value and value[0].upper() + value[1:] 62 capfirst.is_safe=True 63 capfirst = stringfilter(capfirst) 64 65 _js_escapes = ( 66 ('\\', '\\\\'), 67 ('"', '\\"'), 68 ("'", "\\'"), 69 ('\n', '\\n'), 70 ('\r', '\\r'), 71 ('\b', '\\b'), 72 ('\f', '\\f'), 73 ('\t', '\\t'), 74 ('\v', '\\v'), 75 ('</', '<\\/'), 76 ) 77 def escapejs(value): 78 """Backslash-escapes characters for use in JavaScript strings.""" 79 for bad, good in _js_escapes: 80 value = value.replace(bad, good) 81 return value 82 escapejs = stringfilter(escapejs) 83 84 def fix_ampersands(value): 85 """Replaces ampersands with ``&`` entities.""" 86 from django.utils.html import fix_ampersands 87 return fix_ampersands(value) 88 fix_ampersands.is_safe=True 89 fix_ampersands = stringfilter(fix_ampersands) 90 91 def floatformat(text, arg=-1): 92 """ 93 Displays a float to a specified number of decimal places. 94 95 If called without an argument, it displays the floating point number with 96 one decimal place -- but only if there's a decimal place to be displayed: 97 98 * num1 = 34.23234 99 * num2 = 34.00000 100 * num3 = 34.26000 101 * {{ num1|floatformat }} displays "34.2" 102 * {{ num2|floatformat }} displays "34" 103 * {{ num3|floatformat }} displays "34.3" 104 105 If arg is positive, it will always display exactly arg number of decimal 106 places: 107 108 * {{ num1|floatformat:3 }} displays "34.232" 109 * {{ num2|floatformat:3 }} displays "34.000" 110 * {{ num3|floatformat:3 }} displays "34.260" 111 112 If arg is negative, it will display arg number of decimal places -- but 113 only if there are places to be displayed: 114 115 * {{ num1|floatformat:"-3" }} displays "34.232" 116 * {{ num2|floatformat:"-3" }} displays "34" 117 * {{ num3|floatformat:"-3" }} displays "34.260" 118 """ 119 try: 120 f = float(text) 121 except (ValueError, TypeError): 122 return u'' 123 try: 124 d = int(arg) 125 except ValueError: 126 return force_unicode(f) 127 try: 128 m = f - int(f) 129 except OverflowError: 130 return force_unicode(f) 131 if not m and d < 0: 132 return mark_safe(u'%d' % int(f)) 133 else: 134 formatstr = u'%%.%df' % abs(d) 135 return mark_safe(formatstr % f) 136 floatformat.is_safe = True 137 138 def iriencode(value): 139 """Escapes an IRI value for use in a URL.""" 140 return force_unicode(iri_to_uri(value)) 141 iriencode.is_safe = True 142 iriencode = stringfilter(iriencode) 143 144 def linenumbers(value, autoescape=None): 145 """Displays text with line numbers.""" 146 from django.utils.html import escape 147 lines = value.split(u'\n') 148 # Find the maximum width of the line count, for use with zero padding 149 # string format command 150 width = unicode(len(unicode(len(lines)))) 151 if not autoescape or isinstance(value, SafeData): 152 for i, line in enumerate(lines): 153 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) 154 else: 155 for i, line in enumerate(lines): 156 lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) 157 return mark_safe(u'\n'.join(lines)) 158 linenumbers.is_safe = True 159 linenumbers.needs_autoescape = True 160 linenumbers = stringfilter(linenumbers) 161 162 def lower(value): 163 """Converts a string into all lowercase.""" 164 return value.lower() 165 lower.is_safe = True 166 lower = stringfilter(lower) 167 168 def make_list(value): 169 """ 170 Returns the value turned into a list. 171 172 For an integer, it's a list of digits. 173 For a string, it's a list of characters. 174 """ 175 return list(value) 176 make_list.is_safe = False 177 make_list = stringfilter(make_list) 178 179 def slugify(value): 180 """ 181 Normalizes string, converts to lowercase, removes non-alpha characters, 182 and converts spaces to hyphens. 183 """ 184 import unicodedata 185 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') 186 value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) 187 return mark_safe(re.sub('[-\s]+', '-', value)) 188 slugify.is_safe = True 189 slugify = stringfilter(slugify) 190 191 def stringformat(value, arg): 192 """ 193 Formats the variable according to the arg, a string formatting specifier. 194 195 This specifier uses Python string formating syntax, with the exception that 196 the leading "%" is dropped. 197 198 See http://docs.python.org/lib/typesseq-strings.html for documentation 199 of Python string formatting 200 """ 201 try: 202 return (u"%" + unicode(arg)) % value 203 except (ValueError, TypeError): 204 return u"" 205 stringformat.is_safe = True 206 207 def title(value): 208 """Converts a string into titlecase.""" 209 return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) 210 title.is_safe = True 211 title = stringfilter(title) 212 213 def truncatewords(value, arg): 214 """ 215 Truncates a string after a certain number of words. 216 217 Argument: Number of words to truncate after. 218 """ 219 from django.utils.text import truncate_words 220 try: 221 length = int(arg) 222 except ValueError: # Invalid literal for int(). 223 return value # Fail silently. 224 return truncate_words(value, length) 225 truncatewords.is_safe = True 226 truncatewords = stringfilter(truncatewords) 227 228 def truncatewords_html(value, arg): 229 """ 230 Truncates HTML after a certain number of words. 231 232 Argument: Number of words to truncate after. 233 """ 234 from django.utils.text import truncate_html_words 235 try: 236 length = int(arg) 237 except ValueError: # invalid literal for int() 238 return value # Fail silently. 239 return truncate_html_words(value, length) 240 truncatewords_html.is_safe = True 241 truncatewords_html = stringfilter(truncatewords_html) 242 243 def upper(value): 244 """Converts a string into all uppercase.""" 245 return value.upper() 246 upper.is_safe = False 247 upper = stringfilter(upper) 248 249 def urlencode(value): 250 """Escapes a value for use in a URL.""" 251 from django.utils.http import urlquote 252 return urlquote(value) 253 urlencode.is_safe = False 254 urlencode = stringfilter(urlencode) 255 256 def urlize(value, autoescape=None): 257 """Converts URLs in plain text into clickable links.""" 258 from django.utils.html import urlize 259 return mark_safe(urlize(value, nofollow=True, autoescape=autoescape)) 260 urlize.is_safe=True 261 urlize.needs_autoescape = True 262 urlize = stringfilter(urlize) 263 264 def urlizetrunc(value, limit, autoescape=None): 265 """ 266 Converts URLs into clickable links, truncating URLs to the given character 267 limit, and adding 'rel=nofollow' attribute to discourage spamming. 268 269 Argument: Length to truncate URLs to. 270 """ 271 from django.utils.html import urlize 272 return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True, 273 autoescape=autoescape)) 274 urlizetrunc.is_safe = True 275 urlizetrunc.needs_autoescape = True 276 urlizetrunc = stringfilter(urlizetrunc) 277 278 def wordcount(value): 279 """Returns the number of words.""" 280 return len(value.split()) 281 wordcount.is_safe = False 282 wordcount = stringfilter(wordcount) 283 284 def wordwrap(value, arg): 285 """ 286 Wraps words at specified line length. 287 288 Argument: number of characters to wrap the text at. 289 """ 290 from django.utils.text import wrap 291 return wrap(value, int(arg)) 292 wordwrap.is_safe = True 293 wordwrap = stringfilter(wordwrap) 294 295 def ljust(value, arg): 296 """ 297 Left-aligns the value in a field of a given width. 298 299 Argument: field size. 300 """ 301 return value.ljust(int(arg)) 302 ljust.is_safe = True 303 ljust = stringfilter(ljust) 304 305 def rjust(value, arg): 306 """ 307 Right-aligns the value in a field of a given width. 308 309 Argument: field size. 310 """ 311 return value.rjust(int(arg)) 312 rjust.is_safe = True 313 rjust = stringfilter(rjust) 314 315 def center(value, arg): 316 """Centers the value in a field of a given width.""" 317 return value.center(int(arg)) 318 center.is_safe = True 319 center = stringfilter(center) 320 321 def cut(value, arg): 322 """ 323 Removes all values of arg from the given string. 324 """ 325 safe = isinstance(value, SafeData) 326 value = value.replace(arg, u'') 327 if safe and arg != ';': 328 return mark_safe(value) 329 return value 330 cut = stringfilter(cut) 331 332 ################### 333 # HTML STRINGS # 334 ################### 335 336 def escape(value): 337 """ 338 Marks the value as a string that should not be auto-escaped. 339 """ 340 from django.utils.safestring import mark_for_escaping 341 return mark_for_escaping(value) 342 escape.is_safe = True 343 escape = stringfilter(escape) 344 345 def force_escape(value): 346 """ 347 Escapes a string's HTML. This returns a new string containing the escaped 348 characters (as opposed to "escape", which marks the content for later 349 possible escaping). 350 """ 351 from django.utils.html import escape 352 return mark_safe(escape(value)) 353 force_escape = stringfilter(force_escape) 354 force_escape.is_safe = True 355 356 def linebreaks(value, autoescape=None): 357 """ 358 Replaces line breaks in plain text with appropriate HTML; a single 359 newline becomes an HTML line break (``<br />``) and a new line 360 followed by a blank line becomes a paragraph break (``</p>``). 361 """ 362 from django.utils.html import linebreaks 363 autoescape = autoescape and not isinstance(value, SafeData) 364 return mark_safe(linebreaks(value, autoescape)) 365 linebreaks.is_safe = True 366 linebreaks.needs_autoescape = True 367 linebreaks = stringfilter(linebreaks) 368 369 def linebreaksbr(value, autoescape=None): 370 """ 371 Converts all newlines in a piece of plain text to HTML line breaks 372 (``<br />``). 373 """ 374 if autoescape and not isinstance(value, SafeData): 375 from django.utils.html import escape 376 value = escape(value) 377 return mark_safe(value.replace('\n', '<br />')) 378 linebreaksbr.is_safe = True 379 linebreaksbr.needs_autoescape = True 380 linebreaksbr = stringfilter(linebreaksbr) 381 382 def safe(value): 383 """ 384 Marks the value as a string that should not be auto-escaped. 385 """ 386 from django.utils.safestring import mark_safe 387 return mark_safe(value) 388 safe.is_safe = True 389 safe = stringfilter(safe) 390 391 def removetags(value, tags): 392 """Removes a space separated list of [X]HTML tags from the output.""" 393 tags = [re.escape(tag) for tag in tags.split()] 394 tags_re = u'(%s)' % u'|'.join(tags) 395 starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) 396 endtag_re = re.compile(u'</%s>' % tags_re) 397 value = starttag_re.sub(u'', value) 398 value = endtag_re.sub(u'', value) 399 return value 400 removetags.is_safe = True 401 removetags = stringfilter(removetags) 402 403 def striptags(value): 404 """Strips all [X]HTML tags.""" 405 from django.utils.html import strip_tags 406 return strip_tags(value) 407 striptags.is_safe = True 408 striptags = stringfilter(striptags) 409 410 ################### 411 # LISTS # 412 ################### 413 414 def dictsort(value, arg): 415 """ 416 Takes a list of dicts, returns that list sorted by the property given in 417 the argument. 418 """ 419 var_resolve = Variable(arg).resolve 420 decorated = [(var_resolve(item), item) for item in value] 421 decorated.sort() 422 return [item[1] for item in decorated] 423 dictsort.is_safe = False 424 425 def dictsortreversed(value, arg): 426 """ 427 Takes a list of dicts, returns that list sorted in reverse order by the 428 property given in the argument. 429 """ 430 var_resolve = Variable(arg).resolve 431 decorated = [(var_resolve(item), item) for item in value] 432 decorated.sort() 433 decorated.reverse() 434 return [item[1] for item in decorated] 435 dictsortreversed.is_safe = False 436 437 def first(value): 438 """Returns the first item in a list.""" 439 try: 440 return value[0] 441 except IndexError: 442 return u'' 443 first.is_safe = False 444 445 def join(value, arg): 446 """Joins a list with a string, like Python's ``str.join(list)``.""" 447 try: 448 data = arg.join(map(force_unicode, value)) 449 except AttributeError: # fail silently but nicely 450 return value 451 safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData), 452 value, True) 453 if safe_args: 454 return mark_safe(data) 455 else: 456 return data 457 join.is_safe = True 458 459 def last(value): 460 "Returns the last item in a list" 461 try: 462 return value[-1] 463 except IndexError: 464 return u'' 465 last.is_safe = True 466 467 def length(value): 468 """Returns the length of the value - useful for lists.""" 469 return len(value) 470 length.is_safe = True 471 472 def length_is(value, arg): 473 """Returns a boolean of whether the value's length is the argument.""" 474 return len(value) == int(arg) 475 length_is.is_safe = True 476 477 def random(value): 478 """Returns a random item from the list.""" 479 return random_module.choice(value) 480 random.is_safe = True 481 482 def slice_(value, arg): 483 """ 484 Returns a slice of the list. 485 486 Uses the same syntax as Python's list slicing; see 487 http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice 488 for an introduction. 489 """ 490 try: 491 bits = [] 492 for x in arg.split(u':'): 493 if len(x) == 0: 494 bits.append(None) 495 else: 496 bits.append(int(x)) 497 return value[slice(*bits)] 498 499 except (ValueError, TypeError): 500 return value # Fail silently. 501 slice_.is_safe = True 502 503 def unordered_list(value, autoescape=None): 504 """ 505 Recursively takes a self-nested list and returns an HTML unordered list -- 506 WITHOUT opening and closing <ul> tags. 507 508 The list is assumed to be in the proper format. For example, if ``var`` 509 contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, 510 then ``{{ var|unordered_list }}`` would return:: 511 512 <li>States 513 <ul> 514 <li>Kansas 515 <ul> 516 <li>Lawrence</li> 517 <li>Topeka</li> 518 </ul> 519 </li> 520 <li>Illinois</li> 521 </ul> 522 </li> 523 """ 524 if autoescape: 525 from django.utils.html import conditional_escape 526 escaper = conditional_escape 527 else: 528 escaper = lambda x: x 529 def convert_old_style_list(list_): 530 """ 531 Converts old style lists to the new easier to understand format. 532 533 The old list format looked like: 534 ['Item 1', [['Item 1.1', []], ['Item 1.2', []]] 535 536 And it is converted to: 537 ['Item 1', ['Item 1.1', 'Item 1.2]] 538 """ 539 if not isinstance(list_, (tuple, list)) or len(list_) != 2: 540 return list_, False 541 first_item, second_item = list_ 542 if second_item == []: 543 return [first_item], True 544 old_style_list = True 545 new_second_item = [] 546 for sublist in second_item: 547 item, old_style_list = convert_old_style_list(sublist) 548 if not old_style_list: 549 break 550 new_second_item.extend(item) 551 if old_style_list: 552 second_item = new_second_item 553 return [first_item, second_item], old_style_list 554 def _helper(list_, tabs=1): 555 indent = u'\t' * tabs 556 output = [] 557 558 list_length = len(list_) 559 i = 0 560 while i < list_length: 561 title = list_[i] 562 sublist = '' 563 sublist_item = None 564 if isinstance(title, (list, tuple)): 565 sublist_item = title 566 title = '' 567 elif i < list_length - 1: 568 next_item = list_[i+1] 569 if next_item and isinstance(next_item, (list, tuple)): 570 # The next item is a sub-list. 571 sublist_item = next_item 572 # We've processed the next item now too. 573 i += 1 574 if sublist_item: 575 sublist = _helper(sublist_item, tabs+1) 576 sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist, 577 indent, indent) 578 output.append('%s<li>%s%s</li>' % (indent, 579 escaper(force_unicode(title)), sublist)) 580 i += 1 581 return '\n'.join(output) 582 value, converted = convert_old_style_list(value) 583 return mark_safe(_helper(value)) 584 unordered_list.is_safe = True 585 unordered_list.needs_autoescape = True 586 587 ################### 588 # INTEGERS # 589 ################### 590 591 def add(value, arg): 592 """Adds the arg to the value.""" 593 return int(value) + int(arg) 594 add.is_safe = False 595 596 def get_digit(value, arg): 597 """ 598 Given a whole number, returns the requested digit of it, where 1 is the 599 right-most digit, 2 is the second-right-most digit, etc. Returns the 600 original value for invalid input (if input or argument is not an integer, 601 or if argument is less than 1). Otherwise, output is always an integer. 602 """ 603 try: 604 arg = int(arg) 605 value = int(value) 606 except ValueError: 607 return value # Fail silently for an invalid argument 608 if arg < 1: 609 return value 610 try: 611 return int(str(value)[-arg]) 612 except IndexError: 613 return 0 614 get_digit.is_safe = False 615 616 ################### 617 # DATES # 618 ################### 619 620 def date(value, arg=None): 621 """Formats a date according to the given format.""" 622 from django.utils.dateformat import format 623 if not value: 624 return u'' 625 if arg is None: 626 arg = settings.DATE_FORMAT 627 return format(value, arg) 628 date.is_safe = False 629 630 def time(value, arg=None): 631 """Formats a time according to the given format.""" 632 from django.utils.dateformat import time_format 633 if value in (None, u''): 634 return u'' 635 if arg is None: 636 arg = settings.TIME_FORMAT 637 return time_format(value, arg) 638 time.is_safe = False 639 640 def timesince(value, arg=None): 641 """Formats a date as the time since that date (i.e. "4 days, 6 hours").""" 642 from django.utils.timesince import timesince 643 if not value: 644 return u'' 645 if arg: 646 return timesince(arg, value) 647 return timesince(value) 648 timesince.is_safe = False 649 650 def timeuntil(value, arg=None): 651 """Formats a date as the time until that date (i.e. "4 days, 6 hours").""" 652 from django.utils.timesince import timesince 653 from datetime import datetime 654 if not value: 655 return u'' 656 if arg: 657 return timesince(arg, value) 658 return timesince(datetime.now(), value) 659 timeuntil.is_safe = False 660 661 ################### 662 # LOGIC # 663 ################### 664 665 def default(value, arg): 666 """If value is unavailable, use given default.""" 667 return value or arg 668 default.is_safe = False 669 670 def default_if_none(value, arg): 671 """If value is None, use given default.""" 672 if value is None: 673 return arg 674 return value 675 default_if_none.is_safe = False 676 677 def divisibleby(value, arg): 678 """Returns True if the value is devisible by the argument.""" 679 return int(value) % int(arg) == 0 680 divisibleby.is_safe = False 681 682 def yesno(value, arg=None): 683 """ 684 Given a string mapping values for true, false and (optionally) None, 685 returns one of those strings accoding to the value: 686 687 ========== ====================== ================================== 688 Value Argument Outputs 689 ========== ====================== ================================== 690 ``True`` ``"yeah,no,maybe"`` ``yeah`` 691 ``False`` ``"yeah,no,maybe"`` ``no`` 692 ``None`` ``"yeah,no,maybe"`` ``maybe`` 693 ``None`` ``"yeah,no"`` ``"no"`` (converts None to False 694 if no mapping for None is given. 695 ========== ====================== ================================== 696 """ 697 if arg is None: 698 arg = ugettext('yes,no,maybe') 699 bits = arg.split(u',') 700 if len(bits) < 2: 701 return value # Invalid arg. 702 try: 703 yes, no, maybe = bits 704 except ValueError: 705 # Unpack list of wrong size (no "maybe" value provided). 706 yes, no, maybe = bits[0], bits[1], bits[1] 707 if value is None: 708 return maybe 709 if value: 710 return yes 711 return no 712 yesno.is_safe = False 713 714 ################### 715 # MISC # 716 ################### 717 718 def filesizeformat(bytes): 719 """ 720 Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 721 102 bytes, etc). 722 """ 723 try: 724 bytes = float(bytes) 725 except TypeError: 726 return u"0 bytes" 727 728 if bytes < 1024: 729 return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} 730 if bytes < 1024 * 1024: 731 return ugettext("%.1f KB") % (bytes / 1024) 732 if bytes < 1024 * 1024 * 1024: 733 return ugettext("%.1f MB") % (bytes / (1024 * 1024)) 734 return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024)) 735 filesizeformat.is_safe = True 736 737 def pluralize(value, arg=u's'): 738 """ 739 Returns a plural suffix if the value is not 1. By default, 's' is used as 740 the suffix: 741 742 * If value is 0, vote{{ value|pluralize }} displays "0 votes". 743 * If value is 1, vote{{ value|pluralize }} displays "1 vote". 744 * If value is 2, vote{{ value|pluralize }} displays "2 votes". 745 746 If an argument is provided, that string is used instead: 747 748 * If value is 0, class{{ value|pluralize:"es" }} displays "0 classes". 749 * If value is 1, class{{ value|pluralize:"es" }} displays "1 class". 750 * If value is 2, class{{ value|pluralize:"es" }} displays "2 classes". 751 752 If the provided argument contains a comma, the text before the comma is 753 used for the singular case and the text after the comma is used for the 754 plural case: 755 756 * If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies". 757 * If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy". 758 * If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies". 759 """ 760 if not u',' in arg: 761 arg = u',' + arg 762 bits = arg.split(u',') 763 if len(bits) > 2: 764 return u'' 765 singular_suffix, plural_suffix = bits[:2] 766 767 try: 768 if int(value) != 1: 769 return plural_suffix 770 except ValueError: # Invalid string that's not a number. 771 pass 772 except TypeError: # Value isn't a string or a number; maybe it's a list? 773 try: 774 if len(value) != 1: 775 return plural_suffix 776 except TypeError: # len() of unsized object. 777 pass 778 return singular_suffix 779 pluralize.is_safe = False 780 781 def phone2numeric(value): 782 """Takes a phone number and converts it in to its numerical equivalent.""" 783 from django.utils.text import phone2numeric 784 return phone2numeric(value) 785 phone2numeric.is_safe = True 786 787 def pprint(value): 788 """A wrapper around pprint.pprint -- for debugging, really.""" 789 from pprint import pformat 790 try: 791 return pformat(value) 792 except Exception, e: 793 return u"Error in formatting: %s" % force_unicode(e, errors="replace") 794 pprint.is_safe = True 795 796 # Syntax: register.filter(name of filter, callback) 797 register.filter(add) 798 register.filter(addslashes) 799 register.filter(capfirst) 800 register.filter(center) 801 register.filter(cut) 802 register.filter(date) 803 register.filter(default) 804 register.filter(default_if_none) 805 register.filter(dictsort) 806 register.filter(dictsortreversed) 807 register.filter(divisibleby) 808 register.filter(escape) 809 register.filter(escapejs) 810 register.filter(filesizeformat) 811 register.filter(first) 812 register.filter(fix_ampersands) 813 register.filter(floatformat) 814 register.filter(force_escape) 815 register.filter(get_digit) 816 register.filter(iriencode) 817 register.filter(join) 818 register.filter(last) 819 register.filter(length) 820 register.filter(length_is) 821 register.filter(linebreaks) 822 register.filter(linebreaksbr) 823 register.filter(linenumbers) 824 register.filter(ljust) 825 register.filter(lower) 826 register.filter(make_list) 827 register.filter(phone2numeric) 828 register.filter(pluralize) 829 register.filter(pprint) 830 register.filter(removetags) 831 register.filter(random) 832 register.filter(rjust) 833 register.filter(safe) 834 register.filter('slice', slice_) 835 register.filter(slugify) 836 register.filter(stringformat) 837 register.filter(striptags) 838 register.filter(time) 839 register.filter(timesince) 840 register.filter(timeuntil) 841 register.filter(title) 842 register.filter(truncatewords) 843 register.filter(truncatewords_html) 844 register.filter(unordered_list) 845 register.filter(upper) 846 register.filter(urlencode) 847 register.filter(urlize) 848 register.filter(urlizetrunc) 849 register.filter(wordcount) 850 register.filter(wordwrap) 851 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..535457a
- + 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.conf import settings 15 from django.utils.encoding import smart_str, smart_unicode 16 from django.utils.itercompat import groupby 17 from django.utils.safestring import mark_safe 18 19 register = Library() 20 21 class AutoEscapeControlNode(Node): 22 """Implements the actions of the autoescape tag.""" 23 def __init__(self, setting, nodelist): 24 self.setting, self.nodelist = setting, nodelist 25 26 def render(self, context): 27 old_setting = context.autoescape 28 context.autoescape = self.setting 29 output = self.nodelist.render(context) 30 context.autoescape = old_setting 31 if self.setting: 32 return mark_safe(output) 33 else: 34 return output 35 36 class CommentNode(Node): 37 def render(self, context): 38 return '' 39 40 class CycleNode(Node): 41 def __init__(self, cyclevars, variable_name=None): 42 self.cycle_iter = itertools_cycle([Variable(v) for v in cyclevars]) 43 self.variable_name = variable_name 44 45 def render(self, context): 46 value = self.cycle_iter.next().resolve(context) 47 if self.variable_name: 48 context[self.variable_name] = value 49 return value 50 51 class DebugNode(Node): 52 def render(self, context): 53 from pprint import pformat 54 output = [pformat(val) for val in context] 55 output.append('\n\n') 56 output.append(pformat(sys.modules)) 57 return ''.join(output) 58 59 class FilterNode(Node): 60 def __init__(self, filter_expr, nodelist): 61 self.filter_expr, self.nodelist = filter_expr, nodelist 62 63 def render(self, context): 64 output = self.nodelist.render(context) 65 # Apply filters. 66 context.update({'var': output}) 67 filtered = self.filter_expr.resolve(context) 68 context.pop() 69 return filtered 70 71 class FirstOfNode(Node): 72 def __init__(self, vars): 73 self.vars = map(Variable, vars) 74 75 def render(self, context): 76 for var in self.vars: 77 try: 78 value = var.resolve(context) 79 except VariableDoesNotExist: 80 continue 81 if value: 82 return smart_unicode(value) 83 return u'' 84 85 class ForNode(Node): 86 def __init__(self, loopvars, sequence, is_reversed, nodelist_loop): 87 self.loopvars, self.sequence = loopvars, sequence 88 self.is_reversed = is_reversed 89 self.nodelist_loop = nodelist_loop 90 91 def __repr__(self): 92 reversed_text = self.is_reversed and ' reversed' or '' 93 return "<For Node: for %s in %s, tail_len: %d%s>" % \ 94 (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), 95 reversed_text) 96 97 def __iter__(self): 98 for node in self.nodelist_loop: 99 yield node 100 101 def get_nodes_by_type(self, nodetype): 102 nodes = [] 103 if isinstance(self, nodetype): 104 nodes.append(self) 105 nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype)) 106 return nodes 107 108 def render(self, context): 109 nodelist = NodeList() 110 if 'forloop' in context: 111 parentloop = context['forloop'] 112 else: 113 parentloop = {} 114 context.push() 115 try: 116 values = self.sequence.resolve(context, True) 117 except VariableDoesNotExist: 118 values = [] 119 if values is None: 120 values = [] 121 if not hasattr(values, '__len__'): 122 values = list(values) 123 len_values = len(values) 124 if self.is_reversed: 125 values = reversed(values) 126 unpack = len(self.loopvars) > 1 127 # Create a forloop value in the context. We'll update counters on each 128 # iteration just below. 129 loop_dict = context['forloop'] = {'parentloop': parentloop} 130 for i, item in enumerate(values): 131 # Shortcuts for current loop iteration number. 132 loop_dict['counter0'] = i 133 loop_dict['counter'] = i+1 134 # Reverse counter iteration numbers. 135 loop_dict['revcounter'] = len_values - i 136 loop_dict['revcounter0'] = len_values - i - 1 137 # Boolean values designating first and last times through loop. 138 loop_dict['first'] = (i == 0) 139 loop_dict['last'] = (i == len_values - 1) 140 141 if unpack: 142 # If there are multiple loop variables, unpack the item into 143 # them. 144 context.update(dict(zip(self.loopvars, item))) 145 else: 146 context[self.loopvars[0]] = item 147 for node in self.nodelist_loop: 148 nodelist.append(node.render(context)) 149 if unpack: 150 # The loop variables were pushed on to the context so pop them 151 # off again. This is necessary because the tag lets the length 152 # of loopvars differ to the length of each set of items and we 153 # don't want to leave any vars from the previous loop on the 154 # context. 155 context.pop() 156 context.pop() 157 return nodelist.render(context) 158 159 class IfChangedNode(Node): 160 def __init__(self, nodelist, *varlist): 161 self.nodelist = nodelist 162 self._last_seen = None 163 self._varlist = map(Variable, varlist) 164 self._id = str(id(self)) 165 166 def render(self, context): 167 if 'forloop' in context and self._id not in context['forloop']: 168 self._last_seen = None 169 context['forloop'][self._id] = 1 170 try: 171 if self._varlist: 172 # Consider multiple parameters. This automatically behaves 173 # like an OR evaluation of the multiple variables. 174 compare_to = [var.resolve(context) for var in self._varlist] 175 else: 176 compare_to = self.nodelist.render(context) 177 except VariableDoesNotExist: 178 compare_to = None 179 180 if compare_to != self._last_seen: 181 firstloop = (self._last_seen == None) 182 self._last_seen = compare_to 183 context.push() 184 context['ifchanged'] = {'firstloop': firstloop} 185 content = self.nodelist.render(context) 186 context.pop() 187 return content 188 else: 189 return '' 190 191 class IfEqualNode(Node): 192 def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): 193 self.var1, self.var2 = Variable(var1), Variable(var2) 194 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 195 self.negate = negate 196 197 def __repr__(self): 198 return "<IfEqualNode>" 199 200 def render(self, context): 201 try: 202 val1 = self.var1.resolve(context) 203 except VariableDoesNotExist: 204 val1 = None 205 try: 206 val2 = self.var2.resolve(context) 207 except VariableDoesNotExist: 208 val2 = None 209 if (self.negate and val1 != val2) or (not self.negate and val1 == val2): 210 return self.nodelist_true.render(context) 211 return self.nodelist_false.render(context) 212 213 class IfNode(Node): 214 def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type): 215 self.bool_exprs = bool_exprs 216 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false 217 self.link_type = link_type 218 219 def __repr__(self): 220 return "<If node>" 221 222 def __iter__(self): 223 for node in self.nodelist_true: 224 yield node 225 for node in self.nodelist_false: 226 yield node 227 228 def get_nodes_by_type(self, nodetype): 229 nodes = [] 230 if isinstance(self, nodetype): 231 nodes.append(self) 232 nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype)) 233 nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype)) 234 return nodes 235 236 def render(self, context): 237 if self.link_type == IfNode.LinkTypes.or_: 238 for ifnot, bool_expr in self.bool_exprs: 239 try: 240 value = bool_expr.resolve(context, True) 241 except VariableDoesNotExist: 242 value = None 243 if (value and not ifnot) or (ifnot and not value): 244 return self.nodelist_true.render(context) 245 return self.nodelist_false.render(context) 246 else: 247 for ifnot, bool_expr in self.bool_exprs: 248 try: 249 value = bool_expr.resolve(context, True) 250 except VariableDoesNotExist: 251 value = None 252 if not ((value and not ifnot) or (ifnot and not value)): 253 return self.nodelist_false.render(context) 254 return self.nodelist_true.render(context) 255 256 class LinkTypes: 257 and_ = 0, 258 or_ = 1 259 260 class RegroupNode(Node): 261 def __init__(self, target, expression, var_name): 262 self.target, self.expression = target, expression 263 self.var_name = var_name 264 265 def render(self, context): 266 obj_list = self.target.resolve(context, True) 267 if obj_list == None: 268 # target variable wasn't found in context; fail silently. 269 context[self.var_name] = [] 270 return '' 271 # List of dictionaries in the format: 272 # {'grouper': 'key', 'list': [list of contents]}. 273 context[self.var_name] = [ 274 {'grouper': key, 'list': list(val)} 275 for key, val in 276 groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True)) 277 ] 278 return '' 279 280 def include_is_allowed(filepath): 281 for root in settings.ALLOWED_INCLUDE_ROOTS: 282 if filepath.startswith(root): 283 return True 284 return False 285 286 class SsiNode(Node): 287 def __init__(self, filepath, parsed): 288 self.filepath, self.parsed = filepath, parsed 289 290 def render(self, context): 291 if not include_is_allowed(self.filepath): 292 if settings.DEBUG: 293 return "[Didn't have permission to include file]" 294 else: 295 return '' # Fail silently for invalid includes. 296 try: 297 fp = open(self.filepath, 'r') 298 output = fp.read() 299 fp.close() 300 except IOError: 301 output = '' 302 if self.parsed: 303 try: 304 t = Template(output, name=self.filepath) 305 return t.render(context) 306 except TemplateSyntaxError, e: 307 if settings.DEBUG: 308 return "[Included template had syntax error: %s]" % e 309 else: 310 return '' # Fail silently for invalid included templates. 311 return output 312 313 class LoadNode(Node): 314 def render(self, context): 315 return '' 316 317 class NowNode(Node): 318 def __init__(self, format_string): 319 self.format_string = format_string 320 321 def render(self, context): 322 from datetime import datetime 323 from django.utils.dateformat import DateFormat 324 df = DateFormat(datetime.now()) 325 return df.format(self.format_string) 326 327 class SpacelessNode(Node): 328 def __init__(self, nodelist): 329 self.nodelist = nodelist 330 331 def render(self, context): 332 from django.utils.html import strip_spaces_between_tags 333 return strip_spaces_between_tags(self.nodelist.render(context).strip()) 334 335 class TemplateTagNode(Node): 336 mapping = {'openblock': BLOCK_TAG_START, 337 'closeblock': BLOCK_TAG_END, 338 'openvariable': VARIABLE_TAG_START, 339 'closevariable': VARIABLE_TAG_END, 340 'openbrace': SINGLE_BRACE_START, 341 'closebrace': SINGLE_BRACE_END, 342 'opencomment': COMMENT_TAG_START, 343 'closecomment': COMMENT_TAG_END, 344 } 345 346 def __init__(self, tagtype): 347 self.tagtype = tagtype 348 349 def render(self, context): 350 return self.mapping.get(self.tagtype, '') 351 352 class URLNode(Node): 353 def __init__(self, view_name, args, kwargs): 354 self.view_name = view_name 355 self.args = args 356 self.kwargs = kwargs 357 358 def render(self, context): 359 from django.core.urlresolvers import reverse, NoReverseMatch 360 args = [arg.resolve(context) for arg in self.args] 361 kwargs = dict([(smart_str(k,'ascii'), v.resolve(context)) 362 for k, v in self.kwargs.items()]) 363 try: 364 return reverse(self.view_name, args=args, kwargs=kwargs) 365 except NoReverseMatch: 366 try: 367 project_name = settings.SETTINGS_MODULE.split('.')[0] 368 return reverse(project_name + '.' + self.view_name, 369 args=args, kwargs=kwargs) 370 except NoReverseMatch: 371 return '' 372 373 class WidthRatioNode(Node): 374 def __init__(self, val_expr, max_expr, max_width): 375 self.val_expr = val_expr 376 self.max_expr = max_expr 377 self.max_width = max_width 378 379 def render(self, context): 380 try: 381 value = self.val_expr.resolve(context) 382 maxvalue = self.max_expr.resolve(context) 383 except VariableDoesNotExist: 384 return '' 385 try: 386 value = float(value) 387 maxvalue = float(maxvalue) 388 ratio = (value / maxvalue) * int(self.max_width) 389 except (ValueError, ZeroDivisionError): 390 return '' 391 return str(int(round(ratio))) 392 393 class WithNode(Node): 394 def __init__(self, var, name, nodelist): 395 self.var = var 396 self.name = name 397 self.nodelist = nodelist 398 399 def __repr__(self): 400 return "<WithNode>" 401 402 def render(self, context): 403 val = self.var.resolve(context) 404 context.push() 405 context[self.name] = val 406 output = self.nodelist.render(context) 407 context.pop() 408 return output 409 410 #@register.tag 411 def autoescape(parser, token): 412 """ 413 Force autoescape behaviour for this block. 414 """ 415 args = token.contents.split() 416 if len(args) != 2: 417 raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.") 418 arg = args[1] 419 if arg not in (u'on', u'off'): 420 raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'") 421 nodelist = parser.parse(('endautoescape',)) 422 parser.delete_first_token() 423 return AutoEscapeControlNode((arg == 'on'), nodelist) 424 autoescape = register.tag(autoescape) 425 426 #@register.tag 427 def comment(parser, token): 428 """ 429 Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. 430 """ 431 parser.skip_past('endcomment') 432 return CommentNode() 433 comment = register.tag(comment) 434 435 #@register.tag 436 def cycle(parser, token): 437 """ 438 Cycles among the given strings each time this tag is encountered. 439 440 Within a loop, cycles among the given strings each time through 441 the loop:: 442 443 {% for o in some_list %} 444 <tr class="{% cycle 'row1' 'row2' %}"> 445 ... 446 </tr> 447 {% endfor %} 448 449 Outside of a loop, give the values a unique name the first time you call 450 it, then use that name each sucessive time through:: 451 452 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> 453 <tr class="{% cycle rowcolors %}">...</tr> 454 <tr class="{% cycle rowcolors %}">...</tr> 455 456 You can use any number of values, separated by spaces. Commas can also 457 be used to separate values; if a comma is used, the cycle values are 458 interpreted as literal strings. 459 """ 460 461 # Note: This returns the exact same node on each {% cycle name %} call; 462 # that is, the node object returned from {% cycle a b c as name %} and the 463 # one returned from {% cycle name %} are the exact same object. This 464 # shouldn't cause problems (heh), but if it does, now you know. 465 # 466 # Ugly hack warning: This stuffs the named template dict into parser so 467 # that names are only unique within each template (as opposed to using 468 # a global variable, which would make cycle names have to be unique across 469 # *all* templates. 470 471 args = token.split_contents() 472 473 if len(args) < 2: 474 raise TemplateSyntaxError("'cycle' tag requires at least two arguments") 475 476 if ',' in args[1]: 477 # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %} 478 # case. 479 args[1:2] = ['"%s"' % arg for arg in args[1].split(",")] 480 481 if len(args) == 2: 482 # {% cycle foo %} case. 483 name = args[1] 484 if not hasattr(parser, '_namedCycleNodes'): 485 raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) 486 if not name in parser._namedCycleNodes: 487 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) 488 return parser._namedCycleNodes[name] 489 490 if len(args) > 4 and args[-2] == 'as': 491 name = args[-1] 492 node = CycleNode(args[1:-2], name) 493 if not hasattr(parser, '_namedCycleNodes'): 494 parser._namedCycleNodes = {} 495 parser._namedCycleNodes[name] = node 496 else: 497 node = CycleNode(args[1:]) 498 return node 499 cycle = register.tag(cycle) 500 501 def debug(parser, token): 502 """ 503 Outputs a whole load of debugging information, including the current 504 context and imported modules. 505 506 Sample usage:: 507 508 <pre> 509 {% debug %} 510 </pre> 511 """ 512 return DebugNode() 513 debug = register.tag(debug) 514 515 #@register.tag(name="filter") 516 def do_filter(parser, token): 517 """ 518 Filters the contents of the block through variable filters. 519 520 Filters can also be piped through each other, and they can have 521 arguments -- just like in variable syntax. 522 523 Sample usage:: 524 525 {% filter force_escape|lower %} 526 This text will be HTML-escaped, and will appear in lowercase. 527 {% endfilter %} 528 """ 529 _, rest = token.contents.split(None, 1) 530 filter_expr = parser.compile_filter("var|%s" % (rest)) 531 for func, unused in filter_expr.filters: 532 if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'): 533 raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__) 534 nodelist = parser.parse(('endfilter',)) 535 parser.delete_first_token() 536 return FilterNode(filter_expr, nodelist) 537 do_filter = register.tag("filter", do_filter) 538 539 #@register.tag 540 def firstof(parser, token): 541 """ 542 Outputs the first variable passed that is not False. 543 544 Outputs nothing if all the passed variables are False. 545 546 Sample usage:: 547 548 {% firstof var1 var2 var3 %} 549 550 This is equivalent to:: 551 552 {% if var1 %} 553 {{ var1 }} 554 {% else %}{% if var2 %} 555 {{ var2 }} 556 {% else %}{% if var3 %} 557 {{ var3 }} 558 {% endif %}{% endif %}{% endif %} 559 560 but obviously much cleaner! 561 562 You can also use a literal string as a fallback value in case all 563 passed variables are False:: 564 565 {% firstof var1 var2 var3 "fallback value" %} 566 567 """ 568 bits = token.split_contents()[1:] 569 if len(bits) < 1: 570 raise TemplateSyntaxError("'firstof' statement requires at least one" 571 " argument") 572 return FirstOfNode(bits) 573 firstof = register.tag(firstof) 574 575 #@register.tag(name="for") 576 def do_for(parser, token): 577 """ 578 Loops over each item in an array. 579 580 For example, to display a list of athletes given ``athlete_list``:: 581 582 <ul> 583 {% for athlete in athlete_list %} 584 <li>{{ athlete.name }}</li> 585 {% endfor %} 586 </ul> 587 588 You can loop over a list in reverse by using 589 ``{% for obj in list reversed %}``. 590 591 You can also unpack multiple values from a two-dimensional array:: 592 593 {% for key,value in dict.items %} 594 {{ key }}: {{ value }} 595 {% endfor %} 596 597 The for loop sets a number of variables available within the loop: 598 599 ========================== ================================================ 600 Variable Description 601 ========================== ================================================ 602 ``forloop.counter`` The current iteration of the loop (1-indexed) 603 ``forloop.counter0`` The current iteration of the loop (0-indexed) 604 ``forloop.revcounter`` The number of iterations from the end of the 605 loop (1-indexed) 606 ``forloop.revcounter0`` The number of iterations from the end of the 607 loop (0-indexed) 608 ``forloop.first`` True if this is the first time through the loop 609 ``forloop.last`` True if this is the last time through the loop 610 ``forloop.parentloop`` For nested loops, this is the loop "above" the 611 current one 612 ========================== ================================================ 613 614 """ 615 bits = token.contents.split() 616 if len(bits) < 4: 617 raise TemplateSyntaxError("'for' statements should have at least four" 618 " words: %s" % token.contents) 619 620 is_reversed = bits[-1] == 'reversed' 621 in_index = is_reversed and -3 or -2 622 if bits[in_index] != 'in': 623 raise TemplateSyntaxError("'for' statements should use the format" 624 " 'for x in y': %s" % token.contents) 625 626 loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',') 627 for var in loopvars: 628 if not var or ' ' in var: 629 raise TemplateSyntaxError("'for' tag received an invalid argument:" 630 " %s" % token.contents) 631 632 sequence = parser.compile_filter(bits[in_index+1]) 633 nodelist_loop = parser.parse(('endfor',)) 634 parser.delete_first_token() 635 return ForNode(loopvars, sequence, is_reversed, nodelist_loop) 636 do_for = register.tag("for", do_for) 637 638 def do_ifequal(parser, token, negate): 639 bits = list(token.split_contents()) 640 if len(bits) != 3: 641 raise TemplateSyntaxError, "%r takes two arguments" % bits[0] 642 end_tag = 'end' + bits[0] 643 nodelist_true = parser.parse(('else', end_tag)) 644 token = parser.next_token() 645 if token.contents == 'else': 646 nodelist_false = parser.parse((end_tag,)) 647 parser.delete_first_token() 648 else: 649 nodelist_false = NodeList() 650 return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate) 651 652 #@register.tag 653 def ifequal(parser, token): 654 """ 655 Outputs the contents of the block if the two arguments equal each other. 656 657 Examples:: 658 659 {% ifequal user.id comment.user_id %} 660 ... 661 {% endifequal %} 662 663 {% ifnotequal user.id comment.user_id %} 664 ... 665 {% else %} 666 ... 667 {% endifnotequal %} 668 """ 669 return do_ifequal(parser, token, False) 670 ifequal = register.tag(ifequal) 671 672 #@register.tag 673 def ifnotequal(parser, token): 674 """ 675 Outputs the contents of the block if the two arguments are not equal. 676 See ifequal. 677 """ 678 return do_ifequal(parser, token, True) 679 ifnotequal = register.tag(ifnotequal) 680 681 #@register.tag(name="if") 682 def do_if(parser, token): 683 """ 684 The ``{% if %}`` tag evaluates a variable, and if that variable is "true" 685 (i.e., exists, is not empty, and is not a false boolean value), the 686 contents of the block are output: 687 688 :: 689 690 {% if athlete_list %} 691 Number of athletes: {{ athlete_list|count }} 692 {% else %} 693 No athletes. 694 {% endif %} 695 696 In the above, if ``athlete_list`` is not empty, the number of athletes will 697 be displayed by the ``{{ athlete_list|count }}`` variable. 698 699 As you can see, the ``if`` tag can take an option ``{% else %}`` clause 700 that will be displayed if the test fails. 701 702 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of 703 variables or to negate a given variable:: 704 705 {% if not athlete_list %} 706 There are no athletes. 707 {% endif %} 708 709 {% if athlete_list or coach_list %} 710 There are some athletes or some coaches. 711 {% endif %} 712 713 {% if athlete_list and coach_list %} 714 Both atheletes and coaches are available. 715 {% endif %} 716 717 {% if not athlete_list or coach_list %} 718 There are no athletes, or there are some coaches. 719 {% endif %} 720 721 {% if athlete_list and not coach_list %} 722 There are some athletes and absolutely no coaches. 723 {% endif %} 724 725 ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag, 726 because the order of logic would be ambigous. For example, this is 727 invalid:: 728 729 {% if athlete_list and coach_list or cheerleader_list %} 730 731 If you need to combine ``and`` and ``or`` to do advanced logic, just use 732 nested if tags. For example:: 733 734 {% if athlete_list %} 735 {% if coach_list or cheerleader_list %} 736 We have athletes, and either coaches or cheerleaders! 737 {% endif %} 738 {% endif %} 739 """ 740 bits = token.contents.split() 741 del bits[0] 742 if not bits: 743 raise TemplateSyntaxError("'if' statement requires at least one argument") 744 # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d'] 745 bitstr = ' '.join(bits) 746 boolpairs = bitstr.split(' and ') 747 boolvars = [] 748 if len(boolpairs) == 1: 749 link_type = IfNode.LinkTypes.or_ 750 boolpairs = bitstr.split(' or ') 751 else: 752 link_type = IfNode.LinkTypes.and_ 753 if ' or ' in bitstr: 754 raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'" 755 for boolpair in boolpairs: 756 if ' ' in boolpair: 757 try: 758 not_, boolvar = boolpair.split() 759 except ValueError: 760 raise TemplateSyntaxError, "'if' statement improperly formatted" 761 if not_ != 'not': 762 raise TemplateSyntaxError, "Expected 'not' in if statement" 763 boolvars.append((True, parser.compile_filter(boolvar))) 764 else: 765 boolvars.append((False, parser.compile_filter(boolpair))) 766 nodelist_true = parser.parse(('else', 'endif')) 767 token = parser.next_token() 768 if token.contents == 'else': 769 nodelist_false = parser.parse(('endif',)) 770 parser.delete_first_token() 771 else: 772 nodelist_false = NodeList() 773 return IfNode(boolvars, nodelist_true, nodelist_false, link_type) 774 do_if = register.tag("if", do_if) 775 776 #@register.tag 777 def ifchanged(parser, token): 778 """ 779 Checks if a value has changed from the last iteration of a loop. 780 781 The 'ifchanged' block tag is used within a loop. It has two possible uses. 782 783 1. Checks its own rendered contents against its previous state and only 784 displays the content if it has changed. For example, this displays a 785 list of days, only displaying the month if it changes:: 786 787 <h1>Archive for {{ year }}</h1> 788 789 {% for date in days %} 790 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} 791 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> 792 {% endfor %} 793 794 2. If given a variable, check whether that variable has changed. 795 For example, the following shows the date every time it changes, but 796 only shows the hour if both the hour and the date have changed:: 797 798 {% for date in days %} 799 {% ifchanged date.date %} {{ date.date }} {% endifchanged %} 800 {% ifchanged date.hour date.date %} 801 {{ date.hour }} 802 {% endifchanged %} 803 {% endfor %} 804 """ 805 bits = token.contents.split() 806 nodelist = parser.parse(('endifchanged',)) 807 parser.delete_first_token() 808 return IfChangedNode(nodelist, *bits[1:]) 809 ifchanged = register.tag(ifchanged) 810 811 #@register.tag 812 def ssi(parser, token): 813 """ 814 Outputs the contents of a given file into the page. 815 816 Like a simple "include" tag, the ``ssi`` tag includes the contents 817 of another file -- which must be specified using an absolute path -- 818 in the current page:: 819 820 {% ssi /home/html/ljworld.com/includes/right_generic.html %} 821 822 If the optional "parsed" parameter is given, the contents of the included 823 file are evaluated as template code, with the current context:: 824 825 {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %} 826 """ 827 bits = token.contents.split() 828 parsed = False 829 if len(bits) not in (2, 3): 830 raise TemplateSyntaxError("'ssi' tag takes one argument: the path to" 831 " the file to be included") 832 if len(bits) == 3: 833 if bits[2] == 'parsed': 834 parsed = True 835 else: 836 raise TemplateSyntaxError("Second (optional) argument to %s tag" 837 " must be 'parsed'" % bits[0]) 838 return SsiNode(bits[1], parsed) 839 ssi = register.tag(ssi) 840 841 #@register.tag 842 def load(parser, token): 843 """ 844 Loads a custom template tag set. 845 846 For example, to load the template tags in 847 ``django/templatetags/news/photos.py``:: 848 849 {% load news.photos %} 850 """ 851 bits = token.contents.split() 852 for taglib in bits[1:]: 853 # add the library to the parser 854 try: 855 lib = get_library(taglib) 856 parser.add_library(lib) 857 except InvalidTemplateLibrary, e: 858 raise TemplateSyntaxError("'%s' is not a valid tag library: %s" % 859 (taglib, e)) 860 return LoadNode() 861 load = register.tag(load) 862 863 #@register.tag 864 def now(parser, token): 865 """ 866 Displays the date, formatted according to the given string. 867 868 Uses the same format as PHP's ``date()`` function; see http://php.net/date 869 for all the possible values. 870 871 Sample usage:: 872 873 It is {% now "jS F Y H:i" %} 874 """ 875 bits = token.contents.split('"') 876 if len(bits) != 3: 877 raise TemplateSyntaxError, "'now' statement takes one argument" 878 format_string = bits[1] 879 return NowNode(format_string) 880 now = register.tag(now) 881 882 #@register.tag 883 def regroup(parser, token): 884 """ 885 Regroups a list of alike objects by a common attribute. 886 887 This complex tag is best illustrated by use of an example: say that 888 ``people`` is a list of ``Person`` objects that have ``first_name``, 889 ``last_name``, and ``gender`` attributes, and you'd like to display a list 890 that looks like: 891 892 * Male: 893 * George Bush 894 * Bill Clinton 895 * Female: 896 * Margaret Thatcher 897 * Colendeeza Rice 898 * Unknown: 899 * Pat Smith 900 901 The following snippet of template code would accomplish this dubious task:: 902 903 {% regroup people by gender as grouped %} 904 <ul> 905 {% for group in grouped %} 906 <li>{{ group.grouper }} 907 <ul> 908 {% for item in group.list %} 909 <li>{{ item }}</li> 910 {% endfor %} 911 </ul> 912 {% endfor %} 913 </ul> 914 915 As you can see, ``{% regroup %}`` populates a variable with a list of 916 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the 917 item that was grouped by; ``list`` contains the list of objects that share 918 that ``grouper``. In this case, ``grouper`` would be ``Male``, ``Female`` 919 and ``Unknown``, and ``list`` is the list of people with those genders. 920 921 Note that `{% regroup %}`` does not work when the list to be grouped is not 922 sorted by the key you are grouping by! This means that if your list of 923 people was not sorted by gender, you'd need to make sure it is sorted 924 before using it, i.e.:: 925 926 {% regroup people|dictsort:"gender" by gender as grouped %} 927 928 """ 929 firstbits = token.contents.split(None, 3) 930 if len(firstbits) != 4: 931 raise TemplateSyntaxError, "'regroup' tag takes five arguments" 932 target = parser.compile_filter(firstbits[1]) 933 if firstbits[2] != 'by': 934 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") 935 lastbits_reversed = firstbits[3][::-1].split(None, 2) 936 if lastbits_reversed[1][::-1] != 'as': 937 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" 938 " be 'as'") 939 940 expression = parser.compile_filter(lastbits_reversed[2][::-1]) 941 942 var_name = lastbits_reversed[0][::-1] 943 return RegroupNode(target, expression, var_name) 944 regroup = register.tag(regroup) 945 946 def spaceless(parser, token): 947 """ 948 Removes whitespace between HTML tags, including tab and newline characters. 949 950 Example usage:: 951 952 {% spaceless %} 953 <p> 954 <a href="foo/">Foo</a> 955 </p> 956 {% endspaceless %} 957 958 This example would return this HTML:: 959 960 <p><a href="foo/">Foo</a></p> 961 962 Only space between *tags* is normalized -- not space between tags and text. 963 In this example, the space around ``Hello`` won't be stripped:: 964 965 {% spaceless %} 966 <strong> 967 Hello 968 </strong> 969 {% endspaceless %} 970 """ 971 nodelist = parser.parse(('endspaceless',)) 972 parser.delete_first_token() 973 return SpacelessNode(nodelist) 974 spaceless = register.tag(spaceless) 975 976 #@register.tag 977 def templatetag(parser, token): 978 """ 979 Outputs one of the bits used to compose template tags. 980 981 Since the template system has no concept of "escaping", to display one of 982 the bits used in template tags, you must use the ``{% templatetag %}`` tag. 983 984 The argument tells which template bit to output: 985 986 ================== ======= 987 Argument Outputs 988 ================== ======= 989 ``openblock`` ``{%`` 990 ``closeblock`` ``%}`` 991 ``openvariable`` ``{{`` 992 ``closevariable`` ``}}`` 993 ``openbrace`` ``{`` 994 ``closebrace`` ``}`` 995 ``opencomment`` ``{#`` 996 ``closecomment`` ``#}`` 997 ================== ======= 998 """ 999 bits = token.contents.split() 1000 if len(bits) != 2: 1001 raise TemplateSyntaxError, "'templatetag' statement takes one argument" 1002 tag = bits[1] 1003 if tag not in TemplateTagNode.mapping: 1004 raise TemplateSyntaxError("Invalid templatetag argument: '%s'." 1005 " Must be one of: %s" % 1006 (tag, TemplateTagNode.mapping.keys())) 1007 return TemplateTagNode(tag) 1008 templatetag = register.tag(templatetag) 1009 1010 def url(parser, token): 1011 """ 1012 Returns an absolute URL matching given view with its parameters. 1013 1014 This is a way to define links that aren't tied to a particular URL 1015 configuration:: 1016 1017 {% url path.to.some_view arg1,arg2,name1=value1 %} 1018 1019 The first argument is a path to a view. It can be an absolute python path 1020 or just ``app_name.view_name`` without the project name if the view is 1021 located inside the project. Other arguments are comma-separated values 1022 that will be filled in place of positional and keyword arguments in the 1023 URL. All arguments for the URL should be present. 1024 1025 For example if you have a view ``app_name.client`` taking client's id and 1026 the corresponding line in a URLconf looks like this:: 1027 1028 ('^client/(\d+)/$', 'app_name.client') 1029 1030 and this app's URLconf is included into the project's URLconf under some 1031 path:: 1032 1033 ('^clients/', include('project_name.app_name.urls')) 1034 1035 then in a template you can create a link for a certain client like this:: 1036 1037 {% url app_name.client client.id %} 1038 1039 The URL will look like ``/clients/client/123/``. 1040 """ 1041 bits = token.contents.split(' ', 2) 1042 if len(bits) < 2: 1043 raise TemplateSyntaxError("'%s' takes at least one argument" 1044 " (path to a view)" % bits[0]) 1045 args = [] 1046 kwargs = {} 1047 if len(bits) > 2: 1048 for arg in bits[2].split(','): 1049 if '=' in arg: 1050 k, v = arg.split('=', 1) 1051 k = k.strip() 1052 kwargs[k] = parser.compile_filter(v) 1053 else: 1054 args.append(parser.compile_filter(arg)) 1055 return URLNode(bits[1], args, kwargs) 1056 url = register.tag(url) 1057 1058 #@register.tag 1059 def widthratio(parser, token): 1060 """ 1061 For creating bar charts and such, this tag calculates the ratio of a given 1062 value to a maximum value, and then applies that ratio to a constant. 1063 1064 For example:: 1065 1066 <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' /> 1067 1068 Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in 1069 the above example will be 88 pixels wide (because 175/200 = .875; 1070 .875 * 100 = 87.5 which is rounded up to 88). 1071 """ 1072 bits = token.contents.split() 1073 if len(bits) != 4: 1074 raise TemplateSyntaxError("widthratio takes three arguments") 1075 tag, this_value_expr, max_value_expr, max_width = bits 1076 try: 1077 max_width = int(max_width) 1078 except ValueError: 1079 raise TemplateSyntaxError("widthratio final argument must be an integer") 1080 return WidthRatioNode(parser.compile_filter(this_value_expr), 1081 parser.compile_filter(max_value_expr), max_width) 1082 widthratio = register.tag(widthratio) 1083 1084 #@register.tag 1085 def do_with(parser, token): 1086 """ 1087 Adds a value to the context (inside of this block) for caching and easy 1088 access. 1089 1090 For example:: 1091 1092 {% with person.some_sql_method as total %} 1093 {{ total }} object{{ total|pluralize }} 1094 {% endwith %} 1095 """ 1096 bits = list(token.split_contents()) 1097 if len(bits) != 4 or bits[2] != "as": 1098 raise TemplateSyntaxError("%r expected format is 'value as name'" % 1099 bits[0]) 1100 var = parser.compile_filter(bits[1]) 1101 name = bits[3] 1102 nodelist = parser.parse(('endwith',)) 1103 parser.delete_first_token() 1104 return WithNode(var, name, nodelist) 1105 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 186b8aa..ac0912d 100644
a b def do_echo(parser, token): 51 51 52 52 register.tag("echo", do_echo) 53 53 54 template.libraries[' django.templatetags.testtags'] = register54 template.libraries['testtags'] = register 55 55 56 56 ##################################### 57 57 # Helper objects for template tests #