Code

036ad190cc67a1b66a9b85bbd05aab1c6b07672f
[roundup.git] / roundup / htmltemplate.py
1 # $Id: htmltemplate.py,v 1.8 2001-07-29 07:01:39 richard Exp $
3 import os, re, StringIO, urllib, cgi, errno
5 import hyperdb, date
7 class Base:
8     def __init__(self, db, templates, classname, nodeid=None, form=None):
9         # TODO: really not happy with the way templates is passed on here
10         self.db, self.templates = db, templates
11         self.classname, self.nodeid = classname, nodeid
12         self.form = form
13         self.cl = self.db.classes[self.classname]
14         self.properties = self.cl.getprops()
16 class Plain(Base):
17     ''' display a String property directly;
19         display a Date property in a specified time zone with an option to
20         omit the time from the date stamp;
22         for a Link or Multilink property, display the key strings of the
23         linked nodes (or the ids if the linked class has no key property)
24     '''
25     def __call__(self, property):
26         if not self.nodeid and self.form is None:
27             return '[Field: not called from item]'
28         propclass = self.properties[property]
29         if self.nodeid:
30             value = self.cl.get(self.nodeid, property)
31         else:
32             # TODO: pull the value from the form
33             if propclass.isMultilinkType: value = []
34             else: value = ''
35         if propclass.isStringType:
36             if value is None: value = ''
37             else: value = str(value)
38         elif propclass.isDateType:
39             value = str(value)
40         elif propclass.isIntervalType:
41             value = str(value)
42         elif propclass.isLinkType:
43             linkcl = self.db.classes[propclass.classname]
44             k = linkcl.labelprop()
45             if value: value = str(linkcl.get(value, k))
46             else: value = '[unselected]'
47         elif propclass.isMultilinkType:
48             linkcl = self.db.classes[propclass.classname]
49             k = linkcl.labelprop()
50             value = ', '.join([linkcl.get(i, k) for i in value])
51         else:
52             s = 'Plain: bad propclass "%s"'%propclass
53         return value
55 class Field(Base):
56     ''' display a property like the plain displayer, but in a text field
57         to be edited
58     '''
59     def __call__(self, property, size=None, height=None, showid=0):
60         if not self.nodeid and self.form is None:
61             return '[Field: not called from item]'
62         propclass = self.properties[property]
63         if self.nodeid:
64             value = self.cl.get(self.nodeid, property)
65         else:
66             # TODO: pull the value from the form
67             if propclass.isMultilinkType: value = []
68             else: value = ''
69         if (propclass.isStringType or propclass.isDateType or
70                 propclass.isIntervalType):
71             size = size or 30
72             if value is None:
73                 value = ''
74             else:
75                 value = cgi.escape(value)
76                 value = '"'.join(value.split('"'))
77             s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
78         elif propclass.isLinkType:
79             linkcl = self.db.classes[propclass.classname]
80             l = ['<select name="%s">'%property]
81             k = linkcl.labelprop()
82             for optionid in linkcl.list():
83                 option = linkcl.get(optionid, k)
84                 s = ''
85                 if optionid == value:
86                     s = 'selected '
87                 if showid:
88                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
89                 else:
90                     lab = option
91                 if size is not None and len(lab) > size:
92                     lab = lab[:size-3] + '...'
93                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
94             l.append('</select>')
95             s = '\n'.join(l)
96         elif propclass.isMultilinkType:
97             linkcl = self.db.classes[propclass.classname]
98             list = linkcl.list()
99             height = height or min(len(list), 7)
100             l = ['<select multiple name="%s" size="%s">'%(property, height)]
101             k = linkcl.labelprop()
102             for optionid in list:
103                 option = linkcl.get(optionid, k)
104                 s = ''
105                 if optionid in value:
106                     s = 'selected '
107                 if showid:
108                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
109                 else:
110                     lab = option
111                 if size is not None and len(lab) > size:
112                     lab = lab[:size-3] + '...'
113                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
114             l.append('</select>')
115             s = '\n'.join(l)
116         else:
117             s = 'Plain: bad propclass "%s"'%propclass
118         return s
120 class Menu(Base):
121     ''' for a Link property, display a menu of the available choices
122     '''
123     def __call__(self, property, size=None, height=None, showid=0):
124         propclass = self.properties[property]
125         if self.nodeid:
126             value = self.cl.get(self.nodeid, property)
127         else:
128             # TODO: pull the value from the form
129             if propclass.isMultilinkType: value = []
130             else: value = None
131         if propclass.isLinkType:
132             linkcl = self.db.classes[propclass.classname]
133             l = ['<select name="%s">'%property]
134             k = linkcl.labelprop()
135             for optionid in linkcl.list():
136                 option = linkcl.get(optionid, k)
137                 s = ''
138                 if optionid == value:
139                     s = 'selected '
140                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
141             l.append('</select>')
142             return '\n'.join(l)
143         if propclass.isMultilinkType:
144             linkcl = self.db.classes[propclass.classname]
145             list = linkcl.list()
146             height = height or min(len(list), 7)
147             l = ['<select multiple name="%s" size="%s">'%(property, height)]
148             k = linkcl.labelprop()
149             for optionid in list:
150                 option = linkcl.get(optionid, k)
151                 s = ''
152                 if optionid in value:
153                     s = 'selected '
154                 if showid:
155                     lab = '%s%s: %s'%(propclass.classname, optionid, option)
156                 else:
157                     lab = option
158                 if size is not None and len(lab) > size:
159                     lab = lab[:size-3] + '...'
160                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
161             l.append('</select>')
162             return '\n'.join(l)
163         return '[Menu: not a link]'
165 #XXX deviates from spec
166 class Link(Base):
167     ''' for a Link or Multilink property, display the names of the linked
168         nodes, hyperlinked to the item views on those nodes
169         for other properties, link to this node with the property as the text
170     '''
171     def __call__(self, property=None, **args):
172         if not self.nodeid and self.form is None:
173             return '[Link: not called from item]'
174         propclass = self.properties[property]
175         if self.nodeid:
176             value = self.cl.get(self.nodeid, property)
177         else:
178             if propclass.isMultilinkType: value = []
179             else: value = ''
180         if propclass.isLinkType:
181             if value is None:
182                 return '[not assigned]'
183             linkcl = self.db.classes[propclass.classname]
184             k = linkcl.labelprop()
185             linkvalue = linkcl.get(value, k)
186             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
187         if propclass.isMultilinkType:
188             linkcl = self.db.classes[propclass.classname]
189             k = linkcl.labelprop()
190             l = []
191             for value in value:
192                 linkvalue = linkcl.get(value, k)
193                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
194             return ', '.join(l)
195         return '<a href="%s%s">%s</a>'%(self.classname, self.nodeid, value)
197 class Count(Base):
198     ''' for a Multilink property, display a count of the number of links in
199         the list
200     '''
201     def __call__(self, property, **args):
202         if not self.nodeid:
203             return '[Count: not called from item]'
204         propclass = self.properties[property]
205         value = self.cl.get(self.nodeid, property)
206         if propclass.isMultilinkType:
207             return str(len(value))
208         return '[Count: not a Multilink]'
210 # XXX pretty is definitely new ;)
211 class Reldate(Base):
212     ''' display a Date property in terms of an interval relative to the
213         current date (e.g. "+ 3w", "- 2d").
215         with the 'pretty' flag, make it pretty
216     '''
217     def __call__(self, property, pretty=0):
218         if not self.nodeid and self.form is None:
219             return '[Reldate: not called from item]'
220         propclass = self.properties[property]
221         if not propclass.isDateType:
222             return '[Reldate: not a Date]'
223         if self.nodeid:
224             value = self.cl.get(self.nodeid, property)
225         else:
226             value = date.Date('.')
227         interval = value - date.Date('.')
228         if pretty:
229             if not self.nodeid:
230                 return 'now'
231             pretty = interval.pretty()
232             if pretty is None:
233                 pretty = value.pretty()
234             return pretty
235         return str(interval)
237 class Download(Base):
238     ''' show a Link("file") or Multilink("file") property using links that
239         allow you to download files
240     '''
241     def __call__(self, property, **args):
242         if not self.nodeid:
243             return '[Download: not called from item]'
244         propclass = self.properties[property]
245         value = self.cl.get(self.nodeid, property)
246         if propclass.isLinkType:
247             linkcl = self.db.classes[propclass.classname]
248             linkvalue = linkcl.get(value, k)
249             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
250         if propclass.isMultilinkType:
251             linkcl = self.db.classes[propclass.classname]
252             l = []
253             for value in value:
254                 linkvalue = linkcl.get(value, k)
255                 l.append('<a href="%s%s">%s</a>'%(linkcl, value, linkvalue))
256             return ', '.join(l)
257         return '[Download: not a link]'
260 class Checklist(Base):
261     ''' for a Link or Multilink property, display checkboxes for the available
262         choices to permit filtering
263     '''
264     def __call__(self, property, **args):
265         propclass = self.properties[property]
266         if self.nodeid:
267             value = self.cl.get(self.nodeid, property)
268         else:
269             value = []
270         if propclass.isLinkType or propclass.isMultilinkType:
271             linkcl = self.db.classes[propclass.classname]
272             l = []
273             k = linkcl.labelprop()
274             for optionid in linkcl.list():
275                 option = linkcl.get(optionid, k)
276                 if optionid in value:
277                     checked = 'checked'
278                 else:
279                     checked = ''
280                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
281                     option, checked, propclass.classname, option))
282             return '\n'.join(l)
283         return '[Checklist: not a link]'
285 class Note(Base):
286     ''' display a "note" field, which is a text area for entering a note to
287         go along with a change. 
288     '''
289     def __call__(self, rows=5, cols=80):
290        # TODO: pull the value from the form
291         return '<textarea name="__note" rows=%s cols=%s></textarea>'%(rows,
292             cols)
294 # XXX new function
295 class List(Base):
296     ''' list the items specified by property using the standard index for
297         the class
298     '''
299     def __call__(self, property, **args):
300         propclass = self.properties[property]
301         if not propclass.isMultilinkType:
302             return '[List: not a Multilink]'
303         fp = StringIO.StringIO()
304         args['show_display_form'] = 0
305         value = self.cl.get(self.nodeid, property)
306         # TODO: really not happy with the way templates is passed on here
307         index(fp, self.templates, self.db, propclass.classname, nodeids=value,
308             show_display_form=0)
309         return fp.getvalue()
311 # XXX new function
312 class History(Base):
313     ''' list the history of the item
314     '''
315     def __call__(self, **args):
316         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
317             '<tr class="list-header">',
318             '<td><span class="list-item"><strong>Date</strong></span></td>',
319             '<td><span class="list-item"><strong>User</strong></span></td>',
320             '<td><span class="list-item"><strong>Action</strong></span></td>',
321             '<td><span class="list-item"><strong>Args</strong></span></td>']
323         for id, date, user, action, args in self.cl.history(self.nodeid):
324             l.append('<tr><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>'%(
325                date, user, action, args))
326         l.append('</table>')
327         return '\n'.join(l)
329 # XXX new function
330 class Submit(Base):
331     ''' add a submit button for the item
332     '''
333     def __call__(self):
334         if self.nodeid:
335             return '<input type="submit" value="Submit Changes">'
336         elif self.form is not None:
337             return '<input type="submit" value="Submit New Entry">'
338         else:
339             return '[Submit: not called from item]'
343 #   INDEX TEMPLATES
345 class IndexTemplateReplace:
346     def __init__(self, globals, locals, props):
347         self.globals = globals
348         self.locals = locals
349         self.props = props
351     def go(self, text, replace=re.compile(
352             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
353             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
354         return replace.sub(self, text)
355         
356     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
357         if m.group('name'):
358             if m.group('name') in self.props:
359                 text = m.group('text')
360                 replace = IndexTemplateReplace(self.globals, {}, self.props)
361                 return replace.go(m.group('text'))
362             else:
363                 return ''
364         if m.group('display'):
365             command = m.group('command')
366             return eval(command, self.globals, self.locals)
367         print '*** unhandled match', m.groupdict()
369 def sortby(sort_name, columns, filter, sort, group, filterspec):
370     l = []
371     w = l.append
372     for k, v in filterspec.items():
373         k = urllib.quote(k)
374         if type(v) == type([]):
375             w('%s=%s'%(k, ','.join(map(urllib.quote, v))))
376         else:
377             w('%s=%s'%(k, urllib.quote(v)))
378     if columns:
379         w(':columns=%s'%','.join(map(urllib.quote, columns)))
380     if filter:
381         w(':filter=%s'%','.join(map(urllib.quote, filter)))
382     if group:
383         w(':group=%s'%','.join(map(urllib.quote, group)))
384     m = []
385     s_dir = ''
386     for name in sort:
387         dir = name[0]
388         if dir == '-':
389             dir = ''
390         else:
391             name = name[1:]
392         if sort_name == name:
393             if dir == '':
394                 s_dir = '-'
395             elif dir == '-':
396                 s_dir = ''
397         else:
398             m.append(dir+urllib.quote(name))
399     m.insert(0, s_dir+urllib.quote(sort_name))
400     # so things don't get completely out of hand, limit the sort to two columns
401     w(':sort=%s'%','.join(m[:2]))
402     return '&'.join(l)
404 def index(client, templates, db, classname, filterspec={}, filter=[],
405         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
406         col_re=re.compile(r'<property\s+name="([^>]+)">')):
407     globals = {
408         'plain': Plain(db, templates, classname, form={}),
409         'field': Field(db, templates, classname, form={}),
410         'menu': Menu(db, templates, classname, form={}),
411         'link': Link(db, templates, classname, form={}),
412         'count': Count(db, templates, classname, form={}),
413         'reldate': Reldate(db, templates, classname, form={}),
414         'download': Download(db, templates, classname, form={}),
415         'checklist': Checklist(db, templates, classname, form={}),
416         'list': List(db, templates, classname, form={}),
417         'history': History(db, templates, classname, form={}),
418         'submit': Submit(db, templates, classname, form={}),
419         'note': Note(db, templates, classname, form={})
420     }
421     cl = db.classes[classname]
422     properties = cl.getprops()
423     w = client.write
425     try:
426         template = open(os.path.join(templates, classname+'.filter')).read()
427         all_filters = col_re.findall(template)
428     except IOError, error:
429         if error.errno != errno.ENOENT: raise
430         template = None
431         all_filters = []
432     if template and filter:
433         # display the filter section
434         w('<form>')
435         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
436         w('<tr class="location-bar">')
437         w(' <th align="left" colspan="2">Filter specification...</th>')
438         w('</tr>')
439         replace = IndexTemplateReplace(globals, locals(), filter)
440         w(replace.go(template))
441         if columns:
442             w('<input type="hidden" name=":columns" value="%s">'%','.join(columns))
443         if filter:
444             w('<input type="hidden" name=":filter" value="%s">'%','.join(filter))
445         if sort:
446             w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
447         if group:
448             w('<input type="hidden" name=":group" value="%s">'%','.join(group))
449         for k, v in filterspec.items():
450             if type(v) == type([]): v = ','.join(v)
451             w('<input type="hidden" name="%s" value="%s">'%(k, v))
452         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
453         w('<td><input type="submit" value="Redisplay"></td></tr>')
454         w('</table>')
455         w('</form>')
457     # XXX deviate from spec here ...
458     # load the index section template and figure the default columns from it
459     template = open(os.path.join(templates, classname+'.index')).read()
460     all_columns = col_re.findall(template)
461     if not columns:
462         columns = []
463         for name in all_columns:
464             columns.append(name)
465     else:
466         # re-sort columns to be the same order as all_columns
467         l = []
468         for name in all_columns:
469             if name in columns:
470                 l.append(name)
471         columns = l
473     # now display the index section
474     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
475     w('<tr class="list-header">')
476     for name in columns:
477         cname = name.capitalize()
478         if show_display_form:
479             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
480                 sort, group, filterspec))
481             w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
482                 anchor, cname))
483         else:
484             w('<td><span class="list-item">%s</span></td>'%cname)
485     w('</tr>')
487     # this stuff is used for group headings - optimise the group names
488     old_group = None
489     group_names = []
490     if group:
491         for name in group:
492             if name[0] == '-': group_names.append(name[1:])
493             else: group_names.append(name)
495     # now actually loop through all the nodes we get from the filter and
496     # apply the template
497     if nodeids is None:
498         nodeids = cl.filter(filterspec, sort, group)
499     for nodeid in nodeids:
500         # check for a group heading
501         if group_names:
502             this_group = [cl.get(nodeid, name) for name in group_names]
503             if this_group != old_group:
504                 l = []
505                 for name in group_names:
506                     prop = properties[name]
507                     if prop.isLinkType:
508                         group_cl = db.classes[prop.classname]
509                         key = group_cl.getkey()
510                         value = cl.get(nodeid, name)
511                         if value is None:
512                             l.append('[unselected %s]'%prop.classname)
513                         else:
514                             l.append(group_cl.get(cl.get(nodeid, name), key))
515                     elif prop.isMultilinkType:
516                         group_cl = db.classes[prop.classname]
517                         key = group_cl.getkey()
518                         for value in cl.get(nodeid, name):
519                             l.append(group_cl.get(value, key))
520                     else:
521                         value = cl.get(nodeid, name)
522                         if value is None:
523                             value = '[empty %s]'%name
524                         l.append(value)
525                 w('<tr class="section-bar">'
526                   '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
527                     len(columns), ', '.join(l)))
528                 old_group = this_group
530         # display this node's row
531         for value in globals.values():
532             if hasattr(value, 'nodeid'):
533                 value.nodeid = nodeid
534         replace = IndexTemplateReplace(globals, locals(), columns)
535         w(replace.go(template))
537     w('</table>')
539     if not show_display_form:
540         return
542     # now add in the filter/columns/group/etc config table form
543     w('<p><form>')
544     w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
545     for k,v in filterspec.items():
546         if type(v) == type([]): v = ','.join(v)
547         w('<input type="hidden" name="%s" value="%s">'%(k, v))
548     if sort:
549         w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
550     names = []
551     for name in cl.getprops().keys():
552         if name in all_filters or name in all_columns:
553             names.append(name)
554     w('<tr class="location-bar">')
555     w('<th align="left" colspan=%s>View customisation...</th></tr>'%
556         (len(names)+1))
557     w('<tr class="location-bar"><th>&nbsp;</th>')
558     for name in names:
559         w('<th>%s</th>'%name.capitalize())
560     w('</tr>')
562     # filter
563     if all_filters:
564         w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
565         for name in names:
566             if name not in all_filters:
567                 w('<td>&nbsp;</td>')
568                 continue
569             if name in filter: checked=' checked'
570             else: checked=''
571             w('<td align=middle>')
572             w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
573                 checked))
574         w('</tr>')
576     # columns
577     if all_columns:
578         w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
579         for name in names:
580             if name not in all_columns:
581                 w('<td>&nbsp;</td>')
582                 continue
583             if name in columns: checked=' checked'
584             else: checked=''
585             w('<td align=middle>')
586             w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
587                 name, checked))
588         w('</tr>')
590         # group
591         w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
592         for name in names:
593             prop = properties[name]
594             if name not in all_columns:
595                 w('<td>&nbsp;</td>')
596                 continue
597             if name in group: checked=' checked'
598             else: checked=''
599             w('<td align=middle>')
600             w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
601                 name, checked))
602         w('</tr>')
604     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
605     w('<td colspan="%s">'%len(names))
606     w('<input type="submit" value="Redisplay"></td></tr>')
607     w('</table>')
608     w('</form>')
612 #   ITEM TEMPLATES
614 class ItemTemplateReplace:
615     def __init__(self, globals, locals, cl, nodeid):
616         self.globals = globals
617         self.locals = locals
618         self.cl = cl
619         self.nodeid = nodeid
621     def go(self, text, replace=re.compile(
622             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
623             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
624         return replace.sub(self, text)
626     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
627         if m.group('name'):
628             if self.nodeid and self.cl.get(self.nodeid, m.group('name')):
629                 replace = ItemTemplateReplace(self.globals, {}, self.cl,
630                     self.nodeid)
631                 return replace.go(m.group('text'))
632             else:
633                 return ''
634         if m.group('display'):
635             command = m.group('command')
636             return eval(command, self.globals, self.locals)
637         print '*** unhandled match', m.groupdict()
639 def item(client, templates, db, classname, nodeid, replace=re.compile(
640             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
641             r'(?P<endprop></property>)|'
642             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
644     globals = {
645         'plain': Plain(db, templates, classname, nodeid),
646         'field': Field(db, templates, classname, nodeid),
647         'menu': Menu(db, templates, classname, nodeid),
648         'link': Link(db, templates, classname, nodeid),
649         'count': Count(db, templates, classname, nodeid),
650         'reldate': Reldate(db, templates, classname, nodeid),
651         'download': Download(db, templates, classname, nodeid),
652         'checklist': Checklist(db, templates, classname, nodeid),
653         'list': List(db, templates, classname, nodeid),
654         'history': History(db, templates, classname, nodeid),
655         'submit': Submit(db, templates, classname, nodeid),
656         'note': Note(db, templates, classname, nodeid)
657     }
659     cl = db.classes[classname]
660     properties = cl.getprops()
662     if properties.has_key('type') and properties.has_key('content'):
663         pass
664         # XXX we really want to return this as a downloadable...
665         #  currently I handle this at a higher level by detecting 'file'
666         #  designators...
668     w = client.write
669     w('<form action="%s%s">'%(classname, nodeid))
670     s = open(os.path.join(templates, classname+'.item')).read()
671     replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
672     w(replace.go(s))
673     w('</form>')
676 def newitem(client, templates, db, classname, form, replace=re.compile(
677             r'((?P<prop><property\s+name="(?P<propname>[^>]+)">)|'
678             r'(?P<endprop></property>)|'
679             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I)):
680     globals = {
681         'plain': Plain(db, templates, classname, form=form),
682         'field': Field(db, templates, classname, form=form),
683         'menu': Menu(db, templates, classname, form=form),
684         'link': Link(db, templates, classname, form=form),
685         'count': Count(db, templates, classname, form=form),
686         'reldate': Reldate(db, templates, classname, form=form),
687         'download': Download(db, templates, classname, form=form),
688         'checklist': Checklist(db, templates, classname, form=form),
689         'list': List(db, templates, classname, form=form),
690         'history': History(db, templates, classname, form=form),
691         'submit': Submit(db, templates, classname, form=form),
692         'note': Note(db, templates, classname, form=form)
693     }
695     cl = db.classes[classname]
696     properties = cl.getprops()
698     w = client.write
699     try:
700         s = open(os.path.join(templates, classname+'.newitem')).read()
701     except:
702         s = open(os.path.join(templates, classname+'.item')).read()
703     w('<form action="new%s">'%classname)
704     replace = ItemTemplateReplace(globals, locals(), None, None)
705     w(replace.go(s))
706     w('</form>')
709 # $Log: not supported by cvs2svn $
710 # Revision 1.7  2001/07/29 05:36:14  richard
711 # Cleanup of the link label generation.
713 # Revision 1.6  2001/07/29 04:06:42  richard
714 # Fixed problem in link display when Link value is None.
716 # Revision 1.5  2001/07/28 08:17:09  richard
717 # fixed use of stylesheet
719 # Revision 1.4  2001/07/28 07:59:53  richard
720 # Replaced errno integers with their module values.
721 # De-tabbed templatebuilder.py
723 # Revision 1.3  2001/07/25 03:39:47  richard
724 # Hrm - displaying links to classes that don't specify a key property. I've
725 # got it defaulting to 'name', then 'title' and then a "random" property (first
726 # one returned by getprops().keys().
727 # Needs to be moved onto the Class I think...
729 # Revision 1.2  2001/07/22 12:09:32  richard
730 # Final commit of Grande Splite
732 # Revision 1.1  2001/07/22 11:58:35  richard
733 # More Grande Splite
736 # vim: set filetype=python ts=4 sw=4 et si