Code

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