From 8cf99c2f712df6aebf04cfce3f3358092a6e5e42 Mon Sep 17 00:00:00 2001
From: Paul Marillonnet <pmarillonnet@entrouvert.com>
Date: Thu, 20 Feb 2020 11:15:03 +0100
Subject: [PATCH 3/4] add a template module, copycatting wcs.qommon.template
 (#37884)

---
 src/authentic2/utils/template.py |  56 +++++++++++++++
 tests/test_template.py           | 113 +++++++++++++++++++++++++++++++
 2 files changed, 169 insertions(+)
 create mode 100644 src/authentic2/utils/template.py
 create mode 100644 tests/test_template.py

diff --git a/src/authentic2/utils/template.py b/src/authentic2/utils/template.py
new file mode 100644
index 000000000..5492ca323
--- /dev/null
+++ b/src/authentic2/utils/template.py
@@ -0,0 +1,56 @@
+# authentic2 - versatile identity manager
+# Copyright (C) 2010-2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.template import VariableDoesNotExist
+from django.template import engines
+from django.template import TemplateSyntaxError
+from django.utils.encoding import force_str
+from django.utils.translation import ugettext_lazy as _
+
+
+class TemplateError(Exception):
+    pass
+
+
+class Template(object):
+    def __init__(self, value, raises=False):
+        self.value = value
+        self.raises = raises
+
+        try:
+            self.template = engines['django'].from_string(value)
+        except TemplateSyntaxError as e:
+            if self.raises:
+                raise TemplateError(_('template syntax error: %s') % e)
+
+    def render(self, context={}):
+        if not hasattr(self, 'template'):
+            # oops, silent error during initialization, let's get outta here
+            return force_str(self.value)
+        try:
+            rendered = self.template.render(context)
+        except TemplateSyntaxError as e:
+            if self.raises:
+                raise TemplateError(_('template syntax error: %s') % e)
+            return force_str(self.value)
+        except VariableDoesNotExist as e:
+            if self.raises:
+                raise TemplateError(_('missing template variable: %s') % e)
+            return force_str(self.value)
+        return force_str(rendered)
+
+    def null_render(self, context={}):
+        return str(self.value)
diff --git a/tests/test_template.py b/tests/test_template.py
new file mode 100644
index 000000000..c91a27c4f
--- /dev/null
+++ b/tests/test_template.py
@@ -0,0 +1,113 @@
+# authentic2 - versatile identity manager
+# Copyright (C) 2010-2020 Entr'ouvert
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Affero General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import pytest
+
+from authentic2.utils.template import Template, TemplateError
+
+pytestmark = pytest.mark.django_db
+
+
+def test_render_template():
+    # 1.1. test a simple conditional
+    value = '{% if foo %}John{% else %}Jim{% endif %}'
+    template = Template(value=value)
+
+    context = {'foo': True}
+    assert template.render(context=context) == 'John'
+
+    context = {'foo': False}
+    assert template.render(context=context) == 'Jim'
+
+    context = {}
+    assert template.render(context=context) == 'Jim'
+
+    # 1.2. test comparison operators
+    value = '{% if foo > bar %}Foo{% else %}Bar{% endif %}'
+    template = Template(value=value)
+
+    context = {'foo': 3, 'bar': 2}
+    assert template.render(context=context) == 'Foo'
+
+    context = {'foo': 3, 'bar': 9}
+    assert template.render(context=context) == 'Bar'
+
+    # 1.3. test set operator
+    value = '{% if foo in bar %}Found{% else %}Missing{% endif %}'
+    template = Template(value=value)
+    context = {
+        'foo': 'john',
+        'bar': ['miles', 'john', 'red', 'paul', 'philly joe']
+    }
+    assert template.render(context=context) == 'Found'
+
+    context['bar'] = ['miles', 'wayne', 'herbie', 'ron', 'tony']
+    assert template.render(context=context) == 'Missing'
+
+    # 2. test 'add' builtin filter
+    tmpl1 = Template(value='{{ base|add:suffix }}')
+    tmpl2 = Template(value='{{ prefix|add:base }}')
+    context = {
+        'prefix': 'Mister ',
+        'base': 'Tony',
+        'suffix': ' Jr.',
+    }
+
+    assert tmpl1.render(context=context) == 'Tony Jr.'
+    assert tmpl2.render(context=context) == 'Mister Tony'
+
+    # 3. test 'with' tag
+    value = '{% with name=user.name age=user.age %}{{ name }}: {{ age }}{% endwith %}'
+    template = Template(value=value)
+
+    context = {'user': {'name': 'Robert', 'age': 39 }}
+    assert template.render(context=context) == 'Robert: 39'
+
+    # 4. test 'firstof' tag
+    value = '{% firstof name surname nickname %}'
+    template = Template(value=value)
+
+    context = {'surname': 'Smith', 'nickname': 'Mitch'}
+    assert template.render(context=context) == 'Smith'
+
+    context.pop('surname')
+    assert template.render(context=context) == 'Mitch'
+
+
+def test_render_template_syntax_error():
+    value = '{% if foo %}John{% else %}Jim{% endif %' # oops
+    template = Template(value=value)
+
+    context = {'foo': True}
+    assert template.render(context=context) == value
+
+    with pytest.raises(TemplateError) as raised:
+        template = Template(value=value, raises=True)
+        assert 'template syntax error' in raised
+
+
+def test_render_template_missing_variable():
+    value = '{{ foo|add:bar }}'
+    template = Template(value=value)
+
+    context = {'foo': True}
+    assert template.render(context=context) == value
+
+    # missing variable errors happen at render time with a particular context
+    template = Template(value=value, raises=True)
+    with pytest.raises(TemplateError) as raised:
+        template.render(context=context)
+        assert 'missing template variable' in raised
-- 
2.25.1

