Code

bug #473124: UI inconsistency with Link fields.
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sun, 21 Oct 2001 04:44:50 +0000 (04:44 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Sun, 21 Oct 2001 04:44:50 +0000 (04:44 +0000)
   This also prompted me to fix a fairly long-standing usability issue -
   that of being able to turn off certain filters.

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@322 57a73879-2fb5-44c3-a270-3262357dd7e2

CHANGES.txt
roundup/cgi_client.py
roundup/htmltemplate.py
roundup/hyperdb.py

index 3dc98a97268383df849de09878a06c71b6207f65..23bcb266bbf7a7cbfa014ee24dd2ddc84d3b8724 100644 (file)
@@ -3,6 +3,7 @@ are given with the most recent entry first.
 
 2001-10-?? - 0.3.0
 Feature:
+ . MailGW now moves 'unread' to 'chatting' on receiving e-mail for an issue.
  Admin Tool (roundup-admin):
   . Interactive mode for running multiple (independant at present) commands.
   . Tabular display of nodes.
@@ -20,9 +21,12 @@ Fixed:
    customisation section may now be hidden (patch from Roch'e Compaan.)
  . bug #473122: Issue id sorting (hyperdb sorts strings-that-look-like-numbers
    as numbers now.
+ . bug #473124: UI inconsistency with Link fields.
+   This also prompted me to fix a fairly long-standing usability issue -
+   that of being able to turn off certain filters.
+ . bug #473125: Paragraph in e-mails
  . bug #473126: Sender unknown
  . bug #473130: Nosy list not set correctly
- . bug #473125: Paragraph in e-mails
 
 2001-10-11 - 0.3.0 pre 2
 Fixed:
index 55ced042a158a93b77fd0e0016a7695d14c4afd7..e46098ba0aa2273593bbb56c400825db403dddb4 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: cgi_client.py,v 1.35 2001-10-21 00:17:54 richard Exp $
+# $Id: cgi_client.py,v 1.36 2001-10-21 04:44:50 richard Exp $
 
 import os, cgi, pprint, StringIO, urlparse, re, traceback, mimetypes
 import base64, Cookie, time
@@ -129,7 +129,7 @@ class Client:
             return arg.value.split(',')
         return []
 
-    def index_filterspec(self):
+    def index_filterspec(self, filter):
         ''' pull the index filter spec from the form
 
         Links and multilinks want to be lists - the rest are straight
@@ -141,6 +141,7 @@ class Client:
         for key in self.form.keys():
             if key[0] == ':': continue
             if not props.has_key(key): continue
+            if key not in filter: continue
             prop = props[key]
             value = self.form[key]
             if (isinstance(prop, hyperdb.Link) or
@@ -173,24 +174,32 @@ class Client:
 
     default_index_sort = ['-activity']
     default_index_group = ['priority']
-    default_index_filter = []
+    default_index_filter = ['status']
     default_index_columns = ['id','activity','title','status','assignedto']
     default_index_filterspec = {'status': ['1', '2', '3', '4', '5', '6', '7']}
     def index(self):
         ''' put up an index
         '''
         self.classname = 'issue'
-        if self.form.has_key(':sort'): sort = self.index_arg(':sort')
-        else: sort = self.default_index_sort
-        if self.form.has_key(':group'): group = self.index_arg(':group')
-        else: group = self.default_index_group
-        if self.form.has_key(':filter'): filter = self.index_arg(':filter')
-        else: filter = self.default_index_filter
-        if self.form.has_key(':columns'): columns = self.index_arg(':columns')
-        else: columns = self.default_index_columns
-        filterspec = self.index_filterspec()
-        if not filterspec:
+        # see if the web has supplied us with any customisation info
+        defaults = 1
+        for key in ':sort', ':group', ':filter', ':columns':
+            if self.form.has_key(key):
+                defaults = 0
+                break
+        if defaults:
+            # no info supplied - use the defaults
+            sort = self.default_index_sort
+            group = self.default_index_group
+            filter = self.default_index_filter
+            columns = self.default_index_columns
             filterspec = self.default_index_filterspec
+        else:
+            sort = self.index_arg(':sort')
+            group = self.index_arg(':group')
+            filter = self.index_arg(':filter')
+            columns = self.index_arg(':columns')
+            filterspec = self.index_filterspec(filter)
         return self.list(columns=columns, filter=filter, group=group,
             sort=sort, filterspec=filterspec)
 
@@ -216,7 +225,7 @@ class Client:
         if group is None: group = self.index_arg(':group')
         if filter is None: filter = self.index_arg(':filter')
         if columns is None: columns = self.index_arg(':columns')
-        if filterspec is None: filterspec = self.index_filterspec()
+        if filterspec is None: filterspec = self.index_filterspec(filter)
         if show_customization is None:
             show_customization = self.customization_widget()
 
@@ -676,7 +685,7 @@ class ExtendedClient(Client):
 
     default_index_sort = ['-activity']
     default_index_group = ['priority']
-    default_index_filter = []
+    default_index_filter = ['status']
     default_index_columns = ['activity','status','title','assignedto']
     default_index_filterspec = {'status': ['1', '2', '3', '4', '5', '6', '7']}
 
@@ -699,8 +708,8 @@ class ExtendedClient(Client):
         if self.user not in (None, 'anonymous'):
             userid = self.db.user.lookup(self.user)
             user_info = '''
-<a href="issue?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">My Issues</a> |
-<a href="support?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">My Support</a> |
+<a href="issue?assignedto=%s&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:filter=status,assignedto&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">My Issues</a> |
+<a href="support?assignedto=%s&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:filter=status,assignedto&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">My Support</a> |
 <a href="user%s">My Details</a> | <a href="logout">Logout</a>
 '''%(userid, userid, userid)
         else:
@@ -725,11 +734,11 @@ class ExtendedClient(Client):
 <td align=right valign=bottom>%s</td></tr>
 <tr class="location-bar">
 <td align=left>All
-<a href="issue?status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>,
-<a href="support?status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">Support</a>
+<a href="issue?status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:filter=status&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>,
+<a href="support?status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:filter=status&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">Support</a>
 | Unassigned
-<a href="issue?assignedto=admin&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>,
-<a href="support?assignedto=admin&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">Support</a>
+<a href="issue?assignedto=-1&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:filter=status,assignedto&:columns=id,activity,status,title,assignedto&:group=priority&show_customization=1">Issues</a>,
+<a href="support?assignedto=-1&status=-1,unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:filter=status,assignedto&:columns=id,activity,status,title,assignedto&:group=customername&show_customization=1">Support</a>
 %s
 %s</td>
 <td align=right>%s</td>
@@ -758,14 +767,19 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
             value = date.Interval(form[key].value.strip())
         elif isinstance(proptype, hyperdb.Link):
             value = form[key].value.strip()
-            # handle key values
-            link = cl.properties[key].classname
-            if not num_re.match(value):
-                try:
-                    value = db.classes[link].lookup(value)
-                except KeyError:
-                    raise ValueError, 'property "%s": %s not a %s'%(
-                        key, value, link)
+            # see if it's the "no selection" choice
+            if value == '-1':
+                # don't set this property
+                continue
+            else:
+                # handle key values
+                link = cl.properties[key].classname
+                if not num_re.match(value):
+                    try:
+                        value = db.classes[link].lookup(value)
+                    except KeyError:
+                        raise ValueError, 'property "%s": %s not a %s'%(
+                            key, value, link)
         elif isinstance(proptype, hyperdb.Multilink):
             value = form[key]
             if type(value) != type([]):
@@ -794,6 +808,10 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.35  2001/10/21 00:17:54  richard
+# CGI interface view customisation section may now be hidden (patch from
+#  Roch'e Compaan.)
+#
 # Revision 1.34  2001/10/20 11:58:48  richard
 # Catch errors in login - no username or password supplied.
 # Fixed editing of password (Password property type) thanks Roch'e Compaan.
index c65ef4dea6763cd0ad5db16f6df981b888585938..c720bb18f310976a1d915292b5bcdf3b832580a0 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: htmltemplate.py,v 1.29 2001-10-21 00:17:56 richard Exp $
+# $Id: htmltemplate.py,v 1.30 2001-10-21 04:44:50 richard Exp $
 
 import os, re, StringIO, urllib, cgi, errno
 
@@ -116,6 +116,11 @@ class Field(Base):
             linkcl = self.db.classes[propclass.classname]
             l = ['<select name="%s">'%property]
             k = linkcl.labelprop()
+            if value is None:
+                s = 'selected '
+            else:
+                s = ''
+            l.append('<option %svalue="-1">- no selection -</option>'%s)
             for optionid in linkcl.list():
                 option = linkcl.get(optionid, k)
                 s = ''
@@ -169,6 +174,10 @@ class Menu(Base):
             linkcl = self.db.classes[propclass.classname]
             l = ['<select name="%s">'%property]
             k = linkcl.labelprop()
+            s = ''
+            if value is None:
+                s = 'selected '
+            l.append('<option %svalue="-1">- no selection -</option>'%s)
             for optionid in linkcl.list():
                 option = linkcl.get(optionid, k)
                 s = ''
@@ -335,6 +344,15 @@ class Checklist(Base):
                 checked = ''
             l.append('%s:<input type="checkbox" %s name="%s" value="%s">'%(
                 option, checked, property, option))
+
+        # for Links, allow the "unselected" option too
+        if isinstance(propclass, hyperdb.Link):
+            if value is None or '-1' in value:
+                checked = 'checked'
+            else:
+                checked = ''
+            l.append('[unselected]:<input type="checkbox" %s name="%s" '
+                'value="-1">'%(checked, property))
         return '\n'.join(l)
 
 class Note(Base):
@@ -529,12 +547,88 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
                 l.append(name)
         columns = l
 
+    # display the filter section
+    filter_section(w, cl, filter, columns, group, all_filters, all_columns,
+        show_display_form, show_customization)
+
+    # now display the index section
+    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-header"><a href="%s">%s</a></span></td>\n'%(
+                anchor, cname))
+        else:
+            w('<td><span class="list-header">%s</span></td>\n'%cname)
+    w('</tr>\n')
+
+    # this stuff is used for group headings - optimise the group names
+    old_group = None
+    group_names = []
+    if group:
+        for name in group:
+            if name[0] == '-': group_names.append(name[1:])
+            else: group_names.append(name)
+
+    # now actually loop through all the nodes we get from the filter and
+    # apply the template
+    if nodeids is None:
+        nodeids = cl.filter(filterspec, sort, group)
+    for nodeid in nodeids:
+        # check for a group heading
+        if group_names:
+            this_group = [cl.get(nodeid, name) for name in group_names]
+            if this_group != old_group:
+                l = []
+                for name in group_names:
+                    prop = properties[name]
+                    if isinstance(prop, hyperdb.Link):
+                        group_cl = db.classes[prop.classname]
+                        key = group_cl.getkey()
+                        value = cl.get(nodeid, name)
+                        if value is None:
+                            l.append('[unselected %s]'%prop.classname)
+                        else:
+                            l.append(group_cl.get(cl.get(nodeid, name), key))
+                    elif isinstance(prop, hyperdb.Multilink):
+                        group_cl = db.classes[prop.classname]
+                        key = group_cl.getkey()
+                        for value in cl.get(nodeid, name):
+                            l.append(group_cl.get(value, key))
+                    else:
+                        value = cl.get(nodeid, name)
+                        if value is None:
+                            value = '[empty %s]'%name
+                        else:
+                            value = str(value)
+                        l.append(value)
+                w('<tr class="section-bar">'
+                  '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
+                    len(columns), ', '.join(l)))
+                old_group = this_group
+
+        # display this node's row
+        for value in globals.values():
+            if hasattr(value, 'nodeid'):
+                value.nodeid = nodeid
+        replace = IndexTemplateReplace(globals, locals(), columns)
+        w(replace.go(template))
+
+    w('</table>')
+
+
+def filter_section(w, cl, filter, columns, group, all_filters, all_columns,
+        show_display_form, show_customization):
     # now add in the filter/columns/group/etc config table form
     w('<input type="hidden" name="show_customization" value="%s">' %
         show_customization )
     w('<table width=100% border=0 cellspacing=0 cellpadding=2>\n')
     names = []
-    for name in cl.getprops().keys():
+    properties = cl.getprops()
+    for name in properties.keys():
         if name in all_filters or name in all_columns:
             names.append(name)
     w('<tr class="location-bar">')
@@ -616,75 +710,6 @@ def index(client, templates, db, classname, filterspec={}, filter=[],
         w('</table>\n')
         w('</form>\n')
 
-    # now display the index section
-    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-header"><a href="%s">%s</a></span></td>\n'%(
-                anchor, cname))
-        else:
-            w('<td><span class="list-header">%s</span></td>\n'%cname)
-    w('</tr>\n')
-
-    # this stuff is used for group headings - optimise the group names
-    old_group = None
-    group_names = []
-    if group:
-        for name in group:
-            if name[0] == '-': group_names.append(name[1:])
-            else: group_names.append(name)
-
-    # now actually loop through all the nodes we get from the filter and
-    # apply the template
-    if nodeids is None:
-        nodeids = cl.filter(filterspec, sort, group)
-    for nodeid in nodeids:
-        # check for a group heading
-        if group_names:
-            this_group = [cl.get(nodeid, name) for name in group_names]
-            if this_group != old_group:
-                l = []
-                for name in group_names:
-                    prop = properties[name]
-                    if isinstance(prop, hyperdb.Link):
-                        group_cl = db.classes[prop.classname]
-                        key = group_cl.getkey()
-                        value = cl.get(nodeid, name)
-                        if value is None:
-                            l.append('[unselected %s]'%prop.classname)
-                        else:
-                            l.append(group_cl.get(cl.get(nodeid, name), key))
-                    elif isinstance(prop, hyperdb.Multilink):
-                        group_cl = db.classes[prop.classname]
-                        key = group_cl.getkey()
-                        for value in cl.get(nodeid, name):
-                            l.append(group_cl.get(value, key))
-                    else:
-                        value = cl.get(nodeid, name)
-                        if value is None:
-                            value = '[empty %s]'%name
-                        else:
-                            value = str(value)
-                        l.append(value)
-                w('<tr class="section-bar">'
-                  '<td align=middle colspan=%s><strong>%s</strong></td></tr>'%(
-                    len(columns), ', '.join(l)))
-                old_group = this_group
-
-        # display this node's row
-        for value in globals.values():
-            if hasattr(value, 'nodeid'):
-                value.nodeid = nodeid
-        replace = IndexTemplateReplace(globals, locals(), columns)
-        w(replace.go(template))
-
-    w('</table>')
-
-
 #
 #   ITEM TEMPLATES
 #
@@ -790,6 +815,10 @@ def newitem(client, templates, db, classname, form, replace=re.compile(
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.29  2001/10/21 00:17:56  richard
+# CGI interface view customisation section may now be hidden (patch from
+#  Roch'e Compaan.)
+#
 # Revision 1.28  2001/10/21 00:00:16  richard
 # Fixed Checklist function - wasn't always working on a list.
 #
index 391c6f3c1b80a411dd6d4ebc12a8974c3ed07ad4..8bcd4e919ef474deb227f0dc786336318e2a0f4f 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.27 2001-10-20 23:44:27 richard Exp $
+# $Id: hyperdb.py,v 1.28 2001-10-21 04:44:50 richard Exp $
 
 # standard python modules
 import cPickle, re, string
@@ -584,11 +584,12 @@ class Class:
                 u = []
                 link_class =  self.db.classes[propclass.classname]
                 for entry in v:
-                    if not num_re.match(entry):
+                    if entry == '-1': entry = None
+                    elif not num_re.match(entry):
                         try:
                             entry = link_class.lookup(entry)
                         except:
-                            raise ValueError, 'new property "%s": %s not a %s'%(
+                            raise ValueError, 'property "%s": %s not a %s'%(
                                 k, entry, self.properties[k].classname)
                     u.append(entry)
 
@@ -846,6 +847,9 @@ def Choice(name, *options):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.27  2001/10/20 23:44:27  richard
+# Hyperdatabase sorts strings-that-look-like-numbers as numbers now.
+#
 # Revision 1.26  2001/10/16 03:48:01  richard
 # admin tool now complains if a "find" is attempted with a non-link property.
 #