1ce86ca759890ee3297bce19362493ed7122a69d
1 # $Id: htmltemplate.py,v 1.15 2001-07-30 08:12:17 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, propclass.classname, 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]'
358 #
359 # INDEX TEMPLATES
360 #
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)
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%%"> </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> </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> </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> </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> </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%"> </td>')
615 w('<td colspan="%s">'%len(names))
616 w('<input type="submit" value="Redisplay"></td></tr>')
617 w('</table>')
618 w('</form>')
621 #
622 # ITEM TEMPLATES
623 #
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>')
724 #
725 # $Log: not supported by cvs2svn $
726 # Revision 1.14 2001/07/30 06:17:45 richard
727 # Features:
728 # . Added ability for cgi newblah forms to indicate that the new node
729 # should be linked somewhere.
730 # Fixed:
731 # . Fixed the agument handling for the roundup-admin find command.
732 # . Fixed handling of summary when no note supplied for newblah. Again.
733 # . Fixed detection of no form in htmltemplate Field display.
734 #
735 # Revision 1.13 2001/07/30 02:37:53 richard
736 # Temporary measure until we have decent schema migration.
737 #
738 # Revision 1.12 2001/07/30 01:24:33 richard
739 # Handles new node display now.
740 #
741 # Revision 1.11 2001/07/29 09:31:35 richard
742 # oops
743 #
744 # Revision 1.10 2001/07/29 09:28:23 richard
745 # Fixed sorting by clicking on column headings.
746 #
747 # Revision 1.9 2001/07/29 08:27:40 richard
748 # Fixed handling of passed-in values in form elements (ie. during a
749 # drill-down)
750 #
751 # Revision 1.8 2001/07/29 07:01:39 richard
752 # Added vim command to all source so that we don't get no steenkin' tabs :)
753 #
754 # Revision 1.7 2001/07/29 05:36:14 richard
755 # Cleanup of the link label generation.
756 #
757 # Revision 1.6 2001/07/29 04:06:42 richard
758 # Fixed problem in link display when Link value is None.
759 #
760 # Revision 1.5 2001/07/28 08:17:09 richard
761 # fixed use of stylesheet
762 #
763 # Revision 1.4 2001/07/28 07:59:53 richard
764 # Replaced errno integers with their module values.
765 # De-tabbed templatebuilder.py
766 #
767 # Revision 1.3 2001/07/25 03:39:47 richard
768 # Hrm - displaying links to classes that don't specify a key property. I've
769 # got it defaulting to 'name', then 'title' and then a "random" property (first
770 # one returned by getprops().keys().
771 # Needs to be moved onto the Class I think...
772 #
773 # Revision 1.2 2001/07/22 12:09:32 richard
774 # Final commit of Grande Splite
775 #
776 # Revision 1.1 2001/07/22 11:58:35 richard
777 # More Grande Splite
778 #
779 #
780 # vim: set filetype=python ts=4 sw=4 et si