Skip to content

Commit 7b0ad06

Browse files
committed
New Chameleon templating engine, engine is now configurable.
We now have two configurable templating engines, the old Zope TAL templates (called zopetal in the config) and the new Chameleon (called chameleon in the config). A new config-option "template_engine" under [main] can take these config-options, the default is zopetal. Thanks to Cheer Xiao for the idea of making this configurable *and* for the actual implementation! Cheer Xiao commit log: - The original TAL engine ported from Zope is thereafter referred to as "zopetal", in speech and in code - A new option "template_engine" under [main] introduced - Zopetal-specific code stripped from cgi/templating.py to form the new cgi/engine_zopetal.py - Interface to Chameleon in cgi/engine_chameleon.py - Engines are supposed to provide a Templates class that mimics the behavior of the old cgi.templating.Templates. The Templates class is preferably subclassed from cgi.templating.TemplatesBase. - New function cgi.templating.get_templates to get the appropriate engine's Templates instance according to the engine name
1 parent f4400f7 commit 7b0ad06

File tree

6 files changed

+178
-106
lines changed

6 files changed

+178
-106
lines changed

CHANGES.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ Entries without name were done by Richard Jones.
77

88
Features:
99

10+
- We now have two configurable templating engines, the old Zope TAL
11+
templates (called zopetal in the config) and the new Chameleon (called
12+
chameleon in the config). A new config-option "template_engine" under
13+
[main] can take these config-options, the default is zopetal.
14+
Thanks to Cheer Xiao for the idea of making this configurable *and*
15+
for the actual implementation!
1016
- issue2550678: Allow pagesize=-1 which returns all results.
1117
Suggested and implemented by John Kristensen.
1218
Tested by Satchidanand Haridas. (Bernhard)

roundup/cgi/engine_chameleon.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""Templating engine adapter for the Chameleon."""
2+
3+
__docformat__ = 'restructuredtext'
4+
5+
import os.path
6+
from chameleon import PageTemplateLoader
7+
8+
from roundup.cgi.templating import StringIO, context, find_template, TemplatesBase
9+
10+
class Templates(TemplatesBase):
11+
def __init__(self, dir):
12+
self.dir = dir
13+
self.loader = PageTemplateLoader(dir)
14+
15+
def get(self, name, extension=None):
16+
# default the name to "home"
17+
if name is None:
18+
name = 'home'
19+
elif extension is None and '.' in name:
20+
# split name
21+
name, extension = name.split('.')
22+
23+
src, filename = find_template(self.dir, name, extension)
24+
return RoundupPageTemplate(self.loader.load(src))
25+
26+
class RoundupPageTemplate():
27+
def __init__(self, pt):
28+
self._pt = pt
29+
30+
def render(self, client, classname, request, **options):
31+
c = context(client, self, classname, request)
32+
c.update({'options': options})
33+
34+
def translate(msgid, domain=None, mapping=None, default=None):
35+
result = client.translator.translate(domain, msgid,
36+
mapping=mapping, default=default)
37+
return unicode(result, client.translator.OUTPUT_ENCODING)
38+
39+
output = self._pt.render(None, translate, None, **c)
40+
return output.encode(client.charset)
41+
42+
def __getitem__(self, name):
43+
return self._pt[name]
44+
45+
def __getattr__(self, name):
46+
return getattr(self._pt, name)
47+

roundup/cgi/engine_zopetal.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""Templating engine adapter for the legacy TAL implementation ported from
2+
Zope.
3+
"""
4+
__docformat__ = 'restructuredtext'
5+
6+
import errno
7+
import mimetypes
8+
import os
9+
import os.path
10+
11+
from roundup.cgi.templating import StringIO, context, translationService, find_template, TemplatesBase
12+
from roundup.cgi.PageTemplates import PageTemplate, GlobalTranslationService
13+
from roundup.cgi.PageTemplates.Expressions import getEngine
14+
from roundup.cgi.TAL import TALInterpreter
15+
16+
GlobalTranslationService.setGlobalTranslationService(translationService)
17+
18+
class Templates(TemplatesBase):
19+
templates = {}
20+
21+
def __init__(self, dir):
22+
self.dir = dir
23+
24+
def get(self, name, extension=None):
25+
""" Interface to get a template, possibly loading a compiled template.
26+
27+
"name" and "extension" indicate the template we're after, which in
28+
most cases will be "name.extension". If "extension" is None, then
29+
we look for a template just called "name" with no extension.
30+
31+
If the file "name.extension" doesn't exist, we look for
32+
"_generic.extension" as a fallback.
33+
"""
34+
# default the name to "home"
35+
if name is None:
36+
name = 'home'
37+
elif extension is None and '.' in name:
38+
# split name
39+
name, extension = name.split('.')
40+
41+
# find the source
42+
src, filename = find_template(self.dir, name, extension)
43+
44+
# has it changed?
45+
try:
46+
stime = os.stat(src)[os.path.stat.ST_MTIME]
47+
except os.error, error:
48+
if error.errno != errno.ENOENT:
49+
raise
50+
51+
if self.templates.has_key(src) and \
52+
stime <= self.templates[src].mtime:
53+
# compiled template is up to date
54+
return self.templates[src]
55+
56+
# compile the template
57+
pt = RoundupPageTemplate()
58+
# use pt_edit so we can pass the content_type guess too
59+
content_type = mimetypes.guess_type(filename)[0] or 'text/html'
60+
pt.pt_edit(open(src).read(), content_type)
61+
pt.id = filename
62+
pt.mtime = stime
63+
# Add it to the cache. We cannot do this until the template
64+
# is fully initialized, as we could otherwise have a race
65+
# condition when running with multiple threads:
66+
#
67+
# 1. Thread A notices the template is not in the cache,
68+
# adds it, but has not yet set "mtime".
69+
#
70+
# 2. Thread B notices the template is in the cache, checks
71+
# "mtime" (above) and crashes.
72+
#
73+
# Since Python dictionary access is atomic, as long as we
74+
# insert "pt" only after it is fully initialized, we avoid
75+
# this race condition. It's possible that two separate
76+
# threads will both do the work of initializing the template,
77+
# but the risk of wasted work is offset by avoiding a lock.
78+
self.templates[src] = pt
79+
return pt
80+
81+
class RoundupPageTemplate(PageTemplate.PageTemplate):
82+
"""A Roundup-specific PageTemplate.
83+
84+
Interrogate the client to set up Roundup-specific template variables
85+
to be available. See 'context' function for the list of variables.
86+
87+
"""
88+
89+
def render(self, client, classname, request, **options):
90+
"""Render this Page Template"""
91+
92+
if not self._v_cooked:
93+
self._cook()
94+
95+
__traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self)
96+
97+
if self._v_errors:
98+
raise PageTemplate.PTRuntimeError, \
99+
'Page Template %s has errors.'%self.id
100+
101+
# figure the context
102+
c = context(client, self, classname, request)
103+
c.update({'options': options})
104+
105+
# and go
106+
output = StringIO.StringIO()
107+
TALInterpreter.TALInterpreter(self._v_program, self.macros,
108+
getEngine().getContext(c), output, tal=1, strictinsert=0)()
109+
return output.getvalue()
110+

