Mercurial > p > roundup > code
changeset 6176:d25638d1826c
Add roundup-admin filter command; fix bad doc example; add tests
admin_guide.txt had an example using find with username prop. This is
wrong. Find only works with links not string. Fix it to use filter.
Add filter command to roundup-admin.
Add tests for filter, specification and find.
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Mon, 18 May 2020 23:28:03 -0400 |
| parents | 72a69753f49a |
| children | 41907e1f9c3f |
| files | CHANGES.txt doc/admin_guide.txt roundup/admin.py share/man/man1/roundup-admin.1 test/test_admin.py |
| diffstat | 5 files changed, 215 insertions(+), 4 deletions(-) [+] |
line wrap: on
line diff
--- a/CHANGES.txt Sat May 16 21:20:25 2020 -0400 +++ b/CHANGES.txt Mon May 18 23:28:03 2020 -0400 @@ -41,6 +41,9 @@ - Index created for documentation. Links created for website docs and released docs. Needs more refinement, but it exists at least. (John Rouillard) +- New filter command defined in roundup-admin. (Partial fix for + issue724648.) (John Rouillard) + 2020-04-05 2.0.0 beta 0
--- a/doc/admin_guide.txt Sat May 16 21:20:25 2020 -0400 +++ b/doc/admin_guide.txt Mon May 18 23:28:03 2020 -0400 @@ -386,13 +386,14 @@ (or if you know their username, and it happens to be "richard"):: - roundup-admin find username=richard + roundup-admin filter user username=richard -then using the user id you get from one of the above commands, you may -display the user's details:: +then using the user id (e.g. 5) you get from one of the above +commands, you may display the user's details:: - roundup-admin display <userid> + roundup-admin display <designator> +where designator is ``user5``. Running the Servers ===================
--- a/roundup/admin.py Sat May 16 21:20:25 2020 -0400 +++ b/roundup/admin.py Mon May 18 23:28:03 2020 -0400 @@ -718,6 +718,65 @@ self.db_uncommitted = True return 0 + def do_filter(self, args): + ''"""Usage: filter classname propname=value ... + Find the nodes of the given class with a given property value. + + Find the nodes of the given class with a given property value. + Multiple values can be specified by separating them with commas. + If property is a string, all values must match. I.E. it's an + 'and' operation. If the property is a link/multilink any value + matches. I.E. an 'or' operation. + """ + if len(args) < 1: + raise UsageError(_('Not enough arguments supplied')) + classname = args[0] + # get the class + cl = self.get_class(classname) + + # handle the propname=value argument + props = self.props_from_args(args[1:]) + + # convert the user-input value to a value used for filter + # multiple , separated values become a list + for propname, value in props.items(): + if ',' in value: + values = value.split(',') + else: + values = value + + props[propname] = values + + # now do the filter + try: + id = [] + designator = [] + props = { "filterspec": props } + + if self.separator: + if self.print_designator: + id = cl.filter(None, **props) + for i in id: + designator.append(classname + i) + print(self.separator.join(designator), file=sys.stdout) + else: + print(self.separator.join(cl.find(**props)), + file=sys.stdout) + else: + if self.print_designator: + id = cl.filter(None, **props) + for i in id: + designator.append(classname + i) + print(designator,file=sys.stdout) + else: + print(cl.filter(None, **props), file=sys.stdout) + except KeyError: + raise UsageError(_('%(classname)s has no property ' + '"%(propname)s"') % locals()) + except (ValueError, TypeError) as message: + raise UsageError(message) + return 0 + def do_find(self, args): ''"""Usage: find classname propname=value ... Find the nodes of the given class with a given link property value.
--- a/share/man/man1/roundup-admin.1 Sat May 16 21:20:25 2020 -0400 +++ b/share/man/man1/roundup-admin.1 Mon May 18 23:28:03 2020 -0400 @@ -89,6 +89,13 @@ files below $TRACKER_HOME/db/files/ (which can be archived separately). To include the files, use the export command. .TP +\fBfilter\fP \fIclassname propname=value ...\fP +Find the nodes of the given class with a given property value. +Multiple values can be specified by separating them with commas. +If property is a string, all values must match. I.E. it's an +'and' operation. If the property is a link/multilink any value +matches. I.E. an 'or' operation. +.TP \fBfind\fP \fIclassname propname=value ...\fP Find the nodes of the given class with a given link property value. .TP
--- a/test/test_admin.py Sat May 16 21:20:25 2020 -0400 +++ b/test/test_admin.py Mon May 18 23:28:03 2020 -0400 @@ -13,6 +13,24 @@ from .test_mysql import skip_mysql from .test_postgresql import skip_postgresql +# https://stackoverflow.com/questions/4219717/how-to-assert-output-with-nosetest-unittest-in-python +# lightly modified +from contextlib import contextmanager +_py3 = sys.version_info[0] > 2 +if _py3: + from io import StringIO # py3 +else: + from StringIO import StringIO # py2 + +@contextmanager +def captured_output(): + new_out, new_err = StringIO(), StringIO() + old_out, old_err = sys.stdout, sys.stderr + try: + sys.stdout, sys.stderr = new_out, new_err + yield sys.stdout, sys.stderr + finally: + sys.stdout, sys.stderr = old_out, old_err class AdminTest(object): @@ -27,6 +45,27 @@ except OSError as error: if error.errno not in (errno.ENOENT, errno.ESRCH): raise + def install_init(self, type="classic", + settings="mail_domain=example.com," + + "mail_host=localhost," + "tracker_web=http://test/" ): + ''' install tracker with settings for required config.ini settings. + ''' + + admin=AdminTool() + + # Run under context manager to suppress output of help text. + with captured_output() as (out, err): + sys.argv=['main', '-i', '_test_admin', 'install', + type, self.backend, settings ] + ret = admin.main() + self.assertEqual(ret, 0) + + # initialize tracker with initial_data.py. Put password + # on cli so I don't have to respond to prompting. + sys.argv=['main', '-i', '_test_admin', 'initialise', 'admin'] + ret = admin.main() + self.assertEqual(ret, 0) + def testInit(self): import sys self.admin=AdminTool() @@ -68,7 +107,109 @@ self.assertTrue(os.path.isfile(self.dirname + "/schema.py")) config=CoreConfig(self.dirname) self.assertEqual(config['MAIL_DEBUG'], self.dirname + "/SendMail.LOG") + + def testFind(self): + ''' Note the tests will fail if you run this under pdb. + the context managers capture the pdb prompts and this screws + up the stdout strings with (pdb) prefixed to the line. + ''' + import sys, json + + self.admin=AdminTool() + self.install_init() + + with captured_output() as (out, err): + sys.argv=['main', '-i', '_test_admin', 'create', 'issue', + 'title="foo bar"', 'assignedto=admin' ] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + self.assertEqual(out, '1') + + self.admin=AdminTool() + with captured_output() as (out, err): + sys.argv=['main', '-i', '_test_admin', 'create', 'issue', + 'title="bar foo bar"', 'assignedto=anonymous' ] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + self.assertEqual(out, '2') + + self.admin=AdminTool() + with captured_output() as (out, err): + sys.argv=['main', '-i', '_test_admin', 'find', 'issue', + 'assignedto=1'] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + self.assertEqual(out, "['1']") + + # Reopen the db closed by previous filter call + self.admin=AdminTool() + with captured_output() as (out, err): + ''' 1,2 should return all entries that have assignedto + either admin or anonymous + ''' + sys.argv=['main', '-i', '_test_admin', 'find', 'issue', + 'assignedto=1,2'] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + # out can be "['2', '1']" or "['1', '2']" + # so eval to real list so Equal can do a list compare + self.assertEqual(sorted(eval(out)), ['1', '2']) + + # Reopen the db closed by previous filter call + self.admin=AdminTool() + with captured_output() as (out, err): + ''' 1,2 should return all entries that have assignedto + either admin or anonymous + ''' + sys.argv=['main', '-i', '_test_admin', 'find', 'issue', + 'assignedto=admin,anonymous'] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + # out can be "['2', '1']" or "['1', '2']" + # so eval to real list so Equal can do a list compare + self.assertEqual(sorted(eval(out)), ['1', '2']) + + def testSpecification(self): + ''' Note the tests will fail if you run this under pdb. + the context managers capture the pdb prompts and this screws + up the stdout strings with (pdb) prefixed to the line. + ''' + import sys + + self.install_init() + self.admin=AdminTool() + + import inspect + spec='''username: <roundup.hyperdb.String> (key property) + alternate_addresses: <roundup.hyperdb.String> + realname: <roundup.hyperdb.String> + roles: <roundup.hyperdb.String> + organisation: <roundup.hyperdb.String> + queries: <roundup.hyperdb.Multilink to "query"> + phone: <roundup.hyperdb.String> + address: <roundup.hyperdb.String> + timezone: <roundup.hyperdb.String> + password: <roundup.hyperdb.Password>''' + + spec = inspect.cleandoc(spec) + with captured_output() as (out, err): + sys.argv=['main', '-i', '_test_admin', 'specification', 'user'] + ret = self.admin.main() + + out = out.getvalue().strip() + print(out) + self.assertEqual(out, spec) class anydbmAdminTest(AdminTest, unittest.TestCase):
