Login

math tag

Author:
itchyfingrs
Posted:
May 2, 2011
Language:
Python
Version:
Not specified
Score:
2 (after 2 ratings)

Syntax: {% math <argument, ..> "expression" as var_name %}

Evaluates a math expression in the current context and saves the value into a variable with the given name.

"$<number>" is a placeholder in the math expression. It will be replaced by the value of the argument at index <number> - 1. Arguments are static values or variables immediately after 'math' tag and before the expression (the third last token).

Example usage, {% math a b "min($1, $2)" as result %} {% math a|length b|length 3 "($1 + $2) % $3" as result %}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
## mathtag.py
##

from django.template import Node, Library, Variable, FilterExpression, TemplateSyntaxError
import math
import re

register = Library()

# taken from http://lybniz2.sourceforge.net/safeeval.html
# make a list of safe functions
math_safe_list = ['acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh', 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot', 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians', 'sin', 'sinh', 'sqrt', 'tan', 'tanh']

# use the list to filter the local namespace
math_safe_dict = dict([(k, getattr(math, k)) for k in math_safe_list])

# add any needed builtins back in.
for op in [abs, min, max]:
    math_safe_dict[op.__name__] = op


class MathNode(Node):
    def __init__(self, var_name, expr, args):
        self.var_name = var_name
        self.expr = expr
        self.args = args

    def render(self, context):
        expr = self.expr
        for i, a in enumerate(self.args):
            expr = expr.replace('$%d' % (i + 1), str(a.resolve(context)))
        try:
            result = eval(expr, {"__builtins__": None}, math_safe_dict)
            context[self.var_name] = result
        except:
            pass
        return ''
            

@register.tag('math')
def do_math(parser, token):
    """
    Syntax:
        {% math <argument, ..> "expression" as var_name %}

    Evaluates a math expression in the current context and saves the value into a variable with the given name.

    "$<number>" is a placeholder in the math expression. It will be replaced by the value of the argument at index <number> - 1. 
    Arguments are static values or variables immediately after 'math' tag and before the expression (the third last token).

    Example usage,
        {% math a b "min($1, $2)" as result %}
        {% math a|length b|length 3 "($1 + $2) % $3" as result %}
    """
    tokens = token.split_contents()
    if len(tokens) < 5:
        raise TemplateSyntaxError("'math' tag requires at least 4 arguments")
    expr, as_, var_name = tokens[-3:]

    # strip quote if found
    if re.match(r'^(\'|")', expr):
        expr = expr.strip(expr[0])

    args = []
    for a in tokens[1:-3]:
        if a.find('|') != -1:
            args.append(FilterExpression(a, parser))
        else:
            args.append(Variable(a))
    return MathNode(var_name, expr, args)


## tests.py
##

from django.test import TestCase
from django.template import Template, Context
class MathTagTestCase(TestCase):
    def test_mathtag(self):
        tests = {
            'math01': ['{% math a "$1" as result %}', {'a': 1}, '1'],
            'math02': ['{% math a b "$1 + $2" as result %}', {'a': 1, 'b': 2}, '3'],
            'math03': ['{% math a b "$1 / $2" as result %}', {'a': 1, 'b': 2}, '0'],  # NOTE: an int divides by an int
            'math04': ['{% math a b "$1 / $2" as result %}', {'a': 1.0, 'b': 2.0}, '0.5'],
            'math05': ['{% math a b "min($1, $2)" as result %}', {'a': 3, 'b': 1}, '1'],
            'math06': ['{% math a b c "($1 + $2) / $3" as result %}', {'a': 10, 'b': 10, 'c': 2}, '10'],
            'math07': ['{% math a b c "($1 ** $3) * ($2 ** $3)" as result %}', {'a': 2, 'b': 2, 'c': 2}, '16'],
            'math08': ['{% math a|length b|length 3 "($1 + $2) % $3" as result %}', {'a': range(5), 'b': range(15)}, '2'],
        }

        for name, test in tests.items():
            tag_expr, context, expected_val = test
            test_template = Template("{%% load mathtags %%}%s{{ result }}" % tag_expr, name=name)
            context = Context(context)
            try:
                self.assertEqual(test_template.render(context), expected_val)
            except AssertionError, e:
                raise AssertionError(name, *e.args)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 3 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 3 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 10 months, 1 week ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 11 months ago
  5. Help text hyperlinks by sa2812 11 months, 3 weeks ago

Comments

Please login first before commenting.