Code

Fixed CGI client change messages so they actually include the properties
[roundup.git] / roundup / htmltemplate.py
index 036ad190cc67a1b66a9b85bbd05aab1c6b07672f..3d9d69e70b3933f21ab964327230d44135fe302f 100644 (file)
@@ -1,15 +1,33 @@
-# $Id: htmltemplate.py,v 1.8 2001-07-29 07:01:39 richard Exp $
+#
+# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
+# This module is free software, and you may redistribute it and/or modify
+# under the same terms as Python, so long as this copyright message and
+# disclaimer are retained in their original form.
+#
+# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
+# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
+# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+# 
+# $Id: htmltemplate.py,v 1.21 2001-08-16 07:34:59 richard Exp $
 
 import os, re, StringIO, urllib, cgi, errno
 
 import hyperdb, date
 
 class Base:
-    def __init__(self, db, templates, classname, nodeid=None, form=None):
+    def __init__(self, db, templates, classname, nodeid=None, form=None,
+            filterspec=None):
         # TODO: really not happy with the way templates is passed on here
         self.db, self.templates = db, templates
         self.classname, self.nodeid = classname, nodeid
-        self.form = form
+        self.form, self.filterspec = form, filterspec
         self.cl = self.db.classes[self.classname]
         self.properties = self.cl.getprops()
 
@@ -30,21 +48,21 @@ class Plain(Base):
             value = self.cl.get(self.nodeid, property)
         else:
             # TODO: pull the value from the form
-            if propclass.isMultilinkType: value = []
+            if isinstance(propclass, hyperdb.Multilink): value = []
             else: value = ''
-        if propclass.isStringType:
+        if isinstance(propclass, hyperdb.String):
             if value is None: value = ''
             else: value = str(value)
-        elif propclass.isDateType:
+        elif isinstance(propclass, hyperdb.Date):
             value = str(value)
-        elif propclass.isIntervalType:
+        elif isinstance(propclass, hyperdb.Interval):
             value = str(value)
-        elif propclass.isLinkType:
+        elif isinstance(propclass, hyperdb.Link):
             linkcl = self.db.classes[propclass.classname]
             k = linkcl.labelprop()
             if value: value = str(linkcl.get(value, k))
             else: value = '[unselected]'
-        elif propclass.isMultilinkType:
+        elif isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
             k = linkcl.labelprop()
             value = ', '.join([linkcl.get(i, k) for i in value])
