# # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) # This module is free software, and you may redistribute it and/or modify # under the same terms as Python, so long as this copyright message and # disclaimer are retained in their original form. # # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # # $Id: htmltemplate.py,v 1.83 2002-02-27 04:14:31 richard Exp $ __doc__ = """ Template engine. """ import os, re, StringIO, urllib, cgi, errno, types 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 try: from StructuredText.StructuredText import HTML as StructuredText 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 self.filterspec = None self.globals = {} for key in TemplateFunctions.__dict__.keys(): 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; display a Date property in a specified time zone with an option to omit the time from the date stamp; for a Link or Multilink property, display the key strings of the 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]') propclass = self.properties[property] if self.nodeid: # make sure the property is a valid one # TODO: this tests, but we should handle the exception dummy = self.cl.getprops()[property] # get the value for this property try: value = self.cl.get(self.nodeid, property) except KeyError: # a KeyError here means that the node doesn't have a value # for the specified property if isinstance(propclass, hyperdb.Multilink): value = [] else: value = '' else: # TODO: pull the value from the form if isinstance(propclass, hyperdb.Multilink): value = [] else: value = '' if isinstance(propclass, hyperdb.String): if value is None: value = '' else: value = str(value) elif isinstance(propclass, hyperdb.Password): if value is None: value = '' 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 = linkcl.get(value, k) else: value = _('[unselected]') elif isinstance(propclass, hyperdb.Multilink): linkcl = self.db.classes[propclass.classname] k = linkcl.labelprop() value = ', '.join(value) else: value = _('Plain: bad propclass "%(propclass)s"')%locals() if escape: value = cgi.escape(value) return value def do_stext(self, property, escape=0): '''Render as structured text using the StructuredText module (see above for details) ''' s = self.do_plain(property, escape=escape) if not StructuredText: return s return StructuredText(s,level=1,header=0) def determine_value(self, property): '''determine the value of a property using the node, form or filterspec ''' propclass = self.properties[property] if self.nodeid: value = self.cl.get(self.nodeid, property, None) if isinstance(propclass, hyperdb.Multilink) and value is None: return [] return value elif self.filterspec is not None: if isinstance(propclass, hyperdb.Multilink): return self.filterspec.get(property, []) else: return self.filterspec.get(property, '') # TODO: pull the value from the form if isinstance(propclass, hyperdb.Multilink): return [] else: 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)): if value is None: value = '' else: value = cgi.escape(str(value)) value = '"'.join(value.split('"')) s = ''%(property, value, size) elif isinstance(propclass, hyperdb.Password): 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] list = linkcl.list() list.sort(sortfunc) l = [] # 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] options = linkcl.list() options.sort(sortfunc) height = height or min(len(options), 7) l = ['') 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) return _('[Menu: not a link]') #XXX deviates from spec def do_link(self, property=None, is_download=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 text. If is_download is true, append the property value to the generated URL so that the link may be used as a download link and the downloaded file name is correct. ''' if not self.nodeid and self.form is None: 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 isinstance(propclass, hyperdb.Link): linkname = propclass.classname linkcl = self.db.classes[linkname] k = linkcl.labelprop() linkvalue = cgi.escape(linkcl.get(value, k)) if is_download: return '%s'%(linkname, value, linkvalue, linkvalue) else: return '%s'%(linkname, value, linkvalue) if isinstance(propclass, hyperdb.Multilink): linkname = propclass.classname linkcl = self.db.classes[linkname] k = linkcl.labelprop() l = [] for value in value: linkvalue = cgi.escape(linkcl.get(value, k)) if is_download: l.append('%s'%(linkname, value, linkvalue, linkvalue)) else: l.append('%s'%(linkname, value, linkvalue)) return ', '.join(l) if is_download: return '%s'%(self.classname, self.nodeid, value, value) else: return '%s'%(self.classname, self.nodeid, value) def do_count(self, property, **args): ''' for a Multilink property, display a count of the number of links in the list ''' if not self.nodeid: 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) return str(len(value)) # XXX pretty is definitely new ;) def do_reldate(self, property, pretty=0): ''' display a Date property in terms of an interval relative to the current date (e.g. "+ 3w", "- 2d"). with the 'pretty' flag, make it pretty ''' if not self.nodeid and self.form is None: return _('[Reldate: not called from item]') propclass = self.properties[property] if not isinstance(propclass, hyperdb.Date): return _('[Reldate: not a Date]') if self.nodeid: value = self.cl.get(self.nodeid, property) else: 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 str(interval) def do_download(self, property, **args): ''' show a Link("file") or Multilink("file") property using links that allow you to download files ''' if not self.nodeid: return _('[Download: not called from item]') return self.do_link(property, is_download=1) def do_checklist(self, property, **args): ''' for a Link or Multilink property, display checkboxes for the available choices to permit filtering ''' propclass = self.properties[property] if (not isinstance(propclass, hyperdb.Link) and not isinstance(propclass, hyperdb.Multilink)): return _('[Checklist: not a link]') # get our current checkbox state if self.nodeid: # get the info from the node - make sure it's a list if isinstance(propclass, hyperdb.Link): value = [self.cl.get(self.nodeid, property)] else: value = self.cl.get(self.nodeid, property) elif self.filterspec is not None: # get the state from the filter specification (always a list) value = self.filterspec.get(property, []) else: # it's a new node, so there's no state value = [] # so we can map to the linked node's "lable" property linkcl = self.db.classes[propclass.classname] l = [] k = linkcl.labelprop() for optionid in linkcl.list(): option = cgi.escape(linkcl.get(optionid, k)) if optionid in value or option in value: checked = 'checked' else: checked = '' l.append('%s:'%( option, checked, property, option)) # for Links, allow the "unselected" option too if isinstance(propclass, hyperdb.Link): if value is None or '-1' in value: checked = 'checked' else: checked = '' l.append(_('[unselected]:')%(checked, property)) return '\n'.join(l) def do_note(self, rows=5, cols=80): ''' display a "note" field, which is a text area for entering a note to go along with a change. ''' # TODO: pull the value from the form return ''%(rows, cols) # XXX new function def do_list(self, property, reverse=0): ''' list the items specified by property using the standard index for the class ''' propcl = self.properties[property] if not isinstance(propcl, hyperdb.Multilink): 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() try: write_save = self.client.write self.client.write = fp.write index = IndexTemplate(self.client, self.templates, propcl.classname) index.render(nodeids=value, show_display_form=0) finally: self.client.write = write_save return fp.getvalue() # XXX new function 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]") l = ['', '', _(''), _(''), _(''), _(''), ''] 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'] = _('''The linked node no longer exists''') ml.append('%s'%label) else: ml.append('%s'%( classname, linkid, label)) cell.append('%s:\n %s'%(k, ',\n '.join(ml))) elif isinstance(prop, hyperdb.Link) and args[k]: label = classname + args[k] # if we have a label property, try to use it # TODO: test for node existence even when # there's no labelprop! if labelprop is not None: try: label = linkcl.get(args[k], labelprop) except IndexError: comments['no_link'] = _('''The linked node no longer exists''') cell.append(' %s,\n'%label) # "flag" this is done .... euwww label = None if label is not None: cell.append('%s: %s\n'%(k, classname, args[k], label)) elif isinstance(prop, hyperdb.Date) and args[k]: d = date.Date(args[k]) cell.append('%s: %s'%(k, str(d))) elif isinstance(prop, hyperdb.Interval) and args[k]: d = date.Interval(args[k]) cell.append('%s: %s'%(k, str(d))) elif not args[k]: cell.append('%s: (no value)\n'%k) else: cell.append('%s: %s\n'%(k, str(args[k]))) else: # property no longer exists comments['no_exist'] = _('''The indicated property no longer exists''') cell.append('%s: %s\n'%(k, str(args[k]))) arg_s = '
'.join(cell) else: # unkown event!! comments['unknown'] = _('''This event is not handled by the history display!''') arg_s = '' + str(args) + '' date_s = date_s.replace(' ', ' ') l.append('' ''%(date_s, user, action, arg_s)) if comments: l.append(_('')) for entry in comments.values(): l.append(''%entry) l.append('
DateUserActionArgs
%s%s%s%s
Note:
%s
') return '\n'.join(l) # XXX new function def do_submit(self): ''' add a submit button for the item ''' if self.nodeid: return _('') elif self.form is not None: return _('') else: return _('[Submit: not called from item]') def do_classhelp(self, classname, properties, label='?', width='400', height='400'): '''pop up a javascript window with class help This generates a link to a popup window which displays the properties indicated by "properties" of the class named by "classname". The "properties" should be a comma-separated list (eg. 'id,name,description'). You may optionally override the label displayed, the width and height. The popup window will be resizable and scrollable. ''' return '(%s)'%(classname, properties, width, height, label) # # INDEX TEMPLATES # class IndexTemplateReplace: '''Regular-expression based parser that turns the template into HTML. ''' def __init__(self, globals, locals, props): self.globals = globals self.locals = locals self.props = props replace=re.compile( r'(([^>]+)">(?P.+?))|' r'(?P[^"]+)">))', re.I|re.S) def go(self, text): return self.replace.sub(self, text) def __call__(self, m, filter=None, columns=None, sort=None, group=None): if m.group('name'): if m.group('name') in self.props: text = m.group('text') replace = IndexTemplateReplace(self.globals, {}, self.props) return replace.go(text) else: return '' if m.group('display'): command = m.group('command') return eval(command, self.globals, self.locals) return '*** unhandled match: %s'%str(m.groupdict()) class IndexTemplate(TemplateFunctions): '''Templating functionality specifically for index pages ''' def __init__(self, client, templates, classname): TemplateFunctions.__init__(self) self.client = client self.instance = client.instance self.templates = templates self.classname = classname # derived self.db = self.client.db self.cl = self.db.classes[self.classname] self.properties = self.cl.getprops() col_re=re.compile(r']+)">') def render(self, filterspec={}, filter=[], columns=[], sort=[], group=[], show_display_form=1, nodeids=None, show_customization=1): self.filterspec = filterspec w = self.client.write # get the filter template try: filter_template = open(os.path.join(self.templates, self.classname+'.filter')).read() all_filters = self.col_re.findall(filter_template) except IOError, error: if error.errno not in (errno.ENOENT, errno.ESRCH): raise filter_template = None all_filters = [] # XXX deviate from spec here ... # load the index section template and figure the default columns from it try: template = open(os.path.join(self.templates, self.classname+'.index')).read() except IOError, error: if error.errno not in (errno.ENOENT, errno.ESRCH): raise raise MissingTemplateError, self.classname+'.index' all_columns = self.col_re.findall(template) if not columns: columns = [] for name in all_columns: columns.append(name) else: # re-sort columns to be the same order as all_columns l = [] for name in all_columns: if name in columns: l.append(name) columns = l # display the filter section if (show_display_form and self.instance.FILTER_POSITION in ('top and bottom', 'top')): w('
\n'%self.classname) self.filter_section(filter_template, filter, columns, group, all_filters, all_columns, show_customization) # make sure that the sorting doesn't get lost either if sort: w(''% ','.join(sort)) w('
\n') # now display the index section w('\n') w('\n') for name in columns: cname = name.capitalize() if show_display_form: sb = self.sortby(name, filterspec, columns, filter, group, sort) anchor = "%s?%s"%(self.classname, sb) w('\n'%( anchor, cname)) else: w('\n'%cname) w('\n') # this stuff is used for group headings - optimise the group names old_group = None group_names = [] if group: for name in group: if name[0] == '-': group_names.append(name[1:]) else: group_names.append(name) # now actually loop through all the nodes we get from the filter and # apply the template if nodeids is None: nodeids = self.cl.filter(filterspec, sort, group) for nodeid in nodeids: # check for a group heading if group_names: this_group = [self.cl.get(nodeid, name, _('[no value]')) for name in group_names] if this_group != old_group: l = [] for name in group_names: prop = self.properties[name] if isinstance(prop, hyperdb.Link): group_cl = self.db.classes[prop.classname] key = group_cl.getkey() value = self.cl.get(nodeid, name) if value is None: l.append(_('[unselected %(classname)s]')%{ 'classname': prop.classname}) else: l.append(group_cl.get(self.cl.get(nodeid, name), key)) elif isinstance(prop, hyperdb.Multilink): group_cl = self.db.classes[prop.classname] key = group_cl.getkey() for value in self.cl.get(nodeid, name): l.append(group_cl.get(value, key)) else: value = self.cl.get(nodeid, name, _('[no value]')) if value is None: value = _('[empty %(name)s]')%locals() else: value = str(value) l.append(value) w('' ''%( len(columns), ', '.join(l))) old_group = this_group # display this node's row replace = IndexTemplateReplace(self.globals, locals(), columns) self.nodeid = nodeid w(replace.go(template)) self.nodeid = None w('
%s%s
%s
') # display the filter section if (show_display_form and hasattr(self.instance, 'FILTER_POSITION') and self.instance.FILTER_POSITION in ('top and bottom', 'bottom')): w('
\n'%self.classname) self.filter_section(filter_template, filter, columns, group, all_filters, all_columns, show_customization) # make sure that the sorting doesn't get lost either if sort: w(''% ','.join(sort)) w('
\n') def filter_section(self, template, filter, columns, group, all_filters, all_columns, show_customization): w = self.client.write # wrap the template in a single table to ensure the whole widget # is displayed at once w('') for name in names: w(''%name.capitalize()) w('\n') # Filter if all_filters: w(_('\n')) for name in names: if name not in all_filters: w('') continue if name in filter: checked=' checked' else: checked='' w('\n'%(name, checked)) w('\n') # Columns if all_columns: w(_('\n')) for name in names: if name not in all_columns: w('') continue if name in columns: checked=' checked' else: checked='' w('\n'%(name, checked)) w('\n') # Grouping w(_('\n')) for name in names: if name not in all_columns: w('') continue if name in group: checked=' checked' else: checked='' w('\n'%(name, checked)) w('\n') w('') w('')) w('\n') w('
') if template and filter: # display the filter section w('') w('') w(_(' ')) w('') replace = IndexTemplateReplace(self.globals, locals(), filter) w(replace.go(template)) w('') w(_('')) w('
Filter specification...
 
') # now add in the filter/columns/group/etc config table form w('' % show_customization ) w('\n') names = [] seen = {} for name in all_filters + all_columns: if self.properties.has_key(name) and not seen.has_key(name): names.append(name) seen[name] = 1 if show_customization: action = '-' else: action = '+' # hide the values for filters, columns and grouping in the form # if the customization widget is not visible for name in names: if all_filters and name in filter: w('' % name) if all_columns and name in columns: w('' % name) if all_columns and name in group: w('' % name) # TODO: The widget style can go into the stylesheet w(_('\n')%(len(names)+1, action)) if not show_customization: w('
' ' View ' 'customisation...
\n') return w('
 %s
Filters \n') w('
Columns \n') w('
Grouping \n') w('
 '%len(names)) w(_('
\n') # and the outer table w('') def sortby(self, sort_name, filterspec, columns, filter, group, sort): l = [] w = l.append for k, v in filterspec.items(): k = urllib.quote(k) if type(v) == type([]): w('%s=%s'%(k, ','.join(map(urllib.quote, v)))) else: w('%s=%s'%(k, urllib.quote(v))) if columns: w(':columns=%s'%','.join(map(urllib.quote, columns))) if filter: w(':filter=%s'%','.join(map(urllib.quote, filter))) if group: w(':group=%s'%','.join(map(urllib.quote, group))) m = [] s_dir = '' for name in sort: dir = name[0] if dir == '-': name = name[1:] else: dir = '' if sort_name == name: if dir == '-': s_dir = '' else: s_dir = '-' else: m.append(dir+urllib.quote(name)) m.insert(0, s_dir+urllib.quote(sort_name)) # so things don't get completely out of hand, limit the sort to # two columns w(':sort=%s'%','.join(m[:2])) return '&'.join(l) # # ITEM TEMPLATES # class ItemTemplateReplace: '''Regular-expression based parser that turns the template into HTML. ''' def __init__(self, globals, locals, cl, nodeid): self.globals = globals self.locals = locals self.cl = cl self.nodeid = nodeid replace=re.compile( r'(([^>]+)">(?P.+?))|' r'(?P[^"]+)">))', re.I|re.S) def go(self, text): return self.replace.sub(self, text) def __call__(self, m, filter=None, columns=None, sort=None, group=None): if m.group('name'): if self.nodeid and self.cl.get(self.nodeid, m.group('name')): replace = ItemTemplateReplace(self.globals, {}, self.cl, self.nodeid) return replace.go(m.group('text')) else: return '' if m.group('display'): command = m.group('command') return eval(command, self.globals, self.locals) return '*** unhandled match: %s'%str(m.groupdict()) class ItemTemplate(TemplateFunctions): '''Templating functionality specifically for item (node) display ''' def __init__(self, client, templates, classname): TemplateFunctions.__init__(self) self.client = client self.instance = client.instance self.templates = templates self.classname = classname # derived self.db = self.client.db self.cl = self.db.classes[self.classname] self.properties = self.cl.getprops() def render(self, nodeid): self.nodeid = nodeid if (self.properties.has_key('type') and self.properties.has_key('content')): pass # XXX we really want to return this as a downloadable... # currently I handle this at a higher level by detecting 'file' # designators... w = self.client.write w('
'%( self.classname, nodeid)) s = open(os.path.join(self.templates, self.classname+'.item')).read() replace = ItemTemplateReplace(self.globals, locals(), self.cl, nodeid) w(replace.go(s)) w('
') class NewItemTemplate(TemplateFunctions): '''Templating functionality specifically for NEW item (node) display ''' def __init__(self, client, templates, classname): TemplateFunctions.__init__(self) self.client = client self.instance = client.instance self.templates = templates self.classname = classname # derived self.db = self.client.db self.cl = self.db.classes[self.classname] self.properties = self.cl.getprops() def render(self, form): self.form = form w = self.client.write c = self.classname try: s = open(os.path.join(self.templates, c+'.newitem')).read() except IOError: s = open(os.path.join(self.templates, c+'.item')).read() w('
'%c) for key in form.keys(): if key[0] == ':': value = form[key].value if type(value) != type([]): value = [value] for value in value: w(''%(key, value)) replace = ItemTemplateReplace(self.globals, locals(), None, None) w(replace.go(s)) w('
') # # $Log: not supported by cvs2svn $ # Revision 1.82 2002/02/21 23:11:45 richard # . fixed some problems in date calculations (calendar.py doesn't handle over- # and under-flow). Also, hour/minute/second intervals may now be more than # 99 each. # # Revision 1.81 2002/02/21 07:21:38 richard # docco # # Revision 1.80 2002/02/21 07:19:08 richard # ... and label, width and height control for extra flavour! # # Revision 1.79 2002/02/21 06:57:38 richard # . Added popup help for classes using the classhelp html template function. # - add # to an item page, and it generates a link to a popup window which displays # the id, name and description for the priority class. The description # field won't exist in most installations, but it will be added to the # default templates. # # Revision 1.78 2002/02/21 06:23:00 richard # *** empty log message *** # # Revision 1.77 2002/02/20 05:05:29 richard # . Added simple editing for classes that don't define a templated interface. # - access using the admin "class list" interface # - limited to admin-only # - requires the csv module from object-craft (url given if it's missing) # # Revision 1.76 2002/02/16 09:10:52 richard # oops # # Revision 1.75 2002/02/16 08:43:23 richard # . #517906 ] Attribute order in "View customisation" # # Revision 1.74 2002/02/16 08:39:42 richard # . #516854 ] "My Issues" and redisplay # # Revision 1.73 2002/02/15 07:08:44 richard # . Alternate email addresses are now available for users. See the MIGRATION # file for info on how to activate the feature. # # Revision 1.72 2002/02/14 23:39:18 richard # . All forms now have "double-submit" protection when Javascript is enabled # on the client-side. # # Revision 1.71 2002/01/23 06:15:24 richard # real (non-string, duh) sorting of lists by node id # # Revision 1.70 2002/01/23 05:47:57 richard # more HTML template cleanup and unit tests # # Revision 1.69 2002/01/23 05:10:27 richard # More HTML template cleanup and unit tests. # - download() now implemented correctly, replacing link(is_download=1) [fixed in the # templates, but link(is_download=1) will still work for existing templates] # # Revision 1.68 2002/01/22 22:55:28 richard # . htmltemplate list() wasn't sorting... # # Revision 1.67 2002/01/22 22:46:22 richard # more htmltemplate cleanups and unit tests # # Revision 1.66 2002/01/22 06:35:40 richard # more htmltemplate tests and cleanup # # Revision 1.65 2002/01/22 00:12:06 richard # Wrote more unit tests for htmltemplate, and while I was at it, I polished # off the implementation of some of the functions so they behave sanely. # # Revision 1.64 2002/01/21 03:25:59 richard # oops # # Revision 1.63 2002/01/21 02:59:10 richard # Fixed up the HTML display of history so valid links are actually displayed. # Oh for some unit tests! :( # # Revision 1.62 2002/01/18 08:36:12 grubert # . add nowrap to history table date cell i.e.