index 001c1da656ff591eeb61604d9d8709761f52ff8c..324e75f47ac5114833fbdeeb51b05fb6139f2706 100644 (file)
-import sys, cgi, urllib, os, re, os.path, time, errno
+"""Implements the API used in the HTML templating for the web interface.
+"""
-from roundup import hyperdb, date
+todo = '''
+- Most methods should have a "default" arg to supply a value
+ when none appears in the hyperdb or request.
+- Multilink property additions: change_note and new_upload
+- Add class.find() too
+- NumberHTMLProperty should support numeric operations
+- HTMLProperty should have an isset() method
+'''
+
+__docformat__ = 'restructuredtext'
+
+from __future__ import nested_scopes
+
+import sys, cgi, urllib, os, re, os.path, time, errno, mimetypes
+
+from roundup import hyperdb, date, rcsv
from roundup.i18n import _
try:
class NoTemplate(Exception):
pass
+class Unauthorised(Exception):
+ def __init__(self, action, klass):
+ self.action = action
+ self.klass = klass
+ def __str__(self):
+ return 'You are not allowed to %s items of class %s'%(self.action,
+ self.klass)
+
+def find_template(dir, name, extension):
+ ''' Find a template in the nominated dir
+ '''
+ # find the source
+ if extension:
+ filename = '%s.%s'%(name, extension)
+ else:
+ filename = name
+
+ # try old-style
+ src = os.path.join(dir, filename)
+ if os.path.exists(src):
+ return (src, filename)
+
+ # try with a .html extension (new-style)
+ filename = filename + '.html'
+ src = os.path.join(dir, filename)
+ if os.path.exists(src):
+ return (src, filename)
+
+ # no extension == no generic template is possible
+ if not extension:
+ raise NoTemplate, 'Template file "%s" doesn\'t exist'%name
+
+ # try for a _generic template
+ generic = '_generic.%s'%extension
+ src = os.path.join(dir, generic)
+ if os.path.exists(src):
+ return (src, generic)
+
+ # finally, try _generic.html
+ generic = generic + '.html'
+ src = os.path.join(dir, generic)
+ if os.path.exists(src):
+ return (src, generic)
+
+ raise NoTemplate, 'No template file exists for templating "%s" '\
+ 'with template "%s" (neither "%s" nor "%s")'%(name, extension,
+ filename, generic)
+
class Templates:
templates = {}
if os.path.isdir(filename): continue
if '.' in filename:
name, extension = filename.split('.')
- self.getTemplate(name, extension)
+ self.get(name, extension)
else:
- self.getTemplate(filename, None)
+ self.get(filename, None)
def get(self, name, extension=None):
''' Interface to get a template, possibly loading a compiled template.
# split name
name, extension = name.split('.')
- # find the source, figure the time it was last modified
- if extension:
- filename = '%s.%s'%(name, extension)
- else:
- filename = name
+ # find the source
+ src, filename = find_template(self.dir, name, extension)
- src = os.path.join(self.dir, filename)
+ # has it changed?
try:
stime = os.stat(src)[os.path.stat.ST_MTIME]
except os.error, error:
if error.errno != errno.ENOENT:
raise
- if not extension:
- raise NoTemplate, 'Template file "%s" doesn\'t exist'%name
-
- # try for a generic template
- generic = '_generic.%s'%extension
- src = os.path.join(self.dir, generic)
- try:
- stime = os.stat(src)[os.path.stat.ST_MTIME]
- except os.error, error:
- if error.errno != errno.ENOENT:
- raise
- # nicer error
- raise NoTemplate, 'No template file exists for templating '\
- '"%s" with template "%s" (neither "%s" nor "%s")'%(name,
- extension, filename, generic)
- filename = generic
if self.templates.has_key(src) and \
- stime < self.templates[src].mtime:
+ stime <= self.templates[src].mtime:
# compiled template is up to date
return self.templates[src]
# compile the template
self.templates[src] = pt = RoundupPageTemplate()
- pt.write(open(src).read())
+ # use pt_edit so we can pass the content_type guess too
+ content_type = mimetypes.guess_type(filename)[0] or 'text/html'
+ pt.pt_edit(open(src).read(), content_type)
pt.id = filename
- pt.mtime = time.time()
+ pt.mtime = stime
return pt
def __getitem__(self, name):
raise KeyError, message
class RoundupPageTemplate(PageTemplate.PageTemplate):
- ''' A Roundup-specific PageTemplate.
-
- Interrogate the client to set up the various template variables to
- be available:
-
- *context*
- this is one of three things:
- 1. None - we're viewing a "home" page
- 2. The current class of item being displayed. This is an HTMLClass
- instance.
- 3. The current item from the database, if we're viewing a specific
- item, as an HTMLItem instance.
- *request*
- Includes information about the current request, including:
- - the url
- - the current index information (``filterspec``, ``filter`` args,
- ``properties``, etc) parsed out of the form.
- - methods for easy filterspec link generation
- - *user*, the current user node as an HTMLItem instance
- - *form*, the current CGI form information as a FieldStorage
- *config*
- The current tracker config.
- *db*
- The current database, used to access arbitrary database items.
- *utils*
- This is a special class that has its base in the TemplatingUtils
- class in this file. If the tracker interfaces module defines a
- TemplatingUtils class then it is mixed in, overriding the methods
- in the base class.
+ '''A Roundup-specific PageTemplate.
+
+ Interrogate the client to set up the various template variables to
+ be available:
+
+ *context*
+ this is one of three things:
+
+ 1. None - we're viewing a "home" page
+ 2. The current class of item being displayed. This is an HTMLClass
+ instance.
+ 3. The current item from the database, if we're viewing a specific
+ item, as an HTMLItem instance.
+ *request*
+ Includes information about the current request, including:
+
+ - the url
+ - the current index information (``filterspec``, ``filter`` args,
+ ``properties``, etc) parsed out of the form.
+ - methods for easy filterspec link generation
+ - *user*, the current user node as an HTMLItem instance
+ - *form*, the current CGI form information as a FieldStorage
+ *config*
+ The current tracker config.
+ *db*
+ The current database, used to access arbitrary database items.
+ *utils*
+ This is a special class that has its base in the TemplatingUtils
+ class in this file. If the tracker interfaces module defines a
+ TemplatingUtils class then it is mixed in, overriding the methods
+ in the base class.
'''
def getContext(self, client, classname, request):
# construct the TemplatingUtils class
'tracker': client.instance,
'utils': utils(client),
'templates': Templates(client.instance.config.TEMPLATES),
+ 'template': self,
}
# add in the item if there is one
if client.nodeid:
c['context'] = HTMLItem(client, classname, client.nodeid,
anonymous=1)
elif client.db.classes.has_key(classname):
- c['context'] = HTMLClass(client, classname, anonymous=1)
+ if classname == 'user':
+ c['context'] = HTMLUserClass(client, classname, anonymous=1)
+ else:
+ c['context'] = HTMLClass(client, classname, anonymous=1)
return c
def render(self, client, classname, request, **options):
getEngine().getContext(c), output, tal=1, strictinsert=0)()
return output.getvalue()
+ def __repr__(self):
+ return '<Roundup PageTemplate %r>'%self.id
+
class HTMLDatabase:
''' Return HTMLClasses for valid class fetches
'''
def __init__(self, client):
self._client = client
+ self._db = client.db
# we want config to be exposed
self.config = client.db.config
# check to see if we're actually accessing an item
m = desre.match(item)
if m:
- self._client.db.getclass(m.group('cl'))
- return HTMLItem(self._client, m.group('cl'), m.group('id'))
+ cl = m.group('cl')
+ self._client.db.getclass(cl)
+ if cl == 'user':
+ klass = HTMLUser
+ else:
+ klass = HTMLItem
+ return klass(self._client, cl, m.group('id'))
else:
self._client.db.getclass(item)
+ if item == 'user':
+ return HTMLUserClass(self._client, item)
return HTMLClass(self._client, item)
def __getattr__(self, attr):
def classes(self):
l = self._client.db.classes.keys()
l.sort()
- return [HTMLClass(self._client, cn) for cn in l]
-
-def lookupIds(db, prop, ids, num_re=re.compile('-?\d+')):
+ m = []
+ for item in l:
+ if item == 'user':
+ m.append(HTMLUserClass(self._client, item))
+ m.append(HTMLClass(self._client, item))
+ return m
+
+def lookupIds(db, prop, ids, fail_ok=0, num_re=re.compile('-?\d+')):
+ ''' "fail_ok" should be specified if we wish to pass through bad values
+ (most likely form values that we wish to represent back to the user)
+ '''
cl = db.getclass(prop.classname)
l = []
for entry in ids:
else:
try:
l.append(cl.lookup(entry))
- except KeyError:
- # ignore invalid keys
- pass
+ except (TypeError, KeyError):
+ if fail_ok:
+ # pass through the bad value
+ l.append(entry)
+ return l
+
+def lookupKeys(linkcl, key, ids, num_re=re.compile('-?\d+')):
+ ''' Look up the "key" values for "ids" list - though some may already
+ be key values, not ids.
+ '''
+ l = []
+ for entry in ids:
+ if num_re.match(entry):
+ l.append(linkcl.get(entry, key))
+ else:
+ l.append(entry)
return l
class HTMLPermissions:
'''
return self._db.security.hasPermission('Edit', self._client.userid,
self._classname)
+
def is_view_ok(self):
''' Is the user allowed to View the current class?
'''
return self._db.security.hasPermission('View', self._client.userid,
self._classname)
+
def is_only_view_ok(self):
''' Is the user only allowed to View (ie. not Edit) the current class?
'''
return self.is_view_ok() and not self.is_edit_ok()
-class HTMLClass(HTMLPermissions):
+ def view_check(self):
+ ''' Raise the Unauthorised exception if the user's not permitted to
+ view this class.
+ '''
+ if not self.is_view_ok():
+ raise Unauthorised("view", self._classname)
+
+ def edit_check(self):
+ ''' Raise the Unauthorised exception if the user's not permitted to
+ edit this class.
+ '''
+ if not self.is_edit_ok():
+ raise Unauthorised("edit", self._classname)
+
+def input_html4(**attrs):
+ """Generate an 'input' (html4) element with given attributes"""
+ return '<input %s>'%' '.join(['%s="%s"'%item for item in attrs.items()])
+
+def input_xhtml(**attrs):
+ """Generate an 'input' (xhtml) element with given attributes"""
+ return '<input %s/>'%' '.join(['%s="%s"'%item for item in attrs.items()])
+
+class HTMLInputMixin:
+ ''' requires a _client property '''
+ def __init__(self):
+ html_version = 'html4'
+ if hasattr(self._client.instance.config, 'HTML_VERSION'):
+ html_version = self._client.instance.config.HTML_VERSION
+ if html_version == 'xhtml':
+ self.input = input_xhtml
+ else:
+ self.input = input_html4
+
+class HTMLClass(HTMLInputMixin, HTMLPermissions):
''' Accesses through a class (either through *class* or *db.<classname>*)
'''
def __init__(self, client, classname, anonymous=0):
self._klass = self._db.getclass(self.classname)
self._props = self._klass.getprops()
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
return '<HTMLClass(0x%x) %s>'%(id(self), self.classname)
return None
# get the property
- prop = self._props[item]
+ try:
+ prop = self._props[item]
+ except KeyError:
+ raise KeyError, 'No such property "%s" on %s'%(item, self.classname)
# look up the correct HTMLProperty class
form = self._client.form
if form.has_key(item):
if isinstance(prop, hyperdb.Multilink):
value = lookupIds(self._db, prop,
- handleListCGIValue(form[item]))
+ handleListCGIValue(form[item]), fail_ok=1)
elif isinstance(prop, hyperdb.Link):
value = form[item].value.strip()
if value:
- value = lookupIds(self._db, prop, [value])[0]
+ value = lookupIds(self._db, prop, [value],
+ fail_ok=1)[0]
else:
value = None
else:
except KeyError:
raise AttributeError, attr
- def getItem(self, itemid, num_re=re.compile('\d+')):
+ def designator(self):
+ ''' Return this class' designator (classname) '''
+ return self._classname
+
+ def getItem(self, itemid, num_re=re.compile('-?\d+')):
''' Get an item of this class by its item id.
'''
# make sure we're looking at an itemid
- if not num_re.match(itemid):
+ if not isinstance(itemid, type(1)) and not num_re.match(itemid):
itemid = self._klass.lookup(itemid)
if self.classname == 'user':
return klass(self._client, self.classname, itemid)
- def properties(self):
+ def properties(self, sort=1):
''' Return HTMLProperty for all of this class' properties.
'''
l = []
if isinstance(prop, klass):
l.append(htmlklass(self._client, self._classname, '',
prop, name, value, self._anonymous))
+ if sort:
+ l.sort(lambda a,b:cmp(a._name, b._name))
return l
- def list(self):
+ def list(self, sort_on=None):
''' List all items in this class.
'''
if self.classname == 'user':
# get the list and sort it nicely
l = self._klass.list()
- sortfunc = make_sort_function(self._db, self.classname)
+ sortfunc = make_sort_function(self._db, self.classname, sort_on)
l.sort(sortfunc)
l = [klass(self._client, self.classname, x) for x in l]
def csv(self):
''' Return the items of this class as a chunk of CSV text.
'''
- # get the CSV module
- try:
- import csv
- except ImportError:
- return 'Sorry, you need the csv module to use this function.\n'\
- 'Get it from: http://www.object-craft.com.au/projects/csv/'
+ if rcsv.error:
+ return rcsv.error
props = self.propnames()
- p = csv.parser()
s = StringIO.StringIO()
- s.write(p.join(props) + '\n')
+ writer = rcsv.writer(s, rcsv.comma_separated)
+ writer.writerow(props)
for nodeid in self._klass.list():
l = []
for name in props:
l.append(':'.join(map(str, value)))
else:
l.append(str(self._klass.get(nodeid, name)))
- s.write(p.join(l) + '\n')
+ writer.writerow(l)
return s.getvalue()
def propnames(self):
idlessprops.sort()
return ['id'] + idlessprops
- def filter(self, request=None):
+ def filter(self, request=None, filterspec={}, sort=(None,None),
+ group=(None,None)):
''' Return a list of items from this class, filtered and sorted
by the current requested filterspec/filter/sort/group args
+
+ "request" takes precedence over the other three arguments.
'''
if request is not None:
filterspec = request.filterspec
sort = request.sort
group = request.group
- else:
- filterspec = {}
- sort = (None,None)
- group = (None,None)
if self.classname == 'user':
klass = HTMLUser
else:
for x in self._klass.filter(None, filterspec, sort, group)]
return l
- def classhelp(self, properties=None, label='list', width='500',
- height='400'):
+ def classhelp(self, properties=None, label='(list)', width='500',
+ height='400', property=''):
''' Pop up a javascript window with class help
This generates a link to a popup window which displays the
You may optionally override the label displayed, the width and
height. The popup window will be resizable and scrollable.
+
+ If the "property" arg is given, it's passed through to the
+ javascript help_window function.
'''
if properties is None:
properties = self._klass.getprops(protected=0).keys()
properties.sort()
properties = ','.join(properties)
- return '<a href="javascript:help_window(\'%s?:template=help&' \
- 'properties=%s\', \'%s\', \'%s\')"><b>(%s)</b></a>'%(
- self.classname, properties, width, height, label)
+ if property:
+ property = '&property=%s'%property
+ return '<a class="classhelp" href="javascript:help_window(\'%s?'\
+ '@startwith=0&@template=help&properties=%s%s\', \'%s\', \
+ \'%s\')">%s</a>'%(self.classname, properties, property, width,
+ height, label)
def submit(self, label="Submit New Entry"):
''' Generate a submit button (and action hidden element)
'''
- return ' <input type="hidden" name=":action" value="new">\n'\
- ' <input type="submit" name="submit" value="%s">'%label
+ self.view_check()
+ if self.is_edit_ok():
+ return self.input(type="hidden",name="@action",value="new") + \
+ '\n' + self.input(type="submit",name="submit",value=label)
+ return ''
def history(self):
+ self.view_check()
return 'New node - no history'
def renderWith(self, name, **kwargs):
pt = Templates(self._db.config.TEMPLATES).get(self.classname, name)
# use our fabricated request
- return pt.render(self._client, self.classname, req)
+ args = {
+ 'ok_message': self._client.ok_message,
+ 'error_message': self._client.error_message
+ }
+ return pt.render(self._client, self.classname, req, **args)
-class HTMLItem(HTMLPermissions):
+class HTMLItem(HTMLInputMixin, HTMLPermissions):
''' Accesses through an *item*
'''
def __init__(self, client, classname, nodeid, anonymous=0):
# do we prefix the form items with the item's identification?
self._anonymous = anonymous
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
return '<HTMLItem(0x%x) %s %s>'%(id(self), self._classname,
self._nodeid)
return self[attr]
except KeyError:
raise AttributeError, attr
+
+ def designator(self):
+ """Return this item's designator (classname + id)."""
+ return '%s%s'%(self._classname, self._nodeid)
+
+ def is_retired(self):
+ """Is this item retired?"""
+ return self._klass.is_retired(self._nodeid)
def submit(self, label="Submit Changes"):
- ''' Generate a submit button (and action hidden element)
- '''
- return ' <input type="hidden" name=":action" value="edit">\n'\
- ' <input type="submit" name="submit" value="%s">'%label
+ """Generate a submit button.
+
+ Also sneak in the lastactivity and action hidden elements.
+ """
+ return self.input(type="hidden", name="@lastactivity", value=date.Date('.')) + '\n' + \
+ self.input(type="hidden", name="@action", value="edit") + '\n' + \
+ self.input(type="submit", name="submit", value=label)
def journal(self, direction='descending'):
''' Return a list of HTMLJournalEntry instances.
return []
def history(self, direction='descending', dre=re.compile('\d+')):
+ self.view_check()
+
l = ['<table class="history">'
'<tr><th colspan="4" class="header">',
_('History'),
timezone = self._db.getUserTimezone()
if direction == 'descending':
history.reverse()
+ # pre-load the history with the current state
for prop_n in self._props.keys():
prop = self[prop_n]
- if isinstance(prop, HTMLProperty):
- current[prop_n] = prop.plain()
- # make link if hrefable
- if (self._props.has_key(prop_n) and
- isinstance(self._props[prop_n], hyperdb.Link)):
- classname = self._props[prop_n].classname
- if os.path.exists(os.path.join(self._db.config.TEMPLATES, classname + '.item')):
- current[prop_n] = '<a href="%s%s">%s</a>'%(classname,
- self._klass.get(self._nodeid, prop_n, None), current[prop_n])
+ if not isinstance(prop, HTMLProperty):
+ continue
+ current[prop_n] = prop.plain()
+ # make link if hrefable
+ if (self._props.has_key(prop_n) and
+ isinstance(self._props[prop_n], hyperdb.Link)):
+ classname = self._props[prop_n].classname
+ try:
+ template = find_template(self._db.config.TEMPLATES,
+ classname, 'item')
+ if template[1].startswith('_generic'):
+ raise NoTemplate, 'not really...'
+ except NoTemplate:
+ pass
+ else:
+ id = self._klass.get(self._nodeid, prop_n, None)
+ current[prop_n] = '<a href="%s%s">%s</a>'%(
+ classname, id, current[prop_n])
for id, evt_date, user, action, args in history:
date_s = str(evt_date.local(timezone)).replace("."," ")
prop = self._props[k]
except KeyError:
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.getclass(classname)
- except KeyError:
- labelprop = None
- comments[classname] = _('''The linked class
- %(classname)s no longer exists''')%locals()
- labelprop = linkcl.labelprop(1)
- hrefable = os.path.exists(
- os.path.join(self._db.config.TEMPLATES,
- classname+'.item'))
-
- if isinstance(prop, hyperdb.Multilink) and \
- len(args[k]) > 0:
- ml = []
- for linkid in args[k]:
- if isinstance(linkid, type(())):
- sublabel = linkid[0] + ' '
- linkids = linkid[1]
- else:
- sublabel = ''
- linkids = [linkid]
- subml = []
- for linkid in linkids:
- 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 and \
- labelprop != 'id':
- label = linkcl.get(linkid, labelprop)
- except IndexError:
- comments['no_link'] = _('''<strike>The
- linked node no longer
- exists</strike>''')
- subml.append('<strike>%s</strike>'%label)
- else:
- if hrefable:
- subml.append('<a href="%s%s">%s</a>'%(
- classname, linkid, label))
- else:
- subml.append(label)
- ml.append(sublabel + ', '.join(subml))
- cell.append('%s:\n %s'%(k, ', '.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 and labelprop != 'id':
+ if prop is None:
+ # property no longer exists
+ comments['no_exist'] = _('''<em>The indicated property
+ no longer exists</em>''')
+ cell.append('<em>%s: %s</em>\n'%(k, str(args[k])))
+ continue
+
+ 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.getclass(classname)
+ except KeyError:
+ labelprop = None
+ comments[classname] = _('''The linked class
+ %(classname)s no longer exists''')%locals()
+ labelprop = linkcl.labelprop(1)
+ try:
+ template = find_template(self._db.config.TEMPLATES,
+ classname, 'item')
+ if template[1].startswith('_generic'):
+ raise NoTemplate, 'not really...'
+ hrefable = 1
+ except NoTemplate:
+ hrefable = 0
+
+ if isinstance(prop, hyperdb.Multilink) and args[k]:
+ ml = []
+ for linkid in args[k]:
+ if isinstance(linkid, type(())):
+ sublabel = linkid[0] + ' '
+ linkids = linkid[1]
+ else:
+ sublabel = ''
+ linkids = [linkid]
+ subml = []
+ for linkid in linkids:
+ 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:
- label = linkcl.get(args[k], labelprop)
+ if labelprop is not None and \
+ labelprop != 'id':
+ label = linkcl.get(linkid, labelprop)
except IndexError:
comments['no_link'] = _('''<strike>The
linked node no longer
exists</strike>''')
- cell.append(' <strike>%s</strike>,\n'%label)
- # "flag" this is done .... euwww
- label = None
- if label is not None:
- if hrefable:
- old = '<a href="%s%s">%s</a>'%(classname, args[k], label)
+ subml.append('<strike>%s</strike>'%label)
else:
- old = label;
- cell.append('%s: %s' % (k,old))
- if current.has_key(k):
- cell[-1] += ' -> %s'%current[k]
- current[k] = old
-
- elif isinstance(prop, hyperdb.Date) and args[k]:
- d = date.Date(args[k]).local(timezone)
- cell.append('%s: %s'%(k, str(d)))
- if current.has_key(k):
- if not current[k] == '(no value)' and current[k]:
- current[k] = date.Date(current[k]).local(timezone)
- cell[-1] += ' -> %s' % current[k]
- current[k] = str(d)
-
- elif isinstance(prop, hyperdb.Interval) and args[k]:
- d = date.Interval(args[k])
- cell.append('%s: %s'%(k, str(d)))
- if current.has_key(k):
- cell[-1] += ' -> %s'%current[k]
- current[k] = str(d)
-
- elif isinstance(prop, hyperdb.String) and args[k]:
- cell.append('%s: %s'%(k, cgi.escape(args[k])))
- if current.has_key(k):
- cell[-1] += ' -> %s'%current[k]
- current[k] = cgi.escape(args[k])
-
- elif not args[k]:
- if current.has_key(k):
- cell.append('%s: %s'%(k, current[k]))
- current[k] = '(no value)'
+ if hrefable:
+ subml.append('<a href="%s%s">%s</a>'%(
+ classname, linkid, label))
+ else:
+ subml.append(label)
+ ml.append(sublabel + ', '.join(subml))
+ cell.append('%s:\n %s'%(k, ', '.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 and labelprop != 'id':
+ try:
+ label = linkcl.get(args[k], labelprop)
+ except IndexError:
+ comments['no_link'] = _('''<strike>The
+ linked node no longer
+ exists</strike>''')
+ cell.append(' <strike>%s</strike>,\n'%label)
+ # "flag" this is done .... euwww
+ label = None
+ if label is not None:
+ if hrefable:
+ old = '<a href="%s%s">%s</a>'%(classname, args[k], label)
else:
- cell.append('%s: (no value)'%k)
-
- else:
- cell.append('%s: %s'%(k, str(args[k])))
+ old = label;
+ cell.append('%s: %s' % (k,old))
if current.has_key(k):
cell[-1] += ' -> %s'%current[k]
- current[k] = str(args[k])
+ current[k] = old
+
+ elif isinstance(prop, hyperdb.Date) and args[k]:
+ d = date.Date(args[k]).local(timezone)
+ cell.append('%s: %s'%(k, str(d)))
+ if current.has_key(k):
+ cell[-1] += ' -> %s' % current[k]
+ current[k] = str(d)
+
+ elif isinstance(prop, hyperdb.Interval) and args[k]:
+ val = str(date.Interval(args[k]))
+ cell.append('%s: %s'%(k, val))
+ if current.has_key(k):
+ cell[-1] += ' -> %s'%current[k]
+ current[k] = val
+
+ elif isinstance(prop, hyperdb.String) and args[k]:
+ val = cgi.escape(args[k])
+ cell.append('%s: %s'%(k, val))
+ if current.has_key(k):
+ cell[-1] += ' -> %s'%current[k]
+ current[k] = val
+
+ elif isinstance(prop, hyperdb.Boolean) and args[k] is not None:
+ val = args[k] and 'Yes' or 'No'
+ cell.append('%s: %s'%(k, val))
+ if current.has_key(k):
+ cell[-1] += ' -> %s'%current[k]
+ current[k] = val
+
+ elif not args[k]:
+ if current.has_key(k):
+ cell.append('%s: %s'%(k, current[k]))
+ current[k] = '(no value)'
+ else:
+ cell.append('%s: (no value)'%k)
+
else:
- # property no longer exists
- comments['no_exist'] = _('''<em>The indicated property
- no longer exists</em>''')
- cell.append('<em>%s: %s</em>\n'%(k, str(args[k])))
+ cell.append('%s: %s'%(k, str(args[k])))
+ if current.has_key(k):
+ cell[-1] += ' -> %s'%current[k]
+ current[k] = str(args[k])
+
arg_s = '<br />'.join(cell)
else:
# unkown event!!
req.classname = self._klass.get(self._nodeid, 'klass')
name = self._klass.get(self._nodeid, 'name')
req.updateFromURL(self._klass.get(self._nodeid, 'url') +
- '&:queryname=%s'%urllib.quote(name))
+ '&@queryname=%s'%urllib.quote(name))
# new template, using the specified classname and request
pt = Templates(self._db.config.TEMPLATES).get(req.classname, 'search')
# use our fabricated request
return pt.render(self._client, req.classname, req)
-class HTMLUser(HTMLItem):
+ def download_url(self):
+ ''' Assume that this item is a FileClass and that it has a name
+ and content. Construct a URL for the download of the content.
+ '''
+ name = self._klass.get(self._nodeid, 'name')
+ url = '%s%s/%s'%(self._classname, self._nodeid, name)
+ return urllib.quote(url)
+
+
+class HTMLUserPermission:
+
+ def is_edit_ok(self):
+ ''' Is the user allowed to Edit the current class?
+ Also check whether this is the current user's info.
+ '''
+ return self._user_perm_check('Edit')
+
+ def is_view_ok(self):
+ ''' Is the user allowed to View the current class?
+ Also check whether this is the current user's info.
+ '''
+ return self._user_perm_check('View')
+
+ def _user_perm_check(self, type):
+ # some users may view / edit all users
+ s = self._db.security
+ userid = self._client.userid
+ if s.hasPermission(type, userid, self._classname):
+ return 1
+
+ # users may view their own info
+ is_anonymous = self._db.user.get(userid, 'username') == 'anonymous'
+ if getattr(self, '_nodeid', None) == userid and not is_anonymous:
+ return 1
+
+ # may anonymous users register?
+ if (is_anonymous and s.hasPermission('Web Registration', userid,
+ self._classname)):
+ return 1
+
+ # nope, no access here
+ return 0
+
+class HTMLUserClass(HTMLUserPermission, HTMLClass):
+ pass
+
+class HTMLUser(HTMLUserPermission, HTMLItem):
''' Accesses through the *user* (a special case of item)
'''
- def __init__(self, client, classname, nodeid):
- HTMLItem.__init__(self, client, 'user', nodeid)
+ def __init__(self, client, classname, nodeid, anonymous=0):
+ HTMLItem.__init__(self, client, 'user', nodeid, anonymous)
self._default_classname = client.classname
# used for security checks
classname = self._default_classname
return self._security.hasPermission(permission, self._nodeid, classname)
- def is_edit_ok(self):
- ''' Is the user allowed to Edit the current class?
- Also check whether this is the current user's info.
- '''
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname) or self._nodeid == self._client.userid
-
- def is_view_ok(self):
- ''' Is the user allowed to View the current class?
- Also check whether this is the current user's info.
- '''
- return self._db.security.hasPermission('Edit', self._client.userid,
- self._classname) or self._nodeid == self._client.userid
-
-class HTMLProperty:
+class HTMLProperty(HTMLInputMixin, HTMLPermissions):
''' String, Number, Date, Interval HTMLProperty
Has useful attributes:
self._prop = prop
self._value = value
self._anonymous = anonymous
+ self._name = name
if not anonymous:
- self._name = '%s%s@%s'%(classname, nodeid, name)
+ self._formname = '%s%s@%s'%(classname, nodeid, name)
else:
- self._name = name
+ self._formname = name
+
+ HTMLInputMixin.__init__(self)
+
def __repr__(self):
- return '<HTMLProperty(0x%x) %s %r %r>'%(id(self), self._name,
+ return '<HTMLProperty(0x%x) %s %r %r>'%(id(self), self._formname,
self._prop, self._value)
def __str__(self):
return self.plain()
return cmp(self._value, other._value)
return cmp(self._value, other)
+ def isset(self):
+ '''Is my _value None?'''
+ return self._value is None
+
+ def is_edit_ok(self):
+ ''' Is the user allowed to Edit the current class?
+ '''
+ thing = HTMLDatabase(self._client)[self._classname]
+ if self._nodeid:
+ # this is a special-case for the User class where permission's
+ # on a per-item basis :(
+ thing = thing.getItem(self._nodeid)
+ return thing.is_edit_ok()
+
+ def is_view_ok(self):
+ ''' Is the user allowed to View the current class?
+ '''
+ thing = HTMLDatabase(self._client)[self._classname]
+ if self._nodeid:
+ # this is a special-case for the User class where permission's
+ # on a per-item basis :(
+ thing = thing.getItem(self._nodeid)
+ return thing.is_view_ok()
+
class StringHTMLProperty(HTMLProperty):
hyper_re = re.compile(r'((?P<url>\w{3,6}://\S+)|'
- r'(?P<email>[\w\.]+@[\w\.\-]+)|'
+ r'(?P<email>[-+=%/\w\.]+@[\w\.\-]+)|'
r'(?P<item>(?P<class>[a-z_]+)(?P<id>\d+)))')
def _hyper_repl(self, match):
if match.group('url'):
s2 = match.group('id')
try:
# make sure s1 is a valid tracker classname
- self._db.getclass(s1)
- return '<a href="%s">%s %s</a>'%(s, s1, s2)
+ cl = self._db.getclass(s1)
+ if not cl.hasnode(s2):
+ raise KeyError, 'oops'
+ return '<a href="%s">%s%s</a>'%(s, s1, s2)
except KeyError:
return '%s%s'%(s1, s2)
+ def hyperlinked(self):
+ ''' Render a "hyperlinked" version of the text '''
+ return self.plain(hyperlink=1)
+
def plain(self, escape=0, hyperlink=0):
- ''' Render a "plain" representation of the property
+ '''Render a "plain" representation of the property
- "escape" turns on/off HTML quoting
- "hyperlink" turns on/off in-text hyperlinking of URLs, email
- addresses and designators
+ - "escape" turns on/off HTML quoting
+ - "hyperlink" turns on/off in-text hyperlinking of URLs, email
+ addresses and designators
'''
+ self.view_check()
+
if self._value is None:
return ''
if escape:
else:
s = str(self._value)
if hyperlink:
+ # no, we *must* escape this text
if not escape:
s = cgi.escape(s)
s = self.hyper_re.sub(self._hyper_repl, s)
This requires the StructureText module to be installed separately.
'''
+ self.view_check()
+
s = self.plain(escape=escape)
if not StructuredText:
return s
return StructuredText(s,level=1,header=0)
def field(self, size = 30):
- ''' Render a form edit field for the property
+ ''' Render the property as a field in HTML.
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
def multiline(self, escape=0, rows=5, cols=40):
- ''' Render a multiline form edit field for the property
+ ''' Render a multiline form edit field for the property.
+
+ If not editable, just display the plain() value in a <pre> tag.
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
- self._name, rows, cols, value)
+ return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
+ self._formname, rows, cols, value)
+
+ return '<pre>%s</pre>'%self.plain()
def email(self, escape=1):
''' Render the value of the property as an obscured email address
'''
- if self._value is None: value = ''
- else: value = str(self._value)
+ self.view_check()
+
+ if self._value is None:
+ value = ''
+ else:
+ value = str(self._value)
if value.find('@') != -1:
name, domain = value.split('@')
domain = ' '.join(domain.split('.')[:-1])
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return _('*encrypted*')
def field(self, size = 30):
''' Render a form edit field for the property.
+
+ If not editable, just display the value via plain().
'''
- return '<input type="password" name="%s" size="%s">'%(self._name, size)
+ self.view_check()
+
+ if self.is_edit_ok():
+ return self.input(type="password", name=self._formname, size=size)
+
+ return self.plain()
def confirm(self, size = 30):
''' Render a second form edit field for the property, used for
confirmation that the user typed the password correctly. Generates
- a field with name ":confirm:name".
+ a field with name "@confirm@name".
+
+ If not editable, display nothing.
'''
- return '<input type="password" name=":confirm:%s" size="%s">'%(
- self._name, size)
+ self.view_check()
+
+ if self.is_edit_ok():
+ return self.input(type="password",
+ name="@confirm@%s"%self._formname, size=size)
+
+ return ''
class NumberHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
return str(self._value)
def field(self, size = 30):
- ''' Render a form edit field for the property
+ ''' Render a form edit field for the property.
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
+
+ def __int__(self):
+ ''' Return an int of me
+ '''
+ return int(self._value)
+
+ def __float__(self):
+ ''' Return a float of me
+ '''
+ return float(self._value)
+
class BooleanHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return self._value and "Yes" or "No"
def field(self):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
checked = self._value and "checked" or ""
- s = '<input type="radio" name="%s" value="yes" %s>Yes'%(self._name,
- checked)
- if checked:
- checked = ""
+ if self._value:
+ s = self.input(type="radio", name=self._formname, value="yes",
+ checked="checked")
+ s += 'Yes'
+ s +=self.input(type="radio", name=self._formname, value="no")
+ s += 'No'
else:
- checked = "checked"
- s += '<input type="radio" name="%s" value="no" %s>No'%(self._name,
- checked)
+ s = self.input(type="radio", name=self._formname, value="yes")
+ s += 'Yes'
+ s +=self.input(type="radio", name=self._formname, value="no",
+ checked="checked")
+ s += 'No'
return s
class DateHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return str(self._value.local(self._db.getUserTimezone()))
This is useful for defaulting a new value. Returns a
DateHTMLProperty.
'''
- return DateHTMLProperty(self._client, self._nodeid, self._prop,
- self._name, date.Date('.'))
+ self.view_check()
+
+ return DateHTMLProperty(self._client, self._classname, self._nodeid,
+ self._prop, self._formname, date.Date('.'))
def field(self, size = 30):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
- value = cgi.escape(str(self._value.local(self._db.getUserTimezone())))
+ tz = self._db.getUserTimezone()
+ value = cgi.escape(str(self._value.local(tz)))
+
+ if self.is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
def reldate(self, pretty=1):
''' Render the interval between the date and now.
If the "pretty" flag is true, then make the display pretty.
'''
+ self.view_check()
+
if not self._value:
return ''
# figure the interval
- interval = date.Date('.') - self._value
+ interval = self._value - date.Date('.')
if pretty:
return interval.pretty()
return str(interval)
- def pretty(self, format='%d %B %Y'):
+ _marker = []
+ def pretty(self, format=_marker):
''' Render the date in a pretty format (eg. month names, spaces).
The format string is a standard python strftime format string.
string, then it'll be stripped from the output. This is handy
for the situatin when a date only specifies a month and a year.
'''
- return self._value.pretty()
+ self.view_check()
+
+ if format is not self._marker:
+ return self._value.pretty(format)
+ else:
+ return self._value.pretty()
def local(self, offset):
''' Return the date/time as a local (timezone offset) date/time.
'''
- return DateHTMLProperty(self._client, self._nodeid, self._prop,
- self._name, self._value.local(offset))
+ self.view_check()
+
+ return DateHTMLProperty(self._client, self._classname, self._nodeid,
+ self._prop, self._formname, self._value.local(offset))
class IntervalHTMLProperty(HTMLProperty):
def plain(self):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
return str(self._value)
def pretty(self):
''' Render the interval in a pretty format (eg. "yesterday")
'''
+ self.view_check()
+
return self._value.pretty()
def field(self, size = 30):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
if self._value is None:
value = ''
else:
value = cgi.escape(str(self._value))
+
+ if is_edit_ok():
value = '"'.join(value.split('"'))
- return '<input name="%s" value="%s" size="%s">'%(self._name, value, size)
+ return self.input(name=self._formname,value=value,size=size)
+
+ return self.plain()
class LinkHTMLProperty(HTMLProperty):
''' Link HTMLProperty
def plain(self, escape=0):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
if self._value is None:
return ''
linkcl = self._db.classes[self._prop.classname]
def field(self, showid=0, size=None):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
+ # edit field
linkcl = self._db.getclass(self._prop.classname)
- if linkcl.getprops().has_key('order'):
- sort_on = 'order'
- else:
- sort_on = linkcl.labelprop()
- options = linkcl.filter(None, {}, ('+', sort_on), (None, None))
- # TODO: make this a field display, not a menu one!
- l = ['<select name="%s">'%self._name]
- k = linkcl.labelprop(1)
if self._value is None:
- s = 'selected '
+ value = ''
else:
- s = ''
- l.append(_('<option %svalue="-1">- no selection -</option>')%s)
-
- # make sure we list the current value if it's retired
- if self._value and self._value not in options:
- options.insert(0, self._value)
-
- for optionid in options:
- # get the option value, and if it's None use an empty string
- option = linkcl.get(optionid, k) or ''
-
- # figure if this option is selected
- s = ''
- if optionid == self._value:
- s = 'selected '
-
- # figure the label
- if showid:
- lab = '%s%s: %s'%(self._prop.classname, optionid, option)
+ k = linkcl.getkey()
+ if k:
+ value = linkcl.get(self._value, k)
else:
- lab = option
-
- # truncate if it's too long
- if size is not None and len(lab) > size:
- lab = lab[:size-3] + '...'
-
- # and generate
- lab = cgi.escape(lab)
- l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
- l.append('</select>')
- return '\n'.join(l)
+ value = self._value
+ value = cgi.escape(str(value))
+ value = '"'.join(value.split('"'))
+ return '<input name="%s" value="%s" size="%s">'%(self._formname,
+ value, size)
def menu(self, size=None, height=None, showid=0, additional=[],
- **conditions):
+ sort_on=None, **conditions):
''' Render a form select list for this property
+
+ If not editable, just display the value via plain().
'''
- value = self._value
+ self.view_check()
- # sort function
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ if not self.is_edit_ok():
+ return self.plain()
+
+ value = self._value
linkcl = self._db.getclass(self._prop.classname)
- l = ['<select name="%s">'%self._name]
+ l = ['<select name="%s">'%self._formname]
k = linkcl.labelprop(1)
s = ''
if value is None:
- s = 'selected '
+ s = 'selected="selected" '
l.append(_('<option %svalue="-1">- no selection -</option>')%s)
if linkcl.getprops().has_key('order'):
sort_on = ('+', 'order')
else:
- sort_on = ('+', linkcl.labelprop())
+ if sort_on is None:
+ sort_on = ('+', linkcl.labelprop())
+ else:
+ sort_on = ('+', sort_on)
options = linkcl.filter(None, conditions, sort_on, (None, None))
# make sure we list the current value if it's retired
# figure if this option is selected
s = ''
if value in [optionid, option]:
- s = 'selected '
+ s = 'selected="selected" '
# figure the label
if showid:
Also be iterable, returning a wrapper object like the Link case for
each entry in the multilink.
'''
+ def __init__(self, *args, **kwargs):
+ HTMLProperty.__init__(self, *args, **kwargs)
+ if self._value:
+ sortfun = make_sort_function(self._db, self._prop.classname)
+ self._value.sort(sortfun)
+
def __len__(self):
''' length of the multilink '''
return len(self._value)
def __contains__(self, value):
''' Support the "in" operator. We have to make sure the passed-in
- value is a string first, not a *HTMLProperty.
+ value is a string first, not a HTMLProperty.
'''
return str(value) in self._value
+ def isset(self):
+ '''Is my _value []?'''
+ return self._value == []
+
def reverse(self):
''' return the list in reverse order
'''
def plain(self, escape=0):
''' Render a "plain" representation of the property
'''
+ self.view_check()
+
linkcl = self._db.classes[self._prop.classname]
k = linkcl.labelprop(1)
labels = []
def field(self, size=30, showid=0):
''' Render a form edit field for the property
+
+ If not editable, just display the value via plain().
'''
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ self.view_check()
+
+ if not self.is_edit_ok():
+ return self.plain()
+
linkcl = self._db.getclass(self._prop.classname)
value = self._value[:]
- if value:
- value.sort(sortfunc)
# map the id to the label property
if not linkcl.getkey():
showid=1
if not showid:
k = linkcl.labelprop(1)
- value = [linkcl.get(v, k) for v in value]
+ value = lookupKeys(linkcl, k, value)
value = cgi.escape(','.join(value))
- return '<input name="%s" size="%s" value="%s">'%(self._name, size, value)
+ return self.input(name=self._formname,size=size,value=value)
def menu(self, size=None, height=None, showid=0, additional=[],
- **conditions):
+ sort_on=None, **conditions):
''' Render a form select list for this property
+
+ If not editable, just display the value via plain().
'''
- value = self._value
+ self.view_check()
- # sort function
- sortfunc = make_sort_function(self._db, self._prop.classname)
+ if not self.is_edit_ok():
+ return self.plain()
+
+ value = self._value
linkcl = self._db.getclass(self._prop.classname)
- if linkcl.getprops().has_key('order'):
- sort_on = ('+', 'order')
- else:
- sort_on = ('+', linkcl.labelprop())
- options = linkcl.filter(None, conditions, sort_on, (None,None))
+ if sort_on is None:
+ sort_on = ('+', find_sort_key(linkcl))
+ else:
+ sort_on = ('+', sort_on)
+ options = linkcl.filter(None, conditions, sort_on)
height = height or min(len(options), 7)
- l = ['<select multiple name="%s" size="%s">'%(self._name, height)]
+ l = ['<select multiple name="%s" size="%s">'%(self._formname, height)]
k = linkcl.labelprop(1)
# make sure we list the current values if they're retired
# figure if this option is selected
s = ''
if optionid in value or option in value:
- s = 'selected '
+ s = 'selected="selected" '
# figure the label
if showid:
(hyperdb.Multilink, MultilinkHTMLProperty),
)
-def make_sort_function(db, classname):
+def make_sort_function(db, classname, sort_on=None):
'''Make a sort function for a given class
'''
linkcl = db.getclass(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):
+ if sort_on is None:
+ sort_on = find_sort_key(linkcl)
+ def sortfunc(a, b):
return cmp(linkcl.get(a, sort_on), linkcl.get(b, sort_on))
return sortfunc
+def find_sort_key(linkcl):
+ if linkcl.getprops().has_key('order'):
+ return 'order'
+ else:
+ return linkcl.labelprop()
+
def handleListCGIValue(value):
''' Value is either a single item or a list of items. Each item has a
.value that we're actually interested in.
def __getitem__(self, name):
return self.columns.has_key(name)
-class HTMLRequest:
- ''' The *request*, holding the CGI form and environment.
-
- "form" the CGI form as a cgi.FieldStorage
- "env" the CGI environment variables
- "base" the base URL for this instance
- "user" a HTMLUser instance for this user
- "classname" the current classname (possibly None)
- "template" the current template (suffix, also possibly None)
-
- Index args:
- "columns" dictionary of the columns to display in an index page
- "show" a convenience access to columns - request/show/colname will
- be true if the columns should be displayed, false otherwise
- "sort" index sort column (direction, column name)
- "group" index grouping property (direction, column name)
- "filter" properties to filter the index on
- "filterspec" values to filter the index on
- "search_text" text to perform a full-text search on for an index
-
+class HTMLRequest(HTMLInputMixin):
+ '''The *request*, holding the CGI form and environment.
+
+ - "form" the CGI form as a cgi.FieldStorage
+ - "env" the CGI environment variables
+ - "base" the base URL for this instance
+ - "user" a HTMLUser instance for this user
+ - "classname" the current classname (possibly None)
+ - "template" the current template (suffix, also possibly None)
+
+ Index args:
+
+ - "columns" dictionary of the columns to display in an index page
+ - "show" a convenience access to columns - request/show/colname will
+ be true if the columns should be displayed, false otherwise
+ - "sort" index sort column (direction, column name)
+ - "group" index grouping property (direction, column name)
+ - "filter" properties to filter the index on
+ - "filterspec" values to filter the index on
+ - "search_text" text to perform a full-text search on for an index
'''
def __init__(self, client):
- self.client = client
+ # _client is needed by HTMLInputMixin
+ self._client = self.client = client
# easier access vars
self.form = client.form
# the special char to use for special vars
self.special_char = '@'
+ HTMLInputMixin.__init__(self)
+
self._post_init()
def _post_init(self):
if self.classname is not None:
props = db.getclass(self.classname).getprops()
for name in self.filter:
- if self.form.has_key(name):
- prop = props[name]
- fv = self.form[name]
- if (isinstance(prop, hyperdb.Link) or
- isinstance(prop, hyperdb.Multilink)):
- self.filterspec[name] = lookupIds(db, prop,
- handleListCGIValue(fv))
+ if not self.form.has_key(name):
+ continue
+ prop = props[name]
+ fv = self.form[name]
+ if (isinstance(prop, hyperdb.Link) or
+ isinstance(prop, hyperdb.Multilink)):
+ self.filterspec[name] = lookupIds(db, prop,
+ handleListCGIValue(fv))
+ else:
+ if isinstance(fv, type([])):
+ self.filterspec[name] = [v.value for v in fv]
else:
self.filterspec[name] = fv.value
''' Parse the URL for query args, and update my attributes using the
values.
'''
- self.form = {}
- for name, value in cgi.parse_qsl(url):
- if self.form.has_key(name):
- if isinstance(self.form[name], type([])):
- self.form[name].append(cgi.MiniFieldStorage(name, value))
- else:
- self.form[name] = [self.form[name],
- cgi.MiniFieldStorage(name, value)]
- else:
- self.form[name] = cgi.MiniFieldStorage(name, value)
+ env = {'QUERY_STRING': url}
+ self.form = cgi.FieldStorage(environ=env)
+
self._post_init()
def update(self, kwargs):
''' return the current index args as form elements '''
l = []
sc = self.special_char
- s = '<input type="hidden" name="%s" value="%s">'
+ s = self.input(type="hidden",name="%s",value="%s")
if columns and self.columns:
l.append(s%(sc+'columns', ','.join(self.columns)))
if sort and self.sort[1] is not None:
return '\n'.join(l)
def indexargs_url(self, url, args):
- ''' embed the current index args in a URL '''
+ ''' Embed the current index args in a URL
+ '''
sc = self.special_char
l = ['%s=%s'%(k,v) for k,v in args.items()]
- if self.columns and not args.has_key(':columns'):
+
+ # pull out the special values (prefixed by @ or :)
+ specials = {}
+ for key in args.keys():
+ if key[0] in '@:':
+ specials[key[1:]] = args[key]
+
+ # ok, now handle the specials we received in the request
+ if self.columns and not specials.has_key('columns'):
l.append(sc+'columns=%s'%(','.join(self.columns)))
- if self.sort[1] is not None and not args.has_key(':sort'):
+ if self.sort[1] is not None and not specials.has_key('sort'):
if self.sort[0] == '-':
val = '-'+self.sort[1]
else:
val = self.sort[1]
l.append(sc+'sort=%s'%val)
- if self.group[1] is not None and not args.has_key(':group'):
+ if self.group[1] is not None and not specials.has_key('group'):
if self.group[0] == '-':
val = '-'+self.group[1]
else:
val = self.group[1]
l.append(sc+'group=%s'%val)
- if self.filter and not args.has_key(':filter'):
+ if self.filter and not specials.has_key('filter'):
l.append(sc+'filter=%s'%(','.join(self.filter)))
+ if self.search_text and not specials.has_key('search_text'):
+ l.append(sc+'search_text=%s'%self.search_text)
+ if not specials.has_key('pagesize'):
+ l.append(sc+'pagesize=%s'%self.pagesize)
+ if not specials.has_key('startwith'):
+ l.append(sc+'startwith=%s'%self.startwith)
+
+ # finally, the remainder of the filter args in the request
for k,v in self.filterspec.items():
if not args.has_key(k):
if type(v) == type([]):
l.append('%s=%s'%(k, ','.join(v)))
else:
l.append('%s=%s'%(k, v))
- if self.search_text and not args.has_key(':search_text'):
- l.append(sc+'search_text=%s'%self.search_text)
- if not args.has_key(':pagesize'):
- l.append(sc+'pagesize=%s'%self.pagesize)
- if not args.has_key(':startwith'):
- l.append(sc+'startwith=%s'%self.startwith)
return '%s?%s'%(url, '&'.join(l))
indexargs_href = indexargs_url
def base_javascript(self):
return '''
-<script language="javascript">
+<script type="text/javascript">
submitted = false;
function submit_once() {
if (submitted) {
alert("Your request is being processed.\\nPlease be patient.");
+ event.returnValue = 0; // work-around for IE
return 0;
}
submitted = true;
return Batch(self.client, sequence, size, start, end, orphan,
overlap)
+ def url_quote(self, url):
+ '''URL-quote the supplied text.'''
+ return urllib.quote(url)
+
+ def html_quote(self, html):
+ '''HTML-quote the supplied text.'''
+ return cgi.escape(url)
+