Skip to content

Commit b9743aa

Browse files
committed
Untangle template selection logic from template loading functionality.
1 parent 84ac18f commit b9743aa

File tree

6 files changed

+84
-83
lines changed

6 files changed

+84
-83
lines changed

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ Features:
99

1010
- Renamed old Templates classes to Loader classes to clarify sources
1111
for alternative templating engines, updated docs (anatoly techtonik)
12+
- Template selection code is moved from Loader classes into cgi.client
13+
limiting the responsibility of Loaders to compilation and rendering.
14+
Internally, templating.find_template is replaced with
15+
client.selectTemplate (anatoly techtonik)
1216

1317
Fixed:
1418

roundup/cgi/client.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1063,31 +1063,58 @@ def renderFrontPage(self, message):
10631063
self.error_message.append(message)
10641064
self.write_html(self.renderContext())
10651065

1066-
def selectTemplate(self):
1067-
""" Template selection logic """
1066+
def selectTemplate(self, name, view):
1067+
""" Choose existing template for the given combination of
1068+
classname (name parameter) and template request variable
1069+
(view parameter) and return its name.
1070+
1071+
In most cases the name will be "classname.view", but
1072+
if "view" is None, then template name "classname" will
1073+
be returned.
1074+
1075+
If "classname.view" template doesn't exist, the
1076+
"_generic.view" is used as a fallback.
1077+
1078+
[ ] cover with tests
1079+
"""
10681080
loader = self.instance.templates
10691081

1070-
name = self.classname
1071-
view = self.template
1072-
10731082
# if classname is not set, use "home" template
10741083
if name is None:
10751084
name = 'home'
10761085

1077-
return name, view
1086+
tplname = name
1087+
if view:
1088+
tplname = '%s.%s' % (name, view)
1089+
1090+
if loader.check(tplname):
1091+
return tplname
1092+
1093+
# rendering class/context with generic template for this view.
1094+
# with no view it's impossible to choose which generic template to use
1095+
if not view:
1096+
raise templating.NoTemplate('Template "%s" doesn\'t exist' % name)
1097+
1098+
generic = '_generic.%s' % view
1099+
if loader.check(generic):
1100+
return generic
1101+
1102+
raise templating.NoTemplate('No template file exists for templating '
1103+
'"%s" with template "%s" (neither "%s" nor "%s")' % (name, view,
1104+
tplname, generic))
10781105

10791106
def renderContext(self):
10801107
""" Return a PageTemplate for the named page
10811108
"""
1082-
name, view = self.selectTemplate()
1109+
tplname = self.selectTemplate(self.classname, self.template)
10831110

10841111
# catch errors so we can handle PT rendering errors more nicely
10851112
args = {
10861113
'ok_message': self.ok_message,
10871114
'error_message': self.error_message
10881115
}
10891116
try:
1090-
pt = self.instance.templates.load(name, view)
1117+
pt = self.instance.templates.load(tplname)
10911118
# let the template render figure stuff out
10921119
result = pt.render(self, None, None, **args)
10931120
self.additional_headers['Content-Type'] = pt.content_type

roundup/cgi/engine_chameleon.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,22 @@
55
import os.path
66
import chameleon
77

8-
from roundup.cgi.templating import StringIO, context, find_template, LoaderBase
8+
from roundup.cgi.templating import StringIO, context, LoaderBase
99

1010
class Loader(LoaderBase):
1111
def __init__(self, dir):
1212
self.dir = dir
1313
self.loader = chameleon.PageTemplateLoader(dir)
1414

15-
def load(self, name, view=None):
16-
src, filename = find_template(self.dir, name, view)
15+
def check(self, name):
16+
for extension in ['', '.html', '.xml']:
17+
f = name + extension
18+
src = os.path.join(self.dir, f)
19+
if os.path.exists(src):
20+
return (src, f)
21+
22+
def load(self, tplname):
23+
src, filename = self.check(tplname)
1724
return RoundupPageTemplate(self.loader.load(src))
1825

1926
class RoundupPageTemplate(object):

roundup/cgi/engine_zopetal.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
import os.path
1010

11-
from roundup.cgi.templating import StringIO, context, translationService, find_template, LoaderBase
11+
from roundup.cgi.templating import StringIO, context, translationService, LoaderBase
1212
from roundup.cgi.PageTemplates import PageTemplate, GlobalTranslationService
1313
from roundup.cgi.PageTemplates.Expressions import getEngine
1414
from roundup.cgi.TAL import TALInterpreter
@@ -21,9 +21,16 @@ class Loader(LoaderBase):
2121
def __init__(self, dir):
2222
self.dir = dir
2323

24-
def load(self, name, view=None):
24+
def check(self, name):
25+
for extension in ['', '.html', '.xml']:
26+
f = name + extension
27+
src = os.path.join(self.dir, f)
28+
if os.path.exists(src):
29+
return (src, f)
30+
31+
def load(self, tplname):
2532
# find the source
26-
src, filename = find_template(self.dir, name, view)
33+
src, filename = self.check(tplname)
2734

2835
# has it changed?
2936
try:

roundup/cgi/templating.py

Lines changed: 25 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -77,49 +77,8 @@ def __str__(self):
7777
'items of class %(class)s') % {
7878
'action': self.action, 'class': self.klass}
7979

