|
0
|
1 |
"""Default variable filters.""" |
|
|
2 |
|
|
|
3 |
import re |
|
|
4 |
|
|
|
5 |
try: |
|
|
6 |
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP |
|
|
7 |
except ImportError: |
|
|
8 |
from django.utils._decimal import Decimal, InvalidOperation, ROUND_HALF_UP |
|
|
9 |
|
|
|
10 |
import random as random_module |
|
|
11 |
try: |
|
|
12 |
from functools import wraps |
|
|
13 |
except ImportError: |
|
|
14 |
from django.utils.functional import wraps # Python 2.3, 2.4 fallback. |
|
|
15 |
|
|
|
16 |
from django.template import Variable, Library |
|
|
17 |
from django.conf import settings |
|
|
18 |
from django.utils.translation import ugettext, ungettext |
|
|
19 |
from django.utils.encoding import force_unicode, iri_to_uri |
|
|
20 |
from django.utils.safestring import mark_safe, SafeData |
|
|
21 |
|
|
|
22 |
register = Library() |
|
|
23 |
|
|
|
24 |
####################### |
|
|
25 |
# STRING DECORATOR # |
|
|
26 |
####################### |
|
|
27 |
|
|
|
28 |
def stringfilter(func): |
|
|
29 |
""" |
|
|
30 |
Decorator for filters which should only receive unicode objects. The object |
|
|
31 |
passed as the first positional argument will be converted to a unicode |
|
|
32 |
object. |
|
|
33 |
""" |
|
|
34 |
def _dec(*args, **kwargs): |
|
|
35 |
if args: |
|
|
36 |
args = list(args) |
|
|
37 |
args[0] = force_unicode(args[0]) |
|
|
38 |
if isinstance(args[0], SafeData) and getattr(func, 'is_safe', False): |
|
|
39 |
return mark_safe(func(*args, **kwargs)) |
|
|
40 |
return func(*args, **kwargs) |
|
|
41 |
|
|
|
42 |
# Include a reference to the real function (used to check original |
|
|
43 |
# arguments by the template parser). |
|
|
44 |
_dec._decorated_function = getattr(func, '_decorated_function', func) |
|
|
45 |
for attr in ('is_safe', 'needs_autoescape'): |
|
|
46 |
if hasattr(func, attr): |
|
|
47 |
setattr(_dec, attr, getattr(func, attr)) |
|
|
48 |
return wraps(func)(_dec) |
|
|
49 |
|
|
|
50 |
################### |
|
|
51 |
# STRINGS # |
|
|
52 |
################### |
|
|
53 |
|
|
|
54 |
def addslashes(value): |
|
|
55 |
""" |
|
|
56 |
Adds slashes before quotes. Useful for escaping strings in CSV, for |
|
|
57 |
example. Less useful for escaping JavaScript; use the ``escapejs`` |
|
|
58 |
filter instead. |
|
|
59 |
""" |
|
|
60 |
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'") |
|
|
61 |
addslashes.is_safe = True |
|
|
62 |
addslashes = stringfilter(addslashes) |
|
|
63 |
|
|
|
64 |
def capfirst(value): |
|
|
65 |
"""Capitalizes the first character of the value.""" |
|
|
66 |
return value and value[0].upper() + value[1:] |
|
|
67 |
capfirst.is_safe=True |
|
|
68 |
capfirst = stringfilter(capfirst) |
|
|
69 |
|
|
|
70 |
_base_js_escapes = ( |
|
|
71 |
('\\', r'\x5C'), |
|
|
72 |
('\'', r'\x27'), |
|
|
73 |
('"', r'\x22'), |
|
|
74 |
('>', r'\x3E'), |
|
|
75 |
('<', r'\x3C'), |
|
|
76 |
('&', r'\x26'), |
|
|
77 |
('=', r'\x3D'), |
|
|
78 |
('-', r'\x2D'), |
|
|
79 |
(';', r'\x3B'), |
|
|
80 |
(u'\u2028', r'\u2028'), |
|
|
81 |
(u'\u2029', r'\u2029') |
|
|
82 |
) |
|
|
83 |
|
|
|
84 |
# Escape every ASCII character with a value less than 32. |
|
|
85 |
_js_escapes = (_base_js_escapes + |
|
|
86 |
tuple([('%c' % z, '\\x%02X' % z) for z in range(32)])) |
|
|
87 |
|
|
|
88 |
def escapejs(value): |
|
|
89 |
"""Hex encodes characters for use in JavaScript strings.""" |
|
|
90 |
for bad, good in _js_escapes: |
|
|
91 |
value = value.replace(bad, good) |
|
|
92 |
return value |
|
|
93 |
escapejs = stringfilter(escapejs) |
|
|
94 |
|
|
|
95 |
def fix_ampersands(value): |
|
|
96 |
"""Replaces ampersands with ``&`` entities.""" |
|
|
97 |
from django.utils.html import fix_ampersands |
|
|
98 |
return fix_ampersands(value) |
|
|
99 |
fix_ampersands.is_safe=True |
|
|
100 |
fix_ampersands = stringfilter(fix_ampersands) |
|
|
101 |
|
|
|
102 |
# Values for testing floatformat input against infinity and NaN representations, |
|
|
103 |
# which differ across platforms and Python versions. Some (i.e. old Windows |
|
|
104 |
# ones) are not recognized by Decimal but we want to return them unchanged vs. |
|
|
105 |
# returning an empty string as we do for completley invalid input. Note these |
|
|
106 |
# need to be built up from values that are not inf/nan, since inf/nan values do |
|
|
107 |
# not reload properly from .pyc files on Windows prior to some level of Python 2.5 |
|
|
108 |
# (see Python Issue757815 and Issue1080440). |
|
|
109 |
pos_inf = 1e200 * 1e200 |
|
|
110 |
neg_inf = -1e200 * 1e200 |
|
|
111 |
nan = (1e200 * 1e200) / (1e200 * 1e200) |
|
|
112 |
special_floats = [str(pos_inf), str(neg_inf), str(nan)] |
|
|
113 |
|
|
|
114 |
def floatformat(text, arg=-1): |
|
|
115 |
""" |
|
|
116 |
Displays a float to a specified number of decimal places. |
|
|
117 |
|
|
|
118 |
If called without an argument, it displays the floating point number with |
|
|
119 |
one decimal place -- but only if there's a decimal place to be displayed: |
|
|
120 |
|
|
|
121 |
* num1 = 34.23234 |
|
|
122 |
* num2 = 34.00000 |
|
|
123 |
* num3 = 34.26000 |
|
|
124 |
* {{ num1|floatformat }} displays "34.2" |
|
|
125 |
* {{ num2|floatformat }} displays "34" |
|
|
126 |
* {{ num3|floatformat }} displays "34.3" |
|
|
127 |
|
|
|
128 |
If arg is positive, it will always display exactly arg number of decimal |
|
|
129 |
places: |
|
|
130 |
|
|
|
131 |
* {{ num1|floatformat:3 }} displays "34.232" |
|
|
132 |
* {{ num2|floatformat:3 }} displays "34.000" |
|
|
133 |
* {{ num3|floatformat:3 }} displays "34.260" |
|
|
134 |
|
|
|
135 |
If arg is negative, it will display arg number of decimal places -- but |
|
|
136 |
only if there are places to be displayed: |
|
|
137 |
|
|
|
138 |
* {{ num1|floatformat:"-3" }} displays "34.232" |
|
|
139 |
* {{ num2|floatformat:"-3" }} displays "34" |
|
|
140 |
* {{ num3|floatformat:"-3" }} displays "34.260" |
|
|
141 |
|
|
|
142 |
If the input float is infinity or NaN, the (platform-dependent) string |
|
|
143 |
representation of that value will be displayed. |
|
|
144 |
""" |
|
|
145 |
|
|
|
146 |
try: |
|
|
147 |
input_val = force_unicode(text) |
|
|
148 |
d = Decimal(input_val) |
|
|
149 |
except UnicodeEncodeError: |
|
|
150 |
return u'' |
|
|
151 |
except InvalidOperation: |
|
|
152 |
if input_val in special_floats: |
|
|
153 |
return input_val |
|
|
154 |
try: |
|
|
155 |
d = Decimal(force_unicode(float(text))) |
|
|
156 |
except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError): |
|
|
157 |
return u'' |
|
|
158 |
try: |
|
|
159 |
p = int(arg) |
|
|
160 |
except ValueError: |
|
|
161 |
return input_val |
|
|
162 |
|
|
|
163 |
try: |
|
|
164 |
m = int(d) - d |
|
|
165 |
except (OverflowError, InvalidOperation): |
|
|
166 |
return input_val |
|
|
167 |
|
|
|
168 |
if not m and p < 0: |
|
|
169 |
return mark_safe(u'%d' % (int(d))) |
|
|
170 |
|
|
|
171 |
if p == 0: |
|
|
172 |
exp = Decimal(1) |
|
|
173 |
else: |
|
|
174 |
exp = Decimal('1.0') / (Decimal(10) ** abs(p)) |
|
|
175 |
try: |
|
|
176 |
return mark_safe(u'%s' % str(d.quantize(exp, ROUND_HALF_UP))) |
|
|
177 |
except InvalidOperation: |
|
|
178 |
return input_val |
|
|
179 |
floatformat.is_safe = True |
|
|
180 |
|
|
|
181 |
def iriencode(value): |
|
|
182 |
"""Escapes an IRI value for use in a URL.""" |
|
|
183 |
return force_unicode(iri_to_uri(value)) |
|
|
184 |
iriencode.is_safe = True |
|
|
185 |
iriencode = stringfilter(iriencode) |
|
|
186 |
|
|
|
187 |
def linenumbers(value, autoescape=None): |
|
|
188 |
"""Displays text with line numbers.""" |
|
|
189 |
from django.utils.html import escape |
|
|
190 |
lines = value.split(u'\n') |
|
|
191 |
# Find the maximum width of the line count, for use with zero padding |
|
|
192 |
# string format command |
|
|
193 |
width = unicode(len(unicode(len(lines)))) |
|
|
194 |
if not autoescape or isinstance(value, SafeData): |
|
|
195 |
for i, line in enumerate(lines): |
|
|
196 |
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line) |
|
|
197 |
else: |
|
|
198 |
for i, line in enumerate(lines): |
|
|
199 |
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line)) |
|
|
200 |
return mark_safe(u'\n'.join(lines)) |
|
|
201 |
linenumbers.is_safe = True |
|
|
202 |
linenumbers.needs_autoescape = True |
|
|
203 |
linenumbers = stringfilter(linenumbers) |
|
|
204 |
|
|
|
205 |
def lower(value): |
|
|
206 |
"""Converts a string into all lowercase.""" |
|
|
207 |
return value.lower() |
|
|
208 |
lower.is_safe = True |
|
|
209 |
lower = stringfilter(lower) |
|
|
210 |
|
|
|
211 |
def make_list(value): |
|
|
212 |
""" |
|
|
213 |
Returns the value turned into a list. |
|
|
214 |
|
|
|
215 |
For an integer, it's a list of digits. |
|
|
216 |
For a string, it's a list of characters. |
|
|
217 |
""" |
|
|
218 |
return list(value) |
|
|
219 |
make_list.is_safe = False |
|
|
220 |
make_list = stringfilter(make_list) |
|
|
221 |
|
|
|
222 |
def slugify(value): |
|
|
223 |
""" |
|
|
224 |
Normalizes string, converts to lowercase, removes non-alpha characters, |
|
|
225 |
and converts spaces to hyphens. |
|
|
226 |
""" |
|
|
227 |
import unicodedata |
|
|
228 |
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore') |
|
|
229 |
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower()) |
|
|
230 |
return mark_safe(re.sub('[-\s]+', '-', value)) |
|
|
231 |
slugify.is_safe = True |
|
|
232 |
slugify = stringfilter(slugify) |
|
|
233 |
|
|
|
234 |
def stringformat(value, arg): |
|
|
235 |
""" |
|
|
236 |
Formats the variable according to the arg, a string formatting specifier. |
|
|
237 |
|
|
|
238 |
This specifier uses Python string formating syntax, with the exception that |
|
|
239 |
the leading "%" is dropped. |
|
|
240 |
|
|
|
241 |
See http://docs.python.org/lib/typesseq-strings.html for documentation |
|
|
242 |
of Python string formatting |
|
|
243 |
""" |
|
|
244 |
try: |
|
|
245 |
return (u"%" + unicode(arg)) % value |
|
|
246 |
except (ValueError, TypeError): |
|
|
247 |
return u"" |
|
|
248 |
stringformat.is_safe = True |
|
|
249 |
|
|
|
250 |
def title(value): |
|
|
251 |
"""Converts a string into titlecase.""" |
|
|
252 |
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title()) |
|
|
253 |
title.is_safe = True |
|
|
254 |
title = stringfilter(title) |
|
|
255 |
|
|
|
256 |
def truncatewords(value, arg): |
|
|
257 |
""" |
|
|
258 |
Truncates a string after a certain number of words. |
|
|
259 |
|
|
|
260 |
Argument: Number of words to truncate after. |
|
|
261 |
""" |
|
|
262 |
from django.utils.text import truncate_words |
|
|
263 |
try: |
|
|
264 |
length = int(arg) |
|
|
265 |
except ValueError: # Invalid literal for int(). |
|
|
266 |
return value # Fail silently. |
|
|
267 |
return truncate_words(value, length) |
|
|
268 |
truncatewords.is_safe = True |
|
|
269 |
truncatewords = stringfilter(truncatewords) |
|
|
270 |
|
|
|
271 |
def truncatewords_html(value, arg): |
|
|
272 |
""" |
|
|
273 |
Truncates HTML after a certain number of words. |
|
|
274 |
|
|
|
275 |
Argument: Number of words to truncate after. |
|
|
276 |
""" |
|
|
277 |
from django.utils.text import truncate_html_words |
|
|
278 |
try: |
|
|
279 |
length = int(arg) |
|
|
280 |
except ValueError: # invalid literal for int() |
|
|
281 |
return value # Fail silently. |
|
|
282 |
return truncate_html_words(value, length) |
|
|
283 |
truncatewords_html.is_safe = True |
|
|
284 |
truncatewords_html = stringfilter(truncatewords_html) |
|
|
285 |
|
|
|
286 |
def upper(value): |
|
|
287 |
"""Converts a string into all uppercase.""" |
|
|
288 |
return value.upper() |
|
|
289 |
upper.is_safe = False |
|
|
290 |
upper = stringfilter(upper) |
|
|
291 |
|
|
|
292 |
def urlencode(value): |
|
|
293 |
"""Escapes a value for use in a URL.""" |
|
|
294 |
from django.utils.http import urlquote |
|
|
295 |
return urlquote(value) |
|
|
296 |
urlencode.is_safe = False |
|
|
297 |
urlencode = stringfilter(urlencode) |
|
|
298 |
|
|
|
299 |
def urlize(value, autoescape=None): |
|
|
300 |
"""Converts URLs in plain text into clickable links.""" |
|
|
301 |
from django.utils.html import urlize |
|
|
302 |
return mark_safe(urlize(value, nofollow=True, autoescape=autoescape)) |
|
|
303 |
urlize.is_safe=True |
|
|
304 |
urlize.needs_autoescape = True |
|
|
305 |
urlize = stringfilter(urlize) |
|
|
306 |
|
|
|
307 |
def urlizetrunc(value, limit, autoescape=None): |
|
|
308 |
""" |
|
|
309 |
Converts URLs into clickable links, truncating URLs to the given character |
|
|
310 |
limit, and adding 'rel=nofollow' attribute to discourage spamming. |
|
|
311 |
|
|
|
312 |
Argument: Length to truncate URLs to. |
|
|
313 |
""" |
|
|
314 |
from django.utils.html import urlize |
|
|
315 |
return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True, |
|
|
316 |
autoescape=autoescape)) |
|
|
317 |
urlizetrunc.is_safe = True |
|
|
318 |
urlizetrunc.needs_autoescape = True |
|
|
319 |
urlizetrunc = stringfilter(urlizetrunc) |
|
|
320 |
|
|
|
321 |
def wordcount(value): |
|
|
322 |
"""Returns the number of words.""" |
|
|
323 |
return len(value.split()) |
|
|
324 |
wordcount.is_safe = False |
|
|
325 |
wordcount = stringfilter(wordcount) |
|
|
326 |
|
|
|
327 |
def wordwrap(value, arg): |
|
|
328 |
""" |
|
|
329 |
Wraps words at specified line length. |
|
|
330 |
|
|
|
331 |
Argument: number of characters to wrap the text at. |
|
|
332 |
""" |
|
|
333 |
from django.utils.text import wrap |
|
|
334 |
return wrap(value, int(arg)) |
|
|
335 |
wordwrap.is_safe = True |
|
|
336 |
wordwrap = stringfilter(wordwrap) |
|
|
337 |
|
|
|
338 |
def ljust(value, arg): |
|
|
339 |
""" |
|
|
340 |
Left-aligns the value in a field of a given width. |
|
|
341 |
|
|
|
342 |
Argument: field size. |
|
|
343 |
""" |
|
|
344 |
return value.ljust(int(arg)) |
|
|
345 |
ljust.is_safe = True |
|
|
346 |
ljust = stringfilter(ljust) |
|
|
347 |
|
|
|
348 |
def rjust(value, arg): |
|
|
349 |
""" |
|
|
350 |
Right-aligns the value in a field of a given width. |
|
|
351 |
|
|
|
352 |
Argument: field size. |
|
|
353 |
""" |
|
|
354 |
return value.rjust(int(arg)) |
|
|
355 |
rjust.is_safe = True |
|
|
356 |
rjust = stringfilter(rjust) |
|
|
357 |
|
|
|
358 |
def center(value, arg): |
|
|
359 |
"""Centers the value in a field of a given width.""" |
|
|
360 |
return value.center(int(arg)) |
|
|
361 |
center.is_safe = True |
|
|
362 |
center = stringfilter(center) |
|
|
363 |
|
|
|
364 |
def cut(value, arg): |
|
|
365 |
""" |
|
|
366 |
Removes all values of arg from the given string. |
|
|
367 |
""" |
|
|
368 |
safe = isinstance(value, SafeData) |
|
|
369 |
value = value.replace(arg, u'') |
|
|
370 |
if safe and arg != ';': |
|
|
371 |
return mark_safe(value) |
|
|
372 |
return value |
|
|
373 |
cut = stringfilter(cut) |
|
|
374 |
|
|
|
375 |
################### |
|
|
376 |
# HTML STRINGS # |
|
|
377 |
################### |
|
|
378 |
|
|
|
379 |
def escape(value): |
|
|
380 |
""" |
|
|
381 |
Marks the value as a string that should not be auto-escaped. |
|
|
382 |
""" |
|
|
383 |
from django.utils.safestring import mark_for_escaping |
|
|
384 |
return mark_for_escaping(value) |
|
|
385 |
escape.is_safe = True |
|
|
386 |
escape = stringfilter(escape) |
|
|
387 |
|
|
|
388 |
def force_escape(value): |
|
|
389 |
""" |
|
|
390 |
Escapes a string's HTML. This returns a new string containing the escaped |
|
|
391 |
characters (as opposed to "escape", which marks the content for later |
|
|
392 |
possible escaping). |
|
|
393 |
""" |
|
|
394 |
from django.utils.html import escape |
|
|
395 |
return mark_safe(escape(value)) |
|
|
396 |
force_escape = stringfilter(force_escape) |
|
|
397 |
force_escape.is_safe = True |
|
|
398 |
|
|
|
399 |
def linebreaks(value, autoescape=None): |
|
|
400 |
""" |
|
|
401 |
Replaces line breaks in plain text with appropriate HTML; a single |
|
|
402 |
newline becomes an HTML line break (``<br />``) and a new line |
|
|
403 |
followed by a blank line becomes a paragraph break (``</p>``). |
|
|
404 |
""" |
|
|
405 |
from django.utils.html import linebreaks |
|
|
406 |
autoescape = autoescape and not isinstance(value, SafeData) |
|
|
407 |
return mark_safe(linebreaks(value, autoescape)) |
|
|
408 |
linebreaks.is_safe = True |
|
|
409 |
linebreaks.needs_autoescape = True |
|
|
410 |
linebreaks = stringfilter(linebreaks) |
|
|
411 |
|
|
|
412 |
def linebreaksbr(value, autoescape=None): |
|
|
413 |
""" |
|
|
414 |
Converts all newlines in a piece of plain text to HTML line breaks |
|
|
415 |
(``<br />``). |
|
|
416 |
""" |
|
|
417 |
if autoescape and not isinstance(value, SafeData): |
|
|
418 |
from django.utils.html import escape |
|
|
419 |
value = escape(value) |
|
|
420 |
return mark_safe(value.replace('\n', '<br />')) |
|
|
421 |
linebreaksbr.is_safe = True |
|
|
422 |
linebreaksbr.needs_autoescape = True |
|
|
423 |
linebreaksbr = stringfilter(linebreaksbr) |
|
|
424 |
|
|
|
425 |
def safe(value): |
|
|
426 |
""" |
|
|
427 |
Marks the value as a string that should not be auto-escaped. |
|
|
428 |
""" |
|
|
429 |
return mark_safe(value) |
|
|
430 |
safe.is_safe = True |
|
|
431 |
safe = stringfilter(safe) |
|
|
432 |
|
|
|
433 |
def safeseq(value): |
|
|
434 |
""" |
|
|
435 |
A "safe" filter for sequences. Marks each element in the sequence, |
|
|
436 |
individually, as safe, after converting them to unicode. Returns a list |
|
|
437 |
with the results. |
|
|
438 |
""" |
|
|
439 |
return [mark_safe(force_unicode(obj)) for obj in value] |
|
|
440 |
safeseq.is_safe = True |
|
|
441 |
|
|
|
442 |
def removetags(value, tags): |
|
|
443 |
"""Removes a space separated list of [X]HTML tags from the output.""" |
|
|
444 |
tags = [re.escape(tag) for tag in tags.split()] |
|
|
445 |
tags_re = u'(%s)' % u'|'.join(tags) |
|
|
446 |
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U) |
|
|
447 |
endtag_re = re.compile(u'</%s>' % tags_re) |
|
|
448 |
value = starttag_re.sub(u'', value) |
|
|
449 |
value = endtag_re.sub(u'', value) |
|
|
450 |
return value |
|
|
451 |
removetags.is_safe = True |
|
|
452 |
removetags = stringfilter(removetags) |
|
|
453 |
|
|
|
454 |
def striptags(value): |
|
|
455 |
"""Strips all [X]HTML tags.""" |
|
|
456 |
from django.utils.html import strip_tags |
|
|
457 |
return strip_tags(value) |
|
|
458 |
striptags.is_safe = True |
|
|
459 |
striptags = stringfilter(striptags) |
|
|
460 |
|
|
|
461 |
################### |
|
|
462 |
# LISTS # |
|
|
463 |
################### |
|
|
464 |
|
|
|
465 |
def dictsort(value, arg): |
|
|
466 |
""" |
|
|
467 |
Takes a list of dicts, returns that list sorted by the property given in |
|
|
468 |
the argument. |
|
|
469 |
""" |
|
|
470 |
var_resolve = Variable(arg).resolve |
|
|
471 |
decorated = [(var_resolve(item), item) for item in value] |
|
|
472 |
decorated.sort() |
|
|
473 |
return [item[1] for item in decorated] |
|
|
474 |
dictsort.is_safe = False |
|
|
475 |
|
|
|
476 |
def dictsortreversed(value, arg): |
|
|
477 |
""" |
|
|
478 |
Takes a list of dicts, returns that list sorted in reverse order by the |
|
|
479 |
property given in the argument. |
|
|
480 |
""" |
|
|
481 |
var_resolve = Variable(arg).resolve |
|
|
482 |
decorated = [(var_resolve(item), item) for item in value] |
|
|
483 |
decorated.sort() |
|
|
484 |
decorated.reverse() |
|
|
485 |
return [item[1] for item in decorated] |
|
|
486 |
dictsortreversed.is_safe = False |
|
|
487 |
|
|
|
488 |
def first(value): |
|
|
489 |
"""Returns the first item in a list.""" |
|
|
490 |
try: |
|
|
491 |
return value[0] |
|
|
492 |
except IndexError: |
|
|
493 |
return u'' |
|
|
494 |
first.is_safe = False |
|
|
495 |
|
|
|
496 |
def join(value, arg, autoescape=None): |
|
|
497 |
""" |
|
|
498 |
Joins a list with a string, like Python's ``str.join(list)``. |
|
|
499 |
""" |
|
|
500 |
value = map(force_unicode, value) |
|
|
501 |
if autoescape: |
|
|
502 |
from django.utils.html import conditional_escape |
|
|
503 |
value = [conditional_escape(v) for v in value] |
|
|
504 |
try: |
|
|
505 |
data = arg.join(value) |
|
|
506 |
except AttributeError: # fail silently but nicely |
|
|
507 |
return value |
|
|
508 |
return mark_safe(data) |
|
|
509 |
join.is_safe = True |
|
|
510 |
join.needs_autoescape = True |
|
|
511 |
|
|
|
512 |
def last(value): |
|
|
513 |
"Returns the last item in a list" |
|
|
514 |
try: |
|
|
515 |
return value[-1] |
|
|
516 |
except IndexError: |
|
|
517 |
return u'' |
|
|
518 |
last.is_safe = True |
|
|
519 |
|
|
|
520 |
def length(value): |
|
|
521 |
"""Returns the length of the value - useful for lists.""" |
|
|
522 |
try: |
|
|
523 |
return len(value) |
|
|
524 |
except (ValueError, TypeError): |
|
|
525 |
return '' |
|
|
526 |
length.is_safe = True |
|
|
527 |
|
|
|
528 |
def length_is(value, arg): |
|
|
529 |
"""Returns a boolean of whether the value's length is the argument.""" |
|
|
530 |
try: |
|
|
531 |
return len(value) == int(arg) |
|
|
532 |
except (ValueError, TypeError): |
|
|
533 |
return '' |
|
|
534 |
length_is.is_safe = False |
|
|
535 |
|
|
|
536 |
def random(value): |
|
|
537 |
"""Returns a random item from the list.""" |
|
|
538 |
return random_module.choice(value) |
|
|
539 |
random.is_safe = True |
|
|
540 |
|
|
|
541 |
def slice_(value, arg): |
|
|
542 |
""" |
|
|
543 |
Returns a slice of the list. |
|
|
544 |
|
|
|
545 |
Uses the same syntax as Python's list slicing; see |
|
|
546 |
http://diveintopython.org/native_data_types/lists.html#odbchelper.list.slice |
|
|
547 |
for an introduction. |
|
|
548 |
""" |
|
|
549 |
try: |
|
|
550 |
bits = [] |
|
|
551 |
for x in arg.split(u':'): |
|
|
552 |
if len(x) == 0: |
|
|
553 |
bits.append(None) |
|
|
554 |
else: |
|
|
555 |
bits.append(int(x)) |
|
|
556 |
return value[slice(*bits)] |
|
|
557 |
|
|
|
558 |
except (ValueError, TypeError): |
|
|
559 |
return value # Fail silently. |
|
|
560 |
slice_.is_safe = True |
|
|
561 |
|
|
|
562 |
def unordered_list(value, autoescape=None): |
|
|
563 |
""" |
|
|
564 |
Recursively takes a self-nested list and returns an HTML unordered list -- |
|
|
565 |
WITHOUT opening and closing <ul> tags. |
|
|
566 |
|
|
|
567 |
The list is assumed to be in the proper format. For example, if ``var`` |
|
|
568 |
contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``, |
|
|
569 |
then ``{{ var|unordered_list }}`` would return:: |
|
|
570 |
|
|
|
571 |
<li>States |
|
|
572 |
<ul> |
|
|
573 |
<li>Kansas |
|
|
574 |
<ul> |
|
|
575 |
<li>Lawrence</li> |
|
|
576 |
<li>Topeka</li> |
|
|
577 |
</ul> |
|
|
578 |
</li> |
|
|
579 |
<li>Illinois</li> |
|
|
580 |
</ul> |
|
|
581 |
</li> |
|
|
582 |
""" |
|
|
583 |
if autoescape: |
|
|
584 |
from django.utils.html import conditional_escape |
|
|
585 |
escaper = conditional_escape |
|
|
586 |
else: |
|
|
587 |
escaper = lambda x: x |
|
|
588 |
def convert_old_style_list(list_): |
|
|
589 |
""" |
|
|
590 |
Converts old style lists to the new easier to understand format. |
|
|
591 |
|
|
|
592 |
The old list format looked like: |
|
|
593 |
['Item 1', [['Item 1.1', []], ['Item 1.2', []]] |
|
|
594 |
|
|
|
595 |
And it is converted to: |
|
|
596 |
['Item 1', ['Item 1.1', 'Item 1.2]] |
|
|
597 |
""" |
|
|
598 |
if not isinstance(list_, (tuple, list)) or len(list_) != 2: |
|
|
599 |
return list_, False |
|
|
600 |
first_item, second_item = list_ |
|
|
601 |
if second_item == []: |
|
|
602 |
return [first_item], True |
|
|
603 |
old_style_list = True |
|
|
604 |
new_second_item = [] |
|
|
605 |
for sublist in second_item: |
|
|
606 |
item, old_style_list = convert_old_style_list(sublist) |
|
|
607 |
if not old_style_list: |
|
|
608 |
break |
|
|
609 |
new_second_item.extend(item) |
|
|
610 |
if old_style_list: |
|
|
611 |
second_item = new_second_item |
|
|
612 |
return [first_item, second_item], old_style_list |
|
|
613 |
def _helper(list_, tabs=1): |
|
|
614 |
indent = u'\t' * tabs |
|
|
615 |
output = [] |
|
|
616 |
|
|
|
617 |
list_length = len(list_) |
|
|
618 |
i = 0 |
|
|
619 |
while i < list_length: |
|
|
620 |
title = list_[i] |
|
|
621 |
sublist = '' |
|
|
622 |
sublist_item = None |
|
|
623 |
if isinstance(title, (list, tuple)): |
|
|
624 |
sublist_item = title |
|
|
625 |
title = '' |
|
|
626 |
elif i < list_length - 1: |
|
|
627 |
next_item = list_[i+1] |
|
|
628 |
if next_item and isinstance(next_item, (list, tuple)): |
|
|
629 |
# The next item is a sub-list. |
|
|
630 |
sublist_item = next_item |
|
|
631 |
# We've processed the next item now too. |
|
|
632 |
i += 1 |
|
|
633 |
if sublist_item: |
|
|
634 |
sublist = _helper(sublist_item, tabs+1) |
|
|
635 |
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist, |
|
|
636 |
indent, indent) |
|
|
637 |
output.append('%s<li>%s%s</li>' % (indent, |
|
|
638 |
escaper(force_unicode(title)), sublist)) |
|
|
639 |
i += 1 |
|
|
640 |
return '\n'.join(output) |
|
|
641 |
value, converted = convert_old_style_list(value) |
|
|
642 |
return mark_safe(_helper(value)) |
|
|
643 |
unordered_list.is_safe = True |
|
|
644 |
unordered_list.needs_autoescape = True |
|
|
645 |
|
|
|
646 |
################### |
|
|
647 |
# INTEGERS # |
|
|
648 |
################### |
|
|
649 |
|
|
|
650 |
def add(value, arg): |
|
|
651 |
"""Adds the arg to the value.""" |
|
|
652 |
return int(value) + int(arg) |
|
|
653 |
add.is_safe = False |
|
|
654 |
|
|
|
655 |
def get_digit(value, arg): |
|
|
656 |
""" |
|
|
657 |
Given a whole number, returns the requested digit of it, where 1 is the |
|
|
658 |
right-most digit, 2 is the second-right-most digit, etc. Returns the |
|
|
659 |
original value for invalid input (if input or argument is not an integer, |
|
|
660 |
or if argument is less than 1). Otherwise, output is always an integer. |
|
|
661 |
""" |
|
|
662 |
try: |
|
|
663 |
arg = int(arg) |
|
|
664 |
value = int(value) |
|
|
665 |
except ValueError: |
|
|
666 |
return value # Fail silently for an invalid argument |
|
|
667 |
if arg < 1: |
|
|
668 |
return value |
|
|
669 |
try: |
|
|
670 |
return int(str(value)[-arg]) |
|
|
671 |
except IndexError: |
|
|
672 |
return 0 |
|
|
673 |
get_digit.is_safe = False |
|
|
674 |
|
|
|
675 |
################### |
|
|
676 |
# DATES # |
|
|
677 |
################### |
|
|
678 |
|
|
|
679 |
def date(value, arg=None): |
|
|
680 |
"""Formats a date according to the given format.""" |
|
|
681 |
from django.utils.dateformat import format |
|
|
682 |
if not value: |
|
|
683 |
return u'' |
|
|
684 |
if arg is None: |
|
|
685 |
arg = settings.DATE_FORMAT |
|
|
686 |
try: |
|
|
687 |
return format(value, arg) |
|
|
688 |
except AttributeError: |
|
|
689 |
return '' |
|
|
690 |
date.is_safe = False |
|
|
691 |
|
|
|
692 |
def time(value, arg=None): |
|
|
693 |
"""Formats a time according to the given format.""" |
|
|
694 |
from django.utils.dateformat import time_format |
|
|
695 |
if value in (None, u''): |
|
|
696 |
return u'' |
|
|
697 |
if arg is None: |
|
|
698 |
arg = settings.TIME_FORMAT |
|
|
699 |
try: |
|
|
700 |
return time_format(value, arg) |
|
|
701 |
except AttributeError: |
|
|
702 |
return '' |
|
|
703 |
time.is_safe = False |
|
|
704 |
|
|
|
705 |
def timesince(value, arg=None): |
|
|
706 |
"""Formats a date as the time since that date (i.e. "4 days, 6 hours").""" |
|
|
707 |
from django.utils.timesince import timesince |
|
|
708 |
if not value: |
|
|
709 |
return u'' |
|
|
710 |
try: |
|
|
711 |
if arg: |
|
|
712 |
return timesince(value, arg) |
|
|
713 |
return timesince(value) |
|
|
714 |
except (ValueError, TypeError): |
|
|
715 |
return u'' |
|
|
716 |
timesince.is_safe = False |
|
|
717 |
|
|
|
718 |
def timeuntil(value, arg=None): |
|
|
719 |
"""Formats a date as the time until that date (i.e. "4 days, 6 hours").""" |
|
|
720 |
from django.utils.timesince import timeuntil |
|
|
721 |
from datetime import datetime |
|
|
722 |
if not value: |
|
|
723 |
return u'' |
|
|
724 |
try: |
|
|
725 |
return timeuntil(value, arg) |
|
|
726 |
except (ValueError, TypeError): |
|
|
727 |
return u'' |
|
|
728 |
timeuntil.is_safe = False |
|
|
729 |
|
|
|
730 |
################### |
|
|
731 |
# LOGIC # |
|
|
732 |
################### |
|
|
733 |
|
|
|
734 |
def default(value, arg): |
|
|
735 |
"""If value is unavailable, use given default.""" |
|
|
736 |
return value or arg |
|
|
737 |
default.is_safe = False |
|
|
738 |
|
|
|
739 |
def default_if_none(value, arg): |
|
|
740 |
"""If value is None, use given default.""" |
|
|
741 |
if value is None: |
|
|
742 |
return arg |
|
|
743 |
return value |
|
|
744 |
default_if_none.is_safe = False |
|
|
745 |
|
|
|
746 |
def divisibleby(value, arg): |
|
|
747 |
"""Returns True if the value is devisible by the argument.""" |
|
|
748 |
return int(value) % int(arg) == 0 |
|
|
749 |
divisibleby.is_safe = False |
|
|
750 |
|
|
|
751 |
def yesno(value, arg=None): |
|
|
752 |
""" |
|
|
753 |
Given a string mapping values for true, false and (optionally) None, |
|
|
754 |
returns one of those strings accoding to the value: |
|
|
755 |
|
|
|
756 |
========== ====================== ================================== |
|
|
757 |
Value Argument Outputs |
|
|
758 |
========== ====================== ================================== |
|
|
759 |
``True`` ``"yeah,no,maybe"`` ``yeah`` |
|
|
760 |
``False`` ``"yeah,no,maybe"`` ``no`` |
|
|
761 |
``None`` ``"yeah,no,maybe"`` ``maybe`` |
|
|
762 |
``None`` ``"yeah,no"`` ``"no"`` (converts None to False |
|
|
763 |
if no mapping for None is given. |
|
|
764 |
========== ====================== ================================== |
|
|
765 |
""" |
|
|
766 |
if arg is None: |
|
|
767 |
arg = ugettext('yes,no,maybe') |
|
|
768 |
bits = arg.split(u',') |
|
|
769 |
if len(bits) < 2: |
|
|
770 |
return value # Invalid arg. |
|
|
771 |
try: |
|
|
772 |
yes, no, maybe = bits |
|
|
773 |
except ValueError: |
|
|
774 |
# Unpack list of wrong size (no "maybe" value provided). |
|
|
775 |
yes, no, maybe = bits[0], bits[1], bits[1] |
|
|
776 |
if value is None: |
|
|
777 |
return maybe |
|
|
778 |
if value: |
|
|
779 |
return yes |
|
|
780 |
return no |
|
|
781 |
yesno.is_safe = False |
|
|
782 |
|
|
|
783 |
################### |
|
|
784 |
# MISC # |
|
|
785 |
################### |
|
|
786 |
|
|
|
787 |
def filesizeformat(bytes): |
|
|
788 |
""" |
|
|
789 |
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, |
|
|
790 |
102 bytes, etc). |
|
|
791 |
""" |
|
|
792 |
try: |
|
|
793 |
bytes = float(bytes) |
|
|
794 |
except TypeError: |
|
|
795 |
return u"0 bytes" |
|
|
796 |
|
|
|
797 |
if bytes < 1024: |
|
|
798 |
return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes} |
|
|
799 |
if bytes < 1024 * 1024: |
|
|
800 |
return ugettext("%.1f KB") % (bytes / 1024) |
|
|
801 |
if bytes < 1024 * 1024 * 1024: |
|
|
802 |
return ugettext("%.1f MB") % (bytes / (1024 * 1024)) |
|
|
803 |
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024)) |
|
|
804 |
filesizeformat.is_safe = True |
|
|
805 |
|
|
|
806 |
def pluralize(value, arg=u's'): |
|
|
807 |
""" |
|
|
808 |
Returns a plural suffix if the value is not 1. By default, 's' is used as |
|
|
809 |
the suffix: |
|
|
810 |
|
|
|
811 |
* If value is 0, vote{{ value|pluralize }} displays "0 votes". |
|
|
812 |
* If value is 1, vote{{ value|pluralize }} displays "1 vote". |
|
|
813 |
* If value is 2, vote{{ value|pluralize }} displays "2 votes". |
|
|
814 |
|
|
|
815 |
If an argument is provided, that string is used instead: |
|
|
816 |
|
|
|
817 |
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes". |
|
|
818 |
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class". |
|
|
819 |
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes". |
|
|
820 |
|
|
|
821 |
If the provided argument contains a comma, the text before the comma is |
|
|
822 |
used for the singular case and the text after the comma is used for the |
|
|
823 |
plural case: |
|
|
824 |
|
|
|
825 |
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies". |
|
|
826 |
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy". |
|
|
827 |
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies". |
|
|
828 |
""" |
|
|
829 |
if not u',' in arg: |
|
|
830 |
arg = u',' + arg |
|
|
831 |
bits = arg.split(u',') |
|
|
832 |
if len(bits) > 2: |
|
|
833 |
return u'' |
|
|
834 |
singular_suffix, plural_suffix = bits[:2] |
|
|
835 |
|
|
|
836 |
try: |
|
|
837 |
if int(value) != 1: |
|
|
838 |
return plural_suffix |
|
|
839 |
except ValueError: # Invalid string that's not a number. |
|
|
840 |
pass |
|
|
841 |
except TypeError: # Value isn't a string or a number; maybe it's a list? |
|
|
842 |
try: |
|
|
843 |
if len(value) != 1: |
|
|
844 |
return plural_suffix |
|
|
845 |
except TypeError: # len() of unsized object. |
|
|
846 |
pass |
|
|
847 |
return singular_suffix |
|
|
848 |
pluralize.is_safe = False |
|
|
849 |
|
|
|
850 |
def phone2numeric(value): |
|
|
851 |
"""Takes a phone number and converts it in to its numerical equivalent.""" |
|
|
852 |
from django.utils.text import phone2numeric |
|
|
853 |
return phone2numeric(value) |
|
|
854 |
phone2numeric.is_safe = True |
|
|
855 |
|
|
|
856 |
def pprint(value): |
|
|
857 |
"""A wrapper around pprint.pprint -- for debugging, really.""" |
|
|
858 |
from pprint import pformat |
|
|
859 |
try: |
|
|
860 |
return pformat(value) |
|
|
861 |
except Exception, e: |
|
|
862 |
return u"Error in formatting: %s" % force_unicode(e, errors="replace") |
|
|
863 |
pprint.is_safe = True |
|
|
864 |
|
|
|
865 |
# Syntax: register.filter(name of filter, callback) |
|
|
866 |
register.filter(add) |
|
|
867 |
register.filter(addslashes) |
|
|
868 |
register.filter(capfirst) |
|
|
869 |
register.filter(center) |
|
|
870 |
register.filter(cut) |
|
|
871 |
register.filter(date) |
|
|
872 |
register.filter(default) |
|
|
873 |
register.filter(default_if_none) |
|
|
874 |
register.filter(dictsort) |
|
|
875 |
register.filter(dictsortreversed) |
|
|
876 |
register.filter(divisibleby) |
|
|
877 |
register.filter(escape) |
|
|
878 |
register.filter(escapejs) |
|
|
879 |
register.filter(filesizeformat) |
|
|
880 |
register.filter(first) |
|
|
881 |
register.filter(fix_ampersands) |
|
|
882 |
register.filter(floatformat) |
|
|
883 |
register.filter(force_escape) |
|
|
884 |
register.filter(get_digit) |
|
|
885 |
register.filter(iriencode) |
|
|
886 |
register.filter(join) |
|
|
887 |
register.filter(last) |
|
|
888 |
register.filter(length) |
|
|
889 |
register.filter(length_is) |
|
|
890 |
register.filter(linebreaks) |
|
|
891 |
register.filter(linebreaksbr) |
|
|
892 |
register.filter(linenumbers) |
|
|
893 |
register.filter(ljust) |
|
|
894 |
register.filter(lower) |
|
|
895 |
register.filter(make_list) |
|
|
896 |
register.filter(phone2numeric) |
|
|
897 |
register.filter(pluralize) |
|
|
898 |
register.filter(pprint) |
|
|
899 |
register.filter(removetags) |
|
|
900 |
register.filter(random) |
|
|
901 |
register.filter(rjust) |
|
|
902 |
register.filter(safe) |
|
|
903 |
register.filter(safeseq) |
|
|
904 |
register.filter('slice', slice_) |
|
|
905 |
register.filter(slugify) |
|
|
906 |
register.filter(stringformat) |
|
|
907 |
register.filter(striptags) |
|
|
908 |
register.filter(time) |
|
|
909 |
register.filter(timesince) |
|
|
910 |
register.filter(timeuntil) |
|
|
911 |
register.filter(title) |
|
|
912 |
register.filter(truncatewords) |
|
|
913 |
register.filter(truncatewords_html) |
|
|
914 |
register.filter(unordered_list) |
|
|
915 |
register.filter(upper) |
|
|
916 |
register.filter(urlencode) |
|
|
917 |
register.filter(urlize) |
|
|
918 |
register.filter(urlizetrunc) |
|
|
919 |
register.filter(wordcount) |
|
|
920 |
register.filter(wordwrap) |
|
|
921 |
register.filter(yesno) |