X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=roundup%2Fhtmltemplate.py;h=2f6557a1fdb0abba2b3e4d660048c9cfdc2e923b;hb=f04ca7e8c6067e05296508ed2b7c302425ffbcc8;hp=9afbd099ae3db685bd46f99b2cf880e5bdb2a5b4;hpb=f5e833e9e210c42600484653f92248adf4d49eee;p=roundup.git diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py index 9afbd09..2f6557a 100644 --- a/roundup/htmltemplate.py +++ b/roundup/htmltemplate.py @@ -15,11 +15,16 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: htmltemplate.py,v 1.44 2001-11-21 23:35:45 jhermann Exp $ +# $Id: htmltemplate.py,v 1.89 2002-05-15 06:34:47 richard Exp $ -import os, re, StringIO, urllib, cgi, errno +__doc__ = """ +Template engine. +""" -import hyperdb, date, password +import os, re, StringIO, urllib, cgi, errno, types, urllib + +import hyperdb, date +from i18n import _ # This imports the StructureText functionality for the do_stext function # get it from http://dev.zope.org/Members/jim/StructuredTextWiki/NGReleases @@ -28,7 +33,15 @@ try: except ImportError: StructuredText = None +class MissingTemplateError(ValueError): + '''Error raised when a template file is missing + ''' + pass + class TemplateFunctions: + '''Defines the templating functions that are used in the HTML templates + of the roundup web interface. + ''' def __init__(self): self.form = None self.nodeid = None @@ -38,6 +51,15 @@ class TemplateFunctions: if key[:3] == 'do_': self.globals[key[3:]] = getattr(self, key) + # These are added by the subclass where appropriate + self.client = None + self.instance = None + self.templates = None + self.classname = None + self.db = None + self.cl = None + self.properties = None + def do_plain(self, property, escape=0): ''' display a String property directly; @@ -48,12 +70,12 @@ class TemplateFunctions: linked nodes (or the ids if the linked class has no key property) ''' if not self.nodeid and self.form is None: - return '[Field: not called from item]' + return _('[Field: not called from item]') propclass = self.properties[property] if self.nodeid: # make sure the property is a valid one # TODO: this tests, but we should handle the exception - prop_test = self.cl.getprops()[property] + dummy = self.cl.getprops()[property] # get the value for this property try: @@ -72,22 +94,28 @@ class TemplateFunctions: else: value = str(value) elif isinstance(propclass, hyperdb.Password): if value is None: value = '' - else: value = '*encrypted*' + else: value = _('*encrypted*') elif isinstance(propclass, hyperdb.Date): + # this gives "2002-01-17.06:54:39", maybe replace the "." by a " ". value = str(value) elif isinstance(propclass, hyperdb.Interval): value = str(value) elif isinstance(propclass, hyperdb.Link): linkcl = self.db.classes[propclass.classname] k = linkcl.labelprop() - if value: value = str(linkcl.get(value, k)) - else: value = '[unselected]' + if value: + value = linkcl.get(value, k) + else: + value = _('[unselected]') elif isinstance(propclass, hyperdb.Multilink): linkcl = self.db.classes[propclass.classname] k = linkcl.labelprop() - value = ', '.join([linkcl.get(i, k) for i in value]) + labels = [] + for v in value: + labels.append(linkcl.get(v, k)) + value = ', '.join(labels) else: - s = _('Plain: bad propclass "%(propclass)s"')%locals() + value = _('Plain: bad propclass "%(propclass)s"')%locals() if escape: value = cgi.escape(value) return value @@ -101,52 +129,83 @@ class TemplateFunctions: return s return StructuredText(s,level=1,header=0) - def do_field(self, property, size=None, height=None, showid=0): - ''' display a property like the plain displayer, but in a text field - to be edited + def determine_value(self, property): + '''determine the value of a property using the node, form or + filterspec ''' - if not self.nodeid and self.form is None and self.filterspec is None: - return _('[Field: not called from item]') propclass = self.properties[property] if self.nodeid: value = self.cl.get(self.nodeid, property, None) - # TODO: remove this from the code ... it's only here for - # handling schema changes, and they should be handled outside - # of this code... if isinstance(propclass, hyperdb.Multilink) and value is None: - value = [] + return [] + return value elif self.filterspec is not None: if isinstance(propclass, hyperdb.Multilink): - value = self.filterspec.get(property, []) + return self.filterspec.get(property, []) else: - value = self.filterspec.get(property, '') + return self.filterspec.get(property, '') + # TODO: pull the value from the form + if isinstance(propclass, hyperdb.Multilink): + return [] else: - # TODO: pull the value from the form - if isinstance(propclass, hyperdb.Multilink): value = [] - else: value = '' + return '' + + def make_sort_function(self, classname): + '''Make a sort function for a given class + ''' + linkcl = self.db.classes[classname] + if linkcl.getprops().has_key('order'): + sort_on = 'order' + else: + sort_on = linkcl.labelprop() + def sortfunc(a, b, linkcl=linkcl, sort_on=sort_on): + return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on)) + return sortfunc + + def do_field(self, property, size=None, showid=0): + ''' display a property like the plain displayer, but in a text field + to be edited + + Note: if you would prefer an option list style display for + link or multilink editing, use menu(). + ''' + if not self.nodeid and self.form is None and self.filterspec is None: + return _('[Field: not called from item]') + + if size is None: + size = 30 + + propclass = self.properties[property] + + # get the value + value = self.determine_value(property) + + # now display if (isinstance(propclass, hyperdb.String) or isinstance(propclass, hyperdb.Date) or isinstance(propclass, hyperdb.Interval)): - size = size or 30 if value is None: value = '' else: - value = cgi.escape(value) + value = cgi.escape(str(value)) value = '"'.join(value.split('"')) s = ''%(property, value, size) elif isinstance(propclass, hyperdb.Password): - size = size or 30 s = ''%(property, size) elif isinstance(propclass, hyperdb.Link): + sortfunc = self.make_sort_function(propclass.classname) linkcl = self.db.classes[propclass.classname] + options = linkcl.list() + options.sort(sortfunc) + # TODO: make this a field display, not a menu one! l = ['') s = '\n'.join(l) elif isinstance(propclass, hyperdb.Multilink): + sortfunc = self.make_sort_function(propclass.classname) + linkcl = self.db.classes[propclass.classname] + if value: + value.sort(sortfunc) + # map the id to the label property + if not showid: + k = linkcl.labelprop() + value = [linkcl.get(v, k) for v in value] + value = cgi.escape(','.join(value)) + s = ''%(property, size, value) + else: + s = _('Plain: bad propclass "%(propclass)s"')%locals() + return s + + def do_multiline(self, property, rows=5, cols=40): + ''' display a string property in a multiline text edit field + ''' + if not self.nodeid and self.form is None and self.filterspec is None: + return _('[Multiline: not called from item]') + + propclass = self.properties[property] + + # make sure this is a link property + if not isinstance(propclass, hyperdb.String): + return _('[Multiline: not a string]') + + # get the value + value = self.determine_value(property) + if value is None: + value = '' + + # display + return ''%( + property, rows, cols, value) + + def do_menu(self, property, size=None, height=None, showid=0): + ''' for a Link property, display a menu of the available choices + ''' + if not self.nodeid and self.form is None and self.filterspec is None: + return _('[Field: not called from item]') + + propclass = self.properties[property] + + # make sure this is a link property + if not (isinstance(propclass, hyperdb.Link) or + isinstance(propclass, hyperdb.Multilink)): + return _('[Menu: not a link]') + + # sort function + sortfunc = self.make_sort_function(propclass.classname) + + # get the value + value = self.determine_value(property) + + # display + if isinstance(propclass, hyperdb.Multilink): linkcl = self.db.classes[propclass.classname] - list = linkcl.list() - height = height or min(len(list), 7) + options = linkcl.list() + options.sort(sortfunc) + height = height or min(len(options), 7) l = ['') - s = '\n'.join(l) - else: - s = 'Plain: bad propclass "%s"'%propclass - return s - - def do_menu(self, property, size=None, height=None, showid=0): - ''' for a Link property, display a menu of the available choices - ''' - propclass = self.properties[property] - if self.nodeid: - value = self.cl.get(self.nodeid, property) - else: - # TODO: pull the value from the form - if isinstance(propclass, hyperdb.Multilink): value = [] - else: value = None + return '\n'.join(l) if isinstance(propclass, hyperdb.Link): + # force the value to be a single choice + if type(value) is types.ListType: + value = value[0] linkcl = self.db.classes[propclass.classname] l = ['') - return '\n'.join(l) - if isinstance(propclass, hyperdb.Multilink): - linkcl = self.db.classes[propclass.classname] - list = linkcl.list() - height = height or min(len(list), 7) - l = ['') return '\n'.join(l) - return '[Menu: not a link]' + return _('[Menu: not a link]') #XXX deviates from spec - def do_link(self, property=None, is_download=0): + def do_link(self, property=None, is_download=0, showid=0): '''For a Link or Multilink property, display the names of the linked nodes, hyperlinked to the item views on those nodes. For other properties, link to this node with the property as the @@ -244,42 +341,52 @@ class TemplateFunctions: downloaded file name is correct. ''' if not self.nodeid and self.form is None: - return '[Link: not called from item]' + return _('[Link: not called from item]') + + # get the value + value = self.determine_value(property) + if not value: + return _('[no %(propname)s]')%{'propname':property.capitalize()} + propclass = self.properties[property] - if self.nodeid: - value = self.cl.get(self.nodeid, property) - else: - if isinstance(propclass, hyperdb.Multilink): value = [] - elif isinstance(propclass, hyperdb.Link): value = None - else: value = '' if isinstance(propclass, hyperdb.Link): linkname = propclass.classname - if value is None: return '[no %s]'%property.capitalize() linkcl = self.db.classes[linkname] k = linkcl.labelprop() - linkvalue = linkcl.get(value, k) + linkvalue = cgi.escape(str(linkcl.get(value, k))) + if showid: + label = value + title = ' title="%s"'%linkvalue + # note ... this should be urllib.quote(linkcl.get(value, k)) + else: + label = linkvalue + title = '' if is_download: - return '%s'%(linkname, value, - linkvalue, linkvalue) + return '%s'%(linkname, value, + linkvalue, title, label) else: - return '%s'%(linkname, value, linkvalue) + return '%s'%(linkname, value, title, label) if isinstance(propclass, hyperdb.Multilink): linkname = propclass.classname linkcl = self.db.classes[linkname] k = linkcl.labelprop() - if not value : return '[no %s]'%property.capitalize() l = [] for value in value: - linkvalue = linkcl.get(value, k) + linkvalue = cgi.escape(str(linkcl.get(value, k))) + if showid: + label = value + title = ' title="%s"'%linkvalue + # note ... this should be urllib.quote(linkcl.get(value, k)) + else: + label = linkvalue + title = '' if is_download: - l.append('%s'%(linkname, value, - linkvalue, linkvalue)) + l.append('%s'%(linkname, value, + linkvalue, title, label)) else: - l.append('%s'%(linkname, value, - linkvalue)) + l.append('%s'%(linkname, value, + title, label)) return ', '.join(l) - if isinstance(propclass, hyperdb.String): - if value == '': value = '[no %s]'%property.capitalize() if is_download: return '%s'%(self.classname, self.nodeid, value, value) @@ -291,12 +398,15 @@ class TemplateFunctions: the list ''' if not self.nodeid: - return '[Count: not called from item]' + return _('[Count: not called from item]') + propclass = self.properties[property] + if not isinstance(propclass, hyperdb.Multilink): + return _('[Count: not a Multilink]') + + # figure the length then... value = self.cl.get(self.nodeid, property) - if isinstance(propclass, hyperdb.Multilink): - return str(len(value)) - return '[Count: not a Multilink]' + return str(len(value)) # XXX pretty is definitely new ;) def do_reldate(self, property, pretty=0): @@ -306,22 +416,25 @@ class TemplateFunctions: with the 'pretty' flag, make it pretty ''' if not self.nodeid and self.form is None: - return '[Reldate: not called from item]' + return _('[Reldate: not called from item]') + propclass = self.properties[property] - if isinstance(not propclass, hyperdb.Date): - return '[Reldate: not a Date]' + if not isinstance(propclass, hyperdb.Date): + return _('[Reldate: not a Date]') + if self.nodeid: value = self.cl.get(self.nodeid, property) else: - value = date.Date('.') - interval = value - date.Date('.') + return '' + if not value: + return '' + + # figure the interval + interval = date.Date('.') - value if pretty: if not self.nodeid: - return 'now' - pretty = interval.pretty() - if pretty is None: - pretty = value.pretty() - return pretty + return _('now') + return interval.pretty() return str(interval) def do_download(self, property, **args): @@ -329,21 +442,8 @@ class TemplateFunctions: allow you to download files ''' if not self.nodeid: - return '[Download: not called from item]' - propclass = self.properties[property] - value = self.cl.get(self.nodeid, property) - if isinstance(propclass, hyperdb.Link): - linkcl = self.db.classes[propclass.classname] - linkvalue = linkcl.get(value, k) - return '%s'%(linkcl, value, linkvalue) - if isinstance(propclass, hyperdb.Multilink): - linkcl = self.db.classes[propclass.classname] - l = [] - for value in value: - linkvalue = linkcl.get(value, k) - l.append('%s'%(linkcl, value, linkvalue)) - return ', '.join(l) - return '[Download: not a link]' + return _('[Download: not called from item]') + return self.do_link(property, is_download=1) def do_checklist(self, property, **args): @@ -353,7 +453,7 @@ class TemplateFunctions: propclass = self.properties[property] if (not isinstance(propclass, hyperdb.Link) and not isinstance(propclass, hyperdb.Multilink)): - return '[Checklist: not a link]' + return _('[Checklist: not a link]') # get our current checkbox state if self.nodeid: @@ -374,7 +474,7 @@ class TemplateFunctions: l = [] k = linkcl.labelprop() for optionid in linkcl.list(): - option = linkcl.get(optionid, k) + option = cgi.escape(str(linkcl.get(optionid, k))) if optionid in value or option in value: checked = 'checked' else: @@ -388,8 +488,8 @@ class TemplateFunctions: checked = 'checked' else: checked = '' - l.append('[unselected]:'%(checked, property)) + l.append(_('[unselected]:')%(checked, property)) return '\n'.join(l) def do_note(self, rows=5, cols=80): @@ -407,10 +507,18 @@ class TemplateFunctions: ''' propcl = self.properties[property] if not isinstance(propcl, hyperdb.Multilink): - return '[List: not a Multilink]' - value = self.cl.get(self.nodeid, property) + return _('[List: not a Multilink]') + + value = self.determine_value(property) + if not value: + return '' + + # sort, possibly revers and then re-stringify + value = map(int, value) + value.sort() if reverse: value.reverse() + value = map(str, value) # render the sub-index into a string fp = StringIO.StringIO() @@ -425,22 +533,141 @@ class TemplateFunctions: return fp.getvalue() # XXX new function - def do_history(self, **args): + def do_history(self, direction='descending'): ''' list the history of the item + + If "direction" is 'descending' then the most recent event will + be displayed first. If it is 'ascending' then the oldest event + will be displayed first. ''' if self.nodeid is None: - return "[History: node doesn't exist]" + return _("[History: node doesn't exist]") l = ['
Date | ', - 'User | ', - 'Action | ', - 'Args | '] - - for id, date, user, action, args in self.cl.history(self.nodeid): - l.append('
%s | %s | %s | %s | Date | '), + _('User | '), + _('Action | '), + _('Args | '), + ''] + + comments = {} + history = self.cl.history(self.nodeid) + history.sort() + if direction == 'descending': + history.reverse() + for id, evt_date, user, action, args in history: + date_s = str(evt_date).replace("."," ") + arg_s = '' + if action == 'link' and type(args) == type(()): + if len(args) == 3: + linkcl, linkid, key = args + arg_s += '%s%s %s'%(linkcl, linkid, + linkcl, linkid, key) + else: + arg_s = str(args) + + elif action == 'unlink' and type(args) == type(()): + if len(args) == 3: + linkcl, linkid, key = args + arg_s += '%s%s %s'%(linkcl, linkid, + linkcl, linkid, key) + else: + arg_s = str(args) + + elif type(args) == type({}): + cell = [] + for k in args.keys(): + # try to get the relevant property and treat it + # specially + try: + prop = self.properties[k] + except: + prop = None + if prop is not None: + if args[k] and (isinstance(prop, hyperdb.Multilink) or + isinstance(prop, hyperdb.Link)): + # figure what the link class is + classname = prop.classname + try: + linkcl = self.db.classes[classname] + except KeyError: + labelprop = None + comments[classname] = _('''The linked class + %(classname)s no longer exists''')%locals() + labelprop = linkcl.labelprop() + + if isinstance(prop, hyperdb.Multilink) and \ + len(args[k]) > 0: + ml = [] + for linkid in args[k]: + label = classname + linkid + # if we have a label property, try to use it + # TODO: test for node existence even when + # there's no labelprop! + try: + if labelprop is not None: + label = linkcl.get(linkid, labelprop) + except IndexError: + comments['no_link'] = _('''
---|---|---|---|
%s | %s | ' + '%s | %s |
Note: | |||
%s |