From 7a54d92801f66fa75edc78947976e173ab602086 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 12:56:56 +0000 Subject: [PATCH 01/23] Blitz Dataset getQueryString() handles filter by 'image' --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index beaa744421d..6cce0c7444d 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -5732,11 +5732,17 @@ def _getQueryString(cls, opts=None): """ query, clauses, params = super( _DatasetWrapper, cls)._getQueryString(opts) - if opts is not None and 'project' in opts: + if opts is None: + opts = {} + if 'project' in opts: query += ' join obj.projectLinks plink' clauses.append('plink.parent.id = :pid') params.add('pid', rlong(opts['project'])) - if opts is not None and opts.get('orphaned'): + if 'image' in opts: + query += ' join obj.imageLinks imagelinks' + clauses.append('imagelinks.child.id = :iid') + params.add('iid', rlong(opts['image'])) + if opts.get('orphaned'): clauses.append( """ not exists ( From 0bf88d27170ccd6eff69e9f511830f48c7761727 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 12:57:31 +0000 Subject: [PATCH 02/23] api DatasetsView handles ?image=1 --- components/tools/OmeroWeb/omeroweb/api/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 4c7add6389e..ca69bc57b48 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -374,6 +374,10 @@ def get_opts(self, request, **kwargs): project = getIntOrDefault(request, 'project', None) if project is not None: opts['project'] = project + # Filter Datasets by child 'image' + image = getIntOrDefault(request, 'image', None) + if image is not None: + opts['image'] = image return opts # Urls to add to marshalled object. See ProjectsView for more details From ec492b86eedbf0ff7543f6522975bc5059f143ee Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 13:04:46 +0000 Subject: [PATCH 03/23] Blitz Project getQueryString() support filter by 'dataset' --- .../OmeroPy/src/omero/gateway/__init__.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 6cce0c7444d..24ec1a9ae09 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -5725,6 +5725,7 @@ def _getQueryString(cls, opts=None): Extend base query to handle filtering of Datasets by Projects. Returns a tuple of (query, clauses, params). Supported opts: 'project': to filter by Project + 'image': to filter by child Image 'orphaned': . Filter by 'not in Project' :param opts: Dictionary of optional parameters. @@ -5780,6 +5781,26 @@ class _ProjectWrapper (BlitzObjectWrapper): CHILD_WRAPPER_CLASS = 'DatasetWrapper' PARENT_WRAPPER_CLASS = None + @classmethod + def _getQueryString(cls, opts=None): + """ + Extend base query to handle filtering of Projects by Datasets. + Returns a tuple of (query, clauses, params). + Supported opts: 'dataset': to filter by Dataset + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + query, clauses, params = super( + _ProjectWrapper, cls)._getQueryString(opts) + if opts is None: + opts = {} + if 'dataset' in opts: + query += ' join obj.datasetLinks datasetLinks' + clauses.append('datasetLinks.child.id = :dataset_id') + params.add('dataset_id', rlong(opts['dataset'])) + return (query, clauses, params) + ProjectWrapper = _ProjectWrapper From cd2ea10541c9e8f207a58cb50db91e40fc7eebea Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 13:05:17 +0000 Subject: [PATCH 04/23] api ProjectsView supports filter by ?dataset=1 --- components/tools/OmeroWeb/omeroweb/api/views.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index ca69bc57b48..afee3e10b4b 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -343,6 +343,10 @@ def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ProjectsView, self).get_opts(request, **kwargs) opts['order_by'] = 'lower(obj.name)' + # Filter Projects by child 'dataset' + dataset = getIntOrDefault(request, 'dataset', None) + if dataset is not None: + opts['dataset'] = dataset return opts # To add a url to marshalled object add to this dict From 415c0958eacb2db14245a988244aff864574ff1b Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 15:37:34 +0000 Subject: [PATCH 05/23] url:datasets and url:projects for browsing I->D->P --- .../tools/OmeroWeb/omeroweb/api/urls.py | 20 +++++++++++ .../tools/OmeroWeb/omeroweb/api/views.py | 33 +++++++++++++++---- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index fe04b93a5cb..88345908ab1 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -122,6 +122,15 @@ GET Images in Dataset, using omero-marshal to generate json """ +api_dataset_projects = url( + r'^v(?P%s)/m/datasets/' + '(?P[0-9]+)/projects/$' % versions, + views.ProjectsView.as_view(), + name='api_dataset_projects') +""" +GET Projects in Dataset, using omero-marshal to generate json +""" + api_image = url( r'^v(?P%s)/m/images/(?P[0-9]+)/$' % versions, views.ImageView.as_view(), @@ -130,6 +139,15 @@ Image url to GET or DELETE a single Image """ +api_image_datasets = url( + r'^v(?P%s)/m/images/' + '(?P[0-9]+)/datasets/$' % versions, + views.DatasetsView.as_view(), + name='api_image_datasets') +""" +GET Datasets parents of Image, using omero-marshal to generate json +""" + api_screen = url( r'^v(?P%s)/m/screens/(?P[0-9]+)/$' % versions, views.ScreenView.as_view(), @@ -208,7 +226,9 @@ api_dataset, api_images, api_dataset_images, + api_dataset_projects, api_image, + api_image_datasets, api_screen, api_screens, api_plates, diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index afee3e10b4b..99c2081a476 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -224,6 +224,8 @@ class DatasetView(ObjectView): urls = { 'url:images': {'name': 'api_dataset_images', 'kwargs': {'dataset_id': 'OBJECT_ID'}}, + 'url:projects': {'name': 'api_dataset_projects', + 'kwargs': {'dataset_id': 'OBJECT_ID'}}, } @@ -234,6 +236,12 @@ class ImageView(ObjectView): CAN_DELETE = False + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'url:datasets': {'name': 'api_image_datasets', + 'kwargs': {'image_id': 'OBJECT_ID'}}, + } + def get_opts(self, request): """Add support for load_pixels and load_channels.""" opts = super(ImageView, self).get_opts(request) @@ -343,10 +351,14 @@ def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ProjectsView, self).get_opts(request, **kwargs) opts['order_by'] = 'lower(obj.name)' - # Filter Projects by child 'dataset' - dataset = getIntOrDefault(request, 'dataset', None) - if dataset is not None: - opts['dataset'] = dataset + # at /datasets/:dataset_id/projects/ we have 'dataset_id' in kwargs + if 'dataset_id' in kwargs: + opts['dataset'] = long(kwargs['dataset_id']) + else: + # Filter Projects by child 'dataset' + dataset = getIntOrDefault(request, 'dataset', None) + if dataset is not None: + opts['dataset'] = dataset return opts # To add a url to marshalled object add to this dict @@ -379,9 +391,12 @@ def get_opts(self, request, **kwargs): if project is not None: opts['project'] = project # Filter Datasets by child 'image' - image = getIntOrDefault(request, 'image', None) - if image is not None: - opts['image'] = image + if 'image_id' in kwargs: + opts['image'] = long(kwargs['image_id']) + else: + image = getIntOrDefault(request, 'image', None) + if image is not None: + opts['image'] = image return opts # Urls to add to marshalled object. See ProjectsView for more details @@ -390,6 +405,8 @@ def get_opts(self, request, **kwargs): 'kwargs': {'dataset_id': 'OBJECT_ID'}}, 'url:dataset': {'name': 'api_dataset', 'kwargs': {'object_id': 'OBJECT_ID'}}, + 'url:projects': {'name': 'api_dataset_projects', + 'kwargs': {'dataset_id': 'OBJECT_ID'}}, } @@ -450,6 +467,8 @@ class ImagesView(ObjectsView): urls = { 'url:image': {'name': 'api_image', 'kwargs': {'object_id': 'OBJECT_ID'}}, + 'url:datasets': {'name': 'api_image_datasets', + 'kwargs': {'image_id': 'OBJECT_ID'}}, } def get_opts(self, request, **kwargs): From dac376dac04585d800b7b3982082f3ae5c8874a8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 16:10:29 +0000 Subject: [PATCH 06/23] Blitz Screen getQueryString() supports filtering by 'plate' --- .../OmeroPy/src/omero/gateway/__init__.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 24ec1a9ae09..63c4d4ea274 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -5814,6 +5814,26 @@ class _ScreenWrapper (BlitzObjectWrapper): CHILD_WRAPPER_CLASS = 'PlateWrapper' PARENT_WRAPPER_CLASS = None + @classmethod + def _getQueryString(cls, opts=None): + """ + Extend base query to handle filtering of Screens by Plate. + Returns a tuple of (query, clauses, params). + Supported opts: 'plate': to filter by Plate + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + query, clauses, params = super( + _ScreenWrapper, cls)._getQueryString(opts) + if opts is None: + opts = {} + if 'plate' in opts: + query += ' join obj.plateLinks plateLinks' + clauses.append('plateLinks.child.id = :plate_id') + params.add('plate_id', rlong(opts['plate'])) + return (query, clauses, params) + ScreenWrapper = _ScreenWrapper From 06201794930aa674e34580e56f4b22ca6733ed1f Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 16:12:18 +0000 Subject: [PATCH 07/23] api support filtering /screens/?plate=id and plate/:id/screens --- components/tools/OmeroWeb/omeroweb/api/urls.py | 10 ++++++++++ components/tools/OmeroWeb/omeroweb/api/views.py | 13 +++++++++++++ 2 files changed, 23 insertions(+) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index 88345908ab1..bbaaacef65c 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -211,6 +211,15 @@ Well url to GET or DELETE a single Well """ +api_plate_screens = url( + r'^v(?P%s)/m/plates/' + '(?P[0-9]+)/screens/$' % versions, + views.ScreensView.as_view(), + name='api_plate_screens') +""" +GET Screens for child Plate, using omero-marshal to generate json +""" + urlpatterns = patterns( '', api_versions, @@ -237,4 +246,5 @@ api_wells, api_plate_wells, api_well, + api_plate_screens, ) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 99c2081a476..af7e1dcb5d9 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -429,6 +429,19 @@ def get_opts(self, request, **kwargs): 'kwargs': {'object_id': 'OBJECT_ID'}} } + def get_opts(self, request, **kwargs): + """Add filtering by 'plate' to the opts dict.""" + opts = super(ScreensView, self).get_opts(request, **kwargs) + # at /plate/:plate_id/screens/ we have 'plate_id' in kwargs + if 'plate_id' in kwargs: + opts['plate'] = long(kwargs['plate_id']) + else: + # filter by query /screens/?plate=:id + plate = getIntOrDefault(request, 'plate', None) + if plate is not None: + opts['plate'] = plate + return opts + class PlatesView(ObjectsView): """Handles GET for /plates/ to list available Plates.""" From b5972a6f49ce54aefb3ee86014a483a6383ba570 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 7 Feb 2017 16:26:13 +0000 Subject: [PATCH 08/23] Add 'url:screens' to plate json --- .../tools/OmeroWeb/omeroweb/api/views.py | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index af7e1dcb5d9..e2df7258468 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -272,7 +272,9 @@ class PlateView(ObjectView): # Urls to add to marshalled object. See ProjectsView for more details urls = { 'url:wells': {'name': 'api_plate_wells', - 'kwargs': {'plate_id': 'OBJECT_ID'}} + 'kwargs': {'plate_id': 'OBJECT_ID'}}, + 'url:screens': {'name': 'api_plate_screens', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, } @@ -419,19 +421,6 @@ def get_opts(self, request, **kwargs): """Add extra parameters to the opts dict.""" opts = super(ScreensView, self).get_opts(request, **kwargs) opts['order_by'] = 'lower(obj.name)' - return opts - - # Urls to add to marshalled object. See ProjectsView for more details - urls = { - 'url:plates': {'name': 'api_screen_plates', - 'kwargs': {'screen_id': 'OBJECT_ID'}}, - 'url:screen': {'name': 'api_screen', - 'kwargs': {'object_id': 'OBJECT_ID'}} - } - - def get_opts(self, request, **kwargs): - """Add filtering by 'plate' to the opts dict.""" - opts = super(ScreensView, self).get_opts(request, **kwargs) # at /plate/:plate_id/screens/ we have 'plate_id' in kwargs if 'plate_id' in kwargs: opts['plate'] = long(kwargs['plate_id']) @@ -442,6 +431,14 @@ def get_opts(self, request, **kwargs): opts['plate'] = plate return opts + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'url:plates': {'name': 'api_screen_plates', + 'kwargs': {'screen_id': 'OBJECT_ID'}}, + 'url:screen': {'name': 'api_screen', + 'kwargs': {'object_id': 'OBJECT_ID'}} + } + class PlatesView(ObjectsView): """Handles GET for /plates/ to list available Plates.""" @@ -467,7 +464,9 @@ def get_opts(self, request, **kwargs): 'url:wells': {'name': 'api_plate_wells', 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:plate': {'name': 'api_plate', - 'kwargs': {'object_id': 'OBJECT_ID'}} + 'kwargs': {'object_id': 'OBJECT_ID'}}, + 'url:screens': {'name': 'api_plate_screens', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, } From 861d6450b16877dcbb5e171e8f9ef31be52384e6 Mon Sep 17 00:00:00 2001 From: William Moore Date: Wed, 8 Feb 2017 13:49:47 +0000 Subject: [PATCH 09/23] New test 'test_pdi_parent_urls' in teat_api_containers.py --- .../test/integration/test_api_containers.py | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 4b5c6fef5d1..b665f43c2f1 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -538,3 +538,63 @@ def test_pdi_urls(self, user1, project_datasets): rsp = _get_response_json(client, images_json[0]['url:image'], {}) assert_objects(conn, [rsp['data']], images[0:1], dtype='Image', opts={'load_channels': True}) + + def test_pdi_parent_urls(self, user1, project_datasets): + """Test browsing via urls in json /api/image -> project.""" + conn = get_connection(user1) + user_name = conn.getUser().getName() + client = self.new_django_client(user_name, user_name) + version = settings.API_VERSIONS[-1] + + # Get image... + project, dataset = project_datasets + datasets = project.linkedDatasetList() + datasets.sort(cmp_name_insensitive) + # ...from last dataset + images = datasets[-1].linkedImageList() + dataset_id = datasets[-1].id.val + + # Listing images - all have link to parents + imgs_url = reverse('api_images', kwargs={'api_version': version}) + rsp = _get_response_json(client, imgs_url, {'dataset': dataset_id}) + for i in rsp['data']: + datasets_url = build_url(client, 'api_image_datasets', + {'api_version': version, + 'image_id': i['@id']}) + assert i['url:datasets'] == datasets_url + + # Single Image has link to parents... + img_url = imgs_url + '%s/' % images[0].id.val + rsp = _get_response_json(client, img_url, {}) + img_json = rsp + image_datasets_url = build_url(client, 'api_image_datasets', + {'api_version': version, + 'image_id': images[0].id.val}) + assert img_json['url:datasets'] == image_datasets_url + + # List parent datasets + rsp = _get_response_json(client, image_datasets_url, {}) + assert_objects(conn, rsp['data'], [datasets[-1]], dtype='Dataset') + + # Listing Datasets (in Project) - all have link to parents + datasets_url = reverse('api_datasets', kwargs={'api_version': version}) + rsp = _get_response_json(client, datasets_url, + {'project': project.id.val}) + for d in rsp['data']: + projects_url = build_url(client, 'api_dataset_projects', + {'api_version': version, + 'dataset_id': d['@id']}) + assert d['url:projects'] == projects_url + + # Single Dataset has link to parents... + dataset_url = datasets_url + '%s/' % dataset_id + rsp = _get_response_json(client, dataset_url, {}) + dataset_json = rsp + dataset_projects_url = build_url(client, 'api_dataset_projects', + {'api_version': version, + 'dataset_id': dataset_id}) + assert dataset_json['url:projects'] == dataset_projects_url + + # List parent Projects + rsp = _get_response_json(client, dataset_projects_url, {}) + assert_objects(conn, rsp['data'], [project]) From 52359aa4f3a040ef7e4e83ab7ab70e260c55e8b4 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 24 Feb 2017 11:40:31 +0000 Subject: [PATCH 10/23] Tiny fix to avoid merge conflict --- components/tools/OmeroWeb/omeroweb/api/views.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index e2df7258468..695782d4b95 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -271,10 +271,10 @@ class PlateView(ObjectView): # Urls to add to marshalled object. See ProjectsView for more details urls = { - 'url:wells': {'name': 'api_plate_wells', - 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:screens': {'name': 'api_plate_screens', 'kwargs': {'plate_id': 'OBJECT_ID'}}, + 'url:wells': {'name': 'api_plate_wells', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, } @@ -461,12 +461,12 @@ def get_opts(self, request, **kwargs): # Urls to add to marshalled object. See ProjectsView for more details urls = { + 'url:screens': {'name': 'api_plate_screens', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:wells': {'name': 'api_plate_wells', 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:plate': {'name': 'api_plate', 'kwargs': {'object_id': 'OBJECT_ID'}}, - 'url:screens': {'name': 'api_plate_screens', - 'kwargs': {'plate_id': 'OBJECT_ID'}}, } From 304b8e8aafc832be2464a15df1d92b60f7cae548 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 24 Feb 2017 15:59:57 +0000 Subject: [PATCH 11/23] Add 'X-OMERO-ApiVersion' = '0.0' to response Headers --- .../tools/OmeroWeb/omeroweb/api/api_settings.py | 2 ++ components/tools/OmeroWeb/omeroweb/api/decorators.py | 11 +++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/api_settings.py b/components/tools/OmeroWeb/omeroweb/api/api_settings.py index 3c8ec797b1f..0c83be07295 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_settings.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_settings.py @@ -55,3 +55,5 @@ # E.g. /api/v0/ # TODO - need to decide how this is configured, strategy for extending etc. API_VERSIONS = ('0',) + +API_VERSION = '0.0' diff --git a/components/tools/OmeroWeb/omeroweb/api/decorators.py b/components/tools/OmeroWeb/omeroweb/api/decorators.py index dab11da2d8a..dec1a93e2ce 100644 --- a/components/tools/OmeroWeb/omeroweb/api/decorators.py +++ b/components/tools/OmeroWeb/omeroweb/api/decorators.py @@ -27,6 +27,7 @@ import traceback from django.http import JsonResponse from functools import update_wrapper +from . import api_settings from api_exceptions import BadRequestError, \ CreatedObject, \ MethodNotSupportedError, \ @@ -58,6 +59,12 @@ def __init__(self): """Initialise the decorator.""" pass + def create_response(self, response, status=200): + """Create the Json response and set global headers.""" + response = JsonResponse(response, status=status) + response['X-OMERO-ApiVersion'] = api_settings.API_VERSION + return response + def handle_success(self, rv): """ Handle successful response from wrapped function. @@ -65,7 +72,7 @@ def handle_success(self, rv): By default, we simply return a JsonResponse() but this can be overwritten by subclasses if needed. """ - return JsonResponse(rv) + return self.create_response(rv) def handle_error(self, ex, trace): """ @@ -96,7 +103,7 @@ def handle_error(self, ex, trace): if isinstance(ex, CreatedObject): status = ex.status rsp_json = ex.response - return JsonResponse(rsp_json, status=status) + return self.create_response(rsp_json, status=status) def __call__(self, f): """ From 9f620a51efe4386f2e8ef3aaf133efe093e9ef8c Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 27 Feb 2017 16:44:44 +0000 Subject: [PATCH 12/23] Update Training/python/Json_Api/Login.py with new urls etc --- examples/Training/python/Json_Api/Login.py | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/examples/Training/python/Json_Api/Login.py b/examples/Training/python/Json_Api/Login.py index ada738c3ada..3aea3aa2ee7 100644 --- a/examples/Training/python/Json_Api/Login.py +++ b/examples/Training/python/Json_Api/Login.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2016 University of Dundee & Open Microscopy Environment. +# Copyright (C) 2016-2017 University of Dundee & Open Microscopy Environment. # All Rights Reserved. # Use is subject to license terms supplied in LICENSE.txt # @@ -10,6 +10,7 @@ import requests from Parse_OMERO_Properties import USERNAME, PASSWORD, OMERO_WEB_HOST +SERVER_NAME = 'omero' session = requests.Session() @@ -22,18 +23,18 @@ # use most recent version... version = versions[-1] # get the 'base' url -base_url = version['base_url'] +base_url = version['url:base'] r = session.get(base_url) # which lists a bunch of urls as starting points urls = r.json() -servers_url = urls['servers_url'] -login_url = urls['login_url'] -projects_url = urls['projects_url'] -save_url = urls['save_url'] -schema_url = urls['schema_url'] +servers_url = urls['url:servers'] +login_url = urls['url:login'] +projects_url = urls['url:projects'] +save_url = urls['url:save'] +schema_url = urls['url:schema'] # To login we need to get CSRF token -token_url = urls['token_url'] +token_url = urls['url:token'] token = session.get(token_url).json()['data'] print 'CSRF token', token # We add this to our session header @@ -49,10 +50,10 @@ print ' name:', s['server'] print ' host:', s['host'] print ' port:', s['port'] -# find one called 'omero' -servers = [s for s in servers if s['server'] == 'omero'] +# find one called SERVER_NAME +servers = [s for s in servers if s['server'] == SERVER_NAME] if len(servers) < 1: - print "Found no server called 'omero'" + print "Found no server called '%s'" % SERVER_NAME server = servers[0] # Login with username, password and token @@ -89,7 +90,8 @@ url = save_url + '?group=' + str(groupId) r = session.post(url, json={'Name': 'API TEST foo', '@type': projType}) assert r.status_code == 201 -project = r.json() +project = r.json()['data'] +print project project_id = project['@id'] print 'Created Project:', project_id, project['Name'] From 68ba82d11273cc7c3807cc697a58d93153109125 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 12:27:58 +0000 Subject: [PATCH 13/23] flake8 fix --- .../tools/OmeroWeb/test/integration/test_api_containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 3050bb46321..c6a61973d4d 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -559,7 +559,7 @@ def test_pdi_parent_urls(self, user1, project_datasets): conn = get_connection(user1) user_name = conn.getUser().getName() client = self.new_django_client(user_name, user_name) - version = settings.API_VERSIONS[-1] + version = api_settings.API_VERSIONS[-1] # Get image... project, dataset = project_datasets From 81b3ce8eda686148c082f6d6becfb1dd6e52cf6a Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 12:32:19 +0000 Subject: [PATCH 14/23] Fix test_pdi_parent_urls --- .../tools/OmeroWeb/test/integration/test_api_containers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index c6a61973d4d..a09cc3752cb 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -581,7 +581,7 @@ def test_pdi_parent_urls(self, user1, project_datasets): # Single Image has link to parents... img_url = imgs_url + '%s/' % images[0].id.val rsp = _get_response_json(client, img_url, {}) - img_json = rsp + img_json = rsp['data'] image_datasets_url = build_url(client, 'api_image_datasets', {'api_version': version, 'image_id': images[0].id.val}) @@ -604,7 +604,7 @@ def test_pdi_parent_urls(self, user1, project_datasets): # Single Dataset has link to parents... dataset_url = datasets_url + '%s/' % dataset_id rsp = _get_response_json(client, dataset_url, {}) - dataset_json = rsp + dataset_json = rsp['data'] dataset_projects_url = build_url(client, 'api_dataset_projects', {'api_version': version, 'dataset_id': dataset_id}) From f461940862a7f7b133f888fd86e37708377dbc2d Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 15:16:31 +0000 Subject: [PATCH 15/23] Blitz Plate.getQueryString() supports filter by 'well' --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 19c67c18cb0..313e1872e7c 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6071,10 +6071,16 @@ def _getQueryString(cls, opts=None): # NB: we don't use base _getQueryString. clauses = [] params = omero.sys.ParametersI() - if opts is not None and 'screen' in opts: + if opts is None: + opts = {} + if 'screen' in opts: clauses.append('spl.parent.id = :sid') params.add('sid', rlong(opts['screen'])) - if opts is not None and opts.get('orphaned'): + if 'well' in opts: + query += ' join obj.wells wells' + clauses.append('wells.id = :well_id') + params.add('well_id', rlong(opts['well'])) + if opts.get('orphaned'): clauses.append( """ not exists ( From 8c27b92df5e8396523569d3f7f9c21463e87e729 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 15:28:02 +0000 Subject: [PATCH 16/23] Add support for /wells/:id/plates/ --- components/tools/OmeroWeb/omeroweb/api/urls.py | 14 ++++++++++++-- components/tools/OmeroWeb/omeroweb/api/views.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index ccaac350e88..075da745aa0 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -128,7 +128,7 @@ views.ProjectsView.as_view(), name='api_dataset_projects') """ -GET Projects in Dataset, using omero-marshal to generate json +GET Projects that contain a Dataset, using omero-marshal to generate json """ api_image = url( @@ -145,7 +145,7 @@ views.DatasetsView.as_view(), name='api_image_datasets') """ -GET Datasets parents of Image, using omero-marshal to generate json +GET Datasets that contain an Image, using omero-marshal to generate json """ api_screen = url( @@ -179,6 +179,15 @@ GET Plates in Screen, using omero-marshal to generate json """ +api_well_plates = url( + r'^v(?P%s)/m/wells/' + '(?P[0-9]+)/plates/$' % versions, + views.PlatesView.as_view(), + name='api_well_plates') +""" +GET Plates that contain a Well, using omero-marshal to generate json +""" + api_plate = url( r'^v(?P%s)/m/plates/(?P[0-9]+)/$' % versions, views.PlateView.as_view(), @@ -289,6 +298,7 @@ api_screens, api_plates, api_screen_plates, + api_well_plates, api_plate, api_wells, api_plate_plateacquisitions, diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 4e2bf9fe274..7ca493dd219 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -335,6 +335,12 @@ class WellView(ObjectView): CAN_DELETE = False + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'url:plates': {'name': 'api_well_plates', + 'kwargs': {'well_id': 'OBJECT_ID'}}, + } + def get_opts(self, request): """Add support for load_images.""" opts = super(WellView, self).get_opts(request) @@ -508,6 +514,14 @@ def get_opts(self, request, **kwargs): screen = getIntOrDefault(request, 'screen', None) if screen is not None: opts['screen'] = screen + # Filter Plates by Well + if 'well_id' in kwargs: + opts['well'] = long(kwargs['well_id']) + else: + # filter by query /plates/?well=:id + well = getIntOrDefault(request, 'well', None) + if well is not None: + opts['well'] = well return opts # Urls to add to marshalled object. See ProjectsView for more details @@ -605,6 +619,8 @@ class WellsView(ObjectsView): urls = { 'url:well': {'name': 'api_well', 'kwargs': {'object_id': 'OBJECT_ID'}}, + 'url:plates': {'name': 'api_well_plates', + 'kwargs': {'well_id': 'OBJECT_ID'}}, } def get_opts(self, request, **kwargs): From 9cc3938391c3a22de765a0e63969ae49037e8624 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 15:28:52 +0000 Subject: [PATCH 17/23] Blitz docstring update --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 313e1872e7c..335b06cba8b 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -6058,6 +6058,7 @@ def _getQueryString(cls, opts=None): Also handles filtering of Plates by Screens. Returns a tuple of (query, clauses, params). Supported opts: 'screen': to filter by Screen + 'well': to filter by Well 'orphaned': . Filter by 'not in Screen' :param opts: Dictionary of optional parameters. From 8f81f9fc2e071f8de68fa35234d8332e0e500744 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 16:09:46 +0000 Subject: [PATCH 18/23] Add test 'test_spw_parent_urls' --- .../test/integration/test_api_containers.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index a09cc3752cb..06cd7cb47c0 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -483,6 +483,56 @@ def test_spw_urls(self, user1, screen_plates): assert_objects(conn, wells_json, [well_id], dtype='Well', extra=extra, opts={'load_images': True}, client=client) + def test_spw_parent_urls(self, user1, screen_plates): + """Test browsing via urls in json /api/image -> well, plate, screen.""" + conn = get_connection(user1) + user_name = conn.getUser().getName() + client = self.new_django_client(user_name, user_name) + version = api_settings.API_VERSIONS[-1] + screen, plate = screen_plates + plates = screen.linkedPlateList() + plates.sort(cmp_name_insensitive) + + # Listing wells - all have link to parents + wells_url = reverse('api_wells', kwargs={'api_version': version}) + rsp = _get_response_json(client, wells_url, {}) + for w in rsp['data']: + plates_url = build_url(client, 'api_well_plates', + {'api_version': version, + 'well_id': w['@id']}) + assert w['url:plates'] == plates_url + + # Single Well has link to parents... + well_id = rsp['data'][0]['@id'] + well_url = wells_url + '%s/' % well_id + rsp = _get_response_json(client, well_url, {}) + well_json = rsp['data'] + well_plates_url = build_url(client, 'api_well_plates', + {'api_version': version, + 'well_id': well_id}) + assert well_json['url:plates'] == well_plates_url + + # Get parent plate (Plates list, filtered by Well) + print 'well_plates_url', well_plates_url + rsp = _get_response_json(client, well_plates_url, {}) + plates_json = rsp['data'] + # check for link to Screen + screens_url = build_url(client, 'api_plate_screens', + {'api_version': version, + 'plate_id': plates_json[0]['@id']}) + assert plates_json[0]['url:screens'] == screens_url + plate_url = plates_json[0]['url:plate'] + assert_objects(conn, plates_json, [plates[0]], dtype='Plate') + + # Get the same Plate by ID + rsp = _get_response_json(client, plate_url, {}) + assert rsp['data']['url:screens'] == screens_url + + # Get Screen + rsp = _get_response_json(client, screens_url, {}) + assert_objects(conn, rsp['data'], [screen], dtype='Screen') + + def test_pdi_urls(self, user1, project_datasets): """Test browsing via urls in json /api/->PDI.""" conn = get_connection(user1) From 149669e8237c6729e4153c7a915ccf6366eb65c0 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 16:14:37 +0000 Subject: [PATCH 19/23] Remove print from Training Login.py --- examples/Training/python/Json_Api/Login.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/Training/python/Json_Api/Login.py b/examples/Training/python/Json_Api/Login.py index 3aea3aa2ee7..ace6cd51f2e 100644 --- a/examples/Training/python/Json_Api/Login.py +++ b/examples/Training/python/Json_Api/Login.py @@ -91,7 +91,6 @@ r = session.post(url, json={'Name': 'API TEST foo', '@type': projType}) assert r.status_code == 201 project = r.json()['data'] -print project project_id = project['@id'] print 'Created Project:', project_id, project['Name'] From 73372cd4a3576454850e988f8e7973c83da39b12 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 16:25:52 +0000 Subject: [PATCH 20/23] Add Json_Api/Login.py to Training test suite --- examples/Training/python/__main__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/Training/python/__main__.py b/examples/Training/python/__main__.py index aea494f485e..f1cc33bc7f7 100755 --- a/examples/Training/python/__main__.py +++ b/examples/Training/python/__main__.py @@ -33,3 +33,4 @@ execfile(os.path.join(training_dir, 'Task_Scripts/Raw_Data_Task.py')) execfile(os.path.join(training_dir, 'Task_Scripts/Write_Data_4.py')) execfile(os.path.join(training_dir, 'Task_Scripts/Write_Data_3.py')) + execfile(os.path.join(training_dir, 'Json_Api/Login.py')) From 65777ad4b0ecdb6eee860cf985ed98682e93d5cd Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 28 Feb 2017 23:31:38 +0000 Subject: [PATCH 21/23] flake8 fix --- .../tools/OmeroWeb/test/integration/test_api_containers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 06cd7cb47c0..06bdf274f01 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -532,7 +532,6 @@ def test_spw_parent_urls(self, user1, screen_plates): rsp = _get_response_json(client, screens_url, {}) assert_objects(conn, rsp['data'], [screen], dtype='Screen') - def test_pdi_urls(self, user1, project_datasets): """Test browsing via urls in json /api/->PDI.""" conn = get_connection(user1) From 4d036edf6923a62c3af17389550d1d62acd030ae Mon Sep 17 00:00:00 2001 From: William Moore Date: Sun, 5 Mar 2017 23:24:57 +0000 Subject: [PATCH 22/23] Blitz rename plink -> projectLinks in Dataset.getQueryString() --- components/tools/OmeroPy/src/omero/gateway/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index 335b06cba8b..eed4ab06332 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -5743,8 +5743,8 @@ def _getQueryString(cls, opts=None): if opts is None: opts = {} if 'project' in opts: - query += ' join obj.projectLinks plink' - clauses.append('plink.parent.id = :pid') + query += ' join obj.projectLinks projectLinks' + clauses.append('projectLinks.parent.id = :pid') params.add('pid', rlong(opts['project'])) if 'image' in opts: query += ' join obj.imageLinks imagelinks' From c42a896e2d9346019c3fe2e62551b7a8f226b8a8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 6 Mar 2017 15:46:10 +0000 Subject: [PATCH 23/23] Update comment in urls.py --- components/tools/OmeroWeb/omeroweb/api/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index 075da745aa0..dfa2bfa8c74 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -273,7 +273,7 @@ views.ScreensView.as_view(), name='api_plate_screens') """ -GET Screens for child Plate, using omero-marshal to generate json +GET Screens that contain a Plate, using omero-marshal to generate json """ urlpatterns = patterns(