Skip to content

Commit f80c62d

Browse files
committed
REST: Add key lookup
E.g. /data/status/open or /data/status/name=open Also update documentation and tests
1 parent e636710 commit f80c62d

File tree

3 files changed

+56
-7
lines changed

3 files changed

+56
-7
lines changed

doc/rest.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,17 @@ link for the file or message. If @verbose is >= 3, the content property
9797
is shown in json as a (possibly very long) string. Currently the json
9898
serializer cannot handle files not properly utf-8 encoded, so specifying
9999
@verbose=3 for files is currently discouraged.
100+
Note that if the class has a key attribute (like e.g., the 'status'
101+
class in the classic tracker), you can get an individual status by
102+
specifying the key-attribute e.g. ``/data/status/name=closed``. Note
103+
that ``name`` in this example must be the key-attribute of the class.
104+
A short-form (which might no longer be supported in future version of
105+
the API) is to specify only the value, e.g. ``/data/status/closed``.
106+
This short-form only works when you're sure that the key of the class is
107+
not numeric.
108+
The long-form (with ``=``) is different from a query-parameter like
109+
``/data/status?@name=closed`` which would find all stati that have
110+
``closed`` as a substring.
100111

101112
A ``GET`` method on a property (e.g. ``/data/issue/42/title``) returns the
102113
link, an ``@etag``, the type of the property (e.g. "<type str>") the id

roundup/rest.py

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,8 @@ def get_element(self, class_name, item_id, input):
605605
Args:
606606
class_name (string): class name of the resource (Ex: issue, msg)
607607
item_id (string): id of the resource (Ex: 12, 15)
608+
or (if the class has a key property) this can also be
609+
the key name, e.g. class_name = status, item_id = 'open'
608610
input (list): the submitted form of the user
609611
610612
Returns:
@@ -617,16 +619,37 @@ def get_element(self, class_name, item_id, input):
617619
"""
618620
if class_name not in self.db.classes:
619621
raise NotFound('Class %s not found' % class_name)
622+
class_obj = self.db.getclass(class_name)
623+
uid = self.db.getuid()
624+
# If it's not numeric it is a key
625+
if item_id.isdigit():
626+
id = item_id
627+
else:
628+
keyprop = class_obj.getkey()
629+
try:
630+
k, v = item_id.split('=', 1)
631+
if k != keyprop:
632+
raise UsageError ("Not key property")
633+
except ValueError:
634+
v = item_id
635+
pass
636+
if not self.db.security.hasPermission(
637+
'View', uid, class_name, itemid=item_id, property=keyprop
638+
):
639+
raise Unauthorised(
640+
'Permission to view %s%s.%s denied'
641+
% (class_name, item_id, keyprop)
642+
)
643+
id = class_obj.lookup(v)
620644
if not self.db.security.hasPermission(
621-
'View', self.db.getuid(), class_name, itemid=item_id
645+
'View', uid, class_name, itemid=id
622646
):
623647
raise Unauthorised(
624-
'Permission to view %s%s denied' % (class_name, item_id)
648+
'Permission to view %s%s denied' % (class_name, id)
625649
)
626650

627-
class_obj = self.db.getclass(class_name)
628-
node = class_obj.getnode(item_id)
629-
etag = calculate_etag(node, class_name, item_id)
651+
node = class_obj.getnode(id)
652+
etag = calculate_etag(node, class_name, id)
630653
props = None
631654
protected=False
632655
verbose=1
@@ -651,7 +674,7 @@ def get_element(self, class_name, item_id, input):
651674
for pn in sorted(props):
652675
prop = props[pn]
653676
if not self.db.security.hasPermission(
654-
'View', uid, class_name, pn, item_id
677+
'View', uid, class_name, pn, id
655678
):
656679
continue
657680
v = getattr(node, pn)
@@ -690,7 +713,7 @@ def get_element(self, class_name, item_id, input):
690713
except KeyError as msg:
691714
raise UsageError("%s field not valid" % msg)
692715
result = {
693-
'id': item_id,
716+
'id': id,
694717
'type': class_name,
695718
'link': '%s/%s/%s' % (self.data_path, class_name, item_id),
696719
'attributes': dict(result),

test/rest_common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,21 @@ def testGet(self):
116116
self.assertEqual(results['attributes']['username'], 'joe')
117117
self.assertEqual(results['attributes']['realname'], 'Joe Random')
118118

119+
# Obtain data for 'joe' via username lookup.
120+
results = self.server.get_element('user', 'joe', self.empty_form)
121+
results = results['data']
122+
self.assertEqual(self.dummy_client.response_code, 200)
123+
self.assertEqual(results['attributes']['username'], 'joe')
124+
self.assertEqual(results['attributes']['realname'], 'Joe Random')
125+
126+
# Obtain data for 'joe' via username lookup (long form).
127+
key = 'username=joe'
128+
results = self.server.get_element('user', key, self.empty_form)
129+
results = results['data']
130+
self.assertEqual(self.dummy_client.response_code, 200)
131+
self.assertEqual(results['attributes']['username'], 'joe')
132+
self.assertEqual(results['attributes']['realname'], 'Joe Random')
133+
119134
# Obtain data for 'joe'.
120135
results = self.server.get_attribute(
121136
'user', self.joeid, 'username', self.empty_form

0 commit comments

Comments
 (0)