Mercurial > p > roundup > code
comparison roundup/admin.py @ 6180:4f45ce95f016
pep8 changes.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Tue, 19 May 2020 17:10:08 -0400 |
| parents | 41907e1f9c3f |
| children | cc66cf905147 |
comparison
equal
deleted
inserted
replaced
| 6179:a701c9c81597 | 6180:4f45ce95f016 |
|---|---|
| 23 | 23 |
| 24 __docformat__ = 'restructuredtext' | 24 __docformat__ = 'restructuredtext' |
| 25 | 25 |
| 26 import csv, getopt, getpass, os, re, shutil, sys, operator | 26 import csv, getopt, getpass, os, re, shutil, sys, operator |
| 27 | 27 |
| 28 from roundup import date, hyperdb, roundupdb, init, password, token | 28 from roundup import date, hyperdb, init, password, token |
| 29 from roundup import __version__ as roundup_version | 29 from roundup import __version__ as roundup_version |
| 30 import roundup.instance | 30 import roundup.instance |
| 31 from roundup.configuration import CoreConfig, NoConfigError, UserConfig | 31 from roundup.configuration import CoreConfig, NoConfigError, UserConfig |
| 32 from roundup.i18n import _ | 32 from roundup.i18n import _ |
| 33 from roundup.exceptions import UsageError | 33 from roundup.exceptions import UsageError |
| 37 try: | 37 try: |
| 38 from UserDict import UserDict | 38 from UserDict import UserDict |
| 39 except ImportError: | 39 except ImportError: |
| 40 from collections import UserDict | 40 from collections import UserDict |
| 41 | 41 |
| 42 | |
| 42 class CommandDict(UserDict): | 43 class CommandDict(UserDict): |
| 43 """Simple dictionary that lets us do lookups using partial keys. | 44 """Simple dictionary that lets us do lookups using partial keys. |
| 44 | 45 |
| 45 Original code submitted by Engelbert Gruber. | 46 Original code submitted by Engelbert Gruber. |
| 46 """ | 47 """ |
| 47 _marker = [] | 48 _marker = [] |
| 49 | |
| 48 def get(self, key, default=_marker): | 50 def get(self, key, default=_marker): |
| 49 if key in self.data: | 51 if key in self.data: |
| 50 return [(key, self.data[key])] | 52 return [(key, self.data[key])] |
| 51 keylist = sorted(self.data) | 53 keylist = sorted(self.data) |
| 52 l = [] | 54 l = [] |
| 54 if ki.startswith(key): | 56 if ki.startswith(key): |
| 55 l.append((ki, self.data[ki])) | 57 l.append((ki, self.data[ki])) |
| 56 if not l and default is self._marker: | 58 if not l and default is self._marker: |
| 57 raise KeyError(key) | 59 raise KeyError(key) |
| 58 return l | 60 return l |
| 61 | |
| 59 | 62 |
| 60 class AdminTool: | 63 class AdminTool: |
| 61 """ A collection of methods used in maintaining Roundup trackers. | 64 """ A collection of methods used in maintaining Roundup trackers. |
| 62 | 65 |
| 63 Typically these methods are accessed through the roundup-admin | 66 Typically these methods are accessed through the roundup-admin |
| 87 """Get the class - raise an exception if it doesn't exist. | 90 """Get the class - raise an exception if it doesn't exist. |
| 88 """ | 91 """ |
| 89 try: | 92 try: |
| 90 return self.db.getclass(classname) | 93 return self.db.getclass(classname) |
| 91 except KeyError: | 94 except KeyError: |
| 92 raise UsageError(_('no such class "%(classname)s"')%locals()) | 95 raise UsageError(_('no such class "%(classname)s"') % locals()) |
| 93 | 96 |
| 94 def props_from_args(self, args): | 97 def props_from_args(self, args): |
| 95 """ Produce a dictionary of prop: value from the args list. | 98 """ Produce a dictionary of prop: value from the args list. |
| 96 | 99 |
| 97 The args list is specified as ``prop=value prop=value ...``. | 100 The args list is specified as ``prop=value prop=value ...``. |
| 98 """ | 101 """ |
| 99 props = {} | 102 props = {} |
| 100 for arg in args: | 103 for arg in args: |
| 101 if arg.find('=') == -1: | 104 if arg.find('=') == -1: |
| 102 raise UsageError(_('argument "%(arg)s" not propname=value' | 105 raise UsageError(_('argument "%(arg)s" not propname=value') % |
| 103 )%locals()) | 106 locals()) |
| 104 l = arg.split('=') | 107 l = arg.split('=') |
| 105 if len(l) < 2: | 108 if len(l) < 2: |
| 106 raise UsageError(_('argument "%(arg)s" not propname=value' | 109 raise UsageError(_('argument "%(arg)s" not propname=value') % |
| 107 )%locals()) | 110 locals()) |
| 108 key, value = l[0], '='.join(l[1:]) | 111 key, value = l[0], '='.join(l[1:]) |
| 109 if value: | 112 if value: |
| 110 props[key] = value | 113 props[key] = value |
| 111 else: | 114 else: |
| 112 props[key] = None | 115 props[key] = None |
| 114 | 117 |
| 115 def usage(self, message=''): | 118 def usage(self, message=''): |
| 116 """ Display a simple usage message. | 119 """ Display a simple usage message. |
| 117 """ | 120 """ |
| 118 if message: | 121 if message: |
| 119 message = _('Problem: %(message)s\n\n')%locals() | 122 message = _('Problem: %(message)s\n\n')% locals() |
| 120 sys.stdout.write( _("""%(message)sUsage: roundup-admin [options] [<command> <arguments>] | 123 sys.stdout.write(_("""%(message)sUsage: roundup-admin [options] [<command> <arguments>] |
| 121 | 124 |
| 122 Options: | 125 Options: |
| 123 -i instance home -- specify the issue tracker "home directory" to administer | 126 -i instance home -- specify the issue tracker "home directory" to administer |
| 124 -u -- the user[:password] to use for commands (default admin) | 127 -u -- the user[:password] to use for commands (default admin) |
| 125 -d -- print full designators not just class id numbers | 128 -d -- print full designators not just class id numbers |
| 136 Help: | 139 Help: |
| 137 roundup-admin -h | 140 roundup-admin -h |
| 138 roundup-admin help -- this help | 141 roundup-admin help -- this help |
| 139 roundup-admin help <command> -- command-specific help | 142 roundup-admin help <command> -- command-specific help |
| 140 roundup-admin help all -- all available help | 143 roundup-admin help all -- all available help |
| 141 """)%locals()) | 144 """) % locals()) |
| 142 self.help_commands() | 145 self.help_commands() |
| 143 | 146 |
| 144 def help_commands(self): | 147 def help_commands(self): |
| 145 """List the commands available with their help summary. | 148 """List the commands available with their help summary. |
| 146 """ | 149 """ |
| 147 sys.stdout.write( _('Commands: ')) | 150 sys.stdout.write(_('Commands: ')) |
| 148 commands = [''] | 151 commands = [''] |
| 149 for command in self.commands.values(): | 152 for command in self.commands.values(): |
| 150 h = _(command.__doc__).split('\n')[0] | 153 h = _(command.__doc__).split('\n')[0] |
| 151 commands.append(' '+h[7:]) | 154 commands.append(' '+h[7:]) |
| 152 commands.sort() | 155 commands.sort() |
| 157 | 160 |
| 158 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): | 161 def help_commands_html(self, indent_re=re.compile(r'^(\s+)\S+')): |
| 159 """ Produce an HTML command list. | 162 """ Produce an HTML command list. |
| 160 """ | 163 """ |
| 161 commands = sorted(iter(self.commands.values()), | 164 commands = sorted(iter(self.commands.values()), |
| 162 key=operator.attrgetter('__name__')) | 165 key=operator.attrgetter('__name__')) |
| 163 for command in commands: | 166 for command in commands: |
| 164 h = _(command.__doc__).split('\n') | 167 h = _(command.__doc__).split('\n') |
| 165 name = command.__name__[3:] | 168 name = command.__name__[3:] |
| 166 usage = h[0] | 169 usage = h[0] |
| 167 print(""" | 170 print(""" |
| 239 "." means "right now" | 242 "." means "right now" |
| 240 | 243 |
| 241 Command help: | 244 Command help: |
| 242 """)) | 245 """)) |
| 243 for name, command in list(self.commands.items()): | 246 for name, command in list(self.commands.items()): |
| 244 print(_('%s:')%name) | 247 print(_('%s:') % name) |
| 245 print(' ', _(command.__doc__)) | 248 print(' ', _(command.__doc__)) |
| 246 | 249 |
| 247 def do_help(self, args, nl_re=re.compile('[\r\n]'), | 250 def do_help(self, args, nl_re=re.compile('[\r\n]'), |
| 248 indent_re=re.compile(r'^(\s+)\S+')): | 251 indent_re=re.compile(r'^(\s+)\S+')): |
| 249 ''"""Usage: help topic | 252 ''"""Usage: help topic |
| 250 Give help about topic. | 253 Give help about topic. |
| 251 | 254 |
| 252 commands -- list commands | 255 commands -- list commands |
| 253 <command> -- help specific to a command | 256 <command> -- help specific to a command |
| 254 initopts -- init command options | 257 initopts -- init command options |
| 255 all -- all available help | 258 all -- all available help |
| 256 """ | 259 """ |
| 257 if len(args)>0: | 260 if len(args) > 0: |
| 258 topic = args[0] | 261 topic = args[0] |
| 259 else: | 262 else: |
| 260 topic = 'help' | 263 topic = 'help' |
| 261 | |
| 262 | 264 |
| 263 # try help_ methods | 265 # try help_ methods |
| 264 if topic in self.help: | 266 if topic in self.help: |
| 265 self.help[topic]() | 267 self.help[topic]() |
| 266 return 0 | 268 return 0 |
| 267 | 269 |
| 268 # try command docstrings | 270 # try command docstrings |
| 269 try: | 271 try: |
| 270 l = self.commands.get(topic) | 272 l = self.commands.get(topic) |
| 271 except KeyError: | 273 except KeyError: |
| 272 print(_('Sorry, no help for "%(topic)s"')%locals()) | 274 print(_('Sorry, no help for "%(topic)s"') % locals()) |
| 273 return 1 | 275 return 1 |
| 274 | 276 |
| 275 # display the help for each match, removing the docsring indent | 277 # display the help for each match, removing the docstring indent |
| 276 for name, help in l: | 278 for _name, help in l: |
| 277 lines = nl_re.split(_(help.__doc__)) | 279 lines = nl_re.split(_(help.__doc__)) |
| 278 print(lines[0]) | 280 print(lines[0]) |
| 279 indent = indent_re.match(lines[1]) | 281 indent = indent_re.match(lines[1]) |
| 280 if indent: indent = len(indent.group(1)) | 282 if indent: indent = len(indent.group(1)) |
| 281 for line in lines[1:]: | 283 for line in lines[1:]: |
| 315 # we're interested in where the directory containing "share" is | 317 # we're interested in where the directory containing "share" is |
| 316 templates = {} | 318 templates = {} |
| 317 for N in 2, 4, 5, 6: | 319 for N in 2, 4, 5, 6: |
| 318 path = __file__ | 320 path = __file__ |
| 319 # move up N elements in the path | 321 # move up N elements in the path |
| 320 for i in range(N): | 322 for _i in range(N): |
| 321 path = os.path.dirname(path) | 323 path = os.path.dirname(path) |
| 322 tdir = os.path.join(path, 'share', 'roundup', 'templates') | 324 tdir = os.path.join(path, 'share', 'roundup', 'templates') |
| 323 if os.path.isdir(tdir): | 325 if os.path.isdir(tdir): |
| 324 templates = init.listTemplates(tdir) | 326 templates = init.listTemplates(tdir) |
| 325 break | 327 break |
| 326 | 328 |
| 327 # OK, now try as if we're in the roundup source distribution | 329 # OK, now try as if we're in the roundup source distribution |
| 328 # directory, so this module will be in .../roundup-*/roundup/admin.py | 330 # directory, so this module will be in .../roundup-*/roundup/admin.py |
| 329 # and we're interested in the .../roundup-*/ part. | 331 # and we're interested in the .../roundup-*/ part. |
| 330 path = __file__ | 332 path = __file__ |
| 331 for i in range(2): | 333 for _i in range(2): |
| 332 path = os.path.dirname(path) | 334 path = os.path.dirname(path) |
| 333 tdir = os.path.join(path, 'templates') | 335 tdir = os.path.join(path, 'templates') |
| 334 if os.path.isdir(tdir): | 336 if os.path.isdir(tdir): |
| 335 templates.update(init.listTemplates(tdir)) | 337 templates.update(init.listTemplates(tdir)) |
| 336 | 338 |
| 380 # make sure the tracker home can be created | 382 # make sure the tracker home can be created |
| 381 tracker_home = os.path.abspath(tracker_home) | 383 tracker_home = os.path.abspath(tracker_home) |
| 382 parent = os.path.split(tracker_home)[0] | 384 parent = os.path.split(tracker_home)[0] |
| 383 if not os.path.exists(parent): | 385 if not os.path.exists(parent): |
| 384 raise UsageError(_('Instance home parent directory "%(parent)s"' | 386 raise UsageError(_('Instance home parent directory "%(parent)s"' |
| 385 ' does not exist')%locals()) | 387 ' does not exist') % locals()) |
| 386 | 388 |
| 387 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) | 389 config_ini_file = os.path.join(tracker_home, CoreConfig.INI_FILE) |
| 388 # check for both old- and new-style configs | 390 # check for both old- and new-style configs |
| 389 if list(filter(os.path.exists, [config_ini_file, | 391 if list(filter(os.path.exists, [config_ini_file, |
| 390 os.path.join(tracker_home, 'config.py')])): | 392 os.path.join(tracker_home, 'config.py')])): |
| 421 | 423 |
| 422 # Process configuration file definitions | 424 # Process configuration file definitions |
| 423 if len(args) > 3: | 425 if len(args) > 3: |
| 424 try: | 426 try: |
| 425 defns = dict([item.split("=") for item in args[3].split(",")]) | 427 defns = dict([item.split("=") for item in args[3].split(",")]) |
| 426 except: | 428 except Exception: |
| 427 print(_('Error in configuration settings: "%s"') % args[3]) | 429 print(_('Error in configuration settings: "%s"') % args[3]) |
| 428 raise | 430 raise |
| 429 else: | 431 else: |
| 430 defns = {} | 432 defns = {} |
| 431 | 433 |
| 432 defns['rdbms_backend'] = backend | 434 defns['rdbms_backend'] = backend |
| 433 | 435 |
| 434 # load config_ini.ini from template if it exists. | 436 # load config_ini.ini from template if it exists. |
| 435 # it sets parameters like template_engine that are | 437 # it sets parameters like template_engine that are |
| 436 # template specific. | 438 # template specific. |
| 437 template_config=UserConfig(templates[template]['path'] + | 439 template_config = UserConfig(templates[template]['path'] + |
| 438 "/config_ini.ini") | 440 "/config_ini.ini") |
| 439 for k in template_config.keys(): | 441 for k in template_config.keys(): |
| 440 if k == 'HOME': # ignore home. It is a default param. | 442 if k == 'HOME': # ignore home. It is a default param. |
| 441 continue | 443 continue |
| 442 defns[k] = template_config[k] | 444 defns[k] = template_config[k] |
| 443 | 445 |
| 444 # install! | 446 # install! |
| 445 init.install(tracker_home, templates[template]['path'], settings=defns) | 447 init.install(tracker_home, templates[template]['path'], settings=defns) |
| 542 adminpw = getpass.getpass(_('Admin Password: ')) | 544 adminpw = getpass.getpass(_('Admin Password: ')) |
| 543 confirm = getpass.getpass(_(' Confirm: ')) | 545 confirm = getpass.getpass(_(' Confirm: ')) |
| 544 | 546 |
| 545 # make sure the tracker home is installed | 547 # make sure the tracker home is installed |
| 546 if not os.path.exists(tracker_home): | 548 if not os.path.exists(tracker_home): |
| 547 raise UsageError(_('Instance home does not exist')%locals()) | 549 raise UsageError(_('Instance home does not exist') % locals()) |
| 548 try: | 550 try: |
| 549 tracker = roundup.instance.open(tracker_home) | 551 tracker = roundup.instance.open(tracker_home) |
| 550 except roundup.instance.TrackerError: | 552 except roundup.instance.TrackerError: |
| 551 raise UsageError(_('Instance has not been installed')%locals()) | 553 raise UsageError(_('Instance has not been installed') % locals()) |
| 552 | 554 |
| 553 # is there already a database? | 555 # is there already a database? |
| 554 if tracker.exists(): | 556 if tracker.exists(): |
| 555 if not self.force: | 557 if not self.force: |
| 556 ok = my_input(_( | 558 ok = my_input(_( |
| 563 # nuke it | 565 # nuke it |
| 564 tracker.nuke() | 566 tracker.nuke() |
| 565 | 567 |
| 566 # GO | 568 # GO |
| 567 tracker.init(password.Password(adminpw, config=tracker.config), | 569 tracker.init(password.Password(adminpw, config=tracker.config), |
| 568 tx_Source = 'cli') | 570 tx_Source='cli') |
| 569 | 571 |
| 570 return 0 | 572 return 0 |
| 571 | |
| 572 | 573 |
| 573 def do_get(self, args): | 574 def do_get(self, args): |
| 574 ''"""Usage: get property designator[,designator]* | 575 ''"""Usage: get property designator[,designator]* |
| 575 Get the given property of one or more designator(s). | 576 Get the given property of one or more designator(s). |
| 576 | 577 |
| 593 raise UsageError(message) | 594 raise UsageError(message) |
| 594 | 595 |
| 595 # get the class | 596 # get the class |
| 596 cl = self.get_class(classname) | 597 cl = self.get_class(classname) |
| 597 try: | 598 try: |
| 598 id=[] | 599 id = [] |
| 599 if self.separator: | 600 if self.separator: |
| 600 if self.print_designator: | 601 if self.print_designator: |
| 601 # see if property is a link or multilink for | 602 # see if property is a link or multilink for |
| 602 # which getting a desginator make sense. | 603 # which getting a desginator make sense. |
| 603 # Algorithm: Get the properties of the | 604 # Algorithm: Get the properties of the |
| 611 # append the new designators | 612 # append the new designators |
| 612 # print | 613 # print |
| 613 properties = cl.getprops() | 614 properties = cl.getprops() |
| 614 property = properties[propname] | 615 property = properties[propname] |
| 615 if not (isinstance(property, hyperdb.Multilink) or | 616 if not (isinstance(property, hyperdb.Multilink) or |
| 616 isinstance(property, hyperdb.Link)): | 617 isinstance(property, hyperdb.Link)): |
| 617 raise UsageError(_('property %s is not of type' | 618 raise UsageError(_('property %s is not of type' |
| 618 ' Multilink or Link so -d flag does not ' | 619 ' Multilink or Link so -d flag does not ' |
| 619 'apply.')%propname) | 620 'apply.') % propname) |
| 620 propclassname = self.db.getclass(property.classname).classname | 621 propclassname = self.db.getclass(property.classname).classname |
| 621 id = cl.get(nodeid, propname) | 622 id = cl.get(nodeid, propname) |
| 622 for i in id: | 623 for i in id: |
| 623 l.append(propclassname + i) | 624 l.append(propclassname + i) |
| 624 else: | 625 else: |
| 628 else: | 629 else: |
| 629 if self.print_designator: | 630 if self.print_designator: |
| 630 properties = cl.getprops() | 631 properties = cl.getprops() |
| 631 property = properties[propname] | 632 property = properties[propname] |
| 632 if not (isinstance(property, hyperdb.Multilink) or | 633 if not (isinstance(property, hyperdb.Multilink) or |
| 633 isinstance(property, hyperdb.Link)): | 634 isinstance(property, hyperdb.Link)): |
| 634 raise UsageError(_('property %s is not of type' | 635 raise UsageError(_('property %s is not of type' |
| 635 ' Multilink or Link so -d flag does not ' | 636 ' Multilink or Link so -d flag does not ' |
| 636 'apply.')%propname) | 637 'apply.') % propname) |
| 637 propclassname = self.db.getclass(property.classname).classname | 638 propclassname = self.db.getclass(property.classname).classname |
| 638 id = cl.get(nodeid, propname) | 639 id = cl.get(nodeid, propname) |
| 639 for i in id: | 640 for i in id: |
| 640 print(propclassname + i) | 641 print(propclassname + i) |
| 641 else: | 642 else: |
| 642 print(cl.get(nodeid, propname)) | 643 print(cl.get(nodeid, propname)) |
| 643 except IndexError: | 644 except IndexError: |
| 644 raise UsageError(_('no such %(classname)s node ' | 645 raise UsageError(_('no such %(classname)s node ' |
| 645 '"%(nodeid)s"')%locals()) | 646 '"%(nodeid)s"') % locals()) |
| 646 except KeyError: | 647 except KeyError: |
| 647 raise UsageError(_('no such %(classname)s property ' | 648 raise UsageError(_('no such %(classname)s property ' |
| 648 '"%(propname)s"')%locals()) | 649 '"%(propname)s"') % locals()) |
| 649 if self.separator: | 650 if self.separator: |
| 650 print(self.separator.join(l)) | 651 print(self.separator.join(l)) |
| 651 | 652 |
| 652 return 0 | 653 return 0 |
| 653 | |
| 654 | 654 |
| 655 def do_set(self, args): | 655 def do_set(self, args): |
| 656 ''"""Usage: set items property=value property=value ... | 656 ''"""Usage: set items property=value property=value ... |
| 657 Set the given properties of one or more items(s). | 657 Set the given properties of one or more items(s). |
| 658 | 658 |
| 665 This command sets the properties to the values for all designators | 665 This command sets the properties to the values for all designators |
| 666 given. If the value is missing (ie. "property=") then the property | 666 given. If the value is missing (ie. "property=") then the property |
| 667 is un-set. If the property is a multilink, you specify the linked | 667 is un-set. If the property is a multilink, you specify the linked |
| 668 ids for the multilink as comma-separated numbers (ie "1,2,3"). | 668 ids for the multilink as comma-separated numbers (ie "1,2,3"). |
| 669 """ | 669 """ |
| 670 import copy # needed for copying props list | 670 import copy # needed for copying props list |
| 671 | 671 |
| 672 if len(args) < 2: | 672 if len(args) < 2: |
| 673 raise UsageError(_('Not enough arguments supplied')) | 673 raise UsageError(_('Not enough arguments supplied')) |
| 674 from roundup import hyperdb | 674 from roundup import hyperdb |
| 675 | 675 |
| 687 designators = [hyperdb.splitDesignator(x) for x in designators] | 687 designators = [hyperdb.splitDesignator(x) for x in designators] |
| 688 except hyperdb.DesignatorError as message: | 688 except hyperdb.DesignatorError as message: |
| 689 raise UsageError(message) | 689 raise UsageError(message) |
| 690 | 690 |
| 691 # get the props from the args | 691 # get the props from the args |
| 692 propset = self.props_from_args(args[1:]) # parse the cli once | 692 propset = self.props_from_args(args[1:]) # parse the cli once |
| 693 | 693 |
| 694 # now do the set for all the nodes | 694 # now do the set for all the nodes |
| 695 for classname, itemid in designators: | 695 for classname, itemid in designators: |
| 696 props = copy.copy(propset) # make a new copy for every designator | 696 props = copy.copy(propset) # make a new copy for every designator |
| 697 cl = self.get_class(classname) | 697 cl = self.get_class(classname) |
| 698 | 698 |
| 699 for key, value in list(props.items()): | 699 for key, value in list(props.items()): |
| 700 try: | 700 try: |
| 701 # You must reinitialize the props every time though. | 701 # You must reinitialize the props every time though. |
| 703 # set to 'demo,admin' (assuming it was set to demo | 703 # set to 'demo,admin' (assuming it was set to demo |
| 704 # in the db) after rawToHyperdb returns. | 704 # in the db) after rawToHyperdb returns. |
| 705 # This new value is used for all the rest of the | 705 # This new value is used for all the rest of the |
| 706 # designators if not reinitalized. | 706 # designators if not reinitalized. |
| 707 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, | 707 props[key] = hyperdb.rawToHyperdb(self.db, cl, itemid, |
| 708 key, value) | 708 key, value) |
| 709 except hyperdb.HyperdbValueError as message: | 709 except hyperdb.HyperdbValueError as message: |
| 710 raise UsageError(message) | 710 raise UsageError(message) |
| 711 | 711 |
| 712 # try the set | 712 # try the set |
| 713 try: | 713 try: |
| 760 if self.separator: | 760 if self.separator: |
| 761 if self.print_designator: | 761 if self.print_designator: |
| 762 id = cl.filter(None, **props) | 762 id = cl.filter(None, **props) |
| 763 for i in id: | 763 for i in id: |
| 764 designator.append(classname + i) | 764 designator.append(classname + i) |
| 765 print(self.separator.join(designator), file=sys.stdout) | 765 print(self.separator.join(designator)) |
| 766 else: | 766 else: |
| 767 print(self.separator.join(cl.find(**props)), | 767 print(self.separator.join(cl.find(**props))) |
| 768 file=sys.stdout) | |
| 769 else: | 768 else: |
| 770 if self.print_designator: | 769 if self.print_designator: |
| 771 id = cl.filter(None, **props) | 770 id = cl.filter(None, **props) |
| 772 for i in id: | 771 for i in id: |
| 773 designator.append(classname + i) | 772 designator.append(classname + i) |
| 774 print(designator,file=sys.stdout) | 773 print(designator) |
| 775 else: | 774 else: |
| 776 print(cl.filter(None, **props), file=sys.stdout) | 775 print(cl.filter(None, **props)) |
| 777 except KeyError: | 776 except KeyError: |
| 778 raise UsageError(_('%(classname)s has no property ' | 777 raise UsageError(_('%(classname)s has no property ' |
| 779 '"%(propname)s"') % locals()) | 778 '"%(propname)s"') % locals()) |
| 780 except (ValueError, TypeError) as message: | 779 except (ValueError, TypeError) as message: |
| 781 raise UsageError(message) | 780 raise UsageError(message) |
| 804 values = value.split(',') | 803 values = value.split(',') |
| 805 else: | 804 else: |
| 806 values = [value] | 805 values = [value] |
| 807 d = props[propname] = {} | 806 d = props[propname] = {} |
| 808 for value in values: | 807 for value in values: |
| 809 value = hyperdb.rawToHyperdb(self.db, cl, None, propname, value) | 808 value = hyperdb.rawToHyperdb(self.db, cl, None, |
| 809 propname, value) | |
| 810 if isinstance(value, list): | 810 if isinstance(value, list): |
| 811 for entry in value: | 811 for entry in value: |
| 812 d[entry] = 1 | 812 d[entry] = 1 |
| 813 else: | 813 else: |
| 814 d[value] = 1 | 814 d[value] = 1 |
| 834 print(designator) | 834 print(designator) |
| 835 else: | 835 else: |
| 836 print(cl.find(**props)) | 836 print(cl.find(**props)) |
| 837 except KeyError: | 837 except KeyError: |
| 838 raise UsageError(_('%(classname)s has no property ' | 838 raise UsageError(_('%(classname)s has no property ' |
| 839 '"%(propname)s"')%locals()) | 839 '"%(propname)s"') % locals()) |
| 840 except (ValueError, TypeError) as message: | 840 except (ValueError, TypeError) as message: |
| 841 raise UsageError(message) | 841 raise UsageError(message) |
| 842 return 0 | 842 return 0 |
| 843 | 843 |
| 844 def do_specification(self, args): | 844 def do_specification(self, args): |
| 856 # get the key property | 856 # get the key property |
| 857 keyprop = cl.getkey() | 857 keyprop = cl.getkey() |
| 858 for key in cl.properties: | 858 for key in cl.properties: |
| 859 value = cl.properties[key] | 859 value = cl.properties[key] |
| 860 if keyprop == key: | 860 if keyprop == key: |
| 861 sys.stdout.write( _('%(key)s: %(value)s (key property)\n')%locals() ) | 861 sys.stdout.write(_('%(key)s: %(value)s (key property)\n') % |
| 862 locals()) | |
| 862 else: | 863 else: |
| 863 sys.stdout.write( _('%(key)s: %(value)s\n')%locals() ) | 864 sys.stdout.write(_('%(key)s: %(value)s\n') % locals()) |
| 864 | 865 |
| 865 def do_display(self, args): | 866 def do_display(self, args): |
| 866 ''"""Usage: display designator[,designator]* | 867 ''"""Usage: display designator[,designator]* |
| 867 Show the property values for the given node(s). | 868 Show the property values for the given node(s). |
| 868 | 869 |
| 887 | 888 |
| 888 # display the values | 889 # display the values |
| 889 keys = sorted(cl.properties) | 890 keys = sorted(cl.properties) |
| 890 for key in keys: | 891 for key in keys: |
| 891 value = cl.get(nodeid, key) | 892 value = cl.get(nodeid, key) |
| 892 print(_('%(key)s: %(value)s')%locals()) | 893 print(_('%(key)s: %(value)s') % locals()) |
| 893 | 894 |
| 894 def do_create(self, args): | 895 def do_create(self, args): |
| 895 ''"""Usage: create classname property=value ... | 896 ''"""Usage: create classname property=value ... |
| 896 Create a new entry of a given class. | 897 Create a new entry of a given class. |
| 897 | 898 |
| 908 # get the class | 909 # get the class |
| 909 cl = self.get_class(classname) | 910 cl = self.get_class(classname) |
| 910 | 911 |
| 911 # now do a create | 912 # now do a create |
| 912 props = {} | 913 props = {} |
| 913 properties = cl.getprops(protected = 0) | 914 properties = cl.getprops(protected=0) |
| 914 if len(args) == 1: | 915 if len(args) == 1: |
| 915 # ask for the properties | 916 # ask for the properties |
| 916 for key in properties: | 917 for key in properties: |
| 917 if key == 'id': continue | 918 if key == 'id': continue |
| 918 value = properties[key] | 919 value = properties[key] |
| 919 name = value.__class__.__name__ | 920 name = value.__class__.__name__ |
| 920 if isinstance(value , hyperdb.Password): | 921 if isinstance(value, hyperdb.Password): |
| 921 again = None | 922 again = None |
| 922 while value != again: | 923 while value != again: |
| 923 value = getpass.getpass(_('%(propname)s (Password): ')%{ | 924 value = getpass.getpass(_('%(propname)s (Password): ') |
| 924 'propname': key.capitalize()}) | 925 % |
| 925 again = getpass.getpass(_(' %(propname)s (Again): ')%{ | 926 {'propname': key.capitalize()}) |
| 926 'propname': key.capitalize()}) | 927 again = getpass.getpass(_(' %(propname)s (Again): ') |
| 928 % | |
| 929 {'propname': key.capitalize()}) | |
| 927 if value != again: print(_('Sorry, try again...')) | 930 if value != again: print(_('Sorry, try again...')) |
| 928 if value: | 931 if value: |
| 929 props[key] = value | 932 props[key] = value |
| 930 else: | 933 else: |
| 931 value = my_input(_('%(propname)s (%(proptype)s): ')%{ | 934 value = my_input(_('%(propname)s (%(proptype)s): ') % { |
| 932 'propname': key.capitalize(), 'proptype': name}) | 935 'propname': key.capitalize(), 'proptype': name}) |
| 933 if value: | 936 if value: |
| 934 props[key] = value | 937 props[key] = value |
| 935 else: | 938 else: |
| 936 props = self.props_from_args(args[1:]) | 939 props = self.props_from_args(args[1:]) |
| 945 | 948 |
| 946 # check for the key property | 949 # check for the key property |
| 947 propname = cl.getkey() | 950 propname = cl.getkey() |
| 948 if propname and propname not in props: | 951 if propname and propname not in props: |
| 949 raise UsageError(_('you must provide the "%(propname)s" ' | 952 raise UsageError(_('you must provide the "%(propname)s" ' |
| 950 'property.')%locals()) | 953 'property.') % locals()) |
| 951 | 954 |
| 952 # do the actual create | 955 # do the actual create |
| 953 try: | 956 try: |
| 954 sys.stdout.write(cl.create(**props) + '\n') | 957 sys.stdout.write(cl.create(**props) + '\n') |
| 955 except (TypeError, IndexError, ValueError) as message: | 958 except (TypeError, IndexError, ValueError) as message: |
| 973 if len(args) > 2: | 976 if len(args) > 2: |
| 974 raise UsageError(_('Too many arguments supplied')) | 977 raise UsageError(_('Too many arguments supplied')) |
| 975 if len(args) < 1: | 978 if len(args) < 1: |
| 976 raise UsageError(_('Not enough arguments supplied')) | 979 raise UsageError(_('Not enough arguments supplied')) |
| 977 classname = args[0] | 980 classname = args[0] |
| 978 | 981 |
| 979 # get the class | 982 # get the class |
| 980 cl = self.get_class(classname) | 983 cl = self.get_class(classname) |
| 981 | 984 |
| 982 # figure the property | 985 # figure the property |
| 983 if len(args) > 1: | 986 if len(args) > 1: |
| 986 propname = cl.labelprop() | 989 propname = cl.labelprop() |
| 987 | 990 |
| 988 if self.separator: | 991 if self.separator: |
| 989 if len(args) == 2: | 992 if len(args) == 2: |
| 990 # create a list of propnames since user specified propname | 993 # create a list of propnames since user specified propname |
| 991 proplist=[] | 994 proplist = [] |
| 992 for nodeid in cl.list(): | 995 for nodeid in cl.list(): |
| 993 try: | 996 try: |
| 994 proplist.append(cl.get(nodeid, propname)) | 997 proplist.append(cl.get(nodeid, propname)) |
| 995 except KeyError: | 998 except KeyError: |
| 996 raise UsageError(_('%(classname)s has no property ' | 999 raise UsageError(_('%(classname)s has no property ' |
| 997 '"%(propname)s"')%locals()) | 1000 '"%(propname)s"') % locals()) |
| 998 print(self.separator.join(proplist)) | 1001 print(self.separator.join(proplist)) |
| 999 else: | 1002 else: |
| 1000 # create a list of index id's since user didn't specify | 1003 # create a list of index id's since user didn't specify |
| 1001 # otherwise | 1004 # otherwise |
| 1002 print(self.separator.join(cl.list())) | 1005 print(self.separator.join(cl.list())) |
| 1004 for nodeid in cl.list(): | 1007 for nodeid in cl.list(): |
| 1005 try: | 1008 try: |
| 1006 value = cl.get(nodeid, propname) | 1009 value = cl.get(nodeid, propname) |
| 1007 except KeyError: | 1010 except KeyError: |
| 1008 raise UsageError(_('%(classname)s has no property ' | 1011 raise UsageError(_('%(classname)s has no property ' |
| 1009 '"%(propname)s"')%locals()) | 1012 '"%(propname)s"') % locals()) |
| 1010 print(_('%(nodeid)4s: %(value)s')%locals()) | 1013 print(_('%(nodeid)4s: %(value)s') % locals()) |
| 1011 return 0 | 1014 return 0 |
| 1012 | 1015 |
| 1013 def do_table(self, args): | 1016 def do_table(self, args): |
| 1014 ''"""Usage: table classname [property[,property]*] | 1017 ''"""Usage: table classname [property[,property]*] |
| 1015 List the instances of a class in tabular form. | 1018 List the instances of a class in tabular form. |
| 1054 if ':' in spec: | 1057 if ':' in spec: |
| 1055 try: | 1058 try: |
| 1056 propname, width = spec.split(':') | 1059 propname, width = spec.split(':') |
| 1057 except (ValueError, TypeError): | 1060 except (ValueError, TypeError): |
| 1058 raise UsageError(_('"%(spec)s" not ' | 1061 raise UsageError(_('"%(spec)s" not ' |
| 1059 'name:width')%locals()) | 1062 'name:width') % locals()) |
| 1060 else: | 1063 else: |
| 1061 propname = spec | 1064 propname = spec |
| 1062 if propname not in all_props: | 1065 if propname not in all_props: |
| 1063 raise UsageError(_('%(classname)s has no property ' | 1066 raise UsageError(_('%(classname)s has no property ' |
| 1064 '"%(propname)s"')%locals()) | 1067 '"%(propname)s"') % locals()) |
| 1065 else: | 1068 else: |
| 1066 prop_names = cl.getprops() | 1069 prop_names = cl.getprops() |
| 1067 | 1070 |
| 1068 # now figure column widths | 1071 # now figure column widths |
| 1069 props = [] | 1072 props = [] |
| 1073 if width == '': | 1076 if width == '': |
| 1074 props.append((name, len(spec))) | 1077 props.append((name, len(spec))) |
| 1075 else: | 1078 else: |
| 1076 props.append((name, int(width))) | 1079 props.append((name, int(width))) |
| 1077 else: | 1080 else: |
| 1078 # this is going to be slow | 1081 # this is going to be slow |
| 1079 maxlen = len(spec) | 1082 maxlen = len(spec) |
| 1080 for nodeid in cl.list(): | 1083 for nodeid in cl.list(): |
| 1081 curlen = len(str(cl.get(nodeid, spec))) | 1084 curlen = len(str(cl.get(nodeid, spec))) |
| 1082 if curlen > maxlen: | 1085 if curlen > maxlen: |
| 1083 maxlen = curlen | 1086 maxlen = curlen |
| 1084 props.append((spec, maxlen)) | 1087 props.append((spec, maxlen)) |
| 1085 | 1088 |
| 1086 # now display the heading | 1089 # now display the heading |
| 1087 print(' '.join([name.capitalize().ljust(width) for name,width in props])) | 1090 print(' '.join([name.capitalize().ljust(width) |
| 1091 for name, width in props])) | |
| 1088 | 1092 |
| 1089 # and the table data | 1093 # and the table data |
| 1090 for nodeid in cl.list(): | 1094 for nodeid in cl.list(): |
| 1091 l = [] | 1095 l = [] |
| 1092 for name, width in props: | 1096 for name, width in props: |
| 1098 # KeyError here means the node just doesn't have a | 1102 # KeyError here means the node just doesn't have a |
| 1099 # value for it | 1103 # value for it |
| 1100 value = '' | 1104 value = '' |
| 1101 else: | 1105 else: |
| 1102 value = str(nodeid) | 1106 value = str(nodeid) |
| 1103 f = '%%-%ds'%width | 1107 f = '%%-%ds' % width |
| 1104 l.append(f%value[:width]) | 1108 l.append(f % value[:width]) |
| 1105 print(' '.join(l)) | 1109 print(' '.join(l)) |
| 1106 return 0 | 1110 return 0 |
| 1107 | 1111 |
| 1108 def do_history(self, args): | 1112 def do_history(self, args): |
| 1109 ''"""Usage: history designator [skipquiet] | 1113 ''"""Usage: history designator [skipquiet] |
| 1133 | 1137 |
| 1134 try: | 1138 try: |
| 1135 print(self.db.getclass(classname).history(nodeid, | 1139 print(self.db.getclass(classname).history(nodeid, |
| 1136 skipquiet=skipquiet)) | 1140 skipquiet=skipquiet)) |
| 1137 except KeyError: | 1141 except KeyError: |
| 1138 raise UsageError(_('no such class "%(classname)s"')%locals()) | 1142 raise UsageError(_('no such class "%(classname)s"') % locals()) |
| 1139 except IndexError: | 1143 except IndexError: |
| 1140 raise UsageError(_('no such %(classname)s node ' | 1144 raise UsageError(_('no such %(classname)s node ' |
| 1141 '"%(nodeid)s"')%locals()) | 1145 '"%(nodeid)s"') % locals()) |
| 1142 return 0 | 1146 return 0 |
| 1143 | 1147 |
| 1144 def do_commit(self, args): | 1148 def do_commit(self, args): |
| 1145 ''"""Usage: commit | 1149 ''"""Usage: commit |
| 1146 Commit changes made to the database during an interactive session. | 1150 Commit changes made to the database during an interactive session. |
| 1188 except hyperdb.DesignatorError as message: | 1192 except hyperdb.DesignatorError as message: |
| 1189 raise UsageError(message) | 1193 raise UsageError(message) |
| 1190 try: | 1194 try: |
| 1191 self.db.getclass(classname).retire(nodeid) | 1195 self.db.getclass(classname).retire(nodeid) |
| 1192 except KeyError: | 1196 except KeyError: |
| 1193 raise UsageError(_('no such class "%(classname)s"')%locals()) | 1197 raise UsageError(_('no such class "%(classname)s"') % locals()) |
| 1194 except IndexError: | 1198 except IndexError: |
| 1195 raise UsageError(_('no such %(classname)s node ' | 1199 raise UsageError(_('no such %(classname)s node ' |
| 1196 '"%(nodeid)s"')%locals()) | 1200 '"%(nodeid)s"') % locals()) |
| 1197 self.db_uncommitted = True | 1201 self.db_uncommitted = True |
| 1198 return 0 | 1202 return 0 |
| 1199 | 1203 |
| 1200 def do_restore(self, args): | 1204 def do_restore(self, args): |
| 1201 ''"""Usage: restore designator[,designator]* | 1205 ''"""Usage: restore designator[,designator]* |
| 1215 except hyperdb.DesignatorError as message: | 1219 except hyperdb.DesignatorError as message: |
| 1216 raise UsageError(message) | 1220 raise UsageError(message) |
| 1217 try: | 1221 try: |
| 1218 self.db.getclass(classname).restore(nodeid) | 1222 self.db.getclass(classname).restore(nodeid) |
| 1219 except KeyError: | 1223 except KeyError: |
| 1220 raise UsageError(_('no such class "%(classname)s"')%locals()) | 1224 raise UsageError(_('no such class "%(classname)s"') % locals()) |
| 1221 except IndexError: | 1225 except IndexError: |
| 1222 raise UsageError(_('no such %(classname)s node ' | 1226 raise UsageError(_('no such %(classname)s node ' |
| 1223 '"%(nodeid)s"')%locals()) | 1227 '" % (nodeid)s"')%locals()) |
| 1224 self.db_uncommitted = True | 1228 self.db_uncommitted = True |
| 1225 return 0 | 1229 return 0 |
| 1226 | 1230 |
| 1227 def do_export(self, args, export_files=True): | 1231 def do_export(self, args, export_files=True): |
| 1228 ''"""Usage: export [[-]class[,class]] export_dir | 1232 ''"""Usage: export [[-]class[,class]] export_dir |
| 1244 dir = args[-1] | 1248 dir = args[-1] |
| 1245 | 1249 |
| 1246 # get the list of classes to export | 1250 # get the list of classes to export |
| 1247 if len(args) == 2: | 1251 if len(args) == 2: |
| 1248 if args[0].startswith('-'): | 1252 if args[0].startswith('-'): |
| 1249 classes = [ c for c in self.db.classes | 1253 classes = [c for c in self.db.classes |
| 1250 if not c in args[0][1:].split(',') ] | 1254 if c not in args[0][1:].split(',')] |
| 1251 else: | 1255 else: |
| 1252 classes = args[0].split(',') | 1256 classes = args[0].split(',') |
| 1253 else: | 1257 else: |
| 1254 classes = self.db.classes | 1258 classes = self.db.classes |
| 1255 | 1259 |
| 1266 # do all the classes specified | 1270 # do all the classes specified |
| 1267 for classname in classes: | 1271 for classname in classes: |
| 1268 cl = self.get_class(classname) | 1272 cl = self.get_class(classname) |
| 1269 | 1273 |
| 1270 if not export_files and hasattr(cl, 'export_files'): | 1274 if not export_files and hasattr(cl, 'export_files'): |
| 1271 sys.stdout.write('Exporting %s WITHOUT the files\r\n'% | 1275 sys.stdout.write('Exporting %s WITHOUT the files\r\n' % |
| 1272 classname) | 1276 classname) |
| 1273 | 1277 |
| 1274 f = open(os.path.join(dir, classname+'.csv'), 'w') | 1278 f = open(os.path.join(dir, classname+'.csv'), 'w') |
| 1275 writer = csv.writer(f, colon_separated) | 1279 writer = csv.writer(f, colon_separated) |
| 1276 | 1280 |
| 1281 writer.writerow(fields) | 1285 writer.writerow(fields) |
| 1282 | 1286 |
| 1283 # all nodes for this class | 1287 # all nodes for this class |
| 1284 for nodeid in cl.getnodeids(): | 1288 for nodeid in cl.getnodeids(): |
| 1285 if self.verbose: | 1289 if self.verbose: |
| 1286 sys.stdout.write('\rExporting %s - %s'%(classname, nodeid)) | 1290 sys.stdout.write('\rExporting %s - %s' % |
| 1291 (classname, nodeid)) | |
| 1287 sys.stdout.flush() | 1292 sys.stdout.flush() |
| 1288 node = cl.getnode(nodeid) | 1293 node = cl.getnode(nodeid) |
| 1289 exp = cl.export_list(propnames, nodeid) | 1294 exp = cl.export_list(propnames, nodeid) |
| 1290 lensum = sum ([len (repr_export(node[p])) for p in propnames]) | 1295 lensum = sum([len(repr_export(node[p])) for p in propnames]) |
| 1291 # for a safe upper bound of field length we add | 1296 # for a safe upper bound of field length we add |
| 1292 # difference between CSV len and sum of all field lengths | 1297 # difference between CSV len and sum of all field lengths |
| 1293 d = sum ([len(x) for x in exp]) - lensum | 1298 d = sum([len(x) for x in exp]) - lensum |
| 1294 if not d > 0: | 1299 if not d > 0: |
| 1295 raise AssertionError("Bad assertion d > 0") | 1300 raise AssertionError("Bad assertion d > 0") |
| 1296 for p in propnames: | 1301 for p in propnames: |
| 1297 ll = len(repr_export(node[p])) + d | 1302 ll = len(repr_export(node[p])) + d |
| 1298 if ll > max_len: | 1303 if ll > max_len: |
| 1312 journals = csv.writer(jf, colon_separated) | 1317 journals = csv.writer(jf, colon_separated) |
| 1313 for row in cl.export_journals(): | 1318 for row in cl.export_journals(): |
| 1314 journals.writerow(row) | 1319 journals.writerow(row) |
| 1315 jf.close() | 1320 jf.close() |
| 1316 if max_len > self.db.config.CSV_FIELD_SIZE: | 1321 if max_len > self.db.config.CSV_FIELD_SIZE: |
| 1317 print("Warning: config csv_field_size should be at least %s"%max_len, file=sys.stderr) | 1322 print("Warning: config csv_field_size should be at least %s" % |
| 1323 max_len, file=sys.stderr) | |
| 1318 return 0 | 1324 return 0 |
| 1319 | 1325 |
| 1320 def do_exporttables(self, args): | 1326 def do_exporttables(self, args): |
| 1321 ''"""Usage: exporttables [[-]class[,class]] export_dir | 1327 ''"""Usage: exporttables [[-]class[,class]] export_dir |
| 1322 Export the database to colon-separated-value files, excluding the | 1328 Export the database to colon-separated-value files, excluding the |
| 1352 create a new database using the imported data, then create a new | 1358 create a new database using the imported data, then create a new |
| 1353 database (or, tediously, retire all the old data.) | 1359 database (or, tediously, retire all the old data.) |
| 1354 """ | 1360 """ |
| 1355 if len(args) < 1: | 1361 if len(args) < 1: |
| 1356 raise UsageError(_('Not enough arguments supplied')) | 1362 raise UsageError(_('Not enough arguments supplied')) |
| 1357 from roundup import hyperdb | 1363 |
| 1358 | 1364 if hasattr(csv, 'field_size_limit'): |
| 1359 if hasattr (csv, 'field_size_limit'): | |
| 1360 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) | 1365 csv.field_size_limit(self.db.config.CSV_FIELD_SIZE) |
| 1361 | 1366 |
| 1362 # directory to import from | 1367 # directory to import from |
| 1363 dir = args[0] | 1368 dir = args[0] |
| 1364 | 1369 |
| 1384 if file_props is None: | 1389 if file_props is None: |
| 1385 file_props = r | 1390 file_props = r |
| 1386 continue | 1391 continue |
| 1387 | 1392 |
| 1388 if self.verbose: | 1393 if self.verbose: |
| 1389 sys.stdout.write('\rImporting %s - %s'%(classname, n)) | 1394 sys.stdout.write('\rImporting %s - %s' % (classname, n)) |
| 1390 sys.stdout.flush() | 1395 sys.stdout.flush() |
| 1391 | 1396 |
| 1392 # do the import and figure the current highest nodeid | 1397 # do the import and figure the current highest nodeid |
| 1393 nodeid = cl.import_list(file_props, r) | 1398 nodeid = cl.import_list(file_props, r) |
| 1394 if hasattr(cl, 'import_files'): | 1399 if hasattr(cl, 'import_files'): |
| 1445 m = date_re.match(value) | 1450 m = date_re.match(value) |
| 1446 if not m: | 1451 if not m: |
| 1447 raise ValueError(_('Invalid format')) | 1452 raise ValueError(_('Invalid format')) |
| 1448 m = m.groupdict() | 1453 m = m.groupdict() |
| 1449 if m['period']: | 1454 if m['period']: |
| 1450 pack_before = date.Date(". - %s"%value) | 1455 pack_before = date.Date(". - %s" % value) |
| 1451 elif m['date']: | 1456 elif m['date']: |
| 1452 pack_before = date.Date(value) | 1457 pack_before = date.Date(value) |
| 1453 self.db.pack(pack_before) | 1458 self.db.pack(pack_before) |
| 1454 self.db_uncommitted = True | 1459 self.db_uncommitted = True |
| 1455 return 0 | 1460 return 0 |
| 1467 if m: | 1472 if m: |
| 1468 cl = self.get_class(m.group(1)) | 1473 cl = self.get_class(m.group(1)) |
| 1469 try: | 1474 try: |
| 1470 cl.index(m.group(2)) | 1475 cl.index(m.group(2)) |
| 1471 except IndexError: | 1476 except IndexError: |
| 1472 raise UsageError(_('no such item "%(designator)s"')%{ | 1477 raise UsageError(_('no such item "%(designator)s"') % { |
| 1473 'designator': arg}) | 1478 'designator': arg}) |
| 1474 else: | 1479 else: |
| 1475 cl = self.get_class(arg) | 1480 cl = self.get_class(arg) |
| 1476 self.db.reindex(arg) | 1481 self.db.reindex(arg) |
| 1477 else: | 1482 else: |
| 1485 if len(args) == 1: | 1490 if len(args) == 1: |
| 1486 role = args[0] | 1491 role = args[0] |
| 1487 try: | 1492 try: |
| 1488 roles = [(args[0], self.db.security.role[args[0]])] | 1493 roles = [(args[0], self.db.security.role[args[0]])] |
| 1489 except KeyError: | 1494 except KeyError: |
| 1490 sys.stdout.write( _('No such Role "%(role)s"\n')%locals() ) | 1495 sys.stdout.write(_('No such Role "%(role)s"\n') % locals()) |
| 1491 return 1 | 1496 return 1 |
| 1492 else: | 1497 else: |
| 1493 roles = list(self.db.security.role.items()) | 1498 roles = list(self.db.security.role.items()) |
| 1494 role = self.db.config.NEW_WEB_USER_ROLES | 1499 role = self.db.config.NEW_WEB_USER_ROLES |
| 1495 if ',' in role: | 1500 if ',' in role: |
| 1496 sys.stdout.write( _('New Web users get the Roles "%(role)s"\n')%locals() ) | 1501 sys.stdout.write(_('New Web users get the Roles "%(role)s"\n') |
| 1502 % locals()) | |
| 1497 else: | 1503 else: |
| 1498 sys.stdout.write( _('New Web users get the Role "%(role)s"\n')%locals() ) | 1504 sys.stdout.write(_('New Web users get the Role "%(role)s"\n') |
| 1505 % locals()) | |
| 1499 role = self.db.config.NEW_EMAIL_USER_ROLES | 1506 role = self.db.config.NEW_EMAIL_USER_ROLES |
| 1500 if ',' in role: | 1507 if ',' in role: |
| 1501 sys.stdout.write( _('New Email users get the Roles "%(role)s"\n')%locals() ) | 1508 sys.stdout.write(_('New Email users get the Roles "%(role)s"\n') % locals()) |
| 1502 else: | 1509 else: |
| 1503 sys.stdout.write( _('New Email users get the Role "%(role)s"\n')%locals() ) | 1510 sys.stdout.write(_('New Email users get the Role "%(role)s"\n') % locals()) |
| 1504 roles.sort() | 1511 roles.sort() |
| 1505 for rolename, role in roles: | 1512 for _rolename, role in roles: |
| 1506 sys.stdout.write( _('Role "%(name)s":\n')%role.__dict__ ) | 1513 sys.stdout.write(_('Role "%(name)s":\n') % role.__dict__) |
| 1507 for permission in role.permissions: | 1514 for permission in role.permissions: |
| 1508 d = permission.__dict__ | 1515 d = permission.__dict__ |
| 1509 if permission.klass: | 1516 if permission.klass: |
| 1510 if permission.properties: | 1517 if permission.properties: |
| 1511 sys.stdout.write( _(' %(description)s (%(name)s for "%(klass)s"' + | 1518 sys.stdout.write(_(' %(description)s (%(name)s for "%(klass)s"' + |
| 1512 ': %(properties)s only)\n')%d ) | 1519 ': %(properties)s only)\n') % d) |
| 1513 # verify that properties exist; report bad props | 1520 # verify that properties exist; report bad props |
| 1514 bad_props=[] | 1521 bad_props = [] |
| 1515 cl = self.db.getclass(permission.klass) | 1522 cl = self.db.getclass(permission.klass) |
| 1516 class_props = cl.getprops(protected=True) | 1523 class_props = cl.getprops(protected=True) |
| 1517 for p in permission.properties: | 1524 for p in permission.properties: |
| 1518 if p in class_props: | 1525 if p in class_props: |
| 1519 continue | 1526 continue |
| 1520 else: | 1527 else: |
| 1521 bad_props.append(p) | 1528 bad_props.append(p) |
| 1522 if bad_props: | 1529 if bad_props: |
| 1523 sys.stdout.write( _('\n **Invalid properties for %(class)s: %(props)s\n\n') % { "class": permission.klass, "props": bad_props }) | 1530 sys.stdout.write(_('\n **Invalid properties for %(class)s: %(props)s\n\n') % {"class": permission.klass, "props": bad_props}) |
| 1524 else: | 1531 else: |
| 1525 sys.stdout.write( _(' %(description)s (%(name)s for "%(klass)s" ' + | 1532 sys.stdout.write(_(' %(description)s (%(name)s for ' |
| 1526 'only)\n')%d ) | 1533 '"%(klass)s" only)\n') % d) |
| 1527 else: | 1534 else: |
| 1528 sys.stdout.write( _(' %(description)s (%(name)s)\n')%d ) | 1535 sys.stdout.write(_(' %(description)s (%(name)s)\n') % d) |
| 1529 return 0 | 1536 return 0 |
| 1530 | |
| 1531 | 1537 |
| 1532 def do_migrate(self, args): | 1538 def do_migrate(self, args): |
| 1533 ''"""Usage: migrate | 1539 ''"""Usage: migrate |
| 1534 Update a tracker's database to be compatible with the Roundup | 1540 Update a tracker's database to be compatible with the Roundup |
| 1535 codebase. | 1541 codebase. |
| 1536 | 1542 |
| 1537 You should run the "migrate" command for your tracker once you've | 1543 You should run the "migrate" command for your tracker once you've |
| 1538 installed the latest codebase. | 1544 installed the latest codebase. |
| 1539 | 1545 |
| 1540 Do this before you use the web, command-line or mail interface and | 1546 Do this before you use the web, command-line or mail interface and |
| 1541 before any users access the tracker. | 1547 before any users access the tracker. |
| 1542 | 1548 |
| 1543 This command will respond with either "Tracker updated" (if you've | 1549 This command will respond with either "Tracker updated" (if you've |
| 1560 """ | 1566 """ |
| 1561 command = args[0] | 1567 command = args[0] |
| 1562 | 1568 |
| 1563 # handle help now | 1569 # handle help now |
| 1564 if command == 'help': | 1570 if command == 'help': |
| 1565 if len(args)>1: | 1571 if len(args) > 1: |
| 1566 self.do_help(args[1:]) | 1572 self.do_help(args[1:]) |
| 1567 return 0 | 1573 return 0 |
| 1568 self.do_help(['help']) | 1574 self.do_help(['help']) |
| 1569 return 0 | 1575 return 0 |
| 1570 if command == 'morehelp': | 1576 if command == 'morehelp': |
| 1577 try: | 1583 try: |
| 1578 functions = self.commands.get(command) | 1584 functions = self.commands.get(command) |
| 1579 except KeyError: | 1585 except KeyError: |
| 1580 # not a valid command | 1586 # not a valid command |
| 1581 print(_('Unknown command "%(command)s" ("help commands" for a ' | 1587 print(_('Unknown command "%(command)s" ("help commands" for a ' |
| 1582 'list)')%locals()) | 1588 'list)') % locals()) |
| 1583 return 1 | 1589 return 1 |
| 1584 | 1590 |
| 1585 # check for multiple matches | 1591 # check for multiple matches |
| 1586 if len(functions) > 1: | 1592 if len(functions) > 1: |
| 1587 print(_('Multiple commands match "%(command)s": %(list)s')%{'command': | 1593 print(_('Multiple commands match "%(command)s": %(list)s') % \ |
| 1588 command, 'list': ', '.join([i[0] for i in functions])}) | 1594 {'command': command, |
| 1595 'list': ', '.join([i[0] for i in functions])}) | |
| 1589 return 1 | 1596 return 1 |
| 1590 command, function = functions[0] | 1597 command, function = functions[0] |
| 1591 | 1598 |
| 1592 # make sure we have a tracker_home | 1599 # make sure we have a tracker_home |
| 1593 while not self.tracker_home: | 1600 while not self.tracker_home: |
| 1598 | 1605 |
| 1599 # before we open the db, we may be doing an install or init | 1606 # before we open the db, we may be doing an install or init |
| 1600 if command == 'initialise': | 1607 if command == 'initialise': |
| 1601 try: | 1608 try: |
| 1602 return self.do_initialise(self.tracker_home, args) | 1609 return self.do_initialise(self.tracker_home, args) |
| 1603 except UsageError as message: | 1610 except UsageError as message: # noqa: F841 |
| 1604 print(_('Error: %(message)s')%locals()) | 1611 print(_('Error: %(message)s') % locals()) |
| 1605 return 1 | 1612 return 1 |
| 1606 elif command == 'install': | 1613 elif command == 'install': |
| 1607 try: | 1614 try: |
| 1608 return self.do_install(self.tracker_home, args) | 1615 return self.do_install(self.tracker_home, args) |
| 1609 except UsageError as message: | 1616 except UsageError as message: # noqa: F841 |
| 1610 print(_('Error: %(message)s')%locals()) | 1617 print(_('Error: %(message)s') % locals()) |
| 1611 return 1 | 1618 return 1 |
| 1612 | 1619 |
| 1613 # get the tracker | 1620 # get the tracker |
| 1614 try: | 1621 try: |
| 1615 tracker = roundup.instance.open(self.tracker_home) | 1622 tracker = roundup.instance.open(self.tracker_home) |
| 1616 except ValueError as message: | 1623 except ValueError as message: # noqa: F841 |
| 1617 self.tracker_home = '' | 1624 self.tracker_home = '' |
| 1618 print(_("Error: Couldn't open tracker: %(message)s")%locals()) | 1625 print(_("Error: Couldn't open tracker: %(message)s") % locals()) |
| 1619 return 1 | 1626 return 1 |
| 1620 except NoConfigError as message: | 1627 except NoConfigError as message: # noqa: F841 |
| 1621 self.tracker_home = '' | 1628 self.tracker_home = '' |
| 1622 print(_("Error: Couldn't open tracker: %(message)s")%locals()) | 1629 print(_("Error: Couldn't open tracker: %(message)s") % locals()) |
| 1623 return 1 | 1630 return 1 |
| 1624 | 1631 |
| 1625 # only open the database once! | 1632 # only open the database once! |
| 1626 if not self.db: | 1633 if not self.db: |
| 1627 self.db = tracker.open(self.name) | 1634 self.db = tracker.open(self.name) |
| 1630 | 1637 |
| 1631 # do the command | 1638 # do the command |
| 1632 ret = 0 | 1639 ret = 0 |
| 1633 try: | 1640 try: |
| 1634 ret = function(args[1:]) | 1641 ret = function(args[1:]) |
| 1635 except UsageError as message: | 1642 except UsageError as message: # noqa: F841 |
| 1636 print(_('Error: %(message)s')%locals()) | 1643 print(_('Error: %(message)s') % locals()) |
| 1637 print() | 1644 print() |
| 1638 print(function.__doc__) | 1645 print(function.__doc__) |
| 1639 ret = 1 | 1646 ret = 1 |
| 1640 except: | 1647 except Exception: |
| 1641 import traceback | 1648 import traceback |
| 1642 traceback.print_exc() | 1649 traceback.print_exc() |
| 1643 ret = 1 | 1650 ret = 1 |
| 1644 return ret | 1651 return ret |
| 1645 | 1652 |
| 1646 def interactive(self): | 1653 def interactive(self): |
| 1647 """Run in an interactive mode | 1654 """Run in an interactive mode |
| 1648 """ | 1655 """ |
| 1649 print(_('Roundup %s ready for input.\nType "help" for help.' | 1656 print(_('Roundup %s ready for input.\nType "help" for help.' |
| 1650 % roundup_version)) | 1657 % roundup_version)) |
| 1651 try: | 1658 try: |
| 1652 import readline | 1659 import readline # noqa: F401 |
| 1653 except ImportError: | 1660 except ImportError: |
| 1654 print(_('Note: command history and editing not available')) | 1661 print(_('Note: command history and editing not available')) |
| 1655 | 1662 |
| 1656 while 1: | 1663 while 1: |
| 1657 try: | 1664 try: |
| 1661 break | 1668 break |
| 1662 if not command: continue | 1669 if not command: continue |
| 1663 try: | 1670 try: |
| 1664 args = token.token_split(command) | 1671 args = token.token_split(command) |
| 1665 except ValueError: | 1672 except ValueError: |
| 1666 continue # Ignore invalid quoted token | 1673 continue # Ignore invalid quoted token |
| 1667 if not args: continue | 1674 if not args: continue |
| 1668 if args[0] in ('quit', 'exit'): break | 1675 if args[0] in ('quit', 'exit'): break |
| 1669 self.run_command(args) | 1676 self.run_command(args) |
| 1670 | 1677 |
| 1671 # exit.. check for transactions | 1678 # exit.. check for transactions |
| 1697 for opt, arg in opts: | 1704 for opt, arg in opts: |
| 1698 if opt == '-h': | 1705 if opt == '-h': |
| 1699 self.usage() | 1706 self.usage() |
| 1700 return 0 | 1707 return 0 |
| 1701 elif opt == '-v': | 1708 elif opt == '-v': |
| 1702 print('%s (python %s)'%(roundup_version, sys.version.split()[0])) | 1709 print('%s (python %s)' % (roundup_version, |
| 1710 sys.version.split()[0])) | |
| 1703 return 0 | 1711 return 0 |
| 1704 elif opt == '-V': | 1712 elif opt == '-V': |
| 1705 self.verbose = 1 | 1713 self.verbose = 1 |
| 1706 elif opt == '-i': | 1714 elif opt == '-i': |
| 1707 self.tracker_home = arg | 1715 self.tracker_home = arg |
| 1708 elif opt == '-c': | 1716 elif opt == '-c': |
| 1709 if self.separator != None: | 1717 if self.separator is not None: |
| 1710 self.usage('Only one of -c, -S and -s may be specified') | 1718 self.usage('Only one of -c, -S and -s may be specified') |
| 1711 return 1 | 1719 return 1 |
| 1712 self.separator = ',' | 1720 self.separator = ',' |
| 1713 elif opt == '-S': | 1721 elif opt == '-S': |
| 1714 if self.separator != None: | 1722 if self.separator is not None: |
| 1715 self.usage('Only one of -c, -S and -s may be specified') | 1723 self.usage('Only one of -c, -S and -s may be specified') |
| 1716 return 1 | 1724 return 1 |
| 1717 self.separator = arg | 1725 self.separator = arg |
| 1718 elif opt == '-s': | 1726 elif opt == '-s': |
| 1719 if self.separator != None: | 1727 if self.separator is not None: |
| 1720 self.usage('Only one of -c, -S and -s may be specified') | 1728 self.usage('Only one of -c, -S and -s may be specified') |
| 1721 return 1 | 1729 return 1 |
| 1722 self.separator = ' ' | 1730 self.separator = ' ' |
| 1723 elif opt == '-d': | 1731 elif opt == '-d': |
| 1724 self.print_designator = 1 | 1732 self.print_designator = 1 |
| 1740 return ret | 1748 return ret |
| 1741 finally: | 1749 finally: |
| 1742 if self.db: | 1750 if self.db: |
| 1743 self.db.close() | 1751 self.db.close() |
| 1744 | 1752 |
| 1753 | |
| 1745 if __name__ == '__main__': | 1754 if __name__ == '__main__': |
| 1746 tool = AdminTool() | 1755 tool = AdminTool() |
| 1747 sys.exit(tool.main()) | 1756 sys.exit(tool.main()) |
| 1748 | 1757 |
| 1749 # vim: set filetype=python sts=4 sw=4 et si : | 1758 # vim: set filetype=python sts=4 sw=4 et si : |
