X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fhtmltemplate.py;h=1a083b056711fdf9193e0fa013c73145487bd7e1;hb=e6c4c7f8d5631098772610818c9907b51081dac2;hp=de6f10de8c200c229f2ea923cc81fef7c62bd9eb;hpb=d65cfd8585f28fa9411cd5a6f75bd63691b01ea7;p=roundup.git
diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py
index de6f10d..1a083b0 100644
--- a/roundup/htmltemplate.py
+++ b/roundup/htmltemplate.py
@@ -15,32 +15,38 @@
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: htmltemplate.py,v 1.23 2001-09-10 09:47:18 richard Exp $
+# $Id: htmltemplate.py,v 1.38 2001-10-31 06:58:51 richard Exp $
import os, re, StringIO, urllib, cgi, errno
-import hyperdb, date
-
-class Base:
- def __init__(self, db, templates, classname, nodeid=None, form=None,
- filterspec=None):
- # TODO: really not happy with the way templates is passed on here
- self.db, self.templates = db, templates
- self.classname, self.nodeid = classname, nodeid
- self.form, self.filterspec = form, filterspec
- self.cl = self.db.classes[self.classname]
- self.properties = self.cl.getprops()
-
-class Plain(Base):
- ''' 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)
- '''
- def __call__(self, property):
+import hyperdb, date, password
+
+# 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 TemplateFunctions:
+ 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)
+
+ 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]
@@ -53,6 +59,9 @@ class Plain(Base):
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):
value = str(value)
elif isinstance(propclass, hyperdb.Interval):
@@ -68,13 +77,23 @@ class Plain(Base):
value = ', '.join([linkcl.get(i, k) for i in value])
else:
s = 'Plain: bad propclass "%s"'%propclass
+ if escape:
+ value = cgi.escape(value)
return value
-class Field(Base):
- ''' display a property like the plain displayer, but in a text field
- to be edited
- '''
- def __call__(self, property, size=None, height=None, showid=0):
+ 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 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
+ '''
if not self.nodeid and self.form is None and self.filterspec is None:
return '[Field: not called from item]'
propclass = self.properties[property]
@@ -104,10 +123,18 @@ class Field(Base):
value = cgi.escape(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):
linkcl = self.db.classes[propclass.classname]
l = [''%property]
k = linkcl.labelprop()
+ if value is None:
+ s = 'selected '
+ else:
+ s = ''
+ l.append('- no selection - '%s)
for optionid in linkcl.list():
option = linkcl.get(optionid, k)
s = ''
@@ -146,10 +173,9 @@ class Field(Base):
s = 'Plain: bad propclass "%s"'%propclass
return s
-class Menu(Base):
- ''' for a Link property, display a menu of the available choices
- '''
- def __call__(self, property, size=None, height=None, showid=0):
+ 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)
@@ -161,6 +187,10 @@ class Menu(Base):
linkcl = self.db.classes[propclass.classname]
l = [''%property]
k = linkcl.labelprop()
+ s = ''
+ if value is None:
+ s = 'selected '
+ l.append('- no selection - '%s)
for optionid in linkcl.list():
option = linkcl.get(optionid, k)
s = ''
@@ -191,13 +221,17 @@ class Menu(Base):
return '\n'.join(l)
return '[Menu: not a link]'
-#XXX deviates from spec
-class Link(Base):
- ''' 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
- '''
- def __call__(self, property=None, **args):
+ #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]'
propclass = self.properties[property]
@@ -205,31 +239,46 @@ class Link(Base):
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 '[not assigned]'
+ if value is None: return '[no %s]'%property.capitalize()
linkcl = self.db.classes[linkname]
k = linkcl.labelprop()
linkvalue = linkcl.get(value, k)
- return '%s '%(linkname, value, linkvalue)
+ 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()
+ if not value : return '[no %s]'%property.capitalize()
l = []
for value in value:
linkvalue = linkcl.get(value, k)
- l.append('%s '%(linkname, value, linkvalue))
+ if is_download:
+ l.append('%s '%(linkname, value,
+ linkvalue, linkvalue))
+ else:
+ l.append('%s '%(linkname, value,
+ linkvalue))
return ', '.join(l)
- return '%s '%(self.classname, self.nodeid, value)
+ if isinstance(propclass, hyperdb.String):
+ if value == '': value = '[no %s]'%property.capitalize()
+ if is_download:
+ return '%s '%(self.classname, self.nodeid,
+ value, value)
+ else:
+ return '%s '%(self.classname, self.nodeid, value)
-class Count(Base):
- ''' for a Multilink property, display a count of the number of links in
- the list
- '''
- def __call__(self, property, **args):
+ 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]
@@ -238,14 +287,13 @@ class Count(Base):
return str(len(value))
return '[Count: not a Multilink]'
-# XXX pretty is definitely new ;)
-class Reldate(Base):
- ''' display a Date property in terms of an interval relative to the
- current date (e.g. "+ 3w", "- 2d").
+ # 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
- '''
- def __call__(self, property, pretty=0):
+ 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]
@@ -265,11 +313,10 @@ class Reldate(Base):
return pretty
return str(interval)
-class Download(Base):
- ''' show a Link("file") or Multilink("file") property using links that
- allow you to download files
- '''
- def __call__(self, property, **args):
+ 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]'
propclass = self.properties[property]
@@ -288,66 +335,88 @@ class Download(Base):
return '[Download: not a link]'
-class Checklist(Base):
- ''' for a Link or Multilink property, display checkboxes for the available
- choices to permit filtering
- '''
- def __call__(self, property, **args):
+ 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:
- value = self.cl.get(self.nodeid, property)
+ # 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 = []
- if (isinstance(propclass, hyperdb.Link) or
- isinstance(propclass, hyperdb.Multilink)):
- linkcl = self.db.classes[propclass.classname]
- l = []
- k = linkcl.labelprop()
- for optionid in linkcl.list():
- option = linkcl.get(optionid, k)
- if optionid in value or option in value:
- checked = 'checked'
- else:
- checked = ''
- l.append('%s: '%(
- option, checked, property, option))
- return '\n'.join(l)
- return '[Checklist: not a link]'
-
-class Note(Base):
- ''' display a "note" field, which is a text area for entering a note to
- go along with a change.
- '''
- def __call__(self, rows=5, cols=80):
- # TODO: pull the value from the form
- return ''%(rows,
- cols)
-
-# XXX new function
-class List(Base):
- ''' list the items specified by property using the standard index for
- the class
- '''
- def __call__(self, property, reverse=0):
- propclass = self.properties[property]
- if isinstance(not propclass, hyperdb.Multilink):
+
+ # 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 = 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]'
- fp = StringIO.StringIO()
value = self.cl.get(self.nodeid, property)
if reverse:
value.reverse()
- # TODO: really not happy with the way templates is passed on here
- index(fp, self.templates, self.db, propclass.classname, nodeids=value,
- show_display_form=0)
+
+ # 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
-class History(Base):
- ''' list the history of the item
- '''
- def __call__(self, **args):
+ # XXX new function
+ def do_history(self, **args):
+ ''' list the history of the item
+ '''
if self.nodeid is None:
return "[History: node doesn't exist]"
@@ -364,11 +433,10 @@ class History(Base):
l.append('')
return '\n'.join(l)
-# XXX new function
-class Submit(Base):
- ''' add a submit button for the item
- '''
- def __call__(self):
+ # XXX new function
+ def do_submit(self):
+ ''' add a submit button for the item
+ '''
if self.nodeid:
return ' '
elif self.form is not None:
@@ -386,10 +454,11 @@ class IndexTemplateReplace:
self.locals = locals
self.props = props
- def go(self, text, replace=re.compile(
- r'(([^>]+)">(?P.+?) )|'
- r'(?P[^"]+)">))', re.I|re.S)):
- return replace.sub(self, text)
+ 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'):
@@ -404,241 +473,280 @@ class IndexTemplateReplace:
return eval(command, self.globals, self.locals)
print '*** unhandled match', m.groupdict()
-def sortby(sort_name, columns, filter, sort, group, filterspec):
- 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)
-
-def index(client, templates, db, classname, filterspec={}, filter=[],
- columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
- col_re=re.compile(r']+)">')):
- globals = {
- 'plain': Plain(db, templates, classname, filterspec=filterspec),
- 'field': Field(db, templates, classname, filterspec=filterspec),
- 'menu': Menu(db, templates, classname, filterspec=filterspec),
- 'link': Link(db, templates, classname, filterspec=filterspec),
- 'count': Count(db, templates, classname, filterspec=filterspec),
- 'reldate': Reldate(db, templates, classname, filterspec=filterspec),
- 'download': Download(db, templates, classname, filterspec=filterspec),
- 'checklist': Checklist(db, templates, classname, filterspec=filterspec),
- 'list': List(db, templates, classname, filterspec=filterspec),
- 'history': History(db, templates, classname, filterspec=filterspec),
- 'submit': Submit(db, templates, classname, filterspec=filterspec),
- 'note': Note(db, templates, classname, filterspec=filterspec)
- }
- cl = db.classes[classname]
- properties = cl.getprops()
- w = client.write
- w('\n')
+ # display the filter section
+ if (hasattr(self.client, 'FILTER_POSITION') and
+ self.client.FILTER_POSITION in ('top and bottom', 'bottom')):
+ w('\n')
+ self.filter_section(filter_template, filter, columns, group,
+ all_filters, all_columns, show_display_form, show_customization)
+ w(' \n')
+
+
+ def filter_section(self, template, filter, columns, group, all_filters,
+ all_columns, show_display_form, show_customization):
+
+ w = self.client.write
+
+ if template and filter:
+ # display the filter section
+ w('')
+
+ # now add in the filter/columns/group/etc config table form
+ w(' ' %
+ show_customization )
+ w('\n')
+
+ 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
@@ -650,10 +758,11 @@ class ItemTemplateReplace:
self.cl = cl
self.nodeid = nodeid
- def go(self, text, replace=re.compile(
- r'(([^>]+)">(?P.+?) )|'
- r'(?P[^"]+)">))', re.I|re.S)):
- return replace.sub(self, text)
+ 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'):
@@ -668,83 +777,133 @@ class ItemTemplateReplace:
return eval(command, self.globals, self.locals)
print '*** unhandled match', m.groupdict()
-def item(client, templates, db, classname, nodeid, replace=re.compile(
- r'((?P[^>]+)">)|'
- r'(?P )|'
- r'(?P[^"]+)">))', re.I)):
-
- globals = {
- 'plain': Plain(db, templates, classname, nodeid),
- 'field': Field(db, templates, classname, nodeid),
- 'menu': Menu(db, templates, classname, nodeid),
- 'link': Link(db, templates, classname, nodeid),
- 'count': Count(db, templates, classname, nodeid),
- 'reldate': Reldate(db, templates, classname, nodeid),
- 'download': Download(db, templates, classname, nodeid),
- 'checklist': Checklist(db, templates, classname, nodeid),
- 'list': List(db, templates, classname, nodeid),
- 'history': History(db, templates, classname, nodeid),
- 'submit': Submit(db, templates, classname, nodeid),
- 'note': Note(db, templates, classname, nodeid)
- }
-
- cl = db.classes[classname]
- properties = cl.getprops()
-
- 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 = client.write
- w(''%(classname, nodeid))
- s = open(os.path.join(templates, classname+'.item')).read()
- replace = ItemTemplateReplace(globals, locals(), cl, nodeid)
- w(replace.go(s))
- w(' ')
-
-
-def newitem(client, templates, db, classname, form, replace=re.compile(
- r'((?P[^>]+)">)|'
- r'(?P )|'
- r'(?P[^"]+)">))', re.I)):
- globals = {
- 'plain': Plain(db, templates, classname, form=form),
- 'field': Field(db, templates, classname, form=form),
- 'menu': Menu(db, templates, classname, form=form),
- 'link': Link(db, templates, classname, form=form),
- 'count': Count(db, templates, classname, form=form),
- 'reldate': Reldate(db, templates, classname, form=form),
- 'download': Download(db, templates, classname, form=form),
- 'checklist': Checklist(db, templates, classname, form=form),
- 'list': List(db, templates, classname, form=form),
- 'history': History(db, templates, classname, form=form),
- 'submit': Submit(db, templates, classname, form=form),
- 'note': Note(db, templates, classname, form=form)
- }
-
- cl = db.classes[classname]
- properties = cl.getprops()
-
- w = client.write
- try:
- s = open(os.path.join(templates, classname+'.newitem')).read()
- except:
- s = open(os.path.join(templates, classname+'.item')).read()
- w(''%classname)
- 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(globals, locals(), None, None)
- w(replace.go(s))
- w(' ')
+
+class ItemTemplate(TemplateFunctions):
+ def __init__(self, client, templates, classname):
+ self.client = client
+ self.templates = templates
+ self.classname = classname
+
+ # derived
+ self.db = self.client.db
+ self.cl = self.db.classes[self.classname]
+ self.properties = self.cl.getprops()
+
+ TemplateFunctions.__init__(self)
+
+ 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):
+ def __init__(self, client, templates, classname):
+ self.client = client
+ self.templates = templates
+ self.classname = classname
+
+ # derived
+ self.db = self.client.db
+ self.cl = self.db.classes[self.classname]
+ self.properties = self.cl.getprops()
+
+ TemplateFunctions.__init__(self)
+
+ 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:
+ 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.37 2001/10/31 06:24:35 richard
+# Added do_stext to htmltemplate, thanks Brad Clements.
+#
+# Revision 1.36 2001/10/28 22:51:38 richard
+# Fixed ENOENT/WindowsError thing, thanks Juergen Hermann
+#
+# Revision 1.35 2001/10/24 00:04:41 richard
+# Removed the "infinite authentication loop", thanks Roch'e
+#
+# Revision 1.34 2001/10/23 22:56:36 richard
+# Bugfix in filter "widget" placement, thanks Roch'e
+#
+# Revision 1.33 2001/10/23 01:00:18 richard
+# Re-enabled login and registration access after lopping them off via
+# disabling access for anonymous users.
+# Major re-org of the htmltemplate code, cleaning it up significantly. Fixed
+# a couple of bugs while I was there. Probably introduced a couple, but
+# things seem to work OK at the moment.
+#
+# Revision 1.32 2001/10/22 03:25:01 richard
+# Added configuration for:
+# . anonymous user access and registration (deny/allow)
+# . filter "widget" location on index page (top, bottom, both)
+# Updated some documentation.
+#
+# Revision 1.31 2001/10/21 07:26:35 richard
+# feature #473127: Filenames. I modified the file.index and htmltemplate
+# source so that the filename is used in the link and the creation
+# information is displayed.
+#
+# Revision 1.30 2001/10/21 04:44:50 richard
+# bug #473124: UI inconsistency with Link fields.
+# This also prompted me to fix a fairly long-standing usability issue -
+# that of being able to turn off certain filters.
+#
+# Revision 1.29 2001/10/21 00:17:56 richard
+# CGI interface view customisation section may now be hidden (patch from
+# Roch'e Compaan.)
+#
+# Revision 1.28 2001/10/21 00:00:16 richard
+# Fixed Checklist function - wasn't always working on a list.
+#
+# Revision 1.27 2001/10/20 12:13:44 richard
+# Fixed grouping of non-str properties (thanks Roch'e Compaan)
+#
+# Revision 1.26 2001/10/14 10:55:00 richard
+# Handle empty strings in HTML template Link function
+#
+# Revision 1.25 2001/10/09 07:25:59 richard
+# Added the Password property type. See "pydoc roundup.password" for
+# implementation details. Have updated some of the documentation too.
+#
+# Revision 1.24 2001/09/27 06:45:58 richard
+# *gak* ... xmp is Old Skool apparently. Am using pre again by have the option
+# on the plain() template function to escape the text for HTML.
+#
+# Revision 1.23 2001/09/10 09:47:18 richard
+# Fixed bug in the generation of links to Link/Multilink in indexes.
+# (thanks Hubert Hoegl)
+# Added AssignedTo to the "classic" schema's item page.
+#
# Revision 1.22 2001/08/30 06:01:17 richard
# Fixed missing import in mailgw :(
#