Code

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