diff --git a/weblab/entities/processing.py b/weblab/entities/processing.py index 6ae3576ea..2809eafe6 100644 --- a/weblab/entities/processing.py +++ b/weblab/entities/processing.py @@ -6,10 +6,10 @@ from django.core.urlresolvers import reverse from django.db import IntegrityError -from experiments.models import Experiment, PlannedExperiment +from experiments.models import PlannedExperiment from repocache.models import ProtocolInterface, ProtocolIoputs -from .models import AnalysisTask +from .models import AnalysisTask, ModelEntity, ProtocolEntity logger = logging.getLogger(__name__) @@ -160,8 +160,10 @@ def process_check_protocol_callback(data): def record_experiments_to_run(user, entity, commit): """Record what experiments to run automatically on a new entity version. - Any experiments that were run with the parent version(s) (that are visible to the user) - should be repeated with the new version. + Find all experiments run with the entity for which we're adding a new version (consider case of a model) + Get the list of corresponding protocol IDs. We now know both our model ID, and all protocols that have + previously had any version run on any version of this model. + Run our new model version under the latest (visible) version of all those protocols. :param user: the user that created the new version :param entity: the entity that has had a new version created @@ -171,21 +173,24 @@ def record_experiments_to_run(user, entity, commit): entity.entity_type: entity, entity.entity_type + '_version': commit.sha, } - for parent in commit.parents: - # Find visible experiments involving this parent - parent_kwargs = { - entity.entity_type: entity, - entity.entity_type + '_version': entity.repocache.get_version(parent.sha), - } - for expt in Experiment.objects.filter(**parent_kwargs): - if expt.is_visible_to_user(user): + if entity.other_type == entity.ENTITY_TYPE_MODEL: + other_model = ModelEntity + else: + other_model = ProtocolEntity + search_args = { + entity.other_type + '_experiments__' + entity.entity_type: entity, + } + other_entities = other_model.objects.visible_to_user(user).filter(**search_args) + for other_entity in other_entities: + # Look for latest visible version + for other_version in other_entity.cachedentity.versions.all(): + if other_entity.is_version_visible_to_user(other_version.sha, user): # Record the new experiment to run kwargs = { 'submitter': user, - 'model_id': expt.model_id, - 'model_version': expt.model_version.sha, - 'protocol_id': expt.protocol_id, - 'protocol_version': expt.protocol_version.sha, + entity.other_type: other_entity, + entity.other_type + '_version': other_version.sha, } kwargs.update(new_version_kwargs) PlannedExperiment.objects.get_or_create(**kwargs) + break diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index 4cbde5827..4eb7263fd 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -1347,15 +1347,16 @@ def test_rolls_back_if_tag_exists(self, logged_in_user, client, helpers): @pytest.mark.parametrize("route", ['main_form', 'edit_file']) def test_rerun_experiments(self, logged_in_user, other_user, client, helpers, route): # Set up previous experiments - model = recipes.model.make(author=logged_in_user) - model_first_commit = helpers.add_version(model, visibility='private') - model_commit = helpers.add_version(model, visibility='private') - other_model = recipes.model.make(author=other_user) - other_model_commit = helpers.add_version(other_model, visibility='public') + m1 = recipes.model.make(author=logged_in_user) + m1v1 = helpers.add_version(m1, visibility='private') + m1v2 = helpers.add_version(m1, visibility='private') + + m2 = recipes.model.make(author=other_user) + m2v1 = helpers.add_version(m2, visibility='public') def _add_experiment(proto_author, proto_vis, shared=False, proto=None, proto_commit=None, - model=model, model_commit=model_commit): + model=m1, model_commit=m1v1): if proto is None: proto = recipes.protocol.make(author=proto_author) proto_commit = helpers.add_version(proto, visibility=proto_vis) @@ -1370,29 +1371,29 @@ def _add_experiment(proto_author, proto_vis, shared=False, assign_perm('edit_entity', logged_in_user, proto) return proto - my_private_protocol = _add_experiment(logged_in_user, 'private') # Re-run case 1 - _add_experiment(logged_in_user, 'private', # Shouldn't be re-run - model=model, model_commit=model_first_commit) - _add_experiment(logged_in_user, 'private', # Does get re-run because same proto version as case 1 - proto=my_private_protocol, proto_commit=my_private_protocol.repo.latest_commit, - model=model, model_commit=model_first_commit) - public_protocol = _add_experiment(other_user, 'public') # Re-run case 2 - _add_experiment(other_user, 'public', # Not re-run as other model - proto=public_protocol, proto_commit=public_protocol.repo.latest_commit, - model=other_model, model_commit=other_model_commit) - _add_experiment(other_user, 'private') # Not re-run as can't see protocol - visible_protocol = _add_experiment(other_user, 'private', shared=True) # Re-run case 3 + p1 = _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v2) + p1v2 = helpers.add_version(p1, visibility='private') + _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v1, proto=p1, proto_commit=p1v2) + _add_experiment(logged_in_user, 'private', model=m2, model_commit=m2v1, proto=p1, proto_commit=p1v2) + _add_experiment(logged_in_user, 'public', model=m2, model_commit=m2v1) + p3 = _add_experiment(other_user, 'public', model=m1, model_commit=m1v2) + p3v1 = p3.repocache.latest_version + p3v2 = helpers.add_version(p3, visibility='private') + _add_experiment(logged_in_user, 'private', model=m1, model_commit=m1v2, proto=p3, proto_commit=p3v2) + p4 = _add_experiment(other_user, 'public', model=m1, model_commit=m1v2, shared=True) + p4v2 = helpers.add_version(p4, visibility='private') + _add_experiment(other_user, 'private', model=m1, model_commit=m1v2) # Create a new version of our model, re-running experiments helpers.add_permission(logged_in_user, 'create_model') if route == 'main_form': recipes.model_file.make( - entity=model, + entity=m1, upload=SimpleUploadedFile('file2.txt', b'file 2'), original_name='file2.txt', ) response = client.post( - '/entities/models/%d/versions/new' % model.pk, + '/entities/models/%d/versions/new' % m1.pk, data={ 'filename[]': ['uploads/file2.txt'], 'mainEntry': ['file2.txt'], @@ -1403,12 +1404,12 @@ def _add_experiment(proto_author, proto_vis, shared=False, }, ) assert response.status_code == 302 - new_commit = model.repo.latest_commit - assert response.url == '/entities/models/%d/versions/%s' % (model.id, new_commit.sha) + new_commit = m1.repo.latest_commit + assert response.url == '/entities/models/%d/versions/%s' % (m1.id, new_commit.sha) else: assert route == 'edit_file' - response = client.post('/entities/models/%d/versions/edit' % model.id, json.dumps({ - 'parent_hexsha': model_commit.sha, + response = client.post('/entities/models/%d/versions/edit' % m1.id, json.dumps({ + 'parent_hexsha': m1v2.sha, 'file_name': 'file1.txt', 'file_contents': 'new file 1', 'visibility': 'private', @@ -1420,20 +1421,20 @@ def _add_experiment(proto_author, proto_vis, shared=False, detail = json.loads(response.content.decode()) assert 'updateEntityFile' in detail assert detail['updateEntityFile']['response'] - new_commit = model.repo.latest_commit + new_commit = m1.repo.latest_commit assert detail['updateEntityFile']['url'] == '/entities/models/%d/versions/%s' % ( - model.id, new_commit.sha) + m1.id, new_commit.sha) # Test that planned experiments have been added correctly expected_proto_versions = set([ - (my_private_protocol, my_private_protocol.repo.latest_commit.sha), - (public_protocol, public_protocol.repo.latest_commit.sha), - (visible_protocol, visible_protocol.repo.latest_commit.sha), + (p1, p1v2.sha), + (p3, p3v1.sha), + (p4, p4v2.sha), ]) assert len(expected_proto_versions) == PlannedExperiment.objects.count() for planned_experiment in PlannedExperiment.objects.all(): assert planned_experiment.submitter == logged_in_user - assert planned_experiment.model == model + assert planned_experiment.model == m1 assert planned_experiment.model_version == new_commit.sha assert (planned_experiment.protocol, planned_experiment.protocol_version) in expected_proto_versions