Code

b7cf387be5b728ad8cd0d0bf218369af088af920
[roundup.git] / roundup / template_funcs.py
1 import hyperdb, date, password
2 from i18n import _
3 import htmltemplate
4 import cgi, os, StringIO, urllib, types
7 def do_plain(client, classname, cl, props, nodeid, filterspec, property, escape=0, lookup=1):
8     ''' display a String property directly;
10         display a Date property in a specified time zone with an option to
11         omit the time from the date stamp;
13         for a Link or Multilink property, display the key strings of the
14         linked nodes (or the ids if the linked class has no key property)
15         when the lookup argument is true, otherwise just return the
16         linked ids
17     '''
18     if not nodeid and client.form is None:
19         return _('[Field: not called from item]')
20     propclass = props[property]
21     value = determine_value(cl, props, nodeid, filterspec, property)
22         
23     if isinstance(propclass, hyperdb.Password):
24         value = _('*encrypted*')
25     elif isinstance(propclass, hyperdb.Boolean):
26         value = value and "Yes" or "No"
27     elif isinstance(propclass, hyperdb.Link):
28         if value:
29             if lookup:
30                 linkcl = client.db.classes[propclass.classname]
31                 k = linkcl.labelprop(1)
32                 value = linkcl.get(value, k)
33         else:
34             value = _('[unselected]')
35     elif isinstance(propclass, hyperdb.Multilink):
36         if value:
37             if lookup:
38                 linkcl = client.db.classes[propclass.classname]
39                 k = linkcl.labelprop(1)
40                 labels = []
41                 for v in value:
42                     labels.append(linkcl.get(v, k))
43                 value = ', '.join(labels)
44             else:
45                 value = ', '.join(value)
46         else:
47             value = ''
48     else:
49         value = str(value)
50             
51     if escape:
52         value = cgi.escape(value)
53     return value
55 def do_stext(client, classname, cl, props, nodeid, filterspec, property, escape=0):
56     '''Render as structured text using the StructuredText module
57        (see above for details)
58     '''
59     s = do_plain(client, classname, cl, props, nodeid, filterspec, property, escape=escape)
60     if not StructuredText:
61         return s
62     return StructuredText(s,level=1,header=0)
64 def determine_value(cl, props, nodeid, filterspec, property):
65     '''determine the value of a property using the node, form or
66        filterspec
67     '''
68     if nodeid:
69         value = cl.get(nodeid, property, None)
70         if value is None:
71             if isinstance(props[property], hyperdb.Multilink):
72                 return []
73             return ''
74         return value
75     elif filterspec is not None:
76         if isinstance(props[property], hyperdb.Multilink):
77             return filterspec.get(property, [])
78         else:
79             return filterspec.get(property, '')
80     # TODO: pull the value from the form
81     if isinstance(props[property], hyperdb.Multilink):
82         return []
83     else:
84         return ''
86 def make_sort_function(client, filterspec, classname):
87     '''Make a sort function for a given class
88     '''
89     linkcl = client.db.getclass(classname)
90     if linkcl.getprops().has_key('order'):
91         sort_on = 'order'
92     else:
93         sort_on = linkcl.labelprop()
94     def sortfunc(a, b, linkcl=linkcl, sort_on=sort_on):
95         return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on))
96     return sortfunc
98 def do_field(client, classname, cl, props, nodeid, filterspec, property, size=None, showid=0):
99     ''' display a property like the plain displayer, but in a text field
100         to be edited
102         Note: if you would prefer an option list style display for
103         link or multilink editing, use menu().
104     '''
105     if not nodeid and client.form is None and filterspec is None:
106         return _('[Field: not called from item]')
107     if size is None:
108         size = 30
110     propclass = props[property]
112     # get the value
113     value = determine_value(cl, props, nodeid, filterspec, property)
114     # now display
115     if (isinstance(propclass, hyperdb.String) or
116             isinstance(propclass, hyperdb.Date) or
117             isinstance(propclass, hyperdb.Interval)):
118         if value is None:
119             value = ''
120         else:
121             value = cgi.escape(str(value))
122             value = '"'.join(value.split('"'))
123         s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
124     elif isinstance(propclass, hyperdb.Boolean):
125         checked = value and "checked" or ""
126         s = '<input type="radio" name="%s" value="yes" %s>Yes'%(property, checked)
127         if checked:
128             checked = ""
129         else:
130             checked = "checked"
131         s += '<input type="radio" name="%s" value="no" %s>No'%(property, checked)
132     elif isinstance(propclass, hyperdb.Number):
133         s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
134     elif isinstance(propclass, hyperdb.Password):
135         s = '<input type="password" name="%s" size="%s">'%(property, size)
136     elif isinstance(propclass, hyperdb.Link):
137         linkcl = client.db.getclass(propclass.classname)
138         if linkcl.getprops().has_key('order'):  
139             sort_on = 'order'  
140         else:  
141             sort_on = linkcl.labelprop()  
142         options = linkcl.filter(None, {}, [sort_on], []) 
143         # TODO: make this a field display, not a menu one!
144         l = ['<select name="%s">'%property]
145         k = linkcl.labelprop(1)
146         if value is None:
147             s = 'selected '
148         else:
149             s = ''
150         l.append(_('<option %svalue="-1">- no selection -</option>')%s)
151         for optionid in options:
152             option = linkcl.get(optionid, k)
153             s = ''
154             if optionid == value:
155                 s = 'selected '
156             if showid:
157                 lab = '%s%s: %s'%(propclass.classname, optionid, option)
158             else:
159                 lab = option
160             if size is not None and len(lab) > size:
161                 lab = lab[:size-3] + '...'
162             lab = cgi.escape(lab)
163             l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
164         l.append('</select>')
165         s = '\n'.join(l)
166     elif isinstance(propclass, hyperdb.Multilink):
167         sortfunc = make_sort_function(client, filterspec, propclass.classname)
168         linkcl = client.db.getclass(propclass.classname)
169         if value:
170             value.sort(sortfunc)
171         # map the id to the label property
172         if not showid:
173             k = linkcl.labelprop(1)
174             value = [linkcl.get(v, k) for v in value]
175         value = cgi.escape(','.join(value))
176         s = '<input name="%s" size="%s" value="%s">'%(property, size, value)
177     else:
178         s = _('Plain: bad propclass "%(propclass)s"')%locals()
179     return s
181 def do_multiline(client, classname, cl, props, nodeid, filterspec, property, rows=5, cols=40):
182     ''' display a string property in a multiline text edit field
183     '''
184     if not nodeid and client.form is None and filterspec is None:
185         return _('[Multiline: not called from item]')
187     propclass = props[property]
189     # make sure this is a link property
190     if not isinstance(propclass, hyperdb.String):
191         return _('[Multiline: not a string]')
193     # get the value
194     value = determine_value(cl, props, nodeid, filterspec, property)
195     if value is None:
196         value = ''
198     # display
199     return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
200         property, rows, cols, value)
202 def do_menu(client, classname, cl, props, nodeid, filterspec, property, size=None, height=None, showid=0,
203         additional=[], **conditions):
204     ''' For a Link/Multilink property, display a menu of the available
205         choices
207         If the additional properties are specified, they will be
208         included in the text of each option in (brackets, with, commas).
209     '''
210     if not nodeid and client.form is None and filterspec is None:
211         return _('[Field: not called from item]')
213     propclass = props[property]
215     # make sure this is a link property
216     if not (isinstance(propclass, hyperdb.Link) or
217             isinstance(propclass, hyperdb.Multilink)):
218         return _('[Menu: not a link]')
220     # sort function
221     sortfunc = make_sort_function(client, filterspec, propclass.classname)
223     # get the value
224     value = determine_value(cl, props, nodeid, filterspec, property)
226     # display
227     if isinstance(propclass, hyperdb.Multilink):
228         linkcl = client.db.getclass(propclass.classname)
229         if linkcl.getprops().has_key('order'):  
230             sort_on = 'order'  
231         else:  
232             sort_on = linkcl.labelprop()
233         options = linkcl.filter(None, conditions, [sort_on], []) 
234         height = height or min(len(options), 7)
235         l = ['<select multiple name="%s" size="%s">'%(property, height)]
236         k = linkcl.labelprop(1)
237         for optionid in options:
238             option = linkcl.get(optionid, k)
239             s = ''
240             if optionid in value or option in value:
241                 s = 'selected '
242             if showid:
243                 lab = '%s%s: %s'%(propclass.classname, optionid, option)
244             else:
245                 lab = option
246             if size is not None and len(lab) > size:
247                 lab = lab[:size-3] + '...'
248             if additional:
249                 m = []
250                 for propname in additional:
251                     m.append(linkcl.get(optionid, propname))
252                 lab = lab + ' (%s)'%', '.join(m)
253             lab = cgi.escape(lab)
254             l.append('<option %svalue="%s">%s</option>'%(s, optionid,
255                 lab))
256         l.append('</select>')
257         return '\n'.join(l)
258     if isinstance(propclass, hyperdb.Link):
259         # force the value to be a single choice
260         if type(value) is types.ListType:
261             value = value[0]
262         linkcl = client.db.getclass(propclass.classname)
263         l = ['<select name="%s">'%property]
264         k = linkcl.labelprop(1)
265         s = ''
266         if value is None:
267             s = 'selected '
268         l.append(_('<option %svalue="-1">- no selection -</option>')%s)
269         if linkcl.getprops().has_key('order'):  
270             sort_on = 'order'  
271         else:  
272             sort_on = linkcl.labelprop() 
273         options = linkcl.filter(None, conditions, [sort_on], []) 
274         for optionid in options:
275             option = linkcl.get(optionid, k)
276             s = ''
277             if value in [optionid, option]:
278                 s = 'selected '
279             if showid:
280                 lab = '%s%s: %s'%(propclass.classname, optionid, option)
281             else:
282                 lab = option
283             if size is not None and len(lab) > size:
284                 lab = lab[:size-3] + '...'
285             if additional:
286                 m = []
287                 for propname in additional:
288                     m.append(linkcl.get(optionid, propname))
289                 lab = lab + ' (%s)'%', '.join(map(str, m))
290             lab = cgi.escape(lab)
291             l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
292         l.append('</select>')
293         return '\n'.join(l)
294     return _('[Menu: not a link]')
296 #XXX deviates from spec
297 def do_link(client, classname, cl, props, nodeid, filterspec, property=None, is_download=0, showid=0):
298     '''For a Link or Multilink property, display the names of the linked
299        nodes, hyperlinked to the item views on those nodes.
300        For other properties, link to this node with the property as the
301        text.
303        If is_download is true, append the property value to the generated
304        URL so that the link may be used as a download link and the
305        downloaded file name is correct.
306     '''
307     if not nodeid and client.form is None:
308         return _('[Link: not called from item]')
310     # get the value
311     value = determine_value(cl, props, nodeid, filterspec, property)
312     propclass = props[property]
313     if isinstance(propclass, hyperdb.Boolean):
314         value = value and "Yes" or "No"
315     elif isinstance(propclass, hyperdb.Link):
316         if value in ('', None, []):
317             return _('[no %(propname)s]')%{'propname':property.capitalize()}
318         linkname = propclass.classname
319         linkcl = client.db.getclass(linkname)
320         k = linkcl.labelprop(1)
321         linkvalue = cgi.escape(str(linkcl.get(value, k)))
322         if showid:
323             label = value
324             title = ' title="%s"'%linkvalue
325             # note ... this should be urllib.quote(linkcl.get(value, k))
326         else:
327             label = linkvalue
328             title = ''
329         if is_download:
330             return '<a href="%s%s/%s"%s>%s</a>'%(linkname, value,
331                 linkvalue, title, label)
332         else:
333             return '<a href="%s%s"%s>%s</a>'%(linkname, value, title, label)
334     elif isinstance(propclass, hyperdb.Multilink):
335         if value in ('', None, []):
336             return _('[no %(propname)s]')%{'propname':property.capitalize()}
337         linkname = propclass.classname
338         linkcl = client.db.getclass(linkname)
339         k = linkcl.labelprop(1)
340         l = []
341         for value in value:
342             linkvalue = cgi.escape(str(linkcl.get(value, k)))
343             if showid:
344                 label = value
345                 title = ' title="%s"'%linkvalue
346                 # note ... this should be urllib.quote(linkcl.get(value, k))
347             else:
348                 label = linkvalue
349                 title = ''
350             if is_download:
351                 l.append('<a href="%s%s/%s"%s>%s</a>'%(linkname, value,
352                     linkvalue, title, label))
353             else:
354                 l.append('<a href="%s%s"%s>%s</a>'%(linkname, value,
355                     title, label))
356         return ', '.join(l)
357     if is_download:
358         if value in ('', None, []):
359             return _('[no %(propname)s]')%{'propname':property.capitalize()}
360         return '<a href="%s%s/%s">%s</a>'%(classname, nodeid,
361             value, value)
362     else:
363         if value in ('', None, []):
364             value =  _('[no %(propname)s]')%{'propname':property.capitalize()}
365         return '<a href="%s%s">%s</a>'%(classname, nodeid, value)
367 def do_count(client, classname, cl, props, nodeid, filterspec, property, **args):
368     ''' for a Multilink property, display a count of the number of links in
369         the list
370     '''
371     if not nodeid:
372         return _('[Count: not called from item]')
374     propclass = props[property]
375     if not isinstance(propclass, hyperdb.Multilink):
376         return _('[Count: not a Multilink]')
378     # figure the length then...
379     value = cl.get(nodeid, property)
380     return str(len(value))
382 # XXX pretty is definitely new ;)
383 def do_reldate(client, classname, cl, props, nodeid, filterspec, property, pretty=0):
384     ''' display a Date property in terms of an interval relative to the
385         current date (e.g. "+ 3w", "- 2d").
387         with the 'pretty' flag, make it pretty
388     '''
389     if not nodeid and client.form is None:
390         return _('[Reldate: not called from item]')
392     propclass = props[property]
393     if not isinstance(propclass, hyperdb.Date):
394         return _('[Reldate: not a Date]')
396     if nodeid:
397         value = cl.get(nodeid, property)
398     else:
399         return ''
400     if not value:
401         return ''
403     # figure the interval
404     interval = date.Date('.') - value
405     if pretty:
406         if not nodeid:
407             return _('now')
408         return interval.pretty()
409     return str(interval)
411 def do_download(client, classname, cl, props, nodeid, filterspec, property, **args):
412     ''' show a Link("file") or Multilink("file") property using links that
413         allow you to download files
414     '''
415     if not nodeid:
416         return _('[Download: not called from item]')
417     return do_link(client, classname, cl, props, nodeid, filterspec, property, is_download=1)
420 def do_checklist(client, classname, cl, props, nodeid, filterspec, property, sortby=None):
421     ''' for a Link or Multilink property, display checkboxes for the
422         available choices to permit filtering
424         sort the checklist by the argument (+/- property name)
425     '''
426     propclass = props[property]
427     if (not isinstance(propclass, hyperdb.Link) and not
428             isinstance(propclass, hyperdb.Multilink)):
429         return _('[Checklist: not a link]')
431     # get our current checkbox state
432     if nodeid:
433         # get the info from the node - make sure it's a list
434         if isinstance(propclass, hyperdb.Link):
435             value = [cl.get(nodeid, property)]
436         else:
437             value = cl.get(nodeid, property)
438     elif filterspec is not None:
439         # get the state from the filter specification (always a list)
440         value = filterspec.get(property, [])
441     else:
442         # it's a new node, so there's no state
443         value = []
445     # so we can map to the linked node's "lable" property
446     linkcl = client.db.getclass(propclass.classname)
447     l = []
448     k = linkcl.labelprop(1)
450     # build list of options and then sort it, either
451     # by id + label or <sortby>-value + label;
452     # a minus reverses the sort order, while + or no
453     # prefix sort in increasing order
454     reversed = 0
455     if sortby:
456         if sortby[0] == '-':
457             reversed = 1
458             sortby = sortby[1:]
459         elif sortby[0] == '+':
460             sortby = sortby[1:]
461     options = []
462     for optionid in linkcl.list():
463         if sortby:
464             sortval = linkcl.get(optionid, sortby)
465         else:
466             sortval = int(optionid)
467         option = cgi.escape(str(linkcl.get(optionid, k)))
468         options.append((sortval, option, optionid))
469     options.sort()
470     if reversed:
471         options.reverse()
473     # build checkboxes
474     for sortval, option, optionid in options:
475         if optionid in value or option in value:
476             checked = 'checked'
477         else:
478             checked = ''
479         l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
480             option, checked, property, option))
482     # for Links, allow the "unselected" option too
483     if isinstance(propclass, hyperdb.Link):
484         if value is None or '-1' in value:
485             checked = 'checked'
486         else:
487             checked = ''
488         l.append(_('[unselected]:<input type="checkbox" %s name="%s" '
489             'value="-1">')%(checked, property))
490     return '\n'.join(l)
492 def do_note(client, classname, cl, props, nodeid, filterspec, rows=5, cols=80):
493     ''' display a "note" field, which is a text area for entering a note to
494         go along with a change. 
495     '''
496     # TODO: pull the value from the form
497     return '<textarea name="__note" wrap="hard" rows=%s cols=%s>'\
498         '</textarea>'%(rows, cols)
500 # XXX new function
501 def do_list(client, classname, cl, props, nodeid, filterspec, property, reverse=0, xtracols=None):
502     ''' list the items specified by property using the standard index for
503         the class
504     '''
505     propcl = props[property]
506     if not isinstance(propcl, hyperdb.Multilink):
507         return _('[List: not a Multilink]')
509     value = determine_value(cl, props, nodeid, filterspec, property)
510     if not value:
511         return ''
513     # sort, possibly revers and then re-stringify
514     value = map(int, value)
515     value.sort()
516     if reverse:
517         value.reverse()
518     value = map(str, value)
520     # render the sub-index into a string
521     fp = StringIO.StringIO()
522     try:
523         write_save = client.write
524         client.write = fp.write
525         client.listcontext = ('%s%s' % (classname, nodeid), property)
526         index = htmltemplate.IndexTemplate(client, client.instance.TEMPLATES, propcl.classname)
527         index.render(nodeids=value, show_display_form=0, xtracols=xtracols)
528     finally:
529         client.listcontext = None
530         client.write = write_save
532     return fp.getvalue()
534 # XXX new function
535 def do_history(client, classname, cl, props, nodeid, filterspec, direction='descending'):
536     ''' list the history of the item
538         If "direction" is 'descending' then the most recent event will
539         be displayed first. If it is 'ascending' then the oldest event
540         will be displayed first.
541     '''
542     if nodeid is None:
543         return _("[History: node doesn't exist]")
545     l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
546         '<tr class="list-header">',
547         _('<th align=left><span class="list-item">Date</span></th>'),
548         _('<th align=left><span class="list-item">User</span></th>'),
549         _('<th align=left><span class="list-item">Action</span></th>'),
550         _('<th align=left><span class="list-item">Args</span></th>'),
551         '</tr>']
552     comments = {}
553     history = cl.history(nodeid)
554     history.sort()
555     if direction == 'descending':
556         history.reverse()
557     for id, evt_date, user, action, args in history:
558         date_s = str(evt_date).replace("."," ")
559         arg_s = ''
560         if action == 'link' and type(args) == type(()):
561             if len(args) == 3:
562                 linkcl, linkid, key = args
563                 arg_s += '<a href="%s%s">%s%s %s</a>'%(linkcl, linkid,
564                     linkcl, linkid, key)
565             else:
566                 arg_s = str(args)
568         elif action == 'unlink' and type(args) == type(()):
569             if len(args) == 3:
570                 linkcl, linkid, key = args
571                 arg_s += '<a href="%s%s">%s%s %s</a>'%(linkcl, linkid,
572                     linkcl, linkid, key)
573             else:
574                 arg_s = str(args)
576         elif type(args) == type({}):
577             cell = []
578             for k in args.keys():
579                 # try to get the relevant property and treat it
580                 # specially
581                 try:
582                     prop = props[k]
583                 except:
584                     prop = None
585                 if prop is not None:
586                     if args[k] and (isinstance(prop, hyperdb.Multilink) or
587                             isinstance(prop, hyperdb.Link)):
588                         # figure what the link class is
589                         classname = prop.classname
590                         try:
591                             linkcl = client.db.getclass(classname)
592                         except KeyError:
593                             labelprop = None
594                             comments[classname] = _('''The linked class
595                                 %(classname)s no longer exists''')%locals()
596                         labelprop = linkcl.labelprop(1)
597                         hrefable = os.path.exists(
598                             os.path.join(client.instance.TEMPLATES, classname+'.item'))
600                     if isinstance(prop, hyperdb.Multilink) and \
601                             len(args[k]) > 0:
602                         ml = []
603                         for linkid in args[k]:
604                             label = classname + linkid
605                             # if we have a label property, try to use it
606                             # TODO: test for node existence even when
607                             # there's no labelprop!
608                             try:
609                                 if labelprop is not None:
610                                     label = linkcl.get(linkid, labelprop)
611                             except IndexError:
612                                 comments['no_link'] = _('''<strike>The
613                                     linked node no longer
614                                     exists</strike>''')
615                                 ml.append('<strike>%s</strike>'%label)
616                             else:
617                                 if hrefable:
618                                     ml.append('<a href="%s%s">%s</a>'%(
619                                         classname, linkid, label))
620                                 else:
621                                     ml.append(label)
622                         cell.append('%s:\n  %s'%(k, ',\n  '.join(ml)))
623                     elif isinstance(prop, hyperdb.Link) and args[k]:
624                         label = classname + args[k]
625                         # if we have a label property, try to use it
626                         # TODO: test for node existence even when
627                         # there's no labelprop!
628                         if labelprop is not None:
629                             try:
630                                 label = linkcl.get(args[k], labelprop)
631                             except IndexError:
632                                 comments['no_link'] = _('''<strike>The
633                                     linked node no longer
634                                     exists</strike>''')
635                                 cell.append(' <strike>%s</strike>,\n'%label)
636                                 # "flag" this is done .... euwww
637                                 label = None
638                         if label is not None:
639                             if hrefable:
640                                 cell.append('%s: <a href="%s%s">%s</a>\n'%(k,
641                                     classname, args[k], label))
642                             else:
643                                 cell.append('%s: %s' % (k,label))
645                     elif isinstance(prop, hyperdb.Date) and args[k]:
646                         d = date.Date(args[k])
647                         cell.append('%s: %s'%(k, str(d)))
649                     elif isinstance(prop, hyperdb.Interval) and args[k]:
650                         d = date.Interval(args[k])
651                         cell.append('%s: %s'%(k, str(d)))
653                     elif isinstance(prop, hyperdb.String) and args[k]:
654                         cell.append('%s: %s'%(k, cgi.escape(args[k])))
656                     elif not args[k]:
657                         cell.append('%s: (no value)\n'%k)
659                     else:
660                         cell.append('%s: %s\n'%(k, str(args[k])))
661                 else:
662                     # property no longer exists
663                     comments['no_exist'] = _('''<em>The indicated property
664                         no longer exists</em>''')
665                     cell.append('<em>%s: %s</em>\n'%(k, str(args[k])))
666             arg_s = '<br />'.join(cell)
667         else:
668             # unkown event!!
669             comments['unknown'] = _('''<strong><em>This event is not
670                 handled by the history display!</em></strong>''')
671             arg_s = '<strong><em>' + str(args) + '</em></strong>'
672         date_s = date_s.replace(' ', '&nbsp;')
673         l.append('<tr><td nowrap valign=top>%s</td><td valign=top>%s</td>'
674             '<td valign=top>%s</td><td valign=top>%s</td></tr>'%(date_s,
675             user, action, arg_s))
676     if comments:
677         l.append(_('<tr><td colspan=4><strong>Note:</strong></td></tr>'))
678     for entry in comments.values():
679         l.append('<tr><td colspan=4>%s</td></tr>'%entry)
680     l.append('</table>')
681     return '\n'.join(l)
683 # XXX new function
684 def do_submit(client, classname, cl, props, nodeid, filterspec, value=None):
685     ''' add a submit button for the item
686     '''
687     if value is None:
688         if nodeid:
689             value = "Submit Changes"
690         else:
691             value = "Submit New Entry"
692     if nodeid or client.form is not None:
693         return _('<input type="submit" name="submit" value="%s">' % value)
694     else:
695         return _('[Submit: not called from item]')
697 def do_classhelp(client, classname, cl, props, nodeid, filterspec, clname, properties, label='?', width='400',
698         height='400'):
699     '''pop up a javascript window with class help
701        This generates a link to a popup window which displays the 
702        properties indicated by "properties" of the class named by
703        "classname". The "properties" should be a comma-separated list
704        (eg. 'id,name,description').
706        You may optionally override the label displayed, the width and
707        height. The popup window will be resizable and scrollable.
708     '''
709     return '<a href="javascript:help_window(\'classhelp?classname=%s&' \
710         'properties=%s\', \'%s\', \'%s\')"><b>(%s)</b></a>'%(clname,
711         properties, width, height, label)
713 def do_email(client, classname, cl, props, nodeid, filterspec, property, escape=0):
714     '''display the property as one or more "fudged" email addrs
715     '''
716     
717     if not nodeid and client.form is None:
718         return _('[Email: not called from item]')
719     propclass = props[property]
720     if nodeid:
721         # get the value for this property
722         try:
723             value = cl.get(nodeid, property)
724         except KeyError:
725             # a KeyError here means that the node doesn't have a value
726             # for the specified property
727             value = ''
728     else:
729         value = ''
730     if isinstance(propclass, hyperdb.String):
731         if value is None: value = ''
732         else: value = str(value)
733         value = value.replace('@', ' at ')
734         value = value.replace('.', ' ')
735     else:
736         value = _('[Email: not a string]')%locals()
737     if escape:
738         value = cgi.escape(value)
739     return value
741 def do_filterspec(client, classname, cl, props, nodeid, filterspec, classprop, urlprop):
742     qs = cl.get(nodeid, urlprop)
743     classname = cl.get(nodeid, classprop)
744     filterspec = {}
745     query = cgi.parse_qs(qs)
746     for k,v in query.items():
747         query[k] = v[0].split(',')
748     pagesize = query.get(':pagesize',['25'])[0]
749     search_text = query.get('search_text', [''])[0]
750     search_text = urllib.unquote(search_text)
751     for k,v in query.items():
752         if k[0] != ':':
753             filterspec[k] = v
754     ixtmplt = htmltemplate.IndexTemplate(client, client.instance.TEMPLATES, classname)
755     qform = '<form onSubmit="return submit_once()" action="%s%s">\n'%(
756         classname,nodeid)
757     qform += ixtmplt.filter_form(search_text,
758                                  query.get(':filter', []),
759                                  query.get(':columns', []),
760                                  query.get(':group', []),
761                                  [],
762                                  query.get(':sort',[]),
763                                  filterspec,
764                                  pagesize)
765     return qform + '</table>\n'
767 def do_href(client, classname, cl, props, nodeid, filterspec, property, prefix='', suffix='', label=''):
768     value = determine_value(cl, props, nodeid, filterspec, property)
769     return '<a href="%s%s%s">%s</a>' % (prefix, value, suffix, label)
771 def do_remove(client, classname, cl, props, nodeid, filterspec):
772     ''' put a remove href for an item in a list '''
773     if not nodeid:
774         return _('[Remove not called from item]')
775     try:
776         parentdesignator, mlprop = client.listcontext
777     except (AttributeError, TypeError):
778         return _('[Remove not called form listing of multilink]')
779     return '<a href="remove?:target=%s%s&:multilink=%s:%s">[Remove]</a>' % (classname, nodeid, parentdesignator, mlprop)
781     
782