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