1 #
2 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
3 # This module is free software, and you may redistribute it and/or modify
4 # under the same terms as Python, so long as this copyright message and
5 # disclaimer are retained in their original form.
6 #
7 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
8 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
9 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
10 # POSSIBILITY OF SUCH DAMAGE.
11 #
12 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 #
18 # $Id: htmltemplate.py,v 1.25 2001-10-09 07:25:59 richard Exp $
20 import os, re, StringIO, urllib, cgi, errno
22 import hyperdb, date, password
24 class Base:
25 def __init__(self, db, templates, classname, nodeid=None, form=None,
26 filterspec=None):
27 # TODO: really not happy with the way templates is passed on here
28 self.db, self.templates = db, templates
29 self.classname, self.nodeid = classname, nodeid
30 self.form, self.filterspec = form, filterspec
31 self.cl = self.db.classes[self.classname]
32 self.properties = self.cl.getprops()
34 class Plain(Base):
35 ''' display a String property directly;
37 display a Date property in a specified time zone with an option to
38 omit the time from the date stamp;
40 for a Link or Multilink property, display the key strings of the
41 linked nodes (or the ids if the linked class has no key property)
42 '''
43 def __call__(self, property, escape=0):
44 if not self.nodeid and self.form is None:
45 return '[Field: not called from item]'
46 propclass = self.properties[property]
47 if self.nodeid:
48 value = self.cl.get(self.nodeid, property)
49 else:
50 # TODO: pull the value from the form
51 if isinstance(propclass, hyperdb.Multilink): value = []
52 else: value = ''
53 if isinstance(propclass, hyperdb.String):
54 if value is None: value = ''
55 else: value = str(value)
56 elif isinstance(propclass, hyperdb.Password):
57 if value is None: value = ''
58 else: value = '*encrypted*'
59 elif isinstance(propclass, hyperdb.Date):
60 value = str(value)
61 elif isinstance(propclass, hyperdb.Interval):
62 value = str(value)
63 elif isinstance(propclass, hyperdb.Link):
64 linkcl = self.db.classes[propclass.classname]
65 k = linkcl.labelprop()
66 if value: value = str(linkcl.get(value, k))
67 else: value = '[unselected]'
68 elif isinstance(propclass, hyperdb.Multilink):
69 linkcl = self.db.classes[propclass.classname]
70 k = linkcl.labelprop()
71 value = ', '.join([linkcl.get(i, k) for i in value])
72 else:
73 s = 'Plain: bad propclass "%s"'%propclass
74 if escape:
75 return cgi.escape(value)
76 return value
78 class Field(Base):
79 ''' display a property like the plain displayer, but in a text field
80 to be edited
81 '''
82 def __call__(self, property, size=None, height=None, showid=0):
83 if not self.nodeid and self.form is None and self.filterspec is None:
84 return '[Field: not called from item]'
85 propclass = self.properties[property]
86 if self.nodeid:
87 value = self.cl.get(self.nodeid, property, None)
88 # TODO: remove this from the code ... it's only here for
89 # handling schema changes, and they should be handled outside
90 # of this code...
91 if isinstance(propclass, hyperdb.Multilink) and value is None:
92 value = []
93 elif self.filterspec is not None:
94 if isinstance(propclass, hyperdb.Multilink):
95 value = self.filterspec.get(property, [])
96 else:
97 value = self.filterspec.get(property, '')
98 else:
99 # TODO: pull the value from the form
100 if isinstance(propclass, hyperdb.Multilink): value = []
101 else: value = ''
102 if (isinstance(propclass, hyperdb.String) or
103 isinstance(propclass, hyperdb.Date) or
104 isinstance(propclass, hyperdb.Interval)):
105 size = size or 30
106 if value is None:
107 value = ''
108 else:
109 value = cgi.escape(value)
110 value = '"'.join(value.split('"'))
111 s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
112 elif isinstance(propclass, hyperdb.Password):
113 size = size or 30
114 s = '<input type="password" name="%s" size="%s">'%(property, size)
115 elif isinstance(propclass, hyperdb.Link):
116 linkcl = self.db.classes[propclass.classname]
117 l = ['<select name="%s">'%property]
118 k = linkcl.labelprop()
119 for optionid in linkcl.list():
120 option = linkcl.get(optionid, k)
121 s = ''
122 if optionid == value:
123 s = 'selected '
124 if showid:
125 lab = '%s%s: %s'%(propclass.classname, optionid, option)
126 else:
127 lab = option
128 if size is not None and len(lab) > size:
129 lab = lab[:size-3] + '...'
130 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
131 l.append('</select>')
132 s = '\n'.join(l)
133 elif isinstance(propclass, hyperdb.Multilink):
134 linkcl = self.db.classes[propclass.classname]
135 list = linkcl.list()
136 height = height or min(len(list), 7)
137 l = ['<select multiple name="%s" size="%s">'%(property, height)]
138 k = linkcl.labelprop()
139 for optionid in list:
140 option = linkcl.get(optionid, k)
141 s = ''
142 if optionid in value:
143 s = 'selected '
144 if showid:
145 lab = '%s%s: %s'%(propclass.classname, optionid, option)
146 else:
147 lab = option
148 if size is not None and len(lab) > size:
149 lab = lab[:size-3] + '...'
150 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
151 l.append('</select>')
152 s = '\n'.join(l)
153 else:
154 s = 'Plain: bad propclass "%s"'%propclass
155 return s
157 class Menu(Base):
158 ''' for a Link property, display a menu of the available choices
159 '''
160 def __call__(self, property, size=None, height=None, showid=0):
161 propclass = self.properties[property]
162 if self.nodeid:
163 value = self.cl.get(self.nodeid, property)
164 else:
165 # TODO: pull the value from the form
166 if isinstance(propclass, hyperdb.Multilink): value = []
167 else: value = None
168 if isinstance(propclass, hyperdb.Link):
169 linkcl = self.db.classes[propclass.classname]
170 l = ['<select name="%s">'%property]
171 k = linkcl.labelprop()
172 for optionid in linkcl.list():
173 option = linkcl.get(optionid, k)
174 s = ''
175 if optionid == value:
176 s = 'selected '
177 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
178 l.append('</select>')
179 return '\n'.join(l)
180 if isinstance(propclass, hyperdb.Multilink):
181 linkcl = self.db.classes[propclass.classname]
182 list = linkcl.list()
183 height = height or min(len(list), 7)
184 l = ['<select multiple name="%s" size="%s">'%(property, height)]
185 k = linkcl.labelprop()
186 for optionid in list:
187 option = linkcl.get(optionid, k)
188 s = ''
189 if optionid in value:
190 s = 'selected '
191 if showid:
192 lab = '%s%s: %s'%(propclass.classname, optionid, option)
193 else:
194 lab = option
195 if size is not None and len(lab) > size:
196 lab = lab[:size-3] + '...'
197 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
198 l.append('</select>')
199 return '\n'.join(l)
200 return '[Menu: not a link]'
202 #XXX deviates from spec
203 class Link(Base):
204 ''' for a Link or Multilink property, display the names of the linked
205 nodes, hyperlinked to the item views on those nodes
206 for other properties, link to this node with the property as the text
207 '''
208 def __call__(self, property=None, **args):
209 if not self.nodeid and self.form is None:
210 return '[Link: not called from item]'
211 propclass = self.properties[property]
212 if self.nodeid:
213 value = self.cl.get(self.nodeid, property)
214 else:
215 if isinstance(propclass, hyperdb.Multilink): value = []
216 else: value = ''
217 if isinstance(propclass, hyperdb.Link):
218 linkname = propclass.classname
219 if value is None:
220 return '[not assigned]'
221 linkcl = self.db.classes[linkname]
222 k = linkcl.labelprop()
223 linkvalue = linkcl.get(value, k)
224 return '<a href="%s%s">%s</a>'%(linkname, value, linkvalue)
225 if isinstance(propclass, hyperdb.Multilink):
226 linkname = propclass.classname
227 linkcl = self.db.classes[linkname]
228 k = linkcl.labelprop()
229 l = []
230 for value in value:
231 linkvalue = linkcl.get(value, k)
232 l.append('<a href="%s%s">%s</a>'%(linkname, value, linkvalue))
233 return ', '.join(l)
234 return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
236 class Count(Base):
237 ''' for a Multilink property, display a count of the number of links in
238 the list
239 '''
240 def __call__(self, property, **args):
241 if not self.nodeid:
242 return '[Count: not called from item]'
243 propclass = self.properties[property]
244 value = self.cl.get(self.nodeid, property)
245 if isinstance(propclass, hyperdb.Multilink):
246 return str(len(value))
247 return '[Count: not a Multilink]'
249 # XXX pretty is definitely new ;)
250 class Reldate(Base):
251 ''' display a Date property in terms of an interval relative to the
252 current date (e.g. "+ 3w", "- 2d").
254 with the 'pretty' flag, make it pretty
255 '''
256 def __call__(self, property, pretty=0):
257 if not self.nodeid and self.form is None:
258 return '[Reldate: not called from item]'
259 propclass = self.properties[property]
260 if isinstance(not propclass, hyperdb.Date):
261 return '[Reldate: not a Date]'
262 if self.nodeid:
263 value = self.cl.get(self.nodeid, property)
264 else:
265 value = date.Date('.')
266 interval = value - date.Date('.')
267 if pretty:
268 if not self.nodeid:
269 return 'now'
270 pretty = interval.pretty()
271 if pretty is None:
272 pretty = value.pretty()
273 return pretty
274 return str(interval)
276 class Download(Base):
277 ''' show a Link("file") or Multilink("file") property using links that
278 allow you to download files
279 '''
280 def __call__(self, property, **args):
281 if not self.nodeid:
282 return '[Download: not called from item]'
283 propclass = self.properties[property]
284 value = self.cl.get(self.nodeid, property)
285 if isinstance(propclass, hyperdb.Link):
286 linkcl = self.db.classes[propclass.classname]
287 linkvalue = linkcl.get(value, k)
288 return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
289 if isinstance(propclass, hyperdb.Multilink):
290 linkcl = self.db.classes[propclass.classname]
291 l = []
292 for value in value:
293 linkvalue = linkcl.get(value, k)
294 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
295 return ', '.join(l)
296 return '[Download: not a link]'
299 class Checklist(Base):
300 ''' for a Link or Multilink property, display checkboxes for the available
301 choices to permit filtering
302 '''
303 def __call__(self, property, **args):
304 propclass = self.properties[property]
305 if self.nodeid:
306 value = self.cl.get(self.nodeid, property)
307 elif self.filterspec is not None:
308 value = self.filterspec.get(property, [])
309 else:
310 value = []
311 if (isinstance(propclass, hyperdb.Link) or
312 isinstance(propclass, hyperdb.Multilink)):
313 linkcl = self.db.classes[propclass.classname]
314 l = []
315 k = linkcl.labelprop()
316 for optionid in linkcl.list():
317 option = linkcl.get(optionid, k)
318 if optionid in value or option in value:
319 checked = 'checked'
320 else:
321 checked = ''
322 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
323 option, checked, property, option))
324 return '\n'.join(l)
325 return '[Checklist: not a link]'
327 class Note(Base):
328 ''' display a "note" field, which is a text area for entering a note to
329 go along with a change.
330 '''
331 def __call__(self, rows=5, cols=80):
332 # TODO: pull the value from the form
333 return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
334 cols)
336 # XXX new function
337 class List(Base):
338 ''' list the items specified by property using the standard index for
339 the class
340 '''
341 def __call__(self, property, reverse=0):
342 propclass = self.properties[property]
343 if isinstance(not propclass, hyperdb.Multilink):
344 return '[List: not a Multilink]'
345 fp = StringIO.StringIO()
346 value = self.cl.get(self.nodeid, property)
347 if reverse:
348 value.reverse()
349 # TODO: really not happy with the way templates is passed on here
350 index(fp, self.templates, self.db, propclass.classname, nodeids=value,
351 show_display_form=0)
352 return fp.getvalue()
354 # XXX new function
355 class History(Base):
356 ''' list the history of the item
357 '''
358 def __call__(self, **args):
359 if self.nodeid is None:
360 return "[History: node doesn't exist]"
362 l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
363 '<tr class="list-header">',
364 '<td><span class="list-item"><strong>Date</strong></span></td>',
365 '<td><span class="list-item"><strong>User</strong></span></td>',
366 '<td><span class="list-item"><strong>Action</strong></span></td>',
367 '<td><span class="list-item"><strong>Args</strong></span></td>']
369 for id, date, user, action, args in self.cl.history(self.nodeid):
370 l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
371 date, user, action, args))
372 l.append('</table>')
373 return '\n'.join(l)
375 # XXX new function
376 class Submit(Base):
377 ''' add a submit button for the item
378 '''
379 def __call__(self):
380 if self.nodeid:
381 return '<input type="submit" value="Submit Changes">'
382 elif self.form is not None:
383 return '<input type="submit" value="Submit New Entry">'
384 else:
385 return '[Submit: not called from item]'
388 #
389 # INDEX TEMPLATES
390 #
391 class IndexTemplateReplace:
392 def __init__(self, globals, locals, props):
393 self.globals = globals
394 self.locals = locals
395 self.props = props
397 def go(self, text, replace=re.compile(
398 r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
399 r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
400 return replace.sub(self, text)
402 def __call__(self, m, filter=None, columns=None, sort=None, group=None):
403 if m.group('name'):
404 if m.group('name') in self.props:
405 text = m.group('text')
406 replace = IndexTemplateReplace(self.globals, {}, self.props)
407 return replace.go(m.group('text'))
408 else:
409 return ''
410 if m.group('display'):
411 command = m.group('command')
412 return eval(command, self.globals, self.locals)
413 print '*** unhandled match', m.groupdict()
415 def sortby(sort_name, columns, filter, sort, group, filterspec):
416 l = []
417 w = l.append
418 for k, v in filterspec.items():
419 k = urllib.quote(k)
420 if type(v) == type([]):
421 w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
422 else:
423 w('%s=%s'%(k, urllib.quote(v)))
424 if columns:
425 w(':columns=%s'%','.join(map(urllib.quote, columns)))
426 if filter:
427 w(':filter=%s'%','.join(map(urllib.quote, filter)))
428 if group:
429 w(':group=%s'%','.join(map(urllib.quote, group)))
430 m = []
431 s_dir = ''
432 for name in sort:
433 dir = name[0]
434 if dir == '-':
435 name = name[1:]
436 else:
437 dir = ''
438 if sort_name == name:
439 if dir == '-':
440 s_dir = ''
441 else:
442 s_dir = '-'
443 else:
444 m.append(dir+urllib.quote(name))
445 m.insert(0, s_dir+urllib.quote(sort_name))
446 # so things don't get completely out of hand, limit the sort to two columns
447 w(':sort=%s'%','.join(m[:2]))
448 return '&'.join(l)
450 def index(client, templates, db, classname, filterspec={}, filter=[],
451 columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
452 col_re=re.compile(r'<property\s+name="([^>]+)">')):
453 globals = {
454 'plain': Plain(db, templates, classname, filterspec=filterspec),
455 'field': Field(db, templates, classname, filterspec=filterspec),
456 'menu': Menu(db, templates, classname, filterspec=filterspec),
457 'link': Link(db, templates, classname, filterspec=filterspec),
458 'count': Count(db, templates, classname, filterspec=filterspec),
459 'reldate': Reldate(db, templates, classname, filterspec=filterspec),
460 'download': Download(db, templates, classname, filterspec=filterspec),
461 'checklist': Checklist(db, templates, classname, filterspec=filterspec),
462 'list': List(db, templates, classname, filterspec=filterspec),
463 'history': History(db, templates, classname, filterspec=filterspec),
464 'submit': Submit(db, templates, classname, filterspec=filterspec),
465 'note': Note(db, templates, classname, filterspec=filterspec)
466 }
467 cl = db.classes[classname]
468 properties = cl.getprops()
469 w = client.write
470 w('<form>')
472 try:
473 template = open(os.path.join(templates, classname+'.filter')).read()
474 all_filters = col_re.findall(template)
475 except IOError, error:
476 if error.errno != errno.ENOENT: raise
477 template = None
478 all_filters = []
479 if template and filter:
480 # display the filter section
481 w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
482 w('<tr class="location-bar">')
483 w(' <th align="left" colspan="2">Filter specification...</th>')
484 w('</tr>')
485 replace = IndexTemplateReplace(globals, locals(), filter)
486 w(replace.go(template))
487 w('<tr class="location-bar"><td width="1%%"> </td>')
488 w('<td><input type="submit" value="Redisplay"></td></tr>')
489 w('</table>')
491 # If the filters aren't being displayed, then hide their current
492 # value in the form
493 if not filter:
494 for k, v in filterspec.items():
495 if type(v) == type([]): v = ','.join(v)
496 w('<input type="hidden" name="%s" value="%s">'%(k, v))
498 # make sure that the sorting doesn't get lost either
499 if sort:
500 w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
502 # XXX deviate from spec here ...
503 # load the index section template and figure the default columns from it
504 template = open(os.path.join(templates, classname+'.index')).read()
505 all_columns = col_re.findall(template)
506 if not columns:
507 columns = []
508 for name in all_columns:
509 columns.append(name)
510 else:
511 # re-sort columns to be the same order as all_columns
512 l = []
513 for name in all_columns:
514 if name in columns:
515 l.append(name)
516 columns = l
518 # now display the index section
519 w('<table width=100% border=0 cellspacing=0 cellpadding=2>\n')
520 w('<tr class="list-header">\n')
521 for name in columns:
522 cname = name.capitalize()
523 if show_display_form:
524 anchor = "%s?%s"%(classname, sortby(name, columns, filter,
525 sort, group, filterspec))
526 w('<td><span class="list-header"><a href="%s">%s</a></span></td>\n'%(
527 anchor, cname))
528 else:
529 w('<td><span class="list-header">%s</span></td>\n'%cname)
530 w('</tr>\n')
532 # this stuff is used for group headings - optimise the group names
533 old_group = None
534 group_names = []
535 if group:
536 for name in group:
537 if name[0] == '-': group_names.append(name[1:])
538 else: group_names.append(name)
540 # now actually loop through all the nodes we get from the filter and
541 # apply the template
542 if nodeids is None:
543 nodeids = cl.filter(filterspec, sort, group)
544 for nodeid in nodeids:
545 # check for a group heading
546 if group_names:
547 this_group = [cl.get(nodeid, name) for name in group_names]
548 if this_group != old_group:
549 l = []
550 for name in group_names:
551 prop = properties[name]
552 if isinstance(prop, hyperdb.Link):
553 group_cl = db.classes[prop.classname]
554 key = group_cl.getkey()
555 value = cl.get(nodeid, name)
556 if value is None:
557 l.append('[unselected %s]'%prop.classname)
558 else:
559 l.append(group_cl.get(cl.get(nodeid, name), key))
560 elif isinstance(prop, hyperdb.Multilink):
561 group_cl = db.classes[prop.classname]
562 key = group_cl.getkey()
563 for value in cl.get(nodeid, name):
564 l.append(group_cl.get(value, key))
565 else:
566 value = cl.get(nodeid, name)
567 if value is None:
568 value = '[empty %s]'%name
569 l.append(value)
570 w('<tr class="section-bar">'
571 '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
572 len(columns), ', '.join(l)))
573 old_group = this_group
575 # display this node's row
576 for value in globals.values():
577 if hasattr(value, 'nodeid'):
578 value.nodeid = nodeid
579 replace = IndexTemplateReplace(globals, locals(), columns)
580 w(replace.go(template))
582 w('</table>')
584 if not show_display_form:
585 return
587 # now add in the filter/columns/group/etc config table form
588 w('<p>')
589 w('<table width=100% border=0 cellspacing=0 cellpadding=2>\n')
590 names = []
591 for name in cl.getprops().keys():
592 if name in all_filters or name in all_columns:
593 names.append(name)
594 w('<tr class="location-bar">')
595 w('<th align="left" colspan=%s>View customisation...</th></tr>\n'%
596 (len(names)+1))
597 w('<tr class="location-bar"><th> </th>')
598 for name in names:
599 w('<th>%s</th>'%name.capitalize())
600 w('</tr>\n')
602 # filter
603 if all_filters:
604 w('<tr><th width="1%" align=right class="location-bar">Filters</th>\n')
605 for name in names:
606 if name not in all_filters:
607 w('<td> </td>')
608 continue
609 if name in filter: checked=' checked'
610 else: checked=''
611 w('<td align=middle>\n')
612 w(' <input type="checkbox" name=":filter" value="%s" %s></td>\n'%(
613 name, checked))
614 w('</tr>\n')
616 # columns
617 if all_columns:
618 w('<tr><th width="1%" align=right class="location-bar">Columns</th>\n')
619 for name in names:
620 if name not in all_columns:
621 w('<td> </td>')
622 continue
623 if name in columns: checked=' checked'
624 else: checked=''
625 w('<td align=middle>\n')
626 w(' <input type="checkbox" name=":columns" value="%s" %s></td>\n'%(
627 name, checked))
628 w('</tr>\n')
630 # group
631 w('<tr><th width="1%" align=right class="location-bar">Grouping</th>\n')
632 for name in names:
633 prop = properties[name]
634 if name not in all_columns:
635 w('<td> </td>')
636 continue
637 if name in group: checked=' checked'
638 else: checked=''
639 w('<td align=middle>\n')
640 w(' <input type="checkbox" name=":group" value="%s" %s></td>\n'%(
641 name, checked))
642 w('</tr>\n')
644 w('<tr class="location-bar"><td width="1%"> </td>')
645 w('<td colspan="%s">'%len(names))
646 w('<input type="submit" value="Redisplay"></td></tr>\n')
647 w('</table>\n')
648 w('</form>\n')
651 #
652 # ITEM TEMPLATES
653 #
654 class ItemTemplateReplace:
655 def __init__(self, globals, locals, cl, nodeid):
656 self.globals = globals
657 self.locals = locals
658 self.cl = cl
659 self.nodeid = nodeid
661 def go(self, text, replace=re.compile(
662 r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
663 r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
664 return replace.sub(self, text)
666 def __call__(self, m, filter=None, columns=None, sort=None, group=None):
667 if m.group('name'):
668 if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
669 replace = ItemTemplateReplace(self.globals, {}, self.cl,
670 self.nodeid)
671 return replace.go(m.group('text'))
672 else:
673 return ''
674 if m.group('display'):
675 command = m.group('command')
676 return eval(command, self.globals, self.locals)
677 print '*** unhandled match', m.groupdict()
679 def item(client, templates, db, classname, nodeid, replace=re.compile(
680 r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
681 r'(?P<endprop></property>)|'
682 r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
684 globals = {
685 'plain': Plain(db, templates, classname, nodeid),
686 'field': Field(db, templates, classname, nodeid),
687 'menu': Menu(db, templates, classname, nodeid),
688 'link': Link(db, templates, classname, nodeid),
689 'count': Count(db, templates, classname, nodeid),
690 'reldate': Reldate(db, templates, classname, nodeid),
691 'download': Download(db, templates, classname, nodeid),
692 'checklist': Checklist(db, templates, classname, nodeid),
693 'list': List(db, templates, classname, nodeid),
694 'history': History(db, templates, classname, nodeid),
695 'submit': Submit(db, templates, classname, nodeid),
696 'note': Note(db, templates, classname, nodeid)
697 }
699 cl = db.classes[classname]
700 properties = cl.getprops()
702 if properties.has_key('type') and properties.has_key('content'):
703 pass
704 # XXX we really want to return this as a downloadable...
705 # currently I handle this at a higher level by detecting 'file'
706 # designators...
708 w = client.write
709 w('<form action="%s%s">'%(classname, nodeid))
710 s = open(os.path.join(templates, classname+'.item')).read()
711 replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
712 w(replace.go(s))
713 w('</form>')
716 def newitem(client, templates, db, classname, form, replace=re.compile(
717 r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
718 r'(?P<endprop></property>)|'
719 r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
720 globals = {
721 'plain': Plain(db, templates, classname, form=form),
722 'field': Field(db, templates, classname, form=form),
723 'menu': Menu(db, templates, classname, form=form),
724 'link': Link(db, templates, classname, form=form),
725 'count': Count(db, templates, classname, form=form),
726 'reldate': Reldate(db, templates, classname, form=form),
727 'download': Download(db, templates, classname, form=form),
728 'checklist': Checklist(db, templates, classname, form=form),
729 'list': List(db, templates, classname, form=form),
730 'history': History(db, templates, classname, form=form),
731 'submit': Submit(db, templates, classname, form=form),
732 'note': Note(db, templates, classname, form=form)
733 }
735 cl = db.classes[classname]
736 properties = cl.getprops()
738 w = client.write
739 try:
740 s = open(os.path.join(templates, classname+'.newitem')).read()
741 except:
742 s = open(os.path.join(templates, classname+'.item')).read()
743 w('<form action="new%s" method="POST" enctype="multipart/form-data">'%classname)
744 for key in form.keys():
745 if key[0] == ':':
746 value = form[key].value
747 if type(value) != type([]): value = [value]
748 for value in value:
749 w('<input type="hidden" name="%s" value="%s">'%(key, value))
750 replace = ItemTemplateReplace(globals, locals(), None, None)
751 w(replace.go(s))
752 w('</form>')
754 #
755 # $Log: not supported by cvs2svn $
756 # Revision 1.24 2001/09/27 06:45:58 richard
757 # *gak* ... xmp is Old Skool apparently. Am using pre again by have the option
758 # on the plain() template function to escape the text for HTML.
759 #
760 # Revision 1.23 2001/09/10 09:47:18 richard
761 # Fixed bug in the generation of links to Link/Multilink in indexes.
762 # (thanks Hubert Hoegl)
763 # Added AssignedTo to the "classic" schema's item page.
764 #
765 # Revision 1.22 2001/08/30 06:01:17 richard
766 # Fixed missing import in mailgw :(
767 #
768 # Revision 1.21 2001/08/16 07:34:59 richard
769 # better CGI text searching - but hidden filter fields are disappearing...
770 #
771 # Revision 1.20 2001/08/15 23:43:18 richard
772 # Fixed some isFooTypes that I missed.
773 # Refactored some code in the CGI code.
774 #
775 # Revision 1.19 2001/08/12 06:32:36 richard
776 # using isinstance(blah, Foo) now instead of isFooType
777 #
778 # Revision 1.18 2001/08/07 00:24:42 richard
779 # stupid typo
780 #
781 # Revision 1.17 2001/08/07 00:15:51 richard
782 # Added the copyright/license notice to (nearly) all files at request of
783 # Bizar Software.
784 #
785 # Revision 1.16 2001/08/01 03:52:23 richard
786 # Checklist was using wrong name.
787 #
788 # Revision 1.15 2001/07/30 08:12:17 richard
789 # Added time logging and file uploading to the templates.
790 #
791 # Revision 1.14 2001/07/30 06:17:45 richard
792 # Features:
793 # . Added ability for cgi newblah forms to indicate that the new node
794 # should be linked somewhere.
795 # Fixed:
796 # . Fixed the agument handling for the roundup-admin find command.
797 # . Fixed handling of summary when no note supplied for newblah. Again.
798 # . Fixed detection of no form in htmltemplate Field display.
799 #
800 # Revision 1.13 2001/07/30 02:37:53 richard
801 # Temporary measure until we have decent schema migration.
802 #
803 # Revision 1.12 2001/07/30 01:24:33 richard
804 # Handles new node display now.
805 #
806 # Revision 1.11 2001/07/29 09:31:35 richard
807 # oops
808 #
809 # Revision 1.10 2001/07/29 09:28:23 richard
810 # Fixed sorting by clicking on column headings.
811 #
812 # Revision 1.9 2001/07/29 08:27:40 richard
813 # Fixed handling of passed-in values in form elements (ie. during a
814 # drill-down)
815 #
816 # Revision 1.8 2001/07/29 07:01:39 richard
817 # Added vim command to all source so that we don't get no steenkin' tabs :)
818 #
819 # Revision 1.7 2001/07/29 05:36:14 richard
820 # Cleanup of the link label generation.
821 #
822 # Revision 1.6 2001/07/29 04:06:42 richard
823 # Fixed problem in link display when Link value is None.
824 #
825 # Revision 1.5 2001/07/28 08:17:09 richard
826 # fixed use of stylesheet
827 #
828 # Revision 1.4 2001/07/28 07:59:53 richard
829 # Replaced errno integers with their module values.
830 # De-tabbed templatebuilder.py
831 #
832 # Revision 1.3 2001/07/25 03:39:47 richard
833 # Hrm - displaying links to classes that don't specify a key property. I've
834 # got it defaulting to 'name', then 'title' and then a "random" property (first
835 # one returned by getprops().keys().
836 # Needs to be moved onto the Class I think...
837 #
838 # Revision 1.2 2001/07/22 12:09:32 richard
839 # Final commit of Grande Splite
840 #
841 # Revision 1.1 2001/07/22 11:58:35 richard
842 # More Grande Splite
843 #
844 #
845 # vim: set filetype=python ts=4 sw=4 et si