roundup/cgi/templating.py

Lines changed: 10 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
__docformat__ = 'restructuredtext'
2121

2222

23-
import sys, cgi, urllib, os, re, os.path, time, errno, mimetypes, csv
23+
import cgi, urllib, re, os.path, mimetypes, csv
2424
import calendar, textwrap
2525

2626
from roundup import hyperdb, date, support
@@ -50,17 +50,13 @@
5050
ReStructuredText = None
5151

5252
# bring in the templating support
53-
from roundup.cgi.PageTemplates import PageTemplate, GlobalTranslationService
54-
from roundup.cgi.PageTemplates.Expressions import getEngine
55-
from roundup.cgi.TAL import TALInterpreter
5653
from roundup.cgi import TranslationService, ZTUtils
5754

5855
### i18n services
5956
# this global translation service is not thread-safe.
6057
# it is left here for backward compatibility
6158
# until all Web UI translations are done via client.translator object
6259
translationService = TranslationService.get_translation()
63-
GlobalTranslationService.setGlobalTranslationService(translationService)
6460

6561
### templating
6662

@@ -121,12 +117,8 @@ def find_template(dir, name, view):
121117
'with template "%s" (neither "%s" nor "%s")'%(name, view,
122118
filename, generic))
123119

124-
class Templates:
125-
templates = {}
126-
127-
def __init__(self, dir):
128-
self.dir = dir
129-
120+
class TemplatesBase:
121+
"""Base for engine-specific Templates class."""
130122
def precompileTemplates(self):
131123
""" Go through a directory and precompile all the templates therein
132124
"""
@@ -152,63 +144,6 @@ def precompileTemplates(self):
152144
else:
153145
self.get(filename, None)
154146

