Code

oops
[roundup.git] / roundup / admin.py
index 34ec404e672527ce05817fbc137fdeceb4ad1b43..b213c14d70624d8cee19dcd3cfc038ae3694fa89 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: admin.py,v 1.4 2002-01-14 06:51:09 richard Exp $
+# $Id: admin.py,v 1.18 2002-07-18 11:17:30 gmcm Exp $
 
-import sys, os, getpass, getopt, re, UserDict, shlex
+import sys, os, getpass, getopt, re, UserDict, shlex, shutil
 try:
     import csv
 except ImportError:
     csv = None
 from roundup import date, hyperdb, roundupdb, init, password, token
+from roundup import __version__ as roundup_version
 import roundup.instance
 from roundup.i18n import _
 
@@ -71,7 +72,7 @@ class AdminTool:
         except KeyError:
             raise UsageError, _('no such class "%(classname)s"')%locals()
 
-    def props_from_args(self, args, klass=None):
+    def props_from_args(self, args):
         props = {}
         for arg in args:
             if arg.find('=') == -1:
@@ -209,7 +210,11 @@ Command help:
         initopts  -- init command options
         all       -- all available help
         '''
-        topic = args[0]
+        if len(args)>0:
+            topic = args[0]
+        else:
+            topic = 'help'
 
         # try help_ methods
         if self.help.has_key(topic):
@@ -245,19 +250,31 @@ Command help:
         print _('Back ends:'), ', '.join(backends)
 
 
-    def do_initialise(self, instance_home, args):
-        '''Usage: initialise [template [backend [admin password]]]
-        Initialise a new Roundup instance.
+    def do_install(self, instance_home, args):
+        '''Usage: install [template [backend [admin password]]]
+        Install a new Roundup instance.
 
         The command will prompt for the instance home directory (if not supplied
         through INSTANCE_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
+        initial database contents before running that command by editing
+        the instance'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]
+        if not os.path.exists(parent):
+            raise UsageError, _('Instance home parent directory "%(parent)s"'
+                ' does not exist')%locals()
+
         # select template
         import roundup.templates
         templates = roundup.templates.listTemplates()
@@ -269,6 +286,7 @@ Command help:
             if not template:
                 template = 'classic'
 
+        # select hyperdb backend
         import roundup.backends
         backends = roundup.backends.__all__
         backend = len(args) > 2 and args[2] or ''
@@ -278,15 +296,64 @@ Command help:
             backend = raw_input(_('Select backend [anydbm]: ')).strip()
             if not backend:
                 backend = 'anydbm'
-        if len(args) > 3:
-            adminpw = confirm = args[3]
+
+        # install!
+        init.install(instance_home, template, backend)
+
+        print _('''
+ You should now edit the instance configuration file:
+   %(instance_config_file)s
+ ... at a minimum, you must set MAILHOST, MAIL_DOMAIN and ADMIN_EMAIL.
+
+ If you wish to modify the default schema, you should also edit the database
+ initialisation file:
+   %(database_config_file)s
+ ... see the documentation on customizing for more information.
+''')%{
+    'instance_config_file': os.path.join(instance_home, 'instance_config.py'),
+    'database_config_file': os.path.join(instance_home, 'dbinit.py')
+}
+        return 0
+
+
+    def do_initialise(self, instance_home, args):
+        '''Usage: initialise [adminpw]
+        Initialise a new Roundup instance.
+
+        The administrator details will be set at this step.
+
+        Execute the instance's initialisation function dbinit.init()
+        '''
+        # password
+        if len(args) > 1:
+            adminpw = args[1]
         else:
             adminpw = ''
             confirm = 'x'
-        while adminpw != confirm:
-            adminpw = getpass.getpass(_('Admin Password: '))
-            confirm = getpass.getpass(_('       Confirm: '))
-        init.init(instance_home, template, backend, adminpw)
+            while adminpw != confirm:
+                adminpw = getpass.getpass(_('Admin Password: '))
+                confirm = getpass.getpass(_('       Confirm: '))
+
+        # make sure the instance home is installed
+        if not os.path.exists(instance_home):
+            raise UsageError, _('Instance home does not exist')%locals()
+        if not os.path.exists(os.path.join(instance_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')):
+            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()
+            if ok.lower() != 'y':
+                return 0
+
+            # nuke it
+            shutil.rmtree(os.path.join(instance_home, 'db'))
+
+        # GO
+        init.initialise(instance_home, adminpw)
+
         return 0
 
 
@@ -372,6 +439,10 @@ Command help:
                     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):
+                    props[key] = int(value)
 
             # try the set
             try:
@@ -420,9 +491,6 @@ Command help:
                 except TypeError:
                     raise UsageError, _('%(classname)s has no key property"')%{
                         'classname': link_class.classname}
-                except KeyError:
-                    raise UsageError, _('%(classname)s has no entry "%(propname)s"')%{
-                        'classname': link_class.classname, 'propname': propname}
 
         # now do the find 
         try:
@@ -525,7 +593,7 @@ Command help:
             props = self.props_from_args(args[1:])
 
         # convert types
-        for propname in props.keys():
+        for propname, value in props.items():
             # get the property
             try:
                 proptype = properties[propname]
@@ -547,6 +615,10 @@ Command help:
                 props[propname] = password.Password(value)
             elif isinstance(proptype, hyperdb.Multilink):
                 props[propname] = value.split(',')
+            elif isinstance(proptype, hyperdb.Boolean):
+                props[propname] = value.lower() in ('yes', 'true', 'on', '1')
+            elif isinstance(proptype, hyperdb.Number):
+                props[propname] = int(value)
 
         # check for the key property
         propname = cl.getkey()
@@ -832,6 +904,9 @@ Command help:
             while 1:
                 l = p.parse(line)
                 if l: break
+                line = f.readline()
+                if not line:
+                    raise ValueError, "Unexpected EOF during CSV parse"
 
             # make the new node's property map
             d = {}
@@ -857,6 +932,55 @@ Command help:
             apply(cl.create, (), d)
         return 0
 
+    def do_pack(self, args):
+        '''Usage: pack period | date
+
+Remove journal entries older than a period of time specified or
+before a certain date.
+
+A period is specified using the suffixes "y", "m", and "d". The
+suffix "w" (for "week") means 7 days.
+
+      "3y" means three years
+      "2y 1m" means two years and one month
+      "1m 25d" means one month and 25 days
+      "2w 3d" means two weeks and three days
+
+Date format is "YYYY-MM-DD" eg:
+    2001-01-01
+    
+        '''
+        if len(args) <> 1:
+            raise UsageError, _('Not enough arguments supplied')
+        
+        # are we dealing with a period or a date
+        value = args[0]
+        date_re = re.compile(r'''
+              (?P<date>\d\d\d\d-\d\d?-\d\d?)? # yyyy-mm-dd
+              (?P<period>(\d+y\s*)?(\d+m\s*)?(\d+d\s*)?)?
+              ''', re.VERBOSE)
+        m = date_re.match(value)
+        if not m:
+            raise ValueError, _('Invalid format')
+        m = m.groupdict()
+        if m['period']:
+            pack_before = date.Date(". - %s"%value)
+        elif m['date']:
+            pack_before = date.Date(value)
+        self.db.pack(pack_before)
+        return 0
+
+    def do_reindex(self, args):
+        '''Usage: reindex
+        Re-generate an instance's search indexes.
+
+        This will re-generate the search indexes for an instance. This will
+        typically happen automatically.
+        '''
+        self.db.indexer.force_reindex()
+        self.db.reindex()
+        return 0
+
     def run_command(self, args):
         '''Run a single command
         '''
@@ -895,16 +1019,26 @@ Command help:
         while not self.instance_home:
             self.instance_home = raw_input(_('Enter instance home: ')).strip()
 
-        # before we open the db, we may be doing an init
+        # before we open the db, we may be doing an install or init
         if command == 'initialise':
-            return self.do_initialise(self.instance_home, args)
+            try:
+                return self.do_initialise(self.instance_home, args)
+            except UsageError, message:
+                print _('Error: %(message)s')%locals()
+                return 1
+        elif command == 'install':
+            try:
+                return self.do_install(self.instance_home, args)
+            except UsageError, message:
+                print _('Error: %(message)s')%locals()
+                return 1
 
         # get the instance
         try:
             instance = roundup.instance.open(self.instance_home)
         except ValueError, message:
             self.instance_home = ''
-            print _("Couldn't open instance: %(message)s")%locals()
+            print _("Error: Couldn't open instance: %(message)s")%locals()
             return 1
 
         # only open the database once!
@@ -917,6 +1051,7 @@ Command help:
             ret = function(args[1:])
         except UsageError, message:
             print _('Error: %(message)s')%locals()
+            print
             print function.__doc__
             ret = 1
         except:
@@ -928,7 +1063,7 @@ Command help:
     def interactive(self):
         '''Run in an interactive mode
         '''
-        print _('Roundup {version} ready for input.')
+        print _('Roundup %s ready for input.'%roundup_version)
         print _('Type "help" for help.')
         try:
             import readline
@@ -963,6 +1098,7 @@ Command help:
 
         # handle command-line args
         self.instance_home = os.environ.get('ROUNDUP_INSTANCE', '')
+        # TODO: reinstate the user/password stuff (-u arg too)
         name = password = ''
         if os.environ.has_key('ROUNDUP_LOGIN'):
             l = os.environ['ROUNDUP_LOGIN'].split(':')
@@ -995,6 +1131,53 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.17  2002/07/14 06:05:50  richard
+#  . fixed the date module so that Date(". - 2d") works
+#
+# Revision 1.16  2002/07/09 04:19:09  richard
+# Added reindex command to roundup-admin.
+# Fixed reindex on first access.
+# Also fixed reindexing of entries that change.
+#
+# Revision 1.15  2002/06/17 23:14:44  richard
+# . #569415 ] {version}
+#
+# Revision 1.14  2002/06/11 06:41:50  richard
+# Removed prompt for admin email in initialisation.
+#
+# Revision 1.13  2002/05/30 23:58:14  richard
+# oops
+#
+# Revision 1.12  2002/05/26 09:04:42  richard
+# out by one in the init args
+#
+# Revision 1.11  2002/05/23 01:14:20  richard
+#  . split instance initialisation into two steps, allowing config changes
+#    before the database is initialised.
+#
+# Revision 1.10  2002/04/27 10:07:23  richard
+# minor fix to error message
+#
+# Revision 1.9  2002/03/12 22:51:47  richard
+#  . #527416 ] roundup-admin uses undefined value
+#  . #527503 ] unfriendly init blowup when parent dir
+#    (also handles UsageError correctly now in init)
+#
+# Revision 1.8  2002/02/27 03:28:21  richard
+# Ran it through pychecker, made fixes
+#
+# Revision 1.7  2002/02/20 05:04:32  richard
+# Wasn't handling the cvs parser feeding properly.
+#
+# Revision 1.6  2002/01/23 07:27:19  grubert
+#  . allow abbreviation of "help" in admin tool too.
+#
+# Revision 1.5  2002/01/21 16:33:19  rochecompaan
+# You can now use the roundup-admin tool to pack the database
+#
+# Revision 1.4  2002/01/14 06:51:09  richard
+#  . #503164 ] create and passwords
+#
 # Revision 1.3  2002/01/08 05:26:32  rochecompaan
 # Missing "self" in props_from_args
 #