Skip to content

Commit 12eeb2d

Browse files
committed
issue2550522 - add transitive searching to filter in roundup-admin
issue2550522 - Add 'filter' command to command-line interface. Filter command was actually added in 2.0.0, but this issue requested transitive searching. So that: roundup-admin -i . filter issue assignedto.username=Admin will work. This also fixes two bugs. If assignedto.username had no matches, all issues would be returned. admin.py had a find() call where the should have been a filter() call. Was tripped when -S, -c or -s were used.
1 parent 28eb9b9 commit 12eeb2d

File tree

4 files changed

+146
-3
lines changed

4 files changed

+146
-3
lines changed

CHANGES.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@ Fixed:
2323
- issue2550564 - Roundup sets "Precedence: bulk" on all outgoing mail,
2424
which seems wrong. Handle Auto-Submitted header on *inbound* email
2525
like we do precedence bulk. This is part of this issue.
26+
- roundup-admin filter calls find() not filter when using -s -c -S
27+
(John Rouillard)
28+
29+
Features:
30+
- issue2550522 - Add 'filter' command to command-line
31+
interface. Filter command was actually added in 2.0.0, but this
32+
issue requested transitive searching. So that:
33+
roundup-admin -i . filter issue assignedto.username=Admin
34+
will work. This also fixes a bug. If assignedto.username had no
35+
matches, all issues would be returned. This is also fixed.
36+
(John Rouillard)
2637

2738
2020-07-13 2.0.0
2839

roundup/admin.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -746,9 +746,33 @@ def do_filter(self, args):
746746
values = [ value ]
747747

748748
props[propname] = []
749+
# start handling transitive props
750+
# given filter issue assignedto.roles=Admin
751+
# start at issue
752+
curclass = cl
753+
lastprop = propname # handle case 'issue assignedto=admin'
754+
if '.' in propname:
755+
# start splitting transitive prop into components
756+
# we end when we have no more links
757+
for pn in propname.split('.'):
758+
try:
759+
lastprop=pn # get current component
760+
# get classname for this link
761+
try:
762+
curclassname = curclass.getprops()[pn].classname
763+
except KeyError:
764+
raise UsageError(_("Class %(curclassname)s has "
765+
"no property %(pn)s in %(propname)s." % locals()))
766+
# get class object
767+
curclass = self.get_class(curclassname)
768+
except AttributeError:
769+
# curclass.getprops()[pn].classname raises this
770+
# when we are at a non link/multilink property
771+
pass
772+
749773
for value in values:
750-
val = hyperdb.rawToHyperdb(self.db, cl, None,
751-
propname, value)
774+
val = hyperdb.rawToHyperdb(self.db, curclass, None,
775+
lastprop, value)
752776
props[propname].append(val)
753777

754778
# now do the filter
@@ -764,7 +788,7 @@ def do_filter(self, args):
764788
designator.append(classname + i)
765789
print(self.separator.join(designator))
766790
else:
767-
print(self.separator.join(cl.find(**props)))
791+
print(self.separator.join(cl.filter(None, **props)))
768792
else:
769793
if self.print_designator:
770794
id = cl.filter(None, **props)

roundup/hyperdb.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,8 @@ def search(self, search_matches=None, sort=True, retired=False):
620620
exact_match_spec[p.name] = exact
621621
if subst:
622622
filterspec[p.name] = subst
623+
elif not exact: # don't set if we have exact criteria
624+
filterspec[p.name] =[ '-1' ] # no match was found
623625
else:
624626
assert not isinstance(p.val, Exact_Match)
625627
filterspec[p.name] = p.val

test/test_admin.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,112 @@ def testFilter(self):
553553
print(out)
554554
self.assertEqual(sorted(eval(out)), ['1', '2'])
555555

556+
# Reopen the db closed by previous filter call
557+
#
558+
# case: transitive property valid match
559+
self.admin=AdminTool()
560+
with captured_output() as (out, err):
561+
sys.argv=['main', '-i', self.dirname, 'filter', 'issue',
562+
'assignedto.roles=Anonymous']
563+
ret = self.admin.main()
564+
565+
out = out.getvalue().strip()
566+
print(out)
567+
self.assertEqual(out, "['2']")
568+
569+
# Reopen the db closed by previous filter call
570+
# self.admin=AdminTool()
571+
# case: transitive propery invalid prop
572+
self.admin=AdminTool()
573+
with captured_output() as (out, err):
574+
''' assignedto is not a valid property=value, so
575+
report error.
576+
'''
577+
sys.argv=['main', '-i', self.dirname, 'filter', 'issue',
578+
'assignedto.badprop=Admin']
579+
ret = self.admin.main()
580+
581+
out = out.getvalue().strip()
582+
expected='Error: Class user has no property badprop in assignedto.badprop.'
583+
print(out[0:len(expected)])
584+
self.assertEqual(expected, out[0:len(expected)])
585+
586+
# Reopen the db closed by previous filter call
587+
#
588+
# case: transitive property invalid match
589+
self.admin=AdminTool()
590+
with captured_output() as (out, err):
591+
sys.argv=['main', '-i', self.dirname,
592+
'filter', 'issue',
593+
'assignedto.username=NoNAme']
594+
ret = self.admin.main()
595+
596+
out = out.getvalue().strip()
597+
print("me: " + out)
598+
print(err.getvalue().strip())
599+
self.assertEqual(out, "[]")
600+
601+
# Reopen the db closed by previous filter call
602+
#
603+
# case: transitive property invalid match
604+
self.admin=AdminTool()
605+
with captured_output() as (out, err):
606+
sys.argv=['main', '-i', self.dirname, '-c',
607+
'filter', 'issue',
608+
'assignedto.username=NoNAme']
609+
ret = self.admin.main()
610+
611+
out = out.getvalue().strip()
612+
print("me: " + out)
613+
print(err.getvalue().strip())
614+
self.assertEqual(out, "")
615+
616+
# Reopen the db closed by previous filter call
617+
#
618+
# case: transitive property invalid match
619+
self.admin=AdminTool()
620+
with captured_output() as (out, err):
621+
sys.argv=['main', '-i', self.dirname, '-c',
622+
'filter', 'issue',
623+
'assignedto.username=A']
624+
ret = self.admin.main()
625+
626+
out = out.getvalue().strip()
627+
print("me: " + out)
628+
print(err.getvalue().strip())
629+
self.assertEqual(out, "1,2")
630+
631+
# Reopen the db closed by previous filter call
632+
#
633+
# case: transitive property invalid match
634+
self.admin=AdminTool()
635+
with captured_output() as (out, err):
636+
sys.argv=['main', '-i', self.dirname, '-s',
637+
'filter', 'issue',
638+
'assignedto.username=A']
639+
ret = self.admin.main()
640+
641+
out = out.getvalue().strip()
642+
print("me: " + out)
643+
print(err.getvalue().strip())
644+
self.assertEqual(out, "1 2")
645+
646+
# Reopen the db closed by previous filter call
647+
#
648+
# case: transitive property invalid match
649+
self.admin=AdminTool()
650+
with captured_output() as (out, err):
651+
sys.argv=['main', '-i', self.dirname, '-S', ':',
652+
'filter', 'issue',
653+
'assignedto.username=A']
654+
ret = self.admin.main()
655+
656+
out = out.getvalue().strip()
657+
print("me: " + out)
658+
print(err.getvalue().strip())
659+
self.assertEqual(out, "1:2")
660+
661+
556662
def disabletestHelpInitopts(self):
557663

558664
''' Note the tests will fail if you run this under pdb.

0 commit comments

Comments
 (0)