From 7ecb974fffe32ffd5947180b270ae09ad3d276fc Mon Sep 17 00:00:00 2001
From: Jorrit Schaap <schaap@astron.nl>
Date: Thu, 2 Sep 2021 21:35:17 +0200
Subject: [PATCH] TMSS-917: now that we reverted to a single parent
 TaskBlueprint for a Subtask there is no need anymore for a SubtaskOutput to
 have a task_blueprint reference.

---
 .../scheduling/lib/dynamic_scheduling.py      |  2 +-
 .../tmss/tmssapp/migrations/0001_initial.py   |  7 +-----
 .../src/tmss/tmssapp/models/scheduling.py     |  1 -
 SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py | 25 +++++++------------
 .../test/t_tmssapp_scheduling_REST_API.py     | 20 ---------------
 .../test/t_tmssapp_scheduling_django_API.py   | 21 +++++++---------
 SAS/TMSS/backend/test/test_environment.py     |  8 +++---
 .../test/tmss_test_data_django_models.py      |  6 +----
 SAS/TMSS/backend/test/tmss_test_data_rest.py  |  6 +----
 9 files changed, 26 insertions(+), 70 deletions(-)

diff --git a/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py b/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py
index 296c28b0f2d..bbf6e5d542b 100644
--- a/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py
+++ b/SAS/TMSS/backend/services/scheduling/lib/dynamic_scheduling.py
@@ -293,7 +293,7 @@ def create_dynamic_scheduling_service(exchange: str=DEFAULT_BUSNAME, broker: str
 def get_dynamically_schedulable_scheduling_units() -> [models.SchedulingUnitBlueprint]:
     '''get a list of all dynamically schedulable scheduling_units'''
     defined_independend_subtasks = models.Subtask.independent_subtasks().filter(state__value='defined')
-    defined_independend_subtask_ids = defined_independend_subtasks.values('task_blueprints__scheduling_unit_blueprint_id').distinct().all()
+    defined_independend_subtask_ids = defined_independend_subtasks.values('task_blueprint__scheduling_unit_blueprint_id').distinct().all()
     scheduling_units = models.SchedulingUnitBlueprint.objects.filter(id__in=defined_independend_subtask_ids) \
                                                              .filter(scheduling_constraints_template__isnull=False).all()
     return [su for su in scheduling_units if su.status == 'schedulable']
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
index 8942a477ae8..b39c19f3e09 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 3.0.9 on 2021-09-02 15:48
+# Generated by Django 3.0.9 on 2021-09-02 19:01
 
 from django.conf import settings
 import django.contrib.auth.models
@@ -1170,11 +1170,6 @@ class Migration(migrations.Migration):
             name='subtask',
             field=models.ForeignKey(help_text='Subtask to which this output specification refers.', on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='tmssapp.Subtask'),
         ),
-        migrations.AddField(
-            model_name='subtaskoutput',
-            name='task_blueprint',
-            field=models.ForeignKey(help_text='Task to which this output specification refers.', on_delete=django.db.models.deletion.CASCADE, related_name='outputs', to='tmssapp.TaskBlueprint'),
-        ),
         migrations.AddField(
             model_name='subtaskinput',
             name='dataproducts',
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
index 72eabea2299..60806b9b48e 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/models/scheduling.py
@@ -386,7 +386,6 @@ class SubtaskInput(BasicCommon, TemplateSchemaMixin):
 
 class SubtaskOutput(BasicCommon):
     subtask = ForeignKey('Subtask', null=False, on_delete=CASCADE, related_name='outputs', help_text='Subtask to which this output specification refers.')
-    task_blueprint = ForeignKey('TaskBlueprint', null=False, on_delete=CASCADE, related_name='outputs', help_text='Task to which this output specification refers.')
 
 
 class SAP(BasicCommon, TemplateSchemaMixin):
diff --git a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
index b389b09bb52..3def90606c9 100644
--- a/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
+++ b/SAS/TMSS/backend/src/tmss/tmssapp/subtasks.py
@@ -500,8 +500,7 @@ def create_observation_control_subtask_from_task_blueprint(task_blueprint: TaskB
 
     # step 2: create and link subtask input/output
     # an observation has no input, it just produces output data
-    subtask_output = SubtaskOutput.objects.create(subtask=subtask,
-                                                  task_blueprint=task_blueprint)
+    subtask_output = SubtaskOutput.objects.create(subtask=subtask)
 
     # step 3: set state to DEFINED
     subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value)
@@ -566,8 +565,7 @@ def create_qafile_subtask_from_observation_subtask(observation_subtask: Subtask)
                                                            selection_doc=selection_doc,
                                                            selection_template=selection_template)
 