80-
def find_template(dir, name, view):
81-
""" Find a template in the nominated dir
82-
"""
83-
# find the source
84-
if view:
85-
filename = '%s.%s'%(name, view)
86-
else:
87-
filename = name
88-
89-
# try old-style
90-
src = os.path.join(dir, filename)
91-
if os.path.exists(src):
92-
return (src, filename)
93-
94-
# try with a .html or .xml extension (new-style)
95-
for extension in '.html', '.xml':
96-
f = filename + extension
97-
src = os.path.join(dir, f)
98-
if os.path.exists(src):
99-
return (src, f)
100-
101-
# no view == no generic template is possible
102-
if not view:
103-
raise NoTemplate, 'Template file "%s" doesn\'t exist'%name
104-
105-
# try _generic template for the view
106-
generic = '_generic.%s'%view
107-
src = os.path.join(dir, generic)
108-
if os.path.exists(src):
109-
return (src, generic)
110-
111-
# finally, try _generic.html
112-
generic = generic + '.html'
113-
src = os.path.join(dir, generic)
114-
if os.path.exists(src):
115-
return (src, generic)
116-
117-
raise NoTemplate('No template file exists for templating "%s" '
118-
'with template "%s" (neither "%s" nor "%s")'%(name, view,
119-
filename, generic))
120-
12180
class LoaderBase:
122-
"""Base for engine-specific template Loader class."""
81+
""" Base for engine-specific template Loader class."""
12382
def precompileTemplates(self):
12483
""" Go through a directory and precompile all the templates therein
12584
"""
@@ -137,37 +96,31 @@ def precompileTemplates(self):
13796

13897
# remove extension
13998
filename = filename[:-len(extension)]
99+
self.load(filename)
140100

141-
# load the template
142-
if '.' in filename:
143-
name, view = filename.split('.', 1)
144-
self.load(name, view)
145-
else:
146-
self.load(filename, None)
147-
148-
def load(self, name, view=None):
101+
def load(self, tplname):
149102
""" Load template and return template object with render() method.
150103
151-
"name" and "view" are used to select the template, which in
152-
most cases will be "name.view". If "view" is None, then a
153-
template called "name" will be selected.
104+
"tplname" is a template name. For filesystem loaders it is a
105+
filename without extensions, typically in the "classname.view"
106+
format.
107+
"""
108+
raise NotImplementedError
154109

155-
If the file "name.view" doesn't exist, we look for
156-
"_generic.view" as a fallback.
110+
def check(self, name):
111+
""" Check if template with the given name exists. Return None or
112+
a tuple (src, filename) that can be reused in load() method.
157113
"""
158-
# [ ] document default 'home' template and other special pages
159114
raise NotImplementedError
160115

161116
def __getitem__(self, name):
162117
"""Special method to access templates by loader['name']"""
163-
view = None
164-
if '.' in name:
165-
name, view = name.split('.', 1)
166118
try:
167-
return self.load(name, view)
119+
return self.load(name)
168120
except NoTemplate, message:
169121
raise KeyError, message
170122

123+
171124
def get_loader(dir, engine_name):
172125
if engine_name == 'chameleon':
173126
import engine_chameleon as engine
@@ -700,7 +653,9 @@ def renderWith(self, name, **kwargs):
700653
req.update(kwargs)
701654

702655
# new template, using the specified classname and request
703-
pt = self._client.instance.templates.load(self.classname, name)
656+
# [ ] this code is too similar to client.renderContext()
657+
tplname = self._client.selectTemplate(self.classname, name)
658+
pt = self._client.instance.templates.load(tplname)
704659

705660
# use our fabricated request
706661
args = {
@@ -845,9 +800,8 @@ def history(self, direction='descending', dre=re.compile('^\d+$'),
845800
isinstance(self._props[prop_n], hyperdb.Link)):
846801
classname = self._props[prop_n].classname
847802
try:
848-
template = find_template(self._db.config.TEMPLATES,
849-
classname, 'item')
850-
if template[1].startswith('_generic'):
803+
template = self._client.selectTemplate(classname, 'item')
804+
if template.startswith('_generic.'):
851805
raise NoTemplate, 'not really...'
852806
except NoTemplate:
853807
pass
@@ -917,9 +871,9 @@ def history(self, direction='descending', dre=re.compile('^\d+$'),
917871
) % locals()
918872
labelprop = linkcl.labelprop(1)
919873
try:
920-
template = find_template(self._db.config.TEMPLATES,
921-
classname, 'item')
922-
if template[1].startswith('_generic'):
874+
template = self._client.selectTemplate(classname,
875+
'item')
876+
if template.startswith('_generic.'):
923877
raise NoTemplate, 'not really...'
924878
hrefable = 1
925879
except NoTemplate:
@@ -1087,7 +1041,10 @@ def renderQueryForm(self):
10871041
'&@queryname=%s'%urllib.quote(name))
10881042

10891043
# new template, using the specified classname and request
1090-
pt = self._client.instance.templates.load(req.classname, 'search')
1044+
# [ ] the custom logic for search page doesn't belong to
1045+
# generic templating module (techtonik)
1046+
tplname = self._client.selectTemplate(req.classname, 'search')
1047+
pt = self._client.instance.templates.load(tplname)
10911048
# The context for a search page should be the class, not any
10921049
# node.
10931050
self._client.nodeid = None

test/test_templating.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ class NoTemplate(Exception):
336336
class Unauthorised(Exception):
337337
def __init__(self, action, klass):
338338
def __str__(self):
339-
def find_template(dir, name, view):
340339
341340
class Loader:
342341
def __init__(self, dir):

0 commit comments

Comments
 (0)