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