a01fa7c768b2740d13e176a2e2eb12b0e22989f2
1 import sys, cgi, urllib, os
3 from roundup import hyperdb, date
4 from roundup.i18n import _
7 try:
8 import StructuredText
9 except ImportError:
10 StructuredText = None
12 # Make sure these modules are loaded
13 # I need these to run PageTemplates outside of Zope :(
14 # If we're running in a Zope environment, these modules will be loaded
15 # already...
16 if not sys.modules.has_key('zLOG'):
17 import zLOG
18 sys.modules['zLOG'] = zLOG
19 if not sys.modules.has_key('MultiMapping'):
20 import MultiMapping
21 sys.modules['MultiMapping'] = MultiMapping
22 if not sys.modules.has_key('ComputedAttribute'):
23 import ComputedAttribute
24 sys.modules['ComputedAttribute'] = ComputedAttribute
25 if not sys.modules.has_key('ExtensionClass'):
26 import ExtensionClass
27 sys.modules['ExtensionClass'] = ExtensionClass
28 if not sys.modules.has_key('Acquisition'):
29 import Acquisition
30 sys.modules['Acquisition'] = Acquisition
32 # now it's safe to import PageTemplates and ZTUtils
33 from PageTemplates import PageTemplate
34 import ZTUtils
36 class RoundupPageTemplate(PageTemplate.PageTemplate):
37 ''' A Roundup-specific PageTemplate.
39 Interrogate the client to set up the various template variables to
40 be available:
42 *class*
43 The current class of node being displayed as an HTMLClass
44 instance.
45 *item*
46 The current node from the database, if we're viewing a specific
47 node, as an HTMLItem instance. If it doesn't exist, then we're
48 on a new item page.
49 (*classname*)
50 this is one of two things:
52 1. the *item* is also available under its classname, so a *user*
53 node would also be available under the name *user*. This is
54 also an HTMLItem instance.
55 2. if there's no *item* then the current class is available
56 through this name, thus "user/name" and "user/name/menu" will
57 still work - the latter will pull information from the form
58 if it can.
59 *form*
60 The current CGI form information as a mapping of form argument
61 name to value
62 *request*
63 Includes information about the current request, including:
64 - the url
65 - the current index information (``filterspec``, ``filter`` args,
66 ``properties``, etc) parsed out of the form.
67 - methods for easy filterspec link generation
68 - *user*, the current user node as an HTMLItem instance
69 *instance*
70 The current instance
71 *db*
72 The current database, through which db.config may be reached.
74 Maybe also:
76 *modules*
77 python modules made available (XXX: not sure what's actually in
78 there tho)
79 '''
80 def __init__(self, client, classname=None, request=None):
81 ''' Extract the vars from the client and install in the context.
82 '''
83 self.client = client
84 self.classname = classname or self.client.classname
85 self.request = request or HTMLRequest(self.client)
87 def pt_getContext(self):
88 c = {
89 'klass': HTMLClass(self.client, self.classname),
90 'options': {},
91 'nothing': None,
92 'request': self.request,
93 'content': self.client.content,
94 'db': HTMLDatabase(self.client),
95 'instance': self.client.instance
96 }
97 # add in the item if there is one
98 if self.client.nodeid:
99 c['item'] = HTMLItem(self.client.db, self.classname,
100 self.client.nodeid)
101 c[self.classname] = c['item']
102 else:
103 c[self.classname] = c['klass']
104 return c
106 def render(self, *args, **kwargs):
107 if not kwargs.has_key('args'):
108 kwargs['args'] = args
109 return self.pt_render(extra_context={'options': kwargs})
111 class HTMLDatabase:
112 ''' Return HTMLClasses for valid class fetches
113 '''
114 def __init__(self, client):
115 self.client = client
116 self.config = client.db.config
117 def __getattr__(self, attr):
118 self.client.db.getclass(attr)
119 return HTMLClass(self.client, attr)
120 def classes(self):
121 l = self.client.db.classes.keys()
122 l.sort()
123 return [HTMLClass(self.client, cn) for cn in l]
125 class HTMLClass:
126 ''' Accesses through a class (either through *class* or *db.<classname>*)
127 '''
128 def __init__(self, client, classname):
129 self.client = client
130 self.db = client.db
131 self.classname = classname
132 if classname is not None:
133 self.klass = self.db.getclass(self.classname)
134 self.props = self.klass.getprops()
136 def __repr__(self):
137 return '<HTMLClass(0x%x) %s>'%(id(self), self.classname)
139 def __getattr__(self, attr):
140 ''' return an HTMLItem instance'''
141 #print 'getattr', (self, attr)
142 if attr == 'creator':
143 return HTMLUser(self.client)
145 if not self.props.has_key(attr):
146 raise AttributeError, attr
147 prop = self.props[attr]
149 # look up the correct HTMLProperty class
150 for klass, htmlklass in propclasses:
151 if isinstance(prop, hyperdb.Multilink):
152 value = []
153 else:
154 value = None
155 if isinstance(prop, klass):
156 return htmlklass(self.db, '', prop, attr, value)
158 # no good
159 raise AttributeError, attr
161 def properties(self):
162 ''' Return HTMLProperty for all props
163 '''
164 l = []
165 for name, prop in self.props.items():
166 for klass, htmlklass in propclasses:
167 if isinstance(prop, hyperdb.Multilink):
168 value = []
169 else:
170 value = None
171 if isinstance(prop, klass):
172 l.append(htmlklass(self.db, '', prop, name, value))
173 return l
175 def list(self):
176 l = [HTMLItem(self.db, self.classname, x) for x in self.klass.list()]
177 return l
179 def filter(self, request=None):
180 ''' Return a list of items from this class, filtered and sorted
181 by the current requested filterspec/filter/sort/group args
182 '''
183 if request is not None:
184 filterspec = request.filterspec
185 sort = request.sort
186 group = request.group
187 l = [HTMLItem(self.db, self.classname, x)
188 for x in self.klass.filter(None, filterspec, sort, group)]
189 return l
191 def classhelp(self, properties, label='?', width='400', height='400'):
192 '''pop up a javascript window with class help
194 This generates a link to a popup window which displays the
195 properties indicated by "properties" of the class named by
196 "classname". The "properties" should be a comma-separated list
197 (eg. 'id,name,description').
199 You may optionally override the label displayed, the width and
200 height. The popup window will be resizable and scrollable.
201 '''
202 return '<a href="javascript:help_window(\'classhelp?classname=%s&' \
203 'properties=%s\', \'%s\', \'%s\')"><b>(%s)</b></a>'%(self.classname,
204 properties, width, height, label)
206 def submit(self, label="Submit New Entry"):
207 ''' Generate a submit button (and action hidden element)
208 '''
209 return ' <input type="hidden" name=":action" value="new">\n'\
210 ' <input type="submit" name="submit" value="%s">'%label
212 def history(self):
213 return 'New node - no history'
215 def renderWith(self, name, **kwargs):
216 ''' Render this class with the given template.
217 '''
218 # create a new request and override the specified args
219 req = HTMLRequest(self.client)
220 req.classname = self.classname
221 req.__dict__.update(kwargs)
223 # new template, using the specified classname and request
224 pt = RoundupPageTemplate(self.client, self.classname, req)
226 # use the specified template
227 name = self.classname + '.' + name
228 pt.write(open('/tmp/test/html/%s'%name).read())
229 pt.id = name
231 # XXX handle PT rendering errors here nicely
232 try:
233 return pt.render()
234 except PageTemplate.PTRuntimeError, message:
235 return '<strong>%s</strong><ol>%s</ol>'%(message,
236 cgi.escape('<li>'.join(pt._v_errors)))
238 class HTMLItem:
239 ''' Accesses through an *item*
240 '''
241 def __init__(self, db, classname, nodeid):
242 self.db = db
243 self.classname = classname
244 self.nodeid = nodeid
245 self.klass = self.db.getclass(classname)
246 self.props = self.klass.getprops()
248 def __repr__(self):
249 return '<HTMLItem(0x%x) %s %s>'%(id(self), self.classname, self.nodeid)
251 def __getattr__(self, attr):
252 ''' return an HTMLItem instance'''
253 #print 'getattr', (self, attr)
254 if attr == 'id':
255 return self.nodeid
257 if not self.props.has_key(attr):
258 raise AttributeError, attr
259 prop = self.props[attr]
261 # get the value, handling missing values
262 value = self.klass.get(self.nodeid, attr, None)
263 if value is None:
264 if isinstance(self.props[attr], hyperdb.Multilink):
265 value = []
267 # look up the correct HTMLProperty class
268 for klass, htmlklass in propclasses:
269 if isinstance(prop, klass):
270 return htmlklass(self.db, self.nodeid, prop, attr, value)
272 # no good
273 raise AttributeError, attr
275 def submit(self, label="Submit Changes"):
276 ''' Generate a submit button (and action hidden element)
277 '''
278 return ' <input type="hidden" name=":action" value="edit">\n'\
279 ' <input type="submit" name="submit" value="%s">'%label
281 # XXX this probably should just return the history items, not the HTML
282 def history(self, direction='descending'):
283 l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
284 '<tr class="list-header">',
285 _('<th align=left><span class="list-item">Date</span></th>'),
286 _('<th align=left><span class="list-item">User</span></th>'),
287 _('<th align=left><span class="list-item">Action</span></th>'),
288 _('<th align=left><span class="list-item">Args</span></th>'),
289 '</tr>']
290 comments = {}
291 history = self.klass.history(self.nodeid)
292 history.sort()
293 if direction == 'descending':
294 history.reverse()
295 for id, evt_date, user, action, args in history:
296 date_s = str(evt_date).replace("."," ")
297 arg_s = ''
298 if action == 'link' and type(args) == type(()):
299 if len(args) == 3:
300 linkcl, linkid, key = args
301 arg_s += '<a href="%s%s">%s%s %s</a>'%(linkcl, linkid,
302 linkcl, linkid, key)
303 else:
304 arg_s = str(args)
306 elif action == 'unlink' and type(args) == type(()):
307 if len(args) == 3:
308 linkcl, linkid, key = args
309 arg_s += '<a href="%s%s">%s%s %s</a>'%(linkcl, linkid,
310 linkcl, linkid, key)
311 else:
312 arg_s = str(args)
314 elif type(args) == type({}):
315 cell = []
316 for k in args.keys():
317 # try to get the relevant property and treat it
318 # specially
319 try:
320 prop = self.props[k]
321 except KeyError:
322 prop = None
323 if prop is not None:
324 if args[k] and (isinstance(prop, hyperdb.Multilink) or
325 isinstance(prop, hyperdb.Link)):
326 # figure what the link class is
327 classname = prop.classname
328 try:
329 linkcl = self.db.getclass(classname)
330 except KeyError:
331 labelprop = None
332 comments[classname] = _('''The linked class
333 %(classname)s no longer exists''')%locals()
334 labelprop = linkcl.labelprop(1)
335 hrefable = os.path.exists(
336 os.path.join(self.db.config.TEMPLATES,
337 classname+'.item'))
339 if isinstance(prop, hyperdb.Multilink) and \
340 len(args[k]) > 0:
341 ml = []
342 for linkid in args[k]:
343 if isinstance(linkid, type(())):
344 sublabel = linkid[0] + ' '
345 linkids = linkid[1]
346 else:
347 sublabel = ''
348 linkids = [linkid]
349 subml = []
350 for linkid in linkids:
351 label = classname + linkid
352 # if we have a label property, try to use it
353 # TODO: test for node existence even when
354 # there's no labelprop!
355 try:
356 if labelprop is not None:
357 label = linkcl.get(linkid, labelprop)
358 except IndexError:
359 comments['no_link'] = _('''<strike>The
360 linked node no longer
361 exists</strike>''')
362 subml.append('<strike>%s</strike>'%label)
363 else:
364 if hrefable:
365 subml.append('<a href="%s%s">%s</a>'%(
366 classname, linkid, label))
367 ml.append(sublabel + ', '.join(subml))
368 cell.append('%s:\n %s'%(k, ', '.join(ml)))
369 elif isinstance(prop, hyperdb.Link) and args[k]:
370 label = classname + args[k]
371 # if we have a label property, try to use it
372 # TODO: test for node existence even when
373 # there's no labelprop!
374 if labelprop is not None:
375 try:
376 label = linkcl.get(args[k], labelprop)
377 except IndexError:
378 comments['no_link'] = _('''<strike>The
379 linked node no longer
380 exists</strike>''')
381 cell.append(' <strike>%s</strike>,\n'%label)
382 # "flag" this is done .... euwww
383 label = None
384 if label is not None:
385 if hrefable:
386 cell.append('%s: <a href="%s%s">%s</a>\n'%(k,
387 classname, args[k], label))
388 else:
389 cell.append('%s: %s' % (k,label))
391 elif isinstance(prop, hyperdb.Date) and args[k]:
392 d = date.Date(args[k])
393 cell.append('%s: %s'%(k, str(d)))
395 elif isinstance(prop, hyperdb.Interval) and args[k]:
396 d = date.Interval(args[k])
397 cell.append('%s: %s'%(k, str(d)))
399 elif isinstance(prop, hyperdb.String) and args[k]:
400 cell.append('%s: %s'%(k, cgi.escape(args[k])))
402 elif not args[k]:
403 cell.append('%s: (no value)\n'%k)
405 else:
406 cell.append('%s: %s\n'%(k, str(args[k])))
407 else:
408 # property no longer exists
409 comments['no_exist'] = _('''<em>The indicated property
410 no longer exists</em>''')
411 cell.append('<em>%s: %s</em>\n'%(k, str(args[k])))
412 arg_s = '<br />'.join(cell)
413 else:
414 # unkown event!!
415 comments['unknown'] = _('''<strong><em>This event is not
416 handled by the history display!</em></strong>''')
417 arg_s = '<strong><em>' + str(args) + '</em></strong>'
418 date_s = date_s.replace(' ', ' ')
419 l.append('<tr><td nowrap valign=top>%s</td><td valign=top>%s</td>'
420 '<td valign=top>%s</td><td valign=top>%s</td></tr>'%(date_s,
421 user, action, arg_s))
422 if comments:
423 l.append(_('<tr><td colspan=4><strong>Note:</strong></td></tr>'))
424 for entry in comments.values():
425 l.append('<tr><td colspan=4>%s</td></tr>'%entry)
426 l.append('</table>')
427 return '\n'.join(l)
429 def remove(self):
430 # XXX do what?
431 return ''
433 class HTMLUser(HTMLItem):
434 ''' Accesses through the *user* (a special case of item)
435 '''
436 def __init__(self, client):
437 HTMLItem.__init__(self, client.db, 'user', client.userid)
438 self.default_classname = client.classname
439 self.userid = client.userid
441 # used for security checks
442 self.security = client.db.security
443 _marker = []
444 def hasPermission(self, role, classname=_marker):
445 ''' Determine if the user has the Role.
447 The class being tested defaults to the template's class, but may
448 be overidden for this test by suppling an alternate classname.
449 '''
450 if classname is self._marker:
451 classname = self.default_classname
452 return self.security.hasPermission(role, self.userid, classname)
454 class HTMLProperty:
455 ''' String, Number, Date, Interval HTMLProperty
457 A wrapper object which may be stringified for the plain() behaviour.
458 '''
459 def __init__(self, db, nodeid, prop, name, value):
460 self.db = db
461 self.nodeid = nodeid
462 self.prop = prop
463 self.name = name
464 self.value = value
465 def __repr__(self):
466 return '<HTMLProperty(0x%x) %s %r %r>'%(id(self), self.name, self.prop, self.value)
467 def __str__(self):
468 return self.plain()
469 def __cmp__(self, other):
470 if isinstance(other, HTMLProperty):
471 return cmp(self.value, other.value)
472 return cmp(self.value, other)
474 class StringHTMLProperty(HTMLProperty):
475 def plain(self, escape=0):
476 if self.value is None:
477 return ''
478 if escape:
479 return cgi.escape(str(self.value))
480 return str(self.value)
482 def stext(self, escape=0):
483 s = self.plain(escape=escape)
484 if not StructuredText:
485 return s
486 return StructuredText(s,level=1,header=0)
488 def field(self, size = 30):
489 if self.value is None:
490 value = ''
491 else:
492 value = cgi.escape(str(self.value))
493 value = '"'.join(value.split('"'))
494 return '<input name="%s" value="%s" size="%s">'%(self.name, value, size)
496 def multiline(self, escape=0, rows=5, cols=40):
497 if self.value is None:
498 value = ''
499 else:
500 value = cgi.escape(str(self.value))
501 value = '"'.join(value.split('"'))
502 return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
503 self.name, rows, cols, value)
505 def email(self, escape=1):
506 ''' fudge email '''
507 if self.value is None: value = ''
508 else: value = str(self.value)
509 value = value.replace('@', ' at ')
510 value = value.replace('.', ' ')
511 if escape:
512 value = cgi.escape(value)
513 return value
515 class PasswordHTMLProperty(HTMLProperty):
516 def plain(self):
517 if self.value is None:
518 return ''
519 return _('*encrypted*')
521 def field(self, size = 30):
522 return '<input type="password" name="%s" size="%s">'%(self.name, size)
524 class NumberHTMLProperty(HTMLProperty):
525 def plain(self):
526 return str(self.value)
528 def field(self, size = 30):
529 if self.value is None:
530 value = ''
531 else:
532 value = cgi.escape(str(self.value))
533 value = '"'.join(value.split('"'))
534 return '<input name="%s" value="%s" size="%s">'%(self.name, value, size)
536 class BooleanHTMLProperty(HTMLProperty):
537 def plain(self):
538 if self.value is None:
539 return ''
540 return self.value and "Yes" or "No"
542 def field(self):
543 checked = self.value and "checked" or ""
544 s = '<input type="radio" name="%s" value="yes" %s>Yes'%(self.name,
545 checked)
546 if checked:
547 checked = ""
548 else:
549 checked = "checked"
550 s += '<input type="radio" name="%s" value="no" %s>No'%(self.name,
551 checked)
552 return s
554 class DateHTMLProperty(HTMLProperty):
555 def plain(self):
556 if self.value is None:
557 return ''
558 return str(self.value)
560 def field(self, size = 30):
561 if self.value is None:
562 value = ''
563 else:
564 value = cgi.escape(str(self.value))
565 value = '"'.join(value.split('"'))
566 return '<input name="%s" value="%s" size="%s">'%(self.name, value, size)
568 def reldate(self, pretty=1):
569 if not self.value:
570 return ''
572 # figure the interval
573 interval = date.Date('.') - self.value
574 if pretty:
575 return interval.pretty()
576 return str(interval)
578 class IntervalHTMLProperty(HTMLProperty):
579 def plain(self):
580 if self.value is None:
581 return ''
582 return str(self.value)
584 def pretty(self):
585 return self.value.pretty()
587 def field(self, size = 30):
588 if self.value is None:
589 value = ''
590 else:
591 value = cgi.escape(str(self.value))
592 value = '"'.join(value.split('"'))
593 return '<input name="%s" value="%s" size="%s">'%(self.name, value, size)
595 class LinkHTMLProperty(HTMLProperty):
596 ''' Link HTMLProperty
597 Include the above as well as being able to access the class
598 information. Stringifying the object itself results in the value
599 from the item being displayed. Accessing attributes of this object
600 result in the appropriate entry from the class being queried for the
601 property accessed (so item/assignedto/name would look up the user
602 entry identified by the assignedto property on item, and then the
603 name property of that user)
604 '''
605 def __getattr__(self, attr):
606 ''' return a new HTMLItem '''
607 #print 'getattr', (self, attr, self.value)
608 if not self.value:
609 raise AttributeError, "Can't access missing value"
610 i = HTMLItem(self.db, self.prop.classname, self.value)
611 return getattr(i, attr)
613 def plain(self, escape=0):
614 if self.value is None:
615 return _('[unselected]')
616 linkcl = self.db.classes[self.klass.classname]
617 k = linkcl.labelprop(1)
618 value = str(linkcl.get(self.value, k))
619 if escape:
620 value = cgi.escape(value)
621 return value
623 # XXX most of the stuff from here down is of dubious utility - it's easy
624 # enough to do in the template by hand (and in some cases, it's shorter
625 # and clearer...
627 def field(self):
628 linkcl = self.db.getclass(self.prop.classname)
629 if linkcl.getprops().has_key('order'):
630 sort_on = 'order'
631 else:
632 sort_on = linkcl.labelprop()
633 options = linkcl.filter(None, {}, [sort_on], [])
634 # TODO: make this a field display, not a menu one!
635 l = ['<select name="%s">'%property]
636 k = linkcl.labelprop(1)
637 if value is None:
638 s = 'selected '
639 else:
640 s = ''
641 l.append(_('<option %svalue="-1">- no selection -</option>')%s)
642 for optionid in options:
643 option = linkcl.get(optionid, k)
644 s = ''
645 if optionid == value:
646 s = 'selected '
647 if showid:
648 lab = '%s%s: %s'%(self.prop.classname, optionid, option)
649 else:
650 lab = option
651 if size is not None and len(lab) > size:
652 lab = lab[:size-3] + '...'
653 lab = cgi.escape(lab)
654 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
655 l.append('</select>')
656 return '\n'.join(l)
658 def download(self, showid=0):
659 linkname = self.prop.classname
660 linkcl = self.db.getclass(linkname)
661 k = linkcl.labelprop(1)
662 linkvalue = cgi.escape(str(linkcl.get(self.value, k)))
663 if showid:
664 label = value
665 title = ' title="%s"'%linkvalue
666 # note ... this should be urllib.quote(linkcl.get(value, k))
667 else:
668 label = linkvalue
669 title = ''
670 return '<a href="%s%s/%s"%s>%s</a>'%(linkname, self.value,
671 linkvalue, title, label)
673 def menu(self, size=None, height=None, showid=0, additional=[],
674 **conditions):
675 value = self.value
677 # sort function
678 sortfunc = make_sort_function(self.db, self.prop.classname)
680 # force the value to be a single choice
681 if isinstance(value, type('')):
682 value = value[0]
683 linkcl = self.db.getclass(self.prop.classname)
684 l = ['<select name="%s">'%self.name]
685 k = linkcl.labelprop(1)
686 s = ''
687 if value is None:
688 s = 'selected '
689 l.append(_('<option %svalue="-1">- no selection -</option>')%s)
690 if linkcl.getprops().has_key('order'):
691 sort_on = 'order'
692 else:
693 sort_on = linkcl.labelprop()
694 options = linkcl.filter(None, conditions, [sort_on], [])
695 for optionid in options:
696 option = linkcl.get(optionid, k)
697 s = ''
698 if value in [optionid, option]:
699 s = 'selected '
700 if showid:
701 lab = '%s%s: %s'%(self.prop.classname, optionid, option)
702 else:
703 lab = option
704 if size is not None and len(lab) > size:
705 lab = lab[:size-3] + '...'
706 if additional:
707 m = []
708 for propname in additional:
709 m.append(linkcl.get(optionid, propname))
710 lab = lab + ' (%s)'%', '.join(map(str, m))
711 lab = cgi.escape(lab)
712 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
713 l.append('</select>')
714 return '\n'.join(l)
716 # def checklist(self, ...)
718 class MultilinkHTMLProperty(HTMLProperty):
719 ''' Multilink HTMLProperty
721 Also be iterable, returning a wrapper object like the Link case for
722 each entry in the multilink.
723 '''
724 def __len__(self):
725 ''' length of the multilink '''
726 return len(self.value)
728 def __getattr__(self, attr):
729 ''' no extended attribute accesses make sense here '''
730 raise AttributeError, attr
732 def __getitem__(self, num):
733 ''' iterate and return a new HTMLItem '''
734 #print 'getitem', (self, num)
735 value = self.value[num]
736 return HTMLItem(self.db, self.prop.classname, value)
738 def plain(self, escape=0):
739 linkcl = self.db.classes[self.prop.classname]
740 k = linkcl.labelprop(1)
741 labels = []
742 for v in self.value:
743 labels.append(linkcl.get(v, k))
744 value = ', '.join(labels)
745 if escape:
746 value = cgi.escape(value)
747 return value
749 # XXX most of the stuff from here down is of dubious utility - it's easy
750 # enough to do in the template by hand (and in some cases, it's shorter
751 # and clearer...
753 def field(self, size=30, showid=0):
754 sortfunc = make_sort_function(self.db, self.prop.classname)
755 linkcl = self.db.getclass(self.prop.classname)
756 value = self.value[:]
757 if value:
758 value.sort(sortfunc)
759 # map the id to the label property
760 if not showid:
761 k = linkcl.labelprop(1)
762 value = [linkcl.get(v, k) for v in value]
763 value = cgi.escape(','.join(value))
764 return '<input name="%s" size="%s" value="%s">'%(self.name, size, value)
766 def menu(self, size=None, height=None, showid=0, additional=[],
767 **conditions):
768 value = self.value
770 # sort function
771 sortfunc = make_sort_function(self.db, self.prop.classname)
773 linkcl = self.db.getclass(self.prop.classname)
774 if linkcl.getprops().has_key('order'):
775 sort_on = 'order'
776 else:
777 sort_on = linkcl.labelprop()
778 options = linkcl.filter(None, conditions, [sort_on], [])
779 height = height or min(len(options), 7)
780 l = ['<select multiple name="%s" size="%s">'%(self.name, height)]
781 k = linkcl.labelprop(1)
782 for optionid in options:
783 option = linkcl.get(optionid, k)
784 s = ''
785 if optionid in value or option in value:
786 s = 'selected '
787 if showid:
788 lab = '%s%s: %s'%(self.prop.classname, optionid, option)
789 else:
790 lab = option
791 if size is not None and len(lab) > size:
792 lab = lab[:size-3] + '...'
793 if additional:
794 m = []
795 for propname in additional:
796 m.append(linkcl.get(optionid, propname))
797 lab = lab + ' (%s)'%', '.join(m)
798 lab = cgi.escape(lab)
799 l.append('<option %svalue="%s">%s</option>'%(s, optionid,
800 lab))
801 l.append('</select>')
802 return '\n'.join(l)
804 # set the propclasses for HTMLItem
805 propclasses = (
806 (hyperdb.String, StringHTMLProperty),
807 (hyperdb.Number, NumberHTMLProperty),
808 (hyperdb.Boolean, BooleanHTMLProperty),
809 (hyperdb.Date, DateHTMLProperty),
810 (hyperdb.Interval, IntervalHTMLProperty),
811 (hyperdb.Password, PasswordHTMLProperty),
812 (hyperdb.Link, LinkHTMLProperty),
813 (hyperdb.Multilink, MultilinkHTMLProperty),
814 )
816 def make_sort_function(db, classname):
817 '''Make a sort function for a given class
818 '''
819 linkcl = db.getclass(classname)
820 if linkcl.getprops().has_key('order'):
821 sort_on = 'order'
822 else:
823 sort_on = linkcl.labelprop()
824 def sortfunc(a, b, linkcl=linkcl, sort_on=sort_on):
825 return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on))
826 return sortfunc
828 def handleListCGIValue(value):
829 ''' Value is either a single item or a list of items. Each item has a
830 .value that we're actually interested in.
831 '''
832 if isinstance(value, type([])):
833 return [value.value for value in value]
834 else:
835 return value.value.split(',')
837 # XXX This is starting to look a lot (in data terms) like the client object
838 # itself!
839 class HTMLRequest:
840 ''' The *request*, holding the CGI form and environment.
842 '''
843 def __init__(self, client):
844 self.client = client
846 # easier access vars
847 self.form = client.form
848 self.env = client.env
849 self.base = client.base
850 self.user = HTMLUser(client)
852 # store the current class name and action
853 self.classname = client.classname
854 self.template_type = client.template_type
856 # extract the index display information from the form
857 self.columns = {}
858 if self.form.has_key(':columns'):
859 for entry in handleListCGIValue(self.form[':columns']):
860 self.columns[entry] = 1
861 self.sort = []
862 if self.form.has_key(':sort'):
863 self.sort = handleListCGIValue(self.form[':sort'])
864 self.group = []
865 if self.form.has_key(':group'):
866 self.group = handleListCGIValue(self.form[':group'])
867 self.filter = []
868 if self.form.has_key(':filter'):
869 self.filter = handleListCGIValue(self.form[':filter'])
870 self.filterspec = {}
871 for name in self.filter:
872 if self.form.has_key(name):
873 self.filterspec[name]=handleListCGIValue(self.form[name])
875 def __str__(self):
876 d = {}
877 d.update(self.__dict__)
878 f = ''
879 for k in self.form.keys():
880 f += '\n %r=%r'%(k,handleListCGIValue(self.form[k]))
881 d['form'] = f
882 e = ''
883 for k,v in self.env.items():
884 e += '\n %r=%r'%(k, v)
885 d['env'] = e
886 return '''
887 form: %(form)s
888 base: %(base)r
889 classname: %(classname)r
890 template_type: %(template_type)r
891 columns: %(columns)r
892 sort: %(sort)r
893 group: %(group)r
894 filter: %(filter)r
895 filterspec: %(filterspec)r
896 env: %(env)s
897 '''%d
899 def indexargs_form(self):
900 ''' return the current index args as form elements '''
901 l = []
902 s = '<input type="hidden" name="%s" value="%s">'
903 if self.columns:
904 l.append(s%(':columns', ','.join(self.columns.keys())))
905 if self.sort:
906 l.append(s%(':sort', ','.join(self.sort)))
907 if self.group:
908 l.append(s%(':group', ','.join(self.group)))
909 if self.filter:
910 l.append(s%(':filter', ','.join(self.filter)))
911 for k,v in self.filterspec.items():
912 l.append(s%(k, ','.join(v)))
913 return '\n'.join(l)
915 def indexargs_href(self, url, args):
916 l = ['%s=%s'%(k,v) for k,v in args.items()]
917 if self.columns:
918 l.append(':columns=%s'%(','.join(self.columns.keys())))
919 if self.sort:
920 l.append(':sort=%s'%(','.join(self.sort)))
921 if self.group:
922 l.append(':group=%s'%(','.join(self.group)))
923 if self.filter:
924 l.append(':filter=%s'%(','.join(self.filter)))
925 for k,v in self.filterspec.items():
926 l.append('%s=%s'%(k, ','.join(v)))
927 return '%s?%s'%(url, '&'.join(l))
929 def base_javascript(self):
930 return '''
931 <script language="javascript">
932 submitted = false;
933 function submit_once() {
934 if (submitted) {
935 alert("Your request is being processed.\\nPlease be patient.");
936 return 0;
937 }
938 submitted = true;
939 return 1;
940 }
942 function help_window(helpurl, width, height) {
943 HelpWin = window.open('%s/' + helpurl, 'RoundupHelpWindow', 'scrollbars=yes,resizable=yes,toolbar=no,height='+height+',width='+width);
944 }
945 </script>
946 '''%self.base
948 def batch(self):
949 ''' Return a batch object for results from the "current search"
950 '''
951 filterspec = self.filterspec
952 sort = self.sort
953 group = self.group
955 # get the list of ids we're batching over
956 klass = self.client.db.getclass(self.classname)
957 l = klass.filter(None, filterspec, sort, group)
959 # figure batch args
960 if self.form.has_key(':pagesize'):
961 size = int(self.form[':pagesize'].value)
962 else:
963 size = 50
964 if self.form.has_key(':startwith'):
965 start = int(self.form[':startwith'].value)
966 else:
967 start = 0
969 # return the batch object
970 return Batch(self.client, self.classname, l, size, start)
972 class Batch(ZTUtils.Batch):
973 def __init__(self, client, classname, l, size, start, end=0, orphan=0, overlap=0):
974 self.client = client
975 self.classname = classname
976 ZTUtils.Batch.__init__(self, l, size, start, end, orphan, overlap)
978 # overwrite so we can late-instantiate the HTMLItem instance
979 def __getitem__(self, index):
980 if index < 0:
981 if index + self.end < self.first: raise IndexError, index
982 return self._sequence[index + self.end]
984 if index >= self.length: raise IndexError, index
986 # wrap the return in an HTMLItem
987 return HTMLItem(self.client.db, self.classname,
988 self._sequence[index+self.first])
990 # override these 'cos we don't have access to acquisition
991 def previous(self):
992 print self.start
993 if self.start == 1:
994 return None
995 return Batch(self.client, self.classname, self._sequence, self._size,
996 self.first - self._size + self.overlap, 0, self.orphan,
997 self.overlap)
999 def next(self):
1000 try:
1001 self._sequence[self.end]
1002 except IndexError:
1003 return None
1004 return Batch(self.client, self.classname, self._sequence, self._size,
1005 self.end - self.overlap, 0, self.orphan, self.overlap)
1007 def length(self):
1008 self.sequence_length = l = len(self._sequence)
1009 return l