-    qafile_subtask_output = SubtaskOutput.objects.create(subtask=qafile_subtask,
-                                                         task_blueprint=observation_subtask.task_blueprint)
+    qafile_subtask_output = SubtaskOutput.objects.create(subtask=qafile_subtask)
 
     # step 3: set state to DEFINED
     qafile_subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value)
@@ -643,8 +641,7 @@ def create_qaplots_subtask_from_qafile_subtask(qafile_subtask: Subtask) -> Subta
                                                         selection_doc=selection_doc,
                                                         selection_template=selection_template)
 
-    qaplots_subtask_output = SubtaskOutput.objects.create(subtask=qaplots_subtask,
-                                                          task_blueprint=qafile_subtask.task_blueprint)
+    qaplots_subtask_output = SubtaskOutput.objects.create(subtask=qaplots_subtask)
 
     # step 3: set state to DEFINED
     qaplots_subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value)
@@ -695,14 +692,12 @@ def create_pipeline_subtask_from_task_blueprint(task_blueprint: TaskBlueprint, s
         predecessor_observation_subtasks = [st for st in producing_task_blueprint.subtasks.order_by('id').all() if st.specifications_template.type.value == SubtaskType.Choices.OBSERVATION.value]
         for predecessor_obs_subtask in predecessor_observation_subtasks:
             for predecessor_subtask_output in predecessor_obs_subtask.outputs.all():
-                if predecessor_subtask_output.task_blueprint == task_relation_blueprint.producer:
-                    subtask_input = SubtaskInput.objects.create(subtask=subtask,
-                                                                producer=predecessor_subtask_output,
-                                                                selection_doc=task_relation_blueprint.selection_doc,
-                                                                selection_template=task_relation_blueprint.selection_template)
+                subtask_input = SubtaskInput.objects.create(subtask=subtask,
+                                                            producer=predecessor_subtask_output,
+                                                            selection_doc=task_relation_blueprint.selection_doc,
+                                                            selection_template=task_relation_blueprint.selection_template)
 
-    subtask_output = SubtaskOutput.objects.create(subtask=subtask,
-                                                  task_blueprint=task_blueprint)
+    subtask_output = SubtaskOutput.objects.create(subtask=subtask)
 
     # step 3: set state to DEFINED
     subtask.state = SubtaskState.objects.get(value=SubtaskState.Choices.DEFINED.value)
@@ -1800,9 +1795,7 @@ def schedule_ingest_subtask(ingest_subtask: Subtask):
         ingest_subtask_input.dataproducts.set(input_dataproducts)
 
         # define output and create output dataproducts.
-        tb = ingest_subtask_input.producer.task_blueprint  # output dataproducts are linked to the same task as its input dataproduct
-        ingest_subtask_output = SubtaskOutput.objects.create(subtask=ingest_subtask,
-                                                             task_blueprint=tb)
+        ingest_subtask_output = SubtaskOutput.objects.create(subtask=ingest_subtask)
 
         # prepare identifiers in bulk for each output_dataproduct
         dp_gids = [SIPidentifier(source="TMSS") for _ in input_dataproducts]
diff --git a/SAS/TMSS/backend/test/t_tmssapp_scheduling_REST_API.py b/SAS/TMSS/backend/test/t_tmssapp_scheduling_REST_API.py
index d9fa22bf3c0..165cbf1e71e 100755
--- a/SAS/TMSS/backend/test/t_tmssapp_scheduling_REST_API.py
+++ b/SAS/TMSS/backend/test/t_tmssapp_scheduling_REST_API.py
@@ -343,26 +343,6 @@ class SubtaskTestCase(unittest.TestCase):
         # DELETE and check it's gone
         DELETE_and_assert_gone(self, url)
 
-    def test_subtask_PROTECT_behavior_on_state_choice_deleted(self):
-        st_test_data = test_data_creator.Subtask(cluster_url=self.cluster_url, task_blueprint_url=self.task_blueprint_url, specifications_template_url=self.specifications_template_url)
-
-        # create dependency that is safe to delete (enums are not populated / re-established between tests)
-        state_data = {'value': 'defining'}
-        POST_and_assert_expected_response(self, BASE_URL + '/subtask_state/', state_data, 201, state_data)
-        state_url =  BASE_URL + '/subtask_state/kickme'
-
-        # POST new item and verify
-        test_data = dict(st_test_data)
-        test_data['state'] = state_url
-        url = POST_and_assert_expected_response(self, BASE_URL + '/subtask/', test_data, 201, test_data)['url']
-        GET_OK_and_assert_equal_expected_response(self, url, test_data)
-
-        # Try to DELETE dependency, verify that was not successful
-        # Unfortunately we don't get a nice error in json, but a Django debug page on error 500...
-        response = requests.delete(state_url, auth=AUTH)
-        self.assertEqual(500, response.status_code)
-        self.assertTrue("ProtectedError" in str(response.content))
-        GET_OK_and_assert_equal_expected_response(self, state_url, state_data)
 
     def test_subtask_SET_NULL_behavior_on_task_blueprint_deleted(self):
         # make new task_blueprint_url instance, but reuse related data for speed
diff --git a/SAS/TMSS/backend/test/t_tmssapp_scheduling_django_API.py b/SAS/TMSS/backend/test/t_tmssapp_scheduling_django_API.py
index 4006b45507b..8208d564f31 100755
--- a/SAS/TMSS/backend/test/t_tmssapp_scheduling_django_API.py
+++ b/SAS/TMSS/backend/test/t_tmssapp_scheduling_django_API.py
@@ -132,7 +132,6 @@ class SubtaskOutputTest(unittest.TestCase):
         # setup
         test_data = dict(SubtaskOutput_test_data())
         test_data['subtask'] = None
-        test_data['task_blueprint'] = None
 
         # assert
         with self.assertRaises(IntegrityError):
@@ -233,7 +232,7 @@ class SubtaskTest(unittest.TestCase):
         subtask1:models.Subtask = models.Subtask.objects.create(**Subtask_test_data(task_blueprint=self.task_blueprint))
         subtask2:models.Subtask = models.Subtask.objects.create(**Subtask_test_data(task_blueprint=self.task_blueprint))
 
-        output1 = models.SubtaskOutput.objects.create(subtask=subtask1, task_blueprint=self.task_blueprint)
+        output1 = models.SubtaskOutput.objects.create(subtask=subtask1)
         models.SubtaskInput.objects.create(**SubtaskInput_test_data(subtask=subtask2, producer=output1))
 
         self.assertEqual(subtask1, subtask2.predecessors.all()[0])
@@ -251,12 +250,12 @@ class SubtaskTest(unittest.TestCase):
         #      |        |
         # ST2 -          -> ST5 ---> ST6
 
-        output1 = models.SubtaskOutput.objects.create(subtask=subtask1, task_blueprint=self.task_blueprint)
-        output2 = models.SubtaskOutput.objects.create(subtask=subtask2, task_blueprint=self.task_blueprint)
-        output3 = models.SubtaskOutput.objects.create(subtask=subtask3, task_blueprint=self.task_blueprint)
-        output4 = models.SubtaskOutput.objects.create(subtask=subtask4, task_blueprint=self.task_blueprint)
-        output5 = models.SubtaskOutput.objects.create(subtask=subtask5, task_blueprint=self.task_blueprint)
-        output6 = models.SubtaskOutput.objects.create(subtask=subtask6, task_blueprint=self.task_blueprint)
+        output1 = models.SubtaskOutput.objects.create(subtask=subtask1)
+        output2 = models.SubtaskOutput.objects.create(subtask=subtask2)
+        output3 = models.SubtaskOutput.objects.create(subtask=subtask3)
+        output4 = models.SubtaskOutput.objects.create(subtask=subtask4)
+        output5 = models.SubtaskOutput.objects.create(subtask=subtask5)
+        output6 = models.SubtaskOutput.objects.create(subtask=subtask6)
 
         models.SubtaskInput.objects.create(**SubtaskInput_test_data(subtask=subtask3, producer=output1))
         models.SubtaskInput.objects.create(**SubtaskInput_test_data(subtask=subtask3, producer=output2))
@@ -278,8 +277,7 @@ class SubtaskTest(unittest.TestCase):
     def test_Subtask_transformed_dataproducts(self):
         # setup
         subtask1:models.Subtask = models.Subtask.objects.create(**Subtask_test_data())
-        output1:models.SubtaskOutput = models.SubtaskOutput.objects.create(subtask=subtask1,
-                                                                           task_blueprint=self.task_blueprint)
+        output1:models.SubtaskOutput = models.SubtaskOutput.objects.create(subtask=subtask1)
         output1_dp:models.Dataproduct = models.Dataproduct.objects.create(**Dataproduct_test_data(producer=output1))
 
         subtask2:models.Subtask = models.Subtask.objects.create(**Subtask_test_data())
@@ -287,8 +285,7 @@ class SubtaskTest(unittest.TestCase):
         input2_dp = output1_dp
         input2.dataproducts.set([input2_dp])
         input2.save()
-        output2:models.SubtaskOutput = models.SubtaskOutput.objects.create(subtask=subtask2,
-                                                                           task_blueprint=self.task_blueprint)
+        output2:models.SubtaskOutput = models.SubtaskOutput.objects.create(subtask=subtask2)
         output2_dp:models.Dataproduct = models.Dataproduct.objects.create(**Dataproduct_test_data(producer=output2))
 
         models.DataproductTransform.objects.create(input=input2_dp, output=output2_dp, identity=True)
diff --git a/SAS/TMSS/backend/test/test_environment.py b/SAS/TMSS/backend/test/test_environment.py
index 183ded21b40..5654fadc6e2 100644
--- a/SAS/TMSS/backend/test/test_environment.py
+++ b/SAS/TMSS/backend/test/test_environment.py
@@ -671,7 +671,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int
             self.create_output_dataproducts = create_output_dataproducts
 
         def need_to_handle(self, subtask: models.Subtask) -> bool:
-            if self.scheduling_unit_blueprint_id not in [tb.scheduling_unit_blueprint.id for tb in subtask.task_blueprints.all()]:
+            if self.scheduling_unit_blueprint_id != subtask.task_blueprint.scheduling_unit_blueprint.id:
                 return False
 
             if subtask.specifications_template.type.value == models.SubtaskType.Choices.OBSERVATION.value and not self.handle_observations:
@@ -712,7 +712,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int
             super().start_handling()
 
             # trick: trigger any already scheduled subtasks, cascading in events simulating the run
-            scheduled_subtasks = models.Subtask.objects.filter(task_blueprints__scheduling_unit_blueprint_id=self.scheduling_unit_blueprint_id).filter(state__value=models.SubtaskState.Choices.SCHEDULED.value).all()
+            scheduled_subtasks = models.Subtask.objects.filter(task_blueprint__scheduling_unit_blueprint_id=self.scheduling_unit_blueprint_id).filter(state__value=models.SubtaskState.Choices.SCHEDULED.value).all()
             for subtask in scheduled_subtasks:
                 self.onSubTaskStatusChanged(subtask.id, "scheduled")
 
@@ -802,7 +802,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int
                         output_dp.feedback_doc = feedback_doc
                         output_dp.save()
                 elif subtask.specifications_template.type.value == models.SubtaskType.Choices.INGEST.value:
-                    project_name = subtask.task_blueprints.first().draft.scheduling_unit_draft.scheduling_set.project.name    # todo: support for multiple projects needs to be picked up in TMSS-689
+                    project_name = subtask.project.name    # todo: support for multiple projects needs to be picked up in TMSS-689
 
                     for output_dp in subtask.output_dataproducts:
                         try:
@@ -834,7 +834,7 @@ def create_scheduling_unit_blueprint_simulator(scheduling_unit_blueprint_id: int
 
                     if subtask.specifications_template.type.value == 'ingest':
                         logger.info("subtask id=%d is an ingest task which requires permission in order to be scheduled", subtask.id)
-                        if self.auto_grant_ingest_permission and any([tb.scheduling_unit_blueprint.ingest_permission_required for tb in subtask.task_blueprints.all()]):
+                        if self.auto_grant_ingest_permission and subtask.task_blueprint.scheduling_unit_blueprint.ingest_permission_required:
                             # just granting the permission triggers the scheduling_service to check and schedulable ingest subtasks,
                             # resulting in a scheduled ingest subtask.
                             logger.info("granting ingest subtask id=%d ingest_permission", subtask.id)
diff --git a/SAS/TMSS/backend/test/tmss_test_data_django_models.py b/SAS/TMSS/backend/test/tmss_test_data_django_models.py
index 60553c4230c..a2942421cd8 100644
--- a/SAS/TMSS/backend/test/tmss_test_data_django_models.py
+++ b/SAS/TMSS/backend/test/tmss_test_data_django_models.py
@@ -374,15 +374,11 @@ def DataproductFeedbackTemplate_test_data() -> dict:
             "schema": minimal_json_schema(),
             "tags": ["TMSS", "TESTING"]}
 
-def SubtaskOutput_test_data(subtask: models.Subtask=None, task_blueprint: models.TaskBlueprint=None) -> dict:
+def SubtaskOutput_test_data(subtask: models.Subtask=None) -> dict:
     if subtask is None:
         subtask = models.Subtask.objects.create(**Subtask_test_data())
 
-    if task_blueprint is None:
-        task_blueprint = models. TaskBlueprint.objects.create(**TaskBlueprint_test_data(()))
-
     return {"subtask": subtask,
-            "task_blueprint": task_blueprint,
             "tags":[]}
 
 def SubtaskInput_test_data(subtask: models.Subtask=None, producer: models.SubtaskOutput=None, selection_doc=None, selection_template: models.TaskRelationSelectionTemplate=None) -> dict:
diff --git a/SAS/TMSS/backend/test/tmss_test_data_rest.py b/SAS/TMSS/backend/test/tmss_test_data_rest.py
index 47b33d858b0..6d550ac2d05 100644
--- a/SAS/TMSS/backend/test/tmss_test_data_rest.py
+++ b/SAS/TMSS/backend/test/tmss_test_data_rest.py
@@ -688,16 +688,12 @@ class TMSSRESTTestDataCreator():
             self._subtask_url = self.post_data_and_get_url(self.Subtask(), '/subtask/')
             return self._subtask_url
 
-    def SubtaskOutput(self, subtask_url=None, task_blueprint_url=None):
+    def SubtaskOutput(self, subtask_url=None):
 
         if subtask_url is None:
             subtask_url = self.cached_subtask_url
 
-        if task_blueprint_url is None:
-            task_blueprint_url = self.cached_task_blueprint_url
-
         return {"subtask": subtask_url,
-                "task_blueprint": task_blueprint_url,
                 "tags": []}
 
     @property
-- 
GitLab