Code

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