12
19
# STRING DECORATOR #
13
20
#######################
15
def smart_string(obj):
16
# FUTURE: Unicode strings should probably be normalized to a specific
17
# encoding and non-unicode strings should be converted to unicode too.
18
# if isinstance(obj, unicode):
19
# obj = obj.encode(settings.DEFAULT_CHARSET)
21
# obj = unicode(obj, settings.DEFAULT_CHARSET)
22
# FUTURE: Replace dumb string logic below with cool unicode logic above.
23
if not isinstance(obj, basestring):
27
22
def stringfilter(func):
29
Decorator for filters which should only receive strings. The object passed
30
as the first positional argument will be converted to a string.
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
32
28
def _dec(*args, **kwargs):
35
args[0] = smart_string(args[0])
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))
36
34
return func(*args, **kwargs)
38
36
# Include a reference to the real function (used to check original
39
37
# arguments by the template parser).
40
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
###################
48
49
def addslashes(value):
49
"Adds slashes - useful for passing strings to JavaScript, for example."
51
Adds slashes before quotes. Useful for escaping strings in CSV, for
52
example. Less useful for escaping JavaScript; use the ``escapejs``
50
55
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
56
addslashes.is_safe = True
51
57
addslashes = stringfilter(addslashes)
53
59
def capfirst(value):
54
"Capitalizes the first character of the value"
60
"""Capitalizes the first character of the value."""
55
61
return value and value[0].upper() + value[1:]
56
63
capfirst = stringfilter(capfirst)
77
# Escape every ASCII character with a value less than 32.
78
_js_escapes = (_base_js_escapes +
79
tuple([('%c' % z, '\\x%02X' % z) for z in range(32)]))
82
"""Hex encodes characters for use in JavaScript strings."""
83
for bad, good in _js_escapes:
84
value = value.replace(bad, good)
86
escapejs = stringfilter(escapejs)
58
88
def fix_ampersands(value):
59
"Replaces ampersands with ``&`` entities"
89
"""Replaces ampersands with ``&`` entities."""
60
90
from django.utils.html import fix_ampersands
61
91
return fix_ampersands(value)
92
fix_ampersands.is_safe=True
62
93
fix_ampersands = stringfilter(fix_ampersands)
64
95
def floatformat(text, arg=-1):
66
If called without an argument, displays a floating point
67
number as 34.2 -- but only if there's a point to be displayed.
68
With a positive numeric argument, it displays that many decimal places
70
With a negative numeric argument, it will display that many decimal
71
places -- but only if there's places to be displayed.
97
Displays a float to a specified number of decimal places.
99
If called without an argument, it displays the floating point number with
100
one decimal place -- but only if there's a decimal place to be displayed:
76
* num1|floatformat results in 34.2
77
* num2|floatformat is 34
78
* num1|floatformat:3 is 34.232
79
* num2|floatformat:3 is 34.000
80
* num1|floatformat:-3 is 34.232
81
* num2|floatformat:-3 is 34
105
* {{ num1|floatformat }} displays "34.2"
106
* {{ num2|floatformat }} displays "34"
107
* {{ num3|floatformat }} displays "34.3"
109
If arg is positive, it will always display exactly arg number of decimal
112
* {{ num1|floatformat:3 }} displays "34.232"
113
* {{ num2|floatformat:3 }} displays "34.000"
114
* {{ num3|floatformat:3 }} displays "34.260"
116
If arg is negative, it will display arg number of decimal places -- but
117
only if there are places to be displayed:
119
* {{ num1|floatformat:"-3" }} displays "34.232"
120
* {{ num2|floatformat:"-3" }} displays "34"
121
* {{ num3|floatformat:"-3" }} displays "34.260"
125
except (ValueError, TypeError):
89
129
except ValueError:
90
return smart_string(f)
130
return force_unicode(f)
133
except OverflowError:
134
return force_unicode(f)
92
135
if not m and d < 0:
136
return mark_safe(u'%d' % int(f))
95
formatstr = '%%.%df' % abs(d)
98
def linenumbers(value):
99
"Displays text with line numbers"
138
formatstr = u'%%.%df' % abs(d)
139
return mark_safe(formatstr % f)
140
floatformat.is_safe = True
142
def iriencode(value):
143
"""Escapes an IRI value for use in a URL."""
144
return force_unicode(iri_to_uri(value))
145
iriencode.is_safe = True
146
iriencode = stringfilter(iriencode)
148
def linenumbers(value, autoescape=None):
149
"""Displays text with line numbers."""
100
150
from django.utils.html import escape
101
lines = value.split('\n')
102
# Find the maximum width of the line count, for use with zero padding string format command
103
width = str(len(str(len(lines))))
104
for i, line in enumerate(lines):
105
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
106
return '\n'.join(lines)
151
lines = value.split(u'\n')
152
# Find the maximum width of the line count, for use with zero padding
153
# string format command
154
width = unicode(len(unicode(len(lines))))
155
if not autoescape or isinstance(value, SafeData):
156
for i, line in enumerate(lines):
157
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, line)
159
for i, line in enumerate(lines):
160
lines[i] = (u"%0" + width + u"d. %s") % (i + 1, escape(line))
161
return mark_safe(u'\n'.join(lines))
162
linenumbers.is_safe = True
163
linenumbers.needs_autoescape = True
107
164
linenumbers = stringfilter(linenumbers)
109
166
def lower(value):
110
"Converts a string into all lowercase"
167
"""Converts a string into all lowercase."""
111
168
return value.lower()
112
170
lower = stringfilter(lower)
114
172
def make_list(value):
116
Returns the value turned into a list. For an integer, it's a list of
117
digits. For a string, it's a list of characters.
174
Returns the value turned into a list.
176
For an integer, it's a list of digits.
177
For a string, it's a list of characters.
119
179
return list(value)
180
make_list.is_safe = False
120
181
make_list = stringfilter(make_list)
122
183
def slugify(value):
123
"Converts to lowercase, removes non-alpha chars and converts spaces to hyphens"
124
value = re.sub('[^\w\s-]', '', value).strip().lower()
125
return re.sub('[-\s]+', '-', value)
185
Normalizes string, converts to lowercase, removes non-alpha characters,
186
and converts spaces to hyphens.
189
value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
190
value = unicode(re.sub('[^\w\s-]', '', value).strip().lower())
191
return mark_safe(re.sub('[-\s]+', '-', value))
192
slugify.is_safe = True
126
193
slugify = stringfilter(slugify)
128
195
def stringformat(value, arg):
130
Formats the variable according to the argument, a string formatting specifier.
197
Formats the variable according to the arg, a string formatting specifier.
131
199
This specifier uses Python string formating syntax, with the exception that
132
200
the leading "%" is dropped.
135
203
of Python string formatting
138
return ("%" + str(arg)) % value
206
return (u"%" + unicode(arg)) % value
139
207
except (ValueError, TypeError):
209
stringformat.is_safe = True
142
211
def title(value):
143
"Converts a string into titlecase"
212
"""Converts a string into titlecase."""
144
213
return re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
145
215
title = stringfilter(title)
147
217
def truncatewords(value, arg):
149
Truncates a string after a certain number of words
219
Truncates a string after a certain number of words.
151
Argument: Number of words to truncate after
221
Argument: Number of words to truncate after.
153
223
from django.utils.text import truncate_words
155
225
length = int(arg)
156
except ValueError: # invalid literal for int()
226
except ValueError: # Invalid literal for int().
157
227
return value # Fail silently.
158
if not isinstance(value, basestring):
160
228
return truncate_words(value, length)
229
truncatewords.is_safe = True
161
230
truncatewords = stringfilter(truncatewords)
163
232
def truncatewords_html(value, arg):
165
Truncates HTML after a certain number of words
234
Truncates HTML after a certain number of words.
167
Argument: Number of words to truncate after
236
Argument: Number of words to truncate after.
169
238
from django.utils.text import truncate_html_words
171
240
length = int(arg)
172
241
except ValueError: # invalid literal for int()
173
242
return value # Fail silently.
174
if not isinstance(value, basestring):
176
243
return truncate_html_words(value, length)
244
truncatewords_html.is_safe = True
177
245
truncatewords_html = stringfilter(truncatewords_html)
179
247
def upper(value):
180
"Converts a string into all uppercase"
248
"""Converts a string into all uppercase."""
181
249
return value.upper()
250
upper.is_safe = False
182
251
upper = stringfilter(upper)
184
253
def urlencode(value):
185
"Escapes a value for use in a URL"
187
if not isinstance(value, basestring):
189
return urllib.quote(value)
254
"""Escapes a value for use in a URL."""
255
from django.utils.http import urlquote
256
return urlquote(value)
257
urlencode.is_safe = False
190
258
urlencode = stringfilter(urlencode)
193
"Converts URLs in plain text into clickable links"
260
def urlize(value, autoescape=None):
261
"""Converts URLs in plain text into clickable links."""
194
262
from django.utils.html import urlize
195
return urlize(value, nofollow=True)
263
return mark_safe(urlize(value, nofollow=True, autoescape=autoescape))
265
urlize.needs_autoescape = True
196
266
urlize = stringfilter(urlize)
198
def urlizetrunc(value, limit):
268
def urlizetrunc(value, limit, autoescape=None):
200
Converts URLs into clickable links, truncating URLs to the given character limit,
201
and adding 'rel=nofollow' attribute to discourage spamming.
270
Converts URLs into clickable links, truncating URLs to the given character
271
limit, and adding 'rel=nofollow' attribute to discourage spamming.
203
273
Argument: Length to truncate URLs to.
205
275
from django.utils.html import urlize
206
return urlize(value, trim_url_limit=int(limit), nofollow=True)
276
return mark_safe(urlize(value, trim_url_limit=int(limit), nofollow=True,
277
autoescape=autoescape))
278
urlizetrunc.is_safe = True
279
urlizetrunc.needs_autoescape = True
207
280
urlizetrunc = stringfilter(urlizetrunc)
209
282
def wordcount(value):
210
"Returns the number of words"
283
"""Returns the number of words."""
211
284
return len(value.split())
285
wordcount.is_safe = False
212
286
wordcount = stringfilter(wordcount)
214
288
def wordwrap(value, arg):
216
Wraps words at specified line length
290
Wraps words at specified line length.
218
292
Argument: number of characters to wrap the text at.
220
294
from django.utils.text import wrap
221
295
return wrap(value, int(arg))
296
wordwrap.is_safe = True
222
297
wordwrap = stringfilter(wordwrap)
224
299
def ljust(value, arg):
226
Left-aligns the value in a field of a given width
301
Left-aligns the value in a field of a given width.
303
Argument: field size.
230
305
return value.ljust(int(arg))
231
307
ljust = stringfilter(ljust)
233
309
def rjust(value, arg):
235
Right-aligns the value in a field of a given width
311
Right-aligns the value in a field of a given width.
313
Argument: field size.
239
315
return value.rjust(int(arg))
240
317
rjust = stringfilter(rjust)
242
319
def center(value, arg):
243
"Centers the value in a field of a given width"
320
"""Centers the value in a field of a given width."""
244
321
return value.center(int(arg))
322
center.is_safe = True
245
323
center = stringfilter(center)
247
325
def cut(value, arg):
248
"Removes all values of arg from the given string"
249
return value.replace(arg, '')
327
Removes all values of arg from the given string.
329
safe = isinstance(value, SafeData)
330
value = value.replace(arg, u'')
331
if safe and arg != ';':
332
return mark_safe(value)
250
334
cut = stringfilter(cut)
252
336
###################
254
338
###################
256
340
def escape(value):
257
"Escapes a string's HTML"
258
from django.utils.html import escape
342
Marks the value as a string that should not be auto-escaped.
344
from django.utils.safestring import mark_for_escaping
345
return mark_for_escaping(value)
346
escape.is_safe = True
260
347
escape = stringfilter(escape)
262
def linebreaks(value):
263
"Converts newlines into <p> and <br />s"
349
def force_escape(value):
351
Escapes a string's HTML. This returns a new string containing the escaped
352
characters (as opposed to "escape", which marks the content for later
355
from django.utils.html import escape
356
return mark_safe(escape(value))
357
force_escape = stringfilter(force_escape)
358
force_escape.is_safe = True
360
def linebreaks(value, autoescape=None):
362
Replaces line breaks in plain text with appropriate HTML; a single
363
newline becomes an HTML line break (``<br />``) and a new line
364
followed by a blank line becomes a paragraph break (``</p>``).
264
366
from django.utils.html import linebreaks
265
return linebreaks(value)
367
autoescape = autoescape and not isinstance(value, SafeData)
368
return mark_safe(linebreaks(value, autoescape))
369
linebreaks.is_safe = True
370
linebreaks.needs_autoescape = True
266
371
linebreaks = stringfilter(linebreaks)
268
def linebreaksbr(value):
269
"Converts newlines into <br />s"
270
return value.replace('\n', '<br />')
373
def linebreaksbr(value, autoescape=None):
375
Converts all newlines in a piece of plain text to HTML line breaks
378
if autoescape and not isinstance(value, SafeData):
379
from django.utils.html import escape
380
value = escape(value)
381
return mark_safe(value.replace('\n', '<br />'))
382
linebreaksbr.is_safe = True
383
linebreaksbr.needs_autoescape = True
271
384
linebreaksbr = stringfilter(linebreaksbr)
388
Marks the value as a string that should not be auto-escaped.
390
from django.utils.safestring import mark_safe
391
return mark_safe(value)
393
safe = stringfilter(safe)
273
395
def removetags(value, tags):
274
"Removes a space separated list of [X]HTML tags from the output"
396
"""Removes a space separated list of [X]HTML tags from the output."""
275
397
tags = [re.escape(tag) for tag in tags.split()]
276
tags_re = '(%s)' % '|'.join(tags)
277
starttag_re = re.compile(r'<%s(/?>|(\s+[^>]*>))' % tags_re)
278
endtag_re = re.compile('</%s>' % tags_re)
279
value = starttag_re.sub('', value)
280
value = endtag_re.sub('', value)
398
tags_re = u'(%s)' % u'|'.join(tags)
399
starttag_re = re.compile(ur'<%s(/?>|(\s+[^>]*>))' % tags_re, re.U)
400
endtag_re = re.compile(u'</%s>' % tags_re)
401
value = starttag_re.sub(u'', value)
402
value = endtag_re.sub(u'', value)
404
removetags.is_safe = True
282
405
removetags = stringfilter(removetags)
284
407
def striptags(value):
285
"Strips all [X]HTML tags"
408
"""Strips all [X]HTML tags."""
286
409
from django.utils.html import strip_tags
287
410
return strip_tags(value)
411
striptags.is_safe = True
288
412
striptags = stringfilter(striptags)
290
414
###################
296
420
Takes a list of dicts, returns that list sorted by the property given in
299
decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
423
var_resolve = Variable(arg).resolve
424
decorated = [(var_resolve(item), item) for item in value]
301
426
return [item[1] for item in decorated]
427
dictsort.is_safe = False
303
429
def dictsortreversed(value, arg):
305
431
Takes a list of dicts, returns that list sorted in reverse order by the
306
432
property given in the argument.
308
decorated = [(resolve_variable('var.' + arg, {'var' : item}), item) for item in value]
434
var_resolve = Variable(arg).resolve
435
decorated = [(var_resolve(item), item) for item in value]
310
437
decorated.reverse()
311
438
return [item[1] for item in decorated]
439
dictsortreversed.is_safe = False
313
441
def first(value):
314
"Returns the first item in a list"
442
"""Returns the first item in a list."""
317
445
except IndexError:
447
first.is_safe = False
320
449
def join(value, arg):
321
"Joins a list with a string, like Python's ``str.join(list)``"
450
"""Joins a list with a string, like Python's ``str.join(list)``."""
323
return arg.join(map(smart_string, value))
452
data = arg.join(map(force_unicode, value))
324
453
except AttributeError: # fail silently but nicely
455
safe_args = reduce(lambda lhs, rhs: lhs and isinstance(rhs, SafeData),
458
return mark_safe(data)
464
"Returns the last item in a list"
327
471
def length(value):
328
"Returns the length of the value - useful for lists"
472
"""Returns the length of the value - useful for lists."""
329
473
return len(value)
474
length.is_safe = True
331
476
def length_is(value, arg):
332
"Returns a boolean of whether the value's length is the argument"
477
"""Returns a boolean of whether the value's length is the argument."""
333
478
return len(value) == int(arg)
479
length_is.is_safe = True
335
481
def random(value):
336
"Returns a random item from the list"
482
"""Returns a random item from the list."""
337
483
return random_module.choice(value)
484
random.is_safe = True
339
486
def slice_(value, arg):
380
def _helper(value, tabs):
383
return '%s<li>%s\n%s<ul>\n%s\n%s</ul>\n%s</li>' % (indent, value[0], indent,
384
'\n'.join([_helper(v, tabs+1) for v in value[1]]), indent, indent)
386
return '%s<li>%s</li>' % (indent, value[0])
387
return _helper(value, 1)
529
from django.utils.html import conditional_escape
530
escaper = conditional_escape
532
escaper = lambda x: x
533
def convert_old_style_list(list_):
535
Converts old style lists to the new easier to understand format.
537
The old list format looked like:
538
['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
540
And it is converted to:
541
['Item 1', ['Item 1.1', 'Item 1.2]]
543
if not isinstance(list_, (tuple, list)) or len(list_) != 2:
545
first_item, second_item = list_
546
if second_item == []:
547
return [first_item], True
548
old_style_list = True
550
for sublist in second_item:
551
item, old_style_list = convert_old_style_list(sublist)
552
if not old_style_list:
554
new_second_item.extend(item)
556
second_item = new_second_item
557
return [first_item, second_item], old_style_list
558
def _helper(list_, tabs=1):
559
indent = u'\t' * tabs
562
list_length = len(list_)
564
while i < list_length:
568
if isinstance(title, (list, tuple)):
571
elif i < list_length - 1:
572
next_item = list_[i+1]
573
if next_item and isinstance(next_item, (list, tuple)):
574
# The next item is a sub-list.
575
sublist_item = next_item
576
# We've processed the next item now too.
579
sublist = _helper(sublist_item, tabs+1)
580
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
582
output.append('%s<li>%s%s</li>' % (indent,
583
escaper(force_unicode(title)), sublist))
585
return '\n'.join(output)
586
value, converted = convert_old_style_list(value)
587
return mark_safe(_helper(value))
588
unordered_list.is_safe = True
589
unordered_list.needs_autoescape = True
389
591
###################
391
593
###################
393
595
def add(value, arg):
394
"Adds the arg to the value"
596
"""Adds the arg to the value."""
395
597
return int(value) + int(arg)
397
600
def get_digit(value, arg):
412
615
return int(str(value)[-arg])
413
616
except IndexError:
618
get_digit.is_safe = False
416
620
###################
418
622
###################
420
624
def date(value, arg=None):
421
"Formats a date according to the given format"
625
"""Formats a date according to the given format."""
422
626
from django.utils.dateformat import format
426
630
arg = settings.DATE_FORMAT
427
631
return format(value, arg)
429
634
def time(value, arg=None):
430
"Formats a time according to the given format"
635
"""Formats a time according to the given format."""
431
636
from django.utils.dateformat import time_format
432
if value in (None, ''):
637
if value in (None, u''):
435
640
arg = settings.TIME_FORMAT
436
641
return time_format(value, arg)
438
644
def timesince(value, arg=None):
439
'Formats a date as the time since that date (i.e. "4 days, 6 hours")'
645
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
440
646
from django.utils.timesince import timesince
444
return timesince(arg, value)
445
return timesince(value)
651
return timesince(value, arg)
652
return timesince(value)
653
except (ValueError, TypeError):
655
timesince.is_safe = False
447
657
def timeuntil(value, arg=None):
448
'Formats a date as the time until that date (i.e. "4 days, 6 hours")'
449
from django.utils.timesince import timesince
658
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
659
from django.utils.timesince import timeuntil
450
660
from datetime import datetime
454
return timesince(arg, value)
455
return timesince(datetime.now(), value)
664
return timeuntil(value, arg)
665
except (ValueError, TypeError):
667
timeuntil.is_safe = False
457
669
###################
459
671
###################
461
673
def default(value, arg):
462
"If value is unavailable, use given default"
674
"""If value is unavailable, use given default."""
463
675
return value or arg
676
default.is_safe = False
465
678
def default_if_none(value, arg):
466
"If value is None, use given default"
679
"""If value is None, use given default."""
467
680
if value is None:
683
default_if_none.is_safe = False
471
685
def divisibleby(value, arg):
472
"Returns true if the value is devisible by the argument"
686
"""Returns True if the value is devisible by the argument."""
473
687
return int(value) % int(arg) == 0
688
divisibleby.is_safe = False
475
690
def yesno(value, arg=None):
509
726
def filesizeformat(bytes):
511
Format the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB, 102
728
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
515
732
bytes = float(bytes)
516
733
except TypeError:
520
return "%d byte%s" % (bytes, bytes != 1 and 's' or '')
737
return ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
521
738
if bytes < 1024 * 1024:
522
return "%.1f KB" % (bytes / 1024)
739
return ugettext("%.1f KB") % (bytes / 1024)
523
740
if bytes < 1024 * 1024 * 1024:
524
return "%.1f MB" % (bytes / (1024 * 1024))
525
return "%.1f GB" % (bytes / (1024 * 1024 * 1024))
527
def pluralize(value, arg='s'):
529
Returns a plural suffix if the value is not 1, for '1 vote' vs. '2 votes'
530
By default, 's' is used as a suffix; if an argument is provided, that string
531
is used instead. If the provided argument contains a comma, the text before
532
the comma is used for the singular case.
536
bits = arg.split(',')
741
return ugettext("%.1f MB") % (bytes / (1024 * 1024))
742
return ugettext("%.1f GB") % (bytes / (1024 * 1024 * 1024))
743
filesizeformat.is_safe = True
745
def pluralize(value, arg=u's'):
747
Returns a plural suffix if the value is not 1. By default, 's' is used as
750
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
751
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
752
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
754
If an argument is provided, that string is used instead:
756
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
757
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
758
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
760
If the provided argument contains a comma, the text before the comma is
761
used for the singular case and the text after the comma is used for the
764
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
765
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
766
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
770
bits = arg.split(u',')
537
771
if len(bits) > 2:
539
773
singular_suffix, plural_suffix = bits[:2]
542
776
if int(value) != 1:
543
777
return plural_suffix
544
except ValueError: # invalid string that's not a number
778
except ValueError: # Invalid string that's not a number.
546
except TypeError: # value isn't a string or a number; maybe it's a list?
780
except TypeError: # Value isn't a string or a number; maybe it's a list?
548
782
if len(value) != 1:
549
783
return plural_suffix
550
except TypeError: # len() of unsized object
784
except TypeError: # len() of unsized object.
552
786
return singular_suffix
787
pluralize.is_safe = False
554
789
def phone2numeric(value):
555
"Takes a phone number and converts it in to its numerical equivalent"
790
"""Takes a phone number and converts it in to its numerical equivalent."""
556
791
from django.utils.text import phone2numeric
557
792
return phone2numeric(value)
793
phone2numeric.is_safe = True
559
795
def pprint(value):
560
"A wrapper around pprint.pprint -- for debugging, really"
796
"""A wrapper around pprint.pprint -- for debugging, really."""
561
797
from pprint import pformat
563
799
return pformat(value)
564
800
except Exception, e:
565
return "Error in formatting:%s" % e
801
return u"Error in formatting: %s" % force_unicode(e, errors="replace")
802
pprint.is_safe = True
567
804
# Syntax: register.filter(name of filter, callback)
568
805
register.filter(add)