]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
allow optional use of filter based on existence 1248/head
authorAmy <leiamy12@gmail.com>
Fri, 26 Jun 2020 19:26:04 +0000 (15:26 -0400)
committerAmy <leiamy12@gmail.com>
Sun, 4 Apr 2021 17:09:31 +0000 (13:09 -0400)
CHANGES.rst
src/jinja2/compiler.py
src/jinja2/runtime.py
tests/test_filters.py

index 26c928b0db40f077a4877eb3c678d406d508271b..4516d4229b8ea6bbcca95e640ce2e304c0625aa9 100644 (file)
@@ -39,6 +39,8 @@ Unreleased
 -   Update the template globals when calling
     ``Environment.get_template(globals=...)`` even if the template was
     already loaded. :issue:`295`
+-   Do not raise an error for undefined filters in unexecuted
+    if-statements and conditional expressions. :issue:`842`
 
 
 Version 2.11.3
index 50f98f1a9c064e3f270eed5ae547b1138584bce0..e3bde168e61b987c406c449e0e1f04e060eaf80c 100644 (file)
@@ -136,6 +136,11 @@ class Frame:
         self.loop_frame = False
         self.block_frame = False
 
+        # track whether the frame is being used in an if-statement or conditional
+        # expression as it determines which errors should be raised during runtime
+        # or compile time.
+        self.soft_frame = False
+
     def copy(self):
         """Create a copy of the current one."""
         rv = object.__new__(self.__class__)
@@ -154,10 +159,12 @@ class Frame:
         standalone thing as it shares the resources with the frame it
         was created of, but it's not a rootlevel frame any longer.
 
-        This is only used to implement if-statements.
+        This is only used to implement if-statements and conditional
+        expressions.
         """
         rv = self.copy()
         rv.rootlevel = False
+        rv.soft_frame = True
         return rv
 
     __copy__ = copy
@@ -442,7 +449,22 @@ class CodeGenerator(NodeVisitor):
             for name in getattr(visitor, dependency):
                 if name not in mapping:
                     mapping[name] = self.temporary_identifier()
+                # add check during runtime that dependencies used inside of executed
+                # blocks are defined, as this step may be skipped during compile time
+                self.writeline("try:")
+                self.indent()
                 self.writeline(f"{mapping[name]} = environment.{dependency}[{name!r}]")
+                self.outdent()
+                self.writeline("except KeyError:")
+                self.indent()
+                self.writeline("@internalcode")
+                self.writeline(f"def {mapping[name]}(*unused):")
+                self.indent()
+                self.writeline(
+                    f'raise TemplateRuntimeError("no filter named {name!r} found")'
+                )
+                self.outdent()
+                self.outdent()
 
     def enter_frame(self, frame):
         undefs = []
@@ -1641,7 +1663,7 @@ class CodeGenerator(NodeVisitor):
             self.write("await auto_await(")
         self.write(self.filters[node.name] + "(")
         func = self.environment.filters.get(node.name)
-        if func is None:
+        if func is None and not frame.soft_frame:
             self.fail(f"no filter named {node.name!r}", node.lineno)
         if getattr(func, "contextfilter", False) is True:
             self.write("context, ")
@@ -1679,6 +1701,8 @@ class CodeGenerator(NodeVisitor):
 
     @optimizeconst
     def visit_CondExpr(self, node, frame):
+        frame = frame.soft()
+
         def write_expr2():
             if node.expr2 is not None:
                 return self.visit(node.expr2, frame)
index 4a3c36eeaf5a480b824e6c01f4ac6034e4d9e65a..9468a3739b5b1729807d908bd3073d431e6da903 100644 (file)
@@ -36,6 +36,7 @@ exported = [
     "TemplateNotFound",
     "Namespace",
     "Undefined",
+    "internalcode",
 ]
 
 
index efc82bcf81f9cab3e5ecc58f51ed2d05b1e2405a..44be6ad21407add2e9d4cb74de80b63999750b9b 100644 (file)
@@ -6,7 +6,9 @@ import pytest
 from jinja2 import Environment
 from jinja2 import Markup
 from jinja2 import StrictUndefined
+from jinja2 import TemplateRuntimeError
 from jinja2 import UndefinedError
+from jinja2.exceptions import TemplateAssertionError
 
 
 class Magic:
@@ -774,3 +776,49 @@ class TestFilter:
         t = env.from_string("{{ s|wordwrap(20) }}")
         result = t.render(s="Hello!\nThis is Jinja saying something.")
         assert result == "Hello!\nThis is Jinja saying\nsomething."
+
+    def test_filter_undefined(self, env):
+        with pytest.raises(TemplateAssertionError, match="no filter named 'f'"):
+            env.from_string("{{ var|f }}")
+
+    def test_filter_undefined_in_if(self, env):
+        t = env.from_string("{%- if x is defined -%}{{ x|f }}{%- else -%}x{% endif %}")
+        assert t.render() == "x"
+        with pytest.raises(TemplateRuntimeError, match="no filter named 'f'"):
+            t.render(x=42)
+
+    def test_filter_undefined_in_elif(self, env):
+        t = env.from_string(
+            "{%- if x is defined -%}{{ x }}{%- elif y is defined -%}"
+            "{{ y|f }}{%- else -%}foo{%- endif -%}"
+        )
+        assert t.render() == "foo"
+        with pytest.raises(TemplateRuntimeError, match="no filter named 'f'"):
+            t.render(y=42)
+
+    def test_filter_undefined_in_else(self, env):
+        t = env.from_string(
+            "{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}"
+        )
+        assert t.render() == "foo"
+        with pytest.raises(TemplateRuntimeError, match="no filter named 'f'"):
+            t.render(x=42)
+
+    def test_filter_undefined_in_nested_if(self, env):
+        t = env.from_string(
+            "{%- if x is not defined -%}foo{%- else -%}{%- if y "
+            "is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}"
+        )
+        assert t.render() == "foo"
+        assert t.render(x=42) == "42"
+        with pytest.raises(TemplateRuntimeError, match="no filter named 'f'"):
+            t.render(x=24, y=42)
+
+    def test_filter_undefined_in_condexpr(self, env):
+        t1 = env.from_string("{{ x|f if x is defined else 'foo' }}")
+        t2 = env.from_string("{{ 'foo' if x is not defined else x|f }}")
+        assert t1.render() == t2.render() == "foo"
+
+        with pytest.raises(TemplateRuntimeError, match="no filter named 'f'"):
+            t1.render(x=42)
+            t2.render(x=42)