[#8288] remove remaining genshi references
diff --git a/Allura/allura/config/app_cfg.py b/Allura/allura/config/app_cfg.py
index abd4076..cd4314f 100644
--- a/Allura/allura/config/app_cfg.py
+++ b/Allura/allura/config/app_cfg.py
@@ -55,7 +55,7 @@
     def __init__(self, root_controller=None):
         AppConfig.__init__(self, minimal=True, root_controller=root_controller)
         self.package = allura
-        self.renderers = ['json', 'genshi', 'mako', 'jinja']
+        self.renderers = ['json', 'mako', 'jinja']
         self.default_renderer = 'jinja'
         self.register_rendering_engine(AlluraJinjaRenderer)
         self.use_sqlalchemy = False
diff --git a/Allura/allura/lib/custom_middleware.py b/Allura/allura/lib/custom_middleware.py
index 4c7c530..7764588 100644
--- a/Allura/allura/lib/custom_middleware.py
+++ b/Allura/allura/lib/custom_middleware.py
@@ -289,7 +289,6 @@
 class AlluraTimerMiddleware(TimerMiddleware):
 
     def timers(self):
-        import genshi
         import jinja2
         import markdown
         import ming
@@ -329,7 +328,6 @@
             Timer('mongo', pymongo.cursor.Cursor, 'count', 'distinct',
                   '_refresh'),
             # urlopen and socket io may or may not overlap partially
-            Timer('render', genshi.Stream, 'render'),
             Timer('repo.Blob.{method_name}', allura.model.repository.Blob, '*'),
             Timer('repo.Commit.{method_name}', allura.model.repository.Commit, '*'),
             Timer('repo.LastCommit.{method_name}',
@@ -340,8 +338,6 @@
             Timer('socket_write', socket._fileobject, 'write', 'writelines',
                   'flush', debug_each_call=False),
             Timer('solr', pysolr.Solr, 'add', 'delete', 'search', 'commit'),
-            Timer('template', genshi.template.Template, '_prepare', '_parse',
-                  'generate'),
             Timer('urlopen', urllib2, 'urlopen'),
             Timer('base_repo_tool.{method_name}',
                   allura.model.repository.RepositoryImplementation, 'last_commit_ids'),
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 5b2fe56..7fdfacc 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -41,7 +41,6 @@
 
 import emoji
 import tg
-import genshi.template
 try:
     import cchardet as chardet
 except ImportError:
@@ -578,20 +577,6 @@
             setattr(self, k, v)
 
 
-def render_genshi_plaintext(template_name, **template_vars):
-    assert os.path.exists(template_name)
-    fd = open(template_name)
-    try:
-        tpl_text = fd.read()
-    finally:
-        fd.close()
-    filepath = os.path.dirname(template_name)
-    tt = genshi.template.NewTextTemplate(tpl_text,
-                                         filepath=filepath, filename=template_name)
-    stream = tt.generate(**template_vars)
-    return stream.render(encoding='utf-8').decode('utf-8')
-
-
 @tg.expose(content_type='text/plain')
 def json_validation_error(controller, **kwargs):
     exc = request.validation['exception']
diff --git a/Allura/allura/tests/data/genshi_hello_tmpl b/Allura/allura/tests/data/genshi_hello_tmpl
deleted file mode 100644
index 5dde161..0000000
--- a/Allura/allura/tests/data/genshi_hello_tmpl
+++ /dev/null
@@ -1 +0,0 @@
-Hello, $object!
diff --git a/Allura/allura/tests/test_helpers.py b/Allura/allura/tests/test_helpers.py
index bada67c..3abde3b 100644
--- a/Allura/allura/tests/test_helpers.py
+++ b/Allura/allura/tests/test_helpers.py
@@ -99,13 +99,6 @@
     assert isinstance(s, unicode)
 
 
-def test_render_genshi_plaintext():
-    here_dir = path.dirname(__file__)
-    tpl = path.join(here_dir, 'data/genshi_hello_tmpl')
-    text = h.render_genshi_plaintext(tpl, object='world')
-    eq_(u'Hello, world!\n', text)
-
-
 def test_find_project():
     proj, rest = h.find_project('/p/test/foo')
     assert_equals(proj.shortname, 'test')
diff --git a/Allura/setup.py b/Allura/setup.py
index 81133c9..041dd1a 100644
--- a/Allura/setup.py
+++ b/Allura/setup.py
@@ -71,7 +71,7 @@
                              ]},
     message_extractors={'allura': [
         ('**.py', 'python', None),
-        ('templates/**.html', 'genshi', None),
+        ('templates/**.html', 'jinja2', None),
         ('public/**', 'ignore', None)]},
 
     # These entry points define what tools and plugins are available for Allura.
