Code

. Added simple editing for classes that don't define a templated interface.
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 20 Feb 2002 05:05:29 +0000 (05:05 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Wed, 20 Feb 2002 05:05:29 +0000 (05:05 +0000)
   - access using the admin "class list" interface
   - limited to admin-only
   - requires the csv module from object-craft (url given if it's missing)

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

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

index f20f74d587bbab3a63c650c685398b9da936f344..646fa55d749219f75c036b0f4415762bd93075cc 100644 (file)
@@ -8,7 +8,10 @@ Feature:
      better configuration system.
  . Alternate email addresses are now available for users. See the MIGRATION
    file for info on how to activate the feature.
-
+ . Added simple editing for classes that don't define a templated interface.
+   - access using the admin "class list" interface
+   - limited to admin-only
+   - requires the csv module from object-craft (url given if it's missing)
 
 Fixed:
  . Clean up mail handling, multipart handling.
@@ -25,6 +28,7 @@ Fixed:
  . #516854 ] "My Issues" and redisplay
  . #517906 ] Attribute order in "View customisation"
  . #514854 ] History: "User" is always ticket creator
+ . wasn't handling cvs parser feeding correctly
 
 
 2002-01-24 - 0.4.0
index 661dbb06b9f0d153059a59127998d5b47ae52303..0e446d48c3416eba68af6f936306d1c5e1bed7cd 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.102 2002-02-15 07:08:44 richard Exp $
+# $Id: cgi_client.py,v 1.103 2002-02-20 05:05:28 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
@@ -298,10 +298,88 @@ function submit_once() {
             show_customization = self.customization_widget()
 
         index = htmltemplate.IndexTemplate(self, self.instance.TEMPLATES, cn)
-        index.render(filterspec, filter, columns, sort, group,
-            show_customization=show_customization)
+        try:
+            index.render(filterspec, filter, columns, sort, group,
+                show_customization=show_customization)
+        except htmltemplate.MissingTemplateError:
+            self.basicClassEditPage()
         self.pagefoot()
 
+    def basicClassEditPage(self):
+        '''Display a basic edit page that allows simple editing of the
+           nodes of the current class
+        '''
+        if self.user != 'admin':
+            raise Unauthorised
+        w = self.write
+        cn = self.classname
+        cl = self.db.classes[cn]
+        props = ['id'] + cl.getprops(protected=0).keys()
+
+        # get the CSV module
+        try:
+            import csv
+        except ImportError:
+            w(_('Sorry, you need the csv module to use this function.<br>\n'
+                'Get it from: <a href="http://www.object-craft.com.au/projects/csv/">http://www.object-craft.com.au/projects/csv/'))
+            return
+
+        # do the edit
+        if self.form.has_key('rows'):
+            rows = self.form['rows'].value.splitlines()
+            p = csv.parser()
+            idlessprops = props[1:]
+            found = {}
+            for row in rows:
+                values = p.parse(row)
+                # not a complete row, keep going
+                if not values: continue
+
+                # extract the nodeid
+                nodeid, values = values[0], values[1:]
+                found[nodeid] = 1
+
+                # extract the new values
+                d = {}
+                for name, value in zip(idlessprops, values):
+                    d[name] = value.strip()
+
+                # perform the edit
+                if cl.hasnode(nodeid):
+                    # edit existing
+                    cl.set(nodeid, **d)
+                else:
+                    # new node
+                    found[cl.create(**d)] = 1
+
+            # retire the removed entries
+            for nodeid in cl.list():
+                if not found.has_key(nodeid):
+                    cl.retire(nodeid)
+
+        w(_('''<p class="form-help">You may edit the contents of the
+        "%(classname)s" class using this form.</p>
+        <p class="form-help">Remove entries by deleting their line. Add
+        new entries by appending
+        them to the table - put an X in the id column.</p>''')%{'classname':cn})
+
+        l = []
+        for name in props:
+            l.append(name)
+        w('<tt>')
+        w(', '.join(l) + '\n')
+        w('</tt>')
+
+        w('<form onSubmit="return submit_once()" method="POST">')
+       w('<textarea name="rows" cols=80 rows=15>')
+        for nodeid in cl.list():
+            l = []
+            for name in props:
+                l.append(cgi.escape(str(cl.get(nodeid, name))))
+            w(', '.join(l) + '\n')
+
+        w(_('</textarea><br><input type="submit" value="Save Changes"></form>'))
+
     def shownode(self, message=None):
         ''' display an item
         '''
@@ -744,7 +822,8 @@ function submit_once() {
             self.write('<table border=0 cellspacing=0 cellpadding=2>\n')
             for cn in classnames:
                 cl = self.db.getclass(cn)
-                self.write('<tr class="list-header"><th colspan=2 align=left>%s</th></tr>'%cn.capitalize())
+                self.write('<tr class="list-header"><th colspan=2 align=left>'
+                    '<a href="%s">%s</a></th></tr>'%(cn, cn.capitalize()))
                 for key, value in cl.properties.items():
                     if value is None: value = ''
                     else: value = str(value)
@@ -1012,6 +1091,8 @@ function submit_once() {
         if action == 'logout':
             self.logout()
             return
+
+        # see if we're to display an existing node
         m = dre.match(action)
         if m:
             self.classname = m.group(1)
@@ -1030,6 +1111,8 @@ function submit_once() {
                 raise NotFound
             func()
             return
+
+        # see if we're to put up the new node page
         m = nre.match(action)
         if m:
             self.classname = m.group(1)
@@ -1039,6 +1122,8 @@ function submit_once() {
                 raise NotFound
             func()
             return
+
+        # otherwise, display the named class
         self.classname = action
         try:
             self.db.getclass(self.classname)
@@ -1202,6 +1287,10 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.102  2002/02/15 07:08:44  richard
+#  . Alternate email addresses are now available for users. See the MIGRATION
+#    file for info on how to activate the feature.
+#
 # Revision 1.101  2002/02/14 23:39:18  richard
 # . All forms now have "double-submit" protection when Javascript is enabled
 #   on the client-side.
index ad7d8dec93a033695389c723b7dee0da1b8920e9..92c0937e5e603aab95407b8f92399fd9ca848431 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.76 2002-02-16 09:10:52 richard Exp $
+# $Id: htmltemplate.py,v 1.77 2002-02-20 05:05:29 richard Exp $
 
 __doc__ = """
 Template engine.
@@ -33,6 +33,9 @@ try:
 except ImportError:
     StructuredText = None
 
+class MissingTemplateError(ValueError):
+    pass
+
 class TemplateFunctions:
     def __init__(self):
         self.form = None
@@ -714,8 +717,12 @@ class IndexTemplate(TemplateFunctions):
 
         # XXX deviate from spec here ...
         # load the index section template and figure the default columns from it
-        template = open(os.path.join(self.templates,
-            self.classname+'.index')).read()
+        try:
+            template = open(os.path.join(self.templates,
+                self.classname+'.index')).read()
+        except IOError, error:
+            if error.errno not in (errno.ENOENT, errno.ESRCH): raise
+            raise MissingTemplateError, self.classname+'.index'
         all_columns = self.col_re.findall(template)
         if not columns:
             columns = []
@@ -1070,6 +1077,9 @@ class NewItemTemplate(TemplateFunctions):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.76  2002/02/16 09:10:52  richard
+# oops
+#
 # Revision 1.75  2002/02/16 08:43:23  richard
 #  . #517906 ] Attribute order in "View customisation"
 #
index f0aaf929637b1fdd6143266719275f5e0a780537..0386a7371fe6b154de8f2a2d928a49971de66ecc 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.55 2002-02-15 07:27:12 richard Exp $
+# $Id: hyperdb.py,v 1.56 2002-02-20 05:05:28 richard Exp $
 
 __doc__ = """
 Hyperdatabase implementation, especially field types.
@@ -490,6 +490,7 @@ class Class:
         if node.has_key(self.db.RETIRED_FLAG):
             raise IndexError
         num_re = re.compile('^\d+$')
+        set = {}
         for key, value in propvalues.items():
             # check to make sure we're not duplicating an existing key
             if key == self.key and node[key] != value:
@@ -505,6 +506,13 @@ class Class:
             # the writeable properties.
             prop = self.properties[key]
 
+            # if the value's the same as the existing value, no sense in
+            # doing anything
+            if value == node[key]:
+                del propvalues[key]
+                continue
+
+            # do stuff based on the prop type
             if isinstance(prop, Link):
                 link_class = self.properties[key].classname
                 # if it isn't a number, it's a key
@@ -598,6 +606,11 @@ class Class:
 
             node[key] = value
 
+        # nothing to do?
+        if not propvalues:
+            return
+
+        # do the set, and journal it
         self.db.setnode(self.classname, nodeid, node)
         self.db.addjournal(self.classname, nodeid, 'set', propvalues)
 
@@ -633,6 +646,10 @@ class Class:
         return self.db.getjournal(self.classname, nodeid)
 
     # Locating nodes:
+    def hasnode(self, nodeid):
+        '''Determine if the given nodeid actually exists
+        '''
+        return self.db.hasnode(self.classname, nodeid)
 
     def setkey(self, propname):
         """Select a String property of this class to be the key property.
@@ -1066,6 +1083,9 @@ def Choice(name, *options):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.55  2002/02/15 07:27:12  richard
+# Oops, precedences around the way w0rng.
+#
 # Revision 1.54  2002/02/15 07:08:44  richard
 #  . Alternate email addresses are now available for users. See the MIGRATION
 #    file for info on how to activate the feature.