Mercurial > p > roundup > code
comparison roundup/cgi/templating.py @ 1204:b862bbf2067a
Replaced the content() callback ickiness with Page Template macro usage
changed the default CSS style to be less offensive to some ;)
better handling of Page Template compilation errors
removed dependency on ComputedAttribute
| author | Richard Jones <richard@users.sourceforge.net> |
|---|---|
| date | Wed, 25 Sep 2002 02:10:25 +0000 |
| parents | 01a143f9382e |
| children | 3a5e05edcd87 |
comparison
equal
deleted
inserted
replaced
| 1203:735adcbfc665 | 1204:b862bbf2067a |
|---|---|
| 20 from roundup.cgi.PageTemplates import PageTemplate | 20 from roundup.cgi.PageTemplates import PageTemplate |
| 21 from roundup.cgi.PageTemplates.Expressions import getEngine | 21 from roundup.cgi.PageTemplates.Expressions import getEngine |
| 22 from roundup.cgi.TAL.TALInterpreter import TALInterpreter | 22 from roundup.cgi.TAL.TALInterpreter import TALInterpreter |
| 23 from roundup.cgi import ZTUtils | 23 from roundup.cgi import ZTUtils |
| 24 | 24 |
| 25 # XXX WAH pagetemplates aren't pickleable :( | |
| 26 #def getTemplate(dir, name, classname=None, request=None): | |
| 27 # ''' Interface to get a template, possibly loading a compiled template. | |
| 28 # ''' | |
| 29 # # source | |
| 30 # src = os.path.join(dir, name) | |
| 31 # | |
| 32 # # see if we can get a compile from the template"c" directory (most | |
| 33 # # likely is "htmlc" | |
| 34 # split = list(os.path.split(dir)) | |
| 35 # split[-1] = split[-1] + 'c' | |
| 36 # cdir = os.path.join(*split) | |
| 37 # split.append(name) | |
| 38 # cpl = os.path.join(*split) | |
| 39 # | |
| 40 # # ok, now see if the source is newer than the compiled (or if the | |
| 41 # # compiled even exists) | |
| 42 # MTIME = os.path.stat.ST_MTIME | |
| 43 # if (not os.path.exists(cpl) or os.stat(cpl)[MTIME] < os.stat(src)[MTIME]): | |
| 44 # # nope, we need to compile | |
| 45 # pt = RoundupPageTemplate() | |
| 46 # pt.write(open(src).read()) | |
| 47 # pt.id = name | |
| 48 # | |
| 49 # # save off the compiled template | |
| 50 # if not os.path.exists(cdir): | |
| 51 # os.makedirs(cdir) | |
| 52 # f = open(cpl, 'wb') | |
| 53 # pickle.dump(pt, f) | |
| 54 # f.close() | |
| 55 # else: | |
| 56 # # yay, use the compiled template | |
| 57 # f = open(cpl, 'rb') | |
| 58 # pt = pickle.load(f) | |
| 59 # return pt | |
| 60 | |
| 61 templates = {} | |
| 62 | |
| 63 class NoTemplate(Exception): | 25 class NoTemplate(Exception): |
| 64 pass | 26 pass |
| 65 | 27 |
| 66 def precompileTemplates(dir): | 28 class Templates: |
| 67 ''' Go through a directory and precompile all the templates therein | 29 templates = {} |
| 68 ''' | 30 |
| 69 for filename in os.listdir(dir): | 31 def __init__(self, dir): |
| 70 if os.path.isdir(filename): continue | 32 self.dir = dir |
| 71 if '.' in filename: | 33 |
| 72 name, extension = filename.split('.') | 34 def precompileTemplates(self): |
| 73 getTemplate(dir, name, extension) | 35 ''' Go through a directory and precompile all the templates therein |
| 74 else: | 36 ''' |
| 75 getTemplate(dir, filename, None) | 37 for filename in os.listdir(self.dir): |
| 76 | 38 if os.path.isdir(filename): continue |
| 77 def getTemplate(dir, name, extension, classname=None, request=None): | 39 if '.' in filename: |
| 78 ''' Interface to get a template, possibly loading a compiled template. | 40 name, extension = filename.split('.') |
| 79 | 41 self.getTemplate(name, extension) |
| 80 "name" and "extension" indicate the template we're after, which in | 42 else: |
| 81 most cases will be "name.extension". If "extension" is None, then | 43 self.getTemplate(filename, None) |
| 82 we look for a template just called "name" with no extension. | 44 |
| 83 | 45 def get(self, name, extension): |
| 84 If the file "name.extension" doesn't exist, we look for | 46 ''' Interface to get a template, possibly loading a compiled template. |
| 85 "_generic.extension" as a fallback. | 47 |
| 86 ''' | 48 "name" and "extension" indicate the template we're after, which in |
| 87 # default the name to "home" | 49 most cases will be "name.extension". If "extension" is None, then |
| 88 if name is None: | 50 we look for a template just called "name" with no extension. |
| 89 name = 'home' | 51 |
| 90 | 52 If the file "name.extension" doesn't exist, we look for |
| 91 # find the source, figure the time it was last modified | 53 "_generic.extension" as a fallback. |
| 92 if extension: | 54 ''' |
| 93 filename = '%s.%s'%(name, extension) | 55 # default the name to "home" |
| 94 else: | 56 if name is None: |
| 95 filename = name | 57 name = 'home' |
| 96 src = os.path.join(dir, filename) | 58 |
| 97 try: | 59 # find the source, figure the time it was last modified |
| 98 stime = os.stat(src)[os.path.stat.ST_MTIME] | 60 if extension: |
| 99 except os.error, error: | 61 filename = '%s.%s'%(name, extension) |
| 100 if error.errno != errno.ENOENT: | 62 else: |
| 101 raise | 63 filename = name |
| 102 if not extension: | 64 src = os.path.join(self.dir, filename) |
| 103 raise NoTemplate, 'Template file "%s" doesn\'t exist'%name | |
| 104 | |
| 105 # try for a generic template | |
| 106 generic = '_generic.%s'%extension | |
| 107 src = os.path.join(dir, generic) | |
| 108 try: | 65 try: |
| 109 stime = os.stat(src)[os.path.stat.ST_MTIME] | 66 stime = os.stat(src)[os.path.stat.ST_MTIME] |
| 110 except os.error, error: | 67 except os.error, error: |
| 111 if error.errno != errno.ENOENT: | 68 if error.errno != errno.ENOENT: |
| 112 raise | 69 raise |
| 113 # nicer error | 70 if not extension: |
| 114 raise NoTemplate, 'No template file exists for templating '\ | 71 raise NoTemplate, 'Template file "%s" doesn\'t exist'%name |
| 115 '"%s" with template "%s" (neither "%s" nor "%s")'%(name, | 72 |
| 116 extension, filename, generic) | 73 # try for a generic template |
| 117 filename = generic | 74 generic = '_generic.%s'%extension |
| 118 | 75 src = os.path.join(self.dir, generic) |
| 119 key = (dir, filename) | 76 try: |
| 120 if templates.has_key(key) and stime < templates[key].mtime: | 77 stime = os.stat(src)[os.path.stat.ST_MTIME] |
| 121 # compiled template is up to date | 78 except os.error, error: |
| 122 return templates[key] | 79 if error.errno != errno.ENOENT: |
| 123 | 80 raise |
| 124 # compile the template | 81 # nicer error |
| 125 templates[key] = pt = RoundupPageTemplate() | 82 raise NoTemplate, 'No template file exists for templating '\ |
| 126 pt.write(open(src).read()) | 83 '"%s" with template "%s" (neither "%s" nor "%s")'%(name, |
| 127 pt.id = filename | 84 extension, filename, generic) |
| 128 pt.mtime = time.time() | 85 filename = generic |
| 129 return pt | 86 |
| 87 if self.templates.has_key(filename) and \ | |
| 88 stime < self.templates[filename].mtime: | |
| 89 # compiled template is up to date | |
| 90 return self.templates[filename] | |
| 91 | |
| 92 # compile the template | |
| 93 self.templates[filename] = pt = RoundupPageTemplate() | |
| 94 pt.write(open(src).read()) | |
| 95 pt.id = filename | |
| 96 pt.mtime = time.time() | |
| 97 return pt | |
| 98 | |
| 99 def __getitem__(self, name): | |
| 100 name, extension = os.path.splitext(name) | |
| 101 if extension: | |
| 102 extension = extension[1:] | |
| 103 try: | |
| 104 return self.get(name, extension) | |
| 105 except NoTemplate, message: | |
| 106 raise KeyError, message | |
| 130 | 107 |
| 131 class RoundupPageTemplate(PageTemplate.PageTemplate): | 108 class RoundupPageTemplate(PageTemplate.PageTemplate): |
| 132 ''' A Roundup-specific PageTemplate. | 109 ''' A Roundup-specific PageTemplate. |
| 133 | 110 |
| 134 Interrogate the client to set up the various template variables to | 111 Interrogate the client to set up the various template variables to |
| 147 - the current index information (``filterspec``, ``filter`` args, | 124 - the current index information (``filterspec``, ``filter`` args, |
| 148 ``properties``, etc) parsed out of the form. | 125 ``properties``, etc) parsed out of the form. |
| 149 - methods for easy filterspec link generation | 126 - methods for easy filterspec link generation |
| 150 - *user*, the current user node as an HTMLItem instance | 127 - *user*, the current user node as an HTMLItem instance |
| 151 - *form*, the current CGI form information as a FieldStorage | 128 - *form*, the current CGI form information as a FieldStorage |
| 152 *instance* | 129 *tracker* |
| 153 The current instance | 130 The current tracker |
| 154 *db* | 131 *db* |
| 155 The current database, through which db.config may be reached. | 132 The current database, through which db.config may be reached. |
| 156 ''' | 133 ''' |
| 157 def getContext(self, client, classname, request): | 134 def getContext(self, client, classname, request): |
| 158 c = { | 135 c = { |
| 159 'options': {}, | 136 'options': {}, |
| 160 'nothing': None, | 137 'nothing': None, |
| 161 'request': request, | 138 'request': request, |
| 162 'content': client.content, | |
| 163 'db': HTMLDatabase(client), | 139 'db': HTMLDatabase(client), |
| 164 'instance': client.instance, | 140 'tracker': client.instance, |
| 165 'utils': TemplatingUtils(client), | 141 'utils': TemplatingUtils(client), |
| 142 'templates': Templates(client.instance.config.TEMPLATES), | |
| 166 } | 143 } |
| 167 # add in the item if there is one | 144 # add in the item if there is one |
| 168 if client.nodeid: | 145 if client.nodeid: |
| 169 if classname == 'user': | 146 if classname == 'user': |
| 170 c['context'] = HTMLUser(client, classname, client.nodeid) | 147 c['context'] = HTMLUser(client, classname, client.nodeid) |
| 171 else: | 148 else: |
| 172 c['context'] = HTMLItem(client, classname, client.nodeid) | 149 c['context'] = HTMLItem(client, classname, client.nodeid) |
| 173 else: | 150 elif client.db.classes.has_key(classname): |
| 174 c['context'] = HTMLClass(client, classname) | 151 c['context'] = HTMLClass(client, classname) |
| 175 return c | 152 return c |
| 176 | 153 |
| 177 def render(self, client, classname, request, **options): | 154 def render(self, client, classname, request, **options): |
| 178 """Render this Page Template""" | 155 """Render this Page Template""" |
| 192 c = self.getContext(client, classname, request) | 169 c = self.getContext(client, classname, request) |
| 193 c.update({'options': options}) | 170 c.update({'options': options}) |
| 194 | 171 |
| 195 # and go | 172 # and go |
| 196 output = StringIO.StringIO() | 173 output = StringIO.StringIO() |
| 197 TALInterpreter(self._v_program, self._v_macros, | 174 TALInterpreter(self._v_program, self.macros, |
| 198 getEngine().getContext(c), output, tal=1, strictinsert=0)() | 175 getEngine().getContext(c), output, tal=1, strictinsert=0)() |
| 199 return output.getvalue() | 176 return output.getvalue() |
| 200 | 177 |
| 201 class HTMLDatabase: | 178 class HTMLDatabase: |
| 202 ''' Return HTMLClasses for valid class fetches | 179 ''' Return HTMLClasses for valid class fetches |
| 258 self._db = client.db | 235 self._db = client.db |
| 259 | 236 |
| 260 # we want classname to be exposed, but _classname gives a | 237 # we want classname to be exposed, but _classname gives a |
| 261 # consistent API for extending Class/Item | 238 # consistent API for extending Class/Item |
| 262 self._classname = self.classname = classname | 239 self._classname = self.classname = classname |
| 263 if classname is not None: | 240 self._klass = self._db.getclass(self.classname) |
| 264 self._klass = self._db.getclass(self.classname) | 241 self._props = self._klass.getprops() |
| 265 self._props = self._klass.getprops() | |
| 266 | 242 |
| 267 def __repr__(self): | 243 def __repr__(self): |
| 268 return '<HTMLClass(0x%x) %s>'%(id(self), self.classname) | 244 return '<HTMLClass(0x%x) %s>'%(id(self), self.classname) |
| 269 | 245 |
| 270 def __getitem__(self, item): | 246 def __getitem__(self, item): |
| 424 if properties is None: | 400 if properties is None: |
| 425 properties = self._klass.getprops(protected=0).keys() | 401 properties = self._klass.getprops(protected=0).keys() |
| 426 properties.sort() | 402 properties.sort() |
| 427 properties = ','.join(properties) | 403 properties = ','.join(properties) |
| 428 return '<a href="javascript:help_window(\'%s?:template=help&' \ | 404 return '<a href="javascript:help_window(\'%s?:template=help&' \ |
| 429 ':contentonly=1&properties=%s\', \'%s\', \'%s\')"><b>'\ | 405 'properties=%s\', \'%s\', \'%s\')"><b>(%s)</b></a>'%( |
| 430 '(%s)</b></a>'%(self.classname, properties, width, height, label) | 406 self.classname, properties, width, height, label) |
| 431 | 407 |
| 432 def submit(self, label="Submit New Entry"): | 408 def submit(self, label="Submit New Entry"): |
| 433 ''' Generate a submit button (and action hidden element) | 409 ''' Generate a submit button (and action hidden element) |
| 434 ''' | 410 ''' |
| 435 return ' <input type="hidden" name=":action" value="new">\n'\ | 411 return ' <input type="hidden" name=":action" value="new">\n'\ |
| 445 req = HTMLRequest(self._client) | 421 req = HTMLRequest(self._client) |
| 446 req.classname = self.classname | 422 req.classname = self.classname |
| 447 req.update(kwargs) | 423 req.update(kwargs) |
| 448 | 424 |
| 449 # new template, using the specified classname and request | 425 # new template, using the specified classname and request |
| 450 pt = getTemplate(self._db.config.TEMPLATES, self.classname, name) | 426 pt = Templates(self._db.config.TEMPLATES).get(self.classname, name) |
| 451 | 427 |
| 452 # use our fabricated request | 428 # use our fabricated request |
| 453 return pt.render(self._client, self.classname, req) | 429 return pt.render(self._client, self.classname, req) |
| 454 | 430 |
| 455 class HTMLItem(HTMLPermissions): | 431 class HTMLItem(HTMLPermissions): |
| 1438 submitted = true; | 1414 submitted = true; |
| 1439 return 1; | 1415 return 1; |
| 1440 } | 1416 } |
| 1441 | 1417 |
| 1442 function help_window(helpurl, width, height) { | 1418 function help_window(helpurl, width, height) { |
| 1443 HelpWin = window.open('%s/' + helpurl, 'RoundupHelpWindow', 'scrollbars=yes,resizable=yes,toolbar=no,height='+height+',width='+width); | 1419 HelpWin = window.open('%s' + helpurl, 'RoundupHelpWindow', 'scrollbars=yes,resizable=yes,toolbar=no,height='+height+',width='+width); |
| 1444 } | 1420 } |
| 1445 </script> | 1421 </script> |
| 1446 '''%self.base | 1422 '''%self.base |
| 1447 | 1423 |
| 1448 def batch(self): | 1424 def batch(self): |
