X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fhtmltemplate.py;h=1a083b056711fdf9193e0fa013c73145487bd7e1;hb=e6c4c7f8d5631098772610818c9907b51081dac2;hp=1a8e1d91efb3e4901de092a4b8c33835902b7111;hpb=f8321089b2a445b0ee36ade9b6e65924073eff6b;p=roundup.git
diff --git a/roundup/htmltemplate.py b/roundup/htmltemplate.py
index 1a8e1d9..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.20 2001-08-15 23:43: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,29 +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):
- if value is None:
- return '[not assigned]'
- linkcl = self.db.classes[propclass.classname]
+ 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)
- return '%s '%(linkcl, value, linkvalue)
+ if is_download:
+ return '%s '%(linkname, value,
+ linkvalue, linkvalue)
+ else:
+ return '%s '%(linkname, value, linkvalue)
if isinstance(propclass, hyperdb.Multilink):
- linkcl = self.db.classes[propclass.classname]
+ 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 '%(linkcl, 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]
@@ -236,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]
@@ -263,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]
@@ -286,65 +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, **args):
- 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()
- args['show_display_form'] = 0
value = self.cl.get(self.nodeid, property)
- # 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)
+ if reverse:
+ value.reverse()
+
+ # 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]"
@@ -361,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:
@@ -383,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'):
@@ -401,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 = '-'
+class IndexTemplate(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)
+
+ 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
+ template = open(os.path.join(self.templates,
+ self.classname+'.index')).read()
+ all_columns = self.col_re.findall(template)
+ if not columns:
+ columns = []
+ for name in all_columns:
+ columns.append(name)
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('