Skip to content
Snippets Groups Projects
Commit f5cefc99 authored by Megan Henning's avatar Megan Henning
Browse files

Merge pull request #240 from scitran/add-tags-to-groups

Add tags to groups
parents 6826826c cdd55427
No related branches found
No related tags found
No related merge requests found
......@@ -61,7 +61,7 @@ routing_regexes = {
# tag regex
# length between 3 and 24 characters
# any character allowed except '/''
'tag_re': '[^/]{3,24}',
'tag_re': '[^/]{1,32}',
# filename regex
# any character allowed except '/'
'filename_re': '[^/]+',
......@@ -128,8 +128,12 @@ routes = [
webapp2.Route(_format(r'/api/<cont_name:groups>/<cid:{group_id_re}>/<list_name:roles>'), listhandler.ListHandler, name='group_roles_post'),
webapp2.Route(_format(r'/api/<cont_name:groups>/<cid:{group_id_re}>/<list_name:roles>/<site:{site_id_re}>/<_id:{user_id_re}>'), listhandler.ListHandler, name='group_roles', methods=['GET', 'PUT', 'DELETE']),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/<list_name:tags>'), listhandler.ListHandler, methods=['POST'], name='tags_post'),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/<list_name:tags>/<value:{tag_re}>'), listhandler.ListHandler, name='tags'),
webapp2.Route(_format(r'/api/<cont_name:groups>/<cid:{group_id_re}>/<list_name:tags>'), listhandler.TagsListHandler, methods=['POST'], name='tags_post'),
webapp2.Route(_format(r'/api/<cont_name:groups>/<cid:{group_id_re}>/<list_name:tags>/<value:{tag_re}>'), listhandler.TagsListHandler, name='tags'),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/<list_name:tags>'), listhandler.TagsListHandler, methods=['POST'], name='tags_post'),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/<list_name:tags>/<value:{tag_re}>'), listhandler.TagsListHandler, name='tags'),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/packfile'), listhandler.FileListHandler, name='packfile', handler_method='packfile', methods=['POST']),
webapp2.Route(_format(r'/api/<cont_name:{cont_name_re}>/<cid:{cid_re}>/<list_name:files>'), listhandler.FileListHandler, name='files_post', methods=['POST']),
......
......@@ -52,6 +52,22 @@ def group_roles_sublist(handler, container):
return f
return g
def group_tags_sublist(handler, container):
"""
This is the customized permissions checker for tags operations.
"""
access = _get_access(handler.uid, handler.user_site, container)
def g(exec_op):
def f(method, _id, query_params = None, payload = None, exclude_params=None):
if method == 'GET' and access >= INTEGER_ROLES['ro']:
return exec_op(method, _id, query_params, payload, exclude_params)
elif access >= INTEGER_ROLES['admin']:
return exec_op(method, _id, query_params, payload, exclude_params)
else:
handler.abort(403, 'user not authorized to perform a {} operation on the list'.format(method))
return f
return g
def permissions_sublist(handler, container):
"""
the customized permissions checker for permissions operations.
......
......@@ -67,7 +67,14 @@ def initialize_list_configurations():
'get_full_container': True,
'storage_schema_file': 'permission.json',
'input_schema_file': 'permission.json'
}
},
'tags': {
'storage': liststorage.StringListStorage,
'permchecker': listauth.group_tags_sublist,
'use_object_id': False,
'storage_schema_file': 'tag.json',
'input_schema_file': 'tag.json'
},
},
'projects': copy.deepcopy(container_default_configurations),
'sessions': copy.deepcopy(container_default_configurations),
......@@ -205,6 +212,7 @@ class PermissionsListHandler(ListHandler):
def put(self, cont_name, list_name, **kwargs):
_id = kwargs.get('cid')
result = super(PermissionsListHandler, self).put(cont_name, list_name, **kwargs)
if cont_name == 'projects':
self._propagate_project_permissions(_id)
......@@ -275,6 +283,55 @@ class NotesListHandler(ListHandler):
return {'modified':result.modified_count}
class TagsListHandler(ListHandler):
"""
TagsListHandler overrides put, delete methods of ListHandler to propagate changes to group tags
If a tag is renamed or deleted at the group level, project, session and acquisition tags will also be renamed/deleted
"""
def put(self, cont_name, list_name, **kwargs):
_id = kwargs.get('cid')
result = super(TagsListHandler, self).put(cont_name, list_name, **kwargs)
if cont_name == 'groups':
payload = self.request.json_body
current_value = kwargs.get('value')
new_value = payload.get('value')
query = {'$and':[{'tags': current_value}, {'tags': {'$ne': new_value}}]}
update = {'$set': {'tags.$': new_value}}
self._propagate_group_tags(_id, query, update)
return result
def delete(self, cont_name, list_name, **kwargs):
_id = kwargs.get('cid')
result = super(TagsListHandler, self).delete(cont_name, list_name, **kwargs)
if cont_name == 'groups':
deleted_tag = kwargs.get('value')
query = {}
update = {'$pull': {'tags': deleted_tag}}
self._propagate_group_tags(_id, query, update)
def _propagate_group_tags(self, _id, query, update):
"""
method to propagate tag changes from a group to its projects, sessions and acquisitions
"""
try:
project_ids = [p['_id'] for p in config.db.projects.find({'group': _id}, [])]
session_ids = [s['_id'] for s in config.db.sessions.find({'project': {'$in': project_ids}}, [])]
project_q = query.copy()
project_q['_id'] = {'$in': project_ids}
session_q = query.copy()
session_q['_id'] = {'$in': session_ids}
acquisition_q = query.copy()
acquisition_q['session'] = {'$in': session_ids}
config.db.projects.update_many(project_q, update)
config.db.sessions.update_many(session_q, update)
config.db.acquisitions.update_many(acquisition_q, update)
except:
log.debug(e)
self.abort(500, 'tag change not propagated down heirarchy from group {}'.format(_id))
class FileListHandler(ListHandler):
"""
......
......@@ -2,7 +2,7 @@
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"value": {"type": "string"}
"value": {"type": "string", "minLength": 1, "maxLength": 32}
},
"required": ["value"],
"additionalProperties": false
......
......@@ -11,11 +11,14 @@ def test_tags(with_a_group_and_a_project, api_as_admin):
tag = 'test_tag'
new_tag = 'new_test_tag'
other_tag = 'other_test_tag'
short_tag = 't'
too_long_tag = 'this_tag_is_much_too_long_only_allow_32_characters'
tags_path = '/projects/' + data.project_id + '/tags'
tag_path = tags_path + '/' + tag
new_tag_path = tags_path + '/' + new_tag
other_tag_path = tags_path + '/' + other_tag
short_tag_path = tags_path + '/' + short_tag
# Add tag and verify
r = api_as_admin.get(tag_path)
......@@ -39,6 +42,15 @@ def test_tags(with_a_group_and_a_project, api_as_admin):
assert r.ok
assert json.loads(r.content) == new_tag
# Add short tag and verify
payload = json.dumps({'value': short_tag})
r = api_as_admin.post(tags_path, data=payload)
assert r.ok
# Add too long tag and verify
payload = json.dumps({'value': too_long_tag})
r = api_as_admin.post(tags_path, data=payload)
assert r.status_code == 400
# Attempt to update tag, returns 404
payload = json.dumps({'value': new_tag})
r = api_as_admin.put(tag_path, data=payload)
......@@ -65,3 +77,7 @@ def test_tags(with_a_group_and_a_project, api_as_admin):
assert r.ok
r = api_as_admin.get(new_tag_path)
assert r.status_code == 404
r = api_as_admin.delete(short_tag_path) # url for 'DELETE' is the same as the one for 'GET'
assert r.ok
r = api_as_admin.get(short_tag_path)
assert r.status_code == 404
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment