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