diff --git a/ForgeTracker/forgetracker/data/ticket_changed.html b/ForgeTracker/forgetracker/data/ticket_changed.html
new file mode 100644
index 0000000..668b08d
--- /dev/null
+++ b/ForgeTracker/forgetracker/data/ticket_changed.html
@@ -0,0 +1,45 @@
+{#
+       Licensed to the Apache Software Foundation (ASF) under one
+       or more contributor license agreements.  See the NOTICE file
+       distributed with this work for additional information
+       regarding copyright ownership.  The ASF licenses this file
+       to you under the Apache License, Version 2.0 (the
+       "License"); you may not use this file except in compliance
+       with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing,
+       software distributed under the License is distributed on an
+       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+       KIND, either express or implied.  See the License for the
+       specific language governing permissions and limitations
+       under the License.
+#}
+{%- for key, values in changelist -%}
+{% if key in ['description', 'attachments'] -%}
+- {{ key.capitalize() }} has changed:
+
+Diff:
+
+~~~~
+
+{{ h.unidiff(values[0], values[1]) }}
+
+~~~~
+
+{% else -%}
+{% set oldv, newv = values -%}
+{% if key == 'assigned_to' or oldv.type_s == 'User' or newv.type_s == 'User' -%}
+- **{{key}}**: {% if oldv %}{{oldv.get_pref('display_name')}} --> {% endif %}{% if newv %}{{newv.get_pref('display_name')}}{% else %} nobody {% endif %}
+{% elif key == 'labels' -%}
+- **{{key}}**: {{', '.join(oldv)}} --> {{', '.join(newv)}}
+{% else -%}
+- **{{key}}**: {{oldv if oldv is not none else ''}} --> {{newv if newv is not none else ''}}
+{% endif -%}
+{% endif -%}
+{% endfor -%}
+{% if comment %}- **Comment**:
+
+{{ comment }}
+{% endif %}
\ No newline at end of file
diff --git a/ForgeTracker/forgetracker/data/ticket_changed_tmpl b/ForgeTracker/forgetracker/data/ticket_changed_tmpl
deleted file mode 100644
index c04d92d..0000000
--- a/ForgeTracker/forgetracker/data/ticket_changed_tmpl
+++ /dev/null
@@ -1,60 +0,0 @@
-{#
-       Licensed to the Apache Software Foundation (ASF) under one
-       or more contributor license agreements.  See the NOTICE file
-       distributed with this work for additional information
-       regarding copyright ownership.  The ASF licenses this file
-       to you under the Apache License, Version 2.0 (the
-       "License"); you may not use this file except in compliance
-       with the License.  You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-       Unless required by applicable law or agreed to in writing,
-       software distributed under the License is distributed on an
-       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-       KIND, either express or implied.  See the License for the
-       specific language governing permissions and limitations
-       under the License.
-#}\
-{% python import difflib %}\
-{% python from allura.model import User %}\
-{% for key, values in changelist %}\
-{% choose %}\
-{% when key in ['description', 'attachments'] %}\
-- ${key.capitalize()} has changed:
-
-Diff:
-
-~~~~
-
-${'\n'.join(difflib.unified_diff(
-                            a=values[0].splitlines(),
-                            b=values[1].splitlines(),
-                            fromfile='old',
-                            tofile='new',
-                            lineterm=''))}
-
-~~~~
-
-{% end %}\
-{% otherwise %}\
-{% with oldv, newv = values %}\
-{% choose %}\
-{% when key == 'assigned_to' or isinstance(oldv, User) or isinstance(newv, User) %}\
-- **${key}**: {% if oldv %}${oldv.get_pref('display_name')} --> {% end %}{% choose %}{% when newv %}${newv.get_pref('display_name')}{% end %}{% otherwise %} nobody {% end %}{% end %}
-{% end %}\
-{% when key == 'labels' %}\
-- **${key}**: ${', '.join(oldv)} --> ${', '.join(newv)}
-{% end %}\
-{% otherwise %}\
-- **${key}**: ${oldv} --> ${newv}
-{% end %}\
-{% end %}\
-{% end %}\
-{% end %}\
-{% end %}\
-{% end %}\
-{% if comment %}- **Comment**:
-
-${comment}
-{% end %}\
diff --git a/ForgeTracker/forgetracker/tests/functional/test_root.py b/ForgeTracker/forgetracker/tests/functional/test_root.py
index 5cdb2b1..8790c7a 100644
--- a/ForgeTracker/forgetracker/tests/functional/test_root.py
+++ b/ForgeTracker/forgetracker/tests/functional/test_root.py
@@ -496,6 +496,7 @@
         })
         M.MonQTask.run_ready()
         r = self.app.get('/p/test/bugs/1/')
