Code

#614188 ] Exception in mailgw.py
[roundup.git] / roundup / admin.py
index fd9d9fd8d7a2f3b178f5201d1f007ab7aa0b7157..ff961b77eb63c50084f049ff32a7d8835e20ba46 100644 (file)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: admin.py,v 1.26 2002-09-10 03:01:18 richard Exp $
+# $Id: admin.py,v 1.32 2002-09-24 01:36:04 richard Exp $
 
 import sys, os, getpass, getopt, re, UserDict, shlex, shutil
 try:
@@ -61,7 +61,7 @@ class AdminTool:
         for k in AdminTool.__dict__.keys():
             if k[:5] == 'help_':
                 self.help[k[5:]] = getattr(self, k)
-        self.instance_home = ''
+        self.tracker_home = ''
         self.db = None
 
     def get_class(self, classname):
@@ -81,23 +81,28 @@ class AdminTool:
                 key, value = arg.split('=')
             except ValueError:
                 raise UsageError, _('argument "%(arg)s" not propname=value')%locals()
-            props[key] = value
+            if value:
+                props[key] = value
+            else:
+                props[key] = None
         return props
 
     def usage(self, message=''):
         if message:
             message = _('Problem: %(message)s)\n\n')%locals()
-        print _('''%(message)sUsage: roundup-admin [-i instance home] [-u login] [-c] <command> <arguments>
+        print _('''%(message)sUsage: roundup-admin [options] <command> <arguments>
+
+Options:
+ -i instance home  -- specify the issue tracker "home directory" to administer
+ -u                -- the user[:password] to use for commands
+ -c                -- when outputting lists of data, just comma-separate them
 
 Help:
  roundup-admin -h
  roundup-admin help                       -- this help
  roundup-admin help <command>             -- command-specific help
  roundup-admin help all                   -- all available help
-Options:
- -i instance home  -- specify the issue tracker "home directory" to administer
- -u                -- the user[:password] to use for commands
- -c                -- when outputting lists of data, just comma-separate them''')%locals()
+''')%locals()
         self.help_commands()
 
     def help_commands(self):
