From 3f1169ea2ce17de960c9e982d14095310101aeb2 Mon Sep 17 00:00:00 2001 From: richard Date: Tue, 3 Sep 2002 02:51:46 +0000 Subject: [PATCH] missed removing this one git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@1030 57a73879-2fb5-44c3-a270-3262357dd7e2 --- roundup/htmltemplate.py | 1280 --------------------------------------- 1 file changed, 1280 deletions(-) delete mode 100644 roundup/htmltemplate.py diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py deleted file mode 100644 index a3578e6..0000000 --- a/roundup/htmltemplate.py +++ /dev/null @@ -1,1280 +0,0 @@ -# -# 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.112 2002-08-19 00:21:37 richard Exp $ - -__doc__ = """ -Template engine. - -Three types of template files exist: - .index used by IndexTemplate - .item used by ItemTemplate and NewItemTemplate - .filter used by IndexTemplate - -Templating works by instantiating one of the *Template classes above, -passing in a handle to the cgi client, identifying the class and the -template source directory. - -The *Template class reads in the parsed template (parsing and caching -as needed). When the render() method is called, the parse tree is -traversed. Each node is either text (immediately output), a Require -instance (resulting in a call to _test()), a Property instance (treated -differently by .item and .index) or a Diplay instance (resulting in -a call to one of the template_funcs.py functions). - -In a .index list, Property tags are used to determine columns, and -disappear before the actual rendering. Note that the template will -be rendered many times in a .index. - -In a .item, Property tags check if the node has the property. - -Templating is tested by the test_htmltemplate unit test suite. If you add -a template function, add a test for all data types or the angry pink bunny -will hunt you down. -""" -import weakref, os, types, cgi, sys, urllib, re, traceback -try: - import cStringIO as StringIO -except ImportError: - import StringIO -try: - import cPickle as pickle -except ImportError: - import pickle -from template_parser import RoundupTemplate, Display, Property, Require -from i18n import _ -import hyperdb, template_funcs - -MTIME = os.path.stat.ST_MTIME - -class MissingTemplateError(ValueError): - '''Error raised when a template file is missing - ''' - pass - -# what a tag results in -def _test(attributes, client, classname, nodeid): - tests = {} - for nm, val in attributes: - tests[nm] = val - userid = client.db.user.lookup(client.user) - security = client.db.security - perms = tests.get('permission', None) - if perms: - del tests['permission'] - perms = perms.split(',') - for value in perms: - if security.hasPermission(value, userid, classname): - # just passing the permission is OK - return 1 - # try the attr conditions until one is met - if nodeid is None: - return 0 - if not tests: - return 0 - for propname, value in tests.items(): - if value == '$userid': - tests[propname] = userid - return security.hasNodePermission(classname, nodeid, **tests) - -# what a tag results in -def _display(attributes, client, classname, cl, props, nodeid, filterspec=None): - call = attributes[0][1] #eg "field('prop2')" - pos = call.find('(') - funcnm = call[:pos] - func = templatefuncs.get(funcnm, None) - if func: - argstr = call[pos:] - args, kws = eval('splitargs'+argstr) - args = (client, classname, cl, props, nodeid, filterspec) + args - rslt = func(*args, **kws) - else: - rslt = _('no template function %s' % funcnm) - client.write(rslt) - -# what a tag results in -def _exists(attributes, cl, props, nodeid): - nm = attributes[0][1] - if nodeid: - return cl.get(nodeid, nm) - return props.get(nm, 0) - -class Template: - ''' base class of all templates. - - knows how to compile & load a template. - knows how to render one item. ''' - def __init__(self, client, templates, classname): - if isinstance(client, weakref.ProxyType): - self.client = client - else: - self.client = weakref.proxy(client) - self.templatedir = templates - self.compiledtemplatedir = self.templatedir + 'c' - self.classname = classname - self.cl = self.client.db.getclass(self.classname) - self.properties = self.cl.getprops() - self.template = self._load() - self.filterspec = None - self.columns = None - self.nodeid = None - - def _load(self): - ''' Load a template from disk and parse it. - - Once parsed, the template is stored as a pickle in the - "htmlc" directory of the instance. If the file in there is - newer than the source template file, it's used in preference so - we don't have to re-parse. - ''' - # figure where the template source is - src = os.path.join(self.templatedir, self.classname + self.extension) - - if not os.path.exists(src): - # hrm, nothing exactly matching what we're after, see if we can - # fall back on another template - if hasattr(self, 'fallbackextension'): - self.extension = self.fallbackextension - return self._load() - raise MissingTemplateError, self.classname + self.extension - - # figure where the compiled template should be - cpl = os.path.join(self.compiledtemplatedir, - self.classname + self.extension) - - if (not os.path.exists(cpl) - or os.stat(cpl)[MTIME] < os.stat(src)[MTIME]): - # there's either no compiled template, or it's out of date - parser = RoundupTemplate() - parser.feed(open(src, 'r').read()) - tmplt = parser.structure - try: - if not os.path.exists(self.compiledtemplatedir): - os.makedirs(self.compiledtemplatedir) - f = open(cpl, 'wb') - pickle.dump(tmplt, f) - f.close() - except Exception, e: - print "ouch in pickling: got a %s %r" % (e, e.args) - pass - else: - # load the compiled template - f = open(cpl, 'rb') - tmplt = pickle.load(f) - return tmplt - - def _render(self, tmplt=None, test=_test, display=_display, exists=_exists): - ''' Render the template - ''' - if tmplt is None: - tmplt = self.template - - # go through the list of template "commands" - for entry in tmplt: - if isinstance(entry, type('')): - # string - just write it out - self.client.write(entry) - - elif isinstance(entry, Require): - # a tag - if test(entry.attributes, self.client, self.classname, - self.nodeid): - # require test passed, render the ok clause - self._render(entry.ok) - elif entry.fail: - # if there's a fail clause, render it - self._render(entry.fail) - - elif isinstance(entry, Display): - # execute the function - display(entry.attributes, self.client, self.classname, - self.cl, self.properties, self.nodeid, self.filterspec) - - elif isinstance(entry, Property): - # do a test - if self.columns is None: - # doing an Item - see if the property is present - if exists(entry.attributes, self.cl, self.properties, - self.nodeid): - self._render(entry.ok) - # XXX erm, should this be commented out? - #elif entry.attributes[0][1] in self.columns: - else: - self._render(entry.ok) - -class IndexTemplate(Template): - ''' renders lists of items - - shows filter form (for new queries / to refine queries) - has clickable column headers (sort by this column / sort reversed) - has group by lines - has full text search match lines ''' - extension = '.index' - - def __init__(self, client, templates, classname): - Template.__init__(self, client, templates, classname) - - def render(self, **kw): - ''' Render the template - well, wrap the rendering in a try/finally - so we're guaranteed to clean up after ourselves - ''' - try: - self.renderInner(**kw) - finally: - self.cl = self.properties = self.client = None - - def renderInner(self, filterspec={}, search_text='', filter=[], columns=[], - sort=[], group=[], show_display_form=1, nodeids=None, - show_customization=1, show_nodes=1, pagesize=50, startwith=0, - simple_search=1, xtracols=None): - ''' Take all the index arguments and render some HTML - ''' - - self.filterspec = filterspec - w = self.client.write - cl = self.cl - properties = self.properties - if xtracols is None: - xtracols = [] - - # XXX deviate from spec here ... - # load the index section template and figure the default columns from it - displayable_props = [] - all_columns = [] - for node in self.template: - if isinstance(node, Property): - colnm = node.attributes[0][1] - if properties.has_key(colnm): - displayable_props.append(colnm) - all_columns.append(colnm) - elif colnm in xtracols: - all_columns.append(colnm) - if not columns: - columns = all_columns - else: - # re-sort columns to be the same order as displayable_props - l = [] - for name in all_columns: - if name in columns: - l.append(name) - columns = l - self.columns = columns - - # optimize the template - self.template = self._optimize(self.template) - - # display the filter section - if (show_display_form and - self.client.instance.FILTER_POSITION.startswith('top')): - w('
\n'% - self.client.classname) - self.filter_section(search_text, filter, columns, group, - displayable_props, sort, filterspec, pagesize, startwith, - simple_search) - - # now display the index section - w('\n') - w('\n') - for name in columns: - cname = name.capitalize() - if show_display_form and not cname in xtracols: - sb = self.sortby(name, search_text, filterspec, columns, filter, - group, sort, pagesize) - anchor = "%s?%s"%(self.client.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 show_nodes: - matches = None - if nodeids is None: - if search_text != '': - matches = self.client.db.indexer.search( - re.findall(r'\b\w{2,25}\b', search_text), cl) - nodeids = cl.filter(matches, filterspec, sort, group) - linecount = 0 - for nodeid in nodeids[startwith:startwith+pagesize]: - # check for a group heading - if group_names: - this_group = [cl.get(nodeid, name, _('[no value]')) - for name in group_names] - if this_group != old_group: - l = [] - for name in group_names: - prop = properties[name] - if isinstance(prop, hyperdb.Link): - group_cl = self.client.db.getclass(prop.classname) - key = group_cl.getkey() - if key is None: - key = group_cl.labelprop() - value = cl.get(nodeid, name) - if value is None: - l.append(_('[unselected %(classname)s]')%{ - 'classname': prop.classname}) - else: - l.append(group_cl.get(value, key)) - elif isinstance(prop, hyperdb.Multilink): - group_cl = self.client.db.getclass(prop.classname) - key = group_cl.getkey() - for value in cl.get(nodeid, name): - l.append(group_cl.get(value, key)) - else: - value = cl.get(nodeid, name, - _('[no value]')) - if value is None: - value = _('[empty %(name)s]')%locals() - else: - value = str(value) - l.append(value) - w('' - '\n'%( - len(columns), ', '.join(l))) - old_group = this_group - - # display this node's row - self.nodeid = nodeid - self._render() - if matches: - self.node_matches(matches[nodeid], len(columns)) - self.nodeid = None - - w('
%s' - '%s
' - '%s
\n') - # the previous and next links - if nodeids: - baseurl = self.buildurl(filterspec, search_text, filter, - columns, sort, group, pagesize) - if startwith > 0: - prevurl = '<< '\ - 'Previous page'%(baseurl, max(0, startwith-pagesize)) - else: - prevurl = "" - if startwith + pagesize < len(nodeids): - nexturl = 'Next page '\ - '>>'%(baseurl, startwith+pagesize) - else: - nexturl = "" - if prevurl or nexturl: - w(''' - - -
%s%s
\n'''%(prevurl, nexturl)) - - # display the filter section - if (show_display_form and hasattr(self.client.instance, - 'FILTER_POSITION') and - self.client.instance.FILTER_POSITION.endswith('bottom')): - w('\n'% - self.client.classname) - self.filter_section(search_text, filter, columns, group, - displayable_props, sort, filterspec, pagesize, startwith, - simple_search) - - def _optimize(self, tmplt): - columns = self.columns - t = [] - for entry in tmplt: - if isinstance(entry, Property): - if entry.attributes[0][1] in columns: - t.extend(entry.ok) - else: - t.append(entry) - return t - - def buildurl(self, filterspec, search_text, filter, columns, sort, group, - pagesize): - d = {'pagesize':pagesize, 'pagesize':pagesize, - 'classname':self.classname} - if search_text: - d['searchtext'] = 'search_text=%s&' % search_text - else: - d['searchtext'] = '' - d['filter'] = ','.join(map(urllib.quote,filter)) - d['columns'] = ','.join(map(urllib.quote,columns)) - d['sort'] = ','.join(map(urllib.quote,sort)) - d['group'] = ','.join(map(urllib.quote,group)) - tmp = [] - for col, vals in filterspec.items(): - vals = ','.join(map(urllib.quote,vals)) - tmp.append('%s=%s' % (col, vals)) - d['filters'] = '&'.join(tmp) - return ('%(classname)s?%(searchtext)s%(filters)s&:sort=%(sort)s&' - ':filter=%(filter)s&:group=%(group)s&:columns=%(columns)s&' - ':pagesize=%(pagesize)s'%d) - - def node_matches(self, match, colspan): - ''' display the files and messages for a node that matched a - full text search - ''' - w = self.client.write - db = self.client.db - message_links = [] - file_links = [] - if match.has_key('messages'): - for msgid in match['messages']: - k = db.msg.labelprop(1) - lab = db.msg.get(msgid, k) - msgpath = 'msg%s'%msgid - message_links.append('%(lab)s' - %locals()) - w(_('' - '  Matched messages: %s\n')%( - colspan, ', '.join(message_links))) - - if match.has_key('files'): - for fileid in match['files']: - filename = db.file.get(fileid, 'name') - filepath = 'file%s/%s'%(fileid, filename) - file_links.append('%(filename)s' - %locals()) - w(_('' - '  Matched files: %s\n')%( - colspan, ', '.join(file_links))) - - def filter_form(self, search_text, filter, columns, group, all_columns, - sort, filterspec, pagesize): - sortspec = {} - for i in range(len(sort)): - mod = '' - colnm = sort[i] - if colnm[0] == '-': - mod = '-' - colnm = colnm[1:] - sortspec[colnm] = '%d%s' % (i+1, mod) - - startwith = 0 - rslt = [] - w = rslt.append - - # display the filter section - w( '
') - w( '') - w( '') - w(_(' ')) - w( '') - # see if we have any indexed properties - if self.client.classname in self.client.db.config.HEADER_SEARCH_LINKS: - w('') - w(' ') - w(' '%search_text) - w('') - w( '') - w( ' ') - w(_(' ')) - w(_(' ')) - w(_(' ')) - w(_(' ')) - w( '') - - properties = self.client.db.getclass(self.classname).getprops() - all_columns = properties.keys() - all_columns.sort() - for nm in all_columns: - propdescr = properties.get(nm, None) - if not propdescr: - print "hey sysadmin - %s is not a property of %r" % (nm, self.classname) - continue - w( '') - w(_(' ' % nm.capitalize())) - # show column - can't show multilinks - if isinstance(propdescr, hyperdb.Multilink): - w(' ') - else: - checked = columns and nm in columns or 0 - checked = ('', 'checked')[checked] - w(' ' % (nm, checked) ) - # can only group on Link - if isinstance(propdescr, hyperdb.Link): - checked = group and nm in group or 0 - checked = ('', 'checked')[checked] - w(' ' % (nm, checked) ) - else: - w(' ') - # sort - no sort on Multilinks - if isinstance(propdescr, hyperdb.Multilink): - w('') - else: - val = sortspec.get(nm, '') - w('' % (nm,val)) - # condition - val = '' - if isinstance(propdescr, hyperdb.Link): - op = "is in " - xtra = '(list)' \ - % (propdescr.classname, self.client.db.getclass(propdescr.classname).labelprop()) - val = ','.join(filterspec.get(nm, '')) - elif isinstance(propdescr, hyperdb.Multilink): - op = "contains " - xtra = '(list)' \ - % (propdescr.classname, self.client.db.getclass(propdescr.classname).labelprop()) - val = ','.join(filterspec.get(nm, '')) - elif isinstance(propdescr, hyperdb.String) and nm != 'id': - op = "equals " - xtra = "" - val = filterspec.get(nm, '') - elif isinstance(propdescr, hyperdb.Boolean): - op = "is " - xtra = "" - val = filterspec.get(nm, None) - if val is not None: - val = 'True' and val or 'False' - else: - val = '' - elif isinstance(propdescr, hyperdb.Number): - op = "equals " - xtra = "" - val = str(filterspec.get(nm, '')) - else: - w('') - continue - checked = filter and nm in filter or 0 - checked = ('', 'checked')[checked] - w( ' ' \ - % (nm, checked)) - w(_(' ' % (op, nm, val, xtra))) - w( '') - w('') - w(' ') - w('') - w('') - w(_(' ')) - w(' ' % pagesize) - w(' ') - w('') - w('') - w(_(' ')) - w(' ' % startwith) - w(' ') - w(' ') - w('') - w('') - - return '\n'.join(rslt) - - def simple_filter_form(self, search_text, filter, columns, group, all_columns, - sort, filterspec, pagesize): - - startwith = 0 - rslt = [] - w = rslt.append - - # display the filter section - w( '
') - w( '
Filter specification...
Search Terms   ' - '' - '
 ShowGroupSortCondition
%s
%s' - '%s

Pagesize
Start With
') - w( '') - w(_(' ')) - w( '') - - if group: - selectedgroup = group[0] - groupopts = ['',''] - descending = 0 - if sort: - selectedsort = sort[0] - if selectedsort[0] == '-': - selectedsort = selectedsort[1:] - descending = 1 - sortopts = ['', ''] - - for nm in all_columns: - propdescr = self.client.db.getclass(self.client.classname).getprops().get(nm, None) - if not propdescr: - print "hey sysadmin - %s is not a property of %r" % (nm, self.classname) - continue - if isinstance(propdescr, hyperdb.Link): - selected = '' - if nm == selectedgroup: - selected = 'selected' - groupopts.append('' % (nm, selected, nm.capitalize())) - selected = '' - if nm == selectedsort: - selected = 'selected' - sortopts.append('' % (nm, selected, nm.capitalize())) - if len(groupopts) > 2: - groupopts.append('') - groupopts = '\n'.join(groupopts) - w('') - w(' ') - w(' ' % groupopts) - w('') - if len(sortopts) > 2: - sortopts.append('') - sortopts = '\n'.join(sortopts) - w('') - w(' ') - checked = descending and 'checked' or '' - w(' ' % (sortopts, checked)) - w('') - w('' % urllib.quote(search_text)) - w('' % ','.join(filter)) - w('' % ','.join(columns)) - for nm in filterspec.keys(): - w('' % (nm, ','.join(filterspec[nm]))) - w('' % pagesize) - - return '\n'.join(rslt) - - def filter_section(self, search_text, filter, columns, group, all_columns, - sort, filterspec, pagesize, startwith, simpleform=1): - w = self.client.write - if simpleform: - w(self.simple_filter_form(search_text, filter, columns, group, - all_columns, sort, filterspec, pagesize)) - else: - w(self.filter_form(search_text, filter, columns, group, all_columns, - sort, filterspec, pagesize)) - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' \n') - if (not simpleform - and self.client.db.getclass('user').getprops().has_key('queries') - and not self.client.user in (None, "anonymous")): - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' \n') - w(' ') - w(' \n') - w(' \n') - w(' \n' % self.classname) - w(' \n') - w(' \n') - w('
Query modifications...
Group%s
Sort%s Descending' - '

 

NameIf you give the query a name ' - 'and click Save, it will appear on your menu. Saved queries may be ' - 'edited by going to My Details and clicking on the query name.
 
\n') - - def sortby(self, sort_name, search_text, filterspec, columns, filter, - group, sort, pagesize): - ''' Figure the link for a column heading so we can sort by that - column - ''' - l = [] - w = l.append - if search_text: - w('search_text=%s' % search_text) - 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))) - w(':pagesize=%s' % pagesize) - w(':startwith=0') - - # handle the sorting - if we're already sorting by this column, - # then reverse the sorting, otherwise set the sorting to be this - # column only - sorting = None - if len(sort) == 1: - name = sort[0] - dir = name[0] - if dir == '-' and name[1:] == sort_name: - sorting = ':sort=%s'%sort_name - elif name == sort_name: - sorting = ':sort=-%s'%sort_name - if sorting is None: - sorting = ':sort=%s'%sort_name - w(sorting) - - return '&'.join(l) - -class ItemTemplate(Template): - ''' show one node as a form ''' - extension = '.item' - def __init__(self, client, templates, classname): - Template.__init__(self, client, templates, classname) - self.nodeid = client.nodeid - def render(self, nodeid): - try: - cl = self.cl - properties = self.properties - if (properties.has_key('type') and - 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)) - try: - self._render() - except: - # make sure we don't commit any changes - self.client.db.rollback() - s = StringIO.StringIO() - traceback.print_exc(None, s) - w('
%s
'%cgi.escape(s.getvalue())) - w('
') - finally: - self.cl = self.properties = self.client = None - -class NewItemTemplate(Template): - ''' display a form for creating a new node ''' - extension = '.newitem' - fallbackextension = '.item' - def __init__(self, client, templates, classname): - Template.__init__(self, client, templates, classname) - def render(self, form): - try: - self.form = form - w = self.client.write - c = self.client.classname - 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)) - self._render() - w('
') - finally: - self.cl = self.properties = self.client = None - -def splitargs(*args, **kws): - return args, kws -# [('permission', 'perm2,perm3'), ('assignedto', '$userid'), ('status', 'open')] - -templatefuncs = {} -for nm in template_funcs.__dict__.keys(): - if nm.startswith('do_'): - templatefuncs[nm[3:]] = getattr(template_funcs, nm) - -# -# $Log: not supported by cvs2svn $ -# Revision 1.111 2002/08/15 00:40:10 richard -# cleanup -# -# Revision 1.110 2002/08/13 20:16:09 gmcm -# 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. -# -# Revision 1.109 2002/08/01 15:06:08 gmcm -# Use same regex to split search terms as used to index text. -# Fix to back_metakit for not changing journaltag on reopen. -# Fix htmltemplate's do_link so [No ] strings are href'd. -# Fix bogus "nosy edited ok" msg - the **d syntax does NOT share d between caller and callee. -# -# Revision 1.108 2002/07/31 22:40:50 gmcm -# Fixes to the search form and saving queries. -# Fixes to sorting in back_metakit.py. -# -# Revision 1.107 2002/07/30 05:27:30 richard -# nicer error messages, and a bugfix -# -# Revision 1.106 2002/07/30 02:41:04 richard -# Removed the confusing, ugly two-column sorting stuff. Column heading clicks -# now only sort on one column. Nice and simple and obvious. -# -# Revision 1.105 2002/07/26 08:26:59 richard -# Very close now. The cgi and mailgw now use the new security API. The two -# templates have been migrated to that setup. Lots of unit tests. Still some -# issue in the web form for editing Roles assigned to users. -# -# Revision 1.104 2002/07/25 07:14:05 richard -# Bugger it. Here's the current shape of the new security implementation. -# Still to do: -# . call the security funcs from cgi and mailgw -# . change shipped templates to include correct initialisation and remove -# the old config vars -# ... that seems like a lot. The bulk of the work has been done though. Honest :) -# -# Revision 1.103 2002/07/20 19:29:10 gmcm -# Fixes/improvements to the search form & saved queries. -# -# Revision 1.102 2002/07/18 23:07:08 richard -# Unit tests and a few fixes. -# -# Revision 1.101 2002/07/18 11:17:30 gmcm -# Add Number and Boolean types to hyperdb. -# Add conversion cases to web, mail & admin interfaces. -# Add storage/serialization cases to back_anydbm & back_metakit. -# -# Revision 1.100 2002/07/18 07:01:54 richard -# minor bugfix -# -# Revision 1.99 2002/07/17 12:39:10 gmcm -# Saving, running & editing queries. -# -# Revision 1.98 2002/07/10 00:17:46 richard -# . added sorting of checklist HTML display -# -# Revision 1.97 2002/07/09 05:20:09 richard -# . added email display function - mangles email addrs so they're not so easily -# scraped from the web -# -# Revision 1.96 2002/07/09 04:19:09 richard -# Added reindex command to roundup-admin. -# Fixed reindex on first access. -# Also fixed reindexing of entries that change. -# -# Revision 1.95 2002/07/08 15:32:06 gmcm -# Pagination of index pages. -# New search form. -# -# Revision 1.94 2002/06/27 15:38:53 gmcm -# Fix the cycles (a clear method, called after render, that removes -# the bound methods from the globals dict). -# Use cl.filter instead of cl.list followed by sortfunc. For some -# backends (Metakit), filter can sort at C speeds, cutting >10 secs -# off of filling in the box for assigned_to when you -# have 600+ users. -# -# Revision 1.93 2002/06/27 12:05:25 gmcm -# Default labelprops to id. -# In history, make sure there's a .item before making a link / multilink into an href. -# Also in history, cgi.escape String properties. -# Clean up some of the reference cycles. -# -# Revision 1.92 2002/06/11 04:57:04 richard -# Added optional additional property to display in a Multilink form menu. -# -# Revision 1.91 2002/05/31 00:08:02 richard -# can now just display a link/multilink id - useful for stylesheet stuff -# -# Revision 1.90 2002/05/25 07:16:24 rochecompaan -# Merged search_indexing-branch with HEAD -# -# Revision 1.89 2002/05/15 06:34:47 richard -# forgot to fix the templating for last change -# -# Revision 1.88 2002/04/24 08:34:35 rochecompaan -# Sorting was applied to all nodes of the MultiLink class instead of -# the nodes that are actually linked to in the "field" template -# function. This adds about 20+ seconds in the display of an issue if -# your database has a 1000 or more issue in it. -# -# Revision 1.87 2002/04/03 06:12:46 richard -# Fix for date properties as labels. -# -# Revision 1.86 2002/04/03 05:54:31 richard -# Fixed serialisation problem by moving the serialisation step out of the -# hyperdb.Class (get, set) into the hyperdb.Database. -# -# Also fixed htmltemplate after the showid changes I made yesterday. -# -# Unit tests for all of the above written. -# -# Revision 1.85 2002/04/02 01:40:58 richard -# . link() htmltemplate function now has a "showid" option for links and -# multilinks. When true, it only displays the linked node id as the anchor -# text. The link value is displayed as a tooltip using the title anchor -# attribute. -# -# Revision 1.84.2.2 2002/04/20 13:23:32 rochecompaan -# We now have a separate search page for nodes. Search links for -# different classes can be customized in instance_config similar to -# index links. -# -# Revision 1.84.2.1 2002/04/19 19:54:42 rochecompaan -# cgi_client.py -# removed search link for the time being -# moved rendering of matches to htmltemplate -# hyperdb.py -# filtering of nodes on full text search incorporated in filter method -# roundupdb.py -# added paramater to call of filter method -# roundup_indexer.py -# added search method to RoundupIndexer class -# -# Revision 1.84 2002/03/29 19:41:48 rochecompaan -# . Fixed display of mutlilink properties when using the template -# functions, menu and plain. -# -# Revision 1.83 2002/02/27 04:14:31 richard -# Ran it through pychecker, made fixes -# -# 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.