+        r.mustcontain('<li><strong>Major</strong>: True --&gt; False</li>')
         assert '<li><strong>Major</strong>: True --&gt; False</li>' in r
         r = self.app.get('/p/test/bugs/2/')
         assert '<li><strong>Major</strong>: --&gt; False</li>' in r
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index d2c567e..a7a1c74 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -147,10 +147,8 @@
         new_value = ', '.join(new_value)
     changes[name] = old_value
     changes[name] = new_value
-    tpl_fn = pkg_resources.resource_filename(
-        'forgetracker', 'data/ticket_changed_tmpl')
-    return h.render_genshi_plaintext(
-        tpl_fn,
+    tmpl = g.jinja2_env.get_template('forgetracker:data/ticket_changed.html')
+    return tmpl.render(
         changelist=changes.get_changed(),
         comment=None)
 
@@ -171,14 +169,10 @@
 
     Returns tuple (post_text, notification_text)
     """
-    template = pkg_resources.resource_filename(
-        'forgetracker', 'data/ticket_changed_tmpl')
-    render = partial(
-        h.render_genshi_plaintext,
-        template,
-        changelist=changes.get_changed())
-    post_text = render(comment=None)
-    notification_text = render(comment=comment) if comment else None
+    tmpl = g.jinja2_env.get_template('forgetracker:data/ticket_changed.html')
+    changelist = changes.get_changed()
+    post_text = tmpl.render(h=h, changelist=changelist, comment=None)
+    notification_text = tmpl.render(h=h, changelist=changelist, comment=comment) if comment else None
     return post_text, notification_text
 
 
diff --git a/rat-excludes.txt b/rat-excludes.txt
index d518c83..cce8487 100644
--- a/rat-excludes.txt
+++ b/rat-excludes.txt
@@ -59,7 +59,6 @@
 Allura/allura/public/nf/js/Sortable.min.js
 Allura/allura/public/nf/js/twemoji.min.js
 Allura/allura/public/nf/js/underscore-min.js
-Allura/allura/tests/data/genshi_hello_tmpl
 Allura/allura/tests/data/test_mime/text_file.txt
 Allura/docs/make.bat
 Allura/docs/Makefile