Code

forward-porting of fixed edit action / parsePropsFromForm to handle index-page edits...
[roundup.git] / roundup / cgi / templating.py
index ae4f0fa3dd31003560c45abd0559c425caa78146..dc1530951eb343f35c932b4a84b14cfb5a9682c0 100644 (file)
@@ -1,6 +1,12 @@
-import sys, cgi, urllib, os, re, os.path, time, errno
+"""Implements the API used in the HTML templating for the web interface.
+"""
+__docformat__ = 'restructuredtext'
 
-from roundup import hyperdb, date
+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:
@@ -25,6 +31,54 @@ from roundup.cgi import ZTUtils
 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 = {}
 
@@ -38,9 +92,9 @@ class 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.
@@ -59,34 +113,15 @@ class Templates:
             # 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:
@@ -95,7 +130,9 @@ class Templates:
 
         # 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()
         return pt
@@ -110,35 +147,37 @@ class Templates:
             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
@@ -193,6 +232,9 @@ class RoundupPageTemplate(PageTemplate.PageTemplate):
             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
     '''
@@ -246,17 +288,52 @@ 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):
@@ -270,6 +347,8 @@ class HTMLClass(HTMLPermissions):
         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)
 
@@ -320,11 +399,15 @@ class HTMLClass(HTMLPermissions):
         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':
@@ -334,7 +417,7 @@ class HTMLClass(HTMLPermissions):
 
         return klass(self._client, self.classname, itemid)
 
-    def properties(self):
+    def properties(self, sort=1):
         ''' Return HTMLProperty for all of this class' properties.
         '''
         l = []
@@ -347,9 +430,11 @@ class HTMLClass(HTMLPermissions):
                 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':
@@ -359,7 +444,7 @@ class HTMLClass(HTMLPermissions):
 
         # 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]
@@ -368,17 +453,13 @@ class HTMLClass(HTMLPermissions):
     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:
@@ -389,7 +470,7 @@ class HTMLClass(HTMLPermissions):
                     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):
@@ -399,18 +480,17 @@ class HTMLClass(HTMLPermissions):
         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:
@@ -419,8 +499,8 @@ class HTMLClass(HTMLPermissions):
              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 
@@ -432,22 +512,32 @@ class HTMLClass(HTMLPermissions):
 
             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 = '&amp;property=%s'%property
+        return '<a class="classhelp" href="javascript:help_window(\'%s?'\
+            '@startwith=0&amp;@template=help&amp;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):
@@ -462,9 +552,13 @@ class HTMLClass(HTMLPermissions):
         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):
@@ -478,6 +572,8 @@ class HTMLItem(HTMLPermissions):
         # 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)
@@ -514,12 +610,16 @@ class HTMLItem(HTMLPermissions):
             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 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
+        return 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.
@@ -528,6 +628,8 @@ class HTMLItem(HTMLPermissions):
         return []
 
     def history(self, direction='descending', dre=re.compile('\d+')):
+        self.view_check()
+
         l = ['<table class="history">'
              '<tr><th colspan="4" class="header">',
              _('History'),
@@ -552,9 +654,17 @@ class HTMLItem(HTMLPermissions):
                     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])
+                        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("."," ")
@@ -584,116 +694,123 @@ class HTMLItem(HTMLPermissions):
                         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 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:
-                                        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):
-                                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]:
+                        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)'
+                        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!!
@@ -722,7 +839,7 @@ class HTMLItem(HTMLPermissions):
         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')
@@ -756,16 +873,18 @@ class HTMLUser(HTMLItem):
             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
+            self._classname) or (self._nodeid == self._client.userid and
+            self._db.user.get(self._client.userid, 'username') != 'anonymous')
 
     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
+        return self._db.security.hasPermission('View', self._client.userid,
+            self._classname) or (self._nodeid == self._client.userid and
+            self._db.user.get(self._client.userid, 'username') != 'anonymous')
 
-class HTMLProperty:
+class HTMLProperty(HTMLInputMixin, HTMLPermissions):
     ''' String, Number, Date, Interval HTMLProperty
 
         Has useful attributes:
@@ -789,6 +908,9 @@ class HTMLProperty:
             self._formname = '%s%s@%s'%(classname, nodeid, name)
         else:
             self._formname = name
+
+        HTMLInputMixin.__init__(self)
+
     def __repr__(self):
         return '<HTMLProperty(0x%x) %s %r %r>'%(id(self), self._formname,
             self._prop, self._value)
@@ -799,9 +921,29 @@ class HTMLProperty:
             return cmp(self._value, other._value)
         return cmp(self._value, other)
 
+    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'):
@@ -821,13 +963,19 @@ class StringHTMLProperty(HTMLProperty):
             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:
@@ -835,6 +983,7 @@ class StringHTMLProperty(HTMLProperty):
         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)
@@ -845,37 +994,59 @@ class StringHTMLProperty(HTMLProperty):
 
             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 = '&quot;'.join(value.split('"'))
