| | 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) |