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