From d26f06bea0c721420d7526c7814e488f859528ca Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Thu, 13 Feb 2020 10:32:20 +0000 Subject: [PATCH 01/22] Introduction of runnable entity --- weblab/entities/views.py | 4 +-- weblab/experiments/admin.py | 10 +----- weblab/experiments/apps.py | 4 +-- weblab/experiments/forms.py | 4 +-- .../migrations/0025_auto_20200212_1633.py | 27 ++++++++++++++++ .../migrations/0026_experimentversion.py | 26 +++++++++++++++ .../0027_experimentversion_experiment_key.py | 21 ++++++++++++ .../migrations/0028_auto_20200212_1701.py | 27 ++++++++++++++++ .../migrations/0029_auto_20200212_1731.py | 21 ++++++++++++ .../migrations/0030_auto_20200212_1732.py | 21 ++++++++++++ .../0031_remove_runnable_experiment.py | 19 +++++++++++ weblab/experiments/models.py | 17 +++++++--- weblab/experiments/processing.py | 32 +++++++++---------- weblab/experiments/views.py | 28 ++++++++-------- 14 files changed, 212 insertions(+), 49 deletions(-) create mode 100644 weblab/experiments/migrations/0025_auto_20200212_1633.py create mode 100644 weblab/experiments/migrations/0026_experimentversion.py create mode 100644 weblab/experiments/migrations/0027_experimentversion_experiment_key.py create mode 100644 weblab/experiments/migrations/0028_auto_20200212_1701.py create mode 100644 weblab/experiments/migrations/0029_auto_20200212_1731.py create mode 100644 weblab/experiments/migrations/0030_auto_20200212_1732.py create mode 100644 weblab/experiments/migrations/0031_remove_runnable_experiment.py diff --git a/weblab/entities/views.py b/weblab/entities/views.py index 765b3738e..489c43828 100644 --- a/weblab/entities/views.py +++ b/weblab/entities/views.py @@ -38,7 +38,7 @@ from guardian.shortcuts import get_objects_for_user from core.visibility import Visibility, VisibilityMixin -from experiments.models import Experiment, ExperimentVersion, PlannedExperiment +from experiments.models import Experiment, Runnable, PlannedExperiment from fitting.models import FittingSpec from repocache.exceptions import RepoCacheMiss from repocache.models import CachedProtocolVersion @@ -1043,7 +1043,7 @@ def post(self, request, *args, **kwargs): 'experiment__' + this_entity.entity_type + '_id': this_entity.id, 'experiment__' + this_entity.entity_type + '_version_id': this_version.id, } - if ExperimentVersion.objects.filter(**filter_kwargs).exists(): + if Runnable.objects.filter(**filter_kwargs).exists(): continue exper_kwargs = { 'submitter': request.user, diff --git a/weblab/experiments/admin.py b/weblab/experiments/admin.py index d1f591730..d1fd84fa4 100644 --- a/weblab/experiments/admin.py +++ b/weblab/experiments/admin.py @@ -1,13 +1,5 @@ from django.contrib import admin -from .models import Experiment, ExperimentVersion +from .models import Experiment, Runnable -class ExperimentVersionInline(admin.StackedInline): - model = ExperimentVersion - extra = 0 - - -@admin.register(Experiment) -class ExperimentAdmin(admin.ModelAdmin): - inlines = [ExperimentVersionInline] diff --git a/weblab/experiments/apps.py b/weblab/experiments/apps.py index 03d41a9fe..156ae9065 100644 --- a/weblab/experiments/apps.py +++ b/weblab/experiments/apps.py @@ -8,7 +8,7 @@ class ExperimentsConfig(AppConfig): name = 'experiments' def ready(self): - from .models import ExperimentVersion, RunningExperiment + from .models import Runnable, RunningExperiment - pre_delete.connect(experiment_version_deleted, ExperimentVersion) + pre_delete.connect(experiment_version_deleted, Runnable) pre_delete.connect(running_experiment_deleted, RunningExperiment) diff --git a/weblab/experiments/forms.py b/weblab/experiments/forms.py index 6d2e6c38a..48a839ad2 100644 --- a/weblab/experiments/forms.py +++ b/weblab/experiments/forms.py @@ -1,6 +1,6 @@ from django import forms -from .models import ExperimentVersion +from .models import Runnable from .processing import ChasteProcessingStatus @@ -21,5 +21,5 @@ class ExperimentSimulateCallbackForm(forms.ModelForm): upload = forms.FileField(required=False) class Meta: - model = ExperimentVersion + model = Runnable fields = () diff --git a/weblab/experiments/migrations/0025_auto_20200212_1633.py b/weblab/experiments/migrations/0025_auto_20200212_1633.py new file mode 100644 index 000000000..142f8f42c --- /dev/null +++ b/weblab/experiments/migrations/0025_auto_20200212_1633.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 16:33 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0024_merge_20200211_0929'), + ] + + operations = [ + migrations.RenameModel( + old_name='ExperimentVersion', + new_name='Runnable', + ), + migrations.RemoveIndex( + model_name='runnable', + name='experiments_created_00e2f1_idx', + ), + migrations.AddIndex( + model_name='runnable', + index=models.Index(fields=['created_at'], name='experiments_created_a4e4b7_idx'), + ), + ] diff --git a/weblab/experiments/migrations/0026_experimentversion.py b/weblab/experiments/migrations/0026_experimentversion.py new file mode 100644 index 000000000..e9d1911b9 --- /dev/null +++ b/weblab/experiments/migrations/0026_experimentversion.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 16:36 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0025_auto_20200212_1633'), + ] + + operations = [ + migrations.CreateModel( + name='ExperimentVersion', + fields=[ + ('runnable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='experiments.Runnable')), + ], + options={ + 'abstract': False, + }, + bases=('experiments.runnable',), + ), + ] diff --git a/weblab/experiments/migrations/0027_experimentversion_experiment_key.py b/weblab/experiments/migrations/0027_experimentversion_experiment_key.py new file mode 100644 index 000000000..5b394100b --- /dev/null +++ b/weblab/experiments/migrations/0027_experimentversion_experiment_key.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 17:00 +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0026_experimentversion'), + ] + + operations = [ + migrations.AddField( + model_name='experimentversion', + name='experiment_key', + field=models.CharField(default=1, max_length=50), + preserve_default=False, + ), + ] diff --git a/weblab/experiments/migrations/0028_auto_20200212_1701.py b/weblab/experiments/migrations/0028_auto_20200212_1701.py new file mode 100644 index 000000000..7084d7ad7 --- /dev/null +++ b/weblab/experiments/migrations/0028_auto_20200212_1701.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 17:01 +from __future__ import unicode_literals + +from django.db import migrations + + +def populate_runnable(apps, schema_editor): + ExperimentVersion = apps.get_model('experiments', 'ExperimentVersion') + Runnable = apps.get_model('experiments', 'Runnable') + for runnable in Runnable.objects.all(): + experiment_version = ExperimentVersion(id=runnable.id, + created_at=runnable.created_at, + author_id=runnable.author_id, + experiment_id=runnable.experiment_id, + experiment_key=runnable.experiment_id) + experiment_version.save() + + +class Migration(migrations.Migration): + dependencies = [ + ('experiments', '0027_experimentversion_experiment_key'), + ] + + operations = [ + migrations.RunPython(populate_runnable), + ] diff --git a/weblab/experiments/migrations/0029_auto_20200212_1731.py b/weblab/experiments/migrations/0029_auto_20200212_1731.py new file mode 100644 index 000000000..b7380ec46 --- /dev/null +++ b/weblab/experiments/migrations/0029_auto_20200212_1731.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 17:31 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0028_auto_20200212_1701'), + ] + + operations = [ + migrations.AlterField( + model_name='runnable', + name='experiment', + field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment'), + ), + ] diff --git a/weblab/experiments/migrations/0030_auto_20200212_1732.py b/weblab/experiments/migrations/0030_auto_20200212_1732.py new file mode 100644 index 000000000..cd9cc9fd8 --- /dev/null +++ b/weblab/experiments/migrations/0030_auto_20200212_1732.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-12 17:32 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0029_auto_20200212_1731'), + ] + + operations = [ + migrations.AlterField( + model_name='runnable', + name='experiment', + field=models.ForeignKey(db_constraint=False, db_index=False, on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment'), + ), + ] diff --git a/weblab/experiments/migrations/0031_remove_runnable_experiment.py b/weblab/experiments/migrations/0031_remove_runnable_experiment.py new file mode 100644 index 000000000..6f537205d --- /dev/null +++ b/weblab/experiments/migrations/0031_remove_runnable_experiment.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.23 on 2020-02-13 10:19 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0030_auto_20200212_1732'), + ] + + operations = [ + migrations.RemoveField( + model_name='runnable', + name='experiment', + ), + ] diff --git a/weblab/experiments/models.py b/weblab/experiments/models.py index 589d81677..3c1a736ac 100644 --- a/weblab/experiments/models.py +++ b/weblab/experiments/models.py @@ -106,11 +106,11 @@ def nice_protocol_version(self): def latest_result(self): try: return self.latest_version.status - except ExperimentVersion.DoesNotExist: + except Runnable.DoesNotExist: return '' -class ExperimentVersion(UserCreatedModelMixin, FileCollectionMixin, models.Model): +class Runnable(UserCreatedModelMixin, FileCollectionMixin, models.Model): STATUS_QUEUED = "QUEUED" STATUS_RUNNING = "RUNNING" STATUS_SUCCESS = "SUCCESS" @@ -127,7 +127,6 @@ class ExperimentVersion(UserCreatedModelMixin, FileCollectionMixin, models.Model (STATUS_INAPPLICABLE, STATUS_INAPPLICABLE), ) - experiment = models.ForeignKey(Experiment, related_name='versions') finished_at = models.DateTimeField(null=True, blank=True) status = models.CharField( max_length=16, @@ -197,13 +196,23 @@ def update(self, status, txt): self.save() +class ExperimentVersion(Runnable): + + experiment_key = models.CharField(max_length=50) + + @property + def parent(self): + """The Experiment this is a version of.""" + return self.experiment + + class RunningExperiment(models.Model): """ A current run of an ExperimentVersion """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - experiment_version = models.ForeignKey(ExperimentVersion, related_name='running') + experiment_version = models.ForeignKey(Runnable, related_name='running') task_id = models.CharField(max_length=50) diff --git a/weblab/experiments/processing.py b/weblab/experiments/processing.py index 92b2f8607..3b04b77fc 100644 --- a/weblab/experiments/processing.py +++ b/weblab/experiments/processing.py @@ -9,7 +9,7 @@ from django.utils.timezone import now from .emails import send_experiment_finished_email -from .models import Experiment, ExperimentVersion, RunningExperiment +from .models import Experiment, Runnable, RunningExperiment logger = logging.getLogger(__name__) @@ -23,11 +23,11 @@ class ChasteProcessingStatus: INAPPLICABLE = "inapplicable" MODEL_STATUSES = { - SUCCESS: ExperimentVersion.STATUS_SUCCESS, - RUNNING: ExperimentVersion.STATUS_RUNNING, - PARTIAL: ExperimentVersion.STATUS_PARTIAL, - FAILED: ExperimentVersion.STATUS_FAILED, - INAPPLICABLE: ExperimentVersion.STATUS_INAPPLICABLE, + SUCCESS: Runnable.STATUS_SUCCESS, + RUNNING: Runnable.STATUS_RUNNING, + PARTIAL: Runnable.STATUS_PARTIAL, + FAILED: Runnable.STATUS_FAILED, + INAPPLICABLE: Runnable.STATUS_INAPPLICABLE, } @classmethod @@ -59,18 +59,18 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re # Check there isn't an existing version if we're not allowed to re-run if not rerun_ok: try: - version, created = ExperimentVersion.objects.get_or_create( + version, created = Runnable.objects.get_or_create( experiment=experiment, defaults={ 'author': user, } ) except MultipleObjectsReturned: - return ExperimentVersion.objects.filter(experiment=experiment).latest('created_at'), False + return Runnable.objects.filter(experiment=experiment).latest('created_at'), False if not created: return version, False else: - version = ExperimentVersion.objects.create( + version = Runnable.objects.create( experiment=experiment, author=user, ) @@ -102,7 +102,7 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re response = requests.post(settings.CHASTE_URL, body) except requests.exceptions.ConnectionError: run.delete() - version.status = ExperimentVersion.STATUS_FAILED + version.status = Runnable.STATUS_FAILED version.return_text = 'Unable to connect to experiment runner service' version.save() logger.exception(version.return_text) @@ -113,7 +113,7 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re if not res.startswith(signature): run.delete() - version.status = ExperimentVersion.STATUS_FAILED + version.status = Runnable.STATUS_FAILED version.return_text = 'Chaste backend answered with something unexpected: %s' % res version.save() logger.error(version.return_text) @@ -126,11 +126,11 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re run.save() elif status == 'inapplicable': run.delete() - version.status = ExperimentVersion.STATUS_INAPPLICABLE + version.status = Runnable.STATUS_INAPPLICABLE else: run.delete() logger.error('Chaste backend answered with error: %s' % status) - version.status = ExperimentVersion.STATUS_FAILED + version.status = Runnable.STATUS_FAILED version.return_text = status version.save() @@ -187,7 +187,7 @@ def process_callback(data, files): exp.save() - if exp.is_finished or exp.status == ExperimentVersion.STATUS_INAPPLICABLE: + if exp.is_finished or exp.status == Runnable.STATUS_INAPPLICABLE: # We unset the task_id to ensure the delete() below doesn't send a message to the back-end cancelling # the task, causing it to be killed while still sending us its 'finished' message! run.task_id = '' @@ -197,7 +197,7 @@ def process_callback(data, files): send_experiment_finished_email(exp) if not files.get('experiment'): - exp.update(ExperimentVersion.STATUS_FAILED, + exp.update(Runnable.STATUS_FAILED, '%s (backend returned no archive)' % exp.return_text) return {'error': 'no archive found'} @@ -210,7 +210,7 @@ def process_callback(data, files): try: zipfile.ZipFile(str(exp.archive_path)) except zipfile.BadZipFile as e: - exp.update(ExperimentVersion.STATUS_FAILED, 'error reading archive: %s' % e) + exp.update(Runnable.STATUS_FAILED, 'error reading archive: %s' % e) return {'experiment': 'failed'} return {'experiment': 'ok'} diff --git a/weblab/experiments/views.py b/weblab/experiments/views.py index 047aa7929..28e8efc8e 100644 --- a/weblab/experiments/views.py +++ b/weblab/experiments/views.py @@ -25,7 +25,7 @@ from .forms import ExperimentSimulateCallbackForm from .models import ( Experiment, - ExperimentVersion, + Runnable, PlannedExperiment, RunningExperiment, ) @@ -59,7 +59,7 @@ def get_queryset(self): def post(self, request): for running_exp_id in request.POST.getlist('chkBoxes[]'): - exp_version = ExperimentVersion.objects.get(id=running_exp_id) + exp_version = Runnable.objects.get(id=running_exp_id) if not exp_version.author == self.request.user: raise Http404 exp_version.delete() @@ -267,7 +267,7 @@ def get(self, request, *args, **kwargs): protocol__in=q_protocols, protocol_version__in=q_protocol_versions, ) - q_experiment_versions = ExperimentVersion.objects.filter( + q_experiment_versions = Runnable.objects.filter( experiment__in=q_experiments, ).order_by( 'experiment__id', @@ -309,7 +309,7 @@ def handle_no_permission(self): def post(self, request, *args, **kwargs): if 'rerun' in request.POST: - exp_ver = get_object_or_404(ExperimentVersion, pk=request.POST['rerun']) + exp_ver = get_object_or_404(Runnable, pk=request.POST['rerun']) exp = exp_ver.experiment model = exp.model protocol = exp.protocol @@ -323,8 +323,8 @@ def post(self, request, *args, **kwargs): version, is_new = submit_experiment(model, model_version, protocol, protocol_version, request.user, 'rerun' in request.POST or 'planned' in request.POST) - queued = version.status == ExperimentVersion.STATUS_QUEUED - if is_new and version.status != ExperimentVersion.STATUS_FAILED: + queued = version.status == Runnable.STATUS_QUEUED + if is_new and version.status != Runnable.STATUS_FAILED: # Remove from planned experiments PlannedExperiment.objects.filter( model=model, model_version=model_version, @@ -363,7 +363,7 @@ def post(self, request, *args, **kwargs): class ExperimentVersionView(VisibilityMixin, DetailView): - model = ExperimentVersion + model = Runnable context_object_name = 'version' @@ -385,7 +385,7 @@ class ExperimentVersionDeleteView(dataset_views.DatasetDeleteView): """ Delete a single version of an experiment """ - model = ExperimentVersion + model = Runnable def get_success_url(self, *args, **kwargs): return reverse('experiments:versions', args=[self.get_object().experiment.id]) @@ -399,7 +399,7 @@ class ExperimentComparisonView(TemplateView): def get_context_data(self, **kwargs): pks = set(map(int, self.kwargs['version_pks'].strip('/').split('/'))) - versions = ExperimentVersion.objects.filter(pk__in=pks).order_by('created_at') + versions = Runnable.objects.filter(pk__in=pks).order_by('created_at') versions = [v for v in versions if v.experiment.is_visible_to_user(self.request.user)] if len(versions) < len(pks): @@ -445,7 +445,7 @@ def _version_json(self, version, model_version_in_name, protocol_version_in_name def get(self, request, *args, **kwargs): pks = {int(pk) for pk in self.kwargs['version_pks'][1:].split('/') if pk} - versions = ExperimentVersion.objects.filter(pk__in=pks).order_by('created_at') + versions = Runnable.objects.filter(pk__in=pks).order_by('created_at') versions = [v for v in versions if v.experiment.is_visible_to_user(self.request.user)] models = set((v.experiment.model, v.experiment.model_version) for v in versions) @@ -472,7 +472,7 @@ class ExperimentSimulateCallbackView(FormMixin, DetailView): This is mainly for debug purposes. """ - model = ExperimentVersion + model = Runnable form_class = ExperimentSimulateCallbackForm template_name = 'experiments/simulate_callback_form.html' context_object_name = 'version' @@ -511,7 +511,7 @@ class ExperimentVersionJsonView(VisibilityMixin, SingleObjectMixin, View): """ Serve up json view of an experiment verson """ - model = ExperimentVersion + model = Runnable def get(self, request, *args, **kwargs): version = self.get_object() @@ -532,14 +532,14 @@ class ExperimentFileDownloadView(dataset_views.DatasetFileDownloadView): """ Download an individual file from an experiment """ - model = ExperimentVersion + model = Runnable class ExperimentVersionArchiveView(dataset_views.DatasetArchiveView): """ Download a combine archive of an experiment version """ - model = ExperimentVersion + model = Runnable def get_archive_name(self, version): """For historical reasons this is different from the archive_name.""" From 460aea34619ff4ff93f07485b0ae6ffbc67deb05 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Thu, 27 Feb 2020 14:21:52 +0000 Subject: [PATCH 02/22] Runnable class - resolve unit test failures --- ...remove_experimentversion_experiment_key.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py diff --git a/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py b/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py new file mode 100644 index 000000000..df6ea848e --- /dev/null +++ b/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py @@ -0,0 +1,20 @@ + +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-02-27 12:12 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0032_experimentversion_experiment'), + ] + + operations = [ + migrations.RemoveField( + model_name='experimentversion', + name='experiment_key', + ), + ] From f28644cdacf8bed2e474dc9551328f9d4b9f6b2a Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Thu, 27 Feb 2020 14:22:10 +0000 Subject: [PATCH 03/22] Runnable class - resolve unit test failures --- weblab/entities/views.py | 4 +-- .../0032_experimentversion_experiment.py | 22 +++++++++++++++ weblab/experiments/models.py | 2 +- weblab/experiments/processing.py | 8 +++--- weblab/experiments/views.py | 28 +++++++++---------- 5 files changed, 43 insertions(+), 21 deletions(-) create mode 100644 weblab/experiments/migrations/0032_experimentversion_experiment.py diff --git a/weblab/entities/views.py b/weblab/entities/views.py index 489c43828..765b3738e 100644 --- a/weblab/entities/views.py +++ b/weblab/entities/views.py @@ -38,7 +38,7 @@ from guardian.shortcuts import get_objects_for_user from core.visibility import Visibility, VisibilityMixin -from experiments.models import Experiment, Runnable, PlannedExperiment +from experiments.models import Experiment, ExperimentVersion, PlannedExperiment from fitting.models import FittingSpec from repocache.exceptions import RepoCacheMiss from repocache.models import CachedProtocolVersion @@ -1043,7 +1043,7 @@ def post(self, request, *args, **kwargs): 'experiment__' + this_entity.entity_type + '_id': this_entity.id, 'experiment__' + this_entity.entity_type + '_version_id': this_version.id, } - if Runnable.objects.filter(**filter_kwargs).exists(): + if ExperimentVersion.objects.filter(**filter_kwargs).exists(): continue exper_kwargs = { 'submitter': request.user, diff --git a/weblab/experiments/migrations/0032_experimentversion_experiment.py b/weblab/experiments/migrations/0032_experimentversion_experiment.py new file mode 100644 index 000000000..8272795fa --- /dev/null +++ b/weblab/experiments/migrations/0032_experimentversion_experiment.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.27 on 2020-02-27 12:11 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0031_remove_runnable_experiment'), + ] + + operations = [ + migrations.AddField( + model_name='experimentversion', + name='experiment', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='experiments.Experiment'), + preserve_default=False, + ), + ] diff --git a/weblab/experiments/models.py b/weblab/experiments/models.py index 3c1a736ac..e4c491f44 100644 --- a/weblab/experiments/models.py +++ b/weblab/experiments/models.py @@ -198,7 +198,7 @@ def update(self, status, txt): class ExperimentVersion(Runnable): - experiment_key = models.CharField(max_length=50) + experiment = models.ForeignKey(Experiment, related_name='versions') @property def parent(self): diff --git a/weblab/experiments/processing.py b/weblab/experiments/processing.py index 3b04b77fc..2cc03cdad 100644 --- a/weblab/experiments/processing.py +++ b/weblab/experiments/processing.py @@ -9,7 +9,7 @@ from django.utils.timezone import now from .emails import send_experiment_finished_email -from .models import Experiment, Runnable, RunningExperiment +from .models import Experiment, Runnable, RunningExperiment, ExperimentVersion logger = logging.getLogger(__name__) @@ -59,18 +59,18 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re # Check there isn't an existing version if we're not allowed to re-run if not rerun_ok: try: - version, created = Runnable.objects.get_or_create( + version, created = ExperimentVersion.objects.get_or_create( experiment=experiment, defaults={ 'author': user, } ) except MultipleObjectsReturned: - return Runnable.objects.filter(experiment=experiment).latest('created_at'), False + return ExperimentVersion.objects.filter(experiment=experiment).latest('created_at'), False if not created: return version, False else: - version = Runnable.objects.create( + version = ExperimentVersion.objects.create( experiment=experiment, author=user, ) diff --git a/weblab/experiments/views.py b/weblab/experiments/views.py index 28e8efc8e..047aa7929 100644 --- a/weblab/experiments/views.py +++ b/weblab/experiments/views.py @@ -25,7 +25,7 @@ from .forms import ExperimentSimulateCallbackForm from .models import ( Experiment, - Runnable, + ExperimentVersion, PlannedExperiment, RunningExperiment, ) @@ -59,7 +59,7 @@ def get_queryset(self): def post(self, request): for running_exp_id in request.POST.getlist('chkBoxes[]'): - exp_version = Runnable.objects.get(id=running_exp_id) + exp_version = ExperimentVersion.objects.get(id=running_exp_id) if not exp_version.author == self.request.user: raise Http404 exp_version.delete() @@ -267,7 +267,7 @@ def get(self, request, *args, **kwargs): protocol__in=q_protocols, protocol_version__in=q_protocol_versions, ) - q_experiment_versions = Runnable.objects.filter( + q_experiment_versions = ExperimentVersion.objects.filter( experiment__in=q_experiments, ).order_by( 'experiment__id', @@ -309,7 +309,7 @@ def handle_no_permission(self): def post(self, request, *args, **kwargs): if 'rerun' in request.POST: - exp_ver = get_object_or_404(Runnable, pk=request.POST['rerun']) + exp_ver = get_object_or_404(ExperimentVersion, pk=request.POST['rerun']) exp = exp_ver.experiment model = exp.model protocol = exp.protocol @@ -323,8 +323,8 @@ def post(self, request, *args, **kwargs): version, is_new = submit_experiment(model, model_version, protocol, protocol_version, request.user, 'rerun' in request.POST or 'planned' in request.POST) - queued = version.status == Runnable.STATUS_QUEUED - if is_new and version.status != Runnable.STATUS_FAILED: + queued = version.status == ExperimentVersion.STATUS_QUEUED + if is_new and version.status != ExperimentVersion.STATUS_FAILED: # Remove from planned experiments PlannedExperiment.objects.filter( model=model, model_version=model_version, @@ -363,7 +363,7 @@ def post(self, request, *args, **kwargs): class ExperimentVersionView(VisibilityMixin, DetailView): - model = Runnable + model = ExperimentVersion context_object_name = 'version' @@ -385,7 +385,7 @@ class ExperimentVersionDeleteView(dataset_views.DatasetDeleteView): """ Delete a single version of an experiment """ - model = Runnable + model = ExperimentVersion def get_success_url(self, *args, **kwargs): return reverse('experiments:versions', args=[self.get_object().experiment.id]) @@ -399,7 +399,7 @@ class ExperimentComparisonView(TemplateView): def get_context_data(self, **kwargs): pks = set(map(int, self.kwargs['version_pks'].strip('/').split('/'))) - versions = Runnable.objects.filter(pk__in=pks).order_by('created_at') + versions = ExperimentVersion.objects.filter(pk__in=pks).order_by('created_at') versions = [v for v in versions if v.experiment.is_visible_to_user(self.request.user)] if len(versions) < len(pks): @@ -445,7 +445,7 @@ def _version_json(self, version, model_version_in_name, protocol_version_in_name def get(self, request, *args, **kwargs): pks = {int(pk) for pk in self.kwargs['version_pks'][1:].split('/') if pk} - versions = Runnable.objects.filter(pk__in=pks).order_by('created_at') + versions = ExperimentVersion.objects.filter(pk__in=pks).order_by('created_at') versions = [v for v in versions if v.experiment.is_visible_to_user(self.request.user)] models = set((v.experiment.model, v.experiment.model_version) for v in versions) @@ -472,7 +472,7 @@ class ExperimentSimulateCallbackView(FormMixin, DetailView): This is mainly for debug purposes. """ - model = Runnable + model = ExperimentVersion form_class = ExperimentSimulateCallbackForm template_name = 'experiments/simulate_callback_form.html' context_object_name = 'version' @@ -511,7 +511,7 @@ class ExperimentVersionJsonView(VisibilityMixin, SingleObjectMixin, View): """ Serve up json view of an experiment verson """ - model = Runnable + model = ExperimentVersion def get(self, request, *args, **kwargs): version = self.get_object() @@ -532,14 +532,14 @@ class ExperimentFileDownloadView(dataset_views.DatasetFileDownloadView): """ Download an individual file from an experiment """ - model = Runnable + model = ExperimentVersion class ExperimentVersionArchiveView(dataset_views.DatasetArchiveView): """ Download a combine archive of an experiment version """ - model = Runnable + model = ExperimentVersion def get_archive_name(self, version): """For historical reasons this is different from the archive_name.""" From a0eb41140a4488e1a9c501b6146d1cef2ee3dfe4 Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Thu, 27 Feb 2020 15:47:02 +0000 Subject: [PATCH 04/22] Changed linkage to experiment via experiment version --- weblab/entities/views.py | 2 +- weblab/experiments/admin.py | 10 +++++++++- weblab/experiments/views.py | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/weblab/entities/views.py b/weblab/entities/views.py index 765b3738e..ca30260c2 100644 --- a/weblab/entities/views.py +++ b/weblab/entities/views.py @@ -663,7 +663,7 @@ def check_access_token(self, token): """ from entities.models import AnalysisTask from experiments.models import RunningExperiment - entity_field = 'experiment_version__experiment__%s' % self.kwargs['entity_type'] + entity_field = 'experiment_version__experimentversion__experiment__%s' % self.kwargs['entity_type'] self_id = self._get_object().id return (RunningExperiment.objects.filter( id=token, diff --git a/weblab/experiments/admin.py b/weblab/experiments/admin.py index d1fd84fa4..0c4ab262c 100644 --- a/weblab/experiments/admin.py +++ b/weblab/experiments/admin.py @@ -1,5 +1,13 @@ from django.contrib import admin -from .models import Experiment, Runnable +from .models import Experiment, ExperimentVersion +class ExperimentVersionInline(admin.StackedInline): + model = ExperimentVersion + extra = 0 + + +@admin.register(Experiment) +class ExperimentAdmin(admin.ModelAdmin): + inlines = [ExperimentVersionInline] \ No newline at end of file diff --git a/weblab/experiments/views.py b/weblab/experiments/views.py index 047aa7929..294e8f71e 100644 --- a/weblab/experiments/views.py +++ b/weblab/experiments/views.py @@ -55,7 +55,7 @@ def get_queryset(self): experiment_version__author=self.request.user ).order_by( 'experiment_version__created_at', - ).select_related('experiment_version', 'experiment_version__experiment') + ).select_related('experiment_version', 'experiment_version__experimentversion__experiment') def post(self, request): for running_exp_id in request.POST.getlist('chkBoxes[]'): From d6f2972f2954d8744200373d7b74213b11f0c1f5 Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Thu, 27 Feb 2020 16:55:53 +0000 Subject: [PATCH 05/22] renamed migration method to reference correct entity --- weblab/experiments/migrations/0028_auto_20200212_1701.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weblab/experiments/migrations/0028_auto_20200212_1701.py b/weblab/experiments/migrations/0028_auto_20200212_1701.py index 7084d7ad7..034500e72 100644 --- a/weblab/experiments/migrations/0028_auto_20200212_1701.py +++ b/weblab/experiments/migrations/0028_auto_20200212_1701.py @@ -5,7 +5,7 @@ from django.db import migrations -def populate_runnable(apps, schema_editor): +def populate_exp_version(apps, schema_editor): ExperimentVersion = apps.get_model('experiments', 'ExperimentVersion') Runnable = apps.get_model('experiments', 'Runnable') for runnable in Runnable.objects.all(): @@ -23,5 +23,5 @@ class Migration(migrations.Migration): ] operations = [ - migrations.RunPython(populate_runnable), + migrations.RunPython(populate_exp_version), ] From c982b88a95e642defc819dfde049d82b62c1e304 Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Thu, 27 Feb 2020 16:59:21 +0000 Subject: [PATCH 06/22] Whitespace issue --- weblab/experiments/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weblab/experiments/admin.py b/weblab/experiments/admin.py index 0c4ab262c..d1f591730 100644 --- a/weblab/experiments/admin.py +++ b/weblab/experiments/admin.py @@ -10,4 +10,4 @@ class ExperimentVersionInline(admin.StackedInline): @admin.register(Experiment) class ExperimentAdmin(admin.ModelAdmin): - inlines = [ExperimentVersionInline] \ No newline at end of file + inlines = [ExperimentVersionInline] From d6f0629b8cb14ee2a6ca8ec84b2c78a6697589c5 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 28 Feb 2020 12:24:49 +0000 Subject: [PATCH 07/22] change migration path for upgrade to runnable base class --- weblab/config/settings/base.py | 2 +- weblab/experiments/apps.py | 4 ++-- weblab/experiments/forms.py | 4 ++-- .../0027_experimentversion_experiment_key.py | 21 ------------------- ...20200212_1701.py => 0027_move_runnable.py} | 2 +- ....py => 0028_remove_runnable_experiment.py} | 2 +- .../migrations/0029_auto_20200212_1731.py | 21 ------------------- ...y => 0029_experimentversion_experiment.py} | 2 +- .../migrations/0030_auto_20200212_1732.py | 21 ------------------- ...remove_experimentversion_experiment_key.py | 20 ------------------ weblab/experiments/models.py | 17 ++++++++++++--- weblab/experiments/signals.py | 2 +- 12 files changed, 23 insertions(+), 95 deletions(-) delete mode 100644 weblab/experiments/migrations/0027_experimentversion_experiment_key.py rename weblab/experiments/migrations/{0028_auto_20200212_1701.py => 0027_move_runnable.py} (93%) rename weblab/experiments/migrations/{0031_remove_runnable_experiment.py => 0028_remove_runnable_experiment.py} (87%) delete mode 100644 weblab/experiments/migrations/0029_auto_20200212_1731.py rename weblab/experiments/migrations/{0032_experimentversion_experiment.py => 0029_experimentversion_experiment.py} (90%) delete mode 100644 weblab/experiments/migrations/0030_auto_20200212_1732.py delete mode 100644 weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py diff --git a/weblab/config/settings/base.py b/weblab/config/settings/base.py index 94cd9b89a..4e4cb93f9 100644 --- a/weblab/config/settings/base.py +++ b/weblab/config/settings/base.py @@ -117,7 +117,7 @@ # configure db url from DATABASE_URL env var if supplied DATABASES['default'] = dj_database_url.config( - default='postgres://weblab:weblab@localhost:5432/weblab' + default='postgres://postgres:postgres@localhost:5432/postgres5' ) diff --git a/weblab/experiments/apps.py b/weblab/experiments/apps.py index 156ae9065..3775bf0ce 100644 --- a/weblab/experiments/apps.py +++ b/weblab/experiments/apps.py @@ -1,7 +1,7 @@ from django.apps import AppConfig from django.db.models.signals import pre_delete -from .signals import experiment_version_deleted, running_experiment_deleted +from .signals import runnable_deleted, running_experiment_deleted class ExperimentsConfig(AppConfig): @@ -10,5 +10,5 @@ class ExperimentsConfig(AppConfig): def ready(self): from .models import Runnable, RunningExperiment - pre_delete.connect(experiment_version_deleted, Runnable) + pre_delete.connect(runnable_deleted, Runnable) pre_delete.connect(running_experiment_deleted, RunningExperiment) diff --git a/weblab/experiments/forms.py b/weblab/experiments/forms.py index 48a839ad2..6d2e6c38a 100644 --- a/weblab/experiments/forms.py +++ b/weblab/experiments/forms.py @@ -1,6 +1,6 @@ from django import forms -from .models import Runnable +from .models import ExperimentVersion from .processing import ChasteProcessingStatus @@ -21,5 +21,5 @@ class ExperimentSimulateCallbackForm(forms.ModelForm): upload = forms.FileField(required=False) class Meta: - model = Runnable + model = ExperimentVersion fields = () diff --git a/weblab/experiments/migrations/0027_experimentversion_experiment_key.py b/weblab/experiments/migrations/0027_experimentversion_experiment_key.py deleted file mode 100644 index 5b394100b..000000000 --- a/weblab/experiments/migrations/0027_experimentversion_experiment_key.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 17:00 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0026_experimentversion'), - ] - - operations = [ - migrations.AddField( - model_name='experimentversion', - name='experiment_key', - field=models.CharField(default=1, max_length=50), - preserve_default=False, - ), - ] diff --git a/weblab/experiments/migrations/0028_auto_20200212_1701.py b/weblab/experiments/migrations/0027_move_runnable.py similarity index 93% rename from weblab/experiments/migrations/0028_auto_20200212_1701.py rename to weblab/experiments/migrations/0027_move_runnable.py index 034500e72..d60aa9087 100644 --- a/weblab/experiments/migrations/0028_auto_20200212_1701.py +++ b/weblab/experiments/migrations/0027_move_runnable.py @@ -19,7 +19,7 @@ def populate_exp_version(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('experiments', '0027_experimentversion_experiment_key'), + ('experiments', '0026_experimentversion'), ] operations = [ diff --git a/weblab/experiments/migrations/0031_remove_runnable_experiment.py b/weblab/experiments/migrations/0028_remove_runnable_experiment.py similarity index 87% rename from weblab/experiments/migrations/0031_remove_runnable_experiment.py rename to weblab/experiments/migrations/0028_remove_runnable_experiment.py index 6f537205d..c6b2cdf2e 100644 --- a/weblab/experiments/migrations/0031_remove_runnable_experiment.py +++ b/weblab/experiments/migrations/0028_remove_runnable_experiment.py @@ -8,7 +8,7 @@ class Migration(migrations.Migration): dependencies = [ - ('experiments', '0030_auto_20200212_1732'), + ('experiments', '0027_move_runnable'), ] operations = [ diff --git a/weblab/experiments/migrations/0029_auto_20200212_1731.py b/weblab/experiments/migrations/0029_auto_20200212_1731.py deleted file mode 100644 index b7380ec46..000000000 --- a/weblab/experiments/migrations/0029_auto_20200212_1731.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 17:31 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0028_auto_20200212_1701'), - ] - - operations = [ - migrations.AlterField( - model_name='runnable', - name='experiment', - field=models.ForeignKey(db_constraint=False, on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment'), - ), - ] diff --git a/weblab/experiments/migrations/0032_experimentversion_experiment.py b/weblab/experiments/migrations/0029_experimentversion_experiment.py similarity index 90% rename from weblab/experiments/migrations/0032_experimentversion_experiment.py rename to weblab/experiments/migrations/0029_experimentversion_experiment.py index 8272795fa..2fd39d9ff 100644 --- a/weblab/experiments/migrations/0032_experimentversion_experiment.py +++ b/weblab/experiments/migrations/0029_experimentversion_experiment.py @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ('experiments', '0031_remove_runnable_experiment'), + ('experiments', '0028_remove_runnable_experiment'), ] operations = [ diff --git a/weblab/experiments/migrations/0030_auto_20200212_1732.py b/weblab/experiments/migrations/0030_auto_20200212_1732.py deleted file mode 100644 index cd9cc9fd8..000000000 --- a/weblab/experiments/migrations/0030_auto_20200212_1732.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 17:32 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0029_auto_20200212_1731'), - ] - - operations = [ - migrations.AlterField( - model_name='runnable', - name='experiment', - field=models.ForeignKey(db_constraint=False, db_index=False, on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment'), - ), - ] diff --git a/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py b/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py deleted file mode 100644 index df6ea848e..000000000 --- a/weblab/experiments/migrations/0033_remove_experimentversion_experiment_key.py +++ /dev/null @@ -1,20 +0,0 @@ - -# -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2020-02-27 12:12 -from __future__ import unicode_literals - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0032_experimentversion_experiment'), - ] - - operations = [ - migrations.RemoveField( - model_name='experimentversion', - name='experiment_key', - ), - ] diff --git a/weblab/experiments/models.py b/weblab/experiments/models.py index e4c491f44..a273c6503 100644 --- a/weblab/experiments/models.py +++ b/weblab/experiments/models.py @@ -111,6 +111,11 @@ def latest_result(self): class Runnable(UserCreatedModelMixin, FileCollectionMixin, models.Model): + """ Runnable base class + Represents experiments and fitting specs that have the facility to + run on the back-end, + The current status of the run is recorded as well as the and results when completed. + """ STATUS_QUEUED = "QUEUED" STATUS_RUNNING = "RUNNING" STATUS_SUCCESS = "SUCCESS" @@ -197,7 +202,11 @@ def update(self, status, txt): class ExperimentVersion(Runnable): - + """ ExperimentVersion class + This records a single run of a particular Experiment. + The same model/protocol combination may be run more than once, + resulting in an Experiment having multiple versions. + """ experiment = models.ForeignKey(Experiment, related_name='versions') @property @@ -207,8 +216,10 @@ def parent(self): class RunningExperiment(models.Model): - """ - A current run of an ExperimentVersion + """ Class to track an in-progress Runnable instance. + It adds functionality to link to the task id on the back-end system, so that returned results + can be linked to the appropriate Runnable. + The running tasks can be cancelled by deleting using the front-end. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) diff --git a/weblab/experiments/signals.py b/weblab/experiments/signals.py index b35a7ce53..befb51892 100644 --- a/weblab/experiments/signals.py +++ b/weblab/experiments/signals.py @@ -1,7 +1,7 @@ from shutil import rmtree -def experiment_version_deleted(sender, instance, **kwargs): +def runnable_deleted(sender, instance, **kwargs): """ Signal callback when an experiment version is about to be deleted. From ae7aa195a1caa7a308e2a4ca77d409a113d55408 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 28 Feb 2020 17:36:49 +0000 Subject: [PATCH 08/22] revert accidental check-in --- weblab/config/settings/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weblab/config/settings/base.py b/weblab/config/settings/base.py index 4e4cb93f9..94cd9b89a 100644 --- a/weblab/config/settings/base.py +++ b/weblab/config/settings/base.py @@ -117,7 +117,7 @@ # configure db url from DATABASE_URL env var if supplied DATABASES['default'] = dj_database_url.config( - default='postgres://postgres:postgres@localhost:5432/postgres5' + default='postgres://weblab:weblab@localhost:5432/weblab' ) From 34ad42a1c8d045acb29e617af20bb213ace87558 Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 6 Mar 2020 11:04:54 +0000 Subject: [PATCH 09/22] Changed migration link to runnable --- weblab/experiments/migrations/0027_move_runnable.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/weblab/experiments/migrations/0027_move_runnable.py b/weblab/experiments/migrations/0027_move_runnable.py index d60aa9087..b2ddac692 100644 --- a/weblab/experiments/migrations/0027_move_runnable.py +++ b/weblab/experiments/migrations/0027_move_runnable.py @@ -9,12 +9,10 @@ def populate_exp_version(apps, schema_editor): ExperimentVersion = apps.get_model('experiments', 'ExperimentVersion') Runnable = apps.get_model('experiments', 'Runnable') for runnable in Runnable.objects.all(): - experiment_version = ExperimentVersion(id=runnable.id, - created_at=runnable.created_at, - author_id=runnable.author_id, - experiment_id=runnable.experiment_id, - experiment_key=runnable.experiment_id) - experiment_version.save() + + experiment_version = ExperimentVersion(runnable_ptr=runnable, + experiment_id=runnable.experiment_id) + experiment_version.save_base(raw=True) class Migration(migrations.Migration): From 90e70f8e2d869c33c0221a3345027720e177cba2 Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Fri, 6 Mar 2020 12:08:13 +0000 Subject: [PATCH 10/22] Corrected mapping to experiment in email template --- weblab/templates/emails/experiment_finished.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/weblab/templates/emails/experiment_finished.txt b/weblab/templates/emails/experiment_finished.txt index 6a2240b64..5b7e75272 100644 --- a/weblab/templates/emails/experiment_finished.txt +++ b/weblab/templates/emails/experiment_finished.txt @@ -4,7 +4,7 @@ An experiment you submitted has finished. Status: {{ experiment_version.status }} -The resulting files can be viewed at: {{ base_url }}{% url 'experiments:version' experiment_version.experiment.id experiment_version.id %} +The resulting files can be viewed at: {{ base_url }}{% url 'experiments:version' experiment_version.experimentversion.experiment.id experiment_version.id %} Your sincerely, Cardiac Web Lab website From 6da1298c295a5d00e58042e18269ab402e8cb63f Mon Sep 17 00:00:00 2001 From: Steve Roderick Date: Fri, 6 Mar 2020 15:16:25 +0000 Subject: [PATCH 11/22] Changed migration link to runnable --- .../migrations/0026_experimentversion.py | 7 ++++++ .../migrations/0027_move_runnable.py | 2 +- .../0028_remove_runnable_experiment.py | 1 + .../0029_experimentversion_experiment.py | 22 +++++++++++-------- 4 files changed, 22 insertions(+), 10 deletions(-) diff --git a/weblab/experiments/migrations/0026_experimentversion.py b/weblab/experiments/migrations/0026_experimentversion.py index e9d1911b9..0b32514d6 100644 --- a/weblab/experiments/migrations/0026_experimentversion.py +++ b/weblab/experiments/migrations/0026_experimentversion.py @@ -23,4 +23,11 @@ class Migration(migrations.Migration): }, bases=('experiments.runnable',), ), + + migrations.AddField( + model_name='ExperimentVersion', + name='experiment_key', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment', + related_name='versions_copy'), + ), ] diff --git a/weblab/experiments/migrations/0027_move_runnable.py b/weblab/experiments/migrations/0027_move_runnable.py index b2ddac692..8d29f7d56 100644 --- a/weblab/experiments/migrations/0027_move_runnable.py +++ b/weblab/experiments/migrations/0027_move_runnable.py @@ -11,7 +11,7 @@ def populate_exp_version(apps, schema_editor): for runnable in Runnable.objects.all(): experiment_version = ExperimentVersion(runnable_ptr=runnable, - experiment_id=runnable.experiment_id) + experiment_key_id=runnable.experiment_id) experiment_version.save_base(raw=True) diff --git a/weblab/experiments/migrations/0028_remove_runnable_experiment.py b/weblab/experiments/migrations/0028_remove_runnable_experiment.py index c6b2cdf2e..0aac3daad 100644 --- a/weblab/experiments/migrations/0028_remove_runnable_experiment.py +++ b/weblab/experiments/migrations/0028_remove_runnable_experiment.py @@ -12,6 +12,7 @@ class Migration(migrations.Migration): ] operations = [ + migrations.RemoveField( model_name='runnable', name='experiment', diff --git a/weblab/experiments/migrations/0029_experimentversion_experiment.py b/weblab/experiments/migrations/0029_experimentversion_experiment.py index 2fd39d9ff..8a9fb74e6 100644 --- a/weblab/experiments/migrations/0029_experimentversion_experiment.py +++ b/weblab/experiments/migrations/0029_experimentversion_experiment.py @@ -11,12 +11,16 @@ class Migration(migrations.Migration): dependencies = [ ('experiments', '0028_remove_runnable_experiment'), ] - - operations = [ - migrations.AddField( - model_name='experimentversion', - name='experiment', - field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='experiments.Experiment'), - preserve_default=False, - ), - ] +operations = [ + migrations.RenameField( + model_name='ExperimentVersion', + old_name='experiment_key', + new_name='experiment', + ), + migrations.AlterField( + model_name='ExperimentVersion', + name='experiment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', + to='experiments.Experiment'), + ), +] From 13e86e88c0a3c369abab615c1373cf763a481fde Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Mon, 16 Mar 2020 14:41:38 +0000 Subject: [PATCH 12/22] Refactor experiment_version to runnable --- weblab/conftest.py | 2 +- weblab/core/recipes.py | 4 +++- weblab/entities/tests/test_views.py | 2 +- weblab/entities/views.py | 2 +- weblab/experiments/emails.py | 6 +++--- weblab/experiments/models.py | 2 +- weblab/experiments/processing.py | 4 ++-- weblab/experiments/tests/test_models.py | 2 +- weblab/experiments/tests/test_views.py | 12 ++++++------ weblab/experiments/views.py | 7 ++++--- weblab/templates/emails/experiment_finished.txt | 4 ++-- 11 files changed, 25 insertions(+), 22 deletions(-) diff --git a/weblab/conftest.py b/weblab/conftest.py index 39ba2f6d8..77197e12e 100644 --- a/weblab/conftest.py +++ b/weblab/conftest.py @@ -177,7 +177,7 @@ def queued_experiment(model_with_version, protocol_with_version): experiment__protocol=protocol_with_version, experiment__protocol_version=protocol_with_version.repocache.latest_version, ) - recipes.running_experiment.make(experiment_version=version) + recipes.running_experiment.make(runnable=version) return version diff --git a/weblab/core/recipes.py b/weblab/core/recipes.py index dc8715f20..5d9bf19fa 100644 --- a/weblab/core/recipes.py +++ b/weblab/core/recipes.py @@ -38,9 +38,11 @@ protocol_version=foreign_key(cached_protocol_version), ) +runnable = Recipe('Runnable') + experiment_version = Recipe('ExperimentVersion', experiment=foreign_key(experiment)) -running_experiment = Recipe('RunningExperiment', experiment_version=foreign_key(experiment_version)) +running_experiment = Recipe('RunningExperiment', runnable=foreign_key(runnable)) dataset = Recipe('Dataset', diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index 0177e8120..d965d0bc8 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -483,7 +483,7 @@ def test_applies_visibility(self, client, helpers, experiment_version): exp.model.set_version_visibility('latest', 'public') exp.protocol.set_version_visibility('latest', 'public') protocol_version = helpers.add_version(protocol, visibility='private') - recipes.experiment_version.make( + recipes.runnable.make( experiment__protocol=protocol, experiment__protocol_version=protocol.repocache.get_version(protocol_version.sha), experiment__model=exp.model, diff --git a/weblab/entities/views.py b/weblab/entities/views.py index ca30260c2..8cf72e589 100644 --- a/weblab/entities/views.py +++ b/weblab/entities/views.py @@ -663,7 +663,7 @@ def check_access_token(self, token): """ from entities.models import AnalysisTask from experiments.models import RunningExperiment - entity_field = 'experiment_version__experimentversion__experiment__%s' % self.kwargs['entity_type'] + entity_field = 'runnable__experimentversion__experiment__%s' % self.kwargs['entity_type'] self_id = self._get_object().id return (RunningExperiment.objects.filter( id=token, diff --git a/weblab/experiments/emails.py b/weblab/experiments/emails.py index c7cc10c1e..ef20b7662 100644 --- a/weblab/experiments/emails.py +++ b/weblab/experiments/emails.py @@ -3,15 +3,15 @@ from django.template.loader import render_to_string -def send_experiment_finished_email(experiment_version): - author = experiment_version.author +def send_experiment_finished_email(runnable): + author = runnable.author if author.receive_emails: body = render_to_string( 'emails/experiment_finished.txt', { 'user': author, - 'experiment_version': experiment_version, + 'runnable': runnable, 'base_url': settings.BASE_URL, } ) diff --git a/weblab/experiments/models.py b/weblab/experiments/models.py index a273c6503..fcd12f954 100644 --- a/weblab/experiments/models.py +++ b/weblab/experiments/models.py @@ -223,7 +223,7 @@ class RunningExperiment(models.Model): """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) - experiment_version = models.ForeignKey(Runnable, related_name='running') + runnable = models.ForeignKey(Runnable, related_name='running') task_id = models.CharField(max_length=50) diff --git a/weblab/experiments/processing.py b/weblab/experiments/processing.py index 2cc03cdad..b25969315 100644 --- a/weblab/experiments/processing.py +++ b/weblab/experiments/processing.py @@ -75,7 +75,7 @@ def submit_experiment(model, model_version, protocol, protocol_version, user, re author=user, ) - run = RunningExperiment.objects.create(experiment_version=version) + run = RunningExperiment.objects.create(runnable=version) signature = version.signature model_url = reverse( @@ -161,7 +161,7 @@ def process_callback(data, files): try: run = RunningExperiment.objects.get(id=signature) - exp = run.experiment_version + exp = run.runnable except RunningExperiment.DoesNotExist: return {'error': 'invalid signature'} diff --git a/weblab/experiments/tests/test_models.py b/weblab/experiments/tests/test_models.py index e83ff92fd..d9453a1fd 100644 --- a/weblab/experiments/tests/test_models.py +++ b/weblab/experiments/tests/test_models.py @@ -147,7 +147,7 @@ def test_archive_path(self, fake_experiment_path): def test_signature(self): running = recipes.running_experiment.make() - assert running.experiment_version.signature == str(running.id) + assert running.runnable.signature == str(running.id) @pytest.mark.parametrize('status, is_running', [ ('QUEUED', False), diff --git a/weblab/experiments/tests/test_views.py b/weblab/experiments/tests/test_views.py index a60c0d1d4..382fdb498 100644 --- a/weblab/experiments/tests/test_views.py +++ b/weblab/experiments/tests/test_views.py @@ -970,7 +970,7 @@ def test_load_page_logged_in_user(self, client): def test_get_queryset_other_user(self, other_user, client, experiment_version): experiment_version.author = other_user experiment_version.save() - recipes.running_experiment.make(experiment_version=experiment_version) + recipes.running_experiment.make(runnable=experiment_version) assert RunningExperiment.objects.count() == 1 response = client.get('/experiments/tasks') assert len(response.context['runningexperiment_list']) == 0 @@ -1002,7 +1002,7 @@ def test_get_queryset(self, logged_in_user, client, helpers): experiment__protocol_version=protocol_1.repocache.get_version(protocol_1_version2.sha), author=logged_in_user, ) - running_exp_version2 = recipes.running_experiment.make(experiment_version=exp_version_2) + running_exp_version2 = recipes.running_experiment.make(runnable=exp_version_2) exp_version_3 = recipes.experiment_version.make( status=ExperimentVersion.STATUS_RUNNING, @@ -1012,7 +1012,7 @@ def test_get_queryset(self, logged_in_user, client, helpers): experiment__protocol_version=protocol_2.repocache.get_version(protocol_2_version.sha), author=logged_in_user, ) - running_exp_version3 = recipes.running_experiment.make(experiment_version=exp_version_3) + running_exp_version3 = recipes.running_experiment.make(runnable=exp_version_3) assert ExperimentVersion.objects.count() == 3 assert RunningExperiment.objects.count() == 2 @@ -1022,7 +1022,7 @@ def test_get_queryset(self, logged_in_user, client, helpers): assert set(response.context['runningexperiment_list']) == {running_exp_version2, running_exp_version3} # Cancel one of the running versions check the other one is still present - client.post('/experiments/tasks', {'chkBoxes[]': [running_exp_version2.experiment_version.id]}) + client.post('/experiments/tasks', {'chkBoxes[]': [running_exp_version2.runnable.id]}) response = client.get('/experiments/tasks') assert set(response.context['runningexperiment_list']) == {running_exp_version3} assert RunningExperiment.objects.count() == 1 @@ -1031,9 +1031,9 @@ def test_get_queryset(self, logged_in_user, client, helpers): def test_returns_404_incorrect_owner(self, other_user, client, experiment_version): experiment_version.author = other_user experiment_version.save() - running_exp = recipes.running_experiment.make(experiment_version=experiment_version) + running_exp = recipes.running_experiment.make(runnable=experiment_version) assert RunningExperiment.objects.count() == 1 - response = client.post('/experiments/tasks', {'chkBoxes[]': [running_exp.experiment_version.id]}) + response = client.post('/experiments/tasks', {'chkBoxes[]': [running_exp.runnable.id]}) assert RunningExperiment.objects.count() == 1 assert response.status_code == 404 diff --git a/weblab/experiments/views.py b/weblab/experiments/views.py index 294e8f71e..42638d5d3 100644 --- a/weblab/experiments/views.py +++ b/weblab/experiments/views.py @@ -52,10 +52,11 @@ class ExperimentTasks(LoginRequiredMixin, ListView): def get_queryset(self): return RunningExperiment.objects.filter( - experiment_version__author=self.request.user + runnable__author=self.request.user ).order_by( - 'experiment_version__created_at', - ).select_related('experiment_version', 'experiment_version__experimentversion__experiment') + 'runnable__created_at', + ).select_related('runnable' + '', 'runnable__experimentversion__experiment') def post(self, request): for running_exp_id in request.POST.getlist('chkBoxes[]'): diff --git a/weblab/templates/emails/experiment_finished.txt b/weblab/templates/emails/experiment_finished.txt index 5b7e75272..f1e7b9900 100644 --- a/weblab/templates/emails/experiment_finished.txt +++ b/weblab/templates/emails/experiment_finished.txt @@ -2,9 +2,9 @@ Hi {{ user.full_name }} An experiment you submitted has finished. -Status: {{ experiment_version.status }} +Status: {{ runnable.status }} -The resulting files can be viewed at: {{ base_url }}{% url 'experiments:version' experiment_version.experimentversion.experiment.id experiment_version.id %} +The resulting files can be viewed at: {{ base_url }}{% url 'experiments:version' runnable.experimentversion.experiment.id runnable.id %} Your sincerely, Cardiac Web Lab website From dfe184f2b40046d204f6ed457a995d8381c87faa Mon Sep 17 00:00:00 2001 From: "steve@roderick.com" Date: Mon, 16 Mar 2020 15:13:31 +0000 Subject: [PATCH 13/22] isort correction --- weblab/experiments/processing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/weblab/experiments/processing.py b/weblab/experiments/processing.py index b25969315..40c8c62f9 100644 --- a/weblab/experiments/processing.py +++ b/weblab/experiments/processing.py @@ -9,7 +9,12 @@ from django.utils.timezone import now from .emails import send_experiment_finished_email -from .models import Experiment, Runnable, RunningExperiment, ExperimentVersion +from .models import ( + Experiment, + ExperimentVersion, + Runnable, + RunningExperiment, +) logger = logging.getLogger(__name__) From 41a8e98660b7c7fdae76d91b163bf3de1cb37e54 Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Mon, 16 Mar 2020 17:13:03 +0000 Subject: [PATCH 14/22] Alternative approach to migrations These were created by removing much of the view logic etc (but not committing) so I could get Django to make the migrations automatically, with the exception of the one RunPython migration to move the experiment keys between models. Experiment tests pass and I can now view the matrix after migrating my local data. I'd recommend more manual testing that things still work before merging though! --- ...212_1633.py => 0025_auto_20200316_1652.py} | 2 +- .../migrations/0026_experimentversion.py | 12 +++------ .../migrations/0027_move_runnable.py | 18 ++++++++++--- .../migrations/0028_auto_20200316_1706.py | 21 +++++++++++++++ .../0029_experimentversion_experiment.py | 26 ------------------- ....py => 0029_remove_runnable_experiment.py} | 5 ++-- .../migrations/0030_auto_20200316_1707.py | 20 ++++++++++++++ .../migrations/0031_auto_20200316_1708.py | 21 +++++++++++++++ 8 files changed, 82 insertions(+), 43 deletions(-) rename weblab/experiments/migrations/{0025_auto_20200212_1633.py => 0025_auto_20200316_1652.py} (93%) create mode 100644 weblab/experiments/migrations/0028_auto_20200316_1706.py delete mode 100644 weblab/experiments/migrations/0029_experimentversion_experiment.py rename weblab/experiments/migrations/{0028_remove_runnable_experiment.py => 0029_remove_runnable_experiment.py} (74%) create mode 100644 weblab/experiments/migrations/0030_auto_20200316_1707.py create mode 100644 weblab/experiments/migrations/0031_auto_20200316_1708.py diff --git a/weblab/experiments/migrations/0025_auto_20200212_1633.py b/weblab/experiments/migrations/0025_auto_20200316_1652.py similarity index 93% rename from weblab/experiments/migrations/0025_auto_20200212_1633.py rename to weblab/experiments/migrations/0025_auto_20200316_1652.py index 142f8f42c..43e9d91a0 100644 --- a/weblab/experiments/migrations/0025_auto_20200212_1633.py +++ b/weblab/experiments/migrations/0025_auto_20200316_1652.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 16:33 +# Generated by Django 1.11.20 on 2020-03-16 16:52 from __future__ import unicode_literals from django.db import migrations, models diff --git a/weblab/experiments/migrations/0026_experimentversion.py b/weblab/experiments/migrations/0026_experimentversion.py index 0b32514d6..e0913ae0a 100644 --- a/weblab/experiments/migrations/0026_experimentversion.py +++ b/weblab/experiments/migrations/0026_experimentversion.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 16:36 +# Generated by Django 1.11.20 on 2020-03-16 17:00 from __future__ import unicode_literals from django.db import migrations, models @@ -9,7 +9,7 @@ class Migration(migrations.Migration): dependencies = [ - ('experiments', '0025_auto_20200212_1633'), + ('experiments', '0025_auto_20200316_1652'), ] operations = [ @@ -17,17 +17,11 @@ class Migration(migrations.Migration): name='ExperimentVersion', fields=[ ('runnable_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='experiments.Runnable')), + ('experiment_key', models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='versions_copy', to='experiments.Experiment')), ], options={ 'abstract': False, }, bases=('experiments.runnable',), ), - - migrations.AddField( - model_name='ExperimentVersion', - name='experiment_key', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='experiments.Experiment', - related_name='versions_copy'), - ), ] diff --git a/weblab/experiments/migrations/0027_move_runnable.py b/weblab/experiments/migrations/0027_move_runnable.py index 8d29f7d56..4389faa89 100644 --- a/weblab/experiments/migrations/0027_move_runnable.py +++ b/weblab/experiments/migrations/0027_move_runnable.py @@ -1,25 +1,35 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-12 17:01 +# Generated by Django 1.11.20 on 2020-03-16 17:02 from __future__ import unicode_literals from django.db import migrations -def populate_exp_version(apps, schema_editor): + +def runnable_to_exp_version(apps, schema_editor): ExperimentVersion = apps.get_model('experiments', 'ExperimentVersion') Runnable = apps.get_model('experiments', 'Runnable') for runnable in Runnable.objects.all(): - experiment_version = ExperimentVersion(runnable_ptr=runnable, experiment_key_id=runnable.experiment_id) experiment_version.save_base(raw=True) +def exp_version_to_runnable(apps, schema_editor): + ExperimentVersion = apps.get_model('experiments', 'ExperimentVersion') + Runnable = apps.get_model('experiments', 'Runnable') + for experiment_version in ExperimentVersion.objects.all(): + runnable = experiment_version.runnable_ptr + runnable.experiment_id = experiment_version.experiment_key_id + runnable.save() + + class Migration(migrations.Migration): + dependencies = [ ('experiments', '0026_experimentversion'), ] operations = [ - migrations.RunPython(populate_exp_version), + migrations.RunPython(runnable_to_exp_version, exp_version_to_runnable), ] diff --git a/weblab/experiments/migrations/0028_auto_20200316_1706.py b/weblab/experiments/migrations/0028_auto_20200316_1706.py new file mode 100644 index 000000000..016d1ebda --- /dev/null +++ b/weblab/experiments/migrations/0028_auto_20200316_1706.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2020-03-16 17:06 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0027_move_runnable'), + ] + + operations = [ + migrations.AlterField( + model_name='runnable', + name='experiment', + field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='versions', to='experiments.Experiment'), + ), + ] diff --git a/weblab/experiments/migrations/0029_experimentversion_experiment.py b/weblab/experiments/migrations/0029_experimentversion_experiment.py deleted file mode 100644 index 8a9fb74e6..000000000 --- a/weblab/experiments/migrations/0029_experimentversion_experiment.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.11.27 on 2020-02-27 12:11 -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('experiments', '0028_remove_runnable_experiment'), - ] -operations = [ - migrations.RenameField( - model_name='ExperimentVersion', - old_name='experiment_key', - new_name='experiment', - ), - migrations.AlterField( - model_name='ExperimentVersion', - name='experiment', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', - to='experiments.Experiment'), - ), -] diff --git a/weblab/experiments/migrations/0028_remove_runnable_experiment.py b/weblab/experiments/migrations/0029_remove_runnable_experiment.py similarity index 74% rename from weblab/experiments/migrations/0028_remove_runnable_experiment.py rename to weblab/experiments/migrations/0029_remove_runnable_experiment.py index 0aac3daad..482808398 100644 --- a/weblab/experiments/migrations/0028_remove_runnable_experiment.py +++ b/weblab/experiments/migrations/0029_remove_runnable_experiment.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.23 on 2020-02-13 10:19 +# Generated by Django 1.11.20 on 2020-03-16 17:06 from __future__ import unicode_literals from django.db import migrations @@ -8,11 +8,10 @@ class Migration(migrations.Migration): dependencies = [ - ('experiments', '0027_move_runnable'), + ('experiments', '0028_auto_20200316_1706'), ] operations = [ - migrations.RemoveField( model_name='runnable', name='experiment', diff --git a/weblab/experiments/migrations/0030_auto_20200316_1707.py b/weblab/experiments/migrations/0030_auto_20200316_1707.py new file mode 100644 index 000000000..7b35ed1e9 --- /dev/null +++ b/weblab/experiments/migrations/0030_auto_20200316_1707.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2020-03-16 17:07 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0029_remove_runnable_experiment'), + ] + + operations = [ + migrations.RenameField( + model_name='experimentversion', + old_name='experiment_key', + new_name='experiment', + ), + ] diff --git a/weblab/experiments/migrations/0031_auto_20200316_1708.py b/weblab/experiments/migrations/0031_auto_20200316_1708.py new file mode 100644 index 000000000..80a0bee59 --- /dev/null +++ b/weblab/experiments/migrations/0031_auto_20200316_1708.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2020-03-16 17:08 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0030_auto_20200316_1707'), + ] + + operations = [ + migrations.AlterField( + model_name='experimentversion', + name='experiment', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='versions', to='experiments.Experiment'), + ), + ] From f094e4923f1cc729ad065d94b408f7945b06f63c Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 09:17:40 +0000 Subject: [PATCH 15/22] Fix template so it refers to runnable --- weblab/templates/experiments/experiment_tasks.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/weblab/templates/experiments/experiment_tasks.html b/weblab/templates/experiments/experiment_tasks.html index 7bb5aced8..33d95e96b 100644 --- a/weblab/templates/experiments/experiment_tasks.html +++ b/weblab/templates/experiments/experiment_tasks.html @@ -33,18 +33,20 @@