155-
def get(self, name, extension=None):
156-
""" Interface to get a template, possibly loading a compiled template.
157-
158-
"name" and "extension" indicate the template we're after, which in
159-
most cases will be "name.extension". If "extension" is None, then
160-
we look for a template just called "name" with no extension.
161-
162-
If the file "name.extension" doesn't exist, we look for
163-
"_generic.extension" as a fallback.
164-
"""
165-
# default the name to "home"
166-
if name is None:
167-
name = 'home'
168-
elif extension is None and '.' in name:
169-
# split name
170-
name, extension = name.split('.')
171-
172-
# find the source
173-
src, filename = find_template(self.dir, name, extension)
174-
175-
# has it changed?
176-
try:
177-
stime = os.stat(src)[os.path.stat.ST_MTIME]
178-
except os.error, error:
179-
if error.errno != errno.ENOENT:
180-
raise
181-
182-
if self.templates.has_key(src) and \
183-
stime <= self.templates[src].mtime:
184-
# compiled template is up to date
185-
return self.templates[src]
186-
187-
# compile the template
188-
pt = RoundupPageTemplate()
189-
# use pt_edit so we can pass the content_type guess too
190-
content_type = mimetypes.guess_type(filename)[0] or 'text/html'
191-
pt.pt_edit(open(src).read(), content_type)
192-
pt.id = filename
193-
pt.mtime = stime
194-
# Add it to the cache. We cannot do this until the template
195-
# is fully initialized, as we could otherwise have a race
196-
# condition when running with multiple threads:
197-
#
198-
# 1. Thread A notices the template is not in the cache,
199-
# adds it, but has not yet set "mtime".
200-
#
201-
# 2. Thread B notices the template is in the cache, checks
202-
# "mtime" (above) and crashes.
203-
#
204-
# Since Python dictionary access is atomic, as long as we
205-
# insert "pt" only after it is fully initialized, we avoid
206-
# this race condition. It's possible that two separate
207-
# threads will both do the work of initializing the template,
208-
# but the risk of wasted work is offset by avoiding a lock.
209-
self.templates[src] = pt
210-
return pt
211-
212147
def __getitem__(self, name):
213148
name, extension = os.path.splitext(name)
214149
if extension:
@@ -218,6 +153,13 @@ def __getitem__(self, name):
218153
except NoTemplate, message:
219154
raise KeyError, message
220155

156+
def get_templates(dir, engine_name):
157+
if engine_name == 'chameleon':
158+
import engine_chameleon as engine
159+
else:
160+
import engine_zopetal as engine
161+
return engine.Templates(dir)
162+
221163
def context(client, template=None, classname=None, request=None):
222164
"""Return the rendering context dictionary
223165
@@ -304,43 +246,6 @@ def context(client, template=None, classname=None, request=None):
304246
c['context'] = HTMLClass(client, classname, anonymous=1)
305247
return c
306248

307-
class RoundupPageTemplate(PageTemplate.PageTemplate):
308-
"""A Roundup-specific PageTemplate.
309-
310-
Interrogate the client to set up Roundup-specific template variables
311-
to be available. See 'context' function for the list of variables.
312-
313-
"""
314-
315-
# 06-jun-2004 [als] i am not sure if this method is used yet
316-
def getContext(self, client, classname, request):
317-
return context(client, self, classname, request)
318-
319-
def render(self, client, classname, request, **options):
320-
"""Render this Page Template"""
321-
322-
if not self._v_cooked:
323-
self._cook()
324-
325-
__traceback_supplement__ = (PageTemplate.PageTemplateTracebackSupplement, self)
326-
327-
if self._v_errors:
328-
raise PageTemplate.PTRuntimeError, \
329-
'Page Template %s has errors.'%self.id
330-
331-
# figure the context
332-
c = context(client, self, classname, request)
333-
c.update({'options': options})
334-
335-
# and go
336-
output = StringIO.StringIO()
337-
TALInterpreter.TALInterpreter(self._v_program, self.macros,
338-
getEngine().getContext(c), output, tal=1, strictinsert=0)()
339-
return output.getvalue()
340-
341-
def __repr__(self):
342-
return '<Roundup PageTemplate %r>'%self.id
343-
344249
class HTMLDatabase:
345250
""" Return HTMLClasses for valid class fetches
346251
"""

roundup/configuration.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,10 @@ def str2value(self, value):
466466
SETTINGS = (
467467
("main", (
468468
(FilePathOption, "database", "db", "Database directory path."),
469+
(Option, "template_engine", "zopetal",
470+
"Templating engine to use.\n"
471+
"Possible values are 'zopetal' for the old TAL engine\n"
472+
"ported from Zope, or 'chameleon' for Chamaleon."),
469473
(FilePathOption, "templates", "html",
470474
"Path to the HTML templates directory."),
471475
(NullableFilePathOption, "static_files", "",

roundup/instance.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def __init__(self, tracker_home, optimize=0):
6161
self.cgi_actions = {}
6262
self.templating_utils = {}
6363
self.load_interfaces()
64-
self.templates = templating.Templates(self.config["TEMPLATES"])
64+
self.templates = templating.get_templates(self.config["TEMPLATES"], self.config["TEMPLATE_ENGINE"])
6565
self.backend = backends.get_backend(self.get_backend_name())
6666
if self.optimize:
6767
libdir = os.path.join(self.tracker_home, 'lib')

0 commit comments

Comments
 (0)