Code

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