@@ -136,12 +141,12 @@ Options:
 
     def help_all(self):
         print _('''
-All commands (except help) require an instance specifier. This is just the path
-to the roundup instance you're working with. A roundup instance is where 
+All commands (except help) require a tracker specifier. This is just the path
+to the roundup tracker you're working with. A roundup tracker is where 
 roundup keeps the database and configuration file that defines an issue
 tracker. It may be thought of as the issue tracker's "home directory". It may
 be specified in the environment variable TRACKER_HOME or on the command
-line as "-i instance".
+line as "-i tracker".
 
 A designator is a classname and a nodeid concatenated, eg. bug1, user10, ...
 
@@ -249,27 +254,27 @@ Command help:
         backends = roundup.backends.__all__
         print _('Back ends:'), ', '.join(backends)
 
-    def do_install(self, instance_home, args):
+    def do_install(self, tracker_home, args):
         '''Usage: install [template [backend [admin password]]]
-        Install a new Roundup instance.
+        Install a new Roundup tracker.
 
-        The command will prompt for the instance home directory (if not supplied
+        The command will prompt for the tracker home directory (if not supplied
         through TRACKER_HOME or the -i option). The template, backend and admin
         password may be specified on the command-line as arguments, in that
         order.
 
         The initialise command must be called after this command in order
-        to initialise the instance's database. You may edit the instance's
+        to initialise the tracker's database. You may edit the tracker's
         initial database contents before running that command by editing
-        the instance's dbinit.py module init() function.
+        the tracker's dbinit.py module init() function.
 
         See also initopts help.
         '''
         if len(args) < 1:
             raise UsageError, _('Not enough arguments supplied')
 
-        # make sure the instance home can be created
-        parent = os.path.split(instance_home)[0]
+        # make sure the tracker home can be created
+        parent = os.path.split(tracker_home)[0]
         if not os.path.exists(parent):
             raise UsageError, _('Instance home parent directory "%(parent)s"'
                 ' does not exist')%locals()
@@ -295,12 +300,13 @@ Command help:
             backend = raw_input(_('Select backend [anydbm]: ')).strip()
             if not backend:
                 backend = 'anydbm'
+        # XXX perform a unit test based on the user's selections
 
         # install!
-        init.install(instance_home, template, backend)
+        init.install(tracker_home, template, backend)
 
         print _('''
- You should now edit the instance configuration file:
+ You should now edit the tracker configuration file:
    %(config_file)s
  ... at a minimum, you must set MAILHOST, MAIL_DOMAIN and ADMIN_EMAIL.
 
@@ -309,19 +315,19 @@ Command help:
    %(database_config_file)s
  ... see the documentation on customizing for more information.
 ''')%{
-    'config_file': os.path.join(instance_home, 'config.py'),
-    'database_config_file': os.path.join(instance_home, 'dbinit.py')
+    'config_file': os.path.join(tracker_home, 'config.py'),
+    'database_config_file': os.path.join(tracker_home, 'dbinit.py')
 }
         return 0
 
 
-    def do_initialise(self, instance_home, args):
+    def do_initialise(self, tracker_home, args):
         '''Usage: initialise [adminpw]
-        Initialise a new Roundup instance.
+        Initialise a new Roundup tracker.
 
         The administrator details will be set at this step.
 
-        Execute the instance's initialisation function dbinit.init()
+        Execute the tracker's initialisation function dbinit.init()
         '''
         # password
         if len(args) > 1:
@@ -333,14 +339,14 @@ Command help:
                 adminpw = getpass.getpass(_('Admin Password: '))
                 confirm = getpass.getpass(_('       Confirm: '))
 
-        # make sure the instance home is installed
-        if not os.path.exists(instance_home):
+        # make sure the tracker home is installed
+        if not os.path.exists(tracker_home):
             raise UsageError, _('Instance home does not exist')%locals()
-        if not os.path.exists(os.path.join(instance_home, 'html')):
+        if not os.path.exists(os.path.join(tracker_home, 'html')):
             raise UsageError, _('Instance has not been installed')%locals()
 
         # is there already a database?
-        if os.path.exists(os.path.join(instance_home, 'db')):
+        if os.path.exists(os.path.join(tracker_home, 'db')):
             print _('WARNING: The database is already initialised!')
             print _('If you re-initialise it, you will lose all the data!')
             ok = raw_input(_('Erase it? Y/[N]: ')).strip()
@@ -348,10 +354,10 @@ Command help:
                 return 0
 
             # nuke it
-            shutil.rmtree(os.path.join(instance_home, 'db'))
+            shutil.rmtree(os.path.join(tracker_home, 'db'))
 
         # GO
-        init.initialise(instance_home, adminpw)
+        init.initialise(tracker_home, adminpw)
 
         return 0
 
@@ -392,35 +398,53 @@ Command help:
 
 
     def do_set(self, args):
-        '''Usage: set designator[,designator]* propname=value ...
-        Set the given property of one or more designator(s).
+        '''Usage: set [items] property=value property=value ...
+        Set the given properties of one or more items(s).
+
+        The items may be specified as a class or as a comma-separeted
+        list of item designators (ie "designator[,designator,...]").
 
-        Sets the property to the value for all designators given.
+        This command sets the properties to the values for all designators
+        given. If the value is missing (ie. "property=") then the property is
+        un-set.
         '''
         if len(args) < 2:
             raise UsageError, _('Not enough arguments supplied')
         from roundup import hyperdb
 
         designators = args[0].split(',')
+        if len(designators) == 1:
+            designator = designators[0]
+            try:
+                designator = hyperdb.splitDesignator(designator)
+                designators = [designator]
+            except hyperdb.DesignatorError:
+                cl = self.get_class(designator)
+                designators = [(designator, x) for x in cl.list()]
+        else:
+            try:
+                designators = [hyperdb.splitDesignator(x) for x in designators]
+            except hyperdb.DesignatorError, message:
+                raise UsageError, message
 
         # get the props from the args
         props = self.props_from_args(args[1:])
 
         # now do the set for all the nodes
-        for designator in designators:
-            # decode the node designator
-            try:
-                classname, nodeid = hyperdb.splitDesignator(designator)
-            except hyperdb.DesignatorError, message:
-                raise UsageError, message
-
-            # get the class
+        for classname, itemid in designators:
             cl = self.get_class(classname)
 
             properties = cl.getprops()
             for key, value in props.items():
                 proptype =  properties[key]
-                if isinstance(proptype, hyperdb.String):
+                if isinstance(proptype, hyperdb.Multilink):
+                    if value is None:
+                        props[key] = []
+                    else:
+                        props[key] = value.split(',')
+                elif value is None:
+                    continue
+                elif isinstance(proptype, hyperdb.String):
                     continue
                 elif isinstance(proptype, hyperdb.Password):
                     props[key] = password.Password(value)
@@ -436,8 +460,6 @@ Command help:
                         raise UsageError, '"%s": %s'%(value, message)
                 elif isinstance(proptype, hyperdb.Link):
                     props[key] = value
-                elif isinstance(proptype, hyperdb.Multilink):
-                    props[key] = value.split(',')
                 elif isinstance(proptype, hyperdb.Boolean):
                     props[key] = value.lower() in ('yes', 'true', 'on', '1')
                 elif isinstance(proptype, hyperdb.Number):
@@ -445,7 +467,7 @@ Command help:
 
             # try the set
             try:
-                apply(cl.set, (nodeid, ), props)
+                apply(cl.set, (itemid, ), props)
             except (TypeError, IndexError, ValueError), message:
                 raise UsageError, message
         return 0
@@ -809,11 +831,11 @@ Command help:
 
     def do_export(self, args):
         '''Usage: export [class[,class]] export_dir
-        Export the database to tab-separated-value files.
+        Export the database to colon-separated-value files.
 
         This action exports the current data from the database into
-        tab-separated-value files that are placed in the nominated destination
-        directory. The journals are not exported.
+        colon-separated-value files that are placed in the nominated
+        destination directory. The journals are not exported.
         '''
         # we need the CSV module
         if csv is None:
@@ -860,9 +882,9 @@ Command help:
         The imported nodes will have the same nodeid as defined in the
         import file, thus replacing any existing content.
 
-        XXX The new nodes are added to the existing database - if you want to
-        XXX create a new database using the imported data, then create a new
-        XXX database (or, tediously, retire all the old data.)
+        The new nodes are added to the existing database - if you want to
+        create a new database using the imported data, then create a new
+        database (or, tediously, retire all the old data.)
         '''
         if len(args) < 1:
             raise UsageError, _('Not enough arguments supplied')
@@ -909,8 +931,8 @@ Command help:
                 # do the import and figure the current highest nodeid
                 maxid = max(maxid, int(cl.import_list(propnames, l)))
 
-            print 'setting', classname, maxid
-            self.db.setid(classname, str(maxid))
+            print 'setting', classname, maxid+1
+            self.db.setid(classname, str(maxid+1))
         return 0
 
     def do_pack(self, args):
@@ -953,9 +975,9 @@ Date format is "YYYY-MM-DD" eg:
 
     def do_reindex(self, args):
         '''Usage: reindex
-        Re-generate an instance's search indexes.
+        Re-generate a tracker's search indexes.
 
-        This will re-generate the search indexes for an instance. This will
+        This will re-generate the search indexes for a tracker. This will
         typically happen automatically.
         '''
         self.db.indexer.force_reindex()
@@ -1030,35 +1052,35 @@ Date format is "YYYY-MM-DD" eg:
             return 1
         command, function = functions[0]
 
-        # make sure we have an instance_home
-        while not self.instance_home:
-            self.instance_home = raw_input(_('Enter instance home: ')).strip()
+        # make sure we have a tracker_home
+        while not self.tracker_home:
+            self.tracker_home = raw_input(_('Enter tracker home: ')).strip()
 
         # before we open the db, we may be doing an install or init
         if command == 'initialise':
             try:
-                return self.do_initialise(self.instance_home, args)
+                return self.do_initialise(self.tracker_home, args)
             except UsageError, message:
                 print _('Error: %(message)s')%locals()
                 return 1
         elif command == 'install':
             try:
-                return self.do_install(self.instance_home, args)
+                return self.do_install(self.tracker_home, args)
             except UsageError, message:
                 print _('Error: %(message)s')%locals()
                 return 1
 
-        # get the instance
+        # get the tracker
         try:
-            instance = roundup.instance.open(self.instance_home)
+            tracker = roundup.instance.open(self.tracker_home)
         except ValueError, message:
-            self.instance_home = ''
-            print _("Error: Couldn't open instance: %(message)s")%locals()
+            self.tracker_home = ''
+            print _("Error: Couldn't open tracker: %(message)s")%locals()
             return 1
 
         # only open the database once!
         if not self.db:
-            self.db = instance.open('admin')
+            self.db = tracker.open('admin')
 
         # do the command
         ret = 0
@@ -1112,7 +1134,7 @@ Date format is "YYYY-MM-DD" eg:
             return 1
 
         # handle command-line args
-        self.instance_home = os.environ.get('TRACKER_HOME', '')
+        self.tracker_home = os.environ.get('TRACKER_HOME', '')
         # TODO: reinstate the user/password stuff (-u arg too)
         name = password = ''
         if os.environ.has_key('ROUNDUP_LOGIN'):
@@ -1126,19 +1148,23 @@ Date format is "YYYY-MM-DD" eg:
                 self.usage()
                 return 0
             if opt == '-i':
-                self.instance_home = arg
+                self.tracker_home = arg
             if opt == '-c':
                 self.comma_sep = 1
 
         # if no command - go interactive
+        # wrap in a try/finally so we always close off the db
         ret = 0
-        if not args:
-            self.interactive()
-        else:
-            ret = self.run_command(args)
-            if self.db: self.db.commit()
-        return ret
-
+        try:
+            if not args:
+                self.interactive()
+            else:
+                ret = self.run_command(args)
+                if self.db: self.db.commit()
+            return ret
+        finally:
+            if self.db:
+                self.db.close()
 
 if __name__ == '__main__':
     tool = AdminTool()