This recipe combines the delicious PageTemplates package with the flavorful Medusa package to serve up rendered PageTemplates from the file system.
Why not just use Zope you say? As far as I know, the current Zope release isn't an option with Python 2.2 code. The project for which this recipe was devised requires Python 2.2.
Ingredients you'll need:
Python 2.2. If you're using 2.1, try Zope instead, as it does everything this recipe can do plus a whole lot more.
ExtensionClass and friends. There is more than one way to get ExtensionClass installed, but the method I've used successfully is to install StandaloneZODB. That package is available here: http://www.zope.org/Products/StandaloneZODB
PageTemplates, TAL, and ZTUtils packages. These are available in the Zope source releases but must be installed manually. Again, there's more than one way to make these packages available in your system. The method I've used is to copy the package directories from the Zope source archive into the Python lib/site-packages/ directory. The Zope source is available from this link: http://www.zope.org/Products
Medusa. I used Medusa 0.5.2 to develop this recipe, but you may have an equally pleasant experience with other versions. You can get Medusa here: http://oedipus.sourceforge.net/medusa/
A Medusa startup script. As with all Medusa handlers, you must explicitly construct a PageTemplates handler and associate it with an HTTP server. You can modify a copy of the sample startup script included with Medusa or create your own.
Some PageTemplates. The code below reads PageTemplates markup from files stored in your file system. Give your markup files a ".pt" or ".ptx" extension, and the handler will try to render them as PageTemplates before returning their markup.
Once you have all these items in place, modify your Medusa start up script:
Save the code below as "pagetemplate_handler.py", and bring it into your script: import pagetemplate_handler
Construct a pagetemplate_handler.pagetemplate_handler, pagetemplate_handler.pagetemplate_xml_handler, or both. These types need a filesystem object, just like the default_handler.
Associate your pagetemplate_handler with your HTTP server.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 | #! /usr/bin/env python
import cgi
import os
import re
import medusa.script_handler
import medusa.xmlrpc_handler
import medusa.producers
import PageTemplates.PageTemplate
import PageTemplates.Expressions
class MedusaPageTemplate(PageTemplates.PageTemplate.PageTemplate):
""" MedusaPageTemplate -> PageTemplate type for use by Medusa handlers
This type constructs a PT context from a Medusa request when
instantiated. Instances also read in their content from disk when
created. Clients supply the path to the page template file,
along with the HTTP query string and header data.
The built-ins that this type provides may differ from the built-ins
provided by ZopePageTemplates. Specifically:
* request - Constructed from the query string and form data if present.
* here - This mapping may be provided by the client. Default is empty.
* root - This mapping may be provided by the client. Default is empty.
* user - This mapping may be provided by the client. Default is empty.
* container - By default, a custom mapping filled with the results of
stat and other os module functions. Clients can specify that this data
not be made available.
* modules - Instances reuse PageTemplates.Expressions.SecureModuleImporter
for importing modules. The behavior of SecureModuleImporter allows an
unrestricted import of any module, but in practice, modules sometimes
cannot be imported.
"""
modules = PageTemplates.Expressions.SecureModuleImporter
context_keys = ('template', 'nothing', 'options', 'request', 'modules',
'here', 'container', 'root', 'user', )
stat_keys = ('st_mode', 'st_ino', 'st_dev', 'st_nlink', 'st_uid', 'st_gid',
'st_size', 'st_atime', 'st_mtime', 'st_ctime')
def __init__(self, path, query, header, here={}, options={}, root={},
user={}, restricted=0, edit_content_type='text/html'):
head, tail = os.path.split(path)
## these two attributes are so common in PT markup that it would be a
## shame to not set them.
self.id = tail
self.title = "Page Template '%s'" % (tail, )
self.here = here
self.options = options
self.root = root
self.template = self
self.user = user
## gather data about the folder that contains this script
self.container = {}
if not restricted:
self.container.update({'is_mount' : os.path.ismount(head),
'is_link' : os.path.islink(head),
'files' : os.listdir(head),
'name' : os.path.split(head)[-1], })
self.container.update(dict(zip(self.stat_keys, os.stat(head))))
## turn a query string and/or form data in the header into a mapping
self.request = r = {}
if query:
if query.startswith('?'):
query = query[1:]
r.update(cgi.parse_qs(query, 0, 0))
if header:
r.update(cgi.parse_qs(header, 0, 0))
[r.__setitem__(k, v[0]) for k, v in r.items() if len(v) == 1]
## finally, read in the file as a convenience for the client
self.pt_edit(open(path, 'r').read(), edit_content_type)
def pt_getContext(self):
""" redefine pt_getContext() to produce our kind of context """
return dict([(k, getattr(self, k, None)) for k in self.context_keys])
class pagetemplate_handler_base(medusa.script_handler.script_handler):
""" pagetemplate_handler_base -> handler that serves up rendered PT files
This handler type reuses the majority of the behavior defined by
medusa.script_handler.script_handler. The 'match' function is the most
notable of the reused methods.
The 'handle_request' method defined below mirrors the script_handler
behavior. It also allows POST verbs, and uses the collector from
medusa.xmlrpc_handler to gather POST data.
Clients should not use this class directly. Instead, clients should
instantiate or subclass the two concrete classes defined below,
pagetemplate_handler and pagetemplate_xml_handler.
"""
pt_exceptions = (PageTemplates.PageTemplate.PTRuntimeError,
PageTemplates.TALES.TALESError,)
def __init__(self, filesystem):
## explicit call to the super; don't want clients to forget to do this
medusa.script_handler.script_handler.__init__(self, filesystem)
def handle_request(self, request):
[path, params, query, fragment] = request.split_uri()
if not self.filesystem.isfile(path):
request.error(404)
return
self.hits.increment()
request.script_filename = self.filesystem.translate(path)
if request.command in ('post', 'put'):
cl = request.get_header('content-length')
length = int(cl)
if not cl:
request.error(411)
return
else:
col = medusa.xmlrpc_handler.collector(self, request)
request.collector = col
elif request.command in ('get', ):
self.continue_request(None, request)
else:
request.error(405)
return
def continue_request(self, data, request):
""" implements the signature expected by xmlrpc_handler.collector """
path, params, query, fragment = request.split_uri()
try:
pt_obj = self.get_pagetemplate(request.script_filename, query, data)
response = self.render_pagetemplate(request, pt_obj)
except self.pt_exceptions, ex:
print '%r' % ex, ex
response = '<exception>%s</exception>' % (ex, )
request['Content-Type'] = self.content_type
request['Content-Length'] = len(response)
request.push(response)
request.done()
return
def get_pagetemplate(self, filename, query, data):
""" get_pagetemplate -> return a PT object. subclasses can override """
pt = MedusaPageTemplate(filename, query, data)
return pt
def render_pagetemplate(self, request, pagetemplate):
""" render_pagetemplate -> render a PT object. subclasses can override """
options = {}
res = pagetemplate(**options)
return res
def __repr__(self):
return '<page template request handler at %s>' % id(self)
def status(self):
return medusa.producers.simple_producer("""
<li>PageTemplate Handler
<ul><li><b>Hits:</b> %s</li>
<li><b>Exceptions:</b> %s</li>
</ul>
</li>""" % (self.hits, self.exceptions, ))
class pagetemplate_handler(pagetemplate_handler_base):
""" pagetemplate_handler -> Medusa HTTP handler
Concrete handler that returns rendered PT markup as HTML.
"""
content_type = 'text/html'
extension = 'pt'
script_regex = re.compile(r'.*/([^/]+\.%s)' % extension, re.IGNORECASE)
class pagetemplate_xml_handler(pagetemplate_handler_base):
""" pagetemplate_xml_handler -> Medusa HTTP handler
Concrete handler that returns rendered PT markup as XML.
"""
content_type = 'text/xml'
extension = 'ptx'
script_regex = re.compile(r'.*/([^/]+\.%s)' % extension, re.IGNORECASE)
|
There are a few caveats, of course: A. Most of the ZPT idioms don't work, e.g., "container/title".
B. METAL macros don't work, and I don't know why.
C. Access to modules via "module" is spotty.
This recipe isn't perfect, but it can be made better. Please send me any feedback you have. Enjoy!