Code

1a8e1d91efb3e4901de092a4b8c33835902b7111
[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.20 2001-08-15 23:43: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             if value is None:
211                 return '[not assigned]'
212             linkcl = self.db.classes[propclass.classname]
213             k = linkcl.labelprop()
214             linkvalue = linkcl.get(value, k)
215             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
216         if isinstance(propclass, hyperdb.Multilink):
217             linkcl = self.db.classes[propclass.classname]
218             k = linkcl.labelprop()
219             l = []
220             for value in value:
221                 linkvalue = linkcl.get(value, k)
222                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
223             return ', '.join(l)
224         return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
226 class Count(Base):
227     ''' for a Multilink property, display a count of the number of links in
228         the list
229     '''
230     def __call__(self, property, **args):
231         if not self.nodeid:
232             return '[Count: not called from item]'
233         propclass = self.properties[property]
234         value = self.cl.get(self.nodeid, property)
235         if isinstance(propclass, hyperdb.Multilink):
236             return str(len(value))
237         return '[Count: not a Multilink]'
239 # XXX pretty is definitely new ;)
240 class Reldate(Base):
241     ''' display a Date property in terms of an interval relative to the
242         current date (e.g. "+ 3w", "- 2d").
244         with the 'pretty' flag, make it pretty
245     '''
246     def __call__(self, property, pretty=0):
247         if not self.nodeid and self.form is None:
248             return '[Reldate: not called from item]'
249         propclass = self.properties[property]
250         if isinstance(not propclass, hyperdb.Date):
251             return '[Reldate: not a Date]'
252         if self.nodeid:
253             value = self.cl.get(self.nodeid, property)
254         else:
255             value = date.Date('.')
256         interval = value - date.Date('.')
257         if pretty:
258             if not self.nodeid:
259                 return 'now'
260             pretty = interval.pretty()
261             if pretty is None:
262                 pretty = value.pretty()
263             return pretty
264         return str(interval)
266 class Download(Base):
267     ''' show a Link("file") or Multilink("file") property using links that
268         allow you to download files
269     '''
270     def __call__(self, property, **args):
271         if not self.nodeid:
272             return '[Download: not called from item]'
273         propclass = self.properties[property]
274         value = self.cl.get(self.nodeid, property)
275         if isinstance(propclass, hyperdb.Link):
276             linkcl = self.db.classes[propclass.classname]
277             linkvalue = linkcl.get(value, k)
278             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
279         if isinstance(propclass, hyperdb.Multilink):
280             linkcl = self.db.classes[propclass.classname]
281             l = []
282             for value in value:
283                 linkvalue = linkcl.get(value, k)
284                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
285             return ', '.join(l)
286         return '[Download: not a link]'
289 class Checklist(Base):
290     ''' for a Link or Multilink property, display checkboxes for the available
291         choices to permit filtering
292     '''
293     def __call__(self, property, **args):
294         propclass = self.properties[property]
295         if self.nodeid:
296             value = self.cl.get(self.nodeid, property)
297         elif self.filterspec is not None:
298             value = self.filterspec.get(property, [])
299         else:
300             value = []
301         if (isinstance(propclass, hyperdb.Link) or
302                 isinstance(propclass, hyperdb.Multilink)):
303             linkcl = self.db.classes[propclass.classname]
304             l = []
305             k = linkcl.labelprop()
306             for optionid in linkcl.list():
307                 option = linkcl.get(optionid, k)
308                 if optionid in value or option in value:
309                     checked = 'checked'
310                 else:
311                     checked = ''
312                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
313                     option, checked, property, option))
314             return '\n'.join(l)
315         return '[Checklist: not a link]'
317 class Note(Base):
318     ''' display a "note" field, which is a text area for entering a note to
319         go along with a change. 
320     '''
321     def __call__(self, rows=5, cols=80):
322        # TODO: pull the value from the form
323         return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
324             cols)
326 # XXX new function
327 class List(Base):
328     ''' list the items specified by property using the standard index for
329         the class
330     '''
331     def __call__(self, property, **args):
332         propclass = self.properties[property]
333         if isinstance(not propclass, hyperdb.Multilink):
334             return '[List: not a Multilink]'
335         fp = StringIO.StringIO()
336         args['show_display_form'] = 0
337         value = self.cl.get(self.nodeid, property)
338         # TODO: really not happy with the way templates is passed on here
339         index(fp, self.templates, self.db, propclass.classname, nodeids=value,
340             show_display_form=0)
341         return fp.getvalue()
343 # XXX new function
344 class History(Base):
345     ''' list the history of the item
346     '''
347     def __call__(self, **args):
348         if self.nodeid is None:
349             return "[History: node doesn't exist]"
351         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
352             '<tr class="list-header">',
353             '<td><span class="list-item"><strong>Date</strong></span></td>',
354             '<td><span class="list-item"><strong>User</strong></span></td>',
355             '<td><span class="list-item"><strong>Action</strong></span></td>',
356             '<td><span class="list-item"><strong>Args</strong></span></td>']
358         for id, date, user, action, args in self.cl.history(self.nodeid):
359             l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
360                date, user, action, args))
361         l.append('</table>')
362         return '\n'.join(l)
364 # XXX new function
365 class Submit(Base):
366     ''' add a submit button for the item
367     '''
368     def __call__(self):
369         if self.nodeid:
370             return '<input type="submit" value="Submit Changes">'
371         elif self.form is not None:
372             return '<input type="submit" value="Submit New Entry">'
373         else:
374             return '[Submit: not called from item]'
378 #   INDEX TEMPLATES
380 class IndexTemplateReplace:
381     def __init__(self, globals, locals, props):
382         self.globals = globals
383         self.locals = locals
384         self.props = props
386     def go(self, text, replace=re.compile(
387             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
388             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
389         return replace.sub(self, text)
391     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
392         if m.group('name'):
393             if m.group('name') in self.props:
394                 text = m.group('text')
395                 replace = IndexTemplateReplace(self.globals, {}, self.props)
396                 return replace.go(m.group('text'))
397             else:
398                 return ''
399         if m.group('display'):
400             command = m.group('command')
401             return eval(command, self.globals, self.locals)
402         print '*** unhandled match', m.groupdict()
404 def sortby(sort_name, columns, filter, sort, group, filterspec):
405     l = []
406     w = l.append
407     for k, v in filterspec.items():
408         k = urllib.quote(k)
409         if type(v) == type([]):
410             w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
411         else:
412             w('%s=%s'%(k, urllib.quote(v)))
413     if columns:
414         w(':columns=%s'%','.join(map(urllib.quote, columns)))
415     if filter:
416         w(':filter=%s'%','.join(map(urllib.quote, filter)))
417     if group:
418         w(':group=%s'%','.join(map(urllib.quote, group)))
419     m = []
420     s_dir = ''
421     for name in sort:
422         dir = name[0]
423         if dir == '-':
424             name = name[1:]
425         else:
426             dir = ''
427         if sort_name == name:
428             if dir == '-':
429                 s_dir = ''
430             else:
431                 s_dir = '-'
432         else:
433             m.append(dir+urllib.quote(name))
434     m.insert(0, s_dir+urllib.quote(sort_name))
435     # so things don't get completely out of hand, limit the sort to two columns
436     w(':sort=%s'%','.join(m[:2]))
437     return '&'.join(l)
439 def index(client, templates, db, classname, filterspec={}, filter=[],
440         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
441         col_re=re.compile(r'<property\s+name="([^>]+)">')):
442     globals = {
443         'plain': Plain(db, templates, classname, filterspec=filterspec),
444         'field': Field(db, templates, classname, filterspec=filterspec),
445         'menu': Menu(db, templates, classname, filterspec=filterspec),
446         'link': Link(db, templates, classname, filterspec=filterspec),
447         'count': Count(db, templates, classname, filterspec=filterspec),
448         'reldate': Reldate(db, templates, classname, filterspec=filterspec),
449         'download': Download(db, templates, classname, filterspec=filterspec),
450         'checklist': Checklist(db, templates, classname, filterspec=filterspec),
451         'list': List(db, templates, classname, filterspec=filterspec),
452         'history': History(db, templates, classname, filterspec=filterspec),
453         'submit': Submit(db, templates, classname, filterspec=filterspec),
454         'note': Note(db, templates, classname, filterspec=filterspec)
455     }
456     cl = db.classes[classname]
457     properties = cl.getprops()
458     w = client.write
459     w('<form>')
461     try:
462         template = open(os.path.join(templates, classname+'.filter')).read()
463         all_filters = col_re.findall(template)
464     except IOError, error:
465         if error.errno != errno.ENOENT: raise
466         template = None
467         all_filters = []
468     if template and filter:
469         # display the filter section
470         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
471         w('<tr class="location-bar">')
472         w(' <th align="left" colspan="2">Filter specification...</th>')
473         w('</tr>')
474         replace = IndexTemplateReplace(globals, locals(), filter)
475         w(replace.go(template))
476         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
477         w('<td><input type="submit" value="Redisplay"></td></tr>')
478         w('</table>')
480     # If the filters aren't being displayed, then hide their current
481     # value in the form
482     if not filter:
483         for k, v in filterspec.items():
484             if type(v) == type([]): v = ','.join(v)
485             w('<input type="hidden" name="%s" value="%s">'%(k, v))
487     # make sure that the sorting doesn't get lost either
488     if sort:
489         w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
491     # XXX deviate from spec here ...
492     # load the index section template and figure the default columns from it
493     template = open(os.path.join(templates, classname+'.index')).read()
494     all_columns = col_re.findall(template)
495     if not columns:
496         columns = []
497         for name in all_columns:
498             columns.append(name)
499     else:
500         # re-sort columns to be the same order as all_columns
501         l = []
502         for name in all_columns:
503             if name in columns:
504                 l.append(name)
505         columns = l
507     # now display the index section
508     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
509     w('<tr class="list-header">')
510     for name in columns:
511         cname = name.capitalize()
512         if show_display_form:
513             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
514                 sort, group, filterspec))
515             w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
516                 anchor, cname))
517         else:
518             w('<td><span class="list-item">%s</span></td>'%cname)
519     w('</tr>')
521     # this stuff is used for group headings - optimise the group names
522     old_group = None
523     group_names = []
524     if group:
525         for name in group:
526             if name[0] == '-': group_names.append(name[1:])
527             else: group_names.append(name)
529     # now actually loop through all the nodes we get from the filter and
530     # apply the template
531     if nodeids is None:
532         nodeids = cl.filter(filterspec, sort, group)
533     for nodeid in nodeids:
534         # check for a group heading
535         if group_names:
536             this_group = [cl.get(nodeid, name) for name in group_names]
537             if this_group != old_group:
538                 l = []
539                 for name in group_names:
540                     prop = properties[name]
541                     if isinstance(prop, hyperdb.Link):
542                         group_cl = db.classes[prop.classname]
543                         key = group_cl.getkey()
544                         value = cl.get(nodeid, name)
545                         if value is None:
546                             l.append('[unselected %s]'%prop.classname)
547                         else:
548                             l.append(group_cl.get(cl.get(nodeid, name), key))
549                     elif isinstance(prop, hyperdb.Multilink):
550                         group_cl = db.classes[prop.classname]
551                         key = group_cl.getkey()
552                         for value in cl.get(nodeid, name):
553                             l.append(group_cl.get(value, key))
554                     else:
555                         value = cl.get(nodeid, name)
556                         if value is None:
557                             value = '[empty %s]'%name
558                         l.append(value)
559                 w('<tr class="section-bar">'
560                   '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
561                     len(columns), ', '.join(l)))
562                 old_group = this_group
564         # display this node's row
565         for value in globals.values():
566             if hasattr(value, 'nodeid'):
567                 value.nodeid = nodeid
568         replace = IndexTemplateReplace(globals, locals(), columns)
569         w(replace.go(template))
571     w('</table>')
573     if not show_display_form:
574         return
576     # now add in the filter/columns/group/etc config table form
577     w('<p>')
578     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
579     names = []
580     for name in cl.getprops().keys():
581         if name in all_filters or name in all_columns:
582             names.append(name)
583     w('<tr class="location-bar">')
584     w('<th align="left" colspan=%s>View customisation...</th></tr>'%
585         (len(names)+1))
586     w('<tr class="location-bar"><th>&nbsp;</th>')
587     for name in names:
588         w('<th>%s</th>'%name.capitalize())
589     w('</tr>')
591     # filter
592     if all_filters:
593         w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
594         for name in names:
595             if name not in all_filters:
596                 w('<td>&nbsp;</td>')
597                 continue
598             if name in filter: checked=' checked'
599             else: checked=''
600             w('<td align=middle>')
601             w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
602                 checked))
603         w('</tr>')
605     # columns
606     if all_columns:
607         w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
608         for name in names:
609             if name not in all_columns:
610                 w('<td>&nbsp;</td>')
611                 continue
612             if name in columns: checked=' checked'
613             else: checked=''
614             w('<td align=middle>')
615             w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
616                 name, checked))
617         w('</tr>')
619         # group
620         w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
621         for name in names:
622             prop = properties[name]
623             if name not in all_columns:
624                 w('<td>&nbsp;</td>')
625                 continue
626             if name in group: checked=' checked'
627             else: checked=''
628             w('<td align=middle>')
629             w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
630                 name, checked))
631         w('</tr>')
633     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
634     w('<td colspan="%s">'%len(names))
635     w('<input type="submit" value="Redisplay"></td></tr>')
636     w('</table>')
637     w('</form>')
641 #   ITEM TEMPLATES
643 class ItemTemplateReplace:
644     def __init__(self, globals, locals, cl, nodeid):
645         self.globals = globals
646         self.locals = locals
647         self.cl = cl
648         self.nodeid = nodeid
650     def go(self, text, replace=re.compile(
651             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
652             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
653         return replace.sub(self, text)
655     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
656         if m.group('name'):
657             if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
658                 replace = ItemTemplateReplace(self.globals, {}, self.cl,
659                     self.nodeid)
660                 return replace.go(m.group('text'))
661             else:
662                 return ''
663         if m.group('display'):
664             command = m.group('command')
665             return eval(command, self.globals, self.locals)
666         print '*** unhandled match', m.groupdict()
668 def item(client, templates, db, classname, nodeid, replace=re.compile(
669             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
670             r'(?P<endprop></property>)|'
671             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
673     globals = {
674         'plain': Plain(db, templates, classname, nodeid),
675         'field': Field(db, templates, classname, nodeid),
676         'menu': Menu(db, templates, classname, nodeid),
677         'link': Link(db, templates, classname, nodeid),
678         'count': Count(db, templates, classname, nodeid),
679         'reldate': Reldate(db, templates, classname, nodeid),
680         'download': Download(db, templates, classname, nodeid),
681         'checklist': Checklist(db, templates, classname, nodeid),
682         'list': List(db, templates, classname, nodeid),
683         'history': History(db, templates, classname, nodeid),
684         'submit': Submit(db, templates, classname, nodeid),
685         'note': Note(db, templates, classname, nodeid)
686     }
688     cl = db.classes[classname]
689     properties = cl.getprops()
691     if properties.has_key('type') and properties.has_key('content'):
692         pass
693         # XXX we really want to return this as a downloadable...
694         #  currently I handle this at a higher level by detecting 'file'
695         #  designators...
697     w = client.write
698     w('<form action="%s%s">'%(classname, nodeid))
699     s = open(os.path.join(templates, classname+'.item')).read()
700     replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
701     w(replace.go(s))
702     w('</form>')
705 def newitem(client, templates, db, classname, form, replace=re.compile(
706             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
707             r'(?P<endprop></property>)|'
708             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
709     globals = {
710         'plain': Plain(db, templates, classname, form=form),
711         'field': Field(db, templates, classname, form=form),
712         'menu': Menu(db, templates, classname, form=form),
713         'link': Link(db, templates, classname, form=form),
714         'count': Count(db, templates, classname, form=form),
715         'reldate': Reldate(db, templates, classname, form=form),
716         'download': Download(db, templates, classname, form=form),
717         'checklist': Checklist(db, templates, classname, form=form),
718         'list': List(db, templates, classname, form=form),
719         'history': History(db, templates, classname, form=form),
720         'submit': Submit(db, templates, classname, form=form),
721         'note': Note(db, templates, classname, form=form)
722     }
724     cl = db.classes[classname]
725     properties = cl.getprops()
727     w = client.write
728     try:
729         s = open(os.path.join(templates, classname+'.newitem')).read()
730     except:
731         s = open(os.path.join(templates, classname+'.item')).read()
732     w('<form action="new%s" method="POST" enctype="multipart/form-data">'%classname)
733     for key in form.keys():
734         if key[0] == ':':
735             value = form[key].value
736             if type(value) != type([]): value = [value]
737             for value in value:
738                 w('<input type="hidden" name="%s" value="%s">'%(key, value))
739     replace = ItemTemplateReplace(globals, locals(), None, None)
740     w(replace.go(s))
741     w('</form>')
744 # $Log: not supported by cvs2svn $
745 # Revision 1.19  2001/08/12 06:32:36  richard
746 # using isinstance(blah, Foo) now instead of isFooType
748 # Revision 1.18  2001/08/07 00:24:42  richard
749 # stupid typo
751 # Revision 1.17  2001/08/07 00:15:51  richard
752 # Added the copyright/license notice to (nearly) all files at request of
753 # Bizar Software.
755 # Revision 1.16  2001/08/01 03:52:23  richard
756 # Checklist was using wrong name.
758 # Revision 1.15  2001/07/30 08:12:17  richard
759 # Added time logging and file uploading to the templates.
761 # Revision 1.14  2001/07/30 06:17:45  richard
762 # Features:
763 #  . Added ability for cgi newblah forms to indicate that the new node
764 #    should be linked somewhere.
765 # Fixed:
766 #  . Fixed the agument handling for the roundup-admin find command.
767 #  . Fixed handling of summary when no note supplied for newblah. Again.
768 #  . Fixed detection of no form in htmltemplate Field display.
770 # Revision 1.13  2001/07/30 02:37:53  richard
771 # Temporary measure until we have decent schema migration.
773 # Revision 1.12  2001/07/30 01:24:33  richard
774 # Handles new node display now.
776 # Revision 1.11  2001/07/29 09:31:35  richard
777 # oops
779 # Revision 1.10  2001/07/29 09:28:23  richard
780 # Fixed sorting by clicking on column headings.
782 # Revision 1.9  2001/07/29 08:27:40  richard
783 # Fixed handling of passed-in values in form elements (ie. during a
784 # drill-down)
786 # Revision 1.8  2001/07/29 07:01:39  richard
787 # Added vim command to all source so that we don't get no steenkin' tabs :)
789 # Revision 1.7  2001/07/29 05:36:14  richard
790 # Cleanup of the link label generation.
792 # Revision 1.6  2001/07/29 04:06:42  richard
793 # Fixed problem in link display when Link value is None.
795 # Revision 1.5  2001/07/28 08:17:09  richard
796 # fixed use of stylesheet
798 # Revision 1.4  2001/07/28 07:59:53  richard
799 # Replaced errno integers with their module values.
800 # De-tabbed templatebuilder.py
802 # Revision 1.3  2001/07/25 03:39:47  richard
803 # Hrm - displaying links to classes that don't specify a key property. I've
804 # got it defaulting to 'name', then 'title' and then a "random" property (first
805 # one returned by getprops().keys().
806 # Needs to be moved onto the Class I think...
808 # Revision 1.2  2001/07/22 12:09:32  richard
809 # Final commit of Grande Splite
811 # Revision 1.1  2001/07/22 11:58:35  richard
812 # More Grande Splite
815 # vim: set filetype=python ts=4 sw=4 et si