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