963 lines
27 KiB
Python
963 lines
27 KiB
Python
"""Default variable filters."""
|
|
from __future__ import unicode_literals
|
|
|
|
import re
|
|
import random as random_module
|
|
from decimal import Decimal, InvalidOperation, Context, ROUND_HALF_UP
|
|
from functools import wraps
|
|
from pprint import pformat
|
|
|
|
from django.template.base import Variable, Library, VariableDoesNotExist
|
|
from django.conf import settings
|
|
from django.utils import formats
|
|
from django.utils.dateformat import format, time_format
|
|
from django.utils.encoding import force_text, iri_to_uri
|
|
from django.utils.html import (conditional_escape, escapejs,
|
|
escape, urlize as _urlize, linebreaks, strip_tags, avoid_wrapping,
|
|
remove_tags)
|
|
from django.utils.http import urlquote
|
|
from django.utils.text import Truncator, wrap, phone2numeric
|
|
from django.utils.safestring import mark_safe, SafeData, mark_for_escaping
|
|
from django.utils import six
|
|
from django.utils.timesince import timesince, timeuntil
|
|
from django.utils.translation import ugettext, ungettext
|
|
from django.utils.text import normalize_newlines, slugify as _slugify
|
|
|
|
register = Library()
|
|
|
|
|
|
#######################
|
|
# STRING DECORATOR #
|
|
#######################
|
|
|
|
def stringfilter(func):
|
|
"""
|
|
Decorator for filters which should only receive unicode objects. The object
|
|
passed as the first positional argument will be converted to a unicode
|
|
object.
|
|
"""
|
|
def _dec(*args, **kwargs):
|
|
if args:
|
|
args = list(args)
|
|
args[0] = force_text(args[0])
|
|
if (isinstance(args[0], SafeData) and
|
|
getattr(_dec._decorated_function, 'is_safe', False)):
|
|
return mark_safe(func(*args, **kwargs))
|
|
return func(*args, **kwargs)
|
|
|
|
# Include a reference to the real function (used to check original
|
|
# arguments by the template parser, and to bear the 'is_safe' attribute
|
|
# when multiple decorators are applied).
|
|
_dec._decorated_function = getattr(func, '_decorated_function', func)
|
|
|
|
return wraps(func)(_dec)
|
|
|
|
|
|
###################
|
|
# STRINGS #
|
|
###################
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def addslashes(value):
|
|
"""
|
|
Adds slashes before quotes. Useful for escaping strings in CSV, for
|
|
example. Less useful for escaping JavaScript; use the ``escapejs``
|
|
filter instead.
|
|
"""
|
|
return value.replace('\\', '\\\\').replace('"', '\\"').replace("'", "\\'")
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def capfirst(value):
|
|
"""Capitalizes the first character of the value."""
|
|
return value and value[0].upper() + value[1:]
|
|
|
|
|
|
@register.filter("escapejs")
|
|
@stringfilter
|
|
def escapejs_filter(value):
|
|
"""Hex encodes characters for use in JavaScript strings."""
|
|
return escapejs(value)
|
|
|
|
|
|
# Values for testing floatformat input against infinity and NaN representations,
|
|
# which differ across platforms and Python versions. Some (i.e. old Windows
|
|
# ones) are not recognized by Decimal but we want to return them unchanged vs.
|
|
# returning an empty string as we do for completely invalid input. Note these
|
|
# need to be built up from values that are not inf/nan, since inf/nan values do
|
|
# not reload properly from .pyc files on Windows prior to some level of Python 2.5
|
|
# (see Python Issue757815 and Issue1080440).
|
|
pos_inf = 1e200 * 1e200
|
|
neg_inf = -1e200 * 1e200
|
|
nan = (1e200 * 1e200) // (1e200 * 1e200)
|
|
special_floats = [str(pos_inf), str(neg_inf), str(nan)]
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def floatformat(text, arg=-1):
|
|
"""
|
|
Displays a float to a specified number of decimal places.
|
|
|
|
If called without an argument, it displays the floating point number with
|
|
one decimal place -- but only if there's a decimal place to be displayed:
|
|
|
|
* num1 = 34.23234
|
|
* num2 = 34.00000
|
|
* num3 = 34.26000
|
|
* {{ num1|floatformat }} displays "34.2"
|
|
* {{ num2|floatformat }} displays "34"
|
|
* {{ num3|floatformat }} displays "34.3"
|
|
|
|
If arg is positive, it will always display exactly arg number of decimal
|
|
places:
|
|
|
|
* {{ num1|floatformat:3 }} displays "34.232"
|
|
* {{ num2|floatformat:3 }} displays "34.000"
|
|
* {{ num3|floatformat:3 }} displays "34.260"
|
|
|
|
If arg is negative, it will display arg number of decimal places -- but
|
|
only if there are places to be displayed:
|
|
|
|
* {{ num1|floatformat:"-3" }} displays "34.232"
|
|
* {{ num2|floatformat:"-3" }} displays "34"
|
|
* {{ num3|floatformat:"-3" }} displays "34.260"
|
|
|
|
If the input float is infinity or NaN, the (platform-dependent) string
|
|
representation of that value will be displayed.
|
|
"""
|
|
|
|
try:
|
|
input_val = force_text(text)
|
|
d = Decimal(input_val)
|
|
except UnicodeEncodeError:
|
|
return ''
|
|
except InvalidOperation:
|
|
if input_val in special_floats:
|
|
return input_val
|
|
try:
|
|
d = Decimal(force_text(float(text)))
|
|
except (ValueError, InvalidOperation, TypeError, UnicodeEncodeError):
|
|
return ''
|
|
try:
|
|
p = int(arg)
|
|
except ValueError:
|
|
return input_val
|
|
|
|
try:
|
|
m = int(d) - d
|
|
except (ValueError, OverflowError, InvalidOperation):
|
|
return input_val
|
|
|
|
if not m and p < 0:
|
|
return mark_safe(formats.number_format('%d' % (int(d)), 0))
|
|
|
|
if p == 0:
|
|
exp = Decimal(1)
|
|
else:
|
|
exp = Decimal('1.0') / (Decimal(10) ** abs(p))
|
|
try:
|
|
# Set the precision high enough to avoid an exception, see #15789.
|
|
tupl = d.as_tuple()
|
|
units = len(tupl[1]) - tupl[2]
|
|
prec = abs(p) + units + 1
|
|
|
|
# Avoid conversion to scientific notation by accessing `sign`, `digits`
|
|
# and `exponent` from `Decimal.as_tuple()` directly.
|
|
sign, digits, exponent = d.quantize(exp, ROUND_HALF_UP,
|
|
Context(prec=prec)).as_tuple()
|
|
digits = [six.text_type(digit) for digit in reversed(digits)]
|
|
while len(digits) <= abs(exponent):
|
|
digits.append('0')
|
|
digits.insert(-exponent, '.')
|
|
if sign:
|
|
digits.append('-')
|
|
number = ''.join(reversed(digits))
|
|
return mark_safe(formats.number_format(number, abs(p)))
|
|
except InvalidOperation:
|
|
return input_val
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def iriencode(value):
|
|
"""Escapes an IRI value for use in a URL."""
|
|
return force_text(iri_to_uri(value))
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
@stringfilter
|
|
def linenumbers(value, autoescape=None):
|
|
"""Displays text with line numbers."""
|
|
lines = value.split('\n')
|
|
# Find the maximum width of the line count, for use with zero padding
|
|
# string format command
|
|
width = six.text_type(len(six.text_type(len(lines))))
|
|
if not autoescape or isinstance(value, SafeData):
|
|
for i, line in enumerate(lines):
|
|
lines[i] = ("%0" + width + "d. %s") % (i + 1, line)
|
|
else:
|
|
for i, line in enumerate(lines):
|
|
lines[i] = ("%0" + width + "d. %s") % (i + 1, escape(line))
|
|
return mark_safe('\n'.join(lines))
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def lower(value):
|
|
"""Converts a string into all lowercase."""
|
|
return value.lower()
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
@stringfilter
|
|
def make_list(value):
|
|
"""
|
|
Returns the value turned into a list.
|
|
|
|
For an integer, it's a list of digits.
|
|
For a string, it's a list of characters.
|
|
"""
|
|
return list(value)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def slugify(value):
|
|
"""
|
|
Converts to lowercase, removes non-word characters (alphanumerics and
|
|
underscores) and converts spaces to hyphens. Also strips leading and
|
|
trailing whitespace.
|
|
"""
|
|
return _slugify(value)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def stringformat(value, arg):
|
|
"""
|
|
Formats the variable according to the arg, a string formatting specifier.
|
|
|
|
This specifier uses Python string formating syntax, with the exception that
|
|
the leading "%" is dropped.
|
|
|
|
See http://docs.python.org/lib/typesseq-strings.html for documentation
|
|
of Python string formatting
|
|
"""
|
|
try:
|
|
return ("%" + six.text_type(arg)) % value
|
|
except (ValueError, TypeError):
|
|
return ""
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def title(value):
|
|
"""Converts a string into titlecase."""
|
|
t = re.sub("([a-z])'([A-Z])", lambda m: m.group(0).lower(), value.title())
|
|
return re.sub("\d([A-Z])", lambda m: m.group(0).lower(), t)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def truncatechars(value, arg):
|
|
"""
|
|
Truncates a string after a certain number of characters.
|
|
|
|
Argument: Number of characters to truncate after.
|
|
"""
|
|
try:
|
|
length = int(arg)
|
|
except ValueError: # Invalid literal for int().
|
|
return value # Fail silently.
|
|
return Truncator(value).chars(length)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def truncatechars_html(value, arg):
|
|
"""
|
|
Truncates HTML after a certain number of chars.
|
|
|
|
Argument: Number of chars to truncate after.
|
|
|
|
Newlines in the HTML are preserved.
|
|
"""
|
|
try:
|
|
length = int(arg)
|
|
except ValueError: # invalid literal for int()
|
|
return value # Fail silently.
|
|
return Truncator(value).chars(length, html=True)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def truncatewords(value, arg):
|
|
"""
|
|
Truncates a string after a certain number of words.
|
|
|
|
Argument: Number of words to truncate after.
|
|
|
|
Newlines within the string are removed.
|
|
"""
|
|
try:
|
|
length = int(arg)
|
|
except ValueError: # Invalid literal for int().
|
|
return value # Fail silently.
|
|
return Truncator(value).words(length, truncate=' ...')
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def truncatewords_html(value, arg):
|
|
"""
|
|
Truncates HTML after a certain number of words.
|
|
|
|
Argument: Number of words to truncate after.
|
|
|
|
Newlines in the HTML are preserved.
|
|
"""
|
|
try:
|
|
length = int(arg)
|
|
except ValueError: # invalid literal for int()
|
|
return value # Fail silently.
|
|
return Truncator(value).words(length, html=True, truncate=' ...')
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
@stringfilter
|
|
def upper(value):
|
|
"""Converts a string into all uppercase."""
|
|
return value.upper()
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
@stringfilter
|
|
def urlencode(value, safe=None):
|
|
"""
|
|
Escapes a value for use in a URL.
|
|
|
|
Takes an optional ``safe`` parameter used to determine the characters which
|
|
should not be escaped by Django's ``urlquote`` method. If not provided, the
|
|
default safe characters will be used (but an empty string can be provided
|
|
when *all* characters should be escaped).
|
|
"""
|
|
kwargs = {}
|
|
if safe is not None:
|
|
kwargs['safe'] = safe
|
|
return urlquote(value, **kwargs)
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
@stringfilter
|
|
def urlize(value, autoescape=None):
|
|
"""Converts URLs in plain text into clickable links."""
|
|
return mark_safe(_urlize(value, nofollow=True, autoescape=autoescape))
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
@stringfilter
|
|
def urlizetrunc(value, limit, autoescape=None):
|
|
"""
|
|
Converts URLs into clickable links, truncating URLs to the given character
|
|
limit, and adding 'rel=nofollow' attribute to discourage spamming.
|
|
|
|
Argument: Length to truncate URLs to.
|
|
"""
|
|
return mark_safe(_urlize(value, trim_url_limit=int(limit), nofollow=True,
|
|
autoescape=autoescape))
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
@stringfilter
|
|
def wordcount(value):
|
|
"""Returns the number of words."""
|
|
return len(value.split())
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def wordwrap(value, arg):
|
|
"""
|
|
Wraps words at specified line length.
|
|
|
|
Argument: number of characters to wrap the text at.
|
|
"""
|
|
return wrap(value, int(arg))
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def ljust(value, arg):
|
|
"""
|
|
Left-aligns the value in a field of a given width.
|
|
|
|
Argument: field size.
|
|
"""
|
|
return value.ljust(int(arg))
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def rjust(value, arg):
|
|
"""
|
|
Right-aligns the value in a field of a given width.
|
|
|
|
Argument: field size.
|
|
"""
|
|
return value.rjust(int(arg))
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def center(value, arg):
|
|
"""Centers the value in a field of a given width."""
|
|
return value.center(int(arg))
|
|
|
|
|
|
@register.filter
|
|
@stringfilter
|
|
def cut(value, arg):
|
|
"""
|
|
Removes all values of arg from the given string.
|
|
"""
|
|
safe = isinstance(value, SafeData)
|
|
value = value.replace(arg, '')
|
|
if safe and arg != ';':
|
|
return mark_safe(value)
|
|
return value
|
|
|
|
|
|
###################
|
|
# HTML STRINGS #
|
|
###################
|
|
|
|
@register.filter("escape", is_safe=True)
|
|
@stringfilter
|
|
def escape_filter(value):
|
|
"""
|
|
Marks the value as a string that should not be auto-escaped.
|
|
"""
|
|
return mark_for_escaping(value)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def force_escape(value):
|
|
"""
|
|
Escapes a string's HTML. This returns a new string containing the escaped
|
|
characters (as opposed to "escape", which marks the content for later
|
|
possible escaping).
|
|
"""
|
|
return escape(value)
|
|
|
|
|
|
@register.filter("linebreaks", is_safe=True, needs_autoescape=True)
|
|
@stringfilter
|
|
def linebreaks_filter(value, autoescape=None):
|
|
"""
|
|
Replaces line breaks in plain text with appropriate HTML; a single
|
|
newline becomes an HTML line break (``<br />``) and a new line
|
|
followed by a blank line becomes a paragraph break (``</p>``).
|
|
"""
|
|
autoescape = autoescape and not isinstance(value, SafeData)
|
|
return mark_safe(linebreaks(value, autoescape))
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
@stringfilter
|
|
def linebreaksbr(value, autoescape=None):
|
|
"""
|
|
Converts all newlines in a piece of plain text to HTML line breaks
|
|
(``<br />``).
|
|
"""
|
|
autoescape = autoescape and not isinstance(value, SafeData)
|
|
value = normalize_newlines(value)
|
|
if autoescape:
|
|
value = escape(value)
|
|
return mark_safe(value.replace('\n', '<br />'))
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def safe(value):
|
|
"""
|
|
Marks the value as a string that should not be auto-escaped.
|
|
"""
|
|
return mark_safe(value)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def safeseq(value):
|
|
"""
|
|
A "safe" filter for sequences. Marks each element in the sequence,
|
|
individually, as safe, after converting them to unicode. Returns a list
|
|
with the results.
|
|
"""
|
|
return [mark_safe(force_text(obj)) for obj in value]
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def removetags(value, tags):
|
|
"""Removes a space separated list of [X]HTML tags from the output."""
|
|
return remove_tags(value, tags)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
@stringfilter
|
|
def striptags(value):
|
|
"""Strips all [X]HTML tags."""
|
|
return strip_tags(value)
|
|
|
|
|
|
###################
|
|
# LISTS #
|
|
###################
|
|
|
|
@register.filter(is_safe=False)
|
|
def dictsort(value, arg):
|
|
"""
|
|
Takes a list of dicts, returns that list sorted by the property given in
|
|
the argument.
|
|
"""
|
|
try:
|
|
return sorted(value, key=Variable(arg).resolve)
|
|
except (TypeError, VariableDoesNotExist):
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def dictsortreversed(value, arg):
|
|
"""
|
|
Takes a list of dicts, returns that list sorted in reverse order by the
|
|
property given in the argument.
|
|
"""
|
|
try:
|
|
return sorted(value, key=Variable(arg).resolve, reverse=True)
|
|
except (TypeError, VariableDoesNotExist):
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def first(value):
|
|
"""Returns the first item in a list."""
|
|
try:
|
|
return value[0]
|
|
except IndexError:
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
def join(value, arg, autoescape=None):
|
|
"""
|
|
Joins a list with a string, like Python's ``str.join(list)``.
|
|
"""
|
|
value = map(force_text, value)
|
|
if autoescape:
|
|
value = [conditional_escape(v) for v in value]
|
|
try:
|
|
data = conditional_escape(arg).join(value)
|
|
except AttributeError: # fail silently but nicely
|
|
return value
|
|
return mark_safe(data)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def last(value):
|
|
"Returns the last item in a list"
|
|
try:
|
|
return value[-1]
|
|
except IndexError:
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def length(value):
|
|
"""Returns the length of the value - useful for lists."""
|
|
try:
|
|
return len(value)
|
|
except (ValueError, TypeError):
|
|
return 0
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def length_is(value, arg):
|
|
"""Returns a boolean of whether the value's length is the argument."""
|
|
try:
|
|
return len(value) == int(arg)
|
|
except (ValueError, TypeError):
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def random(value):
|
|
"""Returns a random item from the list."""
|
|
return random_module.choice(value)
|
|
|
|
|
|
@register.filter("slice", is_safe=True)
|
|
def slice_filter(value, arg):
|
|
"""
|
|
Returns a slice of the list.
|
|
|
|
Uses the same syntax as Python's list slicing; see
|
|
http://www.diveintopython3.net/native-datatypes.html#slicinglists
|
|
for an introduction.
|
|
"""
|
|
try:
|
|
bits = []
|
|
for x in arg.split(':'):
|
|
if len(x) == 0:
|
|
bits.append(None)
|
|
else:
|
|
bits.append(int(x))
|
|
return value[slice(*bits)]
|
|
|
|
except (ValueError, TypeError):
|
|
return value # Fail silently.
|
|
|
|
|
|
@register.filter(is_safe=True, needs_autoescape=True)
|
|
def unordered_list(value, autoescape=None):
|
|
"""
|
|
Recursively takes a self-nested list and returns an HTML unordered list --
|
|
WITHOUT opening and closing <ul> tags.
|
|
|
|
The list is assumed to be in the proper format. For example, if ``var``
|
|
contains: ``['States', ['Kansas', ['Lawrence', 'Topeka'], 'Illinois']]``,
|
|
then ``{{ var|unordered_list }}`` would return::
|
|
|
|
<li>States
|
|
<ul>
|
|
<li>Kansas
|
|
<ul>
|
|
<li>Lawrence</li>
|
|
<li>Topeka</li>
|
|
</ul>
|
|
</li>
|
|
<li>Illinois</li>
|
|
</ul>
|
|
</li>
|
|
"""
|
|
if autoescape:
|
|
escaper = conditional_escape
|
|
else:
|
|
escaper = lambda x: x
|
|
|
|
def convert_old_style_list(list_):
|
|
"""
|
|
Converts old style lists to the new easier to understand format.
|
|
|
|
The old list format looked like:
|
|
['Item 1', [['Item 1.1', []], ['Item 1.2', []]]
|
|
|
|
And it is converted to:
|
|
['Item 1', ['Item 1.1', 'Item 1.2]]
|
|
"""
|
|
if not isinstance(list_, (tuple, list)) or len(list_) != 2:
|
|
return list_, False
|
|
first_item, second_item = list_
|
|
if second_item == []:
|
|
return [first_item], True
|
|
try:
|
|
# see if second item is iterable
|
|
iter(second_item)
|
|
except TypeError:
|
|
return list_, False
|
|
old_style_list = True
|
|
new_second_item = []
|
|
for sublist in second_item:
|
|
item, old_style_list = convert_old_style_list(sublist)
|
|
if not old_style_list:
|
|
break
|
|
new_second_item.extend(item)
|
|
if old_style_list:
|
|
second_item = new_second_item
|
|
return [first_item, second_item], old_style_list
|
|
|
|
def _helper(list_, tabs=1):
|
|
indent = '\t' * tabs
|
|
output = []
|
|
|
|
list_length = len(list_)
|
|
i = 0
|
|
while i < list_length:
|
|
title = list_[i]
|
|
sublist = ''
|
|
sublist_item = None
|
|
if isinstance(title, (list, tuple)):
|
|
sublist_item = title
|
|
title = ''
|
|
elif i < list_length - 1:
|
|
next_item = list_[i + 1]
|
|
if next_item and isinstance(next_item, (list, tuple)):
|
|
# The next item is a sub-list.
|
|
sublist_item = next_item
|
|
# We've processed the next item now too.
|
|
i += 1
|
|
if sublist_item:
|
|
sublist = _helper(sublist_item, tabs + 1)
|
|
sublist = '\n%s<ul>\n%s\n%s</ul>\n%s' % (indent, sublist,
|
|
indent, indent)
|
|
output.append('%s<li>%s%s</li>' % (indent,
|
|
escaper(force_text(title)), sublist))
|
|
i += 1
|
|
return '\n'.join(output)
|
|
value, converted = convert_old_style_list(value)
|
|
return mark_safe(_helper(value))
|
|
|
|
|
|
###################
|
|
# INTEGERS #
|
|
###################
|
|
|
|
@register.filter(is_safe=False)
|
|
def add(value, arg):
|
|
"""Adds the arg to the value."""
|
|
try:
|
|
return int(value) + int(arg)
|
|
except (ValueError, TypeError):
|
|
try:
|
|
return value + arg
|
|
except Exception:
|
|
return ''
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def get_digit(value, arg):
|
|
"""
|
|
Given a whole number, returns the requested digit of it, where 1 is the
|
|
right-most digit, 2 is the second-right-most digit, etc. Returns the
|
|
original value for invalid input (if input or argument is not an integer,
|
|
or if argument is less than 1). Otherwise, output is always an integer.
|
|
"""
|
|
try:
|
|
arg = int(arg)
|
|
value = int(value)
|
|
except ValueError:
|
|
return value # Fail silently for an invalid argument
|
|
if arg < 1:
|
|
return value
|
|
try:
|
|
return int(str(value)[-arg])
|
|
except IndexError:
|
|
return 0
|
|
|
|
|
|
###################
|
|
# DATES #
|
|
###################
|
|
|
|
@register.filter(expects_localtime=True, is_safe=False)
|
|
def date(value, arg=None):
|
|
"""Formats a date according to the given format."""
|
|
if value in (None, ''):
|
|
return ''
|
|
if arg is None:
|
|
arg = settings.DATE_FORMAT
|
|
try:
|
|
return formats.date_format(value, arg)
|
|
except AttributeError:
|
|
try:
|
|
return format(value, arg)
|
|
except AttributeError:
|
|
return ''
|
|
|
|
|
|
@register.filter(expects_localtime=True, is_safe=False)
|
|
def time(value, arg=None):
|
|
"""Formats a time according to the given format."""
|
|
if value in (None, ''):
|
|
return ''
|
|
if arg is None:
|
|
arg = settings.TIME_FORMAT
|
|
try:
|
|
return formats.time_format(value, arg)
|
|
except AttributeError:
|
|
try:
|
|
return time_format(value, arg)
|
|
except AttributeError:
|
|
return ''
|
|
|
|
|
|
@register.filter("timesince", is_safe=False)
|
|
def timesince_filter(value, arg=None):
|
|
"""Formats a date as the time since that date (i.e. "4 days, 6 hours")."""
|
|
if not value:
|
|
return ''
|
|
try:
|
|
if arg:
|
|
return timesince(value, arg)
|
|
return timesince(value)
|
|
except (ValueError, TypeError):
|
|
return ''
|
|
|
|
|
|
@register.filter("timeuntil", is_safe=False)
|
|
def timeuntil_filter(value, arg=None):
|
|
"""Formats a date as the time until that date (i.e. "4 days, 6 hours")."""
|
|
if not value:
|
|
return ''
|
|
try:
|
|
return timeuntil(value, arg)
|
|
except (ValueError, TypeError):
|
|
return ''
|
|
|
|
|
|
###################
|
|
# LOGIC #
|
|
###################
|
|
|
|
@register.filter(is_safe=False)
|
|
def default(value, arg):
|
|
"""If value is unavailable, use given default."""
|
|
return value or arg
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def default_if_none(value, arg):
|
|
"""If value is None, use given default."""
|
|
if value is None:
|
|
return arg
|
|
return value
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def divisibleby(value, arg):
|
|
"""Returns True if the value is devisible by the argument."""
|
|
return int(value) % int(arg) == 0
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def yesno(value, arg=None):
|
|
"""
|
|
Given a string mapping values for true, false and (optionally) None,
|
|
returns one of those strings according to the value:
|
|
|
|
========== ====================== ==================================
|
|
Value Argument Outputs
|
|
========== ====================== ==================================
|
|
``True`` ``"yeah,no,maybe"`` ``yeah``
|
|
``False`` ``"yeah,no,maybe"`` ``no``
|
|
``None`` ``"yeah,no,maybe"`` ``maybe``
|
|
``None`` ``"yeah,no"`` ``"no"`` (converts None to False
|
|
if no mapping for None is given.
|
|
========== ====================== ==================================
|
|
"""
|
|
if arg is None:
|
|
arg = ugettext('yes,no,maybe')
|
|
bits = arg.split(',')
|
|
if len(bits) < 2:
|
|
return value # Invalid arg.
|
|
try:
|
|
yes, no, maybe = bits
|
|
except ValueError:
|
|
# Unpack list of wrong size (no "maybe" value provided).
|
|
yes, no, maybe = bits[0], bits[1], bits[1]
|
|
if value is None:
|
|
return maybe
|
|
if value:
|
|
return yes
|
|
return no
|
|
|
|
|
|
###################
|
|
# MISC #
|
|
###################
|
|
|
|
@register.filter(is_safe=True)
|
|
def filesizeformat(bytes):
|
|
"""
|
|
Formats the value like a 'human-readable' file size (i.e. 13 KB, 4.1 MB,
|
|
102 bytes, etc).
|
|
"""
|
|
try:
|
|
bytes = float(bytes)
|
|
except (TypeError, ValueError, UnicodeDecodeError):
|
|
value = ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0}
|
|
return avoid_wrapping(value)
|
|
|
|
filesize_number_format = lambda value: formats.number_format(round(value, 1), 1)
|
|
|
|
KB = 1 << 10
|
|
MB = 1 << 20
|
|
GB = 1 << 30
|
|
TB = 1 << 40
|
|
PB = 1 << 50
|
|
|
|
if bytes < KB:
|
|
value = ungettext("%(size)d byte", "%(size)d bytes", bytes) % {'size': bytes}
|
|
elif bytes < MB:
|
|
value = ugettext("%s KB") % filesize_number_format(bytes / KB)
|
|
elif bytes < GB:
|
|
value = ugettext("%s MB") % filesize_number_format(bytes / MB)
|
|
elif bytes < TB:
|
|
value = ugettext("%s GB") % filesize_number_format(bytes / GB)
|
|
elif bytes < PB:
|
|
value = ugettext("%s TB") % filesize_number_format(bytes / TB)
|
|
else:
|
|
value = ugettext("%s PB") % filesize_number_format(bytes / PB)
|
|
|
|
return avoid_wrapping(value)
|
|
|
|
|
|
@register.filter(is_safe=False)
|
|
def pluralize(value, arg='s'):
|
|
"""
|
|
Returns a plural suffix if the value is not 1. By default, 's' is used as
|
|
the suffix:
|
|
|
|
* If value is 0, vote{{ value|pluralize }} displays "0 votes".
|
|
* If value is 1, vote{{ value|pluralize }} displays "1 vote".
|
|
* If value is 2, vote{{ value|pluralize }} displays "2 votes".
|
|
|
|
If an argument is provided, that string is used instead:
|
|
|
|
* If value is 0, class{{ value|pluralize:"es" }} displays "0 classes".
|
|
* If value is 1, class{{ value|pluralize:"es" }} displays "1 class".
|
|
* If value is 2, class{{ value|pluralize:"es" }} displays "2 classes".
|
|
|
|
If the provided argument contains a comma, the text before the comma is
|
|
used for the singular case and the text after the comma is used for the
|
|
plural case:
|
|
|
|
* If value is 0, cand{{ value|pluralize:"y,ies" }} displays "0 candies".
|
|
* If value is 1, cand{{ value|pluralize:"y,ies" }} displays "1 candy".
|
|
* If value is 2, cand{{ value|pluralize:"y,ies" }} displays "2 candies".
|
|
"""
|
|
if ',' not in arg:
|
|
arg = ',' + arg
|
|
bits = arg.split(',')
|
|
if len(bits) > 2:
|
|
return ''
|
|
singular_suffix, plural_suffix = bits[:2]
|
|
|
|
try:
|
|
if int(value) != 1:
|
|
return plural_suffix
|
|
except ValueError: # Invalid string that's not a number.
|
|
pass
|
|
except TypeError: # Value isn't a string or a number; maybe it's a list?
|
|
try:
|
|
if len(value) != 1:
|
|
return plural_suffix
|
|
except TypeError: # len() of unsized object.
|
|
pass
|
|
return singular_suffix
|
|
|
|
|
|
@register.filter("phone2numeric", is_safe=True)
|
|
def phone2numeric_filter(value):
|
|
"""Takes a phone number and converts it in to its numerical equivalent."""
|
|
return phone2numeric(value)
|
|
|
|
|
|
@register.filter(is_safe=True)
|
|
def pprint(value):
|
|
"""A wrapper around pprint.pprint -- for debugging, really."""
|
|
try:
|
|
return pformat(value)
|
|
except Exception as e:
|
|
return "Error in formatting: %s" % force_text(e, errors="replace")
|