List of active tasks

{% endif %} {% for running_experiment in object_list %} + {% with running_experiment.runnable as runnable %}
  • -
    - {{ running_experiment.experiment_version.experiment.name }} Version: {{ running_experiment.experiment_version.name }} + value="{{ runnable.id }}"> +
    + {{ runnable.experimentversion.experiment.name }} Version: {{ runnable.name }}
+ {% endwith %} {% endfor %} From 7f8bbdcfffd03c555d03a7232f2efa3ca5706aa0 Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 09:30:57 +0000 Subject: [PATCH 16/22] Tidying --- weblab/experiments/views.py | 4 ++-- weblab/templates/experiments/experiment_tasks.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/weblab/experiments/views.py b/weblab/experiments/views.py index 42638d5d3..0514d8784 100644 --- a/weblab/experiments/views.py +++ b/weblab/experiments/views.py @@ -55,8 +55,8 @@ def get_queryset(self): runnable__author=self.request.user ).order_by( 'runnable__created_at', - ).select_related('runnable' - '', 'runnable__experimentversion__experiment') + ).select_related('runnable', + 'runnable__experimentversion__experiment') def post(self, request): for running_exp_id in request.POST.getlist('chkBoxes[]'): diff --git a/weblab/templates/experiments/experiment_tasks.html b/weblab/templates/experiments/experiment_tasks.html index 33d95e96b..09325ef9c 100644 --- a/weblab/templates/experiments/experiment_tasks.html +++ b/weblab/templates/experiments/experiment_tasks.html @@ -39,7 +39,7 @@