@@ -57,17 +75,28 @@ class Field(Base):
         to be edited
     '''
     def __call__(self, property, size=None, height=None, showid=0):
-        if not self.nodeid and self.form is None:
+        if not self.nodeid and self.form is None and self.filterspec is None:
             return '[Field: not called from item]'
         propclass = self.properties[property]
         if self.nodeid:
-            value = self.cl.get(self.nodeid, property)
+            value = self.cl.get(self.nodeid, property, None)
+            # TODO: remove this from the code ... it's only here for
+            # handling schema changes, and they should be handled outside
+            # of this code...
+            if isinstance(propclass, hyperdb.Multilink) and value is None:
+                value = []
+        elif self.filterspec is not None:
+            if isinstance(propclass, hyperdb.Multilink):
+                value = self.filterspec.get(property, [])
+            else:
+                value = self.filterspec.get(property, '')
         else:
             # TODO: pull the value from the form
-            if propclass.isMultilinkType: value = []
+            if isinstance(propclass, hyperdb.Multilink): value = []
             else: value = ''
-        if (propclass.isStringType or propclass.isDateType or
-                propclass.isIntervalType):
+        if (isinstance(propclass, hyperdb.String) or
+                isinstance(propclass, hyperdb.Date) or
+                isinstance(propclass, hyperdb.Interval)):
             size = size or 30
             if value is None:
                 value = ''
@@ -75,7 +104,7 @@ class Field(Base):
                 value = cgi.escape(value)
                 value = '"'.join(value.split('"'))
             s = '<input name="%s" value="%s" size="%s">'%(property, value, size)
-        elif propclass.isLinkType:
+        elif isinstance(propclass, hyperdb.Link):
             linkcl = self.db.classes[propclass.classname]
             l = ['<select name="%s">'%property]
             k = linkcl.labelprop()
@@ -93,7 +122,7 @@ class Field(Base):
                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, lab))
             l.append('</select>')
             s = '\n'.join(l)
-        elif propclass.isMultilinkType:
+        elif isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
             list = linkcl.list()
             height = height or min(len(list), 7)
@@ -126,9 +155,9 @@ class Menu(Base):
             value = self.cl.get(self.nodeid, property)
         else:
             # TODO: pull the value from the form
-            if propclass.isMultilinkType: value = []
+            if isinstance(propclass, hyperdb.Multilink): value = []
             else: value = None
-        if propclass.isLinkType:
+        if isinstance(propclass, hyperdb.Link):
             linkcl = self.db.classes[propclass.classname]
             l = ['<select name="%s">'%property]
             k = linkcl.labelprop()
@@ -140,7 +169,7 @@ class Menu(Base):
                 l.append('<option %svalue="%s">%s</option>'%(s, optionid, option))
             l.append('</select>')
             return '\n'.join(l)
-        if propclass.isMultilinkType:
+        if isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
             list = linkcl.list()
             height = height or min(len(list), 7)
@@ -175,16 +204,16 @@ class Link(Base):
         if self.nodeid:
             value = self.cl.get(self.nodeid, property)
         else:
-            if propclass.isMultilinkType: value = []
+            if isinstance(propclass, hyperdb.Multilink): value = []
             else: value = ''
-        if propclass.isLinkType:
+        if isinstance(propclass, hyperdb.Link):
             if value is None:
                 return '[not assigned]'
             linkcl = self.db.classes[propclass.classname]
             k = linkcl.labelprop()
             linkvalue = linkcl.get(value, k)
             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
-        if propclass.isMultilinkType:
+        if isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
             k = linkcl.labelprop()
             l = []
@@ -203,7 +232,7 @@ class Count(Base):
             return '[Count: not called from item]'
         propclass = self.properties[property]
         value = self.cl.get(self.nodeid, property)
-        if propclass.isMultilinkType:
+        if isinstance(propclass, hyperdb.Multilink):
             return str(len(value))
         return '[Count: not a Multilink]'
 
@@ -218,7 +247,7 @@ class Reldate(Base):
         if not self.nodeid and self.form is None:
             return '[Reldate: not called from item]'
         propclass = self.properties[property]
-        if not propclass.isDateType:
+        if isinstance(not propclass, hyperdb.Date):
             return '[Reldate: not a Date]'
         if self.nodeid:
             value = self.cl.get(self.nodeid, property)
@@ -243,11 +272,11 @@ class Download(Base):
             return '[Download: not called from item]'
         propclass = self.properties[property]
         value = self.cl.get(self.nodeid, property)
-        if propclass.isLinkType:
+        if isinstance(propclass, hyperdb.Link):
             linkcl = self.db.classes[propclass.classname]
             linkvalue = linkcl.get(value, k)
             return '<a href="%s%s">%s</a>'%(linkcl, value, linkvalue)
-        if propclass.isMultilinkType:
+        if isinstance(propclass, hyperdb.Multilink):
             linkcl = self.db.classes[propclass.classname]
             l = []
             for value in value:
@@ -265,20 +294,23 @@ class Checklist(Base):
         propclass = self.properties[property]
         if self.nodeid:
             value = self.cl.get(self.nodeid, property)
+        elif self.filterspec is not None:
+            value = self.filterspec.get(property, [])
         else:
             value = []
-        if propclass.isLinkType or propclass.isMultilinkType:
+        if (isinstance(propclass, hyperdb.Link) or
+                isinstance(propclass, hyperdb.Multilink)):
             linkcl = self.db.classes[propclass.classname]
             l = []
             k = linkcl.labelprop()
             for optionid in linkcl.list():
                 option = linkcl.get(optionid, k)
-                if optionid in value:
+                if optionid in value or option in value:
                     checked = 'checked'
                 else:
                     checked = ''
                 l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
-                    option, checked, propclass.classname, option))
+                    option, checked, property, option))
             return '\n'.join(l)
         return '[Checklist: not a link]'
 
@@ -298,7 +330,7 @@ class List(Base):
     '''
     def __call__(self, property, **args):
         propclass = self.properties[property]