-        return '<input name="%s" value="%s" size="%s">'%(self._formname, 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 = '&quot;'.join(value.split('"'))
-        return '<textarea name="%s" rows="%s" cols="%s">%s</textarea>'%(
-            self._formname, 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])
@@ -891,38 +1062,64 @@ class PasswordHTMLProperty(HTMLProperty):
     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._formname, 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._formname, 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 = '&quot;'.join(value.split('"'))
-        return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+            return self.input(name=self._formname,value=value,size=size)
+
+        return self.plain()
 
     def __int__(self):
         ''' Return an int of me
@@ -939,28 +1136,43 @@ 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 is_edit_ok():
+            return self.plain()
+
         checked = self._value and "checked" or ""
-        s = '<input type="radio" name="%s" value="yes" %s>Yes'%(self._formname,
-            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._formname,
-            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()))
@@ -971,29 +1183,42 @@ class DateHTMLProperty(HTMLProperty):
             This is useful for defaulting a new value. Returns a
             DateHTMLProperty.
         '''
+        self.view_check()
+
         return DateHTMLProperty(self._client, 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 is_edit_ok():
             value = '&quot;'.join(value.split('"'))
-        return '<input name="%s" value="%s" size="%s">'%(self._formname, 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)
@@ -1007,6 +1232,8 @@ class DateHTMLProperty(HTMLProperty):
             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.
         '''
+        self.view_check()
+
         if format is not self._marker:
             return self._value.pretty(format)
         else:
@@ -1015,6 +1242,8 @@ class DateHTMLProperty(HTMLProperty):
     def local(self, offset):
         ''' Return the date/time as a local (timezone offset) date/time.
         '''
+        self.view_check()
+
         return DateHTMLProperty(self._client, self._nodeid, self._prop,
             self._formname, self._value.local(offset))
 
@@ -1022,6 +1251,8 @@ 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)
@@ -1029,17 +1260,27 @@ class IntervalHTMLProperty(HTMLProperty):
     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 = '&quot;'.join(value.split('"'))
-        return '<input name="%s" value="%s" size="%s">'%(self._formname, value, size)
+            return self.input(name=self._formname,value=value,size=size)
+
+        return self.plain()
 
 class LinkHTMLProperty(HTMLProperty):
     ''' Link HTMLProperty
@@ -1073,6 +1314,8 @@ class LinkHTMLProperty(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]
@@ -1084,71 +1327,56 @@ class LinkHTMLProperty(HTMLProperty):
 
     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._formname]
-        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:
+                label = 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)
+                label = self._value
+            value = cgi.escape(str(self._value))
+            value = '&quot;'.join(value.split('"'))
+        return '<input name="%s" value="%s" size="%s">'%(self._formname,
+            label, 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()
+
+        if not self.is_edit_ok():
+            return self.plain()
 
-        # sort function
-        sortfunc = make_sort_function(self._db, self._prop.classname)
+        value = self._value
 
         linkcl = self._db.getclass(self._prop.classname)
         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
@@ -1162,7 +1390,7 @@ class LinkHTMLProperty(HTMLProperty):
             # figure if this option is selected
             s = ''
             if value in [optionid, option]:
-                s = 'selected '
+                s = 'selected="selected" '
 
             # figure the label
             if showid:
@@ -1192,6 +1420,12 @@ class MultilinkHTMLProperty(HTMLProperty):
         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)
@@ -1213,7 +1447,7 @@ class MultilinkHTMLProperty(HTMLProperty):
 
     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
 
@@ -1231,6 +1465,8 @@ class MultilinkHTMLProperty(HTMLProperty):
     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 = []
@@ -1243,12 +1479,16 @@ class MultilinkHTMLProperty(HTMLProperty):
 
     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
@@ -1256,23 +1496,27 @@ class MultilinkHTMLProperty(HTMLProperty):
             k = linkcl.labelprop(1)
             value = [linkcl.get(v, k) for v in value]
         value = cgi.escape(','.join(value))
-        return '<input name="%s" size="%s" value="%s">'%(self._formname, 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()
+
+        if not self.is_edit_ok():
+            return self.plain()
 
-        # sort function
-        sortfunc = make_sort_function(self._db, self._prop.classname)
+        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._formname, height)]
         k = linkcl.labelprop(1)
@@ -1289,7 +1533,7 @@ class MultilinkHTMLProperty(HTMLProperty):
             # 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:
@@ -1324,18 +1568,22 @@ propclasses = (
     (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.
@@ -1358,29 +1606,30 @@ class ShowDict:
     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
@@ -1395,6 +1644,8 @@ class HTMLRequest:
         # the special char to use for special vars
         self.special_char = '@'
 
+        HTMLInputMixin.__init__(self)
+
         self._post_init()
 
     def _post_init(self):
@@ -1447,13 +1698,17 @@ class HTMLRequest:
         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
 
@@ -1482,16 +1737,9 @@ class HTMLRequest:
         ''' 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):
@@ -1550,7 +1798,7 @@ env: %(env)s
         ''' 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:
@@ -1627,11 +1875,12 @@ env: %(env)s
 
     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;