List of active tasks

  • + value="{{ runnable.experimentversion.id }}">
    {{ runnable.experimentversion.experiment.name }} Version: {{ runnable.name }}
    From 0450c5102fcb913cb31fae051cea2ca45dd59175 Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 09:31:07 +0000 Subject: [PATCH 17/22] I missed a migration! --- .../migrations/0032_auto_20200317_0927.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 weblab/experiments/migrations/0032_auto_20200317_0927.py diff --git a/weblab/experiments/migrations/0032_auto_20200317_0927.py b/weblab/experiments/migrations/0032_auto_20200317_0927.py new file mode 100644 index 000000000..42cba7888 --- /dev/null +++ b/weblab/experiments/migrations/0032_auto_20200317_0927.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.20 on 2020-03-17 09:27 +from __future__ import unicode_literals + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('experiments', '0031_auto_20200316_1708'), + ] + + operations = [ + migrations.RenameField( + model_name='runningexperiment', + old_name='experiment_version', + new_name='runnable', + ), + ] From b10b44703e7330af8f5e6c89a6b7132403c0e46a Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 09:31:24 +0000 Subject: [PATCH 18/22] Fix the expt detail view --- weblab/templates/experiments/experimentversion_detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weblab/templates/experiments/experimentversion_detail.html b/weblab/templates/experiments/experimentversion_detail.html index 898411813..6fc0ab82f 100644 --- a/weblab/templates/experiments/experimentversion_detail.html +++ b/weblab/templates/experiments/experimentversion_detail.html @@ -45,9 +45,9 @@

    {% endif %}
    Corresponding model: - {{ experiment.model.name }} @ {{ experiment.nice_model_version }} + {{ experiment.model.name }} @ {{ experiment.nice_model_version }} & protocol: - {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }} + {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }}

    From 8b1ee4bba76e508132064da505ec0577fc57d2fd Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 09:57:28 +0000 Subject: [PATCH 19/22] Why don't tests flag this as an error? --- requirements/dev.txt | 3 +-- requirements/test.txt | 4 ++-- weblab/experiments/tests/test_views.py | 6 +++++- weblab/templates/experiments/experimentversion_detail.html | 4 ++-- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/requirements/dev.txt b/requirements/dev.txt index 96e7cc398..418255ce9 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -16,8 +16,7 @@ defusedxml==0.5.0 # via python3-openid, social-auth-core dj-database-url==0.4.2 django-braces==1.11.0 django-guardian==1.4.9 -django==1.11.27 -first==2.0.2 # via pip-tools +django==1.11.28 flake8==3.4.1 gitdb2==2.0.5 # via gitpython gitpython==2.1.7 diff --git a/requirements/test.txt b/requirements/test.txt index 14a5a997f..ee8cbb947 100644 --- a/requirements/test.txt +++ b/requirements/test.txt @@ -28,8 +28,8 @@ pycodestyle==2.5.0 # via flake8 pyflakes==2.1.1 # via flake8 pyjwt==1.7.1 # via social-auth-core pytest-cov==2.5.1 -pytest-django==3.1.2 -pytest==3.2.0 +pytest-django==3.8.0 +pytest==3.6.0 python3-openid==3.1.0 # via social-auth-core pytz==2018.9 # via django requests-oauthlib==1.2.0 # via social-auth-core diff --git a/weblab/experiments/tests/test_views.py b/weblab/experiments/tests/test_views.py index 382fdb498..ad2df62ba 100644 --- a/weblab/experiments/tests/test_views.py +++ b/weblab/experiments/tests/test_views.py @@ -13,6 +13,7 @@ from django.core.urlresolvers import reverse from django.test import Client from django.utils.dateparse import parse_datetime +from pytest_django.asserts import assertContains, assertTemplateUsed from core import recipes from experiments.models import ( @@ -950,8 +951,11 @@ def test_view_experiment_version(self, client, experiment_version): experiment_version.pk)) ) - assert response.status_code == 200 assert response.context['version'] == experiment_version + assertTemplateUsed(response, 'experiments/experimentversion_detail.html') + assertContains(response, 'Download archive of all files') + print(response.content) + assert False @pytest.mark.django_db diff --git a/weblab/templates/experiments/experimentversion_detail.html b/weblab/templates/experiments/experimentversion_detail.html index 6fc0ab82f..898411813 100644 --- a/weblab/templates/experiments/experimentversion_detail.html +++ b/weblab/templates/experiments/experimentversion_detail.html @@ -45,9 +45,9 @@

    {% endif %}
    Corresponding model: - {{ experiment.model.name }} @ {{ experiment.nice_model_version }} + {{ experiment.model.name }} @ {{ experiment.nice_model_version }} & protocol: - {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }} + {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }} From 4abf49fcc4f76108c50b7cd531d170411a3b5982 Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 16:58:09 +0000 Subject: [PATCH 20/22] Now the test fails for the right reason! --- weblab/core/recipes.py | 10 +++++----- weblab/experiments/tests/test_views.py | 2 -- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/weblab/core/recipes.py b/weblab/core/recipes.py index 5d9bf19fa..65d75ca91 100644 --- a/weblab/core/recipes.py +++ b/weblab/core/recipes.py @@ -1,19 +1,19 @@ from model_mommy.recipe import Recipe, foreign_key, seq -user = Recipe('accounts.User', institution='UCL') +user = Recipe('accounts.User', institution='UCL', full_name=seq('test user ')) model = Recipe( 'ModelEntity', - entity_type='model', name=seq('mymodel') + entity_type='model', name=seq('my model') ) protocol = Recipe( 'ProtocolEntity', - entity_type='protocol', name=seq('myprotocol') + entity_type='protocol', name=seq('my protocol') ) fittingspec = Recipe( 'FittingSpec', - entity_type='fittingspec', name=seq('myspec'), + entity_type='fittingspec', name=seq('my spec'), protocol=foreign_key(protocol), ) @@ -46,7 +46,7 @@ dataset = Recipe('Dataset', - name=seq('mydataset'), + name=seq('my dataset'), protocol=foreign_key(protocol)) dataset_file = Recipe('DatasetFile', diff --git a/weblab/experiments/tests/test_views.py b/weblab/experiments/tests/test_views.py index ad2df62ba..98109c57c 100644 --- a/weblab/experiments/tests/test_views.py +++ b/weblab/experiments/tests/test_views.py @@ -954,8 +954,6 @@ def test_view_experiment_version(self, client, experiment_version): assert response.context['version'] == experiment_version assertTemplateUsed(response, 'experiments/experimentversion_detail.html') assertContains(response, 'Download archive of all files') - print(response.content) - assert False @pytest.mark.django_db From f6a190c6841685a5804a18af212d5a6f515f8814 Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 16:59:50 +0000 Subject: [PATCH 21/22] Fix the test again --- weblab/templates/experiments/experimentversion_detail.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/weblab/templates/experiments/experimentversion_detail.html b/weblab/templates/experiments/experimentversion_detail.html index 898411813..6fc0ab82f 100644 --- a/weblab/templates/experiments/experimentversion_detail.html +++ b/weblab/templates/experiments/experimentversion_detail.html @@ -45,9 +45,9 @@

    {% endif %}
    Corresponding model: - {{ experiment.model.name }} @ {{ experiment.nice_model_version }} + {{ experiment.model.name }} @ {{ experiment.nice_model_version }} & protocol: - {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }} + {{ experiment.protocol.name }} @ {{ experiment.nice_protocol_version }} From 3384655203c8e0c4bb276f8a72c6c374364bf5dd Mon Sep 17 00:00:00 2001 From: Jonathan Cooper Date: Tue, 17 Mar 2020 17:09:41 +0000 Subject: [PATCH 22/22] Fix other tests that assumed entity names --- weblab/entities/tests/test_views.py | 44 ++++++++++++++--------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/weblab/entities/tests/test_views.py b/weblab/entities/tests/test_views.py index d965d0bc8..e38f454f0 100644 --- a/weblab/entities/tests/test_views.py +++ b/weblab/entities/tests/test_views.py @@ -431,10 +431,10 @@ def test_complex_visibilities(self, client, logged_in_user, other_user, helpers) assert len(interfaces) == 4 expected = { - 'myprotocol1': {'required': ['p1r2'], 'optional': ['p1o2']}, - 'myprotocol2': {'required': ['p2r2'], 'optional': ['p2o2']}, - 'myprotocol3': {'required': ['p3r1'], 'optional': ['p3o1']}, - 'myprotocol4': {'required': ['p4r3'], 'optional': ['p4o3']}, + protocol1.name: {'required': ['p1r2'], 'optional': ['p1o2']}, + protocol2.name: {'required': ['p2r2'], 'optional': ['p2o2']}, + protocol3.name: {'required': ['p3r1'], 'optional': ['p3o1']}, + protocol4.name: {'required': ['p4r3'], 'optional': ['p4o3']}, } for iface in interfaces: assert iface['name'] in expected @@ -1666,7 +1666,7 @@ def test_download_archive(self, client, helpers): archive = zipfile.ZipFile(BytesIO(response.content)) assert archive.filelist[0].filename == 'file1.txt' assert response['Content-Disposition'] == ( - 'attachment; filename=%s_%s.zip' % (model.name, commit.sha) + 'attachment; filename=%s_%s.zip' % (model.name.replace(' ', '_'), commit.sha) ) def test_returns_404_if_no_commits_yet(self, logged_in_user, client): @@ -2115,7 +2115,7 @@ def test_view_run_experiment_model(self, client, helpers, logged_in_user): assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2147,14 +2147,14 @@ def test_view_run_experiment_model_multiple_users(self, client, helpers, logged_ assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] assert response.context['other_object_list'] == [ {'id': other_protocol.pk, 'entity': other_protocol, - 'name': 'myprotocol2', + 'name': other_protocol.name, 'versions': [{'commit': other_version2, 'tags': ['v1'], 'latest': True}, {'commit': other_version1, 'tags': [], 'latest': False}]}, ] @@ -2179,7 +2179,7 @@ def test_view_run_experiment_model_post(self, client, helpers, logged_in_user): assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2229,7 +2229,7 @@ def test_view_run_experiment_model_post_exclude_existing(self, client, helpers, assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2281,14 +2281,14 @@ def test_view_run_experiment_post_model_multiple_users(self, client, helpers, lo assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] assert response.context['other_object_list'] == [ {'id': other_protocol.pk, 'entity': other_protocol, - 'name': 'myprotocol2', + 'name': other_protocol.name, 'versions': [{'commit': other_version2, 'tags': ['v1'], 'latest': True}, {'commit': other_version1, 'tags': [], 'latest': False}]}, ] @@ -2338,7 +2338,7 @@ def test_view_run_experiment_model_not_latest(self, client, helpers, logged_in_u assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2384,7 +2384,7 @@ def test_view_run_experiment_protocol(self, client, helpers, logged_in_user): assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2416,14 +2416,14 @@ def test_view_run_experiment_protocol_multiple_users(self, client, helpers, logg assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] assert response.context['other_object_list'] == [ {'id': other_model.pk, 'entity': other_model, - 'name': 'mymodel2', + 'name': other_model.name, 'versions': [{'commit': other_version2, 'tags': ['v1'], 'latest': True}, {'commit': other_version1, 'tags': [], 'latest': False}]}, ] @@ -2446,7 +2446,7 @@ def test_view_run_experiment_protocol_post(self, client, helpers, logged_in_user assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2502,7 +2502,7 @@ def test_view_run_experiment_protocol_post_exclude_existing(self, client, helper assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2551,14 +2551,14 @@ def test_view_run_experiment_post_protocol_multiple_users(self, client, helpers, assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] assert response.context['other_object_list'] == [ {'id': other_model.pk, 'entity': other_model, - 'name': 'mymodel2', + 'name': other_model.name, 'versions': [{'commit': other_version2, 'tags': ['v1'], 'latest': True}, {'commit': other_version1, 'tags': [], 'latest': False}]}, ] @@ -2605,7 +2605,7 @@ def test_view_run_experiment_none_checked(self, client, helpers, logged_in_user) assert response.status_code == 200 assert response.context['object_list'] == [{'id': protocol.pk, 'entity': protocol, - 'name': 'myprotocol1', + 'name': protocol.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ] @@ -2653,7 +2653,7 @@ def test_view_run_experiment_protocol_not_latest(self, client, helpers, logged_i assert response.status_code == 200 assert response.context['object_list'] == [{'id': model.pk, 'entity': model, - 'name': 'mymodel1', + 'name': model.name, 'versions': [{'commit': version2, 'tags': ['v1'], 'latest': True}, {'commit': version1, 'tags': [], 'latest': False}]}, ]