-        if not propclass.isMultilinkType:
+        if isinstance(not propclass, hyperdb.Multilink):
             return '[List: not a Multilink]'
         fp = StringIO.StringIO()
         args['show_display_form'] = 0
@@ -313,6 +345,9 @@ class History(Base):
     ''' list the history of the item
     '''
     def __call__(self, **args):
+        if self.nodeid is None:
+            return "[History: node doesn't exist]"
+
         l = ['<table width=100% border=0 cellspacing=0 cellpadding=2>',
             '<tr class="list-header">',
             '<td><span class="list-item"><strong>Date</strong></span></td>',
@@ -352,7 +387,7 @@ class IndexTemplateReplace:
             r'((<property\s+name="(?P<name>[^>]+)">(?P<text>.+?)</property>)|'
             r'(?P<display><display\s+call="(?P<command>[^"]+)">))', re.I|re.S)):
         return replace.sub(self, text)
-        
+
     def __call__(self, m, filter=None, columns=None, sort=None, group=None):
         if m.group('name'):
             if m.group('name') in self.props:
@@ -386,14 +421,14 @@ def sortby(sort_name, columns, filter, sort, group, filterspec):
     for name in sort:
         dir = name[0]
         if dir == '-':
-            dir = ''
-        else:
             name = name[1:]
+        else:
+            dir = ''
         if sort_name == name:
-            if dir == '':
-                s_dir = '-'
-            elif dir == '-':
+            if dir == '-':
                 s_dir = ''
+            else:
+                s_dir = '-'
         else:
             m.append(dir+urllib.quote(name))
     m.insert(0, s_dir+urllib.quote(sort_name))
@@ -405,22 +440,23 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
         columns=[], sort=[], group=[], show_display_form=1, nodeids=None,
         col_re=re.compile(r'<property\s+name="([^>]+)">')):
     globals = {
-        'plain': Plain(db, templates, classname, form={}),
-        'field': Field(db, templates, classname, form={}),
-        'menu': Menu(db, templates, classname, form={}),
-        'link': Link(db, templates, classname, form={}),
-        'count': Count(db, templates, classname, form={}),
-        'reldate': Reldate(db, templates, classname, form={}),
-        'download': Download(db, templates, classname, form={}),
-        'checklist': Checklist(db, templates, classname, form={}),
-        'list': List(db, templates, classname, form={}),
-        'history': History(db, templates, classname, form={}),
-        'submit': Submit(db, templates, classname, form={}),
-        'note': Note(db, templates, classname, form={})
+        'plain': Plain(db, templates, classname, filterspec=filterspec),
+        'field': Field(db, templates, classname, filterspec=filterspec),
+        'menu': Menu(db, templates, classname, filterspec=filterspec),
+        'link': Link(db, templates, classname, filterspec=filterspec),
+        'count': Count(db, templates, classname, filterspec=filterspec),
+        'reldate': Reldate(db, templates, classname, filterspec=filterspec),
+        'download': Download(db, templates, classname, filterspec=filterspec),
+        'checklist': Checklist(db, templates, classname, filterspec=filterspec),
+        'list': List(db, templates, classname, filterspec=filterspec),
+        'history': History(db, templates, classname, filterspec=filterspec),
+        'submit': Submit(db, templates, classname, filterspec=filterspec),
+        'note': Note(db, templates, classname, filterspec=filterspec)
     }
     cl = db.classes[classname]
     properties = cl.getprops()
     w = client.write
+    w('<form>')
 
     try:
         template = open(os.path.join(templates, classname+'.filter')).read()
@@ -431,28 +467,26 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
         all_filters = []
     if template and filter:
         # display the filter section
-        w('<form>')
         w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
         w('<tr class="location-bar">')
         w(' <th align="left" colspan="2">Filter specification...</th>')
         w('</tr>')
         replace = IndexTemplateReplace(globals, locals(), filter)
         w(replace.go(template))
-        if columns:
-            w('<input type="hidden" name=":columns" value="%s">'%','.join(columns))
-        if filter:
-            w('<input type="hidden" name=":filter" value="%s">'%','.join(filter))
-        if sort:
-            w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
-        if group:
-            w('<input type="hidden" name=":group" value="%s">'%','.join(group))
-        for k, v in filterspec.items():
-            if type(v) == type([]): v = ','.join(v)
-            w('<input type="hidden" name="%s" value="%s">'%(k, v))
         w('<tr class="location-bar"><td width="1%%">&nbsp;</td>')
         w('<td><input type="submit" value="Redisplay"></td></tr>')
         w('</table>')
