comparison roundup/admin.py @ 4357:13b3155869e0

Beginnings of a big code cleanup / modernisation to make 2to3 happy
author Richard Jones <richard@users.sourceforge.net>
date Mon, 22 Feb 2010 05:26:57 +0000
parents e16a1131ba67
children a0be2bc223f5
comparison
equal deleted inserted replaced
4356:05a65559d873 4357:13b3155869e0
19 19
20 """Administration commands for maintaining Roundup trackers. 20 """Administration commands for maintaining Roundup trackers.
21 """ 21 """
22 __docformat__ = 'restructuredtext' 22 __docformat__ = 'restructuredtext'
23 23
24 import csv, getopt, getpass, os, re, shutil, sys, UserDict 24 import csv, getopt, getpass, os, re, shutil, sys, UserDict, operator
25 25
26 from roundup import date, hyperdb, roundupdb, init, password, token 26 from roundup import date, hyperdb, roundupdb, init, password, token
27 from roundup import __version__ as roundup_version 27 from roundup import __version__ as roundup_version
28 import roundup.instance 28 import roundup.instance
29 from roundup.configuration import CoreConfig 29 from roundup.configuration import CoreConfig
35 35
36 Original code submitted by Engelbert Gruber. 36 Original code submitted by Engelbert Gruber.
37 """ 37 """
38 _marker = [] 38 _marker = []
39 def get(self, key, default=_marker): 39 def get(self, key, default=_marker):
40 if self.data.has_key(key): 40 if key in self.data:
41 return [(key, self.data[key])] 41 return [(key, self.data[key])]
42 keylist = self.data.keys() 42 keylist = sorted(self.data)
43 keylist.sort()
44 l = [] 43 l = []
45 for ki in keylist: 44 for ki in keylist:
46 if ki.startswith(key): 45 if ki.startswith(key):
47 l.append((ki, self.data[ki])) 46 l.append((ki, self.data[ki]))
48 if not l and default is self._marker: 47 if not l and default is self._marker:
49 raise KeyError, key 48 raise KeyError(key)
50 return l 49 return l
51 50
52 class AdminTool: 51 class AdminTool:
53 """ A collection of methods used in maintaining Roundup trackers. 52 """ A collection of methods used in maintaining Roundup trackers.
54 53
61 60
62 Additional help may be supplied by help_*() methods. 61 Additional help may be supplied by help_*() methods.
63 """ 62 """
64 def __init__(self): 63 def __init__(self):
65 self.commands = CommandDict() 64 self.commands = CommandDict()
66 for k in AdminTool.__dict__.keys(): 65 for k in AdminTool.__dict__:
67 if k[:3] == 'do_': 66 if k[:3] == 'do_':
68 self.commands[k[3:]] = getattr(self, k) 67 self.commands[k[3:]] = getattr(self, k)
69 self.help = {} 68 self.help = {}
70 for k in AdminTool.__dict__.keys(): 69 for k in AdminTool.__dict__:
71 if k[:5] == 'help_': 70 if k[:5] == 'help_':
72 self.help[k[5:]] = getattr(self, k) 71 self.help[k[5:]] = getattr(self, k)
73 self.tracker_home = '' 72 self.tracker_home = ''
74 self.db = None 73 self.db = None
75 self.db_uncommitted = False 74 self.db_uncommitted = False
78 """Get the class - raise an exception if it doesn't exist. 77 """Get the class - raise an exception if it doesn't exist.
79 """ 78 """
80 try: 79 try:
81 return self.db.getclass(classname) 80 return self.db.getclass(classname)
82 except KeyError: 81 except KeyError:
83 raise UsageError, _('no such class "%(classname)s"')%locals() 82 raise UsageError(_('no such class "%(classname)s"')%locals())
84 83
85 def props_from_args(self, args): 84 def props_from_args(self, args):
86 """ Produce a dictionary of prop: value from the args list. 85 """ Produce a dictionary of prop: value from the args list.
87 86
88 The args list is specified as ``prop=value prop=value ...``. 87 The args list is specified as ``prop=value prop=value ...``.
89 """ 88 """
90 props = {} 89 props = {}
91 for arg in args: 90 for arg in args:
92 if arg.find('=') == -1: 91 if arg.find('=') == -1:
93 raise UsageError, _('argument "%(arg)s" not propname=value' 92 raise UsageError(_('argument "%(arg)s" not propname=value'
94 )%locals() 93 )%locals())
95 l = arg.split('=') 94 l = arg.split('=')
96 if len(l) < 2: 95 if len(l) < 2:
97 raise UsageError, _('argument "%(arg)s" not propname=value' 96 raise UsageError(_('argument "%(arg)s" not propname=value'
98 )%locals() 97 )%locals())
99 key, value = l[0], '='.join(l[1:]) 98 key, value = l[0], '='.join(l[1:])
100 if value: 99 if value:
101 props[key] = value 100 props[key] = value
102 else: 101 else:
103 props[key] = None 102 props[key] = None
135 def help_commands(self): 134 def help_commands(self):
136 """List the commands available with their help summary. 135 """List the commands available with their help summary.
137 """ 136 """
138 print _('Commands:'), 137 print _('Commands:'),
139 commands = [''] 138 commands = ['']
140 for command in self.commands.values(): 139 for command in self.commands.itervalues():
141 h = _(command.__doc__).split('\n')[0] 140 h = _(command.__doc__).split('\n')[0]
142 commands.append(' '+h[7:]) 141 commands.append(' '+h[7:])
143 commands.sort() 142 commands.sort()
144 commands.append(_( 143 commands.append(_(
145 """Commands may be abbreviated as long as the abbreviation 144 """Commands may be abbreviated as long as the abbreviation
148 print 147 print
149 148
150 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): 149 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')):
151 """ Produce an HTML command list. 150 """ Produce an HTML command list.
152 """ 151 """
153 commands = self.commands.values() 152 commands = sorted(self.commands.itervalues(),
154 def sortfun(a, b): 153 operator.attrgetter('__name__'))
155 return cmp(a.__name__, b.__name__)
156 commands.sort(sortfun)
157 for command in commands: 154 for command in commands:
158 h = _(command.__doc__).split('\n') 155 h = _(command.__doc__).split('\n')
159 name = command.__name__[3:] 156 name = command.__name__[3:]
160 usage = h[0] 157 usage = h[0]
161 print """ 158 print """
253 else: 250 else:
254 topic = 'help' 251 topic = 'help'
255 252
256 253
257 # try help_ methods 254 # try help_ methods
258 if self.help.has_key(topic): 255 if topic in self.help:
259 self.help[topic]() 256 self.help[topic]()
260 return 0 257 return 0
261 258
262 # try command docstrings 259 # try command docstrings
263 try: 260 try:
338 335
339 return templates 336 return templates
340 337
341 def help_initopts(self): 338 def help_initopts(self):
342 templates = self.listTemplates() 339 templates = self.listTemplates()
343 print _('Templates:'), ', '.join(templates.keys()) 340 print _('Templates:'), ', '.join(templates)
344 import roundup.backends 341 import roundup.backends
345 backends = roundup.backends.list_backends() 342 backends = roundup.backends.list_backends()
346 print _('Back ends:'), ', '.join(backends) 343 print _('Back ends:'), ', '.join(backends)
347 344
348 def do_install(self, tracker_home, args): 345 def do_install(self, tracker_home, args):
367 the tracker's dbinit.py module init() function. 364 the tracker's dbinit.py module init() function.
368 365
369 See also initopts help. 366 See also initopts help.
370 """ 367 """
371 if len(args) < 1: 368 if len(args) < 1:
372 raise UsageError, _('Not enough arguments supplied') 369 raise UsageError(_('Not enough arguments supplied'))
373 370
374 # make sure the tracker home can be created 371 # make sure the tracker home can be created
375 tracker_home = os.path.abspath(tracker_home) 372 tracker_home = os.path.abspath(tracker_home)
376 parent = os.path.split(tracker_home)[0] 373 parent = os.path.split(tracker_home)[0]
377 if not os.path.exists(parent): 374 if not os.path.exists(parent):
378 raise UsageError, _('Instance home parent directory "%(parent)s"' 375 raise UsageError(_('Instance home parent directory "%(parent)s"'
379 ' does not exist')%locals() 376 ' does not exist')%locals())
380 377
381 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) 378 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE)
382 # check for both old- and new-style configs 379 # check for both old- and new-style configs
383 if filter(os.path.exists, [config_ini_file, 380 if list(filter(os.path.exists, [config_ini_file,
384 os.path.join(tracker_home, 'config.py')]): 381 os.path.join(tracker_home, 'config.py')])):
385 ok = raw_input(_( 382 ok = raw_input(_(
386 """WARNING: There appears to be a tracker in "%(tracker_home)s"! 383 """WARNING: There appears to be a tracker in "%(tracker_home)s"!
387 If you re-install it, you will lose all the data! 384 If you re-install it, you will lose all the data!
388 Erase it? Y/N: """) % locals()) 385 Erase it? Y/N: """) % locals())
389 if ok.strip().lower() != 'y': 386 if ok.strip().lower() != 'y':
393 shutil.rmtree(tracker_home) 390 shutil.rmtree(tracker_home)
394 391
395 # select template 392 # select template
396 templates = self.listTemplates() 393 templates = self.listTemplates()
397 template = len(args) > 1 and args[1] or '' 394 template = len(args) > 1 and args[1] or ''
398 if not templates.has_key(template): 395 if template not in templates:
399 print _('Templates:'), ', '.join(templates.keys()) 396 print _('Templates:'), ', '.join(templates)
400 while not templates.has_key(template): 397 while template not in templates:
401 template = raw_input(_('Select template [classic]: ')).strip() 398 template = raw_input(_('Select template [classic]: ')).strip()
402 if not template: 399 if not template:
403 template = 'classic' 400 template = 'classic'
404 401
405 # select hyperdb backend 402 # select hyperdb backend
437 # XXX config._get_unset_options() is marked as private 434 # XXX config._get_unset_options() is marked as private
438 # (leading underscore). make it public or don't care? 435 # (leading underscore). make it public or don't care?
439 need_set = CoreConfig(tracker_home)._get_unset_options() 436 need_set = CoreConfig(tracker_home)._get_unset_options()
440 if need_set: 437 if need_set:
441 print _(" ... at a minimum, you must set following options:") 438 print _(" ... at a minimum, you must set following options:")
442 for section, options in need_set.items(): 439 for section in need_set:
443 print " [%s]: %s" % (section, ", ".join(options)) 440 print " [%s]: %s" % (section, ", ".join(need_set[section]))
444 441
445 # note about schema modifications 442 # note about schema modifications
446 print _(""" 443 print _("""
447 If you wish to modify the database schema, 444 If you wish to modify the database schema,
448 you should also edit the schema file: 445 you should also edit the schema file:
464 ''"""Usage: genconfig <filename> 461 ''"""Usage: genconfig <filename>
465 Generate a new tracker config file (ini style) with default values 462 Generate a new tracker config file (ini style) with default values
466 in <filename>. 463 in <filename>.
467 """ 464 """
468 if len(args) < 1: 465 if len(args) < 1:
469 raise UsageError, _('Not enough arguments supplied') 466 raise UsageError(_('Not enough arguments supplied'))
470 config = CoreConfig() 467 config = CoreConfig()
471 config.save(args[0]) 468 config.save(args[0])
472 469
473 def do_initialise(self, tracker_home, args): 470 def do_initialise(self, tracker_home, args):
474 ''"""Usage: initialise [adminpw] 471 ''"""Usage: initialise [adminpw]
488 adminpw = getpass.getpass(_('Admin Password: ')) 485 adminpw = getpass.getpass(_('Admin Password: '))
489 confirm = getpass.getpass(_(' Confirm: ')) 486 confirm = getpass.getpass(_(' Confirm: '))
490 487
491 # make sure the tracker home is installed 488 # make sure the tracker home is installed
492 if not os.path.exists(tracker_home): 489 if not os.path.exists(tracker_home):
493 raise UsageError, _('Instance home does not exist')%locals() 490 raise UsageError(_('Instance home does not exist')%locals())
494 try: 491 try:
495 tracker = roundup.instance.open(tracker_home) 492 tracker = roundup.instance.open(tracker_home)
496 except roundup.instance.TrackerError: 493 except roundup.instance.TrackerError:
497 raise UsageError, _('Instance has not been installed')%locals() 494 raise UsageError(_('Instance has not been installed')%locals())
498 495
499 # is there already a database? 496 # is there already a database?
500 if tracker.exists(): 497 if tracker.exists():
501 ok = raw_input(_( 498 ok = raw_input(_(
502 """WARNING: The database is already initialised! 499 """WARNING: The database is already initialised!
528 525
529 Retrieves the property value of the nodes specified 526 Retrieves the property value of the nodes specified
530 by the designators. 527 by the designators.
531 """ 528 """
532 if len(args) < 2: 529 if len(args) < 2:
533 raise UsageError, _('Not enough arguments supplied') 530 raise UsageError(_('Not enough arguments supplied'))
534 propname = args[0] 531 propname = args[0]
535 designators = args[1].split(',') 532 designators = args[1].split(',')
536 l = [] 533 l = []
537 for designator in designators: 534 for designator in designators:
538 # decode the node designator 535 # decode the node designator
539 try: 536 try:
540 classname, nodeid = hyperdb.splitDesignator(designator) 537 classname, nodeid = hyperdb.splitDesignator(designator)
541 except hyperdb.DesignatorError, message: 538 except hyperdb.DesignatorError, message:
542 raise UsageError, message 539 raise UsageError(message)
543 540
544 # get the class 541 # get the class
545 cl = self.get_class(classname) 542 cl = self.get_class(classname)
546 try: 543 try:
547 id=[] 544 id=[]
561 # print 558 # print
562 properties = cl.getprops() 559 properties = cl.getprops()
563 property = properties[propname] 560 property = properties[propname]
564 if not (isinstance(property, hyperdb.Multilink) or 561 if not (isinstance(property, hyperdb.Multilink) or
565 isinstance(property, hyperdb.Link)): 562 isinstance(property, hyperdb.Link)):
566 raise UsageError, _('property %s is not of type Multilink or Link so -d flag does not apply.')%propname 563 raise UsageError(_('property %s is not of type'
564 ' Multilink or Link so -d flag does not '
565 'apply.')%propname)
567 propclassname = self.db.getclass(property.classname).classname 566 propclassname = self.db.getclass(property.classname).classname
568 id = cl.get(nodeid, propname) 567 id = cl.get(nodeid, propname)
569 for i in id: 568 for i in id:
570 l.append(propclassname + i) 569 l.append(propclassname + i)
571 else: 570 else:
576 if self.print_designator: 575 if self.print_designator:
577 properties = cl.getprops() 576 properties = cl.getprops()
578 property = properties[propname] 577 property = properties[propname]
579 if not (isinstance(property, hyperdb.Multilink) or 578 if not (isinstance(property, hyperdb.Multilink) or
580 isinstance(property, hyperdb.Link)): 579 isinstance(property, hyperdb.Link)):
581 raise UsageError, _('property %s is not of type Multilink or Link so -d flag does not apply.')%propname 580 raise UsageError(_('property %s is not of type'
581 ' Multilink or Link so -d flag does not '
582 'apply.')%propname)
582 propclassname = self.db.getclass(property.classname).classname 583 propclassname = self.db.getclass(property.classname).classname
583 id = cl.get(nodeid, propname) 584 id = cl.get(nodeid, propname)
584 for i in id: 585 for i in id:
585 print propclassname + i 586 print propclassname + i
586 else: 587 else:
587 print cl.get(nodeid, propname) 588 print cl.get(nodeid, propname)
588 except IndexError: 589 except IndexError:
589 raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals() 590 raise UsageError(_('no such %(classname)s node '
591 '"%(nodeid)s"')%locals())
590 except KeyError: 592 except KeyError:
591 raise UsageError, _('no such %(classname)s property ' 593 raise UsageError(_('no such %(classname)s property '
592 '"%(propname)s"')%locals() 594 '"%(propname)s"')%locals())
593 if self.separator: 595 if self.separator:
594 print self.separator.join(l) 596 print self.separator.join(l)
595 597
596 return 0 598 return 0
597 599
610 given. If the value is missing (ie. "property=") then the property 612 given. If the value is missing (ie. "property=") then the property
611 is un-set. If the property is a multilink, you specify the linked 613 is un-set. If the property is a multilink, you specify the linked
612 ids for the multilink as comma-separated numbers (ie "1,2,3"). 614 ids for the multilink as comma-separated numbers (ie "1,2,3").
613 """ 615 """
614 if len(args) < 2: 616 if len(args) < 2:
615 raise UsageError, _('Not enough arguments supplied') 617 raise UsageError(_('Not enough arguments supplied'))
616 from roundup import hyperdb 618 from roundup import hyperdb
617 619
618 designators = args[0].split(',') 620 designators = args[0].split(',')
619 if len(designators) == 1: 621 if len(designators) == 1:
620 designator = designators[0] 622 designator = designators[0]
626 designators = [(designator, x) for x in cl.list()] 628 designators = [(designator, x) for x in cl.list()]
627 else: 629 else:
628 try: 630 try:
629 designators = [hyperdb.splitDesignator(x) for x in designators] 631 designators = [hyperdb.splitDesignator(x) for x in designators]
630 except hyperdb.DesignatorError, message: 632 except hyperdb.DesignatorError, message:
631 raise UsageError, message 633 raise UsageError(message)
632 634
633 # get the props from the args 635 # get the props from the args
634 props = self.props_from_args(args[1:]) 636 props = self.props_from_args(args[1:])
635 637
636 # now do the set for all the nodes 638 # now do the set for all the nodes
641 for key, value in props.items(): 643 for key, value in props.items():
642 try: 644 try:
643 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, 645 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid,
644 key, value) 646 key, value)
645 except hyperdb.HyperdbValueError, message: 647 except hyperdb.HyperdbValueError, message:
646 raise UsageError, message 648 raise UsageError(message)
647 649
648 # try the set 650 # try the set
649 try: 651 try:
650 apply(cl.set, (itemid, ), props) 652 cl.set(itemid, **props)
651 except (TypeError, IndexError, ValueError), message: 653 except (TypeError, IndexError, ValueError), message:
652 import traceback; traceback.print_exc() 654 import traceback; traceback.print_exc()
653 raise UsageError, message 655 raise UsageError(message)
654 self.db_uncommitted = True 656 self.db_uncommitted = True
655 return 0 657 return 0
656 658
657 def do_find(self, args): 659 def do_find(self, args):
658 ''"""Usage: find classname propname=value ... 660 ''"""Usage: find classname propname=value ...
661 Find the nodes of the given class with a given link property value. 663 Find the nodes of the given class with a given link property value.
662 The value may be either the nodeid of the linked node, or its key 664 The value may be either the nodeid of the linked node, or its key
663 value. 665 value.
664 """ 666 """
665 if len(args) < 1: 667 if len(args) < 1:
666 raise UsageError, _('Not enough arguments supplied') 668 raise UsageError(_('Not enough arguments supplied'))
667 classname = args[0] 669 classname = args[0]
668 # get the class 670 # get the class
669 cl = self.get_class(classname) 671 cl = self.get_class(classname)
670 672
671 # handle the propname=value argument 673 # handle the propname=value argument
672 props = self.props_from_args(args[1:]) 674 props = self.props_from_args(args[1:])
673 675
674 # convert the user-input value to a value used for find() 676 # convert the user-input value to a value used for find()
675 for propname, value in props.items(): 677 for propname, value in props.iteritems():
676 if ',' in value: 678 if ',' in value:
677 values = value.split(',') 679 values = value.split(',')
678 else: 680 else:
679 values = [value] 681 values = [value]
680 d = props[propname] = {} 682 d = props[propname] = {}
690 try: 692 try:
691 id = [] 693 id = []
692 designator = [] 694 designator = []
693 if self.separator: 695 if self.separator:
694 if self.print_designator: 696 if self.print_designator:
695 id=apply(cl.find, (), props) 697 id = cl.find(**props)
696 for i in id: 698 for i in id:
697 designator.append(classname + i) 699 designator.append(classname + i)
698 print self.separator.join(designator) 700 print self.separator.join(designator)
699 else: 701 else:
700 print self.separator.join(apply(cl.find, (), props)) 702 print self.separator.join(cl.find(**props))
701 703
702 else: 704 else:
703 if self.print_designator: 705 if self.print_designator:
704 id=apply(cl.find, (), props) 706 id = cl.find(**props)
705 for i in id: 707 for i in id:
706 designator.append(classname + i) 708 designator.append(classname + i)
707 print designator 709 print designator
708 else: 710 else:
709 print apply(cl.find, (), props) 711 print cl.find(**props)
710 except KeyError: 712 except KeyError:
711 raise UsageError, _('%(classname)s has no property ' 713 raise UsageError(_('%(classname)s has no property '
712 '"%(propname)s"')%locals() 714 '"%(propname)s"')%locals())
713 except (ValueError, TypeError), message: 715 except (ValueError, TypeError), message:
714 raise UsageError, message 716 raise UsageError(message)
715 return 0 717 return 0
716 718
717 def do_specification(self, args): 719 def do_specification(self, args):
718 ''"""Usage: specification classname 720 ''"""Usage: specification classname
719 Show the properties for a classname. 721 Show the properties for a classname.
720 722
721 This lists the properties for a given class. 723 This lists the properties for a given class.
722 """ 724 """
723 if len(args) < 1: 725 if len(args) < 1:
724 raise UsageError, _('Not enough arguments supplied') 726 raise UsageError(_('Not enough arguments supplied'))
725 classname = args[0] 727 classname = args[0]
726 # get the class 728 # get the class
727 cl = self.get_class(classname) 729 cl = self.get_class(classname)
728 730
729 # get the key property 731 # get the key property
730 keyprop = cl.getkey() 732 keyprop = cl.getkey()
731 for key, value in cl.properties.items(): 733 for key in cl.properties:
734 value = cl.properties[key]
732 if keyprop == key: 735 if keyprop == key:
733 print _('%(key)s: %(value)s (key property)')%locals() 736 print _('%(key)s: %(value)s (key property)')%locals()
734 else: 737 else:
735 print _('%(key)s: %(value)s')%locals() 738 print _('%(key)s: %(value)s')%locals()
736 739
743 746
744 This lists the properties and their associated values for the given 747 This lists the properties and their associated values for the given
745 node. 748 node.
746 """ 749 """
747 if len(args) < 1: 750 if len(args) < 1:
748 raise UsageError, _('Not enough arguments supplied') 751 raise UsageError(_('Not enough arguments supplied'))
749 752
750 # decode the node designator 753 # decode the node designator
751 for designator in args[0].split(','): 754 for designator in args[0].split(','):
752 try: 755 try:
753 classname, nodeid = hyperdb.splitDesignator(designator) 756 classname, nodeid = hyperdb.splitDesignator(designator)
754 except hyperdb.DesignatorError, message: 757 except hyperdb.DesignatorError, message:
755 raise UsageError, message 758 raise UsageError(message)
756 759
757 # get the class 760 # get the class
758 cl = self.get_class(classname) 761 cl = self.get_class(classname)
759 762
760 # display the values 763 # display the values
761 keys = cl.properties.keys() 764 keys = sorted(cl.properties)
762 keys.sort()
763 for key in keys: 765 for key in keys:
764 value = cl.get(nodeid, key) 766 value = cl.get(nodeid, key)
765 print _('%(key)s: %(value)s')%locals() 767 print _('%(key)s: %(value)s')%locals()
766 768
767 def do_create(self, args): 769 def do_create(self, args):
771 This creates a new entry of the given class using the property 773 This creates a new entry of the given class using the property
772 name=value arguments provided on the command line after the "create" 774 name=value arguments provided on the command line after the "create"
773 command. 775 command.
774 """ 776 """
775 if len(args) < 1: 777 if len(args) < 1:
776 raise UsageError, _('Not enough arguments supplied') 778 raise UsageError(_('Not enough arguments supplied'))
777 from roundup import hyperdb 779 from roundup import hyperdb
778 780
779 classname = args[0] 781 classname = args[0]
780 782
781 # get the class 783 # get the class
784 # now do a create 786 # now do a create
785 props = {} 787 props = {}
786 properties = cl.getprops(protected = 0) 788 properties = cl.getprops(protected = 0)
787 if len(args) == 1: 789 if len(args) == 1:
788 # ask for the properties 790 # ask for the properties
789 for key, value in properties.items(): 791 for key in properties:
790 if key == 'id': continue 792 if key == 'id': continue
793 value = properties[key]
791 name = value.__class__.__name__ 794 name = value.__class__.__name__
792 if isinstance(value , hyperdb.Password): 795 if isinstance(value , hyperdb.Password):
793 again = None 796 again = None
794 while value != again: 797 while value != again:
795 value = getpass.getpass(_('%(propname)s (Password): ')%{ 798 value = getpass.getpass(_('%(propname)s (Password): ')%{
806 props[key] = value 809 props[key] = value
807 else: 810 else:
808 props = self.props_from_args(args[1:]) 811 props = self.props_from_args(args[1:])
809 812
810 # convert types 813 # convert types
811 for propname, value in props.items(): 814 for propname in props:
812 try: 815 try:
813 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None, 816 props[propname] = hyperdb.rawToHyperdb(self.db, cl, None,
814 propname, value) 817 propname, props[propname])
815 except hyperdb.HyperdbValueError, message: 818 except hyperdb.HyperdbValueError, message:
816 raise UsageError, message 819 raise UsageError(message)
817 820
818 # check for the key property 821 # check for the key property
819 propname = cl.getkey() 822 propname = cl.getkey()
820 if propname and not props.has_key(propname): 823 if propname and propname not in props:
821 raise UsageError, _('you must provide the "%(propname)s" ' 824 raise UsageError(_('you must provide the "%(propname)s" '
822 'property.')%locals() 825 'property.')%locals())
823 826
824 # do the actual create 827 # do the actual create
825 try: 828 try:
826 print apply(cl.create, (), props) 829 print cl.create(**props)
827 except (TypeError, IndexError, ValueError), message: 830 except (TypeError, IndexError, ValueError), message:
828 raise UsageError, message 831 raise UsageError(message)
829 self.db_uncommitted = True 832 self.db_uncommitted = True
830 return 0 833 return 0
831 834
832 def do_list(self, args): 835 def do_list(self, args):
833 ''"""Usage: list classname [property] 836 ''"""Usage: list classname [property]
841 With -c, -S or -s print a list of item id's if no property 844 With -c, -S or -s print a list of item id's if no property
842 specified. If property specified, print list of that property 845 specified. If property specified, print list of that property
843 for every class instance. 846 for every class instance.
844 """ 847 """
845 if len(args) > 2: 848 if len(args) > 2:
846 raise UsageError, _('Too many arguments supplied') 849 raise UsageError(_('Too many arguments supplied'))
847 if len(args) < 1: 850 if len(args) < 1:
848 raise UsageError, _('Not enough arguments supplied') 851 raise UsageError(_('Not enough arguments supplied'))
849 classname = args[0] 852 classname = args[0]
850 853
851 # get the class 854 # get the class
852 cl = self.get_class(classname) 855 cl = self.get_class(classname)
853 856
863 proplist=[] 866 proplist=[]
864 for nodeid in cl.list(): 867 for nodeid in cl.list():
865 try: 868 try:
866 proplist.append(cl.get(nodeid, propname)) 869 proplist.append(cl.get(nodeid, propname))
867 except KeyError: 870 except KeyError:
868 raise UsageError, _('%(classname)s has no property ' 871 raise UsageError(_('%(classname)s has no property '
869 '"%(propname)s"')%locals() 872 '"%(propname)s"')%locals())
870 print self.separator.join(proplist) 873 print self.separator.join(proplist)
871 else: 874 else:
872 # create a list of index id's since user didn't specify 875 # create a list of index id's since user didn't specify
873 # otherwise 876 # otherwise
874 print self.separator.join(cl.list()) 877 print self.separator.join(cl.list())
875 else: 878 else:
876 for nodeid in cl.list(): 879 for nodeid in cl.list():
877 try: 880 try:
878 value = cl.get(nodeid, propname) 881 value = cl.get(nodeid, propname)
879 except KeyError: 882 except KeyError:
880 raise UsageError, _('%(classname)s has no property ' 883 raise UsageError(_('%(classname)s has no property '
881 '"%(propname)s"')%locals() 884 '"%(propname)s"')%locals())
882 print _('%(nodeid)4s: %(value)s')%locals() 885 print _('%(nodeid)4s: %(value)s')%locals()
883 return 0 886 return 0
884 887
885 def do_table(self, args): 888 def do_table(self, args):
886 ''"""Usage: table classname [property[,property]*] 889 ''"""Usage: table classname [property[,property]*]
910 4 feat 913 4 feat
911 914
912 will result in a the 4 character wide "Name" column. 915 will result in a the 4 character wide "Name" column.
913 """ 916 """
914 if len(args) < 1: 917 if len(args) < 1:
915 raise UsageError, _('Not enough arguments supplied') 918 raise UsageError(_('Not enough arguments supplied'))
916 classname = args[0] 919 classname = args[0]
917 920
918 # get the class 921 # get the class
919 cl = self.get_class(classname) 922 cl = self.get_class(classname)
920 923
925 for spec in prop_names: 928 for spec in prop_names:
926 if ':' in spec: 929 if ':' in spec:
927 try: 930 try:
928 propname, width = spec.split(':') 931 propname, width = spec.split(':')
929 except (ValueError, TypeError): 932 except (ValueError, TypeError):
930 raise UsageError, _('"%(spec)s" not name:width')%locals() 933 raise UsageError(_('"%(spec)s" not '
934 'name:width')%locals())
931 else: 935 else:
932 propname = spec 936 propname = spec
933 if not all_props.has_key(propname): 937 if propname not in all_props:
934 raise UsageError, _('%(classname)s has no property ' 938 raise UsageError(_('%(classname)s has no property '
935 '"%(propname)s"')%locals() 939 '"%(propname)s"')%locals())
936 else: 940 else:
937 prop_names = cl.getprops().keys() 941 prop_names = cl.getprops()
938 942
939 # now figure column widths 943 # now figure column widths
940 props = [] 944 props = []
941 for spec in prop_names: 945 for spec in prop_names:
942 if ':' in spec: 946 if ':' in spec:
984 eg. bug1, user10, ... 988 eg. bug1, user10, ...
985 989
986 Lists the journal entries for the node identified by the designator. 990 Lists the journal entries for the node identified by the designator.
987 """ 991 """
988 if len(args) < 1: 992 if len(args) < 1:
989 raise UsageError, _('Not enough arguments supplied') 993 raise UsageError(_('Not enough arguments supplied'))
990 try: 994 try:
991 classname, nodeid = hyperdb.splitDesignator(args[0]) 995 classname, nodeid = hyperdb.splitDesignator(args[0])
992 except hyperdb.DesignatorError, message: 996 except hyperdb.DesignatorError, message:
993 raise UsageError, message 997 raise UsageError(message)
994 998
995 try: 999 try:
996 print self.db.getclass(classname).history(nodeid) 1000 print self.db.getclass(classname).history(nodeid)
997 except KeyError: 1001 except KeyError:
998 raise UsageError, _('no such class "%(classname)s"')%locals() 1002 raise UsageError(_('no such class "%(classname)s"')%locals())
999 except IndexError: 1003 except IndexError:
1000 raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals() 1004 raise UsageError(_('no such %(classname)s node '
1005 '"%(nodeid)s"')%locals())
1001 return 0 1006 return 0
1002 1007
1003 def do_commit(self, args): 1008 def do_commit(self, args):
1004 ''"""Usage: commit 1009 ''"""Usage: commit
1005 Commit changes made to the database during an interactive session. 1010 Commit changes made to the database during an interactive session.
1037 1042
1038 This action indicates that a particular node is not to be retrieved 1043 This action indicates that a particular node is not to be retrieved
1039 by the list or find commands, and its key value may be re-used. 1044 by the list or find commands, and its key value may be re-used.
1040 """ 1045 """
1041 if len(args) < 1: 1046 if len(args) < 1:
1042 raise UsageError, _('Not enough arguments supplied') 1047 raise UsageError(_('Not enough arguments supplied'))
1043 designators = args[0].split(',') 1048 designators = args[0].split(',')
1044 for designator in designators: 1049 for designator in designators:
1045 try: 1050 try:
1046 classname, nodeid = hyperdb.splitDesignator(designator) 1051 classname, nodeid = hyperdb.splitDesignator(designator)
1047 except hyperdb.DesignatorError, message: 1052 except hyperdb.DesignatorError, message:
1048 raise UsageError, message 1053 raise UsageError(message)
1049 try: 1054 try:
1050 self.db.getclass(classname).retire(nodeid) 1055 self.db.getclass(classname).retire(nodeid)
1051 except KeyError: 1056 except KeyError:
1052 raise UsageError, _('no such class "%(classname)s"')%locals() 1057 raise UsageError(_('no such class "%(classname)s"')%locals())
1053 except IndexError: 1058 except IndexError:
1054 raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals() 1059 raise UsageError(_('no such %(classname)s node '
1060 '"%(nodeid)s"')%locals())
1055 self.db_uncommitted = True 1061 self.db_uncommitted = True
1056 return 0 1062 return 0
1057 1063
1058 def do_restore(self, args): 1064 def do_restore(self, args):
1059 ''"""Usage: restore designator[,designator]* 1065 ''"""Usage: restore designator[,designator]*
1063 eg. bug1, user10, ... 1069 eg. bug1, user10, ...
1064 1070
1065 The given nodes will become available for users again. 1071 The given nodes will become available for users again.
1066 """ 1072 """
1067 if len(args) < 1: 1073 if len(args) < 1:
1068 raise UsageError, _('Not enough arguments supplied') 1074 raise UsageError(_('Not enough arguments supplied'))
1069 designators = args[0].split(',') 1075 designators = args[0].split(',')
1070 for designator in designators: 1076 for designator in designators:
1071 try: 1077 try:
1072 classname, nodeid = hyperdb.splitDesignator(designator) 1078 classname, nodeid = hyperdb.splitDesignator(designator)
1073 except hyperdb.DesignatorError, message: 1079 except hyperdb.DesignatorError, message:
1074 raise UsageError, message 1080 raise UsageError(message)
1075 try: 1081 try:
1076 self.db.getclass(classname).restore(nodeid) 1082 self.db.getclass(classname).restore(nodeid)
1077 except KeyError: 1083 except KeyError:
1078 raise UsageError, _('no such class "%(classname)s"')%locals() 1084 raise UsageError(_('no such class "%(classname)s"')%locals())
1079 except IndexError: 1085 except IndexError:
1080 raise UsageError, _('no such %(classname)s node "%(nodeid)s"')%locals() 1086 raise UsageError(_('no such %(classname)s node '
1087 '"%(nodeid)s"')%locals())
1081 self.db_uncommitted = True 1088 self.db_uncommitted = True
1082 return 0 1089 return 0
1083 1090
1084 def do_export(self, args, export_files=True): 1091 def do_export(self, args, export_files=True):
1085 ''"""Usage: export [[-]class[,class]] export_dir 1092 ''"""Usage: export [[-]class[,class]] export_dir
1094 colon-separated-value files that are placed in the nominated 1101 colon-separated-value files that are placed in the nominated
1095 destination directory. 1102 destination directory.
1096 """ 1103 """
1097 # grab the directory to export to 1104 # grab the directory to export to
1098 if len(args) < 1: 1105 if len(args) < 1:
1099 raise UsageError, _('Not enough arguments supplied') 1106 raise UsageError(_('Not enough arguments supplied'))
1100 1107
1101 dir = args[-1] 1108 dir = args[-1]
1102 1109
1103 # get the list of classes to export 1110 # get the list of classes to export
1104 if len(args) == 2: 1111 if len(args) == 2:
1105 if args[0].startswith('-'): 1112 if args[0].startswith('-'):
1106 classes = [ c for c in self.db.classes.keys() 1113 classes = [ c for c in self.db.classes
1107 if not c in args[0][1:].split(',') ] 1114 if not c in args[0][1:].split(',') ]
1108 else: 1115 else:
1109 classes = args[0].split(',') 1116 classes = args[0].split(',')
1110 else: 1117 else:
1111 classes = self.db.classes.keys() 1118 classes = self.db.classes
1112 1119
1113 class colon_separated(csv.excel): 1120 class colon_separated(csv.excel):
1114 delimiter = ':' 1121 delimiter = ':'
1115 1122
1116 # make sure target dir exists 1123 # make sure target dir exists
1164 jf = open(os.path.join(dir, classname+'-journals.csv'), 'wb') 1171 jf = open(os.path.join(dir, classname+'-journals.csv'), 'wb')
1165 if self.verbose: 1172 if self.verbose:
1166 sys.stdout.write("\nExporting Journal for %s\n" % classname) 1173 sys.stdout.write("\nExporting Journal for %s\n" % classname)
1167 sys.stdout.flush() 1174 sys.stdout.flush()
1168 journals = csv.writer(jf, colon_separated) 1175 journals = csv.writer(jf, colon_separated)
1169 map(journals.writerow, cl.export_journals()) 1176 for row in cl.export_journals():
1177 journals.writerow(row)
1170 jf.close() 1178 jf.close()
1171 if max_len > self.db.config.CSV_FIELD_SIZE: 1179 if max_len > self.db.config.CSV_FIELD_SIZE:
1172 print >> sys.stderr, \ 1180 print >> sys.stderr, \
1173 "Warning: config csv_field_size should be at least %s"%max_len 1181 "Warning: config csv_field_size should be at least %s"%max_len
1174 return 0 1182 return 0
1207 The new nodes are added to the existing database - if you want to 1215 The new nodes are added to the existing database - if you want to
1208 create a new database using the imported data, then create a new 1216 create a new database using the imported data, then create a new
1209 database (or, tediously, retire all the old data.) 1217 database (or, tediously, retire all the old data.)
1210 """ 1218 """
1211 if len(args) < 1: 1219 if len(args) < 1:
1212 raise UsageError, _('Not enough arguments supplied') 1220 raise UsageError(_('Not enough arguments supplied'))
1213 from roundup import hyperdb 1221 from roundup import hyperdb
1214 1222
1215 if hasattr (csv, 'field_size_limit'): 1223 if hasattr (csv, 'field_size_limit'):
1216 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) 1224 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE)
1217 1225
1248 # do the import and figure the current highest nodeid 1256 # do the import and figure the current highest nodeid
1249 nodeid = cl.import_list(file_props, r) 1257 nodeid = cl.import_list(file_props, r)
1250 if hasattr(cl, 'import_files'): 1258 if hasattr(cl, 'import_files'):
1251 cl.import_files(dir, nodeid) 1259 cl.import_files(dir, nodeid)
1252 maxid = max(maxid, int(nodeid)) 1260 maxid = max(maxid, int(nodeid))
1261
1262 # (print to sys.stdout here to allow tests to squash it .. ugh)
1253 print >> sys.stdout 1263 print >> sys.stdout
1264
1254 f.close() 1265 f.close()
1255 1266
1256 # import the journals 1267 # import the journals
1257 f = open(os.path.join(args[0], classname + '-journals.csv'), 'r') 1268 f = open(os.path.join(args[0], classname + '-journals.csv'), 'r')
1258 reader = csv.reader(f, colon_separated) 1269 reader = csv.reader(f, colon_separated)
1259 cl.import_journals(reader) 1270 cl.import_journals(reader)
1260 f.close() 1271 f.close()
1261 1272
1273 # (print to sys.stdout here to allow tests to squash it .. ugh)
1274 print >> sys.stdout, 'setting', classname, maxid+1
1275
1262 # set the id counter 1276 # set the id counter
1263 print >> sys.stdout, 'setting', classname, maxid+1
1264 self.db.setid(classname, str(maxid+1)) 1277 self.db.setid(classname, str(maxid+1))
1265 1278
1266 self.db_uncommitted = True 1279 self.db_uncommitted = True
1267 return 0 1280 return 0
1268 1281
1282 1295
1283 Date format is "YYYY-MM-DD" eg: 1296 Date format is "YYYY-MM-DD" eg:
1284 2001-01-01 1297 2001-01-01
1285 1298
1286 """ 1299 """
1287 if len(args) <> 1: 1300 if len(args) != 1:
1288 raise UsageError, _('Not enough arguments supplied') 1301 raise UsageError(_('Not enough arguments supplied'))
1289 1302
1290 # are we dealing with a period or a date 1303 # are we dealing with a period or a date
1291 value = args[0] 1304 value = args[0]
1292 date_re = re.compile(r""" 1305 date_re = re.compile(r"""
1293 (?P<date>\d\d\d\d-\d\d?-\d\d?)? # yyyy-mm-dd 1306 (?P<date>\d\d\d\d-\d\d?-\d\d?)? # yyyy-mm-dd
1294 (?P<period>(\d+y\s*)?(\d+m\s*)?(\d+d\s*)?)? 1307 (?P<period>(\d+y\s*)?(\d+m\s*)?(\d+d\s*)?)?
1295 """, re.VERBOSE) 1308 """, re.VERBOSE)
1296 m = date_re.match(value) 1309 m = date_re.match(value)
1297 if not m: 1310 if not m:
1298 raise ValueError, _('Invalid format') 1311 raise ValueError(_('Invalid format'))
1299 m = m.groupdict() 1312 m = m.groupdict()
1300 if m['period']: 1313 if m['period']:
1301 pack_before = date.Date(". - %s"%value) 1314 pack_before = date.Date(". - %s"%value)
1302 elif m['date']: 1315 elif m['date']:
1303 pack_before = date.Date(value) 1316 pack_before = date.Date(value)
1318 if m: 1331 if m:
1319 cl = self.get_class(m.group(1)) 1332 cl = self.get_class(m.group(1))
1320 try: 1333 try:
1321 cl.index(m.group(2)) 1334 cl.index(m.group(2))
1322 except IndexError: 1335 except IndexError:
1323 raise UsageError, _('no such item "%(designator)s"')%{ 1336 raise UsageError(_('no such item "%(designator)s"')%{
1324 'designator': arg} 1337 'designator': arg})
1325 else: 1338 else:
1326 cl = self.get_class(arg) 1339 cl = self.get_class(arg)
1327 self.db.reindex(arg) 1340 self.db.reindex(arg)
1328 else: 1341 else:
1329 self.db.reindex(show_progress=True) 1342 self.db.reindex(show_progress=True)
1339 roles = [(args[0], self.db.security.role[args[0]])] 1352 roles = [(args[0], self.db.security.role[args[0]])]
1340 except KeyError: 1353 except KeyError:
1341 print _('No such Role "%(role)s"')%locals() 1354 print _('No such Role "%(role)s"')%locals()
1342 return 1 1355 return 1
1343 else: 1356 else:
1344 roles = self.db.security.role.items() 1357 roles = list(self.db.security.role.items())
1345 role = self.db.config.NEW_WEB_USER_ROLES 1358 role = self.db.config.NEW_WEB_USER_ROLES
1346 if ',' in role: 1359 if ',' in role:
1347 print _('New Web users get the Roles "%(role)s"')%locals() 1360 print _('New Web users get the Roles "%(role)s"')%locals()
1348 else: 1361 else:
1349 print _('New Web users get the Role "%(role)s"')%locals() 1362 print _('New Web users get the Role "%(role)s"')%locals()
1515 1528
1516 # handle command-line args 1529 # handle command-line args
1517 self.tracker_home = os.environ.get('TRACKER_HOME', '') 1530 self.tracker_home = os.environ.get('TRACKER_HOME', '')
1518 # TODO: reinstate the user/password stuff (-u arg too) 1531 # TODO: reinstate the user/password stuff (-u arg too)
1519 name = password = '' 1532 name = password = ''
1520 if os.environ.has_key('ROUNDUP_LOGIN'): 1533 if 'ROUNDUP_LOGIN' in os.environ:
1521 l = os.environ['ROUNDUP_LOGIN'].split(':') 1534 l = os.environ['ROUNDUP_LOGIN'].split(':')
1522 name = l[0] 1535 name = l[0]
1523 if len(l) > 1: 1536 if len(l) > 1:
1524 password = l[1] 1537 password = l[1]
1525 self.separator = None 1538 self.separator = None

Roundup Issue Tracker: http://roundup-tracker.org/