X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=roundup%2Fadmin.py;h=88cbace2c8e6e3bb253a4d1ec896c38eadd70d07;hb=0ae2ea5bdf5ec4dc7abc72628924faf0141a265b;hp=51710810eafa2a7876bea19887604788e8505d10;hpb=a0161e1c5ebc4fef4a65b01934224e9e05bcbedd;p=roundup.git diff --git a/roundup/admin.py b/roundup/admin.py index 5171081..88cbace 100644 --- a/roundup/admin.py +++ b/roundup/admin.py @@ -16,7 +16,10 @@ # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. # -# $Id: admin.py,v 1.27 2002-09-10 07:07:16 richard Exp $ +# $Id: admin.py,v 1.35 2002-10-03 06:56:28 richard Exp $ + +'''Administration commands for maintaining Roundup trackers. +''' import sys, os, getpass, getopt, re, UserDict, shlex, shutil try: @@ -51,7 +54,17 @@ class UsageError(ValueError): pass class AdminTool: + ''' A collection of methods used in maintaining Roundup trackers. + + Typically these methods are accessed through the roundup-admin + script. The main() method provided on this class gives the main + loop for the roundup-admin script. + Actions are defined by do_*() methods, with help for the action + given in the method docstring. + + Additional help may be supplied by help_*() methods. + ''' def __init__(self): self.commands = CommandDict() for k in AdminTool.__dict__.keys(): @@ -73,18 +86,29 @@ class AdminTool: raise UsageError, _('no such class "%(classname)s"')%locals() def props_from_args(self, args): + ''' Produce a dictionary of prop: value from the args list. + + The args list is specified as ``prop=value prop=value ...``. + ''' props = {} for arg in args: if arg.find('=') == -1: - raise UsageError, _('argument "%(arg)s" not propname=value')%locals() + raise UsageError, _('argument "%(arg)s" not propname=value' + )%locals() try: key, value = arg.split('=') except ValueError: - raise UsageError, _('argument "%(arg)s" not propname=value')%locals() - props[key] = value + raise UsageError, _('argument "%(arg)s" not propname=value' + )%locals() + if value: + props[key] = value + else: + props[key] = None return props def usage(self, message=''): + ''' Display a simple usage message. + ''' if message: message = _('Problem: %(message)s)\n\n')%locals() print _('''%(message)sUsage: roundup-admin [options] @@ -103,6 +127,8 @@ Help: self.help_commands() def help_commands(self): + ''' List the commands available with their precis help. + ''' print _('Commands:'), commands = [''] for command in self.commands.values(): @@ -115,11 +141,13 @@ Help: print def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): - commands = self.commands.values() + ''' Produce an HTML command list. + ''' + commands = self.commands.values() def sortfun(a, b): return cmp(a.__name__, b.__name__) commands.sort(sortfun) - for command in commands: + for command in commands: h = command.__doc__.split('\n') name = command.__name__[3:] usage = h[0] @@ -297,6 +325,7 @@ 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(tracker_home, template, backend) @@ -304,7 +333,8 @@ Command help: print _(''' You should now edit the tracker configuration file: %(config_file)s - ... at a minimum, you must set MAILHOST, MAIL_DOMAIN and ADMIN_EMAIL. + ... at a minimum, you must set MAILHOST, TRACKER_WEB, MAIL_DOMAIN and + ADMIN_EMAIL. If you wish to modify the default schema, you should also edit the database initialisation file: @@ -393,39 +423,65 @@ Command help: return 0 - def do_set(self, args): - '''Usage: set designator[,designator]* propname=value ... - Set the given property of one or more designator(s). + def do_set(self, args, pwre = re.compile(r'{(\w+)}(.+)')): + '''Usage: set [items] property=value property=value ... + Set the given properties of one or more items(s). - Sets the property to the value for all designators given. + The items may be specified as a class or as a comma-separeted + list of item designators (ie "designator[,designator,...]"). + + 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) + m = pwre.match(value) + if m: + # password is being given to us encrypted + p = password.Password() + p.scheme = m.group(1) + p.password = m.group(2) + props[key] = p + else: + props[key] = password.Password(value) elif isinstance(proptype, hyperdb.Date): try: props[key] = date.Date(value) @@ -438,8 +494,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): @@ -447,8 +501,9 @@ Command help: # try the set try: - apply(cl.set, (nodeid, ), props) + apply(cl.set, (itemid, ), props) except (TypeError, IndexError, ValueError), message: + import traceback; traceback.print_exc() raise UsageError, message return 0 @@ -550,7 +605,7 @@ Command help: value = cl.get(nodeid, key) print _('%(key)s: %(value)s')%locals() - def do_create(self, args): + def do_create(self, args, pwre = re.compile(r'{(\w+)}(.+)')): '''Usage: create classname property=value ... Create a new entry of a given class. @@ -613,7 +668,15 @@ Command help: except ValueError, message: raise UsageError, _('"%(value)s": %(message)s')%locals() elif isinstance(proptype, hyperdb.Password): - props[propname] = password.Password(value) + m = pwre.match(value) + if m: + # password is being given to us encrypted + p = password.Password() + p.scheme = m.group(1) + p.password = m.group(2) + props[propname] = p + else: + props[propname] = password.Password(value) elif isinstance(proptype, hyperdb.Multilink): props[propname] = value.split(',') elif isinstance(proptype, hyperdb.Boolean): @@ -811,11 +874,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: @@ -862,9 +925,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') @@ -911,8 +974,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): @@ -1133,14 +1196,18 @@ Date format is "YYYY-MM-DD" eg: 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()