Mercurial > p > roundup > code
comparison roundup/template_funcs.py @ 934:fdcf16b444a9
Use a real parser for templates.
Rewrite htmltemplate to use the parser (hack, hack).
Move the "do_XXX" methods to template_funcs.py.
Redo the funcion tests (but not Template tests - they're hopeless).
Simplified query form in cgi_client.
Ability to delete msgs, files, queries.
Ability to edit the metadata on files.
| author | Gordon B. McMillan <gmcm@users.sourceforge.net> |
|---|---|
| date | Tue, 13 Aug 2002 20:16:10 +0000 |
| parents | |
| children | 57d09949380e |
comparison
equal
deleted
inserted
replaced
| 933:6bc4e245258f | 934:fdcf16b444a9 |
|---|---|
| 1 import hyperdb, date, password | |
| 2 from i18n import _ | |
| 3 import htmltemplate | |
| 4 import cgi, os, StringIO, urllib, types | |
| 5 | |
| 6 | |
| 7 def do_plain(client, classname, cl, props, nodeid, filterspec, property, escape=0, lookup=1): | |
| 8 ''' display a String property directly; | |
| 9 | |
| 10 display a Date property in a specified time zone with an option to | |
| 11 omit the time from the date stamp; | |
| 12 | |
| 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 | |
| 54 | |
| 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) | |
| 63 | |
| 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 '' | |
| 85 | |
| 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 | |
| 97 | |
| 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 | |
| 101 | |
| 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 | |
| 109 | |
| 110 propclass = props[property] | |
| 111 | |
| 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 | |
| 180 | |
| 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]') | |
| 186 | |
| 187 propclass = props[property] | |
| 188 | |
| 189 # make sure this is a link property | |
| 190 if not isinstance(propclass, hyperdb.String): | |
| 191 return _('[Multiline: not a string]') | |
| 192 | |
| 193 # get the value | |
| 194 value = determine_value(cl, props, nodeid, filterspec, property) | |
| 195 if value is None: | |
| 196 value = '' | |
| 197 | |
| 198 # display | |
| 199 return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%( | |
| 200 property, rows, cols, value) | |
| 201 | |
| 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 | |
| 206 | |
| 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]') | |
| 212 | |
| 213 propclass = props[property] | |
| 214 | |
| 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]') | |
| 219 | |
| 220 # sort function | |
| 221 sortfunc = make_sort_function(client, filterspec, propclass.classname) | |
| 222 | |
| 223 # get the value | |
| 224 value = determine_value(cl, props, nodeid, filterspec, property) | |
| 225 | |
| 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]') | |
| 295 | |
| 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. | |
| 302 | |
| 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]') | |
| 309 | |
| 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) | |
| 366 | |
| 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]') | |
| 373 | |
| 374 propclass = props[property] | |
| 375 if not isinstance(propclass, hyperdb.Multilink): | |
| 376 return _('[Count: not a Multilink]') | |
| 377 | |
| 378 # figure the length then... | |
| 379 value = cl.get(nodeid, property) | |
| 380 return str(len(value)) | |
| 381 | |
| 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"). | |
| 386 | |
| 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]') | |
| 391 | |
| 392 propclass = props[property] | |
| 393 if not isinstance(propclass, hyperdb.Date): | |
| 394 return _('[Reldate: not a Date]') | |
| 395 | |
| 396 if nodeid: | |
| 397 value = cl.get(nodeid, property) | |
| 398 else: | |
| 399 return '' | |
| 400 if not value: | |
| 401 return '' | |
| 402 | |
| 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) | |
| 410 | |
| 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) | |
| 418 | |
| 419 | |
| 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 | |
| 423 | |
| 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]') | |
| 430 | |
| 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 = [] | |
| 444 | |
| 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) | |
| 449 | |
| 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() | |
| 472 | |
| 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)) | |
| 481 | |
| 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) | |
| 491 | |
| 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) | |
| 499 | |
| 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]') | |
| 508 | |
| 509 value = determine_value(cl, props, nodeid, filterspec, property) | |
| 510 if not value: | |
| 511 return '' | |
| 512 | |
| 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) | |
| 519 | |
| 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 | |
| 531 | |
| 532 return fp.getvalue() | |
| 533 | |
| 534 # XXX new function | |
| 535 def do_history(client, classname, cl, props, nodeid, filterspec, direction='descending'): | |
| 536 ''' list the history of the item | |
| 537 | |
| 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]") | |
| 544 | |
| 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) | |
| 567 | |
| 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) | |
| 575 | |
| 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')) | |
| 599 | |
| 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)) | |
| 644 | |
| 645 elif isinstance(prop, hyperdb.Date) and args[k]: | |
| 646 d = date.Date(args[k]) | |
| 647 cell.append('%s: %s'%(k, str(d))) | |
| 648 | |
| 649 elif isinstance(prop, hyperdb.Interval) and args[k]: | |
| 650 d = date.Interval(args[k]) | |
| 651 cell.append('%s: %s'%(k, str(d))) | |
| 652 | |
| 653 elif isinstance(prop, hyperdb.String) and args[k]: | |
| 654 cell.append('%s: %s'%(k, cgi.escape(args[k]))) | |
| 655 | |
| 656 elif not args[k]: | |
| 657 cell.append('%s: (no value)\n'%k) | |
| 658 | |
| 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(' ', ' ') | |
| 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) | |
| 682 | |
| 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]') | |
| 696 | |
| 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 | |
| 700 | |
| 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'). | |
| 705 | |
| 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) | |
| 712 | |
| 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 | |
| 740 | |
| 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' | |
| 766 | |
| 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) | |
| 770 | |
| 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) | |
| 780 | |
| 781 | |
| 782 |