-        w('</form>')
+
+    # If the filters aren't being displayed, then hide their current
+    # value in the form
+    if not filter:
+        for k, v in filterspec.items():
+            if type(v) == type([]): v = ','.join(v)
+            w('<input type="hidden" name="%s" value="%s">'%(k, v))
+
+    # make sure that the sorting doesn't get lost either
+    if sort:
+        w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
 
     # XXX deviate from spec here ...
     # load the index section template and figure the default columns from it
@@ -471,18 +505,18 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
         columns = l
 
     # now display the index section
-    w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
-    w('<tr class="list-header">')
+    w('<table width=100% border=0 cellspacing=0 cellpadding=2>\n')
+    w('<tr class="list-header">\n')
     for name in columns:
         cname = name.capitalize()
         if show_display_form:
             anchor = "%s?%s"%(classname, sortby(name, columns, filter,
                 sort, group, filterspec))
-            w('<td><span class="list-item"><a href="%s">%s</a></span></td>'%(
+            w('<td><span class="list-item"><a href="%s">%s</a></span></td>\n'%(
                 anchor, cname))
         else:
-            w('<td><span class="list-item">%s</span></td>'%cname)
-    w('</tr>')
+            w('<td><span class="list-item">%s</span></td>\n'%cname)
+    w('</tr>\n')
 
     # this stuff is used for group headings - optimise the group names
     old_group = None
@@ -504,7 +538,7 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
                 l = []
                 for name in group_names:
                     prop = properties[name]
-                    if prop.isLinkType:
+                    if isinstance(prop, hyperdb.Link):
                         group_cl = db.classes[prop.classname]
                         key = group_cl.getkey()
                         value = cl.get(nodeid, name)
@@ -512,7 +546,7 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
                             l.append('[unselected %s]'%prop.classname)
                         else:
                             l.append(group_cl.get(cl.get(nodeid, name), key))
-                    elif prop.isMultilinkType:
+                    elif isinstance(prop, hyperdb.Multilink):
                         group_cl = db.classes[prop.classname]
                         key = group_cl.getkey()
                         for value in cl.get(nodeid, name):
@@ -540,55 +574,50 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
         return
 
     # now add in the filter/columns/group/etc config table form
-    w('<p><form>')
-    w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
-    for k,v in filterspec.items():
-        if type(v) == type([]): v = ','.join(v)
-        w('<input type="hidden" name="%s" value="%s">'%(k, v))
-    if sort:
-        w('<input type="hidden" name=":sort" value="%s">'%','.join(sort))
+    w('<p>')
+    w('<table width=100% border=0 cellspacing=0 cellpadding=2>\n')
     names = []
     for name in cl.getprops().keys():
         if name in all_filters or name in all_columns:
             names.append(name)
     w('<tr class="location-bar">')
-    w('<th align="left" colspan=%s>View customisation...</th></tr>'%
+    w('<th align="left" colspan=%s>View customisation...</th></tr>\n'%
         (len(names)+1))
     w('<tr class="location-bar"><th>&nbsp;</th>')
     for name in names:
         w('<th>%s</th>'%name.capitalize())
-    w('</tr>')
+    w('</tr>\n')
 
     # filter
     if all_filters:
-        w('<tr><th width="1%" align=right class="location-bar">Filters</th>')
+        w('<tr><th width="1%" align=right class="location-bar">Filters</th>\n')
         for name in names:
             if name not in all_filters:
                 w('<td>&nbsp;</td>')
                 continue
             if name in filter: checked=' checked'
             else: checked=''
-            w('<td align=middle>')
-            w('<input type="checkbox" name=":filter" value="%s" %s></td>'%(name,
-                checked))
-        w('</tr>')
+            w('<td align=middle>\n')
+            w(' <input type="checkbox" name=":filter" value="%s" %s></td>\n'%(
+                name, checked))
+        w('</tr>\n')
 
     # columns
     if all_columns:
-        w('<tr><th width="1%" align=right class="location-bar">Columns</th>')
+        w('<tr><th width="1%" align=right class="location-bar">Columns</th>\n')
         for name in names:
             if name not in all_columns:
                 w('<td>&nbsp;</td>')
                 continue
             if name in columns: checked=' checked'
             else: checked=''
-            w('<td align=middle>')
-            w('<input type="checkbox" name=":columns" value="%s" %s></td>'%(
+            w('<td align=middle>\n')
+            w(' <input type="checkbox" name=":columns" value="%s" %s></td>\n'%(
                 name, checked))
-        w('</tr>')
+        w('</tr>\n')
 
         # group
-        w('<tr><th width="1%" align=right class="location-bar">Grouping</th>')
+        w('<tr><th width="1%" align=right class="location-bar">Grouping</th>\n')
         for name in names:
             prop = properties[name]
             if name not in all_columns:
@@ -596,16 +625,16 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
                 continue
             if name in group: checked=' checked'
             else: checked=''
-            w('<td align=middle>')
-            w('<input type="checkbox" name=":group" value="%s" %s></td>'%(
+            w('<td align=middle>\n')
+            w(' <input type="checkbox" name=":group" value="%s" %s></td>\n'%(
                 name, checked))
-        w('</tr>')
+        w('</tr>\n')
 
     w('<tr class="location-bar"><td width="1%">&nbsp;</td>')
     w('<td colspan="%s">'%len(names))
-    w('<input type="submit" value="Redisplay"></td></tr>')
-    w('</table>')
-    w('</form>')
+    w('<input type="submit" value="Redisplay"></td></tr>\n')
+    w('</table>\n')
+    w('</form>\n')
 
 
 #
@@ -700,13 +729,67 @@ def newitem(client, templates, db, classname, form, replace=re.compile(
         s = open(os.path.join(templates, classname+'.newitem')).read()
     except:
         s = open(os.path.join(templates, classname+'.item')).read()
-    w('<form action="new%s">'%classname)
+    w('<form action="new%s" method="POST" enctype="multipart/form-data">'%classname)
+    for key in form.keys():
+        if key[0] == ':':
+            value = form[key].value
+            if type(value) != type([]): value = [value]
+            for value in value:
+                w('<input type="hidden" name="%s" value="%s">'%(key, value))
     replace = ItemTemplateReplace(globals, locals(), None, None)
     w(replace.go(s))
     w('</form>')
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.20  2001/08/15 23:43:18  richard
+# Fixed some isFooTypes that I missed.
+# Refactored some code in the CGI code.
+#
+# Revision 1.19  2001/08/12 06:32:36  richard
+# using isinstance(blah, Foo) now instead of isFooType
+#
+# Revision 1.18  2001/08/07 00:24:42  richard
+# stupid typo
+#
+# Revision 1.17  2001/08/07 00:15:51  richard
+# Added the copyright/license notice to (nearly) all files at request of
+# Bizar Software.
+#
+# Revision 1.16  2001/08/01 03:52:23  richard
+# Checklist was using wrong name.
+#
+# Revision 1.15  2001/07/30 08:12:17  richard
+# Added time logging and file uploading to the templates.
+#
+# Revision 1.14  2001/07/30 06:17:45  richard
+# Features:
+#  . Added ability for cgi newblah forms to indicate that the new node
+#    should be linked somewhere.
+# Fixed:
+#  . Fixed the agument handling for the roundup-admin find command.
+#  . Fixed handling of summary when no note supplied for newblah. Again.
+#  . Fixed detection of no form in htmltemplate Field display.
+#
+# Revision 1.13  2001/07/30 02:37:53  richard
+# Temporary measure until we have decent schema migration.
+#
+# Revision 1.12  2001/07/30 01:24:33  richard
+# Handles new node display now.
+#
+# Revision 1.11  2001/07/29 09:31:35  richard
+# oops
+#
+# Revision 1.10  2001/07/29 09:28:23  richard
+# Fixed sorting by clicking on column headings.
+#
+# Revision 1.9  2001/07/29 08:27:40  richard
+# Fixed handling of passed-in values in form elements (ie. during a
+# drill-down)
+#
+# Revision 1.8  2001/07/29 07:01:39  richard
+# Added vim command to all source so that we don't get no steenkin' tabs :)
+#
 # Revision 1.7  2001/07/29 05:36:14  richard
 # Cleanup of the link label generation.
 #