diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index faf8a265ec1..3c986dfdc7d 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -700,6 +700,15 @@ def _save_object(self, request, conn, object_json, **kwargs): # Therefore we ignore any details for now: obj.unloadDetails() + # TODO: Unlink children for Projects, Datasets and Screens to avoid + # breaking links. See PR #4930 + if hasattr(obj, 'unloadDatasetLinks'): + obj.unloadDatasetLinks() + if hasattr(obj, 'unloadImageLinks'): + obj.unloadImageLinks() + if hasattr(obj, 'unloadPlateLinks'): + obj.unloadPlateLinks() + conn.SERVICE_OPTS.setOmeroGroup(group) obj = conn.getUpdateService().saveAndReturnObject(obj, conn.SERVICE_OPTS) diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 124c0ad0a7c..b9ea9cc3228 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -32,6 +32,7 @@ PlateI, \ ProjectI, \ ScreenI, \ + TagAnnotationI, \ WellI, \ WellSampleI from omero.rtypes import rstring, rint @@ -410,6 +411,66 @@ def test_screens(self, user1, user_screens): assert_objects(conn, rsp['data'], user_screens, dtype="Screen", extra=extra) + def test_screen_plates_update(self, user1, screen_plates): + """Test update of Screen doesn't break links to Plate.""" + conn = get_connection(user1) + user_name = conn.getUser().getName() + django_client = self.new_django_client(user_name, user_name) + version = api_settings.API_VERSIONS[-1] + screen = screen_plates[0] + plate_count = len(screen.linkedPlateList()) + screen_url = reverse('api_screen', + kwargs={'api_version': version, + 'object_id': screen.id.val}) + save_url = reverse('api_save', kwargs={'api_version': version}) + # Get Screen, update and save back + rsp = _get_response_json(django_client, screen_url, {}) + screen_json = rsp['data'] + screen_json['Name'] = 'renamed Screen' + _csrf_put_json(django_client, save_url, screen_json) + + # Check Screen has been updated and still has child Plates + scr = conn.getObject('Screen', screen.id.val) + assert scr.getName() == 'renamed Screen' + assert len(list(scr.listChildren())) == plate_count + + @pytest.mark.parametrize("dtype", [('project', ProjectI), + ('dataset', DatasetI), + ('screen', ScreenI)]) + def test_container_tags_update(self, user1, dtype): + """ + Test updating a Object without losing linked Tags. + + If we load a Object without loading Annotations, then update + and save the Object, we don't want to lose Annotation links + """ + conn = get_connection(user1) + user_name = conn.getUser().getName() + django_client = self.new_django_client(user_name, user_name) + + container = dtype[1]() + container.name = rstring('test_container_tags_update') + tag = TagAnnotationI() + tag.textValue = rstring('tag') + container.linkAnnotation(tag) + container = get_update_service(user1).saveAndReturnObject(container) + + version = api_settings.API_VERSIONS[-1] + object_url = reverse('api_%s' % dtype[0], + kwargs={'api_version': version, + 'object_id': container.id.val}) + save_url = reverse('api_save', kwargs={'api_version': version}) + # Get container, update and save back + rsp = _get_response_json(django_client, object_url, {}) + object_json = rsp['data'] + object_json['Name'] = 'renamed container' + _csrf_put_json(django_client, save_url, object_json) + + # Check container has been updated and still has annotation links + proj = conn.getObject(dtype[0], container.id.val) + assert proj.getName() == 'renamed container' + assert len(list(proj.listAnnotations())) == 1 + def test_spw_urls(self, user1, screen_plates): """Test browsing via urls in json /api/->SPW.""" conn = get_connection(user1) diff --git a/components/tools/OmeroWeb/test/integration/test_api_projects.py b/components/tools/OmeroWeb/test/integration/test_api_projects.py index 60bdd40ef41..c15d2607ea0 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_projects.py +++ b/components/tools/OmeroWeb/test/integration/test_api_projects.py @@ -28,7 +28,7 @@ from django.test import Client import pytest from omero.gateway import BlitzGateway -from omero.model import ProjectI, DatasetI +from omero.model import DatasetI, ProjectI from omero.rtypes import unwrap, rstring from omero_marshal import get_encoder, OME_SCHEMA_URL @@ -600,6 +600,44 @@ def test_project_update(self, user1): rsp = _get_response_json(django_client, project_url, {}) assert rsp['data']['Description'] == 'New test description update' + @pytest.mark.parametrize("dtype", ['Project', 'Dataset']) + def test_project_datasets_update(self, user1, dtype, + project_hierarchy_user1_group1): + """ + Test updating a Project without losing child Datasets. + + If we load a Project without loading Datasets, then update + and save the Project, we don't want to lose Dataset links + """ + conn = get_connection(user1) + user_name = conn.getUser().getName() + django_client = self.new_django_client(user_name, user_name) + + if dtype == 'Project': + parent = project_hierarchy_user1_group1[0] + child_count = len(parent.linkedDatasetList()) + url_name = 'api_project' + else: + parent = project_hierarchy_user1_group1[0].linkedDatasetList()[0] + child_count = len(parent.linkedImageList()) + url_name = 'api_dataset' + + version = api_settings.API_VERSIONS[-1] + parent_url = reverse(url_name, + kwargs={'api_version': version, + 'object_id': parent.id.val}) + save_url = reverse('api_save', kwargs={'api_version': version}) + # Get Parent, update and save back + rsp = _get_response_json(django_client, parent_url, {}) + parent_json = rsp['data'] + parent_json['Name'] = 'renamed %s' % dtype + _csrf_put_json(django_client, save_url, parent_json) + + # Check Parent has been updated and still has children linked + parentWrapper = conn.getObject(dtype, parent.id.val) + assert parentWrapper.getName() == 'renamed %s' % dtype + assert len(list(parentWrapper.listChildren())) == child_count + def test_project_delete(self, user1): conn = get_connection(user1) user_name = conn.getUser().getName()