diff --git a/django/template/smartif.py b/django/template/smartif.py index 718c457c60..b5ae510b0f 100644 --- a/django/template/smartif.py +++ b/django/template/smartif.py @@ -97,6 +97,7 @@ OPERATORS = { 'in': infix(9, lambda context, x, y: x.eval(context) in y.eval(context)), 'not in': infix(9, lambda context, x, y: x.eval(context) not in y.eval(context)), 'is': infix(10, lambda context, x, y: x.eval(context) is y.eval(context)), + 'is not': infix(10, lambda context, x, y: x.eval(context) is not y.eval(context)), '==': infix(10, lambda context, x, y: x.eval(context) == y.eval(context)), '!=': infix(10, lambda context, x, y: x.eval(context) != y.eval(context)), '>': infix(10, lambda context, x, y: x.eval(context) > y.eval(context)), @@ -149,13 +150,16 @@ class IfParser(object): error_class = ValueError def __init__(self, tokens): - # pre-pass necessary to turn 'not','in' into single token + # Turn 'is','not' and 'not','in' into single tokens. l = len(tokens) mapped_tokens = [] i = 0 while i < l: token = tokens[i] - if token == "not" and i + 1 < l and tokens[i + 1] == "in": + if token == "is" and i + 1 < l and tokens[i + 1] == "not": + token = "is not" + i += 1 # skip 'not' + elif token == "not" and i + 1 < l and tokens[i + 1] == "in": token = "not in" i += 1 # skip 'in' mapped_tokens.append(self.translate_token(token)) diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt index 37abc62608..e11646f6bb 100644 --- a/docs/ref/templates/builtins.txt +++ b/docs/ref/templates/builtins.txt @@ -432,7 +432,8 @@ Use of actual parentheses in the :ttag:`if` tag is invalid syntax. If you need them to indicate precedence, you should use nested :ttag:`if` tags. :ttag:`if` tags may also use the operators ``==``, ``!=``, ``<``, ``>``, -``<=``, ``>=``, ``in``, and ``is`` which work as follows: +``<=``, ``>=``, ``in``, ``not in``, ``is``, and ``is not`` which work as +follows: ``==`` operator ^^^^^^^^^^^^^^^ @@ -526,6 +527,18 @@ Object identity. Tests if two values are the same object. Example:: This will output if and only if value is None. {% endif %} +``is not`` operator +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 1.10 + +Tests if two values are not the same object. This is the negation of +the ``is`` operator. Example:: + + {% if value is not None %} + This will output if and only if value is not None. + {% endif %} + Filters ~~~~~~~ diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index adae2524da..2002905b2d 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -409,7 +409,7 @@ Templates :class:`~django.template.backends.django.DjangoTemplates` backend and the :class:`~django.template.Engine` class. -* Added the ``is`` comparison operator to the :ttag:`if` tag. +* Added the ``is`` and ``is not`` comparison operators to the :ttag:`if` tag. * Allowed :tfilter:`dictsort` to order a list of lists by an element at a specified index. diff --git a/tests/template_tests/syntax_tests/test_if.py b/tests/template_tests/syntax_tests/test_if.py index 507a26b0b7..c4b5c53250 100644 --- a/tests/template_tests/syntax_tests/test_if.py +++ b/tests/template_tests/syntax_tests/test_if.py @@ -537,3 +537,15 @@ class IfTagTests(SimpleTestCase): def test_if_is_no_match(self): output = self.engine.render_to_string('template', {'foo': 1}) self.assertEqual(output, 'no') + + @setup({'template': '{% if foo is not None %}yes{% else %}no{% endif %}'}) + def test_if_is_not_match(self): + # For this to act as a regression test, it's important not to use + # foo=True because True is (not None) + output = self.engine.render_to_string('template', {'foo': False}) + self.assertEqual(output, 'yes') + + @setup({'template': '{% if foo is not None %}yes{% else %}no{% endif %}'}) + def test_if_is_not_no_match(self): + output = self.engine.render_to_string('template', {'foo': None}) + self.assertEqual(output, 'no')