Implemented 'smart if' template tag, allowing filters and various operators to be used in the 'if' tag
Thanks to Chris Beaven for the initial patch, Fredrik Lundh for the basis of the parser methodology and Russell Keith-Magee for code reviews. There are some BACKWARDS INCOMPATIBILITIES in rare cases - in particular, if you were using the keywords 'and', 'or' or 'not' as variable names within the 'if' expression, which was previously allowed in some cases. git-svn-id: http://code.djangoproject.com/svn/django/trunk@11806 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
25020ddb05
commit
2c2f5aee4d
|
@ -11,6 +11,7 @@ except NameError:
|
|||
from django.template import Node, NodeList, Template, Context, Variable
|
||||
from django.template import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
|
||||
from django.template import get_library, Library, InvalidTemplateLibrary
|
||||
from django.template.smartif import IfParser, Literal
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_str, smart_unicode
|
||||
from django.utils.itercompat import groupby
|
||||
|
@ -227,10 +228,9 @@ class IfEqualNode(Node):
|
|||
return self.nodelist_false.render(context)
|
||||
|
||||
class IfNode(Node):
|
||||
def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
|
||||
self.bool_exprs = bool_exprs
|
||||
def __init__(self, var, nodelist_true, nodelist_false=None):
|
||||
self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
|
||||
self.link_type = link_type
|
||||
self.var = var
|
||||
|
||||
def __repr__(self):
|
||||
return "<If node>"
|
||||
|
@ -250,28 +250,10 @@ class IfNode(Node):
|
|||
return nodes
|
||||
|
||||
def render(self, context):
|
||||
if self.link_type == IfNode.LinkTypes.or_:
|
||||
for ifnot, bool_expr in self.bool_exprs:
|
||||
try:
|
||||
value = bool_expr.resolve(context, True)
|
||||
except VariableDoesNotExist:
|
||||
value = None
|
||||
if (value and not ifnot) or (ifnot and not value):
|
||||
return self.nodelist_true.render(context)
|
||||
return self.nodelist_false.render(context)
|
||||
else:
|
||||
for ifnot, bool_expr in self.bool_exprs:
|
||||
try:
|
||||
value = bool_expr.resolve(context, True)
|
||||
except VariableDoesNotExist:
|
||||
value = None
|
||||
if not ((value and not ifnot) or (ifnot and not value)):
|
||||
return self.nodelist_false.render(context)
|
||||
if self.var.eval(context):
|
||||
return self.nodelist_true.render(context)
|
||||
|
||||
class LinkTypes:
|
||||
and_ = 0,
|
||||
or_ = 1
|
||||
else:
|
||||
return self.nodelist_false.render(context)
|
||||
|
||||
class RegroupNode(Node):
|
||||
def __init__(self, target, expression, var_name):
|
||||
|
@ -761,6 +743,27 @@ def ifnotequal(parser, token):
|
|||
return do_ifequal(parser, token, True)
|
||||
ifnotequal = register.tag(ifnotequal)
|
||||
|
||||
class TemplateLiteral(Literal):
|
||||
def __init__(self, value, text):
|
||||
self.value = value
|
||||
self.text = text # for better error messages
|
||||
|
||||
def display(self):
|
||||
return self.text
|
||||
|
||||
def eval(self, context):
|
||||
return self.value.resolve(context, ignore_failures=True)
|
||||
|
||||
class TemplateIfParser(IfParser):
|
||||
error_class = TemplateSyntaxError
|
||||
|
||||
def __init__(self, parser, *args, **kwargs):
|
||||
self.template_parser = parser
|
||||
return super(TemplateIfParser, self).__init__(*args, **kwargs)
|
||||
|
||||
def create_var(self, value):
|
||||
return TemplateLiteral(self.template_parser.compile_filter(value), value)
|
||||
|
||||
#@register.tag(name="if")
|
||||
def do_if(parser, token):
|
||||
"""
|
||||
|
@ -805,47 +808,21 @@ def do_if(parser, token):
|
|||
There are some athletes and absolutely no coaches.
|
||||
{% endif %}
|
||||
|
||||
``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
|
||||
because the order of logic would be ambigous. For example, this is
|
||||
invalid::
|
||||
Comparison operators are also available, and the use of filters is also
|
||||
allowed, for example:
|
||||
|
||||
{% if athlete_list and coach_list or cheerleader_list %}
|
||||
{% if articles|length >= 5 %}...{% endif %}
|
||||
|
||||
If you need to combine ``and`` and ``or`` to do advanced logic, just use
|
||||
nested if tags. For example::
|
||||
Arguments and operators _must_ have a space between them, so
|
||||
``{% if 1>2 %}`` is not a valid if tag.
|
||||
|
||||
{% if athlete_list %}
|
||||
{% if coach_list or cheerleader_list %}
|
||||
We have athletes, and either coaches or cheerleaders!
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
All supported operators are: ``or``, ``and``, ``in``, ``==`` (or ``=``),
|
||||
``!=``, ``>``, ``>=``, ``<`` and ``<=``.
|
||||
|
||||
Operator precedence follows Python.
|
||||
"""
|
||||
bits = token.contents.split()
|
||||
del bits[0]
|
||||
if not bits:
|
||||
raise TemplateSyntaxError("'if' statement requires at least one argument")
|
||||
# Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
|
||||
bitstr = ' '.join(bits)
|
||||
boolpairs = bitstr.split(' and ')
|
||||
boolvars = []
|
||||
if len(boolpairs) == 1:
|
||||
link_type = IfNode.LinkTypes.or_
|
||||
boolpairs = bitstr.split(' or ')
|
||||
else:
|
||||
link_type = IfNode.LinkTypes.and_
|
||||
if ' or ' in bitstr:
|
||||
raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
|
||||
for boolpair in boolpairs:
|
||||
if ' ' in boolpair:
|
||||
try:
|
||||
not_, boolvar = boolpair.split()
|
||||
except ValueError:
|
||||
raise TemplateSyntaxError, "'if' statement improperly formatted"
|
||||
if not_ != 'not':
|
||||
raise TemplateSyntaxError, "Expected 'not' in if statement"
|
||||
boolvars.append((True, parser.compile_filter(boolvar)))
|
||||
else:
|
||||
boolvars.append((False, parser.compile_filter(boolpair)))
|
||||
bits = token.split_contents()[1:]
|
||||
var = TemplateIfParser(parser, bits).parse()
|
||||
nodelist_true = parser.parse(('else', 'endif'))
|
||||
token = parser.next_token()
|
||||
if token.contents == 'else':
|
||||
|
@ -853,7 +830,7 @@ def do_if(parser, token):
|
|||
parser.delete_first_token()
|
||||
else:
|
||||
nodelist_false = NodeList()
|
||||
return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
|
||||
return IfNode(var, nodelist_true, nodelist_false)
|
||||
do_if = register.tag("if", do_if)
|
||||
|
||||
#@register.tag
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
"""
|
||||
Parser and utilities for the smart 'if' tag
|
||||
"""
|
||||
import operator
|
||||
|
||||
# Using a simple top down parser, as described here:
|
||||
# http://effbot.org/zone/simple-top-down-parsing.htm.
|
||||
# 'led' = left denotation
|
||||
# 'nud' = null denotation
|
||||
# 'bp' = binding power (left = lbp, right = rbp)
|
||||
|
||||
class TokenBase(object):
|
||||
"""
|
||||
Base class for operators and literals, mainly for debugging and for throwing
|
||||
syntax errors.
|
||||
"""
|
||||
id = None # node/token type name
|
||||
value = None # used by literals
|
||||
first = second = None # used by tree nodes
|
||||
|
||||
def nud(self, parser):
|
||||
# Null denotation - called in prefix context
|
||||
raise parser.error_class(
|
||||
"Not expecting '%s' in this position in if tag." % self.id
|
||||
)
|
||||
|
||||
def led(self, left, parser):
|
||||
# Left denotation - called in infix context
|
||||
raise parser.error_class(
|
||||
"Not expecting '%s' as infix operator in if tag." % self.id
|
||||
)
|
||||
|
||||
def display(self):
|
||||
"""
|
||||
Returns what to display in error messages for this node
|
||||
"""
|
||||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
out = [str(x) for x in [self.id, self.first, self.second] if x is not None]
|
||||
return "(" + " ".join(out) + ")"
|
||||
|
||||
|
||||
def infix(bp, func):
|
||||
"""
|
||||
Creates an infix operator, given a binding power and a function that
|
||||
evaluates the node
|
||||
"""
|
||||
class Operator(TokenBase):
|
||||
lbp = bp
|
||||
|
||||
def led(self, left, parser):
|
||||
self.first = left
|
||||
self.second = parser.expression(bp)
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
try:
|
||||
return func(self.first.eval(context), self.second.eval(context))
|
||||
except Exception:
|
||||
# Templates shouldn't throw exceptions when rendering. We are
|
||||
# most likely to get exceptions for things like {% if foo in bar
|
||||
# %} where 'bar' does not support 'in', so default to False
|
||||
return False
|
||||
|
||||
return Operator
|
||||
|
||||
|
||||
def prefix(bp, func):
|
||||
"""
|
||||
Creates a prefix operator, given a binding power and a function that
|
||||
evaluates the node.
|
||||
"""
|
||||
class Operator(TokenBase):
|
||||
lbp = bp
|
||||
|
||||
def nud(self, parser):
|
||||
self.first = parser.expression(bp)
|
||||
self.second = None
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
try:
|
||||
return func(self.first.eval(context))
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return Operator
|
||||
|
||||
|
||||
# Operator precedence follows Python.
|
||||
# NB - we can get slightly more accurate syntax error messages by not using the
|
||||
# same object for '==' and '='.
|
||||
|
||||
OPERATORS = {
|
||||
'or': infix(6, lambda x, y: x or y),
|
||||
'and': infix(7, lambda x, y: x and y),
|
||||
'not': prefix(8, operator.not_),
|
||||
'in': infix(9, lambda x, y: x in y),
|
||||
'=': infix(10, operator.eq),
|
||||
'==': infix(10, operator.eq),
|
||||
'!=': infix(10, operator.ne),
|
||||
'>': infix(10, operator.gt),
|
||||
'>=': infix(10, operator.ge),
|
||||
'<': infix(10, operator.lt),
|
||||
'<=': infix(10, operator.le),
|
||||
}
|
||||
|
||||
# Assign 'id' to each:
|
||||
for key, op in OPERATORS.items():
|
||||
op.id = key
|
||||
|
||||
|
||||
class Literal(TokenBase):
|
||||
"""
|
||||
A basic self-resolvable object similar to a Django template variable.
|
||||
"""
|
||||
# IfParser uses Literal in create_var, but TemplateIfParser overrides
|
||||
# create_var so that a proper implementation that actually resolves
|
||||
# variables, filters etc is used.
|
||||
id = "literal"
|
||||
lbp = 0
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def display(self):
|
||||
return repr(self.value)
|
||||
|
||||
def nud(self, parser):
|
||||
return self
|
||||
|
||||
def eval(self, context):
|
||||
return self.value
|
||||
|
||||
def __repr__(self):
|
||||
return "(%s %r)" % (self.id, self.value)
|
||||
|
||||
|
||||
class EndToken(TokenBase):
|
||||
lbp = 0
|
||||
|
||||
def nud(self, parser):
|
||||
raise parser.error_class("Unexpected end of expression in if tag.")
|
||||
|
||||
EndToken = EndToken()
|
||||
|
||||
|
||||
class IfParser(object):
|
||||
error_class = ValueError
|
||||
|
||||
def __init__(self, tokens):
|
||||
self.tokens = map(self.translate_tokens, tokens)
|
||||
self.pos = 0
|
||||
self.current_token = self.next()
|
||||
|
||||
def translate_tokens(self, token):
|
||||
try:
|
||||
op = OPERATORS[token]
|
||||
except (KeyError, TypeError):
|
||||
return self.create_var(token)
|
||||
else:
|
||||
return op()
|
||||
|
||||
def next(self):
|
||||
if self.pos >= len(self.tokens):
|
||||
return EndToken
|
||||
else:
|
||||
retval = self.tokens[self.pos]
|
||||
self.pos += 1
|
||||
return retval
|
||||
|
||||
def parse(self):
|
||||
retval = self.expression()
|
||||
# Check that we have exhausted all the tokens
|
||||
if self.current_token is not EndToken:
|
||||
raise self.error_class("Unused '%s' at end of if expression." %
|
||||
self.current_token.display())
|
||||
return retval
|
||||
|
||||
def expression(self, rbp=0):
|
||||
t = self.current_token
|
||||
self.current_token = self.next()
|
||||
left = t.nud(self)
|
||||
while rbp < self.current_token.lbp:
|
||||
t = self.current_token
|
||||
self.current_token = self.next()
|
||||
left = t.led(left, self)
|
||||
return left
|
||||
|
||||
def create_var(self, value):
|
||||
return Literal(value)
|
|
@ -313,6 +313,9 @@ displayed by the ``{{ athlete_list|length }}`` variable.
|
|||
As you can see, the ``if`` tag can take an optional ``{% else %}`` clause that
|
||||
will be displayed if the test fails.
|
||||
|
||||
Boolean operators
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
``if`` tags may use ``and``, ``or`` or ``not`` to test a number of variables or
|
||||
to negate a given variable::
|
||||
|
||||
|
@ -338,24 +341,153 @@ to negate a given variable::
|
|||
There are some athletes and absolutely no coaches.
|
||||
{% endif %}
|
||||
|
||||
``if`` tags don't allow ``and`` and ``or`` clauses within the same tag, because
|
||||
the order of logic would be ambiguous. For example, this is invalid::
|
||||
.. versionchanged:: 1.2
|
||||
|
||||
Use of both ``and`` and ``or`` clauses within the same tag is allowed, with
|
||||
``and`` having higher precedence than ``or`` e.g.::
|
||||
|
||||
{% if athlete_list and coach_list or cheerleader_list %}
|
||||
|
||||
If you need to combine ``and`` and ``or`` to do advanced logic, just use nested
|
||||
``if`` tags. For example::
|
||||
will be interpreted like:
|
||||
|
||||
{% if athlete_list %}
|
||||
{% if coach_list or cheerleader_list %}
|
||||
We have athletes, and either coaches or cheerleaders!
|
||||
{% endif %}
|
||||
.. code-block:: python
|
||||
|
||||
if (athlete_list and coach_list) or cheerleader_list
|
||||
|
||||
Use of actual brackets in the ``if`` tag is invalid syntax. If you need them to
|
||||
indicate precedence, you should use nested ``if`` tags.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
|
||||
|
||||
``if`` tags may also use the operators ``==``, ``!=``, ``<``, ``>``,
|
||||
``<=``, ``>=`` and ``in`` which work as follows:
|
||||
|
||||
|
||||
``==`` operator
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Equality. Example::
|
||||
|
||||
{% if somevar == "x" %}
|
||||
This appears if variable somevar equals the string "x"
|
||||
{% endif %}
|
||||
|
||||
Multiple uses of the same logical operator are fine, as long as you use the
|
||||
same operator. For example, this is valid::
|
||||
``!=`` operator
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Inequality. Example::
|
||||
|
||||
{% if somevar != "x" %}
|
||||
This appears if variable somevar does not equal the string "x",
|
||||
or if somevar is not found in the context
|
||||
{% endif %}
|
||||
|
||||
``<`` operator
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Less than. Example::
|
||||
|
||||
{% if somevar < 100 %}
|
||||
This appears if variable somevar is less than 100.
|
||||
{% endif %}
|
||||
|
||||
``>`` operator
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
Greater than. Example::
|
||||
|
||||
{% if somevar > 0 %}
|
||||
This appears if variable somevar is greater than 0.
|
||||
{% endif %}
|
||||
|
||||
``<=`` operator
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Less than or equal to. Example::
|
||||
|
||||
{% if somevar <= 100 %}
|
||||
This appears if variable somevar is less than 100 or equal to 100.
|
||||
{% endif %}
|
||||
|
||||
``>=`` operator
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Greater than or equal to. Example::
|
||||
|
||||
{% if somevar >= 1 %}
|
||||
This appears if variable somevar is greater than 1 or equal to 1.
|
||||
{% endif %}
|
||||
|
||||
``in`` operator
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Contained within. This operator is supported by many Python containers to test
|
||||
whether the given value is in the container. The following are some examples of
|
||||
how ``x in y`` will be interpreted::
|
||||
|
||||
{% if "bc" in "abcdef" %}
|
||||
This appears since "bc" is a substring of "abcdef"
|
||||
{% endif %}
|
||||
|
||||
{% if "hello" in greetings %}
|
||||
If greetings is a list or set, one element of which is the string
|
||||
"hello", this will appear.
|
||||
{% endif %}
|
||||
|
||||
{% if user in users %}
|
||||
If users is a QuerySet, this will appear if user is an
|
||||
instance that belongs to the QuerySet.
|
||||
{% endif %}
|
||||
|
||||
|
||||
The comparison operators cannot be 'chained' like in Python or in mathematical
|
||||
notation. For example, instead of using::
|
||||
|
||||
{% if a > b > c %} (WRONG)
|
||||
|
||||
you should use::
|
||||
|
||||
{% if a > b and b > c %}
|
||||
|
||||
|
||||
Filters
|
||||
^^^^^^^
|
||||
|
||||
You can also use filters in the ``if`` expression. For example::
|
||||
|
||||
{% if messages|length >= 100 %}
|
||||
You have lots of messages today!
|
||||
{% endif %}
|
||||
|
||||
Complex expressions
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
All of the above can be combined to form complex expressions. For such
|
||||
expressions, it can be important to know how the operators are grouped when the
|
||||
expression is evaluated - that is, the precedence rules. The precedence of the
|
||||
operators, from lowest to highest, is as follows:
|
||||
|
||||
* ``or``
|
||||
* ``and``
|
||||
* ``not``
|
||||
* ``in``
|
||||
* ``==``, ``!=``, ``<``, ``>``,``<=``, ``>=``
|
||||
|
||||
(This follows Python exactly). So, for example, the following complex if tag:
|
||||
|
||||
{% if a == b or c == d and e %}
|
||||
|
||||
...will be interpreted as:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
(a == b) or ((c == d) and e)
|
||||
|
||||
If you need different precedence, you will need to use nested if tags. Sometimes
|
||||
that is better for clarity anyway, for the sake of those who do not know the
|
||||
precedence rules.
|
||||
|
||||
{% if athlete_list or coach_list or parent_list or teacher_list %}
|
||||
|
||||
.. templatetag:: ifchanged
|
||||
|
||||
|
@ -427,6 +559,9 @@ You cannot check for equality with Python objects such as ``True`` or
|
|||
``False``. If you need to test if something is true or false, use the ``if``
|
||||
tag instead.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
An alternative to the ``ifequal`` tag is to use the :ttag:`if` tag and the ``==`` operator.
|
||||
|
||||
.. templatetag:: ifnotequal
|
||||
|
||||
ifnotequal
|
||||
|
@ -434,6 +569,9 @@ ifnotequal
|
|||
|
||||
Just like ``ifequal``, except it tests that the two arguments are not equal.
|
||||
|
||||
.. versionadded:: 1.2
|
||||
An alternative to the ``ifnotequal`` tag is to use the :ttag:`if` tag and the ``!=`` operator.
|
||||
|
||||
.. templatetag:: include
|
||||
|
||||
include
|
||||
|
|
|
@ -42,6 +42,15 @@ changes that developers must be aware of:
|
|||
* All of the CSRF has moved from contrib to core (with backwards compatible
|
||||
imports in the old locations, which are deprecated).
|
||||
|
||||
:ttag:`if` tag changes
|
||||
----------------------
|
||||
|
||||
Due to new features in the :ttag:`if` template tag, it no longer accepts 'and',
|
||||
'or' and 'not' as valid **variable** names. Previously that worked in some
|
||||
cases even though these strings were normally treated as keywords. Now, the
|
||||
keyword status is always enforced, and template code like ``{% if not %}`` or
|
||||
``{% if and %}`` will throw a TemplateSyntaxError.
|
||||
|
||||
``LazyObject``
|
||||
--------------
|
||||
|
||||
|
@ -196,3 +205,37 @@ messaging, for both anonymous and authenticated clients. The messages framework
|
|||
replaces the deprecated user message API and allows you to temporarily store
|
||||
messages in one request and retrieve them for display in a subsequent request
|
||||
(usually the next one).
|
||||
|
||||
'Smart' if tag
|
||||
--------------
|
||||
|
||||
The :ttag:`if` tag has been upgraded to be much more powerful. First, support
|
||||
for comparison operators has been added. No longer will you have to type:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% ifnotequal a b %}
|
||||
...
|
||||
{% endifnotequal %}
|
||||
|
||||
...as you can now do:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
{% if a != b %}
|
||||
...
|
||||
{% endif %}
|
||||
|
||||
The operators supported are ``==``, ``!=``, ``<``, ``>``, ``<=``, ``>=`` and
|
||||
``in``, all of which work like the Python operators, in addition to ``and``,
|
||||
``or`` and ``not`` which were already supported.
|
||||
|
||||
Also, filters may now be used in the ``if`` expression. For example:
|
||||
|
||||
.. code-block:: html+django
|
||||
|
||||
<div
|
||||
{% if user.email|lower == message.recipient|lower %}
|
||||
class="highlight"
|
||||
{% endif %}
|
||||
>{{ message }}</div>
|
||||
|
|
|
@ -187,8 +187,8 @@ tags:
|
|||
<li>{{ athlete.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
:ttag:`if` and :ttag:`else`
|
||||
|
||||
:ttag:`if` and ``else``
|
||||
Evaluates a variable, and if that variable is "true" the contents of the
|
||||
block are displayed::
|
||||
|
||||
|
@ -200,20 +200,15 @@ tags:
|
|||
|
||||
In the above, if ``athlete_list`` is not empty, the number of athletes
|
||||
will be displayed by the ``{{ athlete_list|length }}`` variable.
|
||||
|
||||
:ttag:`ifequal` and :ttag:`ifnotequal`
|
||||
Display some contents if two arguments are or are not equal. For example::
|
||||
|
||||
{% ifequal athlete.name coach.name %}
|
||||
...
|
||||
{% endifequal %}
|
||||
You can also use filters and various operators in the ``if`` tag::
|
||||
|
||||
Or::
|
||||
{% if athlete_list|length > 1 %}
|
||||
Team: {% for athlete in athlete_list %} ... {% endfor %}
|
||||
{% else %}
|
||||
Athlete: {{ athlete_list.0.name }}
|
||||
{% endif %}
|
||||
|
||||
{% ifnotequal athlete.name "Joe" %}
|
||||
...
|
||||
{% endifnotequal %}
|
||||
|
||||
:ttag:`block` and :ttag:`extends`
|
||||
Set up `template inheritance`_ (see below), a powerful way
|
||||
of cutting down on "boilerplate" in templates.
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import unittest
|
||||
from django.template.smartif import IfParser, Literal
|
||||
|
||||
class SmartIfTests(unittest.TestCase):
|
||||
|
||||
def assertCalcEqual(self, expected, tokens):
|
||||
self.assertEqual(expected, IfParser(tokens).parse().eval({}))
|
||||
|
||||
# We only test things here that are difficult to test elsewhere
|
||||
# Many other tests are found in the main tests for builtin template tags
|
||||
# Test parsing via the printed parse tree
|
||||
def test_not(self):
|
||||
var = IfParser(["not", False]).parse()
|
||||
self.assertEqual("(not (literal False))", repr(var))
|
||||
self.assert_(var.eval({}))
|
||||
|
||||
self.assertFalse(IfParser(["not", True]).parse().eval({}))
|
||||
|
||||
def test_or(self):
|
||||
var = IfParser([True, "or", False]).parse()
|
||||
self.assertEqual("(or (literal True) (literal False))", repr(var))
|
||||
self.assert_(var.eval({}))
|
||||
|
||||
def test_in(self):
|
||||
list_ = [1,2,3]
|
||||
self.assertCalcEqual(True, [1, 'in', list_])
|
||||
self.assertCalcEqual(False, [1, 'in', None])
|
||||
self.assertCalcEqual(False, [None, 'in', list_])
|
||||
|
||||
def test_precedence(self):
|
||||
# (False and False) or True == True <- we want this one, like Python
|
||||
# False and (False or True) == False
|
||||
self.assertCalcEqual(True, [False, 'and', False, 'or', True])
|
||||
|
||||
# True or (False and False) == True <- we want this one, like Python
|
||||
# (True or False) and False == False
|
||||
self.assertCalcEqual(True, [True, 'or', False, 'and', False])
|
||||
|
||||
# (1 or 1) == 2 -> False
|
||||
# 1 or (1 == 2) -> True <- we want this one
|
||||
self.assertCalcEqual(True, [1, 'or', 1, '==', 2])
|
||||
|
||||
self.assertCalcEqual(True, [True, '==', True, 'or', True, '==', False])
|
||||
|
||||
self.assertEqual("(or (and (== (literal 1) (literal 2)) (literal 3)) (literal 4))",
|
||||
repr(IfParser([1, '==', 2, 'and', 3, 'or', 4]).parse()))
|
|
@ -24,6 +24,7 @@ from context import context_tests
|
|||
from custom import custom_filters
|
||||
from parser import filter_parsing, variable_parsing
|
||||
from unicode import unicode_tests
|
||||
from smartif import *
|
||||
|
||||
try:
|
||||
from loaders import *
|
||||
|
@ -534,6 +535,27 @@ class Templates(unittest.TestCase):
|
|||
'if-tag02': ("{% if foo %}yes{% else %}no{% endif %}", {"foo": False}, "no"),
|
||||
'if-tag03': ("{% if foo %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
|
||||
# Filters
|
||||
'if-tag-filter01': ("{% if foo|length == 5 %}yes{% else %}no{% endif %}", {'foo': 'abcde'}, "yes"),
|
||||
'if-tag-filter02': ("{% if foo|upper == 'ABC' %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
|
||||
# Equality
|
||||
'if-tag-eq01': ("{% if foo == bar %}yes{% else %}no{% endif %}", {}, "yes"),
|
||||
'if-tag-eq02': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1}, "no"),
|
||||
'if-tag-eq03': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 1}, "yes"),
|
||||
'if-tag-eq04': ("{% if foo == bar %}yes{% else %}no{% endif %}", {'foo': 1, 'bar': 2}, "no"),
|
||||
'if-tag-eq05': ("{% if foo == '' %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
|
||||
# Comparison
|
||||
'if-tag-gt-01': ("{% if 2 > 1 %}yes{% else %}no{% endif %}", {}, "yes"),
|
||||
'if-tag-gt-02': ("{% if 1 > 1 %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
'if-tag-gte-01': ("{% if 1 >= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
|
||||
'if-tag-gte-02': ("{% if 1 >= 2 %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
'if-tag-lt-01': ("{% if 1 < 2 %}yes{% else %}no{% endif %}", {}, "yes"),
|
||||
'if-tag-lt-02': ("{% if 1 < 1 %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
'if-tag-lte-01': ("{% if 1 <= 1 %}yes{% else %}no{% endif %}", {}, "yes"),
|
||||
'if-tag-lte-02': ("{% if 2 <= 1 %}yes{% else %}no{% endif %}", {}, "no"),
|
||||
|
||||
# AND
|
||||
'if-tag-and01': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'yes'),
|
||||
'if-tag-and02': ("{% if foo and bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': False}, 'no'),
|
||||
|
@ -554,14 +576,13 @@ class Templates(unittest.TestCase):
|
|||
'if-tag-or07': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'foo': True}, 'yes'),
|
||||
'if-tag-or08': ("{% if foo or bar %}yes{% else %}no{% endif %}", {'bar': True}, 'yes'),
|
||||
|
||||
# TODO: multiple ORs
|
||||
# multiple ORs
|
||||
'if-tag-or09': ("{% if foo or bar or baz %}yes{% else %}no{% endif %}", {'baz': True}, 'yes'),
|
||||
|
||||
# NOT
|
||||
'if-tag-not01': ("{% if not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'yes'),
|
||||
'if-tag-not02': ("{% if not %}yes{% else %}no{% endif %}", {'foo': True}, 'no'),
|
||||
'if-tag-not03': ("{% if not %}yes{% else %}no{% endif %}", {'not': True}, 'yes'),
|
||||
'if-tag-not04': ("{% if not not %}no{% else %}yes{% endif %}", {'not': True}, 'yes'),
|
||||
'if-tag-not05': ("{% if not not %}no{% else %}yes{% endif %}", {}, 'no'),
|
||||
'if-tag-not02': ("{% if not not foo %}no{% else %}yes{% endif %}", {'foo': True}, 'no'),
|
||||
# not03 to not05 removed, now TemplateSyntaxErrors
|
||||
|
||||
'if-tag-not06': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {}, 'no'),
|
||||
'if-tag-not07': ("{% if foo and not bar %}yes{% else %}no{% endif %}", {'foo': True, 'bar': True}, 'no'),
|
||||
|
@ -599,12 +620,21 @@ class Templates(unittest.TestCase):
|
|||
'if-tag-not34': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': True}, 'yes'),
|
||||
'if-tag-not35': ("{% if not foo or not bar %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, 'yes'),
|
||||
|
||||
# AND and OR raises a TemplateSyntaxError
|
||||
'if-tag-error01': ("{% if foo or bar and baz %}yes{% else %}no{% endif %}", {'foo': False, 'bar': False}, template.TemplateSyntaxError),
|
||||
# Various syntax errors
|
||||
'if-tag-error01': ("{% if %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error02': ("{% if foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
|
||||
'if-tag-error03': ("{% if foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
|
||||
'if-tag-error04': ("{% if not foo and %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
|
||||
'if-tag-error05': ("{% if not foo or %}yes{% else %}no{% endif %}", {'foo': True}, template.TemplateSyntaxError),
|
||||
'if-tag-error06': ("{% if abc def %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error07': ("{% if not %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error08': ("{% if and %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error09': ("{% if or %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error10': ("{% if == %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error11': ("{% if 1 == %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
'if-tag-error12': ("{% if a not b %}yes{% endif %}", {}, template.TemplateSyntaxError),
|
||||
|
||||
# Additional, more precise parsing tests are in SmartIfTests
|
||||
|
||||
### IFCHANGED TAG #########################################################
|
||||
'ifchanged01': ('{% for n in num %}{% ifchanged %}{{ n }}{% endifchanged %}{% endfor %}', {'num': (1,2,3)}, '123'),
|
||||
|
|
Loading…
Reference in New Issue